From 673c745d694ab2e44edb1b81e36a6608555072db Mon Sep 17 00:00:00 2001 From: Marko Oldenburg Date: Mon, 26 Apr 2021 21:37:53 +0200 Subject: [PATCH 01/18] change version number --- FHEM/74_GardenaSmartDevice.pm | 2 +- controls_GardenaSmartDevice.txt | 2 +- 2 files changed, 2 insertions(+), 2 deletions(-) diff --git a/FHEM/74_GardenaSmartDevice.pm b/FHEM/74_GardenaSmartDevice.pm index 42d678b..c3195e4 100644 --- a/FHEM/74_GardenaSmartDevice.pm +++ b/FHEM/74_GardenaSmartDevice.pm @@ -1257,7 +1257,7 @@ sub SetPredefinedStartPoints { ], "release_status": "stable", "license": "GPL_2", - "version": "v2.2.2", + "version": "v2.2.3", "author": [ "Marko Oldenburg " ], diff --git a/controls_GardenaSmartDevice.txt b/controls_GardenaSmartDevice.txt index 1178321..25cc020 100644 --- a/controls_GardenaSmartDevice.txt +++ b/controls_GardenaSmartDevice.txt @@ -1,2 +1,2 @@ UPD 2021-04-22_18:29:35 46169 FHEM/73_GardenaSmartBridge.pm -UPD 2021-04-26_20:00:59 48421 FHEM/74_GardenaSmartDevice.pm +UPD 2021-04-26_21:37:06 48421 FHEM/74_GardenaSmartDevice.pm From bd2e6a041450da99d2f13823d40bebd3255008b7 Mon Sep 17 00:00:00 2001 From: Sebastian Date: Fri, 14 May 2021 12:10:44 +0200 Subject: [PATCH 02/18] add IrrigationControl Stuff some code moved/ckeanup --- FHEM/73_GardenaSmartBridge.pm | 4 +-- FHEM/74_GardenaSmartDevice.pm | 51 ++++++++++++++++++++++++++--------- 2 files changed, 40 insertions(+), 15 deletions(-) diff --git a/FHEM/73_GardenaSmartBridge.pm b/FHEM/73_GardenaSmartBridge.pm index 2e1b586..dcc93d5 100644 --- a/FHEM/73_GardenaSmartBridge.pm +++ b/FHEM/73_GardenaSmartBridge.pm @@ -1219,7 +1219,7 @@ sub createHttpValueStrings { $uri = '/auth/token' if ( !defined( $hash->{helper}{session_id} ) ); if ( defined( $hash->{helper}{locations_id} ) ) { - if ( defined($abilities) && $abilities eq 'mower_settings' ) { + if ( defined($abilities) && $abilities =~ /.*_settings/ ) { $method = 'PUT'; my $dhash = $modules{GardenaSmartDevice}{defptr}{$deviceId}; @@ -1231,7 +1231,7 @@ sub createHttpValueStrings { . $service_id if ( defined($abilities) && defined($payload) - && $abilities eq 'mower_settings' ); + && $abilities =~ /.*_settings/ ); } # park until next schedules or override elsif (defined($abilities) diff --git a/FHEM/74_GardenaSmartDevice.pm b/FHEM/74_GardenaSmartDevice.pm index 58f4ea9..560c69c 100644 --- a/FHEM/74_GardenaSmartDevice.pm +++ b/FHEM/74_GardenaSmartDevice.pm @@ -209,6 +209,14 @@ sub Define { $hash->{helper}{STARTINGPOINTID} = ''; $hash->{helper}{schedules_paused_until_id} = ''; $hash->{helper}{eco_mode_id} = ''; + # IrrigationControl valve control max 6 + $hash->{helper}{schedules_paused_until_1_id} = ''; + $hash->{helper}{schedules_paused_until_2_id} = ''; + $hash->{helper}{schedules_paused_until_3_id} = ''; + $hash->{helper}{schedules_paused_until_4_id} = ''; + $hash->{helper}{schedules_paused_until_5_id} = ''; + $hash->{helper}{schedules_paused_until_6_id} = ''; + CommandAttr( undef, "$name IODev $modules{GardenaSmartBridge}{defptr}{BRIDGE}->{NAME}" ) @@ -282,6 +290,18 @@ sub Set { my $abilities; my $service_id; my $mainboard_version = ReadingsVal( $name, 'mower_type-mainboard_version', 0.0 ); + + #set default abilitie ... overwrite in cmd to change + $abilities = 'mower' + if ( AttrVal( $name, 'model', 'unknown' ) eq 'mower' ); + $abilities = 'watering' + if ( AttrVal( $name, 'model', 'unknown' ) eq 'ic24' + || AttrVal( $name, 'model', 'unknown' ) eq 'watering_computer' ); + $abilities = 'power' + if ( AttrVal( $name, 'model', 'unknown' ) eq 'power' ); + $abilities = 'manual_watering' + if ( AttrVal( $name, 'model', 'unknown' ) eq 'electronic_pressure_pump' ); + ### mower # service_id (eco, parkuntilfurhternotice, startpoints) if ( lc $cmd eq 'parkuntilfurthernotice' ) { @@ -383,6 +403,21 @@ sub Set { . ',"valve_id":' . $valve_id . '}}'; } + elsif ( $cmd eq 'closeAllValves' ){ + $payload = '"name":"close_all_valves","parameters":{}'; + } + elsif ( $cmd =~ '/.*ScheduleValve/' ){ + my $valve_id = $1; + $abilities = 'irrigation_settings'; + $service_id = $hash->{helper}->{'schedules_paused_until_'.$valve_id.'_id'}; + $payload = '"settings":{"name":"schedules_paused_until_' + .$valve_id. + '", "value":"' + . ($cmd eq 'resumeScheduleValve') ? '' : '2040-12-31T22:00:00.000Z' + .'","device":"' + .$hash->{DEVICEID} + .'"}'; + } ### Sensors elsif ( lc $cmd eq 'refresh' ) { @@ -420,7 +455,7 @@ sub Set { if ( AttrVal( $name, 'model', 'unknown' ) eq 'watering_computer' ); $list .= -'manualDurationValve1:slider,1,1,59 manualDurationValve2:slider,1,1,59 manualDurationValve3:slider,1,1,59 manualDurationValve4:slider,1,1,59 manualDurationValve5:slider,1,1,59 manualDurationValve6:slider,1,1,59 cancelOverrideValve1:noArg cancelOverrideValve2:noArg cancelOverrideValve3:noArg cancelOverrideValve4:noArg cancelOverrideValve5:noArg cancelOverrideValve6:noArg' +'closeAllValves:noArg stopScheduleValve:selectnumbers,1,1,6,0,lin resumeScheduleValve:selectnumbers,1,1,6,0,lin manualDurationValve1:slider,1,1,59 manualDurationValve2:slider,1,1,59 manualDurationValve3:slider,1,1,59 manualDurationValve4:slider,1,1,59 manualDurationValve5:slider,1,1,59 manualDurationValve6:slider,1,1,59 cancelOverrideValve1:noArg cancelOverrideValve2:noArg cancelOverrideValve3:noArg cancelOverrideValve4:noArg cancelOverrideValve5:noArg cancelOverrideValve6:noArg' if ( AttrVal( $name, 'model', 'unknown' ) eq 'ic24' ); $list .= 'refresh:temperature,humidity' @@ -435,17 +470,6 @@ sub Set { return "Unknown argument $cmd, choose one of $list"; } - - $abilities = 'mower' - if ( AttrVal( $name, 'model', 'unknown' ) eq 'mower' ) - && ($abilities !~ /mower_settings|mower_timer/); - $abilities = 'watering' - if ( AttrVal( $name, 'model', 'unknown' ) eq 'ic24' - || AttrVal( $name, 'model', 'unknown' ) eq 'watering_computer' ); - $abilities = 'power' - if ( AttrVal( $name, 'model', 'unknown' ) eq 'power' ); - $abilities = 'manual_watering' - if ( AttrVal( $name, 'model', 'unknown' ) eq 'electronic_pressure_pump' ); $hash->{helper}{deviceAction} = $payload; readingsSingleUpdate( $hash, "state", "send command to gardena cloud", 1 ); @@ -614,7 +638,8 @@ sub WriteReadings { #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} eq 'schedules_paused_until' + && ( + $decode_json->{settings}[$settings]{name} =~ /schedules_paused_until_?\d?$/ || $decode_json->{settings}[$settings]{name} eq 'eco_mode' ) ) { From 59c2d4338a320b876aba2ba97adde0b62104d337 Mon Sep 17 00:00:00 2001 From: Sebastian Date: Fri, 14 May 2021 12:34:03 +0200 Subject: [PATCH 03/18] commit hook test --- FHEM/74_GardenaSmartDevice.pm | 1 - controls_GardenaSmartDevice.txt | 4 ++-- 2 files changed, 2 insertions(+), 3 deletions(-) diff --git a/FHEM/74_GardenaSmartDevice.pm b/FHEM/74_GardenaSmartDevice.pm index 560c69c..566ff6e 100644 --- a/FHEM/74_GardenaSmartDevice.pm +++ b/FHEM/74_GardenaSmartDevice.pm @@ -217,7 +217,6 @@ sub Define { $hash->{helper}{schedules_paused_until_5_id} = ''; $hash->{helper}{schedules_paused_until_6_id} = ''; - CommandAttr( undef, "$name IODev $modules{GardenaSmartBridge}{defptr}{BRIDGE}->{NAME}" ) if ( AttrVal( $name, 'IODev', 'none' ) eq 'none' ); diff --git a/controls_GardenaSmartDevice.txt b/controls_GardenaSmartDevice.txt index 25cc020..7b0971d 100644 --- a/controls_GardenaSmartDevice.txt +++ b/controls_GardenaSmartDevice.txt @@ -1,2 +1,2 @@ -UPD 2021-04-22_18:29:35 46169 FHEM/73_GardenaSmartBridge.pm -UPD 2021-04-26_21:37:06 48421 FHEM/74_GardenaSmartDevice.pm +UPD 2021-05-14_12:05:31 47661 FHEM/73_GardenaSmartBridge.pm +UPD 2021-05-14_12:33:55 52198 FHEM/74_GardenaSmartDevice.pm From 7f1c8ae1e3f5228976df34ba53595c2ca7af8240 Mon Sep 17 00:00:00 2001 From: Sebastian Date: Fri, 14 May 2021 12:36:48 +0200 Subject: [PATCH 04/18] checksum commit --- FHEM/73_GardenaSmartBridge.pm | 1 - controls_GardenaSmartDevice.txt | 2 +- 2 files changed, 1 insertion(+), 2 deletions(-) diff --git a/FHEM/73_GardenaSmartBridge.pm b/FHEM/73_GardenaSmartBridge.pm index dcc93d5..6960693 100644 --- a/FHEM/73_GardenaSmartBridge.pm +++ b/FHEM/73_GardenaSmartBridge.pm @@ -147,7 +147,6 @@ if ($@) { ## Import der FHEM Funktionen #-- Run before package compilation BEGIN { - # Import from main context GP_Import( qw(readingsSingleUpdate diff --git a/controls_GardenaSmartDevice.txt b/controls_GardenaSmartDevice.txt index 7b0971d..963cc94 100644 --- a/controls_GardenaSmartDevice.txt +++ b/controls_GardenaSmartDevice.txt @@ -1,2 +1,2 @@ -UPD 2021-05-14_12:05:31 47661 FHEM/73_GardenaSmartBridge.pm +UPD 2021-05-14_12:36:37 47659 FHEM/73_GardenaSmartBridge.pm UPD 2021-05-14_12:33:55 52198 FHEM/74_GardenaSmartDevice.pm From 49b15725bf9e5b83c77e90e0edeb6cc3690696eb Mon Sep 17 00:00:00 2001 From: Sebastian Date: Fri, 14 May 2021 12:51:41 +0200 Subject: [PATCH 05/18] test commit --- FHEM/73_GardenaSmartBridge.pm | 2 +- controls_GardenaSmartDevice.txt | 2 +- 2 files changed, 2 insertions(+), 2 deletions(-) diff --git a/FHEM/73_GardenaSmartBridge.pm b/FHEM/73_GardenaSmartBridge.pm index 6960693..de0fd7b 100644 --- a/FHEM/73_GardenaSmartBridge.pm +++ b/FHEM/73_GardenaSmartBridge.pm @@ -70,7 +70,7 @@ my $missingModul = ''; eval "use Encode qw(encode encode_utf8 decode_utf8);1" or $missingModul .= "Encode "; -# eval "use JSON;1" || $missingModul .= 'JSON '; +# eval "use JSON;1" || $missingModul .= 'JSON '; eval "use IO::Socket::SSL;1" or $missingModul .= 'IO::Socket::SSL '; # try to use JSON::MaybeXS wrapper diff --git a/controls_GardenaSmartDevice.txt b/controls_GardenaSmartDevice.txt index 963cc94..64012e8 100644 --- a/controls_GardenaSmartDevice.txt +++ b/controls_GardenaSmartDevice.txt @@ -1,2 +1,2 @@ -UPD 2021-05-14_12:36:37 47659 FHEM/73_GardenaSmartBridge.pm +UPD 2021-05-14_12:51:33 47648 FHEM/73_GardenaSmartBridge.pm UPD 2021-05-14_12:33:55 52198 FHEM/74_GardenaSmartDevice.pm From cb83d0bfb8ad6e3843aa35c5eb75ccca17a24b55 Mon Sep 17 00:00:00 2001 From: Marko Oldenburg Date: Fri, 14 May 2021 12:55:18 +0200 Subject: [PATCH 06/18] check pre-commit hook --- FHEM/73_GardenaSmartBridge.pm | 1 - FHEM/74_GardenaSmartDevice.pm | 2 -- controls_GardenaSmartDevice.txt | 4 ++-- 3 files changed, 2 insertions(+), 5 deletions(-) diff --git a/FHEM/73_GardenaSmartBridge.pm b/FHEM/73_GardenaSmartBridge.pm index de0fd7b..69acaec 100644 --- a/FHEM/73_GardenaSmartBridge.pm +++ b/FHEM/73_GardenaSmartBridge.pm @@ -57,7 +57,6 @@ package FHEM::GardenaSmartBridge; use GPUtils qw(GP_Import GP_Export); -#use Data::Dumper; #only for Debugging use strict; use warnings; diff --git a/FHEM/74_GardenaSmartDevice.pm b/FHEM/74_GardenaSmartDevice.pm index 566ff6e..5ba6d8f 100644 --- a/FHEM/74_GardenaSmartDevice.pm +++ b/FHEM/74_GardenaSmartDevice.pm @@ -64,8 +64,6 @@ use POSIX; use FHEM::Meta; use Time::Local; -#use Data::Dumper; # only for debugging - # try to use JSON::MaybeXS wrapper # for chance of better performance + open code eval { diff --git a/controls_GardenaSmartDevice.txt b/controls_GardenaSmartDevice.txt index 64012e8..8a7410d 100644 --- a/controls_GardenaSmartDevice.txt +++ b/controls_GardenaSmartDevice.txt @@ -1,2 +1,2 @@ -UPD 2021-05-14_12:51:33 47648 FHEM/73_GardenaSmartBridge.pm -UPD 2021-05-14_12:33:55 52198 FHEM/74_GardenaSmartDevice.pm +UPD 2021-05-14_12:55:05 46108 FHEM/73_GardenaSmartBridge.pm +UPD 2021-05-14_12:54:55 50825 FHEM/74_GardenaSmartDevice.pm From f68397ead3bbe0a9f5920367c51102969952f9a7 Mon Sep 17 00:00:00 2001 From: Sebastian Date: Fri, 14 May 2021 13:16:53 +0200 Subject: [PATCH 07/18] test coommt --- FHEM/73_GardenaSmartBridge.pm | 2994 +++++++++++++++---------------- FHEM/74_GardenaSmartDevice.pm | 2654 +++++++++++++-------------- controls_GardenaSmartDevice.txt | 4 +- 3 files changed, 2826 insertions(+), 2826 deletions(-) diff --git a/FHEM/73_GardenaSmartBridge.pm b/FHEM/73_GardenaSmartBridge.pm index 69acaec..004ee92 100644 --- a/FHEM/73_GardenaSmartBridge.pm +++ b/FHEM/73_GardenaSmartBridge.pm @@ -1,1497 +1,1497 @@ -############################################################################### -# -# Developed with Kate -# -# (c) 2017-2011 Copyright: Marko Oldenburg (fhemdevelopment at cooltux dot net) -# All rights reserved -# -# Special thanks goes to comitters: -# - Michael (mbrak) Thanks for Commandref -# - Matthias (Kenneth) Thanks for Wiki entry -# - BioS Thanks for predefined start points Code -# - fettgu Thanks for Debugging Irrigation Control data flow -# - Sebastian (BOFH) Thanks for new Auth Code after API Change -# -# -# 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 FHEM::GardenaSmartBridge; -use GPUtils qw(GP_Import GP_Export); - - -use strict; -use warnings; -use POSIX; -use FHEM::Meta; - -use HttpUtils; - -my $missingModul = ''; -eval "use Encode qw(encode encode_utf8 decode_utf8);1" - or $missingModul .= "Encode "; - -# eval "use JSON;1" || $missingModul .= 'JSON '; -eval "use IO::Socket::SSL;1" or $missingModul .= 'IO::Socket::SSL '; - -# try to use JSON::MaybeXS wrapper -# for chance of better performance + open code -eval { - require JSON::MaybeXS; - import JSON::MaybeXS qw( decode_json encode_json ); - 1; -}; - -if ($@) { - $@ = undef; - - # try to use JSON wrapper - # for chance of better performance - eval { - - # JSON preference order - local $ENV{PERL_JSON_BACKEND} = - 'Cpanel::JSON::XS,JSON::XS,JSON::PP,JSON::backportPP' - unless ( defined( $ENV{PERL_JSON_BACKEND} ) ); - - require JSON; - import JSON qw( decode_json encode_json ); - 1; - }; - - if ($@) { - $@ = undef; - - # In rare cases, Cpanel::JSON::XS may - # be installed but JSON|JSON::MaybeXS not ... - eval { - require Cpanel::JSON::XS; - import Cpanel::JSON::XS qw(decode_json encode_json); - 1; - }; - - if ($@) { - $@ = undef; - - # In rare cases, JSON::XS may - # be installed but JSON not ... - eval { - require JSON::XS; - import JSON::XS qw(decode_json encode_json); - 1; - }; - - if ($@) { - $@ = undef; - - # Fallback to built-in JSON which SHOULD - # be available since 5.014 ... - eval { - require JSON::PP; - import JSON::PP qw(decode_json encode_json); - 1; - }; - - if ($@) { - $@ = undef; - - # Fallback to JSON::backportPP in really rare cases - require JSON::backportPP; - import JSON::backportPP qw(decode_json encode_json); - 1; - } - } - } - } -} - -## Import der FHEM Funktionen -#-- Run before package compilation -BEGIN { - # Import from main context - GP_Import( - qw(readingsSingleUpdate - readingsBulkUpdate - readingsBulkUpdateIfChanged - readingsBeginUpdate - readingsEndUpdate - Log3 - devspec2array - asyncOutput - CommandAttr - AttrVal - InternalVal - ReadingsVal - CommandDefMod - modules - setKeyValue - getKeyValue - getUniqueId - RemoveInternalTimer - readingFnAttributes - InternalTimer - defs - init_done - IsDisabled - deviceEvents - HttpUtils_NonblockingGet - gettimeofday - Dispatch) - ); -} - -#-- Export to main context with different name -GP_Export( - qw( - Initialize - ) -); - -sub Initialize { - my $hash = shift; - - # Provider - $hash->{WriteFn} = \&Write; - $hash->{Clients} = ':GardenaSmartDevice:'; - $hash->{MatchList} = { '1:GardenaSmartDevice' => '^{"id":".*' }; - - # Consumer - $hash->{SetFn} = \&Set; - $hash->{GetFn} = \&Get; - $hash->{DefFn} = \&Define; - $hash->{UndefFn} = \&Undef; - $hash->{DeleteFn} = \&Delete; - $hash->{RenameFn} = \&Rename; - $hash->{NotifyFn} = \&Notify; - - $hash->{AttrFn} = \&Attr; - $hash->{AttrList} = - 'debugJSON:0,1 ' - . 'disable:1 ' - . 'interval ' - . 'disabledForIntervals ' - . 'gardenaAccountEmail ' - . 'gardenaBaseURL ' - . $readingFnAttributes; - $hash->{parseParams} = 1; - - return FHEM::Meta::InitMod( __FILE__, $hash ); -} - -sub Define { - my $hash = shift // return; - my $aArg = shift // return; - - return $@ unless ( FHEM::Meta::SetInternals($hash) ); - use version 0.60; our $VERSION = FHEM::Meta::Get( $hash, 'version' ); - - return 'too few parameters: define GardenaSmartBridge' - if ( scalar( @{$aArg} ) != 2 ); - return - 'Cannot define Gardena Bridge device. Perl modul ' - . ${missingModul} - . ' is missing.' - if ($missingModul); - - my $name = shift @$aArg; - $hash->{BRIDGE} = 1; - $hash->{URL} = - AttrVal( $name, 'gardenaBaseURL', - 'https://smart.gardena.com' ) - . '/v1'; - $hash->{VERSION} = version->parse($VERSION)->normal; - $hash->{INTERVAL} = 60; - $hash->{NOTIFYDEV} = "global,$name"; - - CommandAttr( undef, $name . ' room GardenaSmart' ) - if ( AttrVal( $name, 'room', 'none' ) eq 'none' ); - - readingsSingleUpdate( $hash, 'token', 'none', 1 ); - readingsSingleUpdate( $hash, 'state', 'initialized', 1 ); - - Log3 $name, 3, "GardenaSmartBridge ($name) - defined GardenaSmartBridge"; - - $modules{GardenaSmartBridge}{defptr}{BRIDGE} = $hash; - - return; -} - -sub Undef { - my $hash = shift; - my $name = shift; - - RemoveInternalTimer($hash); - delete $modules{GardenaSmartBridge}{defptr}{BRIDGE} - if ( defined( $modules{GardenaSmartBridge}{defptr}{BRIDGE} ) ); - - return; -} - -sub Delete { - my $hash = shift; - my $name = shift; - - setKeyValue( $hash->{TYPE} . '_' . $name . '_passwd', undef ); - return; -} - -sub Attr { - my ( $cmd, $name, $attrName, $attrVal ) = @_; - my $hash = $defs{$name}; - - if ( $attrName eq 'disable' ) { - if ( $cmd eq 'set' && $attrVal eq '1' ) { - RemoveInternalTimer($hash); - readingsSingleUpdate( $hash, 'state', 'inactive', 1 ); - Log3 $name, 3, "GardenaSmartBridge ($name) - disabled"; - } - elsif ( $cmd eq 'del' ) { - readingsSingleUpdate( $hash, 'state', 'active', 1 ); - Log3 $name, 3, "GardenaSmartBridge ($name) - enabled"; - } - } - elsif ( $attrName eq 'disabledForIntervals' ) { - if ( $cmd eq 'set' ) { - return -"check disabledForIntervals Syntax HH:MM-HH:MM or 'HH:MM-HH:MM HH:MM-HH:MM ...'" - if ( $attrVal !~ /^((\d{2}:\d{2})-(\d{2}:\d{2})\s?)+$/ ); - Log3 $name, 3, "GardenaSmartBridge ($name) - disabledForIntervals"; - } - elsif ( $cmd eq 'del' ) { - readingsSingleUpdate( $hash, 'state', 'active', 1 ); - Log3 $name, 3, "GardenaSmartBridge ($name) - enabled"; - } - } - elsif ( $attrName eq 'interval' ) { - if ( $cmd eq 'set' ) { - return 'Interval must be greater than 0' - if ( $attrVal == 0 ); - RemoveInternalTimer($hash); - $hash->{INTERVAL} = $attrVal; - Log3 $name, 3, - "GardenaSmartBridge ($name) - set interval: $attrVal"; - } - elsif ( $cmd eq 'del' ) { - RemoveInternalTimer($hash); - $hash->{INTERVAL} = 60; - Log3 $name, 3, -"GardenaSmartBridge ($name) - delete User interval and set default: 60"; - } - } - elsif ( $attrName eq 'gardenaBaseURL' ) { - if ( $cmd eq 'set' ) { - $hash->{URL} = $attrVal; - Log3 $name, 3, - "GardenaSmartBridge ($name) - set gardenaBaseURL to: $attrVal"; - } - elsif ( $cmd eq 'del' ) { - $hash->{URL} = 'https://smart.gardena.com/v1'; - } - } - - return; -} - -sub Notify { - my $hash = shift // return; - my $dev = shift // return; - - my $name = $hash->{NAME}; - return if ( IsDisabled($name) ); - - my $devname = $dev->{NAME}; - my $devtype = $dev->{TYPE}; - my $events = deviceEvents( $dev, 1 ); - return if ( !$events ); - - getToken($hash) - if ( - ( - $devtype eq 'Global' - && ( - grep /^INITIALIZED$/, - @{$events} or grep /^REREADCFG$/, - @{$events} or grep /^DEFINED.$name$/, - @{$events} or grep /^MODIFIED.$name$/, - @{$events} or grep /^ATTR.$name.gardenaAccountEmail.+/, - @{$events} - ) - ) - - || ( - $devtype eq 'GardenaSmartBridge' - && ( - grep /^gardenaAccountPassword.+/, - @{$events} - ) - ) - ); - - getDevices($hash) - if ( - $devtype eq 'Global' - && ( - grep /^DELETEATTR.$name.disable$/, - @{$events} or grep /^ATTR.$name.disable.0$/, - @{$events} or grep /^DELETEATTR.$name.interval$/, - @{$events} or grep /^ATTR.$name.interval.[0-9]+/, - @{$events} - ) - && $init_done - ); - - if ( - $devtype eq 'GardenaSmartBridge' - && ( - grep /^state:.Connected$/, - @{$events} or grep /^lastRequestState:.request_error$/, - @{$events} - ) - ) - { - - InternalTimer( gettimeofday() + $hash->{INTERVAL}, - "FHEM::GardenaSmartBridge::getDevices", $hash ); - Log3 $name, 4, -"GardenaSmartBridge ($name) - set internal timer function for recall getDevices sub"; - } - - return; -} -sub Get { - my $hash = shift // return; - my $aArg = shift // return; - - my $name = shift @$aArg // return; - my $cmd = shift @$aArg - // return qq{"get $name" needs at least one argument}; - - 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 { - my $list = ""; - $list .= " debug_devices_list:" - .join( ',', @{ $hash->{helper}{deviceList} }) - if ( AttrVal( $name, "debugJSON", "none") ne "none" - && exists($hash->{helper}{deviceList}) ); - return "Unknown argument $cmd,choose one of $list"; - } -} - -sub Set { - my $hash = shift // return; - my $aArg = shift // return; - - my $name = shift @$aArg // return; - 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!!! - - if ( lc $cmd eq 'getdevicesstate' ) { - getDevices($hash); - - } - elsif ( lc $cmd eq 'gettoken' ) { - return "please set Attribut gardenaAccountEmail first" - if ( AttrVal( $name, 'gardenaAccountEmail', 'none' ) eq 'none' ); - return "please set gardenaAccountPassword first" - if ( not defined( ReadPassword( $hash, $name ) ) ); - return "token is up to date" - if ( defined( $hash->{helper}{session_id} ) ); - - getToken($hash); - } - elsif ( lc $cmd eq 'gardenaaccountpassword' ) { - return "please set Attribut gardenaAccountEmail first" - if ( AttrVal( $name, 'gardenaAccountEmail', 'none' ) eq 'none' ); - return "usage: $cmd " if ( scalar( @{$aArg} ) != 1 ); - - StorePassword( $hash, $name, $aArg->[0] ); - } - elsif ( lc $cmd eq 'deleteaccountpassword' ) { - return "usage: $cmd" if ( scalar( @{$aArg} ) != 0 ); - - DeletePassword($hash); - } - else { - - my $list = "getDevicesState:noArg getToken:noArg" - if ( defined( ReadPassword( $hash, $name ) ) ); - $list .= " gardenaAccountPassword" - if ( not defined( ReadPassword( $hash, $name ) ) ); - $list .= " deleteAccountPassword:noArg" - if ( defined( ReadPassword( $hash, $name ) ) ); - return "Unknown argument $cmd, choose one of $list"; - } - - return; -} - -sub Write { - my ( $hash, $payload, $deviceId, $abilities, $service_id ) = @_; - my $name = $hash->{NAME}; - - my ( $session_id, $header, $uri, $method ); - - ( $payload, $session_id, $header, $uri, $method, $deviceId, $service_id ) = - createHttpValueStrings( $hash, $payload, $deviceId, $abilities, $service_id ); - - HttpUtils_NonblockingGet( - { - url => $hash->{URL} . $uri, - timeout => 15, - hash => $hash, - device_id => $deviceId, - data => $payload, - method => $method, - header => $header, - doTrigger => 1, - cl => $hash->{CL}, - callback => \&ErrorHandling - } - ); - - Log3( $name, 4, -"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"); - - return; -} - -sub ErrorHandling { - my $param = shift; - my $err = shift; - my $data = shift; - - my $hash = $param->{hash}; - my $name = $hash->{NAME}; - my $dhash = $hash; - - $dhash = $modules{GardenaSmartDevice}{defptr}{ $param->{'device_id'} } - if ( defined( $param->{'device_id'} ) ); - - my $dname = $dhash->{NAME}; - - Log3 $name, 4, "GardenaSmartBridge ($name) - Request: $data"; - - my $decode_json = eval { decode_json($data) }; - if ($@) { - Log3 $name, 3, "GardenaSmartBridge ($name) - JSON error while request"; - } - - if ( defined($err) ) { - if ( $err ne "" ) { - - readingsBeginUpdate($dhash); - readingsBulkUpdate( $dhash, "state", "$err" ) - if ( ReadingsVal( $dname, "state", 1 ) ne "initialized" ); - - readingsBulkUpdate( $dhash, "lastRequestState", "request_error", - 1 ); - - if ( $err =~ /timed out/ ) { - - Log3 $dname, 5, -"GardenaSmartBridge ($dname) - RequestERROR: connect to gardena cloud is timed out. check network"; - } - - elsif ($err =~ /Keine Route zum Zielrechner/ - || $err =~ /no route to target/ ) - { - - Log3 $dname, 5, -"GardenaSmartBridge ($dname) - RequestERROR: no route to target. bad network configuration or network is down"; - - } - else { - - Log3 $dname, 5, - "GardenaSmartBridge ($dname) - RequestERROR: $err"; - } - - readingsEndUpdate( $dhash, 1 ); - - Log3 $dname, 5, -"GardenaSmartBridge ($dname) - RequestERROR: GardenaSmartBridge RequestErrorHandling: error while requesting gardena cloud: $err"; - - delete $dhash->{helper}{deviceAction} - if ( defined( $dhash->{helper}{deviceAction} ) ); - - return; - } - } - - if ( $data eq "" && exists( $param->{code} ) && $param->{code} != 200 ) { - - readingsBeginUpdate($dhash); - readingsBulkUpdate( $dhash, "state", $param->{code}, 1 ) - if ( ReadingsVal( $dname, "state", 1 ) ne "initialized" ); - - readingsBulkUpdateIfChanged( $dhash, "lastRequestState", - "request_error", 1 ); - - if ( $param->{code} == 401 && $hash eq $dhash ) { - - if ( ReadingsVal( $dname, 'token', 'none' ) eq 'none' ) { - readingsBulkUpdate( $dhash, "state", "no token available", 1 ); - readingsBulkUpdateIfChanged( $dhash, "lastRequestState", - "no token available", 1 ); - } - - Log3 $dname, 5, - "GardenaSmartBridge ($dname) - RequestERROR: " . $param->{code}; - - } - elsif ($param->{code} == 204 - && $dhash ne $hash - && defined( $dhash->{helper}{deviceAction} ) ) - { - - readingsBulkUpdate( $dhash, "state", "the command is processed", - 1 ); - InternalTimer( - gettimeofday() + 5, - "FHEM::GardenaSmartBridge::getDevices", - $hash, 1 - ); - - } - elsif ( $param->{code} != 200 ) { - - Log3 $dname, 5, - "GardenaSmartBridge ($dname) - RequestERROR: " . $param->{code}; - } - - readingsEndUpdate( $dhash, 1 ); - - Log3 $dname, 5, - "GardenaSmartBridge ($dname) - RequestERROR: received http code " - . $param->{code} - . " without any data after requesting gardena cloud"; - - delete $dhash->{helper}{deviceAction} - if ( defined( $dhash->{helper}{deviceAction} ) ); - - return; - } - - if ( - $data =~ /Error/ - || ( defined($decode_json) - && ref($decode_json) eq 'HASH' - && defined( $decode_json->{errors} ) ) - ) - { - readingsBeginUpdate($dhash); - readingsBulkUpdate( $dhash, "state", $param->{code}, 1 ) - if ( ReadingsVal( $dname, "state", 0 ) ne "initialized" ); - - readingsBulkUpdate( $dhash, "lastRequestState", "request_error", 1 ); - - if ( $param->{code} == 400 ) { - if ($decode_json) { - if ( ref( $decode_json->{errors} ) eq "ARRAY" - && defined( $decode_json->{errors} ) ) - { - readingsBulkUpdate( - $dhash, - "state", - $decode_json->{errors}[0]{error} . ' ' - . $decode_json->{errors}[0]{attribute}, - 1 - ); - readingsBulkUpdate( - $dhash, - "lastRequestState", - $decode_json->{errors}[0]{error} . ' ' - . $decode_json->{errors}[0]{attribute}, - 1 - ); - Log3 $dname, 5, - "GardenaSmartBridge ($dname) - RequestERROR: " - . $decode_json->{errors}[0]{error} . " " - . $decode_json->{errors}[0]{attribute}; - } - } - else { - readingsBulkUpdate( $dhash, "lastRequestState", - "Error 400 Bad Request", 1 ); - Log3 $dname, 5, -"GardenaSmartBridge ($dname) - RequestERROR: Error 400 Bad Request"; - } - } - elsif ( $param->{code} == 503 ) { - - Log3 $dname, 5, -"GardenaSmartBridge ($dname) - RequestERROR: Error 503 Service Unavailable"; - readingsBulkUpdate( $dhash, "state", "Service Unavailable", 1 ); - readingsBulkUpdate( $dhash, "lastRequestState", - "Error 503 Service Unavailable", 1 ); - - } - elsif ( $param->{code} == 404 ) { - if ( defined( $dhash->{helper}{deviceAction} ) && $dhash ne $hash ) - { - readingsBulkUpdate( $dhash, "state", "device Id not found", 1 ); - readingsBulkUpdate( $dhash, "lastRequestState", - "device id not found", 1 ); - } - - Log3 $dname, 5, - "GardenaSmartBridge ($dname) - RequestERROR: Error 404 Not Found"; - - } - elsif ( $param->{code} == 500 ) { - - Log3 $dname, 5, - "GardenaSmartBridge ($dname) - RequestERROR: check the ???"; - - } - else { - - Log3 $dname, 5, - "GardenaSmartBridge ($dname) - RequestERROR: http error " - . $param->{code}; - } - - readingsEndUpdate( $dhash, 1 ); - - Log3 $dname, 5, - "GardenaSmartBridge ($dname) - RequestERROR: received http code " - . $param->{code} - . " receive Error after requesting gardena cloud"; - - delete $dhash->{helper}{deviceAction} - if ( defined( $dhash->{helper}{deviceAction} ) ); - - return; - } - if (defined($hash->{helper}{debug_device})){ - Log3 $name, 5, "GardenaSmartBridge DEBUG Device"; - 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"; - $output .= "name: $dev_settings->{name} "; - if (ref ($dev_settings->{value}) eq 'ARRAY' - || ref ($dev_settings->{value}) eq 'HASH'){ - $output .= 'N/A \n'; - } else { - $output .= "value: $dev_settings->{value} \n"; - } - } - $hash->{helper}{debug_device_output} = $output; - asyncOutput($param->{cl}, $hash->{helper}{debug_device_output}); - return; - } - readingsSingleUpdate( $hash, 'state', 'Connected', 1 ) - if ( defined( $hash->{helper}{locations_id} ) ); - ResponseProcessing( $hash, $data ) - if ( ref($decode_json) eq 'HASH' ); - - return; -} - -sub ResponseProcessing { - my $hash = shift; - my $json = shift; - - 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 ); - } - } - - # print Dumper $decode_json; - - 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}; - - Write( $hash, undef, undef, undef ); - Log3 $name, 3, "GardenaSmartBridge ($name) - fetch locations id"; - readingsSingleUpdate( $hash, 'token', $hash->{helper}{session_id}, 1 ); - - return; - - } - elsif ( !defined( $hash->{helper}{locations_id} ) - && defined( $decode_json->{locations} ) - && ref( $decode_json->{locations} ) eq 'ARRAY' - && scalar( @{ $decode_json->{locations} } ) > 0 ) - { - for my $location ( @{ $decode_json->{locations} } ) { - - $hash->{helper}{locations_id} = $location->{id}; - - WriteReadings( $hash, $location ); - } - - Log3 $name, 3, - "GardenaSmartBridge ($name) - processed locations id. ID is " - . $hash->{helper}{locations_id}; - Write( $hash, undef, undef, undef ); - - return; - } - elsif (defined( $decode_json->{devices} ) - && ref( $decode_json->{devices} ) eq 'ARRAY' - && scalar( @{ $decode_json->{devices} } ) > 0 ) - { - - my @buffer = split( '"devices":\[', $json ); - - my ( $json, $tail ) = 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; - - if ( defined($tail) and $tail ) { - $decode_json = eval { decode_json($json) }; - if ($@) { - Log3 $name, 5, -"GardenaSmartBridge ($name) - JSON error while request: $@"; - } - - Dispatch( $hash, $json, undef ) - if ( $decode_json->{category} ne 'gateway' ); - WriteReadings( $hash, $decode_json ) - if ( defined( $decode_json->{category} ) - && $decode_json->{category} eq 'gateway' ); - } - - ( $json, $tail ) = 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"; - - return; -} - -sub WriteReadings { - my $hash = shift; - my $decode_json = shift; - - # print Dumper $decode_json; - - my $name = $hash->{NAME}; - - if ( defined( $decode_json->{id} ) - && $decode_json->{id} - && defined( $decode_json->{name} ) - && $decode_json->{name} ) - { - readingsBeginUpdate($hash); - if ( $decode_json->{id} eq $hash->{helper}{locations_id} ) { - - 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 ); - } - } - elsif ($decode_json->{id} ne $hash->{helper}{locations_id} - && ref( $decode_json->{abilities} ) eq 'ARRAY' - && ref( $decode_json->{abilities}[0]{properties} ) eq 'ARRAY' ) - { - my $properties = - scalar( @{ $decode_json->{abilities}[0]{properties} } ); - - do { - while ( ( my ( $t, $v ) ) = - each - %{ $decode_json->{abilities}[0]{properties}[$properties] } ) - { - next - if ( ref($v) eq 'ARRAY' ); - - #$v = encode_utf8($v); - readingsBulkUpdateIfChanged( - $hash, - $decode_json->{abilities}[0]{properties}[$properties] - {name} . '-' . $t, - $v - ) - if ($decode_json->{abilities}[0]{properties}[$properties]{name} !~ /ethernet_status|wifi_status/ ); - if ( - ( - $decode_json->{abilities}[0]{properties} - [$properties]{name} eq 'ethernet_status' - || $decode_json->{abilities}[0]{properties} - [$properties]{name} eq 'wifi_status' - ) - && ref($v) eq 'HASH' - ) - { - if ( $decode_json->{abilities}[0]{properties} - [$properties]{name} eq 'ethernet_status' ) - { - readingsBulkUpdateIfChanged( $hash, - 'ethernet_status-mac', $v->{mac} ); - readingsBulkUpdateIfChanged( $hash, - 'ethernet_status-ip', $v->{ip} ) - if ( ref( $v->{ip} ) ne 'HASH' ); - readingsBulkUpdateIfChanged( $hash, - 'ethernet_status-isconnected', - $v->{isConnected} ); - } - elsif ( $decode_json->{abilities}[0]{properties} - [$properties]{name} eq 'wifi_status' ) - { - #TODO: read valies if bridge connected to wifi - readingsBulkUpdateIfChanged( $hash, - 'wifi_status-ssid', $v->{ssid} ) - if (ref($v->{ssid}) ne 'HASH'); - readingsBulkUpdateIfChanged( $hash, - 'wifi_status-mac', $v->{mac} ); - readingsBulkUpdateIfChanged( $hash, - 'wifi_status-ip', $v->{ip} ) - if ( ref( $v->{ip} ) ne 'HASH' ); - readingsBulkUpdateIfChanged( $hash, - 'wifi_status-isconnected', $v->{isConnected} ); - readingsBulkUpdateIfChanged( $hash, - 'wifi_status-signal', $v->{signal} ); - } - } - } - $properties--; - - } while ( $properties >= 0 ); - } - readingsEndUpdate( $hash, 1 ); - } - - Log3 $name, 4, "GardenaSmartBridge ($name) - readings would be written"; - - return; -} - -#################################### -#################################### -#### my little helpers Sub's ####### - -sub getDevices { - my $hash = shift; - - my $name = $hash->{NAME}; - - RemoveInternalTimer($hash); - - if ( not IsDisabled($name) ) { - - delete $hash->{helper}{deviceList}; - my @list; - @list = devspec2array('TYPE=GardenaSmartDevice'); - for my $gardenaDev (@list){ - push( @{ $hash->{helper}{deviceList} }, $gardenaDev ); - } - Write( $hash, undef, undef, undef ); - Log3 $name, 4, - "GardenaSmartBridge ($name) - fetch device list and device states"; - } - else { - - readingsSingleUpdate( $hash, 'state', 'disabled', 1 ); - Log3 $name, 3, "GardenaSmartBridge ($name) - device is disabled"; - } - - return; -} - -sub getToken { - my $hash = shift; - - my $name = $hash->{NAME}; - - return readingsSingleUpdate( $hash, 'state', - 'please set Attribut gardenaAccountEmail first', 1 ) - if ( AttrVal( $name, 'gardenaAccountEmail', 'none' ) eq 'none' ); - return readingsSingleUpdate( $hash, 'state', - 'please set gardena account password first', 1 ) - if ( !defined( ReadPassword( $hash, $name ) ) ); - readingsSingleUpdate( $hash, 'state', 'get token', 1 ); - - delete $hash->{helper}{session_id} - if ( defined( $hash->{helper}{session_id} ) - && $hash->{helper}{session_id} ); - delete $hash->{helper}{user_id} - if ( defined( $hash->{helper}{user_id} ) && $hash->{helper}{user_id} ); - delete $hash->{helper}{locations_id} - if ( defined( $hash->{helper}{locations_id} ) - && $hash->{helper}{locations_id} ); - - # Write( - # $hash, - # '"sessions": {"email": "' - # . AttrVal( $name, 'gardenaAccountEmail', 'none' ) - # . '","password": "' - # . ReadPassword( $hash, $name ) . '"}', - # undef, - # undef - # ); - - Write( - $hash, - '"data": {"type":"token", "attributes":{"username": "' - . AttrVal( $name, 'gardenaAccountEmail', 'none' ) - . '","password": "' - . 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, 3, -"GardenaSmartBridge ($name) - send credentials to fetch Token and locationId"; - - return; -} - -sub StorePassword { - my $hash = shift; - my $name = shift; - my $password = shift; - - my $index = $hash->{TYPE} . "_" . $name . "_passwd"; - my $key = getUniqueId() . $index; - my $enc_pwd = ""; - - if ( eval "use Digest::MD5;1" ) { - - $key = Digest::MD5::md5_hex( unpack "H*", $key ); - $key .= Digest::MD5::md5_hex($key); - } - - for my $char ( split //, $password ) { - - my $encode = chop($key); - $enc_pwd .= sprintf( "%.2x", ord($char) ^ ord($encode) ); - $key = $encode . $key; - } - - my $err = setKeyValue( $index, $enc_pwd ); - return "error while saving the password - $err" if ( defined($err) ); - - return "password successfully saved"; -} - -sub ReadPassword { - my $hash = shift; - my $name = shift; - - my $index = $hash->{TYPE} . "_" . $name . "_passwd"; - my $key = getUniqueId() . $index; - my ( $password, $err ); - - Log3 $name, 4, "GardenaSmartBridge ($name) - Read password from file"; - - ( $err, $password ) = getKeyValue($index); - - if ( defined($err) ) { - - Log3 $name, 3, -"GardenaSmartBridge ($name) - unable to read password from file: $err"; - return undef; - - } - - if ( defined($password) ) { - if ( eval "use Digest::MD5;1" ) { - - $key = Digest::MD5::md5_hex( unpack "H*", $key ); - $key .= Digest::MD5::md5_hex($key); - } - - my $dec_pwd = ''; - - for my $char ( map { pack( 'C', hex($_) ) } ( $password =~ /(..)/g ) ) { - - my $decode = chop($key); - $dec_pwd .= chr( ord($char) ^ ord($decode) ); - $key = $decode . $key; - } - - return $dec_pwd; - - } - else { - - Log3 $name, 3, "GardenaSmartBridge ($name) - No password in file"; - return undef; - } - - return; -} - -sub Rename { - my $new = shift; - my $old = shift; - - my $hash = $defs{$new}; - - StorePassword( $hash, $new, ReadPassword( $hash, $old ) ); - setKeyValue( $hash->{TYPE} . "_" . $old . "_passwd", undef ); - - 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 ) = @_; - - my $session_id = $hash->{helper}{session_id}; - my $header = "Content-Type: application/json"; - my $uri = ''; - my $method = 'POST'; - $header .= "\r\nAuthorization: Bearer $session_id" - if ( defined($hash->{helper}{session_id}) ); - $header .= "\r\nAuthorization-Provider: husqvarna" - if ( defined($hash->{helper}{session_id}) ); - - # $header .= "\r\nx-api-key: $session_id" - # if ( defined( $hash->{helper}{session_id} ) ); - $payload = '{' . $payload . '}' if ( defined($payload) ); - $payload = '{}' if ( !defined($payload) ); - - if ( $payload eq '{}' ) { - $method = 'GET'; - $payload = ''; - $uri .= '/locations?locatioId=null&user_id=' . $hash->{helper}{user_id} - if ( exists( $hash->{helper}{user_id} ) - && !defined( $hash->{helper}{locations_id} ) ); - readingsSingleUpdate( $hash, 'state', 'fetch locationId', 1 ) - if ( !defined( $hash->{helper}{locations_id} ) ); - $uri .= '/auth/token' if ( !defined( $hash->{helper}{session_id} ) ); - $uri .= '/devices' - if (!defined($abilities) - && defined( $hash->{helper}{locations_id} ) ); - } - - $uri = '/devices/'.InternalVal($hash->{helper}{debug_device}, 'DEVICEID', 0 ) if ( exists ($hash->{helper}{debug_device})); - $uri = '/auth/token' if ( !defined( $hash->{helper}{session_id} ) ); - - if ( defined( $hash->{helper}{locations_id} ) ) { - if ( defined($abilities) && $abilities =~ /.*_settings/ ) { - - $method = 'PUT'; - my $dhash = $modules{GardenaSmartDevice}{defptr}{$deviceId}; - - $uri .= - '/devices/' - . $deviceId - . '/settings/' - . $service_id - if ( defined($abilities) - && defined($payload) - && $abilities =~ /.*_settings/ ); - - } # park until next schedules or override - elsif (defined($abilities) - && defined($payload) - && $abilities eq 'mower_timer' ) - { - my $valve_id; - $method = 'PUT'; - - $uri .= - '/devices/' - . $deviceId - . '/abilities/' - . $abilities - . '/properties/mower_timer'; - - } - elsif (defined($abilities) - && defined($payload) - && $abilities eq 'watering' ) - { - my $valve_id; - $method = 'PUT'; - - if ( $payload =~ m#watering_timer_(\d)# ) { - $valve_id = $1; - } - $uri .= - '/devices/' - . $deviceId - . '/abilities/' - . $abilities - . '/properties/watering_timer_' - . $valve_id; - - } - elsif (defined($abilities) - && defined($payload) - && $abilities eq 'manual_watering' ) - { - my $valve_id; - $method = 'PUT'; - - $uri .= - '/devices/' - . $deviceId - . '/abilities/' - . $abilities - . '/properties/manual_watering_timer'; - - } - elsif (defined($abilities) - && defined($payload) - && $abilities eq 'power' ) - { - my $valve_id; - $method = 'PUT'; - - $uri .= - '/devices/' - . $deviceId - . '/abilities/' - . $abilities - . '/properties/power_timer'; - - } - else { - $uri .= - '/devices/' . $deviceId . '/abilities/' . $abilities . '/command' - if ( defined($abilities) && defined($payload) ); - } - - $uri .= '?locationId=' . $hash->{helper}{locations_id}; - } - - return ( $payload, $session_id, $header, $uri, $method, $deviceId, - $abilities ); -} - -sub DeletePassword { - my $hash = shift; - - setKeyValue( $hash->{TYPE} . "_" . $hash->{NAME} . "_passwd", undef ); - - return; -} - -1; - -=pod - -=item device -=item summary Modul to communicate with the GardenaCloud -=item summary_DE Modul zur Datenübertragung zur GardenaCloud - -=begin html - - -

GardenaSmartBridge

-
    - Prerequisite -

    -
  • In combination with GardenaSmartDevice this FHEM Module controls the communication between the GardenaCloud and connected Devices like Mover, Watering_Computer, Temperature_Sensors
  • -
  • Installation of the following packages: apt-get install libio-socket-ssl-perl
  • -
  • The Gardena-Gateway and all connected Devices must be correctly installed in the GardenaAPP
  • -
-
- -Define -

    - define <name> GardenaSmartBridge -

    - Beispiel: -

      - define Gardena_Bridge GardenaSmartBridge
      -
    -
    - The GardenaSmartBridge device is created in the room GardenaSmart, then the devices of Your system are recognized automatically and created in FHEM. From now on the devices can be controlled and changes in the GardenaAPP are synchronized with the state and readings of the devices. -

    - -

    - Readings -
      -
    • address - your Adress (Longversion)
    • -
    • authorized_user_ids -
    • -
    • city - Zip, City
    • -
    • devices - Number of Devices in the Cloud (Gateway included)
    • -
    • lastRequestState - Last Status Result
    • -
    • latitude - Breitengrad des Grundstücks
    • -
    • longitude - Längengrad des Grundstücks
    • -
    • name - Name of your Garden – Default „My Garden“
    • -
    • state - State of the Bridge
    • -
    • token - SessionID
    • -
    -

    - - set -
      -
    • getDeviceState - Starts a Datarequest
    • -
    • getToken - Gets a new Session-ID
    • -
    • gardenaAccountPassword - Passwort which was used in the GardenaAPP
    • -
    • deleteAccountPassword - delete the password from store
    • -
    -

    - - Attributes -
      -
    • debugJSON -
    • -
    • disable - Disables the Bridge
    • -
    • interval - Interval in seconds (Default=60)
    • -
    • gardenaAccountEmail - Email Adresse which was used in the GardenaAPP
    • -
    -
- -=end html -=begin html_DE - - -

GardenaSmartBridge

-
    - Voraussetzungen -

    -
  • Zusammen mit dem Device GardenaSmartDevice stellt dieses FHEM Modul die Kommunikation zwischen der GardenaCloud und Fhem her. Es können damit Rasenmäher, Bewässerungscomputer und Bodensensoren überwacht und gesteuert werden
  • -
  • Das Perl-Modul "SSL Packet" wird benötigt.
  • -
  • Unter Debian (basierten) System, kann dies mittels "apt-get install libio-socket-ssl-perl" installiert werden.
  • -
  • Das Gardena-Gateway und alle damit verbundenen Geräte und Sensoren müssen vorab in der GardenaApp eingerichtet sein.
  • -
-
- -Define -

    - define <name> GardenaSmartBridge -

    - Beispiel: -

      - define Gardena_Bridge GardenaSmartBridge
      -
    -
    - Das Bridge Device wird im Raum GardenaSmart angelegt und danach erfolgt das Einlesen und automatische Anlegen der Geräte. Von nun an können die eingebundenen Geräte gesteuert werden. Änderungen in der APP werden mit den Readings und dem Status syncronisiert. -

    - -

    - Readings -
      -
    • address - Adresse, welche in der App eingetragen wurde (Langversion)
    • -
    • authorized_user_ids -
    • -
    • city - PLZ, Stadt
    • -
    • devices - Anzahl der Geräte, welche in der GardenaCloud angemeldet sind (Gateway zählt mit)
    • -
    • lastRequestState - Letzter abgefragter Status der Bridge
    • -
    • latitude - Breitengrad des Grundstücks
    • -
    • longitude - Längengrad des Grundstücks
    • -
    • name - Name für das Grundstück – Default „My Garden“
    • -
    • state - Status der Bridge
    • -
    • token - SessionID
    • -
    -

    - - set -
      -
    • getDeviceState - Startet eine Abfrage der Daten.
    • -
    • getToken - Holt eine neue Session-ID
    • -
    • gardenaAccountPassword - Passwort, welches in der GardenaApp verwendet wurde
    • -
    • deleteAccountPassword - l&oml;scht das Passwort aus dem Passwortstore
    • -
    -

    - - Attribute -
      -
    • debugJSON - JSON Fehlermeldungen
    • -
    • disable - Schaltet die Datenübertragung der Bridge ab
    • -
    • interval - Abfrageinterval in Sekunden (default: 60)
    • -
    • gardenaAccountEmail - Email Adresse, die auch in der GardenaApp verwendet wurde
    • -
    -
- -=end html_DE - -=for :application/json;q=META.json 73_GardenaSmartBridge.pm -{ - "abstract": "Modul to communicate with the GardenaCloud", - "x_lang": { - "de": { - "abstract": "Modul zur Datenübertragung zur GardenaCloud" - } - }, - "keywords": [ - "fhem-mod-device", - "fhem-core", - "Garden", - "Gardena", - "Smart" - ], - "release_status": "stable", - "license": "GPL_2", - "version": "v2.2.2", - "author": [ - "Marko Oldenburg " - ], - "x_fhem_maintainer": [ - "CoolTux" - ], - "x_fhem_maintainer_github": [ - "LeonGaultier" - ], - "prereqs": { - "runtime": { - "requires": { - "FHEM": 5.00918799, - "perl": 5.016, - "Meta": 0, - "IO::Socket::SSL": 0, - "JSON": 0, - "HttpUtils": 0, - "Encode": 0 - }, - "recommends": { - }, - "suggests": { - } - } - } -} -=end :application/json;q=META.json - -=cut +############################################################################### +# +# Developed with Kate +# +# (c) 2017-2011 Copyright: Marko Oldenburg (fhemdevelopment at cooltux dot net) +# All rights reserved +# +# Special thanks goes to comitters: +# - Michael (mbrak) Thanks for Commandref +# - Matthias (Kenneth) Thanks for Wiki entry +# - BioS Thanks for predefined start points Code +# - fettgu Thanks for Debugging Irrigation Control data flow +# - Sebastian (BOFH) Thanks for new Auth Code after API Change +# +# +# 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 FHEM::GardenaSmartBridge; +use GPUtils qw(GP_Import GP_Export); + + +use strict; +use warnings; +use POSIX; +use FHEM::Meta; + +use HttpUtils; + +my $missingModul = ''; +eval "use Encode qw(encode encode_utf8 decode_utf8);1" + or $missingModul .= "Encode "; + +# eval "use JSON;1" || $missingModul .= 'JSON '; +eval "use IO::Socket::SSL;1" or $missingModul .= 'IO::Socket::SSL '; + +# try to use JSON::MaybeXS wrapper +# for chance of better performance + open code +eval { + require JSON::MaybeXS; + import JSON::MaybeXS qw( decode_json encode_json ); + 1; +}; + +if ($@) { + $@ = undef; + + # try to use JSON wrapper + # for chance of better performance + eval { + + # JSON preference order + local $ENV{PERL_JSON_BACKEND} = + 'Cpanel::JSON::XS,JSON::XS,JSON::PP,JSON::backportPP' + unless ( defined( $ENV{PERL_JSON_BACKEND} ) ); + + require JSON; + import JSON qw( decode_json encode_json ); + 1; + }; + + if ($@) { + $@ = undef; + + # In rare cases, Cpanel::JSON::XS may + # be installed but JSON|JSON::MaybeXS not ... + eval { + require Cpanel::JSON::XS; + import Cpanel::JSON::XS qw(decode_json encode_json); + 1; + }; + + if ($@) { + $@ = undef; + + # In rare cases, JSON::XS may + # be installed but JSON not ... + eval { + require JSON::XS; + import JSON::XS qw(decode_json encode_json); + 1; + }; + + if ($@) { + $@ = undef; + + # Fallback to built-in JSON which SHOULD + # be available since 5.014 ... + eval { + require JSON::PP; + import JSON::PP qw(decode_json encode_json); + 1; + }; + + if ($@) { + $@ = undef; + + # Fallback to JSON::backportPP in really rare cases + require JSON::backportPP; + import JSON::backportPP qw(decode_json encode_json); + 1; + } + } + } + } +} + +## Import der FHEM Funktionen +#-- Run before package compilation +BEGIN { + # Import from main context + GP_Import( + qw(readingsSingleUpdate + readingsBulkUpdate + readingsBulkUpdateIfChanged + readingsBeginUpdate + readingsEndUpdate + Log3 + devspec2array + asyncOutput + CommandAttr + AttrVal + InternalVal + ReadingsVal + CommandDefMod + modules + setKeyValue + getKeyValue + getUniqueId + RemoveInternalTimer + readingFnAttributes + InternalTimer + defs + init_done + IsDisabled + deviceEvents + HttpUtils_NonblockingGet + gettimeofday + Dispatch) + ); +} + +#-- Export to main context with different name +GP_Export( + qw( + Initialize + ) +); + +sub Initialize { + my $hash = shift; + + # Provider + $hash->{WriteFn} = \&Write; + $hash->{Clients} = ':GardenaSmartDevice:'; + $hash->{MatchList} = { '1:GardenaSmartDevice' => '^{"id":".*' }; + + # Consumer + $hash->{SetFn} = \&Set; + $hash->{GetFn} = \&Get; + $hash->{DefFn} = \&Define; + $hash->{UndefFn} = \&Undef; + $hash->{DeleteFn} = \&Delete; + $hash->{RenameFn} = \&Rename; + $hash->{NotifyFn} = \&Notify; + + $hash->{AttrFn} = \&Attr; + $hash->{AttrList} = + 'debugJSON:0,1 ' + . 'disable:1 ' + . 'interval ' + . 'disabledForIntervals ' + . 'gardenaAccountEmail ' + . 'gardenaBaseURL ' + . $readingFnAttributes; + $hash->{parseParams} = 1; + + return FHEM::Meta::InitMod( __FILE__, $hash ); +} + +sub Define { + my $hash = shift // return; + my $aArg = shift // return; + + return $@ unless ( FHEM::Meta::SetInternals($hash) ); + use version 0.60; our $VERSION = FHEM::Meta::Get( $hash, 'version' ); + + return 'too few parameters: define GardenaSmartBridge' + if ( scalar( @{$aArg} ) != 2 ); + return + 'Cannot define Gardena Bridge device. Perl modul ' + . ${missingModul} + . ' is missing.' + if ($missingModul); + + my $name = shift @$aArg; + $hash->{BRIDGE} = 1; + $hash->{URL} = + AttrVal( $name, 'gardenaBaseURL', + 'https://smart.gardena.com' ) + . '/v1'; + $hash->{VERSION} = version->parse($VERSION)->normal; + $hash->{INTERVAL} = 60; + $hash->{NOTIFYDEV} = "global,$name"; + + CommandAttr( undef, $name . ' room GardenaSmart' ) + if ( AttrVal( $name, 'room', 'none' ) eq 'none' ); + + readingsSingleUpdate( $hash, 'token', 'none', 1 ); + readingsSingleUpdate( $hash, 'state', 'initialized', 1 ); + + Log3 $name, 3, "GardenaSmartBridge ($name) - defined GardenaSmartBridge"; + + $modules{GardenaSmartBridge}{defptr}{BRIDGE} = $hash; + + return; +} + +sub Undef { + my $hash = shift; + my $name = shift; + + RemoveInternalTimer($hash); + delete $modules{GardenaSmartBridge}{defptr}{BRIDGE} + if ( defined( $modules{GardenaSmartBridge}{defptr}{BRIDGE} ) ); + + return; +} + +sub Delete { + my $hash = shift; + my $name = shift; + + setKeyValue( $hash->{TYPE} . '_' . $name . '_passwd', undef ); + return; +} + +sub Attr { + my ( $cmd, $name, $attrName, $attrVal ) = @_; + my $hash = $defs{$name}; + + if ( $attrName eq 'disable' ) { + if ( $cmd eq 'set' && $attrVal eq '1' ) { + RemoveInternalTimer($hash); + readingsSingleUpdate( $hash, 'state', 'inactive', 1 ); + Log3 $name, 3, "GardenaSmartBridge ($name) - disabled"; + } + elsif ( $cmd eq 'del' ) { + readingsSingleUpdate( $hash, 'state', 'active', 1 ); + Log3 $name, 3, "GardenaSmartBridge ($name) - enabled"; + } + } + elsif ( $attrName eq 'disabledForIntervals' ) { + if ( $cmd eq 'set' ) { + return +"check disabledForIntervals Syntax HH:MM-HH:MM or 'HH:MM-HH:MM HH:MM-HH:MM ...'" + if ( $attrVal !~ /^((\d{2}:\d{2})-(\d{2}:\d{2})\s?)+$/ ); + Log3 $name, 3, "GardenaSmartBridge ($name) - disabledForIntervals"; + } + elsif ( $cmd eq 'del' ) { + readingsSingleUpdate( $hash, 'state', 'active', 1 ); + Log3 $name, 3, "GardenaSmartBridge ($name) - enabled"; + } + } + elsif ( $attrName eq 'interval' ) { + if ( $cmd eq 'set' ) { + return 'Interval must be greater than 0' + if ( $attrVal == 0 ); + RemoveInternalTimer($hash); + $hash->{INTERVAL} = $attrVal; + Log3 $name, 3, + "GardenaSmartBridge ($name) - set interval: $attrVal"; + } + elsif ( $cmd eq 'del' ) { + RemoveInternalTimer($hash); + $hash->{INTERVAL} = 60; + Log3 $name, 3, +"GardenaSmartBridge ($name) - delete User interval and set default: 60"; + } + } + elsif ( $attrName eq 'gardenaBaseURL' ) { + if ( $cmd eq 'set' ) { + $hash->{URL} = $attrVal; + Log3 $name, 3, + "GardenaSmartBridge ($name) - set gardenaBaseURL to: $attrVal"; + } + elsif ( $cmd eq 'del' ) { + $hash->{URL} = 'https://smart.gardena.com/v1'; + } + } + + return; +} + +sub Notify { + my $hash = shift // return; + my $dev = shift // return; + + my $name = $hash->{NAME}; + return if ( IsDisabled($name) ); + + my $devname = $dev->{NAME}; + my $devtype = $dev->{TYPE}; + my $events = deviceEvents( $dev, 1 ); + return if ( !$events ); + + getToken($hash) + if ( + ( + $devtype eq 'Global' + && ( + grep /^INITIALIZED$/, + @{$events} or grep /^REREADCFG$/, + @{$events} or grep /^DEFINED.$name$/, + @{$events} or grep /^MODIFIED.$name$/, + @{$events} or grep /^ATTR.$name.gardenaAccountEmail.+/, + @{$events} + ) + ) + + || ( + $devtype eq 'GardenaSmartBridge' + && ( + grep /^gardenaAccountPassword.+/, + @{$events} + ) + ) + ); + + getDevices($hash) + if ( + $devtype eq 'Global' + && ( + grep /^DELETEATTR.$name.disable$/, + @{$events} or grep /^ATTR.$name.disable.0$/, + @{$events} or grep /^DELETEATTR.$name.interval$/, + @{$events} or grep /^ATTR.$name.interval.[0-9]+/, + @{$events} + ) + && $init_done + ); + + if ( + $devtype eq 'GardenaSmartBridge' + && ( + grep /^state:.Connected$/, + @{$events} or grep /^lastRequestState:.request_error$/, + @{$events} + ) + ) + { + + InternalTimer( gettimeofday() + $hash->{INTERVAL}, + "FHEM::GardenaSmartBridge::getDevices", $hash ); + Log3 $name, 4, +"GardenaSmartBridge ($name) - set internal timer function for recall getDevices sub"; + } + + return; +} +sub Get { + my $hash = shift // return; + my $aArg = shift // return; + + my $name = shift @$aArg // return; + my $cmd = shift @$aArg + // return qq{"get $name" needs at least one argument}; + + 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 { + my $list = ""; + $list .= " debug_devices_list:" + .join( ',', @{ $hash->{helper}{deviceList} }) + if ( AttrVal( $name, "debugJSON", "none") ne "none" + && exists($hash->{helper}{deviceList}) ); + return "Unknown argument $cmd,choose one of $list"; + } +} + +sub Set { + my $hash = shift // return; + my $aArg = shift // return; + + my $name = shift @$aArg // return; + 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!!! + + if ( lc $cmd eq 'getdevicesstate' ) { + getDevices($hash); + + } + elsif ( lc $cmd eq 'gettoken' ) { + return "please set Attribut gardenaAccountEmail first" + if ( AttrVal( $name, 'gardenaAccountEmail', 'none' ) eq 'none' ); + return "please set gardenaAccountPassword first" + if ( not defined( ReadPassword( $hash, $name ) ) ); + return "token is up to date" + if ( defined( $hash->{helper}{session_id} ) ); + + getToken($hash); + } + elsif ( lc $cmd eq 'gardenaaccountpassword' ) { + return "please set Attribut gardenaAccountEmail first" + if ( AttrVal( $name, 'gardenaAccountEmail', 'none' ) eq 'none' ); + return "usage: $cmd " if ( scalar( @{$aArg} ) != 1 ); + + StorePassword( $hash, $name, $aArg->[0] ); + } + elsif ( lc $cmd eq 'deleteaccountpassword' ) { + return "usage: $cmd" if ( scalar( @{$aArg} ) != 0 ); + + DeletePassword($hash); + } + else { + + my $list = "getDevicesState:noArg getToken:noArg" + if ( defined( ReadPassword( $hash, $name ) ) ); + $list .= " gardenaAccountPassword" + if ( not defined( ReadPassword( $hash, $name ) ) ); + $list .= " deleteAccountPassword:noArg" + if ( defined( ReadPassword( $hash, $name ) ) ); + return "Unknown argument $cmd, choose one of $list"; + } + + return; +} + +sub Write { + my ( $hash, $payload, $deviceId, $abilities, $service_id ) = @_; + my $name = $hash->{NAME}; + + my ( $session_id, $header, $uri, $method ); + + ( $payload, $session_id, $header, $uri, $method, $deviceId, $service_id ) = + createHttpValueStrings( $hash, $payload, $deviceId, $abilities, $service_id ); + + HttpUtils_NonblockingGet( + { + url => $hash->{URL} . $uri, + timeout => 15, + hash => $hash, + device_id => $deviceId, + data => $payload, + method => $method, + header => $header, + doTrigger => 1, + cl => $hash->{CL}, + callback => \&ErrorHandling + } + ); + + Log3( $name, 4, +"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"); + + return; +} + +sub ErrorHandling { + my $param = shift; + my $err = shift; + my $data = shift; + + my $hash = $param->{hash}; + my $name = $hash->{NAME}; + my $dhash = $hash; + + $dhash = $modules{GardenaSmartDevice}{defptr}{ $param->{'device_id'} } + if ( defined( $param->{'device_id'} ) ); + + my $dname = $dhash->{NAME}; + + Log3 $name, 4, "GardenaSmartBridge ($name) - Request: $data"; + + my $decode_json = eval { decode_json($data) }; + if ($@) { + Log3 $name, 3, "GardenaSmartBridge ($name) - JSON error while request"; + } + + if ( defined($err) ) { + if ( $err ne "" ) { + + readingsBeginUpdate($dhash); + readingsBulkUpdate( $dhash, "state", "$err" ) + if ( ReadingsVal( $dname, "state", 1 ) ne "initialized" ); + + readingsBulkUpdate( $dhash, "lastRequestState", "request_error", + 1 ); + + if ( $err =~ /timed out/ ) { + + Log3 $dname, 5, +"GardenaSmartBridge ($dname) - RequestERROR: connect to gardena cloud is timed out. check network"; + } + + elsif ($err =~ /Keine Route zum Zielrechner/ + || $err =~ /no route to target/ ) + { + + Log3 $dname, 5, +"GardenaSmartBridge ($dname) - RequestERROR: no route to target. bad network configuration or network is down"; + + } + else { + + Log3 $dname, 5, + "GardenaSmartBridge ($dname) - RequestERROR: $err"; + } + + readingsEndUpdate( $dhash, 1 ); + + Log3 $dname, 5, +"GardenaSmartBridge ($dname) - RequestERROR: GardenaSmartBridge RequestErrorHandling: error while requesting gardena cloud: $err"; + + delete $dhash->{helper}{deviceAction} + if ( defined( $dhash->{helper}{deviceAction} ) ); + + return; + } + } + + if ( $data eq "" && exists( $param->{code} ) && $param->{code} != 200 ) { + + readingsBeginUpdate($dhash); + readingsBulkUpdate( $dhash, "state", $param->{code}, 1 ) + if ( ReadingsVal( $dname, "state", 1 ) ne "initialized" ); + + readingsBulkUpdateIfChanged( $dhash, "lastRequestState", + "request_error", 1 ); + + if ( $param->{code} == 401 && $hash eq $dhash ) { + + if ( ReadingsVal( $dname, 'token', 'none' ) eq 'none' ) { + readingsBulkUpdate( $dhash, "state", "no token available", 1 ); + readingsBulkUpdateIfChanged( $dhash, "lastRequestState", + "no token available", 1 ); + } + + Log3 $dname, 5, + "GardenaSmartBridge ($dname) - RequestERROR: " . $param->{code}; + + } + elsif ($param->{code} == 204 + && $dhash ne $hash + && defined( $dhash->{helper}{deviceAction} ) ) + { + + readingsBulkUpdate( $dhash, "state", "the command is processed", + 1 ); + InternalTimer( + gettimeofday() + 5, + "FHEM::GardenaSmartBridge::getDevices", + $hash, 1 + ); + + } + elsif ( $param->{code} != 200 ) { + + Log3 $dname, 5, + "GardenaSmartBridge ($dname) - RequestERROR: " . $param->{code}; + } + + readingsEndUpdate( $dhash, 1 ); + + Log3 $dname, 5, + "GardenaSmartBridge ($dname) - RequestERROR: received http code " + . $param->{code} + . " without any data after requesting gardena cloud"; + + delete $dhash->{helper}{deviceAction} + if ( defined( $dhash->{helper}{deviceAction} ) ); + + return; + } + + if ( + $data =~ /Error/ + || ( defined($decode_json) + && ref($decode_json) eq 'HASH' + && defined( $decode_json->{errors} ) ) + ) + { + readingsBeginUpdate($dhash); + readingsBulkUpdate( $dhash, "state", $param->{code}, 1 ) + if ( ReadingsVal( $dname, "state", 0 ) ne "initialized" ); + + readingsBulkUpdate( $dhash, "lastRequestState", "request_error", 1 ); + + if ( $param->{code} == 400 ) { + if ($decode_json) { + if ( ref( $decode_json->{errors} ) eq "ARRAY" + && defined( $decode_json->{errors} ) ) + { + readingsBulkUpdate( + $dhash, + "state", + $decode_json->{errors}[0]{error} . ' ' + . $decode_json->{errors}[0]{attribute}, + 1 + ); + readingsBulkUpdate( + $dhash, + "lastRequestState", + $decode_json->{errors}[0]{error} . ' ' + . $decode_json->{errors}[0]{attribute}, + 1 + ); + Log3 $dname, 5, + "GardenaSmartBridge ($dname) - RequestERROR: " + . $decode_json->{errors}[0]{error} . " " + . $decode_json->{errors}[0]{attribute}; + } + } + else { + readingsBulkUpdate( $dhash, "lastRequestState", + "Error 400 Bad Request", 1 ); + Log3 $dname, 5, +"GardenaSmartBridge ($dname) - RequestERROR: Error 400 Bad Request"; + } + } + elsif ( $param->{code} == 503 ) { + + Log3 $dname, 5, +"GardenaSmartBridge ($dname) - RequestERROR: Error 503 Service Unavailable"; + readingsBulkUpdate( $dhash, "state", "Service Unavailable", 1 ); + readingsBulkUpdate( $dhash, "lastRequestState", + "Error 503 Service Unavailable", 1 ); + + } + elsif ( $param->{code} == 404 ) { + if ( defined( $dhash->{helper}{deviceAction} ) && $dhash ne $hash ) + { + readingsBulkUpdate( $dhash, "state", "device Id not found", 1 ); + readingsBulkUpdate( $dhash, "lastRequestState", + "device id not found", 1 ); + } + + Log3 $dname, 5, + "GardenaSmartBridge ($dname) - RequestERROR: Error 404 Not Found"; + + } + elsif ( $param->{code} == 500 ) { + + Log3 $dname, 5, + "GardenaSmartBridge ($dname) - RequestERROR: check the ???"; + + } + else { + + Log3 $dname, 5, + "GardenaSmartBridge ($dname) - RequestERROR: http error " + . $param->{code}; + } + + readingsEndUpdate( $dhash, 1 ); + + Log3 $dname, 5, + "GardenaSmartBridge ($dname) - RequestERROR: received http code " + . $param->{code} + . " receive Error after requesting gardena cloud"; + + delete $dhash->{helper}{deviceAction} + if ( defined( $dhash->{helper}{deviceAction} ) ); + + return; + } + if (defined($hash->{helper}{debug_device})){ + Log3 $name, 5, "GardenaSmartBridge DEBUG Device"; + 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"; + $output .= "name: $dev_settings->{name} "; + if (ref ($dev_settings->{value}) eq 'ARRAY' + || ref ($dev_settings->{value}) eq 'HASH'){ + $output .= 'N/A \n'; + } else { + $output .= "value: $dev_settings->{value} \n"; + } + } + $hash->{helper}{debug_device_output} = $output; + asyncOutput($param->{cl}, $hash->{helper}{debug_device_output}); + return; + } + readingsSingleUpdate( $hash, 'state', 'Connected', 1 ) + if ( defined( $hash->{helper}{locations_id} ) ); + ResponseProcessing( $hash, $data ) + if ( ref($decode_json) eq 'HASH' ); + + return; +} + +sub ResponseProcessing { + my $hash = shift; + my $json = shift; + + 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 ); + } + } + + # print Dumper $decode_json; + + 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}; + + Write( $hash, undef, undef, undef ); + Log3 $name, 3, "GardenaSmartBridge ($name) - fetch locations id"; + readingsSingleUpdate( $hash, 'token', $hash->{helper}{session_id}, 1 ); + + return; + + } + elsif ( !defined( $hash->{helper}{locations_id} ) + && defined( $decode_json->{locations} ) + && ref( $decode_json->{locations} ) eq 'ARRAY' + && scalar( @{ $decode_json->{locations} } ) > 0 ) + { + for my $location ( @{ $decode_json->{locations} } ) { + + $hash->{helper}{locations_id} = $location->{id}; + + WriteReadings( $hash, $location ); + } + + Log3 $name, 3, + "GardenaSmartBridge ($name) - processed locations id. ID is " + . $hash->{helper}{locations_id}; + Write( $hash, undef, undef, undef ); + + return; + } + elsif (defined( $decode_json->{devices} ) + && ref( $decode_json->{devices} ) eq 'ARRAY' + && scalar( @{ $decode_json->{devices} } ) > 0 ) + { + + my @buffer = split( '"devices":\[', $json ); + + my ( $json, $tail ) = 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; + + if ( defined($tail) and $tail ) { + $decode_json = eval { decode_json($json) }; + if ($@) { + Log3 $name, 5, +"GardenaSmartBridge ($name) - JSON error while request: $@"; + } + + Dispatch( $hash, $json, undef ) + if ( $decode_json->{category} ne 'gateway' ); + WriteReadings( $hash, $decode_json ) + if ( defined( $decode_json->{category} ) + && $decode_json->{category} eq 'gateway' ); + } + + ( $json, $tail ) = 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"; + + return; +} + +sub WriteReadings { + my $hash = shift; + my $decode_json = shift; + + # print Dumper $decode_json; + + my $name = $hash->{NAME}; + + if ( defined( $decode_json->{id} ) + && $decode_json->{id} + && defined( $decode_json->{name} ) + && $decode_json->{name} ) + { + readingsBeginUpdate($hash); + if ( $decode_json->{id} eq $hash->{helper}{locations_id} ) { + + 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 ); + } + } + elsif ($decode_json->{id} ne $hash->{helper}{locations_id} + && ref( $decode_json->{abilities} ) eq 'ARRAY' + && ref( $decode_json->{abilities}[0]{properties} ) eq 'ARRAY' ) + { + my $properties = + scalar( @{ $decode_json->{abilities}[0]{properties} } ); + + do { + while ( ( my ( $t, $v ) ) = + each + %{ $decode_json->{abilities}[0]{properties}[$properties] } ) + { + next + if ( ref($v) eq 'ARRAY' ); + + #$v = encode_utf8($v); + readingsBulkUpdateIfChanged( + $hash, + $decode_json->{abilities}[0]{properties}[$properties] + {name} . '-' . $t, + $v + ) + if ($decode_json->{abilities}[0]{properties}[$properties]{name} !~ /ethernet_status|wifi_status/ ); + if ( + ( + $decode_json->{abilities}[0]{properties} + [$properties]{name} eq 'ethernet_status' + || $decode_json->{abilities}[0]{properties} + [$properties]{name} eq 'wifi_status' + ) + && ref($v) eq 'HASH' + ) + { + if ( $decode_json->{abilities}[0]{properties} + [$properties]{name} eq 'ethernet_status' ) + { + readingsBulkUpdateIfChanged( $hash, + 'ethernet_status-mac', $v->{mac} ); + readingsBulkUpdateIfChanged( $hash, + 'ethernet_status-ip', $v->{ip} ) + if ( ref( $v->{ip} ) ne 'HASH' ); + readingsBulkUpdateIfChanged( $hash, + 'ethernet_status-isconnected', + $v->{isConnected} ); + } + elsif ( $decode_json->{abilities}[0]{properties} + [$properties]{name} eq 'wifi_status' ) + { + #TODO: read valies if bridge connected to wifi + readingsBulkUpdateIfChanged( $hash, + 'wifi_status-ssid', $v->{ssid} ) + if (ref($v->{ssid}) ne 'HASH'); + readingsBulkUpdateIfChanged( $hash, + 'wifi_status-mac', $v->{mac} ); + readingsBulkUpdateIfChanged( $hash, + 'wifi_status-ip', $v->{ip} ) + if ( ref( $v->{ip} ) ne 'HASH' ); + readingsBulkUpdateIfChanged( $hash, + 'wifi_status-isconnected', $v->{isConnected} ); + readingsBulkUpdateIfChanged( $hash, + 'wifi_status-signal', $v->{signal} ); + } + } + } + $properties--; + + } while ( $properties >= 0 ); + } + readingsEndUpdate( $hash, 1 ); + } + + Log3 $name, 4, "GardenaSmartBridge ($name) - readings would be written"; + + return; +} + +#################################### +#################################### +#### my little helpers Sub's ####### + +sub getDevices { + my $hash = shift; + + my $name = $hash->{NAME}; + + RemoveInternalTimer($hash); + + if ( not IsDisabled($name) ) { + + delete $hash->{helper}{deviceList}; + my @list; + @list = devspec2array('TYPE=GardenaSmartDevice'); + for my $gardenaDev (@list){ + push( @{ $hash->{helper}{deviceList} }, $gardenaDev ); + } + Write( $hash, undef, undef, undef ); + Log3 $name, 4, + "GardenaSmartBridge ($name) - fetch device list and device states"; + } + else { + + readingsSingleUpdate( $hash, 'state', 'disabled', 1 ); + Log3 $name, 3, "GardenaSmartBridge ($name) - device is disabled"; + } + + return; +} + +sub getToken { + my $hash = shift; + + my $name = $hash->{NAME}; + + return readingsSingleUpdate( $hash, 'state', + 'please set Attribut gardenaAccountEmail first', 1 ) + if ( AttrVal( $name, 'gardenaAccountEmail', 'none' ) eq 'none' ); + return readingsSingleUpdate( $hash, 'state', + 'please set gardena account password first', 1 ) + if ( !defined( ReadPassword( $hash, $name ) ) ); + readingsSingleUpdate( $hash, 'state', 'get token', 1 ); + + delete $hash->{helper}{session_id} + if ( defined( $hash->{helper}{session_id} ) + && $hash->{helper}{session_id} ); + delete $hash->{helper}{user_id} + if ( defined( $hash->{helper}{user_id} ) && $hash->{helper}{user_id} ); + delete $hash->{helper}{locations_id} + if ( defined( $hash->{helper}{locations_id} ) + && $hash->{helper}{locations_id} ); + + # Write( + # $hash, + # '"sessions": {"email": "' + # . AttrVal( $name, 'gardenaAccountEmail', 'none' ) + # . '","password": "' + # . ReadPassword( $hash, $name ) . '"}', + # undef, + # undef + # ); + + Write( + $hash, + '"data": {"type":"token", "attributes":{"username": "' + . AttrVal( $name, 'gardenaAccountEmail', 'none' ) + . '","password": "' + . 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, 3, +"GardenaSmartBridge ($name) - send credentials to fetch Token and locationId"; + + return; +} + +sub StorePassword { + my $hash = shift; + my $name = shift; + my $password = shift; + + my $index = $hash->{TYPE} . "_" . $name . "_passwd"; + my $key = getUniqueId() . $index; + my $enc_pwd = ""; + + if ( eval "use Digest::MD5;1" ) { + + $key = Digest::MD5::md5_hex( unpack "H*", $key ); + $key .= Digest::MD5::md5_hex($key); + } + + for my $char ( split //, $password ) { + + my $encode = chop($key); + $enc_pwd .= sprintf( "%.2x", ord($char) ^ ord($encode) ); + $key = $encode . $key; + } + + my $err = setKeyValue( $index, $enc_pwd ); + return "error while saving the password - $err" if ( defined($err) ); + + return "password successfully saved"; +} + +sub ReadPassword { + my $hash = shift; + my $name = shift; + + my $index = $hash->{TYPE} . "_" . $name . "_passwd"; + my $key = getUniqueId() . $index; + my ( $password, $err ); + + Log3 $name, 4, "GardenaSmartBridge ($name) - Read password from file"; + + ( $err, $password ) = getKeyValue($index); + + if ( defined($err) ) { + + Log3 $name, 3, +"GardenaSmartBridge ($name) - unable to read password from file: $err"; + return undef; + + } + + if ( defined($password) ) { + if ( eval "use Digest::MD5;1" ) { + + $key = Digest::MD5::md5_hex( unpack "H*", $key ); + $key .= Digest::MD5::md5_hex($key); + } + + my $dec_pwd = ''; + + for my $char ( map { pack( 'C', hex($_) ) } ( $password =~ /(..)/g ) ) { + + my $decode = chop($key); + $dec_pwd .= chr( ord($char) ^ ord($decode) ); + $key = $decode . $key; + } + + return $dec_pwd; + + } + else { + + Log3 $name, 3, "GardenaSmartBridge ($name) - No password in file"; + return undef; + } + + return; +} + +sub Rename { + my $new = shift; + my $old = shift; + + my $hash = $defs{$new}; + + StorePassword( $hash, $new, ReadPassword( $hash, $old ) ); + setKeyValue( $hash->{TYPE} . "_" . $old . "_passwd", undef ); + + 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 ) = @_; + + my $session_id = $hash->{helper}{session_id}; + my $header = "Content-Type: application/json"; + my $uri = ''; + my $method = 'POST'; + $header .= "\r\nAuthorization: Bearer $session_id" + if ( defined($hash->{helper}{session_id}) ); + $header .= "\r\nAuthorization-Provider: husqvarna" + if ( defined($hash->{helper}{session_id}) ); + + # $header .= "\r\nx-api-key: $session_id" + # if ( defined( $hash->{helper}{session_id} ) ); + $payload = '{' . $payload . '}' if ( defined($payload) ); + $payload = '{}' if ( !defined($payload) ); + + if ( $payload eq '{}' ) { + $method = 'GET'; + $payload = ''; + $uri .= '/locations?locatioId=null&user_id=' . $hash->{helper}{user_id} + if ( exists( $hash->{helper}{user_id} ) + && !defined( $hash->{helper}{locations_id} ) ); + readingsSingleUpdate( $hash, 'state', 'fetch locationId', 1 ) + if ( !defined( $hash->{helper}{locations_id} ) ); + $uri .= '/auth/token' if ( !defined( $hash->{helper}{session_id} ) ); + $uri .= '/devices' + if (!defined($abilities) + && defined( $hash->{helper}{locations_id} ) ); + } + + $uri = '/devices/'.InternalVal($hash->{helper}{debug_device}, 'DEVICEID', 0 ) if ( exists ($hash->{helper}{debug_device})); + $uri = '/auth/token' if ( !defined( $hash->{helper}{session_id} ) ); + + if ( defined( $hash->{helper}{locations_id} ) ) { + if ( defined($abilities) && $abilities =~ /.*_settings/ ) { + + $method = 'PUT'; + my $dhash = $modules{GardenaSmartDevice}{defptr}{$deviceId}; + + $uri .= + '/devices/' + . $deviceId + . '/settings/' + . $service_id + if ( defined($abilities) + && defined($payload) + && $abilities =~ /.*_settings/ ); + + } # park until next schedules or override + elsif (defined($abilities) + && defined($payload) + && $abilities eq 'mower_timer' ) + { + my $valve_id; + $method = 'PUT'; + + $uri .= + '/devices/' + . $deviceId + . '/abilities/' + . $abilities + . '/properties/mower_timer'; + + } + elsif (defined($abilities) + && defined($payload) + && $abilities eq 'watering' ) + { + my $valve_id; + $method = 'PUT'; + + if ( $payload =~ m#watering_timer_(\d)# ) { + $valve_id = $1; + } + $uri .= + '/devices/' + . $deviceId + . '/abilities/' + . $abilities + . '/properties/watering_timer_' + . $valve_id; + + } + elsif (defined($abilities) + && defined($payload) + && $abilities eq 'manual_watering' ) + { + my $valve_id; + $method = 'PUT'; + + $uri .= + '/devices/' + . $deviceId + . '/abilities/' + . $abilities + . '/properties/manual_watering_timer'; + + } + elsif (defined($abilities) + && defined($payload) + && $abilities eq 'power' ) + { + my $valve_id; + $method = 'PUT'; + + $uri .= + '/devices/' + . $deviceId + . '/abilities/' + . $abilities + . '/properties/power_timer'; + + } + else { + $uri .= + '/devices/' . $deviceId . '/abilities/' . $abilities . '/command' + if ( defined($abilities) && defined($payload) ); + } + + $uri .= '?locationId=' . $hash->{helper}{locations_id}; + } + + return ( $payload, $session_id, $header, $uri, $method, $deviceId, + $abilities ); +} + +sub DeletePassword { + my $hash = shift; + + setKeyValue( $hash->{TYPE} . "_" . $hash->{NAME} . "_passwd", undef ); + + return; +} + +1; + +=pod + +=item device +=item summary Modul to communicate with the GardenaCloud +=item summary_DE Modul zur Datenübertragung zur GardenaCloud + +=begin html + + +

GardenaSmartBridge

+
    + Prerequisite +

    +
  • In combination with GardenaSmartDevice this FHEM Module controls the communication between the GardenaCloud and connected Devices like Mover, Watering_Computer, Temperature_Sensors
  • +
  • Installation of the following packages: apt-get install libio-socket-ssl-perl
  • +
  • The Gardena-Gateway and all connected Devices must be correctly installed in the GardenaAPP
  • +
+
+ +Define +

    + define <name> GardenaSmartBridge +

    + Beispiel: +

      + define Gardena_Bridge GardenaSmartBridge
      +
    +
    + The GardenaSmartBridge device is created in the room GardenaSmart, then the devices of Your system are recognized automatically and created in FHEM. From now on the devices can be controlled and changes in the GardenaAPP are synchronized with the state and readings of the devices. +

    + +

    + Readings +
      +
    • address - your Adress (Longversion)
    • +
    • authorized_user_ids -
    • +
    • city - Zip, City
    • +
    • devices - Number of Devices in the Cloud (Gateway included)
    • +
    • lastRequestState - Last Status Result
    • +
    • latitude - Breitengrad des Grundstücks
    • +
    • longitude - Längengrad des Grundstücks
    • +
    • name - Name of your Garden – Default „My Garden“
    • +
    • state - State of the Bridge
    • +
    • token - SessionID
    • +
    +

    + + set +
      +
    • getDeviceState - Starts a Datarequest
    • +
    • getToken - Gets a new Session-ID
    • +
    • gardenaAccountPassword - Passwort which was used in the GardenaAPP
    • +
    • deleteAccountPassword - delete the password from store
    • +
    +

    + + Attributes +
      +
    • debugJSON -
    • +
    • disable - Disables the Bridge
    • +
    • interval - Interval in seconds (Default=60)
    • +
    • gardenaAccountEmail - Email Adresse which was used in the GardenaAPP
    • +
    +
+ +=end html +=begin html_DE + + +

GardenaSmartBridge

+
    + Voraussetzungen +

    +
  • Zusammen mit dem Device GardenaSmartDevice stellt dieses FHEM Modul die Kommunikation zwischen der GardenaCloud und Fhem her. Es können damit Rasenmäher, Bewässerungscomputer und Bodensensoren überwacht und gesteuert werden
  • +
  • Das Perl-Modul "SSL Packet" wird benötigt.
  • +
  • Unter Debian (basierten) System, kann dies mittels "apt-get install libio-socket-ssl-perl" installiert werden.
  • +
  • Das Gardena-Gateway und alle damit verbundenen Geräte und Sensoren müssen vorab in der GardenaApp eingerichtet sein.
  • +
+
+ +Define +

    + define <name> GardenaSmartBridge +

    + Beispiel: +

      + define Gardena_Bridge GardenaSmartBridge
      +
    +
    + Das Bridge Device wird im Raum GardenaSmart angelegt und danach erfolgt das Einlesen und automatische Anlegen der Geräte. Von nun an können die eingebundenen Geräte gesteuert werden. Änderungen in der APP werden mit den Readings und dem Status syncronisiert. +

    + +

    + Readings +
      +
    • address - Adresse, welche in der App eingetragen wurde (Langversion)
    • +
    • authorized_user_ids -
    • +
    • city - PLZ, Stadt
    • +
    • devices - Anzahl der Geräte, welche in der GardenaCloud angemeldet sind (Gateway zählt mit)
    • +
    • lastRequestState - Letzter abgefragter Status der Bridge
    • +
    • latitude - Breitengrad des Grundstücks
    • +
    • longitude - Längengrad des Grundstücks
    • +
    • name - Name für das Grundstück – Default „My Garden“
    • +
    • state - Status der Bridge
    • +
    • token - SessionID
    • +
    +

    + + set +
      +
    • getDeviceState - Startet eine Abfrage der Daten.
    • +
    • getToken - Holt eine neue Session-ID
    • +
    • gardenaAccountPassword - Passwort, welches in der GardenaApp verwendet wurde
    • +
    • deleteAccountPassword - l&oml;scht das Passwort aus dem Passwortstore
    • +
    +

    + + Attribute +
      +
    • debugJSON - JSON Fehlermeldungen
    • +
    • disable - Schaltet die Datenübertragung der Bridge ab
    • +
    • interval - Abfrageinterval in Sekunden (default: 60)
    • +
    • gardenaAccountEmail - Email Adresse, die auch in der GardenaApp verwendet wurde
    • +
    +
+ +=end html_DE + +=for :application/json;q=META.json 73_GardenaSmartBridge.pm +{ + "abstract": "Modul to communicate with the GardenaCloud", + "x_lang": { + "de": { + "abstract": "Modul zur Datenübertragung zur GardenaCloud" + } + }, + "keywords": [ + "fhem-mod-device", + "fhem-core", + "Garden", + "Gardena", + "Smart" + ], + "release_status": "stable", + "license": "GPL_2", + "version": "v2.2.2", + "author": [ + "Marko Oldenburg " + ], + "x_fhem_maintainer": [ + "CoolTux" + ], + "x_fhem_maintainer_github": [ + "LeonGaultier" + ], + "prereqs": { + "runtime": { + "requires": { + "FHEM": 5.00918799, + "perl": 5.016, + "Meta": 0, + "IO::Socket::SSL": 0, + "JSON": 0, + "HttpUtils": 0, + "Encode": 0 + }, + "recommends": { + }, + "suggests": { + } + } + } +} +=end :application/json;q=META.json + +=cut diff --git a/FHEM/74_GardenaSmartDevice.pm b/FHEM/74_GardenaSmartDevice.pm index 5ba6d8f..5075adb 100644 --- a/FHEM/74_GardenaSmartDevice.pm +++ b/FHEM/74_GardenaSmartDevice.pm @@ -1,1327 +1,1327 @@ -############################################################################### -# -# Developed with Kate -# -# (c) 2017-2021 Copyright: Marko Oldenburg (fhemdevelopment at cooltux dot net) -# All rights reserved -# -# Special thanks goes to comitters: -# - Michael (mbrak) Thanks for Commandref -# - Matthias (Kenneth) Thanks for Wiki entry -# - BioS Thanks for predefined start points Code -# - fettgu Thanks for Debugging Irrigation Control data flow -# - Sebastian (BOFH) Thanks for new Auth Code after API Change -# -# -# 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/ -# -## -## - -## unserer packagename -package FHEM::GardenaSmartDevice; -use GPUtils qw(GP_Import GP_Export); - -use strict; -use warnings; -use POSIX; -use FHEM::Meta; -use Time::Local; - -# try to use JSON::MaybeXS wrapper -# for chance of better performance + open code -eval { - require JSON::MaybeXS; - import JSON::MaybeXS qw( decode_json encode_json ); - 1; -}; - -if ($@) { - $@ = undef; - - # try to use JSON wrapper - # for chance of better performance - eval { - - # JSON preference order - local $ENV{PERL_JSON_BACKEND} = - 'Cpanel::JSON::XS,JSON::XS,JSON::PP,JSON::backportPP' - unless ( defined( $ENV{PERL_JSON_BACKEND} ) ); - - require JSON; - import JSON qw( decode_json encode_json ); - 1; - }; - - if ($@) { - $@ = undef; - - # In rare cases, Cpanel::JSON::XS may - # be installed but JSON|JSON::MaybeXS not ... - eval { - require Cpanel::JSON::XS; - import Cpanel::JSON::XS qw(decode_json encode_json); - 1; - }; - - if ($@) { - $@ = undef; - - # In rare cases, JSON::XS may - # be installed but JSON not ... - eval { - require JSON::XS; - import JSON::XS qw(decode_json encode_json); - 1; - }; - - if ($@) { - $@ = undef; - - # Fallback to built-in JSON which SHOULD - # be available since 5.014 ... - eval { - require JSON::PP; - import JSON::PP qw(decode_json encode_json); - 1; - }; - - if ($@) { - $@ = undef; - - # Fallback to JSON::backportPP in really rare cases - require JSON::backportPP; - import JSON::backportPP qw(decode_json encode_json); - 1; - } - } - } - } -} - -## Import der FHEM Funktionen -#-- Run before package compilation -BEGIN { - - # Import from main context - GP_Import( - qw(readingsSingleUpdate - readingsBulkUpdate - readingsBulkUpdateIfChanged - readingsBeginUpdate - readingsEndUpdate - Log3 - CommandAttr - AttrVal - ReadingsVal - readingFnAttributes - AssignIoPort - modules - IOWrite - defs - makeDeviceName) - ); -} - -#-- Export to main context with different name -GP_Export( - qw( - Initialize - ) -); - -sub Initialize { - my $hash = shift; - - $hash->{Match} = '^{"id":".*'; - - $hash->{SetFn} = \&Set; - $hash->{DefFn} = \&Define; - $hash->{UndefFn} = \&Undef; - $hash->{ParseFn} = \&Parse; - - $hash->{AttrFn} = \&Attr; - $hash->{AttrList} = - "readingValueLanguage:de,en " - . "model:watering_computer,sensor,mower,ic24,power,electronic_pressure_pump " - . "IODev " - . $readingFnAttributes; - $hash->{parseParams} = 1; - - return FHEM::Meta::InitMod( __FILE__, $hash ); -} - -sub Define { - my $hash = shift // return; - my $aArg = shift // return; - - return $@ unless ( FHEM::Meta::SetInternals($hash) ); - use version 0.60; our $VERSION = FHEM::Meta::Get( $hash, 'version' ); - - return - "too few parameters: define GardenaSmartDevice " - if ( scalar( @{$aArg} ) < 3 ); - - my $name = $aArg->[0]; - my $deviceId = $aArg->[2]; - my $category = $aArg->[3]; - - $hash->{DEVICEID} = $deviceId; - $hash->{VERSION} = version->parse($VERSION)->normal; - $hash->{helper}{STARTINGPOINTID} = ''; - $hash->{helper}{schedules_paused_until_id} = ''; - $hash->{helper}{eco_mode_id} = ''; - # IrrigationControl valve control max 6 - $hash->{helper}{schedules_paused_until_1_id} = ''; - $hash->{helper}{schedules_paused_until_2_id} = ''; - $hash->{helper}{schedules_paused_until_3_id} = ''; - $hash->{helper}{schedules_paused_until_4_id} = ''; - $hash->{helper}{schedules_paused_until_5_id} = ''; - $hash->{helper}{schedules_paused_until_6_id} = ''; - - CommandAttr( undef, - "$name IODev $modules{GardenaSmartBridge}{defptr}{BRIDGE}->{NAME}" ) - if ( AttrVal( $name, 'IODev', 'none' ) eq 'none' ); - - my $iodev = AttrVal( $name, 'IODev', 'none' ); - - 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 ); - - CommandAttr( undef, $name . ' room GardenaSmart' ) - if ( AttrVal( $name, 'room', 'none' ) eq 'none' ); - - CommandAttr( undef, $name . ' model ' . $category ) - if ( AttrVal( $name, 'model', 'none' ) eq 'none' ); - - Log3 $name, 3, -"GardenaSmartDevice ($name) - defined GardenaSmartDevice with DEVICEID: $deviceId"; - readingsSingleUpdate( $hash, 'state', 'initialized', 1 ); - - $modules{GardenaSmartDevice}{defptr}{$deviceId} = $hash; - - return; -} - -sub Undef { - my $hash = shift; - my $arg = shift; - - my $name = $hash->{NAME}; - my $deviceId = $hash->{DEVICEID}; - - delete $modules{GardenaSmartDevice}{defptr}{$deviceId}; - - return; -} - -sub Attr { - - my ( $cmd, $name, $attrName, $attrVal ) = @_; - my $hash = $defs{$name}; - - return; -} - -sub Set { - my $hash = shift // return; - my $aArg = shift // return; - - my $name = shift @$aArg; - 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 ); - - #set default abilitie ... overwrite in cmd to change - $abilities = 'mower' - if ( AttrVal( $name, 'model', 'unknown' ) eq 'mower' ); - $abilities = 'watering' - if ( AttrVal( $name, 'model', 'unknown' ) eq 'ic24' - || AttrVal( $name, 'model', 'unknown' ) eq 'watering_computer' ); - $abilities = 'power' - if ( AttrVal( $name, 'model', 'unknown' ) eq 'power' ); - $abilities = 'manual_watering' - if ( AttrVal( $name, 'model', 'unknown' ) eq 'electronic_pressure_pump' ); - - ### mower - # service_id (eco, parkuntilfurhternotice, startpoints) - if ( lc $cmd eq 'parkuntilfurthernotice' ) { - $payload = '"name":"park_until_further_notice"'; - if ( $mainboard_version > 10.30 ) { - $payload = ' "settings":{"name":"schedules_paused_until","value":"2040-12-31T22: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}' ; - $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' ; - $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 .'}'; - $abilities = 'mower_timer'; - } - - } - elsif ( lc $cmd eq 'startpoint' ) { - my $err; - ( $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}.'"}'; - $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 ); - } - ### electronic_pressure_pump - elsif ( lc $cmd eq 'pumptimer' ) { - $payload = - '"name":"pump_manual_watering_timer","parameters":{"duration":' - . $aArg->[0] . '}'; - } - ### watering_computer - elsif ( lc $cmd eq 'manualoverride' ) { - $payload = - '"properties":{"name":"watering_timer_1' - . '","value":{"state":"manual","duration":' - . $aArg->[0] * 60 - . ',"valve_id":1}}'; - } - elsif ( $cmd =~ m{\AcancelOverride}xms ) { - - my $valve_id = 1; - - if ( $cmd =~ m{\AcancelOverrideValve(\d)\z}xms ) { - $valve_id = $1; - } - - $payload = - '"properties":{"name":"watering_timer_' - . $valve_id - . '","value":{"state":"idle","duration":' - . 0 - . ',"valve_id":' - . $valve_id . '}}'; - } - elsif ( lc $cmd eq 'on' || lc $cmd eq 'off' || lc $cmd eq 'on-for-timer' ) { - my $val = ( - defined($aArg) && ref($aArg) eq 'ARRAY' - ? $aArg->[0] * 60 - : lc $cmd - ); - - $payload = '"properties":{"value":"' . $val . '"}'; - } - ### Watering ic24 - elsif ( $cmd =~ m{\AmanualDurationValve\d\z}xms ) { - my $valve_id; - - if ( $cmd =~ m{\AmanualDurationValve(\d)\z}xms ) { - $valve_id = $1; - } - - $payload = - '"properties":{"name":"watering_timer_' - . $valve_id - . '","value":{"state":"manual","duration":' - . $aArg->[0] * 60 - . ',"valve_id":' - . $valve_id . '}}'; - } - elsif ( $cmd eq 'closeAllValves' ){ - $payload = '"name":"close_all_valves","parameters":{}'; - } - elsif ( $cmd =~ '/.*ScheduleValve/' ){ - my $valve_id = $1; - $abilities = 'irrigation_settings'; - $service_id = $hash->{helper}->{'schedules_paused_until_'.$valve_id.'_id'}; - $payload = '"settings":{"name":"schedules_paused_until_' - .$valve_id. - '", "value":"' - . ($cmd eq 'resumeScheduleValve') ? '' : '2040-12-31T22:00:00.000Z' - .'","device":"' - .$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') { - $payload = '"name":"measure_ambient_temperature"'; - $abilities = 'ambient_temperature'; - } else { - $payload = '"name":"measure_soil_temperature"'; - $abilities = 'soil_temperature'; - } - } - elsif ( lc $sensname eq 'light' ) { - $payload = '"name":"measure_light"'; - $abilities = 'light'; - - } - elsif ( lc $sensname eq 'humidity' ) { - $payload = '"name":"measure_soil_humidity"'; - $abilities = 'humidity'; - } - - - } - else { - - my $list = ''; - - $list .= -'parkUntilFurtherNotice:noArg parkUntilNextTimer:noArg startResumeSchedule:noArg startOverrideTimer:slider,0,1,60 startpoint' - if ( AttrVal( $name, 'model', 'unknown' ) eq 'mower' ); - - $list .= 'manualOverride:slider,1,1,59 cancelOverride:noArg' - if ( AttrVal( $name, 'model', 'unknown' ) eq 'watering_computer' ); - - $list .= -'closeAllValves:noArg stopScheduleValve:selectnumbers,1,1,6,0,lin resumeScheduleValve:selectnumbers,1,1,6,0,lin manualDurationValve1:slider,1,1,59 manualDurationValve2:slider,1,1,59 manualDurationValve3:slider,1,1,59 manualDurationValve4:slider,1,1,59 manualDurationValve5:slider,1,1,59 manualDurationValve6:slider,1,1,59 cancelOverrideValve1:noArg cancelOverrideValve2:noArg cancelOverrideValve3:noArg cancelOverrideValve4:noArg cancelOverrideValve5:noArg cancelOverrideValve6:noArg' - if ( AttrVal( $name, 'model', 'unknown' ) eq 'ic24' ); - - $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' ); - - $list .= 'on:noArg off:noArg on-for-timer:slider,0,1,60' - if ( AttrVal( $name, 'model', 'unknown' ) eq 'power' ); - - return "Unknown argument $cmd, choose one of $list"; - } - - $hash->{helper}{deviceAction} = $payload; - readingsSingleUpdate( $hash, "state", "send command to gardena cloud", 1 ); - - IOWrite( $hash, $payload, $hash->{DEVICEID}, $abilities, $service_id ); - Log3 $name, 4, -"GardenaSmartBridge ($name) - IOWrite: $payload $hash->{DEVICEID} $abilities IODevHash=$hash->{IODev}"; - - return; -} - -sub Parse { - my $io_hash = shift; - my $json = shift; - - my $name = $io_hash->{NAME}; - - my $decode_json = eval { decode_json($json) }; - if ($@) { - Log3 $name, 3, - "GardenaSmartDevice ($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}; - - WriteReadings( $hash, $decode_json ); - Log3 $name, 4, - "GardenaSmartDevice ($name) - find logical device: $hash->{NAME}"; - - return $hash->{NAME}; - - } - else { - - Log3 $name, 3, - "GardenaSmartDevice ($name) - autocreate new device " - . makeDeviceName( $decode_json->{name} ) - . " with deviceId $decode_json->{id}, model $decode_json->{category}"; - return - "UNDEFINED " - . makeDeviceName( $decode_json->{name} ) - . " GardenaSmartDevice $decode_json->{id} $decode_json->{category}"; - } - } - - return; -} - -sub WriteReadings { - my $hash = shift; - my $decode_json = shift; - - my $name = $hash->{NAME}; - my $abilities = scalar( @{ $decode_json->{abilities} } ); - my $settings = scalar( @{ $decode_json->{settings} } ); - - readingsBeginUpdate($hash); - - do { - - if ( - ref( $decode_json->{abilities}[$abilities]{properties} ) eq "ARRAY" - && scalar( @{ $decode_json->{abilities}[$abilities]{properties} } ) - > 0 ) - { - for my $propertie ( - @{ $decode_json->{abilities}[$abilities]{properties} } ) - { - readingsBulkUpdateIfChanged( - $hash, - $decode_json->{abilities}[$abilities]{name} . '-' - . $propertie->{name}, - RigReadingsValue( $hash, $propertie->{value} ) - ) - if ( defined( $propertie->{value} ) - && $decode_json->{abilities}[$abilities]{name} . '-' - . $propertie->{name} ne 'radio-quality' - && $decode_json->{abilities}[$abilities]{name} . '-' - . $propertie->{name} ne 'battery-level' - && $decode_json->{abilities}[$abilities]{name} . '-' - . $propertie->{name} ne 'internal_temperature-temperature' - && $decode_json->{abilities}[$abilities]{name} . '-' - . $propertie->{name} ne 'ambient_temperature-temperature' - && $decode_json->{abilities}[$abilities]{name} . '-' - . $propertie->{name} ne 'soil_temperature-temperature' - && $decode_json->{abilities}[$abilities]{name} . '-' - . $propertie->{name} ne 'humidity-humidity' - && $decode_json->{abilities}[$abilities]{name} . '-' - . $propertie->{name} ne 'light-light' - && ref( $propertie->{value} ) ne "HASH" ); - - readingsBulkUpdateIfChanged( - $hash, - $decode_json->{abilities}[$abilities]{name} . '-' - . $propertie->{name}, - RigReadingsValue( $hash, $propertie->{value} ) - ) - if ( - defined( $propertie->{value} ) - && ( $decode_json->{abilities}[$abilities]{name} . '-' - . $propertie->{name} eq 'radio-quality' - || $decode_json->{abilities}[$abilities]{name} . '-' - . $propertie->{name} eq 'battery-level' - || $decode_json->{abilities}[$abilities]{name} . '-' - . $propertie->{name} eq - 'internal_temperature-temperature' - || $decode_json->{abilities}[$abilities]{name} . '-' - . $propertie->{name} eq - 'ambient_temperature-temperature' - || $decode_json->{abilities}[$abilities]{name} . '-' - . $propertie->{name} eq 'soil_temperature-temperature' - || $decode_json->{abilities}[$abilities]{name} . '-' - . $propertie->{name} eq 'humidity-humidity' - || $decode_json->{abilities}[$abilities]{name} . '-' - . $propertie->{name} eq 'light-light' ) - ); - - readingsBulkUpdateIfChanged( - $hash, - $decode_json->{abilities}[$abilities]{name} . '-' - . $propertie->{name}, - join( ',', @{ $propertie->{value} } ) - ) - if ( defined( $propertie->{value} ) - && $decode_json->{abilities}[$abilities]{name} . '-' - . $propertie->{name} eq 'ic24-valves_connected' ); - - readingsBulkUpdateIfChanged( - $hash, - $decode_json->{abilities}[$abilities]{name} . '-' - . $propertie->{name}, - join( ',', @{ $propertie->{value} } ) - ) - if ( defined( $propertie->{value} ) - && $decode_json->{abilities}[$abilities]{name} . '-' - . $propertie->{name} eq 'ic24-valves_master_config' ); - - if ( ref( $propertie->{value} ) eq "HASH" ) { - while ( my ( $r, $v ) = each %{ $propertie->{value} } ) { - readingsBulkUpdate( - $hash, - $decode_json->{abilities}[$abilities]{name} . '-' - . $propertie->{name} . '_' - . $r, - RigReadingsValue( $hash, $v ) - ); - } - } - } - } - - $abilities--; - } 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"); - - if ( exists($decode_json->{settings}[$settings]{name}) - && ( - $decode_json->{settings}[$settings]{name} =~ /schedules_paused_until_?\d?$/ - || $decode_json->{settings}[$settings]{name} eq 'eco_mode' ) - ) - { - if ( $hash->{helper}{$decode_json->{settings}[$settings]{name}.'_id'} ne - $decode_json->{settings}[$settings]{id} ) - { - $hash->{helper}{$decode_json->{settings}[$settings]{name}.'_id'} = - $decode_json->{settings}[$settings]{id}; - } - } - - if ( ref( $decode_json->{settings}[$settings]{value} ) eq "ARRAY" - && $decode_json->{settings}[$settings]{name} eq 'starting_points' ) - { - #save the startingpointid needed to update the startingpoints - if ( $hash->{helper}{STARTINGPOINTID} ne - $decode_json->{settings}[$settings]{id} ) - { - $hash->{helper}{STARTINGPOINTID} = - $decode_json->{settings}[$settings]{id}; - } - - $hash->{helper}{STARTINGPOINTS} = - '{ "name": "starting_points", "value": ' - . encode_json( $decode_json->{settings}[$settings]{value} ) . '}'; - my $startpoint_cnt = 0; - - for my $startingpoint ( - @{ $decode_json->{settings}[$settings]{value} } ) - { - $startpoint_cnt++; - readingsBulkUpdateIfChanged( - $hash, - 'startpoint-' . $startpoint_cnt . '-enabled', - $startingpoint->{enabled} - ); - } - } - - $settings--; - } while ( $settings >= 0 ); - - - my $online_state = ReadingsVal($name , 'device_info-connection_status', 'unknown'); - - readingsBulkUpdate( $hash, 'state', - $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 - ? 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'); - - # 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( - $hash, 'state', - 'scheduled watering next start: ' - . ( - ReadingsVal( - $name, 'scheduling-scheduled_watering_next_start', - 'no timer' - ) - ) - ) if ( AttrVal( $name, 'model', 'unknown' ) eq 'ic24' ); - - readingsBulkUpdate( $hash, 'state', - ReadingsVal( $name, 'power-power_timer', 'no info from power-timer' ) ) - if ( AttrVal( $name, 'model', 'unknown' ) eq 'power' ); - - readingsEndUpdate( $hash, 1 ); - - Log3 $name, 4, "GardenaSmartDevice ($name) - readings was written"; - - return; -} - -################################## -################################## -#### my little helpers ########### - -sub ReadingLangGerman { - my $hash = shift; - my $readingValue = shift; - - my $name = $hash->{NAME}; - my %langGermanMapp = ( - 'ok_cutting' => 'mähen', - 'paused' => 'pausiert', - 'ok_searching' => 'suche Ladestation', - 'ok_charging' => 'lädt', - 'ok_leaving' => 'unterwegs zum Startpunkt', - 'wait_updating' => 'wird aktualisiert ...', - 'wait_power_up' => 'wird eingeschaltet ...', - 'parked_timer' => 'geparkt nach Zeitplan', - 'parked_park_selected' => 'geparkt', - 'off_disabled' => 'der Mäher ist ausgeschaltet', - 'off_hatch_open' => - 'deaktiviert. Abdeckung ist offen oder PIN-Code erforderlich', - 'unknown' => 'unbekannter Status', - 'error' => 'Fehler', - 'error_at_power_up' => 'Neustart ...', - 'off_hatch_closed' => 'Deaktiviert. Manueller Start erforderlich', - 'ok_cutting_timer_overridden' => 'manuelles mähen', - 'parked_autotimer' => 'geparkt durch SensorControl', - 'parked_daily_limit_reached' => 'abgeschlossen', - 'no_message' => 'kein Fehler', - 'outside_working_area' => 'außerhalb des Arbeitsbereichs', - 'no_loop_signal' => 'kein Schleifensignal', - 'wrong_loop_signal' => 'falsches Schleifensignal', - 'loop_sensor_problem_front' => 'Problem Schleifensensor, vorne', - 'loop_sensor_problem_rear' => 'Problem Schleifensensor, hinten', - 'trapped' => 'eingeschlossen', - 'upside_down' => 'steht auf dem Kopf', - 'low_battery' => 'niedriger Batteriestand', - 'empty_battery' => 'Batterie leer', - 'no_drive' => 'fährt nicht', - 'lifted' => 'angehoben', - 'stuck_in_charging_station' => 'eingeklemmt in Ladestation', - 'charging_station_blocked' => 'Ladestation blockiert', - 'collision_sensor_problem_rear' => 'Problem Stoßsensor hinten', - 'collision_sensor_problem_front' => 'Problem Stoßsensor vorne', - 'wheel_motor_blocked_right' => 'Radmotor rechts blockiert', - 'wheel_motor_blocked_left' => 'Radmotor links blockiert', - 'wheel_drive_problem_right' => 'Problem Antrieb, rechts', - 'wheel_drive_problem_left' => 'Problem Antrieb, links', - 'cutting_system_blocked' => 'Schneidsystem blockiert', - 'invalid_sub_device_combination' => 'fehlerhafte Verbindung', - 'settings_restored' => 'Standardeinstellungen', - 'electronic_problem' => 'elektronisches Problem', - 'charging_system_problem' => 'Problem Ladesystem', - 'tilt_sensor_problem' => 'Kippsensor Problem', - 'wheel_motor_overloaded_right' => 'rechter Radmotor überlastet', - 'wheel_motor_overloaded_left' => 'linker Radmotor überlastet', - 'charging_current_too_high' => 'Ladestrom zu hoch', - 'temporary_problem' => 'vorübergehendes Problem', - 'guide_1_not_found' => 'SK 1 nicht gefunden', - 'guide_2_not_found' => 'SK 2 nicht gefunden', - 'guide_3_not_found' => 'SK 3 nicht gefunden', - 'difficult_finding_home' => 'Problem die Ladestation zu finden', - 'guide_calibration_accomplished' => - 'Kalibrierung des Suchkabels beendet', - 'guide_calibration_failed' => - 'Kalibrierung des Suchkabels fehlgeschlagen', - 'temporary_battery_problem' => 'kurzzeitiges Batterieproblem', - 'battery_problem' => 'Batterieproblem', - 'alarm_mower_switched_off' => 'Alarm! Mäher ausgeschalten', - 'alarm_mower_stopped' => 'Alarm! Mäher gestoppt', - 'alarm_mower_lifted' => 'Alarm! Mäher angehoben', - 'alarm_mower_tilted' => 'Alarm! Mäher gekippt', - 'connection_changed' => 'Verbindung geändert', - 'connection_not_changed' => 'Verbindung nicht geändert', - 'com_board_not_available' => 'COM Board nicht verfügbar', - 'slipped' => 'rutscht', - 'out_of_operation' => 'ausser Betrieb', - 'replace_now' => 'kritischer Batteriestand, wechseln Sie jetzt', - 'low' => 'niedrig', - 'ok' => 'ok', - 'no_source' => 'ok', - 'mower_charging' => 'Mäher wurde geladen', - 'completed_cutting_autotimer' => 'Sensor Control erreicht', - 'week_timer' => 'Wochentimer erreicht', - 'countdown_timer' => 'Stoppuhr Timer', - 'undefined' => 'unklar', - 'unknown' => 'unklar', - 'status_device_unreachable' => 'Gerät ist nicht in Reichweite', - 'status_device_alive' => 'Gerät ist in Reichweite', - 'bad' => 'schlecht', - 'poor' => 'schwach', - 'good' => 'gut', - 'undefined' => 'unklar', - 'idle' => 'nichts zu tun', - 'firmware_cancel' => 'Firmwareupload unterbrochen', - 'firmware_upload' => 'Firmwareupload', - 'unsupported' => 'nicht unterstützt', - 'up_to_date' => 'auf dem neusten Stand', - 'mower' => 'Mäher', - 'watering_computer' => 'Bewässerungscomputer', - 'no_frost' => 'kein Frost', - 'open' => 'offen', - 'closed' => 'geschlossen', - 'included' => 'inbegriffen', - 'active' => 'aktiv', - 'inactive' => 'nicht aktiv' - ); - - if ( - defined( $langGermanMapp{$readingValue} ) - && ( AttrVal( 'global', 'language', 'none' ) eq 'DE' - || AttrVal( $name, 'readingValueLanguage', 'none' ) eq 'de' ) - && AttrVal( $name, 'readingValueLanguage', 'none' ) ne 'en' - ) - { - return $langGermanMapp{$readingValue}; - } - else { - return $readingValue; - } - - return; -} - -sub RigReadingsValue { - my $hash = shift; - my $readingValue = shift; - - my $rigReadingValue; - - if ( $readingValue =~ /^(\d+)-(\d\d)-(\d\d)T(\d\d)/ ) { - $rigReadingValue = Zulu2LocalString($readingValue); - } - else { - $rigReadingValue = ReadingLangGerman( $hash, $readingValue ); - } - - return $rigReadingValue; -} - -sub Zulu2LocalString { - my $t = shift; - - my ( $datehour, $datemin, $rest ) = split( /:/, $t, 3 ); - - my ( $year, $month, $day, $hour, $min ) = - $datehour =~ /(\d+)-(\d\d)-(\d\d)T(\d\d)/; - my $epoch = timegm( 0, 0, $hour, $day, $month - 1, $year ); - - my ( $lyear, $lmonth, $lday, $lhour, $isdst ) = - ( localtime($epoch) )[ 5, 4, 3, 2, -1 ]; - - $lyear += 1900; # year is 1900 based - $lmonth++; # month number is zero based - - if ( defined($rest) ) { - return ( - sprintf( - "%04d-%02d-%02d %02d:%02d:%s", - $lyear, $lmonth, $lday, - $lhour, $datemin, substr( $rest, 0, 2 ) - ) - ); - } - elsif ( $lyear < 2000 ) { - return 'temporarily unavailable'; - } - else { - return ( - sprintf( - "%04d-%02d-%02d %02d:%02d", - $lyear, $lmonth, $lday, $lhour, substr( $datemin, 0, 2 ) - ) - ); - } - - return; -} - -sub SetPredefinedStartPoints { - my $hash = shift; - my $aArg = shift; - - my ($startpoint_state,$startpoint_num,@morestartpoints) = @{$aArg}; - - my $name = $hash->{NAME}; - my $payload; - my $abilities; - - if ( defined($startpoint_state) && defined($startpoint_num) ) { - if ( defined( $hash->{helper}{STARTINGPOINTS} ) - && $hash->{helper}{STARTINGPOINTS} ne '' ) - { -# add needed parameters to saved settings config and change the value in request - my $decode_json_settings = - eval { decode_json( $hash->{helper}{STARTINGPOINTS} ) }; - if ($@) { - Log3 $name, 3, -"GardenaSmartBridge ($name) - JSON error while setting startpoint: $@"; - } - - $decode_json_settings->{device} = $hash->{DEVICEID}; - my $setval = $startpoint_state eq 'disable' ? \0 : \1; - $decode_json_settings->{value}[ $startpoint_num - 1 ]{enabled} = - $setval; - - #set more startpoints - if ( - defined scalar(@morestartpoints) - && ( scalar(@morestartpoints) == 2 - || scalar(@morestartpoints) == 4 ) - ) - { - if ( scalar(@morestartpoints) == 2 ) { - $setval = $morestartpoints[0] eq 'disable' ? \0 : \1; - $decode_json_settings->{value}[ $morestartpoints[1] - 1 ] - {enabled} = $setval; - - } - elsif ( scalar(@morestartpoints) == 4 ) { - $setval = $morestartpoints[0] eq 'disable' ? \0 : \1; - $decode_json_settings->{value}[ $morestartpoints[1] - 1 ] - {enabled} = $setval; - $setval = $morestartpoints[2] eq 'disable' ? \0 : \1; - $decode_json_settings->{value}[ $morestartpoints[3] - 1 ] - {enabled} = $setval; - } - } - - $payload = '"settings": ' . encode_json($decode_json_settings); - $abilities = 'mower_settings'; - #$abilities['service_id'] = $hash->{helper}{STARTINGPOINTID}; - } - else { - return - "startingpoints not loaded yet, please wait a couple of minutes", - undef, undef; - } - } - else { - return - "startpoint usage: set " - . $hash->{NAME} - . " startpoint disable 1 [enable 2] [disable 3]", undef, undef; - } - - return undef, $payload, $abilities; -} - -1; - -=pod - -=item device -=item summary Modul to control GardenaSmart Devices -=item summary_DE Modul zur Steuerung von GardenaSmartgeräten - -=begin html - - -

GardenaSmartDevice

-
    - In combination with GardenaSmartBridge this FHEM Module controls the GardenaSmart Device using the GardenaCloud -

    - Once the Bridge device is created, the connected devices are automatically recognized and created in FHEM.
    - From now on the devices can be controlled and changes in the GardenaAPP are synchronized with the state and readings of the devices. - -


    - Readings -
      -
    • battery-charging - Indicator if the Battery is charged (0/1) or with newer Firmware (false/true)
    • -
    • battery-level - load percentage of the Battery
    • -
    • battery-rechargeable_battery_status - healthyness of the battery (out_of_operation/replace_now/low/ok)
    • -
    • device_info-category - category of device (mower/watering_computer)
    • -
    • device_info-last_time_online - timestamp of last radio contact
    • -
    • device_info-manufacturer - manufacturer
    • -
    • device_info-product - product type
    • -
    • device_info-serial_number - serial number
    • -
    • device_info-sgtin -
    • -
    • device_info-version - firmware version
    • -
    • firmware-firmware_command - firmware command (idle/firmware_cancel/firmware_upload/unsupported)
    • -
    • firmware-firmware_status - firmware status
    • -
    • firmware-firmware_update_start - indicator when a firmwareupload is started
    • -
    • firmware-firmware_upload_progress - progress indicator of firmware update
    • -
    • firmware-inclusion_status - inclusion status
    • -
    • internal_temperature-temperature - internal device temperature
    • -
    • mower-error - actual error message -
        -
      • no_message
      • -
      • outside_working_area
      • -
      • no_loop_signal
      • -
      • wrong_loop_signal
      • -
      • loop_sensor_problem_front
      • -
      • loop_sensor_problem_rear
      • -
      • trapped
      • -
      • upside_down
      • -
      • low_battery
      • -
      • empty_battery
      • -
      • no_drive
      • -
      • lifted
      • -
      • stuck_in_charging_station
      • -
      • charging_station_blocked
      • -
      • collision_sensor_problem_rear
      • -
      • collision_sensor_problem_front
      • -
      • wheel_motor_blocked_right
      • -
      • wheel_motor_blocked_left
      • -
      • wheel_drive_problem_right
      • -
      • wheel_drive_problem_left
      • -
      • cutting_system_blocked
      • -
      • invalid_sub_device_combination
      • -
      • settings_restored
      • -
      • electronic_problem
      • -
      • charging_system_problem
      • -
      • tilt_sensor_problem
      • -
      • wheel_motor_overloaded_right
      • -
      • wheel_motor_overloaded_left
      • -
      • charging_current_too_high
      • -
      • temporary_problem
      • -
      • guide_1_not_found
      • -
      • guide_2_not_found
      • -
      • guide_3_not_found
      • -
      • difficult_finding_home
      • -
      • guide_calibration_accomplished
      • -
      • guide_calibration_failed
      • -
      • temporary_battery_problem
      • -
      • battery_problem
      • -
      • alarm_mower_switched_off
      • -
      • alarm_mower_stopped
      • -
      • alarm_mower_lifted
      • -
      • alarm_mower_tilted
      • -
      • connection_changed
      • -
      • connection_not_changed
      • -
      • com_board_not_available
      • -
      • slipped
      • -
      -
    • -
    • mower-manual_operation - (0/1) or with newer Firmware (false/true)
    • -
    • mower-override_end_time - manual override end time
    • -
    • mower-source_for_next_start - source for the next start -
        -
      • no_source
      • -
      • mower_charging
      • -
      • completed_cutting_autotimer
      • -
      • week_timer
      • -
      • countdown_timer
      • -
      • undefined
      • -
      -
    • -
    • mower-status - mower state (see state)
    • -
    • mower-timestamp_next_start - timestamp of next scheduled start
    • -
    • radio-connection_status - state of connection
    • -
    • radio-quality - percentage of the radio quality
    • -
    • radio-state - radio state (bad/poor/good/undefined)
    • -
    • state - state of the mower -
        -
      • paused
      • -
      • ok_cutting
      • -
      • ok_searching
      • -
      • ok_charging
      • -
      • ok_leaving
      • -
      • wait_updating
      • -
      • wait_power_up
      • -
      • parked_timer
      • -
      • parked_park_selected
      • -
      • off_disabled
      • -
      • off_hatch_open
      • -
      • unknown
      • -
      • error
      • -
      • error_at_power_up
      • -
      • off_hatch_closed
      • -
      • ok_cutting_timer_overridden
      • -
      • parked_autotimer
      • -
      • parked_daily_limit_reached
      • -
      -
    • -
    -

    - - Attributes -
      -
    • readingValueLanguage - Change the Language of Readings (de,en/if not set the default is english and the global language is not set at german)
    • -
    • model -
    • -
    -

    - - set -
      -
    • parkUntilFurtherNotice
    • -
    • parkUntilNextTimer
    • -
    • startOverrideTimer - (in minutes, 60 = 1h, 1440 = 24h, 4320 = 72h)
    • -
    • startResumeSchedule
    • -
    • startpoint enable|disable 1|2|3 - enables or disables one or more predefined start points
    • -
        -
      • set NAME startpoint enable 1
      • -
      • set NAME startpoint disable 3 enable 1
      • -
      -
    -
- -=end html -=begin html_DE - - -

GardenaSmartDevice

-
    - Zusammen mit dem Device GardenaSmartDevice stellt dieses FHEM Modul die Kommunikation zwischen der GardenaCloud und Fhem her. -

    - Wenn das GardenaSmartBridge Device erzeugt wurde, werden verbundene Geräte automatisch erkannt und in Fhem angelegt.
    - Von nun an können die eingebundenen Geräte gesteuert werden. Änderungen in der APP werden mit den Readings und dem Status syncronisiert. - -
-
-
    - Readings -
      -
    • battery-charging - Ladeindikator (0/1) oder mit neuerer Firmware (false/true)
    • -
    • battery-level - Ladezustand der Batterie in Prozent
    • -
    • battery-rechargeable_battery_status - Zustand der Batterie (Ausser Betrieb/Kritischer Batteriestand, wechseln Sie jetzt/Niedrig/oK)
    • -
    • device_info-category - Eigenschaft des Gerätes (Mäher/Bewässerungscomputer/Bodensensor)
    • -
    • device_info-last_time_online - Zeitpunkt der letzten Funkübertragung
    • -
    • device_info-manufacturer - Hersteller
    • -
    • device_info-product - Produkttyp
    • -
    • device_info-serial_number - Seriennummer
    • -
    • device_info-sgtin -
    • -
    • device_info-version - Firmware Version
    • -
    • firmware-firmware_command - Firmware Kommando (Nichts zu tun/Firmwareupload unterbrochen/Firmwareupload/nicht unterstützt)
    • -
    • firmware-firmware_status - Firmware Status
    • -
    • firmware-firmware_update_start - Firmwareupdate (0/1) oder mit neuerer Firmware (false/true)
    • -
    • firmware-firmware_upload_progress - Firmwareupdatestatus in Prozent
    • -
    • firmware-inclusion_status - Einbindungsstatus
    • -
    • internal_temperature-temperature - Interne Geräte Temperatur
    • -
    • mower-error - Aktuelle Fehler Meldung -
        -
      • Kein Fehler
      • -
      • Außerhalb des Arbeitsbereichs
      • -
      • Kein Schleifensignal
      • -
      • Falsches Schleifensignal
      • -
      • Problem Schleifensensor, vorne
      • -
      • Problem Schleifensensor, hinten
      • -
      • Eingeschlossen
      • -
      • Steht auf dem Kopf
      • -
      • Niedriger Batteriestand
      • -
      • Batterie ist leer
      • -
      • Kein Antrieb
      • -
      • Angehoben
      • -
      • Eingeklemmt in Ladestation
      • -
      • Ladestation blockiert
      • -
      • Problem Stoßsensor hinten
      • -
      • Problem Stoßsensor vorne
      • -
      • Radmotor rechts blockiert
      • -
      • Radmotor links blockiert
      • -
      • Problem Antrieb, rechts
      • -
      • Problem Antrieb, links
      • -
      • Schneidsystem blockiert
      • -
      • Fehlerhafte Verbindung
      • -
      • Standardeinstellungen
      • -
      • Elektronisches Problem
      • -
      • Problem Ladesystem
      • -
      • Kippsensorproblem
      • -
      • Rechter Radmotor überlastet
      • -
      • Linker Radmotor überlastet
      • -
      • Ladestrom zu hoch
      • -
      • Vorübergehendes Problem
      • -
      • SK 1 nicht gefunden
      • -
      • SK 2 nicht gefunden
      • -
      • SK 3 nicht gefunden
      • -
      • Problem die Ladestation zu finden
      • -
      • Kalibration des Suchkabels beendet
      • -
      • Kalibration des Suchkabels fehlgeschlagen
      • -
      • Kurzzeitiges Batterieproblem
      • -
      • Batterieproblem
      • -
      • Alarm! Mäher ausgeschalten
      • -
      • Alarm! Mäher gestoppt
      • -
      • Alarm! Mäher angehoben
      • -
      • Alarm! Mäher gekippt
      • -
      • Verbindung geändert
      • -
      • Verbindung nicht geändert
      • -
      • COM board nicht verfügbar
      • -
      • Rutscht
      • -
      -
    • -
    • mower-manual_operation - Manueller Betrieb (0/1) oder mit neuerer Firmware (false/true)
    • -
    • mower-override_end_time - Zeitpunkt wann der manuelle Betrieb beendet ist
    • -
    • mower-source_for_next_start - Grund für den nächsten Start -
        -
      • Kein Grund
      • -
      • Mäher wurde geladen
      • -
      • SensorControl erreicht
      • -
      • Wochentimer erreicht
      • -
      • Stoppuhr Timer
      • -
      • Undefiniert
      • -
      -
    • -
    • mower-status - Mäher Status (siehe state)
    • -
    • mower-timestamp_next_start - Zeitpunkt des nächsten geplanten Starts
    • -
    • radio-connection_status - Status der Funkverbindung
    • -
    • radio-quality - Indikator für die Funkverbindung in Prozent
    • -
    • radio-state - radio state (schlecht/schwach/gut/Undefiniert)
    • -
    • state - Staus des Mähers -
        -
      • Pausiert
      • -
      • Mähen
      • -
      • Suche Ladestation
      • -
      • Lädt
      • -
      • Mähen
      • -
      • Wird aktualisiert ...
      • -
      • Wird eingeschaltet ...
      • -
      • Geparkt nach Zeitplan
      • -
      • Geparkt
      • -
      • Der Mäher ist ausgeschaltet
      • -
      • Deaktiviert. Abdeckung ist offen oder PIN-Code erforderlich
      • -
      • Unbekannter Status
      • -
      • Fehler
      • -
      • Neustart ...
      • -
      • Deaktiviert. Manueller Start erforderlich
      • -
      • Manuelles Mähen
      • -
      • Geparkt durch SensorControl
      • -
      • Abgeschlossen
      • -
      -
    • -
    -

    - - Attribute -
      -
    • readingValueLanguage - Änderung der Sprache der Readings (de,en/wenn nichts gesetzt ist, dann Englisch es sei denn deutsch ist als globale Sprache gesetzt)
    • -
    • model -
    • -
    - - set -
      -
    • parkUntilFurtherNotice - Parken des Mähers unter Umgehung des Zeitplans
    • -
    • parkUntilNextTimer - Parken bis zum nächsten Zeitplan
    • -
    • startOverrideTimer - Manuelles mähen (in Minuten, 60 = 1h, 1440 = 24h, 4320 = 72h)
    • -
    • startResumeSchedule - Weiterführung des Zeitplans
    • -
    • startpoint enable|disable 1|2|3 - Aktiviert oder deaktiviert einen vordefinierten Startbereich
    • -
        -
      • set NAME startpoint enable 1
      • -
      • set NAME startpoint disable 3 enable 1
      • -
      -
    -
- -=end html_DE - -=for :application/json;q=META.json 74_GardenaSmartDevice.pm -{ - "abstract": "Modul to control GardenaSmart Devices", - "x_lang": { - "de": { - "abstract": "Modul zur Steuerung von Gardena Smart Geräten" - } - }, - "keywords": [ - "fhem-mod-device", - "fhem-core", - "Garden", - "Gardena", - "Smart" - ], - "release_status": "stable", - "license": "GPL_2", - "version": "v2.2.3", - "author": [ - "Marko Oldenburg " - ], - "x_fhem_maintainer": [ - "CoolTux" - ], - "x_fhem_maintainer_github": [ - "LeonGaultier" - ], - "prereqs": { - "runtime": { - "requires": { - "FHEM": 5.00918799, - "perl": 5.016, - "Meta": 0, - "JSON": 0, - "Time::Local": 0 - }, - "recommends": { - }, - "suggests": { - } - } - } -} -=end :application/json;q=META.json - -=cut +############################################################################### +# +# Developed with Kate +# +# (c) 2017-2021 Copyright: Marko Oldenburg (fhemdevelopment at cooltux dot net) +# All rights reserved +# +# Special thanks goes to comitters: +# - Michael (mbrak) Thanks for Commandref +# - Matthias (Kenneth) Thanks for Wiki entry +# - BioS Thanks for predefined start points Code +# - fettgu Thanks for Debugging Irrigation Control data flow +# - Sebastian (BOFH) Thanks for new Auth Code after API Change +# +# +# 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/ +# +## +## + +## unserer packagename +package FHEM::GardenaSmartDevice; +use GPUtils qw(GP_Import GP_Export); + +use strict; +use warnings; +use POSIX; +use FHEM::Meta; +use Time::Local; + +# try to use JSON::MaybeXS wrapper +# for chance of better performance + open code +eval { + require JSON::MaybeXS; + import JSON::MaybeXS qw( decode_json encode_json ); + 1; +}; + +if ($@) { + $@ = undef; + + # try to use JSON wrapper + # for chance of better performance + eval { + + # JSON preference order + local $ENV{PERL_JSON_BACKEND} = + 'Cpanel::JSON::XS,JSON::XS,JSON::PP,JSON::backportPP' + unless ( defined( $ENV{PERL_JSON_BACKEND} ) ); + + require JSON; + import JSON qw( decode_json encode_json ); + 1; + }; + + if ($@) { + $@ = undef; + + # In rare cases, Cpanel::JSON::XS may + # be installed but JSON|JSON::MaybeXS not ... + eval { + require Cpanel::JSON::XS; + import Cpanel::JSON::XS qw(decode_json encode_json); + 1; + }; + + if ($@) { + $@ = undef; + + # In rare cases, JSON::XS may + # be installed but JSON not ... + eval { + require JSON::XS; + import JSON::XS qw(decode_json encode_json); + 1; + }; + + if ($@) { + $@ = undef; + + # Fallback to built-in JSON which SHOULD + # be available since 5.014 ... + eval { + require JSON::PP; + import JSON::PP qw(decode_json encode_json); + 1; + }; + + if ($@) { + $@ = undef; + + # Fallback to JSON::backportPP in really rare cases + require JSON::backportPP; + import JSON::backportPP qw(decode_json encode_json); + 1; + } + } + } + } +} + +## Import der FHEM Funktionen +#-- Run before package compilation +BEGIN { + + # Import from main context + GP_Import( + qw(readingsSingleUpdate + readingsBulkUpdate + readingsBulkUpdateIfChanged + readingsBeginUpdate + readingsEndUpdate + Log3 + CommandAttr + AttrVal + ReadingsVal + readingFnAttributes + AssignIoPort + modules + IOWrite + defs + makeDeviceName) + ); +} + +#-- Export to main context with different name +GP_Export( + qw( + Initialize + ) +); + +sub Initialize { + my $hash = shift; + + $hash->{Match} = '^{"id":".*'; + + $hash->{SetFn} = \&Set; + $hash->{DefFn} = \&Define; + $hash->{UndefFn} = \&Undef; + $hash->{ParseFn} = \&Parse; + + $hash->{AttrFn} = \&Attr; + $hash->{AttrList} = + "readingValueLanguage:de,en " + . "model:watering_computer,sensor,mower,ic24,power,electronic_pressure_pump " + . "IODev " + . $readingFnAttributes; + $hash->{parseParams} = 1; + + return FHEM::Meta::InitMod( __FILE__, $hash ); +} + +sub Define { + my $hash = shift // return; + my $aArg = shift // return; + + return $@ unless ( FHEM::Meta::SetInternals($hash) ); + use version 0.60; our $VERSION = FHEM::Meta::Get( $hash, 'version' ); + + return + "too few parameters: define GardenaSmartDevice " + if ( scalar( @{$aArg} ) < 3 ); + + my $name = $aArg->[0]; + my $deviceId = $aArg->[2]; + my $category = $aArg->[3]; + + $hash->{DEVICEID} = $deviceId; + $hash->{VERSION} = version->parse($VERSION)->normal; + $hash->{helper}{STARTINGPOINTID} = ''; + $hash->{helper}{schedules_paused_until_id} = ''; + $hash->{helper}{eco_mode_id} = ''; + # IrrigationControl valve control max 6 + $hash->{helper}{schedules_paused_until_1_id} = ''; + $hash->{helper}{schedules_paused_until_2_id} = ''; + $hash->{helper}{schedules_paused_until_3_id} = ''; + $hash->{helper}{schedules_paused_until_4_id} = ''; + $hash->{helper}{schedules_paused_until_5_id} = ''; + $hash->{helper}{schedules_paused_until_6_id} = ''; + + CommandAttr( undef, + "$name IODev $modules{GardenaSmartBridge}{defptr}{BRIDGE}->{NAME}" ) + if ( AttrVal( $name, 'IODev', 'none' ) eq 'none' ); + + my $iodev = AttrVal( $name, 'IODev', 'none' ); + + 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 ); + + CommandAttr( undef, $name . ' room GardenaSmart' ) + if ( AttrVal( $name, 'room', 'none' ) eq 'none' ); + + CommandAttr( undef, $name . ' model ' . $category ) + if ( AttrVal( $name, 'model', 'none' ) eq 'none' ); + + Log3 $name, 3, +"GardenaSmartDevice ($name) - defined GardenaSmartDevice with DEVICEID: $deviceId"; + readingsSingleUpdate( $hash, 'state', 'initialized', 1 ); + + $modules{GardenaSmartDevice}{defptr}{$deviceId} = $hash; + + return; +} + +sub Undef { + my $hash = shift; + my $arg = shift; + + my $name = $hash->{NAME}; + my $deviceId = $hash->{DEVICEID}; + + delete $modules{GardenaSmartDevice}{defptr}{$deviceId}; + + return; +} + +sub Attr { + + my ( $cmd, $name, $attrName, $attrVal ) = @_; + my $hash = $defs{$name}; + + return; +} + +sub Set { + my $hash = shift // return; + my $aArg = shift // return; + + my $name = shift @$aArg; + 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 ); + + #set default abilitie ... overwrite in cmd to change + $abilities = 'mower' + if ( AttrVal( $name, 'model', 'unknown' ) eq 'mower' ); + $abilities = 'watering' + if ( AttrVal( $name, 'model', 'unknown' ) eq 'ic24' + || AttrVal( $name, 'model', 'unknown' ) eq 'watering_computer' ); + $abilities = 'power' + if ( AttrVal( $name, 'model', 'unknown' ) eq 'power' ); + $abilities = 'manual_watering' + if ( AttrVal( $name, 'model', 'unknown' ) eq 'electronic_pressure_pump' ); + + ### mower + # service_id (eco, parkuntilfurhternotice, startpoints) + if ( lc $cmd eq 'parkuntilfurthernotice' ) { + $payload = '"name":"park_until_further_notice"'; + if ( $mainboard_version > 10.30 ) { + $payload = ' "settings":{"name":"schedules_paused_until","value":"2040-12-31T22: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}' ; + $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' ; + $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 .'}'; + $abilities = 'mower_timer'; + } + + } + elsif ( lc $cmd eq 'startpoint' ) { + my $err; + ( $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}.'"}'; + $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 ); + } + ### electronic_pressure_pump + elsif ( lc $cmd eq 'pumptimer' ) { + $payload = + '"name":"pump_manual_watering_timer","parameters":{"duration":' + . $aArg->[0] . '}'; + } + ### watering_computer + elsif ( lc $cmd eq 'manualoverride' ) { + $payload = + '"properties":{"name":"watering_timer_1' + . '","value":{"state":"manual","duration":' + . $aArg->[0] * 60 + . ',"valve_id":1}}'; + } + elsif ( $cmd =~ m{\AcancelOverride}xms ) { + + my $valve_id = 1; + + if ( $cmd =~ m{\AcancelOverrideValve(\d)\z}xms ) { + $valve_id = $1; + } + + $payload = + '"properties":{"name":"watering_timer_' + . $valve_id + . '","value":{"state":"idle","duration":' + . 0 + . ',"valve_id":' + . $valve_id . '}}'; + } + elsif ( lc $cmd eq 'on' || lc $cmd eq 'off' || lc $cmd eq 'on-for-timer' ) { + my $val = ( + defined($aArg) && ref($aArg) eq 'ARRAY' + ? $aArg->[0] * 60 + : lc $cmd + ); + + $payload = '"properties":{"value":"' . $val . '"}'; + } + ### Watering ic24 + elsif ( $cmd =~ m{\AmanualDurationValve\d\z}xms ) { + my $valve_id; + + if ( $cmd =~ m{\AmanualDurationValve(\d)\z}xms ) { + $valve_id = $1; + } + + $payload = + '"properties":{"name":"watering_timer_' + . $valve_id + . '","value":{"state":"manual","duration":' + . $aArg->[0] * 60 + . ',"valve_id":' + . $valve_id . '}}'; + } + elsif ( $cmd eq 'closeAllValves' ){ + $payload = '"name":"close_all_valves","parameters":{}'; + } + elsif ( $cmd =~ '/.*ScheduleValve/' ){ + my $valve_id = $1; + $abilities = 'irrigation_settings'; + $service_id = $hash->{helper}->{'schedules_paused_until_'.$valve_id.'_id'}; + $payload = '"settings":{"name":"schedules_paused_until_' + .$valve_id. + '", "value":"' + . ($cmd eq 'resumeScheduleValve') ? '' : '2040-12-31T22:00:00.000Z' + .'","device":"' + .$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') { + $payload = '"name":"measure_ambient_temperature"'; + $abilities = 'ambient_temperature'; + } else { + $payload = '"name":"measure_soil_temperature"'; + $abilities = 'soil_temperature'; + } + } + elsif ( lc $sensname eq 'light' ) { + $payload = '"name":"measure_light"'; + $abilities = 'light'; + + } + elsif ( lc $sensname eq 'humidity' ) { + $payload = '"name":"measure_soil_humidity"'; + $abilities = 'humidity'; + } + + + } + else { + + my $list = ''; + + $list .= +'parkUntilFurtherNotice:noArg parkUntilNextTimer:noArg startResumeSchedule:noArg startOverrideTimer:slider,0,1,60 startpoint' + if ( AttrVal( $name, 'model', 'unknown' ) eq 'mower' ); + + $list .= 'manualOverride:slider,1,1,59 cancelOverride:noArg' + if ( AttrVal( $name, 'model', 'unknown' ) eq 'watering_computer' ); + + $list .= +'closeAllValves:noArg stopScheduleValve:selectnumbers,1,1,6,0,lin resumeScheduleValve:selectnumbers,1,1,6,0,lin manualDurationValve1:slider,1,1,59 manualDurationValve2:slider,1,1,59 manualDurationValve3:slider,1,1,59 manualDurationValve4:slider,1,1,59 manualDurationValve5:slider,1,1,59 manualDurationValve6:slider,1,1,59 cancelOverrideValve1:noArg cancelOverrideValve2:noArg cancelOverrideValve3:noArg cancelOverrideValve4:noArg cancelOverrideValve5:noArg cancelOverrideValve6:noArg' + if ( AttrVal( $name, 'model', 'unknown' ) eq 'ic24' ); + + $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' ); + + $list .= 'on:noArg off:noArg on-for-timer:slider,0,1,60' + if ( AttrVal( $name, 'model', 'unknown' ) eq 'power' ); + + return "Unknown argument $cmd, choose one of $list"; + } + + $hash->{helper}{deviceAction} = $payload; + readingsSingleUpdate( $hash, "state", "send command to gardena cloud", 1 ); + + IOWrite( $hash, $payload, $hash->{DEVICEID}, $abilities, $service_id ); + Log3 $name, 4, +"GardenaSmartBridge ($name) - IOWrite: $payload $hash->{DEVICEID} $abilities IODevHash=$hash->{IODev}"; + + return; +} + +sub Parse { + my $io_hash = shift; + my $json = shift; + + my $name = $io_hash->{NAME}; + + my $decode_json = eval { decode_json($json) }; + if ($@) { + Log3 $name, 3, + "GardenaSmartDevice ($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}; + + WriteReadings( $hash, $decode_json ); + Log3 $name, 4, + "GardenaSmartDevice ($name) - find logical device: $hash->{NAME}"; + + return $hash->{NAME}; + + } + else { + + Log3 $name, 3, + "GardenaSmartDevice ($name) - autocreate new device " + . makeDeviceName( $decode_json->{name} ) + . " with deviceId $decode_json->{id}, model $decode_json->{category}"; + return + "UNDEFINED " + . makeDeviceName( $decode_json->{name} ) + . " GardenaSmartDevice $decode_json->{id} $decode_json->{category}"; + } + } + + return; +} + +sub WriteReadings { + my $hash = shift; + my $decode_json = shift; + + my $name = $hash->{NAME}; + my $abilities = scalar( @{ $decode_json->{abilities} } ); + my $settings = scalar( @{ $decode_json->{settings} } ); + + readingsBeginUpdate($hash); + + do { + + if ( + ref( $decode_json->{abilities}[$abilities]{properties} ) eq "ARRAY" + && scalar( @{ $decode_json->{abilities}[$abilities]{properties} } ) + > 0 ) + { + for my $propertie ( + @{ $decode_json->{abilities}[$abilities]{properties} } ) + { + readingsBulkUpdateIfChanged( + $hash, + $decode_json->{abilities}[$abilities]{name} . '-' + . $propertie->{name}, + RigReadingsValue( $hash, $propertie->{value} ) + ) + if ( defined( $propertie->{value} ) + && $decode_json->{abilities}[$abilities]{name} . '-' + . $propertie->{name} ne 'radio-quality' + && $decode_json->{abilities}[$abilities]{name} . '-' + . $propertie->{name} ne 'battery-level' + && $decode_json->{abilities}[$abilities]{name} . '-' + . $propertie->{name} ne 'internal_temperature-temperature' + && $decode_json->{abilities}[$abilities]{name} . '-' + . $propertie->{name} ne 'ambient_temperature-temperature' + && $decode_json->{abilities}[$abilities]{name} . '-' + . $propertie->{name} ne 'soil_temperature-temperature' + && $decode_json->{abilities}[$abilities]{name} . '-' + . $propertie->{name} ne 'humidity-humidity' + && $decode_json->{abilities}[$abilities]{name} . '-' + . $propertie->{name} ne 'light-light' + && ref( $propertie->{value} ) ne "HASH" ); + + readingsBulkUpdateIfChanged( + $hash, + $decode_json->{abilities}[$abilities]{name} . '-' + . $propertie->{name}, + RigReadingsValue( $hash, $propertie->{value} ) + ) + if ( + defined( $propertie->{value} ) + && ( $decode_json->{abilities}[$abilities]{name} . '-' + . $propertie->{name} eq 'radio-quality' + || $decode_json->{abilities}[$abilities]{name} . '-' + . $propertie->{name} eq 'battery-level' + || $decode_json->{abilities}[$abilities]{name} . '-' + . $propertie->{name} eq + 'internal_temperature-temperature' + || $decode_json->{abilities}[$abilities]{name} . '-' + . $propertie->{name} eq + 'ambient_temperature-temperature' + || $decode_json->{abilities}[$abilities]{name} . '-' + . $propertie->{name} eq 'soil_temperature-temperature' + || $decode_json->{abilities}[$abilities]{name} . '-' + . $propertie->{name} eq 'humidity-humidity' + || $decode_json->{abilities}[$abilities]{name} . '-' + . $propertie->{name} eq 'light-light' ) + ); + + readingsBulkUpdateIfChanged( + $hash, + $decode_json->{abilities}[$abilities]{name} . '-' + . $propertie->{name}, + join( ',', @{ $propertie->{value} } ) + ) + if ( defined( $propertie->{value} ) + && $decode_json->{abilities}[$abilities]{name} . '-' + . $propertie->{name} eq 'ic24-valves_connected' ); + + readingsBulkUpdateIfChanged( + $hash, + $decode_json->{abilities}[$abilities]{name} . '-' + . $propertie->{name}, + join( ',', @{ $propertie->{value} } ) + ) + if ( defined( $propertie->{value} ) + && $decode_json->{abilities}[$abilities]{name} . '-' + . $propertie->{name} eq 'ic24-valves_master_config' ); + + if ( ref( $propertie->{value} ) eq "HASH" ) { + while ( my ( $r, $v ) = each %{ $propertie->{value} } ) { + readingsBulkUpdate( + $hash, + $decode_json->{abilities}[$abilities]{name} . '-' + . $propertie->{name} . '_' + . $r, + RigReadingsValue( $hash, $v ) + ); + } + } + } + } + + $abilities--; + } 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"); + + if ( exists($decode_json->{settings}[$settings]{name}) + && ( + $decode_json->{settings}[$settings]{name} =~ /schedules_paused_until_?\d?$/ + || $decode_json->{settings}[$settings]{name} eq 'eco_mode' ) + ) + { + if ( $hash->{helper}{$decode_json->{settings}[$settings]{name}.'_id'} ne + $decode_json->{settings}[$settings]{id} ) + { + $hash->{helper}{$decode_json->{settings}[$settings]{name}.'_id'} = + $decode_json->{settings}[$settings]{id}; + } + } + + if ( ref( $decode_json->{settings}[$settings]{value} ) eq "ARRAY" + && $decode_json->{settings}[$settings]{name} eq 'starting_points' ) + { + #save the startingpointid needed to update the startingpoints + if ( $hash->{helper}{STARTINGPOINTID} ne + $decode_json->{settings}[$settings]{id} ) + { + $hash->{helper}{STARTINGPOINTID} = + $decode_json->{settings}[$settings]{id}; + } + + $hash->{helper}{STARTINGPOINTS} = + '{ "name": "starting_points", "value": ' + . encode_json( $decode_json->{settings}[$settings]{value} ) . '}'; + my $startpoint_cnt = 0; + + for my $startingpoint ( + @{ $decode_json->{settings}[$settings]{value} } ) + { + $startpoint_cnt++; + readingsBulkUpdateIfChanged( + $hash, + 'startpoint-' . $startpoint_cnt . '-enabled', + $startingpoint->{enabled} + ); + } + } + + $settings--; + } while ( $settings >= 0 ); + + + my $online_state = ReadingsVal($name , 'device_info-connection_status', 'unknown'); + + readingsBulkUpdate( $hash, 'state', + $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 + ? 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'); + + # 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( + $hash, 'state', + 'scheduled watering next start: ' + . ( + ReadingsVal( + $name, 'scheduling-scheduled_watering_next_start', + 'no timer' + ) + ) + ) if ( AttrVal( $name, 'model', 'unknown' ) eq 'ic24' ); + + readingsBulkUpdate( $hash, 'state', + ReadingsVal( $name, 'power-power_timer', 'no info from power-timer' ) ) + if ( AttrVal( $name, 'model', 'unknown' ) eq 'power' ); + + readingsEndUpdate( $hash, 1 ); + + Log3 $name, 4, "GardenaSmartDevice ($name) - readings was written"; + + return; +} + +################################## +################################## +#### my little helpers ########### + +sub ReadingLangGerman { + my $hash = shift; + my $readingValue = shift; + + my $name = $hash->{NAME}; + my %langGermanMapp = ( + 'ok_cutting' => 'mähen', + 'paused' => 'pausiert', + 'ok_searching' => 'suche Ladestation', + 'ok_charging' => 'lädt', + 'ok_leaving' => 'unterwegs zum Startpunkt', + 'wait_updating' => 'wird aktualisiert ...', + 'wait_power_up' => 'wird eingeschaltet ...', + 'parked_timer' => 'geparkt nach Zeitplan', + 'parked_park_selected' => 'geparkt', + 'off_disabled' => 'der Mäher ist ausgeschaltet', + 'off_hatch_open' => + 'deaktiviert. Abdeckung ist offen oder PIN-Code erforderlich', + 'unknown' => 'unbekannter Status', + 'error' => 'Fehler', + 'error_at_power_up' => 'Neustart ...', + 'off_hatch_closed' => 'Deaktiviert. Manueller Start erforderlich', + 'ok_cutting_timer_overridden' => 'manuelles mähen', + 'parked_autotimer' => 'geparkt durch SensorControl', + 'parked_daily_limit_reached' => 'abgeschlossen', + 'no_message' => 'kein Fehler', + 'outside_working_area' => 'außerhalb des Arbeitsbereichs', + 'no_loop_signal' => 'kein Schleifensignal', + 'wrong_loop_signal' => 'falsches Schleifensignal', + 'loop_sensor_problem_front' => 'Problem Schleifensensor, vorne', + 'loop_sensor_problem_rear' => 'Problem Schleifensensor, hinten', + 'trapped' => 'eingeschlossen', + 'upside_down' => 'steht auf dem Kopf', + 'low_battery' => 'niedriger Batteriestand', + 'empty_battery' => 'Batterie leer', + 'no_drive' => 'fährt nicht', + 'lifted' => 'angehoben', + 'stuck_in_charging_station' => 'eingeklemmt in Ladestation', + 'charging_station_blocked' => 'Ladestation blockiert', + 'collision_sensor_problem_rear' => 'Problem Stoßsensor hinten', + 'collision_sensor_problem_front' => 'Problem Stoßsensor vorne', + 'wheel_motor_blocked_right' => 'Radmotor rechts blockiert', + 'wheel_motor_blocked_left' => 'Radmotor links blockiert', + 'wheel_drive_problem_right' => 'Problem Antrieb, rechts', + 'wheel_drive_problem_left' => 'Problem Antrieb, links', + 'cutting_system_blocked' => 'Schneidsystem blockiert', + 'invalid_sub_device_combination' => 'fehlerhafte Verbindung', + 'settings_restored' => 'Standardeinstellungen', + 'electronic_problem' => 'elektronisches Problem', + 'charging_system_problem' => 'Problem Ladesystem', + 'tilt_sensor_problem' => 'Kippsensor Problem', + 'wheel_motor_overloaded_right' => 'rechter Radmotor überlastet', + 'wheel_motor_overloaded_left' => 'linker Radmotor überlastet', + 'charging_current_too_high' => 'Ladestrom zu hoch', + 'temporary_problem' => 'vorübergehendes Problem', + 'guide_1_not_found' => 'SK 1 nicht gefunden', + 'guide_2_not_found' => 'SK 2 nicht gefunden', + 'guide_3_not_found' => 'SK 3 nicht gefunden', + 'difficult_finding_home' => 'Problem die Ladestation zu finden', + 'guide_calibration_accomplished' => + 'Kalibrierung des Suchkabels beendet', + 'guide_calibration_failed' => + 'Kalibrierung des Suchkabels fehlgeschlagen', + 'temporary_battery_problem' => 'kurzzeitiges Batterieproblem', + 'battery_problem' => 'Batterieproblem', + 'alarm_mower_switched_off' => 'Alarm! Mäher ausgeschalten', + 'alarm_mower_stopped' => 'Alarm! Mäher gestoppt', + 'alarm_mower_lifted' => 'Alarm! Mäher angehoben', + 'alarm_mower_tilted' => 'Alarm! Mäher gekippt', + 'connection_changed' => 'Verbindung geändert', + 'connection_not_changed' => 'Verbindung nicht geändert', + 'com_board_not_available' => 'COM Board nicht verfügbar', + 'slipped' => 'rutscht', + 'out_of_operation' => 'ausser Betrieb', + 'replace_now' => 'kritischer Batteriestand, wechseln Sie jetzt', + 'low' => 'niedrig', + 'ok' => 'ok', + 'no_source' => 'ok', + 'mower_charging' => 'Mäher wurde geladen', + 'completed_cutting_autotimer' => 'Sensor Control erreicht', + 'week_timer' => 'Wochentimer erreicht', + 'countdown_timer' => 'Stoppuhr Timer', + 'undefined' => 'unklar', + 'unknown' => 'unklar', + 'status_device_unreachable' => 'Gerät ist nicht in Reichweite', + 'status_device_alive' => 'Gerät ist in Reichweite', + 'bad' => 'schlecht', + 'poor' => 'schwach', + 'good' => 'gut', + 'undefined' => 'unklar', + 'idle' => 'nichts zu tun', + 'firmware_cancel' => 'Firmwareupload unterbrochen', + 'firmware_upload' => 'Firmwareupload', + 'unsupported' => 'nicht unterstützt', + 'up_to_date' => 'auf dem neusten Stand', + 'mower' => 'Mäher', + 'watering_computer' => 'Bewässerungscomputer', + 'no_frost' => 'kein Frost', + 'open' => 'offen', + 'closed' => 'geschlossen', + 'included' => 'inbegriffen', + 'active' => 'aktiv', + 'inactive' => 'nicht aktiv' + ); + + if ( + defined( $langGermanMapp{$readingValue} ) + && ( AttrVal( 'global', 'language', 'none' ) eq 'DE' + || AttrVal( $name, 'readingValueLanguage', 'none' ) eq 'de' ) + && AttrVal( $name, 'readingValueLanguage', 'none' ) ne 'en' + ) + { + return $langGermanMapp{$readingValue}; + } + else { + return $readingValue; + } + + return; +} + +sub RigReadingsValue { + my $hash = shift; + my $readingValue = shift; + + my $rigReadingValue; + + if ( $readingValue =~ /^(\d+)-(\d\d)-(\d\d)T(\d\d)/ ) { + $rigReadingValue = Zulu2LocalString($readingValue); + } + else { + $rigReadingValue = ReadingLangGerman( $hash, $readingValue ); + } + + return $rigReadingValue; +} + +sub Zulu2LocalString { + my $t = shift; + + my ( $datehour, $datemin, $rest ) = split( /:/, $t, 3 ); + + my ( $year, $month, $day, $hour, $min ) = + $datehour =~ /(\d+)-(\d\d)-(\d\d)T(\d\d)/; + my $epoch = timegm( 0, 0, $hour, $day, $month - 1, $year ); + + my ( $lyear, $lmonth, $lday, $lhour, $isdst ) = + ( localtime($epoch) )[ 5, 4, 3, 2, -1 ]; + + $lyear += 1900; # year is 1900 based + $lmonth++; # month number is zero based + + if ( defined($rest) ) { + return ( + sprintf( + "%04d-%02d-%02d %02d:%02d:%s", + $lyear, $lmonth, $lday, + $lhour, $datemin, substr( $rest, 0, 2 ) + ) + ); + } + elsif ( $lyear < 2000 ) { + return 'temporarily unavailable'; + } + else { + return ( + sprintf( + "%04d-%02d-%02d %02d:%02d", + $lyear, $lmonth, $lday, $lhour, substr( $datemin, 0, 2 ) + ) + ); + } + + return; +} + +sub SetPredefinedStartPoints { + my $hash = shift; + my $aArg = shift; + + my ($startpoint_state,$startpoint_num,@morestartpoints) = @{$aArg}; + + my $name = $hash->{NAME}; + my $payload; + my $abilities; + + if ( defined($startpoint_state) && defined($startpoint_num) ) { + if ( defined( $hash->{helper}{STARTINGPOINTS} ) + && $hash->{helper}{STARTINGPOINTS} ne '' ) + { +# add needed parameters to saved settings config and change the value in request + my $decode_json_settings = + eval { decode_json( $hash->{helper}{STARTINGPOINTS} ) }; + if ($@) { + Log3 $name, 3, +"GardenaSmartBridge ($name) - JSON error while setting startpoint: $@"; + } + + $decode_json_settings->{device} = $hash->{DEVICEID}; + my $setval = $startpoint_state eq 'disable' ? \0 : \1; + $decode_json_settings->{value}[ $startpoint_num - 1 ]{enabled} = + $setval; + + #set more startpoints + if ( + defined scalar(@morestartpoints) + && ( scalar(@morestartpoints) == 2 + || scalar(@morestartpoints) == 4 ) + ) + { + if ( scalar(@morestartpoints) == 2 ) { + $setval = $morestartpoints[0] eq 'disable' ? \0 : \1; + $decode_json_settings->{value}[ $morestartpoints[1] - 1 ] + {enabled} = $setval; + + } + elsif ( scalar(@morestartpoints) == 4 ) { + $setval = $morestartpoints[0] eq 'disable' ? \0 : \1; + $decode_json_settings->{value}[ $morestartpoints[1] - 1 ] + {enabled} = $setval; + $setval = $morestartpoints[2] eq 'disable' ? \0 : \1; + $decode_json_settings->{value}[ $morestartpoints[3] - 1 ] + {enabled} = $setval; + } + } + + $payload = '"settings": ' . encode_json($decode_json_settings); + $abilities = 'mower_settings'; + #$abilities['service_id'] = $hash->{helper}{STARTINGPOINTID}; + } + else { + return + "startingpoints not loaded yet, please wait a couple of minutes", + undef, undef; + } + } + else { + return + "startpoint usage: set " + . $hash->{NAME} + . " startpoint disable 1 [enable 2] [disable 3]", undef, undef; + } + + return undef, $payload, $abilities; +} + +1; + +=pod + +=item device +=item summary Modul to control GardenaSmart Devices +=item summary_DE Modul zur Steuerung von GardenaSmartgeräten + +=begin html + + +

GardenaSmartDevice

+
    + In combination with GardenaSmartBridge this FHEM Module controls the GardenaSmart Device using the GardenaCloud +

    + Once the Bridge device is created, the connected devices are automatically recognized and created in FHEM.
    + From now on the devices can be controlled and changes in the GardenaAPP are synchronized with the state and readings of the devices. + +


    + Readings +
      +
    • battery-charging - Indicator if the Battery is charged (0/1) or with newer Firmware (false/true)
    • +
    • battery-level - load percentage of the Battery
    • +
    • battery-rechargeable_battery_status - healthyness of the battery (out_of_operation/replace_now/low/ok)
    • +
    • device_info-category - category of device (mower/watering_computer)
    • +
    • device_info-last_time_online - timestamp of last radio contact
    • +
    • device_info-manufacturer - manufacturer
    • +
    • device_info-product - product type
    • +
    • device_info-serial_number - serial number
    • +
    • device_info-sgtin -
    • +
    • device_info-version - firmware version
    • +
    • firmware-firmware_command - firmware command (idle/firmware_cancel/firmware_upload/unsupported)
    • +
    • firmware-firmware_status - firmware status
    • +
    • firmware-firmware_update_start - indicator when a firmwareupload is started
    • +
    • firmware-firmware_upload_progress - progress indicator of firmware update
    • +
    • firmware-inclusion_status - inclusion status
    • +
    • internal_temperature-temperature - internal device temperature
    • +
    • mower-error - actual error message +
        +
      • no_message
      • +
      • outside_working_area
      • +
      • no_loop_signal
      • +
      • wrong_loop_signal
      • +
      • loop_sensor_problem_front
      • +
      • loop_sensor_problem_rear
      • +
      • trapped
      • +
      • upside_down
      • +
      • low_battery
      • +
      • empty_battery
      • +
      • no_drive
      • +
      • lifted
      • +
      • stuck_in_charging_station
      • +
      • charging_station_blocked
      • +
      • collision_sensor_problem_rear
      • +
      • collision_sensor_problem_front
      • +
      • wheel_motor_blocked_right
      • +
      • wheel_motor_blocked_left
      • +
      • wheel_drive_problem_right
      • +
      • wheel_drive_problem_left
      • +
      • cutting_system_blocked
      • +
      • invalid_sub_device_combination
      • +
      • settings_restored
      • +
      • electronic_problem
      • +
      • charging_system_problem
      • +
      • tilt_sensor_problem
      • +
      • wheel_motor_overloaded_right
      • +
      • wheel_motor_overloaded_left
      • +
      • charging_current_too_high
      • +
      • temporary_problem
      • +
      • guide_1_not_found
      • +
      • guide_2_not_found
      • +
      • guide_3_not_found
      • +
      • difficult_finding_home
      • +
      • guide_calibration_accomplished
      • +
      • guide_calibration_failed
      • +
      • temporary_battery_problem
      • +
      • battery_problem
      • +
      • alarm_mower_switched_off
      • +
      • alarm_mower_stopped
      • +
      • alarm_mower_lifted
      • +
      • alarm_mower_tilted
      • +
      • connection_changed
      • +
      • connection_not_changed
      • +
      • com_board_not_available
      • +
      • slipped
      • +
      +
    • +
    • mower-manual_operation - (0/1) or with newer Firmware (false/true)
    • +
    • mower-override_end_time - manual override end time
    • +
    • mower-source_for_next_start - source for the next start +
        +
      • no_source
      • +
      • mower_charging
      • +
      • completed_cutting_autotimer
      • +
      • week_timer
      • +
      • countdown_timer
      • +
      • undefined
      • +
      +
    • +
    • mower-status - mower state (see state)
    • +
    • mower-timestamp_next_start - timestamp of next scheduled start
    • +
    • radio-connection_status - state of connection
    • +
    • radio-quality - percentage of the radio quality
    • +
    • radio-state - radio state (bad/poor/good/undefined)
    • +
    • state - state of the mower +
        +
      • paused
      • +
      • ok_cutting
      • +
      • ok_searching
      • +
      • ok_charging
      • +
      • ok_leaving
      • +
      • wait_updating
      • +
      • wait_power_up
      • +
      • parked_timer
      • +
      • parked_park_selected
      • +
      • off_disabled
      • +
      • off_hatch_open
      • +
      • unknown
      • +
      • error
      • +
      • error_at_power_up
      • +
      • off_hatch_closed
      • +
      • ok_cutting_timer_overridden
      • +
      • parked_autotimer
      • +
      • parked_daily_limit_reached
      • +
      +
    • +
    +

    + + Attributes +
      +
    • readingValueLanguage - Change the Language of Readings (de,en/if not set the default is english and the global language is not set at german)
    • +
    • model -
    • +
    +

    + + set +
      +
    • parkUntilFurtherNotice
    • +
    • parkUntilNextTimer
    • +
    • startOverrideTimer - (in minutes, 60 = 1h, 1440 = 24h, 4320 = 72h)
    • +
    • startResumeSchedule
    • +
    • startpoint enable|disable 1|2|3 - enables or disables one or more predefined start points
    • +
        +
      • set NAME startpoint enable 1
      • +
      • set NAME startpoint disable 3 enable 1
      • +
      +
    +
+ +=end html +=begin html_DE + + +

GardenaSmartDevice

+
    + Zusammen mit dem Device GardenaSmartDevice stellt dieses FHEM Modul die Kommunikation zwischen der GardenaCloud und Fhem her. +

    + Wenn das GardenaSmartBridge Device erzeugt wurde, werden verbundene Geräte automatisch erkannt und in Fhem angelegt.
    + Von nun an können die eingebundenen Geräte gesteuert werden. Änderungen in der APP werden mit den Readings und dem Status syncronisiert. + +
+
+
    + Readings +
      +
    • battery-charging - Ladeindikator (0/1) oder mit neuerer Firmware (false/true)
    • +
    • battery-level - Ladezustand der Batterie in Prozent
    • +
    • battery-rechargeable_battery_status - Zustand der Batterie (Ausser Betrieb/Kritischer Batteriestand, wechseln Sie jetzt/Niedrig/oK)
    • +
    • device_info-category - Eigenschaft des Gerätes (Mäher/Bewässerungscomputer/Bodensensor)
    • +
    • device_info-last_time_online - Zeitpunkt der letzten Funkübertragung
    • +
    • device_info-manufacturer - Hersteller
    • +
    • device_info-product - Produkttyp
    • +
    • device_info-serial_number - Seriennummer
    • +
    • device_info-sgtin -
    • +
    • device_info-version - Firmware Version
    • +
    • firmware-firmware_command - Firmware Kommando (Nichts zu tun/Firmwareupload unterbrochen/Firmwareupload/nicht unterstützt)
    • +
    • firmware-firmware_status - Firmware Status
    • +
    • firmware-firmware_update_start - Firmwareupdate (0/1) oder mit neuerer Firmware (false/true)
    • +
    • firmware-firmware_upload_progress - Firmwareupdatestatus in Prozent
    • +
    • firmware-inclusion_status - Einbindungsstatus
    • +
    • internal_temperature-temperature - Interne Geräte Temperatur
    • +
    • mower-error - Aktuelle Fehler Meldung +
        +
      • Kein Fehler
      • +
      • Außerhalb des Arbeitsbereichs
      • +
      • Kein Schleifensignal
      • +
      • Falsches Schleifensignal
      • +
      • Problem Schleifensensor, vorne
      • +
      • Problem Schleifensensor, hinten
      • +
      • Eingeschlossen
      • +
      • Steht auf dem Kopf
      • +
      • Niedriger Batteriestand
      • +
      • Batterie ist leer
      • +
      • Kein Antrieb
      • +
      • Angehoben
      • +
      • Eingeklemmt in Ladestation
      • +
      • Ladestation blockiert
      • +
      • Problem Stoßsensor hinten
      • +
      • Problem Stoßsensor vorne
      • +
      • Radmotor rechts blockiert
      • +
      • Radmotor links blockiert
      • +
      • Problem Antrieb, rechts
      • +
      • Problem Antrieb, links
      • +
      • Schneidsystem blockiert
      • +
      • Fehlerhafte Verbindung
      • +
      • Standardeinstellungen
      • +
      • Elektronisches Problem
      • +
      • Problem Ladesystem
      • +
      • Kippsensorproblem
      • +
      • Rechter Radmotor überlastet
      • +
      • Linker Radmotor überlastet
      • +
      • Ladestrom zu hoch
      • +
      • Vorübergehendes Problem
      • +
      • SK 1 nicht gefunden
      • +
      • SK 2 nicht gefunden
      • +
      • SK 3 nicht gefunden
      • +
      • Problem die Ladestation zu finden
      • +
      • Kalibration des Suchkabels beendet
      • +
      • Kalibration des Suchkabels fehlgeschlagen
      • +
      • Kurzzeitiges Batterieproblem
      • +
      • Batterieproblem
      • +
      • Alarm! Mäher ausgeschalten
      • +
      • Alarm! Mäher gestoppt
      • +
      • Alarm! Mäher angehoben
      • +
      • Alarm! Mäher gekippt
      • +
      • Verbindung geändert
      • +
      • Verbindung nicht geändert
      • +
      • COM board nicht verfügbar
      • +
      • Rutscht
      • +
      +
    • +
    • mower-manual_operation - Manueller Betrieb (0/1) oder mit neuerer Firmware (false/true)
    • +
    • mower-override_end_time - Zeitpunkt wann der manuelle Betrieb beendet ist
    • +
    • mower-source_for_next_start - Grund für den nächsten Start +
        +
      • Kein Grund
      • +
      • Mäher wurde geladen
      • +
      • SensorControl erreicht
      • +
      • Wochentimer erreicht
      • +
      • Stoppuhr Timer
      • +
      • Undefiniert
      • +
      +
    • +
    • mower-status - Mäher Status (siehe state)
    • +
    • mower-timestamp_next_start - Zeitpunkt des nächsten geplanten Starts
    • +
    • radio-connection_status - Status der Funkverbindung
    • +
    • radio-quality - Indikator für die Funkverbindung in Prozent
    • +
    • radio-state - radio state (schlecht/schwach/gut/Undefiniert)
    • +
    • state - Staus des Mähers +
        +
      • Pausiert
      • +
      • Mähen
      • +
      • Suche Ladestation
      • +
      • Lädt
      • +
      • Mähen
      • +
      • Wird aktualisiert ...
      • +
      • Wird eingeschaltet ...
      • +
      • Geparkt nach Zeitplan
      • +
      • Geparkt
      • +
      • Der Mäher ist ausgeschaltet
      • +
      • Deaktiviert. Abdeckung ist offen oder PIN-Code erforderlich
      • +
      • Unbekannter Status
      • +
      • Fehler
      • +
      • Neustart ...
      • +
      • Deaktiviert. Manueller Start erforderlich
      • +
      • Manuelles Mähen
      • +
      • Geparkt durch SensorControl
      • +
      • Abgeschlossen
      • +
      +
    • +
    +

    + + Attribute +
      +
    • readingValueLanguage - Änderung der Sprache der Readings (de,en/wenn nichts gesetzt ist, dann Englisch es sei denn deutsch ist als globale Sprache gesetzt)
    • +
    • model -
    • +
    + + set +
      +
    • parkUntilFurtherNotice - Parken des Mähers unter Umgehung des Zeitplans
    • +
    • parkUntilNextTimer - Parken bis zum nächsten Zeitplan
    • +
    • startOverrideTimer - Manuelles mähen (in Minuten, 60 = 1h, 1440 = 24h, 4320 = 72h)
    • +
    • startResumeSchedule - Weiterführung des Zeitplans
    • +
    • startpoint enable|disable 1|2|3 - Aktiviert oder deaktiviert einen vordefinierten Startbereich
    • +
        +
      • set NAME startpoint enable 1
      • +
      • set NAME startpoint disable 3 enable 1
      • +
      +
    +
+ +=end html_DE + +=for :application/json;q=META.json 74_GardenaSmartDevice.pm +{ + "abstract": "Modul to control GardenaSmart Devices", + "x_lang": { + "de": { + "abstract": "Modul zur Steuerung von Gardena Smart Geräten" + } + }, + "keywords": [ + "fhem-mod-device", + "fhem-core", + "Garden", + "Gardena", + "Smart" + ], + "release_status": "stable", + "license": "GPL_2", + "version": "v2.2.3", + "author": [ + "Marko Oldenburg " + ], + "x_fhem_maintainer": [ + "CoolTux" + ], + "x_fhem_maintainer_github": [ + "LeonGaultier" + ], + "prereqs": { + "runtime": { + "requires": { + "FHEM": 5.00918799, + "perl": 5.016, + "Meta": 0, + "JSON": 0, + "Time::Local": 0 + }, + "recommends": { + }, + "suggests": { + } + } + } +} +=end :application/json;q=META.json + +=cut diff --git a/controls_GardenaSmartDevice.txt b/controls_GardenaSmartDevice.txt index 8a7410d..107cb3b 100644 --- a/controls_GardenaSmartDevice.txt +++ b/controls_GardenaSmartDevice.txt @@ -1,2 +1,2 @@ -UPD 2021-05-14_12:55:05 46108 FHEM/73_GardenaSmartBridge.pm -UPD 2021-05-14_12:54:55 50825 FHEM/74_GardenaSmartDevice.pm +UPD 2021-05-14_13:16:35 47605 FHEM/73_GardenaSmartBridge.pm +UPD 2021-05-14_13:16:37 52152 FHEM/74_GardenaSmartDevice.pm From 232ad36a8ccf5c719650721e554fc82716665d71 Mon Sep 17 00:00:00 2001 From: Sebastian Date: Fri, 14 May 2021 13:18:59 +0200 Subject: [PATCH 08/18] fix carriage return --- FHEM/73_GardenaSmartBridge.pm | 2994 +++++++++++++++---------------- FHEM/74_GardenaSmartDevice.pm | 2654 +++++++++++++-------------- controls_GardenaSmartDevice.txt | 4 +- 3 files changed, 2826 insertions(+), 2826 deletions(-) diff --git a/FHEM/73_GardenaSmartBridge.pm b/FHEM/73_GardenaSmartBridge.pm index 004ee92..69acaec 100644 --- a/FHEM/73_GardenaSmartBridge.pm +++ b/FHEM/73_GardenaSmartBridge.pm @@ -1,1497 +1,1497 @@ -############################################################################### -# -# Developed with Kate -# -# (c) 2017-2011 Copyright: Marko Oldenburg (fhemdevelopment at cooltux dot net) -# All rights reserved -# -# Special thanks goes to comitters: -# - Michael (mbrak) Thanks for Commandref -# - Matthias (Kenneth) Thanks for Wiki entry -# - BioS Thanks for predefined start points Code -# - fettgu Thanks for Debugging Irrigation Control data flow -# - Sebastian (BOFH) Thanks for new Auth Code after API Change -# -# -# 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 FHEM::GardenaSmartBridge; -use GPUtils qw(GP_Import GP_Export); - - -use strict; -use warnings; -use POSIX; -use FHEM::Meta; - -use HttpUtils; - -my $missingModul = ''; -eval "use Encode qw(encode encode_utf8 decode_utf8);1" - or $missingModul .= "Encode "; - -# eval "use JSON;1" || $missingModul .= 'JSON '; -eval "use IO::Socket::SSL;1" or $missingModul .= 'IO::Socket::SSL '; - -# try to use JSON::MaybeXS wrapper -# for chance of better performance + open code -eval { - require JSON::MaybeXS; - import JSON::MaybeXS qw( decode_json encode_json ); - 1; -}; - -if ($@) { - $@ = undef; - - # try to use JSON wrapper - # for chance of better performance - eval { - - # JSON preference order - local $ENV{PERL_JSON_BACKEND} = - 'Cpanel::JSON::XS,JSON::XS,JSON::PP,JSON::backportPP' - unless ( defined( $ENV{PERL_JSON_BACKEND} ) ); - - require JSON; - import JSON qw( decode_json encode_json ); - 1; - }; - - if ($@) { - $@ = undef; - - # In rare cases, Cpanel::JSON::XS may - # be installed but JSON|JSON::MaybeXS not ... - eval { - require Cpanel::JSON::XS; - import Cpanel::JSON::XS qw(decode_json encode_json); - 1; - }; - - if ($@) { - $@ = undef; - - # In rare cases, JSON::XS may - # be installed but JSON not ... - eval { - require JSON::XS; - import JSON::XS qw(decode_json encode_json); - 1; - }; - - if ($@) { - $@ = undef; - - # Fallback to built-in JSON which SHOULD - # be available since 5.014 ... - eval { - require JSON::PP; - import JSON::PP qw(decode_json encode_json); - 1; - }; - - if ($@) { - $@ = undef; - - # Fallback to JSON::backportPP in really rare cases - require JSON::backportPP; - import JSON::backportPP qw(decode_json encode_json); - 1; - } - } - } - } -} - -## Import der FHEM Funktionen -#-- Run before package compilation -BEGIN { - # Import from main context - GP_Import( - qw(readingsSingleUpdate - readingsBulkUpdate - readingsBulkUpdateIfChanged - readingsBeginUpdate - readingsEndUpdate - Log3 - devspec2array - asyncOutput - CommandAttr - AttrVal - InternalVal - ReadingsVal - CommandDefMod - modules - setKeyValue - getKeyValue - getUniqueId - RemoveInternalTimer - readingFnAttributes - InternalTimer - defs - init_done - IsDisabled - deviceEvents - HttpUtils_NonblockingGet - gettimeofday - Dispatch) - ); -} - -#-- Export to main context with different name -GP_Export( - qw( - Initialize - ) -); - -sub Initialize { - my $hash = shift; - - # Provider - $hash->{WriteFn} = \&Write; - $hash->{Clients} = ':GardenaSmartDevice:'; - $hash->{MatchList} = { '1:GardenaSmartDevice' => '^{"id":".*' }; - - # Consumer - $hash->{SetFn} = \&Set; - $hash->{GetFn} = \&Get; - $hash->{DefFn} = \&Define; - $hash->{UndefFn} = \&Undef; - $hash->{DeleteFn} = \&Delete; - $hash->{RenameFn} = \&Rename; - $hash->{NotifyFn} = \&Notify; - - $hash->{AttrFn} = \&Attr; - $hash->{AttrList} = - 'debugJSON:0,1 ' - . 'disable:1 ' - . 'interval ' - . 'disabledForIntervals ' - . 'gardenaAccountEmail ' - . 'gardenaBaseURL ' - . $readingFnAttributes; - $hash->{parseParams} = 1; - - return FHEM::Meta::InitMod( __FILE__, $hash ); -} - -sub Define { - my $hash = shift // return; - my $aArg = shift // return; - - return $@ unless ( FHEM::Meta::SetInternals($hash) ); - use version 0.60; our $VERSION = FHEM::Meta::Get( $hash, 'version' ); - - return 'too few parameters: define GardenaSmartBridge' - if ( scalar( @{$aArg} ) != 2 ); - return - 'Cannot define Gardena Bridge device. Perl modul ' - . ${missingModul} - . ' is missing.' - if ($missingModul); - - my $name = shift @$aArg; - $hash->{BRIDGE} = 1; - $hash->{URL} = - AttrVal( $name, 'gardenaBaseURL', - 'https://smart.gardena.com' ) - . '/v1'; - $hash->{VERSION} = version->parse($VERSION)->normal; - $hash->{INTERVAL} = 60; - $hash->{NOTIFYDEV} = "global,$name"; - - CommandAttr( undef, $name . ' room GardenaSmart' ) - if ( AttrVal( $name, 'room', 'none' ) eq 'none' ); - - readingsSingleUpdate( $hash, 'token', 'none', 1 ); - readingsSingleUpdate( $hash, 'state', 'initialized', 1 ); - - Log3 $name, 3, "GardenaSmartBridge ($name) - defined GardenaSmartBridge"; - - $modules{GardenaSmartBridge}{defptr}{BRIDGE} = $hash; - - return; -} - -sub Undef { - my $hash = shift; - my $name = shift; - - RemoveInternalTimer($hash); - delete $modules{GardenaSmartBridge}{defptr}{BRIDGE} - if ( defined( $modules{GardenaSmartBridge}{defptr}{BRIDGE} ) ); - - return; -} - -sub Delete { - my $hash = shift; - my $name = shift; - - setKeyValue( $hash->{TYPE} . '_' . $name . '_passwd', undef ); - return; -} - -sub Attr { - my ( $cmd, $name, $attrName, $attrVal ) = @_; - my $hash = $defs{$name}; - - if ( $attrName eq 'disable' ) { - if ( $cmd eq 'set' && $attrVal eq '1' ) { - RemoveInternalTimer($hash); - readingsSingleUpdate( $hash, 'state', 'inactive', 1 ); - Log3 $name, 3, "GardenaSmartBridge ($name) - disabled"; - } - elsif ( $cmd eq 'del' ) { - readingsSingleUpdate( $hash, 'state', 'active', 1 ); - Log3 $name, 3, "GardenaSmartBridge ($name) - enabled"; - } - } - elsif ( $attrName eq 'disabledForIntervals' ) { - if ( $cmd eq 'set' ) { - return -"check disabledForIntervals Syntax HH:MM-HH:MM or 'HH:MM-HH:MM HH:MM-HH:MM ...'" - if ( $attrVal !~ /^((\d{2}:\d{2})-(\d{2}:\d{2})\s?)+$/ ); - Log3 $name, 3, "GardenaSmartBridge ($name) - disabledForIntervals"; - } - elsif ( $cmd eq 'del' ) { - readingsSingleUpdate( $hash, 'state', 'active', 1 ); - Log3 $name, 3, "GardenaSmartBridge ($name) - enabled"; - } - } - elsif ( $attrName eq 'interval' ) { - if ( $cmd eq 'set' ) { - return 'Interval must be greater than 0' - if ( $attrVal == 0 ); - RemoveInternalTimer($hash); - $hash->{INTERVAL} = $attrVal; - Log3 $name, 3, - "GardenaSmartBridge ($name) - set interval: $attrVal"; - } - elsif ( $cmd eq 'del' ) { - RemoveInternalTimer($hash); - $hash->{INTERVAL} = 60; - Log3 $name, 3, -"GardenaSmartBridge ($name) - delete User interval and set default: 60"; - } - } - elsif ( $attrName eq 'gardenaBaseURL' ) { - if ( $cmd eq 'set' ) { - $hash->{URL} = $attrVal; - Log3 $name, 3, - "GardenaSmartBridge ($name) - set gardenaBaseURL to: $attrVal"; - } - elsif ( $cmd eq 'del' ) { - $hash->{URL} = 'https://smart.gardena.com/v1'; - } - } - - return; -} - -sub Notify { - my $hash = shift // return; - my $dev = shift // return; - - my $name = $hash->{NAME}; - return if ( IsDisabled($name) ); - - my $devname = $dev->{NAME}; - my $devtype = $dev->{TYPE}; - my $events = deviceEvents( $dev, 1 ); - return if ( !$events ); - - getToken($hash) - if ( - ( - $devtype eq 'Global' - && ( - grep /^INITIALIZED$/, - @{$events} or grep /^REREADCFG$/, - @{$events} or grep /^DEFINED.$name$/, - @{$events} or grep /^MODIFIED.$name$/, - @{$events} or grep /^ATTR.$name.gardenaAccountEmail.+/, - @{$events} - ) - ) - - || ( - $devtype eq 'GardenaSmartBridge' - && ( - grep /^gardenaAccountPassword.+/, - @{$events} - ) - ) - ); - - getDevices($hash) - if ( - $devtype eq 'Global' - && ( - grep /^DELETEATTR.$name.disable$/, - @{$events} or grep /^ATTR.$name.disable.0$/, - @{$events} or grep /^DELETEATTR.$name.interval$/, - @{$events} or grep /^ATTR.$name.interval.[0-9]+/, - @{$events} - ) - && $init_done - ); - - if ( - $devtype eq 'GardenaSmartBridge' - && ( - grep /^state:.Connected$/, - @{$events} or grep /^lastRequestState:.request_error$/, - @{$events} - ) - ) - { - - InternalTimer( gettimeofday() + $hash->{INTERVAL}, - "FHEM::GardenaSmartBridge::getDevices", $hash ); - Log3 $name, 4, -"GardenaSmartBridge ($name) - set internal timer function for recall getDevices sub"; - } - - return; -} -sub Get { - my $hash = shift // return; - my $aArg = shift // return; - - my $name = shift @$aArg // return; - my $cmd = shift @$aArg - // return qq{"get $name" needs at least one argument}; - - 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 { - my $list = ""; - $list .= " debug_devices_list:" - .join( ',', @{ $hash->{helper}{deviceList} }) - if ( AttrVal( $name, "debugJSON", "none") ne "none" - && exists($hash->{helper}{deviceList}) ); - return "Unknown argument $cmd,choose one of $list"; - } -} - -sub Set { - my $hash = shift // return; - my $aArg = shift // return; - - my $name = shift @$aArg // return; - 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!!! - - if ( lc $cmd eq 'getdevicesstate' ) { - getDevices($hash); - - } - elsif ( lc $cmd eq 'gettoken' ) { - return "please set Attribut gardenaAccountEmail first" - if ( AttrVal( $name, 'gardenaAccountEmail', 'none' ) eq 'none' ); - return "please set gardenaAccountPassword first" - if ( not defined( ReadPassword( $hash, $name ) ) ); - return "token is up to date" - if ( defined( $hash->{helper}{session_id} ) ); - - getToken($hash); - } - elsif ( lc $cmd eq 'gardenaaccountpassword' ) { - return "please set Attribut gardenaAccountEmail first" - if ( AttrVal( $name, 'gardenaAccountEmail', 'none' ) eq 'none' ); - return "usage: $cmd " if ( scalar( @{$aArg} ) != 1 ); - - StorePassword( $hash, $name, $aArg->[0] ); - } - elsif ( lc $cmd eq 'deleteaccountpassword' ) { - return "usage: $cmd" if ( scalar( @{$aArg} ) != 0 ); - - DeletePassword($hash); - } - else { - - my $list = "getDevicesState:noArg getToken:noArg" - if ( defined( ReadPassword( $hash, $name ) ) ); - $list .= " gardenaAccountPassword" - if ( not defined( ReadPassword( $hash, $name ) ) ); - $list .= " deleteAccountPassword:noArg" - if ( defined( ReadPassword( $hash, $name ) ) ); - return "Unknown argument $cmd, choose one of $list"; - } - - return; -} - -sub Write { - my ( $hash, $payload, $deviceId, $abilities, $service_id ) = @_; - my $name = $hash->{NAME}; - - my ( $session_id, $header, $uri, $method ); - - ( $payload, $session_id, $header, $uri, $method, $deviceId, $service_id ) = - createHttpValueStrings( $hash, $payload, $deviceId, $abilities, $service_id ); - - HttpUtils_NonblockingGet( - { - url => $hash->{URL} . $uri, - timeout => 15, - hash => $hash, - device_id => $deviceId, - data => $payload, - method => $method, - header => $header, - doTrigger => 1, - cl => $hash->{CL}, - callback => \&ErrorHandling - } - ); - - Log3( $name, 4, -"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"); - - return; -} - -sub ErrorHandling { - my $param = shift; - my $err = shift; - my $data = shift; - - my $hash = $param->{hash}; - my $name = $hash->{NAME}; - my $dhash = $hash; - - $dhash = $modules{GardenaSmartDevice}{defptr}{ $param->{'device_id'} } - if ( defined( $param->{'device_id'} ) ); - - my $dname = $dhash->{NAME}; - - Log3 $name, 4, "GardenaSmartBridge ($name) - Request: $data"; - - my $decode_json = eval { decode_json($data) }; - if ($@) { - Log3 $name, 3, "GardenaSmartBridge ($name) - JSON error while request"; - } - - if ( defined($err) ) { - if ( $err ne "" ) { - - readingsBeginUpdate($dhash); - readingsBulkUpdate( $dhash, "state", "$err" ) - if ( ReadingsVal( $dname, "state", 1 ) ne "initialized" ); - - readingsBulkUpdate( $dhash, "lastRequestState", "request_error", - 1 ); - - if ( $err =~ /timed out/ ) { - - Log3 $dname, 5, -"GardenaSmartBridge ($dname) - RequestERROR: connect to gardena cloud is timed out. check network"; - } - - elsif ($err =~ /Keine Route zum Zielrechner/ - || $err =~ /no route to target/ ) - { - - Log3 $dname, 5, -"GardenaSmartBridge ($dname) - RequestERROR: no route to target. bad network configuration or network is down"; - - } - else { - - Log3 $dname, 5, - "GardenaSmartBridge ($dname) - RequestERROR: $err"; - } - - readingsEndUpdate( $dhash, 1 ); - - Log3 $dname, 5, -"GardenaSmartBridge ($dname) - RequestERROR: GardenaSmartBridge RequestErrorHandling: error while requesting gardena cloud: $err"; - - delete $dhash->{helper}{deviceAction} - if ( defined( $dhash->{helper}{deviceAction} ) ); - - return; - } - } - - if ( $data eq "" && exists( $param->{code} ) && $param->{code} != 200 ) { - - readingsBeginUpdate($dhash); - readingsBulkUpdate( $dhash, "state", $param->{code}, 1 ) - if ( ReadingsVal( $dname, "state", 1 ) ne "initialized" ); - - readingsBulkUpdateIfChanged( $dhash, "lastRequestState", - "request_error", 1 ); - - if ( $param->{code} == 401 && $hash eq $dhash ) { - - if ( ReadingsVal( $dname, 'token', 'none' ) eq 'none' ) { - readingsBulkUpdate( $dhash, "state", "no token available", 1 ); - readingsBulkUpdateIfChanged( $dhash, "lastRequestState", - "no token available", 1 ); - } - - Log3 $dname, 5, - "GardenaSmartBridge ($dname) - RequestERROR: " . $param->{code}; - - } - elsif ($param->{code} == 204 - && $dhash ne $hash - && defined( $dhash->{helper}{deviceAction} ) ) - { - - readingsBulkUpdate( $dhash, "state", "the command is processed", - 1 ); - InternalTimer( - gettimeofday() + 5, - "FHEM::GardenaSmartBridge::getDevices", - $hash, 1 - ); - - } - elsif ( $param->{code} != 200 ) { - - Log3 $dname, 5, - "GardenaSmartBridge ($dname) - RequestERROR: " . $param->{code}; - } - - readingsEndUpdate( $dhash, 1 ); - - Log3 $dname, 5, - "GardenaSmartBridge ($dname) - RequestERROR: received http code " - . $param->{code} - . " without any data after requesting gardena cloud"; - - delete $dhash->{helper}{deviceAction} - if ( defined( $dhash->{helper}{deviceAction} ) ); - - return; - } - - if ( - $data =~ /Error/ - || ( defined($decode_json) - && ref($decode_json) eq 'HASH' - && defined( $decode_json->{errors} ) ) - ) - { - readingsBeginUpdate($dhash); - readingsBulkUpdate( $dhash, "state", $param->{code}, 1 ) - if ( ReadingsVal( $dname, "state", 0 ) ne "initialized" ); - - readingsBulkUpdate( $dhash, "lastRequestState", "request_error", 1 ); - - if ( $param->{code} == 400 ) { - if ($decode_json) { - if ( ref( $decode_json->{errors} ) eq "ARRAY" - && defined( $decode_json->{errors} ) ) - { - readingsBulkUpdate( - $dhash, - "state", - $decode_json->{errors}[0]{error} . ' ' - . $decode_json->{errors}[0]{attribute}, - 1 - ); - readingsBulkUpdate( - $dhash, - "lastRequestState", - $decode_json->{errors}[0]{error} . ' ' - . $decode_json->{errors}[0]{attribute}, - 1 - ); - Log3 $dname, 5, - "GardenaSmartBridge ($dname) - RequestERROR: " - . $decode_json->{errors}[0]{error} . " " - . $decode_json->{errors}[0]{attribute}; - } - } - else { - readingsBulkUpdate( $dhash, "lastRequestState", - "Error 400 Bad Request", 1 ); - Log3 $dname, 5, -"GardenaSmartBridge ($dname) - RequestERROR: Error 400 Bad Request"; - } - } - elsif ( $param->{code} == 503 ) { - - Log3 $dname, 5, -"GardenaSmartBridge ($dname) - RequestERROR: Error 503 Service Unavailable"; - readingsBulkUpdate( $dhash, "state", "Service Unavailable", 1 ); - readingsBulkUpdate( $dhash, "lastRequestState", - "Error 503 Service Unavailable", 1 ); - - } - elsif ( $param->{code} == 404 ) { - if ( defined( $dhash->{helper}{deviceAction} ) && $dhash ne $hash ) - { - readingsBulkUpdate( $dhash, "state", "device Id not found", 1 ); - readingsBulkUpdate( $dhash, "lastRequestState", - "device id not found", 1 ); - } - - Log3 $dname, 5, - "GardenaSmartBridge ($dname) - RequestERROR: Error 404 Not Found"; - - } - elsif ( $param->{code} == 500 ) { - - Log3 $dname, 5, - "GardenaSmartBridge ($dname) - RequestERROR: check the ???"; - - } - else { - - Log3 $dname, 5, - "GardenaSmartBridge ($dname) - RequestERROR: http error " - . $param->{code}; - } - - readingsEndUpdate( $dhash, 1 ); - - Log3 $dname, 5, - "GardenaSmartBridge ($dname) - RequestERROR: received http code " - . $param->{code} - . " receive Error after requesting gardena cloud"; - - delete $dhash->{helper}{deviceAction} - if ( defined( $dhash->{helper}{deviceAction} ) ); - - return; - } - if (defined($hash->{helper}{debug_device})){ - Log3 $name, 5, "GardenaSmartBridge DEBUG Device"; - 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"; - $output .= "name: $dev_settings->{name} "; - if (ref ($dev_settings->{value}) eq 'ARRAY' - || ref ($dev_settings->{value}) eq 'HASH'){ - $output .= 'N/A \n'; - } else { - $output .= "value: $dev_settings->{value} \n"; - } - } - $hash->{helper}{debug_device_output} = $output; - asyncOutput($param->{cl}, $hash->{helper}{debug_device_output}); - return; - } - readingsSingleUpdate( $hash, 'state', 'Connected', 1 ) - if ( defined( $hash->{helper}{locations_id} ) ); - ResponseProcessing( $hash, $data ) - if ( ref($decode_json) eq 'HASH' ); - - return; -} - -sub ResponseProcessing { - my $hash = shift; - my $json = shift; - - 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 ); - } - } - - # print Dumper $decode_json; - - 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}; - - Write( $hash, undef, undef, undef ); - Log3 $name, 3, "GardenaSmartBridge ($name) - fetch locations id"; - readingsSingleUpdate( $hash, 'token', $hash->{helper}{session_id}, 1 ); - - return; - - } - elsif ( !defined( $hash->{helper}{locations_id} ) - && defined( $decode_json->{locations} ) - && ref( $decode_json->{locations} ) eq 'ARRAY' - && scalar( @{ $decode_json->{locations} } ) > 0 ) - { - for my $location ( @{ $decode_json->{locations} } ) { - - $hash->{helper}{locations_id} = $location->{id}; - - WriteReadings( $hash, $location ); - } - - Log3 $name, 3, - "GardenaSmartBridge ($name) - processed locations id. ID is " - . $hash->{helper}{locations_id}; - Write( $hash, undef, undef, undef ); - - return; - } - elsif (defined( $decode_json->{devices} ) - && ref( $decode_json->{devices} ) eq 'ARRAY' - && scalar( @{ $decode_json->{devices} } ) > 0 ) - { - - my @buffer = split( '"devices":\[', $json ); - - my ( $json, $tail ) = 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; - - if ( defined($tail) and $tail ) { - $decode_json = eval { decode_json($json) }; - if ($@) { - Log3 $name, 5, -"GardenaSmartBridge ($name) - JSON error while request: $@"; - } - - Dispatch( $hash, $json, undef ) - if ( $decode_json->{category} ne 'gateway' ); - WriteReadings( $hash, $decode_json ) - if ( defined( $decode_json->{category} ) - && $decode_json->{category} eq 'gateway' ); - } - - ( $json, $tail ) = 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"; - - return; -} - -sub WriteReadings { - my $hash = shift; - my $decode_json = shift; - - # print Dumper $decode_json; - - my $name = $hash->{NAME}; - - if ( defined( $decode_json->{id} ) - && $decode_json->{id} - && defined( $decode_json->{name} ) - && $decode_json->{name} ) - { - readingsBeginUpdate($hash); - if ( $decode_json->{id} eq $hash->{helper}{locations_id} ) { - - 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 ); - } - } - elsif ($decode_json->{id} ne $hash->{helper}{locations_id} - && ref( $decode_json->{abilities} ) eq 'ARRAY' - && ref( $decode_json->{abilities}[0]{properties} ) eq 'ARRAY' ) - { - my $properties = - scalar( @{ $decode_json->{abilities}[0]{properties} } ); - - do { - while ( ( my ( $t, $v ) ) = - each - %{ $decode_json->{abilities}[0]{properties}[$properties] } ) - { - next - if ( ref($v) eq 'ARRAY' ); - - #$v = encode_utf8($v); - readingsBulkUpdateIfChanged( - $hash, - $decode_json->{abilities}[0]{properties}[$properties] - {name} . '-' . $t, - $v - ) - if ($decode_json->{abilities}[0]{properties}[$properties]{name} !~ /ethernet_status|wifi_status/ ); - if ( - ( - $decode_json->{abilities}[0]{properties} - [$properties]{name} eq 'ethernet_status' - || $decode_json->{abilities}[0]{properties} - [$properties]{name} eq 'wifi_status' - ) - && ref($v) eq 'HASH' - ) - { - if ( $decode_json->{abilities}[0]{properties} - [$properties]{name} eq 'ethernet_status' ) - { - readingsBulkUpdateIfChanged( $hash, - 'ethernet_status-mac', $v->{mac} ); - readingsBulkUpdateIfChanged( $hash, - 'ethernet_status-ip', $v->{ip} ) - if ( ref( $v->{ip} ) ne 'HASH' ); - readingsBulkUpdateIfChanged( $hash, - 'ethernet_status-isconnected', - $v->{isConnected} ); - } - elsif ( $decode_json->{abilities}[0]{properties} - [$properties]{name} eq 'wifi_status' ) - { - #TODO: read valies if bridge connected to wifi - readingsBulkUpdateIfChanged( $hash, - 'wifi_status-ssid', $v->{ssid} ) - if (ref($v->{ssid}) ne 'HASH'); - readingsBulkUpdateIfChanged( $hash, - 'wifi_status-mac', $v->{mac} ); - readingsBulkUpdateIfChanged( $hash, - 'wifi_status-ip', $v->{ip} ) - if ( ref( $v->{ip} ) ne 'HASH' ); - readingsBulkUpdateIfChanged( $hash, - 'wifi_status-isconnected', $v->{isConnected} ); - readingsBulkUpdateIfChanged( $hash, - 'wifi_status-signal', $v->{signal} ); - } - } - } - $properties--; - - } while ( $properties >= 0 ); - } - readingsEndUpdate( $hash, 1 ); - } - - Log3 $name, 4, "GardenaSmartBridge ($name) - readings would be written"; - - return; -} - -#################################### -#################################### -#### my little helpers Sub's ####### - -sub getDevices { - my $hash = shift; - - my $name = $hash->{NAME}; - - RemoveInternalTimer($hash); - - if ( not IsDisabled($name) ) { - - delete $hash->{helper}{deviceList}; - my @list; - @list = devspec2array('TYPE=GardenaSmartDevice'); - for my $gardenaDev (@list){ - push( @{ $hash->{helper}{deviceList} }, $gardenaDev ); - } - Write( $hash, undef, undef, undef ); - Log3 $name, 4, - "GardenaSmartBridge ($name) - fetch device list and device states"; - } - else { - - readingsSingleUpdate( $hash, 'state', 'disabled', 1 ); - Log3 $name, 3, "GardenaSmartBridge ($name) - device is disabled"; - } - - return; -} - -sub getToken { - my $hash = shift; - - my $name = $hash->{NAME}; - - return readingsSingleUpdate( $hash, 'state', - 'please set Attribut gardenaAccountEmail first', 1 ) - if ( AttrVal( $name, 'gardenaAccountEmail', 'none' ) eq 'none' ); - return readingsSingleUpdate( $hash, 'state', - 'please set gardena account password first', 1 ) - if ( !defined( ReadPassword( $hash, $name ) ) ); - readingsSingleUpdate( $hash, 'state', 'get token', 1 ); - - delete $hash->{helper}{session_id} - if ( defined( $hash->{helper}{session_id} ) - && $hash->{helper}{session_id} ); - delete $hash->{helper}{user_id} - if ( defined( $hash->{helper}{user_id} ) && $hash->{helper}{user_id} ); - delete $hash->{helper}{locations_id} - if ( defined( $hash->{helper}{locations_id} ) - && $hash->{helper}{locations_id} ); - - # Write( - # $hash, - # '"sessions": {"email": "' - # . AttrVal( $name, 'gardenaAccountEmail', 'none' ) - # . '","password": "' - # . ReadPassword( $hash, $name ) . '"}', - # undef, - # undef - # ); - - Write( - $hash, - '"data": {"type":"token", "attributes":{"username": "' - . AttrVal( $name, 'gardenaAccountEmail', 'none' ) - . '","password": "' - . 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, 3, -"GardenaSmartBridge ($name) - send credentials to fetch Token and locationId"; - - return; -} - -sub StorePassword { - my $hash = shift; - my $name = shift; - my $password = shift; - - my $index = $hash->{TYPE} . "_" . $name . "_passwd"; - my $key = getUniqueId() . $index; - my $enc_pwd = ""; - - if ( eval "use Digest::MD5;1" ) { - - $key = Digest::MD5::md5_hex( unpack "H*", $key ); - $key .= Digest::MD5::md5_hex($key); - } - - for my $char ( split //, $password ) { - - my $encode = chop($key); - $enc_pwd .= sprintf( "%.2x", ord($char) ^ ord($encode) ); - $key = $encode . $key; - } - - my $err = setKeyValue( $index, $enc_pwd ); - return "error while saving the password - $err" if ( defined($err) ); - - return "password successfully saved"; -} - -sub ReadPassword { - my $hash = shift; - my $name = shift; - - my $index = $hash->{TYPE} . "_" . $name . "_passwd"; - my $key = getUniqueId() . $index; - my ( $password, $err ); - - Log3 $name, 4, "GardenaSmartBridge ($name) - Read password from file"; - - ( $err, $password ) = getKeyValue($index); - - if ( defined($err) ) { - - Log3 $name, 3, -"GardenaSmartBridge ($name) - unable to read password from file: $err"; - return undef; - - } - - if ( defined($password) ) { - if ( eval "use Digest::MD5;1" ) { - - $key = Digest::MD5::md5_hex( unpack "H*", $key ); - $key .= Digest::MD5::md5_hex($key); - } - - my $dec_pwd = ''; - - for my $char ( map { pack( 'C', hex($_) ) } ( $password =~ /(..)/g ) ) { - - my $decode = chop($key); - $dec_pwd .= chr( ord($char) ^ ord($decode) ); - $key = $decode . $key; - } - - return $dec_pwd; - - } - else { - - Log3 $name, 3, "GardenaSmartBridge ($name) - No password in file"; - return undef; - } - - return; -} - -sub Rename { - my $new = shift; - my $old = shift; - - my $hash = $defs{$new}; - - StorePassword( $hash, $new, ReadPassword( $hash, $old ) ); - setKeyValue( $hash->{TYPE} . "_" . $old . "_passwd", undef ); - - 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 ) = @_; - - my $session_id = $hash->{helper}{session_id}; - my $header = "Content-Type: application/json"; - my $uri = ''; - my $method = 'POST'; - $header .= "\r\nAuthorization: Bearer $session_id" - if ( defined($hash->{helper}{session_id}) ); - $header .= "\r\nAuthorization-Provider: husqvarna" - if ( defined($hash->{helper}{session_id}) ); - - # $header .= "\r\nx-api-key: $session_id" - # if ( defined( $hash->{helper}{session_id} ) ); - $payload = '{' . $payload . '}' if ( defined($payload) ); - $payload = '{}' if ( !defined($payload) ); - - if ( $payload eq '{}' ) { - $method = 'GET'; - $payload = ''; - $uri .= '/locations?locatioId=null&user_id=' . $hash->{helper}{user_id} - if ( exists( $hash->{helper}{user_id} ) - && !defined( $hash->{helper}{locations_id} ) ); - readingsSingleUpdate( $hash, 'state', 'fetch locationId', 1 ) - if ( !defined( $hash->{helper}{locations_id} ) ); - $uri .= '/auth/token' if ( !defined( $hash->{helper}{session_id} ) ); - $uri .= '/devices' - if (!defined($abilities) - && defined( $hash->{helper}{locations_id} ) ); - } - - $uri = '/devices/'.InternalVal($hash->{helper}{debug_device}, 'DEVICEID', 0 ) if ( exists ($hash->{helper}{debug_device})); - $uri = '/auth/token' if ( !defined( $hash->{helper}{session_id} ) ); - - if ( defined( $hash->{helper}{locations_id} ) ) { - if ( defined($abilities) && $abilities =~ /.*_settings/ ) { - - $method = 'PUT'; - my $dhash = $modules{GardenaSmartDevice}{defptr}{$deviceId}; - - $uri .= - '/devices/' - . $deviceId - . '/settings/' - . $service_id - if ( defined($abilities) - && defined($payload) - && $abilities =~ /.*_settings/ ); - - } # park until next schedules or override - elsif (defined($abilities) - && defined($payload) - && $abilities eq 'mower_timer' ) - { - my $valve_id; - $method = 'PUT'; - - $uri .= - '/devices/' - . $deviceId - . '/abilities/' - . $abilities - . '/properties/mower_timer'; - - } - elsif (defined($abilities) - && defined($payload) - && $abilities eq 'watering' ) - { - my $valve_id; - $method = 'PUT'; - - if ( $payload =~ m#watering_timer_(\d)# ) { - $valve_id = $1; - } - $uri .= - '/devices/' - . $deviceId - . '/abilities/' - . $abilities - . '/properties/watering_timer_' - . $valve_id; - - } - elsif (defined($abilities) - && defined($payload) - && $abilities eq 'manual_watering' ) - { - my $valve_id; - $method = 'PUT'; - - $uri .= - '/devices/' - . $deviceId - . '/abilities/' - . $abilities - . '/properties/manual_watering_timer'; - - } - elsif (defined($abilities) - && defined($payload) - && $abilities eq 'power' ) - { - my $valve_id; - $method = 'PUT'; - - $uri .= - '/devices/' - . $deviceId - . '/abilities/' - . $abilities - . '/properties/power_timer'; - - } - else { - $uri .= - '/devices/' . $deviceId . '/abilities/' . $abilities . '/command' - if ( defined($abilities) && defined($payload) ); - } - - $uri .= '?locationId=' . $hash->{helper}{locations_id}; - } - - return ( $payload, $session_id, $header, $uri, $method, $deviceId, - $abilities ); -} - -sub DeletePassword { - my $hash = shift; - - setKeyValue( $hash->{TYPE} . "_" . $hash->{NAME} . "_passwd", undef ); - - return; -} - -1; - -=pod - -=item device -=item summary Modul to communicate with the GardenaCloud -=item summary_DE Modul zur Datenübertragung zur GardenaCloud - -=begin html - - -

GardenaSmartBridge

-
    - Prerequisite -

    -
  • In combination with GardenaSmartDevice this FHEM Module controls the communication between the GardenaCloud and connected Devices like Mover, Watering_Computer, Temperature_Sensors
  • -
  • Installation of the following packages: apt-get install libio-socket-ssl-perl
  • -
  • The Gardena-Gateway and all connected Devices must be correctly installed in the GardenaAPP
  • -
-
- -Define -

    - define <name> GardenaSmartBridge -

    - Beispiel: -

      - define Gardena_Bridge GardenaSmartBridge
      -
    -
    - The GardenaSmartBridge device is created in the room GardenaSmart, then the devices of Your system are recognized automatically and created in FHEM. From now on the devices can be controlled and changes in the GardenaAPP are synchronized with the state and readings of the devices. -

    - -

    - Readings -
      -
    • address - your Adress (Longversion)
    • -
    • authorized_user_ids -
    • -
    • city - Zip, City
    • -
    • devices - Number of Devices in the Cloud (Gateway included)
    • -
    • lastRequestState - Last Status Result
    • -
    • latitude - Breitengrad des Grundstücks
    • -
    • longitude - Längengrad des Grundstücks
    • -
    • name - Name of your Garden – Default „My Garden“
    • -
    • state - State of the Bridge
    • -
    • token - SessionID
    • -
    -

    - - set -
      -
    • getDeviceState - Starts a Datarequest
    • -
    • getToken - Gets a new Session-ID
    • -
    • gardenaAccountPassword - Passwort which was used in the GardenaAPP
    • -
    • deleteAccountPassword - delete the password from store
    • -
    -

    - - Attributes -
      -
    • debugJSON -
    • -
    • disable - Disables the Bridge
    • -
    • interval - Interval in seconds (Default=60)
    • -
    • gardenaAccountEmail - Email Adresse which was used in the GardenaAPP
    • -
    -
- -=end html -=begin html_DE - - -

GardenaSmartBridge

-
    - Voraussetzungen -

    -
  • Zusammen mit dem Device GardenaSmartDevice stellt dieses FHEM Modul die Kommunikation zwischen der GardenaCloud und Fhem her. Es können damit Rasenmäher, Bewässerungscomputer und Bodensensoren überwacht und gesteuert werden
  • -
  • Das Perl-Modul "SSL Packet" wird benötigt.
  • -
  • Unter Debian (basierten) System, kann dies mittels "apt-get install libio-socket-ssl-perl" installiert werden.
  • -
  • Das Gardena-Gateway und alle damit verbundenen Geräte und Sensoren müssen vorab in der GardenaApp eingerichtet sein.
  • -
-
- -Define -

    - define <name> GardenaSmartBridge -

    - Beispiel: -

      - define Gardena_Bridge GardenaSmartBridge
      -
    -
    - Das Bridge Device wird im Raum GardenaSmart angelegt und danach erfolgt das Einlesen und automatische Anlegen der Geräte. Von nun an können die eingebundenen Geräte gesteuert werden. Änderungen in der APP werden mit den Readings und dem Status syncronisiert. -

    - -

    - Readings -
      -
    • address - Adresse, welche in der App eingetragen wurde (Langversion)
    • -
    • authorized_user_ids -
    • -
    • city - PLZ, Stadt
    • -
    • devices - Anzahl der Geräte, welche in der GardenaCloud angemeldet sind (Gateway zählt mit)
    • -
    • lastRequestState - Letzter abgefragter Status der Bridge
    • -
    • latitude - Breitengrad des Grundstücks
    • -
    • longitude - Längengrad des Grundstücks
    • -
    • name - Name für das Grundstück – Default „My Garden“
    • -
    • state - Status der Bridge
    • -
    • token - SessionID
    • -
    -

    - - set -
      -
    • getDeviceState - Startet eine Abfrage der Daten.
    • -
    • getToken - Holt eine neue Session-ID
    • -
    • gardenaAccountPassword - Passwort, welches in der GardenaApp verwendet wurde
    • -
    • deleteAccountPassword - l&oml;scht das Passwort aus dem Passwortstore
    • -
    -

    - - Attribute -
      -
    • debugJSON - JSON Fehlermeldungen
    • -
    • disable - Schaltet die Datenübertragung der Bridge ab
    • -
    • interval - Abfrageinterval in Sekunden (default: 60)
    • -
    • gardenaAccountEmail - Email Adresse, die auch in der GardenaApp verwendet wurde
    • -
    -
- -=end html_DE - -=for :application/json;q=META.json 73_GardenaSmartBridge.pm -{ - "abstract": "Modul to communicate with the GardenaCloud", - "x_lang": { - "de": { - "abstract": "Modul zur Datenübertragung zur GardenaCloud" - } - }, - "keywords": [ - "fhem-mod-device", - "fhem-core", - "Garden", - "Gardena", - "Smart" - ], - "release_status": "stable", - "license": "GPL_2", - "version": "v2.2.2", - "author": [ - "Marko Oldenburg " - ], - "x_fhem_maintainer": [ - "CoolTux" - ], - "x_fhem_maintainer_github": [ - "LeonGaultier" - ], - "prereqs": { - "runtime": { - "requires": { - "FHEM": 5.00918799, - "perl": 5.016, - "Meta": 0, - "IO::Socket::SSL": 0, - "JSON": 0, - "HttpUtils": 0, - "Encode": 0 - }, - "recommends": { - }, - "suggests": { - } - } - } -} -=end :application/json;q=META.json - -=cut +############################################################################### +# +# Developed with Kate +# +# (c) 2017-2011 Copyright: Marko Oldenburg (fhemdevelopment at cooltux dot net) +# All rights reserved +# +# Special thanks goes to comitters: +# - Michael (mbrak) Thanks for Commandref +# - Matthias (Kenneth) Thanks for Wiki entry +# - BioS Thanks for predefined start points Code +# - fettgu Thanks for Debugging Irrigation Control data flow +# - Sebastian (BOFH) Thanks for new Auth Code after API Change +# +# +# 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 FHEM::GardenaSmartBridge; +use GPUtils qw(GP_Import GP_Export); + + +use strict; +use warnings; +use POSIX; +use FHEM::Meta; + +use HttpUtils; + +my $missingModul = ''; +eval "use Encode qw(encode encode_utf8 decode_utf8);1" + or $missingModul .= "Encode "; + +# eval "use JSON;1" || $missingModul .= 'JSON '; +eval "use IO::Socket::SSL;1" or $missingModul .= 'IO::Socket::SSL '; + +# try to use JSON::MaybeXS wrapper +# for chance of better performance + open code +eval { + require JSON::MaybeXS; + import JSON::MaybeXS qw( decode_json encode_json ); + 1; +}; + +if ($@) { + $@ = undef; + + # try to use JSON wrapper + # for chance of better performance + eval { + + # JSON preference order + local $ENV{PERL_JSON_BACKEND} = + 'Cpanel::JSON::XS,JSON::XS,JSON::PP,JSON::backportPP' + unless ( defined( $ENV{PERL_JSON_BACKEND} ) ); + + require JSON; + import JSON qw( decode_json encode_json ); + 1; + }; + + if ($@) { + $@ = undef; + + # In rare cases, Cpanel::JSON::XS may + # be installed but JSON|JSON::MaybeXS not ... + eval { + require Cpanel::JSON::XS; + import Cpanel::JSON::XS qw(decode_json encode_json); + 1; + }; + + if ($@) { + $@ = undef; + + # In rare cases, JSON::XS may + # be installed but JSON not ... + eval { + require JSON::XS; + import JSON::XS qw(decode_json encode_json); + 1; + }; + + if ($@) { + $@ = undef; + + # Fallback to built-in JSON which SHOULD + # be available since 5.014 ... + eval { + require JSON::PP; + import JSON::PP qw(decode_json encode_json); + 1; + }; + + if ($@) { + $@ = undef; + + # Fallback to JSON::backportPP in really rare cases + require JSON::backportPP; + import JSON::backportPP qw(decode_json encode_json); + 1; + } + } + } + } +} + +## Import der FHEM Funktionen +#-- Run before package compilation +BEGIN { + # Import from main context + GP_Import( + qw(readingsSingleUpdate + readingsBulkUpdate + readingsBulkUpdateIfChanged + readingsBeginUpdate + readingsEndUpdate + Log3 + devspec2array + asyncOutput + CommandAttr + AttrVal + InternalVal + ReadingsVal + CommandDefMod + modules + setKeyValue + getKeyValue + getUniqueId + RemoveInternalTimer + readingFnAttributes + InternalTimer + defs + init_done + IsDisabled + deviceEvents + HttpUtils_NonblockingGet + gettimeofday + Dispatch) + ); +} + +#-- Export to main context with different name +GP_Export( + qw( + Initialize + ) +); + +sub Initialize { + my $hash = shift; + + # Provider + $hash->{WriteFn} = \&Write; + $hash->{Clients} = ':GardenaSmartDevice:'; + $hash->{MatchList} = { '1:GardenaSmartDevice' => '^{"id":".*' }; + + # Consumer + $hash->{SetFn} = \&Set; + $hash->{GetFn} = \&Get; + $hash->{DefFn} = \&Define; + $hash->{UndefFn} = \&Undef; + $hash->{DeleteFn} = \&Delete; + $hash->{RenameFn} = \&Rename; + $hash->{NotifyFn} = \&Notify; + + $hash->{AttrFn} = \&Attr; + $hash->{AttrList} = + 'debugJSON:0,1 ' + . 'disable:1 ' + . 'interval ' + . 'disabledForIntervals ' + . 'gardenaAccountEmail ' + . 'gardenaBaseURL ' + . $readingFnAttributes; + $hash->{parseParams} = 1; + + return FHEM::Meta::InitMod( __FILE__, $hash ); +} + +sub Define { + my $hash = shift // return; + my $aArg = shift // return; + + return $@ unless ( FHEM::Meta::SetInternals($hash) ); + use version 0.60; our $VERSION = FHEM::Meta::Get( $hash, 'version' ); + + return 'too few parameters: define GardenaSmartBridge' + if ( scalar( @{$aArg} ) != 2 ); + return + 'Cannot define Gardena Bridge device. Perl modul ' + . ${missingModul} + . ' is missing.' + if ($missingModul); + + my $name = shift @$aArg; + $hash->{BRIDGE} = 1; + $hash->{URL} = + AttrVal( $name, 'gardenaBaseURL', + 'https://smart.gardena.com' ) + . '/v1'; + $hash->{VERSION} = version->parse($VERSION)->normal; + $hash->{INTERVAL} = 60; + $hash->{NOTIFYDEV} = "global,$name"; + + CommandAttr( undef, $name . ' room GardenaSmart' ) + if ( AttrVal( $name, 'room', 'none' ) eq 'none' ); + + readingsSingleUpdate( $hash, 'token', 'none', 1 ); + readingsSingleUpdate( $hash, 'state', 'initialized', 1 ); + + Log3 $name, 3, "GardenaSmartBridge ($name) - defined GardenaSmartBridge"; + + $modules{GardenaSmartBridge}{defptr}{BRIDGE} = $hash; + + return; +} + +sub Undef { + my $hash = shift; + my $name = shift; + + RemoveInternalTimer($hash); + delete $modules{GardenaSmartBridge}{defptr}{BRIDGE} + if ( defined( $modules{GardenaSmartBridge}{defptr}{BRIDGE} ) ); + + return; +} + +sub Delete { + my $hash = shift; + my $name = shift; + + setKeyValue( $hash->{TYPE} . '_' . $name . '_passwd', undef ); + return; +} + +sub Attr { + my ( $cmd, $name, $attrName, $attrVal ) = @_; + my $hash = $defs{$name}; + + if ( $attrName eq 'disable' ) { + if ( $cmd eq 'set' && $attrVal eq '1' ) { + RemoveInternalTimer($hash); + readingsSingleUpdate( $hash, 'state', 'inactive', 1 ); + Log3 $name, 3, "GardenaSmartBridge ($name) - disabled"; + } + elsif ( $cmd eq 'del' ) { + readingsSingleUpdate( $hash, 'state', 'active', 1 ); + Log3 $name, 3, "GardenaSmartBridge ($name) - enabled"; + } + } + elsif ( $attrName eq 'disabledForIntervals' ) { + if ( $cmd eq 'set' ) { + return +"check disabledForIntervals Syntax HH:MM-HH:MM or 'HH:MM-HH:MM HH:MM-HH:MM ...'" + if ( $attrVal !~ /^((\d{2}:\d{2})-(\d{2}:\d{2})\s?)+$/ ); + Log3 $name, 3, "GardenaSmartBridge ($name) - disabledForIntervals"; + } + elsif ( $cmd eq 'del' ) { + readingsSingleUpdate( $hash, 'state', 'active', 1 ); + Log3 $name, 3, "GardenaSmartBridge ($name) - enabled"; + } + } + elsif ( $attrName eq 'interval' ) { + if ( $cmd eq 'set' ) { + return 'Interval must be greater than 0' + if ( $attrVal == 0 ); + RemoveInternalTimer($hash); + $hash->{INTERVAL} = $attrVal; + Log3 $name, 3, + "GardenaSmartBridge ($name) - set interval: $attrVal"; + } + elsif ( $cmd eq 'del' ) { + RemoveInternalTimer($hash); + $hash->{INTERVAL} = 60; + Log3 $name, 3, +"GardenaSmartBridge ($name) - delete User interval and set default: 60"; + } + } + elsif ( $attrName eq 'gardenaBaseURL' ) { + if ( $cmd eq 'set' ) { + $hash->{URL} = $attrVal; + Log3 $name, 3, + "GardenaSmartBridge ($name) - set gardenaBaseURL to: $attrVal"; + } + elsif ( $cmd eq 'del' ) { + $hash->{URL} = 'https://smart.gardena.com/v1'; + } + } + + return; +} + +sub Notify { + my $hash = shift // return; + my $dev = shift // return; + + my $name = $hash->{NAME}; + return if ( IsDisabled($name) ); + + my $devname = $dev->{NAME}; + my $devtype = $dev->{TYPE}; + my $events = deviceEvents( $dev, 1 ); + return if ( !$events ); + + getToken($hash) + if ( + ( + $devtype eq 'Global' + && ( + grep /^INITIALIZED$/, + @{$events} or grep /^REREADCFG$/, + @{$events} or grep /^DEFINED.$name$/, + @{$events} or grep /^MODIFIED.$name$/, + @{$events} or grep /^ATTR.$name.gardenaAccountEmail.+/, + @{$events} + ) + ) + + || ( + $devtype eq 'GardenaSmartBridge' + && ( + grep /^gardenaAccountPassword.+/, + @{$events} + ) + ) + ); + + getDevices($hash) + if ( + $devtype eq 'Global' + && ( + grep /^DELETEATTR.$name.disable$/, + @{$events} or grep /^ATTR.$name.disable.0$/, + @{$events} or grep /^DELETEATTR.$name.interval$/, + @{$events} or grep /^ATTR.$name.interval.[0-9]+/, + @{$events} + ) + && $init_done + ); + + if ( + $devtype eq 'GardenaSmartBridge' + && ( + grep /^state:.Connected$/, + @{$events} or grep /^lastRequestState:.request_error$/, + @{$events} + ) + ) + { + + InternalTimer( gettimeofday() + $hash->{INTERVAL}, + "FHEM::GardenaSmartBridge::getDevices", $hash ); + Log3 $name, 4, +"GardenaSmartBridge ($name) - set internal timer function for recall getDevices sub"; + } + + return; +} +sub Get { + my $hash = shift // return; + my $aArg = shift // return; + + my $name = shift @$aArg // return; + my $cmd = shift @$aArg + // return qq{"get $name" needs at least one argument}; + + 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 { + my $list = ""; + $list .= " debug_devices_list:" + .join( ',', @{ $hash->{helper}{deviceList} }) + if ( AttrVal( $name, "debugJSON", "none") ne "none" + && exists($hash->{helper}{deviceList}) ); + return "Unknown argument $cmd,choose one of $list"; + } +} + +sub Set { + my $hash = shift // return; + my $aArg = shift // return; + + my $name = shift @$aArg // return; + 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!!! + + if ( lc $cmd eq 'getdevicesstate' ) { + getDevices($hash); + + } + elsif ( lc $cmd eq 'gettoken' ) { + return "please set Attribut gardenaAccountEmail first" + if ( AttrVal( $name, 'gardenaAccountEmail', 'none' ) eq 'none' ); + return "please set gardenaAccountPassword first" + if ( not defined( ReadPassword( $hash, $name ) ) ); + return "token is up to date" + if ( defined( $hash->{helper}{session_id} ) ); + + getToken($hash); + } + elsif ( lc $cmd eq 'gardenaaccountpassword' ) { + return "please set Attribut gardenaAccountEmail first" + if ( AttrVal( $name, 'gardenaAccountEmail', 'none' ) eq 'none' ); + return "usage: $cmd " if ( scalar( @{$aArg} ) != 1 ); + + StorePassword( $hash, $name, $aArg->[0] ); + } + elsif ( lc $cmd eq 'deleteaccountpassword' ) { + return "usage: $cmd" if ( scalar( @{$aArg} ) != 0 ); + + DeletePassword($hash); + } + else { + + my $list = "getDevicesState:noArg getToken:noArg" + if ( defined( ReadPassword( $hash, $name ) ) ); + $list .= " gardenaAccountPassword" + if ( not defined( ReadPassword( $hash, $name ) ) ); + $list .= " deleteAccountPassword:noArg" + if ( defined( ReadPassword( $hash, $name ) ) ); + return "Unknown argument $cmd, choose one of $list"; + } + + return; +} + +sub Write { + my ( $hash, $payload, $deviceId, $abilities, $service_id ) = @_; + my $name = $hash->{NAME}; + + my ( $session_id, $header, $uri, $method ); + + ( $payload, $session_id, $header, $uri, $method, $deviceId, $service_id ) = + createHttpValueStrings( $hash, $payload, $deviceId, $abilities, $service_id ); + + HttpUtils_NonblockingGet( + { + url => $hash->{URL} . $uri, + timeout => 15, + hash => $hash, + device_id => $deviceId, + data => $payload, + method => $method, + header => $header, + doTrigger => 1, + cl => $hash->{CL}, + callback => \&ErrorHandling + } + ); + + Log3( $name, 4, +"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"); + + return; +} + +sub ErrorHandling { + my $param = shift; + my $err = shift; + my $data = shift; + + my $hash = $param->{hash}; + my $name = $hash->{NAME}; + my $dhash = $hash; + + $dhash = $modules{GardenaSmartDevice}{defptr}{ $param->{'device_id'} } + if ( defined( $param->{'device_id'} ) ); + + my $dname = $dhash->{NAME}; + + Log3 $name, 4, "GardenaSmartBridge ($name) - Request: $data"; + + my $decode_json = eval { decode_json($data) }; + if ($@) { + Log3 $name, 3, "GardenaSmartBridge ($name) - JSON error while request"; + } + + if ( defined($err) ) { + if ( $err ne "" ) { + + readingsBeginUpdate($dhash); + readingsBulkUpdate( $dhash, "state", "$err" ) + if ( ReadingsVal( $dname, "state", 1 ) ne "initialized" ); + + readingsBulkUpdate( $dhash, "lastRequestState", "request_error", + 1 ); + + if ( $err =~ /timed out/ ) { + + Log3 $dname, 5, +"GardenaSmartBridge ($dname) - RequestERROR: connect to gardena cloud is timed out. check network"; + } + + elsif ($err =~ /Keine Route zum Zielrechner/ + || $err =~ /no route to target/ ) + { + + Log3 $dname, 5, +"GardenaSmartBridge ($dname) - RequestERROR: no route to target. bad network configuration or network is down"; + + } + else { + + Log3 $dname, 5, + "GardenaSmartBridge ($dname) - RequestERROR: $err"; + } + + readingsEndUpdate( $dhash, 1 ); + + Log3 $dname, 5, +"GardenaSmartBridge ($dname) - RequestERROR: GardenaSmartBridge RequestErrorHandling: error while requesting gardena cloud: $err"; + + delete $dhash->{helper}{deviceAction} + if ( defined( $dhash->{helper}{deviceAction} ) ); + + return; + } + } + + if ( $data eq "" && exists( $param->{code} ) && $param->{code} != 200 ) { + + readingsBeginUpdate($dhash); + readingsBulkUpdate( $dhash, "state", $param->{code}, 1 ) + if ( ReadingsVal( $dname, "state", 1 ) ne "initialized" ); + + readingsBulkUpdateIfChanged( $dhash, "lastRequestState", + "request_error", 1 ); + + if ( $param->{code} == 401 && $hash eq $dhash ) { + + if ( ReadingsVal( $dname, 'token', 'none' ) eq 'none' ) { + readingsBulkUpdate( $dhash, "state", "no token available", 1 ); + readingsBulkUpdateIfChanged( $dhash, "lastRequestState", + "no token available", 1 ); + } + + Log3 $dname, 5, + "GardenaSmartBridge ($dname) - RequestERROR: " . $param->{code}; + + } + elsif ($param->{code} == 204 + && $dhash ne $hash + && defined( $dhash->{helper}{deviceAction} ) ) + { + + readingsBulkUpdate( $dhash, "state", "the command is processed", + 1 ); + InternalTimer( + gettimeofday() + 5, + "FHEM::GardenaSmartBridge::getDevices", + $hash, 1 + ); + + } + elsif ( $param->{code} != 200 ) { + + Log3 $dname, 5, + "GardenaSmartBridge ($dname) - RequestERROR: " . $param->{code}; + } + + readingsEndUpdate( $dhash, 1 ); + + Log3 $dname, 5, + "GardenaSmartBridge ($dname) - RequestERROR: received http code " + . $param->{code} + . " without any data after requesting gardena cloud"; + + delete $dhash->{helper}{deviceAction} + if ( defined( $dhash->{helper}{deviceAction} ) ); + + return; + } + + if ( + $data =~ /Error/ + || ( defined($decode_json) + && ref($decode_json) eq 'HASH' + && defined( $decode_json->{errors} ) ) + ) + { + readingsBeginUpdate($dhash); + readingsBulkUpdate( $dhash, "state", $param->{code}, 1 ) + if ( ReadingsVal( $dname, "state", 0 ) ne "initialized" ); + + readingsBulkUpdate( $dhash, "lastRequestState", "request_error", 1 ); + + if ( $param->{code} == 400 ) { + if ($decode_json) { + if ( ref( $decode_json->{errors} ) eq "ARRAY" + && defined( $decode_json->{errors} ) ) + { + readingsBulkUpdate( + $dhash, + "state", + $decode_json->{errors}[0]{error} . ' ' + . $decode_json->{errors}[0]{attribute}, + 1 + ); + readingsBulkUpdate( + $dhash, + "lastRequestState", + $decode_json->{errors}[0]{error} . ' ' + . $decode_json->{errors}[0]{attribute}, + 1 + ); + Log3 $dname, 5, + "GardenaSmartBridge ($dname) - RequestERROR: " + . $decode_json->{errors}[0]{error} . " " + . $decode_json->{errors}[0]{attribute}; + } + } + else { + readingsBulkUpdate( $dhash, "lastRequestState", + "Error 400 Bad Request", 1 ); + Log3 $dname, 5, +"GardenaSmartBridge ($dname) - RequestERROR: Error 400 Bad Request"; + } + } + elsif ( $param->{code} == 503 ) { + + Log3 $dname, 5, +"GardenaSmartBridge ($dname) - RequestERROR: Error 503 Service Unavailable"; + readingsBulkUpdate( $dhash, "state", "Service Unavailable", 1 ); + readingsBulkUpdate( $dhash, "lastRequestState", + "Error 503 Service Unavailable", 1 ); + + } + elsif ( $param->{code} == 404 ) { + if ( defined( $dhash->{helper}{deviceAction} ) && $dhash ne $hash ) + { + readingsBulkUpdate( $dhash, "state", "device Id not found", 1 ); + readingsBulkUpdate( $dhash, "lastRequestState", + "device id not found", 1 ); + } + + Log3 $dname, 5, + "GardenaSmartBridge ($dname) - RequestERROR: Error 404 Not Found"; + + } + elsif ( $param->{code} == 500 ) { + + Log3 $dname, 5, + "GardenaSmartBridge ($dname) - RequestERROR: check the ???"; + + } + else { + + Log3 $dname, 5, + "GardenaSmartBridge ($dname) - RequestERROR: http error " + . $param->{code}; + } + + readingsEndUpdate( $dhash, 1 ); + + Log3 $dname, 5, + "GardenaSmartBridge ($dname) - RequestERROR: received http code " + . $param->{code} + . " receive Error after requesting gardena cloud"; + + delete $dhash->{helper}{deviceAction} + if ( defined( $dhash->{helper}{deviceAction} ) ); + + return; + } + if (defined($hash->{helper}{debug_device})){ + Log3 $name, 5, "GardenaSmartBridge DEBUG Device"; + 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"; + $output .= "name: $dev_settings->{name} "; + if (ref ($dev_settings->{value}) eq 'ARRAY' + || ref ($dev_settings->{value}) eq 'HASH'){ + $output .= 'N/A \n'; + } else { + $output .= "value: $dev_settings->{value} \n"; + } + } + $hash->{helper}{debug_device_output} = $output; + asyncOutput($param->{cl}, $hash->{helper}{debug_device_output}); + return; + } + readingsSingleUpdate( $hash, 'state', 'Connected', 1 ) + if ( defined( $hash->{helper}{locations_id} ) ); + ResponseProcessing( $hash, $data ) + if ( ref($decode_json) eq 'HASH' ); + + return; +} + +sub ResponseProcessing { + my $hash = shift; + my $json = shift; + + 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 ); + } + } + + # print Dumper $decode_json; + + 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}; + + Write( $hash, undef, undef, undef ); + Log3 $name, 3, "GardenaSmartBridge ($name) - fetch locations id"; + readingsSingleUpdate( $hash, 'token', $hash->{helper}{session_id}, 1 ); + + return; + + } + elsif ( !defined( $hash->{helper}{locations_id} ) + && defined( $decode_json->{locations} ) + && ref( $decode_json->{locations} ) eq 'ARRAY' + && scalar( @{ $decode_json->{locations} } ) > 0 ) + { + for my $location ( @{ $decode_json->{locations} } ) { + + $hash->{helper}{locations_id} = $location->{id}; + + WriteReadings( $hash, $location ); + } + + Log3 $name, 3, + "GardenaSmartBridge ($name) - processed locations id. ID is " + . $hash->{helper}{locations_id}; + Write( $hash, undef, undef, undef ); + + return; + } + elsif (defined( $decode_json->{devices} ) + && ref( $decode_json->{devices} ) eq 'ARRAY' + && scalar( @{ $decode_json->{devices} } ) > 0 ) + { + + my @buffer = split( '"devices":\[', $json ); + + my ( $json, $tail ) = 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; + + if ( defined($tail) and $tail ) { + $decode_json = eval { decode_json($json) }; + if ($@) { + Log3 $name, 5, +"GardenaSmartBridge ($name) - JSON error while request: $@"; + } + + Dispatch( $hash, $json, undef ) + if ( $decode_json->{category} ne 'gateway' ); + WriteReadings( $hash, $decode_json ) + if ( defined( $decode_json->{category} ) + && $decode_json->{category} eq 'gateway' ); + } + + ( $json, $tail ) = 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"; + + return; +} + +sub WriteReadings { + my $hash = shift; + my $decode_json = shift; + + # print Dumper $decode_json; + + my $name = $hash->{NAME}; + + if ( defined( $decode_json->{id} ) + && $decode_json->{id} + && defined( $decode_json->{name} ) + && $decode_json->{name} ) + { + readingsBeginUpdate($hash); + if ( $decode_json->{id} eq $hash->{helper}{locations_id} ) { + + 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 ); + } + } + elsif ($decode_json->{id} ne $hash->{helper}{locations_id} + && ref( $decode_json->{abilities} ) eq 'ARRAY' + && ref( $decode_json->{abilities}[0]{properties} ) eq 'ARRAY' ) + { + my $properties = + scalar( @{ $decode_json->{abilities}[0]{properties} } ); + + do { + while ( ( my ( $t, $v ) ) = + each + %{ $decode_json->{abilities}[0]{properties}[$properties] } ) + { + next + if ( ref($v) eq 'ARRAY' ); + + #$v = encode_utf8($v); + readingsBulkUpdateIfChanged( + $hash, + $decode_json->{abilities}[0]{properties}[$properties] + {name} . '-' . $t, + $v + ) + if ($decode_json->{abilities}[0]{properties}[$properties]{name} !~ /ethernet_status|wifi_status/ ); + if ( + ( + $decode_json->{abilities}[0]{properties} + [$properties]{name} eq 'ethernet_status' + || $decode_json->{abilities}[0]{properties} + [$properties]{name} eq 'wifi_status' + ) + && ref($v) eq 'HASH' + ) + { + if ( $decode_json->{abilities}[0]{properties} + [$properties]{name} eq 'ethernet_status' ) + { + readingsBulkUpdateIfChanged( $hash, + 'ethernet_status-mac', $v->{mac} ); + readingsBulkUpdateIfChanged( $hash, + 'ethernet_status-ip', $v->{ip} ) + if ( ref( $v->{ip} ) ne 'HASH' ); + readingsBulkUpdateIfChanged( $hash, + 'ethernet_status-isconnected', + $v->{isConnected} ); + } + elsif ( $decode_json->{abilities}[0]{properties} + [$properties]{name} eq 'wifi_status' ) + { + #TODO: read valies if bridge connected to wifi + readingsBulkUpdateIfChanged( $hash, + 'wifi_status-ssid', $v->{ssid} ) + if (ref($v->{ssid}) ne 'HASH'); + readingsBulkUpdateIfChanged( $hash, + 'wifi_status-mac', $v->{mac} ); + readingsBulkUpdateIfChanged( $hash, + 'wifi_status-ip', $v->{ip} ) + if ( ref( $v->{ip} ) ne 'HASH' ); + readingsBulkUpdateIfChanged( $hash, + 'wifi_status-isconnected', $v->{isConnected} ); + readingsBulkUpdateIfChanged( $hash, + 'wifi_status-signal', $v->{signal} ); + } + } + } + $properties--; + + } while ( $properties >= 0 ); + } + readingsEndUpdate( $hash, 1 ); + } + + Log3 $name, 4, "GardenaSmartBridge ($name) - readings would be written"; + + return; +} + +#################################### +#################################### +#### my little helpers Sub's ####### + +sub getDevices { + my $hash = shift; + + my $name = $hash->{NAME}; + + RemoveInternalTimer($hash); + + if ( not IsDisabled($name) ) { + + delete $hash->{helper}{deviceList}; + my @list; + @list = devspec2array('TYPE=GardenaSmartDevice'); + for my $gardenaDev (@list){ + push( @{ $hash->{helper}{deviceList} }, $gardenaDev ); + } + Write( $hash, undef, undef, undef ); + Log3 $name, 4, + "GardenaSmartBridge ($name) - fetch device list and device states"; + } + else { + + readingsSingleUpdate( $hash, 'state', 'disabled', 1 ); + Log3 $name, 3, "GardenaSmartBridge ($name) - device is disabled"; + } + + return; +} + +sub getToken { + my $hash = shift; + + my $name = $hash->{NAME}; + + return readingsSingleUpdate( $hash, 'state', + 'please set Attribut gardenaAccountEmail first', 1 ) + if ( AttrVal( $name, 'gardenaAccountEmail', 'none' ) eq 'none' ); + return readingsSingleUpdate( $hash, 'state', + 'please set gardena account password first', 1 ) + if ( !defined( ReadPassword( $hash, $name ) ) ); + readingsSingleUpdate( $hash, 'state', 'get token', 1 ); + + delete $hash->{helper}{session_id} + if ( defined( $hash->{helper}{session_id} ) + && $hash->{helper}{session_id} ); + delete $hash->{helper}{user_id} + if ( defined( $hash->{helper}{user_id} ) && $hash->{helper}{user_id} ); + delete $hash->{helper}{locations_id} + if ( defined( $hash->{helper}{locations_id} ) + && $hash->{helper}{locations_id} ); + + # Write( + # $hash, + # '"sessions": {"email": "' + # . AttrVal( $name, 'gardenaAccountEmail', 'none' ) + # . '","password": "' + # . ReadPassword( $hash, $name ) . '"}', + # undef, + # undef + # ); + + Write( + $hash, + '"data": {"type":"token", "attributes":{"username": "' + . AttrVal( $name, 'gardenaAccountEmail', 'none' ) + . '","password": "' + . 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, 3, +"GardenaSmartBridge ($name) - send credentials to fetch Token and locationId"; + + return; +} + +sub StorePassword { + my $hash = shift; + my $name = shift; + my $password = shift; + + my $index = $hash->{TYPE} . "_" . $name . "_passwd"; + my $key = getUniqueId() . $index; + my $enc_pwd = ""; + + if ( eval "use Digest::MD5;1" ) { + + $key = Digest::MD5::md5_hex( unpack "H*", $key ); + $key .= Digest::MD5::md5_hex($key); + } + + for my $char ( split //, $password ) { + + my $encode = chop($key); + $enc_pwd .= sprintf( "%.2x", ord($char) ^ ord($encode) ); + $key = $encode . $key; + } + + my $err = setKeyValue( $index, $enc_pwd ); + return "error while saving the password - $err" if ( defined($err) ); + + return "password successfully saved"; +} + +sub ReadPassword { + my $hash = shift; + my $name = shift; + + my $index = $hash->{TYPE} . "_" . $name . "_passwd"; + my $key = getUniqueId() . $index; + my ( $password, $err ); + + Log3 $name, 4, "GardenaSmartBridge ($name) - Read password from file"; + + ( $err, $password ) = getKeyValue($index); + + if ( defined($err) ) { + + Log3 $name, 3, +"GardenaSmartBridge ($name) - unable to read password from file: $err"; + return undef; + + } + + if ( defined($password) ) { + if ( eval "use Digest::MD5;1" ) { + + $key = Digest::MD5::md5_hex( unpack "H*", $key ); + $key .= Digest::MD5::md5_hex($key); + } + + my $dec_pwd = ''; + + for my $char ( map { pack( 'C', hex($_) ) } ( $password =~ /(..)/g ) ) { + + my $decode = chop($key); + $dec_pwd .= chr( ord($char) ^ ord($decode) ); + $key = $decode . $key; + } + + return $dec_pwd; + + } + else { + + Log3 $name, 3, "GardenaSmartBridge ($name) - No password in file"; + return undef; + } + + return; +} + +sub Rename { + my $new = shift; + my $old = shift; + + my $hash = $defs{$new}; + + StorePassword( $hash, $new, ReadPassword( $hash, $old ) ); + setKeyValue( $hash->{TYPE} . "_" . $old . "_passwd", undef ); + + 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 ) = @_; + + my $session_id = $hash->{helper}{session_id}; + my $header = "Content-Type: application/json"; + my $uri = ''; + my $method = 'POST'; + $header .= "\r\nAuthorization: Bearer $session_id" + if ( defined($hash->{helper}{session_id}) ); + $header .= "\r\nAuthorization-Provider: husqvarna" + if ( defined($hash->{helper}{session_id}) ); + + # $header .= "\r\nx-api-key: $session_id" + # if ( defined( $hash->{helper}{session_id} ) ); + $payload = '{' . $payload . '}' if ( defined($payload) ); + $payload = '{}' if ( !defined($payload) ); + + if ( $payload eq '{}' ) { + $method = 'GET'; + $payload = ''; + $uri .= '/locations?locatioId=null&user_id=' . $hash->{helper}{user_id} + if ( exists( $hash->{helper}{user_id} ) + && !defined( $hash->{helper}{locations_id} ) ); + readingsSingleUpdate( $hash, 'state', 'fetch locationId', 1 ) + if ( !defined( $hash->{helper}{locations_id} ) ); + $uri .= '/auth/token' if ( !defined( $hash->{helper}{session_id} ) ); + $uri .= '/devices' + if (!defined($abilities) + && defined( $hash->{helper}{locations_id} ) ); + } + + $uri = '/devices/'.InternalVal($hash->{helper}{debug_device}, 'DEVICEID', 0 ) if ( exists ($hash->{helper}{debug_device})); + $uri = '/auth/token' if ( !defined( $hash->{helper}{session_id} ) ); + + if ( defined( $hash->{helper}{locations_id} ) ) { + if ( defined($abilities) && $abilities =~ /.*_settings/ ) { + + $method = 'PUT'; + my $dhash = $modules{GardenaSmartDevice}{defptr}{$deviceId}; + + $uri .= + '/devices/' + . $deviceId + . '/settings/' + . $service_id + if ( defined($abilities) + && defined($payload) + && $abilities =~ /.*_settings/ ); + + } # park until next schedules or override + elsif (defined($abilities) + && defined($payload) + && $abilities eq 'mower_timer' ) + { + my $valve_id; + $method = 'PUT'; + + $uri .= + '/devices/' + . $deviceId + . '/abilities/' + . $abilities + . '/properties/mower_timer'; + + } + elsif (defined($abilities) + && defined($payload) + && $abilities eq 'watering' ) + { + my $valve_id; + $method = 'PUT'; + + if ( $payload =~ m#watering_timer_(\d)# ) { + $valve_id = $1; + } + $uri .= + '/devices/' + . $deviceId + . '/abilities/' + . $abilities + . '/properties/watering_timer_' + . $valve_id; + + } + elsif (defined($abilities) + && defined($payload) + && $abilities eq 'manual_watering' ) + { + my $valve_id; + $method = 'PUT'; + + $uri .= + '/devices/' + . $deviceId + . '/abilities/' + . $abilities + . '/properties/manual_watering_timer'; + + } + elsif (defined($abilities) + && defined($payload) + && $abilities eq 'power' ) + { + my $valve_id; + $method = 'PUT'; + + $uri .= + '/devices/' + . $deviceId + . '/abilities/' + . $abilities + . '/properties/power_timer'; + + } + else { + $uri .= + '/devices/' . $deviceId . '/abilities/' . $abilities . '/command' + if ( defined($abilities) && defined($payload) ); + } + + $uri .= '?locationId=' . $hash->{helper}{locations_id}; + } + + return ( $payload, $session_id, $header, $uri, $method, $deviceId, + $abilities ); +} + +sub DeletePassword { + my $hash = shift; + + setKeyValue( $hash->{TYPE} . "_" . $hash->{NAME} . "_passwd", undef ); + + return; +} + +1; + +=pod + +=item device +=item summary Modul to communicate with the GardenaCloud +=item summary_DE Modul zur Datenübertragung zur GardenaCloud + +=begin html + + +

GardenaSmartBridge

+
    + Prerequisite +

    +
  • In combination with GardenaSmartDevice this FHEM Module controls the communication between the GardenaCloud and connected Devices like Mover, Watering_Computer, Temperature_Sensors
  • +
  • Installation of the following packages: apt-get install libio-socket-ssl-perl
  • +
  • The Gardena-Gateway and all connected Devices must be correctly installed in the GardenaAPP
  • +
+
+ +Define +

    + define <name> GardenaSmartBridge +

    + Beispiel: +

      + define Gardena_Bridge GardenaSmartBridge
      +
    +
    + The GardenaSmartBridge device is created in the room GardenaSmart, then the devices of Your system are recognized automatically and created in FHEM. From now on the devices can be controlled and changes in the GardenaAPP are synchronized with the state and readings of the devices. +

    + +

    + Readings +
      +
    • address - your Adress (Longversion)
    • +
    • authorized_user_ids -
    • +
    • city - Zip, City
    • +
    • devices - Number of Devices in the Cloud (Gateway included)
    • +
    • lastRequestState - Last Status Result
    • +
    • latitude - Breitengrad des Grundstücks
    • +
    • longitude - Längengrad des Grundstücks
    • +
    • name - Name of your Garden – Default „My Garden“
    • +
    • state - State of the Bridge
    • +
    • token - SessionID
    • +
    +

    + + set +
      +
    • getDeviceState - Starts a Datarequest
    • +
    • getToken - Gets a new Session-ID
    • +
    • gardenaAccountPassword - Passwort which was used in the GardenaAPP
    • +
    • deleteAccountPassword - delete the password from store
    • +
    +

    + + Attributes +
      +
    • debugJSON -
    • +
    • disable - Disables the Bridge
    • +
    • interval - Interval in seconds (Default=60)
    • +
    • gardenaAccountEmail - Email Adresse which was used in the GardenaAPP
    • +
    +
+ +=end html +=begin html_DE + + +

GardenaSmartBridge

+
    + Voraussetzungen +

    +
  • Zusammen mit dem Device GardenaSmartDevice stellt dieses FHEM Modul die Kommunikation zwischen der GardenaCloud und Fhem her. Es können damit Rasenmäher, Bewässerungscomputer und Bodensensoren überwacht und gesteuert werden
  • +
  • Das Perl-Modul "SSL Packet" wird benötigt.
  • +
  • Unter Debian (basierten) System, kann dies mittels "apt-get install libio-socket-ssl-perl" installiert werden.
  • +
  • Das Gardena-Gateway und alle damit verbundenen Geräte und Sensoren müssen vorab in der GardenaApp eingerichtet sein.
  • +
+
+ +Define +

    + define <name> GardenaSmartBridge +

    + Beispiel: +

      + define Gardena_Bridge GardenaSmartBridge
      +
    +
    + Das Bridge Device wird im Raum GardenaSmart angelegt und danach erfolgt das Einlesen und automatische Anlegen der Geräte. Von nun an können die eingebundenen Geräte gesteuert werden. Änderungen in der APP werden mit den Readings und dem Status syncronisiert. +

    + +

    + Readings +
      +
    • address - Adresse, welche in der App eingetragen wurde (Langversion)
    • +
    • authorized_user_ids -
    • +
    • city - PLZ, Stadt
    • +
    • devices - Anzahl der Geräte, welche in der GardenaCloud angemeldet sind (Gateway zählt mit)
    • +
    • lastRequestState - Letzter abgefragter Status der Bridge
    • +
    • latitude - Breitengrad des Grundstücks
    • +
    • longitude - Längengrad des Grundstücks
    • +
    • name - Name für das Grundstück – Default „My Garden“
    • +
    • state - Status der Bridge
    • +
    • token - SessionID
    • +
    +

    + + set +
      +
    • getDeviceState - Startet eine Abfrage der Daten.
    • +
    • getToken - Holt eine neue Session-ID
    • +
    • gardenaAccountPassword - Passwort, welches in der GardenaApp verwendet wurde
    • +
    • deleteAccountPassword - l&oml;scht das Passwort aus dem Passwortstore
    • +
    +

    + + Attribute +
      +
    • debugJSON - JSON Fehlermeldungen
    • +
    • disable - Schaltet die Datenübertragung der Bridge ab
    • +
    • interval - Abfrageinterval in Sekunden (default: 60)
    • +
    • gardenaAccountEmail - Email Adresse, die auch in der GardenaApp verwendet wurde
    • +
    +
+ +=end html_DE + +=for :application/json;q=META.json 73_GardenaSmartBridge.pm +{ + "abstract": "Modul to communicate with the GardenaCloud", + "x_lang": { + "de": { + "abstract": "Modul zur Datenübertragung zur GardenaCloud" + } + }, + "keywords": [ + "fhem-mod-device", + "fhem-core", + "Garden", + "Gardena", + "Smart" + ], + "release_status": "stable", + "license": "GPL_2", + "version": "v2.2.2", + "author": [ + "Marko Oldenburg " + ], + "x_fhem_maintainer": [ + "CoolTux" + ], + "x_fhem_maintainer_github": [ + "LeonGaultier" + ], + "prereqs": { + "runtime": { + "requires": { + "FHEM": 5.00918799, + "perl": 5.016, + "Meta": 0, + "IO::Socket::SSL": 0, + "JSON": 0, + "HttpUtils": 0, + "Encode": 0 + }, + "recommends": { + }, + "suggests": { + } + } + } +} +=end :application/json;q=META.json + +=cut diff --git a/FHEM/74_GardenaSmartDevice.pm b/FHEM/74_GardenaSmartDevice.pm index 5075adb..5ba6d8f 100644 --- a/FHEM/74_GardenaSmartDevice.pm +++ b/FHEM/74_GardenaSmartDevice.pm @@ -1,1327 +1,1327 @@ -############################################################################### -# -# Developed with Kate -# -# (c) 2017-2021 Copyright: Marko Oldenburg (fhemdevelopment at cooltux dot net) -# All rights reserved -# -# Special thanks goes to comitters: -# - Michael (mbrak) Thanks for Commandref -# - Matthias (Kenneth) Thanks for Wiki entry -# - BioS Thanks for predefined start points Code -# - fettgu Thanks for Debugging Irrigation Control data flow -# - Sebastian (BOFH) Thanks for new Auth Code after API Change -# -# -# 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/ -# -## -## - -## unserer packagename -package FHEM::GardenaSmartDevice; -use GPUtils qw(GP_Import GP_Export); - -use strict; -use warnings; -use POSIX; -use FHEM::Meta; -use Time::Local; - -# try to use JSON::MaybeXS wrapper -# for chance of better performance + open code -eval { - require JSON::MaybeXS; - import JSON::MaybeXS qw( decode_json encode_json ); - 1; -}; - -if ($@) { - $@ = undef; - - # try to use JSON wrapper - # for chance of better performance - eval { - - # JSON preference order - local $ENV{PERL_JSON_BACKEND} = - 'Cpanel::JSON::XS,JSON::XS,JSON::PP,JSON::backportPP' - unless ( defined( $ENV{PERL_JSON_BACKEND} ) ); - - require JSON; - import JSON qw( decode_json encode_json ); - 1; - }; - - if ($@) { - $@ = undef; - - # In rare cases, Cpanel::JSON::XS may - # be installed but JSON|JSON::MaybeXS not ... - eval { - require Cpanel::JSON::XS; - import Cpanel::JSON::XS qw(decode_json encode_json); - 1; - }; - - if ($@) { - $@ = undef; - - # In rare cases, JSON::XS may - # be installed but JSON not ... - eval { - require JSON::XS; - import JSON::XS qw(decode_json encode_json); - 1; - }; - - if ($@) { - $@ = undef; - - # Fallback to built-in JSON which SHOULD - # be available since 5.014 ... - eval { - require JSON::PP; - import JSON::PP qw(decode_json encode_json); - 1; - }; - - if ($@) { - $@ = undef; - - # Fallback to JSON::backportPP in really rare cases - require JSON::backportPP; - import JSON::backportPP qw(decode_json encode_json); - 1; - } - } - } - } -} - -## Import der FHEM Funktionen -#-- Run before package compilation -BEGIN { - - # Import from main context - GP_Import( - qw(readingsSingleUpdate - readingsBulkUpdate - readingsBulkUpdateIfChanged - readingsBeginUpdate - readingsEndUpdate - Log3 - CommandAttr - AttrVal - ReadingsVal - readingFnAttributes - AssignIoPort - modules - IOWrite - defs - makeDeviceName) - ); -} - -#-- Export to main context with different name -GP_Export( - qw( - Initialize - ) -); - -sub Initialize { - my $hash = shift; - - $hash->{Match} = '^{"id":".*'; - - $hash->{SetFn} = \&Set; - $hash->{DefFn} = \&Define; - $hash->{UndefFn} = \&Undef; - $hash->{ParseFn} = \&Parse; - - $hash->{AttrFn} = \&Attr; - $hash->{AttrList} = - "readingValueLanguage:de,en " - . "model:watering_computer,sensor,mower,ic24,power,electronic_pressure_pump " - . "IODev " - . $readingFnAttributes; - $hash->{parseParams} = 1; - - return FHEM::Meta::InitMod( __FILE__, $hash ); -} - -sub Define { - my $hash = shift // return; - my $aArg = shift // return; - - return $@ unless ( FHEM::Meta::SetInternals($hash) ); - use version 0.60; our $VERSION = FHEM::Meta::Get( $hash, 'version' ); - - return - "too few parameters: define GardenaSmartDevice " - if ( scalar( @{$aArg} ) < 3 ); - - my $name = $aArg->[0]; - my $deviceId = $aArg->[2]; - my $category = $aArg->[3]; - - $hash->{DEVICEID} = $deviceId; - $hash->{VERSION} = version->parse($VERSION)->normal; - $hash->{helper}{STARTINGPOINTID} = ''; - $hash->{helper}{schedules_paused_until_id} = ''; - $hash->{helper}{eco_mode_id} = ''; - # IrrigationControl valve control max 6 - $hash->{helper}{schedules_paused_until_1_id} = ''; - $hash->{helper}{schedules_paused_until_2_id} = ''; - $hash->{helper}{schedules_paused_until_3_id} = ''; - $hash->{helper}{schedules_paused_until_4_id} = ''; - $hash->{helper}{schedules_paused_until_5_id} = ''; - $hash->{helper}{schedules_paused_until_6_id} = ''; - - CommandAttr( undef, - "$name IODev $modules{GardenaSmartBridge}{defptr}{BRIDGE}->{NAME}" ) - if ( AttrVal( $name, 'IODev', 'none' ) eq 'none' ); - - my $iodev = AttrVal( $name, 'IODev', 'none' ); - - 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 ); - - CommandAttr( undef, $name . ' room GardenaSmart' ) - if ( AttrVal( $name, 'room', 'none' ) eq 'none' ); - - CommandAttr( undef, $name . ' model ' . $category ) - if ( AttrVal( $name, 'model', 'none' ) eq 'none' ); - - Log3 $name, 3, -"GardenaSmartDevice ($name) - defined GardenaSmartDevice with DEVICEID: $deviceId"; - readingsSingleUpdate( $hash, 'state', 'initialized', 1 ); - - $modules{GardenaSmartDevice}{defptr}{$deviceId} = $hash; - - return; -} - -sub Undef { - my $hash = shift; - my $arg = shift; - - my $name = $hash->{NAME}; - my $deviceId = $hash->{DEVICEID}; - - delete $modules{GardenaSmartDevice}{defptr}{$deviceId}; - - return; -} - -sub Attr { - - my ( $cmd, $name, $attrName, $attrVal ) = @_; - my $hash = $defs{$name}; - - return; -} - -sub Set { - my $hash = shift // return; - my $aArg = shift // return; - - my $name = shift @$aArg; - 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 ); - - #set default abilitie ... overwrite in cmd to change - $abilities = 'mower' - if ( AttrVal( $name, 'model', 'unknown' ) eq 'mower' ); - $abilities = 'watering' - if ( AttrVal( $name, 'model', 'unknown' ) eq 'ic24' - || AttrVal( $name, 'model', 'unknown' ) eq 'watering_computer' ); - $abilities = 'power' - if ( AttrVal( $name, 'model', 'unknown' ) eq 'power' ); - $abilities = 'manual_watering' - if ( AttrVal( $name, 'model', 'unknown' ) eq 'electronic_pressure_pump' ); - - ### mower - # service_id (eco, parkuntilfurhternotice, startpoints) - if ( lc $cmd eq 'parkuntilfurthernotice' ) { - $payload = '"name":"park_until_further_notice"'; - if ( $mainboard_version > 10.30 ) { - $payload = ' "settings":{"name":"schedules_paused_until","value":"2040-12-31T22: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}' ; - $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' ; - $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 .'}'; - $abilities = 'mower_timer'; - } - - } - elsif ( lc $cmd eq 'startpoint' ) { - my $err; - ( $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}.'"}'; - $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 ); - } - ### electronic_pressure_pump - elsif ( lc $cmd eq 'pumptimer' ) { - $payload = - '"name":"pump_manual_watering_timer","parameters":{"duration":' - . $aArg->[0] . '}'; - } - ### watering_computer - elsif ( lc $cmd eq 'manualoverride' ) { - $payload = - '"properties":{"name":"watering_timer_1' - . '","value":{"state":"manual","duration":' - . $aArg->[0] * 60 - . ',"valve_id":1}}'; - } - elsif ( $cmd =~ m{\AcancelOverride}xms ) { - - my $valve_id = 1; - - if ( $cmd =~ m{\AcancelOverrideValve(\d)\z}xms ) { - $valve_id = $1; - } - - $payload = - '"properties":{"name":"watering_timer_' - . $valve_id - . '","value":{"state":"idle","duration":' - . 0 - . ',"valve_id":' - . $valve_id . '}}'; - } - elsif ( lc $cmd eq 'on' || lc $cmd eq 'off' || lc $cmd eq 'on-for-timer' ) { - my $val = ( - defined($aArg) && ref($aArg) eq 'ARRAY' - ? $aArg->[0] * 60 - : lc $cmd - ); - - $payload = '"properties":{"value":"' . $val . '"}'; - } - ### Watering ic24 - elsif ( $cmd =~ m{\AmanualDurationValve\d\z}xms ) { - my $valve_id; - - if ( $cmd =~ m{\AmanualDurationValve(\d)\z}xms ) { - $valve_id = $1; - } - - $payload = - '"properties":{"name":"watering_timer_' - . $valve_id - . '","value":{"state":"manual","duration":' - . $aArg->[0] * 60 - . ',"valve_id":' - . $valve_id . '}}'; - } - elsif ( $cmd eq 'closeAllValves' ){ - $payload = '"name":"close_all_valves","parameters":{}'; - } - elsif ( $cmd =~ '/.*ScheduleValve/' ){ - my $valve_id = $1; - $abilities = 'irrigation_settings'; - $service_id = $hash->{helper}->{'schedules_paused_until_'.$valve_id.'_id'}; - $payload = '"settings":{"name":"schedules_paused_until_' - .$valve_id. - '", "value":"' - . ($cmd eq 'resumeScheduleValve') ? '' : '2040-12-31T22:00:00.000Z' - .'","device":"' - .$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') { - $payload = '"name":"measure_ambient_temperature"'; - $abilities = 'ambient_temperature'; - } else { - $payload = '"name":"measure_soil_temperature"'; - $abilities = 'soil_temperature'; - } - } - elsif ( lc $sensname eq 'light' ) { - $payload = '"name":"measure_light"'; - $abilities = 'light'; - - } - elsif ( lc $sensname eq 'humidity' ) { - $payload = '"name":"measure_soil_humidity"'; - $abilities = 'humidity'; - } - - - } - else { - - my $list = ''; - - $list .= -'parkUntilFurtherNotice:noArg parkUntilNextTimer:noArg startResumeSchedule:noArg startOverrideTimer:slider,0,1,60 startpoint' - if ( AttrVal( $name, 'model', 'unknown' ) eq 'mower' ); - - $list .= 'manualOverride:slider,1,1,59 cancelOverride:noArg' - if ( AttrVal( $name, 'model', 'unknown' ) eq 'watering_computer' ); - - $list .= -'closeAllValves:noArg stopScheduleValve:selectnumbers,1,1,6,0,lin resumeScheduleValve:selectnumbers,1,1,6,0,lin manualDurationValve1:slider,1,1,59 manualDurationValve2:slider,1,1,59 manualDurationValve3:slider,1,1,59 manualDurationValve4:slider,1,1,59 manualDurationValve5:slider,1,1,59 manualDurationValve6:slider,1,1,59 cancelOverrideValve1:noArg cancelOverrideValve2:noArg cancelOverrideValve3:noArg cancelOverrideValve4:noArg cancelOverrideValve5:noArg cancelOverrideValve6:noArg' - if ( AttrVal( $name, 'model', 'unknown' ) eq 'ic24' ); - - $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' ); - - $list .= 'on:noArg off:noArg on-for-timer:slider,0,1,60' - if ( AttrVal( $name, 'model', 'unknown' ) eq 'power' ); - - return "Unknown argument $cmd, choose one of $list"; - } - - $hash->{helper}{deviceAction} = $payload; - readingsSingleUpdate( $hash, "state", "send command to gardena cloud", 1 ); - - IOWrite( $hash, $payload, $hash->{DEVICEID}, $abilities, $service_id ); - Log3 $name, 4, -"GardenaSmartBridge ($name) - IOWrite: $payload $hash->{DEVICEID} $abilities IODevHash=$hash->{IODev}"; - - return; -} - -sub Parse { - my $io_hash = shift; - my $json = shift; - - my $name = $io_hash->{NAME}; - - my $decode_json = eval { decode_json($json) }; - if ($@) { - Log3 $name, 3, - "GardenaSmartDevice ($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}; - - WriteReadings( $hash, $decode_json ); - Log3 $name, 4, - "GardenaSmartDevice ($name) - find logical device: $hash->{NAME}"; - - return $hash->{NAME}; - - } - else { - - Log3 $name, 3, - "GardenaSmartDevice ($name) - autocreate new device " - . makeDeviceName( $decode_json->{name} ) - . " with deviceId $decode_json->{id}, model $decode_json->{category}"; - return - "UNDEFINED " - . makeDeviceName( $decode_json->{name} ) - . " GardenaSmartDevice $decode_json->{id} $decode_json->{category}"; - } - } - - return; -} - -sub WriteReadings { - my $hash = shift; - my $decode_json = shift; - - my $name = $hash->{NAME}; - my $abilities = scalar( @{ $decode_json->{abilities} } ); - my $settings = scalar( @{ $decode_json->{settings} } ); - - readingsBeginUpdate($hash); - - do { - - if ( - ref( $decode_json->{abilities}[$abilities]{properties} ) eq "ARRAY" - && scalar( @{ $decode_json->{abilities}[$abilities]{properties} } ) - > 0 ) - { - for my $propertie ( - @{ $decode_json->{abilities}[$abilities]{properties} } ) - { - readingsBulkUpdateIfChanged( - $hash, - $decode_json->{abilities}[$abilities]{name} . '-' - . $propertie->{name}, - RigReadingsValue( $hash, $propertie->{value} ) - ) - if ( defined( $propertie->{value} ) - && $decode_json->{abilities}[$abilities]{name} . '-' - . $propertie->{name} ne 'radio-quality' - && $decode_json->{abilities}[$abilities]{name} . '-' - . $propertie->{name} ne 'battery-level' - && $decode_json->{abilities}[$abilities]{name} . '-' - . $propertie->{name} ne 'internal_temperature-temperature' - && $decode_json->{abilities}[$abilities]{name} . '-' - . $propertie->{name} ne 'ambient_temperature-temperature' - && $decode_json->{abilities}[$abilities]{name} . '-' - . $propertie->{name} ne 'soil_temperature-temperature' - && $decode_json->{abilities}[$abilities]{name} . '-' - . $propertie->{name} ne 'humidity-humidity' - && $decode_json->{abilities}[$abilities]{name} . '-' - . $propertie->{name} ne 'light-light' - && ref( $propertie->{value} ) ne "HASH" ); - - readingsBulkUpdateIfChanged( - $hash, - $decode_json->{abilities}[$abilities]{name} . '-' - . $propertie->{name}, - RigReadingsValue( $hash, $propertie->{value} ) - ) - if ( - defined( $propertie->{value} ) - && ( $decode_json->{abilities}[$abilities]{name} . '-' - . $propertie->{name} eq 'radio-quality' - || $decode_json->{abilities}[$abilities]{name} . '-' - . $propertie->{name} eq 'battery-level' - || $decode_json->{abilities}[$abilities]{name} . '-' - . $propertie->{name} eq - 'internal_temperature-temperature' - || $decode_json->{abilities}[$abilities]{name} . '-' - . $propertie->{name} eq - 'ambient_temperature-temperature' - || $decode_json->{abilities}[$abilities]{name} . '-' - . $propertie->{name} eq 'soil_temperature-temperature' - || $decode_json->{abilities}[$abilities]{name} . '-' - . $propertie->{name} eq 'humidity-humidity' - || $decode_json->{abilities}[$abilities]{name} . '-' - . $propertie->{name} eq 'light-light' ) - ); - - readingsBulkUpdateIfChanged( - $hash, - $decode_json->{abilities}[$abilities]{name} . '-' - . $propertie->{name}, - join( ',', @{ $propertie->{value} } ) - ) - if ( defined( $propertie->{value} ) - && $decode_json->{abilities}[$abilities]{name} . '-' - . $propertie->{name} eq 'ic24-valves_connected' ); - - readingsBulkUpdateIfChanged( - $hash, - $decode_json->{abilities}[$abilities]{name} . '-' - . $propertie->{name}, - join( ',', @{ $propertie->{value} } ) - ) - if ( defined( $propertie->{value} ) - && $decode_json->{abilities}[$abilities]{name} . '-' - . $propertie->{name} eq 'ic24-valves_master_config' ); - - if ( ref( $propertie->{value} ) eq "HASH" ) { - while ( my ( $r, $v ) = each %{ $propertie->{value} } ) { - readingsBulkUpdate( - $hash, - $decode_json->{abilities}[$abilities]{name} . '-' - . $propertie->{name} . '_' - . $r, - RigReadingsValue( $hash, $v ) - ); - } - } - } - } - - $abilities--; - } 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"); - - if ( exists($decode_json->{settings}[$settings]{name}) - && ( - $decode_json->{settings}[$settings]{name} =~ /schedules_paused_until_?\d?$/ - || $decode_json->{settings}[$settings]{name} eq 'eco_mode' ) - ) - { - if ( $hash->{helper}{$decode_json->{settings}[$settings]{name}.'_id'} ne - $decode_json->{settings}[$settings]{id} ) - { - $hash->{helper}{$decode_json->{settings}[$settings]{name}.'_id'} = - $decode_json->{settings}[$settings]{id}; - } - } - - if ( ref( $decode_json->{settings}[$settings]{value} ) eq "ARRAY" - && $decode_json->{settings}[$settings]{name} eq 'starting_points' ) - { - #save the startingpointid needed to update the startingpoints - if ( $hash->{helper}{STARTINGPOINTID} ne - $decode_json->{settings}[$settings]{id} ) - { - $hash->{helper}{STARTINGPOINTID} = - $decode_json->{settings}[$settings]{id}; - } - - $hash->{helper}{STARTINGPOINTS} = - '{ "name": "starting_points", "value": ' - . encode_json( $decode_json->{settings}[$settings]{value} ) . '}'; - my $startpoint_cnt = 0; - - for my $startingpoint ( - @{ $decode_json->{settings}[$settings]{value} } ) - { - $startpoint_cnt++; - readingsBulkUpdateIfChanged( - $hash, - 'startpoint-' . $startpoint_cnt . '-enabled', - $startingpoint->{enabled} - ); - } - } - - $settings--; - } while ( $settings >= 0 ); - - - my $online_state = ReadingsVal($name , 'device_info-connection_status', 'unknown'); - - readingsBulkUpdate( $hash, 'state', - $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 - ? 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'); - - # 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( - $hash, 'state', - 'scheduled watering next start: ' - . ( - ReadingsVal( - $name, 'scheduling-scheduled_watering_next_start', - 'no timer' - ) - ) - ) if ( AttrVal( $name, 'model', 'unknown' ) eq 'ic24' ); - - readingsBulkUpdate( $hash, 'state', - ReadingsVal( $name, 'power-power_timer', 'no info from power-timer' ) ) - if ( AttrVal( $name, 'model', 'unknown' ) eq 'power' ); - - readingsEndUpdate( $hash, 1 ); - - Log3 $name, 4, "GardenaSmartDevice ($name) - readings was written"; - - return; -} - -################################## -################################## -#### my little helpers ########### - -sub ReadingLangGerman { - my $hash = shift; - my $readingValue = shift; - - my $name = $hash->{NAME}; - my %langGermanMapp = ( - 'ok_cutting' => 'mähen', - 'paused' => 'pausiert', - 'ok_searching' => 'suche Ladestation', - 'ok_charging' => 'lädt', - 'ok_leaving' => 'unterwegs zum Startpunkt', - 'wait_updating' => 'wird aktualisiert ...', - 'wait_power_up' => 'wird eingeschaltet ...', - 'parked_timer' => 'geparkt nach Zeitplan', - 'parked_park_selected' => 'geparkt', - 'off_disabled' => 'der Mäher ist ausgeschaltet', - 'off_hatch_open' => - 'deaktiviert. Abdeckung ist offen oder PIN-Code erforderlich', - 'unknown' => 'unbekannter Status', - 'error' => 'Fehler', - 'error_at_power_up' => 'Neustart ...', - 'off_hatch_closed' => 'Deaktiviert. Manueller Start erforderlich', - 'ok_cutting_timer_overridden' => 'manuelles mähen', - 'parked_autotimer' => 'geparkt durch SensorControl', - 'parked_daily_limit_reached' => 'abgeschlossen', - 'no_message' => 'kein Fehler', - 'outside_working_area' => 'außerhalb des Arbeitsbereichs', - 'no_loop_signal' => 'kein Schleifensignal', - 'wrong_loop_signal' => 'falsches Schleifensignal', - 'loop_sensor_problem_front' => 'Problem Schleifensensor, vorne', - 'loop_sensor_problem_rear' => 'Problem Schleifensensor, hinten', - 'trapped' => 'eingeschlossen', - 'upside_down' => 'steht auf dem Kopf', - 'low_battery' => 'niedriger Batteriestand', - 'empty_battery' => 'Batterie leer', - 'no_drive' => 'fährt nicht', - 'lifted' => 'angehoben', - 'stuck_in_charging_station' => 'eingeklemmt in Ladestation', - 'charging_station_blocked' => 'Ladestation blockiert', - 'collision_sensor_problem_rear' => 'Problem Stoßsensor hinten', - 'collision_sensor_problem_front' => 'Problem Stoßsensor vorne', - 'wheel_motor_blocked_right' => 'Radmotor rechts blockiert', - 'wheel_motor_blocked_left' => 'Radmotor links blockiert', - 'wheel_drive_problem_right' => 'Problem Antrieb, rechts', - 'wheel_drive_problem_left' => 'Problem Antrieb, links', - 'cutting_system_blocked' => 'Schneidsystem blockiert', - 'invalid_sub_device_combination' => 'fehlerhafte Verbindung', - 'settings_restored' => 'Standardeinstellungen', - 'electronic_problem' => 'elektronisches Problem', - 'charging_system_problem' => 'Problem Ladesystem', - 'tilt_sensor_problem' => 'Kippsensor Problem', - 'wheel_motor_overloaded_right' => 'rechter Radmotor überlastet', - 'wheel_motor_overloaded_left' => 'linker Radmotor überlastet', - 'charging_current_too_high' => 'Ladestrom zu hoch', - 'temporary_problem' => 'vorübergehendes Problem', - 'guide_1_not_found' => 'SK 1 nicht gefunden', - 'guide_2_not_found' => 'SK 2 nicht gefunden', - 'guide_3_not_found' => 'SK 3 nicht gefunden', - 'difficult_finding_home' => 'Problem die Ladestation zu finden', - 'guide_calibration_accomplished' => - 'Kalibrierung des Suchkabels beendet', - 'guide_calibration_failed' => - 'Kalibrierung des Suchkabels fehlgeschlagen', - 'temporary_battery_problem' => 'kurzzeitiges Batterieproblem', - 'battery_problem' => 'Batterieproblem', - 'alarm_mower_switched_off' => 'Alarm! Mäher ausgeschalten', - 'alarm_mower_stopped' => 'Alarm! Mäher gestoppt', - 'alarm_mower_lifted' => 'Alarm! Mäher angehoben', - 'alarm_mower_tilted' => 'Alarm! Mäher gekippt', - 'connection_changed' => 'Verbindung geändert', - 'connection_not_changed' => 'Verbindung nicht geändert', - 'com_board_not_available' => 'COM Board nicht verfügbar', - 'slipped' => 'rutscht', - 'out_of_operation' => 'ausser Betrieb', - 'replace_now' => 'kritischer Batteriestand, wechseln Sie jetzt', - 'low' => 'niedrig', - 'ok' => 'ok', - 'no_source' => 'ok', - 'mower_charging' => 'Mäher wurde geladen', - 'completed_cutting_autotimer' => 'Sensor Control erreicht', - 'week_timer' => 'Wochentimer erreicht', - 'countdown_timer' => 'Stoppuhr Timer', - 'undefined' => 'unklar', - 'unknown' => 'unklar', - 'status_device_unreachable' => 'Gerät ist nicht in Reichweite', - 'status_device_alive' => 'Gerät ist in Reichweite', - 'bad' => 'schlecht', - 'poor' => 'schwach', - 'good' => 'gut', - 'undefined' => 'unklar', - 'idle' => 'nichts zu tun', - 'firmware_cancel' => 'Firmwareupload unterbrochen', - 'firmware_upload' => 'Firmwareupload', - 'unsupported' => 'nicht unterstützt', - 'up_to_date' => 'auf dem neusten Stand', - 'mower' => 'Mäher', - 'watering_computer' => 'Bewässerungscomputer', - 'no_frost' => 'kein Frost', - 'open' => 'offen', - 'closed' => 'geschlossen', - 'included' => 'inbegriffen', - 'active' => 'aktiv', - 'inactive' => 'nicht aktiv' - ); - - if ( - defined( $langGermanMapp{$readingValue} ) - && ( AttrVal( 'global', 'language', 'none' ) eq 'DE' - || AttrVal( $name, 'readingValueLanguage', 'none' ) eq 'de' ) - && AttrVal( $name, 'readingValueLanguage', 'none' ) ne 'en' - ) - { - return $langGermanMapp{$readingValue}; - } - else { - return $readingValue; - } - - return; -} - -sub RigReadingsValue { - my $hash = shift; - my $readingValue = shift; - - my $rigReadingValue; - - if ( $readingValue =~ /^(\d+)-(\d\d)-(\d\d)T(\d\d)/ ) { - $rigReadingValue = Zulu2LocalString($readingValue); - } - else { - $rigReadingValue = ReadingLangGerman( $hash, $readingValue ); - } - - return $rigReadingValue; -} - -sub Zulu2LocalString { - my $t = shift; - - my ( $datehour, $datemin, $rest ) = split( /:/, $t, 3 ); - - my ( $year, $month, $day, $hour, $min ) = - $datehour =~ /(\d+)-(\d\d)-(\d\d)T(\d\d)/; - my $epoch = timegm( 0, 0, $hour, $day, $month - 1, $year ); - - my ( $lyear, $lmonth, $lday, $lhour, $isdst ) = - ( localtime($epoch) )[ 5, 4, 3, 2, -1 ]; - - $lyear += 1900; # year is 1900 based - $lmonth++; # month number is zero based - - if ( defined($rest) ) { - return ( - sprintf( - "%04d-%02d-%02d %02d:%02d:%s", - $lyear, $lmonth, $lday, - $lhour, $datemin, substr( $rest, 0, 2 ) - ) - ); - } - elsif ( $lyear < 2000 ) { - return 'temporarily unavailable'; - } - else { - return ( - sprintf( - "%04d-%02d-%02d %02d:%02d", - $lyear, $lmonth, $lday, $lhour, substr( $datemin, 0, 2 ) - ) - ); - } - - return; -} - -sub SetPredefinedStartPoints { - my $hash = shift; - my $aArg = shift; - - my ($startpoint_state,$startpoint_num,@morestartpoints) = @{$aArg}; - - my $name = $hash->{NAME}; - my $payload; - my $abilities; - - if ( defined($startpoint_state) && defined($startpoint_num) ) { - if ( defined( $hash->{helper}{STARTINGPOINTS} ) - && $hash->{helper}{STARTINGPOINTS} ne '' ) - { -# add needed parameters to saved settings config and change the value in request - my $decode_json_settings = - eval { decode_json( $hash->{helper}{STARTINGPOINTS} ) }; - if ($@) { - Log3 $name, 3, -"GardenaSmartBridge ($name) - JSON error while setting startpoint: $@"; - } - - $decode_json_settings->{device} = $hash->{DEVICEID}; - my $setval = $startpoint_state eq 'disable' ? \0 : \1; - $decode_json_settings->{value}[ $startpoint_num - 1 ]{enabled} = - $setval; - - #set more startpoints - if ( - defined scalar(@morestartpoints) - && ( scalar(@morestartpoints) == 2 - || scalar(@morestartpoints) == 4 ) - ) - { - if ( scalar(@morestartpoints) == 2 ) { - $setval = $morestartpoints[0] eq 'disable' ? \0 : \1; - $decode_json_settings->{value}[ $morestartpoints[1] - 1 ] - {enabled} = $setval; - - } - elsif ( scalar(@morestartpoints) == 4 ) { - $setval = $morestartpoints[0] eq 'disable' ? \0 : \1; - $decode_json_settings->{value}[ $morestartpoints[1] - 1 ] - {enabled} = $setval; - $setval = $morestartpoints[2] eq 'disable' ? \0 : \1; - $decode_json_settings->{value}[ $morestartpoints[3] - 1 ] - {enabled} = $setval; - } - } - - $payload = '"settings": ' . encode_json($decode_json_settings); - $abilities = 'mower_settings'; - #$abilities['service_id'] = $hash->{helper}{STARTINGPOINTID}; - } - else { - return - "startingpoints not loaded yet, please wait a couple of minutes", - undef, undef; - } - } - else { - return - "startpoint usage: set " - . $hash->{NAME} - . " startpoint disable 1 [enable 2] [disable 3]", undef, undef; - } - - return undef, $payload, $abilities; -} - -1; - -=pod - -=item device -=item summary Modul to control GardenaSmart Devices -=item summary_DE Modul zur Steuerung von GardenaSmartgeräten - -=begin html - - -

GardenaSmartDevice

-
    - In combination with GardenaSmartBridge this FHEM Module controls the GardenaSmart Device using the GardenaCloud -

    - Once the Bridge device is created, the connected devices are automatically recognized and created in FHEM.
    - From now on the devices can be controlled and changes in the GardenaAPP are synchronized with the state and readings of the devices. - -


    - Readings -
      -
    • battery-charging - Indicator if the Battery is charged (0/1) or with newer Firmware (false/true)
    • -
    • battery-level - load percentage of the Battery
    • -
    • battery-rechargeable_battery_status - healthyness of the battery (out_of_operation/replace_now/low/ok)
    • -
    • device_info-category - category of device (mower/watering_computer)
    • -
    • device_info-last_time_online - timestamp of last radio contact
    • -
    • device_info-manufacturer - manufacturer
    • -
    • device_info-product - product type
    • -
    • device_info-serial_number - serial number
    • -
    • device_info-sgtin -
    • -
    • device_info-version - firmware version
    • -
    • firmware-firmware_command - firmware command (idle/firmware_cancel/firmware_upload/unsupported)
    • -
    • firmware-firmware_status - firmware status
    • -
    • firmware-firmware_update_start - indicator when a firmwareupload is started
    • -
    • firmware-firmware_upload_progress - progress indicator of firmware update
    • -
    • firmware-inclusion_status - inclusion status
    • -
    • internal_temperature-temperature - internal device temperature
    • -
    • mower-error - actual error message -
        -
      • no_message
      • -
      • outside_working_area
      • -
      • no_loop_signal
      • -
      • wrong_loop_signal
      • -
      • loop_sensor_problem_front
      • -
      • loop_sensor_problem_rear
      • -
      • trapped
      • -
      • upside_down
      • -
      • low_battery
      • -
      • empty_battery
      • -
      • no_drive
      • -
      • lifted
      • -
      • stuck_in_charging_station
      • -
      • charging_station_blocked
      • -
      • collision_sensor_problem_rear
      • -
      • collision_sensor_problem_front
      • -
      • wheel_motor_blocked_right
      • -
      • wheel_motor_blocked_left
      • -
      • wheel_drive_problem_right
      • -
      • wheel_drive_problem_left
      • -
      • cutting_system_blocked
      • -
      • invalid_sub_device_combination
      • -
      • settings_restored
      • -
      • electronic_problem
      • -
      • charging_system_problem
      • -
      • tilt_sensor_problem
      • -
      • wheel_motor_overloaded_right
      • -
      • wheel_motor_overloaded_left
      • -
      • charging_current_too_high
      • -
      • temporary_problem
      • -
      • guide_1_not_found
      • -
      • guide_2_not_found
      • -
      • guide_3_not_found
      • -
      • difficult_finding_home
      • -
      • guide_calibration_accomplished
      • -
      • guide_calibration_failed
      • -
      • temporary_battery_problem
      • -
      • battery_problem
      • -
      • alarm_mower_switched_off
      • -
      • alarm_mower_stopped
      • -
      • alarm_mower_lifted
      • -
      • alarm_mower_tilted
      • -
      • connection_changed
      • -
      • connection_not_changed
      • -
      • com_board_not_available
      • -
      • slipped
      • -
      -
    • -
    • mower-manual_operation - (0/1) or with newer Firmware (false/true)
    • -
    • mower-override_end_time - manual override end time
    • -
    • mower-source_for_next_start - source for the next start -
        -
      • no_source
      • -
      • mower_charging
      • -
      • completed_cutting_autotimer
      • -
      • week_timer
      • -
      • countdown_timer
      • -
      • undefined
      • -
      -
    • -
    • mower-status - mower state (see state)
    • -
    • mower-timestamp_next_start - timestamp of next scheduled start
    • -
    • radio-connection_status - state of connection
    • -
    • radio-quality - percentage of the radio quality
    • -
    • radio-state - radio state (bad/poor/good/undefined)
    • -
    • state - state of the mower -
        -
      • paused
      • -
      • ok_cutting
      • -
      • ok_searching
      • -
      • ok_charging
      • -
      • ok_leaving
      • -
      • wait_updating
      • -
      • wait_power_up
      • -
      • parked_timer
      • -
      • parked_park_selected
      • -
      • off_disabled
      • -
      • off_hatch_open
      • -
      • unknown
      • -
      • error
      • -
      • error_at_power_up
      • -
      • off_hatch_closed
      • -
      • ok_cutting_timer_overridden
      • -
      • parked_autotimer
      • -
      • parked_daily_limit_reached
      • -
      -
    • -
    -

    - - Attributes -
      -
    • readingValueLanguage - Change the Language of Readings (de,en/if not set the default is english and the global language is not set at german)
    • -
    • model -
    • -
    -

    - - set -
      -
    • parkUntilFurtherNotice
    • -
    • parkUntilNextTimer
    • -
    • startOverrideTimer - (in minutes, 60 = 1h, 1440 = 24h, 4320 = 72h)
    • -
    • startResumeSchedule
    • -
    • startpoint enable|disable 1|2|3 - enables or disables one or more predefined start points
    • -
        -
      • set NAME startpoint enable 1
      • -
      • set NAME startpoint disable 3 enable 1
      • -
      -
    -
- -=end html -=begin html_DE - - -

GardenaSmartDevice

-
    - Zusammen mit dem Device GardenaSmartDevice stellt dieses FHEM Modul die Kommunikation zwischen der GardenaCloud und Fhem her. -

    - Wenn das GardenaSmartBridge Device erzeugt wurde, werden verbundene Geräte automatisch erkannt und in Fhem angelegt.
    - Von nun an können die eingebundenen Geräte gesteuert werden. Änderungen in der APP werden mit den Readings und dem Status syncronisiert. - -
-
-
    - Readings -
      -
    • battery-charging - Ladeindikator (0/1) oder mit neuerer Firmware (false/true)
    • -
    • battery-level - Ladezustand der Batterie in Prozent
    • -
    • battery-rechargeable_battery_status - Zustand der Batterie (Ausser Betrieb/Kritischer Batteriestand, wechseln Sie jetzt/Niedrig/oK)
    • -
    • device_info-category - Eigenschaft des Gerätes (Mäher/Bewässerungscomputer/Bodensensor)
    • -
    • device_info-last_time_online - Zeitpunkt der letzten Funkübertragung
    • -
    • device_info-manufacturer - Hersteller
    • -
    • device_info-product - Produkttyp
    • -
    • device_info-serial_number - Seriennummer
    • -
    • device_info-sgtin -
    • -
    • device_info-version - Firmware Version
    • -
    • firmware-firmware_command - Firmware Kommando (Nichts zu tun/Firmwareupload unterbrochen/Firmwareupload/nicht unterstützt)
    • -
    • firmware-firmware_status - Firmware Status
    • -
    • firmware-firmware_update_start - Firmwareupdate (0/1) oder mit neuerer Firmware (false/true)
    • -
    • firmware-firmware_upload_progress - Firmwareupdatestatus in Prozent
    • -
    • firmware-inclusion_status - Einbindungsstatus
    • -
    • internal_temperature-temperature - Interne Geräte Temperatur
    • -
    • mower-error - Aktuelle Fehler Meldung -
        -
      • Kein Fehler
      • -
      • Außerhalb des Arbeitsbereichs
      • -
      • Kein Schleifensignal
      • -
      • Falsches Schleifensignal
      • -
      • Problem Schleifensensor, vorne
      • -
      • Problem Schleifensensor, hinten
      • -
      • Eingeschlossen
      • -
      • Steht auf dem Kopf
      • -
      • Niedriger Batteriestand
      • -
      • Batterie ist leer
      • -
      • Kein Antrieb
      • -
      • Angehoben
      • -
      • Eingeklemmt in Ladestation
      • -
      • Ladestation blockiert
      • -
      • Problem Stoßsensor hinten
      • -
      • Problem Stoßsensor vorne
      • -
      • Radmotor rechts blockiert
      • -
      • Radmotor links blockiert
      • -
      • Problem Antrieb, rechts
      • -
      • Problem Antrieb, links
      • -
      • Schneidsystem blockiert
      • -
      • Fehlerhafte Verbindung
      • -
      • Standardeinstellungen
      • -
      • Elektronisches Problem
      • -
      • Problem Ladesystem
      • -
      • Kippsensorproblem
      • -
      • Rechter Radmotor überlastet
      • -
      • Linker Radmotor überlastet
      • -
      • Ladestrom zu hoch
      • -
      • Vorübergehendes Problem
      • -
      • SK 1 nicht gefunden
      • -
      • SK 2 nicht gefunden
      • -
      • SK 3 nicht gefunden
      • -
      • Problem die Ladestation zu finden
      • -
      • Kalibration des Suchkabels beendet
      • -
      • Kalibration des Suchkabels fehlgeschlagen
      • -
      • Kurzzeitiges Batterieproblem
      • -
      • Batterieproblem
      • -
      • Alarm! Mäher ausgeschalten
      • -
      • Alarm! Mäher gestoppt
      • -
      • Alarm! Mäher angehoben
      • -
      • Alarm! Mäher gekippt
      • -
      • Verbindung geändert
      • -
      • Verbindung nicht geändert
      • -
      • COM board nicht verfügbar
      • -
      • Rutscht
      • -
      -
    • -
    • mower-manual_operation - Manueller Betrieb (0/1) oder mit neuerer Firmware (false/true)
    • -
    • mower-override_end_time - Zeitpunkt wann der manuelle Betrieb beendet ist
    • -
    • mower-source_for_next_start - Grund für den nächsten Start -
        -
      • Kein Grund
      • -
      • Mäher wurde geladen
      • -
      • SensorControl erreicht
      • -
      • Wochentimer erreicht
      • -
      • Stoppuhr Timer
      • -
      • Undefiniert
      • -
      -
    • -
    • mower-status - Mäher Status (siehe state)
    • -
    • mower-timestamp_next_start - Zeitpunkt des nächsten geplanten Starts
    • -
    • radio-connection_status - Status der Funkverbindung
    • -
    • radio-quality - Indikator für die Funkverbindung in Prozent
    • -
    • radio-state - radio state (schlecht/schwach/gut/Undefiniert)
    • -
    • state - Staus des Mähers -
        -
      • Pausiert
      • -
      • Mähen
      • -
      • Suche Ladestation
      • -
      • Lädt
      • -
      • Mähen
      • -
      • Wird aktualisiert ...
      • -
      • Wird eingeschaltet ...
      • -
      • Geparkt nach Zeitplan
      • -
      • Geparkt
      • -
      • Der Mäher ist ausgeschaltet
      • -
      • Deaktiviert. Abdeckung ist offen oder PIN-Code erforderlich
      • -
      • Unbekannter Status
      • -
      • Fehler
      • -
      • Neustart ...
      • -
      • Deaktiviert. Manueller Start erforderlich
      • -
      • Manuelles Mähen
      • -
      • Geparkt durch SensorControl
      • -
      • Abgeschlossen
      • -
      -
    • -
    -

    - - Attribute -
      -
    • readingValueLanguage - Änderung der Sprache der Readings (de,en/wenn nichts gesetzt ist, dann Englisch es sei denn deutsch ist als globale Sprache gesetzt)
    • -
    • model -
    • -
    - - set -
      -
    • parkUntilFurtherNotice - Parken des Mähers unter Umgehung des Zeitplans
    • -
    • parkUntilNextTimer - Parken bis zum nächsten Zeitplan
    • -
    • startOverrideTimer - Manuelles mähen (in Minuten, 60 = 1h, 1440 = 24h, 4320 = 72h)
    • -
    • startResumeSchedule - Weiterführung des Zeitplans
    • -
    • startpoint enable|disable 1|2|3 - Aktiviert oder deaktiviert einen vordefinierten Startbereich
    • -
        -
      • set NAME startpoint enable 1
      • -
      • set NAME startpoint disable 3 enable 1
      • -
      -
    -
- -=end html_DE - -=for :application/json;q=META.json 74_GardenaSmartDevice.pm -{ - "abstract": "Modul to control GardenaSmart Devices", - "x_lang": { - "de": { - "abstract": "Modul zur Steuerung von Gardena Smart Geräten" - } - }, - "keywords": [ - "fhem-mod-device", - "fhem-core", - "Garden", - "Gardena", - "Smart" - ], - "release_status": "stable", - "license": "GPL_2", - "version": "v2.2.3", - "author": [ - "Marko Oldenburg " - ], - "x_fhem_maintainer": [ - "CoolTux" - ], - "x_fhem_maintainer_github": [ - "LeonGaultier" - ], - "prereqs": { - "runtime": { - "requires": { - "FHEM": 5.00918799, - "perl": 5.016, - "Meta": 0, - "JSON": 0, - "Time::Local": 0 - }, - "recommends": { - }, - "suggests": { - } - } - } -} -=end :application/json;q=META.json - -=cut +############################################################################### +# +# Developed with Kate +# +# (c) 2017-2021 Copyright: Marko Oldenburg (fhemdevelopment at cooltux dot net) +# All rights reserved +# +# Special thanks goes to comitters: +# - Michael (mbrak) Thanks for Commandref +# - Matthias (Kenneth) Thanks for Wiki entry +# - BioS Thanks for predefined start points Code +# - fettgu Thanks for Debugging Irrigation Control data flow +# - Sebastian (BOFH) Thanks for new Auth Code after API Change +# +# +# 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/ +# +## +## + +## unserer packagename +package FHEM::GardenaSmartDevice; +use GPUtils qw(GP_Import GP_Export); + +use strict; +use warnings; +use POSIX; +use FHEM::Meta; +use Time::Local; + +# try to use JSON::MaybeXS wrapper +# for chance of better performance + open code +eval { + require JSON::MaybeXS; + import JSON::MaybeXS qw( decode_json encode_json ); + 1; +}; + +if ($@) { + $@ = undef; + + # try to use JSON wrapper + # for chance of better performance + eval { + + # JSON preference order + local $ENV{PERL_JSON_BACKEND} = + 'Cpanel::JSON::XS,JSON::XS,JSON::PP,JSON::backportPP' + unless ( defined( $ENV{PERL_JSON_BACKEND} ) ); + + require JSON; + import JSON qw( decode_json encode_json ); + 1; + }; + + if ($@) { + $@ = undef; + + # In rare cases, Cpanel::JSON::XS may + # be installed but JSON|JSON::MaybeXS not ... + eval { + require Cpanel::JSON::XS; + import Cpanel::JSON::XS qw(decode_json encode_json); + 1; + }; + + if ($@) { + $@ = undef; + + # In rare cases, JSON::XS may + # be installed but JSON not ... + eval { + require JSON::XS; + import JSON::XS qw(decode_json encode_json); + 1; + }; + + if ($@) { + $@ = undef; + + # Fallback to built-in JSON which SHOULD + # be available since 5.014 ... + eval { + require JSON::PP; + import JSON::PP qw(decode_json encode_json); + 1; + }; + + if ($@) { + $@ = undef; + + # Fallback to JSON::backportPP in really rare cases + require JSON::backportPP; + import JSON::backportPP qw(decode_json encode_json); + 1; + } + } + } + } +} + +## Import der FHEM Funktionen +#-- Run before package compilation +BEGIN { + + # Import from main context + GP_Import( + qw(readingsSingleUpdate + readingsBulkUpdate + readingsBulkUpdateIfChanged + readingsBeginUpdate + readingsEndUpdate + Log3 + CommandAttr + AttrVal + ReadingsVal + readingFnAttributes + AssignIoPort + modules + IOWrite + defs + makeDeviceName) + ); +} + +#-- Export to main context with different name +GP_Export( + qw( + Initialize + ) +); + +sub Initialize { + my $hash = shift; + + $hash->{Match} = '^{"id":".*'; + + $hash->{SetFn} = \&Set; + $hash->{DefFn} = \&Define; + $hash->{UndefFn} = \&Undef; + $hash->{ParseFn} = \&Parse; + + $hash->{AttrFn} = \&Attr; + $hash->{AttrList} = + "readingValueLanguage:de,en " + . "model:watering_computer,sensor,mower,ic24,power,electronic_pressure_pump " + . "IODev " + . $readingFnAttributes; + $hash->{parseParams} = 1; + + return FHEM::Meta::InitMod( __FILE__, $hash ); +} + +sub Define { + my $hash = shift // return; + my $aArg = shift // return; + + return $@ unless ( FHEM::Meta::SetInternals($hash) ); + use version 0.60; our $VERSION = FHEM::Meta::Get( $hash, 'version' ); + + return + "too few parameters: define GardenaSmartDevice " + if ( scalar( @{$aArg} ) < 3 ); + + my $name = $aArg->[0]; + my $deviceId = $aArg->[2]; + my $category = $aArg->[3]; + + $hash->{DEVICEID} = $deviceId; + $hash->{VERSION} = version->parse($VERSION)->normal; + $hash->{helper}{STARTINGPOINTID} = ''; + $hash->{helper}{schedules_paused_until_id} = ''; + $hash->{helper}{eco_mode_id} = ''; + # IrrigationControl valve control max 6 + $hash->{helper}{schedules_paused_until_1_id} = ''; + $hash->{helper}{schedules_paused_until_2_id} = ''; + $hash->{helper}{schedules_paused_until_3_id} = ''; + $hash->{helper}{schedules_paused_until_4_id} = ''; + $hash->{helper}{schedules_paused_until_5_id} = ''; + $hash->{helper}{schedules_paused_until_6_id} = ''; + + CommandAttr( undef, + "$name IODev $modules{GardenaSmartBridge}{defptr}{BRIDGE}->{NAME}" ) + if ( AttrVal( $name, 'IODev', 'none' ) eq 'none' ); + + my $iodev = AttrVal( $name, 'IODev', 'none' ); + + 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 ); + + CommandAttr( undef, $name . ' room GardenaSmart' ) + if ( AttrVal( $name, 'room', 'none' ) eq 'none' ); + + CommandAttr( undef, $name . ' model ' . $category ) + if ( AttrVal( $name, 'model', 'none' ) eq 'none' ); + + Log3 $name, 3, +"GardenaSmartDevice ($name) - defined GardenaSmartDevice with DEVICEID: $deviceId"; + readingsSingleUpdate( $hash, 'state', 'initialized', 1 ); + + $modules{GardenaSmartDevice}{defptr}{$deviceId} = $hash; + + return; +} + +sub Undef { + my $hash = shift; + my $arg = shift; + + my $name = $hash->{NAME}; + my $deviceId = $hash->{DEVICEID}; + + delete $modules{GardenaSmartDevice}{defptr}{$deviceId}; + + return; +} + +sub Attr { + + my ( $cmd, $name, $attrName, $attrVal ) = @_; + my $hash = $defs{$name}; + + return; +} + +sub Set { + my $hash = shift // return; + my $aArg = shift // return; + + my $name = shift @$aArg; + 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 ); + + #set default abilitie ... overwrite in cmd to change + $abilities = 'mower' + if ( AttrVal( $name, 'model', 'unknown' ) eq 'mower' ); + $abilities = 'watering' + if ( AttrVal( $name, 'model', 'unknown' ) eq 'ic24' + || AttrVal( $name, 'model', 'unknown' ) eq 'watering_computer' ); + $abilities = 'power' + if ( AttrVal( $name, 'model', 'unknown' ) eq 'power' ); + $abilities = 'manual_watering' + if ( AttrVal( $name, 'model', 'unknown' ) eq 'electronic_pressure_pump' ); + + ### mower + # service_id (eco, parkuntilfurhternotice, startpoints) + if ( lc $cmd eq 'parkuntilfurthernotice' ) { + $payload = '"name":"park_until_further_notice"'; + if ( $mainboard_version > 10.30 ) { + $payload = ' "settings":{"name":"schedules_paused_until","value":"2040-12-31T22: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}' ; + $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' ; + $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 .'}'; + $abilities = 'mower_timer'; + } + + } + elsif ( lc $cmd eq 'startpoint' ) { + my $err; + ( $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}.'"}'; + $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 ); + } + ### electronic_pressure_pump + elsif ( lc $cmd eq 'pumptimer' ) { + $payload = + '"name":"pump_manual_watering_timer","parameters":{"duration":' + . $aArg->[0] . '}'; + } + ### watering_computer + elsif ( lc $cmd eq 'manualoverride' ) { + $payload = + '"properties":{"name":"watering_timer_1' + . '","value":{"state":"manual","duration":' + . $aArg->[0] * 60 + . ',"valve_id":1}}'; + } + elsif ( $cmd =~ m{\AcancelOverride}xms ) { + + my $valve_id = 1; + + if ( $cmd =~ m{\AcancelOverrideValve(\d)\z}xms ) { + $valve_id = $1; + } + + $payload = + '"properties":{"name":"watering_timer_' + . $valve_id + . '","value":{"state":"idle","duration":' + . 0 + . ',"valve_id":' + . $valve_id . '}}'; + } + elsif ( lc $cmd eq 'on' || lc $cmd eq 'off' || lc $cmd eq 'on-for-timer' ) { + my $val = ( + defined($aArg) && ref($aArg) eq 'ARRAY' + ? $aArg->[0] * 60 + : lc $cmd + ); + + $payload = '"properties":{"value":"' . $val . '"}'; + } + ### Watering ic24 + elsif ( $cmd =~ m{\AmanualDurationValve\d\z}xms ) { + my $valve_id; + + if ( $cmd =~ m{\AmanualDurationValve(\d)\z}xms ) { + $valve_id = $1; + } + + $payload = + '"properties":{"name":"watering_timer_' + . $valve_id + . '","value":{"state":"manual","duration":' + . $aArg->[0] * 60 + . ',"valve_id":' + . $valve_id . '}}'; + } + elsif ( $cmd eq 'closeAllValves' ){ + $payload = '"name":"close_all_valves","parameters":{}'; + } + elsif ( $cmd =~ '/.*ScheduleValve/' ){ + my $valve_id = $1; + $abilities = 'irrigation_settings'; + $service_id = $hash->{helper}->{'schedules_paused_until_'.$valve_id.'_id'}; + $payload = '"settings":{"name":"schedules_paused_until_' + .$valve_id. + '", "value":"' + . ($cmd eq 'resumeScheduleValve') ? '' : '2040-12-31T22:00:00.000Z' + .'","device":"' + .$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') { + $payload = '"name":"measure_ambient_temperature"'; + $abilities = 'ambient_temperature'; + } else { + $payload = '"name":"measure_soil_temperature"'; + $abilities = 'soil_temperature'; + } + } + elsif ( lc $sensname eq 'light' ) { + $payload = '"name":"measure_light"'; + $abilities = 'light'; + + } + elsif ( lc $sensname eq 'humidity' ) { + $payload = '"name":"measure_soil_humidity"'; + $abilities = 'humidity'; + } + + + } + else { + + my $list = ''; + + $list .= +'parkUntilFurtherNotice:noArg parkUntilNextTimer:noArg startResumeSchedule:noArg startOverrideTimer:slider,0,1,60 startpoint' + if ( AttrVal( $name, 'model', 'unknown' ) eq 'mower' ); + + $list .= 'manualOverride:slider,1,1,59 cancelOverride:noArg' + if ( AttrVal( $name, 'model', 'unknown' ) eq 'watering_computer' ); + + $list .= +'closeAllValves:noArg stopScheduleValve:selectnumbers,1,1,6,0,lin resumeScheduleValve:selectnumbers,1,1,6,0,lin manualDurationValve1:slider,1,1,59 manualDurationValve2:slider,1,1,59 manualDurationValve3:slider,1,1,59 manualDurationValve4:slider,1,1,59 manualDurationValve5:slider,1,1,59 manualDurationValve6:slider,1,1,59 cancelOverrideValve1:noArg cancelOverrideValve2:noArg cancelOverrideValve3:noArg cancelOverrideValve4:noArg cancelOverrideValve5:noArg cancelOverrideValve6:noArg' + if ( AttrVal( $name, 'model', 'unknown' ) eq 'ic24' ); + + $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' ); + + $list .= 'on:noArg off:noArg on-for-timer:slider,0,1,60' + if ( AttrVal( $name, 'model', 'unknown' ) eq 'power' ); + + return "Unknown argument $cmd, choose one of $list"; + } + + $hash->{helper}{deviceAction} = $payload; + readingsSingleUpdate( $hash, "state", "send command to gardena cloud", 1 ); + + IOWrite( $hash, $payload, $hash->{DEVICEID}, $abilities, $service_id ); + Log3 $name, 4, +"GardenaSmartBridge ($name) - IOWrite: $payload $hash->{DEVICEID} $abilities IODevHash=$hash->{IODev}"; + + return; +} + +sub Parse { + my $io_hash = shift; + my $json = shift; + + my $name = $io_hash->{NAME}; + + my $decode_json = eval { decode_json($json) }; + if ($@) { + Log3 $name, 3, + "GardenaSmartDevice ($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}; + + WriteReadings( $hash, $decode_json ); + Log3 $name, 4, + "GardenaSmartDevice ($name) - find logical device: $hash->{NAME}"; + + return $hash->{NAME}; + + } + else { + + Log3 $name, 3, + "GardenaSmartDevice ($name) - autocreate new device " + . makeDeviceName( $decode_json->{name} ) + . " with deviceId $decode_json->{id}, model $decode_json->{category}"; + return + "UNDEFINED " + . makeDeviceName( $decode_json->{name} ) + . " GardenaSmartDevice $decode_json->{id} $decode_json->{category}"; + } + } + + return; +} + +sub WriteReadings { + my $hash = shift; + my $decode_json = shift; + + my $name = $hash->{NAME}; + my $abilities = scalar( @{ $decode_json->{abilities} } ); + my $settings = scalar( @{ $decode_json->{settings} } ); + + readingsBeginUpdate($hash); + + do { + + if ( + ref( $decode_json->{abilities}[$abilities]{properties} ) eq "ARRAY" + && scalar( @{ $decode_json->{abilities}[$abilities]{properties} } ) + > 0 ) + { + for my $propertie ( + @{ $decode_json->{abilities}[$abilities]{properties} } ) + { + readingsBulkUpdateIfChanged( + $hash, + $decode_json->{abilities}[$abilities]{name} . '-' + . $propertie->{name}, + RigReadingsValue( $hash, $propertie->{value} ) + ) + if ( defined( $propertie->{value} ) + && $decode_json->{abilities}[$abilities]{name} . '-' + . $propertie->{name} ne 'radio-quality' + && $decode_json->{abilities}[$abilities]{name} . '-' + . $propertie->{name} ne 'battery-level' + && $decode_json->{abilities}[$abilities]{name} . '-' + . $propertie->{name} ne 'internal_temperature-temperature' + && $decode_json->{abilities}[$abilities]{name} . '-' + . $propertie->{name} ne 'ambient_temperature-temperature' + && $decode_json->{abilities}[$abilities]{name} . '-' + . $propertie->{name} ne 'soil_temperature-temperature' + && $decode_json->{abilities}[$abilities]{name} . '-' + . $propertie->{name} ne 'humidity-humidity' + && $decode_json->{abilities}[$abilities]{name} . '-' + . $propertie->{name} ne 'light-light' + && ref( $propertie->{value} ) ne "HASH" ); + + readingsBulkUpdateIfChanged( + $hash, + $decode_json->{abilities}[$abilities]{name} . '-' + . $propertie->{name}, + RigReadingsValue( $hash, $propertie->{value} ) + ) + if ( + defined( $propertie->{value} ) + && ( $decode_json->{abilities}[$abilities]{name} . '-' + . $propertie->{name} eq 'radio-quality' + || $decode_json->{abilities}[$abilities]{name} . '-' + . $propertie->{name} eq 'battery-level' + || $decode_json->{abilities}[$abilities]{name} . '-' + . $propertie->{name} eq + 'internal_temperature-temperature' + || $decode_json->{abilities}[$abilities]{name} . '-' + . $propertie->{name} eq + 'ambient_temperature-temperature' + || $decode_json->{abilities}[$abilities]{name} . '-' + . $propertie->{name} eq 'soil_temperature-temperature' + || $decode_json->{abilities}[$abilities]{name} . '-' + . $propertie->{name} eq 'humidity-humidity' + || $decode_json->{abilities}[$abilities]{name} . '-' + . $propertie->{name} eq 'light-light' ) + ); + + readingsBulkUpdateIfChanged( + $hash, + $decode_json->{abilities}[$abilities]{name} . '-' + . $propertie->{name}, + join( ',', @{ $propertie->{value} } ) + ) + if ( defined( $propertie->{value} ) + && $decode_json->{abilities}[$abilities]{name} . '-' + . $propertie->{name} eq 'ic24-valves_connected' ); + + readingsBulkUpdateIfChanged( + $hash, + $decode_json->{abilities}[$abilities]{name} . '-' + . $propertie->{name}, + join( ',', @{ $propertie->{value} } ) + ) + if ( defined( $propertie->{value} ) + && $decode_json->{abilities}[$abilities]{name} . '-' + . $propertie->{name} eq 'ic24-valves_master_config' ); + + if ( ref( $propertie->{value} ) eq "HASH" ) { + while ( my ( $r, $v ) = each %{ $propertie->{value} } ) { + readingsBulkUpdate( + $hash, + $decode_json->{abilities}[$abilities]{name} . '-' + . $propertie->{name} . '_' + . $r, + RigReadingsValue( $hash, $v ) + ); + } + } + } + } + + $abilities--; + } 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"); + + if ( exists($decode_json->{settings}[$settings]{name}) + && ( + $decode_json->{settings}[$settings]{name} =~ /schedules_paused_until_?\d?$/ + || $decode_json->{settings}[$settings]{name} eq 'eco_mode' ) + ) + { + if ( $hash->{helper}{$decode_json->{settings}[$settings]{name}.'_id'} ne + $decode_json->{settings}[$settings]{id} ) + { + $hash->{helper}{$decode_json->{settings}[$settings]{name}.'_id'} = + $decode_json->{settings}[$settings]{id}; + } + } + + if ( ref( $decode_json->{settings}[$settings]{value} ) eq "ARRAY" + && $decode_json->{settings}[$settings]{name} eq 'starting_points' ) + { + #save the startingpointid needed to update the startingpoints + if ( $hash->{helper}{STARTINGPOINTID} ne + $decode_json->{settings}[$settings]{id} ) + { + $hash->{helper}{STARTINGPOINTID} = + $decode_json->{settings}[$settings]{id}; + } + + $hash->{helper}{STARTINGPOINTS} = + '{ "name": "starting_points", "value": ' + . encode_json( $decode_json->{settings}[$settings]{value} ) . '}'; + my $startpoint_cnt = 0; + + for my $startingpoint ( + @{ $decode_json->{settings}[$settings]{value} } ) + { + $startpoint_cnt++; + readingsBulkUpdateIfChanged( + $hash, + 'startpoint-' . $startpoint_cnt . '-enabled', + $startingpoint->{enabled} + ); + } + } + + $settings--; + } while ( $settings >= 0 ); + + + my $online_state = ReadingsVal($name , 'device_info-connection_status', 'unknown'); + + readingsBulkUpdate( $hash, 'state', + $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 + ? 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'); + + # 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( + $hash, 'state', + 'scheduled watering next start: ' + . ( + ReadingsVal( + $name, 'scheduling-scheduled_watering_next_start', + 'no timer' + ) + ) + ) if ( AttrVal( $name, 'model', 'unknown' ) eq 'ic24' ); + + readingsBulkUpdate( $hash, 'state', + ReadingsVal( $name, 'power-power_timer', 'no info from power-timer' ) ) + if ( AttrVal( $name, 'model', 'unknown' ) eq 'power' ); + + readingsEndUpdate( $hash, 1 ); + + Log3 $name, 4, "GardenaSmartDevice ($name) - readings was written"; + + return; +} + +################################## +################################## +#### my little helpers ########### + +sub ReadingLangGerman { + my $hash = shift; + my $readingValue = shift; + + my $name = $hash->{NAME}; + my %langGermanMapp = ( + 'ok_cutting' => 'mähen', + 'paused' => 'pausiert', + 'ok_searching' => 'suche Ladestation', + 'ok_charging' => 'lädt', + 'ok_leaving' => 'unterwegs zum Startpunkt', + 'wait_updating' => 'wird aktualisiert ...', + 'wait_power_up' => 'wird eingeschaltet ...', + 'parked_timer' => 'geparkt nach Zeitplan', + 'parked_park_selected' => 'geparkt', + 'off_disabled' => 'der Mäher ist ausgeschaltet', + 'off_hatch_open' => + 'deaktiviert. Abdeckung ist offen oder PIN-Code erforderlich', + 'unknown' => 'unbekannter Status', + 'error' => 'Fehler', + 'error_at_power_up' => 'Neustart ...', + 'off_hatch_closed' => 'Deaktiviert. Manueller Start erforderlich', + 'ok_cutting_timer_overridden' => 'manuelles mähen', + 'parked_autotimer' => 'geparkt durch SensorControl', + 'parked_daily_limit_reached' => 'abgeschlossen', + 'no_message' => 'kein Fehler', + 'outside_working_area' => 'außerhalb des Arbeitsbereichs', + 'no_loop_signal' => 'kein Schleifensignal', + 'wrong_loop_signal' => 'falsches Schleifensignal', + 'loop_sensor_problem_front' => 'Problem Schleifensensor, vorne', + 'loop_sensor_problem_rear' => 'Problem Schleifensensor, hinten', + 'trapped' => 'eingeschlossen', + 'upside_down' => 'steht auf dem Kopf', + 'low_battery' => 'niedriger Batteriestand', + 'empty_battery' => 'Batterie leer', + 'no_drive' => 'fährt nicht', + 'lifted' => 'angehoben', + 'stuck_in_charging_station' => 'eingeklemmt in Ladestation', + 'charging_station_blocked' => 'Ladestation blockiert', + 'collision_sensor_problem_rear' => 'Problem Stoßsensor hinten', + 'collision_sensor_problem_front' => 'Problem Stoßsensor vorne', + 'wheel_motor_blocked_right' => 'Radmotor rechts blockiert', + 'wheel_motor_blocked_left' => 'Radmotor links blockiert', + 'wheel_drive_problem_right' => 'Problem Antrieb, rechts', + 'wheel_drive_problem_left' => 'Problem Antrieb, links', + 'cutting_system_blocked' => 'Schneidsystem blockiert', + 'invalid_sub_device_combination' => 'fehlerhafte Verbindung', + 'settings_restored' => 'Standardeinstellungen', + 'electronic_problem' => 'elektronisches Problem', + 'charging_system_problem' => 'Problem Ladesystem', + 'tilt_sensor_problem' => 'Kippsensor Problem', + 'wheel_motor_overloaded_right' => 'rechter Radmotor überlastet', + 'wheel_motor_overloaded_left' => 'linker Radmotor überlastet', + 'charging_current_too_high' => 'Ladestrom zu hoch', + 'temporary_problem' => 'vorübergehendes Problem', + 'guide_1_not_found' => 'SK 1 nicht gefunden', + 'guide_2_not_found' => 'SK 2 nicht gefunden', + 'guide_3_not_found' => 'SK 3 nicht gefunden', + 'difficult_finding_home' => 'Problem die Ladestation zu finden', + 'guide_calibration_accomplished' => + 'Kalibrierung des Suchkabels beendet', + 'guide_calibration_failed' => + 'Kalibrierung des Suchkabels fehlgeschlagen', + 'temporary_battery_problem' => 'kurzzeitiges Batterieproblem', + 'battery_problem' => 'Batterieproblem', + 'alarm_mower_switched_off' => 'Alarm! Mäher ausgeschalten', + 'alarm_mower_stopped' => 'Alarm! Mäher gestoppt', + 'alarm_mower_lifted' => 'Alarm! Mäher angehoben', + 'alarm_mower_tilted' => 'Alarm! Mäher gekippt', + 'connection_changed' => 'Verbindung geändert', + 'connection_not_changed' => 'Verbindung nicht geändert', + 'com_board_not_available' => 'COM Board nicht verfügbar', + 'slipped' => 'rutscht', + 'out_of_operation' => 'ausser Betrieb', + 'replace_now' => 'kritischer Batteriestand, wechseln Sie jetzt', + 'low' => 'niedrig', + 'ok' => 'ok', + 'no_source' => 'ok', + 'mower_charging' => 'Mäher wurde geladen', + 'completed_cutting_autotimer' => 'Sensor Control erreicht', + 'week_timer' => 'Wochentimer erreicht', + 'countdown_timer' => 'Stoppuhr Timer', + 'undefined' => 'unklar', + 'unknown' => 'unklar', + 'status_device_unreachable' => 'Gerät ist nicht in Reichweite', + 'status_device_alive' => 'Gerät ist in Reichweite', + 'bad' => 'schlecht', + 'poor' => 'schwach', + 'good' => 'gut', + 'undefined' => 'unklar', + 'idle' => 'nichts zu tun', + 'firmware_cancel' => 'Firmwareupload unterbrochen', + 'firmware_upload' => 'Firmwareupload', + 'unsupported' => 'nicht unterstützt', + 'up_to_date' => 'auf dem neusten Stand', + 'mower' => 'Mäher', + 'watering_computer' => 'Bewässerungscomputer', + 'no_frost' => 'kein Frost', + 'open' => 'offen', + 'closed' => 'geschlossen', + 'included' => 'inbegriffen', + 'active' => 'aktiv', + 'inactive' => 'nicht aktiv' + ); + + if ( + defined( $langGermanMapp{$readingValue} ) + && ( AttrVal( 'global', 'language', 'none' ) eq 'DE' + || AttrVal( $name, 'readingValueLanguage', 'none' ) eq 'de' ) + && AttrVal( $name, 'readingValueLanguage', 'none' ) ne 'en' + ) + { + return $langGermanMapp{$readingValue}; + } + else { + return $readingValue; + } + + return; +} + +sub RigReadingsValue { + my $hash = shift; + my $readingValue = shift; + + my $rigReadingValue; + + if ( $readingValue =~ /^(\d+)-(\d\d)-(\d\d)T(\d\d)/ ) { + $rigReadingValue = Zulu2LocalString($readingValue); + } + else { + $rigReadingValue = ReadingLangGerman( $hash, $readingValue ); + } + + return $rigReadingValue; +} + +sub Zulu2LocalString { + my $t = shift; + + my ( $datehour, $datemin, $rest ) = split( /:/, $t, 3 ); + + my ( $year, $month, $day, $hour, $min ) = + $datehour =~ /(\d+)-(\d\d)-(\d\d)T(\d\d)/; + my $epoch = timegm( 0, 0, $hour, $day, $month - 1, $year ); + + my ( $lyear, $lmonth, $lday, $lhour, $isdst ) = + ( localtime($epoch) )[ 5, 4, 3, 2, -1 ]; + + $lyear += 1900; # year is 1900 based + $lmonth++; # month number is zero based + + if ( defined($rest) ) { + return ( + sprintf( + "%04d-%02d-%02d %02d:%02d:%s", + $lyear, $lmonth, $lday, + $lhour, $datemin, substr( $rest, 0, 2 ) + ) + ); + } + elsif ( $lyear < 2000 ) { + return 'temporarily unavailable'; + } + else { + return ( + sprintf( + "%04d-%02d-%02d %02d:%02d", + $lyear, $lmonth, $lday, $lhour, substr( $datemin, 0, 2 ) + ) + ); + } + + return; +} + +sub SetPredefinedStartPoints { + my $hash = shift; + my $aArg = shift; + + my ($startpoint_state,$startpoint_num,@morestartpoints) = @{$aArg}; + + my $name = $hash->{NAME}; + my $payload; + my $abilities; + + if ( defined($startpoint_state) && defined($startpoint_num) ) { + if ( defined( $hash->{helper}{STARTINGPOINTS} ) + && $hash->{helper}{STARTINGPOINTS} ne '' ) + { +# add needed parameters to saved settings config and change the value in request + my $decode_json_settings = + eval { decode_json( $hash->{helper}{STARTINGPOINTS} ) }; + if ($@) { + Log3 $name, 3, +"GardenaSmartBridge ($name) - JSON error while setting startpoint: $@"; + } + + $decode_json_settings->{device} = $hash->{DEVICEID}; + my $setval = $startpoint_state eq 'disable' ? \0 : \1; + $decode_json_settings->{value}[ $startpoint_num - 1 ]{enabled} = + $setval; + + #set more startpoints + if ( + defined scalar(@morestartpoints) + && ( scalar(@morestartpoints) == 2 + || scalar(@morestartpoints) == 4 ) + ) + { + if ( scalar(@morestartpoints) == 2 ) { + $setval = $morestartpoints[0] eq 'disable' ? \0 : \1; + $decode_json_settings->{value}[ $morestartpoints[1] - 1 ] + {enabled} = $setval; + + } + elsif ( scalar(@morestartpoints) == 4 ) { + $setval = $morestartpoints[0] eq 'disable' ? \0 : \1; + $decode_json_settings->{value}[ $morestartpoints[1] - 1 ] + {enabled} = $setval; + $setval = $morestartpoints[2] eq 'disable' ? \0 : \1; + $decode_json_settings->{value}[ $morestartpoints[3] - 1 ] + {enabled} = $setval; + } + } + + $payload = '"settings": ' . encode_json($decode_json_settings); + $abilities = 'mower_settings'; + #$abilities['service_id'] = $hash->{helper}{STARTINGPOINTID}; + } + else { + return + "startingpoints not loaded yet, please wait a couple of minutes", + undef, undef; + } + } + else { + return + "startpoint usage: set " + . $hash->{NAME} + . " startpoint disable 1 [enable 2] [disable 3]", undef, undef; + } + + return undef, $payload, $abilities; +} + +1; + +=pod + +=item device +=item summary Modul to control GardenaSmart Devices +=item summary_DE Modul zur Steuerung von GardenaSmartgeräten + +=begin html + + +

GardenaSmartDevice

+
    + In combination with GardenaSmartBridge this FHEM Module controls the GardenaSmart Device using the GardenaCloud +

    + Once the Bridge device is created, the connected devices are automatically recognized and created in FHEM.
    + From now on the devices can be controlled and changes in the GardenaAPP are synchronized with the state and readings of the devices. + +


    + Readings +
      +
    • battery-charging - Indicator if the Battery is charged (0/1) or with newer Firmware (false/true)
    • +
    • battery-level - load percentage of the Battery
    • +
    • battery-rechargeable_battery_status - healthyness of the battery (out_of_operation/replace_now/low/ok)
    • +
    • device_info-category - category of device (mower/watering_computer)
    • +
    • device_info-last_time_online - timestamp of last radio contact
    • +
    • device_info-manufacturer - manufacturer
    • +
    • device_info-product - product type
    • +
    • device_info-serial_number - serial number
    • +
    • device_info-sgtin -
    • +
    • device_info-version - firmware version
    • +
    • firmware-firmware_command - firmware command (idle/firmware_cancel/firmware_upload/unsupported)
    • +
    • firmware-firmware_status - firmware status
    • +
    • firmware-firmware_update_start - indicator when a firmwareupload is started
    • +
    • firmware-firmware_upload_progress - progress indicator of firmware update
    • +
    • firmware-inclusion_status - inclusion status
    • +
    • internal_temperature-temperature - internal device temperature
    • +
    • mower-error - actual error message +
        +
      • no_message
      • +
      • outside_working_area
      • +
      • no_loop_signal
      • +
      • wrong_loop_signal
      • +
      • loop_sensor_problem_front
      • +
      • loop_sensor_problem_rear
      • +
      • trapped
      • +
      • upside_down
      • +
      • low_battery
      • +
      • empty_battery
      • +
      • no_drive
      • +
      • lifted
      • +
      • stuck_in_charging_station
      • +
      • charging_station_blocked
      • +
      • collision_sensor_problem_rear
      • +
      • collision_sensor_problem_front
      • +
      • wheel_motor_blocked_right
      • +
      • wheel_motor_blocked_left
      • +
      • wheel_drive_problem_right
      • +
      • wheel_drive_problem_left
      • +
      • cutting_system_blocked
      • +
      • invalid_sub_device_combination
      • +
      • settings_restored
      • +
      • electronic_problem
      • +
      • charging_system_problem
      • +
      • tilt_sensor_problem
      • +
      • wheel_motor_overloaded_right
      • +
      • wheel_motor_overloaded_left
      • +
      • charging_current_too_high
      • +
      • temporary_problem
      • +
      • guide_1_not_found
      • +
      • guide_2_not_found
      • +
      • guide_3_not_found
      • +
      • difficult_finding_home
      • +
      • guide_calibration_accomplished
      • +
      • guide_calibration_failed
      • +
      • temporary_battery_problem
      • +
      • battery_problem
      • +
      • alarm_mower_switched_off
      • +
      • alarm_mower_stopped
      • +
      • alarm_mower_lifted
      • +
      • alarm_mower_tilted
      • +
      • connection_changed
      • +
      • connection_not_changed
      • +
      • com_board_not_available
      • +
      • slipped
      • +
      +
    • +
    • mower-manual_operation - (0/1) or with newer Firmware (false/true)
    • +
    • mower-override_end_time - manual override end time
    • +
    • mower-source_for_next_start - source for the next start +
        +
      • no_source
      • +
      • mower_charging
      • +
      • completed_cutting_autotimer
      • +
      • week_timer
      • +
      • countdown_timer
      • +
      • undefined
      • +
      +
    • +
    • mower-status - mower state (see state)
    • +
    • mower-timestamp_next_start - timestamp of next scheduled start
    • +
    • radio-connection_status - state of connection
    • +
    • radio-quality - percentage of the radio quality
    • +
    • radio-state - radio state (bad/poor/good/undefined)
    • +
    • state - state of the mower +
        +
      • paused
      • +
      • ok_cutting
      • +
      • ok_searching
      • +
      • ok_charging
      • +
      • ok_leaving
      • +
      • wait_updating
      • +
      • wait_power_up
      • +
      • parked_timer
      • +
      • parked_park_selected
      • +
      • off_disabled
      • +
      • off_hatch_open
      • +
      • unknown
      • +
      • error
      • +
      • error_at_power_up
      • +
      • off_hatch_closed
      • +
      • ok_cutting_timer_overridden
      • +
      • parked_autotimer
      • +
      • parked_daily_limit_reached
      • +
      +
    • +
    +

    + + Attributes +
      +
    • readingValueLanguage - Change the Language of Readings (de,en/if not set the default is english and the global language is not set at german)
    • +
    • model -
    • +
    +

    + + set +
      +
    • parkUntilFurtherNotice
    • +
    • parkUntilNextTimer
    • +
    • startOverrideTimer - (in minutes, 60 = 1h, 1440 = 24h, 4320 = 72h)
    • +
    • startResumeSchedule
    • +
    • startpoint enable|disable 1|2|3 - enables or disables one or more predefined start points
    • +
        +
      • set NAME startpoint enable 1
      • +
      • set NAME startpoint disable 3 enable 1
      • +
      +
    +
+ +=end html +=begin html_DE + + +

GardenaSmartDevice

+
    + Zusammen mit dem Device GardenaSmartDevice stellt dieses FHEM Modul die Kommunikation zwischen der GardenaCloud und Fhem her. +

    + Wenn das GardenaSmartBridge Device erzeugt wurde, werden verbundene Geräte automatisch erkannt und in Fhem angelegt.
    + Von nun an können die eingebundenen Geräte gesteuert werden. Änderungen in der APP werden mit den Readings und dem Status syncronisiert. + +
+
+
    + Readings +
      +
    • battery-charging - Ladeindikator (0/1) oder mit neuerer Firmware (false/true)
    • +
    • battery-level - Ladezustand der Batterie in Prozent
    • +
    • battery-rechargeable_battery_status - Zustand der Batterie (Ausser Betrieb/Kritischer Batteriestand, wechseln Sie jetzt/Niedrig/oK)
    • +
    • device_info-category - Eigenschaft des Gerätes (Mäher/Bewässerungscomputer/Bodensensor)
    • +
    • device_info-last_time_online - Zeitpunkt der letzten Funkübertragung
    • +
    • device_info-manufacturer - Hersteller
    • +
    • device_info-product - Produkttyp
    • +
    • device_info-serial_number - Seriennummer
    • +
    • device_info-sgtin -
    • +
    • device_info-version - Firmware Version
    • +
    • firmware-firmware_command - Firmware Kommando (Nichts zu tun/Firmwareupload unterbrochen/Firmwareupload/nicht unterstützt)
    • +
    • firmware-firmware_status - Firmware Status
    • +
    • firmware-firmware_update_start - Firmwareupdate (0/1) oder mit neuerer Firmware (false/true)
    • +
    • firmware-firmware_upload_progress - Firmwareupdatestatus in Prozent
    • +
    • firmware-inclusion_status - Einbindungsstatus
    • +
    • internal_temperature-temperature - Interne Geräte Temperatur
    • +
    • mower-error - Aktuelle Fehler Meldung +
        +
      • Kein Fehler
      • +
      • Außerhalb des Arbeitsbereichs
      • +
      • Kein Schleifensignal
      • +
      • Falsches Schleifensignal
      • +
      • Problem Schleifensensor, vorne
      • +
      • Problem Schleifensensor, hinten
      • +
      • Eingeschlossen
      • +
      • Steht auf dem Kopf
      • +
      • Niedriger Batteriestand
      • +
      • Batterie ist leer
      • +
      • Kein Antrieb
      • +
      • Angehoben
      • +
      • Eingeklemmt in Ladestation
      • +
      • Ladestation blockiert
      • +
      • Problem Stoßsensor hinten
      • +
      • Problem Stoßsensor vorne
      • +
      • Radmotor rechts blockiert
      • +
      • Radmotor links blockiert
      • +
      • Problem Antrieb, rechts
      • +
      • Problem Antrieb, links
      • +
      • Schneidsystem blockiert
      • +
      • Fehlerhafte Verbindung
      • +
      • Standardeinstellungen
      • +
      • Elektronisches Problem
      • +
      • Problem Ladesystem
      • +
      • Kippsensorproblem
      • +
      • Rechter Radmotor überlastet
      • +
      • Linker Radmotor überlastet
      • +
      • Ladestrom zu hoch
      • +
      • Vorübergehendes Problem
      • +
      • SK 1 nicht gefunden
      • +
      • SK 2 nicht gefunden
      • +
      • SK 3 nicht gefunden
      • +
      • Problem die Ladestation zu finden
      • +
      • Kalibration des Suchkabels beendet
      • +
      • Kalibration des Suchkabels fehlgeschlagen
      • +
      • Kurzzeitiges Batterieproblem
      • +
      • Batterieproblem
      • +
      • Alarm! Mäher ausgeschalten
      • +
      • Alarm! Mäher gestoppt
      • +
      • Alarm! Mäher angehoben
      • +
      • Alarm! Mäher gekippt
      • +
      • Verbindung geändert
      • +
      • Verbindung nicht geändert
      • +
      • COM board nicht verfügbar
      • +
      • Rutscht
      • +
      +
    • +
    • mower-manual_operation - Manueller Betrieb (0/1) oder mit neuerer Firmware (false/true)
    • +
    • mower-override_end_time - Zeitpunkt wann der manuelle Betrieb beendet ist
    • +
    • mower-source_for_next_start - Grund für den nächsten Start +
        +
      • Kein Grund
      • +
      • Mäher wurde geladen
      • +
      • SensorControl erreicht
      • +
      • Wochentimer erreicht
      • +
      • Stoppuhr Timer
      • +
      • Undefiniert
      • +
      +
    • +
    • mower-status - Mäher Status (siehe state)
    • +
    • mower-timestamp_next_start - Zeitpunkt des nächsten geplanten Starts
    • +
    • radio-connection_status - Status der Funkverbindung
    • +
    • radio-quality - Indikator für die Funkverbindung in Prozent
    • +
    • radio-state - radio state (schlecht/schwach/gut/Undefiniert)
    • +
    • state - Staus des Mähers +
        +
      • Pausiert
      • +
      • Mähen
      • +
      • Suche Ladestation
      • +
      • Lädt
      • +
      • Mähen
      • +
      • Wird aktualisiert ...
      • +
      • Wird eingeschaltet ...
      • +
      • Geparkt nach Zeitplan
      • +
      • Geparkt
      • +
      • Der Mäher ist ausgeschaltet
      • +
      • Deaktiviert. Abdeckung ist offen oder PIN-Code erforderlich
      • +
      • Unbekannter Status
      • +
      • Fehler
      • +
      • Neustart ...
      • +
      • Deaktiviert. Manueller Start erforderlich
      • +
      • Manuelles Mähen
      • +
      • Geparkt durch SensorControl
      • +
      • Abgeschlossen
      • +
      +
    • +
    +

    + + Attribute +
      +
    • readingValueLanguage - Änderung der Sprache der Readings (de,en/wenn nichts gesetzt ist, dann Englisch es sei denn deutsch ist als globale Sprache gesetzt)
    • +
    • model -
    • +
    + + set +
      +
    • parkUntilFurtherNotice - Parken des Mähers unter Umgehung des Zeitplans
    • +
    • parkUntilNextTimer - Parken bis zum nächsten Zeitplan
    • +
    • startOverrideTimer - Manuelles mähen (in Minuten, 60 = 1h, 1440 = 24h, 4320 = 72h)
    • +
    • startResumeSchedule - Weiterführung des Zeitplans
    • +
    • startpoint enable|disable 1|2|3 - Aktiviert oder deaktiviert einen vordefinierten Startbereich
    • +
        +
      • set NAME startpoint enable 1
      • +
      • set NAME startpoint disable 3 enable 1
      • +
      +
    +
+ +=end html_DE + +=for :application/json;q=META.json 74_GardenaSmartDevice.pm +{ + "abstract": "Modul to control GardenaSmart Devices", + "x_lang": { + "de": { + "abstract": "Modul zur Steuerung von Gardena Smart Geräten" + } + }, + "keywords": [ + "fhem-mod-device", + "fhem-core", + "Garden", + "Gardena", + "Smart" + ], + "release_status": "stable", + "license": "GPL_2", + "version": "v2.2.3", + "author": [ + "Marko Oldenburg " + ], + "x_fhem_maintainer": [ + "CoolTux" + ], + "x_fhem_maintainer_github": [ + "LeonGaultier" + ], + "prereqs": { + "runtime": { + "requires": { + "FHEM": 5.00918799, + "perl": 5.016, + "Meta": 0, + "JSON": 0, + "Time::Local": 0 + }, + "recommends": { + }, + "suggests": { + } + } + } +} +=end :application/json;q=META.json + +=cut diff --git a/controls_GardenaSmartDevice.txt b/controls_GardenaSmartDevice.txt index 107cb3b..3ead073 100644 --- a/controls_GardenaSmartDevice.txt +++ b/controls_GardenaSmartDevice.txt @@ -1,2 +1,2 @@ -UPD 2021-05-14_13:16:35 47605 FHEM/73_GardenaSmartBridge.pm -UPD 2021-05-14_13:16:37 52152 FHEM/74_GardenaSmartDevice.pm +UPD 2021-05-14_13:18:39 46108 FHEM/73_GardenaSmartBridge.pm +UPD 2021-05-14_13:18:44 50825 FHEM/74_GardenaSmartDevice.pm From 6c220abfa7b40268feb20c4347e5f177f1016163 Mon Sep 17 00:00:00 2001 From: Sebastian Date: Fri, 14 May 2021 15:55:51 +0200 Subject: [PATCH 09/18] version push --- FHEM/73_GardenaSmartBridge.pm | 2 +- FHEM/74_GardenaSmartDevice.pm | 2 +- controls_GardenaSmartDevice.txt | 4 ++-- 3 files changed, 4 insertions(+), 4 deletions(-) diff --git a/FHEM/73_GardenaSmartBridge.pm b/FHEM/73_GardenaSmartBridge.pm index 69acaec..10877c0 100644 --- a/FHEM/73_GardenaSmartBridge.pm +++ b/FHEM/73_GardenaSmartBridge.pm @@ -1464,7 +1464,7 @@ sub DeletePassword { ], "release_status": "stable", "license": "GPL_2", - "version": "v2.2.2", + "version": "v2.2.3", "author": [ "Marko Oldenburg " ], diff --git a/FHEM/74_GardenaSmartDevice.pm b/FHEM/74_GardenaSmartDevice.pm index 5ba6d8f..0e0a61d 100644 --- a/FHEM/74_GardenaSmartDevice.pm +++ b/FHEM/74_GardenaSmartDevice.pm @@ -1296,7 +1296,7 @@ sub SetPredefinedStartPoints { ], "release_status": "stable", "license": "GPL_2", - "version": "v2.2.3", + "version": "v2.3.0", "author": [ "Marko Oldenburg " ], diff --git a/controls_GardenaSmartDevice.txt b/controls_GardenaSmartDevice.txt index 3ead073..0950e51 100644 --- a/controls_GardenaSmartDevice.txt +++ b/controls_GardenaSmartDevice.txt @@ -1,2 +1,2 @@ -UPD 2021-05-14_13:18:39 46108 FHEM/73_GardenaSmartBridge.pm -UPD 2021-05-14_13:18:44 50825 FHEM/74_GardenaSmartDevice.pm +UPD 2021-05-14_15:51:47 46108 FHEM/73_GardenaSmartBridge.pm +UPD 2021-05-14_15:51:25 50825 FHEM/74_GardenaSmartDevice.pm From 5d407bb814304eb2de788963d0190bbca5b223f1 Mon Sep 17 00:00:00 2001 From: Sebastian Date: Sun, 16 May 2021 12:07:28 +0200 Subject: [PATCH 10/18] fix regex --- FHEM/74_GardenaSmartDevice.pm | 4 ++-- controls_GardenaSmartDevice.txt | 2 +- 2 files changed, 3 insertions(+), 3 deletions(-) diff --git a/FHEM/74_GardenaSmartDevice.pm b/FHEM/74_GardenaSmartDevice.pm index 0e0a61d..129e6a4 100644 --- a/FHEM/74_GardenaSmartDevice.pm +++ b/FHEM/74_GardenaSmartDevice.pm @@ -403,7 +403,7 @@ sub Set { elsif ( $cmd eq 'closeAllValves' ){ $payload = '"name":"close_all_valves","parameters":{}'; } - elsif ( $cmd =~ '/.*ScheduleValve/' ){ + elsif ( $cmd =~ /.*ScheduleValve/ ){ my $valve_id = $1; $abilities = 'irrigation_settings'; $service_id = $hash->{helper}->{'schedules_paused_until_'.$valve_id.'_id'}; @@ -1296,7 +1296,7 @@ sub SetPredefinedStartPoints { ], "release_status": "stable", "license": "GPL_2", - "version": "v2.3.0", + "version": "v2.3.1", "author": [ "Marko Oldenburg " ], diff --git a/controls_GardenaSmartDevice.txt b/controls_GardenaSmartDevice.txt index 0950e51..5e6a018 100644 --- a/controls_GardenaSmartDevice.txt +++ b/controls_GardenaSmartDevice.txt @@ -1,2 +1,2 @@ UPD 2021-05-14_15:51:47 46108 FHEM/73_GardenaSmartBridge.pm -UPD 2021-05-14_15:51:25 50825 FHEM/74_GardenaSmartDevice.pm +UPD 2021-05-16_12:07:17 50823 FHEM/74_GardenaSmartDevice.pm From 20f5db1b2cc6c644485901867d4aeb3fe8d01075 Mon Sep 17 00:00:00 2001 From: Sebastian Date: Sun, 16 May 2021 12:22:26 +0200 Subject: [PATCH 11/18] fix scheduleValve --- FHEM/74_GardenaSmartDevice.pm | 14 +++++++------- controls_GardenaSmartDevice.txt | 2 +- 2 files changed, 8 insertions(+), 8 deletions(-) diff --git a/FHEM/74_GardenaSmartDevice.pm b/FHEM/74_GardenaSmartDevice.pm index 129e6a4..9a363a6 100644 --- a/FHEM/74_GardenaSmartDevice.pm +++ b/FHEM/74_GardenaSmartDevice.pm @@ -404,16 +404,16 @@ sub Set { $payload = '"name":"close_all_valves","parameters":{}'; } elsif ( $cmd =~ /.*ScheduleValve/ ){ - my $valve_id = $1; + my $valve_id = $aArg->[0]; $abilities = 'irrigation_settings'; $service_id = $hash->{helper}->{'schedules_paused_until_'.$valve_id.'_id'}; $payload = '"settings":{"name":"schedules_paused_until_' - .$valve_id. - '", "value":"' - . ($cmd eq 'resumeScheduleValve') ? '' : '2040-12-31T22:00:00.000Z' - .'","device":"' - .$hash->{DEVICEID} - .'"}'; + . $valve_id + . '", "value":"' + . ($cmd eq 'resumeScheduleValve' ? '' : '2040-12-31T22:00:00.000Z') + . '","device":"' + . $hash->{DEVICEID} + . '"}'; } ### Sensors elsif ( lc $cmd eq 'refresh' ) { diff --git a/controls_GardenaSmartDevice.txt b/controls_GardenaSmartDevice.txt index 5e6a018..672648b 100644 --- a/controls_GardenaSmartDevice.txt +++ b/controls_GardenaSmartDevice.txt @@ -1,2 +1,2 @@ UPD 2021-05-14_15:51:47 46108 FHEM/73_GardenaSmartBridge.pm -UPD 2021-05-16_12:07:17 50823 FHEM/74_GardenaSmartDevice.pm +UPD 2021-05-16_12:22:12 50836 FHEM/74_GardenaSmartDevice.pm From f3e0b06e9fdfb0879dc398fc3c1764d8c8b35680 Mon Sep 17 00:00:00 2001 From: Sebastian Date: Sun, 16 May 2021 15:16:03 +0200 Subject: [PATCH 12/18] fix watering close all in abilitie for watering --- FHEM/73_GardenaSmartBridge.pm | 3 +-- controls_GardenaSmartDevice.txt | 4 ++-- 2 files changed, 3 insertions(+), 4 deletions(-) diff --git a/FHEM/73_GardenaSmartBridge.pm b/FHEM/73_GardenaSmartBridge.pm index 10877c0..9ef6f33 100644 --- a/FHEM/73_GardenaSmartBridge.pm +++ b/FHEM/73_GardenaSmartBridge.pm @@ -1262,8 +1262,7 @@ sub createHttpValueStrings { . $deviceId . '/abilities/' . $abilities - . '/properties/watering_timer_' - . $valve_id; + . ( defined($valve_id) ? '/properties/watering_timer_'. $valve_id : '/command') } elsif (defined($abilities) diff --git a/controls_GardenaSmartDevice.txt b/controls_GardenaSmartDevice.txt index 672648b..97debbc 100644 --- a/controls_GardenaSmartDevice.txt +++ b/controls_GardenaSmartDevice.txt @@ -1,2 +1,2 @@ -UPD 2021-05-14_15:51:47 46108 FHEM/73_GardenaSmartBridge.pm -UPD 2021-05-16_12:22:12 50836 FHEM/74_GardenaSmartDevice.pm +UPD 2021-05-16_15:14:55 46129 FHEM/73_GardenaSmartBridge.pm +UPD 2021-05-16_15:14:44 50836 FHEM/74_GardenaSmartDevice.pm From 10490fad003ca0536321d5132fe7b8fd62c03fc3 Mon Sep 17 00:00:00 2001 From: Sebastian Date: Sun, 16 May 2021 19:38:43 +0200 Subject: [PATCH 13/18] fix cloase all valces changed max duration in manual override --- FHEM/73_GardenaSmartBridge.pm | 2 +- FHEM/74_GardenaSmartDevice.pm | 4 ++-- controls_GardenaSmartDevice.txt | 4 ++-- 3 files changed, 5 insertions(+), 5 deletions(-) diff --git a/FHEM/73_GardenaSmartBridge.pm b/FHEM/73_GardenaSmartBridge.pm index 9ef6f33..f9b1d79 100644 --- a/FHEM/73_GardenaSmartBridge.pm +++ b/FHEM/73_GardenaSmartBridge.pm @@ -1252,9 +1252,9 @@ sub createHttpValueStrings { && $abilities eq 'watering' ) { my $valve_id; - $method = 'PUT'; if ( $payload =~ m#watering_timer_(\d)# ) { + $method = 'PUT'; $valve_id = $1; } $uri .= diff --git a/FHEM/74_GardenaSmartDevice.pm b/FHEM/74_GardenaSmartDevice.pm index 9a363a6..9c3601d 100644 --- a/FHEM/74_GardenaSmartDevice.pm +++ b/FHEM/74_GardenaSmartDevice.pm @@ -445,14 +445,14 @@ sub Set { my $list = ''; $list .= -'parkUntilFurtherNotice:noArg parkUntilNextTimer:noArg startResumeSchedule:noArg startOverrideTimer:slider,0,1,60 startpoint' +'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' if ( AttrVal( $name, 'model', 'unknown' ) eq 'watering_computer' ); $list .= -'closeAllValves:noArg stopScheduleValve:selectnumbers,1,1,6,0,lin resumeScheduleValve:selectnumbers,1,1,6,0,lin manualDurationValve1:slider,1,1,59 manualDurationValve2:slider,1,1,59 manualDurationValve3:slider,1,1,59 manualDurationValve4:slider,1,1,59 manualDurationValve5:slider,1,1,59 manualDurationValve6:slider,1,1,59 cancelOverrideValve1:noArg cancelOverrideValve2:noArg cancelOverrideValve3:noArg cancelOverrideValve4:noArg cancelOverrideValve5:noArg cancelOverrideValve6:noArg' +'closeAllValves:noArg stopScheduleValve:selectnumbers,1,1,6,0,lin resumeScheduleValve:selectnumbers,1,1,6,0,lin manualDurationValve1:slider,1,1,120 manualDurationValve2:slider,1,1,120 manualDurationValve3:slider,1,1,120 manualDurationValve4:slider,1,1,120 manualDurationValve5:slider,1,1,120 manualDurationValve6:slider,1,1,120cancelOverrideValve1:noArg cancelOverrideValve2:noArg cancelOverrideValve3:noArg cancelOverrideValve4:noArg cancelOverrideValve5:noArg cancelOverrideValve6:noArg' if ( AttrVal( $name, 'model', 'unknown' ) eq 'ic24' ); $list .= 'refresh:temperature,humidity' diff --git a/controls_GardenaSmartDevice.txt b/controls_GardenaSmartDevice.txt index 97debbc..8a363ca 100644 --- a/controls_GardenaSmartDevice.txt +++ b/controls_GardenaSmartDevice.txt @@ -1,2 +1,2 @@ -UPD 2021-05-16_15:14:55 46129 FHEM/73_GardenaSmartBridge.pm -UPD 2021-05-16_15:14:44 50836 FHEM/74_GardenaSmartDevice.pm +UPD 2021-05-16_19:34:18 46133 FHEM/73_GardenaSmartBridge.pm +UPD 2021-05-16_19:35:55 50842 FHEM/74_GardenaSmartDevice.pm From 0964ddd43bddee9f4d2bb0dd615693f996617d26 Mon Sep 17 00:00:00 2001 From: Sebastian Date: Mon, 17 May 2021 18:22:30 +0200 Subject: [PATCH 14/18] changed max valve to 90min. --- FHEM/74_GardenaSmartDevice.pm | 2 +- controls_GardenaSmartDevice.txt | 4 ++-- 2 files changed, 3 insertions(+), 3 deletions(-) diff --git a/FHEM/74_GardenaSmartDevice.pm b/FHEM/74_GardenaSmartDevice.pm index 9c3601d..c270374 100644 --- a/FHEM/74_GardenaSmartDevice.pm +++ b/FHEM/74_GardenaSmartDevice.pm @@ -452,7 +452,7 @@ sub Set { if ( AttrVal( $name, 'model', 'unknown' ) eq 'watering_computer' ); $list .= -'closeAllValves:noArg stopScheduleValve:selectnumbers,1,1,6,0,lin resumeScheduleValve:selectnumbers,1,1,6,0,lin manualDurationValve1:slider,1,1,120 manualDurationValve2:slider,1,1,120 manualDurationValve3:slider,1,1,120 manualDurationValve4:slider,1,1,120 manualDurationValve5:slider,1,1,120 manualDurationValve6:slider,1,1,120cancelOverrideValve1:noArg cancelOverrideValve2:noArg cancelOverrideValve3:noArg cancelOverrideValve4:noArg cancelOverrideValve5:noArg cancelOverrideValve6:noArg' +'closeAllValves:noArg stopScheduleValve:selectnumbers,1,1,6,0,lin resumeScheduleValve:selectnumbers,1,1,6,0,lin manualDurationValve1:slider,1,1,90 manualDurationValve2:slider,1,1,90 manualDurationValve3:slider,1,1,90 manualDurationValve4:slider,1,1,90 manualDurationValve5:slider,1,1,90 manualDurationValve6:slider,1,1,90 cancelOverrideValve1:noArg cancelOverrideValve2:noArg cancelOverrideValve3:noArg cancelOverrideValve4:noArg cancelOverrideValve5:noArg cancelOverrideValve6:noArg' if ( AttrVal( $name, 'model', 'unknown' ) eq 'ic24' ); $list .= 'refresh:temperature,humidity' diff --git a/controls_GardenaSmartDevice.txt b/controls_GardenaSmartDevice.txt index 8a363ca..272d749 100644 --- a/controls_GardenaSmartDevice.txt +++ b/controls_GardenaSmartDevice.txt @@ -1,2 +1,2 @@ -UPD 2021-05-16_19:34:18 46133 FHEM/73_GardenaSmartBridge.pm -UPD 2021-05-16_19:35:55 50842 FHEM/74_GardenaSmartDevice.pm +UPD 2021-05-16_21:23:09 46133 FHEM/73_GardenaSmartBridge.pm +UPD 2021-05-17_18:20:42 50837 FHEM/74_GardenaSmartDevice.pm From 82833104ff3f3c54293c5a4ddd6108ff894b8578 Mon Sep 17 00:00:00 2001 From: Sebastian Date: Mon, 17 May 2021 18:30:29 +0200 Subject: [PATCH 15/18] fix --- FHEM/74_GardenaSmartDevice.pm | 2 +- controls_GardenaSmartDevice.txt | 2 +- 2 files changed, 2 insertions(+), 2 deletions(-) diff --git a/FHEM/74_GardenaSmartDevice.pm b/FHEM/74_GardenaSmartDevice.pm index c270374..1756705 100644 --- a/FHEM/74_GardenaSmartDevice.pm +++ b/FHEM/74_GardenaSmartDevice.pm @@ -452,7 +452,7 @@ sub Set { if ( AttrVal( $name, 'model', 'unknown' ) eq 'watering_computer' ); $list .= -'closeAllValves:noArg stopScheduleValve:selectnumbers,1,1,6,0,lin resumeScheduleValve:selectnumbers,1,1,6,0,lin manualDurationValve1:slider,1,1,90 manualDurationValve2:slider,1,1,90 manualDurationValve3:slider,1,1,90 manualDurationValve4:slider,1,1,90 manualDurationValve5:slider,1,1,90 manualDurationValve6:slider,1,1,90 cancelOverrideValve1:noArg cancelOverrideValve2:noArg cancelOverrideValve3:noArg cancelOverrideValve4:noArg cancelOverrideValve5:noArg cancelOverrideValve6:noArg' +'closeAllValves:noArg stopScheduleValve:selectnumbers,1,1,6,0,lin resumeScheduleValve:selectnumbers,1,1,6,0,lin manualDurationValve1:slider,1,1,90 manualDurationValve2:slider,1,1,90 manualDurationValve3:slider,1,1,90 manualDurationValve4:slider,1,1,90 manualDurationValve5:slider,1,1,90 manualDurationValve6:slider,1,1,90 cancelOverrideValve1:noArg cancelOverrideValve2:noArg cancelOverrideValve3:noArg cancelOverrideValve4:noArg cancelOverrideValve5:noArg cancelOverrideValve6:noArg' if ( AttrVal( $name, 'model', 'unknown' ) eq 'ic24' ); $list .= 'refresh:temperature,humidity' diff --git a/controls_GardenaSmartDevice.txt b/controls_GardenaSmartDevice.txt index 272d749..31d038e 100644 --- a/controls_GardenaSmartDevice.txt +++ b/controls_GardenaSmartDevice.txt @@ -1,2 +1,2 @@ UPD 2021-05-16_21:23:09 46133 FHEM/73_GardenaSmartBridge.pm -UPD 2021-05-17_18:20:42 50837 FHEM/74_GardenaSmartDevice.pm +UPD 2021-05-17_18:30:20 50836 FHEM/74_GardenaSmartDevice.pm From ed2387c89bf6c2e4186b9bfe476188a76687390b Mon Sep 17 00:00:00 2001 From: Sebastian Date: Tue, 18 May 2021 21:29:09 +0200 Subject: [PATCH 16/18] add custom schedule breake ic24 + add commandref --- FHEM/74_GardenaSmartDevice.pm | 13 ++++++++++++- controls_GardenaSmartDevice.txt | 2 +- 2 files changed, 13 insertions(+), 2 deletions(-) diff --git a/FHEM/74_GardenaSmartDevice.pm b/FHEM/74_GardenaSmartDevice.pm index 1756705..2b9b35a 100644 --- a/FHEM/74_GardenaSmartDevice.pm +++ b/FHEM/74_GardenaSmartDevice.pm @@ -63,6 +63,8 @@ use warnings; use POSIX; use FHEM::Meta; use Time::Local; +use Time::Piece; +use Time::Seconds; # try to use JSON::MaybeXS wrapper # for chance of better performance + open code @@ -405,12 +407,14 @@ sub Set { } elsif ( $cmd =~ /.*ScheduleValve/ ){ my $valve_id = $aArg->[0]; + my $duration = (( defined($aArg->[1]) ? ( ((Time::Piece->new)+(ONE_HOUR * $aArg->[1]))->datetime ).'.000Z' : '2040-12-31T22:00:00.000Z')); + $abilities = 'irrigation_settings'; $service_id = $hash->{helper}->{'schedules_paused_until_'.$valve_id.'_id'}; $payload = '"settings":{"name":"schedules_paused_until_' . $valve_id . '", "value":"' - . ($cmd eq 'resumeScheduleValve' ? '' : '2040-12-31T22:00:00.000Z') + . ($cmd eq 'resumeScheduleValve' ? '' : $duration ) . '","device":"' . $hash->{DEVICEID} . '"}'; @@ -1130,6 +1134,10 @@ sub SetPredefinedStartPoints {
  • set NAME startpoint enable 1
  • set NAME startpoint disable 3 enable 1
  • + +
  • resumeScheduleValve - start schedule irrigation on valve n
  • +
  • stopScheduleValve - stop schedule irrigation on valve n (Default: 2040-12-31T22) | optional params hours (now + hours)
  • +
  • closeAllValves - close all valves
  • @@ -1274,6 +1282,9 @@ sub SetPredefinedStartPoints {
  • set NAME startpoint enable 1
  • set NAME startpoint disable 3 enable 1
  • +
  • resumeScheduleValve - Startet Bew&aauml;sserung am Ventil n nach Zeitplan
  • +
  • stopScheduleValve - Setzt Bew&aauml;sserung am Ventil n aus (Default: 2040-12-31T22) | Optionaler Parameter Stunden (Jetzt + Stunden)
  • +
  • closeAllValves - Stopt Bew&aauml;sserung an allen Ventilen
  • diff --git a/controls_GardenaSmartDevice.txt b/controls_GardenaSmartDevice.txt index 31d038e..0cc421c 100644 --- a/controls_GardenaSmartDevice.txt +++ b/controls_GardenaSmartDevice.txt @@ -1,2 +1,2 @@ UPD 2021-05-16_21:23:09 46133 FHEM/73_GardenaSmartBridge.pm -UPD 2021-05-17_18:30:20 50836 FHEM/74_GardenaSmartDevice.pm +UPD 2021-05-18_21:27:40 51588 FHEM/74_GardenaSmartDevice.pm From 36c74acaab86ae598a6b43643abee3e9fcdf01d6 Mon Sep 17 00:00:00 2001 From: Sebastian Date: Tue, 18 May 2021 21:34:38 +0200 Subject: [PATCH 17/18] fix cmdref --- FHEM/74_GardenaSmartDevice.pm | 4 ++-- controls_GardenaSmartDevice.txt | 2 +- 2 files changed, 3 insertions(+), 3 deletions(-) diff --git a/FHEM/74_GardenaSmartDevice.pm b/FHEM/74_GardenaSmartDevice.pm index 2b9b35a..4fb5ed6 100644 --- a/FHEM/74_GardenaSmartDevice.pm +++ b/FHEM/74_GardenaSmartDevice.pm @@ -1136,7 +1136,7 @@ sub SetPredefinedStartPoints {
  • resumeScheduleValve - start schedule irrigation on valve n
  • -
  • stopScheduleValve - stop schedule irrigation on valve n (Default: 2040-12-31T22) | optional params hours (now + hours)
  • +
  • stopScheduleValve - stop schedule irrigation on valve n (Default: 2040-12-31T22:00:00.000Z) | optional params hours (now + hours)
  • closeAllValves - close all valves
  • @@ -1283,7 +1283,7 @@ sub SetPredefinedStartPoints {
  • set NAME startpoint disable 3 enable 1
  • resumeScheduleValve - Startet Bew&aauml;sserung am Ventil n nach Zeitplan
  • -
  • stopScheduleValve - Setzt Bew&aauml;sserung am Ventil n aus (Default: 2040-12-31T22) | Optionaler Parameter Stunden (Jetzt + Stunden)
  • +
  • stopScheduleValve - Setzt Bew&aauml;sserung am Ventil n aus (Default: 2040-12-31T22:00:00.000Z) | Optionaler Parameter Stunden (Jetzt + Stunden)
  • closeAllValves - Stopt Bew&aauml;sserung an allen Ventilen
  • diff --git a/controls_GardenaSmartDevice.txt b/controls_GardenaSmartDevice.txt index 0cc421c..4697890 100644 --- a/controls_GardenaSmartDevice.txt +++ b/controls_GardenaSmartDevice.txt @@ -1,2 +1,2 @@ UPD 2021-05-16_21:23:09 46133 FHEM/73_GardenaSmartBridge.pm -UPD 2021-05-18_21:27:40 51588 FHEM/74_GardenaSmartDevice.pm +UPD 2021-05-18_21:34:31 51610 FHEM/74_GardenaSmartDevice.pm From 929d8228f4fa63dfa1f4a97a8d31ff07e1d33267 Mon Sep 17 00:00:00 2001 From: Sebastian Date: Thu, 20 May 2021 19:59:46 +0200 Subject: [PATCH 18/18] fix timezone offset --- FHEM/74_GardenaSmartDevice.pm | 4 ++-- controls_GardenaSmartDevice.txt | 4 ++-- 2 files changed, 4 insertions(+), 4 deletions(-) diff --git a/FHEM/74_GardenaSmartDevice.pm b/FHEM/74_GardenaSmartDevice.pm index 4fb5ed6..896568a 100644 --- a/FHEM/74_GardenaSmartDevice.pm +++ b/FHEM/74_GardenaSmartDevice.pm @@ -407,7 +407,7 @@ sub Set { } elsif ( $cmd =~ /.*ScheduleValve/ ){ my $valve_id = $aArg->[0]; - my $duration = (( defined($aArg->[1]) ? ( ((Time::Piece->new)+(ONE_HOUR * $aArg->[1]))->datetime ).'.000Z' : '2040-12-31T22:00:00.000Z')); + my $duration = (( defined($aArg->[1]) ? ( ((Time::Piece->new)+(ONE_HOUR * $aArg->[1]) - (Time::Piece->new)->tzoffset )->datetime ).'.000Z' : '2040-12-31T22:00:00.000Z')); $abilities = 'irrigation_settings'; $service_id = $hash->{helper}->{'schedules_paused_until_'.$valve_id.'_id'}; @@ -1307,7 +1307,7 @@ sub SetPredefinedStartPoints { ], "release_status": "stable", "license": "GPL_2", - "version": "v2.3.1", + "version": "v2.3.2", "author": [ "Marko Oldenburg " ], diff --git a/controls_GardenaSmartDevice.txt b/controls_GardenaSmartDevice.txt index 4697890..d10c710 100644 --- a/controls_GardenaSmartDevice.txt +++ b/controls_GardenaSmartDevice.txt @@ -1,2 +1,2 @@ -UPD 2021-05-16_21:23:09 46133 FHEM/73_GardenaSmartBridge.pm -UPD 2021-05-18_21:34:31 51610 FHEM/74_GardenaSmartDevice.pm +UPD 2021-05-20_19:59:31 46133 FHEM/73_GardenaSmartBridge.pm +UPD 2021-05-20_19:57:39 51642 FHEM/74_GardenaSmartDevice.pm