Pisałem ostatnio o połączeniu Raspberry Pi z Integrą produkcji Satela z użyciem interfejsu szeregowego. Przyszła pora, żeby napisać coś więcej o tym przedsięwzięciu od strony programowania – zarówno samej centrali alarmowej, jak i Raspberry Pi – w Node.js, bo jego używa obecnie główna część mojego „inteligentnego domu”. Node.js świetnie nadaje się do obsługi systemu opartego na zdarzeniach, ale oczywiście każdy może przełożyć zamieszczony tu kod na swój ulubiony język.
Tytuł wpisu wyraźnie wskazuje na to, że będziemy monitorować także wejścia, tymczasem opis protokołu opublikowany przez Satela wskazuje, że łatwo możemy dostać się wyłącznie do stanu wyjść (przez komendę 0x17). Oczywiście to żaden problem, ponieważ centrala pozwala definiować wirtualne wyjścia, których stan zależy od stanu wejść (czyli np. czujek ruchu). Możemy więc na bieżąco mieć podgląd wszystkich czujek (w końcu system automatyki domowej ma swoje „oczy”), ale też sprawdzać wiele więcej – stan zasilania, akumulatora, uzbrojenia i wywołania alarmu, sabotażu, przyciśnięci przycisków w pilotach ABAX, rozpoczęcie odliczania czasu na wyjście, alarmy wstępne, itp.
Dla sprawdzania stanu wybranej czujki, definiujemy typ wyjścia jako 14 – naruszenie wejścia i wybieramy o którą czujkę (lub grupę, gdy nie zależy nam na takiej dokładności) nam chodzi. Czas działania (okres, przez który wyjście jest aktywne po wykryciu ruchu) określamy na wystarczająco długi, żeby nie przegapić zmiany i dość krótki, żeby móc obserwować kolejne naruszenia. Na początek testowo wybrałem 2 sekundy przy zapytaniach do centrali co sekundę. Potem być może wydłużę te czasy.
Mamy więc zdefiniowane wyjścia, które zmieniają się zależnie od stanu czujek. Celem na dzisiaj jest załączanie listew led w kuchni po wykryciu ruchu (na czas zależny od miejsca wykrycia ruchu) oraz wysłanie e-maila z informacją gdy zniknie (i zostanie przywrócone) zasilanie w sieci 230V. Pierwsze zadnie realizujemy wysyłając do sterownika listew LED (oparty na ESP8266, może kiedyś opiszę) odpowiedni pakiet UDP. Drugie zadanie sprowadza się do uruchomienia odpowiedniego skryptu wysyłającego pocztę – resztą zajmuje się system SMTP na Raspberry Pi. Jak można wysłać e-maila bez prądu? Centrala alarmowa ma swój akumulator, a „malina” i infrastruktura dostępu do sieci podłączona jest do zasilaczy buforowych, co gwarantuje długie działanie bez prądu.
Patrząc na protokół dostarczony przez firmę Satel dochodzi się do wniosku, że w pierwszej kolejności trzeba napisać jakąś klasę do obsługi ramek wysyłanych do centrali i sprawdzania tych, które z niej wróciły. Nie jest to przesadnie trudne zadanie, bo wszystko jest świetnie opisane w dokumentacji, a protokół jest prosty. Na pewno istotne jest wyliczanie kodu CRC według podanego na końcu algorytmu.
// CLASS ------------------------- function SatFrame() { this.param=new Array(); this.frame=null; } var p = SatFrame.prototype; // METHODS ----------------------- // dodaje kolejny bajt do ramki (bajty danych, nie kontrolne) p.addParam = function(ch) { this.param.push(ch); } // zwraca ramkę jako tablicę p.getFrame = function() { this.makeFrame(); return this.frame; } // zwraca ramkę jako bufor p.getFrameAsBuffer = function() { this.makeFrame(); var buf=new Buffer(this.frame, 'binary'); return(buf); } // wpisuje całą ramkę jako tablicę (do użycia w celu sprawdzenia crc) p.setFrame = function(fr) { this.frame=fr.slice(0); } // sprawdza czy zgadza się CRC pełnej, gotowej ramki p.checkCrc = function() { var crcbegin=this.frame.length-4; if( this.frame[crcbegin]==0xfe ) crcbegin--; if( this.frame[crcbegin-1]==0xfe ) crcbegin--; var params=this.frame.slice(2, crcbegin); var crc=this.frame.slice(crcbegin); var calculatedcrc=crc16(params); if( (calculatedcrc>>8&0xff)!=crc[0] ) return(false); if( crc[0]!=0xfe && (calculatedcrc&0xff)!=crc[1] ) return(false); else if( crc[0]==0xfe && (calculatedcrc&0xff)!=crc[2] ) return(false); return(true); } function rl(x) { return ((x<<1) | (x>>15)) & 0xFFFF; } // kalkuluje crc przy założeniu, że 0xfef0 ma być traktowane jako 0xfe function crc16(param) { var crc=0x147a; for(w=0; w<param.length; w++) { crc=rl(crc); crc=crc ^ 0xffff & 0xffff; crc=crc+(crc>>8 & 0xff)+param[w]; if( param[w]==0xfe && param[w+1]==0xf0 ) w++; } return(crc); } // tworzy ramkę łącznie z crc i zamianą 0xfe na 0xfef0 p.makeFrame = function() { this.frame=new Array(); this.frame.push(0xfe); this.frame.push(0xfe); for( w=0; w<this.param.length; w++ ) { this.frame.push(this.param[w]); if( this.param[w]==0xfe ) this.frame.push( 0xf0 ); } var crc=crc16(this.param); this.frame.push((crc>>8) & 0xff); if( ((crc>>8) & 0xff)==0xfe ) this.frame.push( 0xf0 ); this.frame.push(crc & 0xff); if( (crc & 0xff)==0xfe ) this.frame.push( 0xf0 ); this.frame.push(0xfe); this.frame.push(0x0d); } // EXPORTS ----------------------- exports.SatFrame = SatFrame;
Mając gotową klasę można pomyśleć o komunikacji. Wpierw jednak trzeba wiedzieć jak użyć klasy i stworzyć pierwszą ramkę do wysłania. Niech to będzie zapytanie o stan wyjść. Użyjemy klasy, stworzymy nowy obiekt klasy, dodamy komendę 0x17 oraz zbudujemy ramkę i wyślemy ją do centrali.
var SatFrameClass = require("./satframe"); var sf=new SatFrameClass.SatFrame(); sf.addParam(0x17); var fr=sf.getFrameAsBuffer(); serialPort.write(fr);
Powyższe pokazuje, że użycie klasy jest bardzo proste, jednak trzeba też umieć obsłużyć transmisję przez port szeregowy. Nie będziemy wyważać otwarty drzwi i użyemy gotowej biblioteki. Zbudujmy więc kompletny program realizujący zamierzone funkcje.
var SerialPort = require("serialport"); var SatFrameClass = require("./satframe"); var dgram = require('dgram'); var http = require('http'); var child_process = require('child_process'); var bufsize=1024; var buf=new Buffer(bufsize); var bufpos=0; var outputs=new Array; var outputschange=new Array; // tworzymy zapytanie o stan wyjść var sf=new SatFrameClass.SatFrame(); sf.addParam(0x17); //inicjujemy tablice stanów i zmian wyjść //numery wejść w cenrali są numerowane od 1 do 128 (w przypadku Integry 128) for( w=1; w<=128; w++ ) { outputs[w]=0; outputschange[w]=0; } // uruchamiamy nasłuch portu szeregowego serialListener(); // cykliczne zapytania o stan wyjść setInterval( function() { var fr=sf.getFrameAsBuffer(); serialPort.write(fr); }, 1000 ); function serialListener() { var receivedData = ""; serialPort = new SerialPort.SerialPort("/dev/ttyAMA0", { baudrate: 19200, dataBits: 8, parity: 'none', stopBits: 1, flowControl: false, }); serialPort.on("open", function () { console.log('open serial communication'); serialPort.on('data', function(data) { if(bufpos+data.length>bufsize) bufpos=0; for(w=0; w<data.length; w++ ) { buf[bufpos]=data[w]; bufpos++; } oldbufpos=bufpos; while( (bufpos=parseSatel(buf, bufpos))!=oldbufpos ) oldbufpos=bufpos; }); }); } // szuka pełnej ramki i dba, żeby bufor się nie przepełnił function parseSatel(b, size) { var begin=-1; var end=-1; var frame=new Array; for( w=0; w<size && end==-1; w++ ) { if( b[w]==0xfe && b[w+1]==0xfe ) begin=w; if( b[w]==0xfe && b[w+1]==0x0d ) end=w+1; } if( end!=-1 ) // jest pelna ramka { //przepisujemy gotowa ramke q=0; for(w=begin; w<=end; w++) { frame[q]=b[w]; q++; } //przepisujemy reszte na poczatek bufora q=0; for(w=end+1; w<size; w++) { b[q]=b[w]; q++; } runFrame(frame); return(q); } return(size); } // uruchamia akcje zwiazana z ramka, gdy to potrzebne function runFrame(f) { var tm = new Date(); // sprawdzimy czy crc ramki się zgadza, jak nie to ignorujemy var test=new SatFrameClass.SatFrame(); test.setFrame(f); if( !test.checkCrc() ) { console.log("Niepoprawny CRC dla ramki: "+f); return; } if( f[2]==0x17 ) //wyjscie { for( w=0; w<128; w++ ) { out=f[3+Math.floor(w/8)]&(Math.pow(2,(w%8))); if( out>0 ) out=1; outputschange[w+1]=0; if( out!=outputs[w+1] ) { outputschange[w+1]=1; console.log(tm+" zmiana "+(w+1)+"="+out); } outputs[w+1]=out; } if( outputs[99]==1 && outputschange[99]==1 ) { sendLed("1"); } if( outputs[100]==1 && outputschange[100]==1 ) { sendLed("2"); } if( outputs[91]==1 && outputschange[91]==1 ) { console.log("A tu moglibyśmy zareagować na wciśnięcie przycisku pilota."); } if( outputs[84]==1 && outputschange[84]==1 ) { child_process.exec('/sciezka/mailsend "Brak zasilania centrali"'); } if( outputs[84]==0 && outputschange[84]==1 ) { child_process.exec('/sciezka/mailsend "Zasilanie centrali przywrocone'); } } } // wysłanie polecenia do sterownika LED function sendLed(msg) { var message = new Buffer(msg); var client = dgram.createSocket("udp4"); client.send(message, 0, message.length, 80, "192.168.1.11", function(err) { client.close(); }); }
Program robi to co było zaplanowane – otwiera port, uruchamia cykliczne zapytania o stan wyjść. Przy nadejściu danych dopisuje je do bufora i sprawdza czy są w nim gotowe ramki. Gdy są gotowe ramki wybiera pierwszą a resztę przepisuje na początek bufora. Dla gotowej ramki sprawdza czy jej suma kontrolna jest poprawna. Jeżeli tak, to dla odpowiedzi 0x17 sprawdza stan, których wyjść się zmienił, loguje na konsolę wszystkie zmiany (czas, numer wyjścia i nowy stan), a w przypadku gdy zmieniło się któreś z istotnych wyjść – reaguje. W przypadku dwóch czujek ruchu w kuchni wysyła polecenie do sterownika LED, a w przypadku zaniku zasilania, wysyła e-mail przez gotowy skrypt.
Skuteczne i stabilne, acz wymagające jeszcze nieco dopracowania. Nie ma np. sprawdzania czy połączenie szeregowe zostało zamknięte. W przypadku problemów z jego odtworzeniem także powinna zostać wysłana informacja o problemach. Warto byłoby też logować zmiany według nazw lub funkcji wyjść a nie ich numerów. Pewne drobiazgi pominąłem, bo i tak będą potrzebne jeszcze pewne zmiany przy włączaniu modułu do działającego już serwera automatyki domowej (także działającego w Node.js), a chciałem, żeby kod był możliwie przejrzysty.
Dalsza rozbudowa współpracy centrali alarmowej z systemem inteligentnego domu, w moim przypadku, będzie sprowadzała się głównie do korzystania z informacji pochodzących z czujników centrali. Można sobie wyobrazić wiele nowych funkcji uzyskanych dzięki tym informacjom – zamykanie rolet po uzbrojeniu alarmu, wyłączanie świateł w domu po załączeniu alarmu, mruganie wszystkimi światłami w domu gdy alarm zostanie wywołany, blokowanie możliwości otwarcia bram po uzbrojeniu alarmu, załączanie symulacji obecności domowników, powiadomienie o powrocie dziecka ze szkoły i wiele więcej. Możliwości są duże, wszystko zależy od potrzeb i chęci ich realizacji.
Troszeczke w oderwaniu od tego wpisu.
Jestem na etapie gruntownego remontu domu (włącznie z instalacja elektryczna) i chciałbym dołożyć alarm i system „inteligentnego domu”. Nie mam jeszcze jasnej wizji z jakich elementow bedzie sie on składał ale chciałbym na tym etapie przygotować się tak bardzo jak to możliwe.
Wstępnie planuje okablować dom zgodnie z:
http://www.dipol.com.pl/signalnet-_nowoczesne_okablowanie_zgodne_z_rozporzadzeniem_ministra_infrastruktury_z_dnia_12_marca_2009_r__domnet.htm
Chciałem też użyć Satel integra jako centrale alarmowa.
Reszta na razie jest otwarta.
Czy można prosić o wpis/rade jak rozplanować infrastructure, gdzie i jakie pociągnać przewody, żeby móc w przyszłości budować/rozbudowywać system.
Śledząc wpisy widze, że Pana system ewoluował w czasie i ma Pan wiele „lessons learned” jak coś robić albo czego unikać. Myśle, że taki wpis byłyby ciekawy dla wielu osób które dopiero zaczynają temat 🙂
Pozdrawiam,
Ależ to temat na długi wpis, a nie na komentarz… Podejść może być wiele, nie czuję się autorytetem i nie chcę źle doradzić. Natomiast mam trochę przemyśleń, które być może warto wziąć pod uwagę.
Moim zdaniem bardzo istotne jest, żeby nie zamknąć sobie drogi rozwoju, natomiast sam remontowałem dom i wiem też, że nie można popadać w przesadę i kablowanie wszystkiego na zapas jest po prostu drogie i trudne w realizacji. Jednocześnie jestem zwolennikiem kabli, nie radia, ale też nie w każdym zastosowaniu – czasem warto zastosować rozwiązanie mieszane. Ja w pierwszej kolejności myślałbym o przewodach – skrętki od „centrum sieciowego” (lub scieciowo-alarmowego) do salonu w okolice telewizora, skrętkach do pokoi, skrętne w centralny punkt „rozsiewania wifi”, kablu alarmowym dla termometrów (w różnych, możliwie wielu pomieszczeniach i w dobrych miejsach na zewnąrz – od południa i północy), skrętce do sterowników lamp zewnętrznych (silne oświetlenie terenu w razie potrzeby, oświetlenie wejścia stałe i dodatkowe, oświetlenie ozdobne-świąteczne), i rolet (nie zapominając o zasilaniu od sterownika). Warto pomyśleć o sterowaniu bramą/furtką. Wyprowadziłbym przewody do zasilania elektrozaworów w systemie podlewania i doprowadził skrętkę i zasilanie w miejsce sterowania. Może warto pomyśleć o systemie sterowania kablami grzejnymi w rynnach i elektrycznym ogrzewaniem podłogowym (zrobiłbym przy wejściu do domu, w łazience i… garażu – dla osuszania – niezależnie od pozostałego ogrzewania). Do włączników światła doprowadziłbym koniecznie przewód neutralny dla radiowych systemów sterowania. Nie zapomniałbym o skrętne do kamer IP na zewnątrz, jak i przewodzie alarmowym do zewnętrznych czujek ruchu. Jak już jesteśmy przy alarmie, to warto pamiętać o czujkach dymu i zalania oraz o przewodach dla sygnalizatora. To w skrócie tyle. Na pewno kuszące byłoby zrobienie instalacji niskonapięciowej do włączników (świateł) i oddzielnego sterowania oświetleniem, ale to kilometry kabli i nie wyobrażam sobie robienia czegoś takiego przy remoncie – ale może warto przemyśleć w przypadku wyłączników schodowych, a może w przypadku schodów oprzeć się na czujnikach ruchu? Może to dobry pomysł, żeby sterować oświetleniem sypialni także z włącznika przy łóżku…
Chaotycznie – ale może da materiał do przemyśleń, bo gotowa recepta z pewnością to nie jest. Może kiedyś rozwinę przemyślenia w formie wpisu, w sposób bardziej uporządkowany.
Zdaje sobie sprawę, że temat jest obszerny.
Dzięki za pomysły, wcześniej nie pomyślalem np. o macie grzewczej przed wejściem albo grzaniu rynien.
Co ma Pan na myśli pisząc przewód neutralny w przypadku włączników światła?
Początkowo bardzo chciałem poprowadzić instalacje niskonapięciową ale doszedłem do wniosku, że to troche przerost formy nad treścią i w większości nie będzie wykorzystywana. Teraz raczej myśle o częściowej instalacji np. przy wejsciu albo własnie na schodach.
Jak najlepiej połączyć termometry?
I co Pan myśli o rozprowadzeniu światłowodów?
Proszę nie pisać „Pan”. To dyskusja w Internecie, takie zasady, nikt się nie obrazi…
Grzanie w miejscu gdzie stoją buty jest cenne – szybko schną i nie ma wilgoci. Tylko warto tym inteligentnie sterować – prąd kosztuje. Podobnie jak w przypadku grzaniu rynien – tylko w określonych warunkach.
Jeżeli chodzi o przewód neutralny – warto rzucić okiem na systemy typu Exta Free, Conrad RSL czy ELRO Home Easy. Można w nich znaleźć urządzenia, które pozwalają łączyć normalne sterowanie przyciskiem ściennym, zasilanym z 230V, ze sterowaniem radiowym. Olbrzymia urządzeń wymaga jednak jednej rzeczy, której nie ma w większości instalacji w domach – przewodu neutralnego przy włączniku. Warto przy okazji wymienić puszki na głębokie tam gdzie się da, a w wybranych miejscach może nawet takie z dodatkową przestrzenią z boku. W miejscach gdzie nie ma przewodu neutralnego przy włączniku można instalować urządzenie radiowe w puszce nad włącznikiem, ale często nie ma tam miejsca. Któryś z systemów sterowania być może wkrótce opiszę.
Termometry przez 1-wire – 3-żyły przewodu alarmowego – może być więcej termometrów połączone do jednego przewodu (równolegle), może być architektura gwiaździsta lub mieszana (jak u mnie). Bardzo dobry system, tanie i precyzyjne termometry DS18B20. Chyba przynajmniej wspominałem o tym przy jakiejś okazji, pewnie jeszcze opiszę…
EDIT: Jest wpis o tym – „Internetowy termometr” – wersja na 2 żyłach, ale lepiej poprowadzić 3.
Dla mnie światłowody mają jedną, zasadniczą zaletę – separują galwanicznie. Natomiast mają sporo wad. Pewnie są zastosowania, w których warto użyć, nie są uniwersalne. Przy serwerach tak, przy doprowadzeniu Internetu do domu – tak, przy audio-wideo – tak, ale poza tym – nie mam koncepcji.
Skończyła sie możliwość zagnieżdźania komentarzy, nie wiem gdzie ten trafi 😉
Rozważe te systemy, wydaje się to ciekawą alternatywą dla ciągnięcia wszędzie skrętki.
Techniczn. Czy mozesz sie ze na skontaktowac? Mam kilka pytan.
Pozdrawiam
Proponuję pytać w komentarzach.
Witam, czy zaprezentowany tutaj kod jest na jakiejś konkretnej licencji? Myślę o małym projekcie czerpiącym z tego wpisu, zapewne udostępnił bym to na GitHub.
Na razie wszystko jest w fazie „kiedyś jak będę miał czas..” 🙂
Proszę o jakiś opis projektu, to z pewnością się dogadamy, a może nawet się dołączę…
Amatorski i bardzo niedzielny projekt automatyzacji małego domu. Funkcje alarmu początkowo miały być realizowane przez mikro-kontroler, jako dodatek do całej reszty (bo i tak potrzebuję czujki ruchu i kontaktrony). Doszedłem jednak do wniosku, że instalacja bardziej niezawodnej, gotowej centralki będzie lepszym rozwiązaniem na przyszłość, a finansowo zwróci się w ulgach ubezpieczenia.
Stan wejść satela przydały by mi się między innymi do:
– sterowanie pompą cyrkulacyjną CW
– sterowanie oświetleniem zewnętrznym
– w połączeniu z kamerą/aparatem powiadamianie email z obrazem
– śledzenie aktywności dziecka
– powiadomienia ogrodowe
Od strony oprogramowania zależy mi na wykorzystaniu JavaScript, dlatego właśnie Twój post przykuł moją uwagę. Do tej pory eksperymentowałem z Johnny-Five, Cylon.js oraz Espruino (polecam) – jeśli kupię Satela, to na pewno jedno z tych rozwiązań wykorzystam, a ponieważ wieżę w modularność, zaczął bym od w miarę uniwersalnego modułu do komunikacji z centralką. W tym momencie przydał by się Twój kod.
Nie ma problemu. Proszę tylko o zaznaczenie źródła pochodzenia w kodzie i podlinkowaie do bloga w na stronach projektu.
Czy temat rozbudowy integry się rozwija? Zastanawiam się jak zrobić włącznik światła współpracajwący z integrą działający z poziomu zwykłego włącznika światła i integry.
Możliwości są. Jakie konkretnie masz potrzeby i co już masz?
Hej, zaczynam właśnie własną implementację tego typu integracji i mam problem ze zrozumieniem jednej przykładowej ramki (dokumentacja strona 2):
FE–FE–1C–D7–FE–F0–FE–0D
Zakładam, że:
FEFE – synchronizacja
1CD7 – dane
FEF0 – suma kontrolna, przy czym faktyczna wartość wynosiła przed wysłaniem FE a F0 zostało doczepione w drodze wyjątku
FE0D – zakończenie ramki
I teraz problem. Dla tej jednej wyjątkowej ramki moja (twoja również) funkcja obliczania sumy kontrolnej zwraca inną wartość niż ta widoczna w ramce powyżej. Dla pozostałych przykładów z instrukcji działa prawidłowo. Jakieś pomysły?