# $Id$ ######################################################################################################################## # # 73_km200.pm # Creates the possibility to access the Buderus central heating system via # Buderus KM200, KM100 or KM50 communication module. It uses HttpUtils_NonblockingGet # from Rudolf Koenig to avoid a full blockage of the fhem main system during the # polling procedure. # # Author : Matthias Deeke # Contributions : Olaf Droegehorn, Andreas Hahn, Rudolf Koenig, Markus Bloch, # Contributions (continued) : Stefan M., Furban, KaiKr, grossi33, Morkin, DLindner # e-mail : matthias.deeke(AT)deeke(PUNKT)eu # Fhem Forum : http://forum.fhem.de/index.php/topic,25540.0.html # Fhem Wiki : http://www.fhemwiki.de/wiki/Buderus_Web_Gateway # # This file is part of fhem. # # Fhem 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 # (at your option) any later version. # # Fhem 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. # # You should have received a copy of the GNU General Public License # along with fhem. If not, see . # # fhem.cfg: define km200 # # Example 1 - Bare Passwords: # define myKm200 km200 192.168.178.200 GatewayGeheim PrivateGeheim # # Example 2 - base64 encoded passwords: Both passwords may be pre-encode with base64 # define myKm200 km200 192.168.178.200 R2F0ZXdheUdlaGVpbQ== UHJpdmF0ZUdlaGVpbQ== # ######################################################################################################################## ######################################################################################################################## # List of open Problems: # # ######################################################################################################################## package main; use strict; use warnings; use Blocking; use Time::HiRes qw(gettimeofday sleep usleep); use Digest::MD5 qw(md5 md5_hex md5_base64); use base qw( Exporter ); use List::MoreUtils qw(first_index); use MIME::Base64; use LWP::UserAgent; use JSON; use Crypt::Rijndael; use HttpUtils; use Encode; use constant false => 0; use constant true => 1; sub km200_Define($$); sub km200_Undefine($$); ###START###### Initialize module ##############################################################################START#### sub km200_Initialize($) { my ($hash) = @_; $hash->{STATE} = "Init"; $hash->{DefFn} = "km200_Define"; $hash->{UndefFn} = "km200_Undefine"; $hash->{SetFn} = "km200_Set"; $hash->{GetFn} = "km200_Get"; $hash->{AttrFn} = "km200_Attr"; $hash->{DbLog_splitFn} = "km200_DbLog_splitFn"; $hash->{AttrList} = "do_not_notify:1,0 " . "header " . "disable:1,0 " . "loglevel:0,1,2,3,4,5,6 " . "IntervalDynVal " . "PollingTimeout " . "DoNotPoll " . "ReadBackDelay " . $readingFnAttributes; } ####END####### Initialize module ###############################################################################END##### ###START###### Activate module after module has been used via fhem command "define" ##########################START#### sub km200_Define($$) { my ($hash, $def) = @_; my @a = split("[ \t][ \t]*", $def); my $name = $a[0]; #$a[1] just contains the "km200" module name and we already know that! :-) my $url = $a[2]; my $km200_gateway_password = $a[3]; my $km200_private_password = $a[4]; $hash->{NAME} = $name; $hash->{STATE} = "define"; Log3 $name, 4, $name. " : km200 - Starting to define module"; ### Stop the current timer if one exists errornous RemoveInternalTimer($hash); Log3 $name, 4, $name. " : km200 - InternalTimer has been removed."; ###START###### Define known services of gateway ###########################################################START#### my @KM200_AllServices = ( "/", "/dhwCircuits", "/gateway", "/heatingCircuits", "/heatSources", "/notifications", "/recordings", "/solarCircuits", "/system", "/dhwCircuits", ); ####END####### Define known services of gateway ############################################################END##### ###START### Check whether all variables are available #####################################################START#### if (int(@a) == 5) { ###START### Check whether IPv4 address is valid if ($url =~ m/^(?:(?:25[0-5]|2[0-4][0-9]|[01]?[0-9][0-9]?)\.){3}(?:25[0-5]|2[0-4][0-9]|[01]?[0-9][0-9]?)$/) { Log3 $name, 4, $name. " : km200 - IPv4-address is valid : " . $url; } else { return $name .": Error - IPv4 address is not valid \n Please use \"define km200 \" instead"; } ####END#### Check whether IPv4 address is valid ###START### Check whether gateway password is base64 encoded or bare, has the right length and delete "-" if required my $PasswordEncrypted = false; my $EvalPassWord = $km200_gateway_password; $EvalPassWord =~ tr/-//d; if ( length($EvalPassWord) == 16) { $km200_gateway_password = $EvalPassWord; Log3 $name,4, $name. " : km200 - Provided GatewayPassword provided as bareword has the correct length at least."; } else # Check whether the password is eventually base64 encoded { # Remove additional encoding with base64 my $decryptData = decode_base64($km200_gateway_password); $decryptData =~ tr/-//d; $decryptData =~ s/\r|\n//g; if ( length($decryptData) == 16) { $km200_gateway_password = $decryptData; $PasswordEncrypted = true; Log3 $name, 4, $name. " : km200 - Provided GatewayPassword encoded with base64 has the correct length at least."; } else { return $name .": Error - GatewayPassword does not have the correct length.\n". " Please enter gateway password in the format of \"aaaabbbbccccdddd\" or \"aaaa-bbbb-cccc-dddd\"\n". " You may encode your password with base64 first, in order to prevent bare passwords in fhem.cfg.\n". " If you choose to encrypt your gateway password with base64, you also must encrypt your private password the same way\n"; Log3 $name, 3, $name. " : km200 - Provided Gateway Password does not follow the specifications"; } } ####END#### Check whether gateway password has the right length and delete "-" if required ###START### Check whether private password is available and decode it with base64 if encoding is used if ($PasswordEncrypted == true) { my $decryptData = decode_base64($km200_private_password); $decryptData =~ s/\r|\n//g; if (length($decryptData) > 0) { $km200_private_password = $decryptData; Log3 $name, 4, $name. " : km200 - Provided PrivatePassword exists at least"; } else { return $name .": Error - PrivatePassword does not have the minimum length.\n". " You may encode your password with base64 first, in order to prevent bare passwords in fhem.cfg.\n". " If you choose to encrypt your private password with base64, you also must encrypt your gateway password the same way\n"; } } else # If private password is provided as bare word { if (length($km200_private_password) > 0) { Log3 $name, 4, $name. " : km200 - Provided PrivatePassword exists at least"; } else { return $name .": Error - PrivatePassword has not been provided.\n". " You may encode your password with base64 first, in order to prevent bare passwords in fhem.cfg.\n". " If you choose to encrypt your private password with base64, you also must encrypt your gateway password the same way\n"; Log3 $name, 3, $name. " : km200 - Provided Private Password does not follow the specifications"; } } ####END#### Check whether private password is available and decode it with base64 if encoding is used } else { return $name .": km200 - Error - Not enough parameter provided." . "\n" . "Gateway IPv4 address, Gateway and Private Passwords must be provided" ."\n". "Please use \"define km200 \" instead"; } ####END#### Check whether all variables are available ######################################################END##### ###START###### Create the secret SALT of the MD5-Hash for AES-encoding ####################################START#### my $Buderus_MD5Salt = pack( 'C*', 0x86, 0x78, 0x45, 0xe9, 0x7c, 0x4e, 0x29, 0xdc, 0xe5, 0x22, 0xb9, 0xa7, 0xd3, 0xa3, 0xe0, 0x7b, 0x15, 0x2b, 0xff, 0xad, 0xdd, 0xbe, 0xd7, 0xf5, 0xff, 0xd8, 0x42, 0xe9, 0x89, 0x5a, 0xd1, 0xe4 ); ####END####### Create the secret SALT of the MD5-Hash for AES-encoding #####################################END##### ###START###### Create keys with MD5 #######################################################################START#### # Copy Salt my $km200_crypt_md5_salt = $Buderus_MD5Salt; # First half of the key: MD5 of (km200GatePassword . Salt) my $key_1 = md5($km200_gateway_password . $km200_crypt_md5_salt); # Second half of the key: - Initial: MD5 of ( Salt) my $key_2_initial = md5($km200_crypt_md5_salt); # Second half of the key: - private: MD5 of ( Salt . km200PrivatePassword) my $key_2_private = md5($km200_crypt_md5_salt . $km200_private_password); # Create keys my $km200_crypt_key_initial = ($key_1 . $key_2_initial); my $km200_crypt_key_private = ($key_1 . $key_2_private); ####END####### Create keys with MD5 #########################################################################END##### ###START###### Writing values to global hash ###############################################################START#### $hash->{NAME} = $name; $hash->{URL} = $url; $hash->{INTERVALDYNVAL} = 300; $hash->{DELAYDYNVAL} = 60; $hash->{POLLINGTIMEOUT} = 5; $hash->{READBACKDELAY} = 100; $hash->{temp}{ServiceCounterInit} = 0; $hash->{temp}{ServiceCounterDyn} = 0; $hash->{temp}{ServiceDbLogSplitHash} = (); $hash->{status}{FlagInitRequest} = false; $hash->{status}{FlagGetRequest} = false; $hash->{status}{FlagSetRequest} = false; $hash->{status}{FlagDynRequest} = false; $hash->{status}{FlagStatRequest} = false; $hash->{Secret}{CRYPTKEYPRIVATE} = $km200_crypt_key_private; $hash->{Secret}{CRYPTKEYINITIAL} = $km200_crypt_key_initial; @{$hash->{Secret}{KM200ALLSERVICES}} = sort @KM200_AllServices; @{$hash->{Secret}{KM200ALLSERVICESBACKUP}} = sort @KM200_AllServices; @{$hash->{Secret}{KM200RESPONDINGSERVICES}} = (); @{$hash->{Secret}{KM200WRITEABLESERVICES}} = (); @{$hash->{Secret}{KM200DONOTPOLL}} = (); ####END####### Writing values to global hash ################################################################END##### ###START###### Reset fullResponse error message ############################################################START#### readingsSingleUpdate( $hash, "fullResponse", "OK", 1); ####END####### Reset fullResponse error message #############################################################END##### ###START###### For Debugging purpose only ##################################################################START#### Log3 $name, 4, $name. " : km200 - Define H : " .$hash; Log3 $name, 4, $name. " : km200 - Define D : " .$def; Log3 $name, 4, $name. " : km200 - Define A : " .@a; Log3 $name, 4, $name. " : km200 - Define Name : " .$name; Log3 $name, 4, $name. " : km200 - Define Adr : " .$url; ####END####### For Debugging purpose only ###################################################################END##### ###START###### Check whether communication to the physical unit is possible ################################START#### my $Km200Info =""; $hash->{temp}{service} = "/gateway/DateTime"; $Km200Info = km200_GetSingleService($hash); if ($Km200Info eq "ERROR") { $Km200Info = $hash->{temp}{TransferValue}; $hash->{temp}{TransferValue} = ""; ## Communication with Gateway WRONG !! ## $hash->{STATE}="Error - No Communication"; return ($name .": km200 - ERROR - The communication between fhem and the Buderus KM200 failed! \n". " Please check physical connection, IP-address and passwords! \n"); } elsif ($Km200Info eq "SERVICE NOT AVAILABLE") ## Communication OK but service not available ## { Log3 $name, 4, $name. " : km200 - /gateway/DateTime : NOT AVAILABLE"; } else ## Communication OK and service is available ## { Log3 $name, 4, $name. " : km200 - /gateway/DateTime : AVAILABLE"; } ####END####### Check whether communication to the physical unit is possible ################################END##### ###START###### Initiate the timer for first time polling of values from KM200 but wait 10s ###############START#### InternalTimer(gettimeofday()+10, "km200_GetInitService", $hash, 1); Log3 $name, 4, $name. " : km200 - Internal timer for Initialisation of services started for the first time."; ####END####### Initiate the timer for first time polling of values from KM200 but wait 60s ################END##### return undef; } ####END####### Activate module after module has been used via fhem command "define" ############################END##### ###START###### To bind unit of value to DbLog entries #########################################################START#### sub km200_DbLog_splitFn($$) { my ($event, $name) = @_; my ($reading, $value, $unit); my $hash = $defs{$name}; my @argument = split("[ \t][ \t]*", $event); ### Delete ":" and everything behind in readings name $argument[0] =~ s/:.*//; ### Log entries for debugging Log3 $name, 5, $name. " : km200_DbLog_splitFn - Content of event : " . $event; Log3 $name, 5, $name. " : km200_DbLog_splitFn - Content of argument[0] : " . $argument[0]; Log3 $name, 5, $name. " : km200_DbLog_splitFn - Content of argument[1] : " . $argument[1]; ### If the service to be changed is identical to the one where the unit received from if ($argument[0] = $hash->{temp}{ServiceDbLogSplitHash}{id}) { ### Get values being changed from hash $reading = $argument[0]; $value = $argument[1]; $unit = $hash->{temp}{ServiceDbLogSplitHash}{unitOfMeasure}; } ### Delete temporary json-hash for DbLog-Split $hash->{temp}{ServiceDbLogSplitHash} = (); ### Delete temporary json-hash for DbLog-Split return ($reading, $value, $unit); } ####END####### To bind unit of value to DbLog entries ##########################################################END##### ###START###### Deactivate module module after "undefine" command by fhem ######################################START#### sub km200_Undefine($$) { my ($hash, $def) = @_; my $name = $hash->{NAME}; my $url = $hash->{URL}; ### Stop the internal timer for this module RemoveInternalTimer($hash); Log3 $name, 3, $name. " - km200 has been undefined. The KM unit at $url will no longer polled."; return undef; } ####END####### Deactivate module module after "undefine" command by fhem #######################################END##### ###START###### Handle attributes after changes via fhem GUI ###################################################START#### sub km200_Attr(@) { my @a = @_; my $name = $a[1]; my $hash = $defs{$name}; my $IntervalDynVal = $hash->{INTERVALDYNVAL}; my $DelayDynVal = $hash->{DELAYDYNVAL}; my $ReadBackDelay = $hash->{READBACKDELAY}; ### Check whether disable attribute has been provided if ($a[2] eq "disable") { ###START### Check whether device shall be disabled if ($a[3] == 1) { ### Set new status $hash->{STATE} = "Disabled"; ### Stop the current timer RemoveInternalTimer($hash); Log3 $name, 4, $name. " : km200 - InternalTimer has been removed."; ### Delete all Readings fhem( "deletereading $name .*" ); ### Recover list of root services @{$hash->{Secret}{KM200ALLSERVICES}}= @{$hash->{Secret}{KM200ALLSERVICESBACKUP}}; Log3 $name, 3, $name. " : km200 - Device disabled as per attribute."; } else { ### Initiate the timer for first time polling of values from KM200 but wait 10s InternalTimer(gettimeofday()+10, "km200_GetInitService", $hash, 1); Log3 $name, 4, $name. " : km200 - Internal timer for Initialisation of services re-started."; ### Set new status $hash->{STATE} = "Initiating Sounding..."; Log3 $name, 4, $name. " : km200 - Device enabled as per attribute."; } ####END#### Check whether device shall be disabled } ### Check whether dynamic interval attribute has been provided elsif ($a[2] eq "IntervalDynVal") { $IntervalDynVal = $a[3]; ###START### Check whether polling interval is not too short if ($IntervalDynVal > 19) { $hash->{INTERVALDYNVAL} = $IntervalDynVal; Log3 $name, 4, $name. " : km200 - IntervalDynVal set to attribute value:" . $IntervalDynVal ." s"; } else { return $name .": Error - Gateway interval for IntervalDynVal too small - server response time longer than defined interval, please use something >=20, default is 90"; } ####END#### Check whether polling interval is not too short } ### Check whether polling timeout attribute has been provided elsif($a[2] eq "PollingTimeout") { ###START### Check whether timeout is not too short if ($a[3] >= 5) { $hash->{POLLINGTIMEOUT} = $a[3]; Log3 $name, 4, $name. " : km200 - Polling timeout set to attribute value:" . $a[3] ." s"; } else { Log3 $name, 4, $name. " : km200 - Error - Gateway polling timeout attribute too small: " . $a[3] ." s"; return $name .": Error - Gateway polling timeout attribute is too small - server response time is 5s minimum, default is 5"; } ####END#### Check whether timeout is not too short } ### Check whether DoNotPoll attribute have been provided elsif($a[2] eq "DoNotPoll") { my @KM200_DONOTPOLL = (); my @temp = @a; ### Stop the current timer RemoveInternalTimer($hash); Log3 $name, 4, $name. " : km200 - InternalTimer has been removed."; ### Delete the first 3 items of the array splice @temp, 0, 3; ### Insert empty field as minimum entry push @temp, ""; ### Transform string entries seperated by blank into array @KM200_DONOTPOLL = split(/\s+/, $temp[0]); ### Remove trailing slash of each item if available ### For each item found in this empty parent directory foreach my $item (@KM200_DONOTPOLL) { ### Delete trailing slash $item =~ s/\/$//; } ### Save list of services not to be polled into hash @{$hash->{Secret}{KM200DONOTPOLL}} = @KM200_DONOTPOLL; ### Get original list of root services back @{$hash->{Secret}{KM200ALLSERVICES}} = @{$hash->{Secret}{KM200ALLSERVICESBACKUP}}; ### For every blacklisted service foreach my $SearchWord(@KM200_DONOTPOLL) { ### Filter all blocked root services out of services to be polled my $FoundPosition = first_index{ $_ eq $SearchWord }@{$hash->{Secret}{KM200ALLSERVICES}}; if ($FoundPosition >= 0) { splice(@{$hash->{Secret}{KM200ALLSERVICES}}, $FoundPosition, 1); } } ### Message for debugging purposes Log3 $name, 5, $name. "km200 module is only polling the following services! \n @{$hash->{Secret}{KM200ALLSERVICES}}"; Log3 $name, 5, $name. "km200 module is NOT polling the following services! \n @{$hash->{Secret}{KM200DONOTPOLL}}"; Log3 $name, 4, $name. " : km200 - The following services will not be polled: ". $a[3]; ### Interrupting all currently running Polling @{$hash->{Secret}{KM200DYNSERVICES}} = ""; $hash->{temp}{ServiceCounterDyn} = 0; ### Delete all Readings fhem( "deletereading $name .*" ); ### Re-start the sounding of values from KM200 but wait the period of $hash->{POLLINGTIMEOUT} + 10s InternalTimer(gettimeofday()+$hash->{POLLINGTIMEOUT}+10, "km200_GetInitService", $hash, 1); Log3 $name, 4, $name. " : km200 - Sounding of services re-started after change of DoNotPoll attribute"; } ### Check whether time-out for Read-Back has been provided if($a[2] eq "ReadBackDelay") { $ReadBackDelay = $a[3]; ###START### Check whether ReadBackDelay is valid if ($ReadBackDelay >= 0) { $hash->{READBACKDELAY} = $ReadBackDelay; Log3 $name, 4, $name. " : km200 - ReadBackDelay set to attribute value:" . $ReadBackDelay ." s"; } else { return $name .": Error - Read-Back delay time must be positive. Default is 0us"; } ####END#### Check whether ReadBackDelay is valid } ### If no attributes of the above known ones have been selected else { # Do nothing } return undef; } ####END####### Handle attributes after changes via fhem GUI ####################################################END##### ###START###### Obtain value after "get" command by fhem #######################################################START#### sub km200_Get($@) { my ( $hash, @a ) = @_; ### If not enough arguments have been provided if ( @a < 2 ) { return "\"get km200\" needs at least one argument"; } my $name = shift @a; my $service = shift @a; my $option = shift @a; my %km200_gets; my $ReturnValue; my $ReturnMessage; ### Get the list of possible services and create a hash out of it my @GetServices = @{$hash->{Secret}{KM200ALLSERVICES}}; foreach my $item(@GetServices) { $km200_gets{$item} = ("1"); } ### Remove trailing slash if available $service = $1 if($service=~/(.*)\/$/); ### If service chosen in GUI does not exist if(!$km200_gets{$service}) { my @cList = keys %km200_gets; return "Unknown argument $service, choose one of " . join(" ", @cList); } ### Check whether the initialisation process has been finished if ($hash->{temp}{ServiceCounterInit} == false) { ### Save chosen service into hash $hash->{temp}{service} = $service; ### Read service-hash $ReturnValue = km200_GetSingleService($hash); ### If the "get" - option has been set to "Json" for the return of the raw Json-string if ($option =~ m/json/i) { $ReturnMessage = $hash->{temp}{JsonRaw}; } ### If no option has been chosen, just return the result of the value. else { ### If type is a floatvalue then format decimals if ($ReturnValue->{type} eq "floatValue") { $ReturnMessage = sprintf("%.1f", $ReturnValue->{value}); } ### If type is something else just pass throught else { $ReturnMessage = $ReturnValue->{value}; } } } ### If the initialisation process has NOT been finished else { $ReturnMessage = "The initialisation process is still ongoing. Please wait for the STATE changing to \"Standby\""; } ### Delete temporary values $hash->{temp}{service} = ""; $hash->{temp}{JsonRaw} = ""; ### Return value return($ReturnMessage); } ####END####### Obtain value after "get" command by fhem ########################################################END##### ###START###### Manipulate service after "set" command by fhem #################################################START#### sub km200_Set($@) { my ( $hash, @a ) = @_; ### If not enough arguments have been provided if ( @a < 2 ) { return "\"set km200\" needs at least one argument"; } my $name = shift @a; my $service = shift @a; my $value = join(" ", @a); my %km200_sets; my $ReturnMessage; ### Get the list of possible services and create a hash out of it my @WriteableServices = @{$hash->{Secret}{KM200WRITEABLESERVICES}}; foreach my $item(@WriteableServices) { $km200_sets{$item} = ("1"); } ### If service chosen in GUI does not exist if(!$km200_sets{$service}) { my @cList = keys %km200_sets; return "Unknown argument $service, choose one of " . join(" ", @cList); } ### Check whether the initialisation process has been finished if ($hash->{temp}{ServiceCounterInit} == false) { ### Save chosen service into hash $hash->{temp}{service} = $service; $hash->{temp}{postdata} = $value; ### Call set sub $ReturnMessage = km200_PostSingleService($hash); } ### If the initialisation process has NOT been finished else { $ReturnMessage = "The initialisation process is still ongoing. Please wait for the STATE changing to \"Standby\""; } ### Delete temporary hash values $hash->{temp}{postdata} = ""; $hash->{temp}{service} = ""; return($ReturnMessage); } ####END####### Manipulate service after "Set" command by fhem ##################################################END##### ###START####### Repeats "string" for "count" times ############################################################START#### sub str_repeat($$) { my $string = $_[0]; my $count = $_[1]; return(${string}x${count}); } ####END######## Repeats "string" for "count" times #############################################################END##### ###START###### Subroutine Encrypt Data ########################################################################START#### sub km200_Encrypt($) { my ($hash, $def) = @_; my $km200_crypt_key_private = $hash->{Secret}{CRYPTKEYPRIVATE}; my $name = $hash->{NAME}; my $encryptData = $hash->{temp}{jsoncontent}; # Create Rijndael encryption object my $cipher = Crypt::Rijndael->new($km200_crypt_key_private, Crypt::Rijndael::MODE_ECB() ); # Get blocksize and add PKCS #7 padding my $blocksize = $cipher->blocksize(); my $encrypt_padchar = $blocksize - ( length( $encryptData ) % $blocksize ); $encryptData .= str_repeat( chr( $encrypt_padchar ), $encrypt_padchar ); # Do the encryption my $ciphertext = $cipher->encrypt( $encryptData ); # Do additional encoding with base64 $ciphertext = encode_base64($ciphertext); # Return the encoded text return($ciphertext); } ####END####### Subroutine Encrypt Data #########################################################################END##### ###START###### Subroutine Decrypt Data ########################################################################START#### sub km200_Decrypt($) { my ($hash, $def) = @_; my $km200_crypt_key_private = $hash->{Secret}{CRYPTKEYPRIVATE}; my $name = $hash->{NAME}; my $decryptData = $hash->{temp}{decodedcontent}; ### Log entries for debugging purposes #Log3 $name, 5, $name. " : km200 - decryptData2 - decryptData : " .$decryptData; # Remove additional encoding with base64 $decryptData = decode_base64($decryptData); ### Log entries for debugging purposes #Log3 $name, 5, $name. " : km200 - decryptData2 - base64decode : " .$decryptData; # Check whether the length of the decryptData is NOT multiplies of 16 if ((length($decryptData)&0xF) != 0) { # Return nothing which will end this subroutine return ""; } # Create Rijndael decryption object and do the decryption my $cipher = Crypt::Rijndael->new($km200_crypt_key_private, Crypt::Rijndael::MODE_ECB() ); my $deciphertext = $cipher->decrypt( $decryptData ); # Remove zero padding $deciphertext =~ s/\x00+$//; # Remove PKCS #7 padding my $decipher_len = length($deciphertext); my $decipher_padchar = ord(substr($deciphertext,($decipher_len - 1),1)); my $i = 0; for ( $i = 0; $i < $decipher_padchar ; $i++ ) { if ( $decipher_padchar != ord( substr($deciphertext,($decipher_len - $i - 1),1))) { last; } } # Return decrypted text if ( $i != $decipher_padchar ) { ### Log entries for debugging purposes Log3 $name, 5, $name. " : km200 - decryptData1 - decipher_len : " .$decipher_len; $deciphertext =~ s/\x00+$//; Log3 $name, 5, $name. " : km200 - decryptData1 - deciphertext : " .$deciphertext; ### Log entries for debugging purposes return $deciphertext; } else { $deciphertext = substr($deciphertext,0,$decipher_len - $decipher_padchar); ### Log entries for debugging purposes Log3 $name, 5, $name. " : km200 - decryptData2 - decipher_len : " .$decipher_len; $deciphertext =~ s/\x00+$//; Log3 $name, 5, $name. " : km200 - decryptData2 - deciphertext : " .$deciphertext; ### Log entries for debugging purposes return $deciphertext; } } ####END####### Subroutine Decrypt Data #########################################################################END##### ###START###### Subroutine set individual data value ###########################################################START#### sub km200_PostSingleService($) { my ($hash, $def) = @_; my $Service = $hash->{temp}{service}; my $km200_gateway_host = $hash->{URL} ; my $name = $hash->{NAME} ; my $PollingTimeout = $hash->{POLLINGTIMEOUT}; my $err; my $data; my $jsonSend; my $jsonRead; my $JsonContent; ### Log file entry for debugging Log3 $name, 5, $name. ("km200_Set - Writing value: " . $hash->{temp}{postdata} . " to the service : ". $Service . "\n"); ### Read the current json string $jsonRead = km200_GetSingleService($hash); #### If the get-command returns an error due to an unknown Service requested if ($jsonRead -> {type} eq "ERROR") { ### Rescue original Service request my $WriteService = $Service; ### Try to replace the Post-String with nothing $Service =~ s/\/1-Mo//i; $Service =~ s/\/2-Tu//i; $Service =~ s/\/3-We//i; $Service =~ s/\/4-Th//i; $Service =~ s/\/5-Fr//i; $Service =~ s/\/6-Sa//i; $Service =~ s/\/7-Su//i; ### Save corrected string in hash $hash->{temp}{service} = $Service; ### Log file entry for debugging Log3 $name, 5, $name. "km200_Set - Trying to re-read Service - Assuming its a switchProgram list"; ### Try again to read the current json string again with the corrected service $jsonRead = km200_GetSingleService($hash); ### Check whether the type is an switchProgram. ### If true, the requested service was a particular week of the switchProgram if ($jsonRead -> {type} eq "switchProgram") { ### Log file entry for debugging Log3 $name, 5, $name. "km200_Set - It is a switchProgram list!"; ### For each weekday, get current readings, delete all unnecessary blanks and transform to array my $TempReadingVal; $TempReadingVal = ReadingsVal($name,($Service . "/1-Mo"),""); $TempReadingVal =~ s/\s+/ /g; $TempReadingVal =~ s/\s+$//g; my @TempReadingMo = split(/\s+/, $TempReadingVal,0); $TempReadingVal = ReadingsVal($name,($Service . "/2-Tu"),""); $TempReadingVal =~ s/\s+/ /g; $TempReadingVal =~ s/\s+$//g; my @TempReadingTu = split(/\s+/, $TempReadingVal,0); $TempReadingVal = ReadingsVal($name,($Service . "/3-We"),""); $TempReadingVal =~ s/\s+/ /g; $TempReadingVal =~ s/\s+$//g; my @TempReadingWe = split(/\s+/, $TempReadingVal,0); $TempReadingVal = ReadingsVal($name,($Service . "/4-Th"),""); $TempReadingVal =~ s/\s+/ /g; $TempReadingVal =~ s/\s+$//g; my @TempReadingTh = split(/\s+/, $TempReadingVal,0); $TempReadingVal = ReadingsVal($name,($Service . "/5-Fr"),""); $TempReadingVal =~ s/\s+/ /g; $TempReadingVal =~ s/\s+$//g; my @TempReadingFr = split(/\s+/, $TempReadingVal,0); $TempReadingVal = ReadingsVal($name,($Service . "/6-Sa"),""); $TempReadingVal =~ s/\s+/ /g; $TempReadingVal =~ s/\s+$//g; my @TempReadingSa = split(/\s+/, $TempReadingVal,0); $TempReadingVal = ReadingsVal($name,($Service . "/7-Su"),""); $TempReadingVal =~ s/\s+/ /g; $TempReadingVal =~ s/\s+$//g; my @TempReadingSu = split(/\s+/, $TempReadingVal,0); ### For value to be written, delete all unnecessary blanks and transform to array and get length of array my $ReturnString = $hash->{temp}{postdata}; $ReturnString =~ s/\s+/ /g; $ReturnString =~ s/\s+$//g; my @TempReading = split(/\s+/, $ReturnString); my $TempReadingLength = @TempReading; ### Obtain the allowed terminology for setpoints $hash->{temp}{service} = $jsonRead -> {setpointProperty}{id}; my $TempSetpointsJson = km200_GetSingleService($hash); my @TempSetpointNames =(); ### For each item found in this empty parent directory foreach my $item (@{ $TempSetpointsJson->{references} }) { my $TempSetPoint = substr($item->{id}, (rindex($item->{id}, "/") - length($item->{id}) +1)); ### Add service, which is one of the allowed terminologies at the same time, to the list of all known services push (@TempSetpointNames, $TempSetPoint); } ### Restore the original service $hash->{temp}{service} = $Service; ### If number of switchpoints exceeds maximum allowed if (($TempReadingLength / 2) > $jsonRead -> {maxNbOfSwitchPointsPerDay}) { return ("ERROR - Too much Switchpoints for weeklist inserted. \n Do not add more than " . $jsonRead -> {maxNbOfSwitchPointsPerDay} . " SwitchPoints per day!\n"); } ### If content of array is not even if (($TempReadingLength % 2) != 0) { return "ERROR - At least one Switchtime or Switchpoint is missing. \n Make sure you always have couples of Switchtime and Switchpoint!\n"; } ### Check whether description of setpoints is the same as referenced and the data is in the right order for (my $i=0;$i<$TempReadingLength;$i+=2) { ### If the even element behind the uneven index [1, 3, 5, ...] is not one of the pre-defined setpoints if (! grep /($TempReading[$i+1])/,@TempSetpointNames) { return "ERROR - At least for one Switchpoint the wrong terminology has been used. Only use one of the following items: " . join(' , ',@TempSetpointNames) ."\n";; } ### If the uneven element behind the even index [0, 2, 4, ...]is not a number, hand back an error message if ($TempReading[$i] !~ /^[0-9.-]+$/) { return "ERROR - At least for one Switchtime a number is expected at that position. \n Ensure the correct syntax of time and switchpoint. (E.g. 0600 eco)\n"; } ### Convert timepoint into raster of defined switchPointTimeRaster my $TempHours = substr($TempReading[$i], 0, length($TempReading[$i])-2); if ($TempHours > 23) { $TempHours = 23; } my $TempMinutes = substr($TempReading[$i], -2); if ($TempMinutes > 59) { $TempMinutes = 59; } $TempMinutes = $TempMinutes / ($jsonRead -> {switchPointTimeRaster}); $TempMinutes =~ s/^(.*?)\..*$/$1/; $TempMinutes = $TempMinutes * ($jsonRead -> {switchPointTimeRaster}); $TempMinutes = sprintf ('%02d', $TempMinutes); $TempReading[$i] = ($TempHours . $TempMinutes); } $hash->{temp}{postdata} = join(" ", @TempReading); ### For the requested day to be changed, save new value if ($WriteService =~ m/1-Mo/i) { @TempReadingMo = @TempReading; } elsif ($WriteService =~ m/2-Tu/i) { @TempReadingTu = @TempReading; } elsif ($WriteService =~ m/3-We/i) { @TempReadingWe = @TempReading; } elsif ($WriteService =~ m/4-Th/i) { @TempReadingTh = @TempReading; } elsif ($WriteService =~ m/5-Fr/i) { @TempReadingFr = @TempReading; } elsif ($WriteService =~ m/6-Sa/i) { @TempReadingSa = @TempReading; } elsif ($WriteService =~ m/7-Su/i) { @TempReadingSu = @TempReading; } ### For every weekday create setpoint hash and push it to array of hashes of switchpoints to be send my @SwitchPointsSend =(); for (my $i=0;$i<$#TempReadingMo;$i+=2) { my $TempHashSend; $TempHashSend->{"dayOfWeek"} = "Mo"; my $TempHours = substr($TempReadingMo[$i], 0, length($TempReadingMo[$i])-2); my $TempMinutes = substr($TempReadingMo[$i], -2); $TempHashSend->{"time"} = ($TempHours * 60 ) + $TempMinutes; $TempHashSend->{"setpoint"} = $TempReadingMo[$i+1]; push @SwitchPointsSend, $TempHashSend; } for (my $i=0;$i<$#TempReadingTu;$i+=2) { my $TempHashSend; $TempHashSend->{"dayOfWeek"} = "Tu"; my $TempHours = substr($TempReadingTu[$i], 0, length($TempReadingTu[$i])-2); my $TempMinutes = substr($TempReadingTu[$i], -2); $TempHashSend->{"time"} = ($TempHours * 60 ) + $TempMinutes; $TempHashSend->{"setpoint"} = $TempReadingTu[$i+1]; push @SwitchPointsSend, $TempHashSend; } for (my $i=0;$i<$#TempReadingWe;$i+=2) { my $TempHashSend; $TempHashSend->{"dayOfWeek"} = "We"; my $TempHours = substr($TempReadingWe[$i], 0, length($TempReadingWe[$i])-2); my $TempMinutes = substr($TempReadingWe[$i], -2); $TempHashSend->{"time"} = ($TempHours * 60 ) + $TempMinutes; $TempHashSend->{"setpoint"} = $TempReadingWe[$i+1]; push @SwitchPointsSend, $TempHashSend; } for (my $i=0;$i<$#TempReadingTh;$i+=2) { my $TempHashSend; $TempHashSend->{"dayOfWeek"} = "Th"; my $TempHours = substr($TempReadingTh[$i], 0, length($TempReadingTh[$i])-2); my $TempMinutes = substr($TempReadingTh[$i], -2); $TempHashSend->{"time"} = ($TempHours * 60 ) + $TempMinutes; $TempHashSend->{"setpoint"} = $TempReadingTh[$i+1]; push @SwitchPointsSend, $TempHashSend; } for (my $i=0;$i<$#TempReadingFr;$i+=2) { my $TempHashSend; $TempHashSend->{"dayOfWeek"} = "Fr"; my $TempHours = substr($TempReadingFr[$i], 0, length($TempReadingFr[$i])-2); my $TempMinutes = substr($TempReadingFr[$i], -2); $TempHashSend->{"time"} = ($TempHours * 60 ) + $TempMinutes; $TempHashSend->{"setpoint"} = $TempReadingFr[$i+1]; push @SwitchPointsSend, $TempHashSend; } for (my $i=0;$i<$#TempReadingSa;$i+=2) { my $TempHashSend; $TempHashSend->{"dayOfWeek"} = "Sa"; my $TempHours = substr($TempReadingSa[$i], 0, length($TempReadingSa[$i])-2); my $TempMinutes = substr($TempReadingSa[$i], -2); $TempHashSend->{"time"} = ($TempHours * 60 ) + $TempMinutes; $TempHashSend->{"setpoint"} = $TempReadingSa[$i+1]; push @SwitchPointsSend, $TempHashSend; } for (my $i=0;$i<$#TempReadingSu;$i+=2) { my $TempHashSend; $TempHashSend->{"dayOfWeek"} = "Su"; my $TempHours = substr($TempReadingSu[$i], 0, length($TempReadingSu[$i])-2); my $TempMinutes = substr($TempReadingSu[$i], -2); $TempHashSend->{"time"} = ($TempHours * 60 ) + $TempMinutes; $TempHashSend->{"setpoint"} = $TempReadingSu[$i+1]; push @SwitchPointsSend, $TempHashSend; } ### Save array of hashes of switchpoints into json hash to be send @{$jsonSend->{switchPoints}} = @SwitchPointsSend; ### Create full URL of the current Service to be written my $url ="http://" . $km200_gateway_host . $Service; ### Encode as json $JsonContent = encode_json($jsonSend); ### Delete the name of hash, "{" and "}" out of json String. No idea why but result of Try-and-Error method $JsonContent =~ s/{"switchPoints"://; $JsonContent =~ s/]}/]/g; ### Encrypt $hash->{temp}{jsoncontent} = $JsonContent; $data = km200_Encrypt($hash); ### Log file entry for debugging Log3 $name, 5, $name. "km200_Set - Trying to push switchPoint list to KM-Unit"; ### Create parameter set for HttpUtils_BlockingGet my $param = { url => $url, timeout => $PollingTimeout * 5, data => $data, method => "POST", header => "agent: TeleHeater/2.2.3\r\nUser-Agent: TeleHeater/2.2.3\r\nAccept: application/json", }; ### Block other scheduled and unscheduled routines $hash->{status}{FlagSetRequest} = true; ### Write value with HttpUtils_BlockingGet ($err, $data) = HttpUtils_BlockingGet($param); ### Reset flag $hash->{status}{FlagSetRequest} = false; ### If error message has been returned if($err ne "") { Log3 $name, 2, $name . " - ERROR: $err"; return $err; } Log3 $name, 5, $name. ("Waiting for processing time (READBACKDELAY / [ms]) : " . $hash->{READBACKDELAY} . " \n"); ### Make a pause before ReadBack usleep ($hash->{READBACKDELAY}*1000); ### Read service-hash and format it so it is compareable to the sent content my $ReReadContent = km200_GetSingleService($hash); $ReReadContent = $ReReadContent->{switchPoints}; $ReReadContent = encode_json($ReReadContent); $ReReadContent =~ s/{"switchPoints"://; $ReReadContent =~ s/]}/]/g; ### Transform back into array of hashes eval { $ReReadContent = decode_json(encode_utf8($ReReadContent)); $JsonContent = decode_json(encode_utf8($JsonContent)); 1; } or do { }; ### Set Counter for found items in SwitchPrograms my $FoundJsonItem = 0; ### For every item of the array of SwitchPrograms to be send foreach my $ReReadItem (@{$ReReadContent}) { ### Set Counter for found items of ReRead values my $FoundReReadItem = 0; ### For every item of the array of SwitchPrograms after Re-Reading foreach my $JsonItem (@{$JsonContent}) { ### If the current Switchprogram - hash does not have the same amount of keys if (%$ReReadItem ne %$JsonItem) { ### Do nothing #print "they don't have the same number of keys\n"; } ### If the current Switchprogram - hash do have the same amount of keys else { ### Compare key names and values my %cmp = map { $_ => 1 } keys %$ReReadItem; for my $key (keys %$JsonItem) { last unless exists $cmp{$key}; last unless $$ReReadItem{$key} eq $$JsonItem{$key}; delete $cmp{$key}; } if (%cmp) { ### Do nothing #print "they don't have the same keys or values\n"; } else { ### Inkrement Counter $FoundReReadItem = 1; #print "they have the same keys and values\n"; } } } ### If item has been found if ($FoundReReadItem == 1) { ### Inkrement Counter for found identical SwitchPoints $FoundJsonItem++; } } my $ReturnValue; if ($FoundJsonItem == @{$ReReadContent}) { $ReturnValue = "The service " . $Service . " has been changed succesfully!"; Log3 $name, 5, $name. "Writing $Service succesfully \n"; } else { $ReturnValue = "ERROR - The service " . $Service . " could not changed! \n"; } ### Return the status message return $ReturnValue; } } ### Check whether the type is an switchProgram. ### If true, the requested service is referring to the entire week but not a particular week. if ($jsonRead -> {type} eq "switchProgram") { ### Create full URL of the current Service to be written my $url ="http://" . $km200_gateway_host . $Service; ### Get the string to be send $JsonContent = $hash->{temp}{postdata}; ### Encrypt $hash->{temp}{jsoncontent} = $JsonContent; $data = km200_Encrypt($hash); ### Create parameter set for HttpUtils_BlockingGet my $param = { url => $url, timeout => $PollingTimeout * 5, data => $data, method => "POST", header => "agent: TeleHeater/2.2.3\r\nUser-Agent: TeleHeater/2.2.3\r\nAccept: application/json", }; ### Block other scheduled and unscheduled routines $hash->{status}{FlagSetRequest} = true; ### Write value with HttpUtils_BlockingGet ($err, $data) = HttpUtils_BlockingGet($param); ### Reset flag $hash->{status}{FlagSetRequest} = false; ### If error message has been returned if($err ne "") { Log3 $name, 2, $name . " - ERROR: $err"; return $err; } Log3 $name, 5, $name. ("Waiting for processing time (READBACKDELAY / [ms]) : " . $hash->{READBACKDELAY} . " \n"); ### Make a pause before ReadBack usleep ($hash->{READBACKDELAY}*1000); ### Read service-hash and format it so it is compareable to the sent content my $ReReadContent = km200_GetSingleService($hash); $ReReadContent = $ReReadContent->{switchPoints}; $ReReadContent = encode_json($ReReadContent); $ReReadContent =~ s/{"switchPoints"://; $ReReadContent =~ s/]}/]/g; ### Transform back into array of hashes eval { $ReReadContent = decode_json(encode_utf8($ReReadContent)); $JsonContent = decode_json(encode_utf8($JsonContent)); 1; } or do { }; ### Set Counter for found items in SwitchPrograms my $FoundJsonItem = 0; ### For every item of the array of SwitchPrograms to be send foreach my $ReReadItem (@{$ReReadContent}) { ### Set Counter for found items of ReRead values my $FoundReReadItem = 0; ### For every item of the array of SwitchPrograms after Re-Reading foreach my $JsonItem (@{$JsonContent}) { ### If the current Switchprogram - hash does not have the same amount of keys if (%$ReReadItem != %$JsonItem) { ### Do nothing #print "they don't have the same number of keys\n"; } ### If the current Switchprogram - hash do have the same amount of keys else { ### Compare key names and values my %cmp = map { $_ => 1 } keys %$ReReadItem; for my $key (keys %$JsonItem) { last unless exists $cmp{$key}; last unless $$ReReadItem{$key} eq $$JsonItem{$key}; delete $cmp{$key}; } if (%cmp) { ### Do nothing #print "they don't have the same keys or values\n"; } else { ### Inkrement Counter $FoundReReadItem = 1; #print "they have the same keys and values\n"; } } } ### If item has been found if ($FoundReReadItem == 1) { ### Inkrement Counter for found identical SwitchPoints $FoundJsonItem++; } } my $ReturnValue; if ($FoundJsonItem == @{$ReReadContent}) { $ReturnValue = "The service " . $Service . " has been changed succesfully!"; Log3 $name, 5, $name. "The service $Service has been changed succesfully!"; } else { $ReturnValue = "ERROR - The service " . $Service . " could not changed! \n"; Log3 $name, 5, $name. "Writing $Service was NOT succesfully"; } ### Return the status message return $ReturnValue; } ## Check whether the type is a single value containing a string elsif($jsonRead->{type} eq "stringValue") { ### Save chosen value into hash to be send $jsonSend->{value} = $hash->{temp}{postdata}; ### Log file entry for debugging Log3 $name, 5, $name. "km200_Set - String value"; ### Create full URL of the current Service to be written my $url ="http://" . $km200_gateway_host . $Service; ### Encode as json $JsonContent = encode_json($jsonSend); ### Encrypt $hash->{temp}{jsoncontent} = $JsonContent; $data = km200_Encrypt($hash); ### Create parameter set for HttpUtils_BlockingGet my $param = { url => $url, timeout => $PollingTimeout, data => $data, method => "POST", header => "agent: TeleHeater/2.2.3\r\nUser-Agent: TeleHeater/2.2.3\r\nAccept: application/json", }; ### Block other scheduled and unscheduled routines $hash->{status}{FlagSetRequest} = true; ### Write value with HttpUtils_BlockingGet ($err, $data) = HttpUtils_BlockingGet($param); ### Reset flag $hash->{status}{FlagSetRequest} = false; ### If error message has been returned if($err ne "") { Log3 $name, 2, $name . " - ERROR: $err"; return $err; } ### Make a pause before ReadBack usleep ($hash->{READBACKDELAY}*1000); ### Read service-hash my $ReadValue = km200_GetSingleService($hash); ### Return value my $ReturnValue = ""; if ($ReadValue->{value} eq $hash->{temp}{postdata}) { $ReturnValue = "The service " . $Service . " has been changed to: " . $ReadValue->{value}; Log3 $name, 5, $name. "km200_Set - Writing " . $Service . " succesfully with value: " . $hash->{temp}{postdata}; } else { $ReturnValue = "ERROR - The service " . $Service . " could not changed."; Log3 $name, 5, $name. "km200_Set - Writing " . $Service . " was NOT successful"; } ### Return the status message return $ReturnValue; } ## Check whether the type is a single value containing a float value elsif($jsonRead -> {type} eq "floatValue") { ### Check whether value to be sent is numeric if ($hash->{temp}{postdata} =~ /^[0-9.-]+$/) { ### Save chosen value into hash to be send $jsonSend->{value} = ($hash->{temp}{postdata}) * 1; ### Log file entry for debugging Log3 $name, 5, $name. "km200_Set - Numeric value"; ### Create full URL of the current Service to be written my $url ="http://" . $km200_gateway_host . $Service; ### Encode as json $JsonContent = encode_json($jsonSend); ### Encrypt $hash->{temp}{jsoncontent} = $JsonContent; $data = km200_Encrypt($hash); ### Create parameter set for HttpUtils_BlockingGet my $param = { url => $url, timeout => $PollingTimeout, data => $data, method => "POST", header => "agent: TeleHeater/2.2.3\r\nUser-Agent: TeleHeater/2.2.3\r\nAccept: application/json", }; ### Block other scheduled and unscheduled routines $hash->{status}{FlagSetRequest} = true; ### Write value with HttpUtils_BlockingGet ($err, $data) = HttpUtils_BlockingGet($param); ### Reset flag $hash->{status}{FlagSetRequest} = false; ### If error messsage has been returned if($err ne "") { Log3 $name, 2, $name . " - ERROR: $err"; return $err; } ### Make a pause before ReadBack usleep ($hash->{READBACKDELAY}*1000); ### Read service-hash my $ReadValue = km200_GetSingleService($hash); ### Return value my $ReturnValue = ""; if ($ReadValue->{value} eq $hash->{temp}{postdata}) { $ReturnValue = "The service " . $Service . " has been changed to: " . $ReadValue->{value} . "\n"; Log3 $name, 5, $name. "km200_Set - Writing " . $Service . " succesfully with value: " . $hash->{temp}{postdata}; } elsif ($jsonRead -> {value} == $ReadValue->{value}) { $ReturnValue = "ERROR - The service " . $Service . " could not changed to: " . $hash->{temp}{postdata} . "\n The value is: " . $ReadValue->{value} . "\n"; Log3 $name, 5, $name. "km200_Set - Writing " . $Service . " was NOT successful"; } else { $ReturnValue = "The service " . $Service . " has been rounded to: " . $ReadValue->{value} . "\n"; Log3 $name, 5, $name. "km200_Set - Writing " . $Service . " was rounded and changed successful"; } ### Return the status message return $ReturnValue; } ### If the value to be sent is NOT numeric else { ### Log file entry for debugging Log3 $name, 5, $name. "km200_Set - ERROR - Float value expected!"; return ("km200_Set - ERROR - Float value expected!\n"); } } ## If the type is unknown else { ### Log entries for debugging purposes Log3 $name, 4, $name. " : km200_SetSingleService - type unknown for : " .$Service; } } ####END####### Subroutine set individual data value ############################################################END##### ###START###### Subroutine get individual data value ###########################################################START#### sub km200_GetSingleService($) { my ($hash, $def) = @_; my $Service = $hash->{temp}{service}; my $km200_gateway_host = $hash->{URL}; my $name = $hash->{NAME}; my $PollingTimeout = $hash->{POLLINGTIMEOUT}; my $json -> {type} = ""; $json -> {value} = ""; my $err; my $data; ### Log entries for debugging purposes Log3 $name, 5, $name. " : km200 - GetSingleService - service : " .$Service; ### Create full URL of the current Service to be read my $url ="http://" . $km200_gateway_host . $Service; ### Log entries for debugging purposes Log3 $name, 5, $name. " : km200 - GetSingleService - url : " .$url; ### Create parameter set for HttpUtils_BlockingGet my $param = { url => $url, timeout => $PollingTimeout, method => "GET", header => "agent: TeleHeater/2.2.3\r\nUser-Agent: TeleHeater/2.2.3\r\nAccept: application/json", }; ### Block other scheduled and unscheduled routines $hash->{status}{FlagGetRequest} = true; ### Retrieve data from km200 ($err, $data) = HttpUtils_BlockingGet($param); ### Log entries for debugging purposes Log3 $name, 5, $name. " : km200 - GetSingleService - err : " .$err; Log3 $name, 5, $name. " : km200 - GetSingleService - data : " .$data; ### Block other scheduled and unscheduled routines $hash->{status}{FlagGetRequest} = false; ### If error message has been reported if($err ne "") { Log3 $name, 2, $name . " : ERROR: Service: ".$Service. ": No proper Communication with Gateway: " .$err; my $ReturnMessage ="ERROR"; $json -> {type} = $ReturnMessage; $json -> {value} = $ReturnMessage; return $json; } ### If NO error message has been reported else { $hash->{temp}{decodedcontent} = $data; my $decodedContent = km200_Decrypt($hash); if ($decodedContent ne "") { eval { $json = decode_json(encode_utf8($decodedContent)); 1; } or do { Log3 $name, 5, $name. " : km200_GetSingleService - Data cannot be parsed by JSON on km200 for http://" . $param->{url}; }; ### Check whether the type is a single value containing a string or float value if(($json -> {type} eq "stringValue") || ($json -> {type} eq "floatValue")) { my $JsonId = $json->{id}; my $JsonType = $json->{type}; my $JsonValue = $json->{value}; ### Save json-hash for DbLog-Split $hash->{temp}{ServiceDbLogSplitHash} = $json; $hash->{temp}{JsonRaw} = $decodedContent; ### Write reading for fhem readingsSingleUpdate( $hash, $JsonId, $JsonValue, 1); return $json } ### Check whether the type is an switchProgram elsif ($json -> {type} eq "switchProgram") { my $JsonId = $json->{id}; my $JsonType = $json->{type}; ### Log entries for debugging purposes Log3 $name, 4, $name. " : km200_GetSingleService - value found for : " .$Service; Log3 $name, 5, $name. " : km200_GetSingleService - id : " .$JsonId; Log3 $name, 5, $name. " : km200_GetSingleService - type : " .$JsonType; ### Set up variables my $TempReturnVal = ""; my $TempReadingMo = ""; my $TempReadingTu = ""; my $TempReadingWe = ""; my $TempReadingTh = ""; my $TempReadingFr = ""; my $TempReadingSa = ""; my $TempReadingSu = ""; foreach my $item (@{ $json->{switchPoints} }) { ### Create string for time and switchpoint in fixed format and write part of Reading String my $time = $item->{time}; my $temptime = $time / 60; my $temptimeHH = int($temptime); my $temptimeMM = ($time - ($temptimeHH * 60)); $temptimeHH = sprintf ('%02d', $temptimeHH); $temptimeMM = sprintf ('%02d', $temptimeMM); $temptime = $temptimeHH . $temptimeMM; my $tempsetpoint = $item->{setpoint}; $tempsetpoint =~ s/^(.+)$/sprintf("%s%s", $1, ' 'x(8-length($1)))/e; my $TempReading = $temptime . " " . $tempsetpoint; ### Create ValueString for this day if ($item->{dayOfWeek} eq "Mo") { ### If it is the first entry for this day if ($TempReadingMo eq "") { ### Write the first entry $TempReadingMo = $TempReading; } ### If it is NOT the first entry for this day else { ### Add the next entry $TempReadingMo = $TempReadingMo . " " . $TempReading; } } elsif ($item->{dayOfWeek} eq "Tu") { ### If it is the first entry for this day if ($TempReadingTu eq "") { ### Write the first entry $TempReadingTu = $TempReading; } ### If it is NOT the first entry for this day else { ### Add the next entry $TempReadingTu = $TempReadingTu . " " . $TempReading; } } elsif ($item->{dayOfWeek} eq "We") { ### If it is the first entry for this day if ($TempReadingWe eq "") { ### Write the first entry $TempReadingWe = $TempReading; } ### If it is NOT the first entry for this day else { ### Add the next entry $TempReadingWe = $TempReadingWe . " " . $TempReading; } } elsif ($item->{dayOfWeek} eq "Th") { ### If it is the first entry for this day if ($TempReadingTh eq "") { ### Write the first entry $TempReadingTh = $TempReading; } ### If it is NOT the first entry for this day else { ### Add the next entry $TempReadingTh = $TempReadingTh . " " . $TempReading; } } elsif ($item->{dayOfWeek} eq "Fr") { ### If it is the first entry for this day if ($TempReadingFr eq "") { ### Write the first entry $TempReadingFr = $TempReading; } ### If it is NOT the first entry for this day else { ### Add the next entry $TempReadingFr = $TempReadingFr . " " . $TempReading; } } elsif ($item->{dayOfWeek} eq "Sa") { ### If it is the first entry for this day if ($TempReadingSa eq "") { ### Write the first entry $TempReadingSa = $TempReading; } ### If it is NOT the first entry for this day else { ### Add the next entry $TempReadingSa = $TempReadingSa . " " . $TempReading; } } elsif ($item->{dayOfWeek} eq "Su") { ### If it is the first entry for this day if ($TempReadingSu eq "") { ### Write the first entry $TempReadingSu = $TempReading; } ### If it is NOT the first entry for this day else { ### Add the next entry $TempReadingSu = $TempReadingSu . " " . $TempReading; } } else { Log3 $name, 5, $name. "dayOfWeek of unknow day: " . $item->{dayOfWeek}; } } ### Create new Service and write reading for fhem $TempReturnVal = "1-Mo: " . $TempReadingMo . "\n"; $TempReturnVal = $TempReturnVal . "2-Tu: " . $TempReadingTu . "\n"; $TempReturnVal = $TempReturnVal . "3-We: " . $TempReadingWe . "\n"; $TempReturnVal = $TempReturnVal . "4-Th: " . $TempReadingTh . "\n"; $TempReturnVal = $TempReturnVal . "5-Fr: " . $TempReadingFr . "\n"; $TempReturnVal = $TempReturnVal . "6-Sa: " . $TempReadingSa . "\n"; $TempReturnVal = $TempReturnVal . "7-Su: " . $TempReadingSu . "\n"; ### Save weeklist in "value" $json->{value} = $TempReturnVal; ### Save raw Json string $hash->{temp}{JsonRaw} = $decodedContent; my $TempJsonId; ### Create new Service and write reading for fhem $TempJsonId = $JsonId . "/" . "1-Mo"; readingsSingleUpdate( $hash, $TempJsonId, $TempReadingMo, 1); ### Create new Service and write reading for fhem $TempJsonId = $JsonId . "/" . "2-Tu"; readingsSingleUpdate( $hash, $TempJsonId, $TempReadingTu, 1); ### Create new Service and write reading for fhem $TempJsonId = $JsonId . "/" . "3-We"; readingsSingleUpdate( $hash, $TempJsonId, $TempReadingWe, 1); ### Create new Service and write reading for fhem $TempJsonId = $JsonId . "/" . "4-Th"; readingsSingleUpdate( $hash, $TempJsonId, $TempReadingTh, 1); ### Create new Service and write reading for fhem $TempJsonId = $JsonId . "/" . "5-Fr"; readingsSingleUpdate( $hash, $TempJsonId, $TempReadingFr, 1); ### Create new Service and write reading for fhem $TempJsonId = $JsonId . "/" . "6-Sa"; readingsSingleUpdate( $hash, $TempJsonId, $TempReadingSa, 1); ### Create new Service and write reading for fhem $TempJsonId = $JsonId . "/" . "7-Su"; readingsSingleUpdate( $hash, $TempJsonId, $TempReadingSu, 1); return $json } ### Check whether the type is an errorlist elsif ($json -> {type} eq "errorList") { my $TempErrorList = ""; ### Sort list by timestamps descending my @TempSortedErrorList = sort { $b->{t} cmp $a->{t} } @{ $json->{values} }; # my @TempSortedErrorList = @{ $json->{values} } ; ### For every notification do foreach my $item (@TempSortedErrorList) { ### Create message string with fixed blocksize my $TempTime = $item->{t}; if ($TempTime) {$TempTime =~ s/^(.+)$/sprintf("%s%s", $1, ' 'x(20-length($1)))/e;} my $TempErrorCode = $item->{dcd}; $TempErrorCode =~ s/^(.+)$/sprintf("%s%s", $1, ' 'x(3 -length($1)))/e; my $TempAddCode = $item->{ccd}; $TempAddCode =~ s/^(.+)$/sprintf("%s%s", $1, ' 'x(4 -length($1)))/e; my $TempClassCode = $item->{cat}; $TempClassCode =~ s/^(.+)$/sprintf("%s%s", $1, ' 'x(2- length($1)))/e; my $TempErrorMessage = "Time: " . $TempTime . "-ErrorCode: " . $TempErrorCode . " -AddCode: " . $TempAddCode . " -Category: " . $TempClassCode; ### Create List $TempErrorList = $TempErrorList . $TempErrorMessage . "\n"; } ### Save raw Json string $hash->{temp}{JsonRaw} = $decodedContent; ### Save errorList $json->{value} = $TempErrorList; return $json; } ### Check whether the type is an refEnum which is indicating an empty parent directory elsif ($json -> {type} eq "refEnum") { ### Initialise Return Message my $ReturnMessage = ""; ### For each item found in this empty parent directory foreach my $item (@{ $json->{references} }) { ### If it is the first item in the list if ($ReturnMessage eq "") { $ReturnMessage = $item->{id}; } ### If it is not the first item in the list else { $ReturnMessage = $ReturnMessage . "\n" . $item->{id}; } } ### Return list of available directories $json->{value} = $ReturnMessage; ### Save raw Json string $hash->{temp}{JsonRaw} = $decodedContent; return $json; } ### Check whether the type is a systeminfo elsif ($json -> {type} eq "systeminfo") { my $JsonId = $json->{id}; my $JsonType = $json->{type}; my @JsonValues = $json->{values}; ### Log entries for debugging purposes Log3 $name, 4, $name. " : km200_GetSingleService - value found for : " .$Service; Log3 $name, 5, $name. " : km200_GetSingleService - id : " .$JsonId; Log3 $name, 5, $name. " : km200_GetSingleService - type : " .$JsonType; ### Initialise Return Message my $ReturnMessage = ""; ### Initialise ArrayCounter my $ArrayCounter = 0; foreach my $ArrayItem (@{ $json->{values} }) { ### Incrementation of ArrayCounter $ArrayCounter++; ### Get array from scalar my %ArrayHash = %{$ArrayItem}; while( my( $SystemInfoHashKey, $SystemInfoHashValue ) = each %ArrayHash ) { ### Create new Service and write reading for fhem my $TempJsonId = $JsonId . "/" . sprintf ('%02d', $ArrayCounter) . "/" . $SystemInfoHashKey; readingsSingleUpdate( $hash, $TempJsonId, $SystemInfoHashValue, 1); ### If it is the first item in the list if ($ReturnMessage eq "") { $ReturnMessage = $TempJsonId . " = " . $SystemInfoHashValue; } ### If it is not the first item in the list else { $ReturnMessage = $ReturnMessage . "\n" . $TempJsonId . " = " . $SystemInfoHashValue; } } } ### Return list of available directories $json->{value} = $ReturnMessage; ### Save raw Json string $hash->{temp}{JsonRaw} = $decodedContent; return $json; } ### If the type is unknown else { ### Log entries for debugging purposes Log3 $name, 4, $name. " : km200_GetSingleService - type unknown for : " .$Service; ### Log entries for debugging purposes } } else { Log3 $name, 4, $name. " : km200_GetSingleService: ". $Service . " NOT available"; my $ReturnMessage = "ERROR"; $json -> {type} = $ReturnMessage; $json -> {value} = $ReturnMessage; return $json; } } } ####END####### Subroutine get individual data value ############################################################END##### ###START###### Subroutine initial contact of services via HttpUtils ###########################################START#### sub km200_GetInitService($) { my ($hash, $def) = @_; my $km200_gateway_host = $hash->{URL} ; my $name = $hash->{NAME} ; $hash->{status}{FlagInitRequest} = true; my @KM200_InitServices = @{$hash->{Secret}{KM200ALLSERVICES}}; my $ServiceCounterInit = $hash->{temp}{ServiceCounterInit}; my $PollingTimeout = $hash->{POLLINGTIMEOUT}; my $Service = $KM200_InitServices[$ServiceCounterInit]; ### Stop the current timer RemoveInternalTimer($hash); ### If this this loop is accessed for the first time, stop the timer and set status if ($ServiceCounterInit == 0) { ### Log file entry for debugging Log3 $name, 5, $name. "Sounding and importing of services started"; ### Set status of km200 fhem module $hash->{STATE} = "Sounding..."; } ### Get the values my $url ="http://" . $km200_gateway_host . $Service; my $param = { url => $url, timeout => $PollingTimeout, hash => $hash, method => "GET", header => "agent: TeleHeater/2.2.3\r\nUser-Agent: TeleHeater/2.2.3\r\nAccept: application/json", callback => \&km200_ParseHttpResponseInit }; ### Set flag for initialisation $hash->{status}{FlagInitRequest} = true; ### Get the value HttpUtils_NonblockingGet($param); } ####END####### Subroutine initial contact of services via HttpUtils ############################################END##### ###START###### Subroutine to download complete initial data set from gateway ##################################START#### # For all known, but not excluded services by attribute "DoNotPoll", try reading the respective values from gateway sub km200_ParseHttpResponseInit($) { my ($param, $err, $data) = @_; my $hash = $param->{hash}; my $name = $hash ->{NAME}; my $ServiceCounterInit = $hash ->{temp}{ServiceCounterInit}; my @KM200_RespondingServices = @{$hash ->{Secret}{KM200RESPONDINGSERVICES}}; my @KM200_WriteableServices = @{$hash ->{Secret}{KM200WRITEABLESERVICES}}; my @KM200_InitServices = @{$hash ->{Secret}{KM200ALLSERVICES}}; my $NumberInitServices = ""; my $Service = $KM200_InitServices[$ServiceCounterInit]; my $type; my $json ->{type} = ""; if($err ne "") { ### Create Log entry Log3 $name, 2, $name . " : km200_ParseHttpResponseInit - ERROR : ".$Service. ": No proper Communication with Gateway: " .$err; ### Set status of km200 fhem module $hash->{STATE} = "ERROR - Initial Connection failed... Try to re-connect in 10s"; ### Start the timer for polling again but wait 10s InternalTimer(gettimeofday()+10, "km200_GetInitService", $hash, 1); ### Create Log entry Log3 $name, 2, $name . " : km200_ParseHttpResponseInit - ERROR : Timer restarted to try again in 10s"; Log3 $name, 5, $name . "______________________________________________________________________________________________________________________"; return "ERROR"; } $hash->{temp}{decodedcontent} = $data; my $decodedContent = km200_Decrypt($hash); ### Check whether the decoded content is not empty and therefore available if ($decodedContent ne "") { eval { $json = decode_json(encode_utf8($decodedContent)); 1; } or do { Log3 $name, 4, $name. " : km200_ParseHttpResponseInit - CANNOT be parsed : ". $Service; }; ### Check whether the type is a single value containing a string or float value if(($json -> {type} eq "stringValue") || ($json -> {type} eq "floatValue")) { my $JsonId = $json->{id}; my $JsonType = $json->{type}; my $JsonValue = $json->{value}; ### Log entries for debugging purposes Log3 $name, 5, $name. " : km200_ParseHttpResponseInit - value found for : " .$Service; Log3 $name, 5, $name. " : km200_ParseHttpResponseInit - id : " .$JsonId; Log3 $name, 5, $name. " : km200_ParseHttpResponseInit - type : " .$JsonType; Log3 $name, 5, $name. " : km200_ParseHttpResponseInit - value : " .$JsonValue; ### Add service to the list of responding services push (@KM200_RespondingServices, $Service); ### Delete double entries in the list of responding services and sort in alphabetical order my %FilteredKM200RespondingServices; $FilteredKM200RespondingServices{$_}=0 for @KM200_RespondingServices; @KM200_RespondingServices = (keys %FilteredKM200RespondingServices); @KM200_RespondingServices = sort @KM200_RespondingServices; ### Save json-hash for DbLog-Split $hash->{temp}{ServiceDbLogSplitHash} = $json; ### Write reading for fhem readingsSingleUpdate( $hash, $JsonId, $JsonValue, 1); ### Log file entry for debugging my $LogMessage = " : The following Service can be read"; ### Check whether service is writeable and write name of service in array if ($json->{writeable} == 1) { $LogMessage = $LogMessage . " and is writeable"; push (@KM200_WriteableServices, $Service); } else { # Do nothing $LogMessage = $LogMessage . " "; } ### Log file entry for debugging $LogMessage = $LogMessage . " : " . $JsonId; Log3 $name, 4, $name. $LogMessage; } ### Check whether the type is an switchProgram elsif ($json -> {type} eq "switchProgram") { my $JsonId = $json->{id}; my $JsonType = $json->{type}; my @JsonValues = $json->{switchPoints}; ### Log entries for debugging purposes Log3 $name, 5, $name. " : km200_ParseHttpResponseInit - value found for : " .$Service; Log3 $name, 5, $name. " : km200_ParseHttpResponseInit - id : " .$JsonId; Log3 $name, 5, $name. " : km200_ParseHttpResponseInit - type : " .$JsonType; Log3 $name, 5, $name. " : km200_ParseHttpResponseInit - value : " .@JsonValues; ### Add service to the list of responding services push (@KM200_RespondingServices, $Service); ### Delete double entries in the list of responding services and sort in alphabetical order my %FilteredKM200RespondingServices; $FilteredKM200RespondingServices{$_}=0 for @KM200_RespondingServices; @KM200_RespondingServices = (keys %FilteredKM200RespondingServices); @KM200_RespondingServices = sort @KM200_RespondingServices; ### Log file entry for debugging my $LogMessage = " : The following Service can be read"; ### Check whether service is writeable and write name of service in array if ($json->{writeable} == 1) { $LogMessage = $LogMessage . " and is writeable"; push (@KM200_WriteableServices, $Service); } else { # Do nothing $LogMessage = $LogMessage . " "; } $LogMessage = $LogMessage . " : " .$JsonId; Log3 $name, 4, $name . $LogMessage; ### Set up variables my $TempJsonId = ""; my $TempReadingMo = ""; my $TempReadingTu = ""; my $TempReadingWe = ""; my $TempReadingTh = ""; my $TempReadingFr = ""; my $TempReadingSa = ""; my $TempReadingSu = ""; foreach my $item (@{ $json->{switchPoints} }) { ### Create string for time and switchpoint in fixed format and write part of Reading String my $time = $item->{time}; my $temptime = $time / 60; my $temptimeHH = int($temptime); my $temptimeMM = ($time - ($temptimeHH * 60)); $temptimeHH = sprintf ('%02d', $temptimeHH); $temptimeMM = sprintf ('%02d', $temptimeMM); $temptime = $temptimeHH . $temptimeMM; my $tempsetpoint = $item->{setpoint}; $tempsetpoint =~ s/^(.+)$/sprintf("%s%s", $1, ' 'x(8-length($1)))/e; my $TempReading = $temptime . " " . $tempsetpoint; ### Create ValueString for this day if ($item->{dayOfWeek} eq "Mo") { ### If it is the first entry for this day if ($TempReadingMo eq "") { ### Write the first entry $TempReadingMo = $TempReading; } ### If it is NOT the first entry for this day else { ### Add the next entry $TempReadingMo = $TempReadingMo . " " . $TempReading; } } elsif ($item->{dayOfWeek} eq "Tu") { ### If it is the first entry for this day if ($TempReadingTu eq "") { ### Write the first entry $TempReadingTu = $TempReading; } ### If it is NOT the first entry for this day else { ### Add the next entry $TempReadingTu = $TempReadingTu . " " . $TempReading; } } elsif ($item->{dayOfWeek} eq "We") { ### If it is the first entry for this day if ($TempReadingWe eq "") { ### Write the first entry $TempReadingWe = $TempReading; } ### If it is NOT the first entry for this day else { ### Add the next entry $TempReadingWe = $TempReadingWe . " " . $TempReading; } } elsif ($item->{dayOfWeek} eq "Th") { ### If it is the first entry for this day if ($TempReadingTh eq "") { ### Write the first entry $TempReadingTh = $TempReading; } ### If it is NOT the first entry for this day else { ### Add the next entry $TempReadingTh = $TempReadingTh . " " . $TempReading; } } elsif ($item->{dayOfWeek} eq "Fr") { ### If it is the first entry for this day if ($TempReadingFr eq "") { ### Write the first entry $TempReadingFr = $TempReading; } ### If it is NOT the first entry for this day else { ### Add the next entry $TempReadingFr = $TempReadingFr . " " . $TempReading; } } elsif ($item->{dayOfWeek} eq "Sa") { ### If it is the first entry for this day if ($TempReadingSa eq "") { ### Write the first entry $TempReadingSa = $TempReading; } ### If it is NOT the first entry for this day else { ### Add the next entry $TempReadingSa = $TempReadingSa . " " . $TempReading; } } elsif ($item->{dayOfWeek} eq "Su") { ### If it is the first entry for this day if ($TempReadingSu eq "") { ### Write the first entry $TempReadingSu = $TempReading; } ### If it is NOT the first entry for this day else { ### Add the next entry $TempReadingSu = $TempReadingSu . " " . $TempReading; } } else { Log3 $name, 5, $name. "dayOfWeek of unknow day: " . $item->{dayOfWeek}; } } ### Create new Service and write reading for fhem $TempJsonId = $JsonId . "/" . "1-Mo"; readingsSingleUpdate( $hash, $TempJsonId, $TempReadingMo, 1); ### Log file entry for debugging Log3 $name, 5, $name. " : The following Service can be read and is writeable : " . $TempJsonId; ### Add service to the list of writeable services push (@KM200_WriteableServices, $TempJsonId); ### Create new Service and write reading for fhem $TempJsonId = $JsonId . "/" . "2-Tu"; readingsSingleUpdate( $hash, $TempJsonId, $TempReadingTu, 1); ### Log file entry for debugging Log3 $name, 5, $name. " : The following Service can be read and is writeable : " . $TempJsonId; ### Add service to the list of writeable services push (@KM200_WriteableServices, $TempJsonId); ### Create new Service and write reading for fhem $TempJsonId = $JsonId . "/" . "3-We"; readingsSingleUpdate( $hash, $TempJsonId, $TempReadingWe, 1); ### Log file entry for debugging Log3 $name, 5, $name. " : The following Service can be read and is writeable : " . $TempJsonId; ### Add service to the list of writeable services push (@KM200_WriteableServices, $TempJsonId); ### Create new Service and write reading for fhem $TempJsonId = $JsonId . "/" . "4-Th"; readingsSingleUpdate( $hash, $TempJsonId, $TempReadingTh, 1); ### Log file entry for debugging Log3 $name, 5, $name. " : The following Service can be read and is writeable : " . $TempJsonId; ### Add service to the list of writeable services push (@KM200_WriteableServices, $TempJsonId); ### Create new Service and write reading for fhem $TempJsonId = $JsonId . "/" . "5-Fr"; readingsSingleUpdate( $hash, $TempJsonId, $TempReadingFr, 1); ### Log file entry for debugging Log3 $name, 5, $name. " : The following Service can be read and is writeable : " . $TempJsonId; ### Add service to the list of writeable services push (@KM200_WriteableServices, $TempJsonId); ### Create new Service and write reading for fhem $TempJsonId = $JsonId . "/" . "6-Sa"; readingsSingleUpdate( $hash, $TempJsonId, $TempReadingSa, 1); ### Log file entry for debugging Log3 $name, 5, $name. " : The following Service can be read and is writeable : " . $TempJsonId; ### Add service to the list of writeable services push (@KM200_WriteableServices, $TempJsonId); ### Create new Service and write reading for fhem $TempJsonId = $JsonId . "/" . "7-Su"; readingsSingleUpdate( $hash, $TempJsonId, $TempReadingSu, 1); ### Log file entry for debugging Log3 $name, 5, $name. " : The following Service can be read and is writeable : " . $TempJsonId; ### Add service to the list of writeable services push (@KM200_WriteableServices, $TempJsonId); } ### Check whether the type is an errorlist elsif ($json -> {type} eq "errorList") { my $JsonId = $json->{id}; my $JsonType = $json->{type}; ### Log entries for debugging purposes Log3 $name, 5, $name. " : km200_ParseHttpResponseInit - value found for : " .$Service; Log3 $name, 5, $name. " : km200_ParseHttpResponseInit - id : " .$JsonId; Log3 $name, 5, $name. " : km200_ParseHttpResponseInit - type : " .$JsonType; ### Add service to the list of responding services push (@KM200_RespondingServices, $Service); ### Delete double entries in the list of responding services and sort in alphabetical order my %FilteredKM200RespondingServices; $FilteredKM200RespondingServices{$_}=0 for @KM200_RespondingServices; @KM200_RespondingServices = (keys %FilteredKM200RespondingServices); @KM200_RespondingServices = sort @KM200_RespondingServices; ### Log file entry for debugging my $LogMessage = " : The following Service can be read"; ### Check whether service is writeable and write name of service in array if ($json->{writeable} == 1) { $LogMessage = $LogMessage . " and is writeable "; push (@KM200_WriteableServices, $Service); } else { # Do nothing $LogMessage = $LogMessage . " "; } ### Log file entry for debugging $LogMessage = $LogMessage . " : " . $JsonId; Log3 $name, 4, $name . $LogMessage; ### Sort list by timestamps descending my $TempServiceIndex = 0; my @TempSortedErrorList = sort { $b->{t} cmp $a->{t} } @{ $json->{values} }; # my @TempSortedErrorList = @{ $json->{values} }; foreach my $item (@TempSortedErrorList) { ### Increment Service-Index $TempServiceIndex++; ### Create message string with fixed blocksize my $TempTime = $item->{t}; if ($TempTime) {$TempTime =~ s/^(.+)$/sprintf("%s%s", $1, ' 'x(20-length($1)))/e;} my $TempErrorCode = $item->{dcd}; $TempErrorCode =~ s/^(.+)$/sprintf("%s%s", $1, ' 'x(3 -length($1)))/e; my $TempAddCode = $item->{ccd}; $TempAddCode =~ s/^(.+)$/sprintf("%s%s", $1, ' 'x(4 -length($1)))/e; my $TempClassCode = $item->{cat}; $TempClassCode =~ s/^(.+)$/sprintf("%s%s", $1, ' 'x(2- length($1)))/e; my $TempErrorMessage = "Time: " . $TempTime . "-ErrorCode: " . $TempErrorCode . " -AddCode: " . $TempAddCode . " -Category: " . $TempClassCode; ### Create Service with Increment my $TempServiceString = $Service . "/Error-" . (sprintf("%02d", $TempServiceIndex)); ### Write Reading readingsSingleUpdate( $hash, $TempServiceString, $TempErrorMessage, 1); ### Log file entry for debugging Log3 $name, 5, $name. " : The following Service can be read : " .$TempServiceString; } } ### Check whether the type is an refEnum which is indicating an empty parent directory elsif ($json -> {type} eq "refEnum") { my $JsonId = $json->{id}; my $JsonType = $json->{type}; my @JsonReferences = $json->{references}; ### Log file entry for debugging Log3 $name, 5, $name. " : The following Service is an empty parent directory : " . $JsonId; ### For each item found in this empty parent directory foreach my $item (@{ $json->{references} }) { my $SearchWord = $item->{id}; ### If the Service found is listed as blocked service if ((grep {$_ eq $SearchWord} @{$hash->{Secret}{KM200DONOTPOLL}}) == 1) { ### Do nothing ### Log file entry for debugging Log3 $name, 5, $name. "The following Service has been found but is blacklisted: " . $item->{id}; } ### If the Service found is NOT listed as blocked service else { ### Add service to the list of all known services push (@{$hash ->{Secret}{KM200ALLSERVICES}}, $item->{id}); } } ### Sort the list of all services alphabetically @{$hash ->{Secret}{KM200ALLSERVICES}} = sort @{$hash ->{Secret}{KM200ALLSERVICES}}; } ### Check whether the type is a systeminfo elsif ($json -> {type} eq "systeminfo") { my $JsonId = $json->{id}; my $JsonType = $json->{type}; my @JsonValues = $json->{values}; ### Log entries for debugging purposes Log3 $name, 5, $name. " : km200_ParseHttpResponseInit - value found for : " .$Service; Log3 $name, 5, $name. " : km200_ParseHttpResponseInit - id : " .$JsonId; Log3 $name, 5, $name. " : km200_ParseHttpResponseInit - type : " .$JsonType; Log3 $name, 5, $name. " : km200_ParseHttpResponseInit - value : " .@JsonValues; ### Add service to the list of responding services push (@KM200_RespondingServices, $Service); ### Delete double entries in the list of responding services and sort in alphabetical order my %FilteredKM200RespondingServices; $FilteredKM200RespondingServices{$_}=0 for @KM200_RespondingServices; @KM200_RespondingServices = (keys %FilteredKM200RespondingServices); @KM200_RespondingServices = sort @KM200_RespondingServices; ### Log file entry for debugging Log3 $name, 4, $name . " : The following Service can be read : " .$JsonId; ### Initialise ArrayCounter my $ArrayCounter = 0; foreach my $ArrayItem (@{ $json->{values} }) { ### Incrementation of ArrayCounter $ArrayCounter++; ### Log file entry for debugging Log3 $name, 5, $name . " : The ArrayItem is : " . $ArrayItem ; Log3 $name, 5, $name . " : The keys ArrayItem is : " . (keys %{$ArrayItem}) ; ### Get array from scalar my %ArrayHash = %{$ArrayItem}; while( my( $SystemInfoHashKey, $SystemInfoHashValue ) = each %ArrayHash ) { ### Log file entry for debugging Log3 $name, 5, $name . " : The ArrayHashKey is : " . $SystemInfoHashKey; Log3 $name, 5, $name . " : The ArrayHashValue is : " . $SystemInfoHashValue; ### Create new Service and write reading for fhem my $TempJsonId = $JsonId . "/" . sprintf ('%02d', $ArrayCounter) . "/" . $SystemInfoHashKey; readingsSingleUpdate( $hash, $TempJsonId, $SystemInfoHashValue, 1); ### Log file entry for debugging Log3 $name, 5, $name . " : The following Service can be read : " . $TempJsonId; } } } ### Check whether the type is unknown else { ### Log entries for debugging purposes Log3 $name, 4, $name. " : km200_ParseHttpResponseInit - type unknown for : " .$Service; } } ### Check whether the decoded content is empty and therefore NOT available else { ### Log entries for debugging purposes Log3 $name, 4, $name. " : km200_ParseHttpResponseInit - NOT available : ". $Service; } ### Log entries for debugging purposes #Log3 $name, 5, $name. " : km200_ParseHttpResponseInit : response : " .$data; Log3 $name, 5, $name . "______________________________________________________________________________________________________________________"; ### Get the size of the array @KM200_InitServices = @{$hash ->{Secret}{KM200ALLSERVICES}}; $NumberInitServices = @KM200_InitServices; ### If the list of KM200ALLSERVICES has not been finished yet if ($ServiceCounterInit < ($NumberInitServices-1)) { ++$ServiceCounterInit; $hash->{temp}{ServiceCounterInit} = $ServiceCounterInit; @{$hash->{Secret}{KM200RESPONDINGSERVICES}} = @KM200_RespondingServices; @{$hash->{Secret}{KM200WRITEABLESERVICES}} = @KM200_WriteableServices; km200_GetInitService($hash); } ### If the list of KM200ALLSERVICES is finished else { my @KM200_DynServices = @KM200_RespondingServices; ### Save arrays of services in hash @{$hash->{Secret}{KM200RESPONDINGSERVICES}} = @KM200_RespondingServices; @{$hash->{Secret}{KM200WRITEABLESERVICES}} = @KM200_WriteableServices; @{$hash->{Secret}{KM200DYNSERVICES}} = @KM200_DynServices; ### Reset flag for initialisation $hash->{status}{FlagInitRequest} = false; ###START###### Initiate the timer for continuous polling of dynamical values from KM200 ###################START#### InternalTimer(gettimeofday()+($hash->{INTERVALDYNVAL}), "km200_GetDynService", $hash, 1); Log3 $name, 4, $name. " : km200 - Define: InternalTimer for dynamic values started with interval of: ".($hash->{INTERVALDYNVAL}); ####END####### Initiate the timer for continuous polling of dynamical values from KM200 ####################END##### ### Reset fullResponse error message readingsSingleUpdate( $hash, "fullResponse", "OK", 1); ### Log file entry for debugging Log3 $name, 5, $name. "Sounding and importing of services is completed"; ### Set status of km200 fhem module $hash->{STATE} = "Standby"; ### Disable flag $hash->{temp}{ServiceCounterInit} = false; } ### If the Initialisation process has been interuppted with an error message if (ReadingsVal($name,"fullResponse",0) eq "ERROR") { ### Reset fullResponse error message readingsSingleUpdate( $hash, "fullResponse", "Restarted after ERROR", 1); ### Reset timer for init procedure and start over again until it works InternalTimer(gettimeofday()+5, "km200_GetInitService", $hash, 1); Log3 $name, 5, $name. " : km200 - Internal timer for Initialisation of services restarted after fullResponse - error."; } ### Clear up temporary variables $hash->{temp}{decodedcontent} = ""; return; } ####END####### Subroutine to download complete initial data set from gateway ###################################END##### ###START###### Subroutine obtaining dynamic services via HttpUtils ############################################START#### sub km200_GetDynService($) { my ($hash, $def) = @_; my $km200_gateway_host = $hash->{URL}; my $name = $hash->{NAME}; $hash->{STATE} = "Polling"; my @KM200_DynServices = @{$hash->{Secret}{KM200DYNSERVICES}}; my $ServiceCounterDyn = $hash->{temp}{ServiceCounterDyn}; my $PollingTimeout = $hash->{POLLINGTIMEOUT}; ### Stop the current timer RemoveInternalTimer($hash); ### If at least one service to be polled is available if (@KM200_DynServices != 0) { my $Service = $KM200_DynServices[$ServiceCounterDyn]; ### Log file entry for debugging if ($ServiceCounterDyn == 0) { Log3 $name, 5, $name. "Starting download of dynamic services"; } ### Log file entry for debugging Log3 $name, 5, $name . " - km200_GetDynService - Polling : " . $Service; my $url = "http://" . $km200_gateway_host . $Service; my $param = { url => $url, timeout => $PollingTimeout, hash => $hash, method => "GET", header => "agent: TeleHeater/2.2.3\r\nUser-Agent: TeleHeater/2.2.3\r\nAccept: application/json", callback => \&km200_ParseHttpResponseDyn }; ### Set Status Flag in order state running dynamic request $hash->{status}{FlagDynRequest} = true; ### Get data HttpUtils_NonblockingGet($param); } ### If no service to be polled is available else { Log3 $name, 5, $name . " : No dynamic values available to be read. Skipping download."; } } ####END####### Subroutine get dynamic data value ###############################################################END##### ###START###### Subroutine to download complete dynamic data set from gateway ##################################START#### # For all responding dynamic services read the respective values from gateway sub km200_ParseHttpResponseDyn($) { my ($param, $err, $data) = @_; my $hash = $param->{hash}; my $name = $hash ->{NAME}; my $ServiceCounterDyn = $hash ->{temp}{ServiceCounterDyn}; my @KM200_DynServices = @{$hash ->{Secret}{KM200DYNSERVICES}}; my $NumberDynServices = @KM200_DynServices; my $Service = $KM200_DynServices[$ServiceCounterDyn]; my $type; my $json ->{type} = ""; Log3 $name, 5, $name. " : Parsing response of dynamic service received for : " . $Service; ### Reset Status Flag $hash->{status}{FlagDynRequest} = false; if($err ne "") { Log3 $name, 2, $name . " : ERROR: Service: ".$Service. ": No proper Communication with Gateway: " .$err; readingsSingleUpdate($hash, "fullResponse", "ERROR", 1); } $hash->{temp}{decodedcontent} = $data; my $decodedContent = km200_Decrypt($hash); if ($decodedContent ne "") { eval { $json = decode_json(encode_utf8($decodedContent)); 1; } or do { Log3 $name, 5, $name. " - km200_parseHttpResponseDyn : Data cannot be parsed by JSON on km200 for http://" . $param->{url}; }; ### Check whether the type is a single value containing a string or float value if(($json -> {type} eq "stringValue") || ($json -> {type} eq "floatValue")) { my $JsonId = $json->{id}; my $JsonType = $json->{type}; my $JsonValue = $json->{value}; ### Log entries for debugging purposes Log3 $name, 4, $name. " : km200_parseHttpResponseDyn - value found for : " .$Service; Log3 $name, 5, $name. " : km200_parseHttpResponseDyn - id : " .$JsonId; Log3 $name, 5, $name. " : km200_parseHttpResponseDyn - type : " .$JsonType; Log3 $name, 5, $name. " : km200_parseHttpResponseDyn - value : " .$JsonValue; ### Log entries for debugging purposes ### Save json-hash for DbLog-Split $hash->{temp}{ServiceDbLogSplitHash} = $json; ### Save json-hash for DbLog-Split ### Write reading readingsSingleUpdate( $hash, $JsonId, $JsonValue, 1); ### Write reading } ### Check whether the type is an switchProgram elsif ($json -> {type} eq "switchProgram") { my $JsonId = $json->{id}; my $JsonType = $json->{type}; ### Log entries for debugging purposes Log3 $name, 4, $name. " : km200_parseHttpResponseDyn - value found for : " .$Service; Log3 $name, 5, $name. " : km200_parseHttpResponseDyn - id : " .$JsonId; Log3 $name, 5, $name. " : km200_parseHttpResponseDyn - type : " .$JsonType; ### Set up variables my $TempJsonId = ""; my $TempReadingMo = ""; my $TempReadingTu = ""; my $TempReadingWe = ""; my $TempReadingTh = ""; my $TempReadingFr = ""; my $TempReadingSa = ""; my $TempReadingSu = ""; foreach my $item (@{ $json->{switchPoints} }) { ### Create string for time and switchpoint in fixed format and write part of Reading String my $time = $item->{time}; my $temptime = $time / 60; my $temptimeHH = int($temptime); my $temptimeMM = ($time - ($temptimeHH * 60)); $temptimeHH = sprintf ('%02d', $temptimeHH); $temptimeMM = sprintf ('%02d', $temptimeMM); $temptime = $temptimeHH . $temptimeMM; my $tempsetpoint = $item->{setpoint}; $tempsetpoint =~ s/^(.+)$/sprintf("%s%s", $1, ' 'x(8-length($1)))/e; my $TempReading = $temptime . " " . $tempsetpoint; ### Create ValueString for this day if ($item->{dayOfWeek} eq "Mo") { ### If it is the first entry for this day if ($TempReadingMo eq "") { ### Write the first entry $TempReadingMo = $TempReading; } ### If it is NOT the first entry for this day else { ### Add the next entry $TempReadingMo = $TempReadingMo . " " . $TempReading; } } elsif ($item->{dayOfWeek} eq "Tu") { ### If it is the first entry for this day if ($TempReadingTu eq "") { ### Write the first entry $TempReadingTu = $TempReading; } ### If it is NOT the first entry for this day else { ### Add the next entry $TempReadingTu = $TempReadingTu . " " . $TempReading; } } elsif ($item->{dayOfWeek} eq "We") { ### If it is the first entry for this day if ($TempReadingWe eq "") { ### Write the first entry $TempReadingWe = $TempReading; } ### If it is NOT the first entry for this day else { ### Add the next entry $TempReadingWe = $TempReadingWe . " " . $TempReading; } } elsif ($item->{dayOfWeek} eq "Th") { ### If it is the first entry for this day if ($TempReadingTh eq "") { ### Write the first entry $TempReadingTh = $TempReading; } ### If it is NOT the first entry for this day else { ### Add the next entry $TempReadingTh = $TempReadingTh . " " . $TempReading; } } elsif ($item->{dayOfWeek} eq "Fr") { ### If it is the first entry for this day if ($TempReadingFr eq "") { ### Write the first entry $TempReadingFr = $TempReading; } ### If it is NOT the first entry for this day else { ### Add the next entry $TempReadingFr = $TempReadingFr . " " . $TempReading; } } elsif ($item->{dayOfWeek} eq "Sa") { ### If it is the first entry for this day if ($TempReadingSa eq "") { ### Write the first entry $TempReadingSa = $TempReading; } ### If it is NOT the first entry for this day else { ### Add the next entry $TempReadingSa = $TempReadingSa . " " . $TempReading; } } elsif ($item->{dayOfWeek} eq "Su") { ### If it is the first entry for this day if ($TempReadingSu eq "") { ### Write the first entry $TempReadingSu = $TempReading; } ### If it is NOT the first entry for this day else { ### Add the next entry $TempReadingSu = $TempReadingSu . " " . $TempReading; } } else { Log3 $name, 5, $name. "dayOfWeek of unknow day: " . $item->{dayOfWeek}; } } ### Create new Service and write reading for fhem $TempJsonId = $JsonId . "/" . "1-Mo"; readingsSingleUpdate( $hash, $TempJsonId, $TempReadingMo, 1); ### Create new Service and write reading for fhem $TempJsonId = $JsonId . "/" . "2-Tu"; readingsSingleUpdate( $hash, $TempJsonId, $TempReadingTu, 1); ### Create new Service and write reading for fhem $TempJsonId = $JsonId . "/" . "3-We"; readingsSingleUpdate( $hash, $TempJsonId, $TempReadingWe, 1); ### Create new Service and write reading for fhem $TempJsonId = $JsonId . "/" . "4-Th"; readingsSingleUpdate( $hash, $TempJsonId, $TempReadingTh, 1); ### Create new Service and write reading for fhem $TempJsonId = $JsonId . "/" . "5-Fr"; readingsSingleUpdate( $hash, $TempJsonId, $TempReadingFr, 1); ### Create new Service and write reading for fhem $TempJsonId = $JsonId . "/" . "6-Sa"; readingsSingleUpdate( $hash, $TempJsonId, $TempReadingSa, 1); ### Create new Service and write reading for fhem $TempJsonId = $JsonId . "/" . "7-Su"; readingsSingleUpdate( $hash, $TempJsonId, $TempReadingSu, 1); } ### Check whether the type is an errorlist elsif ($json -> {type} eq "errorList") { my $JsonId = $json->{id}; my $JsonType = $json->{type}; my $TempServiceIndex = 0; ### Sort list by timestamps descending my @TempSortedErrorList = sort { $b->{t} cmp $a->{t} } @{ $json->{values} }; # my @TempSortedErrorList = @{ $json->{values} } ; ### For every notification do foreach my $item (@TempSortedErrorList) { ### Increment Service-Index $TempServiceIndex++; ### Create message string with fixed blocksize my $TempTime = $item->{t}; if ($TempTime) { $TempTime =~ s/^(.+)$/sprintf("%s%s", $1, ' 'x(20-length($1)))/e; } else { $TempTime = "unknown"; } my $TempErrorCode = $item->{dcd}; $TempErrorCode =~ s/^(.+)$/sprintf("%s%s", $1, ' 'x(3 -length($1)))/e; my $TempAddCode = $item->{ccd}; $TempAddCode =~ s/^(.+)$/sprintf("%s%s", $1, ' 'x(4 -length($1)))/e; my $TempClassCode = $item->{cat}; $TempClassCode =~ s/^(.+)$/sprintf("%s%s", $1, ' 'x(2- length($1)))/e; my $TempErrorMessage = "Time: " . $TempTime . "-ErrorCode: " . $TempErrorCode . " -AddCode: " . $TempAddCode . " -Category: " . $TempClassCode; ### Create Service with Increment and leading 0 my $TempServiceString = $Service . "/Error-" . (sprintf("%02d", $TempServiceIndex)); ### Write Reading readingsSingleUpdate( $hash, $TempServiceString, $TempErrorMessage, 1); } } ### Check whether the type is a systeminfo elsif ($json -> {type} eq "systeminfo") { my $JsonId = $json->{id}; my $JsonType = $json->{type}; my @JsonValues = $json->{values}; ### Log entries for debugging purposes Log3 $name, 5, $name. " : km200_ParseHttpResponseInit - value found for : " .$Service; Log3 $name, 5, $name. " : km200_ParseHttpResponseInit - id : " .$JsonId; Log3 $name, 5, $name. " : km200_ParseHttpResponseInit - type : " .$JsonType; ### Initialise ArrayCounter my $ArrayCounter = 0; foreach my $ArrayItem (@{ $json->{values} }) { ### Incrementation of ArrayCounter $ArrayCounter++; ### Get array from scalar my %ArrayHash = %{$ArrayItem}; while( my( $SystemInfoHashKey, $SystemInfoHashValue ) = each %ArrayHash ) { ### Create new Service and write reading for fhem my $TempJsonId = $JsonId . "/" . sprintf ('%02d', $ArrayCounter) . "/" . $SystemInfoHashKey; readingsSingleUpdate( $hash, $TempJsonId, $SystemInfoHashValue, 1); } } } ### Check whether the type is unknown else { ### Log entries for debugging purposes Log3 $name, 4, $name. " : km200_parseHttpResponseDyn - type unknown for : " .$Service; } } else { Log3 $name, 5, $name. " : km200_parseHttpResponseDyn - Data not available on km200 for http://" . $param->{url}; } Log3 $name, 5, $name . "______________________________________________________________________________________________________________________"; ### Clear up temporary variables $hash->{temp}{decodedcontent} = ""; $hash->{temp}{service} = ""; ### Clear up temporary variables ### If list is not complete yet if ($ServiceCounterDyn < ($NumberDynServices-1)) { ++$ServiceCounterDyn; $hash->{temp}{ServiceCounterDyn} = $ServiceCounterDyn; km200_GetDynService($hash); } ### If list is complete else { $hash->{STATE} = "Standby"; $hash->{temp}{ServiceCounterDyn} = 0; ###START###### Re-Start the timer #####################################START#### InternalTimer(gettimeofday()+$hash->{INTERVALDYNVAL}, "km200_GetDynService", $hash, 1); ####END####### Re-Start the timer ######################################END##### ### Update fullResponse Reading readingsSingleUpdate( $hash, "fullResponse", "OK", 1); $hash->{status}{FlagDynRequest} = false; Log3 $name, 5, $name . "______________________________________________________________________________________________________________________"; } return undef; } ####END####### Subroutine to download complete dynamic data set from gateway ###################################END##### 1; ###START###### Description for fhem commandref ################################################################START#### =pod =item device =item summary Connects fhem to Buderus KM300, KM200, KM100, KM50 =item summary_DE Verbindet fhem mit Buderus KM300, KM200, KM100, KM50 =begin html

KM200

    The Buderus KM200, KM100 or KM50 (hereafter described as KMxxx) is a communication device to establish a connection between the Buderus central heating control unit and the internet.
    It has been designed in order to allow the inhabitants accessing their heating system via his Buderus App EasyControl.
    Furthermore it allows the maintenance companies to access the central heating control system to read and change settings.
    The km200 fhem-module enables read/write access to these parameters.

    In order to use the KMxxx with fhem, you must define the private password with the Buderus App EasyControl first.

    Remark:
    Despite the instruction of the Buderus KMxxx Installation guide, the ports 5222 and 5223 should not be opened and allow access to the KMxxx module from outside.
    You should configure (or leave) your internet router with the respective settings.
    If you want to read or change settings on the heating system, you should access the central heating control system via your fhem system only.

    As soon the module has been defined within the fhem.cfg, the module is trying to obtain all known/possible services.
    After this initial contact, the module differs between a set of continuous (dynamically) changing values (e.g.: temperatures) and not changing static values (e.g.: Firmware version).
    This two different set of values can be bound to an individual polling interval. Refer to Attributes

    Define
      define <name> km200 <IPv4-address> <GatewayPassword> <PrivatePassword>
        <name> : The name of the device. Recommendation: "myKm200".
        <IPv4-address> : A valid IPv4 address of the KMxxx. You might look into your router which DHCP address has been given to the KMxxx.
        <GatewayPassword> : The gateway password which is provided on the type sign of the KMxxx.
        <PrivatePassword> : The private password which has been defined by the user via EasyControl.

    Set
      The set function is able to change a value of a service which has the "writeable" - tag within the KMxxx service structure.
      Most of those values have an additional list of allowed values which are the only ones to be set.
      Other floatable type values can be changed only within their range of minimum and maximum value.
      set <service> <value>
        <service> : The name of the service which value shall be set. E.g.: "/heatingCircuits/hc1/operationMode"
        <value> : A valid value for this service.

    Get
      The get function is able to obtain a value of a service within the KMxxx service structure.
      The additional list of allowed values or their range of minimum and maximum value will not be handed back.
      get <service> <option>
        <service> : The name of the service which value shall be obtained. E.g.: "/heatingCircuits/hc1/operationMode"
          It returns only the value but not the unit or the range or list of allowed values possible.
        <option> : The optional Argument for the result of the get-command e.g.: "json"
          The following options are available:
          json - Returns the raw json-answer from the KMxxx as string.

    Attributes
      The following user attributes can be used with the km200 module in addition to the global ones e.g. room.
      • IntervalDynVal :
      • A valid polling interval for the dynamically changing values of the KMxxx. The value must be >=20s to allow the km200 module to perform a full polling procedure.
        The default value is 300s.
      • PollingTimeout :
      • A valid time in order to allow the module to wait for a response of the KMxxx. Usually this value does not need to be changed but might in case of slow network or slow response.
        The default and minimum value is 5s.
      • DoNotPoll :
      • A list of services separated by blanks which shall not be downloaded due to repeatable crashes or irrelevant values.
        The list can be filled with the name of the top - hierarchy service, which means everything below that service will also be ignored.
        The default value (empty) therefore nothing will be ignored.
      • ReadBackDelay :
      • A valid time in milliseconds [ms] for the delay between writing and re-reading of values after using the "set" - command. The value must be >=0ms.
        The default value is 100 = 100ms = 0,1s.
      • disable :
      • Stops the device from further pollings and deletes the existing readings.
        The default value is 0 = activated
=end html =begin html_DE

KM200

    Das Buderus KM200, KM100 or KM50 (ab hier als KMxxx beschrieben) ist eine Schnittstelle zwischen der Buderus Zentralheizungssteuerung un dem Internet.
    Es wurde entwickelt um den Bewohnern den Zugang zu Ihrem Heizungssystem durch die Buderus App EasyControl zu erlauben..
    Darüber hinaus erlaubt es nach vorheriger Freigabe dem Heizungs- bzw. Wartungsbetrieb die Heizungsanlage von aussen zu warten und Werte zu verändern.
    Das km200 fhem-Modul erlaubt den Lese-/Schreibzugriff dieser Parameter durch fhem.

    Um das KMxxx Gerät mit fhem nutzen zu können, muß zunächst ein privates Passwort mit der Buderus Buderus App EasyControl - App gesetzt werden.

    Anmerkung:
    Unabhängig der Installationsanleitung des Buderus KMxxx Geräts, sollten die Ports 5222 und 5223 am Router geschlossen bleiben um keinen Zugriff von außen auf das Gerät zu erlauben.
    Der Router sollte entsprechend Konfiguriert bzw. so belassen werden.
    Wenn der Lese-/Schreibzugriff von aussen gewünscht ist, so sollte man ausschließlich über das fhem-System auf die Zentralheizung zugreifen.

    Sobald das Modul in der fhem.cfg definiert ist, wird das Modul versuchen alle bekannten Services abzuklopfen ob diese in der angeschlossenen Konstellation überhaupt vorhanden sind.
    Nach diesem Initial-Kontakt unterscheidet das Modul zwisachen einem Satz an Services die sich ständig (dynamisch) ändern (z.B.: Vorlauftemperatur) sowie sich nicht ständig (statisch) ändernden Werten (z.B.: Firmware Version).
    Diese beiden Sätze an Services können mir einem individuellen Abfrageintervall versehen werden. Siehe Attributes

    Define
      define <name> km200 <IPv4-address> <GatewayPassword> <PrivatePassword>
        <name> : Der Name des Gerätes. Empfehlung: "myKm200".
        <IPv4-address> : Eine gültige IPv4 Adresse des KM200. Eventuell im Router nachschauen welche DHCP - Addresse dem KM200/KM50 vergeben wurde.
        <GatewayPassword> : Das gateway Passwort, welches auf dem Typenschild des KM200/KM50 zu finden ist.
        <PrivatePassword> : Das private Passwort, welches durch den User mit Hilfe der EasyControl - App vergeben wurde.

    Set
      Die set Funktion ändert die Werte der Services welche das Flag "schreibbar" innerhalb der KMxxx Service Struktur besitzen.
      Die meisten dieser beschreibbaren Werte haben eine exklusive Liste von möglichen Werten innerhalb dessen sich der neue Wert bewegen muss.
      Andere Fließkomma Werte haben einen maximum und minumum Wert, in dessen sich der neue Wert bewegen muß.
      set <service> <value>
        <service> : Der Name des Service welcher gesetzt werden soll. Z.B.: "/heatingCircuits/hc1/operationMode"
        <value> : Ein gültiger Wert für diesen Service.

    Get
      Die get-Funktion ist in der Lage einen Wert eines Service innerhalb der KMxxx Service Struktur auszulesen.
      Die zusätzliche Liste von erlaubten Werten oder der Wertebereich zwischen Minimum und Maximum wird nicht zurück gegeben.
      get <service> <option>
        <service> : Der Name des Service welcher ausgelesen werden soll. Z.B.: "/heatingCircuits/hc1/operationMode"
          Es gibt nur den Wert, aber nicht die Werteliste oder den möglichen Wertebereich zurück.
        <option> : Das optionelle Argument für Ausgabe des get-Befehls Z.B.: "json"
          Folgende Optionen sind verfügbar:
          json - Gibt anstelle des Wertes, die gesamte Json Antwort des KMxxx als String zurück

    Attributes
      Die folgenden Modul-spezifischen Attribute können neben den bekannten globalen Attributen gesetzt werden wie z.B.: room.
      • IntervalDynVal :
      • Ein gültiges Abfrageintervall für die sich ständig verändernden - dynamischen Werte der KMxxx Services. Der Wert muss größer gleich >=20s sein um dem Modul genügend Zeit einzuräumen eine volle Abfrage auszuführen bevor die nächste Abfrage startet.
        Der Default-Wert ist 300s.
      • PollingTimeout :
      • Ein gültiger Zeitwert um dem KMxxx genügend Zeit zur Antwort einzelner Werte einzuräumen. Normalerweise braucht dieser Wert nicht verändert werden, muss jedoch im Falle eines langsamen Netzwerks erhöht werden
        Der Default-Wert ist 5s.
      • DoNotPoll :
      • Eine durch Leerzeichen (Blank) getrennte Liste von Services welche von der Abfrage aufgrund irrelevanter Werte oder fhem - Abstürzen ausgenommen werden sollen.
        Die Liste kann auch Hierarchien von services enthalten. Dies bedeutet, das alle Services unterhalb dieses Services ebenfalls gelöscht werden.
        Der Default Wert ist (empty) somit werden alle bekannten Services abgefragt.
      • ReadBackDelay :
      • Ein gültiger Zeitwert in Mllisekunden [ms] für die Pause zwischen schreiben und zurücklesen des Wertes durch den "set" - Befehl. Der Wert muss >=0ms sein.
        Der Default-Wert ist 100 = 100ms = 0,1s.
      • disable :
      • Deaktiviert das Device und löscht alle bestehenden Readings.
        Der Default-Wert ist 0 = aktiviert
=end html_DE