#################################################################
# $Id: 88_xs1Bridge.pm 25799 2022-03-08 19:48:45Z HomeAuto_User $
#################################################################
# 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_function:0,1 '.
                      'view_Device_name: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}}) {
    $hash = $modules{xs1Bridge}{defptr}{$d};
  }

  return;
}


##########################
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->{BRIDGE}   = 1;
  $hash->{STATE}    = 'Initialized';  ## Der Status des Modules nach Initialisierung.
  $hash->{TIME}     = time();         ## Zeitstempel, derzeit vom anlegen des Moduls
  $hash->{VERSION}  = '1.27';         ## Version

  # Attribut gesetzt
  if( not defined( $attr{$name}{xs1_control} ) ) { $attr{$name}{xs1_control}   = '0'; }
  if( not defined( $attr{$name}{xs1_interval} ) ) { $attr{$name}{xs1_interval}  = '60'; }

  $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
  } else {
    Log3 $name, 4, "$typ: FileLog_xs1Bridge ist definiert";
  }

  return;
}


##########################
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;

  @string_attrValue = split(',',$attrValue) if (defined $attrValue);    ## for Check Blacklist
  my $length = scalar @string_attrValue;                                ## for Check Blacklist

  #### Handling bei set .. attribute
  if ($cmd eq 'set') {
    RemoveInternalTimer($hash);                                   ## Timer löschen
    Debug " $typ: Attr | Cmd:$cmd | RemoveInternalTimer" if($debug == 2);

      ### xs1_interval == 0 ###
    if ($attrName eq 'xs1_interval' && $attrValue == 0) {
      RemoveInternalTimer($hash);
      readingsSingleUpdate($hash, 'state', 'deactive', 1);

      ### xs1_interval >= 30 ###
    } elsif ($attrName eq 'xs1_interval' && $attrValue >= 30) {
      $xs1_ConnectionTry = 1;
      $xs1_interval = $attrValue;
      InternalTimer(gettimeofday()+$xs1_interval, 'xs1Bridge_GetUpDate', $hash);
      readingsSingleUpdate($hash, 'state', 'active', 1);

      ### view_Device_function ###
    } elsif ($attrName eq 'view_Device_function') {
      if ($attrValue eq '1') {
        #Log3 $name, 3, "$typ: Attribut view_Device_function $cmd to $attrValue";
      } elsif ($attrValue eq '0') {
        #Log3 $name, 3, "$typ: Attribut view_Device_function $cmd to $attrValue";
      }

      ### view_Device_name ###
    } elsif ($attrName eq 'view_Device_name') {
        if ($attrValue eq '1') {
          #Log3 $name, 3, "$typ: Attribut view_Device_name $cmd to $attrValue";
        } elsif ($attrValue eq '0') {
          #Log3 $name, 3, "$typ: Attribut view_Device_name $cmd to $attrValue";
          for my $i (0..64) {
            if ($hash->{READINGS}) {
              if ($hash->{READINGS}{'Aktor_'.sprintf("%02d", $i).'_name'})  { delete $hash->{READINGS}{'Aktor_'.sprintf("%02d", $i).'_name'}; }
              if ($hash->{READINGS}{'Sensor_'.sprintf("%02d", $i).'_name'}) { delete $hash->{READINGS}{'Sensor_'.sprintf("%02d", $i).'_name'}; }
            }
          }
        }

      ### update_only_difference - Wertaenderung nur bei Difference ###
    } elsif ($attrName eq 'update_only_difference') {
      if ($attrValue eq '1') {
        #Log3 $name, 3, "$typ: Attribut update_only_difference $cmd to $attrValue";
      } elsif ($attrValue eq '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') {
        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') {
      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') {
      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') {
      RemoveInternalTimer($hash);
      readingsSingleUpdate($hash, 'state', 'deactive', 1);
      Debug " $typ: Attr | Cmd:$cmd | $attrName" if($debug == 2);

    } elsif ($attrName eq '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') {
      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;
}


##########################
sub xs1Bridge_GetUpDate {
  my ($hash) = @_;
  my $name = $hash->{NAME};
  my $typ = $hash->{TYPE};
  my $state = $hash->{STATE};
  my $xs1_ip = $hash->{xs1_ip};

  my $def;
  my $xs1_uptimeNew   = $hash->{helper}{xs1_uptimeNew};
  my $xs1_uptimeOld   = $hash->{helper}{xs1_uptimeOld};
  my $xs1_uptimeStart = $hash->{helper}{xs1_uptimeStart};

  #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 $update_only_difference = AttrVal($hash->{NAME},'update_only_difference',0);
  my $viewDeviceFunction = AttrVal($hash->{NAME},'view_Device_function',0);
  my $viewDeviceName = AttrVal($hash->{NAME},'view_Device_name',0);
  my $xs1_blackl_aktor = AttrVal($hash->{NAME},'xs1_blackl_aktor',0);
  my $xs1_blackl_sensor = AttrVal($hash->{NAME},'xs1_blackl_sensor',0);
  my $xs1_control = AttrVal($hash->{NAME},'xs1_control',0);
  my $xs1_interval = AttrVal($name, 'xs1_interval', 60);

  #### 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}) {    ## 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 $decoded;
      my $json;
      my $json_utf8;

      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";
  }

  return;
}


##########################
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;
  my $valuenew;

  #### 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][=]./);
    $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;
  } elsif ($Http_data ne '') {
    Debug " $typ: Write | Send to xs1 -> $xs1cmd" if($debug == 1);    ## Kontrolle Sendebefehl
  }

  return;
}


##########################
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;
}


##########################
# eigene Sub

sub is_in_array {
  my ($hash,$xs1_id,$i) = @_;
  my $name = $hash->{NAME};
  my $typ = $hash->{TYPE};
  my $xs1_blackl;

  if ($i == 0) {
    $xs1_blackl = AttrVal($hash->{NAME},'xs1_blackl_aktor',0);
  } elsif ($i == 1) {
    $xs1_blackl = AttrVal($hash->{NAME},'xs1_blackl_sensor',0);
  }

  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
