I'm quite new to Lua and Embedded programming. I'm working on a project:
IoT node that can be abstractedinto two parts: sensor and a board that runs Open WRT with Lua 5.1. I'm writing script that would be called from crontab every minute.
In my script I'm accessing data from the sensor via package written with C. The result of reading data from sensor is 'hexadecimal numbers returned in string:
4169999a4180cccd41c9851f424847ae4508e0003ddb22d141700000418e666641c87ae14248147b450800003dc8b439
Then convert it (string) to values I need and POST it to API.
Problem:
Sometimes API is not reachable due to poor network connection.
So I need to implement system where I would read a data from a sensor and then if API is not responding, I would save it to a FIFO queue (buffer). And then next time when script is called to read it would be sending 'old' records first and the newest one and the end.
local queue_filespec = [[/path/to/your/queue/file]]
-- Initially your "queue file" (regular file!) must contain single line:
-- return {}
local function operation_with_queue(func)
local queue = dofile(queue_filespec)
local result = func(queue)
for k, v in ipairs(queue) do
queue[k] = ("%q,\n"):format(v)
end
table.insert(queue, "}\n")
queue[0] = "return {\n"
queue = table.concat(queue, "", 0)
local f = assert(io.open(queue_filespec, "w"))
f:write(queue)
f:close()
return result
end
function add_to_queue(some_data)
operation_with_queue(
function(queue)
table.insert(queue, some_data)
end
)
end
function extract_from_queue()
-- returns nil if queue is empty
return operation_with_queue(
function(queue)
return table.remove(queue, 1)
end
)
end
Usage example:
add_to_queue(42)
add_to_queue("Hello")
print(extract_from_queue()) --> 42
print(extract_from_queue()) --> Hello
print(extract_from_queue()) --> nil
Related
I'm using Corona SDK for the first time and read up on how to call functions from other files, but I seem to be having issues. Here are the two scripts so far:
timer.lua
local M = {}
function M.Timer(n, count) --(period, how many times repeated)
if count > 0 then
local iter= os.time()+n
while iter ~= os.time() do
end
M.onTime(count)
count = count - 1
M.Timer(n,count)
end
end
function M.onTime(count)
display.newtext(count,250,50,native.systemFont,16)
end
return M
main.lua
local timeTool = require("timer")
timeTool.Timer(1,5)
They are located in the same directory. When I run main.lua on the simulator, I get the error attempt to call field 'Timer' (a nil value). This leads me to believe that the main file failed in acquiring the contents of the timer script, but from what I've seen, I am using the correct syntax. Is there something I missed, or am I using the wrong method for calling functions from other scripts?
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)
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).
It is my understanding that in Lua 5.2 that environments are stored in upvalues named _ENV. This has made it really confusing for me to modify the environment of a chunk before running it, but after loading it.
I would like to load a file with some functions and use the chunk to inject those functions into various environments. Example:
chunk = loadfile( "file" )
-- Inject chunk's definitions
chunk._ENV = someTable -- imaginary syntax
chunk( )
chunk._ENV = someOtherTable
chunk( )
Is this possible from within Lua? The only examples I can find of modifying this upvalue are with the C api (another example from C api), but I am trying to do this from within Lua. Is this possible?
Edit: I'm unsure of accepting answers using the debug library. The docs state that the functions may be slow. I'm doing this for efficiency so that entire chunks don't have to be parsed from strings (or a file, even worse) just to inject variable definitions into various environments.
Edit: Looks like this is impossible: Recreating setfenv() in Lua 5.2
Edit: I suppose the best way for me to do this is to bind a C function that can modify the environment. Though this is a much more annoying way of going about it.
Edit: I believe a more natural way to do this would be to load all chunks into separate environments. These can be "inherited" by any other environment by setting a metatable that refers to a global copy of a chunk. This does not require any upvalue modification post-load, but still allows for multiple environments with those function definitions.
The simplest way to allow a chunk to be run in different environments is to make this explicit and have it receive an environment. Adding this line at the top of the chunk achieves this:
_ENV=...
Now you can call chunk(env1) and later chunk(env2) at your pleasure.
There, no debug magic with upvalues.
Although it will be clear if your chunk contains that line, you can add it at load time, by writing a suitable reader function that first sends that line and then the contents of the file.
I do not understand why you want to avoid using the debug library, while you are happy to use a C function (neither is possible in a sandbox.)
It can be done using debug.upvaluejoin:
function newEnvForChunk(chunk, index)
local newEnv = {}
local function source() return newEnv end
debug.upvaluejoin(chunk, 1, source, 1)
if index then setmetatable(newEnv, {__index=index}) end
return newEnv
end
Now load any chunk like this:
local myChunk = load "print(x)"
It will initially inherit the enclosing _ENV. Now give it a new one:
local newEnv = newEnvForChunk(myChunk, _ENV)
and insert a value for 'x':
newEnv.x = 99
Now when you run the chunk, it should see the value for x:
myChunk()
=> 99
If you don't want to modify your chunk (per LHF's great answer) here are two alternatives:
Set up a blank environment, then dynamically change its environment to yours
function compile(code)
local meta = {}
local env = setmetatable({},meta)
return {meta=meta, f=load('return '..code, nil, nil, env)}
end
function eval(block, scope)
block.meta.__index=scope
return block.f()
end
local block = compile('a + b * c')
print(eval(block, {a=1, b=2, c=3})) --> 7
print(eval(block, {a=2, b=3, c=4})) --> 14
Set up a blank environment, and re-set its values with your own each time
function compile(code)
local env = {}
return {env=env, f=load('return '..code, nil, nil, env)}
end
function eval(block, scope)
for k,_ in pairs(block.env) do block.env[k]=nil end
for k,v in pairs(scope) do block.env[k]=v end
return block.f()
end
local block = compile('a + b * c')
print(eval(block, {a=1, b=2, c=3})) --> 7
print(eval(block, {a=2, b=3, c=4})) --> 14
Note that if micro-optimizations matter, the first option is about 2✕ as slow as the _ENV=... answer, while the second options is about 8–9✕ as slow.
I was trying to log in using LibCurl. Actually I am using LuaCurl the binding of libCurl in Lua. I am referring to this web page: http://www.hackthissite.org/articles/read/1078
I tried this:
> require("libcurl")
> c=curl.new()
> c:setopt(curl.OPT_USERAGENT,"Mozilla/4.0")
> c:setopt(curl.OPT_AUTOREFERER,true)
> c:setopt(curl.OPT_FOLLOWLOCATION,true)
> c:setopt(curl.OPT_COOKIEFILE,"")
> c:setopt(curl.OPT_URL,"https://www.chase.com")
> res=c:perform()
But after this last operation the program is stuck as if waiting for something. What am I doing wrong here?
Thanks
I tried your program and it seems to work fine. What I get is the content of the given site pumped to stdout. It seems you are just having networking issues...
If you want to capture the whole output as a string and process it later, you have to provide a callback using OPT_WRITEFUNCTION which will be called with more data which you can save. Here is a simplified version of how I implemented the GET method in my simple web-mining toolbox WDM.
local c = curl.new()
...
function get(url)
c:setopt(curl.OPT_URL,url)
local t = {} -- output will be stored here
c:setopt(curl.OPT_WRITEFUNCTION, function (a, b)
local s
-- luacurl and Lua-cURL friendly way
if type(a) == "string" then s = a else s = b end
table.insert(t, s) -- store this piece of data
return #s
end)
assert(c:perform())
return table.concat(t) -- return the whole content
end