Rögös út vezetett idáig: már tudok úgy csinálni, mintha az én kis ESP8266-om kombinálva a 868 MHz-es rádióadóval egy Computherm Q8RF termosztát lenne, vagyis tudok olyan csomagokat küldeni az éterbe, amit a Computherm vevője megeszik és reagál rá. Viszont mivel vettem egy 868-as vevő egységet is, logikusnak tűnt ezt is hadra fogni.
Vétel
Ha már úgy is itt van, miért ne használjam hát?! Az eszköz megvan, a protokoll ismert, hát vágjunk bele a jelek feldolgozásába is! Nos, itt más fajta kihívásokkal kellett szembenéznem, mint az adásnál. Az elég nyilvánvaló, hogy nem lesz semmi tökéletesen pontosan időzítve, amit kapok, vagyis szinte mindenhol ráhagyással kell dolgozzak. A legelső dolog, amit fel kell ismerjen a program, az a szinkronizációs csomag, vagyis 3 ütem hosszú szünetek és adások egymásutánja. Erre azt a közelítést használtam, hogy a 2.5 x T és 3.5 x T közötti hosszban érkeznek a váltások, akkor én azt elfogadom. Ha 0-t akarok értelmezni, akkor a 2 alacsony után 1 magas jön elvileg, ezt úgy próbáltam értelmezni, hogy a magas rész hossza legyen 0.5 x T és 1.5 x T időtartományon belül, míg az 1-esnél (1 alacsony, 2 magas) 1.5 x T és 2.5 x T között. Ami még érdekes az a STOP üzenet, ami elvileg 6 ütem hosszú, de után mindig egy SYNC jön, ami 3 alacsonnyal indul, így ezek összesen 9 ütemnyi alacsony jelszintet adnak ki. Ezt némi ráhagyással úgy próbáltam értelmezni, hogy 7 x T és 10 x T között legyen. Ezekből jöttek ki a következő konstansok:
#define TICK_LENGTH 220
#define SHORT_MIN TICK_LENGTH * 0.5 // 110
#define LONG_MIN TICK_LENGTH * 1.5 // 330
#define SYNC_MIN TICK_LENGTH * 2.5 // 550
#define SYNC_MAX TICK_LENGTH * 3.5 // 770
#define STOP_MIN TICK_LENGTH * 7 // 1760
#define STOP_MAX TICK_LENGTH * 10 // 2200
Értelem szerűen a fenti levezetésből következik, hogy ami a SHORT-nak a MAX értéke lenne, az ugyanannyi, mint a LONG-nak a MIN és így tovább.
Ami adáskor a delayMicroseconds() volt, az vételnél a micros() lett. Ez a függyvény adja vissza, hogy az MCU indítása óta hány mikroszekundum telt el. Mivel ez egy unsigned long típusú értéket ad vissza, így ha az ember utánaszámol, akkor ez az érték nagyjából 70 percentént túlcsordul és nullázódik. Ez azonban nem fog minket befolyásolni, mert mi minden esetben kivonjuk a kapott értéket egy korábban ugyanettől a függvénytől kapott értékből és csak a különbségüket viszgáljuk. A túlcsordulás pedig ugyanúgy fog bekövetkezni akkor is, amikor egy kicsi számból vonunk ki egy hatalmasat, vagyis nekünk ezzel az egésszel nem is kell fgolalkoznunk!
Hogy a mérések a lehető legpontosabbak legyenek így az nem megoldás, hogy a loop függvényben folyamatosan nézegetjük a bemeneti láb állapotát, ide más fegyverre lesz szükség! Ez pedig a megszakítás:
attachInterrupt(digitalPinToInterrupt(INPUT_PIN), handler, CHANGE);
A mágia pedig a következő helyen van:
void handler() {
static unsigned long lastMs = 0, currMs, diffMs;
currMs = micros();
diffMs = currMs - lastMs;
lastMs = currMs;
if (buffEnd == BUFF_SIZE) {
buffEnd = 0;
}
if (!avail){
if (digitalRead(INPUT_PIN) == LOW){ // Falling edge
if (diffMs >= SHORT_MIN && diffMs <= SYNC_MAX){ // filter out the too short and too long high pulses
if (diffMs >= SYNC_MIN){
buffEnd = 0;
} else {
if (diffMs <= LONG_MIN) {
buff[buffEnd++] = 0;
} else {
if (diffMs < SYNC_MIN) {
buff[buffEnd++] = 1;
}
}
}
}
} else { // Raising edge, only stop could be detected
if (diffMs >= STOP_MIN) {
if (buffEnd == MSG_LENGTH && !isRepeat()) {
avail = true;
} else {
buffEnd = 0;
}
}
}
}
}
Némi magyarázoatot lehet igényel a megszakításkor lefutó kódrészlet:
- Az avail nevű változóban azt tárolom, hogy van-e feldolgozott, kész csomag, amit már lehet a fő ciklusban használni
- Ahogy az látható a leszálló élek időpontja a fontos nekem, a felszálló élek csak a STOP üzenetek detektálásakor fontosak. (Természetesen a felszállóknál is megjegyezzük az időpontot, vagyis nyomunk egyet a stopperórán, hogy utána ahoz tudjuk majd viszonítani a következő leszálló élet.)
- A buff nevű változó maga a puffer, aminek az aktuális hosszát a buffEnd tárolja. Ebben gyakorlatilag 0 és 1 értékek gyülekeznek.
- Az isRepeat() nevű függvény azt nézi meg, hogy ami épp most érkezett, az nem az előző csomag ismétlése-e véletlenül.(Ezt a nem túl bonyolult kódot nem rakom ide, ha valaki idáig eljutott biztosan megtalálja a módját, hogy egy másik változóba mentett puffer-rel össze tudja hasonlítani az érkezettet.)
Szóval emellé jött még pár egyszerű rutin, ami a hexadecimális értékeket kiírja, meg hasonlók, de a lényeg, hogy ezeken az alapokon simán össze lehet rakni a kódot, ami fel tudja dolgozni az éterben keringő adatokat.
Könyvtár
Ha már ilyen sikeresen megcsináltam külön-külön az adó és a vevő részeket, akkor adta magát a feladat, hogy legyen ebből valami olyan, amit könnyedén tudok majd használni. Arduino környezetben nagy kultúrája van a library-k publikálásának, előszedtem hát a régi 433-as libemnél tanultakat és publikáltam a második opensource lib-emet is. Akit érdekel itt található a cucc, van benne egyszerű példa a küldésre és a fogadásra is:
https://github.com/denxhun/ComputhermRF
Kész cucc
Ezután már csak egy lépés volt hátra: csinálni egy tényleg használható programot. Ehhez én egy Wemos D1 mini nevű ESP8266 alapú modult választottam, ami egy őrületesen drága (~2 USD ha jól emlékszem) kínai termék. Erre pár hét alatt összerkatm egy izgi kis projektet:
- Wifi-n csatlakozik az otthoni hálózatomhoz
- MQTT-n csatlakozik a Home Assistant rendszeréhez
- A rákötött vevő segítségével érzékeli, ha a termosztátok bármelyike ki, vagy bekapcsol, ezt az infót pedig beküldi az MQTT-be is.
- Ha az MQTT megfelelő csatornájára üzenünk az eszköznek, akkor a rádióadón keresztül maga küld olyan üzenetet, mintha a termosztát szólt volna a központi egységnek, vagyis ki-be tudja kapcsolgatni a kazánt.
- Mindezt egy csinos kis weboldalon keresztül lehet állítgatni, vagy akár kézzel is kapcsolgatni.
- A rendszer egy websocket alapú egyszerű kommunikációval old meg mindent, a HTML oldal maga statikus, de a böngészőben futó jQuery rendez mindent.
- Természetesen amit csak lehetett igyekeztem nem beleégetni a kódba, hanem a Settings fülön konfigurálhatóvá tettem.
Ez maga a mini weboldal és hónapok óta stabilan fut - ami nem kis büszkeséggel tölt el.
Tanulság és TODO
Miután élesbe állítottam a kis eszközömet, azóta egy dolog biztosan kiderült számomra: van még kihívás a témában. Kiderült ugyanis, hogy a termosztát azt csinálja hogy pár percenként újra meg újra elküldi a be- illetve kikapcsoló üzeneteit a központnak. Vagyis ha megy a fűtés, akkor 2 percenként (emlékeim szerint, de ez a szám csak halvány emlék) újra és újra elküldi a bekapcsoló üzenetet. Azt még nem kisérleteztem ki, hogy a központ kikapcsolja-e a fűtést, ha nem érkezik meg bizonyos időn belül újra az üzenet, de nem tartom ezt sem kizártnak.
Ez nekem azért problémás, mert nem tudom egyszerűen megvalósítani azt a műveletet, hogy a Home Assistant szól az eszközömön keresztül a kazánnak, hogy most kapcsolj be, annak ellenére, hogy a termosztát szerint erre nem lenne most szükség. (Ugye a sorozat elején már vázoltam azt a scenáriót, hogy elutazik a család és beállítom a termosztátot, hogy ne fűtsön a következő napokban, majd amikor indulunk haza, akkor távolról beindítom a fűtést, hogy mire hazaér a család már jó idő legyen.) Viszont van erre egy megoldási ötletem, amit még nem teszteltem, de hamarosan meg lesz az is: a 4 csatornából jelenleg 2 van kihasználva. A most kihasználatlan kettőt párhuzamosan lehetne kötni az éles körökkel, így ha az 1. számű kör ki van kapcsolva (amit a termosztát vezérel) attól én még a 3-as számút be tudom kapcsolni (Home assistant-on és a gateway-en keresztül) így a kazán beindul. Hasonlóan a 2-4 párost is párhuzamosítani lehet. Na ez az ami még a TODO listámon van...