IMAP mail checker
Posted: Mon Jan 23, 2017 3:17 pm
Hi, I've bought Wemos D1 mini to build mail notifier to get email notifications without turning on the computer. It's my first program, so please be gentle to me, improvement suggestions are welcome.
I've tried to comment functions, but basically:
- it connects to Wi-Fi - selection from more networks from array, I use the box on more places
- white LED flash - step started
- green LED flash - receiving data from mail server
- red LED on - unread email exists on server
- sound play via pcm - notifies every new email just once, after notifying of new email just red LED is set without sound, until new mail arrives
- blue LED on - mail from VIP email address
IMAP steps 1 (LOGIN) and 2 (LIST, isn't needed and can be safely removed) are processed just once, other steps are repeated in loop. Step time should be longer, than sound played (or sound will be cut), receiving of new email (FETCH) can take more than one step (but program waits until email is fully received), if you want, you can shorten the received response (I'm waiting for 'OK Fetch completed', this is the safest way to, but if you find length of your messages, so it also contains 'From:' field, which is used for VIP mail recognition). In step 5, the FETCH is processed for new mail, even if no VIP mail check is disabled, then it's really not needed and you can disable it (but be careful to include the 'unread_imaps.uns" stuff).
Informations about number of emails, last played pcm notification and count of new emails (just estimation, because IMAP doesn't report count of new emails, so it's just roughly calculated as EXISTS - UNSEEN + 1), so even after reset ESP knows last played notification and don't play sound, if it was already played for same UNSEEN, NEW and so on. The program runs in loop, but sometimes it crashes (be sure to use init.lua, if you want to use it for longer period), if someone can give me hint, why it crashes, I'll be happy.
I haven't found way, how to check certificates (always reported, that certificate doesn't match), so just encrypted connection to server on port 993 is established without checking of certificates. Port can be changed to insecure IMAP (IMAP_PORT and imap_socket = net.createConnection...), but I recommend to keep SSL on (this probably needs firmware with TLS included).
If you don't want to check emails in loop, but just once, just uncomment step 8 to close the connection to server, delete the last 'else' and stop timer 3.
I've tried to comment functions, but basically:
- it connects to Wi-Fi - selection from more networks from array, I use the box on more places
- white LED flash - step started
- green LED flash - receiving data from mail server
- red LED on - unread email exists on server
- sound play via pcm - notifies every new email just once, after notifying of new email just red LED is set without sound, until new mail arrives
- blue LED on - mail from VIP email address
IMAP steps 1 (LOGIN) and 2 (LIST, isn't needed and can be safely removed) are processed just once, other steps are repeated in loop. Step time should be longer, than sound played (or sound will be cut), receiving of new email (FETCH) can take more than one step (but program waits until email is fully received), if you want, you can shorten the received response (I'm waiting for 'OK Fetch completed', this is the safest way to, but if you find length of your messages, so it also contains 'From:' field, which is used for VIP mail recognition). In step 5, the FETCH is processed for new mail, even if no VIP mail check is disabled, then it's really not needed and you can disable it (but be careful to include the 'unread_imaps.uns" stuff).
Informations about number of emails, last played pcm notification and count of new emails (just estimation, because IMAP doesn't report count of new emails, so it's just roughly calculated as EXISTS - UNSEEN + 1), so even after reset ESP knows last played notification and don't play sound, if it was already played for same UNSEEN, NEW and so on. The program runs in loop, but sometimes it crashes (be sure to use init.lua, if you want to use it for longer period), if someone can give me hint, why it crashes, I'll be happy.
I haven't found way, how to check certificates (always reported, that certificate doesn't match), so just encrypted connection to server on port 993 is established without checking of certificates. Port can be changed to insecure IMAP (IMAP_PORT and imap_socket = net.createConnection...), but I recommend to keep SSL on (this probably needs firmware with TLS included).
If you don't want to check emails in loop, but just once, just uncomment step 8 to close the connection to server, delete the last 'else' and stop timer 3.
Code: Select all
MY_EMAIL = "user@na.me" --IMAP username
EMAIL_PASSWORD = "mailpassword" --IMAP password
IMAP_SERVER = "mail.server.com" --IMAP server name
IMAP_PORT = 993 --IMAP port (change to 143 for not secure connection, 993 for IMAPS)
vipcheck = true -- check VIP mail and set blue LED (for common mails red LED is set)
vipmail = nil
letsplay = true
pcmfile = "wavsound.u8" -- convert to wav "sox wavsound.wav -r 8000 -b 8 -c 1 wavsound.u8" (-r Supported sample rates are 1000, 2000, 4000, 5000, 8000, 10000, 12000, 16000)
vip_sender = "my@lovely.gf" --VIP sender (just one, I don't have more girlfriends;-)
APTABLE =
{
home = { "", "" },--AP array 'descriptor = {"SSID", "SSID_PASSWORD"'}
work = { "", "" }
}
count = 0
atcount = 1
debug = false -- show at responses and other debug stuff, affects performance and functions processing, use with caution!!!
response_processed = false
response = ""
lastplayed = nil
ledgreen=1 -- AT notification
ledred=2 -- unread mail notification
ledblue=4 -- unread vip mail notification
ledwhite=3 --step notification
pcmpin=5 -- pcm output
tmr.stop(1)
tmr.stop(2)
tmr.stop(3)
gpio.mode(ledgreen, gpio.OUTPUT)
gpio.mode(ledred, gpio.OUTPUT)
gpio.mode(ledblue, gpio.OUTPUT)
----PCM thing starts here
function cb_drained(d)
file.seek("set", 0)
end
function cb_stopped(d)
file.seek("set", 0)
file.close()
drv = nil
end
function cb_paused(d)
--
end
function play()
file.open(pcmfile, "r")
drv = pcm.new(pcm.SD, pcmpin)
if file.open(pcmfile, "r") then
drv:on("data", function(drv) return file.read() end)
end
drv:on("drained", cb_drained)
drv:on("stopped", cb_stopped)
drv:on("paused", cb_paused)
drv:play(pcm.RATE_16K) -- Supported sample rates are pcm.RATE_1K, pcm.RATE_2K, pcm.RATE_4K, pcm.RATE_5K, pcm.RATE_8K, pcm.RATE_10K, pcm.RATE_12K, pcm.RATE_16K)
end
---- PCM thing ends here
function aplist(apfound)
for o, d in pairs(APTABLE) do
if d[1] == apfound then
SSID = d[1]
SSID_PASSWORD = d[2]
end
end
--do nothing
end
function flash(led, delay)
gpio.write(led, gpio.HIGH)
i=0
while i < delay*100 do
i = i + 1
end
gpio.write(led, gpio.LOW)
end
function atdisplay(imap_socket, response)
-- Correct responses are:
-- "a1 OK [CAPABILITY..........] Logged in"
-- "ax OK ............. completed."
-- In step 5 (FETCH) whole ending of response 'OK Fetch completed' is checked to avoid detection of 'completed' in email body
response_processed = false
flash(ledgreen,100)
if (debug==true) then
print("atdisplay start "..count)
end
responseend = string.sub(response, -40) --just in case server supports lot of ptotocols
if count == 0 and (string.match(responseend,'IMAP') ~= nil) then
response_processed = true
print("IMAP/IMAPS supported")
elseif count == 1 and (string.match(responseend,'Logged') ~= nil) then
response_processed = true
elseif count ==5 and (string.match(responseend,'OK Fetch completed') ~= nil) then
response_processed = true
elseif count > 1 and (string.match(responseend,' completed') ~= nil) then
response_processed = true
end
if (debug==true) then
print("response = ", response)
print("response processed = ", response_processed)
print("atdisplay end ", count)
end
fromstart = string.find(response,'From: "')
if fromstart ~= nil then
fromcut = string.sub(response, fromstart, fromstart+200)
vipmail = string.find(fromcut,vip_sender)
end
_G.response = response
end
function imapcheck()
if (debug==true) then
print (".")
end
if(response_processed == true) then
if (debug==true) then
print ("..")
end
flash(ledwhite,200)
count = count + 1
if(count == 1) then
imap_socket:send("a" ..atcount.. " LOGIN " ..MY_EMAIL.. " " ..EMAIL_PASSWORD.. "\r\n")
imap_socket:on("receive", atdisplay)
print(count.. " LOGIN")
atcount = atcount + 1
elseif(count == 2) then
imap_socket:send("a" ..atcount.. " LIST \"\" \"*\"\r\n") -- not really needed, every mail has INBOX
imap_socket:on("receive", atdisplay)
print(count.. " LIST - just for fun")
atcount = atcount + 1
elseif(count == 3) then
imap_socket:send("a" ..atcount.. " EXAMINE INBOX\r\n")
imap_socket:on("receive", atdisplay)
print(count.. " EXAMINE INBOX")
atcount = atcount + 1
elseif(count == 4) then
_, _, exists = string.find(_G.response,"([0-9]+) EXISTS(\.)")
unseenfound = string.find(_G.response,"UNSEEN")
print(count.. " looking for unread emails")
if unseenfound ~= nil then
unseenstart = unseenfound+7
unseenstring = string.sub(_G.response, unseenstart, unseenstart+7)
unseenend = (string.find(unseenstring,"]")+unseenstart-2)
firstunseen = tonumber(string.sub(_G.response, unseenstart, unseenend))
newcount = exists - firstunseen + 1
print (" you have " ..newcount.. " unread email/s (might be wrong due to IMAP limitations)")
end
elseif(count == 5) then
print(count.. " seen some some unseen messages?")
if firstunseen ~= nil then
imap_socket:send("a" ..atcount.. " FETCH " ..firstunseen.. " BODY[]\r\n")
imap_socket:on("receive", atdisplay)
print(" FETCH " ..firstunseen)
atcount = atcount + 1
if file.exists("unread_imaps.uns") then
if file.open("unread_imaps.uns", "r+") then
cont = file.read()
oldexists=tonumber(string.sub(cont, 0,string.find(cont,",")-1))
oldfirstunseen=tonumber(string.sub(cont, string.find(cont,",")+1,string.find(cont,",")+string.find(string.sub(cont,(string.find(cont,",")+1)),",")-1))
oldlastplayed=tonumber(string.sub(cont, string.find(cont,",")+(string.find(string.sub(cont,(string.find(cont,",")+1)),",")+1) , string.len(cont) ))
oldnewcount = oldexists - oldfirstunseen + 1
file.close()
else
lastplayed = 0 -- file doesn't exits
end
end
end
elseif(count== 6) then
print(count.. " new mail notification")
if firstunseen ~= nil then
gpio.write(ledred, gpio.HIGH)
if ((oldlastplayed ~= firstunseen) or (oldnewcount ~= newcount)) and letsplay == true then -- play the notification just once for each first UNSEEN
play() -- PCM thing again, comment if no PCM used
lastplayed = firstunseen
end
else
gpio.write(ledred, gpio.LOW)
end
elseif(count == 7) then
print(count.. " VIP mail notification")
if vipcheck == true and vipmail ~= nil then
gpio.write(ledblue, gpio.LOW)
print (" you have new mail from " ..vip_sender.. " :-)")
else
gpio.write(ledblue, gpio.HIGH)
end
if letsplay == true and firstunseen ~= nil and lastplayed == firstunseen then
if file.open("unread_imaps.uns", "w+") then
file.write(exists.. "," ..firstunseen.. "," ..lastplayed)
file.close()
end
end
-- elseif(count == 8) then
-- imap_socket:send("a"..atcount.." LOGOUT\r\n")
-- imap_socket:on("receive",atdisplay)
-- print(count.. " LOGOUT")
-- atcount = atcount + 1
-- imap_socket:close()
else
print(count.. " Loop\n")
gpio.write(ledred, gpio.LOW)
gpio.write(ledgreen, gpio.LOW)
gpio.write(ledwhite, gpio.LOW)
gpio.write(ledblue, gpio.HIGH)
if letsplay == true and drv ~= nil then
drv:stop() ----PCM thing, release PCM driver, comment if no PCM used
end
count = 2 -- skip LOGIN and (useless) LIST
vipmail = nil
-- tmr.stop(3)
end
end
end
function connected(imap_socket)
tmr.alarm(3, 15000, tmr.ALARM_AUTO, imapcheck) -- time should be longer than pcm notification file
end
gpio.write(ledred, gpio.LOW)
gpio.write(ledgreen, gpio.LOW)
gpio.write(ledwhite, gpio.LOW)
gpio.write(ledblue, gpio.HIGH) --blue LED is inverted on my ESP, change for normal logic
wifi.setmode(wifi.STATION)
wifi.sta.getap(function(t)
if t then
for k,v in pairs(t) do
aplist(k)
end
else
--do nothing
end
end)
tmr.alarm(1, 4000, tmr.ALARM_AUTO, function()
if SSID == nil then
print("Waiting for APs")
else
tmr.stop(1)
print ("Connecting to "..SSID)
wifi.sta.config(SSID,SSID_PASSWORD)
end
end)
tmr.alarm(2, 5000, tmr.ALARM_AUTO, function()
if wifi.sta.getip()== nil then
print("IP unavaiable, waiting...")
else
tmr.stop(2)
print("Config done, IP is "..wifi.sta.getip())
print("Connect to server "..IMAP_SERVER.." on port "..IMAP_PORT)
imap_socket = net.createConnection(net.TCP,1) --connect to IMAP/IMAPS server, 0=IMAP, 1=IMAPS)
imap_socket:on("connection",connected)
flash(ledwhite,500)
imap_socket:on("receive",atdisplay) -- to check if server supports IMAP, if you want to skip this check, comment this line and uncomment 'response_processed = true' 2 lines below
imap_socket:connect(IMAP_PORT,IMAP_SERVER)
-- response_processed = true -- fake response if no IMAP support on server is checked
end
end)