W innym wątku dotyczącym OpenHABa pisałem o komunikacji z WAGO bez użycia modbusa. Poniżej przedstawiam moje rozwiązanie tego problemu. Być może modbus byłby lepszy ale nigdy go nie sprawdziłem w akcji. To co poniżej działało i działa dobrze od 2 lat.
Środowisko wygląda tak:
- RaspberryPi z uruchomionym serwerem nginx + PHP + MySQL
- Na tym samym urządzeniu RPi działa również OpenHAB
Serwer www zawiera takie skrypty:
/wago/wago_lookup_refresh.php
/wago/wago_get-var-list.php
/wago/wago_set-var.php
Na sterowniku WAGO (750-880) mam dodaną wizualizację o nazwie "openhab", do której dodaję zmienne, które chcę czytać/zapisywać z openhaba. Wystarczy dodać zwykłe pole tekstowe, a w definicji użyć zmiennej, którą chcemy wyświetlić w tym polu. To wystarczy. Sugeruję nie dodawać nic więcej, bo wizualizacja dość szybko rośnie zajmując cenne miejsce na sterowniku.
Wago stworzy z tego plik openhab.xml dostępny przez WWW lub FTP. Plik będzie dostępny pod adresem:
http://WAGO_IP/plc/openhab.xmlZawartość pliku może (ale nie musi) zmieniać się przy kompilacji programu na sterowniku WAGO. To z kolei może powodować, że określone zmienne zmienią swoje adresy. Konieczne jest odświeżanie listy zmiennych (i ich adresów) w sposób cykliczny lub na żądanie (wtedy trzeba to zrobić ręcznie po kompilacji programu na PLC). U mnie dzieje się to cyklicznie co 5 sekund.
W tym celu wywołuję skrypt PHP "wago_lookup_refresh.php":
<?php
/* SETUP */
$log = false;
$wago_ip = '192.168.1.5';
if ($log) $time_start = microtime(true);
// get a list of visualizations to be parsed
$visu = isset($_GET['visu']) ? $_GET['visu'] : 'plc_visu';
$visu_pages = explode (',', $visu);
$output = array();
// process each visualization, looking for variable list
foreach ($visu_pages as $xml_page)
{
// get XML description for visu page
$f = curl_get('http://'.$wago_ip.'/plc/'.$xml_page.'.xml');
// extract all variable names with memory addresses
preg_match_all("'<variable name=\"(.*?)\">(.*?)</variable>'si", $f, $variablelist, PREG_SET_ORDER);
// create human readable and serialized output files
foreach ($variablelist as $var)
{
$output[] = $var[1].';'.str_replace(',','|',$var[2].'|'.chr(10));
$serial[$var[1]] = '|'.str_replace(',','|',$var[2].'|');
}
}
// sort the arrays (for quicker lookup)
sort($output);
ksort($serial);
// save the files if different
if (file_get_contents('wago_variables.csv') != $output)
{
file_put_contents('wago_variables.csv', $output);
file_put_contents('wago_variables.txt', serialize($serial));
}
// if logging enabled - put some data on the screen
if ($log)
{
$time_end = microtime(true);
$time = $time_end - $time_start ;
echo "<pre>";
echo 'Execution time : ' . $time . ' seconds'.chr(10) ;
print_r ($serial);
echo "</pre>";
$f = file_put_contents('time.log',file_get_contents ('time.log').chr(10).$time_start);
}
// curl web access
function curl_get($url)
{
$defaults = array(
CURLOPT_URL => $url,
CURLOPT_HEADER => 0,
CURLOPT_RETURNTRANSFER => TRUE,
CURLOPT_TIMEOUT => 4
);
$ch = curl_init();
curl_setopt_array($ch, $defaults);
if( ! $result = curl_exec($ch))
{
trigger_error(curl_error($ch));
}
curl_close($ch);
return $result;
}
?>
Skrypt tworzy plik tekstowy "wago_variables.txt" i "wago_variables.csv". W pierwszym (TXT) jest zapisana tablica dla PHP do szybkiego wyszukiwania zmiennych dla potrzeb kolejnych skryptów. Natomiast w CSV są wylistowane wszystkie zmienne w czytelnej dla oka postaci (można sobie to wyświetlić w przeglądarce i podejrzeć co tam aktualnie jest).
W obu przypadkach mamy wypisane pary zmienna + adres na sterowniku WAGO. Dobór rozszerzeń TXT/CSV jak widzę jest niefortunny ale tak to mam zrobione, coś pewnie w trakcie modyfikowałem i tak już zostało.
Należy zwrócić uwagę, aby jednorazowo ustawić uprawnienia do plików TXT/CSV, tak aby skrypt php mógł je tworzyć i modyfikować.
Aby lista zmiennych była zawsze aktualna (pliki "wago_variables"), to w OpenHAB ma dodany taki "rule" wywoływany co 5 sekund:
import org.openhab.io.net.actions.HTTP
rule wagorefresh
when
Time cron "0/5 * * * * ?"
then
var String HTTPString = "http://127.0.0.1/wago/wago_lookup_refresh.php?visu=openhab"
sendHttpGetRequest(HTTPString)
end
Podobne wywołanie skryptu może być z CRONa linuxowego, to wcale nie musi być OpenHAB. Może to też być ręczne wejście na link z powyższego skryptu po każdej kompilacji programu na WAGO.
W efekcie mamy 2 lokalne pliki (na serwerze WWW) z zawsze aktualnymi adresami zmiennych na sterowniku WAGO.
Aby pobrać listę wszystkich zmiennych wraz z ich wartościami należy wywołać kolejny skrypt PHP "wago_get-var-list.php"
<?php
$time_start = microtime(true);
$wago_ip = '192.168.1.5';
$output = 'json';
$var = isset($_GET['var']) ? $_GET['var'] : 'ALL';
$var_file = file_get_contents('wago_variables.txt');
$var_lookup = unserialize($var_file);
if ($var == 'ALL')
$vars = array_keys($var_lookup);
else
$vars = explode (',', $var);
$var_count = sizeof($vars);
$post = '|0|'.$var_count.'|';
for ($i=0; $i<$var_count; $i++)
{
$post .= $i.$var_lookup[$vars[$i]];
}
$wago_response = curl_post('http://'.$wago_ip.'/plc/webvisu.htm', $post);
$wago_response = explode ('|', $wago_response);
$out = '';
if ($output=='json')
{
$out = '{'.chr(10);
for ($i=1; $i<=$var_count; $i++)
{
// do transformations if needed
if ($vars[$i-1] == '.OH_TEST') $wago_response[$i] = transform_OnOff2Boolean($wago_response[$i]);
// output variables
$out .= ' "'.$vars[$i-1].'": "'.$wago_response[$i].'"';
if ($i<$var_count) $out .= ',';
$out .= chr(10);
}
$out .= '}'.chr(10);
}
else
{
for ($i=1; $i<=$var_count; $i++)
$out .= $vars[$i-1].'='.$wago_response[$i].chr(10);
}
echo $out;
function transform_OnOff2Boolean ($val)
{
return ($val==1) ? 'ON' : 'OFF';
}
function curl_post($url, $post)
{
$ch = curl_init();
curl_setopt($ch, CURLOPT_URL, $url);
curl_setopt($ch, CURLOPT_POST, 1);
curl_setopt($ch, CURLOPT_HEADER, 0);
curl_setopt($ch, CURLOPT_RETURNTRANSFER, 1);
curl_setopt($ch, CURLOPT_POSTFIELDS, $post);
$result = curl_exec ($ch);
curl_close ($ch);
return $result;
}
$time_end = microtime(true);
$time = $time_end - $time_start ;
//$f = file_put_contents('time.log',file_get_contents ('time.log').chr(10).$time_start);
//echo chr(10);
//echo 'Execution time : ' . $time . ' seconds' ;
?>
Wywołanie tego skryptu w przeglądarce wyświetli na ekranie plik JSON ze wszystkimi zmiennymi (i wartościami) dostępnymi w wizualizacji openhab.xml (na sterowniku WAGO).
W dalszym kroku mam dodane rozszerzenie (binding) HTTP w OpenHABie, a w http.cfg jest taki wpis:
WagoHttpBinding.url = http://127.0.0.1/wago/wago_get-var-list.php?var=ALL
WagoHttpBinding.updateInterval = 1000
Oznacza to tyle, że mamy do dyspozycji skrót "WagoHttpBinding" z którego możemy korzystać dalej w dowolnym miejscu w OpenHabie. W jego treści będzie zawartość pliku JSON z naszymi zmiennymi. Aby wczytać zmienną do OpenHABa w definicji zmiennych ("items") mam takie wpisy:
Switch Wago_oh_all_lights_off "oh_all_lights_off" <light> {autoupdate="false", http=">[ON:POST:http://127.0.0.1/wago/wago_set-var.php?var=SWIATLA.oh_all_lights_off&val=ON] >[OFF:POST:http://127.0.0.1/wago/wago_set-var.php?var=SWIATLA.oh_all_lights_off&val=OFF] <[WagoHttpBinding:500:JSONPATH($['SWIATLA.oh_all_lights_off'])]" }
Number Wago_oh_light_switch_dol_wiatrolap "oh_light_switch_dol_wiatrolap" <light> {autoupdate="true", http=">[1:POST:http://127.0.0.1/wago/wago_set-var.php?var=SWIATLA.oh_light_switch_dol_wiatrolap&val=1] >[2:POST:http://127.0.0.1/wago/wago_set-var.php?var=SWIATLA.oh_light_switch_dol_wiatrolap&val=2] >[3:POST:http://127.0.0.1/wago/wago_set-var.php?var=SWIATLA.oh_light_switch_dol_wiatrolap&val=3] >[0:POST:http://127.0.0.1/wago/wago_set-var.php?var=SWIATLA.oh_light_switch_dol_wiatrolap&val=0] <[WagoHttpBinding:500:JSONPATH($['SWIATLA.oh_light_switch_dol_wiatrolap'])]" }
Number Wago_oh_light_switch_dol_hol "oh_light_switch_dol_hol" <light> {autoupdate="true", http=">[1:POST:http://127.0.0.1/wago/wago_set-var.php?var=SWIATLA.oh_light_switch_dol_hol&val=1] >[2:POST:http://127.0.0.1/wago/wago_set-var.php?var=SWIATLA.oh_light_switch_dol_hol&val=2] >[3:POST:http://127.0.0.1/wago/wago_set-var.php?var=SWIATLA.oh_light_switch_dol_hol&val=3] >[0:POST:http://127.0.0.1/wago/wago_set-var.php?var=SWIATLA.oh_light_switch_dol_hol&val=0] <[WagoHttpBinding:500:JSONPATH($['SWIATLA.oh_light_switch_dol_hol'])]" }
Number Wago_oh_light_switch_dol_kuchnia "oh_light_switch_dol_kuchnia" <light> {autoupdate="true", http=">[1:POST:http://127.0.0.1/wago/wago_set-var.php?var=SWIATLA.oh_light_switch_dol_kuchnia&val=1] >[2:POST:http://127.0.0.1/wago/wago_set-var.php?var=SWIATLA.oh_light_switch_dol_kuchnia&val=2] >[0:POST:http://127.0.0.1/wago/wago_set-var.php?var=SWIATLA.oh_light_switch_dol_kuchnia&val=0] <[WagoHttpBinding:500:JSONPATH($['SWIATLA.oh_light_switch_dol_kuchnia'])]" }
Ilekroć zawartość zmiennej się zmienia, to OpenHAB wyłuska sobie z pliku JSON odpowiednią wartość i umieści w swoich zmiennych. Dalej możemy sobie robić z nimi co chcemy.
Aby zaktualizować zmienną w sterowniku WAGO należy wywołać skrypt "wago_set-var.php" z odpowiednimi parametrami.
Np.: wago_set-var.php?var=SWIATLA.oh_light_switch_dol_kuchnia&val=2
var to pełna ścieżka do zmiennej widoczna w pliku TXT/CSV, val to wartość jaką chcemy przypisać.
<?php
$time_start = microtime(true);
$wago_ip = '192.168.1.5';
$var = isset($_GET['var']) ? $_GET['var'] : '';
$val = isset($_GET['val']) ? $_GET['val'] : '';
$vars = explode (',', $var);
$var_file = file_get_contents('wago_variables.txt');
$var_lookup = unserialize($var_file);
$var_count = sizeof($vars);
$var_location = isset($var_lookup[$var]) ? $var_lookup[$var] : '';
if ($val == 'ON') $val = 1; // obsługa OpenHab
if ($var_location)
{
// BOOLEAN value, translate ON=>1, OFF=>0
if (substr($var_location,-2) == '0|') $val = ($val>0) ? '1' : '0';
$post = '|1|1|0'.$var_location.$val.'|';
$wago_response = curl_post('http://'.$wago_ip.'/plc/webvisu.htm', $post);
// echo 'Wago response: '.$wago_response;
// echo $post;
}
else
echo 'Unknown variable';
function curl_post($url, $post)
{
$ch = curl_init();
curl_setopt($ch, CURLOPT_URL, $url);
curl_setopt($ch, CURLOPT_POST, 1);
curl_setopt($ch, CURLOPT_HEADER, 0);
curl_setopt($ch, CURLOPT_RETURNTRANSFER, 1);
curl_setopt($ch, CURLOPT_POSTFIELDS, $post);
$result = curl_exec ($ch);
curl_close ($ch);
return $result;
}
$time_end = microtime(true);
$time = $time_end - $time_start ;
//echo '<br>Execution time : ' . $time . ' seconds' ;
?>
Powyższy kod jest w całości mojego autorstwa, choć pewnych rzeczy już nie pamiętam dlaczego zrobiłem to tak czy inaczej. W każdym razie w obecnej formie działa to dobrze i raczej nie potrzebuję tykać tych fragmentów kodu. Wklejam wszystko tak jak mam, niektóre fragmenty są wykomentowane i służyły do doraźnego debugowania skryptów.
Trochę zabawy na styku OpenHAB/WAGO wymaga ogarnięcie stanów zmiennych OH, które bywają przeróżne np. ON/OFF zamiast 0/1. Trzeba to odpowiednio obsługiwać w skryptach.
Jeszcze słowo odnośnie wydajności. W skryptach widać pozostałości po analizie czasu wykonanywania skryptów. Jak pamiętam udało mi się uzyskać stabilne działanie przy częstotliwości odświeżania wartości zmiennych około 600-700ms (WAGO->OpenHAB). Co było w zupełności wystarczające. Teraz mam to ustawione na 1000ms i szybciej nie potrzeba.
Natomiast wywołanie zmiany OpenHAB->WAGO odbywa się natychmiast bez jakiejkolwiek zauważalnej zwłoki. Kliknięcie przycisku w telefonie natychmiast wyłącza światło, tak samo szybko jak przycisk na ścianie.
Wykorzystanie powyższych skryptów PHP nie ogranicza się oczywiście do OpenHABa, można ich użyć z dowolnym innym narzędziem do wizualizacji. Można pokusić się o dopisanie kodu odpowiedzialnego za aktualizację brokera MQTT i w ten sposób korzystać ze zmiennych w innych aplikacjach. Możliwości nieograniczone.