diff --git a/fhem/CHANGED b/fhem/CHANGED index 50e414b11..e0a425ff1 100644 --- a/fhem/CHANGED +++ b/fhem/CHANGED @@ -1,5 +1,6 @@ # Add changes at the top of the list. Keep it in ASCII, and 80-char wide. # Do not insert empty lines here, update check depends on it. + - new: 88_xs1Bridge: New modul to read xs1 EZcontrol - change: 93_DbRep: V7.0.0, changelog: - faster exec if no time attribut/aggregation is set - bugfix in fetchrows if data contain chars like "' diff --git a/fhem/FHEM/88_xs1Bridge.pm b/fhem/FHEM/88_xs1Bridge.pm new file mode 100644 index 000000000..fba5bdfb5 --- /dev/null +++ b/fhem/FHEM/88_xs1Bridge.pm @@ -0,0 +1,401 @@ +############################################################# +# $Id$ +############################################################# +# ToDo´s: (physisches Modul - Verbindung zur Hardware) +# +# ... +############################################################# + +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 POSIX; +use Time::Local; + +use JSON qw( decode_json ); # JSON +use LWP::Simple; # JSON +use utf8; # UTF-8 +use Encode qw(encode_utf8); # UTF-8 +use Net::Ping; # Ping Test Verbindung + +sub xs1Bridge_Initialize($) { + my ($hash) = @_; + + $hash->{WriteFn} = "xs1Bridge_Write"; + $hash->{Clients} = ":xs1Device:"; + $hash->{MatchList} = { "1:xs1Device" => '.*' }; ## zum testen lt. Forum - https://regex101.com/ + + #$hash->{MatchList} = { "1:xs1Device" => '{\n..version.:.+}' }; ## zum testen lt. Forum - https://regex101.com/ + #$hash->{MatchList} = { "1:xs1Device" => '^{"id":".*' }; + + $hash->{DefFn} = "xs1Bridge_Define"; + $hash->{AttrFn} = "xs1Bridge_Attr"; + $hash->{AttrList} = "debug:0,1 ". + "disable:0,1 ". + "ignore:0,1 ". + "interval:10,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); + + return "Usage: define $name " if(@arg != 3); + + # Parameter Define + my $xs1_ip = $arg[2]; ## Zusatzparameter 1 bei Define - ggf. nur in Sub + $hash->{xs1_ip} = $xs1_ip; + + if (&xs1Bridge_Ping == 1) { ## IP - Check + $hash->{STATE} = "Initialized"; ## Der Status des Modules nach Initialisierung. + $hash->{TIME} = time(); ## Zeitstempel, derzeit vom anlegen des Moduls + $hash->{VERSION} = "1.04"; ## Version + $hash->{BRIDGE} = 1; + + # Attribut gesetzt + $attr{$name}{disable} = "0"; + $attr{$name}{interval} = "60"; + $attr{$name}{room} = "xs1" if( not defined( $attr{$name}{room} ) ); + + $modules{xs1Bridge}{defptr}{BRIDGE} = $hash; + + InternalTimer(gettimeofday()+$attr{$name}{interval}, "xs1Bridge_GetUpDate", $hash); ## set Timer + + Log3 $name, 3, "$typ: Module defined with xs1_ip: $xs1_ip"; + return undef; + } + else + { + return "ERROR - Host IP $xs1_ip is not reachable. Please check!"; + } +} + +sub xs1Bridge_Attr(@) { + my ($cmd,$name,$attrName,$attrValue) = @_; + my $hash = $defs{$name}; + my $typ = $hash->{TYPE}; + my $interval = 0; + my $debug = AttrVal($hash->{NAME},"debug",0); + + # $cmd - Vorgangsart - kann die Werte "del" (löschen) oder "set" (setzen) annehmen + # $name - Gerätename + # $attrName/$attrValue sind Attribut-Name und Attribut-Wert + + Debug " $typ: xs1_Attr | Attributes $attrName = $attrValue" if($debug); + + if ($cmd eq "set") { ## Handling bei set .. attribute + RemoveInternalTimer($hash); ## Timer löschen + Debug " $typ: xs1_Attr | Cmd:$cmd | RemoveInternalTimer" if($debug); + + if ($attrName eq "interval") { ## Abfrage Attribute + if (($attrValue !~ /^\d*$/) || ($attrValue < 5)) ## Bildschirmausgabe - Hinweis Wert zu klein + { + return "$typ: Interval is too small. Please define new Interval | (at least: 5 seconds)"; + } + my $interval = $attrValue; + } + elsif ($attrName eq "disable") { + if ($attrValue eq "1") { ## Handling bei attribute disable 1 + readingsSingleUpdate($hash, "state", "deactive", 1); + } + elsif ($attrValue eq "0") { ## Handling bei attribute disable 0 + readingsSingleUpdate($hash, "state", "active", 1); + } + } + } + + if ($cmd eq "del") { ## Handling bei del ... attribute + if ($attrName eq "disable" && !defined $attrValue) { + readingsSingleUpdate($hash, "state", "active", 1); + Debug " $typ: xs1_Attr | Cmd:$cmd | $attrName=$attrValue" if($debug); + } + if ($attrName eq "interval") { + RemoveInternalTimer($hash); + Debug " $typ: xs1_Attr | Cmd:$cmd | $attrName" if($debug); + } + + } + + if ($hash->{STATE} eq "active") { + RemoveInternalTimer($hash); + InternalTimer(gettimeofday()+$interval, "xs1Bridge_GetUpDate", $hash); + Debug " $typ: xs1_Attr | RemoveInternalTimer + InternalTimer" if($debug); + } + return undef; +} + +sub xs1Bridge_Delete($$) { + my ( $hash, $name ) = @_; + RemoveInternalTimer($hash); + return undef; +} + +sub xs1Bridge_Ping() { ## Check before Define + my ($hash) = @_; + my $name = $hash->{NAME}; + my $typ = $hash->{TYPE}; + my $xs1_ip = $hash->{xs1_ip}; + + my $timeout = "3"; + my $connection; + my $p = Net::Ping->new; + my $isAlive = $p->ping($xs1_ip , $timeout); + $p->close; + if ($isAlive) { + $connection = 1; + } else { + $connection = 0; + } +return ($connection); +} + +sub xs1Bridge_GetUpDate() { + my ($hash) = @_; + my $name = $hash->{NAME}; + my $typ = $hash->{TYPE}; + my $state = $hash->{STATE}; + my $def; + + #http://xxx.xxx.xxx.xxx/control?callback=cname&cmd=... + #get_list_actuators - list all actuators + #get_list_sensors - list all sensors + #get_list_timers - list all timers + #get_config_info - list all device info´s + #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 $disable = AttrVal($name, "disable", 0); + my $interval = AttrVal($name, "interval", 60); + + if (AttrVal($hash->{NAME},"disable",0) == 0) { + RemoveInternalTimer($hash); + InternalTimer(gettimeofday()+$interval, "xs1Bridge_GetUpDate", $hash); + Debug "\n ------------- ERROR CHECK - START -------------" if($debug); + Debug " $typ: xs1Bridge_GetUpDate | RemoveInternalTimer + InternalTimer" if($debug); + #Log3 $name, 3, "$typ: xs1Bridge_GetUpDate | RemoveInternalTimer + InternalTimer"; + + if ($state eq "Initialized") { + readingsSingleUpdate($hash, "state", "active", 1); + } + + ### JSON Abfrage - Schleife + my $decoded; + for my $i (0..4) { + my $adress = "http://".$hash->{xs1_ip}.$cmd.$cmdtyp[$i]; + Debug " $typ: xs1Bridge_GetUpDate | Adresse: $adress" if($debug); + + my $ua = LWP::UserAgent->new; ## CHECK JSON Adresse -> JSON-query, sonst FHEM shutdown + my $resp = $ua->request(HTTP::Request->new(GET => $adress)); + if ($resp->code != "200") { ## http://search.cpan.org/~oalders/HTTP-Message-6.13/lib/HTTP/Status.pm + #print "HTTP GET error code: ", $resp->code, "\n"; + #print "HTTP GET error message: ", $resp->message, "\n"; + Log3 $name, 3, "$typ: xs1Bridge_GetUpDate | HTTP GET error code ".$resp->code." -> no JSON-query"; + if ($i == 0 || $i == 1 || $i == 2 || $i == 3) {last}; ## ERROR JSON-query -> Abbruch schleife + } + + my ($json) = get($adress) =~ /[^(]*[}]/g; ## cut cname( + ) am Ende von Ausgabe -> ARRAY Struktur + my $json_utf8 = encode_utf8( $json ); ## UTF-8 character Bearbeitung, da xs1 TempSensoren ERROR + $decoded = decode_json($json_utf8); + + Dispatch($hash,$json_utf8,undef); ## Übergabe an anderes Modul, NUR KOMPLETTES JSON !!! + + if ($i <= 1 ) { ### Aktoren / Sensoren + my @array = @{ $decoded->{$arrayname[$i]} }; + foreach my $f ( @array ) { + if ($f->{"type"} ne "disabled") { + readingsSingleUpdate($hash, $readingsname[$i]."_".sprintf("%02d", $f->{"id"}) , $f->{"value"}, 1); + Debug " $typ: ".$readingsname[$i]."_".sprintf("%02d", $f->{"id"})." | ".$f->{"type"}." | ".$f->{"name"}." | ". $f->{"value"} if($debug); + #Log3 $name, 3, $f->{"id"}." | ".$f->{"type"}." | ".$f->{"name"}." | ". $f->{"value"}; + } + } + } elsif ($i == 2) { ### Info´s + my $features; + my $features_i=0; + while (defined $decoded->{'info'}{'features'}->[$features_i]) { + $features.= $decoded->{'info'}{'features'}->[$features_i]." "; + $features_i++; + } + readingsBeginUpdate($hash); + readingsBulkUpdate($hash, "xs1_devicename" , $decoded->{'info'}{'devicename'}); + readingsBulkUpdate($hash, "xs1_bootloader" , $decoded->{'info'}{'bootloader'}); + readingsBulkUpdate($hash, "xs1_hardware" , $decoded->{'info'}{'hardware'}); + readingsBulkUpdate($hash, "xs1_features" , $features); + readingsBulkUpdate($hash, "xs1_firmware" , $decoded->{'info'}{'firmware'}); + readingsBulkUpdate($hash, "xs1_mac" , $decoded->{'info'}{'mac'}); + readingsEndUpdate($hash, 1); + + Debug " $typ: xs1_devicename: ".$decoded->{'info'}{'devicename'} if($debug); + Debug " $typ: xs1_bootloader: ".$decoded->{'info'}{'bootloader'} if($debug); + Debug " $typ: xs1_hardware: ".$decoded->{'info'}{'hardware'} if($debug); + Debug " $typ: xs1_features: ".$features if($debug); + Debug " $typ: xs1_firmware: ".$decoded->{'info'}{'firmware'} if($debug); + Debug " $typ: xs1_mac: ".$decoded->{'info'}{'mac'} if($debug); + + } elsif ($i == 3) { ### Timers noch BUG !!! + my @array = @{ $decoded->{$arrayname[$i]} }; + foreach my $f ( @array ) { + if ($f->{"type"} ne "disabled") { + 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); + } + } + } elsif ($i == 4) { ### Aktoren / Funktion != disable + my @array2 = @{ $decoded->{$arrayname[0]} }; + foreach my $f2 ( @array2 ) { + + if ($f2->{"type"} ne "disabled") { ## Funktion != actuator -> type disable + my @array = @{ $decoded->{'actuator'}->[($f2->{"id"})-1]->{$arrayname[$i]} }; + my $i2 = 0; ## Funktionscounter + + foreach my $f3 ( @array ) { + $i2 = $i2+1; + if ($f3->{"type"} ne "disabled") { ## Funktion != function -> type disable + Debug " $typ: ".$readingsname[0]."_".sprintf("%02d", $f2->{"id"})." | ".$f2->{"type"}." | ".$arrayname[$i]."_".$i2." | ".$f3->{"type"} if($debug); + #readingsSingleUpdate($hash, $readingsname[0]."_".sprintf("%02d", $f2->{"id"})."_".$arrayname[$i]."_".$i2 , $f3->{"type"} , 1); + } + } + } + } + } + + if ($i < 4) { + Debug "\n ------------- ERROR CHECK - SUB -------------" if($debug); + } + ### Schleifen Ende ### + } + + Debug "\n ------------- ERROR CHECK - ALL END -------------\n\n " if($debug); + } +} + +sub xs1Bridge_Write($) ## Zustellen von Daten via IOWrite() vom logischen zum physischen Modul um diese an die Hardware weiterzureichen. +{ + my ($hash, $Aktor_ID, $cmd) = @_; + my $name = $hash->{NAME}; + my $typ = $hash->{TYPE}; + + Log3 $name, 3, "$typ: xs1Bridge_Write | Aktor_ID=$Aktor_ID, cmd=$cmd"; +} + +# 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 Gerates xs1 der Firma EZControl +=begin html + + +

xs1Bridge

+ +=end html +=begin html_DE + + +

xs1Bridge

+ +=end html_DE +=cut diff --git a/fhem/MAINTAINER.txt b/fhem/MAINTAINER.txt index 4ba52f896..aa376d354 100644 --- a/fhem/MAINTAINER.txt +++ b/fhem/MAINTAINER.txt @@ -366,6 +366,7 @@ FHEM/88_Itach_Relay.pm sachag Automatisierung FHEM/88_Itach_IRDevice ulimaass Sonstige Systeme FHEM/88_VantagePro2.pm sachag Sonstiges FHEM/88_WEBCOUNT.pm sachag Sonstiges +FHEM/88_xs1Bridge.pm HomeAuto_User Sonstige Systeme FHEM/89_FULLY.pm zap Frontends FHEM/89_HEATRONIC.pm heikoranft Sonstige Systeme FHEM/89_VCONTROL.pm adamwit Heizungssteuerung/Raumklima