patch-async_run_parseJson #48
@@ -1,8 +1,8 @@
 | 
			
		||||
###############################################################################
 | 
			
		||||
#
 | 
			
		||||
# Developed with Kate
 | 
			
		||||
# Developed with VSCodium and richterger perl plugin.
 | 
			
		||||
#
 | 
			
		||||
#  (c) 2017-2021 Copyright: Marko Oldenburg (fhemdevelopment at cooltux dot net)
 | 
			
		||||
#  (c) 2017-2022 Copyright: Marko Oldenburg (fhemdevelopment at cooltux dot net)
 | 
			
		||||
#  All rights reserved
 | 
			
		||||
#
 | 
			
		||||
#   Special thanks goes to comitters:
 | 
			
		||||
@@ -57,7 +57,6 @@
 | 
			
		||||
package FHEM::GardenaSmartBridge;
 | 
			
		||||
use GPUtils qw(GP_Import GP_Export);
 | 
			
		||||
 | 
			
		||||
 | 
			
		||||
use strict;
 | 
			
		||||
use warnings;
 | 
			
		||||
use POSIX;
 | 
			
		||||
@@ -237,9 +236,7 @@ sub Define {
 | 
			
		||||
    my $name = shift @$aArg;
 | 
			
		||||
    $hash->{BRIDGE} = 1;
 | 
			
		||||
    $hash->{URL} =
 | 
			
		||||
      AttrVal( $name, 'gardenaBaseURL',
 | 
			
		||||
        'https://smart.gardena.com' )
 | 
			
		||||
      . '/v1';
 | 
			
		||||
      AttrVal( $name, 'gardenaBaseURL', 'https://smart.gardena.com' ) . '/v1';
 | 
			
		||||
    $hash->{VERSION}   = version->parse($VERSION)->normal;
 | 
			
		||||
    $hash->{INTERVAL}  = 60;
 | 
			
		||||
    $hash->{NOTIFYDEV} = "global,$name";
 | 
			
		||||
@@ -261,7 +258,7 @@ sub Undef {
 | 
			
		||||
    my $hash = shift;
 | 
			
		||||
    my $name = shift;
 | 
			
		||||
 | 
			
		||||
    RemoveInternalTimer($hash, "FHEM::GardenaSmartBridge::getDevices");
 | 
			
		||||
    RemoveInternalTimer( $hash, "FHEM::GardenaSmartBridge::getDevices" );
 | 
			
		||||
    delete $modules{GardenaSmartBridge}{defptr}{BRIDGE}
 | 
			
		||||
      if ( defined( $modules{GardenaSmartBridge}{defptr}{BRIDGE} ) );
 | 
			
		||||
 | 
			
		||||
@@ -282,7 +279,8 @@ sub Attr {
 | 
			
		||||
 | 
			
		||||
    if ( $attrName eq 'disable' ) {
 | 
			
		||||
        if ( $cmd eq 'set' && $attrVal eq '1' ) {
 | 
			
		||||
            RemoveInternalTimer($hash, "FHEM::GardenaSmartBridge::getDevices");
 | 
			
		||||
            RemoveInternalTimer( $hash,
 | 
			
		||||
                "FHEM::GardenaSmartBridge::getDevices" );
 | 
			
		||||
            readingsSingleUpdate( $hash, 'state', 'inactive', 1 );
 | 
			
		||||
            Log3 $name, 3, "GardenaSmartBridge ($name) - disabled";
 | 
			
		||||
        }
 | 
			
		||||
@@ -307,13 +305,15 @@ sub Attr {
 | 
			
		||||
        if ( $cmd eq 'set' ) {
 | 
			
		||||
            return 'Interval must be greater than 0'
 | 
			
		||||
              if ( $attrVal == 0 );
 | 
			
		||||
            RemoveInternalTimer($hash, "FHEM::GardenaSmartBridge::getDevices");
 | 
			
		||||
            RemoveInternalTimer( $hash,
 | 
			
		||||
                "FHEM::GardenaSmartBridge::getDevices" );
 | 
			
		||||
            $hash->{INTERVAL} = $attrVal;
 | 
			
		||||
            Log3 $name, 3,
 | 
			
		||||
              "GardenaSmartBridge ($name) - set interval: $attrVal";
 | 
			
		||||
        }
 | 
			
		||||
        elsif ( $cmd eq 'del' ) {
 | 
			
		||||
            RemoveInternalTimer($hash, "FHEM::GardenaSmartBridge::getDevices");
 | 
			
		||||
            RemoveInternalTimer( $hash,
 | 
			
		||||
                "FHEM::GardenaSmartBridge::getDevices" );
 | 
			
		||||
            $hash->{INTERVAL} = 60;
 | 
			
		||||
            Log3 $name, 3,
 | 
			
		||||
"GardenaSmartBridge ($name) - delete User interval and set default: 60";
 | 
			
		||||
@@ -360,13 +360,8 @@ sub Notify {
 | 
			
		||||
            )
 | 
			
		||||
        )
 | 
			
		||||
 | 
			
		||||
        || (
 | 
			
		||||
            $devtype eq 'GardenaSmartBridge'
 | 
			
		||||
            && (
 | 
			
		||||
                grep /^gardenaAccountPassword.+/,
 | 
			
		||||
                @{$events}
 | 
			
		||||
            )
 | 
			
		||||
        )
 | 
			
		||||
        || ( $devtype eq 'GardenaSmartBridge'
 | 
			
		||||
            && ( grep /^gardenaAccountPassword.+/, @{$events} ) )
 | 
			
		||||
        && $init_done
 | 
			
		||||
      );
 | 
			
		||||
 | 
			
		||||
@@ -399,6 +394,7 @@ sub Notify {
 | 
			
		||||
 | 
			
		||||
    return;
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
sub Get {
 | 
			
		||||
    my $hash = shift // return;
 | 
			
		||||
    my $aArg = shift // return;
 | 
			
		||||
@@ -410,14 +406,15 @@ sub Get {
 | 
			
		||||
    if ( lc $cmd eq 'debug_devices_list' ) {
 | 
			
		||||
        my $device = shift @$aArg;
 | 
			
		||||
        $hash->{helper}{debug_device} = $device;
 | 
			
		||||
        Write($hash, undef, undef, undef, undef); 
 | 
			
		||||
        return undef;
 | 
			
		||||
    } else {
 | 
			
		||||
        Write( $hash, undef, undef, undef, undef );
 | 
			
		||||
        return;
 | 
			
		||||
    }
 | 
			
		||||
    else {
 | 
			
		||||
        my $list = "";
 | 
			
		||||
        $list .= " debug_devices_list:"
 | 
			
		||||
          .join( ',', @{ $hash->{helper}{deviceList} }) 
 | 
			
		||||
          if ( AttrVal( $name, "debugDEVICE", "none") ne "none" 
 | 
			
		||||
           && exists($hash->{helper}{deviceList}) );
 | 
			
		||||
        $list .=
 | 
			
		||||
          " debug_devices_list:" . join( ',', @{ $hash->{helper}{deviceList} } )
 | 
			
		||||
          if ( AttrVal( $name, "debugDEVICE", "none" ) ne "none"
 | 
			
		||||
            && exists( $hash->{helper}{deviceList} ) );
 | 
			
		||||
        return "Unknown argument $cmd,choose one of $list";
 | 
			
		||||
    }
 | 
			
		||||
}
 | 
			
		||||
@@ -427,7 +424,8 @@ sub Set {
 | 
			
		||||
    my $aArg = shift // return;
 | 
			
		||||
 | 
			
		||||
    my $name = shift @$aArg // return;
 | 
			
		||||
    my $cmd  = shift @$aArg // return qq{"set $name" needs at least one argument};
 | 
			
		||||
    my $cmd  = shift @$aArg
 | 
			
		||||
      // return qq{"set $name" needs at least one argument};
 | 
			
		||||
 | 
			
		||||
#     Das Argument für das Passwort, also das Passwort an sich darf keine = enthalten!!!
 | 
			
		||||
 | 
			
		||||
@@ -478,7 +476,8 @@ sub Write {
 | 
			
		||||
    my ( $session_id, $header, $uri, $method );
 | 
			
		||||
 | 
			
		||||
    ( $payload, $session_id, $header, $uri, $method, $deviceId, $service_id ) =
 | 
			
		||||
      createHttpValueStrings( $hash, $payload, $deviceId, $abilities, $service_id );
 | 
			
		||||
      createHttpValueStrings( $hash, $payload, $deviceId, $abilities,
 | 
			
		||||
        $service_id );
 | 
			
		||||
 | 
			
		||||
    HttpUtils_NonblockingGet(
 | 
			
		||||
        {
 | 
			
		||||
@@ -500,8 +499,8 @@ sub Write {
 | 
			
		||||
"GardenaSmartBridge ($name) - Send with URL: $hash->{URL}$uri, HEADER: secret!, DATA: secret!, METHOD: $method"
 | 
			
		||||
    );
 | 
			
		||||
 | 
			
		||||
    #  Log3($name, 3,
 | 
			
		||||
    #      "GardenaSmartBridge ($name) - Send with URL: $hash->{URL}$uri, HEADER: $header, DATA: $payload, METHOD: $method");
 | 
			
		||||
#  Log3($name, 3,
 | 
			
		||||
#      "GardenaSmartBridge ($name) - Send with URL: $hash->{URL}$uri, HEADER: $header, DATA: $payload, METHOD: $method");
 | 
			
		||||
 | 
			
		||||
    return;
 | 
			
		||||
}
 | 
			
		||||
@@ -702,6 +701,11 @@ sub ErrorHandling {
 | 
			
		||||
              . $param->{code};
 | 
			
		||||
        }
 | 
			
		||||
 | 
			
		||||
        if ( !defined( $hash->{helper}{session_id} ) ) {
 | 
			
		||||
            readingsSingleUpdate( $hash, 'token', 'none', 1 );
 | 
			
		||||
            InternalTimer( gettimeofday() + 5,
 | 
			
		||||
                "FHEM::GardenaSmartBridge::getToken", $hash );
 | 
			
		||||
        }
 | 
			
		||||
        readingsEndUpdate( $dhash, 1 );
 | 
			
		||||
 | 
			
		||||
        Log3 $dname, 5,
 | 
			
		||||
@@ -712,59 +716,69 @@ sub ErrorHandling {
 | 
			
		||||
        delete $dhash->{helper}{deviceAction}
 | 
			
		||||
          if ( defined( $dhash->{helper}{deviceAction} ) );
 | 
			
		||||
 | 
			
		||||
        readingsSingleUpdate( $hash, 'token', 'none', 1 )
 | 
			
		||||
          if ( !defined( $hash->{helper}{session_id} ) );
 | 
			
		||||
 | 
			
		||||
        getToken($hash) 
 | 
			
		||||
          if ( !defined( $hash->{helper}{session_id} ) );
 | 
			
		||||
        return;
 | 
			
		||||
    }
 | 
			
		||||
    elsif (defined ($decode_json->{message}) 
 | 
			
		||||
          && $decode_json->{message} eq 'Unauthorized') {
 | 
			
		||||
    elsif ( defined( $decode_json->{message} )
 | 
			
		||||
        && $decode_json->{message} eq 'Unauthorized' )
 | 
			
		||||
    {
 | 
			
		||||
        Log3 $name, 3,
 | 
			
		||||
          "GardenaSmartBridge ($name) - Unauthorized -> fetch new token ";
 | 
			
		||||
 | 
			
		||||
        getToken($hash);
 | 
			
		||||
 | 
			
		||||
        return;
 | 
			
		||||
    }
 | 
			
		||||
 | 
			
		||||
  if (defined($hash->{helper}{debug_device})
 | 
			
		||||
    	&& $hash->{helper}{debug_device} ne 'none'
 | 
			
		||||
    	){
 | 
			
		||||
    if ( defined( $hash->{helper}{debug_device} )
 | 
			
		||||
        && $hash->{helper}{debug_device} ne 'none' )
 | 
			
		||||
    {
 | 
			
		||||
        Log3 $name, 4, "GardenaSmartBridge DEBUG Device";
 | 
			
		||||
        delete $hash->{helper}{debug_device};
 | 
			
		||||
      my @device_spec = ("name", "id", "category");
 | 
			
		||||
      my $devJson=$decode_json->{devices};
 | 
			
		||||
      my $output = '.:{ DEBUG OUTPUT for '.$devJson->{name}.' }:. \n';
 | 
			
		||||
 | 
			
		||||
        my @device_spec = ( "name", "id", "category" );
 | 
			
		||||
        my $devJson     = $decode_json->{devices};
 | 
			
		||||
        my $output = '.:{ DEBUG OUTPUT for ' . $devJson->{name} . ' }:. \n';
 | 
			
		||||
 | 
			
		||||
        for my $spec (@device_spec) {
 | 
			
		||||
            $output .= "$spec : $devJson->{$spec} \n";
 | 
			
		||||
        }
 | 
			
		||||
 | 
			
		||||
        #settings
 | 
			
		||||
        $output .= '\n=== Settings \n';
 | 
			
		||||
        my $i = 0;
 | 
			
		||||
      for my $dev_settings ( @ { $devJson->{settings} } ) {
 | 
			
		||||
        $output .= "[".$i++."]id: $dev_settings->{id} \n";
 | 
			
		||||
        for my $dev_settings ( @{ $devJson->{settings} } ) {
 | 
			
		||||
            $output .= "[" . $i++ . "]id: $dev_settings->{id} \n";
 | 
			
		||||
            $output .= "name: $dev_settings->{name} ";
 | 
			
		||||
        if (ref ($dev_settings->{value}) eq 'ARRAY' 
 | 
			
		||||
          || ref ($dev_settings->{value}) eq 'HASH'){
 | 
			
		||||
            if (   ref( $dev_settings->{value} ) eq 'ARRAY'
 | 
			
		||||
                || ref( $dev_settings->{value} ) eq 'HASH' )
 | 
			
		||||
            {
 | 
			
		||||
                $output .= 'N/A \n';
 | 
			
		||||
        } else {
 | 
			
		||||
            }
 | 
			
		||||
            else {
 | 
			
		||||
                $output .= "value: $dev_settings->{value} \n";
 | 
			
		||||
            }
 | 
			
		||||
        }
 | 
			
		||||
 | 
			
		||||
        $output .= '\n=== Abilities \n';
 | 
			
		||||
        $i = 0;
 | 
			
		||||
      for my $dev_settings ( @ { $devJson->{abilities} } ) {
 | 
			
		||||
        $output .= "[".$i++."]id: $dev_settings->{id} \n";
 | 
			
		||||
 | 
			
		||||
        for my $dev_settings ( @{ $devJson->{abilities} } ) {
 | 
			
		||||
            $output .= "[" . $i++ . "]id: $dev_settings->{id} \n";
 | 
			
		||||
            $output .= "name: $dev_settings->{name} ";
 | 
			
		||||
        if (ref ($dev_settings->{value}) eq 'ARRAY' 
 | 
			
		||||
          || ref ($dev_settings->{value}) eq 'HASH'){
 | 
			
		||||
 | 
			
		||||
            if (   ref( $dev_settings->{value} ) eq 'ARRAY'
 | 
			
		||||
                || ref( $dev_settings->{value} ) eq 'HASH' )
 | 
			
		||||
            {
 | 
			
		||||
                $output .= 'N/A \n';
 | 
			
		||||
        } else {
 | 
			
		||||
            }
 | 
			
		||||
            else {
 | 
			
		||||
                $output .= "value: $dev_settings->{value} \n";
 | 
			
		||||
            }
 | 
			
		||||
        }
 | 
			
		||||
 | 
			
		||||
        $hash->{helper}{debug_device_output} = $output;
 | 
			
		||||
      asyncOutput($param->{cl},  $hash->{helper}{debug_device_output});
 | 
			
		||||
        asyncOutput( $param->{cl}, $hash->{helper}{debug_device_output} );
 | 
			
		||||
 | 
			
		||||
        return;
 | 
			
		||||
    }
 | 
			
		||||
    readingsSingleUpdate( $hash, 'state', 'Connected', 1 )
 | 
			
		||||
@@ -796,16 +810,20 @@ sub ResponseProcessing {
 | 
			
		||||
 | 
			
		||||
    # print Dumper $decode_json;
 | 
			
		||||
 | 
			
		||||
    if ( defined( $decode_json->{data} ) && $decode_json->{data} 
 | 
			
		||||
        && ref($decode_json->{data}) eq 'HASH'
 | 
			
		||||
        && !defined( $hash->{helper}->{user_id})) {
 | 
			
		||||
    if (   defined( $decode_json->{data} )
 | 
			
		||||
        && $decode_json->{data}
 | 
			
		||||
        && ref( $decode_json->{data} ) eq 'HASH'
 | 
			
		||||
        && !defined( $hash->{helper}->{user_id} ) )
 | 
			
		||||
    {
 | 
			
		||||
 | 
			
		||||
        $hash->{helper}{session_id} = $decode_json->{data}{id};
 | 
			
		||||
        $hash->{helper}{user_id} = $decode_json->{data}{attributes}->{user_id};
 | 
			
		||||
        $hash->{helper}{refresh_token}  = $decode_json->{data}{attributes}->{refresh_token};
 | 
			
		||||
        $hash->{helper}{token_expired}  = gettimeofday() + $decode_json->{data}{attributes}->{expires_in};
 | 
			
		||||
        $hash->{helper}{refresh_token} =
 | 
			
		||||
          $decode_json->{data}{attributes}->{refresh_token};
 | 
			
		||||
        $hash->{helper}{token_expired} =
 | 
			
		||||
          gettimeofday() + $decode_json->{data}{attributes}->{expires_in};
 | 
			
		||||
 | 
			
		||||
        InternalTimer($hash->{helper}{token_expired},
 | 
			
		||||
        InternalTimer( $hash->{helper}{token_expired},
 | 
			
		||||
            "FHEM::GardenaSmartBridge::getToken", $hash );
 | 
			
		||||
 | 
			
		||||
        Write( $hash, undef, undef, undef );
 | 
			
		||||
@@ -838,27 +856,76 @@ sub ResponseProcessing {
 | 
			
		||||
        && ref( $decode_json->{devices} ) eq 'ARRAY'
 | 
			
		||||
        && scalar( @{ $decode_json->{devices} } ) > 0 )
 | 
			
		||||
    {
 | 
			
		||||
 | 
			
		||||
        my @buffer = split( '"devices":\[', $json );
 | 
			
		||||
 | 
			
		||||
        my ( $json, $tail ) = ParseJSON( $hash, $buffer[1] );
 | 
			
		||||
        require SubProcess;
 | 
			
		||||
 | 
			
		||||
        while ($json) {
 | 
			
		||||
        my $subprocess =
 | 
			
		||||
          SubProcess->new( { onRun => \&ResponseSubprocessing } );
 | 
			
		||||
        $subprocess->{buffer} = $buffer[1];
 | 
			
		||||
 | 
			
		||||
            Log3 $name, 5,
 | 
			
		||||
                "GardenaSmartBridge ($name) - Decoding JSON message. Length: "
 | 
			
		||||
              . length($json)
 | 
			
		||||
              . " Content: "
 | 
			
		||||
              . $json;
 | 
			
		||||
            Log3 $name, 5,
 | 
			
		||||
                "GardenaSmartBridge ($name) - Vor Sub: Laenge JSON: "
 | 
			
		||||
              . length($json)
 | 
			
		||||
              . " Content: "
 | 
			
		||||
              . $json
 | 
			
		||||
              . " Tail: "
 | 
			
		||||
              . $tail;
 | 
			
		||||
        my $pid = $subprocess->run();
 | 
			
		||||
 | 
			
		||||
            if ( defined($tail) and $tail ) {
 | 
			
		||||
        if ( !defined($pid) ) {
 | 
			
		||||
            Log3( $name, 1,
 | 
			
		||||
qq{GardenaSmartBridge ($name) - Cannot execute parse json asynchronously}
 | 
			
		||||
            );
 | 
			
		||||
 | 
			
		||||
            CleanSubprocess($hash);
 | 
			
		||||
            readingsSingleUpdate( $hash, 'state',
 | 
			
		||||
                'Cannot execute parse json asynchronously', 1 );
 | 
			
		||||
            return;
 | 
			
		||||
        }
 | 
			
		||||
 | 
			
		||||
        Log3( $name, 4,
 | 
			
		||||
qq{GardenaSmartBridge ($name) - execute parse json asynchronously (PID="$pid")}
 | 
			
		||||
        );
 | 
			
		||||
 | 
			
		||||
        $hash->{".fhem"}{subprocess} = $subprocess;
 | 
			
		||||
 | 
			
		||||
        InternalTimer( gettimeofday() + 1,
 | 
			
		||||
            "FHEM::GardenaSmartBridge::PollChild", $hash );
 | 
			
		||||
    }
 | 
			
		||||
 | 
			
		||||
    Log3 $name, 3, "GardenaSmartBridge ($name) - no Match for processing data";
 | 
			
		||||
 | 
			
		||||
    return;
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
sub PollChild {
 | 
			
		||||
    my $hash = shift;
 | 
			
		||||
 | 
			
		||||
    my $name = $hash->{NAME};
 | 
			
		||||
 | 
			
		||||
    if ( defined( $hash->{".fhem"}{subprocess} ) ) {
 | 
			
		||||
        my $subprocess = $hash->{".fhem"}{subprocess};
 | 
			
		||||
        my $response   = $subprocess->readFromChild();
 | 
			
		||||
 | 
			
		||||
        if ( !defined($response) ) {
 | 
			
		||||
            Log3( $name, 5,
 | 
			
		||||
qq{GardenaSmartBridge ($name) - still waiting ($subprocess->{lasterror}).}
 | 
			
		||||
            );
 | 
			
		||||
 | 
			
		||||
            InternalTimer( gettimeofday() + 1,
 | 
			
		||||
                "FHEM::GardenaSmartBridge::PollChild", $hash );
 | 
			
		||||
            return;
 | 
			
		||||
        }
 | 
			
		||||
        else {
 | 
			
		||||
            Log3( $name, 4,
 | 
			
		||||
qq{GardenaSmartBridge ($name) - got result from asynchronous parsing}
 | 
			
		||||
            );
 | 
			
		||||
 | 
			
		||||
            my $decode_json;
 | 
			
		||||
 | 
			
		||||
            $subprocess->wait();
 | 
			
		||||
            Log3( $name, 4,
 | 
			
		||||
                qq{GardenaSmartBridge ($name) - asynchronous finished.} );
 | 
			
		||||
 | 
			
		||||
            CleanSubprocess($hash);
 | 
			
		||||
 | 
			
		||||
            for my $json ( @{$response} ) {
 | 
			
		||||
 | 
			
		||||
                #################
 | 
			
		||||
                $decode_json = eval { decode_json($json) };
 | 
			
		||||
                if ($@) {
 | 
			
		||||
                    Log3 $name, 5,
 | 
			
		||||
@@ -871,26 +938,76 @@ sub ResponseProcessing {
 | 
			
		||||
                  if ( defined( $decode_json->{category} )
 | 
			
		||||
                    && $decode_json->{category} eq 'gateway' );
 | 
			
		||||
            }
 | 
			
		||||
        }
 | 
			
		||||
    }
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
            ( $json, $tail ) = ParseJSON( $hash, $tail );
 | 
			
		||||
# ResponseSubprocessin muss in eine async ausgelagert werden
 | 
			
		||||
######################################
 | 
			
		||||
# Begin Childprozess
 | 
			
		||||
######################################
 | 
			
		||||
sub ResponseSubprocessing {
 | 
			
		||||
    my $subprocess = shift;
 | 
			
		||||
    my $buffer     = $subprocess->{buffer};
 | 
			
		||||
    my $response   = [];
 | 
			
		||||
 | 
			
		||||
            Log3 $name, 5,
 | 
			
		||||
                "GardenaSmartBridge ($name) - Nach Sub: Laenge JSON: "
 | 
			
		||||
              . length($json)
 | 
			
		||||
              . " Content: "
 | 
			
		||||
              . $json
 | 
			
		||||
              . " Tail: "
 | 
			
		||||
              . $tail;
 | 
			
		||||
    my ( $json, $tail ) = ParseJSON($buffer);
 | 
			
		||||
 | 
			
		||||
    while ($json) {
 | 
			
		||||
        if ( defined($tail) and $tail ) {
 | 
			
		||||
            push @{$response}, $json;
 | 
			
		||||
        }
 | 
			
		||||
 | 
			
		||||
        return;
 | 
			
		||||
        ( $json, $tail ) = ParseJSON($tail);
 | 
			
		||||
    }
 | 
			
		||||
 | 
			
		||||
    Log3 $name, 3, "GardenaSmartBridge ($name) - no Match for processing data";
 | 
			
		||||
    $subprocess->writeToParent($response);
 | 
			
		||||
 | 
			
		||||
    return;
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
sub ParseJSON {
 | 
			
		||||
    my $buffer = shift;
 | 
			
		||||
 | 
			
		||||
    my $open  = 0;
 | 
			
		||||
    my $close = 0;
 | 
			
		||||
    my $msg   = '';
 | 
			
		||||
    my $tail  = '';
 | 
			
		||||
 | 
			
		||||
    if ($buffer) {
 | 
			
		||||
        for my $c ( split //, $buffer ) {
 | 
			
		||||
            if ( $open == $close && $open > 0 ) {
 | 
			
		||||
                $tail .= $c;
 | 
			
		||||
            }
 | 
			
		||||
            else {
 | 
			
		||||
 | 
			
		||||
                if ( $c eq '{' ) {
 | 
			
		||||
 | 
			
		||||
                    $open++;
 | 
			
		||||
 | 
			
		||||
                }
 | 
			
		||||
                elsif ( $c eq '}' ) {
 | 
			
		||||
 | 
			
		||||
                    $close++;
 | 
			
		||||
                }
 | 
			
		||||
 | 
			
		||||
                $msg .= $c;
 | 
			
		||||
            }
 | 
			
		||||
        }
 | 
			
		||||
 | 
			
		||||
        if ( $open != $close ) {
 | 
			
		||||
 | 
			
		||||
            $tail = $msg;
 | 
			
		||||
            $msg  = '';
 | 
			
		||||
        }
 | 
			
		||||
    }
 | 
			
		||||
 | 
			
		||||
    return ( $msg, $tail );
 | 
			
		||||
}
 | 
			
		||||
######################################
 | 
			
		||||
# End Childprozess
 | 
			
		||||
######################################
 | 
			
		||||
 | 
			
		||||
sub WriteReadings {
 | 
			
		||||
    my $hash        = shift;
 | 
			
		||||
    my $decode_json = shift;
 | 
			
		||||
@@ -941,7 +1058,8 @@ sub WriteReadings {
 | 
			
		||||
                          {name} . '-' . $t,
 | 
			
		||||
                        $v
 | 
			
		||||
                      )
 | 
			
		||||
                      if ($decode_json->{abilities}[0]{properties}[$properties]{name} !~ /ethernet_status|wifi_status/ );
 | 
			
		||||
                      if ( $decode_json->{abilities}[0]{properties}[$properties]
 | 
			
		||||
                        {name} !~ /ethernet_status|wifi_status/ );
 | 
			
		||||
                    if (
 | 
			
		||||
                        (
 | 
			
		||||
                            $decode_json->{abilities}[0]{properties}
 | 
			
		||||
@@ -969,7 +1087,7 @@ sub WriteReadings {
 | 
			
		||||
                        {
 | 
			
		||||
                            readingsBulkUpdateIfChanged( $hash,
 | 
			
		||||
                                'wifi_status-ssid', $v->{ssid} )
 | 
			
		||||
                              if (ref($v->{ssid}) ne 'HASH');
 | 
			
		||||
                              if ( ref( $v->{ssid} ) ne 'HASH' );
 | 
			
		||||
                            readingsBulkUpdateIfChanged( $hash,
 | 
			
		||||
                                'wifi_status-mac', $v->{mac} );
 | 
			
		||||
                            readingsBulkUpdateIfChanged( $hash,
 | 
			
		||||
@@ -1002,24 +1120,22 @@ sub getDevices {
 | 
			
		||||
    my $hash = shift;
 | 
			
		||||
 | 
			
		||||
    my $name = $hash->{NAME};
 | 
			
		||||
    RemoveInternalTimer($hash, "FHEM::GardenaSmartBridge::getDevices");
 | 
			
		||||
    RemoveInternalTimer( $hash, "FHEM::GardenaSmartBridge::getDevices" );
 | 
			
		||||
 | 
			
		||||
    if ( not IsDisabled($name) ) {
 | 
			
		||||
 | 
			
		||||
        delete $hash->{helper}{deviceList};
 | 
			
		||||
        my @list;
 | 
			
		||||
        @list = devspec2array('TYPE=GardenaSmartDevice');
 | 
			
		||||
        for my $gardenaDev (@list){
 | 
			
		||||
        for my $gardenaDev (@list) {
 | 
			
		||||
            push( @{ $hash->{helper}{deviceList} }, $gardenaDev );
 | 
			
		||||
        }
 | 
			
		||||
        if ( AttrVal( $name, 'gardenaAccountEmail', 'none' ) ne 'none'
 | 
			
		||||
          && (
 | 
			
		||||
            defined( ReadPassword( $hash, $name ) ) 
 | 
			
		||||
          )) 
 | 
			
		||||
            && ( defined( ReadPassword( $hash, $name ) ) ) )
 | 
			
		||||
        {
 | 
			
		||||
            Write( $hash, undef, undef, undef );
 | 
			
		||||
            Log3 $name, 4,
 | 
			
		||||
            "GardenaSmartBridge ($name) - fetch device list and device states";
 | 
			
		||||
"GardenaSmartBridge ($name) - fetch device list and device states";
 | 
			
		||||
        }    # fi gardenaAccountEmail
 | 
			
		||||
    }
 | 
			
		||||
    else {
 | 
			
		||||
@@ -1044,27 +1160,29 @@ sub getToken {
 | 
			
		||||
    readingsSingleUpdate( $hash, 'state', 'get token', 1 );
 | 
			
		||||
 | 
			
		||||
    delete $hash->{helper}{session_id}
 | 
			
		||||
      if ( defined( $hash->{helper}{session_id} )
 | 
			
		||||
        && $hash->{helper}{session_id} );
 | 
			
		||||
      if ( exists( $hash->{helper}{session_id} ) );
 | 
			
		||||
    delete $hash->{helper}{user_id}
 | 
			
		||||
      if ( defined( $hash->{helper}{user_id} ) && $hash->{helper}{user_id} );
 | 
			
		||||
      if ( exists( $hash->{helper}{user_id} ) );
 | 
			
		||||
    delete $hash->{helper}{locations_id}
 | 
			
		||||
      if ( defined( $hash->{helper}{locations_id} )
 | 
			
		||||
        && $hash->{helper}{locations_id} );
 | 
			
		||||
      if ( exists( $hash->{helper}{locations_id} ) );
 | 
			
		||||
 | 
			
		||||
    Write(
 | 
			
		||||
        $hash,
 | 
			
		||||
        '"data": {"type":"token", "attributes":{"username": "'
 | 
			
		||||
          . AttrVal( $name, 'gardenaAccountEmail', 'none' )
 | 
			
		||||
          . '","password": "'
 | 
			
		||||
            . ReadPassword( $hash, $name ) . '", "client_id":"smartgarden-jwt-client"}}',
 | 
			
		||||
          . ReadPassword( $hash, $name )
 | 
			
		||||
          . '", "client_id":"smartgarden-jwt-client"}}',
 | 
			
		||||
        undef,
 | 
			
		||||
        undef
 | 
			
		||||
    );
 | 
			
		||||
 | 
			
		||||
    Log3 $name, 4, '"data": {"type":"token", "attributes":{"username": "'     
 | 
			
		||||
               .AttrVal( $name, 'gardenaAccountEmail', 'none' ) . '","password": "'
 | 
			
		||||
               .ReadPassword( $hash, $name ) . '", "client_id":"smartgarden-jwt-client"}}';
 | 
			
		||||
    Log3 $name, 4,
 | 
			
		||||
        '"data": {"type":"token", "attributes":{"username": "'
 | 
			
		||||
      . AttrVal( $name, 'gardenaAccountEmail', 'none' )
 | 
			
		||||
      . '","password": "'
 | 
			
		||||
      . ReadPassword( $hash, $name )
 | 
			
		||||
      . '", "client_id":"smartgarden-jwt-client"}}';
 | 
			
		||||
    Log3 $name, 3,
 | 
			
		||||
"GardenaSmartBridge ($name) - send credentials to fetch Token and locationId";
 | 
			
		||||
 | 
			
		||||
@@ -1115,7 +1233,7 @@ sub ReadPassword {
 | 
			
		||||
 | 
			
		||||
        Log3 $name, 3,
 | 
			
		||||
"GardenaSmartBridge ($name) - unable to read password from file: $err";
 | 
			
		||||
        return undef;
 | 
			
		||||
        return;
 | 
			
		||||
 | 
			
		||||
    }
 | 
			
		||||
 | 
			
		||||
@@ -1141,7 +1259,7 @@ sub ReadPassword {
 | 
			
		||||
    else {
 | 
			
		||||
 | 
			
		||||
        Log3 $name, 3, "GardenaSmartBridge ($name) - No password in file";
 | 
			
		||||
        return undef;
 | 
			
		||||
        return;
 | 
			
		||||
    }
 | 
			
		||||
 | 
			
		||||
    return;
 | 
			
		||||
@@ -1159,59 +1277,6 @@ sub Rename {
 | 
			
		||||
    return;
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
sub ParseJSON {
 | 
			
		||||
    my $hash   = shift;
 | 
			
		||||
    my $buffer = shift;
 | 
			
		||||
 | 
			
		||||
    my $name  = $hash->{NAME};
 | 
			
		||||
    my $open  = 0;
 | 
			
		||||
    my $close = 0;
 | 
			
		||||
    my $msg   = '';
 | 
			
		||||
    my $tail  = '';
 | 
			
		||||
 | 
			
		||||
    if ($buffer) {
 | 
			
		||||
        for my $c ( split //, $buffer ) {
 | 
			
		||||
            if ( $open == $close && $open > 0 ) {
 | 
			
		||||
                $tail .= $c;
 | 
			
		||||
                Log3 $name, 5,
 | 
			
		||||
                  "GardenaSmartBridge ($name) - $open == $close and $open > 0";
 | 
			
		||||
 | 
			
		||||
            }
 | 
			
		||||
            elsif ( ( $open == $close ) && ( $c ne '{' ) ) {
 | 
			
		||||
 | 
			
		||||
                Log3 $name, 5,
 | 
			
		||||
"GardenaSmartBridge ($name) - Garbage character before message: "
 | 
			
		||||
                  . $c;
 | 
			
		||||
 | 
			
		||||
            }
 | 
			
		||||
            else {
 | 
			
		||||
 | 
			
		||||
                if ( $c eq '{' ) {
 | 
			
		||||
 | 
			
		||||
                    $open++;
 | 
			
		||||
 | 
			
		||||
                }
 | 
			
		||||
                elsif ( $c eq '}' ) {
 | 
			
		||||
 | 
			
		||||
                    $close++;
 | 
			
		||||
                }
 | 
			
		||||
 | 
			
		||||
                $msg .= $c;
 | 
			
		||||
            }
 | 
			
		||||
        }
 | 
			
		||||
 | 
			
		||||
        if ( $open != $close ) {
 | 
			
		||||
 | 
			
		||||
            $tail = $msg;
 | 
			
		||||
            $msg  = '';
 | 
			
		||||
        }
 | 
			
		||||
    }
 | 
			
		||||
 | 
			
		||||
    Log3 $name, 5,
 | 
			
		||||
      "GardenaSmartBridge ($name) - return msg: $msg and tail: $tail";
 | 
			
		||||
    return ( $msg, $tail );
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
sub createHttpValueStrings {
 | 
			
		||||
    my ( $hash, $payload, $deviceId, $abilities, $service_id ) = @_;
 | 
			
		||||
 | 
			
		||||
@@ -1220,9 +1285,9 @@ sub createHttpValueStrings {
 | 
			
		||||
    my $uri        = '';
 | 
			
		||||
    my $method     = 'POST';
 | 
			
		||||
    $header .= "\r\nAuthorization: Bearer $session_id"
 | 
			
		||||
      if ( defined($hash->{helper}{session_id}) );
 | 
			
		||||
      if ( defined( $hash->{helper}{session_id} ) );
 | 
			
		||||
    $header .= "\r\nAuthorization-Provider: husqvarna"
 | 
			
		||||
      if ( defined($hash->{helper}{session_id}) );
 | 
			
		||||
      if ( defined( $hash->{helper}{session_id} ) );
 | 
			
		||||
 | 
			
		||||
    #  $header .= "\r\nx-api-key: $session_id"
 | 
			
		||||
    #    if ( defined( $hash->{helper}{session_id} ) );
 | 
			
		||||
@@ -1230,7 +1295,7 @@ sub createHttpValueStrings {
 | 
			
		||||
    $payload = '{}'                 if ( !defined($payload) );
 | 
			
		||||
 | 
			
		||||
    if ( $payload eq '{}' ) {
 | 
			
		||||
        $method = 'GET' if (defined( $hash->{helper}{session_id} ) );
 | 
			
		||||
        $method  = 'GET' if ( defined( $hash->{helper}{session_id} ) );
 | 
			
		||||
        $payload = '';
 | 
			
		||||
        $uri .= '/locations?locatioId=null&user_id=' . $hash->{helper}{user_id}
 | 
			
		||||
          if ( exists( $hash->{helper}{user_id} )
 | 
			
		||||
@@ -1243,7 +1308,9 @@ sub createHttpValueStrings {
 | 
			
		||||
            && defined( $hash->{helper}{locations_id} ) );
 | 
			
		||||
    }
 | 
			
		||||
 | 
			
		||||
    $uri = '/devices/'.InternalVal($hash->{helper}{debug_device}, 'DEVICEID', 0 ) if ( defined ($hash->{helper}{debug_device})  
 | 
			
		||||
    $uri =
 | 
			
		||||
      '/devices/' . InternalVal( $hash->{helper}{debug_device}, 'DEVICEID', 0 )
 | 
			
		||||
      if ( defined( $hash->{helper}{debug_device} )
 | 
			
		||||
        && defined( $hash->{helper}{locations_id} ) );
 | 
			
		||||
    $uri = '/auth/token' if ( !defined( $hash->{helper}{session_id} ) );
 | 
			
		||||
 | 
			
		||||
@@ -1253,11 +1320,7 @@ sub createHttpValueStrings {
 | 
			
		||||
            $method = 'PUT';
 | 
			
		||||
            my $dhash = $modules{GardenaSmartDevice}{defptr}{$deviceId};
 | 
			
		||||
 | 
			
		||||
            $uri .=
 | 
			
		||||
                '/devices/'
 | 
			
		||||
              . $deviceId
 | 
			
		||||
              . '/settings/'
 | 
			
		||||
              . $service_id
 | 
			
		||||
            $uri .= '/devices/' . $deviceId . '/settings/' . $service_id
 | 
			
		||||
              if ( defined($abilities)
 | 
			
		||||
                && defined($payload)
 | 
			
		||||
                && $abilities =~ /.*_settings/ );
 | 
			
		||||
@@ -1293,7 +1356,11 @@ sub createHttpValueStrings {
 | 
			
		||||
              . $deviceId
 | 
			
		||||
              . '/abilities/'
 | 
			
		||||
              . $abilities
 | 
			
		||||
              . ( defined($valve_id) ? '/properties/watering_timer_'. $valve_id : '/command')
 | 
			
		||||
              . (
 | 
			
		||||
                defined($valve_id)
 | 
			
		||||
                ? '/properties/watering_timer_' . $valve_id
 | 
			
		||||
                : '/command'
 | 
			
		||||
              );
 | 
			
		||||
 | 
			
		||||
        }
 | 
			
		||||
        elsif (defined($abilities)
 | 
			
		||||
@@ -1507,9 +1574,9 @@ sub DeletePassword {
 | 
			
		||||
  ],
 | 
			
		||||
  "release_status": "stable",
 | 
			
		||||
  "license": "GPL_2",
 | 
			
		||||
  "version": "v2.4.6",
 | 
			
		||||
  "version": "v2.4.7",
 | 
			
		||||
  "author": [
 | 
			
		||||
    "Marko Oldenburg <leongaultier@gmail.com>"
 | 
			
		||||
    "Marko Oldenburg <fhemdevelopment@cooltux.net>"
 | 
			
		||||
  ],
 | 
			
		||||
  "x_fhem_maintainer": [
 | 
			
		||||
    "CoolTux"
 | 
			
		||||
 
 | 
			
		||||
@@ -1,8 +1,8 @@
 | 
			
		||||
###############################################################################
 | 
			
		||||
#
 | 
			
		||||
# Developed with Kate
 | 
			
		||||
# Developed with VSCodium and richterger perl plugin.
 | 
			
		||||
#
 | 
			
		||||
#  (c) 2017-2021 Copyright: Marko Oldenburg (fhemdevelopment at cooltux dot net)
 | 
			
		||||
#  (c) 2017-2022 Copyright: Marko Oldenburg (fhemdevelopment at cooltux dot net)
 | 
			
		||||
#  All rights reserved
 | 
			
		||||
#
 | 
			
		||||
#   Special thanks goes to comitters:
 | 
			
		||||
@@ -213,6 +213,7 @@ sub Define {
 | 
			
		||||
    $hash->{helper}{winter_mode_id}            = '';
 | 
			
		||||
 | 
			
		||||
    $hash->{helper}{_id} = '';
 | 
			
		||||
 | 
			
		||||
    # IrrigationControl valve control max 6
 | 
			
		||||
    $hash->{helper}{schedules_paused_until_1_id} = '';
 | 
			
		||||
    $hash->{helper}{schedules_paused_until_2_id} = '';
 | 
			
		||||
@@ -287,12 +288,14 @@ sub Set {
 | 
			
		||||
    my $aArg = shift // return;
 | 
			
		||||
 | 
			
		||||
    my $name = shift @$aArg;
 | 
			
		||||
    my $cmd                 = shift @$aArg // return qq{"set $name" needs at least one argument};
 | 
			
		||||
    my $cmd  = shift @$aArg
 | 
			
		||||
      // return qq{"set $name" needs at least one argument};
 | 
			
		||||
 | 
			
		||||
    my $payload;
 | 
			
		||||
    my $abilities;
 | 
			
		||||
    my $service_id;
 | 
			
		||||
    my $mainboard_version   = ReadingsVal( $name, 'mower_type-mainboard_version', 0.0 );
 | 
			
		||||
    my $mainboard_version =
 | 
			
		||||
      ReadingsVal( $name, 'mower_type-mainboard_version', 0.0 );
 | 
			
		||||
 | 
			
		||||
    #set default abilitie ... overwrite in cmd to change
 | 
			
		||||
    $abilities = 'mower'
 | 
			
		||||
@@ -310,46 +313,57 @@ sub Set {
 | 
			
		||||
    if ( lc $cmd eq 'parkuntilfurthernotice' ) {
 | 
			
		||||
        $payload = '"name":"park_until_further_notice"';
 | 
			
		||||
        if ( $mainboard_version > 10.30 ) {
 | 
			
		||||
          $payload = ' "settings":{"name":"schedules_paused_until","value":"2038-01-18T00:00:00.000Z","device":"'.$hash->{DEVICEID}.'"}';
 | 
			
		||||
          $abilities = 'mower_settings'  ;
 | 
			
		||||
            $payload =
 | 
			
		||||
' "settings":{"name":"schedules_paused_until","value":"2038-01-18T00:00:00.000Z","device":"'
 | 
			
		||||
              . $hash->{DEVICEID} . '"}';
 | 
			
		||||
            $abilities  = 'mower_settings';
 | 
			
		||||
            $service_id = $hash->{helper}{schedules_paused_until_id};
 | 
			
		||||
        }
 | 
			
		||||
    }
 | 
			
		||||
    elsif ( lc $cmd eq 'parkuntilnexttimer' ) {
 | 
			
		||||
        $payload = '"name":"park_until_next_timer"';
 | 
			
		||||
        if ( $mainboard_version > 10.30 ){
 | 
			
		||||
          $payload = '"properties":{"name":"mower_timer","value":0}' ;
 | 
			
		||||
        if ( $mainboard_version > 10.30 ) {
 | 
			
		||||
            $payload   = '"properties":{"name":"mower_timer","value":0}';
 | 
			
		||||
            $abilities = 'mower_timer';
 | 
			
		||||
        }
 | 
			
		||||
    }
 | 
			
		||||
    elsif ( lc $cmd eq 'startresumeschedule' ) {
 | 
			
		||||
        $payload = '"name":"start_resume_schedule"';
 | 
			
		||||
        if ( $mainboard_version > 10.30 ) {
 | 
			
		||||
          $payload = ' "settings":{"name":"schedules_paused_until","value":"","device":"'.$hash->{DEVICEID}.'"}';
 | 
			
		||||
          $abilities = 'mower_settings'  ;
 | 
			
		||||
            $payload =
 | 
			
		||||
' "settings":{"name":"schedules_paused_until","value":"","device":"'
 | 
			
		||||
              . $hash->{DEVICEID} . '"}';
 | 
			
		||||
            $abilities  = 'mower_settings';
 | 
			
		||||
            $service_id = $hash->{helper}{schedules_paused_until_id};
 | 
			
		||||
        }
 | 
			
		||||
    }
 | 
			
		||||
    elsif ( lc $cmd eq 'startoverridetimer' ) {
 | 
			
		||||
        $payload = '"name":"start_override_timer","parameters":{"duration":'
 | 
			
		||||
          . $aArg->[0] * 60 . '}';
 | 
			
		||||
        if ( $mainboard_version > 10.30 ){
 | 
			
		||||
          $payload = '"properties":{"name":"mower_timer","value":'.$aArg->[0] * 60 .'}';
 | 
			
		||||
        if ( $mainboard_version > 10.30 ) {
 | 
			
		||||
            $payload = '"properties":{"name":"mower_timer","value":'
 | 
			
		||||
              . $aArg->[0] * 60 . '}';
 | 
			
		||||
            $abilities = 'mower_timer';
 | 
			
		||||
        }
 | 
			
		||||
 | 
			
		||||
    }
 | 
			
		||||
    elsif ( lc $cmd eq 'startpoint' ) {
 | 
			
		||||
        my $err;
 | 
			
		||||
        ( $err, $payload, $abilities ) = SetPredefinedStartPoints( $hash, $aArg );
 | 
			
		||||
        ( $err, $payload, $abilities ) =
 | 
			
		||||
          SetPredefinedStartPoints( $hash, $aArg );
 | 
			
		||||
        $service_id = $hash->{helper}{STARTINGPOINTID};
 | 
			
		||||
        return $err if ( defined($err) );
 | 
			
		||||
    }
 | 
			
		||||
    elsif ( lc $cmd eq 'eco' ) {
 | 
			
		||||
        $payload = '"settings": {"name": "eco_mode", "value": '.$aArg->[0].', "device": "'.$hash->{DEVICEID}.'"}';
 | 
			
		||||
        $payload =
 | 
			
		||||
            '"settings": {"name": "eco_mode", "value": '
 | 
			
		||||
          . $aArg->[0]
 | 
			
		||||
          . ', "device": "'
 | 
			
		||||
          . $hash->{DEVICEID} . '"}';
 | 
			
		||||
        $abilities  = 'mower_settings' if ( $mainboard_version > 10.30 );
 | 
			
		||||
        $service_id = $hash->{helper}{eco_mode_id};
 | 
			
		||||
        #$abilities['service_id'] = $hash->{helper}{SCHEDULESID}  if ( $mainboard_version > 10.30 );
 | 
			
		||||
 | 
			
		||||
#$abilities['service_id'] = $hash->{helper}{SCHEDULESID}  if ( $mainboard_version > 10.30 );
 | 
			
		||||
    }
 | 
			
		||||
    ### electronic_pressure_pump
 | 
			
		||||
    elsif ( lc $cmd eq 'pumptimer' ) {
 | 
			
		||||
@@ -365,16 +379,15 @@ sub Set {
 | 
			
		||||
          . $aArg->[0] * 60
 | 
			
		||||
          . ',"valve_id":1}}';
 | 
			
		||||
    }
 | 
			
		||||
    elsif ( lc $cmd eq 'manualbuttontime'){
 | 
			
		||||
    elsif ( lc $cmd eq 'manualbuttontime' ) {
 | 
			
		||||
        $service_id = $hash->{helper}{button_config_time_id};
 | 
			
		||||
      $payload=
 | 
			
		||||
        $payload =
 | 
			
		||||
            '"properties":{"name":"button_config_time",'
 | 
			
		||||
        .'"value":'
 | 
			
		||||
          . '"value":'
 | 
			
		||||
          . $aArg->[0] * 60
 | 
			
		||||
          . ',"timestamp":"2021-05-26T19:06:23.680Z"'
 | 
			
		||||
          . ',"at_bound":null,"unit":"seconds","ability":"'
 | 
			
		||||
        . $service_id
 | 
			
		||||
        .'"}';
 | 
			
		||||
          . $service_id . '"}';
 | 
			
		||||
        $abilities = 'watering_button_config';
 | 
			
		||||
    }
 | 
			
		||||
    elsif ( $cmd =~ m{\AcancelOverride}xms ) {
 | 
			
		||||
@@ -393,26 +406,40 @@ sub Set {
 | 
			
		||||
          . ',"valve_id":'
 | 
			
		||||
          . $valve_id . '}}';
 | 
			
		||||
    }
 | 
			
		||||
    elsif ( $cmd =~ /.*Schedule/ ){
 | 
			
		||||
      my $duration = (( defined($aArg->[0]) ? ( ((Time::Piece->new)+(ONE_HOUR *  $aArg->[0]) - (Time::Piece->new)->tzoffset )->datetime ).'.000Z' : '2038-01-18T00:00:00.000Z'));
 | 
			
		||||
    elsif ( $cmd =~ /.*Schedule/ ) {
 | 
			
		||||
        my $duration = (
 | 
			
		||||
            (
 | 
			
		||||
                defined( $aArg->[0] )
 | 
			
		||||
                ? (
 | 
			
		||||
                    (
 | 
			
		||||
                        ( Time::Piece->new ) +
 | 
			
		||||
                          ( ONE_HOUR * $aArg->[0] ) -
 | 
			
		||||
                          ( Time::Piece->new )->tzoffset
 | 
			
		||||
                    )->datetime
 | 
			
		||||
                  )
 | 
			
		||||
                  . '.000Z'
 | 
			
		||||
                : '2038-01-18T00:00:00.000Z'
 | 
			
		||||
            )
 | 
			
		||||
        );
 | 
			
		||||
 | 
			
		||||
        $abilities  = 'wateringcomputer_settings';
 | 
			
		||||
        $service_id = $hash->{helper}->{'schedules_paused_until_id'};
 | 
			
		||||
      $payload = '"settings":{"name":"schedules_paused_until"'
 | 
			
		||||
        $payload =
 | 
			
		||||
            '"settings":{"name":"schedules_paused_until"'
 | 
			
		||||
          . ', "value":"'
 | 
			
		||||
                  . ($cmd eq 'resumeSchedule' ? '' : $duration )
 | 
			
		||||
          . ( $cmd eq 'resumeSchedule' ? '' : $duration )
 | 
			
		||||
          . '","device":"'
 | 
			
		||||
                  . $hash->{DEVICEID}
 | 
			
		||||
                  . '"}';
 | 
			
		||||
          . $hash->{DEVICEID} . '"}';
 | 
			
		||||
    }
 | 
			
		||||
    elsif ( lc $cmd eq 'on' || lc $cmd eq 'off' || lc $cmd eq 'on-for-timer' ) {
 | 
			
		||||
        my $val = (
 | 
			
		||||
            scalar(!@$aArg == 0) && ref($aArg) eq 'ARRAY'
 | 
			
		||||
            scalar( !@$aArg == 0 ) && ref($aArg) eq 'ARRAY'
 | 
			
		||||
            ? $aArg->[0] * 60
 | 
			
		||||
            : lc $cmd
 | 
			
		||||
        );
 | 
			
		||||
 | 
			
		||||
        $payload = '"properties":{"name":"power_timer", "value":"' . $val . '"}';
 | 
			
		||||
        $payload =
 | 
			
		||||
          '"properties":{"name":"power_timer", "value":"' . $val . '"}';
 | 
			
		||||
    }
 | 
			
		||||
    ### Watering ic24
 | 
			
		||||
    elsif ( $cmd =~ m{\AmanualDurationValve\d\z}xms ) {
 | 
			
		||||
@@ -430,32 +457,49 @@ sub Set {
 | 
			
		||||
          . ',"valve_id":'
 | 
			
		||||
          . $valve_id . '}}';
 | 
			
		||||
    }
 | 
			
		||||
    elsif ( $cmd eq 'closeAllValves' ){
 | 
			
		||||
    elsif ( $cmd eq 'closeAllValves' ) {
 | 
			
		||||
        $payload = '"name":"close_all_valves","parameters":{}';
 | 
			
		||||
    }
 | 
			
		||||
    elsif ( $cmd =~ /.*ScheduleValve/ ){
 | 
			
		||||
    elsif ( $cmd =~ /.*ScheduleValve/ ) {
 | 
			
		||||
        my $valve_id = $aArg->[0];
 | 
			
		||||
      my $duration = (( defined($aArg->[1]) ? ( ((Time::Piece->new)+(ONE_HOUR *  $aArg->[1]) - (Time::Piece->new)->tzoffset )->datetime ).'.000Z' : '2038-01-18T00:00:00.000Z'));
 | 
			
		||||
        my $duration = (
 | 
			
		||||
            (
 | 
			
		||||
                defined( $aArg->[1] )
 | 
			
		||||
                ? (
 | 
			
		||||
                    (
 | 
			
		||||
                        ( Time::Piece->new ) +
 | 
			
		||||
                          ( ONE_HOUR * $aArg->[1] ) -
 | 
			
		||||
                          ( Time::Piece->new )->tzoffset
 | 
			
		||||
                    )->datetime
 | 
			
		||||
                  )
 | 
			
		||||
                  . '.000Z'
 | 
			
		||||
                : '2038-01-18T00:00:00.000Z'
 | 
			
		||||
            )
 | 
			
		||||
        );
 | 
			
		||||
 | 
			
		||||
        $abilities = 'irrigation_settings';
 | 
			
		||||
      $service_id = $hash->{helper}->{'schedules_paused_until_'.$valve_id.'_id'};
 | 
			
		||||
      $payload = '"settings":{"name":"schedules_paused_until_'
 | 
			
		||||
        $service_id =
 | 
			
		||||
          $hash->{helper}->{ 'schedules_paused_until_' . $valve_id . '_id' };
 | 
			
		||||
        $payload =
 | 
			
		||||
            '"settings":{"name":"schedules_paused_until_'
 | 
			
		||||
          . $valve_id
 | 
			
		||||
          . '", "value":"'
 | 
			
		||||
                  . ($cmd eq 'resumeScheduleValve' ? '' : $duration )
 | 
			
		||||
          . ( $cmd eq 'resumeScheduleValve' ? '' : $duration )
 | 
			
		||||
          . '","device":"'
 | 
			
		||||
                  . $hash->{DEVICEID}
 | 
			
		||||
                  . '"}';
 | 
			
		||||
          . $hash->{DEVICEID} . '"}';
 | 
			
		||||
    }
 | 
			
		||||
    ### Sensors
 | 
			
		||||
    elsif ( lc $cmd eq 'refresh' ) {
 | 
			
		||||
 | 
			
		||||
        my $sensname = $aArg->[0];
 | 
			
		||||
        if ( lc $sensname eq 'temperature' ) {
 | 
			
		||||
            if ( ReadingsVal( $name, 'device_info-category', 'sensor' ) eq 'sensor') {
 | 
			
		||||
            if ( ReadingsVal( $name, 'device_info-category', 'sensor' ) eq
 | 
			
		||||
                'sensor' )
 | 
			
		||||
            {
 | 
			
		||||
                $payload   = '"name":"measure_ambient_temperature"';
 | 
			
		||||
                $abilities = 'ambient_temperature';
 | 
			
		||||
            } else {
 | 
			
		||||
            }
 | 
			
		||||
            else {
 | 
			
		||||
                $payload   = '"name":"measure_soil_temperature"';
 | 
			
		||||
                $abilities = 'soil_temperature';
 | 
			
		||||
            }
 | 
			
		||||
@@ -471,12 +515,12 @@ sub Set {
 | 
			
		||||
        }
 | 
			
		||||
    }
 | 
			
		||||
    ## winter sleep
 | 
			
		||||
    elsif ( lc $cmd eq 'winter_mode') {
 | 
			
		||||
        $payload = '"settings":{"name":"winter_mode","value":"'
 | 
			
		||||
    elsif ( lc $cmd eq 'winter_mode' ) {
 | 
			
		||||
        $payload =
 | 
			
		||||
            '"settings":{"name":"winter_mode","value":"'
 | 
			
		||||
          . $aArg->[0]
 | 
			
		||||
        .'","device":"'
 | 
			
		||||
        . $hash->{DEVICEID}
 | 
			
		||||
        .'"}';
 | 
			
		||||
          . '","device":"'
 | 
			
		||||
          . $hash->{DEVICEID} . '"}';
 | 
			
		||||
        $abilities  = 'winter_settings';
 | 
			
		||||
        $service_id = $hash->{helper}->{'winter_mode_id'};
 | 
			
		||||
    }
 | 
			
		||||
@@ -488,7 +532,8 @@ sub Set {
 | 
			
		||||
'parkUntilFurtherNotice:noArg parkUntilNextTimer:noArg startResumeSchedule:noArg startOverrideTimer:slider,0,1,240 startpoint'
 | 
			
		||||
          if ( AttrVal( $name, 'model', 'unknown' ) eq 'mower' );
 | 
			
		||||
 | 
			
		||||
        $list .= 'manualOverride:slider,1,1,59 cancelOverride:noArg resumeSchedule:noArg stopSchedule manualButtonTime:slider,0,2,100'
 | 
			
		||||
        $list .=
 | 
			
		||||
'manualOverride:slider,1,1,59 cancelOverride:noArg resumeSchedule:noArg stopSchedule manualButtonTime:slider,0,2,100'
 | 
			
		||||
          if ( AttrVal( $name, 'model', 'unknown' ) eq 'watering_computer' );
 | 
			
		||||
 | 
			
		||||
        $list .=
 | 
			
		||||
@@ -497,13 +542,16 @@ sub Set {
 | 
			
		||||
 | 
			
		||||
        $list .= 'refresh:temperature,humidity'
 | 
			
		||||
          if ( AttrVal( $name, 'model', 'unknown' ) eq 'sensor' );
 | 
			
		||||
 | 
			
		||||
        # add light for old sensors
 | 
			
		||||
        $list .= ',light'
 | 
			
		||||
          if ( AttrVal( $name, 'model', 'unknown' ) eq 'sensor'
 | 
			
		||||
            && ReadingsVal($name, 'device_info-category', 'unknown') eq 'sensor' );
 | 
			
		||||
            && ReadingsVal( $name, 'device_info-category', 'unknown' ) eq
 | 
			
		||||
            'sensor' );
 | 
			
		||||
 | 
			
		||||
        $list .= 'on:noArg off:noArg on-for-timer:slider,0,1,720'
 | 
			
		||||
          if ( AttrVal( $name, 'model', 'unknown' ) eq 'power' );
 | 
			
		||||
 | 
			
		||||
        # all devices has abilitie to fall a sleep
 | 
			
		||||
        $list .= ' winter_mode:awake,hibernate';
 | 
			
		||||
        return "Unknown argument $cmd, choose one of $list";
 | 
			
		||||
@@ -584,22 +632,27 @@ sub WriteReadings {
 | 
			
		||||
            for my $propertie (
 | 
			
		||||
                @{ $decode_json->{abilities}[$abilities]{properties} } )
 | 
			
		||||
            {
 | 
			
		||||
              if (   exists($decode_json->{abilities}[$abilities]{name})
 | 
			
		||||
                  && ( 
 | 
			
		||||
                      $decode_json->{abilities}[$abilities]{name} eq 'watering' )
 | 
			
		||||
                ) {
 | 
			
		||||
                  if ( $propertie->{name} eq 'button_config_time'  ) 
 | 
			
		||||
                if (
 | 
			
		||||
                    exists( $decode_json->{abilities}[$abilities]{name} )
 | 
			
		||||
                    && ( $decode_json->{abilities}[$abilities]{name} eq
 | 
			
		||||
                        'watering' )
 | 
			
		||||
                  )
 | 
			
		||||
                {
 | 
			
		||||
                    if ( $hash->{helper}{$propertie->{name}.'_id'} ne
 | 
			
		||||
                    if ( $propertie->{name} eq 'button_config_time' ) {
 | 
			
		||||
                        if ( $hash->{helper}{ $propertie->{name} . '_id' } ne
 | 
			
		||||
                            $decode_json->{abilities}[$abilities]{id} )
 | 
			
		||||
                        {
 | 
			
		||||
                            $hash->{helper}{$propertie->{name}.'_id'} =
 | 
			
		||||
                            $hash->{helper}{ $propertie->{name} . '_id' } =
 | 
			
		||||
                              $decode_json->{abilities}[$abilities]{id};
 | 
			
		||||
                        }
 | 
			
		||||
                        readingsBulkUpdateIfChanged(
 | 
			
		||||
                            $hash,
 | 
			
		||||
                            'manualButtonTime',
 | 
			
		||||
                              (RigReadingsValue( $hash, $propertie->{value} / 60) )
 | 
			
		||||
                            (
 | 
			
		||||
                                RigReadingsValue(
 | 
			
		||||
                                    $hash, $propertie->{value} / 60
 | 
			
		||||
                                )
 | 
			
		||||
                            )
 | 
			
		||||
                        );
 | 
			
		||||
                        next;
 | 
			
		||||
                    }
 | 
			
		||||
@@ -659,14 +712,15 @@ sub WriteReadings {
 | 
			
		||||
                    $decode_json->{abilities}[$abilities]{name} . '-'
 | 
			
		||||
                      . $propertie->{name}
 | 
			
		||||
                      . '_timestamp',
 | 
			
		||||
                      Time::Piece->strptime(RigReadingsValue( $hash, $propertie->{timestamp} ), "%Y-%m-%d %H:%M:%S")->strftime('%s')
 | 
			
		||||
                    Time::Piece->strptime(
 | 
			
		||||
                        RigReadingsValue( $hash, $propertie->{timestamp} ),
 | 
			
		||||
                        "%Y-%m-%d %H:%M:%S" )->strftime('%s')
 | 
			
		||||
 | 
			
		||||
                  )
 | 
			
		||||
                  if (
 | 
			
		||||
                    defined( $propertie->{value} )
 | 
			
		||||
                    && (  $decode_json->{abilities}[$abilities]{name} . '-'
 | 
			
		||||
                        . $propertie->{name} eq 'mower_timer-mower_timer'
 | 
			
		||||
                  )
 | 
			
		||||
                        . $propertie->{name} eq 'mower_timer-mower_timer' )
 | 
			
		||||
                  );
 | 
			
		||||
 | 
			
		||||
                readingsBulkUpdateIfChanged(
 | 
			
		||||
@@ -707,29 +761,31 @@ sub WriteReadings {
 | 
			
		||||
    } while ( $abilities >= 0 );
 | 
			
		||||
 | 
			
		||||
    do {
 | 
			
		||||
        #Log3 $name, 1, "Settings pro Device : ".$decode_json->{settings}[$settings]{name};
 | 
			
		||||
        #Log3 $name, 1, " - KEIN ARRAY" if ( ref( $decode_json->{settings}[$settings]{value} ) ne "ARRAY");
 | 
			
		||||
        #Log3 $name, 1, " - IST ARRAY" if ( ref( $decode_json->{settings}[$settings]{value} ) eq "ARRAY");
 | 
			
		||||
#Log3 $name, 1, "Settings pro Device : ".$decode_json->{settings}[$settings]{name};
 | 
			
		||||
#Log3 $name, 1, " - KEIN ARRAY" if ( ref( $decode_json->{settings}[$settings]{value} ) ne "ARRAY");
 | 
			
		||||
#Log3 $name, 1, " - IST ARRAY" if ( ref( $decode_json->{settings}[$settings]{value} ) eq "ARRAY");
 | 
			
		||||
 | 
			
		||||
        if (   exists($decode_json->{settings}[$settings]{name})
 | 
			
		||||
          && ( 
 | 
			
		||||
            $decode_json->{settings}[$settings]{name} =~ /schedules_paused_until_?\d?$/
 | 
			
		||||
        if (
 | 
			
		||||
            exists( $decode_json->{settings}[$settings]{name} )
 | 
			
		||||
            && ( $decode_json->{settings}[$settings]{name} =~
 | 
			
		||||
                   /schedules_paused_until_?\d?$/
 | 
			
		||||
                || $decode_json->{settings}[$settings]{name} eq 'eco_mode'
 | 
			
		||||
                || $decode_json->{settings}[$settings]{name} eq 'winter_mode' )
 | 
			
		||||
          )
 | 
			
		||||
        {
 | 
			
		||||
            if ( $hash->{helper}{$decode_json->{settings}[$settings]{name}.'_id'} ne
 | 
			
		||||
            if ( $hash->{helper}
 | 
			
		||||
                { $decode_json->{settings}[$settings]{name} . '_id' } ne
 | 
			
		||||
                $decode_json->{settings}[$settings]{id} )
 | 
			
		||||
            {
 | 
			
		||||
                $hash->{helper}{$decode_json->{settings}[$settings]{name}.'_id'} =
 | 
			
		||||
                $hash->{helper}
 | 
			
		||||
                  { $decode_json->{settings}[$settings]{name} . '_id' } =
 | 
			
		||||
                  $decode_json->{settings}[$settings]{id};
 | 
			
		||||
            }
 | 
			
		||||
 | 
			
		||||
            # save winter mode as reading
 | 
			
		||||
            readingsBulkUpdateIfChanged(
 | 
			
		||||
                    $hash,
 | 
			
		||||
                    'winter_mode',
 | 
			
		||||
                    $decode_json->{settings}[$settings]{value}
 | 
			
		||||
                ) if ($decode_json->{settings}[$settings]{name} eq 'winter_mode');
 | 
			
		||||
            readingsBulkUpdateIfChanged( $hash, 'winter_mode',
 | 
			
		||||
                $decode_json->{settings}[$settings]{value} )
 | 
			
		||||
              if ( $decode_json->{settings}[$settings]{name} eq 'winter_mode' );
 | 
			
		||||
        }
 | 
			
		||||
 | 
			
		||||
        if ( ref( $decode_json->{settings}[$settings]{value} ) eq "ARRAY"
 | 
			
		||||
@@ -763,36 +819,50 @@ sub WriteReadings {
 | 
			
		||||
        $settings--;
 | 
			
		||||
    } while ( $settings >= 0 );
 | 
			
		||||
 | 
			
		||||
    
 | 
			
		||||
    my $online_state = ReadingsVal($name , 'device_info-connection_status', 'unknown');
 | 
			
		||||
    my $online_state =
 | 
			
		||||
      ReadingsVal( $name, 'device_info-connection_status', 'unknown' );
 | 
			
		||||
 | 
			
		||||
    readingsBulkUpdate( $hash, 'state',
 | 
			
		||||
        $online_state eq 'online' ?
 | 
			
		||||
          ReadingsVal( $name, 'mower-status', 'readingsValError') : 'offline'
 | 
			
		||||
        )
 | 
			
		||||
        $online_state eq 'online'
 | 
			
		||||
        ? ReadingsVal( $name, 'mower-status', 'readingsValError' )
 | 
			
		||||
        : 'offline' )
 | 
			
		||||
      if ( AttrVal( $name, 'model', 'unknown' ) eq 'mower' );
 | 
			
		||||
    readingsBulkUpdate(
 | 
			
		||||
        $hash, 'state',
 | 
			
		||||
        (
 | 
			
		||||
                ReadingsVal( $name, 'watering-watering_timer_1_duration', 0 )
 | 
			
		||||
                  =~ m{\A[1-9]([0-9]+)?\z}xms
 | 
			
		||||
            ReadingsVal( $name, 'watering-watering_timer_1_duration', 0 ) =~
 | 
			
		||||
              m{\A[1-9]([0-9]+)?\z}xms
 | 
			
		||||
            ? RigReadingsValue( $hash, 'open' )
 | 
			
		||||
            : RigReadingsValue( $hash, 'closed' )
 | 
			
		||||
        )
 | 
			
		||||
    ) if ( AttrVal( $name, 'model', 'unknown' ) eq 'watering_computer' );
 | 
			
		||||
 | 
			
		||||
 | 
			
		||||
    if ( AttrVal( $name, 'model', 'unknown' ) eq 'sensor' ) {
 | 
			
		||||
      my $state_string = ( ReadingsVal($name, 'device_info-category', 'unknown') eq 'sensor') ?  'T: ' .ReadingsVal( $name, 'ambient_temperature-temperature', 'readingsValError' ) . '°C, ' :  'T: ' .ReadingsVal( $name, 'soil_temperature-temperature', 'readingsValError' ) . '°C, ' ;
 | 
			
		||||
      $state_string .=  'H: '. ReadingsVal( $name, 'humidity-humidity', 'readingsValError' ). '%';
 | 
			
		||||
      $state_string .= ', L: ' . ReadingsVal( $name, 'light-light', 'readingsValError' ) . 'lux' if (ReadingsVal($name, 'device_info-category', 'unknown') eq 'sensor');
 | 
			
		||||
        my $state_string =
 | 
			
		||||
          ( ReadingsVal( $name, 'device_info-category', 'unknown' ) eq
 | 
			
		||||
              'sensor' )
 | 
			
		||||
          ? 'T: '
 | 
			
		||||
          . ReadingsVal( $name, 'ambient_temperature-temperature',
 | 
			
		||||
            'readingsValError' )
 | 
			
		||||
          . '°C, '
 | 
			
		||||
          : 'T: '
 | 
			
		||||
          . ReadingsVal( $name, 'soil_temperature-temperature',
 | 
			
		||||
            'readingsValError' )
 | 
			
		||||
          . '°C, ';
 | 
			
		||||
        $state_string .= 'H: '
 | 
			
		||||
          . ReadingsVal( $name, 'humidity-humidity', 'readingsValError' ) . '%';
 | 
			
		||||
        $state_string .= ', L: '
 | 
			
		||||
          . ReadingsVal( $name, 'light-light', 'readingsValError' ) . 'lux'
 | 
			
		||||
          if ( ReadingsVal( $name, 'device_info-category', 'unknown' ) eq
 | 
			
		||||
            'sensor' );
 | 
			
		||||
 | 
			
		||||
      # if ( $online_state eq 'offline') {
 | 
			
		||||
      #   readingsBulkUpdate( $hash, 'humidity-humidity', '-1' );
 | 
			
		||||
      #   readingsBulkUpdate( $hash, 'ambient_temperature-temperature', '-1' ) if (ReadingsVal($name, 'device_info-category', 'unknown') eq 'sensor');
 | 
			
		||||
      #   readingsBulkUpdate( $hash, 'light-light', '-1' ) if (ReadingsVal($name, 'device_info-category', 'unknown') eq 'sensor');
 | 
			
		||||
      # }
 | 
			
		||||
      readingsBulkUpdate($hash, 'state', $online_state eq 'online' ? $state_string : 'offline' )
 | 
			
		||||
# if ( $online_state eq 'offline') {
 | 
			
		||||
#   readingsBulkUpdate( $hash, 'humidity-humidity', '-1' );
 | 
			
		||||
#   readingsBulkUpdate( $hash, 'ambient_temperature-temperature', '-1' ) if (ReadingsVal($name, 'device_info-category', 'unknown') eq 'sensor');
 | 
			
		||||
#   readingsBulkUpdate( $hash, 'light-light', '-1' ) if (ReadingsVal($name, 'device_info-category', 'unknown') eq 'sensor');
 | 
			
		||||
# }
 | 
			
		||||
        readingsBulkUpdate( $hash, 'state',
 | 
			
		||||
            $online_state eq 'online' ? $state_string : 'offline' );
 | 
			
		||||
    }
 | 
			
		||||
 | 
			
		||||
    readingsBulkUpdate(
 | 
			
		||||
@@ -1001,7 +1071,7 @@ sub SetPredefinedStartPoints {
 | 
			
		||||
    my $hash = shift;
 | 
			
		||||
    my $aArg = shift;
 | 
			
		||||
 | 
			
		||||
    my ($startpoint_state,$startpoint_num,@morestartpoints) = @{$aArg};
 | 
			
		||||
    my ( $startpoint_state, $startpoint_num, @morestartpoints ) = @{$aArg};
 | 
			
		||||
 | 
			
		||||
    my $name = $hash->{NAME};
 | 
			
		||||
    my $payload;
 | 
			
		||||
@@ -1049,6 +1119,7 @@ sub SetPredefinedStartPoints {
 | 
			
		||||
 | 
			
		||||
            $payload   = '"settings": ' . encode_json($decode_json_settings);
 | 
			
		||||
            $abilities = 'mower_settings';
 | 
			
		||||
 | 
			
		||||
            #$abilities['service_id'] = $hash->{helper}{STARTINGPOINTID};
 | 
			
		||||
        }
 | 
			
		||||
        else {
 | 
			
		||||
@@ -1406,7 +1477,7 @@ sub SetPredefinedStartPoints {
 | 
			
		||||
  "license": "GPL_2",
 | 
			
		||||
  "version": "v2.4.2",
 | 
			
		||||
  "author": [
 | 
			
		||||
    "Marko Oldenburg <leongaultier@gmail.com>"
 | 
			
		||||
    "Marko Oldenburg <fhemdevelopment@cooltux.net>"
 | 
			
		||||
  ],
 | 
			
		||||
  "x_fhem_maintainer": [
 | 
			
		||||
    "CoolTux"
 | 
			
		||||
 
 | 
			
		||||
@@ -1,2 +1,2 @@
 | 
			
		||||
UPD 2021-09-27_10:50:10 48067 FHEM/73_GardenaSmartBridge.pm
 | 
			
		||||
UPD 2021-09-27_10:50:10 56217 FHEM/74_GardenaSmartDevice.pm
 | 
			
		||||
UPD 2022-01-31_19:35:20 49370 FHEM/73_GardenaSmartBridge.pm
 | 
			
		||||
UPD 2022-01-31_19:35:42 56993 FHEM/74_GardenaSmartDevice.pm
 | 
			
		||||
 
 | 
			
		||||
		Reference in New Issue
	
	Block a user