Скрипт на луне для ESP8266. Мониторинг, управление и обновление ПО по WiFi.
- Войдите на сайт для отправки комментариев
Чт, 02/02/2017 - 21:12
Попалась мне темка, дома организовать очень простенькое управление устройством - 2-3 дискретных выход, возможно вход один. В перспективе не сложней одного ШИМ. Разумеется управлять с мобильника или планшета. Прямо из броузера проще всего. Подумалось о ESP и это как бы намекнуло, что и скриптик управления тоже можна заливать по тому же каналу по WiFi. Это очень кстати, т.к. разбирать для заливки не хочется совсем, а выводить спецом разем для этого практически не возможно. В общем попробовал и написал скетч, который это делает, его сейчас и выложу, если не запретит тот, кто всегда запрещает ;)
Тестился с на ESP8266-01 с NodeMCU 0.9.6 build 20150627 powered by Lua 5.1.4. Броузер Опера 36.0.2130.80 , но это думаю не принципиально. Код.
local WebSockAnsv="HTTP/1.1 101 Switching Protocols\r\n\Upgrade: websocket\r\n\Connection: Upgrade\r\n\Sec-WebSocket-Accept: " local WebSocketEnd="\r\n"; local b='ABCDEFGHIJKLMNOPQRSTUVWXYZabcdefghijklmnopqrstuvwxyz0123456789+/' local HTTP_html="HTTP/1.1 200 OK\nServer: ESP (nodeMCU)\nContent-Length: " local html=[[ <head><meta charset="utf-8"></head><h1>WebSocket for ESP8266</h1> <table><tr><td><input type="button" onclick="OpenWS()" value="Open"/></td><td> <input type="button" onclick="CloseWS()" value="Close"/> </td><td><input type="button" onclick="snd('?print(node.heap())')" value="Send"/> </td><td><input type="button" onclick="ld()" value="Load"/></td> </td></tr></table><input type="file" id="fn"/> <p style="color:red" id="time">--</p><p style="color:red" id="state">--</p> <table><tr><th>GPIO #</th><th>0</th><th>1</th><th>2</th><th>3</th></tr> <tr><td>Input</td><td><span id="g0">?</span></td><td><span id="g1">?</span></td> <td><span id="g2">?</span></td><td><span id="g3">?</span></td></tr> <tr><td rowspan="3">Set mode</td> <td><input type="button" onclick="snd('g0i')" value="Inp"></td> <td><input type="button" onclick="snd('g1i')" value="Inp"></td> <td><input type="button" onclick="snd('g2i')" value="Inp"></td> <td><input type="button" onclick="snd('g3i')" value="Inp"></td></tr> <tr><td><input type="button" onclick="snd('g01')" value="Set HIGH"></td> <td><input type="button" onclick="snd('g11')" value="Set HIGH"></td> <td><input type="button" onclick="snd('g21')" value="Set HIGH"></td> <td><input type="button" onclick="snd('g31')" value="Set HIGH"></td></tr> <tr><td><input type="button" onclick="snd('g00')" value="Set LOW"></td> <td><input type="button" onclick="snd('g10')" value="Set LOW"></td> <td><input type="button" onclick="snd('g20')" value="Set LOW"></td> <td><input type="button" onclick="snd('g30')" value="Set LOW"></td></tr> </table> <p id="all"></p> <script language="Javascript"> ESP_URL = "ws://192.168.0.20:5000"; cmdPas="^9h87hg8yg"; cmdOpen='?file.remove("tmp") file.open("tmp","w+")'; cmdClose='?file.flush() file.close() file.remove("http_serv.lua") file.rename("tmp","http_serv.lua") PRG_mode=nil'; var pr; var prpos;var socketW=null; function getIn(o,s){document.getElementById(o).innerHTML=s;} function addLog(s){getIn('all',document.getElementById('all').innerHTML+s+'<br>');} function RecWS(messageEvent) {var d=messageEvent.data;addLog(d); var s=d.substr(1); if(d.charAt(0)=='t') {getIn('time',s);if(Prg>0) Prg++;} else if(d.charAt(0)=='s') getIn('state',s); else if(d.charAt(0)=='g') getIn('g'+d.charAt(1),d.charAt(2)); if(Prg>0){socketW.send(cmdPas);socketW.onmessage=RecPRG;}} function OnOpen() {funcPing();} function OnClose(Event) {socketW=null;getIn('time',"???")} function OpenWS(){ if(socketW==null) socketW= new WebSocket(ESP_URL); if(socketW!=null) {socketW.onmessage =RecWS;socketW.onopen = OnOpen;socketW.onclose = OnClose;} } function CloseWS(){ if(socketW!=null) socketW.close();} function fdl(event) {pr=event.target.result;prpos=0;socketW.send(cmdOpen);} function fde(event) {getIn('time')=event.data;} function RecPRG(messageEvent) {var d=messageEvent.data;addLog(d); if(pr){s=pr.substr(prpos,100); if(s!=""){ prpos=prpos+100;socketW.send("#"+s);getIn('time',prpos);return;} pr=null;socketW.send(cmdClose);socketW.onmessage=RecWS;Prg=0;} else if(d=="tPRG"){var fd=new FileReader(); fd.onload=fdl;fd.onerror=fde; getIn('time',"PRG:"+document.getElementById("fn").files[0].name); fd.readAsText(document.getElementById("fn").files[0]);} } var Prg=0; function ld(){Prg=1;} function snd(d){if(Prg<2) if(socketW!=null) if (socketW.readyState==1) socketW.send(d);} function funcPing(){snd("#print(node.chipid())");if(socketW!=null); setTimeout(funcPing,1000);} </script>]] local TCP_connect = 0 local ProcessWS local CountWS=0 function OpenWS(conn,payload) if(string.find(payload,"GET / HTTP/1.1\r\n")~=1) then return 1 end --РЅРµ HTTP if(string.find(payload,WebSocketEnd..WebSocketEnd)==0) then return 2 end --РїСЂРёРЅСЏС‚ РЅРµ весь local p1=string.find(payload,"Sec-WebSocket-Key: ", 1, true) if(p1==nil) then return 3 end --это HTTP РЅРѕ РЅРµ WebSocket local key=string.sub(payload, p1+19) key=string.sub(key, 1, string.find(key,WebSocketEnd)-1) key=key.."258EAFA5-E914-47DA-95CA-C5AB0DC85B11" key=WebSockAnsv..enc(crypto.hash("sha1",key))..WebSocketEnd..WebSocketEnd conn:send(key) ProcessWS=ReciveWS return 0 end local PRG_mode local ofs function ReciveWS(conn,payload) ofs=0 local U=string.len(payload) while (ofs<U) do local InpStr=encodeWS(payload) local h=string.sub(InpStr, 1, 1) if(PRG_mode) then local v=string.sub(InpStr, 2) if(h=="?") then node.input(v) SendTextWS(conn, "?") else file.write(v) SendTextWS(conn, ">")end else if(h=="#") then SendTextWS(conn,"t "..CountWS) SendTextWS(conn,"g0"..gpio.read(8)) SendTextWS(conn,"g1"..gpio.read(5)) ---TX SendTextWS(conn,"g2"..gpio.read(9)) SendTextWS(conn, "g3"..gpio.read(4)) ---RX else if(h=="?") then print("h") SendTextWS(conn, "sheap="..node.heap().." Time="..tmr.now()) else if(h=="g") then local p=string.sub(InpStr, 2, 2) if(p==0) then p=8 else if(p==1) then p=5 else if(p==2) then p=9 else p=4 end end end local p=nGPIO(string.sub(InpStr, 2, 2)) local i=string.sub(InpStr, 3, 3) if(i==0) then gpio.mode(p,gpio.OUTPUT,gpio.FLOAT) gpio.write(p,gpio.LOW) else if(i==1) then gpio.mode(p,gpio.OUTPUT,gpio.FLOAT) gpio.write(p,gpio.HIGH) else gpio.mode(p,gpio.INPUTT,gpio.FLOAT) end end else if(h=="^") then if(string.sub(InpStr, 2)=="9h87hg8yg") then PRG_mode=conn SendTextWS(conn,"tPRG") end end end end end end end return 0 end function enc(data) return ((data:gsub('.', function(x) local r,b='',x:byte() for i=8,1,-1 do r=r..(b%2^i-b%2^(i-1)>0 and '1' or '0') end return r; end)..'0000'):gsub('%d%d%d?%d?%d?%d?', function(x) if (#x < 6) then return '' end local c=0 for i=1,6 do c=c+(x:sub(i,i)=='1' and 2^(6-i) or 0) end return b:sub(c+1,c+1) end)..({'','==','='})[#data%3+1]) end local opcod function encodeWS(payload) opcod=bit.band(string.byte( payload, 1),0x0f) local flags=bit.bor(bit.rshift(string.byte( payload, 1),4), bit.band(string.byte( payload, 2),0x0f)) local len=bit.band(string.byte( payload, 2),0x7f) ofs=ofs+3 if(len==126) then len=string.byte( payload, 3)+string.byte( payload, 4)*256 ofs=ofs+2 elseif (len==127) then len=string.byte( payload, 3)+256*(string.byte( payload, 4)+256*(string.byte( payload, 5)+256*string.byte( payload, 6))) ofs=ofs+8 end; local mas={string.byte( payload, ofs),string.byte( payload, ofs+1), string.byte( payload, ofs+2),string.byte( payload, ofs+3)} local result="" ofs=ofs+4 local i=1; repeat result=result..string.char(bit.bxor(mas[i],string.byte( payload, ofs))) len=len-1 ofs=ofs+1 i=i+1 if(i==5) then i=1 end until len==0 return result end function SendTextWS(conn,t) local l=string.len(t) if(l>125) then t="long!" l=5 end conn:send(string.char(bit.bor(opcod,0x80))..string.char(l)..t) end if(wifi.sta.status()~=5) then wifi.setmode(wifi.STATION) wifi.sta.config("*****","*****") wifi.sta.setip({ip="192.168.0.20",netmask="255.255.252.0",gateway="192.168.0.1"}) wifi.sta.connect() end if(srv==nil) then srv=net.createServer(net.TCP,5) end; srv:listen(80,function(conn)end) local html_index=0 local ConectCount=0; ProcessWS=OpenWS srv:listen(5000,function(conn) TCP_connect = 1 ConectCount=ConectCount+1 conn:on("connection", function(conn)TCP_connect=2 end) conn:on("reconnection", function(conn)ProcessWS=OpenWS end) conn:on("disconnection", function(conn) TCP_connect=0 ConectCount=ConectCount-1 if(ConectCount==0) then ProcessWS=OpenWS end end) conn:on("sent", function(conn) if(html_index>0) then local ss=string.sub(html,html_index,html_index+1000) if(string.len(ss)==0) then html_index=0 conn:close() else conn:send(ss) html_index=html_index+1001 end end; end) conn:on("receive",function(conn,payload) if(ProcessWS(conn,payload) == 3) then conn:send(HTTP_html) conn:send(string.len(html)..WebSocketEnd..WebSocketEnd) html_index=1 end end) end) TCP_stat=0 tmr.alarm(0, 10, 1, function() CountWS=CountWS+1 if(TCP_stat~=TCP_connect) then TCP_stat=TCP_connect end end )Код с пылу- с жару, сегодня дотестил. Но вроде работает, цикл обмена в 1 сек держит и софт обновляет ))
Теперь о проблемах.
Скрипт большой, очень большой. Больше в девайс одним куском не лезет. Даже попытка вынести айпишник в строку в начале кода дает not enough memory. Тоже самое при попытке запуска после софт-ресета, только хард;) И после обновления софта тоже ребутнуть. По ходу вопрос 1.Кто знает более экономную версию прошивки?
При разработке пробовал отправлять сообщения по инициативе ESP, но столкнулся с проблемой. Откуда отправлять? Из обработчика периодического по таймер глючит сильно, как при несинхронизации потока. Делать из вечного цикла, как бы некрвсиво, а любой делей в нем намертво убивает процесс обмена - в ТСP канале сразу куча ошибок, повторов передач. Вопрос 2. Кто то делал отправку сообщения по TCP не из обработчиков серверного сокета? Как?
Вобще там Send - источник гемора. В начале пока ответ на GET HTML укладывался в один TCP пакет, у меня 1514 байт, все работало без вопросов. По мере роста - при превышении, отправка одним куском выдавало ошибки. Побил на два куска, отправляемые сразу друг за другом. Работает, пока не доросло до 2*1514байт ))) Делить на 3 отправляемых подряд не помогает. Повесил отправку не первого куска в обработчик sent. Сново все работает, на в 8КБ проверил. Вопрос 3. Я правильно делаю или есть какой другой путь? Думал все. Нет не все. Когда вызовов Send чтало много мелких, и при обмене "тудысюды" раз в сек, снова начал подглючивать, отправляемые данные просто пропадали в, канале их нет. Начал обединять по типу Send(a) Send(b) Send(c) заменил на Send(a..b..c). Вроде попустило. Но не уверен. Вопрос 3 Это че за фигня с net.socket:send()?
Тепер самый прикол. Используются 2 порта, 80 и 5000. 80 открывается, но обработчика на 80 нет ))) Его запросы приходят на 5000, похоже на последний открытый ))) Вопрос 4. Че за фигня, эту багу ктото встречал, её фиксили?
С этой бы багой еще можна было бы жить, если б не запрос favicon.ico посреди обмена по WebSocket(( Вопрос 5 Как в ответе на GET HTML сделать так чтоб сраную favicon.ico не запрашивало? Т.е. можна как то ответить на запрос страницы так, чтоб про икону не спрашивало? Такое возможно? С учетом этого рекомендую открывать сокет секунд через 15 после открытия формы.
С учетом того, что скрипт "под завязку" в памяти (потому ни коментов ни форматирования нет и не будет), далее я собираюсь его разбить на 3-5 частей, как раз по логике там выходит. Соответственно уже одним куском не будет, не выложеш запросто и это собственно побудило опубликовать "как есть", т.к. если кому интересно - то стартонуть с этого проще.