├── .gitignore ├── LICENSE ├── README.md ├── readTS.lua └── sendToTS.lua /.gitignore: -------------------------------------------------------------------------------- 1 | *.stackdump -------------------------------------------------------------------------------- /LICENSE: -------------------------------------------------------------------------------- 1 | The MIT License (MIT) 2 | 3 | Copyright (c) 2015 Nate George 4 | 5 | Permission is hereby granted, free of charge, to any person obtaining a copy 6 | of this software and associated documentation files (the "Software"), to deal 7 | in the Software without restriction, including without limitation the rights 8 | to use, copy, modify, merge, publish, distribute, sublicense, and/or sell 9 | copies of the Software, and to permit persons to whom the Software is 10 | furnished to do so, subject to the following conditions: 11 | 12 | The above copyright notice and this permission notice shall be included in all 13 | copies or substantial portions of the Software. 14 | 15 | THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR 16 | IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, 17 | FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE 18 | AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER 19 | LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, 20 | OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE 21 | SOFTWARE. 22 | 23 | -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 | # esp8266-send-thingspeak 2 | easily send/read data to/from thingspeak 3 | 4 | example for sending data: 5 | 6 | ``` 7 | sendToTS = require("sendToTS") 8 | sendToTS.setKey('YOUR_API_WRITE_KEY') 9 | valSet = sendToTS.setValue(1,12) -- channel, data. sendToTS returns a boolean, true if set successfully 10 | sendToTS.sendData(true, 'callbackfile.lua') -- show debug msgs, optional callback file (can omit this argument) 11 | sendToTS = nil 12 | package.loaded["sendToTS"]=nil -- these last two lines help free up memory 13 | ``` 14 | 15 | reading data is not so easy as of now. 16 | Example: 17 | 18 | ``` 19 | fields = {} 20 | createdTimes = {} 21 | readTS = require("readTS") 22 | readTS.setChannelID("yourChID") 23 | readTS.setKey("your read API key") 24 | readTS.readData(true, 1, 'callbackFile.lua') -- args: filename containing writekey, readkey on two separate lines; show debug msg = true; number of results to return; optional callback file to run after completed request 25 | readTS = nil 26 | package.loaded["readTS"]=nil 27 | print(fields[1]) 28 | ``` 29 | -------------------------------------------------------------------------------- /readTS.lua: -------------------------------------------------------------------------------- 1 | local moduleName = ... 2 | local M = {} 3 | _G[moduleName] = M 4 | local readkey 5 | local channelID 6 | 7 | local address = "184.106.153.149" -- IP for api.thingspeak.com 8 | 9 | local function loadKeys(fileName) 10 | if file.open(fileName) then 11 | local line = file.readline() 12 | channelID = string.sub(line,1,string.len(line)-1) -- hack to remove CR/LF 13 | line = file.readline() 14 | readKey = string.sub(line,1,string.len(line)-1) 15 | line = file.readline() 16 | writeKey = string.sub(line,1,string.len(line)-1) 17 | file.close() 18 | end 19 | end 20 | 21 | function M.setKey(thaKey) 22 | readKey = thaKey 23 | end 24 | 25 | function M.setChannelID(chID) 26 | channelID = chID 27 | end 28 | 29 | function M.setAddr(addr) 30 | -- for setting an IP address other than thingspeak 31 | address = addr 32 | end 33 | 34 | function M.readData(debug, numResults, callback) 35 | -- dataToSend is a table of data to send, 36 | -- each entry is a table, with names of fields as first value in each entrytable 37 | -- the second value is the data 38 | -- if you want to specify exact fields, use fields = true, 39 | -- and make the third value in each dataToSend table the field number 40 | -- fileName is name of file containing channelID and API keys and channel ID 41 | -- callback is a file to run after getting the results 42 | if wifi.sta.status()~=5 and wifi.sta.status()~=1 then 43 | wifi.sta.connect() 44 | end 45 | if readKey == nil then 46 | print("The read key hasn't been set yet! Use readTS.setKey('yourAPIreadkey') to set it before reading data.") 47 | return false 48 | end 49 | if channelID == nil then 50 | print("The channelID hasn't been set yet! Use readTS.setchannelID('yourAPIreadkey') to set it before reading data.") 51 | return false 52 | end 53 | if fields == nil or createdTimes == nil then 54 | print("You need to create the tables 'fields' and 'createdTimes' first before running.") 55 | end 56 | debug = debug or false 57 | numResults = numResults or 100 58 | tmr.alarm(1, 1000, 1, function() 59 | if debug then 60 | print("connecting") 61 | print(node.heap()) 62 | end 63 | if (wifi.sta.status()==5) then 64 | tmr.stop(1) 65 | -- timeout incase it can't connect for some reason 66 | tmr.alarm(1, 5*60*1000, 0, function() 67 | node.restart() 68 | end) 69 | if debug then 70 | print("connected to wifi") 71 | end 72 | sk = net.createConnection(net.TCP, 0) 73 | sk:on("reconnection", function(conn) 74 | if debug then 75 | print("socket reconnected") 76 | end 77 | end) 78 | sk:on("disconnection", function(conn) 79 | if debug then 80 | print("socket disconnected") 81 | end 82 | if callback~=nil then 83 | dofile(callback) 84 | end 85 | end) 86 | sk:on("receive", function(conn, msg) 87 | if debug then 88 | print(msg) 89 | end 90 | for i=1, 8, 1 do 91 | _, _, fields[i] = string.find(msg, "\"field"..tostring(i).."\":\"(%d*%.?%d*)\"") 92 | end 93 | for i=1, numResults, 1 do 94 | _, _, createdTimes[i] = string.find(msg, "\"created_at\":\"(%d*%-%d*%-%d*T%d*:%d*:%d*Z)\",\"entry_id\"") 95 | end 96 | collectgarbage() 97 | tmr.stop(1) 98 | if callback~=nil then 99 | dofile(callback) 100 | end 101 | end) 102 | sk:on("connection", function(conn) 103 | if debug then 104 | print("socket connected") 105 | print("getting data...") 106 | print(node.heap()) 107 | end 108 | sendStr = "GET /channels/"..channelID.."/feed.json?key="..readKey 109 | print(node.heap()) 110 | print('numresults: '..tostring(numResults)) 111 | if (numResults~=100) then 112 | sendStr = sendStr.."&results="..tostring(numResults) 113 | end 114 | print(node.heap()) 115 | sendStr = sendStr.." HTTP/1.1\r\n" 116 | print(node.heap()) 117 | sendStr = sendStr.."Host: "..address.."\r\n" 118 | sendStr = sendStr.."Connection: close\r\n" 119 | sendStr = sendStr.."Accept: */*\r\n" 120 | sendStr = sendStr.."User-Agent: Mozilla/4.0 (compatible; ESP8266;)\r\n" 121 | sendStr = sendStr.."\r\n" 122 | print(node.heap()) 123 | conn:send(sendStr) 124 | if debug then 125 | conn:on("sent", function() print("sent!") end) 126 | end 127 | print(sendStr) 128 | end) 129 | sk:connect(80, address) 130 | end 131 | end) 132 | end 133 | 134 | return M 135 | -------------------------------------------------------------------------------- /sendToTS.lua: -------------------------------------------------------------------------------- 1 | -- *************************************************************************** 2 | -- thingspeak posting module for ESP8266 with nodeMCU 3 | -- 4 | -- I have found with nodemcu devkits I sometimes need to compile the file 5 | -- due to memory limitations 6 | -- 7 | -- Written by Nate George 8 | -- 9 | -- MIT license, http://opensource.org/licenses/MIT 10 | -- 11 | -- Instructions: 12 | -- 1. Load keys using either the loadKeys or setKey function. 13 | -- 2. Get data ready to send using the setValue function. 14 | -- 3. Send data using the sendData function. 15 | -- 16 | -- Example: 17 | -- sendToTS = require('sendToTS') 18 | -- sendToTS.setKey('YOUR_API_WRITE_KEY') 19 | -- valSet = sendToTS.setValue(1,12) -- field number, data. sendToTS returns a boolean, true if set successfully 20 | -- sendToTS.sendData(true, 'callbackfile.lua') -- show debug msgs T/F, callback file to run when done 21 | -- sendToTS = nil 22 | -- package.loaded["sendToTS"]=nil -- these last two lines help free up memory 23 | -- 24 | -- the file 'callbackfile.lua' will be run after the data has been sent 25 | -- *************************************************************************** 26 | 27 | local moduleName = ... 28 | local M = {} 29 | _G[moduleName] = M 30 | 31 | local NUMBER_OF_FIELDS = 9 32 | local values = {} 33 | local address = "184.106.153.149" -- IP for api.thingspeak.com 34 | local readKey 35 | local writeKey 36 | local channelID 37 | local sk 38 | 39 | function M.loadKeys(fileName) 40 | if file.open(fileName) then 41 | local line = file.readline() 42 | channelID = string.sub(line,1,string.len(line)-1) -- hack to remove CR/LF 43 | line = file.readline() 44 | readKey = string.sub(line,1,string.len(line)-1) 45 | line = file.readline() 46 | writeKey = string.sub(line,1,string.len(line)-1) 47 | file.close() 48 | end 49 | end 50 | 51 | function M.setKey(privateKey) 52 | writeKey = privateKey 53 | end 54 | 55 | function M.setAddr(addr) 56 | -- for setting an IP address other than thingspeak 57 | address = addr 58 | end 59 | 60 | function M.setValue(fieldId, fieldValue) 61 | if(fieldId < 1 or fieldId > NUMBER_OF_FIELDS) then 62 | return false; 63 | end 64 | values[fieldId] = fieldValue; 65 | return true; 66 | end 67 | 68 | local function composeQuery() 69 | local result = "/update?key=" .. writeKey 70 | local ct 71 | for ct=1, NUMBER_OF_FIELDS, 1 do 72 | if values[ct] ~= nil then 73 | local fieldParameter = "&field" .. tostring(ct) .. "=" .. tostring(values[ct]); 74 | result = result .. fieldParameter; 75 | values[ct] = nil; 76 | end 77 | end 78 | return result; 79 | end 80 | 81 | function M.sendData(debug, callback) 82 | -- dataToSend is a table of data to send, 83 | -- each entry is a table, with names of fields as first value in each entrytable 84 | -- the second value is the data 85 | -- e.g. 86 | -- dataToSend = {} 87 | -- dataToSend[1] = {'water level', 5} 88 | -- 89 | -- If you want to specify exact fields, use fields = true, 90 | -- and make the third value in each dataToSend table the field number 91 | -- 92 | -- callback is a file to run upon recieving a response from the server 93 | if writeKey ==nil then 94 | print("The API write key hasn't been set yet! Use sendToTS.setKey('yourAPIreadkey') to set it before reading data.") 95 | return false 96 | end 97 | if wifi.sta.status()~=5 then 98 | wifi.sta.connect() 99 | end 100 | local debug = debug or false 101 | local fields = fields or false 102 | tmr.alarm(3,1000,1,function() 103 | if debug then 104 | print("connecting") 105 | print(node.heap()) 106 | end 107 | if (wifi.sta.status()==5) then 108 | tmr.stop(3) 109 | -- timeout incase it can't connect for some reason 110 | tmr.alarm(1, 5*60*1000, 0, function() 111 | node.restart() 112 | end) 113 | if debug then 114 | print("connected") 115 | end 116 | sk = net.createConnection(net.TCP, 0) 117 | sk:on("reconnection",function(conn) if debug then print("socket reconnected") end end) 118 | sk:on("disconnection",function(conn) 119 | if debug then 120 | print("socket disconnected, running callback") 121 | end 122 | if callback~=nil then 123 | dofile(callback) 124 | end 125 | return false 126 | end) 127 | sk:on("receive", function(conn, msg) 128 | if debug then 129 | print(msg) 130 | end 131 | local status, status2, postStatus 132 | _, _, status = string.find(msg, "Status: (200 OK)") 133 | _, _, status2 = string.find(msg, "Status: (.+)\r\n") 134 | _, _, postStatus = string.find(msg, "%d+\r\n%d+\r\n0") 135 | if debug then 136 | print(postStatus) 137 | if postStatus~=0 and postStatus~=nil then 138 | print('successfully updated') 139 | end 140 | if status=='200 OK' then 141 | print('successful send') 142 | print('checked status: '..status) 143 | else 144 | print('unsuccessful send') 145 | print('checked status: '..status) 146 | end 147 | end 148 | if status~=nil then 149 | if debug then 150 | print('successful send') 151 | end 152 | local result = true 153 | else 154 | if debug then 155 | print('no success') 156 | end 157 | local result = false 158 | end 159 | if debug then 160 | print("found status: "..status) 161 | print(status=='200 OK') -- having a problem - next message is recieved before the code gets here 162 | end 163 | collectgarbage() 164 | tmr.stop(1) 165 | if callback~=nil then 166 | if debug then 167 | print('running callback file') 168 | end 169 | collectgarbage() 170 | dofile(callback) 171 | end 172 | return result 173 | end) 174 | sk:on("connection",function(conn) 175 | if debug then 176 | print("socket connected") 177 | print("sending...") 178 | end 179 | local sendStr 180 | local query = composeQuery() 181 | sendStr = "POST /update HTTP/1.1\r\n" 182 | sendStr = sendStr.."Host: "..address.."\r\n" 183 | sendStr = sendStr.."Connection: close\r\n" 184 | sendStr = sendStr.."X-THINGSPEAKAPIKEY: "..writeKey.."\r\n" 185 | sendStr = sendStr.."Content-Type: application/x-www-form-urlencoded\r\n" 186 | sendStr = sendStr.."Content-Length: "..string.len(query).."\r\n\r\n" 187 | sendStr = sendStr..query.."\r\n" 188 | conn:send(sendStr) 189 | if debug then 190 | conn:on("sent",function() if debug then print("sent!") end end) 191 | end 192 | if debug then 193 | print(sendStr) 194 | end 195 | collectgarbage() 196 | end) 197 | sk:connect(80, address) 198 | end 199 | end) 200 | end 201 | 202 | return M 203 | --------------------------------------------------------------------------------