first commit
This commit is contained in:
		
							
								
								
									
										586
									
								
								73_GardenaSmartBridge.pm
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										586
									
								
								73_GardenaSmartBridge.pm
									
									
									
									
									
										Normal file
									
								
							| @@ -0,0 +1,586 @@ | ||||
| ############################################################################### | ||||
| #  | ||||
| # Developed with Kate | ||||
| # | ||||
| #  (c) 2017 Copyright: Marko Oldenburg (leongaultier at gmail dot com) | ||||
| #  All rights reserved | ||||
| # | ||||
| #  This script is free software; you can redistribute it and/or modify | ||||
| #  it under the terms of the GNU General Public License as published by | ||||
| #  the Free Software Foundation; either version 2 of the License, or | ||||
| #  any later version. | ||||
| # | ||||
| #  The GNU General Public License can be found at | ||||
| #  http://www.gnu.org/copyleft/gpl.html. | ||||
| #  A copy is found in the textfile GPL.txt and important notices to the license | ||||
| #  from the author is found in LICENSE.txt distributed with these scripts. | ||||
| # | ||||
| #  This script is distributed in the hope that it will be useful, | ||||
| #  but WITHOUT ANY WARRANTY; without even the implied warranty of | ||||
| #  MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the | ||||
| #  GNU General Public License for more details. | ||||
| # | ||||
| # | ||||
| # $Id$ | ||||
| # | ||||
| ############################################################################### | ||||
| ## | ||||
| ## | ||||
| ## Das JSON Modul immer in einem eval aufrufen | ||||
| # $data = eval{decode_json($data)}; | ||||
| # | ||||
| # if($@){ | ||||
| #   Log3($SELF, 2, "$TYPE ($SELF) - error while request: $@"); | ||||
| #   | ||||
| #   readingsSingleUpdate($hash, "state", "error", 1); | ||||
| # | ||||
| #   return; | ||||
| # } | ||||
| # | ||||
| # | ||||
| ###### Wichtige Notizen | ||||
| # | ||||
| #   apt-get install libio-socket-ssl-perl | ||||
| #   http://www.dxsdata.com/de/2016/07/php-class-for-gardena-smart-system-api/ | ||||
| #    | ||||
| ## | ||||
| ## | ||||
|  | ||||
|  | ||||
|  | ||||
| package main; | ||||
|  | ||||
|  | ||||
| my $missingModul = ""; | ||||
|  | ||||
| use strict; | ||||
| use warnings; | ||||
|  | ||||
| use HttpUtils; | ||||
| use Data::Dumper;   #debugging | ||||
|  | ||||
| eval "use Encode qw(encode encode_utf8 decode_utf8);1" or $missingModul .= "Encode "; | ||||
| eval "use JSON;1" or $missingModul .= "JSON "; | ||||
| ###todo Hier fehlt noch Modulabfrage für ssl | ||||
|  | ||||
|  | ||||
| my $version = "0.0.10"; | ||||
|  | ||||
|  | ||||
|  | ||||
|  | ||||
| # Declare functions | ||||
| sub GardenaSmartBridge_Attr(@); | ||||
| sub GardenaSmartBridge_Define($$); | ||||
| sub GardenaSmartBridge_Initialize($); | ||||
| sub GardenaSmartBridge_Set($@); | ||||
| sub GardenaSmartBridge_Write($@); | ||||
| sub GardenaSmartBridge_Undef($$); | ||||
| sub GardenaSmartBridge_ResponseProcessing($$); | ||||
| sub GardenaSmartBridge_ErrorHandling($$$); | ||||
| sub GardenaSmartBridge_encrypt($); | ||||
| sub GardenaSmartBridge_decrypt($); | ||||
| sub GardenaSmartBridge_WriteReadings($$); | ||||
| sub GardenaSmartBridge_ParseJSON($$); | ||||
| sub GardenaSmartBridge_getDevices($); | ||||
| sub GardenaSmartBridge_getToken($); | ||||
| sub GardenaSmartBridge_InternalTimerGetDeviceData($); | ||||
|  | ||||
|  | ||||
|  | ||||
|  | ||||
| sub GardenaSmartBridge_Initialize($) { | ||||
|  | ||||
|     my ($hash) = @_; | ||||
|  | ||||
|      | ||||
|     # Provider | ||||
|     $hash->{WriteFn}    = "GardenaSmartBridge_Write"; | ||||
|     $hash->{Clients}    = ":GardenaSmartDevice:"; | ||||
|     $hash->{MatchList}  = { "1:GardenaSmartDevice"      => '^{"id":".*' }; | ||||
|      | ||||
|      | ||||
|     # Consumer | ||||
|     $hash->{SetFn}      = "GardenaSmartBridge_Set"; | ||||
|     $hash->{DefFn}      = "GardenaSmartBridge_Define"; | ||||
|     $hash->{UndefFn}    = "GardenaSmartBridge_Undef"; | ||||
|      | ||||
|     $hash->{AttrFn}     = "GardenaSmartBridge_Attr"; | ||||
|     $hash->{AttrList}   = "debugJSON:0,1 ". | ||||
|                           "disable:1 ". | ||||
|                           $readingFnAttributes; | ||||
|      | ||||
|     foreach my $d(sort keys %{$modules{GardenaSmartBridge}{defptr}}) { | ||||
|      | ||||
|         my $hash = $modules{GardenaSmartBridge}{defptr}{$d}; | ||||
|         $hash->{VERSION}      = $version; | ||||
|     } | ||||
| } | ||||
|  | ||||
| sub GardenaSmartBridge_Define($$) { | ||||
|  | ||||
|     my ( $hash, $def ) = @_; | ||||
|      | ||||
|     my @a = split( "[ \t][ \t]*", $def ); | ||||
|  | ||||
|      | ||||
|     return "too few parameters: define <NAME> GardenaSmartBridge <Email> <Passwort>" if( @a != 4 ) ; | ||||
|     return "Cannot define Gardena Bridge device. Perl modul $missingModul is missing." if ( $missingModul ); | ||||
|      | ||||
|     my $name                = $a[0]; | ||||
|     my $user                = $a[2]; | ||||
|     my $pass                = $a[3]; | ||||
|     $hash->{BRIDGE}         = 1; | ||||
|     $hash->{URL}            = 'https://sg-api.dss.husqvarnagroup.net/sg-1'; | ||||
|     $hash->{VERSION}        = $version; | ||||
|     $hash->{INTERVAL}       = 300; | ||||
|      | ||||
|     my $username            = GardenaSmartBridge_encrypt($user); | ||||
|     my $password            = GardenaSmartBridge_encrypt($pass); | ||||
|     Log3 $name, 3, "GardenaSmartBridge ($name) - encrypt $user/$pass to $username/$password" if($user ne $username || $pass ne $password); | ||||
|     $hash->{DEF} = "$username $password"; | ||||
|      | ||||
|     $hash->{helper}{username} = $username; | ||||
|     $hash->{helper}{password} = $password; | ||||
|      | ||||
|  | ||||
|  | ||||
|     $attr{$name}{room} = "GardenaSmart" if( !defined( $attr{$name}{room} ) ); | ||||
|      | ||||
|     readingsSingleUpdate($hash,'state','initialized',1); | ||||
|     Log3 $name, 3, "GardenaSmartBridge ($name) - defined GardenaSmartBridge and crypt your credentials"; | ||||
|  | ||||
|      | ||||
|     if( $init_done ) { | ||||
|      | ||||
|         GardenaSmartBridge_getToken($hash); | ||||
|         readingsSingleUpdate($hash,'state','get token',1); | ||||
|          | ||||
|     } else { | ||||
|      | ||||
|         InternalTimer( gettimeofday()+15, "GardenaSmartBridge_getToken", $hash, 0 ); | ||||
|     } | ||||
|      | ||||
|      | ||||
|     $modules{GardenaSmartBridge}{defptr}{BRIDGE} = $hash; | ||||
|  | ||||
|     return undef; | ||||
| } | ||||
|  | ||||
| sub GardenaSmartBridge_Undef($$) { | ||||
|  | ||||
|     my ( $hash, $arg ) = @_; | ||||
|  | ||||
|  | ||||
|     RemoveInternalTimer($hash); | ||||
|     delete $modules{GardenaSmartBridge}{defptr}{BRIDGE} if( defined($modules{GardenaSmartBridge}{defptr}{BRIDGE}) ); | ||||
|  | ||||
|     return undef; | ||||
| } | ||||
|  | ||||
| sub GardenaSmartBridge_Attr(@) { | ||||
|  | ||||
|     my ( $cmd, $name, $attrName, $attrVal ) = @_; | ||||
|     my $hash = $defs{$name}; | ||||
|  | ||||
|      | ||||
|     if( $attrName eq "disable" ) { | ||||
|         if( $cmd eq "set" and $attrVal eq "1" ) { | ||||
|             RemoveInternalTimer($hash); | ||||
|             readingsSingleUpdate ( $hash, "state", "inactive", 1 ); | ||||
|             Log3 $name, 3, "GardenaSmartBridge ($name) - disabled"; | ||||
|         } | ||||
|  | ||||
|         elsif( $cmd eq "del" ) { | ||||
|             RemoveInternalTimer($hash); | ||||
|             GardenaSmartBridge_InternalTimerGetDeviceData($hash); | ||||
|             readingsSingleUpdate ( $hash, "state", "active", 1 ); | ||||
|             Log3 $name, 3, "GardenaSmartBridge ($name) - enabled"; | ||||
|         } | ||||
|     } | ||||
|      | ||||
|     elsif( $attrName eq "disabledForIntervals" ) { | ||||
|         if( $cmd eq "set" ) { | ||||
|             Log3 $name, 3, "GardenaSmartBridge ($name) - disabledForIntervals"; | ||||
|             readingsSingleUpdate ( $hash, "state", "inactive", 1 ); | ||||
|         } | ||||
|  | ||||
|         elsif( $cmd eq "del" ) { | ||||
|             readingsSingleUpdate ( $hash, "state", "active", 1 ); | ||||
|             Log3 $name, 3, "GardenaSmartBridge ($name) - enabled"; | ||||
|         } | ||||
|     } | ||||
|      | ||||
|     elsif( $attrName eq "interval" ) { | ||||
|         if( $cmd eq "set" ) { | ||||
|             $hash->{INTERVAL}   = $attrVal; | ||||
|             RemoveInternalTimer($hash); | ||||
|             Log3 $name, 3, "GardenaSmartBridge ($name) - set interval: $attrVal"; | ||||
|             GardenaSmartBridge_InternalTimerGetDeviceData($hash); | ||||
|         } | ||||
|  | ||||
|         elsif( $cmd eq "del" ) { | ||||
|             $hash->{INTERVAL}   = 300; | ||||
|             RemoveInternalTimer($hash); | ||||
|             Log3 $name, 3, "GardenaSmartBridge ($name) - delete User interval and set default: 300"; | ||||
|             GardenaSmartBridge_InternalTimerGetDeviceData($hash); | ||||
|         } | ||||
|     } | ||||
|  | ||||
|     return undef; | ||||
| } | ||||
|  | ||||
| sub GardenaSmartBridge_Set($@) { | ||||
|      | ||||
|     my ($hash, $name, $cmd, @args) = @_; | ||||
|     my ($arg, @params) = @args; | ||||
|      | ||||
|      | ||||
|     if( lc $cmd eq 'getdevicesstate' ) { | ||||
|      | ||||
|         GardenaSmartBridge_getDevices($hash); | ||||
|          | ||||
|     } elsif( lc $cmd eq 'dummy2' ) { | ||||
|      | ||||
|  | ||||
|      | ||||
|     } else { | ||||
|         my $list = "getDevicesState:noArg"; | ||||
|         return "Unknown argument $cmd, choose one of $list"; | ||||
|     } | ||||
|      | ||||
|     return undef; | ||||
| } | ||||
|  | ||||
| sub GardenaSmartBridge_InternalTimerGetDeviceData($) { | ||||
|  | ||||
|     my $hash    = shift; | ||||
|     my $name    = $hash->{NAME}; | ||||
|      | ||||
|      | ||||
|     if( not IsDisabled($name) ) { | ||||
|      | ||||
|         GardenaSmartBridge_getDevices($hash); | ||||
|         Log3 $name, 4, "GardenaSmartBridge ($name) - set internal timer function for recall InternalTimerGetDeviceData sub"; | ||||
|          | ||||
|     } else { | ||||
|      | ||||
|         readingsSingleUpdate($hash,'state','disabled',1); | ||||
|         Log3 $name, 3, "GardenaSmartBridge ($name) - device is disabled"; | ||||
|     } | ||||
|      | ||||
|     InternalTimer( gettimeofday()+$hash->{INTERVAL},"GardenaSmartBridge_InternalTimerGetDeviceData", $hash, 1 ); | ||||
| } | ||||
|  | ||||
| sub GardenaSmartBridge_Write($@) { | ||||
|  | ||||
|     my ($hash,$payload,$deviceId,$model)  = @_; | ||||
|     my $name                        = $hash->{NAME}; | ||||
|      | ||||
|     my $session_id                  = $hash->{helper}{session_id}; | ||||
|     my $header                      = "Content-Type: application/json"; | ||||
|     my $uri                         = ''; | ||||
|     my $method                      = 'POST'; | ||||
|     $header                         .= "\r\nX-Session: $session_id"                                     if( defined($hash->{helper}{session_id}) ); | ||||
|     $payload                        = '{' . $payload . '}'                                              if( defined($payload) ); | ||||
|     $payload                        = '{}'                                                              if( not defined($payload) ); | ||||
|  | ||||
|  | ||||
|     if( $payload eq '{}' ) { | ||||
|         $method                         = 'GET'; | ||||
|         $uri                            .= '/locations/?user_id=' . $hash->{helper}{user_id}            if( not defined($hash->{helper}{locations_id}) ); | ||||
|             readingsSingleUpdate($hash,'state','fetch locationId',1)                                      if( not defined($hash->{helper}{locations_id}) ); | ||||
|         $uri                            .= '/sessions'                                                  if( not defined($hash->{helper}{session_id})); | ||||
|         $uri                            .= '/devices'                                                   if( not defined($model) and defined($hash->{helper}{locations_id}) ); | ||||
|     } | ||||
|      | ||||
|     $uri                            .= '/sessions'                                                      if( not defined($hash->{helper}{session_id})); | ||||
|     $uri                            .= '/devices/' . $deviceId . '/abilities/' . $model . '/command'    if( defined($model) and defined($payload) ); | ||||
|     $uri                            .= '?locationId=' . $hash->{helper}{locations_id}                   if( defined($hash->{helper}{locations_id}) ); | ||||
|  | ||||
|      | ||||
|     HttpUtils_NonblockingGet( | ||||
|         { | ||||
|             url         => $hash->{URL} . $uri, | ||||
|             timeout     => 15, | ||||
|             hash        => $hash, | ||||
|             device_id   => $deviceId, | ||||
|             data        => $payload, | ||||
|             method      => $method, | ||||
|             header      => $header, | ||||
|             doTrigger   => 1, | ||||
|             callback    => \&GardenaSmartBridge_ErrorHandling | ||||
|         } | ||||
|     ); | ||||
|  | ||||
|     Log3 $name, 4, "GardenaSmartBridge ($name) - Send with URL: $hash->{URL}$uri, HEADER: $header, DATA: $payload, METHOD: $method"; | ||||
| } | ||||
|  | ||||
| sub GardenaSmartBridge_ErrorHandling($$$) { | ||||
|  | ||||
|     my ($param,$err,$data)    = @_; | ||||
|      | ||||
|     my $hash                        = $param->{hash}; | ||||
|     my $name                        = $hash->{NAME}; | ||||
|  | ||||
|  | ||||
|     ###todo Das gesamte Errorhandling muss hier noch rein | ||||
|      | ||||
|     #Log3 $name, 1, "GardenaSmartBridge ($name) - Header:\n".Dumper($param->{header}); | ||||
|     #Log3 $name, 1, "GardenaSmartBridge ($name) - Error:\n".Dumper($err); | ||||
|     #Log3 $name, 1, "GardenaSmartBridge ($name) - Data:\n".Dumper($data); | ||||
|      | ||||
|  | ||||
|  | ||||
|     readingsSingleUpdate($hash,'state','connect to cloud',1) if( defined($hash->{helper}{locations_id}) ); | ||||
|     GardenaSmartBridge_ResponseProcessing($hash,$data); | ||||
|  | ||||
| } | ||||
|  | ||||
| sub GardenaSmartBridge_ResponseProcessing($$) { | ||||
|  | ||||
|     my ($hash,$json)    = @_; | ||||
|      | ||||
|     my $name            = $hash->{NAME}; | ||||
|  | ||||
|  | ||||
|     my $decode_json =   eval{decode_json($json)}; | ||||
|     if($@){ | ||||
|         Log3 $name, 3, "GardenaSmartBridge ($name) - JSON error while request: $@"; | ||||
|          | ||||
|         if( AttrVal( $name, 'debugJSON', 0 ) == 1 ) { | ||||
|             readingsBeginUpdate($hash); | ||||
|             readingsBulkUpdate($hash, 'JSON_ERROR', $@, 1); | ||||
|             readingsBulkUpdate($hash, 'JSON_ERROR_STRING', $json, 1); | ||||
|             readingsEndUpdate($hash, 1); | ||||
|         } | ||||
|     } | ||||
|      | ||||
|      | ||||
|      | ||||
|     if( defined($decode_json->{sessions}) and $decode_json->{sessions}) { | ||||
|      | ||||
|         $hash->{helper}{session_id}         = $decode_json->{sessions}{token}; | ||||
|         $hash->{helper}{user_id}            = $decode_json->{sessions}{user_id}; | ||||
|          | ||||
|         GardenaSmartBridge_Write($hash,undef,undef,undef); | ||||
|         Log3 $name, 3, "GardenaSmartBridge ($name) - fetch locations id"; | ||||
|          | ||||
|         return; | ||||
|      | ||||
|     } elsif( not defined($hash->{helper}{locations_id}) and defined($decode_json->{locations}) and ref($decode_json->{locations}) eq "ARRAY" and scalar(@{$decode_json->{locations}}) > 0) { | ||||
|      | ||||
|         foreach my $location ( @{$decode_json->{locations}} ) { | ||||
|          | ||||
|             $hash->{helper}{locations_id}    = $location->{id}; | ||||
|  | ||||
|             GardenaSmartBridge_WriteReadings($hash,$location); | ||||
|         } | ||||
|          | ||||
|         Log3 $name, 3, "GardenaSmartBridge ($name) - processed locations id. ID ist " . $hash->{helper}{locations_id}; | ||||
|         GardenaSmartBridge_Write($hash,undef,undef,undef); | ||||
|          | ||||
|         return; | ||||
|          | ||||
|     } elsif( defined($decode_json->{devices}) and ref($decode_json->{devices}) eq "ARRAY" and scalar(@{$decode_json->{devices}}) > 0) { | ||||
|  | ||||
|         my @buffer   = split('"devices":\[',$json); | ||||
|          | ||||
|          | ||||
|         my ($json,$tail) = GardenaSmartBridge_ParseJSON($hash, $buffer[1]); | ||||
|  | ||||
|  | ||||
|         while($json) { | ||||
|          | ||||
|             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; | ||||
|              | ||||
|              | ||||
|             unless( not defined($tail) and not ($tail) ) { | ||||
|              | ||||
|                 $decode_json =   eval{decode_json($json)}; | ||||
|                 if($@){ | ||||
|                     Log3 $name, 3, "GardenaSmartBridge ($name) - JSON error while request: $@"; | ||||
|                 } | ||||
|                  | ||||
|                 Dispatch($hash,$json,undef) | ||||
|                 #Log3 $name, 3, "GardenaSmartBridge ($name) - NAME: $decode_json->{name} und ID: $decode_json->{id} und JSON: $json" | ||||
|                 unless( $decode_json->{category} eq 'gateway' ); | ||||
|             } | ||||
|              | ||||
|             ($json,$tail) = GardenaSmartBridge_ParseJSON($hash, $tail); | ||||
|          | ||||
|             Log3 $name, 5, "GardenaSmartBridge ($name) - Nach Sub: Laenge JSON: " . length($json) . " Content: " . $json . " Tail: " . $tail; | ||||
|         } | ||||
|  | ||||
|         return; | ||||
|     } | ||||
|  | ||||
|         Log3 $name, 3, "GardenaSmartBridge ($name) - no Match for processing data" | ||||
| } | ||||
|  | ||||
| sub GardenaSmartBridge_WriteReadings($$) { | ||||
|  | ||||
|     my ($hash,$decode_json)     = @_; | ||||
|     my $name                    = $hash->{NAME}; | ||||
|  | ||||
|  | ||||
|     if( defined($decode_json->{id}) and $decode_json->{id} and defined($decode_json->{name}) and $decode_json->{name} ) { | ||||
|  | ||||
|         readingsBeginUpdate($hash); | ||||
|         readingsBulkUpdateIfChanged($hash,'name',$decode_json->{name}); | ||||
|         readingsBulkUpdateIfChanged($hash,'authorized_user_ids',scalar(@{$decode_json->{authorized_user_ids}})); | ||||
|         readingsBulkUpdateIfChanged($hash,'devices',scalar(@{$decode_json->{devices}})); | ||||
|          | ||||
|         while( ( my ($t,$v) ) = each %{$decode_json->{geo_position}} ) { | ||||
|             $v  = encode_utf8($v); | ||||
|             readingsBulkUpdateIfChanged($hash,$t,$v); | ||||
|         } | ||||
|          | ||||
|         readingsBulkUpdateIfChanged($hash,'zones',scalar(@{$decode_json->{zones}})); | ||||
|         readingsEndUpdate( $hash, 1 ); | ||||
|     } | ||||
|  | ||||
|     Log3 $name, 3, "GardenaSmartBridge ($name) - readings would be written"; | ||||
| } | ||||
|  | ||||
|  | ||||
| #################################### | ||||
| #################################### | ||||
| #### my little helpers Sub's ####### | ||||
|  | ||||
| sub GardenaSmartBridge_getDevices($) { | ||||
|  | ||||
|     my $hash    = shift; | ||||
|     my $name    = $hash->{NAME}; | ||||
|      | ||||
|      | ||||
|     GardenaSmartBridge_Write($hash,undef,undef,undef); | ||||
|     Log3 $name, 4, "GardenaSmartBridge ($name) - fetch device list and device states"; | ||||
| } | ||||
|  | ||||
| sub GardenaSmartBridge_getToken($) { | ||||
|  | ||||
|     my $hash    = shift; | ||||
|     my $name    = $hash->{NAME}; | ||||
|      | ||||
|      | ||||
|     delete $hash->{helper}{session_id}      if( defined($hash->{helper}{session_id}) and $hash->{helper}{session_id} ); | ||||
|     delete $hash->{helper}{user_id}         if( defined($hash->{helper}{user_id}) and $hash->{helper}{user_id} ); | ||||
|     delete $hash->{helper}{locations_id}    if( defined($hash->{helper}{locations_id}) and $hash->{helper}{locations_id} ); | ||||
|          | ||||
|     GardenaSmartBridge_Write($hash,'"sessions": {"email": "'.GardenaSmartBridge_decrypt($hash->{helper}{username}).'","password": "'.GardenaSmartBridge_decrypt($hash->{helper}{password}).'"}',undef,undef); | ||||
|      | ||||
|     Log3 $name, 3, "GardenaSmartBridge ($name) - send credentials to fetch Token and locationId"; | ||||
| } | ||||
|  | ||||
| sub GardenaSmartBridge_encrypt($) { | ||||
|  | ||||
|     my ($decoded) = @_; | ||||
|     my $key = getUniqueId(); | ||||
|     my $encoded; | ||||
|  | ||||
|     return $decoded if( $decoded =~ /crypt:/ ); | ||||
|  | ||||
|     for my $char (split //, $decoded) { | ||||
|         my $encode = chop($key); | ||||
|         $encoded .= sprintf("%.2x",ord($char)^ord($encode)); | ||||
|         $key = $encode.$key; | ||||
|     } | ||||
|  | ||||
|     return 'crypt:'.$encoded; | ||||
| } | ||||
|  | ||||
| sub GardenaSmartBridge_decrypt($) { | ||||
|  | ||||
|     my ($encoded) = @_; | ||||
|     my $key = getUniqueId(); | ||||
|     my $decoded; | ||||
|  | ||||
|     return $encoded if( $encoded !~ /crypt:/ ); | ||||
|    | ||||
|     $encoded = $1 if( $encoded =~ /crypt:(.*)/ ); | ||||
|  | ||||
|     for my $char (map { pack('C', hex($_)) } ($encoded =~ /(..)/g)) { | ||||
|         my $decode = chop($key); | ||||
|         $decoded .= chr(ord($char)^ord($decode)); | ||||
|         $key = $decode.$key; | ||||
|     } | ||||
|  | ||||
|     return $decoded; | ||||
| } | ||||
|  | ||||
| sub GardenaSmartBridge_ParseJSON($$) { | ||||
|  | ||||
|     my ($hash, $buffer) = @_; | ||||
|      | ||||
|     my $name    = $hash->{NAME}; | ||||
|     my $open    = 0; | ||||
|     my $close   = 0; | ||||
|     my $msg     = ''; | ||||
|     my $tail    = ''; | ||||
|      | ||||
|      | ||||
|     if($buffer) { | ||||
|         foreach my $c (split //, $buffer) { | ||||
|             if($open == $close && $open > 0) { | ||||
|                 $tail .= $c; | ||||
|                 Log3 $name, 5, "GardenaSmartBridge ($name) - $open == $close && $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, 4, "GardenaSmartBridge ($name) - return msg: $msg and tail: $tail"; | ||||
|     return ($msg,$tail); | ||||
| } | ||||
|  | ||||
|  | ||||
|  | ||||
|  | ||||
| 1; | ||||
|  | ||||
|  | ||||
|  | ||||
|  | ||||
|  | ||||
|  | ||||
| =pod | ||||
|  | ||||
| =item device | ||||
| =item summary    Gardena Smart | ||||
| =item summary_DE Gardena Smart | ||||
|  | ||||
| =begin html | ||||
|  | ||||
|  | ||||
|  | ||||
| =end html | ||||
| =begin html_DE | ||||
|  | ||||
|  | ||||
|  | ||||
| =end html_DE | ||||
| =cut | ||||
							
								
								
									
										346
									
								
								74_GardenaSmartDevice.pm
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										346
									
								
								74_GardenaSmartDevice.pm
									
									
									
									
									
										Normal file
									
								
							| @@ -0,0 +1,346 @@ | ||||
| ############################################################################### | ||||
| #  | ||||
| # Developed with Kate | ||||
| # | ||||
| #  (c) 2017 Copyright: Marko Oldenburg (leongaultier at gmail dot com) | ||||
| #  All rights reserved | ||||
| # | ||||
| #  This script is free software; you can redistribute it and/or modify | ||||
| #  it under the terms of the GNU General Public License as published by | ||||
| #  the Free Software Foundation; either version 2 of the License, or | ||||
| #  any later version. | ||||
| # | ||||
| #  The GNU General Public License can be found at | ||||
| #  http://www.gnu.org/copyleft/gpl.html. | ||||
| #  A copy is found in the textfile GPL.txt and important notices to the license | ||||
| #  from the author is found in LICENSE.txt distributed with these scripts. | ||||
| # | ||||
| #  This script is distributed in the hope that it will be useful, | ||||
| #  but WITHOUT ANY WARRANTY; without even the implied warranty of | ||||
| #  MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the | ||||
| #  GNU General Public License for more details. | ||||
| # | ||||
| # | ||||
| # $Id$ | ||||
| # | ||||
| ############################################################################### | ||||
| ## | ||||
| ## | ||||
| ## Das JSON Modul immer in einem eval aufrufen | ||||
| # $data = eval{decode_json($data)}; | ||||
| # | ||||
| # if($@){ | ||||
| #   Log3($SELF, 2, "$TYPE ($SELF) - error while request: $@"); | ||||
| #   | ||||
| #   readingsSingleUpdate($hash, "state", "error", 1); | ||||
| # | ||||
| #   return; | ||||
| # } | ||||
| # | ||||
| # | ||||
| ###### Wichtige Notizen | ||||
| # | ||||
| #   apt-get install libio-socket-ssl-perl | ||||
| #   http://www.dxsdata.com/de/2016/07/php-class-for-gardena-smart-system-api/ | ||||
| #    | ||||
| ## | ||||
| ## | ||||
|  | ||||
|  | ||||
|  | ||||
| package main; | ||||
|  | ||||
|  | ||||
| my $missingModul = ""; | ||||
|  | ||||
| use strict; | ||||
| use warnings; | ||||
|  | ||||
| use Data::Dumper;   #debugging | ||||
|  | ||||
| eval "use Encode qw(encode encode_utf8 decode_utf8);1" or $missingModul .= "Encode "; | ||||
| eval "use JSON;1" or $missingModul .= "JSON "; | ||||
|  | ||||
|  | ||||
| my $version = "0.0.10"; | ||||
|  | ||||
|  | ||||
|  | ||||
|  | ||||
| # Declare functions | ||||
| sub GardenaSmartDevice_Attr(@); | ||||
| sub GardenaSmartDevice_Define($$); | ||||
| sub GardenaSmartDevice_Initialize($); | ||||
| sub GardenaSmartDevice_Set($@); | ||||
| sub GardenaSmartDevice_Undef($$); | ||||
| sub GardenaSmartDevice_WriteReadings($$); | ||||
| sub GardenaSmartDevice_Parse($$); | ||||
|  | ||||
|  | ||||
|  | ||||
|  | ||||
| sub GardenaSmartDevice_Initialize($) { | ||||
|  | ||||
|     my ($hash) = @_; | ||||
|      | ||||
|     $hash->{Match}      = '^{"id":".*'; | ||||
|  | ||||
|     $hash->{SetFn}      = "GardenaSmartDevice_Set"; | ||||
|     $hash->{DefFn}      = "GardenaSmartDevice_Define"; | ||||
|     $hash->{UndefFn}    = "GardenaSmartDevice_Undef"; | ||||
|     $hash->{ParseFn}    = "GardenaSmartDevice_Parse"; | ||||
|      | ||||
|     $hash->{AttrFn}     = "GardenaSmartDevice_Attr"; | ||||
|     $hash->{AttrList}   = "disable:1 ". | ||||
|                             "model ". | ||||
|                             $readingFnAttributes; | ||||
|      | ||||
|     foreach my $d(sort keys %{$modules{GardenaSmartDevice}{defptr}}) { | ||||
|      | ||||
|         my $hash = $modules{GardenaSmartDevice}{defptr}{$d}; | ||||
|         $hash->{VERSION}      = $version; | ||||
|     } | ||||
| } | ||||
|  | ||||
| sub GardenaSmartDevice_Define($$) { | ||||
|  | ||||
|     my ( $hash, $def ) = @_; | ||||
|     my @a = split( "[ \t]+", $def ); | ||||
|     splice( @a, 1, 1 ); | ||||
|     my $iodev; | ||||
|     my $i = 0; | ||||
|  | ||||
|  | ||||
|     foreach my $param ( @a ) { | ||||
|         if( $param =~ m/IODev=([^\s]*)/ ) { | ||||
|          | ||||
|             $iodev = $1; | ||||
|             splice( @a, $i, 3 ); | ||||
|             last; | ||||
|         } | ||||
|          | ||||
|         $i++; | ||||
|     } | ||||
|  | ||||
|  | ||||
|     return "too few parameters: define <NAME> GardenaSmartDevice <device_Id> <model>" if( @a != 3 ) ; | ||||
|     return "Cannot define Gardena Bridge device. Perl modul $missingModul is missing." if ( $missingModul ); | ||||
|      | ||||
|     my ($name,$deviceId,$category)   = @a; | ||||
|      | ||||
|     $hash->{DEVICEID}           = $deviceId; | ||||
|     $hash->{VERSION}            = $version; | ||||
|  | ||||
|     AssignIoPort($hash,$iodev) if( !$hash->{IODev} ); | ||||
|      | ||||
|     if(defined($hash->{IODev}->{NAME})) { | ||||
|      | ||||
|         Log3 $name, 3, "GardenaSmartDevice ($name) - I/O device is " . $hash->{IODev}->{NAME}; | ||||
|      | ||||
|     } else { | ||||
|      | ||||
|         Log3 $name, 1, "GardenaSmartDevice ($name) - no I/O device"; | ||||
|     } | ||||
|      | ||||
|     $iodev = $hash->{IODev}->{NAME}; | ||||
|      | ||||
|     my $d = $modules{GardenaSmartDevice}{defptr}{$deviceId}; | ||||
|      | ||||
|     return "GardenaSmartDevice device $name on GardenaSmartBridge $iodev already defined." | ||||
|     if( defined($d) && $d->{IODev} == $hash->{IODev} && $d->{NAME} ne $name ); | ||||
|  | ||||
|     Log3 $name, 3, "GardenaSmartDevice ($name) - defined with DEVICEID: $deviceId"; | ||||
|      | ||||
|  | ||||
|      | ||||
|      | ||||
|     $attr{$name}{room}          = "GardenaSmart"    if( not defined( $attr{$name}{room} ) ); | ||||
|     $attr{$name}{model}         = $category         if( not defined( $attr{$name}{model} ) ); | ||||
|      | ||||
|     Log3 $name, 3, "GardenaSmartDevice ($name) - defined GardenaSmartDevice"; | ||||
|  | ||||
|     #GardenaSmartDevice_Open( $hash ); | ||||
|      | ||||
|     $modules{GardenaSmartDevice}{defptr}{$deviceId} = $hash; | ||||
|  | ||||
|     return undef; | ||||
| } | ||||
|  | ||||
| sub GardenaSmartDevice_Undef($$) { | ||||
|  | ||||
|     my ( $hash, $arg )  = @_; | ||||
|     my $name            = $hash->{NAME}; | ||||
|     my $deviceId        = $hash->{DEVICEID}; | ||||
|      | ||||
|      | ||||
|     delete $modules{GardenaSmartDevice}{defptr}{$deviceId}; | ||||
|  | ||||
|     return undef; | ||||
| } | ||||
|  | ||||
| sub GardenaSmartDevice_Attr(@) { | ||||
|  | ||||
|     my ( $cmd, $name, $attrName, $attrVal ) = @_; | ||||
|     my $hash = $defs{$name}; | ||||
|      | ||||
|     my $orig = $attrVal; | ||||
|  | ||||
|      | ||||
|     return undef; | ||||
| } | ||||
|  | ||||
| sub GardenaSmartDevice_Set($@) { | ||||
|      | ||||
|     my ($hash, $name, $cmd, @args) = @_; | ||||
|     my ($arg, @params) = @args; | ||||
|      | ||||
|     my $payload; | ||||
|      | ||||
|      | ||||
|     if( lc $cmd eq 'parkuntilfurthernotice' ) { | ||||
|  | ||||
|         $payload    = '"name":"park_until_further_notice"'; | ||||
|      | ||||
|     } elsif( lc $cmd eq 'parkuntilnexttimer' ) { | ||||
|      | ||||
|         $payload    = '"name":"park_until_next_timer"'; | ||||
|          | ||||
|     } elsif( lc $cmd eq 'startresumeschedule' ) { | ||||
|      | ||||
|         $payload    = '"name":"start_resume_schedule"'; | ||||
|      | ||||
|     } elsif( lc $cmd eq 'startoverridetimer' ) { | ||||
|      | ||||
|         my $duration     = join( " ", @args ); | ||||
|         $payload    = '"name":"start_override_timer","parameters":{"duration":' . $duration . '}'; | ||||
|      | ||||
|     } elsif( lc $cmd eq 'statusrequest' ) { | ||||
|      | ||||
|         #$payload    = '"name":"device_info"'; | ||||
|         $payload    = '"name":"measure_ambient_temperature"'; | ||||
|      | ||||
|     } elsif( lc $cmd eq '' ) { | ||||
|      | ||||
|     } elsif( lc $cmd eq '' ) { | ||||
|      | ||||
|     } elsif( lc $cmd eq '' ) { | ||||
|      | ||||
|     } elsif( lc $cmd eq '' ) { | ||||
|      | ||||
|     } elsif( lc $cmd eq '' ) { | ||||
|      | ||||
|     } elsif( lc $cmd eq '' ) { | ||||
|      | ||||
|      | ||||
|     } elsif( lc $cmd eq '' ) { | ||||
|      | ||||
|      | ||||
|     } elsif( lc $cmd eq '' ) { | ||||
|      | ||||
|      | ||||
|     } else { | ||||
|      | ||||
|         my $list    = 'statusRequest:noArg '; | ||||
|         $list       .= 'parkUntilFurtherNotice:noArg parkUntilNextTimer:noArg startResumeSchedule:noArg startOverrideTimer:slider,0,1,1440' if( AttrVal($name,'model','unknown') eq 'mower' ); | ||||
|          | ||||
|         return "Unknown argument $cmd, choose one of $list"; | ||||
|     } | ||||
|      | ||||
|     IOWrite($hash,$payload,$hash->{DEVICEID},AttrVal($name,'model','unknown')); | ||||
|     Log3 $name, 3, "GardenaSmartBridge ($name) - IOWrite: $payload $hash->{DEVICEID} " . AttrVal($name,'model','unknown') . " IODevHash=$hash->{IODev}"; | ||||
|      | ||||
|     return undef; | ||||
| } | ||||
|  | ||||
| sub GardenaSmartDevice_Parse($$) { | ||||
|  | ||||
|     my ($io_hash,$json)  = @_; | ||||
|      | ||||
|     my $name                    = $io_hash->{NAME}; | ||||
|      | ||||
|      | ||||
|      | ||||
|     my $decode_json =   eval{decode_json($json)}; | ||||
|     if($@){ | ||||
|         Log3 $name, 3, "GardenaSmartBridge ($name) - JSON error while request: $@"; | ||||
|     } | ||||
|      | ||||
|     Log3 $name, 4, "GardenaSmartDevice ($name) - ParseFn was called"; | ||||
|     Log3 $name, 4, "GardenaSmartDevice ($name) - JSON: $json"; | ||||
|  | ||||
|      | ||||
|     if( defined($decode_json->{id}) ) { | ||||
|          | ||||
|         my $deviceId                = $decode_json->{id}; | ||||
|  | ||||
|         if( my $hash                = $modules{GardenaSmartDevice}{defptr}{$deviceId} ) {   | ||||
|             my $name                = $hash->{NAME}; | ||||
|                          | ||||
|             GardenaSmartDevice_WriteReadings($hash,$decode_json); | ||||
|             Log3 $name, 3, "GardenaSmartDevice ($name) - find logical device: $hash->{NAME}"; | ||||
|                          | ||||
|             return $hash->{NAME}; | ||||
|              | ||||
|         } else { | ||||
|              | ||||
|             Log3 $name, 3, "GardenaSmartDevice ($name) - Wird angelegt NAME: $decode_json->{name} ID: $decode_json->{id} CATEGORY: $decode_json->{category} IODev=$name"; | ||||
|             return "UNDEFINED $decode_json->{name} GardenaSmartDevice $decode_json->{id} $decode_json->{category} IODev=$name"; | ||||
|         } | ||||
|     } | ||||
| } | ||||
|  | ||||
| sub GardenaSmartDevice_WriteReadings($$) { | ||||
|  | ||||
|     my ($hash,$decode_json)     = @_; | ||||
|      | ||||
|     my $name                    = $hash->{NAME}; | ||||
|     my $abilities               = scalar (@{$decode_json->{abilities}}); | ||||
|  | ||||
|      | ||||
|     do { | ||||
|          | ||||
|         if( ref($decode_json->{abilities}[$abilities]{properties}) eq "ARRAY" and scalar(@{$decode_json->{abilities}[$abilities]{properties}}) > 0 ) {; | ||||
|  | ||||
|             foreach my $propertie (@{$decode_json->{abilities}[$abilities]{properties}}) { | ||||
|                 readingsBeginUpdate($hash); | ||||
|                 readingsBulkUpdateIfChanged($hash,$decode_json->{abilities}[$abilities]{name}.'-'.$propertie->{name},$propertie->{value}); | ||||
|                 readingsEndUpdate( $hash, 1 ); | ||||
|             } | ||||
|         } | ||||
|  | ||||
|         $abilities--; | ||||
|     } while ($abilities >= 0); | ||||
|      | ||||
|     Log3 $name, 4, "GardenaSmartDevice ($name) - readings was written}"; | ||||
| } | ||||
|  | ||||
| ################################## | ||||
| ################################## | ||||
| #### my little helpers ########### | ||||
|  | ||||
|  | ||||
|  | ||||
|  | ||||
|  | ||||
|  | ||||
|  | ||||
|  | ||||
| 1; | ||||
|  | ||||
| =pod | ||||
|  | ||||
| =item device | ||||
| =item summary    Gardena Smart | ||||
| =item summary_DE Gardena Smart | ||||
|  | ||||
| =begin html | ||||
|  | ||||
|  | ||||
|  | ||||
| =end html | ||||
| =begin html_DE | ||||
|  | ||||
|  | ||||
|  | ||||
| =end html_DE | ||||
| =cut | ||||
		Reference in New Issue
	
	Block a user