Niestety nie jest to tak bezproblemowe jak mogłoby się wydawać, więc nie zdecydowałem się na wykorzystanie takiego rozwiązania w produkcyjnych warunkach, ale opiszę to co udało mi się ustalić. Być może komuś się przyda.
Zacznijmy zatem od początku.
W pierwszym kroku należy pobrać OpenHab RuntimeCore.
Znajdziemy je pod adresem http://www.openhab.org/downloads.html
Po rozpakowaniu archiwum zip, należy zajrzeć do katalogu configurations.
Konieczne będzie utworzenie tzw. sitemap'y. Jest to struktura głównego menu, w którym możemy grupować sobie urządzenia na przykład wg pomieszczeń w których się znajdują, czyli salon, łazienka, piętro itd.
W moim przykładzie będę chciał sterować tylko jednym urządzeniem, załóżmy że będzie to taśma led w salonie.
Zatem moja sitemap'a będzie znajdowała się w pliku house.sitemap w podkatalogu sitemaps i będzie zawierała następujące wpisy:
sitemap Dom label="Menu główne"
{
Frame {
Group item=Salon label="Salon" icon="firstfloor"
}
}
Następnie musimy utworzyć plik house.items w podkatalogu items, gdzie określamy kontrolery naszych urządzeń. Dla naszego przypadku testowego będzie on wyglądał następująco:
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Group All | |
Group Salon "Salon" (All) | |
/*Lights */ | |
Switch Light_Salon_LED_Tape "Taśmy LED - salon" (All, Salon) {tcp=">[ON:192.168.0.5:8888:MAP(salonled.map)], >[OFF:192.168.0.5:8888:MAP(salonled.map)], <[192.168.0.5:*:JS(salonled.js)]"} |
Znalazł się w nim jeden element typu Switch, który może przyjmować wartości ON oraz OFF.
Następnie jest informacja iż obsługa przełącznika ma być realizowana za pośrednictwem protokołu TCP, oraz konfiguracja wyjściowa (elementy ze znakiem ">") i wejściowa (elementy ze znakiem "<").
Określone zostało również mapowanie komend które zostanie wykorzystane przy zmianie stanu przycisku. Mapowanie znajduje się w pliku salonled.map. Pliki mapowań umieszczamy w podkatalogu transform. Plik salonled.map w moim przypadku wygląda następująco:
ON=salonled#
OFF=salonled#
Oznacza to że zarówno przy komendzie "ON" (włączenie przełącznika), jak i przy komencie "OFF" (wyłączenie przełącznika) na adres 192.168.0.5:8888 (adres IP arduino) zostanie wysłany ciąg znaków "salonled#".
W ten sposób załatwione mamy sterowanie taśmą LED po stronie OpenHab. Co jednak w przypadku gdy stan oświetlenia zostanie zmieniony bez udziału OpenHab? Na przykład za pomocą przełącznika na ścianie? Konieczne jest aby informacja o zmianie stanu dotarła do OpenHab, aby mógł on wyświetlić użytkownikowi który może znajdować się daleko od domu aktualny stan oświetlenia.
Do tego wykorzystałem komunikację zwrotną, czyli OpenHab również będzie nasłuchiwał na wybranym porcie, a Arduino przy każdej zmianie stanu oświetlenia będzie wysyłało informację na podany numer IP.
W pliku house.items widać że mapowanie w konfiguracji wejściowej OpenHab zrobione jest za pośrednictwem JavaScriptu. Jest to spowodowane problemami na które w międzyczasie się natknąłem.
Założyłem że w przypadku załączenia oświetlenia Arduino wyśle komunikat salonled#1 a w przypadku wyłączenia salonled#0. Pomimo tego że obsłużyłem takie komendy w mapowaniu OpenHab konfiguracja nie działała poprawnie. Dopiero po analizie kodu źródłowego OpenHab'a i podłączeniu JavaScriptu który umożliwił wyświetlenie w log'u komunikatów które docierają do OpenHab okazało się że treść polecenia jest dużo dłuższa i uzupełniona pustymi znakami. Prawdopodobnie przesyłany był pełny bufor TCP - nie badałem tego tematu dokładniej.
Rozwiązaniem okazało się trim'owanie komunikatu i ręczne obsłużenie mapowania na odpowiedni stan przycisku. Udało się to osiągnąć następującym skryptem JS (OpenHab wspiera skrypty RHINO), który umieściłem w pliku salonled.js w podkatalogu transform:
Należy jeszcze włączyć obsługę TCP w OpenHab.
W tym celu należy pobrać dodatkowe plugin'y (OpenHab AddOns), a następnie plik org.openhab.binding.tcp-1.4.0.jar (lub inna wersja) skopiować do katalogu addons z serwera OpenHab.
W pliku configurations/openhab_default.cfg należy odkomentować linię
tcp:port=25001
Dzięki temu OpenHab będzie nasłuchiwał na porcie 25001
Teraz można uruchomić OpenHab skryptem startup.sh (lub startup.bat w przypadku Windows) upewniając się wcześniej że mamy zainstalowaną odpowiednią wersję javy.
Po chwili pod adresem;
http://localhost:8080/openhab.app?sitemap=house
dostępne powinno być menu główne naszej konfiguracji.
To już cała konfiguracja po stronie OpenHab. Pozostaje kod źródłowy po stronie Arduino.
Odsyłam tutaj do wpisu http://technika-laika.blogspot.com/2014/04/arduino-sterowanie-przekaznikiem-za.html gdzie jest pierwotna wersja kodu źródłowego, dokładniejszy opis i wyjaśnienie nazewnictwa plików.
Jedyne zmiany które się pojawiają to zmiany komunikacji zwrotnej w pliku network.ino:
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
function transform(val) { | |
var trimmed = new java.lang.String(val); | |
trimmed = "" + trimmed.trim(); | |
if (trimmed.equals("salonled#1")) { | |
return "ON"; | |
} | |
if (trimmed.equals("salonled#0")) { | |
return "OFF"; | |
} | |
return ""; | |
} | |
transform(input); | |
Należy jeszcze włączyć obsługę TCP w OpenHab.
W tym celu należy pobrać dodatkowe plugin'y (OpenHab AddOns), a następnie plik org.openhab.binding.tcp-1.4.0.jar (lub inna wersja) skopiować do katalogu addons z serwera OpenHab.
W pliku configurations/openhab_default.cfg należy odkomentować linię
tcp:port=25001
Dzięki temu OpenHab będzie nasłuchiwał na porcie 25001
Teraz można uruchomić OpenHab skryptem startup.sh (lub startup.bat w przypadku Windows) upewniając się wcześniej że mamy zainstalowaną odpowiednią wersję javy.
Po chwili pod adresem;
http://localhost:8080/openhab.app?sitemap=house
dostępne powinno być menu główne naszej konfiguracji.
To już cała konfiguracja po stronie OpenHab. Pozostaje kod źródłowy po stronie Arduino.
Odsyłam tutaj do wpisu http://technika-laika.blogspot.com/2014/04/arduino-sterowanie-przekaznikiem-za.html gdzie jest pierwotna wersja kodu źródłowego, dokładniejszy opis i wyjaśnienie nazewnictwa plików.
Jedyne zmiany które się pojawiają to zmiany komunikacji zwrotnej w pliku network.ino:
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
#include <SPI.h> // needed for Arduino versions later than 0018 | |
#include <Ethernet.h> | |
#include <EthernetUdp.h> // UDP library from: bjoern@cs.stanford.edu 12/30/2008 | |
byte mac[] = { | |
0xDE, 0xAD, 0xBE, 0xEF, 0xFE, 0xED | |
}; | |
IPAddress ip(192, 168, 0, 5); | |
IPAddress responseIp(192, 168, 0, 12); //IP number to send response (Ip of OpenHab Server) | |
unsigned int responsePort = 25001; // port to send response -set in OpenHab configuration | |
unsigned int localPort = 8888; // local port to listen on | |
EthernetServer server(localPort); | |
EthernetClient client; | |
char packetBuffer[UDP_TX_PACKET_MAX_SIZE]; //buffer to hold incoming packet, | |
char ReplyBuffer[] = "acknowledged"; // a string to send back | |
void initializeNetwork() { | |
Ethernet.begin(mac, ip); | |
} | |
void checkNetwork() { | |
EthernetClient client = server.available(); | |
if (client) { | |
String clientMsg =""; | |
while (client.connected()) { | |
if (client.available()) { | |
char c = client.read(); | |
clientMsg+=c;//store the recieved chracters in a string | |
if (c == '#') { | |
Serial.println("Message from Client:"+clientMsg);//print it to the serial | |
executeRemoteCommand(clientMsg); | |
client.stop(); | |
} | |
} | |
} | |
} | |
delay(10); | |
} | |
void executeRemoteCommand(String command) { | |
if (command == SALON_LED_REMOTE_COMMAND) { | |
toogleSalonLed(); | |
} | |
} | |
void sendTcpState(String command) { | |
if (client.connect(responseIp, responsePort)) { | |
Serial.println("connected");//report it to the Serial | |
Serial.println("sending Message:"+command);//log to serial | |
client.print(command);//send the message | |
client.stop(); | |
} | |
else { | |
// if you didn't get a connection to the server: | |
Serial.println("connection failed"); | |
} | |
} |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
String SALON_LED_REMOTE_COMMAND("salonled#"); | |
const int SALON_LED_BUTTON_PIN = 22; | |
const int SALON_LED_RELAY_PIN = 23; | |
const int SALON_LED_AFTER_CHANGE_DELAY = 500; | |
int salonLedButtonState; | |
int salonLedState = LOW; | |
void initializeSalonLed() { | |
pinMode(SALON_LED_BUTTON_PIN, INPUT); | |
pinMode(SALON_LED_RELAY_PIN, OUTPUT); | |
setSalonLedState(HIGH); | |
} | |
void checkSalonLed() { | |
salonLedButtonState = digitalRead(SALON_LED_BUTTON_PIN); | |
if (salonLedButtonState == HIGH) { | |
toogleSalonLed(); | |
delay(SALON_LED_AFTER_CHANGE_DELAY); | |
} | |
} | |
void setSalonLedState(int state) { | |
digitalWrite(SALON_LED_RELAY_PIN, state); | |
salonLedState = state; | |
sendTcpState(SALON_LED_REMOTE_COMMAND + state); | |
} | |
void toogleSalonLed() { | |
if (salonLedState == LOW) { | |
setSalonLedState(HIGH); | |
} else { | |
setSalonLedState(LOW); | |
} | |
} |
Generalnie zamierzony cel został osiągnięty, natomiast problemy na które się natknąłem, konieczność zrywania połączenia po obsłudze komunikatu, opóźnienia przy wznawianiu połączenia przez OpenHab powodują że nie polecam takiego rozwiązania w warunkach produkcyjnych. Być może ktoś z Was poradził sobie z tymi problemami. Jeśli tak, proszę o informację w komentarzach. Dużo ciekawiej zapowiada się komunikacja za pomocą mqtt którą postaram się zademonstrować w kolejnym wpisie.