2022-03-16 18:57:21 +00:00
package main ;
use strict ;
use warnings ;
use Data::Dumper ;
use utf8 ;
use Encode qw( encode_utf8 ) ;
use HttpUtils ;
use JSON ;
my % EaseeWallbox_gets = (
update = > "noArg" ,
health = > "noArg" ,
2022-03-16 20:27:34 +00:00
baseData = > "noArg" ,
2022-03-16 18:57:21 +00:00
chargers = > "noArg" ,
sites = > "noArg" ,
profile = > "noArg" ,
config = > "noArg" ,
) ;
my % EaseeWallbox_sets = (
startCharging = > "" ,
stopCharging = > "" ,
pauseCharging = > "" ,
resumeCharging = > "" ,
toggleCharging = > "" ,
interval = > "" ,
refreshToken = > "noArg" ,
cableLock = > "true,false" ,
reboot = > "noArg" ,
updateFirmware = > "noArg" ,
enableSmartCharging = > "true,false" ,
overrideChargingSchedule = > "" ,
pairRfidTag = > "" ,
pricePerKWH = > ""
) ;
my % url = (
getOAuthToken = > 'https://api.easee.cloud/api/accounts/login' ,
getRefreshToken = > 'https://api.easee.cloud/api/accounts/refresh_token' ,
getProfile = > 'https://api.easee.cloud/api/accounts/profile' ,
getChargingSession = >
'https://api.easee.cloud/api/chargers/#ChargerID#/sessions/ongoing' ,
getChargers = > 'https://api.easee.cloud/api/accounts/chargers' ,
getProducts = >
'https://api.easee.cloud/api/accounts/products?userId=#UserId#' ,
getChargerSite = > 'https://api.easee.cloud/api/chargers/#ChargerID#/site' ,
getChargerDetails = >
'https://api.easee.cloud/api/chargers/#ChargerID#/details' ,
getChargerConfiguration = >
'https://api.easee.cloud/api/chargers/#ChargerID#/config' ,
getChargerSessionsMonthly = >
'https://api.easee.cloud/api/sessions/charger/#ChargerID#/monthly' ,
getChargerSessionsDaily = >
'https://api.easee.cloud/api/sessions/charger/#ChargerID#/daily' ,
getChargerState = >
'https://api.easee.cloud/api/chargers/#ChargerID#/state' ,
getCurrentSession = >
'https://api.easee.cloud/api/chargers/#ChargerID#/sessions/ongoing' ,
setCableLockState = >
'https://api.easee.cloud/api/chargers/#ChargerID#/commands/lock_state' ,
setReboot = >
'https://api.easee.cloud/api/chargers/#ChargerID#/commands/reboot' ,
setUpdateFirmware = >
'https://api.easee.cloud/api/chargers/#ChargerID#/commands/update_firmware' ,
setEnableSmartCharging = >
'https://api.easee.cloud/api/chargers/#ChargerID#/commands/smart_charging' ,
setStartCharging = >
'https://api.easee.cloud/api/chargers/#ChargerID#/commands/start_charging' ,
setStopCharging = >
'https://api.easee.cloud/api/chargers/#ChargerID#/commands/stop_charging' ,
setPauseCharging = >
'https://api.easee.cloud/api/chargers/#ChargerID#/commands/pause_charging' ,
setResumeCharging = >
'https://api.easee.cloud/api/chargers/#ChargerID#/commands/resume_charging' ,
setToggleCharging = >
'https://api.easee.cloud/api/chargers/#ChargerID#/commands/toggle_charging' ,
setOverrideChargingSchedule = >
'https://api.easee.cloud/api/chargers/#ChargerID#/commands/override_schedule' ,
setPairRFIDTag = >
'https://api.easee.cloud/api/chargers/#ChargerID#/commands/set_rfid_pairing_mode_async' ,
changeChargerSettings = >
'https://api.easee.cloud/api/chargers/#ChargerID#/settings' ,
setChargingPrice = > 'https://api.easee.cloud/api/sites/#SiteID#/price' ,
) ;
my % reasonForNoCurrent = (
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)
7 = > 'IllegalGridType' ,
8 = > 'PrimaryUnitHasNotReceivedCurrentRequestFromSecondaryUnit' ,
50 = > 'SecondaryUnitNotRequestingCurrent' , #no car connected...
51 = > 'MaxChargerCurrentTooLow' ,
52 = > 'MaxDynamicChargerCurrentTooLow' ,
53 = > 'ChargerDisabled' ,
54 = > 'PendingScheduledCharging' ,
55 = > 'PendingAuthorization' ,
56 = > 'ChargerInErrorState' ,
100 = > 'Undefined'
) ;
my % phaseMode = (
1 = > 'Locked to single phase' ,
2 = > 'Auto' ,
3 = > 'Locked to three phase' ,
) ;
my % operationMode = (
1 = > "Standby" ,
2 = > "Paused" ,
3 = > 'Charging' ,
4 = > 'Completed' ,
5 = > 'Error' ,
6 = > 'CarConnected'
) ;
#Private function to evaluate command-lists
#############################
sub EaseeWallbox_getCmdList ($$$) {
my ( $ hash , $ cmd , $ commands ) = @ _ ;
my % cmdArray = %$ commands ;
my $ name = $ hash - > { NAME } ;
#return, if cmd is valid
return undef if ( defined ( $ cmd ) and defined ( $ cmdArray { $ cmd } ) ) ;
#response for gui or the user, if command is invalid
my $ retVal ;
foreach my $ mySet ( keys % cmdArray ) {
#append set-command
$ retVal = $ retVal . " " if ( defined ( $ retVal ) ) ;
$ retVal = $ retVal . $ mySet ;
#get options
my $ myOpt = $ cmdArray { $ mySet } ;
#append option, if valid
$ retVal = $ retVal . ":" . $ myOpt
if ( defined ( $ myOpt ) and ( length ( $ myOpt ) > 0 ) ) ;
$ myOpt = "" if ( ! defined ( $ myOpt ) ) ;
2022-03-16 19:50:50 +00:00
#Logging makes me crazy...
#Log3 ($name, 5, "parse cmd-table - Set:$mySet, Option:$myOpt, RetVal:$retVal");
2022-03-16 18:57:21 +00:00
}
if ( ! defined ( $ retVal ) ) {
$ retVal = "error while parsing set-table" ;
}
else {
$ retVal = "Unknown argument $cmd, choose one of " . $ retVal ;
}
return $ retVal ;
}
sub EaseeWallbox_Initialize ($) {
my ( $ hash ) = @ _ ;
$ hash - > { DefFn } = 'EaseeWallbox_Define' ;
$ hash - > { UndefFn } = 'EaseeWallbox_Undef' ;
$ hash - > { SetFn } = 'EaseeWallbox_Set' ;
$ hash - > { GetFn } = 'EaseeWallbox_Get' ;
$ hash - > { AttrFn } = 'EaseeWallbox_Attr' ;
$ hash - > { ReadFn } = 'EaseeWallbox_Read' ;
$ hash - > { WriteFn } = 'EaseeWallbox_Write' ;
$ hash - > { Clients } = ':EaseeWallbox:' ;
$ hash - > { MatchList } = { '1:EaseeWallbox' = > '^EaseeWallbox;.*' } ;
$ hash - > { AttrList }
2022-03-16 19:50:50 +00:00
= 'expertMode:yes,no '
. 'ledStuff:yes,no '
. 'SmartCharging:true,false '
2022-03-16 18:57:21 +00:00
. $ readingFnAttributes ;
Log 3 , "EaseeWallbox module initialized." ;
}
sub EaseeWallbox_Define ($$) {
my ( $ hash , $ def ) = @ _ ;
my @ param = split ( "[ \t]+" , $ def ) ;
my $ name = $ hash - > { NAME } ;
Log3 $ name , 3 , "EaseeWallbox_Define $name: called " ;
my $ errmsg = '' ;
2022-03-16 19:50:50 +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
"syntax error: define <name> EaseeWallbox <username> <password> [Interval]" ;
Log3 $ name , 1 , "EaseeWallbox $name: " . $ errmsg ;
return $ errmsg ;
}
#Check if the username is an email address
if ( $ param [ 2 ] =~ /^.+@.+$/ ) {
my $ username = $ param [ 2 ] ;
$ hash - > { Username } = $ username ;
}
else {
$ errmsg
= "specify valid email address within the field username. Format: define <name> EaseeWallbox <username> <password> [interval]" ;
Log3 $ name , 1 , "EaseeWallbox $name: " . $ errmsg ;
return $ errmsg ;
}
#Take password and use custom encryption.
# Encryption is taken from fitbit / withings module
my $ password = EaseeWallbox_encrypt ( $ param [ 3 ] ) ;
$ 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 ] ) {
if ( $ param [ 4 ] =~ /^\d+$/ ) {
$ interval = $ param [ 4 ] ;
}
else {
$ errmsg
= "Specify valid integer value for interval. Whole numbers > 5 only. Format: define <name> EaseeWallbox <username> <password> [interval]" ;
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
EaseeWallbox_RefreshData ( $ hash ) ;
##RemoveInternalTimer($hash);
2022-03-16 19:50:50 +00:00
#Call getZones with delay of 15 seconds, as all devices need to be loaded before timer triggers.
#Otherwise some error messages are generated due to auto created devices...
##InternalTimer(gettimeofday()+15, "EaseeWallbox_GetZones", $hash) if (defined $hash);
2022-03-16 18:57:21 +00:00
##Log3 $name, 1, sprintf("EaseeWallbox_Define %s: Starting timer with interval %s", $name, InternalVal($name,'INTERVAL', undef));
##InternalTimer(gettimeofday()+ InternalVal($name,'INTERVAL', undef), "EaseeWallbox_UpdateDueToTimer", $hash) if (defined $hash);
return undef ;
}
sub EaseeWallbox_Undef ($$) {
my ( $ hash , $ arg ) = @ _ ;
RemoveInternalTimer ( $ hash ) ;
return undef ;
}
2022-03-16 19:50:50 +00:00
sub EaseeWallbox_Get ($@) {
my ( $ hash , $ name , @ args ) = @ _ ;
return '"get EaseeWallbox" needs at least one argument'
if ( int ( @ args ) < 1 ) ;
my $ opt = shift @ args ;
#create response, if cmd is wrong or gui asks
my $ cmdTemp = EaseeWallbox_getCmdList ( $ hash , $ opt , \ % EaseeWallbox_gets ) ;
return $ cmdTemp if ( defined ( $ cmdTemp ) ) ;
my $ cmd = $ args [ 0 ] ;
my $ arg = $ args [ 1 ] ;
2022-03-16 20:27:34 +00:00
if ( $ opt eq 'baseData' ) {
EaseeWallbox_GetChargers ( $ hash ) ;
EaseeWallbox_GetChargerConfig ( $ hash ) ;
EaseeWallbox_GetChargerSite ( $ hash ) ;
EaseeWallbox_RefreshData ( $ hash ) ;
2022-03-16 19:50:50 +00:00
2022-03-16 20:27:34 +00:00
} elsif ( $ opt eq "update" ) {
2022-03-16 19:50:50 +00:00
Log3 $ name , 3 , "EaseeWallbox_Get $name: Updating all data" ;
$ hash - > { LOCAL } = 1 ;
EaseeWallbox_RequestChargerState ( $ hash ) ;
EaseeWallbox_RequestCurrentSession ( $ hash ) ;
delete $ hash - > { LOCAL } ;
2022-03-16 20:27:34 +00:00
return undef ;
} else {
return EaseeWallbox_GetChargers ( $ hash ) if $ opt eq "chargers" ;
return EaseeWallbox_RefreshData ( $ hash ) if $ opt eq "profile" ;
return EaseeWallbox_GetChargerConfig ( $ hash ) if $ opt eq "config" ;
return EaseeWallbox_GetChargerSite ( $ hash ) if $ opt eq "sites" ;
2022-03-16 19:50:50 +00:00
}
}
sub EaseeWallbox_Set ($@) {
my ( $ hash , $ name , @ param ) = @ _ ;
return '"set $name" needs at least one argument' if ( int ( @ param ) < 1 ) ;
my $ opt = shift @ param ;
my $ value = join ( "" , @ param ) ;
#create response, if cmd is wrong or gui asks
my $ cmdTemp = EaseeWallbox_getCmdList ( $ hash , $ opt , \ % EaseeWallbox_sets ) ;
return $ cmdTemp if ( defined ( $ cmdTemp ) ) ;
2022-03-16 20:18:24 +00:00
if ( $ opt eq "stop" ) {
2022-03-16 19:50:50 +00:00
RemoveInternalTimer ( $ hash ) ;
Log3 $ name , 1 ,
"EaseeWallbox_Set $name: Stopped the timer to automatically update readings" ;
readingsSingleUpdate ( $ hash , 'state' , 'Initialized' , 0 ) ;
return undef ;
}
elsif ( $ opt eq "interval" ) {
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-16 20:18:24 +00:00
} else {
$ hash - > { LOCAL } = 1 ;
EaseeWallbox_ExecuteParameterlessCommand ( $ hash , "setStartCharging" ) if $ opt eq "startCharging" ;
EaseeWallbox_ExecuteParameterlessCommand ( $ hash , "setStopCharging" ) if $ opt eq 'stopCharging' ;
EaseeWallbox_ExecuteParameterlessCommand ( $ hash , "setPauseCharging" ) if $ opt eq 'pauseCharging' ;
EaseeWallbox_ExecuteParameterlessCommand ( $ hash , "setResumeCharging" ) if $ opt eq 'resumeCharging' ;
EaseeWallbox_ExecuteParameterlessCommand ( $ hash , "setToggleCharging" ) if $ opt eq 'toggleCharging' ;
EaseeWallbox_ExecuteParameterlessCommand ( $ hash , "setUpdateFirmware" ) if $ opt eq 'updateFirmware' ;
EaseeWallbox_ExecuteParameterlessCommand ( $ hash , "setOverrideChargingSchedule" ) if $ opt eq 'overrideChargingSchedule' ;
EaseeWallbox_ExecuteParameterlessCommand ( $ hash , "setPairRFIDTag" ) if $ opt eq 'pairRfidTag' ;
EaseeWallbox_ExecuteParameterlessCommand ( $ hash , "setReboot" ) if $ opt eq 'reboot' ;
EaseeWallbox_ExecuteParameterlessCommand ( $ hash , "toBeDone" ) if $ opt eq 'enableSmartCharging' ;
EaseeWallbox_SetCableLock ( $ hash , shift @ param ) if $ opt eq 'cableLock' ;
EaseeWallbox_SetPrice ( $ hash , shift @ param ) if $ opt eq 'pricePerKWH' ;
EaseeWallbox_LoadToken ( $ hash ) if $ opt eq 'refreshToken' ;
delete $ hash - > { LOCAL } ;
2022-03-16 19:50:50 +00:00
}
readingsSingleUpdate ( $ hash , 'state' , 'Initialized' , 0 ) ;
return undef ;
}
2022-03-16 18:57:21 +00:00
sub EaseeWallbox_LoadToken {
my $ hash = shift ;
my $ name = $ hash - > { NAME } ;
my $ tokenLifeTime = $ hash - > { TOKEN_LIFETIME } ;
$ tokenLifeTime = 0 if ( ! defined $ tokenLifeTime || $ tokenLifeTime eq '' ) ;
my $ Token = undef ;
$ Token = $ hash - > { '.TOKEN' } ;
if ( $@ || $ tokenLifeTime < gettimeofday ( ) ) {
Log3 $ name , 5 ,
"EaseeWallbox $name" . ": "
. "Error while loading: $@ ,requesting new one"
if $@ ;
Log3 $ name , 5 ,
"EaseeWallbox $name" . ": "
. "Token is expired, requesting new one"
if $ tokenLifeTime < gettimeofday ( ) ;
$ Token = EaseeWallbox_NewTokenRequest ( $ hash ) ;
}
else {
Log3 $ name , 5 ,
"EaseeWallbox $name" . ": "
. "Token expires at "
. localtime ( $ tokenLifeTime ) ;
# if token is about to expire, refresh him
if ( ( $ tokenLifeTime - 45 ) < gettimeofday ( ) ) {
Log3 $ name , 5 ,
"EaseeWallbox $name" . ": "
. "Token will expire soon, refreshing" ;
$ Token = EaseeWallbox_TokenRefresh ( $ hash ) ;
}
}
return $ Token if $ Token ;
}
sub EaseeWallbox_NewTokenRequest {
my $ hash = shift ;
my $ name = $ hash - > { NAME } ;
my $ password
= EaseeWallbox_decrypt ( InternalVal ( $ name , 'Password' , undef ) ) ;
my $ username = InternalVal ( $ name , 'Username' , undef ) ;
Log3 $ name , 5 , "EaseeWallbox $name" . ": " . "calling NewTokenRequest()" ;
my $ data = {
userName = > $ username ,
password = > $ password ,
} ;
my $ param = {
url = > $ url { getOAuthToken } ,
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 ,
"EaseeWallbox $name" . ": "
. "NewTokenRequest: Error while requesting "
. $ param - > { url }
. " - $err" ;
}
elsif ( $ returnData ne "" ) {
Log3 $ name , 5 , "url " . $ param - > { url } . " returned: $returnData" ;
my $ decoded_data = eval { decode_json ( $ returnData ) } ;
if ( $@ ) {
Log3 $ name , 3 , "EaseeWallbox $name" . ": "
. "NewTokenRequest: decode_json failed, invalid json. error: $@ " ;
}
else {
#write token data in hash
if ( defined ( $ decoded_data ) ) {
$ hash - > { '.TOKEN' } = $ decoded_data ;
}
# token lifetime management
if ( defined ( $ decoded_data ) ) {
$ hash - > { TOKEN_LIFETIME }
= gettimeofday ( ) + $ decoded_data - > { 'expires_in' } ;
}
$ hash - > { TOKEN_LIFETIME_HR } = localtime ( $ hash - > { TOKEN_LIFETIME } ) ;
Log3 $ name , 5 ,
"EaseeWallbox $name" . ": "
. "Retrived new authentication token successfully. Valid until "
. localtime ( $ hash - > { TOKEN_LIFETIME } ) ;
$ hash - > { STATE } = "reachable" ;
return $ decoded_data ;
}
}
return ;
}
sub EaseeWallbox_TokenRefresh {
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 = {
url = > $ url { getRefreshToken } ,
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 ,
"EaseeWallbox $name" . ": "
. "TokenRefresh: Error in token retrival while requesting "
. $ param - > { url }
. " - $err" ;
$ hash - > { STATE } = "error" ;
}
elsif ( $ returnData ne "" ) {
Log3 $ name , 5 , "url " . $ param - > { url } . " returned: $returnData" ;
my $ decoded_data = eval { decode_json ( $ returnData ) ; } ;
if ( $@ ) {
Log3 $ name , 3 ,
"EaseeWallbox $name" . ": "
. "TokenRefresh: decode_json failed, invalid json. error:$@\n"
if $@ ;
$ hash - > { STATE } = "error" ;
}
else {
#write token data in file
if ( defined ( $ decoded_data ) ) {
$ hash - > { '.TOKEN' } = $ decoded_data ;
}
# token lifetime management
$ hash - > { TOKEN_LIFETIME }
= gettimeofday ( ) + $ decoded_data - > { 'expires_in' } ;
$ hash - > { TOKEN_LIFETIME_HR } = localtime ( $ hash - > { TOKEN_LIFETIME } ) ;
Log3 $ name , 5 ,
"EaseeWallbox $name" . ": "
. "TokenRefresh: Refreshed authentication token successfully. Valid until "
. localtime ( $ hash - > { TOKEN_LIFETIME } ) ;
$ hash - > { STATE } = "reachable" ;
return $ decoded_data ;
}
}
return ;
}
sub EaseeWallbox_httpSimpleOperationOAuth ($$$;$) {
my ( $ hash , $ url , $ operation , $ message ) = @ _ ;
my ( $ json , $ err , $ data , $ decoded ) ;
my $ name = $ hash - > { NAME } ;
my $ CurrentTokenData = EaseeWallbox_LoadToken ( $ hash ) ;
Log3 $ name , 3 ,
"$CurrentTokenData->{'tokenType'} $CurrentTokenData->{'accessToken'}" ;
my $ request = {
url = > $ url ,
header = > {
"Content-Type" = > "application/json;charset=UTF-8" ,
"Authorization" = >
"$CurrentTokenData->{'tokenType'} $CurrentTokenData->{'accessToken'}"
} ,
method = > $ operation ,
timeout = > 6 ,
hideurl = > 1
} ;
$ request - > { data } = $ message if ( defined $ message ) ;
Log3 $ name , 5 , 'Request: ' . Dumper ( $ request ) ;
( $ err , $ data ) = HttpUtils_BlockingGet ( $ request ) ;
$ json = "" if ( ! $ json ) ;
$ data = "" if ( ! $ data ) ;
Log3 $ name , 4 , "FHEM -> EaseeWallbox: " . $ url ;
Log3 $ name , 4 , "FHEM -> EaseeWallbox: " . $ message
if ( defined $ message ) ;
Log3 $ name , 4 , "EaseeWallbox -> FHEM: " . $ data if ( defined $ data ) ;
Log3 $ name , 4 , "EaseeWallbox -> FHEM: Got empty response."
if ( not defined $ data ) ;
Log3 $ name , 5 , '$err: ' . $ err ;
Log3 $ name , 5 , "method: " . $ operation ;
Log3 $ name , 2 , "Something gone wrong"
if ( $ data =~ "/EaseeWallboxMode/" ) ;
$ err = 1 if ( $ data =~ "/EaseeWallboxMode/" ) ;
if ( defined $ data and ( not $ data eq '' ) and $ operation ne 'DELETE' ) {
eval {
$ decoded = decode_json ( $ data ) if ( ! $ err ) ;
Log3 $ name , 5 , 'Decoded: ' . Dumper ( $ decoded ) ;
return $ decoded ;
} or do {
Log3 $ name , 5 , 'Failure decoding: ' . $@ ;
}
}
else {
return undef ;
}
}
sub EaseeWallbox_ExecuteParameterlessCommand ($$) {
my ( $ hash , $ template ) = @ _ ;
2022-03-16 19:50:50 +00:00
EaseeWallbox_ExecuteCommand ( $ hash , 'POST' , $ template , undef )
}
sub EaseeWallbox_ExecuteCommand ($@) {
my ( $ hash , $ method , $ template , $ message ) = @ _ ;
my $ name = $ hash - > { NAME } ;
my $ urlTemplate = $ url { $ template } ;
2022-03-16 18:57:21 +00:00
if ( not defined $ hash ) {
Log3 'EaseeWallbox' , 1 ,
"Error on EaseeWallbox_ExecuteCommand. Missing hash variable" ;
return undef ;
}
2022-03-16 19:50:50 +00:00
#Check if chargerID is required in URL and replace or alert.
if ( $ urlTemplate =~ m/#ChargerID#/ ) {
my $ chargerId = ReadingsVal ( $ name , 'charger_id' , undef ) ;
if ( not defined $ chargerId ) {
Log3 'EaseeWallbox' , 1 ,
"Error on EaseeWallbox_ExecuteCommand. Missing charger_id. Please ensure basic data is available." ;
return undef ;
}
$ urlTemplate =~ s/#ChargerID#/$chargerId/g ;
2022-03-16 18:57:21 +00:00
}
2022-03-16 19:50:50 +00:00
#Check if siteID is required in URL and replace or alert.
if ( $ urlTemplate =~ m/#SiteID#/ ) {
my $ siteId = ReadingsVal ( $ name , 'site_id' , undef ) ;
if ( not defined $ siteId ) {
Log3 'EaseeWallbox' , 1 ,
"Error on EaseeWallbox_ExecuteCommand. Missing site_id. Please ensure basic data is available." ;
return undef ;
}
$ urlTemplate =~ s/#SiteID#/$siteId/g ;
}
2022-03-16 18:57:21 +00:00
2022-03-16 19:50:50 +00:00
Log3 $ name , 4 , "EaseeWallbox_ExecuteCommand will call Easee API for blocking value update. Name: $name" ;
my $ d = EaseeWallbox_httpSimpleOperationOAuth ( $ hash , $ urlTemplate , $ method , encode_json \ %$ message ) ;
2022-03-16 18:57:21 +00:00
}
sub EaseeWallbox_SetCableLock ($$) {
my ( $ hash , $ value ) = @ _ ;
my % message ;
$ message { 'state' } = $ value ;
2022-03-16 19:50:50 +00:00
EaseeWallbox_ExecuteCommand ( $ hash , "POST" , "setCableLockState" , \ % message ) ;
2022-03-16 18:57:21 +00:00
}
sub EaseeWallbox_SetPrice ($$) {
my ( $ hash , $ value ) = @ _ ;
my % message ;
$ message { 'currencyId' } = "EUR" ;
$ message { 'vat' } = "19" ;
$ message { 'costPerKWh' } = $ value ;
2022-03-16 19:50:50 +00:00
EaseeWallbox_ExecuteCommand ( $ hash , "POST" , "setChargingPrice" , \ % message ) ;
2022-03-16 18:57:21 +00:00
}
sub EaseeWallbox_Attr (@) {
return undef ;
}
sub EaseeWallbox_GetChargers ($) {
my ( $ hash ) = @ _ ;
my $ name = $ hash - > { NAME } ;
if ( not defined $ hash ) {
my $ msg = "Error on EaseeWallbox_GetChargers. Missing hash variable" ;
Log3 'EaseeWallbox' , 1 , $ msg ;
return $ msg ;
}
my $ readTemplate = $ url { "getChargers" } ;
my $ d = EaseeWallbox_httpSimpleOperationOAuth ( $ hash , $ readTemplate ,
'GET' ) ;
if ( defined $ d && ref ( $ d ) eq "HASH" && defined $ d - > { errors } ) {
log 1 , Dumper $ d ;
readingsSingleUpdate ( $ hash ,
"Error: $d->{errors}[0]->{code} / $d->{errors}[0]->{title}" ,
'Undefined' , 1 ) ;
return undef ;
}
else {
readingsBeginUpdate ( $ hash ) ;
my $ site = $ d - > [ 0 ] ;
my $ circuit = $ site - > { circuits } - > [ 0 ] ;
my $ charger = $ circuit - > { chargers } - > [ 0 ] ;
readingsBeginUpdate ( $ hash ) ;
my $ chargerId = $ charger - > { id } ;
readingsBulkUpdate ( $ hash , "charger_id" , $ chargerId ) ;
readingsBulkUpdate ( $ hash , "charger_name" , $ charger - > { name } ) ;
readingsBulkUpdate ( $ hash , "charger_isTemporary" ,
$ charger - > { isTemporary } ) ;
readingsBulkUpdate ( $ hash , "charger_createdOn" ,
$ charger - > { createdOn } ) ;
readingsEndUpdate ( $ hash , 1 ) ;
$ readTemplate = $ url { "getChargerDetails" } ;
$ readTemplate =~ s/#ChargerID#/$chargerId/g ;
$ d = EaseeWallbox_httpSimpleOperationOAuth ( $ hash , $ readTemplate ,
'GET' ) ;
if ( defined $ d && ref ( $ d ) eq "HASH" && defined $ d - > { errors } ) {
log 1 , Dumper $ d ;
readingsSingleUpdate ( $ hash ,
"Error: $d->{errors}[0]->{code} / $d->{errors}[0]->{title}" ,
'Undefined' , 1 ) ;
return undef ;
}
else {
readingsBeginUpdate ( $ hash ) ;
readingsBulkUpdate ( $ hash , "charger_product" , $ d - > { product } ) ;
readingsBulkUpdate ( $ hash , "charger_pincode" , $ d - > { pinCode } ) ;
readingsBulkUpdate ( $ hash , "charger_unitType" , $ d - > { unitType } ) ;
readingsEndUpdate ( $ hash , 1 ) ;
}
return undef ;
}
}
sub EaseeWallbox_GetChargerConfig ($) {
my ( $ hash ) = @ _ ;
my $ name = $ hash - > { NAME } ;
my $ chargerId = ReadingsVal ( $ name , "charger_id" , undef ) ;
if ( not defined $ chargerId ) {
my $ msg
= "Error on EaseeWallbox_GetDevices. Missing Charger ID. Please get Chargers first." ;
Log3 'EaseeWallbox' , 1 , $ msg ;
return $ msg ;
}
my $ readTemplate = $ url { "getChargerConfiguration" } ;
$ readTemplate =~ s/#ChargerID#/$chargerId/g ;
my $ d = EaseeWallbox_httpSimpleOperationOAuth ( $ hash , $ readTemplate ,
'GET' ) ;
if ( defined $ d && ref ( $ d ) eq "HASH" && defined $ d - > { errors } ) {
log 1 , Dumper $ d ;
readingsSingleUpdate ( $ hash , 'state' ,
"Error: $d->{errors}[0]->{code} / $d->{errors}[0]->{title}" , 1 ) ;
return undef ;
}
else {
readingsBeginUpdate ( $ hash ) ;
readingsBulkUpdate ( $ hash , "charger_isEnabled" , $ d - > { isEnabled } ) ;
readingsBulkUpdate (
$ hash ,
"charger_lockCablePermanently" ,
$ d - > { lockCablePermanently }
) ;
readingsBulkUpdate (
$ hash ,
"charger_authorizationRequired" ,
$ d - > { authorizationRequired }
) ;
readingsBulkUpdate ( $ hash , "charger_remoteStartRequired" ,
$ d - > { remoteStartRequired } ) ;
readingsBulkUpdate ( $ hash , "charger_smartButtonEnabled" ,
$ d - > { smartButtonEnabled } ) ;
readingsBulkUpdate ( $ hash , "charger_wiFiSSID" , $ d - > { wiFiSSID } ) ;
readingsBulkUpdate ( $ hash , "charger_offlineChargingMode" ,
$ d - > { offlineChargingMode } ) ;
readingsBulkUpdate ( $ hash , "charger_circuitMaxCurrentP1" ,
$ d - > { circuitMaxCurrentP1 } ) ;
readingsBulkUpdate ( $ hash , "charger_circuitMaxCurrentP2" ,
$ d - > { circuitMaxCurrentP2 } ) ;
readingsBulkUpdate ( $ hash , "charger_circuitMaxCurrentP3" ,
$ d - > { circuitMaxCurrentP3 } ) ;
readingsBulkUpdate ( $ hash , "charger_enableIdleCurrent" ,
$ d - > { enableIdleCurrent } ) ;
readingsBulkUpdate (
$ hash ,
"charger_limitToSinglePhaseCharging" ,
$ d - > { limitToSinglePhaseCharging }
) ;
readingsBulkUpdate ( $ hash , "charger_phaseModeId" , $ d - > { phaseMode } ) ;
readingsBulkUpdate ( $ hash , "charger_phaseMode" ,
$ phaseMode { $ d - > { phaseMode } } ) ;
readingsBulkUpdate ( $ hash , "charger_localNodeType" ,
$ d - > { localNodeType } ) ;
readingsBulkUpdate (
$ hash ,
"charger_localAuthorizationRequired" ,
$ d - > { localAuthorizationRequired }
) ;
readingsBulkUpdate ( $ hash , "charger_localRadioChannel" ,
$ d - > { localRadioChannel } ) ;
readingsBulkUpdate ( $ hash , "charger_localShortAddress" ,
$ d - > { localShortAddress } ) ;
readingsBulkUpdate (
$ hash ,
"charger_localParentAddrOrNumOfNodes" ,
$ d - > { localParentAddrOrNumOfNodes }
) ;
readingsBulkUpdate (
$ hash ,
"charger_localPreAuthorizeEnabled" ,
$ d - > { localPreAuthorizeEnabled }
) ;
readingsBulkUpdate (
$ hash ,
"charger_allowOfflineTxForUnknownId" ,
$ d - > { allowOfflineTxForUnknownId }
) ;
readingsBulkUpdate ( $ hash , "charger_maxChargerCurrent" ,
$ d - > { maxChargerCurrent } ) ;
readingsBulkUpdate ( $ hash , "charger_ledStripBrightness" ,
$ d - > { ledStripBrightness } ) ;
readingsBulkUpdate ( $ hash , "charger_chargingSchedule" ,
$ d - > { chargingSchedule } ) ;
readingsEndUpdate ( $ hash , 1 ) ;
return undef ;
}
EaseeWallbox_RequestDeviceUpdate ( $ hash ) ;
}
sub EaseeWallbox_GetChargerSite ($) {
my ( $ hash ) = @ _ ;
my $ name = $ hash - > { NAME } ;
my $ chargerId = ReadingsVal ( $ name , "charger_id" , undef ) ;
if ( not defined $ chargerId ) {
my $ msg
= "Error on EaseeWallbox_GetChargerSite. Missing Charger ID. Please get Chargers first." ;
Log3 'EaseeWallbox' , 1 , $ msg ;
return $ msg ;
}
my $ readTemplate = $ url { "getChargerSite" } ;
$ readTemplate =~ s/#ChargerID#/$chargerId/g ;
my $ d = EaseeWallbox_httpSimpleOperationOAuth ( $ hash , $ readTemplate ,
'GET' ) ;
if ( defined $ d && ref ( $ d ) eq "HASH" && defined $ d - > { errors } ) {
log 1 , Dumper $ d ;
readingsSingleUpdate ( $ hash , 'state' ,
"Error: $d->{errors}[0]->{code} / $d->{errors}[0]->{title}" , 1 ) ;
return undef ;
}
else {
readingsBeginUpdate ( $ hash ) ;
readingsBulkUpdate ( $ hash , "site_key" , $ d - > { siteKey } ) ;
readingsBulkUpdate ( $ hash , "site_id" , $ d - > { id } ) ;
readingsBulkUpdate ( $ hash , "cost_perKWh" , $ d - > { costPerKWh } ) ;
readingsBulkUpdate ( $ hash , "cost_perKwhExcludeVat" ,
$ d - > { costPerKwhExcludeVat } ) ;
readingsBulkUpdate ( $ hash , "cost_vat" , $ d - > { vat } ) ;
readingsBulkUpdate ( $ hash , "cost_currency" , $ d - > { currencyId } ) ;
readingsBulkUpdate ( $ hash , "site_ratedCurrent" , $ d - > { ratedCurrent } ) ;
readingsBulkUpdate ( $ hash , "site_createdOn" , $ d - > { createdOn } ) ;
readingsBulkUpdate ( $ hash , "site_updatedOn" , $ d - > { updatedOn } ) ;
readingsEndUpdate ( $ hash , 1 ) ;
return undef ;
}
EaseeWallbox_RequestDeviceUpdate ( $ hash ) ;
}
sub EaseeWallbox_RequestCurrentSessionCallback ($) {
my ( $ param , $ err , $ data ) = @ _ ;
my $ hash = $ param - > { hash } ;
my $ name = $ hash - > { NAME } ;
if ( $ err ne "" ) # wenn ein Fehler bei der HTTP Abfrage aufgetreten ist
{
Log3 $ name , 3 ,
"error while requesting "
. $ param - > { url }
. " - $err" ; # Eintrag fürs Log
readingsSingleUpdate ( $ hash , "state" , "ERROR" , 1 ) ;
return undef ;
}
Log3 $ name , 3 ,
"Received non-blocking data from EaseeWallbox regarding current session " ;
Log3 $ name , 4 , "FHEM -> EaseeWallbox: " . $ param - > { url } ;
Log3 $ name , 4 , "FHEM -> EaseeWallbox: " . $ param - > { message }
if ( defined $ param - > { message } ) ;
Log3 $ name , 4 , "EaseeWallbox -> FHEM: " . $ data ;
Log3 $ name , 5 , '$err: ' . $ err ;
Log3 $ name , 5 , "method: " . $ param - > { method } ;
Log3 $ name , 2 , "Something gone wrong"
if ( $ data =~ "/EaseeWallboxMode/" ) ;
eval {
my $ d = decode_json ( $ data ) if ( ! $ err ) ;
Log3 $ name , 5 , 'Decoded: ' . Dumper ( $ d ) ;
readingsBeginUpdate ( $ hash ) ;
readingsBulkUpdate ( $ hash , "session_energy" , $ d - > { sessionEnergy } ) ;
readingsBulkUpdate ( $ hash , "session_start" , $ d - > { sessionStart } ) ;
readingsBulkUpdate ( $ hash , "session_end" , $ d - > { sessionEnd } ) ;
readingsBulkUpdate (
$ hash ,
"session_chargeDurationInSeconds" ,
$ d - > { chargeDurationInSeconds }
) ;
readingsBulkUpdate ( $ hash , "session_firstEnergyTransfer" ,
$ d - > { firstEnergyTransferPeriodStart } ) ;
readingsBulkUpdate ( $ hash , "session_lastEnergyTransfer" ,
$ d - > { lastEnergyTransferPeriodStart } ) ;
readingsBulkUpdate ( $ hash , "session_pricePerKWH" ,
$ d - > { pricePrKwhIncludingVat } ) ;
readingsBulkUpdate ( $ hash , "session_chargingCost" ,
$ d - > { costIncludingVat } ) ;
readingsBulkUpdate ( $ hash , "session_id" , $ d - > { sessionId } ) ;
readingsEndUpdate ( $ hash , 1 ) ;
return undef ;
} or do {
Log3 $ name , 5 , 'Failure decoding: ' . $@ ;
return undef ;
}
}
sub EaseeWallbox_RequestChargerStateCallback ($) {
my ( $ param , $ err , $ data ) = @ _ ;
my $ hash = $ param - > { hash } ;
my $ name = $ hash - > { NAME } ;
if ( $ err ne "" ) # wenn ein Fehler bei der HTTP Abfrage aufgetreten ist
{
Log3 $ name , 3 ,
"error while requesting "
. $ param - > { url }
. " - $err" ; # Eintrag fürs Log
readingsSingleUpdate ( $ hash , "state" , "ERROR" , 1 ) ;
return undef ;
}
Log3 $ name , 3 ,
"Received non-blocking data from EaseeWallbox regarding current state " ;
Log3 $ name , 4 , "FHEM -> EaseeWallbox: " . $ param - > { url } ;
Log3 $ name , 4 , "FHEM -> EaseeWallbox: " . $ param - > { message }
if ( defined $ param - > { message } ) ;
Log3 $ name , 4 , "EaseeWallbox -> FHEM: " . $ data ;
Log3 $ name , 5 , '$err: ' . $ err ;
Log3 $ name , 5 , "method: " . $ param - > { method } ;
Log3 $ name , 2 , "Something gone wrong"
if ( $ data =~ "/EaseeWallboxMode/" ) ;
eval {
my $ d = decode_json ( $ data ) if ( ! $ err ) ;
Log3 $ name , 5 , 'Decoded: ' . Dumper ( $ d ) ;
readingsBeginUpdate ( $ hash ) ;
readingsBulkUpdate ( $ hash , "actual_operationModeCode" ,
$ d - > { chargerOpMode } ) ;
readingsBulkUpdate ( $ hash , "actual_operationMode" ,
$ operationMode { $ d - > { chargerOpMode } } ) ;
readingsBulkUpdate ( $ hash , "actual_power" , $ d - > { totalPower } ) ;
readingsBulkUpdate ( $ hash , "actual_kWhInSession" ,
$ d - > { sessionEnergy } ) ;
readingsBulkUpdate ( $ hash , "actual_phase" , $ d - > { outputPhase } ) ;
readingsBulkUpdate ( $ hash , "actual_latestPulse" , $ d - > { latestPulse } ) ;
readingsBulkUpdate ( $ hash , "actual_current" , $ d - > { outputCurrent } ) ;
readingsBulkUpdate ( $ hash , "actual_dynamicCurrent" ,
$ d - > { dynamicChargerCurrent } ) ;
readingsBulkUpdate (
$ hash ,
"actual_reasonCodeForNoCurrent" ,
$ d - > { reasonForNoCurrent }
) ;
readingsBulkUpdate ( $ hash , "actual_reasonForNoCurrent" ,
$ reasonForNoCurrent { $ d - > { reasonForNoCurrent } } ) ;
readingsBulkUpdate ( $ hash , "errorCode" , $ d - > { errorCode } ) ;
readingsBulkUpdate ( $ hash , "fatalErrorCode" , $ d - > { fatalErrorCode } ) ;
readingsBulkUpdate ( $ hash , "lifetimeEnergy" , $ d - > { lifetimeEnergy } ) ;
readingsBulkUpdate ( $ hash , "online" , $ d - > { isOnline } ) ;
readingsBulkUpdate ( $ hash , "voltage" , $ d - > { voltage } ) ;
readingsBulkUpdate ( $ hash , "wifi_rssi" , $ d - > { wiFiRSSI } ) ;
readingsBulkUpdate ( $ hash , "wifi_apEnabled" , $ d - > { wiFiAPEnabled } ) ;
readingsBulkUpdate ( $ hash , "cell_rssi" , $ d - > { cellRSSI } ) ;
readingsEndUpdate ( $ hash , 1 ) ;
return undef ;
} or do {
Log3 $ name , 5 , 'Failure decoding: ' . $@ ;
return undef ;
}
}
sub EaseeWallbox_UpdateDueToTimer ($) {
my ( $ hash ) = @ _ ;
my $ name = $ hash - > { NAME } ;
2022-03-16 20:27: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-16 18:57:21 +00:00
if ( ! $ hash - > { LOCAL } ) {
RemoveInternalTimer ( $ hash ) ;
#Log3 "Test", 1, Dumper($hash);
InternalTimer (
gettimeofday ( ) + InternalVal ( $ name , 'INTERVAL' , undef ) ,
"EaseeWallbox_UpdateDueToTimer" , $ hash ) ;
readingsSingleUpdate ( $ hash , 'state' , 'Polling' , 0 ) ;
}
#EaseeWallbox_RequestZoneUpdate($hash);
#EaseeWallbox_RequestAirComfortUpdate($hash);
#EaseeWallbox_RequestMobileDeviceUpdate($hash);
#EaseeWallbox_RequestWeatherUpdate($hash);
#EaseeWallbox_RequestDeviceUpdate($hash);
#EaseeWallbox_RequestPresenceUpdate($hash);
}
sub EaseeWallbox_RequestCurrentSession ($) {
my ( $ hash ) = @ _ ;
my $ name = $ hash - > { NAME } ;
if ( not defined $ hash ) {
Log3 'EaseeWallbox' , 1 ,
"Error on EaseeWallbox_RequestCurrentSession. Missing hash variable" ;
return undef ;
}
if ( not defined ReadingsVal ( $ name , 'charger_id' , undef ) ) {
Log3 'EaseeWallbox' , 1 ,
"Error on EaseeWallbox_RequestCurrentSession. Missing charger_id. Please fetch basic data first." ;
return undef ;
}
my $ chargerId = ReadingsVal ( $ name , "charger_id" , undef ) ;
$ hash - > { charger } = $ chargerId ;
Log3 $ name , 4 ,
"EaseeWallbox_RequestCurrentSession Called for non-blocking value update. Name: $name" ;
my $ readTemplate = $ url { "getCurrentSession" } ;
$ readTemplate =~ s/#ChargerID#/$chargerId/g ;
my $ CurrentTokenData = EaseeWallbox_LoadToken ( $ hash ) ;
my $ token
= "$CurrentTokenData->{'tokenType'} $CurrentTokenData->{'accessToken'}" ;
Log3 $ name , 4 , "token beeing used: " . $ token ;
my $ request = {
url = > $ readTemplate ,
header = > {
"Content-Type" = > "application/json;charset=UTF-8" ,
"Authorization" = > $ token ,
} ,
method = > 'GET' ,
timeout = > 5 ,
hideurl = > 1 ,
callback = > \ & EaseeWallbox_RequestCurrentSessionCallback ,
hash = > $ hash
} ;
Log3 $ name , 5 , 'NonBlocking Request: ' . Dumper ( $ request ) ;
HttpUtils_NonblockingGet ( $ request ) ;
}
sub EaseeWallbox_RequestChargerState ($) {
my ( $ hash ) = @ _ ;
my $ name = $ hash - > { NAME } ;
if ( not defined $ hash ) {
Log3 'EaseeWallbox' , 1 ,
"Error on EaseeWallbox_RequestChargerState. Missing hash variable" ;
return undef ;
}
if ( not defined ReadingsVal ( $ name , 'charger_id' , undef ) ) {
Log3 'EaseeWallbox' , 1 ,
"Error on EaseeWallbox_RequestChargerState. Missing charger_id. Please fetch basic data first." ;
return undef ;
}
my $ chargerId = ReadingsVal ( $ name , "charger_id" , undef ) ;
$ hash - > { charger } = $ chargerId ;
Log3 $ name , 4 ,
"EaseeWallbox_RequestChargerState Called for non-blocking value update. Name: $name" ;
my $ readTemplate = $ url { "getChargerState" } ;
$ readTemplate =~ s/#ChargerID#/$chargerId/g ;
my $ CurrentTokenData = EaseeWallbox_LoadToken ( $ hash ) ;
my $ token
= "$CurrentTokenData->{'tokenType'} $CurrentTokenData->{'accessToken'}" ;
Log3 $ name , 4 , "token beeing used: " . $ token ;
my $ request = {
url = > $ readTemplate ,
header = > {
"Content-Type" = > "application/json;charset=UTF-8" ,
"Authorization" = > $ token ,
} ,
method = > 'GET' ,
timeout = > 5 ,
hideurl = > 1 ,
callback = > \ & EaseeWallbox_RequestChargerStateCallback ,
hash = > $ hash
} ;
Log3 $ name , 5 , 'NonBlocking Request: ' . Dumper ( $ request ) ;
HttpUtils_NonblockingGet ( $ request ) ;
}
sub EaseeWallbox_encrypt ($) {
my ( $ decoded ) = @ _ ;
my $ key = getUniqueId ( ) ;
my $ encoded ;
return $ decoded if ( $ decoded =~ /crypt:/ ) ;
for my $ char ( split // , $ decoded ) {
my $ encode = chop ( $ key ) ;
$ encoded . = sprintf ( "%.2x" , ord ( $ char ) ^ ord ( $ encode ) ) ;
$ key = $ encode . $ key ;
}
return 'crypt:' . $ encoded ;
}
sub EaseeWallbox_decrypt ($) {
my ( $ encoded ) = @ _ ;
my $ key = getUniqueId ( ) ;
my $ decoded ;
return $ encoded if ( $ encoded !~ /crypt:/ ) ;
$ encoded = $ 1 if ( $ encoded =~ /crypt:(.*)/ ) ;
for my $ char ( map { pack ( 'C' , hex ( $ _ ) ) } ( $ encoded =~ /(..)/g ) ) {
my $ decode = chop ( $ key ) ;
$ decoded . = chr ( ord ( $ char ) ^ ord ( $ decode ) ) ;
$ key = $ decode . $ key ;
}
return $ decoded ;
}
1 ;
= pod
= begin html
< a name = "EaseeWallbox" > </a>
<h3> EaseeWallbox </h3>
<ul>
<i> EaseeWallbox </i> implements an interface to the EaseeWallbox cloud . The plugin can be used to read and write temperature and settings from or to the EaseeWallbox cloud . The communication is based on the reengineering of the protocol done by Stephen C . Phillips . See < a href = "http://blog.scphillips.com/posts/2017/01/the-EaseeWallbox-api-v2/" > his blog </a> for more details . Not all functions are implemented within this FHEM extension . By now the plugin is capable to interact with the so called zones ( rooms ) and the registered devices . The devices cannot be controlled directly . All interaction - like setting a temperature - must be done via the zone and not the device . This means all configuration like the registration of new devices or the assignment of a device to a room must be done using the EaseeWallbox app or EaseeWallbox website directly . Once the configuration is completed this plugin can be used . This device is the 'bridge device' like a HueBridge or a CUL . Per zone or device a dedicated device of type 'EaseeWallbox' will be created .
The following features / functionalities are defined by now when using EaseeWallbox and EaseeWallboxs:
<ul>
2022-03-16 19:50:50 +00:00
<li> EaseeWallbox Bridge
<br> <ul>
<li> Manages the communication towards the EaseeWallbox cloud environment and documents the status in several readings like which data was refreshed , when it was rerefershed , etc . </li>
<li> <b> Overall Presence status </b> Indicates wether at least one mobile device is 'at Home' </li>
<li> <b> Overall Air Comfort </b> Indicates the air comfort of the whole home . </li>
</ul> </li>
<li> Zone ( basically a room )
<br> <ul>
<li> <b> Temperature Management: </b> Displays the current temperature , allows to set the desired temperature including the EaseeWallbox modes which can do this manually or automatically </li>
<li> <b> Zone Air Comfort </b> Indicates the air comfort of the specific room . </li>
</ul> </li>
<li> Device
<br> <ul>
<li> <b> Connection State: </b> Indicate when the actual device was seen the last time </li>
<li> <b> Battery Level </b> Indicates the current battery level of the device . </li>
<li> <b> Find device </b> Output a 'Hi' message on the display to identify the specific device </li>
</ul> </li>
<li> Mobile Device <
<br> <ul>
<li> <b> Device Configration: </b> Displays information about the device type and the current configuration ( view only ) </li>
<li> <b> Presence status </b> Indicates if the specific mobile device is Home or Away . </li>
</ul> </li>
<li> Weather
<br> <ul>
<li> Displays information about the ouside waether and the solar intensity ( cloud source , not actually measured ) . </li>
</ul> </li>
2022-03-16 18:57:21 +00:00
</ul>
<br>
While previous versions of this plugin were using plain authentication encoding the username and the password directly in the URL this version now uses OAuth2 which does a secure authentication and uses security tokens afterwards . This is a huge security improvement . The implementation is based on code written by Philipp ( Psycho160 ) . Thanks for sharing .
<br>
<br>
< a name = "EaseeWallboxdefine" > </a>
<b> Define </b>
<ul>
<code> define & lt ; name & gt ; EaseeWallbox & lt ; username & gt ; & lt ; password & gt ; & lt ; interval & gt ; </code>
<br>
<br> Example: <code> define EaseeWallboxBridge EaseeWallbox mail @ provider . com somepassword 120 </code>
<br>
<br> The username and password must match the username and password used on the EaseeWallbox website . Please be aware that username and password are stored and send as plain text . They are visible in FHEM user interface . It is recommended to create a dedicated user account for the FHEM integration . The EaseeWallbox extension needs to pull the data from the EaseeWallbox website . The 'Interval' value defines how often the value is refreshed .
</ul>
<br>
<b> Set </b>
<br>
<ul>
<code> set & lt ; name & gt ; & lt ; option & gt ; </code>
<br>
<br> The <i> set </i> command just offers very limited options . If can be used to control the refresh mechanism . The plugin only evaluates the command . Any additional information is ignored .
<br>
<br> Options:
<ul>
<li> <i> interval </i>
<br> Sets how often the values shall be refreshed . This setting overwrites the value set during define . </li>
<li> <i> start </i>
<br> ( Re ) starts the automatic refresh . Refresh is autostarted on define but can be stopped using stop command . Using the start command FHEM will start polling again . </li>
<li> <i> stop </i>
<br> Stops the automatic polling used to refresh all values . </li>
<li> <i> presence </i>
<br> Sets the presence value for the whole EaseeWallbox account . You can set the status to HOME or AWAY and depending on the status all devices will chnange their confiration between home and away mode . If you ' re using the mobile devices and the EaseeWallbox premium feature using geofencing to determine home and away status you should not use this function . </li>
</ul>
</ul>
<br>
< a name = "EaseeWallboxget" > </a>
<b> Get </b>
<br>
<ul>
<code> get & lt ; name & gt ; & lt ; option & gt ; </code>
<br>
<br> You can <i> get </i> the major information from the EaseeWallbox cloud .
<br>
<br> Options:
<ul>
<li> <i> home </i>
<br> Gets the home identifier from EaseeWallbox cloud . The home identifier is required for all further actions towards the EaseeWallbox cloud . Currently the FHEM extension only supports a single home . If you have more than one home only the first home is loaded .
<br/> <b> This function is automatically executed once when a new EaseeWallbox device is defined . </b> </li>
<li> <i> zones </i>
<br> Every zone in the EaseeWallbox cloud represents a room . This command gets all zones defined for the current home . Per zone a new FHEM device is created . The device can be used to display and overwrite the current temperatures . This command can always be executed to update the list of defined zones . It will not touch any existing zone but add new zones added since last update .
<br/> <b> This function is automatically executed once when a new EaseeWallbox device is defined . </b> </li>
<li> <i> update </i>
<br/> Updates the values of:
<br/>
<ul>
<li> All EaseeWallbox zones </li>
<li> The presence status of the whole EaseeWallbox account </li>
<li> All mobile devices - if attribute <i> generateMobileDevices </i> is set to true </li>
<li> All devices - if attribute <i> generateDevices </i> is set to true </li>
<li> The weather device - if attribute <i> generateWeather </i> is set to true </li>
</ul>
This command triggers a single update not a continuous refresh of the values .
</li>
<li> <i> devices </i>
<br/> Fetches all devices from EaseeWallbox cloud and creates one EaseeWallbox instance per fetched device . This command will only be executed if the attribute <i> generateDevices </i> is set to <i> yes </i> . If the attribute is set to <i> no </i> or not existing an error message will be displayed and no communication towards EaseeWallbox will be done . This command can always be executed to update the list of defined devices . It will not touch existing devices but add new ones . Devices will not be updated automatically as there are no values continuously changing .
</li>
<li> <i> mobile_devices </i>
<br/> Fetches all defined mobile devices from EaseeWallbox cloud and creates one EaseeWallbox instance per mobile device . This command will only be executed if the attribute <i> generateMobileDevices </i> is set to <i> yes </i> . If the attribute is set to <i> no </i> or not existing an error message will be displayed and no communication towards EaseeWallbox will be done . This command can always be executed to update the list of defined mobile devices . It will not touch existing devices but add new ones .
</li>
<li> <i> weather </i>
<br/> Creates or updates an additional device for the data bridge containing the weather data provided by EaseeWallbox . This command will only be executed if the attribute <i> generateWeather </i> is set to <i> yes </i> . If the attribute is set to <i> no </i> or not existing an error message will be displayed and no communication towards EaseeWallbox will be done .
</li>
</ul>
</ul>
<br>
< a name = "EaseeWallboxattr" > </a>
<b> Attributes </b>
<ul>
<code> attr & lt ; name & gt ; & lt ; attribute & gt ; & lt ; value & gt ; </code>
<br>
<br> You can change the behaviour of the EaseeWallbox Device .
<br>
<br> Attributes:
<ul>
<li> <i> generateDevices </i>
<br> By default the devices are not fetched and displayed in FHEM as they don ' t offer much functionality . The functionality is handled by the zones not by the devices . But the devices offers an identification function <i> sayHi </i> to show a message on the specific display . If this function is required the Devices can be generated . Therefor the attribute <i> generateDevices </i> must be set to <i> yes </i>
<br/> <b> If this attribute is set to <i> no </i> or if the attribute is not existing no devices will be generated .. </b>
</li>
<li> <i> generateMobileDevices </i>
<br> By default the mobile devices are not fetched and displayed in FHEM as most users already have a person home recognition . If EaseeWallbox shall be used to identify if a mobile device is at home this can be done using the mobile devices . In this case the mobile devices can be generated . Therefor the attribute <i> generateMobileDevices </i> must be set to <i> yes </i>
<br/> <b> If this attribute is set to <i> no </i> or if the attribute is not existing no mobile devices will be generated .. </b>
</li>
<li> <i> generateWeather </i>
<br> By default no weather channel is generated . If you want to use the weather as it is defined by the EaseeWallbox system for your specific environment you must set this attribute . If the attribute <i> generateWeather </i> is set to <i> yes </i> an additional weather channel can be generated .
<br/> <b> If this attribute is set to <i> no </i> or if the attribute is not existing no Devices will be generated .. </b>
</li>
</ul>
</ul>
<br>
< a name = "EaseeWallboxreadings" > </a>
<b> Generated Readings /Events:</ b >
2022-03-16 19:50:50 +00:00
<br>
2022-03-16 18:57:21 +00:00
<ul>
<ul>
<li> <b> DeviceCount </b>
<br> Indicates how many devices ( hardware devices provided by EaseeWallbox ) are registered in the linked EaseeWallbox Account .
<br/> This reading will only be available / updated if the attribute <i>generateDevices</i > is set to <i> yes </i> .
</li>
<li> <b> LastUpdate_Devices </b>
<br> Indicates when the last successful request to update the hardware devices ( EaseeWallboxs ) was send to the EaseeWallbox API . his reading will only be available / updated if the attribute <i>generateDevices</i > is set to <i> yes </i> .
</li>
<li> <b> HomeID </b>
<br> Unique identifier for your EaseeWallbox account instance . All devices are linked to your homeID and the homeID required for almost all EaseeWallbox API requests .
</li>
<li> <b> HomeName </b>
<br> Name of your EaseeWallbox home as you have configured it in your EaseeWallbox account .
</li>
<li> <b> Presence </b>
<br> The current presence status of your home . The status can be HOME or AWAY and is valid for the whole home and all devices and zones linked to this home . The Presence reading can be influences by the <i> set presence </i> command or based on geofencing using mobile devices .
</li>
<li> <b> airComfort_freshness </b>
<br> The overall fresh air indicator for your home . Represents a summary of the single indicators per zone / room .
</li>
<li> <b> airComfort_lastWindowOpen </b>
<br> Inidcates the last time an open window was detected by EaseeWallbox to refresh the air within the home .
</li>
<li> <b> LastUpdate_AirComfort </b>
<br> Indicates when the last successful request to update the air comfort was send to the EaseeWallbox API .
</li>
<li> <b> LastUpdate_MobileDevices </b>
<br> Indicates when the last successful request to update the mobile devices was send to the EaseeWallbox API . his reading will only be available / updated if the attribute <i>generateMobileDevices</i > is set to <i> yes </i> .
</li>
<li> <b> LastUpdate_Weather </b>
<br> Indicates when the last successful request to update the weather was send to the EaseeWallbox API . his reading will only be available / updated if the attribute <i>generateWeather</i > is set to <i> yes </i> .
</li>
<li> <b> LastUpdate_Zones </b>
<br> Indicates when the last successful request to update the zone / room data was send to the EaseeWallbox API .
</li>
</ul>
</ul>
</ul>
= end html
= cut