I have a completely working esp chip that connects to wifi and creates a server. When I send it an OTA command, it runs a function that downloads a file using a socket connection.
This is the upgrader.lua that I am using:
--------------------------------------
-- Upgrader module for NODEMCU
-- LICENCE: http://opensource.org/licenses/MIT
-- cloudzhou<wuyunzhou#espressif.com> - Heavily modified by aschmois
--------------------------------------
--[[
update('file.lua', 'http://IP.ADRESS/path/file.lua')
]]--
local header = ''
local isTruncated = false
local function save(filename, response)
if isTruncated then
file.write(response)
return
end
header = header..response
local i, j = string.find(header, '\r\n\r\n')
if i == nil or j == nil then
return
end
prefixBody = string.sub(header, j+1, -1)
file.write(prefixBody)
header = ''
isTruncated = true
return
end
----
function update(filename, url, cn)
local tmpError = nil
local running = true
local error = nil
local success = false
print("Downloading from: " .. url)
local ip, port, path = string.gmatch(url, 'http://([0-9.]+):?([0-9]*)(/.*)')()
if ip == nil then
return false
end
if port == nil or port == '' then
port = 80
end
port = port + 0
if path == nil or path == '' then
path = '/'
end
print("-- Detailed Connection Info --")
print("IP: ".. ip)
print("Port: ".. port)
print("Path: ".. path)
print("-- END --")
local function timeout()
error = tmpError
file.remove(filename)
conn:close()
running = false
end
conn = net.createConnection(net.TCP, false)
conn:on('connection', function(sck, response)
tmr.stop(1)
file.open(filename, 'w')
conn:send('GET '..path..' HTTP/1.0\r\nHost: '..ip..'\r\n'..'Connection: close\r\nAccept: */*\r\n\r\n')
tmpError = "READ TIMEOUT"
tmr.alarm(1, 10000, 0, timeout)
end)
conn:on('receive', function(sck, response)
tmr.stop(1)
tmpError = "READ(2) TIMEOUT"
tmr.alarm(1, 10000, 0, timeout)
print(response)
save(filename, response)
end)
conn:on('disconnection', function(sck, response)
tmr.stop(1)
local function reset()
local list = file.list()
for k,v in pairs(list) do
if(filename == k) then
if(v == 0) then
success = false
file.close()
file.remove(filename)
else
file.close()
success = true
end
end
end
print(header)
header = ''
isTruncated = false
if(success) then
print(filename..' saved')
else
print("Could not download `".. filename.."`")
end
running = false
end
tmr.alarm(0, 2000, 0, reset)
end)
conn:connect(port, ip)
tmpError = "CONN TIMEOUT"
tmr.alarm(1, 10000, 0, timeout)
tmr.alarm(2, 1000, 1, function()
if(running == false) then
tmr.stop(2)
local buf = ''
if(success) then
buf = buf.."HTTP/1.1 200 OK\r\nServer: WiFi Relay\r\nContent-Type: text/plain\r\n\r\n"
buf = buf.."1"
else
buf = buf.."HTTP/1.1 500\r\nServer: WiFi Relay\r\nContent-Type: text/plain\r\n\r\n"
buf = buf.."0"
buf = buf.."\n"
if(error ~= nil) then
buf = buf..error
else
buf = buf.."UNKNOWN ERROR"
end
end
cn:send(buf)
cn:close()
end
end)
return true
end
As a test I am sending it: filename = rz.lua and url = http://192.168.1.132/rz.lua. The cn variable is the connection to send back information to the client.
The esp chip prints:
Downloading from: http://192.168.1.132/rz.lua
-- Detailed Connection Info --
IP: 192.168.1.132
Ò_ÇRöfJSúfÊÃjêÐÿ (junk reset data)
The problem seems to be connected with the conn:send() command. If it's inside the on connect function it resets. If it's outside, I will get a read timeout (since on read is never called). I really have no idea what else to do.
This is the ESP firmware info:
NodeMCU custom build by frightanic.com
branch: master
commit: 93421f2702fb02ce169f82f96be7f2a8865511e1
SSL: false
modules: node,file,gpio,wifi,net,tmr,uart
You are resetting. The "junk" is a BootROM message at the wrong baud rate.
Don't do a send followed by a close in the same callback. Use an on('sent', ... ) to trigger the close. So the 21 line body of your alarm 2 callback would be better written:
local response = "HTTP/1.1 200 OK\r\nServer: WiFi Relay\r\nContent-Type: text/plain\r\n\r\n%s"
cn:send(response:format(success and "1" or ("0\n\r" .. (error or "UNKNOWN ERROR")))
cn:on('sent', function(cn) cn:close() end)
On that note your 27 line disconnect callback would be better written:
tmr.stop(1)
tmr.alarm(0, 2000, 0, function()
local len = file.list()(filename)
success = len and len > 0
file.close()
if not success then file.remove(filename)
file.flush()
end)
Note that it's always wise to flush the SPIFFS after writing or removing files.
You use a standard pattern, so why not encapsulate it:
local conn = net.createConnection(net.TCP, false)
local function setTimeout(reason)
-- tmr.stop(1) -- not needed is the next line resets the alarm
tmr.alarm(1, 10000, 0, function ()
-- you don't need tmpError as reason is a local and bound as an upval
error, running = reason, false
file.remove(filename) file.flush()
return conn:close()
end)
end
I could go on but I leave this to you. With a little thought, your code would be a third of the size and more readable.
I can't be sure but the problem seems to have been a memory error (weird since there was no panic) so this is what I did to fix it:
local request = table.concat({"GET ", path,
" / HTTP/1.1\r\n",
"Host: ", ip, "\r\n",
"Connection: close\r\n",
"Accept: */*\r\n",
"User-Agent: Mozilla/4.0 (compatible; esp8266 Lua;)",
"\r\n\r\n"})
conn = net.createConnection(net.TCP, false)
conn:on('connection', function(sck, response)
tmr.stop(1)
tmpError = "READ TIMEOUT"
tmr.alarm(1, 10000, 0, timeout)
conn:send(request)
end)
I created the request using the table.concat method and using a table instead of one big string. Hopefully this will help those in need.
Related
When I try and save a table (encoded into a json format) through SetAsync, it provides me with this error:
"ServerScriptService.InventoryService.main:59: attempt to index number with 'SetAsync' - Server"
I have encoded the table with HTTP:JSONEncode() and I decode it when its read again.
heres an example of the table:
{
["A"] = {
["x"] = 98,
["y"] = "xyz",
["z"] = 15
},
["B"] = {
["c"] = "XYZ",
["d"] = 0, 0, 0,
["e"] = "22",
["f"] = "xyz"
}
}
Here is the Sample code
local Datastore = game:GetService("DataStoreService")
local RunService = game:GetService('RunService')
local HTTP = game:GetService('HttpService')
local item_data = Datastore:GetDataStore("Items")
local function save(Player:Player)
local item_data = {} --contains data created by the user
local savedata = {}
--PLAYER_DATA--
if savedata[Player] == "ERR" then return end
local suc, err = pcall(function()
player_data:SetAsync(Player.UserId, savedata)
end)
if err then
warn("Unable to save data for"..Player.Name,err)
end
--ITEM_DATA--
if item_data[Player] == "ERR" then return end
local suc, err = pcall(function()
local item_json = HTTP:JSONEncode(item_data)
itemdata:SetAsync(Player.UserId,item_json)
end)
if err then
warn("Unable to save data for "..Player.Name,err)
end
end
game.Players.PlayerAdded:Connect(function(Player)
local suc, err = pcall(function()
data = player_data:GetAsync(Player.UserId) or "0"
itemdata = HTTP:JSONDecode(item_data:GetAsync(Player.UserId) or "0")
itemdata = HTTP:JSONDecode(itemdata)
end)
if suc then
savedata[Player] = data
else
savedata[Player] = "ERR"
warn("Error fetching "..Player.Name.."'s data.",err)
end
end)
game.Players.PlayerRemoving:Connect(function(Player)
save(Player)
end)
game:BindToClose(function() -- (only needed inside Studio)
print('BindToClose')
if RunService:IsStudio() then -- (only needed inside Studio)
wait(3) -- this will force the "SetAsync" above to complete
end
end)
Thanks in advance, any help is really appreciated!
itemdata = HTTP:JSONDecode(item_data:GetAsync(Player.UserId) or "0")
itemdata = HTTP:JSONDecode(itemdata)
Not sure why you decode it twice here but take a look at or "0". That means itemdata might end up as 0
itemdata:SetAsync(Player.UserId,item_json)
And here you try to call it. That's why it complains about calling a number.
I assume you meant to use item_data, which is a DataStore. Please use more descriptive names here to avoid such confusion :)
I'm new to Lua and trying to implement TCP server and client in Openwrt using luasocket and copas. The goal is to make 3 program communicate with each other via socket in asynchronous networking.
Below is the script
local copas = require("copas")
local socket = require("socket")
local host = "localhost"
local port = 20000
local hostcl1 = "localhost"
local portcl1 = 20001
local hostcl2 = "localhost"
local portcl2 = 20002
local function echoHandler(skt)
skt = copas.wrap(skt)
while true do
local data = skt:receive()
print("data received:", data, "from:", skt:getsockname())
if not data or data == nil then
break
end
end
end
local function sendToNeighbor(host, port, data)
skt = socket.connect(host, port)
if (skt ~= nil) then
skt = copas.wrap(skt)
print("client connected to " ..host.. ":" ..port.. "...")
copas.send(skt, data.."\n")
print("data sent")
skt:close()
print("Closed!")
else
print("client failed to send to " ..host.. ":" ..port.. "...")
end
end
local server = socket.bind(host, port)
copas.addserver(server, echoHandler, 0)
SendInterval = 10
SecBefore = os.date('%S')
SecSend = (SecBefore + SendInterval)%60
while true do
copas.step(0)
local Sec = os.date('%S')
if ( tonumber(Sec) == SecSend ) then
dataToClient1 = "Test1"
dataToClient2 = "Test2"
sendToNeighbor(hostcl1, portcl1, dataToClient1)
sendToNeighbor(hostcl2, portcl2, dataToClient2)
SecBefore = Sec
SecSend = (SecBefore + SendInterval)%60
end
end
On script above, I use 3 similar program in host = "localhost" and 3 different port (20000, 20001, and 20002). I want each program listen to each other and send each other data every 10 seconds. The problem is every time the program send data with copas.send function, this error occurs.
luajit: /usr/local/share/lua/5.1/copas.lua:285: attempt to yield across C-call boundary
I have try using lua 5.1, lua 5.1 + CoCo, and LuaJIT and this error always occur.
Any idea to solve this? thanks
I want to turn on and off a led from a web server using NodeMCU but every time I compile the same error occurs. I am in a beginner level so I need some help. I believe this error has something to do with the function listen(). Maybe because I changed my DNS, the port 80 can't be used.
Error:
dofile("ConectarRedeWireless.lua");
192.168.137.57 255.255.255.0 192.168.137.1
ConectarRedeWireless.lua:13: address in use
stack traceback:
[C]: in function 'listen'
ConectarRedeWireless.lua:13: in main chunk
[C]: in function 'dofile'
stdin:1: in main chunk
Code:
-- Conexao na rede Wifi
wifi.setmode(wifi.STATION)
wifi.sta.config("goncalo","936674888")
print(wifi.sta.getip())
-- Definicoes do pino do led
led1 = 1
gpio.mode(led1, gpio.OUTPUT)
-- Definicoes do Web Server
srv=net.createServer(net.TCP)
srv:listen(80,function(conn)
conn:on("receive", function(client,request)
local buf = "";
local _, _, method, path, vars = string.find(request, "([A-Z]+) (.+)?(.+) HTTP");
if(method == nil)then
_, _, method, path = string.find(request, "([A-Z]+) (.+) HTTP");
end
local _GET = {}
if (vars ~= nil)then
for k, v in string.gmatch(vars, "(%w+)=(%w+)&*") do
_GET[k] = v
end
end
buf = buf.."<h1><u>FILIPEFLOP</u></h1>";
buf = buf.."<h2><i>ESP8266 Web Server</i></h2>";
buf = buf.."<p><button><b>LED 1 LIG</b></button> <br/><br/><button><b>LED 1 DES</b></button></p>";
local _on,_off = "",""
if(_GET.pin == "LIGA1")then
gpio.write(led1, gpio.HIGH);
elseif(_GET.pin == "DESLIGA1")then
gpio.write(led1, gpio.LOW);
end
client:send(buf);
client:close();
collectgarbage();
end)
end)
First of all I suggest you use a proper init sequence in which you wait for your device to be fully connected to the WiFi until you set up the server. We keep a template in our documentation.
srv:listen(80,function(conn) can only be called once for any given port because you can't have multiple server sockets listening on the same port. That's why you get the
address in use
error. Our net.server:listen() documentation shows how to check whether srv is available before you listen. You can extend that by running srv:close() first before you listen again. Something like this:
sv = net.createServer(net.TCP, 30)
if sv then
sv:close()
sv:listen(80, function(conn)
-- do something
end)
end
Btw, where did you get that code sample from? I've seen this several times here on Stack Overflow but it's got more flaws that should be corrected.
here is my lua script ,running on esp8266 with nodemcu firmware,I am keeping a function check_wifi() to check that my esp module is connected ,then I am publishing topic "on" , and setting two event handler for "connect" and for "message",but my handler for
function check_wifi()
local ip = wifi.sta.getip()
if(ip==nil) then
print("Connecting...")
else
tmr.stop(0)
print("Connected to AP!")
print(ip)
m:connect("test.mosquitto.org", 1883, 0, function(conn) print("connected") end)
m:publish("on","1",0,0,0)
m:on("connect",function(m)
m:subscribe("on",0,function(m) print(m);print("subcribed") end)
end )
m:on("message", function(conn, topic, data)
print("in on")
print(topic .. ":" )
if data ~= nil then
print(data)
end
end)
end
end
local SSID = "Crystalrock2"
local SSID_PASSWORD = "nest2abc123"
gpio.mode(4,gpio.OUTPUT)
gpio.write(4,gpio.LOW)
-- configure ESP as a station
wifi.setmode(wifi.STATION)
wifi.sta.config(SSID,SSID_PASSWORD)
wifi.sta.connect()
m = mqtt.Client("dee15", 120, "test", "password")
m:on("message", function(conn, topic, data)
print("in on")
print(topic .. ":" )
if data ~= nil then
print(data)
end
end)
m:on("connect",function(m)
m:subscribe("on",0,function(m) print(m);print("subcribed") end)
end )
m:connect("test.mosquitto.org", 1883, 0, function(conn) print("connected") end)
tmr.alarm(0,2000,1,check_wifi)
here what I am getting:
output:Connecting...
Connecting...
Connected to AP!
192.168.0.100
userdata: 3fff8678
subcribed
why m:on("message" ..)is not triggering and printing message received
Im busy working on a dimming light which my ESP-03 will control. But what I have read up on, I have a problem. Please see my code below then I will explain:
device_id = "553CDA2DEAC90"
query_id = ""
dim = 120
wifi.setmode(wifi.STATION)
wifi.sta.config("SSID","PASSWORD")
wifi.sta.connect()
wifi.sta.setip({ip="10.0.0.122",netmask="255.255.255.0",gateway="10.0.0.2"})
outpin = 7 --GPIO13
gpio.mode(outpin,gpio.OUTPUT)
gpio.write(outpin,gpio.LOW)
inpin = 6 --GPIO12
gpio.mode(inpin,gpio.INT,gpio.PULLUP)
function zero_cross()
dt = 75*dim
tmr.delay(dt)
gpio.write(outpin,gpio.HIGH)
tmr.delay(1)
gpio.write(outpin,gpio.LOW)
tmr.wdclr()
end
gpio.trig(inpin,"up",zero_cross)
function sendData()
if(wifi.sta.status() == 5)then
conn=net.createConnection(net.TCP, 0)
conn:connect(PORT,'IP')
if(firstStart == 0)then
conn:send(device_id)
conn:send("|0|")
else
if(query_id == nil)then
conn:send(device_id)
conn:send("|0|")
conn:send(dim)
else
conn:send(device_id)
conn:send("|")
conn:send(query_id)
conn:send("|")
conn:send(dim)
query_id = nil
end
end
conn:on("receive", function(conn, payload)
payload = string.gsub(payload, " ", "")
dim = string.sub(payload, 0, string.find(payload, "|")-1)
payload = string.gsub(payload, dim.."|", "")
query_id = payload
conn:close()
end)
else
wifi.sta.connect()
end
end
tmr.alarm(6, 1000, 1, sendData )
The problem that I am facing is that when at the bottom of the script I start tmr.alarm()... But the under the function zero_cross() its uses tmr.delay and that seems to make tmr.alarm() not function anymore. All that happens is the ESP just keeps on restarting. If I run the above code seperate (just the dimming function or just the sendData function then everything works perfect). Does anyone have any suggestions?
Nodemcu call lua callback directly from hardware interrupt, while the need to use luahook, (As Lua doesn’t have direct support for interrupts, they have to be emulated). It is probably dangerous.
Solution: use only 1 interrupt same time:
local send_time, last_gpio
last_gpio = gpio.read(6)
send_time = 0
tmr.trigger(1, 10, 1, function()
if (gpio.read(6) == 1 and last_gpio == 0) then
last_gpio = 1
zero_cross()
else if (gpio.read(6) == 0 and last_gpio == 1)
last_gpio = 0
end
send_time = send_time + 1
if (send_time > 100) then
send_data()
end
end)