Ktoś mi ostatnio zarzucił zbytnie przywiązanie do technologii przewodowych. Postanowiłem więc wyjść naprzeciw oczekiwaniom i przygotować bezprzewodową wersję układu sterującego urządzeniami elektrycznymi na 433MHz. Choć tak naprawdę skłoniła mnie do tego konkretna potrzeba. Niezależnie od motywacji powstała nowa wersja bramki IP-433.92MHz, tym razem działająca przez WiFi. Dzięki temu można ją powiesić w miejscu, gdzie jest zasięg Wifi, a jednocześnie odbiorniki 433MHz (gniazdka, urządzenia w puszkach, radiowe włączniki, itp.). Do urządzenia dorzuciłem jeden mały „gratis”, ale o tym za chwilę.
Możliwości jest wiele
Na wstępie warto podsumować zrealizowane już sposoby sterowania urządzeniami elektrycznymi, ponieważ parę pomysłów już wdrożyliśmy z życie. Zaczęło się od sterowania modułami przekaźnikowymi przez Arduino. Dzięki temu możemy sterować dowolnymi urządzeniami na niskie napięcie, bądź 230V. Wykorzystuję to szeroko w domu do sterowania oświetleniem, ogrzewaniem, bramą garażową, cyrkulacją ciepłej wody, nawadnianiem ogrodu i pewnie w jakichś innych miejscach. Te systemy sterowane są przez Ethernet, czyli sieć przewodową.
Kolejnym pomysłem, by nie przeciągać wszędzie przewodów było użycie popularnego i taniego systemu na 433MHz. Zbudowałem centralny sterownik, podłączany przez Ethernet, który jest w stanie nadawać i odbierać sygnały na popularnej częstotliwości, współpracujący z urządzeniami różnych producentów – u mnie Conrad, Kemot i Clarus. Sterownik jest więc bramką IP-433MHz.
Ostatnim podejściem było przygotowanie sterownika WiFi do urządzeń elektrycznych z użyciem modułów przekaźnikowych, których używałem wcześniej z Arduino. To rozwiązanie powstało na potrzeby sterowania ogrzewaniem w jednym z domów zamieszkiwanych okazjonalnie.
Koncepcja i komponenty
Dzisiaj natomiast, przygotujemy połączenie dwóch ostatnich rozwiązań – bramkę IP-433MHz sterowaną przez WiFi. Do budowy użyty zostanie ponownie NodeMCU w wersji LoLin (wersja ma znaczenie tylko dla osób chcących użyć gotowej obudowy z tego wpisu) oraz tanie moduły do nadawania i odbierania danych na 433.92MHz. NodeMCU wybrałem ponownie, bo dla prostych urządzeń wszystko jest dostępne „na pokładzie”. Nie trzeba przygotowywać płytki drukowanej, bo potrzebujemy tylko kilka połączeń między modułami, które spokojnie można zrobić przewodami. Całość będzie stabilna, bo przygotujemy dedykowaną obudowę. NodeMCU daje nam stabilizację napięcia, możliwość zasilenia przez micro USB, a co za tym idzie dwa napięcia, które mogą być potrzebne – 5V i 3,3V. Jest to o tyle istotne, bo daje nam możliwość użycia właściwie dowolnego z popularnych układów do 433MHz – zarówno na 5V, jak i na 3,3V.
Ponieważ rozmiar urządzenia nie był priorytetem, a NodeMCU v3 daje sporo wolnych wejść, postanowiłem zostawić trochę miejsca w obudowie na dodatkowe moduły. Ponieważ w ostatnim czasie zainteresowałem się pomiarami ciśnienia atmosferycznego, użyty zostanie czujnik BMP180 firmy Bosch. W pierwszej chwili chciałem użyć starszego BMP085, jednak mój egzemplarz okazał się wadliwy – dobrze reagował na zmiany ciśnienia, ale źle wskazywał ciśnienie rzeczywiste. Obecnie bardziej naturalnym wyborem byłby nowszy BMP280, ale BMP180 znalazłem w swoich zasobach. Tak naprawdę wersja nie ma znaczenia, bo czujniki dysponują dokładnością istotną tylko przy pomiarze zmian wysokości. Dla obserwacji meteorologicznych nie ma to znaczenia.
Wróćmy jednak na razie do głównej funkcji – sterowania urządzeniami elektrycznymi. Użyjemy do tego dwóch modułów. Wybór jest spory, natomiast zwykle różnią się akceptowanymi napięciami i zasięgiem/czułością. Ja jako odbiornik używam zwykle WL101-341, zasilanego napięciem 3,3V, który po dolutowaniu anteny (choćby przewodu o długości 17,3cm) ma niezłą czułość, w przeciwieństwie do najpopularniejszego modułu MX-05V. Jako nadajnik do tej pory najczęściej używałem FS1000A (MX-FS-03V) zasilany 5V (w tym z NodeMCU), jednak wyraźnie lepsze efekty daje WL102-341 przy zasilaniu 3,3V (także z anteną).
Mamy więc komplet NodeMCU v3, dwa moduły radiowe i jeden czujnik ciśnienia. Nadajnik potrzebuje napięcia 3,3V, masy i wyjścia do przekazywania sygnałów. Odbiornik podobnie, tylko zamiast wyjścia potrzebne jest wejście. Natomiast BMP180 poza zasilaniem i masą wymaga komunikacji przez I2C, więc dwóch dodatkowych przewodów. Tak się składa, że NodeMCU ma wyprowadzone napięcie 3,3V na trzech pinach, a masę nawet na pięciu. Wejścia i wyjścia także nie są problemem. Potrzeba jedynie krótkich przewodów na złącza gold pin (dupont).
Połączenie jest tak proste, że trudno mówić o projekcie. Po prostu łączymy przewodami i zamykamy w obudowie, żeby było stabilne.
Obudowa
Obudowę tradycyjnie projektujemy w OpenSCAD, wzorując się na wcześniejszym projekcie z NodeMCU i przekaźnikami. Miejsca nie oszczędzamy, zostawiając przegrody na moduły. W RepetierHost, z użyciem slicera przygotowujemy plik do wydruku i umieszczamy na karcie pamięci, aby drukarka 3d mogła drukować obudowę bez połączenia z komputerem. Chętni mogą pobrać gotowe pliki obudowy.
Wydruk w jednej wersji wykonamy z mojego ulubionego, przezroczystego filmentu PETG od polskiej firmy DevilDesign. Dzięki temu widać światło diody i wiadomo, że urządzenie działa. Oczywiście to także może być wada, dlatego wydrukowałem też wersję z filamentu PLA tego samego producenta.
Komunikacja
Podobnie jak w ostatnim urządzeniu użyjemy pakietów UDP zwierających 3 liczby oddzielone kropkami. Są to numer protokołu radiowego (zwykle 1 lub 2, zgodnie z biblioteką rcswitch), długość komunikatu w bitach i sam komunikat. Urządzenie ma przyjmować pakiety w tym formacie i wysyłać je radiowo, a w przypadku odebrania sygnału radiowego odsyłać kod do urządzenia nadrzędnego (u mnie Raspberry Pi z nodejs, o czym już pisałem). Ma także potwierdzać otrzymanie pakietu UDP. Nie różni się to niczym w stosunku do opisywanej już bramki przewodowej.
Dodatkowo będziemy obsługiwać czujnik ciśnienia i co zadany czas (pół minuty) wysyłać pakiet UDP z informacją o aktualnym ciśnieniu w Pascalach.
Ciśnienie atmosferyczne
Zanim przejdziemy do części programistycznej, warto poświęcić chwilę na rozważania o ciśnieniu atmosferycznym. Cóż to jest jest to ciśnienie? To siła z jaką powietrze naciska na powierzchnię Ziemi (ściśle stosunek tej siły do powierzchni). Zależy więc od wysokości (bo im wyżej jesteśmy, tym niższy słup jest nad nami). Pierwszym zaskoczeniem, gdy zacząłem się przyglądać ciśnieniu było dla mnie to, jak szybko ciśnienie zmienia się z wysokością. Na piętrze w domu było wyraźnie różne niż na parterze.
Czujniki ciśnienia używane są w lotnictwie do określenia wysokości względnej nad poziomem Ziemi (i morza oczywiście). Z tego powodu lotnisko zawsze podaje pilotom dokładne informacje o aktualnym ciśnieniu na poziomie pasa startowego. Czujniki BMP, są stosowane w dronach, także do określania wysokości. Rozdzielczość pomiaru w nowych czujnikach Boscha dochodzi powinna (przynajmniej w teorii) wykrywać zmiany wysokości na poziomie kilkunastu centymetrów.
I tu dochodzimy do istotnego problemu w pomiarze ciśnienia – skoro 8 metrów wysokości (2 wysokie piętra) powoduje, że ciśnienie jest niższe o 1 hPa, to w jaki sposób w prognozach podaje się ciśnienia dla danego obszaru, skoro różne miejsca mogą się różnić wysokością o choćby kilkadziesiąt metrów? Ano ciśnienie podawane w prognozach, to trochę ściema. Nie jest podawane ciśnienie rzeczywiste, tylko przeliczone do poziomu morza. W związku z tym między plażą nad morzem, a polskimi szczytami górskimi różnica w rzeczywistym ciśnieniu wynosi ponad 300hPa, co jest wartością dużo większą, niż wahania związane ze zmianami pogody…
Żeby policzyć ciśnienie nad poziomem morza, potrzebna jest znajomość temperatury. Układy BMP są wyposażone w czujniki temperatury dla łatwego przeliczenia. Zakładam jednak, że dla przeliczenia istotna jest temperatura na zewnątrz i to ją trzeba brać pod uwagę w obliczeniach. Natomiast oczywiście to, co jest najistotniejsze, to wysokość nad poziomem morza naszego urządzenia pomiarowego.
W każdym razie przyjmiemy, że wysłać należy rzeczywiste ciśnienia, a przeliczenia dokona urządzenie nadrzędne.
ESP8266 a ArduinoIDE
Ponieważ do NodeMCU nie ma w tej chwili dobrej biblioteki do wysyłania i odbierania kodów na 433MHz, a pod Arduino istnieje rcswich, przyjąłem, że pora przyjrzeć się obsłudze ESP8266 pod ArduinoIDE. Doskonały pretekst do czegoś, co chciałem zrobić od dawna.
Samo dodanie obsługi NodeMCU jest banalnie proste, natomiast pisząc program trzeba zdawać sobie sprawę z różnic. Przede wszystkim ESP8266 jest układem jednordzeniowym i poza programem musi wykonywać zadania związane z obsługą WiFi. Kiedy te zadania są wykonywane? Po każdym zakończeniu pętli loop. To jednak może być zbyt żadko, szczególnie gdy pętla trwa powyżej 50ms. Dlatego ESP zajmuje się także obsługą WiFi podczas wywołań funkcji delay (uwaga, nie dotyczy to delayMicroseconds), a także yield (odpowiednik delay(0)). Najlepiej więc pamiętać, żeby w czasochłonnych miejscach dodawać funkcję yield().
Programowanie
Użycie ArduinoIDE ma jeszcze jedną dużą zaletę – większość kodu mamy już gotową, bo przewodowa wersja bramki opierała się o Arduino. To co trzeba zmienić, to minimalne zmiany w inicjalizacji, obsłudze pakietów UDP i dołożyć część odpowiedzialną za czujnik ciśnienia. Dodatkowo, ponieważ używamy tylko pakietów UDP to nie do końca mamy gwarancję, że sieć działa poprawnie. Założymy więc, że co jakiś czas powinien przyjść pakiet z systemu nadrzędnego. Jeżeli nie przyjdzie, to na wszelki wypadek, zrestartujemy urządzenie.
#include <Wire.h> #include <Adafruit_BMP085.h> #include <ESP8266WiFi.h> #include <WiFiUdp.h> #include <RCSwitch.h> const char *ssid = "mojasiec"; const char *pass = "tajnehaslo"; IPAddress master(192,168,200,2); //tam wysyłamy int masterport=8000; //na ten port int localport = 8000; //na tym porcie nasłuchujemy int lastwifistatus = -1; int statuschanges=0; //czujnik ciśnienia Adafruit_BMP085 bmp; String str; char cstr[19]; WiFiUDP UDP; char packetBuffer[UDP_TX_PACKET_MAX_SIZE]; //dla pakietów przychodzących RCSwitch mySwitch = RCSwitch(); #define D5 14 #define D4 2 #define D1 5 #define D2 4 #define SEND_INTERVAL 30000 // wysyłanie ciśnienia co 30 sekund #define NO_COMM_TIMEOUT 1205000 // po jakim czasie uznajemy, że warto zrobić restart unsigned long stime; unsigned long nextSendTime=10000; unsigned long commTimeout=NO_COMM_TIMEOUT; // Update these with values suitable for your network. IPAddress ip(192,168,200,200); //static IP IPAddress gateway(192,168,200,1); IPAddress subnet(255,255,255,0); void setup() { Serial.begin(9600); WiFi.mode(WIFI_STA); WiFi.begin(ssid, pass); WiFi.config(ip, gateway, subnet); stime=millis(); commTimeout=stime+NO_COMM_TIMEOUT; //Wifi connection lastwifistatus=WiFi.status(); bmp.begin(3); mySwitch.enableReceive(D5); // pin modułu odbiorczego mySwitch.enableTransmit(D4); // pin modułu nadawczego mySwitch.setPulseLength(221); // ważne dla gniazdek Clarus UDP.begin(localport); //nasłuchujemy //wysyłamy info o (re)starcie UDP.beginPacket(master, masterport); UDP.write("Restart433"); UDP.endPacket(); } // wysyła ciśnienie w Pa jako UDP int sendPressure(int pressure) { char str[10]; sprintf(str, "%d", pressure); UDP.beginPacket(master, masterport); Serial.println(pressure); UDP.write("Pressure:"); UDP.write(str); return UDP.endPacket(); } // czy timer już nadszedł lub minął (jest mniejszy od time); zakładamy, że czasy nie są dłuższe niż kilka dni i sprawdzamy czy nie miało szansy przekroczenie zakresów boolean isAfter( unsigned long timer ) { if(timer<=stime && !(stime>3000000000L && timer<1000000000L) ) return(true); if(timer>3000000000L && stime<1000000000L) return(true); return(false); } void loop() { int mProtocol; int mSize; unsigned long mValue; char strint[10]; //powiadamiamy o zmianach statusu połączenia wifi if(lastwifistatus!=WiFi.status()) { Serial.print("WiFi new status - "); Serial.print(WiFi.status()); Serial.print(", IP address: "); Serial.println(WiFi.localIP()); lastwifistatus=WiFi.status(); if( lastwifistatus== WL_CONNECTED ) { statuschanges++; UDP.beginPacket(master, masterport); UDP.write("Connected "); sprintf(strint, "%d", statuschanges); UDP.write(strint); UDP.endPacket(); } } stime=millis(); //wysyłanie ciśnienia if( isAfter(nextSendTime) ) { nextSendTime=stime+SEND_INTERVAL; int temperature = bmp.readTemperature(); int pressure = bmp.readPressure(); //pressure = bmp.readSealevelPressure(66); sendPressure(pressure); if( isAfter(nextSendTime) ) nextSendTime=stime; } // odbieranie kodów do wysłania przez 433.92Mhz int packetSize = UDP.parsePacket(); //przyszły jakieś dane przez sieć if (packetSize) { Serial.println("UDP Received"); commTimeout=stime+NO_COMM_TIMEOUT; IPAddress remote = UDP.remoteIP(); if( remote[0]!=192 ) { //tu można dodać autoryzację po adresie źródłowym } UDP.read(packetBuffer, UDP_TX_PACKET_MAX_SIZE); //pobieramy wartości z ciągu znaków mProtocol=packetBuffer[0]-48; mSize=packetBuffer[3]-48+10*(packetBuffer[2]-48); mValue=String(packetBuffer).substring(5, packetSize).toInt(); // wyłaczamy odbieranie kodów, żeby nie odczytywać wysłaego przez siebie mySwitch.disableReceive(); // wysyłamy polecenie radiowo mySwitch.setProtocol(mProtocol); mySwitch.setPulseLength(221); mySwitch.send(mValue, mSize); // włączamy odbieranie ponownie mySwitch.enableReceive(D5); // odsyłamy potwierdzenie wysłania UDP.beginPacket(master, masterport); str = String("P:"+String(mProtocol)+"."+String(mSize)+"."+String(mValue)+"..."); for(int w=0; w<19; w++) cstr[w]=0; String(str).toCharArray(cstr,19); UDP.write(cstr, 19); UDP.endPacket(); } // restartujemy gdy dawno nie przyszedł żaden pakiet if( isAfter(commTimeout) ) ESP.restart(); // przyszły dane radiowo if (mySwitch.available()) { int value = mySwitch.getReceivedValue(); if (value == 0) { Serial.print("Unknown encoding"); } else { str = String(String(mySwitch.getReceivedProtocol())+"."+mySwitch.getReceivedBitlength()+"."+ String(mySwitch.getReceivedValue())); for(int w=0; w<19; w++) cstr[w]=0; str.toCharArray(cstr,19); // wysyłamy pakiet udp UDP.beginPacket(master, masterport); UDP.write(cstr, 19); UDP.endPacket(); } mySwitch.resetAvailable(); } }
Podsumowanie
Urządzenie działa niezawodnie od dłuższego czasu. Wbrew obawom ESP8266 także pod ArduinoIDE jest bardzo stabilne. Jedyne problemy, które napotkałem wynikały z błędu w jednej z bibliotek (starszej wersji), natomiast analiza zrzutów przekazywanych przez port szeregowy, naprowadziły mnie na ślad. Na pewno bardzo wygodne jest to, że NodeMCU zasilane jest przez microUSB oraz duża liczba wejść. Przy prostych urządzeniach można łatwo uniknąć tworzenia dedykowanej płytki drukowanej.
witaj,
bardzo fajny i przydatny opis, za co chyle czoła 🙂
nie do konca rozumiem w jaki sposob rozwiazane jest sterowanie danymi gniazdkami z RPI
mam jeszcze pytanie zwiazane z powyzszym projektem
chcialbym w podobny sposob rozwiazac sprawe z komunikacja czujnika PIR, oraz bezprzewodowych czujnikow kontaktronowych
czyli tylko odbierac informacje o ich statusie i wysylac ja do domoticz’a
podpowiedz prosze co trzeba by bylo zmienic w powyzszym zeby to zadzialalo?
pozdr
T
Rzuć też okiem na inny tekst – https://techniczny.wordpress.com/2016/08/10/radiowe-sterowanie-w-inteligentnym-domu/
Może to co rozjaśni. Zasada jest ta sama, tylko nie przez wifi.
Po stronie Raspberry Pi mam program w nodejs, który nasłuchuje nadchodzących pakietów udp i opowidnio na nie reaguje (a także wysyła w różnych sytuacjach). Oczywiście wysyłanie kodów można bardzo łatwo zrealizować też np. w php lub choćby bashu.
Wszystko zależy co jest Ci bliższe.
Dzień dobry.
Szukam podobnego rozwiązania , ale tylko do kontroli działania dwóch pomp.
Odczyt z przekładników przez sieć 433MHz i zobrazowanie na telefonie komórkowym