ESP8266: Spojrzenie na firmware

Pora na testowanie i zabawę z softem. Istnieje wiele różnych nieformalnych, otwartych projektów, dostarczających niezależne firmware dla modułów WiFi opartych na chipsecie ESP8266. Dlatego postanowiłem bliżej przyjrzeć się kilku wybranym. Pozwoli mi to co nieco zagłebić się w temat i spojrzeć, jak to wygląda od kuchni. A jak wiadomo takie rzeczy najlepiej poznaje się przy tworzeniu prostej aplikacji. A propos, to chyba oczywiste, że docelowo, jeśli będę coś większego projektował na ESP to będzie to w C++. Ale czasem dla prostych, małych projektów, czy typowego prototypowania, czasem lepiej nadają się języki skryptowe. I to akurat właśnie im udało mi się przyjrzeć bliżej – Lua, JavaScript i Python.

Testowy projekt

Testowym projektem jest nie żadne tam „Hello World” ze świata mikrokontrolerów typu miganie diodą LED, ale skoro jest to moduł WiFi to idealnie wydaje się sterować LED-em za pomocą przeglądarki internetowej.

Główna domyślna strona będzie prezentować status diody LED oraz odnośniki umożliwiające kontrolę jej działania (świecenia). Rodzaj akcji będzie określany za pomocą parametru action przyjmującego 2 wartości enable (włączenie) i disable (wyłączenie). A całość poleci jako zwykłe żądanie GET:

http://host/?action=enable
http://host/?action=disable

Diodę podłączyłem do jednego z wolnych pinów, wypadło na GPIO2. I tutaj mała uwaga, GPIO2 uczestniczy w procesie bootowania i na ten czas, przy stracie, musi znajdować się w stanie wysokim, aby umożliwić poprawne uruchomienie „systemu”. To z kolei determinuje sposób sterowania podłączonego elementu – stanem aktywnym będzie stan niski.

esp-gpio2-led-schematic

Maksymalny prąd pinu w ESP8266 wynosi tylko 12mA (datasheet: 5.1. Electrical Characteristics), dlatego niezbędny jest rezystor ograniczający prąd diody do bezpiecznej dla wyjścia wartości. Obecne diody LED przy takim natężeniu powinny bez problemu się rozjaśnić, choć typowy pobór prądu dla zwyklej diody wynosi około 20mA.

esp-gpio2-led-breadboard

Aplikacja oczywiście wykorzystuje wewnętrzny rdzeń modułu, bez potrzeby zaprzęgania zewnętrznego mikrokontrolera, który w przypadku korzystania z AT jest niezbędny. A jeśli ktoś jest ciekawy jak to wyglądałoby właśnie pod systemem AT, to jest taka możliwość, na stronach Botland-a znajduje się przykład sterowania LED-ami przez Wifi z użyciem AT i Arduino…

W przeglądzie poszczególnych rozwiązań nie będę skupiał się na samym języku programowania i detalach oraz idiomach nimi rządzącymi. Jedynie na stronie praktycznej, ściśle związanej z potrzebami niniejszej aplikacji sterownika.

NodeMCU (Lua)

NodeMCU to jeden z najpopularniejszych nieoficjalnych projektów oprogramowania dla ESP8266 umożlwiającego programowanie tego układu w języku Lua z bardzo bogatym zbiorem modułów/bibliotek. Najnowsze wydanie NodeMCU można pobrać z oficjalnego repo na githubie. Kiedyś były tutaj nawet prekompilowane wersje w postaci binarek, ale od wersji 1.4 zaprzestano tego robić. Na szczęście nie trzeba samemu wszystkiego kompilować i budować, można skorzystać z dedykowanego serwisu budowania, który pracuje w chmurze – NodeMCU custom builds. Pozwala on na łatwe konfigurowanie poprzez wybieranie potrzebnych pakietów i modułów, a całość budowania trwa bardzo szybko.

Ja swoją paczkę zbudowałem z mastera ze standardowymi modułami, jakie wydają mi się niezbędne do prostej zabawy i testów:

This was built against the master branch and includes the following modules: file, gpio, net, node, tmr, uart, wifi.

Jeśli ktoś chciałby skorzystać z tych moich binarek to wrzuciłem je tutaj. Budowane są dwie wersje, integer obsługującą tylko operacje na liczbach całkowitych i float, na zmiennoprzecinkowych. Jak można się domyślić, całkowitoliczbowa będzie wydajniejsza i mniej pamięciożerna, a nie planuje w tym projekcie operować na przecinkowych.

NodeMCU ma jakiś swój tool do flashwoania, ale standardowo skorzystałem ze znanych mi już narzędzi. Przy developingu przydatne może być za to inny całkiem niezły tool (nie tylko przy embedingu Lua ale i Pythona), czyli ESPlorer.

Po załadowaniu firmware do modulu i połączniu się z terminala naszym oczom ukaże się interaktywna konsola interpretera Lua. Można w niej od razu pracować, tworząc skrypty i kod w tym języku.

NodeMCU custom build by frightanic.com
        branch: master
        commit: 95e85c74e7310a595aa6bd57dbbc69ec3516e9c6
        SSL: false
        modules: file,gpio,net,node,tmr,uart,wifi
 build  built on: 2016-08-21 16:15
 powered by Lua 5.1.4 on SDK 1.5.4.1(39cb9a32)
lua: cannot open init.lua
> print('dupa123')
dupa123
>

Głęboko liczę, że w innych embeding-ach skryptowych będzie podobnie :)

Projekt posiada doskonałą dokumentację zawierającą szczegółowy opis wszystkich dostępnych modułów. NodeMCU bazuje na projekcie eLua, w pełni implementującego jezyk Lua w wersji 5.1 w embedded world. Opis języka dostępny jest na stronie lua.org (pl), a poćwiczyć można on-line w demie, jakby ktoś nie był zaznajomionym ze składnią i samym językiem.

W prezentowanym wyżej logu z terminala można zaobserwować wyświetlony komunikat, że system „nie może otworzyć pliku init.lua„. Jest to plik startowy, znajdujący się w głównym katalogu systemu plików (tak, tak, jest system plików rezydujący na pamięci flash – SPIFFS), który po stracie i inicjalizacji środowiska jest automatycznie uruchamiany przez NodeMCU. Teoretycznie tutaj mogłaby się zacząć aplikacja użytkownika, ale nie jest to zalecane. Gdyby wystąpiły jakieś błędy w kodzie lub logice, które doprowadziłoby do ciągłych resetów układu, problematyczne stałoby się opanowanie takiej sytuacji. Wtedy może szybciej udałoby się wyczyścić pamięć flash i na nowo załadować całe oprogramowanie.

Dlatego zamiast wrzucania głównego kodu do init.lua, lepiej pozostawić tam tylko inicjalizację i przejście do kodu aplikacji, na przykład tak jak poniżej:

local ssid  = 'MalTest'
local passw = 'dupa123'
 
print('Connecting to WiFi...')
wifi.setmode(wifi.STATION)
wifi.sta.config(ssid, passw)
 
tmr.alarm(0, 1000, tmr.ALARM_AUTO, function()
	if wifi.sta.getip() == nil then
		print('Waiting for IP address...')
	else
		tmr.stop(0)
		print('WiFi connection established')
		print('MAC address: ' .. wifi.sta.getmac())
		print(' IP address: ' .. wifi.sta.getip())
 
		if not file.exists('app.lua') then
			print('file app.lua does not exist')
			return
		end
 
		print('Starting app.lua in 3 seconds')
		print('To abort call abort() function')
		print('Waiting...')
 
		abort = function()
			tmr.stop(1)
			abort = nil
			print('Running app.lua aborted...')
		end
 
		tmr.alarm(1, 3000, tmr.ALARM_SINGLE, function()
			abort = nil
			print('Running app.lua')
			dofile('app.lua')
		end)
 
	end
end)

Przy deweloperce być może lepiej nawet jest pominąć bezpośrednie wywołanie kodu aplikacji i robić to ręcznie za pomocą funkcji dofile. Lub, jak proponuje dokumentacja (How do I avoid a PANIC loop in init.lua?) dodać małe opóźnienie przed uruchomieniem kodu aplikacji potrzebną na ewentualną reakcję dewelopera.

Konfigurowanie radia WiFi w module dostępne jest z poziomu modułu wifi (doc). Jako przykład użycia do połączenia się z lokalnym AP można przedstawić fragment kodu z mojej deweloperskiej implementacji skryptu init.lua załączonej wyżej. Trzeba pamiętać, że sam chipset zapisuje konfigurację we Flashu, więc nie jest potrzebne ręczne ustawianie i wymuszanie połączenia z siecią przy każdym stracie układu, ESP8266 zrobi to automatycznie.

Do obsługi pinów IO istnieje dedykowany moduł gpio (doc). Przy jego obsłudze należy mieć na uwadze fakt, że jego konwencja numeracji pinów odbiega od tej sprzętowej samego układu ESP8266, przystosowana jest do numeracji znajdującej się na płytce nodeMCU. Dlatego trzeba pamiętać o mapowaniu pinu na odpowiadający mu indeks (w dokumentacji stosowna tabela). Dla GPIO2 będzie to 4.

Sterowanie diodą LED może wyglądać tak:

LedPin = 4
pio.mode(LedPin, gpio.OUTPUT)	-- wyjscie
gpio.write(LedPin, gpio.LOW)	-- wlacz diode
gpio.write(LedPin, gpio.HIGH)	-- wylacz diode

Stworzenie serwera http dowolnego serwera jest równie banalne. Stosowny moduł net (doc) udostępniający obsługę sieciową zawiera niezbędne elementy i funkcje. Uruchomienie prostego serwera webowego sprowadza się w istocie do kilku linijek kodu:

srv = net.createServer(net.TCP)
srv:listen(80, function(c)
 
	c:on('receive', function(sck, request)
		sck:send(
			'HTTP/1.1 200 OK\r\n\r\n'..
			'Hello World'
		)
	end)
 
	c:on('sent', function(sck)
		sck:close()
	end)
 
end)

Tworzone jest gniazdko TCP na porcie 80 i rozpoczyna się nasłuchiwanie na przychodzące połączenia. Po pojawieniu się klienta wystarczy zarejestrować obsługę kilku zdarzeń i można przeprowadzać komunikację.

Składając całość do kupy i dodając trochę logiki związanej z aplikacją testową, powstała funkcja OnConnection:

function OnConnection(c)
 
	c:on('receive', function(sck, request)
 
		local req = GetRequestInfo(request)
 
		print('Incomming request: '..req.path);
 
		if (req.method ~= 'GET' or req.pathname ~= '/') then
			print('Invalid method or path')
			sck:send('HTTP/1.1 404 Not Found\r\n')
			return
		end
 
		local action = req.query['action']
 
		if (action == nil) then
			-- index page, do nothing
 
		elseif (action == 'enable') then
			print('Enable Led')
			gpio.write(LedPin, gpio.LOW)
 
		elseif (action == 'disable') then
			print('Disable Led')
			gpio.write(LedPin, gpio.HIGH)
		else
			-- invalid action, do nothing
			print('Invalid action: '..action)
		end
 
		local status = gpio.read(LedPin)
		if (status == 0) then
			status = 'enabled'
		else
			status = 'disabled'
		end
		print('Led status: '..status)
 
		sck:send(
			'HTTP/1.1 200 OK\r\nContent-Type: text/html\r\n\r\n'..
			'<!DOCTYPE html>'..
			'<html><head><title>ESP8266 Test App</title></head><body>'..
			'<h2>Hello from ESP!</h2>'..
			'<p>LED status: '..status..'<br/>'..
			'<a href="?action=enable">enable</a> | <a href="?action=disable">disable</a></p>'..
			'<p>NodeMCU '..ver..'</p>'..
			'</body></html>'
		)
 
	end)
 
	c:on('sent', function(sck)
		sck:close()
	end)
end

Jest to callback przekazywany do serwera przy nasłuchu:

srv = net.createServer(net.TCP)
srv:listen(80, OnConnection)

Kod jest bardzo prosty i chyba nie wymaga za bardzo tłumaczenia. Dodatkowa pomocnicza funkcja GetRequestInfo prasuje zapytanie od przeglądarki klienta i zawraca wypełnioną strukturę tabelkę danymi o metodzie, ścieżce i parametrach żądania http. Na podstawie parametru action zapytania odbywa się manipulacja stanem pinu LedPin, co powoduje odpowiednie sterowanie diodą LED. Na koniec przygotowywana jest zawartość strony wraz z nagłówkiem, która zostaje odesłana do przeglądarki klienta. A po wysłaniu zamykamy socket (połączenie).

esp-test-app-nodemcu

Tworzenie własnej aplikacji i zabawa z ESP w Lua jest bardzo prosta i intuicyjna. Tylko ten język taki trochę dziwny, dla kogoś kto przyzwyczajony jest do średników i klamerek ;)

Espruino (JavaScript)

Projekt Espruino czyli JavaScript on Board również doczekał się portu na mikrokontroler ESP8266 – EspruinoESP8266. Pisanie oprogramowanie dla emebdowanych urządzeń w JavaScripcie może być ciekawe. W przeszłości zdarzało mi się trochę tworzyć w tym języku, m.in pewien silnik, więc mimo różnych pułapek językowych, całkiem fajnie to wspominam.

Najnowsza paczkę z firmware można pobrać na podstronie Download. Paczka taka zawiera wersje binarne oprogramowania do wszystkich wspieranych płytek/mikrokontrolerów. Z readme.txt można dowiedzieć się że istnieją dwie wersje dla ESP8266. Jedna z wersji jest oznaczona jako „combined„, czyli jeden plik, podobnie jak to było w nodeMCU i przeznaczona jest dla urządzeń z pamięcią 512k. Druga wersja zawiera oddzielne pliki, tak jak to ma miejsce w standardowym firmware-u ESP8266.

Projekt posiada dedykowane narzędzie WebIDE ułatwiające pracę, tworzenie i zarzadzanie kodem uruchamianym oraz zapisanym w module. Ja z niego nie korzystałem, opierałem się na typowym wykorzystaniu terminala i funkcji save() zapisującej cały kontekst środowiska w pamięci Flash.

Po sflashowaniu modułu, po magistrali UART dostaniemy się do interaktywnej konsoli JS (yupi!). Podobnie jak w przypadku Lua można rozpocząć zabawę i skryptować moduł bezpośrednio z palca.

 _____                 _
|   __|___ ___ ___ _ _|_|___ ___
|   __|_ -| . |  _| | | |   | . |
|_____|___|  _|_| |___|_|_|_|___|
          |_| http://espruino.com
 1v86 Copyright 2016 G.Williams
 
Espruino is Open Source. Our work is supported
only by sales of official boards and donations:
http://espruino.com/Donate
Flash map 512KB:256/256, manuf 0xc8 chip 0x4013
 
>print('dupa123')
dupa123
=undefined
>

Projekt zawiera bogatą dokumentację opisującą wszystkie dostępne elementy i moduły. Z powodu takiego, że obsługa ESP jest jakby przy okazji, a projekt embeduje JS w świat mikrokontrolerów, głównie ARM ze stajni ST, to zawiera pokaźny zbiór różnych dodatkowych i zewnętrznych modułów. Do tego masa oficjalnych przykładów i tutoriali ułatwiających start w tym środowisku.

Espruino implementuje prawie cały podzbiór specyfikacji JavaScriptu, strona podaje, że jest w około 95% kompatybilny z tym językiem. Brakuje obsługi Unicodu i wyrażeń regularnych.

Chcąc mieć kod wykonujący się przy starcie system, należy dodać handlera do eventu init w globalnym obiekcie E (handlerów można rejestrować więcej), albo jawnie zdefiniować funkcję OnInit.

E.on('init', function() {
 
	print('Connecting to WiFi...')
	var wifi = require('Wifi');
	wifi.connect(ssid, { password: passw }, function (err) {
 
		if (err) {
			print('Connection error: ', err);
			return;
		}
 
		var info = wifi.getIP();
		print('WiFi connection established')
		print('MAC address: ', info.mac)
		print(' IP address: ', info.ip)
 
		print('Running app...')
		app();
	});
});

W przypadku Espruino nie będę kombinował z podobnym skryptem startowym jaki napisałem na potrzeby nodeMCU, ale należy mieć na uwadze, że tutaj również mogą wystąpić takie problemy, o których tam wspominałem. Można wtedy nadpisać miejsce przechowywania danych, fleszując przestrzeń 4KB spod adresu 0x7a000 plikiem blank.bin.

Implementację obsługi modułu WiFi zawarto w klasie Wifi (doc). Jej użycie jest bardzo proste, można skorzystać z przykładowego, zamieszczonego wyżej kodu mojej implementacji OnInit(). Gdzie w typowy sposób następuje konfiguracja i nawiązanie połączenia z Access Pointem.

Zabawa z portami IO jest bardzo prosta dzięki standardowej klasie Pin (doc). Mapowanie pinów sprzętowych z ESP8266 na numery pinów programowych jest odzwierciedlone jeden-do-jednego, to jest D0D15 odpowiada GPIO0GPIO15 z wyjątkiem braku GPIO6 i GPIO11 używanych przez układ do komunikacji z pamięcią Flash.

Sterowanie diodą LED podłączoną do pinu 2 jest bajeczne:

var led = new Pin(D2);
led.mode('output');	// wyjscie
led.reset();		// wlacz diode
led.set();			// wylacz diode

Klasa posiada kilka aliasowych funkcji na te same akcje, np. set(), reset() adekwatne funkcji write(x), a także dostępne są globalne funkcje znane Arduino-wcom (?) typu digitalWrite(), digitalRead()

Obsługa sieciowa w Espruino jest równie łatwa. Podstawowe funkcje internetowe są obsługiwane przy użyciu bibliotek http (doc), net (doc) i tls (doc). Serwer możemy stworzyć korzystając z możliwości klasy net, ale dla webservera lepiej i łatwiej wykorzystać dedykowaną klasę http dla tego protokołu.

Podobnie jak w NodeMCU tutaj również stworzenie prostego webservera ogranicza się do kilku prostych linijek kodu:

var http = require("http");
http.createServer(function (req, res) {
	res.writeHead(200);
	res.end('Hello World');
}).listen(80);

Soket na porcie 80 plus callback do połączeń i komunikacja gotowa.

Implementację logiki testowej aplikacji w głównej funkcji OnPageRequest, odpalanej przy żądaniach klienta, można zapisać w poniższej postaci.

function onPageRequest(req, res) {
 
	var info = url.parse(req.url, true);
	// query is null if not provided any params
	info.query = info.query || {};
 
	print('Incomming request: ' + info.path);
 
	if (info.method != 'GET' || info.pathname != '/') {
		print('Invalid method or path');
		res.writeHead(404, { 'Content-Type': 'text/plain' });
		res.end();
		return;
	}
 
	var action = info.query.action;
 
	if (action == undefined) {
		// index page, do nothing
 
	} else if (action == 'enable') {
		print('Enable Led')
		led.write(LOW)
 
	} else if (action == 'disable') {
		print('Disable Led')
		led.write(HIGH)
 
	} else {
		// invalid action, do nothing
		print('Invalid action: ' + action)
	}
 
	var status = !led.read() ? 'enabled' : 'disabled';
	print('Led status: ' + status);
 
	res.writeHead(200, { 'Content-Type': 'text/html' });
	res.write('<!DOCTYPE html>');
	res.write('<html><head><title>ESP8266 Test App</title></head><body>');
	res.write('<h2>Hello from ESP!</h2>');
	res.write('<p>LED status: ' + status + '<br/>');
	res.write('<a href="?action=enable">enable</a> | <a href="?action=disable">disable</a></p>');
	res.write('<p>Espruino ' + process.version + '</p>');
	res.end('</body></html>');
}

W Espruino biblioteki i klasy są nieco bardziej rozbudowane, więc nie trzeba implementować ręcznego prasowania nagłówków i zapytań HTTP. Przekazywane są one już w dedykowanych obiektach do funkcji. A dodatkowo za pomocą url.parse można w łatwy sposób otrzymać niezbędne dane do routingu akcji.

Uruchomiona aplikacja działa wyśmienicie, co przedstawia poniższy zrzut ekranu.

esp-test-app-espruino

Tworzenie kodu w JS dla mikrokontrolerów jest bardzo fajne i przyjemne, a napisanie nie tylko prostego oprogramowania nie nastręcza żadnych poważnych problemów. Może kiedyś jeszcze wrócę do Espruino na innej platformie, może jakimś małym ARM-ie i zobaczymy co z tego wyjdzie.

MicroPython (Python)

Nieszczególnie przepadam za Pythonem, zdarza mi się czasami coś w nim napisać, ale zdecydowanie to nie mój świat. Jest to dosyć popularny skryptowy jezyk, który także zawitał w świat mikrokontrolerów i elektroniki, szczególnie za sprawą projektu MicroPython. Projekt implementuje specyfikację Pythona 3 dla własnych dedykowanych płytek opartych na mikrokontrolerach ARM ze stajni ST, ale doczekał się szybko także portu na inne platformy, w tym na chipset ESP8266.

Firmware dla układu ESP8266 można pobrać ze strony projektu. Obecnie najnowsze wersje MicroPythona na ESP wymagają modułów zawierających 8MBit pamięci Flash. Na szczęście dostępna ostatnia starsza wersja 1.8.2 zajmuje poniżej 512KB i udało mi się ją uruchomić na moim małym module ESP-01.

Po połączeniu z układem przywitała mnie standardowo interaktywna powłoka pythona:

l-br|--could not open file 'boot.py' for reading
could not open file 'main.py' for reading
 
#3 ets_task(4010035c, 3, 3fff6360, 4)
MicroPython v1.8.2 on 2016-08-04; ESP module with ESP8266
Type "help()" for more information.
>>> print('dupa123');
dupa123
>>>

Obszerna dokumentacja projektu dostępna jest on-line, jest też dedykowana cześć wydzielona dla portu ESP8266. Sam projekt zawiera podstawowe biblioteki umożliwiające łatwą i przyjemną pracę.

W logach z terminala można zauważyć próbę otwarcia dwóch plików boot.py i main.py. Pliki te są związane ze startem systemu i podobnie jak w przedstawionych wcześniej wersjach softu, można za ich pomocą wykonać pewne czynności przy stracie. W boot.py, można ustawić jakieś początkowe inicjalizacje, a plik main.py może posłużyć jako entry-point dla naszej aplikacji.

Mogłoby się wydawać, że wszytko będzie działało poprawnie, skoro udało mi się sflashować moduł z 512KB pamięci Flash. Tak niestety nie jest, o ile większość elementów działa dobrze, to jest problem z systemem plików i jakikolwiek zapisem/odczytem z pamięci Flash. Firmware zajmuje prawie cały obszar pamięci, a file-system alokuje się za nim na wolnej przestrzeni. U mnie niestety za mało zostało wolnej pamięci, aby file-system się poprawnie zainicjalizował.

Dlatego próba wylistowania roota zakończyła się błędem:

>>> import os;os.listdir('')
Traceback (most recent call last):
  File "<stdin>", line 1, in <module>
OSError: [Errno 19] ENODEV

Podobnie przy próbie jakiejkolwiek operacji na plikach.

Mógłbym to przeżyć, ale problem dotyczy także innych elementów narzuconych przez specyfikację działania układu ESP8266. Konfiguracja WiFi w tym układzie zapisywana jest w pamięci Flash, aby po stracie układ mógł się bez problemu połączyć ze światem. Skoro mam problem z flashem, to próba skonfigurowania sieci również zakończy się klęską:

Kernel panic!
Opps! Nie zapisałem sobie logu z terminala...

W obecnej chwili tego problemu nie przeskoczę, więc nie potestuję i nie napiszę skryptu do aplikacji testowej, ale trochę mogę się pobawić. Co prawda mógłbym poszukiwać dużo starszych wersji, jakieś odległe pewnie funkcjonowałyby poprawnie z mniejszą pamięcią, ale byłoby to obarczone nie pełną implementacją w porównaniu do obecnie dostępnej.

Kod mojej funkcji służącej do konfiguracji interfejsu sieciowego i połącznia z moim routerem bezprzewodowo wykorzystuje standardową klasę WLAN (doc) z biblioteki network (doc). Całość można zawrzeć w kilku prostych linijkach pythonowego kodu:

import network
wlan = network.WLAN(network.STA_IF)
wlan.active(True)
if not wlan.isconnected():
	print('Connecting to WiFi...')
	wlan.connect(ssid, passw)
	while not wlan.isconnected():
		pass
print('WiFi connection established')
print('MAC address: ', ubinascii.hexlify(wlan.config('mac'), ':').decode())
print(' IP address: ', wlan.ifconfig()[0])

Obsługa interfejsu I/O jest dostarczana przez klasę Pin (doc) z biblioteki machine (doc). Jest ona intuicyjna i zbliżona funkcjonalnie do innych implementacji. Przykładowe operacje na diodzie LED mogą być zapisane w postaci poniższego kodu:

import machine
led = machine.Pin(2, machine.Pin.OUT)	# pin 2 jako wyjscie
led.low()	# wlacz diode
lde.high()	# wylacz diode

Mapowanie pomiędzy logicznymi pinami w MicroPythonie a fizycznymi jest proste i odpowiada poszczególnym sprzętowym GPIO. Ale nie wszystkie piny są dostępne dla programisty, tylko 05 i 1216.

Prosty serwer webowy można napisać w tradycyjny sposób przy wykorzystaniu standardowych elementów sieciowych, czyli gniazdek, dostępnych w bibliotece socket (doc).

import socket
srv = socket.socket()
srv.bind(socket.getaddrinfo('0.0.0.0', 80)[0][-1])
srv.listen(5)
 
while True:
	sck, addr = srv.accept()
 
	while True:
		line = sck.readline()
		if line == b'' or line == b'\r\n':
			break
 
sck.write(
	'HTTP/1.1 200 OK\r\n\r\n'+
	'Hello World'
)
sck.close()

Jest to tradycyjna metoda tworzenia serwerów znana z większych maszyn, choćby PC. Nie jest to typowy data/event-driven jak to miało miejsce przy NodeMCU i Espruino.

Mała aktualizacja. Przy jednej z prób zapomniałem wyczyścić flasha przed załadowaniem MicroPythona. W poprzednim firmware miałem skonfigurowaną i zapisaną sieć w pamięci, dane te przetrwały flashowanie nowym softem. Sam się zdziwiłem, że po reboocie automatycznie system połączył się z AP.

l--ll---l--l--could not open file 'boot.py' for reading
could not open file 'main.py' for reading
 
#4 ets_task(4010035c, 3, 3fff6360, 4)
MicroPython v1.8.2 on 2016-08-04; ESP module with ESP8266
Type "help()" for more information.
>>> scandone
state: 0 -> 2 (b0)
state: 2 -> 3 (0)
state: 3 -> 5 (10)
add 0
aid 2
cnt
 
connected with MalTest, channel 11
dhcp client start...
ip:192.168.1.21,mask:255.255.255.0,gw:192.168.1.1
pm open,type:2 0

Wygląda na to, że sieć działa poprawnie, interfejs sieciowy jest skonfigurowany:

>>> import network;
>>> wlan = network.WLAN(network.STA_IF);
>>> wlan.ifconfig()
('192.168.1.21', '255.255.255.0', '192.168.1.1', '192.168.1.1')

Mógłbym w tym miejscu porobić jakieś proste eksperymenty z testową aplikacją, ale to może być trochę męczące.

Bazując na dwóch poprzednich implementacjach, napisanie takiej prostej logiki do sterowania LED-em nie powinno nastręczyć problemów nawet poczatkującym pythonowcom ;)

Po różnych testach i zabawach, przyszła pora na kolejny erase i write do Flasha nowego softu i pomimo występującego problemu z filesystemem, z WiFi już było inaczej. Nic nie rzucało wyjątkiem i nie reebootowało się, za to zaczęło działać normalnie…

Dlatego pokusiłem się o próbę zaimplementowania prostej logiki do testowej aplikacji i wyszło mi coś takiego w głównej pętli serwera:

def main():
 
	connect()
 
	print('Starting server...')
	srv = socket.socket()
	srv.bind(socket.getaddrinfo('0.0.0.0', 80)[0][-1])
	srv.listen(5)
 
	while True:
		sck = srv.accept()[0]
		req = getRequestInfo(sck.readline())
 
		# skip the rest part of the header data
		while True:
			line = sck.readline()
			if line == b'' or line == b'\r\n':
				break
 
		print('Incomming request: ' + req['path']);
 
		if req['method'] != 'GET' or req['pathname'] != '/':
			print('Invalid method or path');
			sck.write('HTTP/1.1 404 Not Found\r\n')
			sck.close()
			continue
 
		if not 'action' in req['query']:
			# index page, do nothing
			pass
 
		elif req['query']['action'] == 'enable':
			print('Enable Led')
			led.value(0)
 
		elif req['query']['action'] == 'disable':
			print('Disable Led')
			led.value(1)
 
		else:
			# invalid action, do nothing
			print('Invalid action: ' + req['query']['action'])
 
		status = 'enabled' if not led.value() else 'disabled'
		print('Led status: ' + status);
 
		sck.write(PAGE % status)
		sck.close()

Podobnie jak to było w NodeMCU, w MicroPythonie nie ma żadnej standardowej biblioteki czy klasy do obsługi żądań http i ich prasowania, więc trzeba było coś szybko samemu napisać w postaci prościutkiej funkcji getRequestInfo. A dalej juz prosto, standardowo…

Niestety wersja języka dostępna na platformie jest nieco okrojona i niepełna, dlatego trochę się namęczyłem, aby kod testowany pod zwykłym interpreterem zaczął działać poprawnie pod MicroPythonem. Ale finalnie się udało, czego dowodem poniższy screenshot.

esp-test-app-mpython

Mimo niekorzystania za bardzo z tego języka na co dzień, programowanie mikrokontrolerów być może stanie się o wiele popularniejsze także wśród wyznawców Pythona, podobnie jak to się stało z Arduino. A gdy chociaż ktoś z nich kiedyś bardziej zainteresuje się elektroniką, taką prawdziwą elektroniką, to będzie to zdecydowanie na plus.

I to byłoby na tyle… Trochę czasu zajęła mi ta wycieczka po różnych skryptowych (tak się złożyło) firmware-ach dla modułów sieciowych z układem ESP8266. Pomimo małych nieprzewidzianych problemów, cel udało mi się osiągnąć, bliżej zapoznałem się z dostępnymi rozwiązaniami. A to nie wyczerpuje tematu, bo istnieje jeszcze wiele innych możliwości jeśli chodzi o programowanie i hackowanie kostki ESP8266.

Dla zwolenników Arduino zapewne jedyną słuszną drogą będzie implementacja ESP dostępna na ich IDE w ichniejszym API, czyli Arduino core for ESP8266. Bascom-owcy i ulubieńcy Basica polubią ESP8266 BASIC. Pisanie w Basicu pod mikrokontrolerami może przywoływać wspomnienia z młodości i zabawę z 8-bitowcami z lat ’80. Czemu nie?!

Dla mnie pozostaje C/C++ i gołe SDK. Brzmi strasznie? Wcale nie! Prosto i przyjemnie, choć czasem trzeba więcej kodu napisać, ale za to i większe, a wręcz prawie nieograniczone możliwości. To dobry temat na kolejny odcinek ;)

Obiektywnie porównując działanie i osiągnięte wyniki, to muszę przyznać, że najbardziej stabilnie i szybkościowo reagował kod oparty na NodeMCU. Z JavaScriptem i Pythonem bywało różnie. Albo długie oczekiwanie na odpowiedź i reakcję, zawieszania się na Espurino, albo ciągłe zrywanie połączeń z AP w przypadku MicroPythona. Chyba najbliższy prosty projekt Z ESP8266 oprzę na kodzie pisanym w Lua, choć nie wszyscy podzielają moją ocenę: 4 reasons I abandoned NodeMCU/Lua for ESP8266.

Szkoda, że natrafiłem na problem z małą pamięcią Flash. Może pozostaje mi tylko aktualizacja małego układu scalonego firmy Winbond na większą kostkę z 8Mb lub 16Mb? Muszę nad tym pomyśleć, bo oficjalne SDK i firmware również w typowej konfiguracji wymaga już minimum 8Mb pamięci.

Pełny kod dla wszystkich implementacji wkrótce pojawi się na githubie jest dostępny na githubie w repozytorium ESP8266 Test App. Zbieg okoliczności i przypadek pozwolił mi, jako tako, uruchomić MicroPythona na 512KB module z siecią, więc z jakimiś małymi hackami uda mi się stworzyć wersję pythonową. Kilka zdań o tym dodałem wyżej. A kod do repo dorzucę wkrótce.. Dorzuciłem kilka zdań o tym wyżej, wraz z implementacją na githubie.

Jedno przemyślenie nt. „ESP8266: Spojrzenie na firmware”

  1. Swietny artykul!
    Spojrz tez na projekt easyesp – wg. mnie idealny dla osob ktore nie programuja a chca sie pobawic w IoT. Gotowy flash ktory poza sterowaniem via url daje znaaacznie wiecej mozliwosci out of the box

Dodaj komentarz

Twój adres email nie zostanie opublikowany. Pola, których wypełnienie jest wymagane, są oznaczone symbolem *