2022-03-18 21:44:22 +00:00
package FHEM::EaseeWallbox ;
2022-03-21 09:13:34 +00:00
# use GPUtils qw(GP_Import GP_Export); hast Du auch weiter unten stehen
2022-03-16 18:57:21 +00:00
use strict ;
use warnings ;
use Data::Dumper ;
use utf8 ;
use Encode qw( encode_utf8 ) ;
use HttpUtils ;
use JSON ;
2022-03-19 13:44:14 +00:00
use DateTime ;
use DateTime::Format::Strptime ;
2022-03-16 18:57:21 +00:00
2022-03-18 21:44:22 +00:00
# try to use JSON::MaybeXS wrapper
# for chance of better performance + open code
2022-03-20 19:58:39 +00:00
eval {
require JSON::MaybeXS ;
import JSON:: MaybeXS qw( decode_json encode_json ) ;
1 ;
} or do {
2022-03-18 21:44:22 +00:00
# try to use JSON wrapper
# for chance of better performance
2022-03-20 19:58:39 +00:00
eval {
2022-03-18 21:44:22 +00:00
# JSON preference order
2022-03-20 19:58:39 +00:00
local $ ENV { PERL_JSON_BACKEND } =
'Cpanel::JSON::XS,JSON::XS,JSON::PP,JSON::backportPP'
unless ( defined ( $ ENV { PERL_JSON_BACKEND } ) ) ;
2022-03-18 21:44:22 +00:00
2022-03-20 19:58:39 +00:00
require JSON ;
import JSON qw( decode_json encode_json ) ;
1 ;
} or do {
2022-03-18 21:44:22 +00:00
# In rare cases, Cpanel::JSON::XS may
# be installed but JSON|JSON::MaybeXS not ...
2022-03-20 19:58:39 +00:00
eval {
require Cpanel::JSON::XS ;
import Cpanel::JSON:: XS qw( decode_json encode_json ) ;
1 ;
} or do {
2022-03-18 21:44:22 +00:00
# In rare cases, JSON::XS may
# be installed but JSON not ...
2022-03-20 19:58:39 +00:00
eval {
require JSON::XS ;
import JSON:: XS qw( decode_json encode_json ) ;
1 ;
} or do {
2022-03-18 21:44:22 +00:00
# Fallback to built-in JSON which SHOULD
# be available since 5.014 ...
2022-03-20 19:58:39 +00:00
eval {
require JSON::PP ;
import JSON:: PP qw( decode_json encode_json ) ;
1 ;
} or do {
2022-03-18 21:44:22 +00:00
# Fallback to JSON::backportPP in really rare cases
2022-03-20 19:58:39 +00:00
require JSON::backportPP ;
import JSON:: backportPP qw( decode_json encode_json ) ;
1 ;
} ;
} ;
} ;
} ;
} ;
2022-03-18 21:44:22 +00:00
# Import von Funktionen und/oder Variablen aus der FHEM main
# man kann ::Funktionaname wählen und sich so den Import schenken. Variablen sollten aber
# sauber importiert werden
2022-03-21 09:13:34 +00:00
# use GPUtils qw(GP_Import);
use GPUtils qw( GP_Import GP_Export )
; # da Du beide Funktionen aus dem package verwendest
2022-03-18 21:44:22 +00:00
## Import der FHEM Funktionen
#-- Run before package compilation
BEGIN {
# Import from main context
GP_Import (
qw(
readingFnAttributes
Log3
readingsBeginUpdate
readingsEndUpdate
readingsBulkUpdate
readingsSingleUpdate
2022-03-19 23:03:46 +00:00
readingsDelete
2022-03-18 21:44:22 +00:00
InternalVal
ReadingsVal
RemoveInternalTimer
InternalTimer
HttpUtils_NonblockingGet
HttpUtils_BlockingGet
gettimeofday
getUniqueId
Attr
)
) ;
}
#-- Export to main context with different name
GP_Export (
qw(
Initialize
)
) ;
my % gets = (
2022-03-21 09:13:34 +00:00
update = > "noArg" ,
health = > "noArg" ,
2022-03-18 22:58:40 +00:00
charger = > "noArg" ,
2022-03-16 18:57:21 +00:00
) ;
2022-03-18 21:44:22 +00:00
my % sets = (
2022-03-19 15:00:05 +00:00
enabled = > "" ,
2022-03-21 09:13:34 +00:00
disabled = > "" ,
2022-03-19 15:00:05 +00:00
enableSmartButton = > "true,false" ,
2022-03-21 09:13:34 +00:00
authorizationRequired = > "true,false" ,
2022-03-16 18:57:21 +00:00
startCharging = > "" ,
stopCharging = > "" ,
pauseCharging = > "" ,
resumeCharging = > "" ,
toggleCharging = > "" ,
interval = > "" ,
refreshToken = > "noArg" ,
cableLock = > "true,false" ,
reboot = > "noArg" ,
updateFirmware = > "noArg" ,
enableSmartCharging = > "true,false" ,
2022-03-20 19:48:37 +00:00
ledStripBrightness = > "" ,
2022-03-16 18:57:21 +00:00
overrideChargingSchedule = > "" ,
pairRfidTag = > "" ,
2022-03-16 20:55:18 +00:00
pricePerKWH = > "" ,
activateTimer = > "" ,
deactivateTimer = > "" ,
2022-03-16 18:57:21 +00:00
) ;
2022-03-18 21:44:22 +00:00
## Datapoint, all behind API URI
my % dpoints = (
getOAuthToken = > 'accounts/login' ,
getRefreshToken = > 'accounts/refresh_token' ,
getProfile = > 'accounts/profile' ,
getChargingSession = > 'chargers/#ChargerID#/sessions/ongoing' ,
getChargers = > 'accounts/chargers' ,
getProducts = > 'accounts/products?userId=#UserId#' ,
getChargerSite = > 'chargers/#ChargerID#/site' ,
getChargerDetails = > 'chargers/#ChargerID#/details' ,
getChargerConfiguration = > 'chargers/#ChargerID#/config' ,
getChargerSessionsMonthly = > 'sessions/charger/#ChargerID#/monthly' ,
getChargerSessionsDaily = > 'sessions/charger/#ChargerID#/daily' ,
getChargerState = > 'chargers/#ChargerID#/state' ,
getCurrentSession = > 'chargers/#ChargerID#/sessions/ongoing' ,
setCableLockState = > 'chargers/#ChargerID#/commands/lock_state' ,
setReboot = > 'chargers/#ChargerID#/commands/reboot' ,
2022-03-21 09:13:34 +00:00
setUpdateFirmware = > 'chargers/#ChargerID#/commands/update_firmware' ,
setEnableSmartCharging = > 'chargers/#ChargerID#/commands/smart_charging' ,
setStartCharging = > 'chargers/#ChargerID#/commands/start_charging' ,
setStopCharging = > 'chargers/#ChargerID#/commands/stop_charging' ,
setPauseCharging = > 'chargers/#ChargerID#/commands/pause_charging' ,
setResumeCharging = > 'chargers/#ChargerID#/commands/resume_charging' ,
setToggleCharging = > 'chargers/#ChargerID#/commands/toggle_charging' ,
setOverrideChargingSchedule = >
'chargers/#ChargerID#/commands/override_schedule' ,
setPairRFIDTag = >
'chargers/#ChargerID#/commands/set_rfid_pairing_mode_async' ,
changeChargerSettings = > 'chargers/#ChargerID#/settings' ,
setChargingPrice = > 'sites/#SiteID#/price' ,
2022-03-18 21:44:22 +00:00
) ;
my % reasonsForNoCurrent = (
2022-03-16 18:57:21 +00:00
0 = > 'OK' , #charger is allocated current
1 = > 'MaxCircuitCurrentTooLow' ,
2 = > 'MaxDynamicCircuitCurrentTooLow' ,
3 = > 'MaxDynamicOfflineFallbackCircuitCurrentTooLow' ,
4 = > 'CircuitFuseTooLow' ,
5 = > 'WaitingInQueue' ,
6 = > 'WaitingInFully'
, #charged queue (charger assumes one of: EV uses delayed charging, EV charging complete)
2022-03-21 09:13:34 +00:00
7 = > 'IllegalGridType' ,
8 = > 'PrimaryUnitHasNotReceivedCurrentRequestFromSecondaryUnit' ,
2022-03-16 18:57:21 +00:00
50 = > 'SecondaryUnitNotRequestingCurrent' , #no car connected...
51 = > 'MaxChargerCurrentTooLow' ,
52 = > 'MaxDynamicChargerCurrentTooLow' ,
53 = > 'ChargerDisabled' ,
54 = > 'PendingScheduledCharging' ,
55 = > 'PendingAuthorization' ,
56 = > 'ChargerInErrorState' ,
100 = > 'Undefined'
) ;
2022-03-18 21:44:22 +00:00
my % phaseModes = (
2022-03-16 18:57:21 +00:00
1 = > 'Locked to single phase' ,
2 = > 'Auto' ,
3 = > 'Locked to three phase' ,
) ;
2022-03-18 21:44:22 +00:00
my % operationModes = (
2022-03-19 14:19:19 +00:00
0 = > "Disconnected" ,
2022-03-16 18:57:21 +00:00
1 = > "Standby" ,
2 = > "Paused" ,
3 = > 'Charging' ,
4 = > 'Completed' ,
5 = > 'Error' ,
6 = > 'CarConnected'
) ;
2022-03-19 14:19:19 +00:00
my % commandCodes = (
2022-03-21 09:13:34 +00:00
1 = > "Reboot" ,
2 = > "Poll single observation" ,
3 = > "Poll all observations" ,
4 = > "Upgrade Firmware" ,
5 = > "Download settings" ,
7 = > "Scan Wifi" ,
11 = > "Set smart charging" ,
23 = > "Abort charging" ,
25 = > "Start Charging" ,
26 = > "Stop Charging" ,
29 = > "Set enabled" ,
30 = > "Set cable lock" ,
11 = > "Set smart charging" ,
40 = > "Set lightstripe brightness" ,
43 = > "Add keys" ,
44 = > "Clear keys" ,
48 = > "Pause/Resume/Toggle Charging" ,
60 = > "Add schedule" ,
61 = > "Cear Schedule" ,
62 = > "Get Schedule" ,
63 = > "Override Schedule" ,
64 = > "Purge Schedule" ,
69 = > "Set RFID Pairing Mode" ,
2022-03-19 14:19:19 +00:00
) ;
2022-03-16 18:57:21 +00:00
#Private function to evaluate command-lists
2022-03-18 21:44:22 +00:00
# private funktionen beginnen immer mit _
2022-03-16 18:57:21 +00:00
#############################
2022-03-18 21:44:22 +00:00
sub _GetCmdList {
2022-03-16 18:57:21 +00:00
my ( $ hash , $ cmd , $ commands ) = @ _ ;
my % cmdArray = %$ commands ;
my $ name = $ hash - > { NAME } ;
#return, if cmd is valid
2022-03-21 09:13:34 +00:00
return if ( defined ( $ cmd ) and defined ( $ cmdArray { $ cmd } ) ) ;
2022-03-16 18:57:21 +00:00
#response for gui or the user, if command is invalid
my $ retVal ;
foreach my $ mySet ( keys % cmdArray ) {
#append set-command
2022-03-21 09:13:34 +00:00
$ retVal = $ retVal . " "
if ( defined ( $ retVal ) )
; # Macht denke ich keinen Sinn da durch my $retVal bereits $retVal definiert ist
2022-03-16 18:57:21 +00:00
$ retVal = $ retVal . $ mySet ;
#get options
my $ myOpt = $ cmdArray { $ mySet } ;
#append option, if valid
$ retVal = $ retVal . ":" . $ myOpt
2022-03-21 09:13:34 +00:00
if ( defined ( $ myOpt ) and ( length ( $ myOpt ) > 0 ) ) ;
2022-03-16 18:57:21 +00:00
$ myOpt = "" if ( ! defined ( $ myOpt ) ) ;
2022-03-21 09:13:34 +00:00
#Log3 ($name, 5, "parse cmd-table - Set:$mySet, Option:$myOpt, RetVal:$retVal");
2022-03-16 18:57:21 +00:00
}
if ( ! defined ( $ retVal ) ) {
2022-03-21 09:13:34 +00:00
return "error while parsing set-table" ;
2022-03-16 18:57:21 +00:00
}
2022-03-21 09:13:34 +00:00
return "Unknown argument $cmd, choose one of " . $ retVal ;
# versuche wo wenig wie möglich if else zu verwenden.
# else {
# $retVal = "Unknown argument $cmd, choose one of " . $retVal;
# }
2022-03-16 18:57:21 +00:00
}
2022-03-18 21:44:22 +00:00
sub Initialize {
2022-03-16 18:57:21 +00:00
my ( $ hash ) = @ _ ;
2022-03-18 21:44:22 +00:00
$ hash - > { DefFn } = \ & Define ;
$ hash - > { UndefFn } = \ & Undef ;
$ hash - > { SetFn } = \ & Set ;
$ hash - > { GetFn } = \ & Get ;
$ hash - > { AttrFn } = \ & Attr ;
$ hash - > { ReadFn } = \ & Read ;
$ hash - > { WriteFn } = \ & Write ;
$ hash - > { AttrList } =
'expertMode:yes,no '
. 'ledStuff:yes,no '
. 'SmartCharging:true,false '
. $ readingFnAttributes ;
#Log3, 'EaseeWallbox', 3, "EaseeWallbox module initialized.";
2022-03-21 09:13:34 +00:00
return ;
2022-03-16 18:57:21 +00:00
}
2022-03-18 21:44:22 +00:00
sub Define {
2022-03-16 18:57:21 +00:00
my ( $ hash , $ def ) = @ _ ;
my @ param = split ( "[ \t]+" , $ def ) ;
my $ name = $ hash - > { NAME } ;
2022-03-18 21:44:22 +00:00
# set API URI as Internal Key
$ hash - > { APIURI } = 'https://api.easee.cloud/api/' ;
2022-03-16 18:57:21 +00:00
Log3 $ name , 3 , "EaseeWallbox_Define $name: called " ;
my $ errmsg = '' ;
2022-03-21 09:13:34 +00:00
# Check parameter(s) - Must be min 4 in total (counts strings not purly parameter, interval is optional)
2022-03-16 18:57:21 +00:00
if ( int ( @ param ) < 4 ) {
$ errmsg = return
2022-03-21 09:13:34 +00:00
"syntax error: define <name> EaseeWallbox <username> <password> [Interval]" ;
2022-03-16 18:57:21 +00:00
Log3 $ name , 1 , "EaseeWallbox $name: " . $ errmsg ;
return $ errmsg ;
}
#Check if the username is an email address
2022-03-21 09:13:34 +00:00
if ( $ param [ 2 ] =~ /^.+@.+$/x )
{ # Regular expression without "/x" flag. See page 236 of PBP (RegularExpressions::RequireExtendedFormatting)
2022-03-16 18:57:21 +00:00
my $ username = $ param [ 2 ] ;
$ hash - > { Username } = $ username ;
}
else {
2022-03-21 09:13:34 +00:00
$ errmsg =
"specify valid email address within the field username. Format: define <name> EaseeWallbox <username> <password> [interval]" ;
2022-03-16 18:57:21 +00:00
Log3 $ name , 1 , "EaseeWallbox $name: " . $ errmsg ;
return $ errmsg ;
}
#Take password and use custom encryption.
# Encryption is taken from fitbit / withings module
2022-03-18 21:44:22 +00:00
my $ password = _encrypt ( $ param [ 3 ] ) ;
2022-03-16 18:57:21 +00:00
$ hash - > { Password } = $ password ;
if ( defined $ param [ 4 ] ) {
$ hash - > { DEF } = sprintf ( "%s %s %s" ,
InternalVal ( $ name , 'Username' , undef ) ,
$ password , $ param [ 4 ] ) ;
}
else {
$ hash - > { DEF } = sprintf ( "%s %s" ,
InternalVal ( $ name , 'Username' , undef ) , $ password ) ;
}
#Check if interval is set and numeric.
#If not set -> set to 60 seconds
#If less then 5 seconds set to 5
#If not an integer abort with failure.
my $ interval = 60 ;
if ( defined $ param [ 4 ] ) {
2022-03-21 09:13:34 +00:00
if ( $ param [ 4 ] =~ /^\d+$/x )
{ # Regular expression without "/x" flag. See page 236 of PBP (RegularExpressions::RequireExtendedFormatting)
2022-03-16 18:57:21 +00:00
$ interval = $ param [ 4 ] ;
}
else {
2022-03-21 09:13:34 +00:00
$ errmsg =
"Specify valid integer value for interval. Whole numbers > 5 only. Format: define <name> EaseeWallbox <username> <password> [interval]" ;
2022-03-16 18:57:21 +00:00
Log3 $ name , 1 , "EaseeWallbox $name: " . $ errmsg ;
return $ errmsg ;
}
}
if ( $ interval < 5 ) { $ interval = 5 ; }
$ hash - > { INTERVAL } = $ interval ;
readingsSingleUpdate ( $ hash , 'state' , 'Undefined' , 0 ) ;
#Initial load of data
2022-03-21 09:13:34 +00:00
WriteToCloudAPI ( $ hash , 'getChargers' , 'GET' ) ;
Log3 $ name , 1 ,
sprintf ( "EaseeWallbox_Define %s: Starting timer with interval %s" ,
$ name , InternalVal ( $ name , 'INTERVAL' , undef ) ) ;
InternalTimer ( gettimeofday ( ) + InternalVal ( $ name , 'INTERVAL' , undef ) ,
"FHEM::EaseeWallbox::UpdateDueToTimer" , $ hash )
if ( defined $ hash ) ;
2022-03-16 18:57:21 +00:00
2022-03-21 09:13:34 +00:00
## return; sollte es nicht geben, ein return; ist per see mit Rückgabe undef
return ;
2022-03-16 18:57:21 +00:00
}
2022-03-18 21:44:22 +00:00
sub Undef {
2022-03-16 18:57:21 +00:00
my ( $ hash , $ arg ) = @ _ ;
RemoveInternalTimer ( $ hash ) ;
2022-03-21 09:13:34 +00:00
return ;
2022-03-16 18:57:21 +00:00
}
2022-03-18 21:44:22 +00:00
sub Get {
2022-03-16 19:50:50 +00:00
my ( $ hash , $ name , @ args ) = @ _ ;
return '"get EaseeWallbox" needs at least one argument'
2022-03-21 09:13:34 +00:00
if ( int ( @ args ) < 1 ) ;
2022-03-16 19:50:50 +00:00
my $ opt = shift @ args ;
#create response, if cmd is wrong or gui asks
2022-03-18 21:44:22 +00:00
my $ cmdTemp = _GetCmdList ( $ hash , $ opt , \ % gets ) ;
2022-03-16 19:50:50 +00:00
return $ cmdTemp if ( defined ( $ cmdTemp ) ) ;
2022-03-16 20:55:18 +00:00
$ hash - > { LOCAL } = 1 ;
2022-03-21 09:13:34 +00:00
WriteToCloudAPI ( $ hash , 'getChargers' , 'GET' ) if $ opt eq "charger" ;
RefreshData ( $ hash ) if $ opt eq "update" ;
2022-03-16 20:55:18 +00:00
delete $ hash - > { LOCAL } ;
2022-03-21 09:13:34 +00:00
return ;
2022-03-16 19:50:50 +00:00
}
2022-03-18 21:44:22 +00:00
sub Set {
2022-03-16 19:50:50 +00:00
my ( $ hash , $ name , @ param ) = @ _ ;
return '"set $name" needs at least one argument' if ( int ( @ param ) < 1 ) ;
2022-03-21 09:13:34 +00:00
my $ opt = shift @ param ;
$ value = join ( "" , @ param ) ;
my % message ;
2022-03-16 19:50:50 +00:00
#create response, if cmd is wrong or gui asks
2022-03-18 21:44:22 +00:00
my $ cmdTemp = _GetCmdList ( $ hash , $ opt , \ % sets ) ;
2022-03-16 19:50:50 +00:00
return $ cmdTemp if ( defined ( $ cmdTemp ) ) ;
2022-03-18 22:58:40 +00:00
if ( $ opt eq "deactivateTimer" ) {
2022-03-21 09:13:34 +00:00
# Cascading if-elsif chain. See pages 117,118 of PBP (ControlStructures::ProhibitCascadingIfElse) kann man anders machen. Später machen wir das
2022-03-18 22:58:40 +00:00
RemoveInternalTimer ( $ hash ) ;
Log3 $ name , 1 ,
2022-03-21 09:13:34 +00:00
"EaseeWallbox_Set $name: Stopped the timer to automatically update readings" ;
2022-03-18 22:58:40 +00:00
readingsSingleUpdate ( $ hash , 'state' , 'Initialized' , 0 ) ;
2022-03-21 09:13:34 +00:00
return ;
2022-03-18 22:58:40 +00:00
}
2022-03-21 09:13:34 +00:00
elsif ( $ opt eq "activateTimer" ) {
2022-03-18 22:58:40 +00:00
#Update once manually and then start the timer
RemoveInternalTimer ( $ hash ) ;
$ hash - > { LOCAL } = 1 ;
RefreshData ( $ hash ) ;
2022-03-21 09:13:34 +00:00
delete $ hash - > { LOCAL } ;
InternalTimer ( gettimeofday ( ) + InternalVal ( $ name , 'INTERVAL' , undef ) ,
"FHEM::EaseeWallbox::UpdateDueToTimer" , $ hash ) ;
readingsSingleUpdate ( $ hash , 'state' , 'Started' , 0 ) ;
Log3 $ name , 1 ,
sprintf (
"EaseeWallbox_Set %s: Updated readings and started timer to automatically update readings with interval %s" ,
$ name , InternalVal ( $ name , 'INTERVAL' , undef ) ) ;
2022-03-18 22:58:40 +00:00
}
2022-03-21 09:13:34 +00:00
elsif ( $ opt eq "interval" )
{ # interval wird immer über Attribut gesetzt. Also in die Funktion AttrFn aus Initialize
2022-03-18 22:58:40 +00:00
my $ interval = shift @ param ;
$ interval = 60 unless defined ( $ interval ) ;
if ( $ interval < 5 ) { $ interval = 5 ; }
Log3 $ name , 1 , "EaseeWallbox_Set $name: Set interval to" . $ interval ;
$ hash - > { INTERVAL } = $ interval ;
}
2022-03-21 09:13:34 +00:00
elsif ( $ opt eq "cableLock" ) {
2022-03-18 22:58:40 +00:00
$ message { 'state' } = $ value ;
2022-03-21 09:13:34 +00:00
WriteToCloudAPI ( $ hash , 'setCableLockState' , 'POST' , \ % message ) ;
}
2022-03-18 22:58:40 +00:00
elsif ( $ opt eq "pricePerKWH" ) {
2022-03-21 09:13:34 +00:00
2022-03-18 22:58:40 +00:00
$ message { 'currencyId' } = "EUR" ;
$ message { 'vat' } = "19" ;
$ message { 'costPerKWh' } = shift @ param ;
2022-03-21 09:13:34 +00:00
WriteToCloudAPI ( $ hash , 'setChargingPrice' , 'POST' , \ % message ) ;
}
2022-03-19 14:19:19 +00:00
elsif ( $ opt eq "pairRfidTag" ) {
my $ timeout = shift @ param ;
2022-03-21 09:13:34 +00:00
#if (defined $timeout and /^\d+$/) { print "is a whole number\n" }
$ timeout = '60' if not defined $ timeout or $ timeout = '' ;
2022-03-19 14:19:19 +00:00
$ message { 'timeout' } = "60" ;
2022-03-21 09:13:34 +00:00
WriteToCloudAPI ( $ hash , 'setPairRFIDTag' , 'POST' , \ % message ) ;
}
2022-03-19 14:32:48 +00:00
elsif ( $ opt eq "enableSmartCharging" ) {
2022-03-21 09:13:34 +00:00
2022-03-19 14:32:48 +00:00
$ message { 'smartCharging' } = shift @ param ;
2022-03-21 09:13:34 +00:00
WriteToCloudAPI ( $ hash , 'changeChargerSettings' , 'POST' , \ % message ) ;
}
2022-03-19 15:00:05 +00:00
elsif ( $ opt eq "enabled" ) {
2022-03-21 09:13:34 +00:00
2022-03-19 15:00:05 +00:00
$ message { 'enabled' } = "true" ;
2022-03-21 09:13:34 +00:00
WriteToCloudAPI ( $ hash , 'changeChargerSettings' , 'POST' , \ % message ) ;
}
2022-03-19 15:00:05 +00:00
elsif ( $ opt eq "disabled" ) {
2022-03-21 09:13:34 +00:00
2022-03-19 15:00:05 +00:00
$ message { 'enabled' } = "false" ;
2022-03-21 09:13:34 +00:00
WriteToCloudAPI ( $ hash , 'changeChargerSettings' , 'POST' , \ % message ) ;
}
2022-03-19 15:00:05 +00:00
elsif ( $ opt eq "authorizationRequired" ) {
2022-03-21 09:13:34 +00:00
2022-03-19 15:00:05 +00:00
$ message { 'authorizationRequired' } = shift @ param ;
2022-03-21 09:13:34 +00:00
WriteToCloudAPI ( $ hash , 'changeChargerSettings' , 'POST' , \ % message ) ;
}
2022-03-19 15:00:05 +00:00
elsif ( $ opt eq "enableSmartButton" ) {
2022-03-21 09:13:34 +00:00
2022-03-19 15:00:05 +00:00
$ message { 'smartButtonEnabled' } = shift @ param ;
2022-03-21 09:13:34 +00:00
WriteToCloudAPI ( $ hash , 'changeChargerSettings' , 'POST' , \ % message ) ;
}
2022-03-20 19:48:37 +00:00
elsif ( $ opt eq "ledStripBrightness" ) {
2022-03-21 09:13:34 +00:00
2022-03-20 19:48:37 +00:00
$ message { 'ledStripBrightness' } = shift @ param ;
2022-03-21 09:13:34 +00:00
WriteToCloudAPI ( $ hash , 'changeChargerSettings' , 'POST' , \ % message ) ;
}
else {
$ hash - > { LOCAL } = 1 ;
WriteToCloudAPI ( $ hash , 'setStartCharging' , 'POST' )
if $ opt eq "startCharging" ;
WriteToCloudAPI ( $ hash , 'setStopCharging' , 'POST' )
if $ opt eq 'stopCharging' ;
WriteToCloudAPI ( $ hash , 'setPauseCharging' , 'POST' )
if $ opt eq 'pauseCharging' ;
WriteToCloudAPI ( $ hash , 'setResumeCharging' , 'POST' )
if $ opt eq 'resumeCharging' ;
WriteToCloudAPI ( $ hash , 'setToggleCharging' , 'POST' )
if $ opt eq 'toggleCharging' ;
WriteToCloudAPI ( $ hash , 'setUpdateFirmware' , 'POST' )
if $ opt eq 'updateFirmware' ;
WriteToCloudAPI ( $ hash , 'setOverrideChargingSchedule' , 'POST' )
if $ opt eq 'overrideChargingSchedule' ;
WriteToCloudAPI ( $ hash , 'setReboot' , 'POST' ) if $ opt eq 'reboot' ;
_loadToken ( $ hash ) if $ opt eq 'refreshToken' ;
2022-03-18 22:58:40 +00:00
delete $ hash - > { LOCAL } ;
}
2022-03-21 09:13:34 +00:00
readingsSingleUpdate ( $ hash , 'state' , 'Initialized' , 0 )
; # Die Modulinstanz ist doch nicht erst bei einem set Initialized, das ist doch schon nach dem define. Wenn dann ist hier ein status ala "processing setter" oder so.
return ;
2022-03-18 21:44:22 +00:00
}
2022-03-16 19:50:50 +00:00
2022-03-18 21:44:22 +00:00
sub Attr {
my ( $ cmd , $ name , $ attrName , $ attrVal ) = @ _ ;
2022-03-21 09:13:34 +00:00
# hier kannst Du das setzen des Intervals umsetzen
2022-03-18 21:44:22 +00:00
return ;
}
2022-03-21 09:13:34 +00:00
sub RefreshData {
my $ hash = shift ;
my $ name = $ hash - > { NAME } ;
WriteToCloudAPI ( $ hash , 'getChargerSite' , 'GET' ) ;
WriteToCloudAPI ( $ hash , 'getChargerState' , 'GET' ) ;
WriteToCloudAPI ( $ hash , 'getCurrentSession' , 'GET' ) ;
WriteToCloudAPI ( $ hash , 'getChargerConfiguration' , 'GET' ) ;
WriteToCloudAPI ( $ hash , 'getChargerSessionsMonthly' , 'GET' ) ;
WriteToCloudAPI ( $ hash , 'getChargerSessionsDaily' , 'GET' ) ;
return ; # immer mit einem return eine funktion beenden
2022-03-18 22:10:05 +00:00
}
2022-03-21 09:13:34 +00:00
sub UpdateDueToTimer {
2022-03-18 22:58:40 +00:00
my ( $ hash ) = @ _ ;
my $ name = $ hash - > { NAME } ;
2022-03-21 09:13:34 +00:00
#local allows call of function without adding new timer.
#must be set before call ($hash->{LOCAL} = 1) and removed after (delete $hash->{LOCAL};)
2022-03-18 22:58:40 +00:00
if ( ! $ hash - > { LOCAL } ) {
RemoveInternalTimer ( $ hash ) ;
2022-03-21 09:13:34 +00:00
2022-03-18 22:58:40 +00:00
#Log3 "Test", 1, Dumper($hash);
2022-03-21 09:13:34 +00:00
InternalTimer ( gettimeofday ( ) + InternalVal ( $ name , 'INTERVAL' , undef ) ,
"FHEM::EaseeWallbox::UpdateDueToTimer" , $ hash ) ;
2022-03-18 22:58:40 +00:00
}
2022-03-21 09:13:34 +00:00
return RefreshData ( $ hash ) ;
2022-03-18 22:58:40 +00:00
}
2022-03-18 22:10:05 +00:00
2022-03-18 21:44:22 +00:00
sub WriteToCloudAPI {
2022-03-21 09:13:34 +00:00
my $ hash = shift ;
my $ dpoint = shift ;
my $ method = shift ;
2022-03-18 22:58:40 +00:00
my $ message = shift ;
2022-03-21 09:13:34 +00:00
my $ name = $ hash - > { NAME } ;
my $ url = $ hash - > { APIURI } . $ dpoints { $ dpoint } ;
2022-03-18 21:44:22 +00:00
#########
# CHANGE THIS
2022-03-18 22:58:40 +00:00
my $ payload ;
2022-03-21 09:13:34 +00:00
$ payload = encode_json \ %$ message if defined $ message ;
2022-03-18 21:44:22 +00:00
my $ deviceId = "WC1" ;
2022-03-21 09:13:34 +00:00
if ( not defined $ hash ) {
my $ msg =
"Error on EaseeWallbox_WriteToCloudAPI. Missing hash variable" ;
2022-03-18 21:44:22 +00:00
Log3 'EaseeWallbox' , 1 , $ msg ;
return $ msg ;
2022-03-16 20:55:18 +00:00
}
2022-03-18 21:44:22 +00:00
#Check if chargerID is required in URL and replace or alert.
2022-03-21 09:13:34 +00:00
if ( $ url =~ m/#ChargerID#/x )
{ # Regular expression without "/x" flag. See page 236 of PBP (RegularExpressions::RequireExtendedFormatting)
2022-03-18 21:44:22 +00:00
my $ chargerId = ReadingsVal ( $ name , 'charger_id' , undef ) ;
if ( not defined $ chargerId ) {
2022-03-21 09:13:34 +00:00
my $ error =
"Error on EaseeWallbox_WriteToCloudAPI. Missing charger_id. Please ensure basic data is available." ;
2022-03-18 21:44:22 +00:00
Log3 'EaseeWallbox' , 1 , $ error ;
return $ error ;
}
2022-03-21 09:13:34 +00:00
$ url =~ s/#ChargerID#/$chargerId/xg
; # Regular expression without "/x" flag. See page 236 of PBP (RegularExpressions::RequireExtendedFormatting)
2022-03-16 19:50:50 +00:00
}
2022-03-18 21:44:22 +00:00
#Check if siteID is required in URL and replace or alert.
2022-03-21 09:13:34 +00:00
if ( $ url =~ m/#SiteID#/x )
{ # Regular expression without "/x" flag. See page 236 of PBP (RegularExpressions::RequireExtendedFormatting)
2022-03-18 21:44:22 +00:00
my $ siteId = ReadingsVal ( $ name , 'site_id' , undef ) ;
if ( not defined $ siteId ) {
2022-03-21 09:13:34 +00:00
my $ error =
"Error on EaseeWallbox_WriteToCloudAPI. Missing site_id. Please ensure basic data is available." ;
2022-03-18 21:44:22 +00:00
Log3 'EaseeWallbox' , 1 , $ error ;
return $ error ;
}
2022-03-21 09:13:34 +00:00
$ url =~ s/#SiteID#/$siteId/xg
; # Regular expression without "/x" flag. See page 236 of PBP (RegularExpressions::RequireExtendedFormatting)
2022-03-16 19:50:50 +00:00
}
2022-03-18 21:44:22 +00:00
my $ CurrentTokenData = _loadToken ( $ hash ) ;
2022-03-21 09:13:34 +00:00
my $ header = {
"Content-Type" = > "application/json;charset=UTF-8" ,
"Authorization" = >
"$CurrentTokenData->{'tokenType'} $CurrentTokenData->{'accessToken'}"
} ;
2022-03-18 21:44:22 +00:00
# $method ist GET oder POST
# bei POST ist $payload gleich data
HttpUtils_NonblockingGet (
{
url = > $ url ,
timeout = > 15 ,
incrementalTimeout = > 1 ,
hash = > $ hash ,
dpoint = > $ dpoint ,
device_id = > $ deviceId ,
data = > $ payload ,
method = > $ method ,
header = > $ header ,
callback = > \ & ResponseHandling
}
) ;
return ;
2022-03-16 19:50:50 +00:00
}
2022-03-18 21:44:22 +00:00
sub ResponseHandling {
my $ param = shift ;
my $ err = shift ;
my $ data = shift ;
2022-03-21 09:13:34 +00:00
my $ hash = $ param - > { hash } ;
my $ name = $ hash - > { NAME } ;
2022-03-18 21:44:22 +00:00
Log3 $ name , 4 , "Callback received." . $ param - > { url } ;
2022-03-21 09:13:34 +00:00
if ( $ err ne "" ) # wenn ein Fehler bei der HTTP Abfrage aufgetreten ist
2022-03-18 21:44:22 +00:00
{
2022-03-21 09:13:34 +00:00
Log3 $ name , 3 ,
"error while requesting "
. $ param - > { url }
. " - $err" ; # Eintrag fürs Log
2022-03-18 21:44:22 +00:00
readingsSingleUpdate ( $ hash , "lastResponse" , "ERROR $err" , 1 ) ;
2022-03-21 09:13:34 +00:00
return ;
2022-03-18 21:44:22 +00:00
}
my $ code = $ param - > { code } ;
2022-03-21 09:13:34 +00:00
if ( $ code == 404 and $ param - > { dpoint } eq 'getCurrentSession' )
{ # Entweder == dann number z.B. 404 oder wenn eq dann String also '404'
readingsDelete ( $ hash , 'session_energy' ) ;
readingsDelete ( $ hash , 'session_start' ) ;
readingsDelete ( $ hash , 'session_end' ) ;
readingsDelete ( $ hash , 'session_chargeDurationInSeconds' ) ;
readingsDelete ( $ hash , 'session_firstEnergyTransfer' ) ;
readingsDelete ( $ hash , 'session_lastEnergyTransfer' ) ;
readingsDelete ( $ hash , 'session_pricePerKWH' ) ;
readingsDelete ( $ hash , 'session_chargingCost' ) ;
readingsDelete ( $ hash , 'session_id' ) ;
return ;
2022-03-19 23:03:46 +00:00
}
2022-03-21 09:13:34 +00:00
if ( $ code >= 400 ) {
Log3 $ name , 3 ,
"HTTPS error while requesting "
. $ param - > { url }
. " - $code" ; # Eintrag fürs Log
readingsSingleUpdate ( $ hash , "lastResponse" , "ERROR: HTTP Code $code" ,
1 ) ;
return ;
2022-03-18 21:44:22 +00:00
}
Log3 $ name , 3 ,
2022-03-21 09:13:34 +00:00
"Received non-blocking data from EaseeWallbox regarding current session " ;
2022-03-18 21:44:22 +00:00
Log3 $ name , 4 , "FHEM -> EaseeWallbox: " . $ param - > { url } ;
Log3 $ name , 4 , "FHEM -> EaseeWallbox: " . $ param - > { message }
2022-03-21 09:13:34 +00:00
if ( defined $ param - > { message } ) ;
2022-03-18 21:44:22 +00:00
Log3 $ name , 4 , "EaseeWallbox -> FHEM: " . $ data ;
Log3 $ name , 5 , '$err: ' . $ err ;
Log3 $ name , 5 , "method: " . $ param - > { method } ;
Log3 $ name , 2 , "Something gone wrong"
2022-03-21 09:13:34 +00:00
if ( $ data =~ "/EaseeWallboxMode/" ) ;
2022-03-18 21:44:22 +00:00
2022-03-21 09:13:34 +00:00
my $ decoded_json ;
2022-03-19 23:03:46 +00:00
2022-03-21 09:13:34 +00:00
eval { $ decoded_json = decode_json ( $ data ) } ; # statt eval ist es empfohlen catch try zu verwenden. Machen wir später
if ( $@ ) {
Log3 $ name , 3 , "GardenaSmartBridge ($name) - JSON error while request" ;
}
2022-03-19 23:03:46 +00:00
2022-03-21 09:13:34 +00:00
Log3 $ name , 5 , 'Decoded: ' . Dumper ( $ decoded_json ) ;
Log3 $ name , 5 , 'Ref of d: ' . ref ( $ decoded_json ) ;
2022-03-19 23:03:46 +00:00
2022-03-21 09:13:34 +00:00
my $ value ;
2022-03-18 21:44:22 +00:00
2022-03-21 09:13:34 +00:00
if ( defined $ decoded_json
and $ decoded_json ne ''
and ref ( $ decoded_json ) eq "HASH"
or ( ref ( $ decoded_json ) eq "ARRAY" and $ decoded_json > 0 ) )
{
if ( $ param - > { dpoint } eq 'getChargers' ) {
Processing_DpointGetChargers ( $ hash , $ decode_json ) ;
return ;
}
2022-03-18 21:44:22 +00:00
2022-03-21 09:13:34 +00:00
if ( $ param - > { dpoint } eq 'getChargerSessionsDaily' ) {
Processing_DpointGetChargerSessionsDaily ( $ hash , $ decode_json ) ;
return ;
}
2022-03-18 22:10:05 +00:00
2022-03-21 09:13:34 +00:00
# Und so weiter und so weiter mit den einzelnen Funktionen !!!
if ( $ param - > { dpoint } eq 'getChargerSessionsMonthly' ) {
Log3 $ name , 5 , 'Evaluating getChargerSessionsMonthly' ;
my @ x = $ decoded_json ;
my @ a = ( - 6 .. - 1 ) ;
readingsBeginUpdate ( $ hash ) ;
for ( @ a ) {
Log3 $ name , 5 , 'laeuft noch: ' . $ _ ;
readingsBulkUpdate (
$ hash ,
"monthly_" . ( $ _ + 1 ) . "_energy" ,
sprintf (
"%.2f" , $ decoded_json - > [ $ _ ] - > { 'totalEnergyUsage' }
)
) ;
readingsBulkUpdate (
$ hash ,
"monthly_" . ( $ _ + 1 ) . "_cost" ,
sprintf ( "%.2f" , $ decoded_json - > [ $ _ ] - > { 'totalCost' } )
) ;
2022-03-18 22:10:05 +00:00
}
2022-03-21 09:13:34 +00:00
readingsEndUpdate ( $ hash , 1 ) ;
return ;
}
2022-03-19 23:03:46 +00:00
2022-03-21 09:13:34 +00:00
if ( $ param - > { dpoint } eq 'getChargerConfiguration' ) {
readingsBeginUpdate ( $ hash ) ;
readingsBulkUpdate ( $ hash , "isEnabled" ,
$ decoded_json - > { isEnabled } ) ;
readingsBulkUpdate ( $ hash , "isCablePermanentlyLocked" ,
$ decoded_json - > { lockCablePermanently } ) ;
readingsBulkUpdate ( $ hash , "isAuthorizationRequired" ,
$ decoded_json - > { authorizationRequired } ) ;
readingsBulkUpdate ( $ hash , "isRemoteStartRequired" ,
$ decoded_json - > { remoteStartRequired } ) ;
readingsBulkUpdate ( $ hash , "isSmartButtonEnabled" ,
$ decoded_json - > { smartButtonEnabled } ) ;
readingsBulkUpdate ( $ hash , "wiFiSSID" , $ decoded_json - > { wiFiSSID } ) ;
readingsBulkUpdate ( $ hash , "phaseModeId" ,
$ decoded_json - > { phaseMode } ) ;
readingsBulkUpdate ( $ hash , "phaseMode" ,
$ phaseModes { $ decoded_json - > { phaseMode } } ) ;
readingsBulkUpdate (
$ hash ,
"isLocalAuthorizationRequired" ,
$ decoded_json - > { localAuthorizationRequired }
) ;
readingsBulkUpdate ( $ hash , "maxChargerCurrent" ,
$ decoded_json - > { maxChargerCurrent } ) ;
readingsBulkUpdate ( $ hash , "ledStripBrightness" ,
$ decoded_json - > { ledStripBrightness } ) ;
#readingsBulkUpdate( $hash, "charger_offlineChargingMode",
# $decoded_json->{offlineChargingMode} );
#readingsBulkUpdate( $hash, "charger_circuitMaxCurrentP1",
# $decoded_json->{circuitMaxCurrentP1} );
#readingsBulkUpdate( $hash, "charger_circuitMaxCurrentP2",
# $decoded_json->{circuitMaxCurrentP2} );
#readingsBulkUpdate( $hash, "charger_circuitMaxCurrentP3",
# $decoded_json->{circuitMaxCurrentP3} );
#readingsBulkUpdate( $hash, "charger_enableIdleCurrent",
# $decoded_json->{enableIdleCurrent} );
#readingsBulkUpdate(
# $hash,
# "charger_limitToSinglePhaseCharging",
# $decoded_json->{limitToSinglePhaseCharging}
#);
#readingsBulkUpdate( $hash, "charger_localNodeType",
# $decoded_json->{localNodeType} );
#readingsBulkUpdate( $hash, "charger_localRadioChannel",
# $decoded_json->{localRadioChannel} );
#readingsBulkUpdate( $hash, "charger_localShortAddress",
# $decoded_json->{localShortAddress} );
#readingsBulkUpdate(
# $hash,
# "charger_localParentAddrOrNumOfNodes",
# $decoded_json->{localParentAddrOrNumOfNodes}
#);
#readingsBulkUpdate(
# $hash,
# "charger_localPreAuthorizeEnabled",
# $decoded_json->{localPreAuthorizeEnabled}
#);
#readingsBulkUpdate(
# $hash,
# "charger_allowOfflineTxForUnknownId",
# $decoded_json->{allowOfflineTxForUnknownId}
#);
#readingsBulkUpdate( $hash, "chargingSchedule",
# $decoded_json->{chargingSchedule} );
readingsBulkUpdate ( $ hash , "lastResponse" ,
'OK - getChargerConfig' , 1 ) ;
readingsEndUpdate ( $ hash , 1 ) ;
return ;
}
if ( $ param - > { dpoint } eq 'getCurrentSession' ) {
readingsBeginUpdate ( $ hash ) ;
readingsBulkUpdate ( $ hash , "session_energy" ,
sprintf ( "%.2f" , $ decoded_json - > { sessionEnergy } ) ) ;
$ value =
defined $ decoded_json - > { sessionStart }
? _transcodeDate ( $ decoded_json - > { sessionStart } )
: 'N/A' ;
readingsBulkUpdate ( $ hash , "session_start" , $ value ) ;
$ value =
defined $ decoded_json - > { sessionEnd }
? _transcodeDate ( $ decoded_json - > { sessionEnd } )
: 'N/A' ;
readingsBulkUpdate ( $ hash , "session_end" , $ value ) ;
readingsBulkUpdate (
$ hash ,
"session_chargeDurationInSeconds" ,
$ decoded_json - > { chargeDurationInSeconds }
) ;
$ value =
defined $ decoded_json - > { firstEnergyTransferPeriodStart }
? _transcodeDate (
$ decoded_json - > { firstEnergyTransferPeriodStart } )
: 'N/A' ;
readingsBulkUpdate ( $ hash , "session_firstEnergyTransfer" , $ value ) ;
$ value =
defined $ decoded_json - > { lastEnergyTransferPeriodStart }
? _transcodeDate ( $ decoded_json - > { lastEnergyTransferPeriodStart } )
: 'N/A' ;
readingsBulkUpdate ( $ hash , "session_lastEnergyTransfer" , $ value ) ;
readingsBulkUpdate ( $ hash , "session_pricePerKWH" ,
$ decoded_json - > { pricePrKwhIncludingVat } ) ;
readingsBulkUpdate ( $ hash , "session_chargingCost" ,
sprintf ( "%.2f" , $ decoded_json - > { costIncludingVat } ) ) ;
readingsBulkUpdate ( $ hash , "session_id" ,
$ decoded_json - > { sessionId } ) ;
readingsBulkUpdate ( $ hash , "lastResponse" ,
'OK - getCurrentSession' , 1 ) ;
readingsEndUpdate ( $ hash , 1 ) ;
return ;
2022-03-18 21:44:22 +00:00
}
2022-03-21 09:13:34 +00:00
if ( $ param - > { dpoint } eq 'getChargerSite' ) {
readingsBeginUpdate ( $ hash ) ;
readingsBulkUpdate ( $ hash , "cost_perKWh" ,
$ decoded_json - > { costPerKWh } ) ;
readingsBulkUpdate ( $ hash , "cost_perKwhExcludeVat" ,
$ decoded_json - > { costPerKwhExcludeVat } ) ;
readingsBulkUpdate ( $ hash , "cost_vat" , $ decoded_json - > { vat } ) ;
readingsBulkUpdate ( $ hash , "cost_currency" ,
$ decoded_json - > { currencyId } ) ;
#readingsBulkUpdate( $hash, "site_ratedCurrent", $decoded_json->{ratedCurrent} );
#readingsBulkUpdate( $hash, "site_createdOn", $decoded_json->{createdOn} );
#readingsBulkUpdate( $hash, "site_updatedOn", $decoded_json->{updatedOn} );
readingsBulkUpdate ( $ hash , "lastResponse" ,
'OK - getChargerSite' , 1 ) ;
readingsEndUpdate ( $ hash , 1 ) ;
return ;
}
if ( $ param - > { dpoint } eq 'getChargerState' ) {
readingsBeginUpdate ( $ hash ) ;
readingsBulkUpdate ( $ hash , "operationModeCode" ,
$ decoded_json - > { chargerOpMode } ) ;
readingsBulkUpdate ( $ hash , "operationMode" ,
$ operationModes { $ decoded_json - > { chargerOpMode } } ) ;
readingsBulkUpdate ( $ hash , "power" ,
sprintf ( "%.2f" , $ decoded_json - > { totalPower } ) ) ;
readingsBulkUpdate ( $ hash , "kWhInSession" ,
sprintf ( "%.2f" , $ decoded_json - > { sessionEnergy } ) ) ;
readingsBulkUpdate ( $ hash , "phase" , $ decoded_json - > { outputPhase } ) ;
readingsBulkUpdate ( $ hash , "latestPulse" ,
_transcodeDate ( $ decoded_json - > { latestPulse } ) ) ;
readingsBulkUpdate ( $ hash , "current" ,
$ decoded_json - > { outputCurrent } ) ;
readingsBulkUpdate ( $ hash , "dynamicCurrent" ,
$ decoded_json - > { dynamicChargerCurrent } ) ;
readingsBulkUpdate ( $ hash , "reasonCodeForNoCurrent" ,
$ decoded_json - > { reasonForNoCurrent } ) ;
readingsBulkUpdate ( $ hash , "reasonForNoCurrent" ,
$ reasonsForNoCurrent { $ decoded_json - > { reasonForNoCurrent } } ) ;
readingsBulkUpdate ( $ hash , "errorCode" ,
$ decoded_json - > { errorCode } ) ;
readingsBulkUpdate ( $ hash , "fatalErrorCode" ,
$ decoded_json - > { fatalErrorCode } ) ;
readingsBulkUpdate ( $ hash , "lifetimeEnergy" ,
sprintf ( "%.2f" , $ decoded_json - > { lifetimeEnergy } ) ) ;
readingsBulkUpdate ( $ hash , "online" , $ decoded_json - > { isOnline } ) ;
readingsBulkUpdate ( $ hash , "voltage" ,
sprintf ( "%.2f" , $ decoded_json - > { voltage } ) ) ;
readingsBulkUpdate ( $ hash , "wifi_rssi" , $ decoded_json - > { wiFiRSSI } ) ;
readingsBulkUpdate ( $ hash , "wifi_apEnabled" ,
$ decoded_json - > { wiFiAPEnabled } ) ;
readingsBulkUpdate ( $ hash , "cell_rssi" , $ decoded_json - > { cellRSSI } ) ;
readingsBulkUpdate ( $ hash , "lastResponse" ,
'OK - getChargerState' , 1 ) ;
readingsEndUpdate ( $ hash , 1 ) ;
return ;
}
$ decoded_json = $ decoded_json - > [ 0 ] if ref ( $ decoded_json ) eq "ARRAY" ;
readingsSingleUpdate ( $ hash , "lastResponse" ,
'OK - Action: ' . $ commandCodes { $ decoded_json - > { commandId } } , 1 )
if defined $ decoded_json - > { commandId } ;
readingsSingleUpdate (
$ hash ,
"lastResponse" ,
'ERROR: '
. $ decoded_json - > { title } . ' ('
. $ decoded_json - > { status } . ')' ,
1
)
if defined $ decoded_json - > { status } and defined $ decoded_json - > { title } ;
return ;
}
else {
readingsSingleUpdate ( $ hash , "lastResponse" , 'OK - empty' , 1 ) ;
return ;
}
2022-03-19 23:03:46 +00:00
2022-03-18 21:44:22 +00:00
if ( $@ ) {
2022-03-21 09:13:34 +00:00
readingsSingleUpdate ( $ hash , "lastResponse" ,
'ERROR while deconding response: ' . $@ , 1 ) ;
2022-03-18 21:44:22 +00:00
Log3 $ name , 5 , 'Failure decoding: ' . $@ ;
2022-03-21 09:13:34 +00:00
}
2022-03-18 21:44:22 +00:00
return ;
2022-03-16 20:55:18 +00:00
}
2022-03-21 09:13:34 +00:00
sub Processing_DpointGetChargers {
my $ hash = shift ;
my $ decoded_json = shift ;
my $ site = $ decoded_json - > [ 0 ] ;
my $ circuit = $ site - > { circuits } - > [ 0 ] ;
my $ charger = $ circuit - > { chargers } - > [ 0 ] ;
readingsBeginUpdate ( $ hash ) ;
my $ chargerId = $ charger - > { id } ;
readingsBulkUpdate ( $ hash , "site_id" , $ site - > { id } ) ;
readingsBulkUpdate ( $ hash , "site_key" , $ site - > { siteKey } ) ;
readingsBulkUpdate ( $ hash , "charger_id" , $ chargerId ) ;
readingsBulkUpdate ( $ hash , "charger_name" , $ charger - > { name } ) ;
readingsBulkUpdate ( $ hash , "lastResponse" , 'OK - getReaders' , 1 ) ;
readingsEndUpdate ( $ hash , 1 ) ;
WriteToCloudAPI ( $ hash , 'getChargerConfiguration' , 'GET' ) ;
return ;
}
sub Processing_DpointGetChargerSessionsDaily {
my $ hash = shift ;
my $ decoded_json = shift ;
my $ name = $ hash - > { NAME } ;
Log3 $ name , 5 , 'Evaluating getChargerSessionsDaily' ;
my @ x = $ decoded_json ;
my @ a = ( - 5 .. - 1 ) ;
readingsBeginUpdate ( $ hash ) ;
for ( @ a ) {
Log3 $ name , 5 , 'laeuft noch: ' . $ _ ;
readingsBulkUpdate (
$ hash ,
"daily_" . ( $ _ + 1 ) . "_energy" ,
sprintf ( "%.2f" , $ decoded_json - > [ $ _ ] - > { 'totalEnergyUsage' } )
) ;
readingsBulkUpdate (
$ hash ,
"daily_" . ( $ _ + 1 ) . "_cost" ,
sprintf ( "%.2f" , $ decoded_json - > [ $ _ ] - > { 'totalCost' } )
) ;
}
readingsEndUpdate ( $ hash , 1 ) ;
return ;
}
2022-03-16 20:55:18 +00:00
2022-03-18 21:44:22 +00:00
sub _loadToken {
2022-03-16 18:57:21 +00:00
my $ hash = shift ;
my $ name = $ hash - > { NAME } ;
my $ tokenLifeTime = $ hash - > { TOKEN_LIFETIME } ;
$ tokenLifeTime = 0 if ( ! defined $ tokenLifeTime || $ tokenLifeTime eq '' ) ;
2022-03-21 09:13:34 +00:00
my $ token ;
2022-03-16 18:57:21 +00:00
2022-03-21 09:13:34 +00:00
$ token = $ hash - > { '.TOKEN' } ;
2022-03-16 18:57:21 +00:00
if ( $@ || $ tokenLifeTime < gettimeofday ( ) ) {
Log3 $ name , 5 ,
2022-03-21 09:13:34 +00:00
"EaseeWallbox $name" . ": "
. "Error while loading: $@ ,requesting new one"
if $@ ;
2022-03-16 18:57:21 +00:00
Log3 $ name , 5 ,
2022-03-21 09:13:34 +00:00
"EaseeWallbox $name" . ": " . "Token is expired, requesting new one"
if $ tokenLifeTime < gettimeofday ( ) ;
$ token = _newTokenRequest ( $ hash ) ;
2022-03-16 18:57:21 +00:00
}
else {
Log3 $ name , 5 ,
2022-03-21 09:13:34 +00:00
"EaseeWallbox $name" . ": "
. "Token expires at "
. localtime ( $ tokenLifeTime ) ;
2022-03-16 18:57:21 +00:00
# if token is about to expire, refresh him
2022-03-17 09:13:09 +00:00
if ( ( $ tokenLifeTime - 3700 ) < gettimeofday ( ) ) {
2022-03-16 18:57:21 +00:00
Log3 $ name , 5 ,
2022-03-21 09:13:34 +00:00
"EaseeWallbox $name" . ": "
. "Token will expire soon, refreshing" ;
$ token = _tokenRefresh ( $ hash ) ;
2022-03-16 18:57:21 +00:00
}
}
2022-03-21 09:13:34 +00:00
$ token = $ token ? $ token : undef ;
return $ token ;
2022-03-16 18:57:21 +00:00
}
2022-03-18 21:44:22 +00:00
sub _newTokenRequest {
2022-03-21 09:13:34 +00:00
my $ hash = shift ;
my $ name = $ hash - > { NAME } ;
my $ password = _decrypt ( InternalVal ( $ name , 'Password' , undef ) ) ;
2022-03-16 18:57:21 +00:00
my $ username = InternalVal ( $ name , 'Username' , undef ) ;
Log3 $ name , 5 , "EaseeWallbox $name" . ": " . "calling NewTokenRequest()" ;
my $ data = {
userName = > $ username ,
password = > $ password ,
} ;
my $ param = {
2022-03-18 21:44:22 +00:00
url = > $ hash - > { APIURI } . $ dpoints { getOAuthToken } ,
2022-03-16 18:57:21 +00:00
header = > { "Content-Type" = > "application/json" } ,
method = > 'POST' ,
timeout = > 5 ,
hash = > $ hash ,
data = > encode_json $ data
} ;
Log3 $ name , 5 , 'Request: ' . Dumper ( $ param ) ;
#Log3 $name, 5, 'Blocking GET: ' . Dumper($param);
#Log3 $name, $reqDebug, "EaseeWallbox $name" . ": " . "Request $AuthURL";
my ( $ err , $ returnData ) = HttpUtils_BlockingGet ( $ param ) ;
if ( $ err ne "" ) {
Log3 $ name , 3 ,
2022-03-21 09:13:34 +00:00
"EaseeWallbox $name" . ": "
. "NewTokenRequest: Error while requesting "
. $ param - > { url }
. " - $err" ;
2022-03-16 18:57:21 +00:00
}
elsif ( $ returnData ne "" ) {
Log3 $ name , 5 , "url " . $ param - > { url } . " returned: $returnData" ;
my $ decoded_data = eval { decode_json ( $ returnData ) } ;
if ( $@ ) {
Log3 $ name , 3 , "EaseeWallbox $name" . ": "
2022-03-21 09:13:34 +00:00
. "NewTokenRequest: decode_json failed, invalid json. error: $@ " ;
2022-03-16 18:57:21 +00:00
}
else {
#write token data in hash
if ( defined ( $ decoded_data ) ) {
$ hash - > { '.TOKEN' } = $ decoded_data ;
}
# token lifetime management
if ( defined ( $ decoded_data ) ) {
2022-03-21 09:13:34 +00:00
$ hash - > { TOKEN_LIFETIME } =
gettimeofday ( ) + $ decoded_data - > { 'expiresIn' } ;
2022-03-16 18:57:21 +00:00
}
$ hash - > { TOKEN_LIFETIME_HR } = localtime ( $ hash - > { TOKEN_LIFETIME } ) ;
Log3 $ name , 5 ,
2022-03-21 09:13:34 +00:00
"EaseeWallbox $name" . ": "
. "Retrived new authentication token successfully. Valid until "
. localtime ( $ hash - > { TOKEN_LIFETIME } ) ;
# $hash->{STATE} = "reachable"; # niemals $hash->{STATE} über demn Hash direkt zuweisen
readingsSingleUpdate ( $ hash , 'state' , 'reachable' , 1 ) ;
2022-03-16 18:57:21 +00:00
return $ decoded_data ;
}
}
return ;
}
2022-03-18 21:44:22 +00:00
sub _tokenRefresh {
2022-03-16 18:57:21 +00:00
my $ hash = shift ;
my $ name = $ hash - > { NAME } ;
my $ Token = undef ;
# load token
$ Token = $ hash - > { '.TOKEN' } ;
my $ data = {
accessToken = > $ Token - > { 'accessToken' } ,
refreshToken = > $ Token - > { 'refreshToken' }
} ;
my $ param = {
2022-03-18 21:44:22 +00:00
url = > $ hash - > { APIURI } . $ dpoints { getRefreshToken } ,
2022-03-16 18:57:21 +00:00
header = > { "Content-Type" = > "application/json" } ,
method = > 'POST' ,
timeout = > 5 ,
hash = > $ hash ,
data = > encode_json $ data
} ;
Log3 $ name , 5 , 'Request: ' . Dumper ( $ param ) ;
#Log3 $name, 5, 'Blocking GET TokenRefresh: ' . Dumper($param);
#Log3 $name, $reqDebug, "EaseeWallbox $name" . ": " . "Request $AuthURL";
my ( $ err , $ returnData ) = HttpUtils_BlockingGet ( $ param ) ;
if ( $ err ne "" ) {
Log3 $ name , 3 ,
2022-03-21 09:13:34 +00:00
"EaseeWallbox $name" . ": "
. "TokenRefresh: Error in token retrival while requesting "
. $ param - > { url }
. " - $err" ;
# $hash->{STATE} = "error";
readingsSingleUpdate ( $ hash , 'state' , 'error' , 1 ) ;
2022-03-16 18:57:21 +00:00
}
elsif ( $ returnData ne "" ) {
Log3 $ name , 5 , "url " . $ param - > { url } . " returned: $returnData" ;
my $ decoded_data = eval { decode_json ( $ returnData ) ; } ;
if ( $@ ) {
Log3 $ name , 3 ,
2022-03-21 09:13:34 +00:00
"EaseeWallbox $name" . ": "
. "TokenRefresh: decode_json failed, invalid json. error:$@\n"
if $@ ;
# $hash->{STATE} = "error";
readingsSingleUpdate ( $ hash , 'state' , 'error' , 1 ) ;
2022-03-16 18:57:21 +00:00
}
else {
#write token data in file
if ( defined ( $ decoded_data ) ) {
$ hash - > { '.TOKEN' } = $ decoded_data ;
}
# token lifetime management
2022-03-21 09:13:34 +00:00
$ hash - > { TOKEN_LIFETIME } =
gettimeofday ( ) + $ decoded_data - > { 'expires_in' } ;
2022-03-16 18:57:21 +00:00
$ hash - > { TOKEN_LIFETIME_HR } = localtime ( $ hash - > { TOKEN_LIFETIME } ) ;
Log3 $ name , 5 ,
2022-03-21 09:13:34 +00:00
"EaseeWallbox $name" . ": "
. "TokenRefresh: Refreshed authentication token successfully. Valid until "
. localtime ( $ hash - > { TOKEN_LIFETIME } ) ;
# $hash->{STATE} = "reachable";
readingsSingleUpdate ( $ hash , 'state' , 'reachable' , 1 ) ;
2022-03-16 18:57:21 +00:00
return $ decoded_data ;
}
}
return ;
}
2022-03-21 09:13:34 +00:00
sub _encrypt {
2022-03-16 18:57:21 +00:00
my ( $ decoded ) = @ _ ;
my $ key = getUniqueId ( ) ;
my $ encoded ;
2022-03-21 09:13:34 +00:00
return $ decoded
if ( $ decoded =~ /crypt:/x )
; # Regular expression without "/x" flag. See page 236 of PBP (RegularExpressions::RequireExtendedFormatting)
2022-03-16 18:57:21 +00:00
for my $ char ( split // , $ decoded ) {
my $ encode = chop ( $ key ) ;
$ encoded . = sprintf ( "%.2x" , ord ( $ char ) ^ ord ( $ encode ) ) ;
$ key = $ encode . $ key ;
}
return 'crypt:' . $ encoded ;
}
2022-03-21 09:13:34 +00:00
sub _decrypt {
2022-03-16 18:57:21 +00:00
my ( $ encoded ) = @ _ ;
my $ key = getUniqueId ( ) ;
my $ decoded ;
2022-03-21 09:13:34 +00:00
return $ encoded
if ( $ encoded !~ /crypt:/x )
; # Regular expression without "/x" flag. See page 236 of PBP (RegularExpressions::RequireExtendedFormatting)
2022-03-16 18:57:21 +00:00
2022-03-21 09:13:34 +00:00
$ encoded = $ 1
if ( $ encoded =~ /crypt:(.*)/x )
; # Regular expression without "/x" flag. See page 236 of PBP (RegularExpressions::RequireExtendedFormatting)
2022-03-16 18:57:21 +00:00
2022-03-21 09:13:34 +00:00
for my $ char ( map { pack ( 'C' , hex ( $ _ ) ) } ( $ encoded =~ /(..)/xg ) )
{ # Regular expression without "/x" flag. See page 236 of PBP (RegularExpressions::RequireExtendedFormatting)
2022-03-16 18:57:21 +00:00
my $ decode = chop ( $ key ) ;
$ decoded . = chr ( ord ( $ char ) ^ ord ( $ decode ) ) ;
$ key = $ decode . $ key ;
}
return $ decoded ;
}
1 ;
2022-03-21 09:13:34 +00:00
sub _transcodeDate {
my $ datestr = shift ;
2022-03-20 19:48:37 +00:00
Log3 'EaseeWallbox' , 5 , 'date to parse: ' . $ datestr ;
2022-03-21 09:13:34 +00:00
my $ strp = DateTime::Format::Strptime - > new (
on_error = > 'croak' ,
pattern = > '%Y-%m-%dT%H:%M:%S%z'
) ;
2022-03-19 13:44:14 +00:00
my $ dt = $ strp - > parse_datetime ( $ datestr ) ;
$ dt - > set_time_zone ( 'Europe/Berlin' ) ;
2022-03-21 09:13:34 +00:00
2022-03-19 13:44:14 +00:00
return $ dt - > strftime ( '%Y-%m-%d %H:%M:%S' ) ;
}
2022-03-18 21:44:22 +00:00
2022-03-21 09:13:34 +00:00
1 ; # Ein Modul muss immer mit 1; enden
2022-03-18 21:44:22 +00:00
2022-03-16 18:57:21 +00:00
= pod
= begin html
= end html
= cut