ESP8266, IoT – praktyczny przykład cz. 2
Poprzednio pokazałem jak podłączyć się do modułu ESP8266, wgrać firmware NodeMCU oraz podpiąć się do sieci WiFi. Teraz, czas na interakcje z modułem przez sieć, bo to jest jeden z aspektów IoT – możliwość wpłynięcia na zachowanie urządzeń przez sieć.
W poprzedniej części napisałem, że koniecznie trzeba nazwać nagrywany plik init.lua
. Plik o takiej nazwie jest automatycznie (po restarcie) wykonywany przez ESP8266 z firmware nodeMCU. Jeśli chcesz możesz oczywiście podzielić swój kod na różne pliki, wystarczy w init.lua
wydać komendę dofile("NAZWA")
by wykonać kod zawarty w pliku NAZWA.
Środowisko ESPlorer niestety nie zna pojęcia projekt, więc potem każdy plik, który chcesz używać trzeba otworzyć ręcznie (przez Ctrl+O) – nie jest to wygodne, ale cóż zrobić.
UWAGA w tym wpisie zajmujemy się podłączaniem urządzeń wykorzystujących zasilanie z sieci 230V. Jeżeli jesteś zupełnym laikiem, nie masz doświadczenia w pracy z takimi napięciami – koniecznie znajdź kogoś doświadczonego, kto może Ci pomóc. Nasz artykuł jest przeznaczony dla osób, które wiedzą jak się obchodzić z takimi napięciami. Nie masz doświadczenia – nie rób tego sam.
Zgodnie z zapowiedzią, naszym celem jest zbudowanie serwera WWW na ESP8266, który przez stronę WWW pozwoli na zdalne kontrolowanie urządzenia elektrycznego.
Najpierw – jak sterować urządzeniem elektrycznym? Jak bezpiecznie je włączyć lub wyłączyć? Użyjemy do tego celu przekaźników. Co to jest przekaźnik? Pomyśl o nim jak o włączniku. Posiada on trzy piny. Jest jedno wejście (tzw wejście wspólne) – tam podłączamy zasilanie. Bez zasilania, lub gdy nie jest wysterowany przekaźnik, wejście wspólne jest zwarte do jednego z wyjść. Gdy przekaźnik zostanie wysterowany, wejście wspólne zostanie odłączone od pierwszego wyjścia a przełączone do drugiego.
Do wejścia nr 2 podłącza się zasilanie (dla urządzenia elektrycznego). Gdy przekaźnik nie jest wysterowany wówczas napięcie jest przekazywane na wyjście nr 4. W momencie włączenia przekaźnika przez podanie zasilania na cewkę (wejście 1 i 5) przesuwa ona magnetycznie przełącznik i zasilanie jest teraz na wyjściu nr 3 – tak długo jak cewka jest zasilana.
By zasilić urządzenie 230V – przewód „gorący” (zwykle ten brązowy przewód, ale zawsze korzystaj z próbnika, by upewnić się który to jest przewód) wpina się do wejścia nr 2 i prowadzi przewód z wyjścia nr 3 do urządzenia. Przewód ochronny i neutralny prowadzi bezpośrednio do urządzenia.
Można oczywiście używać samych przekaźników, ale nie jest to takie proste, potrzebne są dodatkowe elementy – tranzystory sterujące, diody zabezpieczające, przydaje się odizolowanie optoelektryczne. Dlatego polecamy użycie gotowego modułu przekaźników, które są „wszystkomające” – wszystko to o czym pisałem wcześniej, czyli układ sterujący, zabezpieczenia, optoizolację. Mamy w ofercie np takie dwukanałowe moduły – pozwalają one sterować dwoma urządzeniami.
Moduły te są sterowane niskim poziomem logicznym, tzn przy podłączeniu jak opisane wcześniej by zasilić urządzenie podłączone do modułu przekaźnika, na wejście sterujące trzeba podać stan niski (zero).
Każdy z przekaźników ma trzy złącza śrubowe. Środkowe, to wspólne wejście, prawe (patrząc od strony złącz śrubowych) domyślne wyjście podłączone oraz po lewej wyjście podłączone przy wysterowaniu stanem niskim. Zasilane są one 5V (więc jeśli użyć będziesz ich chciał w docelowym układzie z ESP8266, to musisz mieć źródło napięcia 3.V dla ESP8266 i 5V dla przekaźników).
W naszym układzie w pierwszej części użyliśmy Arduino UNO jako źródła zasilania dla ESP8266. W tym przypadku jest to kolejna zaleta, bo mamy od razu źródło napięcia 5V do zasilania przekaźników (takie jest napięcie sterujące cewką w przekaźniku). Masę całego układu łączmy z masą modułu przekaźników (pin GND z lewej strony). Najlepszy będzie do tego przewód F-M, wtykamy go w płytkę stykową w szynę zasilającą z masą (niebieska kreska na płytce stykowej) a drugi koniec nakładamy na pin GND. Zasilanie 5V podłączamy takim samym przewodem z gniazda 5V na Arduino do pinu VCC na module przekaźników.
Piny GPIO w ESP8266 i sterowanie w nodeMCU
Nim podłączymy piny sterujące (IN1 oraz IN2) kilka słów o sterowaniu pinami GPIO w ESP8266
Pracując z NodeMCU warto korzystać z opisu modułów. Ta strona to na dziś oficjalne repozytorium z dokumentacją. W naszym projekcie interesuje nas opis korzystania z pinów GPIO, co załatwia moduł gpio. Jedną z podstawowych informacji jest tabelka z opisem numerów GPIO używanych przez nodeMCU a nazwami używanymi na ESP8266. I tak, użyjemy GPIO4 i GPIO5 które wg konwencji nodeMCU mają numer 1 i 2.
Podobnie jak w innych mikrokontrolerach piny GPIO mogą pracować jako wejście lub wyjście. Dlatego pierwszym krokiem jest ustawienie trybu pracy:
gpio.mode(1, gpio.OUTPUT) gpio.mode(2, gpio.OUTPUT)
Piny są gotowe do sterowania przekaźnikami, ale najpierw musimy mieć serwer WWW.
Serwer WWW na ESP8266
Pierwszy krok, to definicja serwera TCP (TCP to rodzaj połączeń sieciowych, które są używane do transmisji stron WWW):
srv = net.createServer(net.TCP)
Mamy obiekt srv
obsługujący połączenia TCP, ale to nie jest wszystko. Połączeń TCP do jednego adresu IP może być bardzo dużo, więc by móc mieć różne serwery TCP na jednym adresie IP protokół przewiduje jeszcze numer portu. Dopiero ten numer z adresem IP pozwala w pełni zaadresować połączenie TCP. Dla serwerów WWW domyślnym numerem portu jest 80. Musimy więc zdefiniować usługę działającą w obrębie serwera TCP na porcie 80. O usłudze oczekującej na połączenia mówi się że „nasłuchuje” (ang. listening) dlatego moduł net
oferuje funkcję net.server.listen
. Przyjmuje ona za argumenty kolejno: nr portu i funkcję która będzie wywoływana dla każdego połączenia przychodzącego na ten port. Funkcja obsługująca połączenie musi akceptować jeden argument, który będzie zmienną net.socket
(jego dokumentacja). To gniazdo (ang. socket) służy do komunikacji z przeglądarką która jest „po drugiej stronie” połączenia. Zaraz się przekonamy w szczegółach jak to działa:
srv:listen(80,function(conn) --KOD end)
wewnątrz funkcji KOD
zostanie zastąpiony przez coś co obsłuży reakcję naszego ESP na połączenie przychodzące. By to lepiej zrozumieć sprawdźmy taki przykład:
srv:listen(80,function(conn) --co zrobic na polaczenie przychodzace conn:on("receive",function(conn,payload) print(payload) conn:send('HTTP/1.1 200 OK\r\nContent-Type: text/html\r\n\r\nOK') end) --co po wyslaniu danych conn:on("sent",function(conn) conn:close() end) end)
UWAGA Kolejny minus środowiska ESPlorer (nie wiem, może to „wina” nodeMCU) jest taki, że nie obsługuje poprawnie znaków UTF, więc nie używaj nigdzie polskich znaczków – zarówno w łańcuchach jak i komentarzach….
Jest to minimalny serwer WWW (daaaleki od poprawności i kompletności). Wewnątrz funkcji go obsługującej definiujemy reakcję na dwa zdarzenia. Pierwsze to „receive
” – gdy nasz serwer WWW odbierze całe zapytanie od przeglądarki. Drugie zdarzenie to „sent
” symbolizujące moment w którym ESP skończy nadawać odpowiedź do przeglądarki.
W tym drugim zdarzeniu zamykamy połączenie przez conn:close()
. Jest to bardzo ważny element każdego serwera TCP. Bez tego zamknięcia połączenia będą nadal aktywne i będą zużywać zasoby zarówno ESP jak i przeglądarki. Ponieważ ESP nie jest potężnym komputerem to szybko mogą się pojawić problemy z ponownym połączeniem (krótko mówiąc implementacje połączeń TCP zużywają pamięć na każde otwarte połączenie, więc zazwyczaj jest ograniczenie ilości otwartych połączeń). Mimo, że będzie działać, nie uda się połączyć z ESP, bo zabraknie miejsca na otwarcie nowego połączenia TCP, wszystkie będą czekać aż upłynie określony czas bez aktywności, nim się zamkną. Dlatego – zamykaj połączenie TCP gdy skończysz z niego korzystać.
Pierwsze zdarzenie, oprócz samego obiektu połączenia jako argument otrzymuje treść całego zapytania wysłanego przez przeglądarkę. W treści tej funkcji wysyłamy całość tego zapytania na konsolę ESP (print("payload")
). Dlatego po wgraniu takiego programu na konsoli ESP powinna się pojawić treść, z której będziemy musieli wyłuskać dane nam potrzebne. Używając conn:send
odsyłamy naszą odpowiedź. By nas przeglądarka WWW zrozumiała wymagane jest zachowanie pewnego formatu, najpierw informacja, że wszystko jest w porządku (HTTP/1.1 200 OK
), potem informacja o tym że odsyłamy tekst, następnie linia odstępu i właściwa odpowiedź do wyświetlenia – czyli napis OK. Proszę wgraj oba pliki (Minimalistyczny serwer WWW) na ESP8266 zobacz jaki adres IP nadał router. Następnie wpisz ten adres w przeglądarkę. Powinieneś dostać pustą stronę z napisem OK. A teraz spójrz na konsolę ESP.
Jeśli masz doświadczenie z pisaniem aplikacji WWW, to pewnie to co zobaczyłeś/aś na konsoli powinno być Tobie doskonale znane. Jeżeli jednak nie masz takich doświadczeń powiem co jest najistotniejsze. Dla naszych celów najważniejsza jest pierwsza linia jaką przysłała przeglądarka. Tutaj to było:
GET / HTTP/1.1
Jest to informacja czego przeglądarka chce od serwera, dzieli się ona na trzy składowe: rodzaj zapytania (GET
) jaki zasób chce otrzymać (/
czyli strona główna) oraz protokół jakiego używa przeglądarka (HTTP/1.1
– czyli HTTP w wersji 1.1).
Pozostałe linie to dodatkowe informacje, np o tym co jest w stanie zrozumieć przeglądarka. Serwery WWW zajmują się analizą tych danych przed przygotowaniem odpowiedzi. Nie jest to trywialne zadanie, ale na potrzeby tego przykładu nie potrzebujemy pisać serwera WWW realizującego wszystkie aspekty protokołu HTTP. Powstanie coś ułomnego, realizującego tylko mały podzbiór większej całości, ale nam to wystarczy do naszych testów.
By się przekonać jak zmieni się pierwsza linia wysyłana przez przeglądarkę, wpiszcie nowy adres w przeglądarkę. U nas ESP dostało adres 10.10.1.130, dopiszcie do adresu IP np 1/off czyli u nas to będzie http://10.10.1.130/1/off Pierwsza linia teraz wygląda tak
GET /1/off HTTP/1.1
Sterować przekaźnikami chcemy w ten sposób, by adres miał format http://IP_ESP/NR_PRZEKAŹNIKA/AKCJA Czyli, w naszej sieci, by włączyć przekaźnik nr 2 powinniśmy wysłać żądanie na adres http://10.10.1.130/2/on By go następnie wyłączyć trzeba użyć adresu http://10.10.1.130/2/off
Po odebraniu żądania z przeglądarki, musimy odczytać nr przekaźnika i akcję. Mając te dane możemy włączyć/wyłączyć przekaźnik lub wysłać stronę główną z kodem HTML jeśli nie rozpoznamy nr przekaźnika i akcji.
Odczyt żądania z przeglądarki
Piszemy więc taki ubogi URL parser (bo URL to właśnie nazwa na takie 'adresy’), bardzo go sobie upraszczając. Po pierwsze, nie będziemy się zastanawiać na rodzajem zapytania, po prostu zignorujemy czy to GET, POST czy inny. Dlatego najpierw znajdziemy pierwsze wystąpienie znaku ukośnika:
local e = string.find(request, "/") if e == nil then return nil,nil end local request_handle = string.sub(request, e + 1)
Pierwsza linia odnajduje pozycję na której występuje pierwszy znak ukośnika. Ostatnia w zmiennej request_handle
zapisuje całość żądania od znaku za ukośnikiem do końca. Jeżeli nie znaleziono znaku ukośnika, znaczy że jest to jakieś zupełnie niestandardowe zapytanie, dlatego przerywamy działanie parsera. Dlaczego dwa razy nil
, to się okaże za chwilę.
W następnym kroku obetniemy to co nam nie jest potrzebne:
e = string.find(request_handle, "HTTP") if e == nil then return nil,nil end request_handle = string.sub(request_handle, 0, (e-2))
Pierwsza linia odnajduje wystąpienie ciągu HTTP (pamiętacie, kończyło się linia napisem HTTP/1.1), jeżeli go nie ma przerywamy działanie. W ostatniej linii zapisujemy to co w żądaniu zostało przed „HTTP” (cofamy się o dwie pozycje, bo przed HTTP jest jeszcze spacja).
Założyliśmy sobie, że nasze żądania będą miały format NR/AKCJA, więc dzielimy to co zostało względem wystąpienia znaku ukośnika:
e = string.find(request_handle,"/") if e == nil then return nil,nil end local id = string.sub(request_handle,0,e-1) local action = string.sub(request_handle,e+1)
Jeśli znaku /
nie ma, to przerwij działanie. By lepiej poczuć jak działa ten kod, po wgraniu całości kodu usuń komentarze przed komendami print w module url_pareser.lua. Wtedy w konsoli ESPlorera zobaczysz jak stopniowo jest 'rozbierany’ URL na kawałki.
Mamy już id oraz akcję, więc przyjrzyjmy się jak wygląda obsługa zdarzenia „receive
” w naszym nowym serwerze:
id,parsed_request = Parser.parse(payload) if parsed_request == 'on' then gpio.write(id, gpio.LOW) end if parsed_request == 'off' then gpio.write(id, gpio.HIGH) end
Parser
to moduł zdefiniowany w url_parser.lua
. Funkcję parse
wyżej omówiliśmy. Powinna zwrócić dwie wartości: nr przekaźnika oraz akcję która ma wykonać. Jeśli nie potrafi odnaleźć danych (bo np to jest żądanie dostarczenia strony głównej, albo ma format inny od naszego założenia) to parser zwróci dwie wartości nil
.W LUA można w ten sposób zwracać więcej niż jedną wartość i zapisać je w dwóch zmiennych oddzielonych przecinkiem (lub więcej niż dwie):
var1, var2 = funkcja()
W razie powodzenia w zależności od akcji jest ustawiane odpowiednie GPIO. Teraz trzeba pamiętać, że stan niski steruje przekaźnikiem, więc gdy mamy komendę ’on
’ to ustawiamy stan niski na GPIO. Dla wartości akcja równej nil żaden z ifów ustawiających GPIO 'nie odpali’.
No to teraz na koniec wyślemy coś do przeglądarki:
if id == nil then main_page(conn) else http_ok(conn) end
Nie mamy id? Czyli to chyba żądanie strony głównej – także proszę bardzo. A jeśli nie zrozumieliśmy co od nas chcą, to też id będzie nil
i jeszcze raz – strona główna. Dla istniejącego id wyślemy tylko potwierdzenie że wszystko działa. Dobrze, także podłączamy GPIO4 i GPIO5 do przekaźników. Pozostaje przetestować.
Nim podłączymy przekaźniki i jakieś napięcie do nich – zwróć uwagę, że kładąc je na płytce stykowej ryzykujesz, że piny pod wysokim napięciem mogą zewrzeć się na płytce stykowej. Niby piny są za szerokie dla płytki stykowej, ale jak naciśniesz mocniej… Dlatego zabezpiecz spód modułu przekaźników np kilkoma warstwami taśmy izolacyjnej.
main_page
oraz http_ok
to funkcje wysyłające dane do przeglądarki. I tutaj ważna zmiana. Wcześniejsze tutoriale do nodeMCU pokazywały wysyłanie danych do przeglądarki przez wielokrotne wywołanie conn:send
. W nowszych SDK takie działanie jest już niepoprawne, ESP się zawiesi wysyłając tak dane. Teraz całość musi być wysłana za jednym wywołaniem conn:send
. Dlatego w kodzie używamy ..
na początku linii do łączenia kolejnych linii w całość:
'pierwsza linia ' ..'druga linia'
zostanie sklejone w
'pierwsza linia druga linia'.
main_page
wysyła kod HTML (z nagłówkami) prostej strona korzystając z Bootstrap by stworzyć przyciski do włączania i wyłączania każdego z przekaźników. Do każdego przycisku jest przypisana funkcja wywołująca odpowiedni URL w stylu 1/off
, itp.
Całość kodu do ściągnięcia: ESP8266 IoT – sterowanie przekaźnikiem Rozpakuj pliki, otwórz wszystkie w ESPlorerze, popraw dane dostępowe do WiFi i wgraj program na ESP. Teraz czekaj aż zaloguje się do sieci WiFi i przetestuj całość. Po wpisaniu adresu IP ESP w przeglądarkę powinieneś zobaczyć stronę z czterema przyciskami. Kliknięcie w nie powinno odpowiednio włączać i wyłączać przekaźniki. Przy włączeniu usłyszysz wyraźny klik oraz zapali się dioda DS1 lub DS2.
Same włączenia i wyłączenia możesz testować wpisując w przeglądarkę adres ESP (załóżmy 10.10.1.130) i włączyć pierwszy przekaźnik: http://10.10.1.130/1/on
Co można poprawić?
Po pierwsze – brakuje sprawdzania czy id ma wartość 1 lub 2 – np wpisanie w przeglądarkę adresu http:/10.10.1.130/2000/on spowoduje restart całego ESP z komunikatem:
PANIC: unprotected error in call to Lua API (server.lua:48: gpio 2000 does not exist)
Stało się tak, bo próbowaliśmy ustawić pin GPIO 2000 – nie ma takiego ;) a NodeMCU restartuje się w razie takich poważniejszych błędów.
Przenoszenie na płytkę
Jak szybko i prosto zrobić z tego układu płytkę z lutowanymi połączeniami – czytaj w części trzeciej.
U mnie jest problem z zamykaniem połączenia co 60 sekund. Zamyka i ponownie otwiera sam.
Program wygląda tak:
A=(„Welcome to NodeMCU”)
srv=net.createServer(net.TCP,28800)
srv:listen(8080,function(c)
c:on(„receive”,function(c,d)
if d == „TEST” then
print(A)
c:send(A)
c:close()
end
end)
end)
@Piotr
Chodzi o to, że nie respektuje tego 28800? Nie robiłem eksperymentów z timeoutem, może jest jakiś bug w NodeMCU. Można się upewnić czy aby na pewno to ESP zrywa połączenie. Zainstaluj Wireshark (https://www.wireshark.org/) i możesz sprawdzić która strona połączenia robi RST w TCP (sprawdzenie tego wymaga pewnej wiedzy n/t TCP i innych by znaleźć tą informację w dumpie z wireshark)
Znalazłem rozwiązanie Resetów, problem jest w składni i TimeSleep
Teoretycznie polecenie srv=net.createServer(net.TCP,28800), powinno utrzymać serwer aktywnie przez 8 godzin, niestety tak nie jest, wpisanie dowolnej wartości powoduje cykliczne Resety co 30 sekund. Aby tego uniknąć wystarczy dodać zapis i nie podawac TimeOut.
srv=net.createServer(net.TCP)
wifi.sleeptype(wifi.NONE_SLEEP)
i możemy dodać Klasę B sercera co da nam pełną moc przy nadawaniu ESP i obniży pobór w stanie nasłuchu.
wifi.setphymode(wifi.PHYMODE_B)
Można na różnych porach przeczytać o tym problemie jednak nikt nie podaje rozwiązania.
Dlatego dzielę się tym co udało się rozwiązać pozytywnie
Jest Bug w srv=net.createServer jeśli choć raz podasz TimeOut, nieważne o jakiej wartości, przedział jest 1-28880, ESP zapisuje trwale własną domyślną wartość = 32. i Zmienia z wifi.sleeptype(wifi.NONE_SLEEP) na wifi.MODEM_SLEEP i to cały problem. Reset ESP nie usuwa tej zmiany, dlatego dla bezpieczeństwa w init dobrze jest dostawić na początku wifi.sleeptype(wifi.NONE_SLEEP)
Po za tym drażni mnie trochę LUA przez swoja składnię, dlatego z niecierpliwością obserwuję rozwój ESP BASIC. Jest fajny bo ma wgrany Edytor bezpośrednio do ESP, co pozwala na pisanie programów z dowolnej przeglądarki, nie potrzebne kable, programatory i inne udziwnienia. Fajne bo jak zamontujesz gdzieś ESP to do przeprogramowania nie potrzeba go demontować, tylko edytujesz sobie np Tabletem program w ESP. Super rozwiązanie.
Koledzy, wykorzystałem pokazany tutaj cały przykład i mam taki problem.
Te przekaźniki sterują roletami w oknach i czasem jest tak że nie używam ich np. przez 3-4 dni, i niby wszystko jest OK. Jednak pewnego razu wklepuję sobie adres modułu i ni groma, ESP nie odpowiada.
Zauważyłem że dzieje się tak, gdy np. zrestartuje się router.
Pomęczyłem to troszkę i doszedłem że nawet wyłączenie samego WiFi powoduje problem z odzyskaniem połączenia przez moduł ESP.
Muszę wtedy zrobić restart ESP i wszystko wraca do normy.
Co może być przyczyną takiego zachowania, i jak sobie z tym poradzić.
Super opis.Wszystko się dzięki niemu udało i działa.Jedno pytanko tylko…jak uruchomić z modułem 4 przekaźników?