NodeMCU webserver closing connection after first send? - lua

I have an small web server running on my ESP-12 with nodemcu firmware:
sv=net.createServer(net.TCP,10)
sv:listen(80,function(c)
c:on("receive", function(c, pl)
if(string.find(pl,"GET / ")) then
print("Asking for index")
c:send("Line 1")
c:send("Line 2")
c:send("Line 3")
c:close()
end
end)
c:on("sent",function(conn)
print("sended something...")
end)
end)
It seems my connection is getting closed after the first send, in my browser I only see the "line 1" text, line 2 a 3 does not appear, and in my serial console im just seeing the "sended something" text one time, even commenting the close statement and letting the connection to timeout does not change the behavior. What am I missing here?

I don't think that you can use send multiple times. Whenever I use one of my ESP8266 as a server I use a buffer variable :
sv=net.createServer(net.TCP,10)
-- 'c' -> connection, 'pl' -> payload
sv:listen(80,function(c)
c:on("receive", function(c, pl)
if(string.find(pl,"GET / ")) then
print("Asking for index")
local buffer = ""
buffer = buffer.."Line 1"
buffer = buffer.."Line 2"
buffer = buffer.."Line 3"
c:send(buffer)
c:close()
end
end)
c:on("sent",function(c)
print("sended something...")
end)
end)
EDIT: After reading the docs again, send can take another argument with a callback function, it can maybe be used to have multiple send command. Never tried it though :(.
EDIT 2: If you have a really long string to send, it's better to use table.concat

The net.socket:send() documentation provides a nice example which I repeat here.
srv = net.createServer(net.TCP)
function receiver(sck, data)
local response = {}
-- if you're sending back HTML over HTTP you'll want something like this instead
-- local response = {"HTTP/1.0 200 OK\r\nServer: NodeMCU on ESP8266\r\nContent-Type: text/html\r\n\r\n"}
response[#response + 1] = "lots of data"
response[#response + 1] = "even more data"
response[#response + 1] = "e.g. content read from a file"
-- sends and removes the first element from the 'response' table
local function send(localSocket)
if #response > 0 then
localSocket:send(table.remove(response, 1))
else
localSocket:close()
response = nil
end
end
-- triggers the send() function again once the first chunk of data was sent
sck:on("sent", send)
send(sck)
end
srv:listen(80, function(conn)
conn:on("receive", receiver)
end)

Related

Lua not instantiating local variable fast enogh to print it? Embeded ESP8266

I got an EPS8226 on which I uploaded a main.lua file and some other configs. When I run main.lua with dofile() in the terminal, the print from the callback function prints only "sensorId" without "sent 0". Yet, if I run main.lua again, or I print() something before (either inside the function() that calls the callback, or inside the callback itself) it will print properly "sensorId sent 0". This also works if I do not use the local variable.
function registerReaders()
for key, value in pairs(sensorConfig.data)
do
gpio.mode(value.pin, gpio.INPUT)
value.timer:alarm(value.polling, tmr.ALARM_AUTO, function() callbacks.sendData(sensorConfig.sensorId[key]) end)
tmr.create():alarm(1000, tmr.ALARM_SINGLE, function() callbacks.sendData(sensorConfig.sensorId[key]) end)
end
end
Printing "[sensorId_value]"
callbacks.sendData = function(sensorId)
local data = 1
print(sensorId .. " sent " .. data)
end
Printing properly ("OK" then "[sensorId_value] sent 0")
callbacks.sendData = function(sensorId)
local data = 1
print("OK")
print(sensorId .. " sent " .. data)
end
Printing properly ("[sensorId_value] sent 1")
callbacks.sendData = function(sensorId)
print(sensorId .. " sent 0")
end
In all examples sensorId is a variable, not a string, so unless sensorId = "sensorId" none of them should be printing "sensorId sent 0".
In the first example (all of them, actually) " sent " is a literal, it should output regardless of the value of data.
Shouldn't the second example print "[...] sent 1"?
Try using a variable name other than data, maybe there's some forward value confusion.

nodemcu lua dofile - how to invoke another lua file?

I have a working init.lua on my nodemcu esp8266:
-- load credentials, 'SSID' and 'PASSWORD' declared and initialize in there
dofile("credentials.lua")
function startup()
if file.open("init.lua") == nil then
print("init.lua deleted or renamed")
else
print("Running")
file.close("init.lua")
-- the actual application is stored in 'application.lua'
-- dofile("application.lua")
end
end
print("Connecting to WiFi access point...")
wifi.setmode(wifi.STATION)
wifi.sta.config(SSID, PASSWORD)
-- wifi.sta.connect() not necessary because config() uses auto-connect=true by default
tmr.create():alarm(1000, tmr.ALARM_AUTO, function(cb_timer)
if wifi.sta.getip() == nil then
print("Waiting for IP address...")
else
cb_timer:unregister()
print("WiFi connection established, IP address: " .. wifi.sta.getip())
print("You have 3 seconds to abort")
print("Waiting...")
tmr.create():alarm(3000, tmr.ALARM_SINGLE, startup)
end
end)
It runs without errors and the wireless connection is established.
Now I have written a second bme280_mqtt.lua that I want to run automatically:
alt=45 -- altitude of the measurement place
bme280.init(3, 4)
P, T = bme280.baro()
-- convert measure air pressure to sea level pressure
QNH = bme280.qfe2qnh(P, alt)
ldk=string.format("Ld=%d.%03d ", QNH/1000, QNH%1000)
H, T = bme280.humi()
if T<0 then
temp=string.format("T=-%d.%02d°C ", -T/100, -T%100)
else
temp=string.format("T=%d.%02d°C ", T/100, T%100)
end
luftf=string.format("Lf=%d%% ", H/1000, H%1000)
D = bme280.dewpoint(H, T)
if D<0 then
taupu=string.format("Tp=-%d.%02d°C ", -D/100, -D%100)
else
taupu=string.format("Tp=%d.%02d°C ", D/100, D%100)
end
m = mqtt.Client("wetterstation", 120)
m:connect("192.168.1.116", 1883)
m:publish("wetterstation",temp .. taupu .. luftf .. ldk,0,0)
node.dsleep(10*1000000)
Called by hand in ESPlorer via Send to ESP Button everything works fine.
But with
dofile(bme280_mqtt.lua)
I get:
dofile('bme280_mqtt.lua')
bme280_mqtt.lua:25: not connected
stack traceback:
[C]: in function 'publish'
bme280_mqtt.lua:25: in main chunk
[C]: in function 'dofile'
stdin:1: in main chunk
What is the mistake here? And how do I call the bme280_mqtt.lua from init.lua correctly?
Kind regards
how do I call the bme280_mqtt.lua from init.lua correctly?
You do invoke it correctly.
bme280_mqtt.lua:25: not connected
Means that there's an error on/from line 25 from bme280_mqtt.lua.
I didn't count the lines but the problem is right here
m:connect("192.168.1.116", 1883)
m:publish("wetterstation",temp .. taupu .. luftf .. ldk,0,0)
You can only publish once the connection to the broker was established. Look at the example at http://nodemcu.readthedocs.io/en/latest/en/modules/mqtt/#example. You can either use the callback function in the connect function to publish or register an on-connect event handler before you call connect like so:
m:on("connect", function(client)
-- publish here
end)

NodeMCU timeout when using while loop

I have a Lua script that sends an email to myself via SMTP. Everything works fine when uploading to the NodeMCU and saying dofile("sendemail.lua").
-- sendmail.lua
-- The email and password from the account you want to send emails from
MY_EMAIL = "REDACTED"
EMAIL_PASSWORD = "REDACTED"
-- The SMTP server and port of your email provider.
-- If you don't know it google [my email provider] SMTP settings
SMTP_SERVER = "isp.smtp.server"
SMTP_PORT = 25
-- The account you want to send email to
mail_to = "REDACTED"
-- Your access point's SSID and password
SSID = "REDACTED"
SSID_PASSWORD = "REDACTED"
-- configure ESP as a station
wifi.setmode(wifi.STATION)
wifi.sta.config(SSID,SSID_PASSWORD)
wifi.sta.autoconnect(1)
email_subject = ""
email_body = ""
count = 0
local smtp_socket = nil -- will be used as socket to email server
-- The display() function will be used to print the SMTP server's response
function display(sck,response)
print(response)
end
-- The do_next() function is used to send the SMTP commands to the SMTP server in the required sequence.
-- I was going to use socket callbacks but the code would not run callbacks after the first 3.
function do_next()
if(count == 0)then
count = count+1
IP_ADDRESS = wifi.sta.getip()
smtp_socket:send("HELO "..IP_ADDRESS.."\r\n")
elseif(count==1) then
count = count+1
smtp_socket:send("AUTH LOGIN\r\n")
elseif(count == 2) then
count = count + 1
smtp_socket:send("REDACTED".."\r\n")
elseif(count == 3) then
count = count + 1
smtp_socket:send("REDACTED".."\r\n")
elseif(count==4) then
count = count+1
smtp_socket:send("MAIL FROM:<" .. MY_EMAIL .. ">\r\n")
elseif(count==5) then
count = count+1
smtp_socket:send("RCPT TO:<" .. mail_to ..">\r\n")
elseif(count==6) then
count = count+1
smtp_socket:send("DATA\r\n")
elseif(count==7) then
count = count+1
local message = string.gsub(
"From: \"".. MY_EMAIL .."\"<"..MY_EMAIL..">\r\n" ..
"To: \"".. mail_to .. "\"<".. mail_to..">\r\n"..
"Subject: ".. email_subject .. "\r\n\r\n" ..
email_body,"\r\n.\r\n","")
smtp_socket:send(message.."\r\n.\r\n")
elseif(count==8) then
count = count+1
tmr.stop(0)
smtp_socket:send("QUIT\r\n")
print("msg sent")
else
smtp_socket:close()
end
print(count)
end
-- The connectted() function is executed when the SMTP socket is connected to the SMTP server.
-- This function will create a timer to call the do_next function which will send the SMTP commands
-- in sequence, one by one, every 5000 seconds.
-- You can change the time to be smaller if that works for you, I used 5000ms just because.
function connected(sck)
tmr.alarm(0,5000,1,do_next)
end
-- #name send_email
-- #description Will initiated a socket connection to the SMTP server and trigger the connected() function
-- #param subject The email's subject
-- #param body The email's body
function send_email(subject,body)
count = 0
email_subject = subject
email_body = body
smtp_socket = net.createConnection(net.TCP,0)
smtp_socket:on("connection",connected)
smtp_socket:on("receive",display)
smtp_socket:connect(SMTP_PORT, SMTP_SERVER)
end
-- Send an email
send_email("ESP8266", "[[Hi, How are your IoT projects coming along? Best Wishes,ESP8266]]")
However, I want to use a loop to monitor an analog input value and only send the email when certain analog input values are detected. Therefore, I added this code at the end of the script, after the sendemail() function definition and immediately before the function sendmail('subject', 'body') is called
vp = 0
gpio.mode(vp, gpio.INPUT)
while true do
local v = adc.read(vp)
if v < 840 or v > 870 then
print(v)
break
end
tmr.wdclr()
end
sendmail('subject', 'body')
The while loop works perfectly, waiting indefinitely for input from the analog pin. Once that input is found, it breaks correctly and calls the sendmail function. However, once that function is called, NodeMCU eventually resets. Sometimes it will get as far as successfully authenticating the SMTP credentials with the server, and sometimes it will not even make the HELO before it shuts down. What could possibly be causing this? Why would the sendmail.lua script work fine then suddenly decide not to work when adding this one small while loop that appears to work perfectly fine on its own?
A little quote from the NodeMCU reference:
tmr.wdclr() Feed the system watchdog.
In general, if you ever need to use this function, you are doing it
wrong.
The event-driven model of NodeMCU means that there is no need to be
sitting in hard loops waiting for things to occur. Rather, simply use
the callbacks to get notified when somethings happens. With this
approach, there should never be a need to manually feed the system
watchdog.
Please note the second line. :)
Not sure what your problem is, but why do you use a while loop in the first place? Why not use timer events to poll your ADC regularly?
Maybe the watchdog is triggered because your feed comes to late for some reason. In the if case you don't feed it at all befor you leave the loop.
Even if it may not be the definite answer I post it as such since the comment input is too small.
First, I suggest you use the script I posted for your previous question. This one isn't handling WiFi setup correctly. You need to wait in a timer until the device got an IP before you can continue. Remember, wifi.sta.config is non-blocking. And since it uses auto-connect=true if not set explicitly it'll try to connect to the AP immediately. That's also the reason why wifi.sta.autoconnect(1) is superfluous.
I don't understand the ADC reading code you posted.
vp = 0
gpio.mode(vp, gpio.INPUT)
Seems unnecessary to me because a) you don't do anything with GPIO 0 and b) adc.read only supports 0.
Rather than using a busy loop and constantly feeding the watch dog, which is a very bad sign, I suggest you use an interval based timer. Furthermore, I guess you don't wanna break the loop the first time the condition is met and never come back? So, you need to stay in the loop and keep triggering send mail, no? Something like this maybe (untested):
tmr.alarm(0, 200, tmr.ALARM_AUTO, function()
local v = adc.read(0)
if v < 840 or v > 870 then
node.task.post(function()
send_email("ESP8266", "[[Hi, How are your IoT projects coming along? Best Wishes,ESP8266]]")
end)
end
end)

Error in receiving data from server

I want to make my test function below print out the message "k isn't nil" but my code doesn't work. It has already received k value from my server but it doesn't check the line if k~=nil then. Below is my code. Thanks for any incoming advice.
local function receiveData( )
local l,e = client:receive()
if l~=nil then
print(l)
return l,e
else
timer.performWithDelay(100,receiveData)
end
end
function test( )
k = receiveData()
if k ~=nil then
print("k isn't nil")
end
end
test()
The problem is that if the data is not received on the first try then k is nil and test returns. The receiveData will be called again at 100 millisecond intervals until data is received, but the return is discarded by performWithDelay and by then test has returned (see first sentence of this answer).
The solution is to set a callback that receiveData can call when the data eventually arrives. The callback can then process the data. Replace return l,e by onReceiveData(l,e) and have that do something that test waits for in a while loop. Of course receiveData could directly set this flag being watched by test but once your app gets larger it is a good idea to separate receive from process.
function receiveData()
...
-- then:
local data = nil
function onReceiveData(l,e)
data = l
print('ready to process data', data, e)
end
funtion test()
receiveData()
while data == nil do sleep(100) end
print('data received and processed')
end
test()
where sleep(100) is what you can come up with since there is no builtin function that does that in Lua or even Corona (although Corona has system.getTimer() which returns ms since app start, so you could have
function sleep(ms)
local start = system.getTimer()
while system.getTimer() - start < ms do
end
end
I'm not too keen on the empty while loop but in a test utility function it is OK. If you are using the socket library it has a sleep function -- check out the Lua wiki for other options).
Are you sure you received the data? What does your program print in the console?
You may consider the following modification
local function receiveData( )
local l,e = client:receive()
if l~=nil then
print(l)
return l,e
else
timer.performWithDelay(100,function() l, e = receiveData() end)
end
return l, e
end
So my guess is, that when receiveData gets called second time, your return values (l, e) are discarded (because performWithDelay doesn't do anything with them).

how do you properly use a pipe with lua to get the output of a program?

I am using Lua with the luaposix library to get the the output of some commands, and sometimes send some too. I am using this code, or variants of this to do my work, but I sometimes get stuck at posix.wait(cpid) or sometimes the command doesn't seem to finish.
-- returns true on connected, else false
function wifi.wpa_supplicant_status()
-- iw dev wlan0-1 link
local r,w = posix.pipe()
local cpid = posix.fork()
if cpid == 0 then --child writes to pipe
--close unused read end
local devnull = posix.open("/dev/null", posix.O_RDWR)
posix.close(r)
posix.dup2(devnull, 0)
posix.dup2(w, 1)
posix.dup2(devnull, 2)
local dir = wifi.wpa_supplicant_dir()
local iface = posix.basename(dir)
iface = string.gsub(iface, "wpa_supplicant%-",'')
posix.exec('/usr/sbin/iw', {'dev', iface, 'link'})
posix._exit(-1)
elseif cpid > 0 then
--parent reads from pipe, close write end
posix.close(w)
local buf = ''
while true do
local tmp = posix.read(r, 100)
if tmp ~= nil and #tmp > 0 then
buf = buf .. tmp
else
break
end
end
-- TODO, check exit value, to see if entry exists or not
while posix.wait(cpid) ~= cpid do print("waiting in wpa_supplicant_status") end
print("status is "..buf)
if string.find(buf, "Connected to", 1, true) then
return true
else
return false
end
end
end
This is what I understand what I have to do(to just get output):
create a single pipe
fork
if child,
close read end of pipe
dup2(write_end, stdout)
exec() to desired process
if parent
close write end of pipe
do blocking reads to read end of pipe, till you get a 0 byte read, which means the child process has terminated, closing the pipe
Am I missing something?
You should have a look at the answers in this question How do you construct a read-write pipe with lua?
to get an idea of the difficulties to implement this. One thing that strikes me is that nobody seems to be able to get it to work with only one child process.
However, In the example you give, it seems you only want to get the output of the command, so I would suggest simply doing this:
function os.capture(cmd)
local f = assert(io.popen(cmd, 'r'))
local s = assert(f:read('*a'))
f:close()
return s
end
function wifi.wpa_supplicant_status()
local dir = wifi.wpa_supplicant_dir()
local iface = posix.basename(dir):gsub("wpa_supplicant%-",'')
local cmd = ('/usr/sbin/iw dev %s link'):format(iface)
local buf = os.capture(cmd)
return buf:find("Connected to", 1, true) ~= nil
end
This is not even tested, but you should get the idea.

Resources