Kurs ESP8266 & MicroPython #6: Prosty serwer WWW i termometr DS18B20

Pora wkroczyć w świat Internetu Rzeczy. W tej lekcji zajmiemy się odczytem temperatury z termometru DS18B20 i wyświetleniem jej na stronie WWW, dostępnej z poziomu każdego urządzenia w Twojej sieci domowej.

DS18B20 jest czujnikiem temperatury wykorzystującym interfejs One-Wire. Oznacza to, że do komunikacji wykorzystywana jest tylko jedna linia, ale odbywa się to kosztem prędkości transmisji. Ponadto czujnik może być zasilany pasożytniczo z linii danych, co ogranicza ilość przewodów do dwóch przy założeniu, że linię danych podciągniemy do zasilania przy samym module. Schemat połączenia czujnika przedstawiony jest na obrazku poniżej.

ESP8266 DS18B20

Dzisiejszy program bardzo łatwo podzielić na dwie części – pierwsza będzie dotyczyć obsługi termometru, zaś druga zapytań od przeglądarki.

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
import machine, onewire, ds18x20, socket
dat = machine.Pin(5)
ds = ds18x20.DS18X20(onewire.OneWire(dat))
tim = machine.Timer(-1)
tim2 = machine.Timer(-1)
roms = ds.scan()
temp = 0
 
def getTemp(p):
    global temp
    temp = round(ds.read_temp(roms[0]))
 
def startConv(p = 0):
    ds.convert_temp()
    tim2.init(period=750, mode=machine.Timer.ONE_SHOT, callback=getTemp)
 
startConv()
tim.init(period=60000, mode=machine.Timer.PERIODIC, callback=startConv)

Ta część nie wymaga chyba zbyt dokładnego wyjaśniania. Warto zwrócić uwagę na to, że metoda scan() zwraca listę adresów czujników, ponieważ do tej samej linii danych możesz podłączyć więcej niż jeden DS18B20. Po wysłaniu sygnału rozpoczęcia konwersji należy poczekać 750 milisekund, zanim będzie można odczytać temperaturę z czujnika. Warto też za pierwszym razem ręcznie wywołać odczyt temperatury, żeby nie czekać 60 sekund po włączeniu modułu. Wywoływanie konwersji przez timer ma tę zaletę, że aktualna temperatura będzie zawsze dostępna w zmiennej temp i po otrzymaniu zapytania od przeglądarki będzie mogła być od razu wysłana, bez oczekiwania 750 ms na zakończenie pomiaru.

20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
addr = socket.getaddrinfo('0.0.0.0', 80)[0][-1]
s = socket.socket()
s.bind(addr)
s.listen(1)
 
f = open('page.html')
html = f.read()
f.close()
 
while True:
    conn, address = s.accept()
    print('Connection from: ', addr)
    while True:
        line = conn.readline()
        print(line)
        if not line or line == b'\r\n':
            break
    conn.send(html[:994])
    conn.send(html[994:].format(temp)) 
    conn.close()

Najpierw określamy adres IP i port na którym chcemy nasłuchiwać. Jest tylko jeden haczyk – ESP już dostało swój adres IP i nie można go w tym momencie zmienić, dlatego najbezpieczniej wpisać 0.0.0.0. Następnie tworzymy gniazdo s, przypisujemy do niego dostępny adres IP i rozpoczynamy nasłuchiwanie. Liczba 1 jako argument metody listen oznacza, że kolejkowane będzie maksymalnie jedno połączenie. Wróćmy jeszcze na chwilę do adresu 0.0.0.0 – oznacza on, że zostaną przypisane wszystkie dostępne adresy IP, czyli w przypadku ESP8266 tylko jeden. Gdybyś wpisał tam adres swojego modułu, serwer też działałby prawidłowo, ale tylko do czasu, gdy router przydzieliłby modułowi inny adres IP. Nawet jeśli wykorzystujesz adres 0.0.0.0, polecam w routerze powiązać adres MAC modułu z konkretnym adresem IP.

Wyświetlana będzie strona, którą możesz pobrać klikając na ten odnośnik: page.html. Z jakiegoś powodu (którego jeszcze nie odkryłem) MicroPython nie może wysłać podczas jednego połączenia więcej niż 1072 znaki, dlatego plik nie zawiera tabulacji i komentarzy. Program MicroPython File Uploader umożliwia wysłanie pliku HTML do modułu.

Następnie akceptujemy połączenie, wyświetlamy adres IP klienta oraz treść zapytania, po czym wysyłamy plik HTML uzupełniony o temperaturę. Obiekt conn służy zarówno do odebrania, jak i wysłania danych. Metoda format zastępuje znaczniki {} wartością liczbową lub tekstem. Ponieważ nawiasy klamrowe znajdują się także w kodzie HTML, plik należy podzielić tak, aby uniknąć konfliktów. Innym rozwiązaniem jest “uciekanie” każdego nawiasu klamrowego, ale ten sposób jest nieco bardziej pracochłonny.

Po wgraniu obu plików (main.py i page.html) i wpisaniu w przeglądarce adresu Twojego modułu, np. 192.168.0.106, zobaczysz taki przyjemny dla oka termometr:

ESP8266 Thermometer

Jeśli posiadasz publiczny adres IP, możesz w routerze przekierować port 80 na adres Twojego modułu. Dzięki temu będziesz miał dostęp do temperatury z każdego miejsca na świecie. Dla pewności wyłączyłbym jednak WebREPL w pliku boot.py, żeby nikt nieupoważniony nie próbował bawić się Twoim modułem.

Kod HTML pochodzi ze strony http://jsfiddle.net/mirceageorgescu/gBW3Y/.

4 myśli na temat “Kurs ESP8266 & MicroPython #6: Prosty serwer WWW i termometr DS18B20

  1. “Ta część nie wymaga chyba zbyt dokładnego wyjaśniania.”

    Nie chciałbym, abyś sądził, że każdy, kto czyta Twoje artykuły, znał się tak dobrze na programowaniu, jak Ty. Raczej traktuj odbiorców jak dobry belfer najbardziej tępych uczniów. 🙂

    Bardzo się cieszę, że piszesz o programowaniu w Pythonie, bo dla mnie syntax jest bardziej zjadliwy, niż w przypadku takiego C/C++. To pewnie dlatego, że dawno, dawno temu grzebałem w Pascalu i Delphi.

    Ciężko jest znaleźć Twoją stronę – trafiłem do Ciebie, dopiero po linku w Twojej stopce, z forum Majsterkowo. Popracuj nad tematami i słowami kluczowymi tekstów, bo masz coraz większą konkurencję w wyszukiwarkach internetowych.

    P.S. Bardzo fajna captcha. 🙂

    1. Staram się zawsze pisać kod w taki sposób, żeby dało się go zrozumieć, ale poprawię ten akapit i wyjaśnię kod 🙂 Niestety mimo captchy i tak część spamu przechodzi i muszę moderować komentarze ręcznie. Dziękuję za komentarz i pozdrawiam 🙂

  2. Mam pytanie, ale na 99% jest one retoryczne. Czy wartość temperatury może być zmiennoprzecinkowa?
    Sądzę, że raczej tego nie da się zrobić. Po szybkiej analizie metody read_temp() z klasy DS18X20, wynika że operacje są wykonywane na liczbach całkowitych (hex) a potem dzielone przez 16(dec).

    def read_temp(self, rom):
    buf = self.read_scratch(rom)
    if rom[0] == 0x10:
    if buf[1]:
    t = buf[0] >> 1 | 0x80
    t = -((~t + 1) & 0xff)
    else:
    t = buf[0] >> 1
    return t – 0.25 + (buf[7] – buf[6]) / buf[7]
    else:
    t = buf[1] << 8 | buf[0]
    if t & 0x8000: # sign bit set
    t = -((t ^ 0xffff) + 1)
    return t / 16

    Popraw mnie jeśli się mylę 🙂
    Pozdrawiam

    1. Nie mam teraz jak sprawdzić, ale chyba może być. Wynikiem wyrażenia t/16 może być liczba z przecinkiem, bo odpowiednikiem typowego dzielenia intów znanego z C w Pythonie byłoby t//16.

Dodaj komentarz

Twój adres e-mail nie zostanie opublikowany. Wymagane pola są oznaczone *

Rozwiąż równanie: *

This site uses Akismet to reduce spam. Learn how your comment data is processed.