2
0
mirror of https://github.com/fhem/fhem-mirror.git synced 2025-03-01 03:24:51 +00:00
fhem-mirror/fhem/FHEM/88_xs1Bridge.pm

886 lines
39 KiB
Perl
Raw Normal View History

#################################################################
# $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> $name <IP>" if(@arg != 3);
#return "Usage: define <NAME> $name <IP> <PORT>" 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&auml;tes xs1 der Firma EZControl
=begin html
<a name="xs1Bridge"></a>
<h3>xs1Bridge</h3>
<ul>
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.
<br><br>
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>
Testet firmware: v4.0.0.5326 (Beta) @me | v3.0.0.4493 @ForumUser<br>
<br><ul>
<u>Currently implemented types of xs1 for processing: </u><br>
<li>Aktor: dimmer, switch, shutter, timerswitch</li>
<li>Sensor: barometer, counter, counterdiff, light, motion, other, rain, rain_1h, rain_24h, rainintensity, remotecontrol, uv_index, waterdetector, winddirection, windgust, windspeed, windvariance</li>
</ul><br><br>
<a name="xs1Bridge_define"></a>
<b>Define</b><br>
<ul>
xs1 without password:&nbsp;&nbsp;<code>define &lt;NAME&gt; xs1Bridge &lt;IP&gt;</code><br>
xs1 with password:&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;<code>define &lt;NAME&gt; xs1Bridge &lt;User&gt;:&lt;Passwort&gt;@&lt;IP&gt;</code>
<br><br>
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.
<ul>
<li><code>&lt;IP&gt;</code> is IP address in the local network</li>
<li><code>&lt;User&gt;</code> is the administrator user admin (default)</li>
<li><code>&lt;Password&gt;</code> is the assigned administrator password in xs1</li>
</ul><br>
examples:
<ul>
define EZcontrol_xs1 xs1Bridge 192.168.1.45<br>
define EZcontrol_xs1 xs1Bridge admin:secret@192.168.1.45<br>
</ul>
</ul><br>
<b>Set</b>
<ul>N/A</ul><br>
<b>Get</b><br>
<ul>N/A</ul><br>
<a name="xs1_attr"></a>
<b>Attributes</b>
<ul>
<li>debug (0,1,2)<br>
This brings the module into a very detailed debug output in the logfile. Program parts can be checked and errors checked.<br>
(Default, debug 0)
</li><br>
<li>update_only_difference (0,1)<br>
The actuators defined in xs1 are only updated when the value changes.<br>
(Default, update_only_difference 0)</li><br>
<li>view_Device_name (0,1)<br>
The actor names defined in xs1 are read as Reading.<br>
(Default, view_Device_name 0)<br>
</li><br>
<li>view_Device_function (0,1)<br>
The actuator functions defined in xs1 are read out as Reading.<br>
(Default, view_Device_function 0)<br>
</li><br>
<li>xs1_blackl_aktor<br>
A comma-separated blacklist of actuators, which should not be created automatically as soon as xs1_control = 1(aktiv).<br>
(Example: 2,40,3)<br>
</li><br>
<li>xs1_blackl_sensor<br>
A comma-separated blacklist of sensors, which should not be created automatically as soon as xs1_control = 1(aktiv).<br>
(Example: 3,37,55)<br>
</li><br>
<li>xs1_control (0,1)<br>
Option to control the xs1. After activating this, the xs1Dev module creates each actuator and sensor in FHEM.<br>
(Default, xs1_control 0)<br><br>
<li>xs1_interval (0,30,60,180,360)<br>
This is the interval in seconds at which readings are read from xs1<br>
<i>For actuators, only different states are updated in the set interval.</i><br>
<i>Sensors are always updated in intervals, regardless of the status.</i><br>
(Default, xs1_interval 60)
</li><br>
</li><br><br>
</ul>
<b>explanation:</b>
<ul>
<li>various Readings:</li>
<ul>
<li>Aktor_(01-64)</li> defined actuator in the device<br>
<li>Aktor_(01-64)_name</li> defined actor name in the device<br>
<li>Aktor_(01-64)_function(1-4)</li> defined actuator function in the device<br>
<li>Sensor_(01-64)</li> defined sensor in the device<br>
<li>Sensor_(01-64)_name</li> defined sensor name in the device<br>
<li>Timer_(01-128)</li> defined timer in the device<br>
<li>xs1_bootloader</li> version of bootloader<br>
<li>xs1_dhcp</li> DHCP on/off<br>
<li>xs1_features</li> purchased feature when buying (A = send | B = receive | C = Skripte/Makros | D = Media Access)<br>
<li>xs1_firmware</li> firmware number<br>
<li>xs1_start</li> device start<br>
</ul><br>
<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>
(This can happen more often with DLAN.)<br><br></li>
<li>If the device has not been connected after 5 connection attempts, the module will switch on < disable > !</li><br>
<li>Create logfile automatically after define | scheme: <code>define FileLog_xs1Bridge FileLog ./log/xs1Bridge-%Y-%m.log &lt;name&gt;</code><br>
The following values are recorded in logfile: Timer | xs1-status information</li>
</ul>
</ul>
=end html
=begin html_DE
<a name="xs1Bridge"></a>
<h3>xs1Bridge</h3>
<ul>
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.
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.
<br><br>
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>
Getestete Firmware: v4.0.0.5326 (Beta) @me | v3.0.0.4493 @ForumUser<br>
<br><ul>
<u>Derzeit implementierte Typen des xs1 zur Verarbeitung: </u><br>
<li>Aktor: dimmer, switch, shutter, timerswitch</li>
<li>Sensor: barometer, counter, counterdiff, light, motion, other, rain, rain_1h, rain_24h, rainintensity, remotecontrol, uv_index, waterdetector, winddirection, windgust, windspeed, windvariance</li>
</ul><br><br>
<a name="xs1Bridge_define"></a>
<b>Define</b><br>
<ul>
xs1 ohne Passwortabfrage:&nbsp;&nbsp;<code>define &lt;NAME&gt; xs1Bridge &lt;IP&gt;</code><br>
xs1 mit Passwortabfrage:&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;<code>define &lt;NAME&gt; xs1Bridge &lt;User&gt;:&lt;Passwort&gt;@&lt;IP&gt;</code>
<br><br>
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.
<ul>
<li><code>&lt;IP&gt;</code> ist IP-Adresse im lokalen Netzwerk</li>
<li><code>&lt;User&gt;</code> ist der Administrator Benutzer admin (standard)</li>
<li><code>&lt;Passwort&gt;</code> ist das vergebene Administrator Passwort im xs1.</li>
</ul><br>
Beispiele:
<ul>
define EZcontrol_xs1 xs1Bridge 192.168.1.45<br>
define EZcontrol_xs1 xs1Bridge admin:geheim@192.168.1.45<br>
</ul>
</ul><br>
<b>Set</b>
<ul>N/A</ul><br>
<b>Get</b><br>
<ul>N/A</ul><br>
<a name="xs1_attr"></a>
<b>Attribute</b>
<ul>
<li>debug (0,1,2)<br>
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>
(Default, debug 0)
</li><br>
<li>update_only_difference (0,1)<br>
Die Aktoren welche im xs1 definiert wurden, werden nur bei Wert&auml;nderung aktualisiert.<br>
(Default, update_only_difference 0)</li><br>
<li>view_Device_name (0,1)<br>
Die Aktor Namen welche im xs1 definiert wurden, werden als Reading ausgelesen.<br>
(Default, view_Device_name 0)<br>
</li><br>
<li>view_Device_function (0,1)<br>
Die Aktor Funktionen welche im xs1 definiert wurden, werden als Reading ausgelesen.<br>
(Default, view_Device_function 0)<br>
</li><br>
<li>xs1_blackl_aktor<br>
Eine kommagetrennte Blacklist der Aktoren, welche nicht automatisch angelegt werden sollen sobald xs1_control = 1(aktiv).<br>
(Beispiel: 2,40,3)<br>
</li><br>
<li>xs1_blackl_sensor<br>
Eine kommagetrennte Blacklist der Sensoren, welche nicht automatisch angelegt werden sollen sobald xs1_control = 1(aktiv).<br>
(Beispiel: 3,37,55)<br>
</li><br>
<li>xs1_control (0,1)<br>
Die Freigabe zur Steuerung des xs1. Nach Aktivierung dieser, wird durch das xs1Dev Modul jeder Aktor und Sensor in FHEM angelegt.<br>
(Default, xs1_control 0)<br>
</li><br>
<li>xs1_interval (0,30,60,180,360)<br>
Das ist der Intervall in Sekunden, in dem die Readings neu gelesen werden vom xs1.<br>
<i>Bei Aktoren werden nur unterschiedliche Zust&auml;nde aktualisiert im eingestellten Intervall.</i><br>
<i>Sensoren werden unabhängig vom Zustand immer im Intervall aktualisiert.</i><br>
(Default, xs1_interval 60)
</li><br><br>
</ul>
<b>Erl&auml;uterung:</b>
<ul>
<li>Auszug Readings:</li>
<ul>
<li>Aktor_(01-64)</li> definierter Aktor mit jeweiligem Zustand im Ger&auml;t<br>
<li>Aktor_(01-64)_name</li> definierter Aktorname im Ger&auml;t<br>
<li>Aktor_(01-64)_function(1-4)</li> definierte Aktorfunktion im Ger&auml;t<br>
<li>Sensor_(01-64)</li> definierter Sensor im Ger&auml;t<br>
<li>Sensor_(01-64)</li> definierter Sensorname im Ger&auml;t<br>
<li>Timer_(01-128)</li> definierter Timer im Ger&auml;t<br>
<li>xs1_bootloader</li> Firmwareversion des Bootloaders<br>
<li>xs1_dhcp</li> DHCP an/aus<br>
<li>xs1_features</li> erworbene Feature beim Kauf (A = SENDEN | B = EMPFANGEN | C = Skripte/Makros | D = Speicherkartenzugriff)<br>
<li>xs1_firmware</li> Firmwareversion<br>
<li>xs1_start</li> Ger&auml;testart<br>
</ul><br>
<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>
(Das kann h&auml;ufiger bei DLAN vorkommen.)<br><br></li>
<li>Sollte das Ger&auml;t nach 5 Verbindungsversuchen ebenfalls keine Verbindung erhalten haben, so schaltet das Modul auf < disable > !</li><br>
<li>Logfile Erstellung erfolgt automatisch nach dem definieren. | Schema: <code>define FileLog_xs1Bridge FileLog ./log/xs1Bridge-%Y-%m.log &lt;Name&gt;</code><br>
Folgende Werte werden im Logfile erfasst: Timer | xs1-Statusinformationen</li><br>
<li>Sollte das Ger&auml;t nach 5 Verbindungsversuchen ebenfalls keine Verbindung erhalten haben, so schaltet das Modul auf < disable > !</li><br>
</ul>
</ul>
=end html_DE
=cut