############################################################################### # # $Id: 96_RenaultZE.pm 2023-10-15 plin $ # 96_RenaultZE.pm # # Forum : https://forum.fhem.de/index.php/topic,116273.0.html # Ref https://renault-api.readthedocs.io/en/latest/endpoints.html # ############################################################################### # # (c) 2017 Copyright: plin # All rights reserved # # This script is free software; you can redistribute it and/or modify # it under the terms of the GNU General Public License as published by # the Free Software Foundation; either version 2 of the License, or # (at your option) any later version. # # The GNU General Public License can be found at # http://www.gnu.org/copyleft/gpl.html. # A copy is found in the textfile GPL.txt and imPORTant notices to the license # from the author is found in LICENSE.txt distributed with these scripts. # # This script is distributed in the hope that it will be useful, # but WITHOUT ANY WARRANTY; without even the implied warranty of # MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the # GNU General Public License for more details. # ################################################################################## ####################################################################### # need: # - HttpUtils # - Time::Piece # - JSON # ######################################################################## ############################################################################################################################ # Version History # v 1.14 changed code for automatic update of readings distance/home during # v 1.13 recalculation of varius readings distance/home during update # v 1.12 implemented new Attribute ze_homeRadius # v 1.11 implemented new function GET checkAPIkeys # v 1.10 addedd attribute brand, 'Dacia' will allow start/stop charge, additional readings after issueing get vehicles # v 1.09 fixed problem with readingsBulkUpdate/readingsSingleUpdate in lines 1002ff # v 1.08 new KAMERON API key # v 1.07 adjusting to new output format from charges # v 1.06 logging "well known error" Failed to forward request to remote service only at log level 5 # v 1.05 fixed timing problem in update request # v 1.04 typo denbled corrected # v 1.03 hvac settings output corrected # v 1.02 some minor corrections # v 1.01 added hvac-settings # v 1.00 added module to the contrib directory # v 0.32 added attribute disabled # v 0.31 changed API keys due to change by Renault # v 0.30 fixed problem with bulk update # v 0.29 fixed problem with from_json # v 0.28 fixed timestamp issue # v 0.27 added error-Reading in case of malformed json string # v 0.26 fixed decode_json issue (additional tests) # v 0.25 fixed decode_json issue # v 0.24 get link for car image from vehicles listing # v 0.23 pretty print ze_lastErr # v 0.22 interpret charges data, default time frames for histories # v 0.21 implemented further get options implemented for Phase 1 already # v 0.20 implemented zTest attribute to test new options # v 0.19 fix for time format "2021-01-27T16:41:42+01:00" # v 0.18 renamed distance to distanceFromHome # v 0.17 added reverse geocoding # v 0.16 added distance from home # v 0.15 minor fix (warning messages) # v 0.14 detect '' in $data (RenaultZE_gData_Step2) # v 0.13 fixed timezone problem for UTC timestamps # v 0.12 fixed attr problem country/county # v 0.11 fixed parameter problem when using timer # v 0.10 fixed timer problem # v 0.9 changed logic, new readings # v 0.8 suppress 0 readings # v 0.7 fixed timer problem # v 0.6 bug fixes # v 0.5 improved feedback and error code checking # v 0.4 fix bug when accId = 0 # v 0.3 adjusted options an placed hint about untested option # v 0.2 set commands were added # v 0.1 first version with get options ############################################################################################################################ # code basis # - https://github.com/jamesremuscat/pyze # - https://gist.github.com/mountbatt/772e4512089802a2aa2622058dd1ded7 # API keys # - https://renault-wrd-prod-1-euw1-myrapp-one.s3-eu-west-1.amazonaws.com/configuration/android/config_de_DE.json # KAMEREON_API -> "wiredProd" -> apikey # GIGYA_API -> "gigyaProd" -> apikey # oder https://renault-wrd-prod-1-euw1-myrapp-one.s3-eu-west-1.amazonaws.com/configuration/iOS/config_de_DE.json ??? ############################################################################################################################ # lock-status package main; use strict; use warnings; use HttpUtils; use Time::Piece; #use JSON qw(decode_json); use JSON; my $RenaultZE_version ="V1.14 / 1.11.2023"; my %RenaultZE_sets = ( "AC:on,cancel" => "", "charge:start,stop" => "", "password" => "", "state" => "" ); my %RenaultZE_gets = ( "charge-history" => "", "charges" => "", "charging-settings:noArg" => "", "hvac-history" => "", "hvac-settings:noArg" => "", "notification-settings:noArg" => "", "update:noArg" => "", "vehicles:noArg" => "", "checkAPIkeys:noArg" => "", "zTest" => "" ); sub RenaultZE_Initialize($) { my ($hash) = @_; $hash->{DefFn} = 'RenaultZE_Define'; $hash->{UndefFn} = 'RenaultZE_Undef'; $hash->{SetFn} = 'RenaultZE_Set'; $hash->{GetFn} = 'RenaultZE_Get'; $hash->{AttrFn} = 'RenaultZE_Attr'; $hash->{ReadFn} = 'RenaultZE_Read'; $hash->{AsyncOutputFn} = 'RenaultZE_AsyncOutput'; $hash->{AttrList} = "ze_phase:1,2 ". "ze_brand:Renault,Dacia ". "ze_user ". "ze_country ". "ze_latitude ". "ze_longitude ". "ze_homeRadius ". "ze_showaddress:0,1 ". "ze_showimage:0,1,2 ". "disabled:0,1 ". $readingFnAttributes; } sub RenaultZE_Define($$) { my ($hash, $def) = @_; my @param = split('[ \t]+', $def); if(int(@param) < 3) { return "too few parameters: define RenaultZE "; } my $name = $param[0]; $hash->{VIN} = $param[2]; $hash->{INTERVAL} = $param[3]; $hash->{STATE} = "defined"; $hash->{GIGYA_API} = '3_7PLksOyBRkHv126x5WhHb-5pqC1qFR8pQjxSeLB6nhAnPERTUlwnYoznHSxwX668'; #$hash->{KAMEREON_API} = 'Ae9FDWugRxZQAGm3Sxgk7uJn6Q4CGEA2'; #$hash->{KAMEREON_API} = 'VAX7XYKGfa92yMvXculCkEFyfZbuM7Ss'; #$hash->{KAMEREON_API} = 'YjkKtHmGfaceeuExUDKGxrLZGGvtVS0J'; $hash->{KAMEREON_API} = 'YjkKtHmGfaceeuExUDKGxrLZGGvtVS0J'; #$hash->{KAMEREON_API} = 'oF09WnKqvBDcrQzcW1rJNpjIuy7KdGaB'; $hash->{VERSION} = $RenaultZE_version; readingsSingleUpdate($hash,"ze_Gigya_JWT_lastCall","0",1) unless (ReadingsVal($name,"ze_Gigya_JWT_lastCall","empty") ne "empty"); readingsSingleUpdate($hash,"ze_Gigya_JWT_Token","",1) unless (ReadingsVal($name,"ze_Gigya_JWT_Token","empty") ne "empty"); $attr{$name}{ze_country} = 'DE' unless (exists($attr{$name}{ze_country})); $attr{$name}{ze_showaddress} = '1' unless (exists($attr{$name}{ze_showaddress})); $attr{$name}{ze_showimage} = '1' unless (exists($attr{$name}{ze_showimage})); my $firstTrigger = gettimeofday() + 2; $hash->{TRIGGERTIME} = $firstTrigger; $hash->{TRIGGERTIME_FMT} = FmtDateTime($firstTrigger); RemoveInternalTimer($hash); InternalTimer($firstTrigger, "RenaultZE_UpdateTimer", $hash, 0); Log3 $hash, 5, "TRAFFIC: ($name) InternalTimer set to call GetUpdate in 2 seconds for the first time"; return undef; } sub RenaultZE_Undef($$) { my ($hash, $arg) = @_; # nothing to do RemoveInternalTimer( $hash ); return undef; } sub RenaultZE_Get($@) { my ($hash, @param) = @_; my $name = shift @param; my $opt = shift @param; my $value = join("", @param); if ($opt ne "?") { $hash->{FUNCTION} = 'GET'; $hash->{PARMS} = $opt; $hash->{PARMVALUE} = $value; $hash->{curCL} = $hash->{CL}; } Log3 $name, 5, "RenaultZE_Get - opt = $opt, value = $value"; readingsSingleUpdate($hash,"ze_lastErr","",1) if ($opt ne "?"); if ($opt eq "update") { readingsSingleUpdate($hash,"ze_Step","getStatus",1); RenaultZE_Main1($hash, @param); } elsif ($opt eq "vehicles") { readingsSingleUpdate($hash,"ze_Step","getVehicles",1); RenaultZE_Main1($hash, @param); } elsif ($opt eq "charge-history") { if ($value eq "") { my $tt = localtime()->strftime('%Y%m%d'); $value = "type=day&start=20000101&end=".$tt; $hash->{PARMVALUE} = $value; } if ( $value =~ /type=month&start=\d{6}&end=\d{6}/ or $value =~ /type=day&start=\d{8}&end=\d{8}/) { readingsSingleUpdate($hash,"ze_Step","getHistory",1); RenaultZE_Main1($hash, @param); } else { return "Syntax error for $opt, correct pattern is 'type=month&start=202012&end=202101' or 'type=day&start=20201212&end=20210120'"; } } elsif ($opt eq "charges") { if ($value eq "") { my $tt = localtime()->strftime('%Y%m%d'); $value = "start=20000101&end=".$tt; $hash->{PARMVALUE} = $value; } if ( $value =~ /start=\d{8}&end=\d{8}/ ) { readingsSingleUpdate($hash,"ze_Step","getCharges",1); RenaultZE_Main1($hash, @param); } else { return "Syntax error for $opt, correct pattern is 'start=20201212&end=20210120'"; } } elsif ($opt eq "hvac-history") { if ($value eq "") { my $tt = localtime()->strftime('%Y%m%d'); $value = "type=day&start=20000101&end=".$tt; $hash->{PARMVALUE} = $value; } if ( $value =~ /type=month&start=\d{6}&end=\d{6}/ or $value =~ /type=day&start=\d{8}&end=\d{8}/) { readingsSingleUpdate($hash,"ze_Step","getHvacHistory",1); RenaultZE_Main1($hash, @param); } else { return "Syntax error for $opt, correct pattern is 'type=month&start=202012&end=202101' or 'type=day&start=20201212&end=20210120'"; } } elsif ($opt eq "hvac-settings") { readingsSingleUpdate($hash,"ze_Step","getHvacSettings",1); RenaultZE_Main1($hash, @param); } elsif ($opt eq "charging-settings") { readingsSingleUpdate($hash,"ze_Step","getChargingSettings",1); RenaultZE_Main1($hash, @param); } elsif ($opt eq "notification-settings") { readingsSingleUpdate($hash,"ze_Step","getNotificationSettings",1); RenaultZE_Main1($hash, @param); } elsif ($opt eq "checkAPIkeys") { readingsSingleUpdate($hash,"ze_Step","getcheckAPIkeys",1); RenaultZE_checkAPIkeys1($hash); } elsif ($opt eq "zTest") { readingsSingleUpdate($hash,"ze_Step","getzTest",1); RenaultZE_Main1($hash, @param); } elsif($opt eq "?") { my @cList = keys %RenaultZE_gets; return "Unknown argument $opt, choose one of " . join(" ", @cList); } return undef; } sub RenaultZE_Set($@) { my ($hash, @param) = @_; my $name = shift @param; my $opt = shift @param; my $value = join("", @param); Log3 $name, 5, "RenaultZE_Set - opt = $opt, value = $value"; if ($opt ne "?") { $hash->{FUNCTION} = 'SET'; $hash->{PARMS} = $opt; $hash->{PARMVALUE} = $value; $hash->{curCL} = $hash->{CL}; } if($opt eq "?") { my @cList = keys %RenaultZE_sets; return "Unknown argument $opt, choose one of " . join(" ", @cList); } readingsSingleUpdate($hash,"ze_lastErr","",1) if ($opt ne "?"); if ($opt eq "AC") { readingsSingleUpdate($hash,"ze_Step","setAC",1); RenaultZE_Main1($hash, @param); } elsif ($opt eq "charge") { readingsSingleUpdate($hash,"ze_Step","setCharge",1); RenaultZE_Main1($hash, @param); } elsif ($opt eq "password" && $value ne "") { return RenaultZE_storePassword($name,$value); } elsif ($opt eq "state") { $hash->{STATE} = $value; } return undef; } sub RenaultZE_AsyncOutput ($$) { my ( $client_hash, $text ) = @_; return $text; } sub RenaultZE_UpdateTimer($) { my ( $hash ) = @_; my $name = $hash->{NAME}; if(AttrVal($name, "disabled", 0 ) == 1){ RemoveInternalTimer ($hash); Log3 $hash, 3, "RenaultZE ($name) is disabled"; readingsSingleUpdate($hash,"ze_Step","RenaultZE ($name) is disabled",1); return undef; } if ( $hash->{INTERVAL}) { RemoveInternalTimer ($hash); delete($hash->{UPDATESCHEDULE}); my $nextTrigger = gettimeofday() + $hash->{INTERVAL}; $hash->{TRIGGERTIME} = $nextTrigger; $hash->{TRIGGERTIME_FMT} = FmtDateTime($nextTrigger); InternalTimer($nextTrigger, "RenaultZE_UpdateTimer", $hash, 0); Log3 $hash, 4, "RenaultZE ($name) internal interval timer set to call StartUpdate again at " . $hash->{TRIGGERTIME_FMT}; readingsSingleUpdate($hash,"ze_Step","getStatus",1); $hash->{PARMS} = "update"; $hash->{FUNCTION} = 'GET'; $hash->{PARMS} = 'update'; my @param = ('GET', 'update'); RenaultZE_Main1($hash, @param); } } sub RenaultZE_Main1($@) { my ($hash, @param) = @_; #my $name = shift @param; #my $opt = shift @param; my $function = $hash->{FUNCTION}; my $opt = $hash->{PARMS}; my $value = $hash->{PARMVALUE}; my $key = $function."_".$opt; my $name = $hash->{NAME}; Log3 $name, 5, "RenaultZE_Main1 - In, key=".$key; #if ($key eq "GET_update" || $key eq "GET_vehicles" || $key eq "GET_ac-state" || $key eq "SET_AC" || $key eq "SET_charge") #{ readingsSingleUpdate($hash,"ze_Step","Main1",1); my $lastErr = $hash->{READINGS}{ze_lastErr}{VAL}; readingsSingleUpdate($hash,"ze_Gigya_JWT_Token","",1) if ($lastErr ne ""); my $ze_Gigya_JWT_Token = $hash->{READINGS}{ze_Gigya_JWT_Token}{VAL}; my $ze_Gigya_JWT_lastCall = $hash->{READINGS}{ze_Gigya_JWT_lastCall}{TIME}; my $res = 0; Log3 $name, 5, "RenaultZE_Main1 - ze_Gigya_JWT_lastCall=".$ze_Gigya_JWT_lastCall; my $gigya_time = Time::Piece->strptime( $ze_Gigya_JWT_lastCall, '%Y-%m-%d %H:%M:%S')->epoch; Log3 $name, 5, "RenaultZE_Main1 - ze_Gigya_JWT_lastCall=".$gigya_time; Log3 $name, 5, "RenaultZE_Main1 - gettimeofday=".gettimeofday(); if ( $ze_Gigya_JWT_Token eq "" || $gigya_time < gettimeofday() - 70000 ) { my $res = RenaultZE_getCreds_Step1($hash); Log3 $name, 5, "RenaultZE_Main1 - RC=".$res if defined($res); } else { Log3 $name, 5, "RenaultZE_Main1 - ze_Gigya_JWT_Token=>".$ze_Gigya_JWT_Token."<"; } RenaultZE_Main2($hash); return undef; #} Log3 $name, 5, "RenaultZE_Main1 - Out"; return undef; } sub RenaultZE_Main2($) { my ($hash) = @_; my $function = $hash->{FUNCTION}; my $opt = $hash->{PARMS}; my $value = $hash->{PARMVALUE}; my $key = $function."_".$opt; my $name = $hash->{NAME}; Log3 $name, 5, "RenaultZE_Main2 - In, key=".$key; my $lastErr = $hash->{READINGS}{ze_lastErr}{VAL}; return undef if ($lastErr ne ""); #if ($key eq "GET_update" || $key eq "GET_vehicles" || $key eq "GET_ac-state" || $key eq "SET_AC" || $key eq "SET_charge") #{ readingsSingleUpdate($hash,"ze_Step","Main2",1); my $ze_Renault_AccId = $hash->{READINGS}{ze_Renault_AccId}{VAL}; my $res = 0; Log3 $name, 5, "RenaultZE_Main2 - ze_Renault_AccId: ".$ze_Renault_AccId; if ( $ze_Renault_AccId eq "" || $ze_Renault_AccId eq "0" ){ $res = RenaultZE_getAccId_Step1($hash); Log3 $name, 5, "RenaultZE_getAccId_Step1 - RC=".$res; } RenaultZE_Main3($hash); return undef; #} Log3 $name, 5, "RenaultZE_Main2 - Out"; return undef; } sub RenaultZE_Main3($) { my ($hash) = @_; my $function = $hash->{FUNCTION}; my $opt = $hash->{PARMS}; my $value = $hash->{PARMVALUE}; my $key = $function."_".$opt; my $name = $hash->{NAME}; readingsSingleUpdate($hash,"ze_Step","Main3",1); Log3 $name, 5, "RenaultZE_Main3 - In, key=".$key; my $lastErr = $hash->{READINGS}{ze_lastErr}{VAL}; return undef if ($lastErr ne ""); my $phase = AttrVal($name,"ze_phase",""); if ($key eq "GET_update") { #my $res = RenaultZE_getData_Step1($hash); my $res = RenaultZE_gData_Step1($hash,'battery-status'); my $model = $hash->{READINGS}{vehicleDetails_model_label}{VAL}; Log3 $name, 5, "RenaultZE_gData_Step1 - battery-status - RC=".$res; InternalTimer( gettimeofday() + 1, sub() { my $a = 1; $res = RenaultZE_gData_Step1($hash,'cockpit'); Log3 $name, 5, "RenaultZE_gData_Step1 - cockpit - RC=".$res; }, undef); InternalTimer( gettimeofday() + 2, sub() { my $a = 1; $res = RenaultZE_gData_Step1($hash,'location') if ($phase eq "2"); Log3 $name, 5, "RenaultZE_gData_Step1 - location - RC=".$res if ($phase eq "2"); }, undef); InternalTimer( gettimeofday() + 3, sub() { my $a = 1; $res = RenaultZE_gData_Step1($hash,'hvac-status') if ($phase eq "1"); Log3 $name, 5, "RenaultZE_gData_Step1 - hvac-status - RC=".$res if ($phase eq "1"); }, undef); InternalTimer( gettimeofday() + 4, sub() { my $a = 1; $res = RenaultZE_gData_Step1($hash,'charge-mode') if ($model ne "SPRING"); Log3 $name, 5, "RenaultZE_gData_Step1 - charge-mode - RC=".$res if ($model ne "SPRING"); }, undef); } if ($key eq "GET_vehicles") { my $res = RenaultZE_gData_Step1($hash,'vehicles'); Log3 $name, 5, "RenaultZE_gData_Step1 - vehicles - RC=".$res; } if ($key eq "GET_charge-history") { my $res = RenaultZE_gData_Step1($hash,'charge-history'); Log3 $name, 5, "RenaultZE_gData_Step1 - charge-history - RC=".$res; } if ($key eq "GET_charges") { my $res = RenaultZE_gData_Step1($hash,'charges'); Log3 $name, 5, "RenaultZE_gData_Step1 - charges - RC=".$res; } if ($key eq "GET_charging-settings") { my $res = RenaultZE_gData_Step1($hash,'charging-settings'); Log3 $name, 5, "RenaultZE_gData_Step1 - charging-settings - RC=".$res; } if ($key eq "GET_hvac-history") { my $res = RenaultZE_gData_Step1($hash,'hvac-history'); Log3 $name, 5, "RenaultZE_gData_Step1 - hvac-history - RC=".$res; } if ($key eq "GET_hvac-settings") { my $res = RenaultZE_gData_Step1($hash,'hvac-settings'); Log3 $name, 5, "RenaultZE_gData_Step1 - hvac-settings - RC=".$res; } if ($key eq "GET_notification-settings") { my $res = RenaultZE_gData_Step1($hash,'notification-settings'); Log3 $name, 5, "RenaultZE_gData_Step1 - notification-settings - RC=".$res; } if ($key eq "GET_zTest") { my $res = RenaultZE_gData_Step1($hash,'zTest'); Log3 $name, 5, "RenaultZE_gData_Step1 - zTest - RC=".$res; } if ($key eq "SET_AC") { my $res = RenaultZE_AC_Step1($hash); Log3 $name, 5, "RenaultZE_AC_Step1 - RC=".$res; } if ($key eq "SET_charge") { my $res = RenaultZE_Charge_Step1($hash); Log3 $name, 5, "RenaultZE_Charge_Step1 - RC=".$res; } Log3 $name, 5, "RenaultZE_Main3 - Out"; } sub RenaultZE_Main4($) { my ($hash) = @_; my $name = $hash->{NAME}; my $lastErr = $hash->{READINGS}{ze_lastErr}{VAL}; return undef if ($lastErr ne ""); readingsSingleUpdate($hash,"ze_Step","done",1); $hash->{STATE} = "updated"; return undef; } sub RenaultZE_Attr(@) { my ($cmd,$name,$attrName,$attrVal) = @_; my $hash = $defs{$name}; if($cmd eq "set") { if (substr($attrName ,0,3) eq "ze_") { $_[3] = $attrVal; $hash->{".reset"} = 1 if defined($hash->{LPID}); } if (($attrName eq "disabled") && ($attrVal == 1)) { readingsSingleUpdate($hash,"state","disabled",1); readingsSingleUpdate($hash,"ze_Step","RenaultZE ($name) is disabled",1); $_[3] = $attrVal; $hash->{".reset"} = 1 if defined($hash->{LPID}); RemoveInternalTimer ($hash); } elsif (($attrName eq "disabled") && ($attrVal == 0)) { readingsSingleUpdate($hash,"ze_Step","RenaultZE ($name) is enabled",1); $_[3] = $attrVal; $hash->{".reset"} = 1 if defined($hash->{LPID}); my $firstTrigger = gettimeofday() + 2; $hash->{TRIGGERTIME} = $firstTrigger; $hash->{TRIGGERTIME_FMT} = FmtDateTime($firstTrigger); InternalTimer($firstTrigger, "RenaultZE_UpdateTimer", $hash, 0); } } elsif ($cmd eq "del") { if (substr($attrName,0,3) eq "ze_") { $_[3] = $attrVal; $hash->{".reset"} = 1 if defined($hash->{LPID}); } elsif (($attrName eq "disabled") ) { readingsSingleUpdate($hash,"state","enabled",1); readingsSingleUpdate($hash,"ze_Step","RenaultZE ($name) is enabled",1); $_[3] = $attrVal; $hash->{".reset"} = 1 if defined($hash->{LPID}); my $firstTrigger = gettimeofday() + 2; $hash->{TRIGGERTIME} = $firstTrigger; $hash->{TRIGGERTIME_FMT} = FmtDateTime($firstTrigger); InternalTimer($firstTrigger, "RenaultZE_UpdateTimer", $hash, 0); } } if ($attrName eq "ze_homeRadius") { my $gpsLatitude = ReadingsVal($name,"gpsLatitude","empty"); my $gpsLongitude = ReadingsVal($name,"gpsLongitude","empty"); my $homeRadius = 20; # defule radius $homeRadius = $attrVal if ( $cmd eq "set" ); $homeRadius = 20 if ( $homeRadius eq "" ); # just in case #Log3 $name, 5, "pre RenaultZE_distanceFromHome - In ".$cmd."/".$gpsLatitude." ".$gpsLongitude."/".$homeRadius; RenaultZE_distanceFromHome($hash,$gpsLatitude,$gpsLongitude,$homeRadius); } return undef; } ###################################################### # storePW & readPW Code geklaut aus 72_FRITZBOX.pm :) ###################################################### sub RenaultZE_storePassword($$) { my ($name, $password) = @_; my $index = "ZE_".$name."_passwd"; my $key = getUniqueId().$index; my $e_pwd = ""; if (eval "use Digest::MD5;1") { $key = Digest::MD5::md5_hex(unpack "H*", $key); $key .= Digest::MD5::md5_hex($key); } for my $char (split //, $password) { my $encode=chop($key); $e_pwd.=sprintf("%.2x",ord($char)^ord($encode)); $key=$encode.$key; } my $error = setKeyValue($index, $e_pwd); return "error while saving ZE user password : $error" if(defined($error)); return "ZE user password successfully saved in FhemUtils/uniqueID Key $index"; } sub RenaultZE_readPassword($) { my ($name) = @_; my $index = "ZE_".$name."_passwd"; my $key = getUniqueId().$index; my ($password, $error); #Log3 $name,5,"$name, read ZE user password from FhemUtils/uniqueID Key $key"; ($error, $password) = getKeyValue($index); if ( defined($error) ) { Log3 $name,5, "$name, cant't read ZE user password from FhemUtils/uniqueID: $error"; return undef; } if ( defined($password) ) { if (eval "use Digest::MD5;1") { $key = Digest::MD5::md5_hex(unpack "H*", $key); $key .= Digest::MD5::md5_hex($key); } my $dec_pwd = ''; for my $char (map { pack('C', hex($_)) } ($password =~ /(..)/g)) { my $decode=chop($key); $dec_pwd.=chr(ord($char)^ord($decode)); $key=$decode.$key; } return $dec_pwd; } else { Log3 $name,3,"$name, no ZE user password found in FhemUtils/uniqueID"; return undef; } } ####### getStatus Dialog ##### # sub RenaultZE_getCreds_Step1($) { my ($hash) = @_; my $name = $hash->{NAME}; Log3 $name, 5, "RenaultZE_getCreds_Step1 - In ".$hash."/".$name; readingsSingleUpdate($hash,"ze_Step","RenaultZE_getCreds_Step1",1); my $gigya_api = $hash->{GIGYA_API}; my $username = AttrVal($name,"ze_user",""); my $password = RenaultZE_readPassword($name); Log3 $name, 5, "RenaultZE_getCreds_Step1 - Parms: ".$gigya_api."/".$username."/".$password; my $step1= { ApiKey => $gigya_api, loginId => $username, password => $password, include => 'data', sessionExpiration => 60 }; Log3 $name, 5, "RenaultZE_getCreds_Step1 - Data".$step1; my $param = { url => "https://accounts.eu1.gigya.com/accounts.login", header => "Content-type: application/x-www-form-urlencoded", hash => $hash, timeout => 15, method => "POST", data => $step1, callback => \&RenaultZE_getCreds_Step2 }; HttpUtils_NonblockingGet($param); # Starten der HTTP Abfrage. Es gibt keinen Return-Code. Log3 $name, 5, "RenaultZE_getCreds_Step1 - Out"; return undef; } sub RenaultZE_getCreds_Step2($) { my ($param, $err, $data) = @_; my $hash = $param->{hash}; my $name = $hash->{NAME}; Log3 $name, 5, "RenaultZE_getCreds_Step2 - In ".$hash."/".$name; readingsSingleUpdate($hash,"ze_Step","RenaultZE_getCreds_Step2",1); RenaultZE_Error_err($hash,"RenaultZE_getCreds_Step2",$param->{url},$err,$data) if($err ne ""); RenaultZE_Log_Data($hash,"RenaultZE_getCreds_Step2",$param->{url},$err,$data) if($data ne ""); return undef if (RenaultZE_CheckJson($hash,$data)); my $decode_json = from_json($data); my $errorCode = $decode_json->{errorCode}; RenaultZE_Error_errorCode1($hash,"RenaultZE_getCreds_Step2",$param->{url},$err,$data) if($errorCode ne 0); my $lastErr = $hash->{READINGS}{ze_lastErr}{VAL}; return undef if ($lastErr ne ""); $decode_json = from_json($data); my $ze_personId = $decode_json->{data}->{personId}; my $oauth_token = $decode_json->{sessionInfo}->{cookieValue}; Log3 $name, 5, "RenaultZE_getCreds_Step2 - ze_personId:".$ze_personId.", ze_cookieValue:".$oauth_token; readingsBeginUpdate($hash); readingsBulkUpdate($hash,"ze_personId",$ze_personId); readingsBulkUpdate($hash,"ze_cookieValue",$oauth_token); readingsEndUpdate($hash, 1 ); my $gigya_api = $hash->{GIGYA_API}; my $step2= { login_token => $oauth_token, ApiKey => $gigya_api, fields => 'data.personId,data.gigyaDataCenter', expiration => 87000 }; Log3 $name, 5, "RenaultZE_getCreds_Step2 - Data".$step2; my $param2 = { url => "https://accounts.eu1.gigya.com/accounts.getJWT", header => "Content-type: application/x-www-form-urlencoded", hash => $hash, timeout => 15, method => "POST", data => $step2, callback => \&RenaultZE_getCreds_Step3 }; HttpUtils_NonblockingGet($param2); # Starten der HTTP Abfrage. Es gibt keinen Return-Code. #my $res = RenaultZE_getStatusPerformHttpRequest2($i, $e, $o, $a); Log3 $name, 5, "RenaultZE_getCreds_Step2 - Out"; return undef; } sub RenaultZE_getCreds_Step3($) { my ($param, $err, $data) = @_; my $hash = $param->{hash}; my $name = $hash->{NAME}; Log3 $name, 5, "RenaultZE_getCreds_Step3 - In ".$hash."/".$name; readingsSingleUpdate($hash,"ze_Step","RenaultZE_getCreds_Step3",1); RenaultZE_Error_err($hash,"RenaultZE_getCreds_Step3",$param->{url},$err,$data) if($err ne ""); RenaultZE_Log_Data($hash,"RenaultZE_getCreds_Step3",$param->{url},$err,$data) if($data ne ""); return undef if (RenaultZE_CheckJson($hash,$data)); my $decode_json = from_json($data); my $errorCode = $decode_json->{errorCode}; RenaultZE_Error_errorCode1($hash,"RenaultZE_getCreds_Step3",$param->{url},$err,$data) if($errorCode ne 0); my $lastErr = $hash->{READINGS}{ze_lastErr}{VAL}; return undef if ($lastErr ne ""); $decode_json = from_json($data); my $id_token = $decode_json->{id_token}; readingsBeginUpdate($hash); readingsBulkUpdate($hash,"ze_Gigya_JWT_Token",$id_token); readingsBulkUpdate($hash,"ze_Gigya_JWT_lastCall",localtime(time)); readingsEndUpdate($hash, 1 ); RenaultZE_Main2($hash); #my $res = RenaultZE_getStatusPerformHttpRequest2($i, $e, $o, $a); Log3 $name, 5, "RenaultZE_getCreds_Step3 - Out"; } sub RenaultZE_getAccId_Step1($) { my ($hash) = @_; my $name = $hash->{NAME}; Log3 $name, 5, "RenaultZE_getAccId_Step1 - In ".$hash."/".$name; readingsSingleUpdate($hash,"ze_Step","RenaultZE_getAccId_Step1",1); my $kamereon_api = $hash->{KAMEREON_API}; my $id_token = $hash->{READINGS}{ze_Gigya_JWT_Token}{VAL}; my $ze_personId = $hash->{READINGS}{ze_personId}{VAL}; my $country = AttrVal($name,"ze_country","DE"); Log3 $name, 5, "RenaultZE_getCreds_Step1 - Parms: ".$kamereon_api."/".$id_token; return undef if ( $id_token eq "" || $ze_personId eq "" ); my $step1= { 'ApiKey' => $kamereon_api, 'x-gigya-id_token' => $id_token }; Log3 $name, 5, "RenaultZE_getCreds_Step1 - Data".$step1; my $url = "https://api-wired-prod-1-euw1.wrd-aws.com/commerce/v1/persons/".$ze_personId."?country=".$country; Log3 $name, 5, "RenaultZE_getCreds_Step1 - URL ".$url; my $param = { url => $url, header => $step1, hash => $hash, timeout => 15, method => "GET", callback => \&RenaultZE_getAccId_Step2 }; HttpUtils_NonblockingGet($param); Log3 $name, 5, "RenaultZE_getAccId_Step1 - Out"; return undef; } sub RenaultZE_getAccId_Step2($) { my ($param, $err, $data) = @_; my $hash = $param->{hash}; my $name = $hash->{NAME}; Log3 $name, 5, "RenaultZE_getAccId_Step2 - In ".$hash."/".$name; readingsSingleUpdate($hash,"ze_Step","RenaultZE_getAccId_Step2",1); RenaultZE_Error_err($hash,"RenaultZE_getAccId_Step2",$param->{url},$err,$data) if($err ne ""); RenaultZE_Log_Data($hash,"RenaultZE_getAccId_Step2",$param->{url},$err,$data) if($data ne ""); RenaultZE_Error_errorCode2($hash,"RenaultZE_getAccId_Step2",$param->{url},$err,$data) if($data =~ /error/); my $lastErr = $hash->{READINGS}{ze_lastErr}{VAL}; return undef if ($lastErr ne ""); return undef if (RenaultZE_CheckJson($hash,$data)); my $decode_json = from_json($data); my $accountId = $decode_json->{accounts}[0]->{accountId}; Log3 $name, 5, "RenaultZE_getCreds_Step2 - accountId:".$accountId; readingsSingleUpdate($hash,"ze_Renault_AccId",$accountId,1); RenaultZE_Main3($hash); Log3 $name, 5, "RenaultZE_getCAccId_Step2 - Out"; } sub RenaultZE_gData_Step1($$) { my ($hash,$tree) = @_; my $name = $hash->{NAME}; my $v1v2 = "v1"; $v1v2 = "v2" if ( $tree eq "battery-status"); my $shortlong = "long"; $shortlong = "short" if ( $tree eq "vehicles"); my $popup = "no"; $popup = "yes" if ( $tree eq "vehicles"); $popup = "yes" if ( $tree eq "charge-mode"); my $testparms = $hash->{PARMVALUE}; my $timespecs = ""; if ( $tree eq "charge-history" or $tree eq "hvac-history" or $tree eq "charges") { $timespecs = "&".$testparms; } Log3 $name, 5, "RenaultZE_gData_Step1 - In ".$hash."/".$tree."/".$name; readingsSingleUpdate($hash,"ze_Step","RenaultZE_gData_Step1",1); my $kamereon_api = $hash->{KAMEREON_API}; my $id_token = $hash->{READINGS}{ze_Gigya_JWT_Token}{VAL}; my $accId = $hash->{READINGS}{ze_Renault_AccId}{VAL}; my $vin = $hash->{VIN}; my $country = AttrVal($name,"ze_country","DE"); Log3 $name, 5, "RenaultZE_gData_Step1 - Parms: ".$kamereon_api."/".$id_token; return 4 if ( $id_token eq "" || $accId eq "" ); my $header= { 'apikey' => $kamereon_api, 'x-gigya-id_token' => $id_token }; Log3 $name, 5, "RenaultZE_getData_Step1 - Data".$header; my $url = ""; if ( $shortlong eq "long") { $url = "https://api-wired-prod-1-euw1.wrd-aws.com/commerce/v1/accounts/".$accId."/kamereon/kca/car-adapter/".$v1v2."/cars/".$vin."/".$tree."?country=".$country.$timespecs; }else { $url = "https://api-wired-prod-1-euw1.wrd-aws.com/commerce/v1/accounts/".$accId."/".$tree."?country=".$country; } # for development of new options and users of a phase 1 Zoe if ( $tree eq "zTest") { my $testparms = $hash->{PARMVALUE}; $url = "https://api-wired-prod-1-euw1.wrd-aws.com/commerce/v1/accounts/".$accId."/kamereon/kca/car-adapter/".$v1v2."/cars/".$vin."/".$testparms; } # In Vorbereit8ung, aber derzeit noch nicht seitens Renault unterstützt: #$url = "https://api-wired-prod-1-euw1.wrd-aws.com/commerce/v1/accounts/".$accId."/kamereon/kca/car-adapter/".$v1v2."/cars/".$vin."/hvac-history?type=day&start=20201101&end=20210108&country=".$country; #$url = "https://api-wired-prod-1-euw1.wrd-aws.com/commerce/v1/accounts/".$accId."/kamereon/kca/car-adapter/".$v1v2."/cars/".$vin."/hvac-sessions?start=20201101&end=20210108&country=".$country; #$url = "https://api-wired-prod-1-euw1.wrd-aws.com/commerce/v1/accounts/".$accId."/kamereon/kca/car-adapter/".$v1v2."/cars/".$vin."/charges?start=20201101&end=20210108&country=".$country; #$url = "https://api-wired-prod-1-euw1.wrd-aws.com/commerce/v1/accounts/".$accId."/kamereon/kca/car-adapter/".$v1v2."/cars/".$vin."/charge-history?type=day&start=20201101&end=20210108&country=".$country; #$url = "https://api-wired-prod-1-euw1.wrd-aws.com/commerce/v1/accounts/".$accId."/kamereon/kca/car-adapter/".$v1v2."/cars/".$vin."/charge-history?type=month&start=202011&end=202101&country=".$country; #$url = "https://api-wired-prod-1-euw1.wrd-aws.com/commerce/v1/accounts/".$accId."/kamereon/kca/car-adapter/".$v1v2."/cars/".$vin."/lock-status?country=".$country; Log3 $name, 5, "RenaultZE_gData_Step1 - URL ".$url; my $param = { url => $url, header => $header, hash => $hash, timeout => 15, method => "GET", callback => \&RenaultZE_gData_Step2 }; HttpUtils_NonblockingGet($param); # Starten der HTTP Abfrage. Es gibt keinen Return-Code. Log3 $name, 5, "RenaultZE_gData_Step1 - Out"; return 0; } sub RenaultZE_gData_Step2($) { my ($param, $err, $data) = @_; my $hash = $param->{hash}; my $name = $hash->{NAME}; Log3 $name, 5, "RenaultZE_gData_Step2 - In ".$hash."/".$name; readingsSingleUpdate($hash,"ze_Step","RenaultZE_gData_Step2",1); RenaultZE_Error_err($hash,"RenaultZE_gData_Step2",$param->{url},$err,$data) if($err ne ""); RenaultZE_Log_Data($hash,"RenaultZE_gData_Step2",$param->{url},$err,$data) if($data ne ""); RenaultZE_Error_errorCode2($hash,"RenaultZE_gData_Step2",$param->{url},$err,$data) if($data =~ /errorMessage/); my $lastErr = $hash->{READINGS}{ze_lastErr}{VAL}; return undef if ($lastErr ne ""); my $lastUrl = $hash->{READINGS}{ze_lastUrl}{VAL}; Log3 $name, 3, "RenaultZE_gData_Step2 - DataError ".$data if ($data =~ /\/); return undef if ($data =~ /\/); my $phase = AttrVal($name,"ze_phase",""); return undef if (RenaultZE_CheckJson($hash,$data)); my $decode_json = from_json($data); if($data =~ /batteryLevel/) { my $timestamp = $decode_json->{data}->{attributes}->{timestamp}; #$timestamp =~ s/\+01:00/Z/sg; # fix for time format "2021-01-27T16:41:42+01:00" #my $t = Time::Piece->strptime($timestamp, "%Y-%m-%dT%H:%M:%SZ")->epoch; my $t = RenaultZE_EpochFromDateTime($timestamp); my $tt = localtime($t)->strftime('%Y-%m-%d %H:%M:%S'); readingsBeginUpdate($hash); readingsBulkUpdate($hash,"timestamp",$tt); readingsBulkUpdate($hash,"batteryLevel",$decode_json->{data}->{attributes}->{batteryLevel}); readingsBulkUpdate($hash,"batteryTemperature",$decode_json->{data}->{attributes}->{batteryTemperature}) if ($phase eq "1"); readingsBulkUpdate($hash,"batteryAutonomy",$decode_json->{data}->{attributes}->{batteryAutonomy}); readingsBulkUpdate($hash,"batteryCapacity",$decode_json->{data}->{attributes}->{batteryCapacity}) if ($decode_json->{data}->{attributes}->{batteryCapacity} gt 0); readingsBulkUpdate($hash,"batteryAvailableEnergy",$decode_json->{data}->{attributes}->{batteryAvailableEnergy}) if ($decode_json->{data}->{attributes}->{batteryAvailableEnergy} gt 0); readingsBulkUpdate($hash,"plugStatus",$decode_json->{data}->{attributes}->{plugStatus}); readingsBulkUpdate($hash,"chargingStatus",$decode_json->{data}->{attributes}->{chargingStatus}); readingsBulkUpdate($hash,"chargingRemainingTime",$decode_json->{data}->{attributes}->{chargingRemainingTime}); readingsBulkUpdate($hash,"chargingInstantaneousPower",$decode_json->{data}->{attributes}->{chargingInstantaneousPower}); readingsEndUpdate($hash, 1 ); return 0; } ### cockpit ### if($data =~ /totalMileage/) { readingsBeginUpdate($hash); readingsBulkUpdate($hash,"totalMileageKm",$decode_json->{data}->{attributes}->{totalMileage}); readingsBulkUpdate($hash,"fuelAutonomy",$decode_json->{data}->{attributes}->{fuelAutonomy}) if ($decode_json->{data}->{attributes}->{fuelAutonomy} gt 0); readingsBulkUpdate($hash,"fuelQuantity",$decode_json->{data}->{attributes}->{fuelQuantity}) if ($decode_json->{data}->{attributes}->{fuelQuantity} gt 0); readingsEndUpdate($hash, 1 ); return 0; } ### hvac-status ### if($data =~ /hvacStatus/) { readingsBeginUpdate($hash); readingsBulkUpdate($hash,"hvacStatus",$decode_json->{data}->{attributes}->{hvacStatus}); readingsBulkUpdate($hash,"socThreshold",$decode_json->{data}->{attributes}->{socThreshold}) if ($decode_json->{data}->{attributes}->{socThreshold} gt 0); readingsBulkUpdate($hash,"xternalTemperature",$decode_json->{data}->{attributes}->{xternalTemperature}) if ($decode_json->{data}->{attributes}->{xternalTemperature} gt 0); readingsEndUpdate($hash, 1 ); return 0; } my $gpsLatitude = ""; my $gpsLongitude = ""; my $lastUpdateTime = ""; if($data =~ /gpsLatitude/) { if ($data =~ /locationStatus/) { $gpsLatitude = $decode_json->{locationStatus}->{attributes}->{gpsLatitude}; $gpsLongitude = $decode_json->{locationStatus}->{attributes}->{gpsLongitude}; $lastUpdateTime = $decode_json->{locationStatus}->{attributes}->{lastUpdateTime}; } else { $gpsLatitude = $decode_json->{data}->{attributes}->{gpsLatitude}; $gpsLongitude = $decode_json->{data}->{attributes}->{gpsLongitude}; $lastUpdateTime = $decode_json->{data}->{attributes}->{lastUpdateTime}; } my $gpsLatitude = $decode_json->{data}->{attributes}->{gpsLatitude}; my $gpsLongitude = $decode_json->{data}->{attributes}->{gpsLongitude}; my $lastUpdateTime = $decode_json->{data}->{attributes}->{lastUpdateTime}; #$lastUpdateTime =~ s/\+01:00/Z/sg; # fix for time format "2021-01-27T16:41:42+01:00" #my $t = Time::Piece->strptime($lastUpdateTime, "%Y-%m-%dT%H:%M:%SZ")->epoch; my $t = RenaultZE_EpochFromDateTime($lastUpdateTime); my $tt = localtime($t)->strftime('%Y-%m-%d %H:%M:%S'); my $oldlat = ReadingsVal($name,"gpsLatitude","empty"); my $oldlong = ReadingsVal($name,"gpsLongitude","empty"); my $link = "Google Maps"; if ( $oldlat != $gpsLatitude or $oldlong != $gpsLongitude ) { Log3 $name, 5, "RenaultZE_gData_Step2 - GPS ".$oldlat."/".$gpsLatitude." ".$oldlong."/".$gpsLongitude; RenaultZE_distanceFromHome($hash,$gpsLatitude,$gpsLongitude,"auto"); } readingsBeginUpdate($hash); readingsBulkUpdate($hash,"gpsLatitude",$gpsLatitude); readingsBulkUpdate($hash,"gpsLongitude",$gpsLongitude); readingsBulkUpdate($hash,"gpsLastUpdateTime",$tt); readingsBulkUpdate($hash,"gpsGoogleMaps",$link); readingsEndUpdate($hash, 1 ); return 0; } if($data =~ /vehicleLink/) { my $decode_json = from_json($data); my $output = JSON->new->ascii->pretty->encode(decode_json join '', $data); # extract image urls my $mtab = $decode_json->{vehicleLinks}; foreach my $item( @$mtab ) { next if ($item->{vin} ne $hash->{VIN}); my $assets = $item->{vehicleDetails}->{assets}; foreach my $ass( @$assets ) { my $cars = $ass->{renditions}; foreach my $car( @$cars ) { my $url = $car->{url}; my $size = $car->{resolutionType}; my $link = ""; readingsSingleUpdate($hash,"img_".$size."_url",$url,1); readingsSingleUpdate($hash,"img_".$size."_img",$link,1) if (AttrVal($name,"ze_showimage","1") gt 0 and $size =~ /SMALL/ ); readingsSingleUpdate($hash,"img_".$size."_img",$link,1) if (AttrVal($name,"ze_showimage","1") eq 2 and $size =~ /LARGE/ ); } } my $detail = $item->{vehicleDetails}->{family}->{code}; readingsSingleUpdate($hash,"vehicleDetails_family_code",$detail,1); $detail = $item->{vehicleDetails}->{family}->{label}; readingsSingleUpdate($hash,"vehicleDetails_family_label",$detail,1); $detail = $item->{vehicleDetails}->{family}->{group}; readingsSingleUpdate($hash,"vehicleDetails_family_group",$detail,1); # $detail = $item->{vehicleDetails}->{engineEnergyType}; readingsSingleUpdate($hash,"vehicleDetails_engineEnergyType",$detail,1); # $detail = $item->{vehicleDetails}->{navigationAssistanceLevel}->{code}; readingsSingleUpdate($hash,"vehicleDetails_navigationAssistanceLevel_code",$detail,1); $detail = $item->{vehicleDetails}->{navigationAssistanceLevel}->{label}; readingsSingleUpdate($hash,"vehicleDetails_navigationAssistanceLevel_label",$detail,1); $detail = $item->{vehicleDetails}->{navigationAssistanceLevel}->{group}; readingsSingleUpdate($hash,"vehicleDetails_navigationAssistanceLevel_group",$detail,1); # $detail = $item->{vehicleDetails}->{version}->{code}; readingsSingleUpdate($hash,"vehicleDetails_version_code",$detail,1); # $detail = $item->{vehicleDetails}->{gearbox}->{code}; readingsSingleUpdate($hash,"vehicleDetails_gearbox_code",$detail,1); $detail = $item->{vehicleDetails}->{gearbox}->{label}; readingsSingleUpdate($hash,"vehicleDetails_gearbox_label",$detail,1); $detail = $item->{vehicleDetails}->{gearbox}->{group}; readingsSingleUpdate($hash,"vehicleDetails_gearbox_group",$detail,1); # $detail = $item->{vehicleDetails}->{radioType}->{code}; readingsSingleUpdate($hash,"vehicleDetails_radioType_code",$detail,1); $detail = $item->{vehicleDetails}->{radioType}->{label}; readingsSingleUpdate($hash,"vehicleDetails_radioType_label",$detail,1); $detail = $item->{vehicleDetails}->{radioType}->{group}; readingsSingleUpdate($hash,"vehicleDetails_radioType_group",$detail,1); # $detail = $item->{vehicleDetails}->{tcu}->{code}; readingsSingleUpdate($hash,"vehicleDetails_tcu_code",$detail,1); $detail = $item->{vehicleDetails}->{tcu}->{label}; readingsSingleUpdate($hash,"vehicleDetails_tcu_label",$detail,1); $detail = $item->{vehicleDetails}->{tcu}->{group}; readingsSingleUpdate($hash,"vehicleDetails_tcu_group",$detail,1); # $detail = $item->{vehicleDetails}->{model}->{code}; readingsSingleUpdate($hash,"vehicleDetails_model_code",$detail,1); $detail = $item->{vehicleDetails}->{model}->{label}; readingsSingleUpdate($hash,"vehicleDetails_model_label",$detail,1); $detail = $item->{vehicleDetails}->{model}->{group}; readingsSingleUpdate($hash,"vehicleDetails_model_group",$detail,1); # $detail = $item->{vehicleDetails}->{engineType}; readingsSingleUpdate($hash,"vehicleDetails_engineType",$detail,1); # $detail = $item->{vehicleDetails}->{modelSCR}; readingsSingleUpdate($hash,"vehicleDetails_modelSCR",$detail,1); # $detail = $item->{vehicleDetails}->{battery}->{code}; readingsSingleUpdate($hash,"vehicleDetails_battery_code",$detail,1); $detail = $item->{vehicleDetails}->{battery}->{label}; readingsSingleUpdate($hash,"vehicleDetails_battery_label",$detail,1); $detail = $item->{vehicleDetails}->{battery}->{group}; readingsSingleUpdate($hash,"vehicleDetails_battery_group",$detail,1); # $detail = $item->{vehicleDetails}->{brand}->{label}; readingsSingleUpdate($hash,"vehicleDetails_brand_label",$detail,1); } asyncOutput( $hash->{curCL}, $output ); return 0; } if($data =~ /externalTemperature/) { my $decode_json = from_json($data); readingsSingleUpdate($hash,"externalTemperature",$decode_json->{data}->{attributes}->{externalTemperature},1); return 0; } if($data =~ /chargeMode/) { my $decode_json = from_json($data); readingsSingleUpdate($hash,"chargeMode",$decode_json->{data}->{attributes}->{chargeMode},1); return 0; } ### charge-history?country=DE&type=day&start=20201212&end=20210120 ### charge-history?country=DE&type=month&start=202012&end=202101 if($data =~ /chargeSummaries.*totalChargesNumber/) { my $mtab = $decode_json->{data}->{attributes}->{chargeSummaries}; my $tperiod = "day"; $tperiod = "month" if ($data =~ /month/); print ">>>".$tperiod."\n"; my $output = "Charge Summaries (".$tperiod.")"; $output = $output.""; $output = $output.""; $output = $output.""; $output = $output.""; $output = $output.""; $output = $output.""; foreach my $item( @$mtab ) { $output = $output.""; $output = $output.""; $output = $output.""; $output = $output.""; $output = $output.""; $output = $output.""; } $output = $output."
".$tperiod."totalChargesNumbertotalChargesDurationtotalChargesErrors
".$item->{$tperiod}."".$item->{totalChargesNumber}."".$item->{totalChargesDuration}."".$item->{totalChargesErrors}."
"; readingsSingleUpdate($hash,"chargeHistory",$output,1); return 0; } ### hvac-history?country=DE&type=month&start=202012&end=202101 ### hvac-history?country=DE&type=day&start=20201212&end=20210120 if($data =~ /hvacSessionsSummaries.*totalHvacSessionsNumber/) { my $mtab = $decode_json->{data}->{attributes}->{hvacSessionsSummaries}; my $tperiod = "day"; $tperiod = "month" if ($data =~ /month/); print ">>>".$tperiod."\n"; my $output = "HVAC Session Summaries (".$tperiod.")"; $output = $output.""; $output = $output.""; $output = $output.""; $output = $output.""; $output = $output.""; foreach my $item( @$mtab ) { $output = $output.""; $output = $output.""; $output = $output.""; $output = $output.""; $output = $output.""; } $output = $output."
".$tperiod."totalHvacSessionsNumbertotalHvacSessionsErrors
".$item->{$tperiod}."".$item->{totalHvacSessionsNumber}."".$item->{totalHvacSessionsErrors}."
"; readingsSingleUpdate($hash,"hvacHistory",$output,1); return 0; } ### hvac-sessions?country=DE&start=20201210&end=20210110 if($data =~ /hvacSessions.*hvacSessionRequestDate/) { my $mtab = $decode_json->{data}->{attributes}->{hvacSessions}; #print scalar @$mtab."\n"; my $output = "HVAC Sessions"; $output = $output.""; $output = $output.""; $output = $output.""; $output = $output.""; $output = $output.""; foreach my $item( @$mtab ) { $output = $output.""; $output = $output.""; $output = $output.""; $output = $output.""; $output = $output.""; } $output = $output."
hvacSessionRequestDatehvacSessionStartDatehvacSessionEndStatus
".$item->{hvacSessionRequestDate}."".$item->{hvacSessionStartDate}."".$item->{hvacSessionEndStatus}."
"; readingsSingleUpdate($hash,"hvacSessions",$output,1); return 0; } ### charging-settings?country=DE if(($data =~ /mode.*schedules/) && ($lastUrl =~ /charg/)){ my $mtab = $decode_json->{data}->{attributes}->{schedules}; my $sss = @$mtab; #print scalar @$mtab."\n"; my $output = "Charging Settings

Mode=".$decode_json->{data}->{attributes}->{mode}."

 

Schedules:

 

"; my @wdays = ("monday", "tuesday", "wednesday", "thursday", "friday", "saturday", "saturday" ); if ( $sss > 0 ) { foreach my $item( @$mtab ) { $output = $output."activated =".$item->{activated}.""; $output = $output.""; $output = $output.""; $output = $output.""; $output = $output.""; $output = $output.""; foreach my $wd( @wdays ) { $output = $output.""; $output = $output.""; $output = $output.""; $output = $output.""; $output = $output.""; } last; } $output = $output."
Day of WeekstartTimeduration
".$wd."".$item->{$wd}->{startTime}."".$item->{$wd}->{duration}."
"; } $output = $output.""; readingsSingleUpdate($hash,"chargingSettings",$output,1); return 0; } ### hvac-settings?country=DE if(($data =~ /mode.*schedules/) && ($lastUrl =~ /hvac/)){ my $mtab = $decode_json->{data}->{attributes}->{schedules}; my $sss = @$mtab; #print scalar @$mtab."\n"; my $output = "HVAC Settings

Mode=".$decode_json->{data}->{attributes}->{mode}."
"; my @wdays = ("monday", "tuesday", "wednesday", "thursday", "friday", "saturday", "saturday" ); if ( $sss > 0 ) { $output = $output."

Schedules"; foreach my $item( @$mtab ) { if ($item->{activated} == 1 ) { $output = $output."

activated =".$item->{activated}.""; $output = $output.", Target Temperature =".$item->{targetTemperature}."
"; $output = $output.""; $output = $output.""; $output = $output.""; $output = $output.""; foreach my $wd( @wdays ) { $output = $output.""; $output = $output.""; $output = $output.""; $output = $output.""; } $output = $output."
Day of WeekreadyAtTime
".$wd."".$item->{$wd}->{readyAtTime}."
"; } } } $output = $output.""; readingsSingleUpdate($hash,"hvacSettings",$output,1); return 0; } ### notification-settings?country=DE if($data =~ /settings.*messageKey/) { my $mtab = $decode_json->{data}->{attributes}->{settings}; my $output = "Notification Settings"; $output = $output.""; $output = $output.""; $output = $output.""; $output = $output.""; $output = $output.""; $output = $output.""; foreach my $item( @$mtab ) { $output = $output.""; $output = $output.""; $output = $output.""; $output = $output.""; $output = $output.""; $output = $output.""; } $output = $output."
messageKeyemailsmspushApp
".$item->{messageKey}."".$item->{email}."".$item->{sms}."".$item->{pushApp}."
"; readingsSingleUpdate($hash,"notificationSettings",$output,1); return 0; } ### charges start=20200202&end=20210202 if($data =~ /charges/) { my $mtab = $decode_json->{data}->{attributes}->{charges}; my $output = "Charges"; $output = $output.""; $output = $output.""; $output = $output.""; $output = $output.""; $output = $output.""; $output = $output.""; $output = $output.""; $output = $output.""; $output = $output.""; foreach my $item( @$mtab ) { $output = $output.""; $output = $output.""; $output = $output.""; $output = $output.""; $output = $output.""; $output = $output.""; $output = $output.""; $output = $output.""; $output = $output.""; } $output = $output."
charge Start Datecharge End Datecharge Durationcharge Start Battery Levelcharge End Battery Levelcharge Energy Recoveredcharge End Status
".$item->{chargeStartDate}."".$item->{chargeEndDate}."".$item->{chargeDuration}."".$item->{chargeStartBatteryLevel}."".$item->{chargeEndBatteryLevel}."".$item->{chargeEnergyRecovered}."".$item->{chargeEndStatus}."
"; $output =~ s/charge//g; readingsSingleUpdate($hash,"chargesDetails",$output,1); return 0; } Log3 $name, 5, "RenaultZE_gData_Step2 - opt=".$hash->{PARMS}; if($hash->{PARMS} eq "zTest") { asyncOutput( $hash->{curCL}, $data ); return 0; } Log3 $name, 5, "RenaultZE_gData_Step2 - Out"; return 0; } sub RenaultZE_AC_Step1($) { my ($hash) = @_; my $name = $hash->{NAME}; my $value = $hash->{PARMVALUE}; Log3 $name, 5, "RenaultZE_AC_Step1 - In ".$hash."/".$name; Log3 $name, 5, "RenaultZE_Set - value = >$value<"; readingsSingleUpdate($hash,"ze_Step","RenaultZE_AC_Step1",1); my $kamereon_api = $hash->{KAMEREON_API}; my $id_token = $hash->{READINGS}{ze_Gigya_JWT_Token}{VAL}; my $accId = $hash->{READINGS}{ze_Renault_AccId}{VAL}; my $vin = $hash->{VIN}; my $country = AttrVal($name,"ze_country","DE"); Log3 $name, 5, "RenaultZE_AC_Step1 - Parms: ".$kamereon_api."/".$id_token; return undef if ( $id_token eq "" || $accId eq "" ); my $step1= { 'Content-type' => 'application/vnd.api+json', 'apikey' => $kamereon_api, 'x-gigya-id_token' => $id_token }; Log3 $name, 5, "RenaultZE_AC_Step1 - Data".$step1; my $url= "empty"; my $jsonData = "empty"; if ( $value eq "on" ) { $jsonData = '{"data":{"type":"HvacStart","attributes":{"action":"start","targetTemperature":"21"}}}'; $url = "https://api-wired-prod-1-euw1.wrd-aws.com/commerce/v1/accounts/".$accId."/kamereon/kca/car-adapter/v1/cars/".$vin."/actions/hvac-start?country=".$country; } else { $jsonData = '{"data":{"type":"HvacStart","attributes":{"action":"cancel"}}}'; $url = "https://api-wired-prod-1-euw1.wrd-aws.com/commerce/v1/accounts/".$accId."/kamereon/kca/car-adapter/v1/cars/".$vin."/actions/hvac-start?country=".$country; } Log3 $name, 5, "RenaultZE_AC_Step1 - URL ".$url; Log3 $name, 5, "RenaultZE_AC_Step1 - jsonData ".$jsonData; my $param = { url => $url, header => $step1, hash => $hash, timeout => 15, method => "POST", data => $jsonData, callback => \&RenaultZE_AC_Step2 }; HttpUtils_NonblockingGet($param); # Starten der HTTP Abfrage. Es gibt keinen Return-Code. Log3 $name, 5, "RenaultZE_AC_Step1 - Out"; return undef; } sub RenaultZE_AC_Step2($) { my ($param, $err, $data) = @_; my $hash = $param->{hash}; my $name = $hash->{NAME}; Log3 $name, 5, "RenaultZE_AC_Step2 - In ".$hash."/".$name; readingsSingleUpdate($hash,"ze_Step","RenaultZE_AC_Step2",1); RenaultZE_Error_err($hash,"RenaultZE_AC_Step2",$param->{url},$err,$data) if($err ne ""); RenaultZE_Log_Data($hash,"RenaultZE_AC_Step2",$param->{url},$err,$data) if($data ne ""); RenaultZE_Error_errorCode2($hash,"RenaultZE_AC_Step2",$param->{url},$err,$data) if($data =~ /error/); my $lastErr = $hash->{READINGS}{ze_lastErr}{VAL}; return undef if ($lastErr ne ""); return undef if (RenaultZE_CheckJson($hash,$data)); my $decode_json = from_json($data); Log3 $name, 5, "RenaultZE_AC_Step2 - returned".$decode_json; my $acstatus = $decode_json->{data}->{attributes}->{action}; my $msg = "AC:".$acstatus; Log3 $name, 5, "RenaultZE_AC_Step2 - acstatus=".$msg; $hash->{STATE} = $msg; #asyncOutput( $hash->{curCL}, $output ); return undef; } sub RenaultZE_Charge_Step1($) { my ($hash) = @_; my $name = $hash->{NAME}; my $value = $hash->{PARMVALUE}; Log3 $name, 5, "RenaultZE_Charge_Step1 - In ".$hash."/".$name; Log3 $name, 5, "RenaultZE_Charge_Step1 - value = >$value<"; readingsSingleUpdate($hash,"ze_Step","RenaultZE_Charge_Step1",1); my $kamereon_api = $hash->{KAMEREON_API}; my $id_token = $hash->{READINGS}{ze_Gigya_JWT_Token}{VAL}; my $accId = $hash->{READINGS}{ze_Renault_AccId}{VAL}; my $vin = $hash->{VIN}; my $country = AttrVal($name,"ze_country","DE"); Log3 $name, 5, "RenaultZE_Charge_Step1 - Parms: ".$kamereon_api."/".$id_token; return undef if ( $id_token eq "" || $accId eq "" ); my $step1= { 'Content-type' => 'application/vnd.api+json', 'apikey' => $kamereon_api, 'x-gigya-id_token' => $id_token }; Log3 $name, 5, "RenaultZE_Charge_Step1 - Data".$step1; my $jsonData = "empty"; my $url= "empty"; my $brand = AttrVal($name,"ze_brand","Renault"); if ( $value eq "start" ) { if ( $brand eq "Renault" ) { $jsonData = '{"data":{"type":"ChargingStart","attributes":{"action":"start"}}}'; $url = "https://api-wired-prod-1-euw1.wrd-aws.com/commerce/v1/accounts/".$accId."/kamereon/kca/car-adapter/v1/cars/".$vin."/actions/charging-start?country=".$country; } if ( $brand eq "Dacia" ) { $jsonData = '{"data":{"type":"ChargePauseResume","attributes":{"action":"resume"}}}'; $url = "https://api-wired-prod-1-euw1.wrd-aws.com/commerce/v1/accounts/".$accId."/kamereon/kcm/v1/vehicles/".$vin."/charge/pause-resume?country=".$country; } } else { if ( $brand eq "Renault" ) { $jsonData = '{"data":{"type":"ChargingStart","attributes":{"action":"stop"}}}'; $url = "https://api-wired-prod-1-euw1.wrd-aws.com/commerce/v1/accounts/".$accId."/kamereon/kca/car-adapter/v1/cars/".$vin."/actions/charging-start?country=".$country; } if ( $brand eq "Dacia" ) { $jsonData = '{"data":{"type":"ChargePauseResume","attributes":{"action":"pause"}}}'; $url = "https://api-wired-prod-1-euw1.wrd-aws.com/commerce/v1/accounts/".$accId."/kamereon/kcm/v1/vehicles/".$vin."/charge/pause-resume?country=".$country; } } Log3 $name, 5, "RenaultZE_Charge_Step1 - URL ".$url; Log3 $name, 5, "RenaultZE_Charge_Step1 - jsonData ".$jsonData; my $param = { url => $url, header => $step1, hash => $hash, timeout => 15, method => "POST", data => $jsonData, callback => \&RenaultZE_Charge_Step2 }; HttpUtils_NonblockingGet($param); # Starten der HTTP Abfrage. Es gibt keinen Return-Code. Log3 $name, 5, "RenaultZE_Charge_Step1 - Out"; return undef; } sub RenaultZE_Charge_Step2($) { my ($param, $err, $data) = @_; my $hash = $param->{hash}; my $name = $hash->{NAME}; Log3 $name, 5, "RenaultZE_Charge_Step2 - In ".$hash."/".$name; readingsSingleUpdate($hash,"ze_Step","RenaultZE_Charge_Step2",1); RenaultZE_Error_err($hash,"RenaultZE_Charge_Step2",$param->{url},$err,$data) if($err ne ""); RenaultZE_Log_Data($hash,"RenaultZE_Charge_Step2",$param->{url},$err,$data) if($data ne ""); RenaultZE_Error_errorCode2($hash,"RenaultZE_Charge_Step2",$param->{url},$err,$data) if($data =~ /error/); my $lastErr = $hash->{READINGS}{ze_lastErr}{VAL}; return undef if ($lastErr ne ""); return undef if (RenaultZE_CheckJson($hash,$data)); my $decode_json = from_json($data); Log3 $name, 5, "RenaultZE_Charge_Step2 - returned".$decode_json; my $chargestatus = $decode_json->{data}->{attributes}->{action}; my $msg = "Charge:".$chargestatus; Log3 $name, 5, "RenaultZE_Charge_Step2 - chargestatus=".$msg; $hash->{STATE} = $msg; #asyncOutput( $hash->{curCL}, $output ); return undef; } sub RenaultZE_Log_Data($$$$$) { my ($hash, $step, $url, $err, $data) = @_; my $name = $hash->{NAME}; Log3 $name, 5, "INFO: ".$step.", url: ".$url.", data: ".$data.", error: ".$err; $err = RenaultZE_pp_err($hash,$err) if($err ne "");; readingsSingleUpdate($hash,"ze_lastUrl",$url,1); readingsSingleUpdate($hash,"ze_lastErr",$err,1); readingsSingleUpdate($hash,"ze_lastData",$data,1); return undef; } sub RenaultZE_Error_err($$$$$) { my ($hash, $step, $url, $err, $data) = @_; my $name = $hash->{NAME}; Log3 $name, 3, "ERROR: ".$step.", error while calling ".$url." - $err"; $err = RenaultZE_pp_err($hash,$err) if($err ne "");; readingsSingleUpdate($hash,"ze_lastUrl",$url,1); readingsSingleUpdate($hash,"ze_lastErr",$err,1); readingsSingleUpdate($hash,"ze_lastData",$data,1); } sub RenaultZE_Error_errorCode1($$$$$) { my ($hash, $step, $url, $err, $data) = @_; my $name = $hash->{NAME}; return undef if (RenaultZE_CheckJson($hash,$data)); my $decode_json = from_json($data); my $errorCode = $decode_json->{errorCode}; my $errorDetails = $decode_json->{errorDetails}; my $errorMessage = $decode_json->{errorMessage}; my $statusReason = $decode_json->{statusReason}; my $msg = "errorCode=".$errorCode.", errorDetails=".$errorDetails.", errorMessage=".$errorMessage.", statusReason=".$statusReason; Log3 $name, 3, "ERROR: (1) ".$step.", errorCode while calling ".$url." - $msg"; $msg = RenaultZE_pp_err($hash,$errorMessage) if($errorMessage ne "");; readingsSingleUpdate($hash,"ze_lastUrl",$url,1); readingsSingleUpdate($hash,"ze_lastErr",$msg,1); readingsSingleUpdate($hash,"ze_lastData",$data,1); } sub RenaultZE_Error_errorCode2($$$$$) { my ($hash, $step, $url, $err, $data) = @_; my $name = $hash->{NAME}; return undef if (RenaultZE_CheckJson($hash,$data)); my $decode_json = from_json($data); my $errorCode = $decode_json->{errors}[0]->{errorCode}; my $errorMessage = $decode_json->{errors}[0]->{errorMessage}; my $msg = "errorCode=".$errorCode.", errorMessage=".$errorMessage; Log3 $name, 3, "ERROR: (2) ".$step.", error (data) while calling ".$url." - $msg" if($errorMessage !~ /Failed to forward request to remote service/);; Log3 $name, 5, "ERROR: (2) ".$step.", error (data) while calling ".$url." - $msg" if($errorMessage =~ /Failed to forward request to remote service/);; $msg = RenaultZE_pp_err($hash,$msg) if($msg ne "");; readingsSingleUpdate($hash,"ze_lastUrl",$url,1); readingsSingleUpdate($hash,"ze_lastErr",$msg,1); readingsSingleUpdate($hash,"ze_lastData",$data,1); } sub RenaultZE_pp_err($$) { my ($hash,$err) = @_; my $name = $hash->{NAME}; Log3 $name, 3, "INFO: pretty printing error ".$err; my $errj = $err; $errj =~ s/.*errorMessage=//g; my $json_out = eval { decode_json($errj) }; my $output = ""; if ($@) { $output = $err; } else { my $decode_json = from_json($errj); my $mtab = $decode_json->{errors}; $output = "Error"; $output = $output.""; $output = $output.""; foreach my $item( @$mtab ) { $output = $output.""; $output = $output.""; $output = $output.""; $output = $output.""; } $output = $output."
raw".$err."
status".$item->{status}."
code".$item->{code}."
title".$item->{title}."
detail".$item->{detail}."
"; } return $output; } sub RenaultZE_distanceFromHome($$$$) { my ($hash, $lat, $long, $homeRadius) = @_; my $name = $hash->{NAME}; Log3 $name, 5, "RenaultZE_distanceFromHome - In ".$hash."/".$lat." ".$long."/".$homeRadius; my $hlong = AttrVal( $name, "ze_longitude", AttrVal( "global", "longitude", 0.0 ) ); my $hlat = AttrVal( $name, "ze_latitude", AttrVal( "global", "latitude", 0.0 ) ); #Kreiszahl Pi my $pi = 3.14159; #Umrechnung von Grad in Radius my $long1 = $long / 180 * $pi; my $lat1 = $lat / 180 * $pi; my $long2 = $hlong / 180 * $pi; my $lat2 = $hlat / 180 * $pi; #Entfernungsberechnung my $distance = acos(sin($long1)*sin($long2) + cos($long1)*cos($long2)*cos($lat2-$lat1)); #Erdrundung einbeziehen $distance = $distance * 6378.137; my $dim = "km"; my $homeinfo = ""; my $homestate = "away"; $homeRadius = AttrVal( $name, "ze_homeRadius", 20 ) if ( $homeRadius eq "auto"); Log3 $name, 5, "RenaultZE_distanceFromHome - Check ".$hash."/".$lat." ".$long."/".$homeRadius; if ($distance < $homeRadius) { $distance = $distance * 1000; $dim = "m"; if ($distance < $homeRadius) { $homeinfo = "home"; $homestate = "home"; } } $distance = sprintf("%.3f", $distance); $homeinfo = $distance." ".$dim." away" if ( $homeinfo eq ""); if ( $hlong != 0.0 and $hlat != 0.0) { readingsSingleUpdate($hash,"distanceFromHome",$distance,1); readingsSingleUpdate($hash,"distanceUnit",$dim,1); readingsSingleUpdate($hash,"homeInfo",$homeinfo,1); readingsSingleUpdate($hash,"homeState",$homestate,1); RenaultZE_gAddress1($hash,$lat,$long) if (AttrVal($name,"ze_showaddress","1") eq 1); } return undef; } sub RenaultZE_gAddress1($$$) { my ($hash,$lat,$long) = @_; my $name = $hash->{NAME}; Log3 $name, 5, "RenaultZE_gAddress1 - In ".$hash."/".$name." ".$lat."/".$long; readingsSingleUpdate($hash,"ze_Step","RenaultZE_gAddress1",1); my $url = "https://www.google.com/maps/place/$lat+$long"; Log3 $name, 5, "RenaultZE_gData_Step1 - URL ".$url; my $param = { url => $url, hash => $hash, timeout => 15, method => "GET", callback => \&RenaultZE_gAddress2 }; HttpUtils_NonblockingGet($param); # Starten der HTTP Abfrage. Es gibt keinen Return-Code. Log3 $name, 5, "RenaultZE_gAddress1 - Out"; return 0; } sub RenaultZE_gAddress2($) { my ($param, $err, $data) = @_; my $hash = $param->{hash}; my $name = $hash->{NAME}; Log3 $name, 5, "RenaultZE_gAddress2 - In ".$hash."/".$name; Log3 $name, 5, "RenaultZE_gAddress2 - In err".$hash."/".$err; Log3 $name, 5, "RenaultZE_gAddress2 - In data".$hash."/".$data; readingsSingleUpdate($hash,"ze_Step","RenaultZE_gAddress2",1); # RenaultZE_Error_err($hash,"RenaultZE_gAddress2",$param->{url},$err,$data) if($err ne ""); # RenaultZE_Log_Data($hash,"RenaultZE_gAddress2",$param->{url},$err,$data) if($data ne ""); # RenaultZE_Error_errorCode2($hash,"RenaultZE_gAddress2",$param->{url},$err,$data) if($data =~ /error/); # my $lastErr = $hash->{READINGS}{ze_lastErr}{VAL}; # return undef if ($lastErr ne ""); $data =~ s/.*meta content=\"(.*)\" itemprop=\"description\".*/$1/sg; Log3 $name, 5, "RenaultZE_gAddress2 - Address ".$data; my $oldinfo = ReadingsVal($name,"homeInfo",""); my $newinfo = $oldinfo." (".$data.")"; readingsSingleUpdate($hash,"homeInfo",$newinfo,1); Log3 $name, 5, "RenaultZE_gAddress2 - Out"; } sub RenaultZE_checkAPIkeys1($) { my ($hash) = @_; my $name = $hash->{NAME}; my $value = $hash->{PARMVALUE}; Log3 $name, 5, "RenaultZE_checkAPIkeys_Step1 - In ".$hash."/".$name; readingsSingleUpdate($hash,"ze_Step","RenaultZE_checkAPIkeys1",1); my $kamereon_api = $hash->{KAMEREON_API}; my $id_token = $hash->{READINGS}{ze_Gigya_JWT_Token}{VAL}; my $country = AttrVal($name,"ze_country","DE"); my $step1= { 'Content-type' => 'application/vnd.api+json' }; my $url= "https://renault-wrd-prod-1-euw1-myrapp-one.s3-eu-west-1.amazonaws.com/configuration/android/config_de_DE.json"; my $jsonData = "empty"; Log3 $name, 5, "RenaultZE_checkAPIkeys1 ".$url; my $param = { url => $url, header => $step1, hash => $hash, timeout => 15, method => "GET", data => $jsonData, callback => \&RenaultZE_checkAPIkeys2 }; HttpUtils_NonblockingGet($param); # Starten der HTTP Abfrage. Es gibt keinen Return-Code. Log3 $name, 5, "RenaultZE_checkAPIkeys1 - Out"; return undef; } sub RenaultZE_checkAPIkeys2($) { my ($param, $err, $data) = @_; my $hash = $param->{hash}; my $name = $hash->{NAME}; my $kamereon_api = $hash->{KAMEREON_API}; my $gigya_api = $hash->{GIGYA_API}; Log3 $name, 5, "RenaultZE_checkAPIkeys2 - In ".$hash."/".$name; readingsSingleUpdate($hash,"ze_Step","RenaultZE_checkAPIkeys2",1); RenaultZE_Error_err($hash,"RenaultZE_checkAPIkeys2",$param->{url},$err,$data) if($err ne ""); RenaultZE_Log_Data($hash,"RenaultZE_checkAPIkeys2",$param->{url},$err,$data) if($data ne ""); RenaultZE_Error_errorCode2($hash,"RenaultZE_checkAPIkeys2",$param->{url},$err,$data) if($data =~ /error/); my $lastErr = $hash->{READINGS}{ze_lastErr}{VAL}; return undef if ($lastErr ne ""); return undef if (RenaultZE_CheckJson($hash,$data)); my $decode_json = from_json($data); Log3 $name, 5, "RenaultZE_checkAPIkeys2 - returned".$decode_json; #my $kameronkey = $decode_jmson->{data}->{servers}->{wiredProd}->{apikey}; #my $gigyakey = $decode_json->{data}->{servers}->{gigyaProd}->{apikey}; my $kameronkey = $decode_json->{servers}->{wiredProd}->{apikey}; my $gigyakey = $decode_json->{servers}->{gigyaProd}->{apikey}; Log3 $name, 5, "RenaultZE_checkAPIkeys2 KAMERON: ".$kameronkey; Log3 $name, 5, "RenaultZE_checkAPIkeys2 GIGYA".$gigyakey; Log3 $name, 5, "RenaultZE_checkAPIkeys2 Out"; my $message = ""; if ($kameronkey ne $kamereon_api) { $message = "Neuer KAMERON API Key: \nIst: ".$kamereon_api."\nNeu: ".$kameronkey."\n\n"; } else { $message = "KAMERON API Key ist OK: \nIst: ".$kamereon_api."\nNeu: ".$kameronkey."\n\n"; } if ($gigyakey ne $gigya_api) { $message = $message."Neuer GIGYA API Key: \nIst: ".$gigya_api."\nNeu: ".$gigyakey."\n"; } else { $message = $message."GIGYA API Key ist OK: \nIst: ".$gigya_api."\nNeu: ".$gigyakey."\n"; } asyncOutput( $hash->{curCL}, $message ); return undef; } sub RenaultZE_CheckJson($$) { my ($hash,$json) = @_; my $name = $hash->{NAME}; my $json_out = eval { decode_json($json) }; if ($@) { readingsSingleUpdate($hash,"ze_lastErr","unexpected json error",1); readingsSingleUpdate($hash,"ze_lastData",$json,1); return 1; } return 0; } sub RenaultZE_EpochFromDateTime($) { my ($timestamp) = @_; my $t; if ( substr($timestamp,-1,1) eq "Z" ) { $t = eval { Time::Piece->strptime($timestamp, "%Y-%m-%dT%H:%M:%SZ")->epoch }; } elsif ( substr($timestamp,-3,1) eq ":" ) { $timestamp =~ s/\+(\d{2}):(\d{2})$/+$1$2/; $timestamp =~ s/\.\d{3}+/+/; $t = eval { Time::Piece->strptime($timestamp, "%Y-%m-%dT%H:%M:%S%z")->epoch }; } elsif ( substr($timestamp,-5,5) =~ /\+(\d{4})/ ) { $t = eval { Time::Piece->strptime($timestamp, "%Y-%m-%dT%H:%M:%S%z")->epoch }; } $t = 0 if ($t eq ""); return $t; } ############################## 1; =pod =begin html

RenaultZE

    RenaultZE implements an interface to the Renualt ZE API


    Define
      define <name> RenaultZE <VIN> <Interval>

      Example: define myZoe RenaultZE VF1....... 300

      VIN is the Vehicle Identification Number (you'll find it in your registration)
      Interval is the interval in seconds between status updates

    Set
      set <name> <option> <value>

      You can set
      • password
        enter your Renault ZE accounts password
      • AC
        either on or cancel (which probably only cancles a timer)
      • Charge
        either on or off (off doesn't work if you have set 'charge mode walways')
      • state

    Get
      get <name> <option>

      You can get
      • charge-history
        summary of charges, parameter options:
        type=day&start=YYYYMMDD&end=YYYYMMDD (default: 1.1.2000 till today)
        type=month&start=YYYYMM&end=YYYYMM
      • charges
        only for Phase1 models
        list of charges, parameter options:
        start=YYYYMMDD&end=YYYYMMD (default: 1.1.2000 till today)
      • charging-settings
        lists the settings for charging
      • hvac-history
        only for Phase1 models
        shows the air condition history, parameter options:
        type=day&start=YYYYMMDD&end=YYYYMMDD (default: 1.1.2000 till today)
        type=month&start=YYYYMM&end=YYYYMM
      • hvac-settings
        shows the ac settingS
      • notification-settings
        only for Phase1 models
        lists the settings for cnotifications
      • update
        force update of the current readings (battery-status, cockpit, location, hvac-status, charge-mode)
      • checkAPIkeys
        check if the API are uptodate
      • vehicles
        get a list of your vehicles with details, set the readings for the images and some technical details
        if the attribute ze_showimage is set you get readings with the cars images
      • zTest
        Option to test new API functions which might be implemented one day ...
        sub parameters are
        hvac-sessions?start=20201101&end=20210108&country=DE
        charge-history?type=day&start=20201101&end=20210108&country=DE
        charge-history?type=month&start=202011&end=202101&country=DE
        lock-status?country=DE
        res-state?country=DE
        As result you will either get a msgbox when the function is implemented by Renault, otherwise the ze-Readings will tell you more

    Attributes
      attr <name> <attribute> <value>

      See commandref#attr for more info about the attr command.

      Attributes:
      • ze_brand Renault|Dacia
        Car brand, default is Renault
      • ze_phase 1|2
        The phase of ZE technology, either 1 or 2, right now only phase 2 is supported
      • ze_country
        2 letter country code, e.g. DE ot GB
      • ze_homeRadius
        allowed distance of car from home in m that is still considered 'home'
      • ze_user
        The user-id that you used to register at Renault
      • ze_latitude
        Latitude of your home location. Is being used to calculate homeInfo. Function also checks for global attribte latitude als default.
      • ze_longitude
        Longitude of your home location. Is being used to calculate homeInfo. Function also checks for global attribte longitude als default.
      • ze_showaddress
        Retrieve address via reverse geocoding fromn Google Maps and add it to homeInfo.
      • ze_showimage
        Show the image of the car that you get from vehicles as reading
        0 = off 1 = only the small image (default)
        2 = both, small and large image
=end html =cut