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)
Related
Here's the deal. When you get arrested in my game, you get sent to jail. To get out you must be bailed out.
The client sends a request to the server to bail them. Every other part seems to work except this part I believe, however it could be the client side script. Is there anything incorrect about this script? I have checked it for any errors that are obvious to me.
local replicatedStorage = game:GetService('ReplicatedStorage')
local createSystemMessage = replicatedStorage:WaitForChild('CreateSystemMessage')
game.ReplicatedStorage.Bail.OnServerEvent:Connect(function(Player,PlayerToBail)
Player = game.Players:FindFirstChild(Player)
local tab = nil
for i,v in pairs(_G.GlobalData) do
if v.Name == Player.Name then
tab = v
end
end
if PlayerToBail.Team == game.Teams:FindFirstChild("Criminal") then
local Bounty = PlayerToBail.leaderstats.Bounty.Value * 2
if tab.Bank <= Bounty then
tab.Bank -= Bounty
PlayerToBail.leaderstats.Bounty.Value = 0
PlayerToBail.Prisoner.Value = false
PlayerToBail.Team = game.Teams:FindFirstChild("Civilian")
createSystemMessage:FireAllClients((Player.Name .. ' has Bailed ' .. PlayerToBail.Name), Color3.fromRGB(0, 250, 0))
end
end
end)
And the local script which works:
script.Parent.AcceptButton.MouseButton1Click:Connect(function()
local PlayerName = script.Parent.TargetName.Text
game.ReplicatedStorage.Bail:FireServer(PlayerName)
print ("Bail Requested")
end)
I believe the problem occurs with the argument you're passing to the remote event.
In your client script you pass PlayerName as an argument. I'm assuming this is a string of the player's name:
game.ReplicatedStorage.Bail:FireServer(PlayerName)
PlayerName will actually be sent to the parameter "PlayerToBail", which I'm assuming is supposed to be a player object. Keep in mind that Roblox's RemoteEvents automatically pass in the player who fired the remote event as the first argument. So the "Player" parameter of the function connected to your remote event is the actual player object that has the local script that fired the remote event.
Instead, I would fire the remote event like this:
game.ReplicatedStorage.Bail:FireServer()
Since the player you want to bail is automatically passed as an argument, you don't need to add any additional arguments to FireServer. In addition, you would get rid of the "PlayerToBail" parameter in your server script.
Also note, it is unnecessary to have this line of code in your server script:
Player = game.Players:FindFirstChild(Player)
Player is already referring to an object in game.Players. In addition, Player is an object, not a string. So this would not work. You can simply use Player as is for your purposes.
More info on Remote Events: https://developer.roblox.com/en-us/articles/Remote-Functions-and-Events
Please feel free to follow up if you still have issues.
When firing the server it automatically gives a parameter which is the player who sent it. So, you don't have to have the playerName part because after the player parameter is already there and you can get the name from that.
I am trying to resolve wifi connectivity using Lua language. I have been combing through the api to find a solution but nothing solid yet. I asked a previous question, dynamically switch between wifi networks and the answer did address the question in the way I asked it, but it didn't accomplish what I expected.
Basically, I have two different networks from two different providers. All I want the ESP8266 12e to do is detect when or if the current network has no internet access and automatically switch to the next network. It must continuously try to connect at say a 3 minute interval until it is successful and not just give up.
For testing purposes I tried this code below. The plan is to use the variable "effectiveRouter" and write some logic to switch based on the current router.
effectiveRouter = nil
function wifiConnect(id,pw)
counter = 0
wifi.sta.config(id,pw)
tmr.alarm(1, 1000, tmr.ALARM_SEMI, function()
counter = counter + 1
if counter < 10 then
if wifi.sta.getip() == nil then
print("NO IP yet! Trying on "..id)
tmr.start(1)
else
print("Connected, IP is "..wifi.sta.getip())
end
end
end)
end
wifiConnect("myNetwork","myPassword")
print(effectiveRouter)
When I run that code, I get effectiveRouter as nil on the console. This tells me that the print statement ran before the method call was complete, print(effectiveRouter). I am very very new to lua as this is my first time with the language. I am certain this boiler plate code must have been done before. Can someone please point me in the right direction? I am open to shifting to the arduino IDE as I already have it set up for the NodeMCU ESP8266. May be I can follow the logic better as I come from a java-OOP background.
I eventually sat down and tested my sketch from the previous answer. Two additional lines and we're good to go...
What I missed is that wifi.sta.config() resets the connection attempts if auto connect == true (which is the default). So, if you call it to connect to AP X while it's in the process of connecting to X it will start from scratch - and thus usually not get an IP before it's called again.
effectiveRouter = nil
counter = 0
wifi.sta.config("dlink", "password1")
tmr.alarm(1, 1000, tmr.ALARM_SEMI, function()
counter = counter + 1
if counter < 30 then
if wifi.sta.getip() == nil then
print("NO IP yet! Keep trying to connect to dlink")
tmr.start(1) -- restart
else
print("Connected to dlink, IP is "..wifi.sta.getip())
effectiveRouter = "dlink"
--startProgram()
end
elseif counter == 30 then
wifi.sta.config("cisco", "password2")
-- there should also be tmr.start(1) in here as suggested in the comment
elseif counter < 60 then
if wifi.sta.getip() == nil then
print("NO IP yet! Keep trying to connect to cisco")
tmr.start(1) -- restart
else
print("Connected to cisco, IP is "..wifi.sta.getip())
effectiveRouter = "cisco"
--startProgram()
end
else
print("Out of options, giving up.")
end
end)
You better to migrate a callback based architecture to be sure that you have successfully connected. Here is doc for it :
https://nodemcu.readthedocs.io/en/master/en/modules/wifi/#wifistaeventmonreg
You can listen for
wifi.STA_GOTIP
And make your custom operations in it. Do not forget to start eventmon.
P.s. I am not able to see your variable effectiveRouter in related function.
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)
I want to get the frame time (relative) of the first packet that is not communicating on sos and DIS ports and ip address is not the one mentioned in the if statement. But the packet should be utilizing port 24111. However, the code below is not working for this purpose. It works, until I add udp_port~=24111. After that it gives me no results, which means that it doesn't go inside that conditional statement. I have tried to write the condition in multiple ways, even separating it out into a new if statement but it doesn't work. What I am doing wrong here. Thanks for suggestions in advance.
Here is the piece of code that I have at the moment
local first_outpacket = 0
local flag = 0
function stats_first_packet()
local udp_port
local frame_time
local ip_addr
frame_time = time_relative_extractor()
udp_port = udp_port_extractor()
ip_addr = ip_addr_extractor()
if ( udp_port ) then
if (not (udp_port == 3000 or udp_port==3838 or flag==1 or ip_addr=="192.168.1.2" or udp_port~=24111)) then
first_outpacket = frame_time
print(frame_time)
flag = 1
else
-- print("tcp_src_port already recorded")
end
else
-- print("no tcp_src_port")
end
end
The problem apparently lies in the data type returned by the extractor() functions. In order to compare them with another value in the if statement they have to be converted into strings using tostring() function.
For example:
if (not (tostring(udp_port) == "3000" or tostring(udp_port)=="3838" or flag==1))
I need some help using socket "select" function.
My server code is like this:
while true do
for _,server in pairs(servers) do
local client = server:accept()
client:settimeout(5)
local line, err = client:receive()
if not err then
client:send(line .. "_SERVER_SIDE\n")
else
client:Send("___ERRORPC"..err)
end
client:close()
end
end
But now i want to use the select function instead of make a forever loop like this.
Reading this: http://w3.impa.br/~diego/software/luasocket/socket.html
I know that i can use something simmilar than:
socket.select(servers, nil, 5)
But i donĀ“t know how i can use this on the code above. Can anyone help me?
I will have to use this inside a while true statement?
The reading operation (first parameter) means that i can only make an accept/receive]? And the seconds parameter means that i can only make a send?
As per the documentation, select receives one or two arrays of sockets and returns an array of sockets that can safely be read from without blocking and an array of sockets that can be safely written to without blocking and an array of sockets that can safely be written without blocking. An important point is that the first array is for both server sockets that want you want to call accept on and for client sockets that you want to call receive on.
The seconds parameter is just a timeout for the select. It doesn't have to do with how many operations you can make.
The basic thing you are going to have to change in your code is that when a receive call fails with a timeout, instead or giving an error you should add that socket to the array of sockets that you pass to select. This way you can have select tell you when that socket becomes active again.
From the documentation for select: "calling select with a server socket in the receive parameter before a call to accept does not guarantee accept will return immediately. Use the settimeout method or accept might block forever." This means that you'd need to use settimeout before your accept call, but assuming you have a list of opened connections you can work with in servers table, you can use select in the following way:
local canread = socket.select(servers, nil, 1)
for _,client in ipairs(canread) do
local line, err = client:receive()
if not err then
client:send(line .. "_SERVER_SIDE\n")
else
client:send("___ERRORPC"..err)
end
end
socket.select will block for up to 1 second, but will return sooner if there is a socket from the list you provided that can be read from. You can block indefinitely if you use socket.select(servers, nil, 0); blocking for some short time is useful if you need to do some other work while waiting for the input.
Updated to use ipairs instead of pairs as the returns table is keyed both on numbers as well as on sockets themselves, so if one socket can be read from, the returned array looks like {[1] = sock, [sock] = 1}.
single demo
local server = socket.bind("*",7777)
local client_tab = {}
while true do
-- socket.select first param is a table of connected socket,
-- you want a connected socket,you need to call accept()
-- if you do not want to block,you should call settimeout(seconds)
local recvt = socket.select(client_tab, nil, 1)
server:settimeout(1)
local client = server:accept()
if client then
client_tab[#client_tab+1] = client
end
if #recvt > 0 then
-- read clients in recvt
end
end