88_xs1Bridge.pm 33 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386387388389390391392393394395396397398399400401402403404405406407408409410411412413414415416417418419420421422423424425426427428429430431432433434435436437438439440441442443444445446447448449450451452453454455456457458459460461462463464465466467468469470471472473474475476477478479480481482483484485486487488489490491492493494495496497498499500501502503504505506507508509510511512513514515516517518519520521522523524525526527528529530531532533534535536537538539540541542543544545546547548549550551552553554555556557558559560561562563564565566567568569570571572573574575576577578579580581582583584585586587588589590591592593594595596597598599600601602603604605606607608609610611612613614615616617618619620621622623624625626627628629630631632633634635636637638639640641642643644645646647648649650651652653654655656657658659660661662663664665666667668669670671672673674675676677678679680681682683684685686687688689690691692693694695696697698699700701702703704705706707708709710711712713714715716717718719720721722723724725726727728729730731732733734735736737738739740741742743744745746747748749750751752753754755756757758759760761762763764765766767768769770771772773774775776777778779780781782783784785786
  1. #################################################################
  2. # $Id: 88_xs1Bridge.pm 16394 2018-03-12 23:09:47Z HomeAuto_User $
  3. #################################################################
  4. # physisches Modul - Verbindung zur Hardware
  5. #
  6. # note / ToDo´s / Bugs:
  7. # - Port Check
  8. #
  9. #
  10. #
  11. #################################################################
  12. package main;
  13. # Laden evtl. abhängiger Perl- bzw. FHEM-Module
  14. use HttpUtils; # um Daten via HTTP auszutauschen https://wiki.fhem.de/wiki/HttpUtils
  15. use strict;
  16. use warnings; # Warnings
  17. use Net::Ping;
  18. my $missingModul = "";
  19. my $xs1_ConnectionTry = 1; # disable Funktion sobald 10x keine Verbindung (Schutzabschaltung)
  20. eval "use Encode qw(encode encode_utf8 decode_utf8);1" or $missingModul .= "Encode ";
  21. eval "use JSON;1" or $missingModul .= "JSON ";
  22. eval "use Net::Ping;1" or $missingModul .= "Net::Ping ";
  23. #$| = 1; #Puffern abschalten, Hilfreich für PEARL WARNINGS Search
  24. sub xs1Bridge_Initialize($) {
  25. my ($hash) = @_;
  26. $hash->{WriteFn} = "xs1Bridge_Write";
  27. $hash->{Clients} = ":xs1Dev:";
  28. $hash->{MatchList} = { "1:xs1Dev" => '[x][s][1][D][e][v][#][A][k][t][o][r]#[0-6][0-9].*|[x][s][1][D][e][v][#][S][e][n][s][o][r]#[0-6][0-9].*' }; ## https://regex101.com/ Testfunktion
  29. $hash->{DefFn} = "xs1Bridge_Define";
  30. $hash->{AttrFn} = "xs1Bridge_Attr";
  31. $hash->{UndefFn} = "xs1Bridge_Undef";
  32. $hash->{AttrList} = "debug:0,1 ".
  33. "disable:0,1 ".
  34. "ignore:0,1 ".
  35. "interval:30,60,180,360 ".
  36. "update_only_difference:0,1 ".
  37. "view_Device_name:0,1 ".
  38. "view_Device_function:0,1 ".
  39. "xs1_control:0,1 ";
  40. ##$readingFnAttributes; ## die Standardattribute von FHEM
  41. foreach my $d(sort keys %{$modules{xs1Bridge}{defptr}}) {
  42. my $hash = $modules{xs1Bridge}{defptr}{$d};
  43. }
  44. }
  45. sub xs1Bridge_Define($$) {
  46. my ($hash, $def) = @_;
  47. my @arg = split("[ \t][ \t]*", $def);
  48. my $name = $hash->{NAME}; ## Der Definitionsname, mit dem das Gerät angelegt wurde.
  49. my $typ = $hash->{TYPE}; ## Der Modulname, mit welchem die Definition angelegt wurde.
  50. my $debug = AttrVal($hash->{NAME},"debug",0);
  51. my $viewDeviceName = AttrVal($hash->{NAME},"view_Device_name",0);
  52. my $viewDeviceFunction = AttrVal($hash->{NAME},"view_Device_function",0);
  53. my $update_only_difference = AttrVal($hash->{NAME},"update_only_difference",0);
  54. # 0 1 2
  55. return "Usage: define <NAME> $name <IP>" if(@arg != 3);
  56. #return "Usage: define <NAME> $name <IP> <PORT>" if(@arg != 4);
  57. return "Your IP is not valid. Please Check!" if not($arg[2] =~ /[0-9]{1,3}[.][0-9]{1,3}[.][0-9]{1,3}[.][0-9]{1,3}/s);
  58. #return "Your PORT is not valid. Please Check!" if not($arg[3] =~ /[0-9]{2,5}/s);
  59. return "Cannot define xs1Bridge device. Perl modul ${missingModul}is missing." if ( $missingModul );
  60. my $xs1check = 0;
  61. if(!defined $modules{xs1Bridge}) {
  62. my $p = Net::Ping->new("tcp", 2);
  63. if(!($p->ping("$arg[2]", 2))) {
  64. $xs1check = 1;
  65. }
  66. $p->close();
  67. return "Your IP is not reachable. Please Check!" if ($xs1check == 1);
  68. }
  69. # Parameter Define
  70. my $xs1_ip = $arg[2]; ## Zusatzparameter 1 bei Define - ggf. nur in Sub
  71. $hash->{xs1_ip} = $xs1_ip;
  72. $hash->{STATE} = "Initialized"; ## Der Status des Modules nach Initialisierung.
  73. $hash->{TIME} = time(); ## Zeitstempel, derzeit vom anlegen des Moduls
  74. $hash->{VERSION} = "1.16"; ## Version
  75. $hash->{BRIDGE} = 1;
  76. # Attribut gesetzt
  77. $attr{$name}{disable} = "0";
  78. $attr{$name}{interval} = "60";
  79. $attr{$name}{room} = "xs1" if( not defined( $attr{$name}{room} ) );
  80. $attr{$name}{xs1_control} = "0";
  81. $modules{xs1Bridge}{defptr}{BRIDGE} = $hash;
  82. InternalTimer(gettimeofday()+$attr{$name}{interval}, "xs1Bridge_GetUpDate", $hash); ## set Timer
  83. Log3 $name, 3, "$typ: Modul defined with xs1_ip: $xs1_ip";
  84. if(!defined($defs{'FileLog_xs1Bridge'})) { ## Logfile existent check
  85. fhem("define FileLog_xs1Bridge FileLog ./log/xs1Bridge-%Y-%m.log ".$arg[0]); ## Logfile define
  86. fhem("attr FileLog_xs1Bridge room xs1"); ## Logfile in xs1 room
  87. }
  88. return undef;
  89. }
  90. sub xs1Bridge_Attr(@) {
  91. my ($cmd,$name,$attrName,$attrValue) = @_;
  92. my $hash = $defs{$name};
  93. my $typ = $hash->{TYPE};
  94. my $interval = 0;
  95. my $debug = AttrVal($hash->{NAME},"debug",0);
  96. my $viewDeviceName = AttrVal($hash->{NAME},"view_Device_name",0);
  97. my $viewDeviceFunction = AttrVal($hash->{NAME},"view_Device_function",0);
  98. my $update_only_difference = AttrVal($hash->{NAME},"update_only_difference",0);
  99. my $xs1_control = AttrVal($hash->{NAME},"xs1_control",0);
  100. # $cmd - Vorgangsart - kann die Werte "del" (löschen) oder "set" (setzen) annehmen
  101. # $name - Gerätename
  102. # $attrName/$attrValue sind Attribut-Name und Attribut-Wert
  103. #### Handling bei set .. attribute
  104. if ($cmd eq "set") {
  105. RemoveInternalTimer($hash); ## Timer löschen
  106. Debug " $typ: Attr | Cmd:$cmd | RemoveInternalTimer" if($debug);
  107. if ($attrName eq "interval") { ## Abfrage Attribute
  108. if (($attrValue !~ /^\d*$/) || ($attrValue < 10))
  109. {
  110. return "$typ: Interval is too small. Please define new Interval | (at least: 10 seconds)";
  111. }
  112. my $interval = $attrValue;
  113. }
  114. elsif ($attrName eq "disable") {
  115. if ($attrValue eq "1") { ## Handling bei attribute disable 1
  116. readingsSingleUpdate($hash, "state", "deactive", 1);
  117. }
  118. elsif ($attrValue eq "0") { ## Handling bei attribute disable 0
  119. $xs1_ConnectionTry = 1;
  120. readingsSingleUpdate($hash, "state", "active", 1);
  121. }
  122. }elsif ($attrName eq "view_Device_function") {
  123. if ($attrValue eq "1") { ## Handling bei attribute view_Device_function 1
  124. Log3 $name, 3, "$typ: Attribut view_Device_function $cmd to $attrValue";
  125. }
  126. elsif ($attrValue eq "0") { ## Handling bei attribute view_Device_function 0
  127. Log3 $name, 3, "$typ: Attribut view_Device_function $cmd to $attrValue";
  128. }
  129. }elsif ($attrName eq "view_Device_name") {
  130. if ($attrValue eq "1") { ## Handling bei attribute view_Device_name 1
  131. Log3 $name, 3, "$typ: Attribut view_Device_name $cmd to $attrValue";
  132. }
  133. elsif ($attrValue eq "0") { ## Handling bei attribute view_Device_name 0
  134. Log3 $name, 3, "$typ: Attribut view_Device_name $cmd to $attrValue";
  135. for my $i (0..64) {
  136. delete $hash->{READINGS}{"Aktor_".sprintf("%02d", $i)."_name"} if($hash->{READINGS});
  137. delete $hash->{READINGS}{"Sensor_".sprintf("%02d", $i)."_name"} if($hash->{READINGS});
  138. }
  139. }
  140. }elsif ($attrName eq "update_only_difference") {
  141. if ($attrValue eq "1") { ## Handling bei attribute update_only_difference 1
  142. Log3 $name, 3, "$typ: Attribut update_only_difference $cmd to $attrValue";
  143. }
  144. elsif ($attrValue eq "0") { ## Handling bei attribute update_only_difference 0
  145. Log3 $name, 3, "$typ: Attribut update_only_difference $cmd to $attrValue";
  146. for my $i (0..64) {
  147. delete $hash->{READINGS}{"Aktor_".sprintf("%02d", $i)."_name"} if($hash->{READINGS});
  148. }
  149. }
  150. }elsif ($attrName eq "xs1_control") {
  151. if ($attrValue eq "1") { ## Handling bei attribute xs1_control 1
  152. if(! $modules{xs1Dev}) { ## Check Modul vorhanden
  153. $attr{$name}{xs1_control} = "0";
  154. return "Module xs1Dev is non-existent or still under development. Please wait"
  155. }
  156. }
  157. }
  158. }
  159. #### Handling bei del ... attribute
  160. if ($cmd eq "del") {
  161. if ($attrName eq "disable" && !defined $attrValue) {
  162. readingsSingleUpdate($hash, "state", "active", 1);
  163. Debug " $typ: Attr | Cmd:$cmd | $attrName=$attrValue" if($debug);
  164. }
  165. elsif ($attrName eq "interval") {
  166. RemoveInternalTimer($hash);
  167. Debug " $typ: Attr | Cmd:$cmd | $attrName" if($debug);
  168. }
  169. elsif ($attrName eq "view_Device_function") {
  170. Log3 $name, 3, "$typ: Attribut view_Device_function delete";
  171. for my $i (0..64) {
  172. for my $i2 (1..4) {
  173. delete $hash->{READINGS}{"Aktor_".sprintf("%02d", $i)."_function_".$i2} if($hash->{READINGS});
  174. }
  175. }
  176. }
  177. elsif ($attrName eq "view_Device_name") {
  178. Log3 $name, 3, "$typ: Attribut view_Device_name delete";
  179. for my $i (0..64) {
  180. delete $hash->{READINGS}{"Aktor_".sprintf("%02d", $i)."_name"} if($hash->{READINGS});
  181. delete $hash->{READINGS}{"Sensor_".sprintf("%02d", $i)."_name"} if($hash->{READINGS});
  182. }
  183. }
  184. elsif ($attrName eq "update_only_difference") {
  185. Log3 $name, 3, "$typ: Attribut update_only_difference delete";
  186. }
  187. }
  188. #### Handling bei state active
  189. if ($hash->{STATE} eq "active") {
  190. RemoveInternalTimer($hash);
  191. InternalTimer(gettimeofday()+$interval, "xs1Bridge_GetUpDate", $hash);
  192. Debug " $typ: Attr | RemoveInternalTimer + InternalTimer" if($debug);
  193. }
  194. return undef;
  195. }
  196. sub xs1Bridge_GetUpDate() {
  197. my ($hash) = @_;
  198. my $name = $hash->{NAME};
  199. my $typ = $hash->{TYPE};
  200. my $state = $hash->{STATE};
  201. my $xs1_ip = $hash->{xs1_ip};
  202. my $xs1_uptimeStart = $hash->{helper}{xs1_uptimeStart};
  203. my $xs1_uptimeOld = $hash->{helper}{xs1_uptimeOld};
  204. my $xs1_uptimeNew = $hash->{helper}{xs1_uptimeNew};
  205. my $def;
  206. #http://x.x.x.x/control?callback=cname&cmd=...
  207. #get_list_actuators - list all actuators i0
  208. #get_list_sensors - list all sensors i1
  209. #get_list_timers - list all timers i3
  210. #get_config_info - list all device info´s i2
  211. #get_protocol_info - list protocol info´s
  212. my $cmd = "/control?callback=cname&cmd=";
  213. my @cmdtyp = ("get_list_actuators","get_list_sensors","get_config_info","get_list_timers","get_list_actuators");
  214. my @arrayname = ("actuator","sensor","info","timer","function");
  215. my @readingsname = ("Aktor","Sensor","","Timer","");
  216. my $debug = AttrVal($hash->{NAME},"debug",0);
  217. my $disable = AttrVal($name, "disable", 0);
  218. my $interval = AttrVal($name, "interval", 60);
  219. my $viewDeviceName = AttrVal($hash->{NAME},"view_Device_name",0);
  220. my $viewDeviceFunction = AttrVal($hash->{NAME},"view_Device_function",0);
  221. my $update_only_difference = AttrVal($hash->{NAME},"update_only_difference",0);
  222. my $xs1_control = AttrVal($hash->{NAME},"xs1_control",0);
  223. #### xs1Bridge disable Option = 0 -> aktiviert zum auslesen
  224. if (AttrVal($hash->{NAME},"disable",0) == 0 && $xs1_ConnectionTry <= 5) {
  225. RemoveInternalTimer($hash); ## Timer löschen
  226. InternalTimer(gettimeofday()+$interval, "xs1Bridge_GetUpDate", $hash);
  227. Debug " -------------- ERROR CHECK - START --------------" if($debug);
  228. Debug " $typ: GetUpDate | RemoveInternalTimer + InternalTimer" if($debug);
  229. #Log3 $name, 3, "$typ: xs1Bridge_GetUpDate | RemoveInternalTimer + InternalTimer";
  230. if ($state eq "Initialized") {
  231. readingsSingleUpdate($hash, "state", "active", 1);
  232. }
  233. my $xs1Dev_check = "ERROR";
  234. #if($modules{xs1Dev} && $modules{xs1Dev}{LOADED}) { ## Check Modul vorhanden + geladen
  235. if($modules{xs1Dev}) { ## Check Modul vorhanden
  236. $xs1Dev_check = "ok";
  237. Debug " $typ: GetUpDate | Modul xs1Dev_check = $xs1Dev_check" if($debug);
  238. } else {
  239. Debug " $typ: GetUpDate ERROR | Modul xs1Dev not existent! Please check it to be available!" if($debug);
  240. #Log3 $name, 3, "$typ: GetUpDate | xs1Dev_check = $xs1Dev_check";
  241. }
  242. #### JSON Abfrage - Schleife
  243. for my $i (0..3) {
  244. #### HTTP Requests #### Start ####
  245. my $connection;
  246. my $Http_err = "";
  247. my $Http_data = "";
  248. my $param = {
  249. url => "http://".$xs1_ip.$cmd.$cmdtyp[$i],
  250. timeout => 3,
  251. method => "GET", # Lesen von Inhalten
  252. };
  253. HttpUtils_BlockingGet($param);
  254. ($Http_err, $Http_data) = HttpUtils_BlockingGet($param);
  255. #### HTTP Requests #### END ####
  256. my $adress = "http://".$xs1_ip.$cmd.$cmdtyp[$i];
  257. my $json;
  258. my $json_utf8;
  259. my $decoded;
  260. Debug " $typ: GetUpDate | Adresse: $adress | xs1_ConnectionTry=$xs1_ConnectionTry" if($debug && $Http_err eq "");
  261. Debug " $typ: GetUpDate | HTTP request: ".$Http_err."| xs1_ConnectionTry=$xs1_ConnectionTry" if($debug && $Http_err ne "");
  262. #### HTTP Requests, ERROR
  263. if ($Http_err ne "") {
  264. # ERROR Message
  265. # http://192.168.2.5/control?callback=cname&cmd=get_list_actuators: Can't connect(1) to http://192.168.2.5:80: IO::Socket::INET: connect: No route to host
  266. # http://192.168.2.5/control?callback=cname&cmd=get_config_info: empty answer received
  267. # http://192.168.2.5/control?callback=cname&cmd=get_config_info: Select timeout/error:
  268. #($Http_err) = $Http_err =~ /[:]\s.*/g;
  269. Log3 $name, 3, "$typ: GetUpDate | Try=$xs1_ConnectionTry loop=$i | Error: ".$Http_err;
  270. $xs1_ConnectionTry++;
  271. last; ## Abbruch Schleife
  272. }
  273. #### HTTP Requests, OK dann ARRAY Verarbeitung
  274. elsif ($Http_data ne "") {
  275. ($json) = $Http_data =~ /[^(]*[}]/g; ## cut cname( + ) am Ende von Ausgabe -> ARRAY Struktur als Antwort vom xs1
  276. $json_utf8 = eval {encode_utf8( $json )}; ## UTF-8 character Bearbeitung, da xs1 TempSensoren ERROR
  277. $decoded = eval {decode_json( $json_utf8 )};
  278. $xs1_ConnectionTry = 1;
  279. #### xs1 Aktoren / Sensoren als Readings
  280. if ($i <= 1 ) {
  281. my $xs1_data;
  282. my @array;
  283. if (defined $decoded->{$arrayname[$i]}) {
  284. @array = @{ $decoded->{$arrayname[$i]} };
  285. } else {
  286. Log3 $name, 3, "$typ: GetUpDate | ARRAY-ERROR xs1 -> no Data in loop $i";
  287. last;
  288. }
  289. my $i3 = 0; ## Counter für real Werte in xs1, da sonst Verschiebungen wenn User ID´s verschiebt, NOTWENDIG!
  290. foreach my $f ( @array ) {
  291. $i3++;
  292. if ($f->{"type"} ne "disabled") {
  293. my $xs1Dev = "xs1Dev";
  294. #### Aktoren spezifisch
  295. my $xs1_function1 = "-";
  296. my $xs1_function2 = "-";
  297. my $xs1_function3 = "-";
  298. my $xs1_function4 = "-";
  299. if ($i == 0) {
  300. #### xs1 Aktoren nur update bei differenten Wert
  301. if ($update_only_difference == 1) {
  302. my $oldState = ReadingsVal($name, $readingsname[$i]."_".sprintf("%02d", $i3), "unknown"); ## Readings Wert
  303. my $newState = sprintf("%.1f" , $f->{"value"}); ## ARRAY Wert xs1 aktuell
  304. Debug " $typ: ".$readingsname[$i]."_".sprintf("%02d", $i3)." oldState=$oldState newState=$newState" if($debug);
  305. if ($oldState ne $newState) {
  306. readingsSingleUpdate($hash, $readingsname[$i]."_".sprintf("%02d", $i3) , $newState, 0);
  307. }
  308. }
  309. #### xs1 Aktoren / Funktion != disable
  310. my @array2 = @{ $decoded->{'actuator'}->[$i3-1]->{$arrayname[4]} };
  311. my $i2 = 0; ## Funktionscounter
  312. foreach my $f2 ( @array2 ) {
  313. $i2++;
  314. #### xs1 Option - Ansicht Funktionsname
  315. if ($viewDeviceFunction == 1) {
  316. my $oldState = ReadingsVal($name, $readingsname[$i]."_".sprintf("%02d", $i3)."_".$arrayname[4]."_".$i2, "unknown"); ## Readings Wert
  317. my $newState = $f2->{'type'}; ## ARRAY Wert xs1 aktuell
  318. if ($oldState ne "unknown" && $newState eq "disabled") { ## FunktionReading del bei disable
  319. Debug " $typ: "."Aktor_".sprintf("%02d", $i3)."_function_".$i2." are disabled" if($debug);
  320. delete $hash->{READINGS}{"Aktor_".sprintf("%02d", $i3)."_function_".$i2} if($hash->{READINGS});
  321. }
  322. if ($f2->{"type"} ne "disabled") { ## Funktion != function -> type disable
  323. if ($oldState ne $newState) {
  324. readingsSingleUpdate($hash, $readingsname[$i]."_".sprintf("%02d", $i3)."_".$arrayname[4]."_".$i2 , $f2->{"type"} , 1);
  325. }
  326. }
  327. }
  328. #### Funktion != function -> type disable
  329. if ($f2->{"type"} ne "disabled") {
  330. if ($i2 == 1) {
  331. $xs1_function1 = $f2->{"type"};
  332. }elsif ($i2 == 2) {
  333. $xs1_function2 = $f2->{"type"};
  334. }elsif ($i2 == 3) {
  335. $xs1_function3 = $f2->{"type"};
  336. }elsif ($i2 == 4) {
  337. $xs1_function4 = $f2->{"type"};
  338. }
  339. }
  340. }
  341. }
  342. #### Value der Aktoren | Sensoren
  343. if ($i == 1 || $i == 0 && $update_only_difference == 0) { # Aktoren | Sensoren im intervall - Format 0.0 bzw. 37.0 wie aus xs1
  344. readingsSingleUpdate($hash, $readingsname[$i]."_".sprintf("%02d", $i3) , sprintf("%.1f" , $f->{"value"}), 0);
  345. $xs1_data = $xs1Dev."#".$readingsname[$i]."#".sprintf("%02d", $i3)."#".$f->{"type"}."#".sprintf("%.1f" , $f->{"value"})."#"."$xs1_function1"."#"."$xs1_function2"."#"."$xs1_function3"."#"."$xs1_function4"."#".$f->{"name"};
  346. } elsif ($i == 0 && $update_only_difference == 1){ # Aktoren | nur bei DIFF - Format 0.0 bzw. 37.0 wie aus xs1
  347. $xs1_data = $xs1Dev."#".$readingsname[$i]."#".sprintf("%02d", $i3)."#".$f->{"type"}."#".sprintf("%.1f" , $f->{"value"})."#"."$xs1_function1"."#"."$xs1_function2"."#"."$xs1_function3"."#"."$xs1_function4"."#".$f->{"name"};
  348. }
  349. #### Ausgaben je Typ unterschiedlich !!!
  350. Debug " $typ: ".$readingsname[$i]."_".sprintf("%02d", $i3)." | ".$f->{"type"}." | ".$f->{"name"}." | ". $f->{"value"}." | "."F1 $xs1_function1 | F2 $xs1_function2 | F3 $xs1_function3 | F4 $xs1_function4" if($debug == 1 && $i == 0);
  351. Debug " $typ: ".$readingsname[$i]."_".sprintf("%02d", $i3)." | ".$f->{"type"}." | ".$f->{"name"}." | ". $f->{"value"} if($debug == 1 && $i != 0);
  352. ### Ansicht Namen der Aktoren | Sensoren als Readings
  353. if ($viewDeviceName == 1) {
  354. my $oldState = ReadingsVal($name, $readingsname[$i]."_".sprintf("%02d", $i3)."_name", "unknown"); ## Readings Wert
  355. my $newState = $f->{"name"}; ## ARRAY Wert xs1 aktuell
  356. if ($oldState ne $newState) { ## Namen nur bei Änderung schreiben
  357. #Log3 $name, 3, "$typ: GetUpDate | newState=$newState ne oldState=$oldState";
  358. readingsSingleUpdate($hash, $readingsname[$i]."_".sprintf("%02d", $i3)."_name" , $f->{"name"} , 1);
  359. }
  360. }
  361. ### Dispatch an xs1Device Modul
  362. if ($xs1Dev_check eq "ok" && $xs1_control == 1) {
  363. Debug " $typ: GetUpDate | Dispatch: $xs1_data" if($debug);
  364. Dispatch($hash,$xs1_data,undef) if($xs1_data);
  365. }
  366. } else {
  367. #### ID bzw. Speicherplatz xs1 ist disabled | Reading are delete
  368. delete $hash->{READINGS}{$readingsname[$i]."_".sprintf("%02d", $i3)} if($hash->{READINGS});
  369. delete $hash->{READINGS}{$readingsname[$i]."_".sprintf("%02d", $i3)."_name"} if($hash->{READINGS});
  370. if ($i == 0) {
  371. for my $count (1..4) {
  372. delete $hash->{READINGS}{$readingsname[$i]."_".sprintf("%02d", $i3)."_function_".$count} if($hash->{READINGS});
  373. }
  374. }
  375. }
  376. }
  377. }
  378. #### xs1 Info´s nur bei uptime Änderung als Readings
  379. elsif ($i == 2) {
  380. my $features;
  381. my $features_i=0;
  382. my @xs1_readings = ("xs1_start","xs1_devicename","xs1_bootloader","xs1_hardware","xs1_features","xs1_firmware","xs1_mac","xs1_dhcp");
  383. my @xs1_decoded = (FmtDateTime(time()-($decoded->{'info'}{'uptime'})) , $decoded->{'info'}{'devicename'} , $decoded->{'info'}{'bootloader'} , $decoded->{'info'}{'hardware'} , $features , $decoded->{'info'}{'firmware'} , $decoded->{'info'}{'mac'} , $decoded->{'info'}{'autoip'});
  384. my $oldState = ReadingsVal($name, $xs1_readings[0], "2000-01-01 03:33:33"); ## Readings Wert
  385. my @oldstate = split (/[-,:,\s\/]/, $oldState); ## Split $year, $month, $mday, $hour, $min, $sec
  386. $oldState = fhemTimeGm($oldstate[5], $oldstate[4], $oldstate[3], $oldstate[2], $oldstate[1]-1, $oldstate[0]-1900); ## Verarbeitung $sec, $min, $hour, $mday, $month-1, $year-1900
  387. my $newState = FmtDateTime(time()-($decoded->{'info'}{'uptime'})); ## ARRAY uptime Wert xs1 aktuell
  388. my @newState = split (/[-,:,\s\/]/, $newState); ## Split $year, $month, $mday, $hour, $min, $sec
  389. $newState = fhemTimeGm($newState[5], $newState[4], $newState[3], $newState[2], $newState[1]-1, $newState[0]-1900); ## Verarbeitung $sec, $min, $hour, $mday, $month-1, $year-1900
  390. #### Vergleich mit 5 Sekunden Tolleranz je Verarbeitungszeit Netzwerk | DLAN | CPU
  391. if (abs($oldState - $newState) > 5 || $debug == 1) {
  392. readingsBeginUpdate($hash);
  393. for my $i2 (0..7) {
  394. if ($i2 == 4) {
  395. while (defined $decoded->{'info'}{'features'}->[$features_i]) {
  396. $features.= $decoded->{'info'}{'features'}->[$features_i]." ";
  397. $features_i++;
  398. }
  399. $xs1_decoded[4] = $features; ## ARRAY Wert xs1_decoded wird definiert
  400. }
  401. if (defined $xs1_decoded[$i2]) {
  402. readingsBulkUpdate($hash, $xs1_readings[$i2] , $xs1_decoded[$i2]);
  403. Debug " $typ: ".$xs1_readings[$i2].": ".$xs1_decoded[$i2] if($debug);
  404. } else {
  405. Log3 $name, 3, "$typ: GetUpDate | ARRAY-ERROR xs1 -> no Data in loop $i|$i2";
  406. last;
  407. }
  408. }
  409. readingsEndUpdate($hash, 1);
  410. }
  411. }
  412. #### xs1 Timers als Readings
  413. elsif ($i == 3) {
  414. my @array = @{ $decoded->{$arrayname[$i]} };
  415. foreach my $f ( @array ) {
  416. my $oldState = ReadingsVal($name, $readingsname[$i]."_".sprintf("%02d", $f->{"id"}), "unknown"); ## Readings Wert
  417. my $newState = FmtDateTime($f->{"next"}); ## ARRAY Wert xs1 aktuell
  418. if ($f->{"type"} ne "disabled") {
  419. if ($oldState ne $newState) { ## Update Reading nur bei Wertänderung
  420. readingsSingleUpdate($hash, $readingsname[$i]."_".sprintf("%02d", $f->{"id"}) , FmtDateTime($f->{"next"}), 1);
  421. }
  422. Debug " $typ: ".$readingsname[$i]."_".sprintf("%02d", $f->{"id"})." | ".$f->{"name"}." | ".$f->{"type"}." | ". $f->{"next"} if($debug);
  423. } elsif ($oldState ne "unknown") { ## deaktive Timer mit Wert werden als Reading entfernt
  424. Log3 $name, 3, "$typ: GetUpDate | ".$readingsname[$i]."_".sprintf("%02d", $f->{"id"})." is deactive in xs1";
  425. delete $defs{$name}{READINGS}{$readingsname[$i]."_".sprintf("%02d", $f->{"id"})};
  426. }
  427. }
  428. }
  429. if ($i < 3) {
  430. Debug " --------------- ERROR CHECK - SUB --------------- " if($debug);
  431. }
  432. ### Schleifen Ende ###
  433. }
  434. }
  435. Debug " ------------- ERROR CHECK - ALL END -------------\n " if($debug);
  436. }
  437. if ($xs1_ConnectionTry == 6) { ## Abschaltung xs1 nach 10 Verbindungsversuchen
  438. $attr{$name}{disable} = "1";
  439. readingsSingleUpdate($hash, "state", "deactive", 1);
  440. RemoveInternalTimer($hash); ## Timer löschen
  441. Log3 $name, 3, "$typ: GetUpDate | connection ERROR -> xs1 set to disable! Device not reachable after 10 attempts";
  442. }
  443. }
  444. sub xs1Bridge_Write($) ## Zustellen von Daten via IOWrite() vom logischen zum physischen Modul
  445. {
  446. my ($hash, $Aktor_ID, $xs1_typ, $cmd, $cmd2) = @_;
  447. my $name = $hash->{NAME};
  448. my $typ = $hash->{TYPE};
  449. my $xs1_ip = $hash->{xs1_ip};
  450. ## Anfrage (Client -> XS1): http://192.168.1.242/control?callback=cname&cmd=set_state_actuator&number=1&value=100
  451. ## Aktor Typen aus xs1: (notwendig zur Verarbeitung)
  452. ## -------------------------------------------------
  453. ## blind - Jalousie | dimmer - Dimmer | door - Tür | disabled - deaktivert
  454. ## switch - Schalter | shutter - Rolladen | sound - Ton | sun-blind - Markise
  455. ## temperature - Temperatur | timerswitch - Zeitschalter | window - Fenster
  456. ## Sensor Typen (Auswahl) aus xs1: (nur Info)
  457. ## ------------------------------------------
  458. ## alarmmat - Alarmmatte | disabled - deaktivert
  459. ## gas_butan - Gasmelder Butan | gas_peak - Gas Spitzenwert
  460. ## mail - Briefmelder | motion - Bewegung
  461. ## other - Andere | presence - Anwesenheit
  462. ## pwr_consump - Energiezähler | pwr_peak - Energie Spitzenwert
  463. ## soilmoisture - Bodenfeuchte | soiltemp - Bodentemperatur
  464. ## leafwetness - Blattfeuchte | remotecontrol - Fernbedienung
  465. ## windowopen - Fenstermelder ...
  466. $Aktor_ID = substr($Aktor_ID, 1,2);
  467. my $xs1cmd;
  468. #### xs1 Typ switch || shutter || timerswitch - Anpassung Sendebefehl
  469. if ($xs1_typ eq "switch" || $xs1_typ eq "shutter" || $xs1_typ eq "timerswitch") {
  470. $xs1cmd = "http://$xs1_ip/control?callback=cname&cmd=set_state_actuator&number=$Aktor_ID&$cmd2";
  471. } elsif ($xs1_typ eq "dimmer") {
  472. if ($cmd eq "off") {
  473. $cmd = 0;
  474. }
  475. $xs1cmd = "http://$xs1_ip/control?callback=cname&cmd=set_state_actuator&number=$Aktor_ID&value=$cmd";
  476. } else {
  477. #### keine Verarbeitung zum senden ####
  478. Log3 $name, 3, "$typ: Write | $xs1_typ not control xs1. Please inform me!";
  479. last;
  480. }
  481. ### HTTP Requests #### Start ####
  482. my $connection;
  483. my $Http_err = "";
  484. my $Http_data;
  485. my $param = {
  486. url => "$xs1cmd",
  487. timeout => 3,
  488. method => "GET", # Lesen von Inhalten
  489. };
  490. HttpUtils_BlockingGet($param);
  491. ($Http_err, $Http_data) = HttpUtils_BlockingGet($param);
  492. ### HTTP Requests #### END ####
  493. if ($Http_err ne "") {
  494. ($Http_err) = $Http_err =~ /[:]\s.*/g;
  495. Log3 $name, 3, "$typ: Write | no Control possible | Error".$Http_err;
  496. return undef;
  497. } elsif ($Http_data ne "") {
  498. Log3 $name, 4, "$typ: Write | Send to xs1 -> $xs1cmd"; ## Kontrolle Sendebefehl
  499. }
  500. }
  501. sub xs1Bridge_Undef($$)
  502. {
  503. my ( $hash, $name) = @_;
  504. my $typ = $hash->{TYPE};
  505. RemoveInternalTimer($hash);
  506. delete $modules{xs1Bridge}{defptr}{BRIDGE} if( defined($modules{xs1Bridge}{defptr}{BRIDGE}) );
  507. foreach my $d (sort keys %defs) {
  508. if(defined($defs{$d}) && defined($defs{$d}{IODev}) && $defs{$d}{IODev} == $hash) {
  509. Log3 $name, 3, "$typ: deleting IODev for $d";
  510. delete $defs{$d}{IODev};
  511. }
  512. }
  513. Log3 $name, 3, "$typ: deleting Device with Name $name";
  514. return undef;
  515. }
  516. # Eval-Rückgabewert für erfolgreiches
  517. # Laden des Moduls
  518. 1;
  519. # Beginn der Commandref
  520. =pod
  521. =item summary Connection of the device xs1 from EZControl
  522. =item summary_DE Anbindung des Ger&auml;tes xs1 der Firma EZControl
  523. =begin html
  524. <a name="xs1Bridge"></a>
  525. <h3>xs1Bridge</h3>
  526. <ul>
  527. With this module you can read out the device xs1 from EZcontrol. There will be actors | Sensors | Timer | Information read from xs1 and written in readings. With each read only readings are created or updated, which are also defined and active in xs1. Actor | Sensor or timer definitions which are deactivated in xs1 are NOT read.
  528. <br><br>
  529. The module was developed based on the firmware version v4-Beta of the xs1. There may be errors due to different adjustments within the manufacturer's firmware.<br>
  530. Testet firmware: v4.0.0.5326 (Beta) @me | v3.0.0.4493 @ForumUser<br><br>
  531. <a name="xs1Bridge_define"></a>
  532. <b>Define</b><br>
  533. <ul>
  534. <code>define &lt;NAME&gt; xs1Bridge &lt;IP&gt;</code>
  535. <br><br>
  536. The module can not create without the IP of the xs1. If the IP can not be reached during module definition, the Define process is aborted.
  537. <ul>
  538. <li><code>&lt;IP&gt;</code> is IP address in the local network.</li>
  539. </ul><br>
  540. example:
  541. <ul>
  542. define EZcontrol_xs1 xs1Bridge 192.168.1.45
  543. </ul>
  544. </ul><br>
  545. <b>Set</b>
  546. <ul>N/A</ul><br>
  547. <b>Get</b><br>
  548. <ul>N/A</ul><br>
  549. <a name="xs1_attr"></a>
  550. <b>Attributes</b>
  551. <ul>
  552. <li>debug (0,1)<br>
  553. This brings the module into a very detailed debug output in the logfile. Program parts can be checked and errors checked.<br>
  554. (Default, debug 0)
  555. </li><br>
  556. <li>disable (0,1)<br>
  557. This function deactivates the interval. With disable 1 no readings are updated.<br>
  558. (Default, disable 0)
  559. </li><br>
  560. <li>interval (30,60,180,360)<br>
  561. This is the interval in seconds at which readings are read from xs1<br>
  562. <i>For actuators, only different states are updated in the set interval.</i><br>
  563. <i>Sensors are always updated in intervals, regardless of the status.</i><br>
  564. (Default, interval 60)
  565. </li><br>
  566. <li>update_only_difference (0,1)<br>
  567. The actuators defined in xs1 are only updated when the value changes.<br>
  568. (Default, update_only_difference 0)</li><br>
  569. <li>view_Device_name (0,1)<br>
  570. The actor names defined in xs1 are read as Reading.<br>
  571. (Default, view_Device_name 0)<br>
  572. </li><br>
  573. <li>view_Device_function (0,1)<br>
  574. The actuator functions defined in xs1 are read out as Reading.<br>
  575. (Default, view_Device_function 0)<br>
  576. </li><br>
  577. <li>xs1_control (0,1)<br>
  578. Option to control the xs1. After activating this, the xs1Dev module creates each actuator and sensor in FHEM.<br>
  579. (Default, xs1_control 0)<br>
  580. </li><br><br>
  581. </ul>
  582. <b>explanation:</b>
  583. <ul>
  584. <li>various Readings:</li>
  585. <ul>
  586. <li>Aktor_(01-64)</li> defined actuator in the device<br>
  587. <li>Aktor_(01-64)_name</li> defined actor name in the device<br>
  588. <li>Aktor_(01-64)_function(1-4)</li> defined actuator function in the device<br>
  589. <li>Sensor_(01-64)</li> defined sensor in the device<br>
  590. <li>Sensor_(01-64)_name</li> defined sensor name in the device<br>
  591. <li>Timer_(01-128)</li> defined timer in the device<br>
  592. <li>xs1_bootloader</li> version of bootloader<br>
  593. <li>xs1_dhcp</li> DHCP on/off<br>
  594. <li>xs1_features</li> purchased feature when buying (A = send | B = receive | C = Skripte/Makros | D = Media Access)<br>
  595. <li>xs1_firmware</li> firmware number<br>
  596. <li>xs1_start</li> device start<br>
  597. </ul><br>
  598. <li>The message "<code>... Can't connect ...</code>" or "<code>ERROR: empty answer received</code>" in the system logfile says that there was no query for a short time.<br>
  599. (This can happen more often with DLAN.)<br><br></li>
  600. <li>If the device has not been connected after 5 connection attempts, the module will switch on < disable > !</li><br>
  601. <li>Create logfile automatically after define | scheme: <code>define FileLog_xs1Bridge FileLog ./log/xs1Bridge-%Y-%m.log &lt;name&gt;</code><br>
  602. The following values ​​are recorded in logfile: Timer | xs1-status information</li>
  603. </ul>
  604. </ul>
  605. =end html
  606. =begin html_DE
  607. <a name="xs1Bridge"></a>
  608. <h3>xs1Bridge</h3>
  609. <ul>
  610. Mit diesem Modul k&ouml;nnen Sie das Gerät xs1 der Firma <a href="http://www.ezcontrol.de/">EZcontrol</a> auslesen. Das Modul ruft die Daten des xs1 via der Kommunikationsschnittstelle ab. Mit einem HTTP GET Requests erh&auml;lt man die Antworten in Textform welche im Datenformat JSON (JavaScript Object Notation) ausgegeben werden.
  611. Es werden Aktoren | Sensoren | Timer | Informationen vom xs1 ausgelesen und in Readings geschrieben. Bei jedem Auslesen werden nur Readings angelegt bzw. aktualisiert, welche auch im xs1 definiert und aktiv sind. Aktor | Sensor bzw. Timer Definitionen welche deaktiviert sind im xs1, werden NICHT ausgelesen.
  612. <br><br>
  613. Das Modul wurde entwickelt basierend auf dem Firmwarestand v4-Beta des xs1. Es kann aufgrund von unterschiedlichen Anpassungen innerhalb der Firmware des Herstellers zu Fehlern kommen.<br>
  614. Getestete Firmware: v4.0.0.5326 (Beta) @me | v3.0.0.4493 @ForumUser<br><br>
  615. <a name="xs1Bridge_define"></a>
  616. <b>Define</b><br>
  617. <ul>
  618. <code>define &lt;NAME&gt; xs1Bridge &lt;IP&gt;</code>
  619. <br><br>
  620. Ein anlegen des Modules ohne Angabe der IP vom xs1 ist nicht m&ouml;glich. Sollte die IP bei der Moduldefinierung nicht erreichbar sein, so bricht der Define Vorgang ab.
  621. <ul>
  622. <li><code>&lt;IP&gt;</code> ist IP-Adresse im lokalen Netzwerk.</li>
  623. </ul><br>
  624. Beispiel:
  625. <ul>
  626. define EZcontrol_xs1 xs1Bridge 192.168.1.45
  627. </ul>
  628. </ul><br>
  629. <b>Set</b>
  630. <ul>N/A</ul><br>
  631. <b>Get</b><br>
  632. <ul>N/A</ul><br>
  633. <a name="xs1_attr"></a>
  634. <b>Attribute</b>
  635. <ul>
  636. <li>debug (0,1)<br>
  637. Dies bringt das Modul in eine sehr ausf&uuml;hrliche Debug-Ausgabe im Logfile. Somit lassen sich Programmteile kontrollieren und Fehler &uuml;berpr&uuml;fen.<br>
  638. (Default, debug 0)
  639. </li><br>
  640. <li>disable (0,1)<br>
  641. Diese Funktion deaktiviert den Interval. Mit <code>disable 1</code> werden keine Readings aktualisiert.<br>
  642. (Default, disable 0)
  643. </li><br>
  644. <li>interval (30,60,180,360)<br>
  645. Das ist der Intervall in Sekunden, in dem die Readings neu gelesen werden vom xs1.<br>
  646. <i>Bei Aktoren werden nur unterschiedliche Zust&auml;nde aktualisiert im eingestellten Intervall.</i><br>
  647. <i>Sensoren werden unabhängig vom Zustand immer im Intervall aktualisiert.</i><br>
  648. (Default, interval 60)
  649. </li><br>
  650. <li>update_only_difference (0,1)<br>
  651. Die Aktoren welche im xs1 definiert wurden, werden nur bei Wert&auml;nderung aktualisiert.<br>
  652. (Default, update_only_difference 0)</li><br>
  653. <li>view_Device_name (0,1)<br>
  654. Die Aktor Namen welche im xs1 definiert wurden, werden als Reading ausgelesen.<br>
  655. (Default, view_Device_name 0)<br>
  656. </li><br>
  657. <li>view_Device_function (0,1)<br>
  658. Die Aktor Funktionen welche im xs1 definiert wurden, werden als Reading ausgelesen.<br>
  659. (Default, view_Device_function 0)<br>
  660. </li><br>
  661. <li>xs1_control (0,1)<br>
  662. Die Freigabe zur Steuerung des xs1. Nach Aktivierung dieser, wird durch das xs1Dev Modul jeder Aktor und Sensor in FHEM angelegt.<br>
  663. (Default, xs1_control 0)<br>
  664. </li><br><br>
  665. </ul>
  666. <b>Erl&auml;uterung:</b>
  667. <ul>
  668. <li>Auszug Readings:</li>
  669. <ul>
  670. <li>Aktor_(01-64)</li> definierter Aktor mit jeweiligem Zustand im Ger&auml;t<br>
  671. <li>Aktor_(01-64)_name</li> definierter Aktorname im Ger&auml;t<br>
  672. <li>Aktor_(01-64)_function(1-4)</li> definierte Aktorfunktion im Ger&auml;t<br>
  673. <li>Sensor_(01-64)</li> definierter Sensor im Ger&auml;t<br>
  674. <li>Sensor_(01-64)</li> definierter Sensorname im Ger&auml;t<br>
  675. <li>Timer_(01-128)</li> definierter Timer im Ger&auml;t<br>
  676. <li>xs1_bootloader</li> Firmwareversion des Bootloaders<br>
  677. <li>xs1_dhcp</li> DHCP an/aus<br>
  678. <li>xs1_features</li> erworbene Feature beim Kauf (A = SENDEN | B = EMPFANGEN | C = Skripte/Makros | D = Speicherkartenzugriff)<br>
  679. <li>xs1_firmware</li> Firmwareversion<br>
  680. <li>xs1_start</li> Ger&auml;testart<br>
  681. </ul><br>
  682. <li>Die Meldung "<code>Error: Can't connect ...</code>" oder "<code>ERROR: empty answer received</code>" im System-Logfile, besagt das kurzzeitig keine Abfrage erfolgen konnte.<br>
  683. (Das kann h&auml;ufiger bei DLAN vorkommen.)<br><br></li>
  684. <li>Sollte das Ger&auml;t nach 5 Verbindungsversuchen ebenfalls keine Verbindung erhalten haben, so schaltet das Modul auf < disable > !</li><br>
  685. <li>Logfile Erstellung erfolgt automatisch nach dem definieren. | Schema: <code>define FileLog_xs1Bridge FileLog ./log/xs1Bridge-%Y-%m.log &lt;Name&gt;</code><br>
  686. Folgende Werte werden im Logfile erfasst: Timer | xs1-Statusinformationen</li><br>
  687. <li>Sollte das Ger&auml;t nach 5 Verbindungsversuchen ebenfalls keine Verbindung erhalten haben, so schaltet das Modul auf < disable > !</li><br>
  688. </ul>
  689. </ul>
  690. =end html_DE
  691. =cut