################################################################# # $Id$ ################################################################# # physisches Modul - Verbindung zur Hardware # # note / ToDo´s / Bugs: # - Port Check ??? # - Sendeausgabe im LOG anpassen # # ################################################################# package main; # Laden evtl. abhängiger Perl- bzw. FHEM-Module use HttpUtils; # um Daten via HTTP auszutauschen https://wiki.fhem.de/wiki/HttpUtils use strict; use warnings; # Warnings use Net::Ping; my $missingModul = ""; my $xs1_ConnectionTry = 1; # disable Funktion sobald 10x keine Verbindung (Schutzabschaltung) my $xs1_id; eval "use Encode qw(encode encode_utf8 decode_utf8);1" or $missingModul .= "Encode "; eval "use JSON;1" or $missingModul .= "JSON "; eval "use Net::Ping;1" or $missingModul .= "Net::Ping "; #$| = 1; #Puffern abschalten, Hilfreich für PEARL WARNINGS Search sub xs1Bridge_Initialize($) { my ($hash) = @_; $hash->{WriteFn} = "xs1Bridge_Write"; $hash->{Clients} = ":xs1Dev:"; $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 $hash->{DefFn} = "xs1Bridge_Define"; $hash->{AttrFn} = "xs1Bridge_Attr"; $hash->{UndefFn} = "xs1Bridge_Undef"; $hash->{AttrList} = "debug:0,1,2 ". "ignore:0,1 ". "update_only_difference:0,1 ". "view_Device_name:0,1 ". "view_Device_function:0,1 ". "xs1_blackl_aktor ". "xs1_blackl_sensor ". "xs1_control:0,1 ". "xs1_interval:0,30,60,180,360 "; ##$readingFnAttributes; ## die Standardattribute von FHEM foreach my $d(sort keys %{$modules{xs1Bridge}{defptr}}) { my $hash = $modules{xs1Bridge}{defptr}{$d}; } } sub xs1Bridge_Define($$) { my ($hash, $def) = @_; my @arg = split("[ \t][ \t]*", $def); my $name = $hash->{NAME}; ## Der Definitionsname, mit dem das Gerät angelegt wurde. my $typ = $hash->{TYPE}; ## Der Modulname, mit welchem die Definition angelegt wurde. my $debug = AttrVal($hash->{NAME},"debug",0); my $viewDeviceName = AttrVal($hash->{NAME},"view_Device_name",0); my $viewDeviceFunction = AttrVal($hash->{NAME},"view_Device_function",0); my $update_only_difference = AttrVal($hash->{NAME},"update_only_difference",0); # 0 1 2 return "Usage: define $name " if(@arg != 3); #return "Usage: define $name " if(@arg != 4); 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); #return "Your PORT is not valid. Please Check!" if not($arg[3] =~ /[0-9]{2,5}/s); return "Cannot define xs1Bridge device. Perl modul ${missingModul}is missing." if ( $missingModul ); my $xs1check = 0; if(!defined $modules{xs1Bridge}) { my $p = Net::Ping->new("tcp", 2); if(!($p->ping("$arg[2]", 2))) { $xs1check = 1; } $p->close(); return "Your IP is not reachable. Please Check!" if ($xs1check == 1); } # Parameter Define my $xs1_ip = $arg[2]; ## Zusatzparameter 1 bei Define - ggf. nur in Sub $hash->{xs1_ip} = $xs1_ip; $hash->{STATE} = "Initialized"; ## Der Status des Modules nach Initialisierung. $hash->{TIME} = time(); ## Zeitstempel, derzeit vom anlegen des Moduls $hash->{VERSION} = "1.26"; ## Version $hash->{BRIDGE} = 1; # Attribut gesetzt $attr{$name}{xs1_interval} = "60" if( not defined( $attr{$name}{xs1_interval} ) ); $attr{$name}{room} = "xs1" if( not defined( $attr{$name}{room} ) ); $attr{$name}{xs1_control} = "0" if( not defined( $attr{$name}{xs1_control} ) ); $modules{xs1Bridge}{defptr}{BRIDGE} = $hash; InternalTimer(gettimeofday()+$attr{$name}{xs1_interval}, "xs1Bridge_GetUpDate", $hash); ## set Timer #Log3 $name, 3, "$typ: IODev defined with xs1_ip: $xs1_ip"; if(!defined($defs{'FileLog_xs1Bridge'})) { ## Logfile existent check Log3 $name, 4, "$typ: FileLog_xs1Bridge ist NICHT definiert"; fhem("define FileLog_xs1Bridge FileLog ./log/xs1Bridge-%Y-%m.log ".$arg[0]); ## Logfile define fhem("attr FileLog_xs1Bridge room xs1"); ## Logfile in xs1 room } else { Log3 $name, 4, "$typ: FileLog_xs1Bridge ist definiert"; } return undef; } sub xs1Bridge_Attr(@) { my ($cmd,$name,$attrName,$attrValue) = @_; my $hash = $defs{$name}; my $typ = $hash->{TYPE}; my $debug = AttrVal($hash->{NAME},"debug",0); my $xs1_interval = 0; my @string_attrValue = split(",",$attrValue) if (defined $attrValue); ## for Check Blacklist my $length = scalar @string_attrValue; ## for Check Blacklist # $cmd - Vorgangsart - kann die Werte "del" (löschen) oder "set" (setzen) annehmen # $name - Gerätename # $attrName/$attrValue sind Attribut-Name und Attribut-Wert #### Handling bei set .. attribute if ($cmd eq "set") { RemoveInternalTimer($hash); ## Timer löschen Debug " $typ: Attr | Cmd:$cmd | RemoveInternalTimer" if($debug == 2); if ($attrName eq "xs1_interval" && $attrValue == 0) { ## Handling xs1_interval == 0 RemoveInternalTimer($hash); readingsSingleUpdate($hash, "state", "deactive", 1); }elsif ($attrName eq "xs1_interval" && $attrValue >= 30) { ## Handling xs1_interval >= 30 $xs1_ConnectionTry = 1; my $xs1_interval = $attrValue; InternalTimer(gettimeofday()+$xs1_interval, "xs1Bridge_GetUpDate", $hash); readingsSingleUpdate($hash, "state", "active", 1); ### Ansicht xs1_Device_function ### }elsif ($attrName eq "view_Device_function") { if ($attrValue eq "1") { ## Handling view_Device_function 1 #Log3 $name, 3, "$typ: Attribut view_Device_function $cmd to $attrValue"; } elsif ($attrValue eq "0") { ## Handling view_Device_function 0 #Log3 $name, 3, "$typ: Attribut view_Device_function $cmd to $attrValue"; } ### Ansicht xs1_Device_name ### }elsif ($attrName eq "view_Device_name") { if ($attrValue eq "1") { ## Handling view_Device_name 1 #Log3 $name, 3, "$typ: Attribut view_Device_name $cmd to $attrValue"; } elsif ($attrValue eq "0") { ## Handling view_Device_name 0 #Log3 $name, 3, "$typ: Attribut view_Device_name $cmd to $attrValue"; for my $i (0..64) { delete $hash->{READINGS}{"Aktor_".sprintf("%02d", $i)."_name"} if($hash->{READINGS}); delete $hash->{READINGS}{"Sensor_".sprintf("%02d", $i)."_name"} if($hash->{READINGS}); } } ### Wertaenderung nur bei Difference ### }elsif ($attrName eq "update_only_difference") { if ($attrValue eq "1") { ## Handling update_only_difference 1 #Log3 $name, 3, "$typ: Attribut update_only_difference $cmd to $attrValue"; } elsif ($attrValue eq "0") { ## Handling update_only_difference 0 #Log3 $name, 3, "$typ: Attribut update_only_difference $cmd to $attrValue"; for my $i (0..64) { delete $hash->{READINGS}{"Aktor_".sprintf("%02d", $i)."_name"} if($hash->{READINGS}); } } ### xs1 - steuern ### }elsif ($attrName eq "xs1_control") { if ($attrValue eq "1") { ## Handling xs1_control 1 if(! $modules{xs1Dev}) { ## Check Modul vorhanden $attr{$name}{xs1_control} = "0"; return "Module xs1Dev is non-existent or still under development. Please wait" } } ### Blacklist - Aktor / Sensor ### }elsif ($attrName eq "xs1_blackl_aktor") { ## Handling xs1_blackl_aktor for (my $x = 0; $x < $length ; $x++) { if ($string_attrValue[$x] =~ /^[1-9]{1}\d*/ && $string_attrValue[$x] <65) { ## RICHTIG ## } else { return "The comma separated value must only 1 to 64"; } } Log3 $name, 4, "$typ: Attribut xs1_blackl_aktor $attrValue"; }elsif ($attrName eq "xs1_blackl_sensor") { ## Handling xs1_blackl_sensor for (my $x = 0; $x < $length ; $x++) { if ($string_attrValue[$x] =~ /^[1-9]{1}\d*/ && $string_attrValue[$x] <65) { ## RICHTIG ## } else { return "The comma separated value must only 1 to 64"; } } Log3 $name, 4, "$typ: Attribut xs1_blackl_sensor $attrValue"; } } #### Handling bei del ... attribute if ($cmd eq "del") { if ($attrName eq "xs1_interval") { ## Handling deleteattr xs1_interval RemoveInternalTimer($hash); readingsSingleUpdate($hash, "state", "deactive", 1); Debug " $typ: Attr | Cmd:$cmd | $attrName" if($debug == 2); } elsif ($attrName eq "view_Device_function") { ## Handling deleteattr view_Device_function Log3 $name, 3, "$typ: Attribut view_Device_function delete"; for my $i (0..64) { for my $i2 (1..4) { delete $hash->{READINGS}{"Aktor_".sprintf("%02d", $i)."_function_".$i2} if($hash->{READINGS}); } } } elsif ($attrName eq "view_Device_name") { ## Handling deleteattr view_Device_name Log3 $name, 3, "$typ: Attribut view_Device_name delete"; for my $i (0..64) { delete $hash->{READINGS}{"Aktor_".sprintf("%02d", $i)."_name"} if($hash->{READINGS}); delete $hash->{READINGS}{"Sensor_".sprintf("%02d", $i)."_name"} if($hash->{READINGS}); } } elsif ($attrName eq "update_only_difference") { Log3 $name, 3, "$typ: Attribut update_only_difference delete"; } } #### Handling bei state active if ($hash->{STATE} eq "active") { RemoveInternalTimer($hash); InternalTimer(gettimeofday()+$xs1_interval, "xs1Bridge_GetUpDate", $hash); Debug " $typ: Attr | RemoveInternalTimer + InternalTimer" if($debug == 2); } return undef; } sub xs1Bridge_GetUpDate() { my ($hash) = @_; my $name = $hash->{NAME}; my $typ = $hash->{TYPE}; my $state = $hash->{STATE}; my $xs1_ip = $hash->{xs1_ip}; my $xs1_uptimeStart = $hash->{helper}{xs1_uptimeStart}; my $xs1_uptimeOld = $hash->{helper}{xs1_uptimeOld}; my $xs1_uptimeNew = $hash->{helper}{xs1_uptimeNew}; my $def; #http://x.x.x.x/control?callback=cname&cmd=... #get_list_actuators - list all actuators i0 #get_list_sensors - list all sensors i1 #get_list_timers - list all timers i3 #get_config_info - list all device info´s i2 #get_protocol_info - list protocol info´s my $cmd = "/control?callback=cname&cmd="; my @cmdtyp = ("get_list_actuators","get_list_sensors","get_config_info","get_list_timers","get_list_actuators"); my @arrayname = ("actuator","sensor","info","timer","function"); my @readingsname = ("Aktor","Sensor","","Timer",""); my $debug = AttrVal($hash->{NAME},"debug",0); my $xs1_interval = AttrVal($name, "xs1_interval", 60); my $viewDeviceName = AttrVal($hash->{NAME},"view_Device_name",0); my $viewDeviceFunction = AttrVal($hash->{NAME},"view_Device_function",0); my $update_only_difference = AttrVal($hash->{NAME},"update_only_difference",0); my $xs1_control = AttrVal($hash->{NAME},"xs1_control",0); my $xs1_blackl_aktor = AttrVal($hash->{NAME},"xs1_blackl_aktor",0); my $xs1_blackl_sensor = AttrVal($hash->{NAME},"xs1_blackl_sensor",0); #### xs1Bridge xs1_interval >= 10 -> aktiviert zum auslesen if ($xs1_interval >= 10 && $xs1_ConnectionTry <= 5) { RemoveInternalTimer($hash); ## Timer löschen InternalTimer(gettimeofday()+$xs1_interval, "xs1Bridge_GetUpDate", $hash); Debug " -------------- ERROR CHECK - START --------------" if($debug == 2 || $debug == 1); Debug " $typ: GetUpDate | RemoveInternalTimer + InternalTimer" if($debug == 2); #Log3 $name, 3, "$typ: xs1Bridge_GetUpDate | RemoveInternalTimer + InternalTimer"; if ($state eq "Initialized") { readingsSingleUpdate($hash, "state", "active", 1); } my $xs1Dev_check = "ERROR"; #if($modules{xs1Dev} && $modules{xs1Dev}{LOADED}) { ## Check Modul vorhanden + geladen if($modules{xs1Dev}) { ## Check Modul vorhanden $xs1Dev_check = "ok"; Debug " $typ: GetUpDate | Modul xs1Dev_check = $xs1Dev_check" if($debug == 2); } else { Debug " $typ: GetUpDate ERROR | Modul xs1Dev not existent! Please check it to be available!" if($debug == 2); #Log3 $name, 3, "$typ: GetUpDate | xs1Dev_check = $xs1Dev_check"; } #### JSON Abfrage - Schleife for my $i (0..3) { #### HTTP Requests #### Start #### my $connection; my $Http_err = ""; my $Http_data = ""; my $param = { url => "http://".$xs1_ip.$cmd.$cmdtyp[$i], timeout => 3, method => "GET", # Lesen von Inhalten }; #HttpUtils_BlockingGet($param); ($Http_err, $Http_data) = HttpUtils_BlockingGet($param); #### HTTP Requests #### END #### my $adress = "http://".$xs1_ip.$cmd.$cmdtyp[$i]; my $json; my $json_utf8; my $decoded; Debug " $typ: GetUpDate | Adresse: $adress | xs1_ConnectionTry=$xs1_ConnectionTry" if($debug == 1 && $Http_err eq ""); Debug " $typ: GetUpDate | HTTP request: ".$Http_err."| xs1_ConnectionTry=$xs1_ConnectionTry" if($debug == 1 && $Http_err ne ""); #### HTTP Requests, ERROR if ($Http_err ne "") { # ERROR Message # 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 # http://192.168.2.5/control?callback=cname&cmd=get_config_info: empty answer received # http://192.168.2.5/control?callback=cname&cmd=get_config_info: Select timeout/error: #($Http_err) = $Http_err =~ /[:]\s.*/g; Log3 $name, 3, "$typ: GetUpDate | Try=$xs1_ConnectionTry loop=$i | Error: ".$Http_err; $xs1_ConnectionTry++; last; ## Abbruch Schleife } #### HTTP Requests, OK dann ARRAY Verarbeitung elsif ($Http_data ne "") { ($json) = $Http_data =~ /[^(]*[}]/g; ## cut cname( + ) am Ende von Ausgabe -> ARRAY Struktur als Antwort vom xs1 $json_utf8 = eval {encode_utf8( $json )}; ## UTF-8 character Bearbeitung, da xs1 TempSensoren ERROR $decoded = eval {decode_json( $json_utf8 )}; $xs1_ConnectionTry = 1; #### xs1 Aktoren / Sensoren als Readings if ($i <= 1 ) { my $xs1_data; my @array; if (defined $decoded->{$arrayname[$i]}) { @array = @{ $decoded->{$arrayname[$i]} }; } else { Log3 $name, 3, "$typ: GetUpDate | ARRAY-ERROR xs1 -> no Data in loop $i"; last; } my $i3 = 0; ## Counter für real Werte in xs1, da sonst Verschiebungen wenn User ID´s verschiebt, NOTWENDIG! foreach my $f ( @array ) { $i3++; $xs1_id = $i3; #### Test ob Aktoren / Sensoren auf xs1_blackl if ($f->{"type"} ne "disabled" && is_in_array($hash,$xs1_id,$i) == 0) { my $xs1Dev = "xs1Dev"; #### Aktoren spezifisch my $xs1_function1 = "-"; my $xs1_function2 = "-"; my $xs1_function3 = "-"; my $xs1_function4 = "-"; if ($i == 0) { #### xs1 Aktoren nur update bei differenten Wert if ($update_only_difference == 1) { my $oldState = ReadingsVal($name, $readingsname[$i]."_".sprintf("%02d", $i3), "unknown"); ## Readings Wert my $newState = sprintf("%.1f" , $f->{"value"}); ## ARRAY Wert xs1 aktuell Debug " $typ: ".$readingsname[$i]."_".sprintf("%02d", $i3)." oldState=$oldState newState=$newState" if($debug == 2); if ($oldState ne $newState) { readingsSingleUpdate($hash, $readingsname[$i]."_".sprintf("%02d", $i3) , $newState, 0); } } #### xs1 Aktoren / Funktion != disable my @array2 = @{ $decoded->{'actuator'}->[$i3-1]->{$arrayname[4]} }; my $i2 = 0; ## Funktionscounter foreach my $f2 ( @array2 ) { $i2++; #### xs1 Option - Ansicht Funktionsname if ($viewDeviceFunction == 1) { my $oldState = ReadingsVal($name, $readingsname[$i]."_".sprintf("%02d", $i3)."_".$arrayname[4]."_".$i2, "unknown"); ## Readings Wert my $newState = $f2->{'type'}; ## ARRAY Wert xs1 aktuell if ($oldState ne "unknown" && $newState eq "disabled") { ## FunktionReading del bei disable Debug " $typ: "."Aktor_".sprintf("%02d", $i3)."_function_".$i2." are disabled" if($debug == 2); delete $hash->{READINGS}{"Aktor_".sprintf("%02d", $i3)."_function_".$i2} if($hash->{READINGS}); } if ($f2->{"type"} ne "disabled") { ## Funktion != function -> type disable if ($oldState ne $newState) { readingsSingleUpdate($hash, $readingsname[$i]."_".sprintf("%02d", $i3)."_".$arrayname[4]."_".$i2 , $f2->{"type"} , 1); } } } #### Funktion != function -> type disable if ($f2->{"type"} ne "disabled") { if ($i2 == 1) { $xs1_function1 = $f2->{"type"}; }elsif ($i2 == 2) { $xs1_function2 = $f2->{"type"}; }elsif ($i2 == 3) { $xs1_function3 = $f2->{"type"}; }elsif ($i2 == 4) { $xs1_function4 = $f2->{"type"}; } } } } #### Value der Aktoren | Sensoren if ($i == 1 || $i == 0 && $update_only_difference == 0) { # Aktoren | Sensoren im intervall - Format 0.0 bzw. 37.0 wie aus xs1 readingsSingleUpdate($hash, $readingsname[$i]."_".sprintf("%02d", $i3) , sprintf("%.1f" , $f->{"value"}), 0); $xs1_data = $xs1Dev."#".$readingsname[$i]."#".sprintf("%02d", $i3)."#".$f->{"type"}."#".sprintf("%.1f" , $f->{"value"})."#"."$xs1_function1"."#"."$xs1_function2"."#"."$xs1_function3"."#"."$xs1_function4"."#".$f->{"name"}; } elsif ($i == 0 && $update_only_difference == 1){ # Aktoren | nur bei DIFF - Format 0.0 bzw. 37.0 wie aus xs1 $xs1_data = $xs1Dev."#".$readingsname[$i]."#".sprintf("%02d", $i3)."#".$f->{"type"}."#".sprintf("%.1f" , $f->{"value"})."#"."$xs1_function1"."#"."$xs1_function2"."#"."$xs1_function3"."#"."$xs1_function4"."#".$f->{"name"}; } #### Ausgaben je Typ unterschiedlich !!! 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 == 2 && $i == 0); Debug " $typ: ".$readingsname[$i]."_".sprintf("%02d", $i3)." | ".$f->{"type"}." | ".$f->{"name"}." | ". $f->{"value"} if($debug == 2 && $i != 0); ### Ansicht Namen der Aktoren | Sensoren als Readings if ($viewDeviceName == 1) { my $oldState = ReadingsVal($name, $readingsname[$i]."_".sprintf("%02d", $i3)."_name", "unknown"); ## Readings Wert my $newState = $f->{"name"}; ## ARRAY Wert xs1 aktuell if ($oldState ne $newState) { ## Namen nur bei Änderung schreiben #Log3 $name, 3, "$typ: GetUpDate | newState=$newState ne oldState=$oldState"; readingsSingleUpdate($hash, $readingsname[$i]."_".sprintf("%02d", $i3)."_name" , $f->{"name"} , 1); } } ### Dispatch an xs1Device Modul if ($xs1Dev_check eq "ok" && $xs1_control == 1) { Debug " $typ: GetUpDate | Dispatch: $xs1_data" if($debug == 2); Dispatch($hash,$xs1_data,undef) if($xs1_data); } } else { #### ID bzw. Speicherplatz xs1 ist disabled | Reading are delete delete $hash->{READINGS}{$readingsname[$i]."_".sprintf("%02d", $i3)} if($hash->{READINGS}); delete $hash->{READINGS}{$readingsname[$i]."_".sprintf("%02d", $i3)."_name"} if($hash->{READINGS}); ### Erweiterung v1.20 ### Device | Logfile | SVG löschen wenn in xs1 disable - TEST my $delDevice = "xs1Dev_".$readingsname[$i]."_".sprintf("%02d", $i3); if (defined($defs{"xs1Dev_".$readingsname[$i]."_".sprintf("%02d", $i3)})) { #Log3 $name, 3, "$typ: GetUpDate | for delete $delDevice"; fhem("delete ".$delDevice); ## delete Device } if (defined($defs{"SVG_xs1Dev_".$readingsname[$i]."_".sprintf("%02d", $i3)})) { #Log3 $name, 3, "$typ: GetUpDate | for delete FileLog_$delDevice"; fhem("delete SVG_".$delDevice); ## delete FileLog_Device } if (defined($defs{"FileLog_xs1Dev_".$readingsname[$i]."_".sprintf("%02d", $i3)})) { #Log3 $name, 3, "$typ: GetUpDate | for delete FileLog_$delDevice"; fhem("delete FileLog_".$delDevice); ## delete FileLog_Device } ### Erweiterung v1.20 ### Device | Logfile | SVG löschen wenn in xs1 disable - TEST ### ENDE ### if ($i == 0) { for my $count (1..4) { delete $hash->{READINGS}{$readingsname[$i]."_".sprintf("%02d", $i3)."_function_".$count} if($hash->{READINGS}); } } } } } #### xs1 Info´s nur bei uptime Änderung als Readings elsif ($i == 2) { my $features; my $features_i=0; my @xs1_readings = ("xs1_start","xs1_devicename","xs1_bootloader","xs1_hardware","xs1_features","xs1_firmware","xs1_mac","xs1_dhcp"); 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'}); my $oldState = ReadingsVal($name, $xs1_readings[0], "2000-01-01 03:33:33"); ## Readings Wert my @oldstate = split (/[-,:,\s\/]/, $oldState); ## Split $year, $month, $mday, $hour, $min, $sec $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 my $newState = FmtDateTime(time()-($decoded->{'info'}{'uptime'})); ## ARRAY uptime Wert xs1 aktuell my @newState = split (/[-,:,\s\/]/, $newState); ## Split $year, $month, $mday, $hour, $min, $sec $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 #### Vergleich mit 5 Sekunden Tolleranz je Verarbeitungszeit Netzwerk | DLAN | CPU if (abs($oldState - $newState) > 5) { readingsBeginUpdate($hash); for my $i2 (0..7) { if ($i2 == 4) { while (defined $decoded->{'info'}{'features'}->[$features_i]) { $features.= $decoded->{'info'}{'features'}->[$features_i]." "; $features_i++; } $xs1_decoded[4] = $features; ## ARRAY Wert xs1_decoded wird definiert } if (defined $xs1_decoded[$i2]) { readingsBulkUpdate($hash, $xs1_readings[$i2] , $xs1_decoded[$i2]); Debug " $typ: ".$xs1_readings[$i2].": ".$xs1_decoded[$i2] if($debug == 2); } else { Log3 $name, 3, "$typ: GetUpDate | ARRAY-ERROR xs1 -> no Data in loop $i|$i2"; last; } } readingsEndUpdate($hash, 1); } } #### xs1 Timers als Readings elsif ($i == 3) { my @array = @{ $decoded->{$arrayname[$i]} }; foreach my $f ( @array ) { my $oldState = ReadingsVal($name, $readingsname[$i]."_".sprintf("%02d", $f->{"id"}), "unknown"); ## Readings Wert my $newState = FmtDateTime($f->{"next"}); ## ARRAY Wert xs1 aktuell if ($f->{"type"} ne "disabled") { if ($oldState ne $newState) { ## Update Reading nur bei Wertänderung readingsSingleUpdate($hash, $readingsname[$i]."_".sprintf("%02d", $f->{"id"}) , FmtDateTime($f->{"next"}), 1); } Debug " $typ: ".$readingsname[$i]."_".sprintf("%02d", $f->{"id"})." | ".$f->{"name"}." | ".$f->{"type"}." | ". $f->{"next"} if($debug == 2); } elsif ($oldState ne "unknown") { ## deaktive Timer mit Wert werden als Reading entfernt Log3 $name, 3, "$typ: GetUpDate | ".$readingsname[$i]."_".sprintf("%02d", $f->{"id"})." is deactive in xs1"; delete $defs{$name}{READINGS}{$readingsname[$i]."_".sprintf("%02d", $f->{"id"})}; } } } if ($i < 2) { Debug " --------------- ERROR CHECK - SUB --------------- " if($debug == 2); } ### Schleifen Ende ### } } Debug " ------------- ERROR CHECK - ALL END -------------\n " if($debug == 2 || $debug == 1); } if ($xs1_ConnectionTry == 6) { ## Abschaltung xs1 nach 5 Verbindungsversuchen $attr{$name}{xs1_interval} = "0"; readingsSingleUpdate($hash, "state", "deactive", 1); RemoveInternalTimer($hash); ## Timer löschen Log3 $name, 3, "$typ: GetUpDate | connection ERROR -> xs1 set to disable! Device not reachable after 5 attempts"; } } sub xs1Bridge_Write($) ## Zustellen von Daten via IOWrite() vom logischen zum physischen Modul { my ($hash, $Aktor_ID, $xs1_typ, $cmd, $cmd2) = @_; my $name = $hash->{NAME}; my $typ = $hash->{TYPE}; my $xs1_ip = $hash->{xs1_ip}; my $debug = AttrVal($hash->{NAME},"debug",0); ## Anfrage (Client -> XS1): http://192.168.1.242/control?callback=cname&cmd=set_state_actuator&number=1&value=100 ## Aktor Typen aus xs1: (notwendig zur Verarbeitung) ## ------------------------------------------------- ## blind - Jalousie | dimmer - Dimmer | door - Tür | disabled - deaktivert ## switch - Schalter | shutter - Rolladen | sound - Ton | sun-blind - Markise ## temperature - Temperatur | timerswitch - Zeitschalter | window - Fenster ## Sensor Typen (Auswahl) aus xs1: (nur Info) ## ------------------------------------------ ## alarmmat - Alarmmatte | disabled - deaktivert ## gas_butan - Gasmelder Butan | gas_peak - Gas Spitzenwert ## mail - Briefmelder | motion - Bewegung ## other - Andere | presence - Anwesenheit ## pwr_consump - Energiezähler | pwr_peak - Energie Spitzenwert ## soilmoisture - Bodenfeuchte | soiltemp - Bodentemperatur ## leafwetness - Blattfeuchte | remotecontrol - Fernbedienung ## windowopen - Fenstermelder ... $Aktor_ID = substr($Aktor_ID, 1,2); my $xs1cmd; #### xs1 Typ switch || shutter || timerswitch - Anpassung Sendebefehl if ($xs1_typ eq "switch" || $xs1_typ eq "shutter" || $xs1_typ eq "timerswitch") { $xs1cmd = "http://$xs1_ip/control?callback=cname&cmd=set_state_actuator&number=$Aktor_ID&$cmd2"; } elsif ($xs1_typ eq "dimmer") { if ($cmd eq "off") { $cmd = 0; } $xs1cmd = "http://$xs1_ip/control?callback=cname&cmd=set_state_actuator&number=$Aktor_ID&$cmd2" if ($cmd2 =~ /[f][u][n][c][t][i][o][n][=]./); my $valuenew = substr($cmd2,3,length($cmd2)-3) if ($cmd2 !~ /[f][u][n][c][t][i][o][n][=]./); #Log3 $name, 3, "$typ: Write | Check cmd=$cmd cmd2=$cmd2 valuenew=$valuenew"; $xs1cmd = "http://$xs1_ip/control?callback=cname&cmd=set_state_actuator&number=$Aktor_ID&value=$valuenew" if ($cmd2 !~ /[f][u][n][c][t][i][o][n][=]./); } else { #### keine Verarbeitung zum senden #### Log3 $name, 3, "$typ: Write | $xs1_typ not control xs1. Please inform me!"; last; } ### HTTP Requests #### Start #### my $connection; my $Http_err = ""; my $Http_data; my $param = { url => "$xs1cmd", timeout => 3, method => "GET", # Lesen von Inhalten }; ($Http_err, $Http_data) = HttpUtils_BlockingGet($param); ### HTTP Requests #### END #### if ($Http_err ne "") { ($Http_err) = $Http_err =~ /[:]\s.*/g; Log3 $name, 3, "$typ: Write | no Control possible | Error".$Http_err; return undef; } elsif ($Http_data ne "") { Debug " $typ: Write | Send to xs1 -> $xs1cmd" if($debug == 1); ## Kontrolle Sendebefehl } } sub xs1Bridge_Undef($$) { my ( $hash, $name) = @_; my $typ = $hash->{TYPE}; RemoveInternalTimer($hash); delete $modules{xs1Bridge}{defptr}{BRIDGE} if( defined($modules{xs1Bridge}{defptr}{BRIDGE}) ); Log3 $name, 3, "$typ: deleting Device with Name $name"; foreach my $d (sort keys %defs) { if(defined($defs{$d}) && defined($defs{$d}{IODev}) && $defs{$d}{IODev} == $hash) { Log3 $name, 3, "$typ: deleting IODev for $d"; delete $defs{$d}{IODev}; } } return undef; } ########################## # eigene Sub sub is_in_array($$$) { my ( $hash,$xs1_id,$i) = @_; my $name = $hash->{NAME}; my $typ = $hash->{TYPE}; my $xs1_blackl = AttrVal($hash->{NAME},"xs1_blackl_aktor",0) if ($i eq 0); $xs1_blackl = AttrVal($hash->{NAME},"xs1_blackl_sensor",0) if ($i eq 1); my @attr_array=split(/,/,$xs1_blackl); if ( grep( /^$xs1_id$/, @attr_array ) ) { #Log3 $name, 3, "$typ: is_in_array | id=$xs1_id auf xs1_blackl Aktoren" if ($i eq 0); #Log3 $name, 3, "$typ: is_in_array | id=$xs1_id auf xs1_blackl Sensoren" if ($i eq 1); return 1; } else { #Log3 $name, 3, "$typ: is_in_array | id=$xs1_id NICHT auf xs1_blackl Aktoren" if ($i eq 0); #Log3 $name, 3, "$typ: is_in_array | id=$xs1_id NICHT auf xs1_blackl Sensoren" if ($i eq 1); return 0; } } ########################## # Eval-Rückgabewert für erfolgreiches # Laden des Moduls 1; # Beginn der Commandref =pod =item summary Connection of the device xs1 from EZControl =item summary_DE Anbindung des Gerätes xs1 der Firma EZControl =begin html

xs1Bridge

    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.

    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.
    Testet firmware: v4.0.0.5326 (Beta) @me | v3.0.0.4493 @ForumUser

      Currently implemented types of xs1 for processing:
    • Aktor: dimmer, switch, shutter, timerswitch
    • Sensor: barometer, counter, counterdiff, light, motion, other, rain, rain_1h, rain_24h, rainintensity, remotecontrol, uv_index, waterdetector, winddirection, windgust, windspeed, windvariance


    Define
      xs1 without password:  define <NAME> xs1Bridge <IP>
      xs1 with password:       define <NAME> xs1Bridge <User>:<Passwort>@<IP>

      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.
      • <IP> is IP address in the local network
      • <User> is the administrator user admin (default)
      • <Password> is the assigned administrator password in xs1

      examples:
        define EZcontrol_xs1 xs1Bridge 192.168.1.45
        define EZcontrol_xs1 xs1Bridge admin:secret@192.168.1.45

    Set
      N/A

    Get
      N/A

    Attributes
    • debug (0,1,2)
      This brings the module into a very detailed debug output in the logfile. Program parts can be checked and errors checked.
      (Default, debug 0)

    • update_only_difference (0,1)
      The actuators defined in xs1 are only updated when the value changes.
      (Default, update_only_difference 0)

    • view_Device_name (0,1)
      The actor names defined in xs1 are read as Reading.
      (Default, view_Device_name 0)

    • view_Device_function (0,1)
      The actuator functions defined in xs1 are read out as Reading.
      (Default, view_Device_function 0)

    • xs1_blackl_aktor
      A comma-separated blacklist of actuators, which should not be created automatically as soon as xs1_control = 1(aktiv).
      (Example: 2,40,3)

    • xs1_blackl_sensor
      A comma-separated blacklist of sensors, which should not be created automatically as soon as xs1_control = 1(aktiv).
      (Example: 3,37,55)

    • xs1_control (0,1)
      Option to control the xs1. After activating this, the xs1Dev module creates each actuator and sensor in FHEM.
      (Default, xs1_control 0)

    • xs1_interval (0,30,60,180,360)
      This is the interval in seconds at which readings are read from xs1
      For actuators, only different states are updated in the set interval.
      Sensors are always updated in intervals, regardless of the status.
      (Default, xs1_interval 60)



    explanation:
    • various Readings:
      • Aktor_(01-64)
      • defined actuator in the device
      • Aktor_(01-64)_name
      • defined actor name in the device
      • Aktor_(01-64)_function(1-4)
      • defined actuator function in the device
      • Sensor_(01-64)
      • defined sensor in the device
      • Sensor_(01-64)_name
      • defined sensor name in the device
      • Timer_(01-128)
      • defined timer in the device
      • xs1_bootloader
      • version of bootloader
      • xs1_dhcp
      • DHCP on/off
      • xs1_features
      • purchased feature when buying (A = send | B = receive | C = Skripte/Makros | D = Media Access)
      • xs1_firmware
      • firmware number
      • xs1_start
      • device start

    • The message "... Can't connect ..." or "ERROR: empty answer received" in the system logfile says that there was no query for a short time.
      (This can happen more often with DLAN.)

    • If the device has not been connected after 5 connection attempts, the module will switch on < disable > !

    • Create logfile automatically after define | scheme: define FileLog_xs1Bridge FileLog ./log/xs1Bridge-%Y-%m.log <name>
      The following values ​​are recorded in logfile: Timer | xs1-status information
=end html =begin html_DE

xs1Bridge

    Mit diesem Modul können Sie das Gerät xs1 der Firma EZcontrol auslesen. Das Modul ruft die Daten des xs1 via der Kommunikationsschnittstelle ab. Mit einem HTTP GET Requests erhält man die Antworten in Textform welche im Datenformat JSON (JavaScript Object Notation) ausgegeben werden. 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.

    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.
    Getestete Firmware: v4.0.0.5326 (Beta) @me | v3.0.0.4493 @ForumUser

      Derzeit implementierte Typen des xs1 zur Verarbeitung:
    • Aktor: dimmer, switch, shutter, timerswitch
    • Sensor: barometer, counter, counterdiff, light, motion, other, rain, rain_1h, rain_24h, rainintensity, remotecontrol, uv_index, waterdetector, winddirection, windgust, windspeed, windvariance


    Define
      xs1 ohne Passwortabfrage:  define <NAME> xs1Bridge <IP>
      xs1 mit Passwortabfrage:     define <NAME> xs1Bridge <User>:<Passwort>@<IP>

      Ein anlegen des Modules ohne Angabe der IP vom xs1 ist nicht möglich. Sollte die IP bei der Moduldefinierung nicht erreichbar sein, so bricht der Define Vorgang ab.
      • <IP> ist IP-Adresse im lokalen Netzwerk
      • <User> ist der Administrator Benutzer admin (standard)
      • <Passwort> ist das vergebene Administrator Passwort im xs1.

      Beispiele:
        define EZcontrol_xs1 xs1Bridge 192.168.1.45
        define EZcontrol_xs1 xs1Bridge admin:geheim@192.168.1.45

    Set
      N/A

    Get
      N/A

    Attribute
    • debug (0,1,2)
      Dies bringt das Modul in eine sehr ausführliche Debug-Ausgabe im Logfile. Somit lassen sich Programmteile kontrollieren und Fehler überprüfen.
      (Default, debug 0)

    • update_only_difference (0,1)
      Die Aktoren welche im xs1 definiert wurden, werden nur bei Wertänderung aktualisiert.
      (Default, update_only_difference 0)

    • view_Device_name (0,1)
      Die Aktor Namen welche im xs1 definiert wurden, werden als Reading ausgelesen.
      (Default, view_Device_name 0)

    • view_Device_function (0,1)
      Die Aktor Funktionen welche im xs1 definiert wurden, werden als Reading ausgelesen.
      (Default, view_Device_function 0)

    • xs1_blackl_aktor
      Eine kommagetrennte Blacklist der Aktoren, welche nicht automatisch angelegt werden sollen sobald xs1_control = 1(aktiv).
      (Beispiel: 2,40,3)

    • xs1_blackl_sensor
      Eine kommagetrennte Blacklist der Sensoren, welche nicht automatisch angelegt werden sollen sobald xs1_control = 1(aktiv).
      (Beispiel: 3,37,55)

    • xs1_control (0,1)
      Die Freigabe zur Steuerung des xs1. Nach Aktivierung dieser, wird durch das xs1Dev Modul jeder Aktor und Sensor in FHEM angelegt.
      (Default, xs1_control 0)

    • xs1_interval (0,30,60,180,360)
      Das ist der Intervall in Sekunden, in dem die Readings neu gelesen werden vom xs1.
      Bei Aktoren werden nur unterschiedliche Zustände aktualisiert im eingestellten Intervall.
      Sensoren werden unabhängig vom Zustand immer im Intervall aktualisiert.
      (Default, xs1_interval 60)


    Erläuterung:
    • Auszug Readings:
      • Aktor_(01-64)
      • definierter Aktor mit jeweiligem Zustand im Gerät
      • Aktor_(01-64)_name
      • definierter Aktorname im Gerät
      • Aktor_(01-64)_function(1-4)
      • definierte Aktorfunktion im Gerät
      • Sensor_(01-64)
      • definierter Sensor im Gerät
      • Sensor_(01-64)
      • definierter Sensorname im Gerät
      • Timer_(01-128)
      • definierter Timer im Gerät
      • xs1_bootloader
      • Firmwareversion des Bootloaders
      • xs1_dhcp
      • DHCP an/aus
      • xs1_features
      • erworbene Feature beim Kauf (A = SENDEN | B = EMPFANGEN | C = Skripte/Makros | D = Speicherkartenzugriff)
      • xs1_firmware
      • Firmwareversion
      • xs1_start
      • Gerätestart

    • Die Meldung "Error: Can't connect ..." oder "ERROR: empty answer received" im System-Logfile, besagt das kurzzeitig keine Abfrage erfolgen konnte.
      (Das kann häufiger bei DLAN vorkommen.)

    • Sollte das Gerät nach 5 Verbindungsversuchen ebenfalls keine Verbindung erhalten haben, so schaltet das Modul auf < disable > !

    • Logfile Erstellung erfolgt automatisch nach dem definieren. | Schema: define FileLog_xs1Bridge FileLog ./log/xs1Bridge-%Y-%m.log <Name>
      Folgende Werte werden im Logfile erfasst: Timer | xs1-Statusinformationen

    • Sollte das Gerät nach 5 Verbindungsversuchen ebenfalls keine Verbindung erhalten haben, so schaltet das Modul auf < disable > !

=end html_DE =cut