From 492e1a1cda71d3ebac001c251f2c279eb7c8f15d Mon Sep 17 00:00:00 2001 From: Max Dhom Date: Sun, 6 Mar 2022 15:59:04 +0100 Subject: [PATCH 01/18] fetching wallbox data, toggle wallbox --- e3dc/_e3dc.py | 58 +++++++++++++++++++++++++++++++++++++++++++++++++++ 1 file changed, 58 insertions(+) diff --git a/e3dc/_e3dc.py b/e3dc/_e3dc.py index be5a517..21c9b4e 100644 --- a/e3dc/_e3dc.py +++ b/e3dc/_e3dc.py @@ -9,6 +9,7 @@ import json import time import uuid +import struct import dateutil.parser import requests @@ -1037,6 +1038,63 @@ def get_system_status(self, keepAlive=False): outObj = {k: SystemStatusBools[v] for k, v in outObj.items()} return outObj + def get_wallbox_data(self): + req = self.sendRequest( + ( + "WB_REQ_DATA", + "Container", + [ + ("WB_INDEX", "UChar8", 0), + ("WB_REQ_ENERGY_ALL", "None", None), + ("WB_REQ_ENERGY_SOLAR", "None", None), + ("WB_REQ_SOC", "None", None), + ("WB_REQ_EXTERN_DATA_ALG", "None", None), + ("WB_REQ_EXTERN_DATA_SUN", "None", None), + ("WB_REQ_EXTERN_DATA_NET", "None", None), + ("WB_REQ_PARAM_1", "None", None), + ("WB_REQ_APP_SOFTWARE", "None", None) + ], + ), + keepAlive=True + ) + + outObj = { + "wallboxIndex": rscpFindTagIndex(req, "WB_INDEX"), + "energyAll": rscpFindTagIndex(req, "WB_ENERGY_ALL"), + "energySolar": rscpFindTagIndex(req, "WB_ENERGY_SOLAR"), + "soc": rscpFindTagIndex(req, "WB_SOC"), + } + + extern_data_sun = rscpFindTag(req, "WB_EXTERN_DATA_SUN") + if extern_data_sun is not None: + extern_data = rscpFindTagIndex(extern_data_sun, "WB_EXTERN_DATA") + outObj["sun"] = struct.unpack("h", extern_data[0:2])[0] + + extern_data_net = rscpFindTag(req, "WB_EXTERN_DATA_NET") + if extern_data_net is not None: + extern_data = rscpFindTagIndex(extern_data_net, "WB_EXTERN_DATA") + outObj["net"] = struct.unpack("h", extern_data[0:2])[0] + + return outObj + + def wallbox_toggle(self): + barry = bytearray([0,0,0,0,1,0]) + req = self.sendRequest( + ( + "WB_REQ_DATA", + "Container", + [ + ("WB_INDEX", "UChar8", 0), + ("WB_REQ_SET_EXTERN", "Container", + [ + ("WB_EXTERN_DATA","ByteArray",barry), + ("WB_EXTERN_DATA_LEN", "UChar8", 6), + ]) + ], + ), + keepAlive=True + ) + def get_battery_data(self, batIndex=None, dcbs=None, keepAlive=False): """Polls the battery data via rscp protocol locally. From 55ddd6adc35900b2d80ef3bbfb1b724fbd6b087e Mon Sep 17 00:00:00 2001 From: Max Dhom Date: Sun, 6 Mar 2022 16:28:48 +0100 Subject: [PATCH 02/18] read out sunMode, chargingCanceled/Active, phases, schukoOn, maxChargeCurrent, setting sunmode --- e3dc/_e3dc.py | 34 ++++++++++++++++++++++++++++++++++ 1 file changed, 34 insertions(+) diff --git a/e3dc/_e3dc.py b/e3dc/_e3dc.py index 21c9b4e..26685e3 100644 --- a/e3dc/_e3dc.py +++ b/e3dc/_e3dc.py @@ -1063,8 +1063,19 @@ def get_wallbox_data(self): "energyAll": rscpFindTagIndex(req, "WB_ENERGY_ALL"), "energySolar": rscpFindTagIndex(req, "WB_ENERGY_SOLAR"), "soc": rscpFindTagIndex(req, "WB_SOC"), + "appSoftware": rscpFindTagIndex(req, "WB_APP_SOFTWARE") } + extern_data_alg = rscpFindTag(req, "WB_EXTERN_DATA_ALG") + if extern_data_alg is not None: + extern_data = rscpFindTagIndex(extern_data_alg, "WB_EXTERN_DATA") + status_byte = extern_data[2] + outObj["sunModeOn"] = (status_byte & 128) == 128 + outObj["chargingCanceled"] = (status_byte & 64) == 64 + outObj["chargingActive"] = (status_byte & 32) == 32 + outObj["phases"] = extern_data[1] + outObj["schukoOn"] = extern_data[5] != 0 + extern_data_sun = rscpFindTag(req, "WB_EXTERN_DATA_SUN") if extern_data_sun is not None: extern_data = rscpFindTagIndex(extern_data_sun, "WB_EXTERN_DATA") @@ -1074,9 +1085,32 @@ def get_wallbox_data(self): if extern_data_net is not None: extern_data = rscpFindTagIndex(extern_data_net, "WB_EXTERN_DATA") outObj["net"] = struct.unpack("h", extern_data[0:2])[0] + + rsp_param_1 = rscpFindTag(req, "WB_RSP_PARAM_1") + if rsp_param_1 is not None: + extern_data = rscpFindTagIndex(rsp_param_1, "WB_EXTERN_DATA") + outObj["maxChargeCurrent"] = extern_data[2] return outObj + def wallbox_set_sunmode(self, active: bool): + barry = bytearray([1 if active else 2,0,0,0,0,0]) + req = self.sendRequest( + ( + "WB_REQ_DATA", + "Container", + [ + ("WB_INDEX", "UChar8", 0), + ("WB_REQ_SET_EXTERN", "Container", + [ + ("WB_EXTERN_DATA","ByteArray",barry), + ("WB_EXTERN_DATA_LEN", "UChar8", 6), + ]) + ], + ), + keepAlive=True + ) + def wallbox_toggle(self): barry = bytearray([0,0,0,0,1,0]) req = self.sendRequest( From df4d704072078508b1c85d27ae072a2d5dc9aaa7 Mon Sep 17 00:00:00 2001 From: Max Dhom Date: Sun, 6 Mar 2022 16:41:50 +0100 Subject: [PATCH 03/18] wallbox_set_schuko --- e3dc/_e3dc.py | 18 ++++++++++++++++++ 1 file changed, 18 insertions(+) diff --git a/e3dc/_e3dc.py b/e3dc/_e3dc.py index 26685e3..4ae82f4 100644 --- a/e3dc/_e3dc.py +++ b/e3dc/_e3dc.py @@ -1111,6 +1111,24 @@ def wallbox_set_sunmode(self, active: bool): keepAlive=True ) + def wallbox_set_schuko(self, on: bool): + barry = bytearray([0,0,0,0,0,1 if on else 0]) + req = self.sendRequest( + ( + "WB_REQ_DATA", + "Container", + [ + ("WB_INDEX", "UChar8", 0), + ("WB_REQ_SET_EXTERN", "Container", + [ + ("WB_EXTERN_DATA","ByteArray",barry), + ("WB_EXTERN_DATA_LEN", "UChar8", 6), + ]) + ], + ), + keepAlive=True + ) + def wallbox_toggle(self): barry = bytearray([0,0,0,0,1,0]) req = self.sendRequest( From 94d53a35777bfc4a3cb8b94a5389a9cf5d666264 Mon Sep 17 00:00:00 2001 From: Max Dhom Date: Sun, 6 Mar 2022 17:01:44 +0100 Subject: [PATCH 04/18] fixed black --- e3dc/_e3dc.py | 70 +++++++++++++++++++++++++++--------------------- e3dc/_rscpLib.py | 8 +++--- 2 files changed, 44 insertions(+), 34 deletions(-) diff --git a/e3dc/_e3dc.py b/e3dc/_e3dc.py index 4ae82f4..4e5e892 100644 --- a/e3dc/_e3dc.py +++ b/e3dc/_e3dc.py @@ -725,7 +725,8 @@ def set_idle_periods(self, idlePeriods, keepAlive=False): + str(idlePeriod["day"]) + " in " + idle_type - + " is not between 00:00 and 23:59" + + " is not between 00:00" + " and 23:59" ) if ( idlePeriod["start"][0] * 60 + idlePeriod["start"][1] @@ -1052,10 +1053,10 @@ def get_wallbox_data(self): ("WB_REQ_EXTERN_DATA_SUN", "None", None), ("WB_REQ_EXTERN_DATA_NET", "None", None), ("WB_REQ_PARAM_1", "None", None), - ("WB_REQ_APP_SOFTWARE", "None", None) + ("WB_REQ_APP_SOFTWARE", "None", None), ], ), - keepAlive=True + keepAlive=True, ) outObj = { @@ -1063,7 +1064,7 @@ def get_wallbox_data(self): "energyAll": rscpFindTagIndex(req, "WB_ENERGY_ALL"), "energySolar": rscpFindTagIndex(req, "WB_ENERGY_SOLAR"), "soc": rscpFindTagIndex(req, "WB_SOC"), - "appSoftware": rscpFindTagIndex(req, "WB_APP_SOFTWARE") + "appSoftware": rscpFindTagIndex(req, "WB_APP_SOFTWARE"), } extern_data_alg = rscpFindTag(req, "WB_EXTERN_DATA_ALG") @@ -1078,73 +1079,82 @@ def get_wallbox_data(self): extern_data_sun = rscpFindTag(req, "WB_EXTERN_DATA_SUN") if extern_data_sun is not None: - extern_data = rscpFindTagIndex(extern_data_sun, "WB_EXTERN_DATA") - outObj["sun"] = struct.unpack("h", extern_data[0:2])[0] + extern_data = rscpFindTagIndex(extern_data_sun, "WB_EXTERN_DATA") + outObj["sun"] = struct.unpack("h", extern_data[0:2])[0] extern_data_net = rscpFindTag(req, "WB_EXTERN_DATA_NET") if extern_data_net is not None: - extern_data = rscpFindTagIndex(extern_data_net, "WB_EXTERN_DATA") - outObj["net"] = struct.unpack("h", extern_data[0:2])[0] + extern_data = rscpFindTagIndex(extern_data_net, "WB_EXTERN_DATA") + outObj["net"] = struct.unpack("h", extern_data[0:2])[0] rsp_param_1 = rscpFindTag(req, "WB_RSP_PARAM_1") if rsp_param_1 is not None: extern_data = rscpFindTagIndex(rsp_param_1, "WB_EXTERN_DATA") outObj["maxChargeCurrent"] = extern_data[2] - + return outObj def wallbox_set_sunmode(self, active: bool): - barry = bytearray([1 if active else 2,0,0,0,0,0]) + barry = bytearray([1 if active else 2, 0, 0, 0, 0, 0]) req = self.sendRequest( ( "WB_REQ_DATA", "Container", [ ("WB_INDEX", "UChar8", 0), - ("WB_REQ_SET_EXTERN", "Container", - [ - ("WB_EXTERN_DATA","ByteArray",barry), - ("WB_EXTERN_DATA_LEN", "UChar8", 6), - ]) + ( + "WB_REQ_SET_EXTERN", + "Container", + [ + ("WB_EXTERN_DATA", "ByteArray", barry), + ("WB_EXTERN_DATA_LEN", "UChar8", 6), + ], + ), ], ), - keepAlive=True + keepAlive=True, ) def wallbox_set_schuko(self, on: bool): - barry = bytearray([0,0,0,0,0,1 if on else 0]) + barry = bytearray([0, 0, 0, 0, 0, 1 if on else 0]) req = self.sendRequest( ( "WB_REQ_DATA", "Container", [ ("WB_INDEX", "UChar8", 0), - ("WB_REQ_SET_EXTERN", "Container", - [ - ("WB_EXTERN_DATA","ByteArray",barry), - ("WB_EXTERN_DATA_LEN", "UChar8", 6), - ]) + ( + "WB_REQ_SET_EXTERN", + "Container", + [ + ("WB_EXTERN_DATA", "ByteArray", barry), + ("WB_EXTERN_DATA_LEN", "UChar8", 6), + ], + ), ], ), - keepAlive=True + keepAlive=True, ) def wallbox_toggle(self): - barry = bytearray([0,0,0,0,1,0]) + barry = bytearray([0, 0, 0, 0, 1, 0]) req = self.sendRequest( ( "WB_REQ_DATA", "Container", [ ("WB_INDEX", "UChar8", 0), - ("WB_REQ_SET_EXTERN", "Container", - [ - ("WB_EXTERN_DATA","ByteArray",barry), - ("WB_EXTERN_DATA_LEN", "UChar8", 6), - ]) + ( + "WB_REQ_SET_EXTERN", + "Container", + [ + ("WB_EXTERN_DATA", "ByteArray", barry), + ("WB_EXTERN_DATA_LEN", "UChar8", 6), + ], + ), ], ), - keepAlive=True + keepAlive=True, ) def get_battery_data(self, batIndex=None, dcbs=None, keepAlive=False): diff --git a/e3dc/_rscpLib.py b/e3dc/_rscpLib.py index f8fac59..763855a 100644 --- a/e3dc/_rscpLib.py +++ b/e3dc/_rscpLib.py @@ -100,8 +100,8 @@ def rscpEncode(tagStr, typeStr=None, data=None): if type(data) is str: data = data.encode("utf-8") - packFmt = ( - " Date: Sun, 6 Mar 2022 17:05:54 +0100 Subject: [PATCH 05/18] added docstrings --- e3dc/_e3dc.py | 47 ++++++++++++++++++++++++++++++++++++++--------- 1 file changed, 38 insertions(+), 9 deletions(-) diff --git a/e3dc/_e3dc.py b/e3dc/_e3dc.py index 4e5e892..ed4bbb5 100644 --- a/e3dc/_e3dc.py +++ b/e3dc/_e3dc.py @@ -1039,7 +1039,19 @@ def get_system_status(self, keepAlive=False): outObj = {k: SystemStatusBools[v] for k, v in outObj.items()} return outObj - def get_wallbox_data(self): + def get_wallbox_data(self, keepAlive=False): + """Polls the wallbox status via rscp protocol locally. + + Args: + keepAlive (Optional[bool]): True to keep connection alive + + Returns: + dict: Dictionary containing the wallbox status structured as follows:: + + { + TODO!!! + } + """ req = self.sendRequest( ( "WB_REQ_DATA", @@ -1056,7 +1068,7 @@ def get_wallbox_data(self): ("WB_REQ_APP_SOFTWARE", "None", None), ], ), - keepAlive=True, + keepAlive=keepAlive, ) outObj = { @@ -1094,7 +1106,13 @@ def get_wallbox_data(self): return outObj - def wallbox_set_sunmode(self, active: bool): + def wallbox_set_sunmode(self, active: bool, keepAlive=False): + """Sets the sun mode of the wallbox via rscp protocol locally. + + Args: + active (bool): True to activate sun mode, otherwise false + keepAlive (Optional[bool]): True to keep connection alive + """ barry = bytearray([1 if active else 2, 0, 0, 0, 0, 0]) req = self.sendRequest( ( @@ -1115,9 +1133,15 @@ def wallbox_set_sunmode(self, active: bool): keepAlive=True, ) - def wallbox_set_schuko(self, on: bool): + def wallbox_set_schuko(self, on: bool, keepAlive=False): + """Sets the Schuko of the wallbox via rscp protocol locally. + + Args: + active (bool): True to activate the Schuko, otherwise false + keepAlive (Optional[bool]): True to keep connection alive + """ barry = bytearray([0, 0, 0, 0, 0, 1 if on else 0]) - req = self.sendRequest( + self.sendRequest( ( "WB_REQ_DATA", "Container", @@ -1133,12 +1157,17 @@ def wallbox_set_schuko(self, on: bool): ), ], ), - keepAlive=True, + keepAlive=keepAlive, ) - def wallbox_toggle(self): + def wallbox_toggle(self, keepAlive=False): + """Toggles charging of the wallbox via rscp protocol locally. + + Args: + keepAlive (Optional[bool]): True to keep connection alive + """ barry = bytearray([0, 0, 0, 0, 1, 0]) - req = self.sendRequest( + self.sendRequest( ( "WB_REQ_DATA", "Container", @@ -1154,7 +1183,7 @@ def wallbox_toggle(self): ), ], ), - keepAlive=True, + keepAlive=keepAlive, ) def get_battery_data(self, batIndex=None, dcbs=None, keepAlive=False): From 95300ed1312083ff951c590ceac59c3a8319b547 Mon Sep 17 00:00:00 2001 From: Max Dhom Date: Sun, 6 Mar 2022 17:07:18 +0100 Subject: [PATCH 06/18] fixing isort --- e3dc/_e3dc.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/e3dc/_e3dc.py b/e3dc/_e3dc.py index ed4bbb5..d6adb7a 100644 --- a/e3dc/_e3dc.py +++ b/e3dc/_e3dc.py @@ -7,9 +7,9 @@ import datetime import hashlib import json +import struct import time import uuid -import struct import dateutil.parser import requests From fb602ef8526efae2616e2b54c1111fae8368baa3 Mon Sep 17 00:00:00 2001 From: Max Dhom Date: Sun, 6 Mar 2022 17:08:54 +0100 Subject: [PATCH 07/18] flake8 --- e3dc/_e3dc.py | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/e3dc/_e3dc.py b/e3dc/_e3dc.py index d6adb7a..dbacc8f 100644 --- a/e3dc/_e3dc.py +++ b/e3dc/_e3dc.py @@ -1114,7 +1114,7 @@ def wallbox_set_sunmode(self, active: bool, keepAlive=False): keepAlive (Optional[bool]): True to keep connection alive """ barry = bytearray([1 if active else 2, 0, 0, 0, 0, 0]) - req = self.sendRequest( + self.sendRequest( ( "WB_REQ_DATA", "Container", @@ -1137,7 +1137,7 @@ def wallbox_set_schuko(self, on: bool, keepAlive=False): """Sets the Schuko of the wallbox via rscp protocol locally. Args: - active (bool): True to activate the Schuko, otherwise false + on (bool): True to activate the Schuko, otherwise false keepAlive (Optional[bool]): True to keep connection alive """ barry = bytearray([0, 0, 0, 0, 0, 1 if on else 0]) From 8dd0a10ffd81e91cc6dec2a9e1e26dec8568b9a3 Mon Sep 17 00:00:00 2001 From: Max Dhom Date: Sun, 6 Mar 2022 17:18:02 +0100 Subject: [PATCH 08/18] set_charging --- e3dc/_e3dc.py | 28 ++++++++++++++++++++++++++++ 1 file changed, 28 insertions(+) diff --git a/e3dc/_e3dc.py b/e3dc/_e3dc.py index dbacc8f..e964e70 100644 --- a/e3dc/_e3dc.py +++ b/e3dc/_e3dc.py @@ -1160,6 +1160,34 @@ def wallbox_set_schuko(self, on: bool, keepAlive=False): keepAlive=keepAlive, ) + def wallbox_set_charging(self, active:bool, keepAlive=False): + """Toggles charging of the wallbox via rscp protocol locally. + + Args: + keepAlive (Optional[bool]): True to keep connection alive + """ + barry_on = bytearray([0, 0, 0, 0, 1, 0]) + barry_off = bytearray([0, 0, 0, 1, 0, 0]) + res = self.sendRequest( + ( + "WB_REQ_DATA", + "Container", + [ + ("WB_INDEX", "UChar8", 0), + ( + "WB_REQ_SET_EXTERN", + "Container", + [ + ("WB_EXTERN_DATA", "ByteArray", barry_on if active else barry_off), + ("WB_EXTERN_DATA_LEN", "UChar8", 6), + ], + ), + ], + ), + keepAlive=keepAlive, + ) + print(res) + def wallbox_toggle(self, keepAlive=False): """Toggles charging of the wallbox via rscp protocol locally. From 0d28279355e0455fe39b2586f9ff2aebf901649e Mon Sep 17 00:00:00 2001 From: Max Dhom Date: Sun, 6 Mar 2022 18:13:08 +0100 Subject: [PATCH 09/18] internal __wallbox_set_extern --- e3dc/_e3dc.py | 75 ++++++--------------------------------------------- 1 file changed, 8 insertions(+), 67 deletions(-) diff --git a/e3dc/_e3dc.py b/e3dc/_e3dc.py index e964e70..32815ef 100644 --- a/e3dc/_e3dc.py +++ b/e3dc/_e3dc.py @@ -1113,25 +1113,7 @@ def wallbox_set_sunmode(self, active: bool, keepAlive=False): active (bool): True to activate sun mode, otherwise false keepAlive (Optional[bool]): True to keep connection alive """ - barry = bytearray([1 if active else 2, 0, 0, 0, 0, 0]) - self.sendRequest( - ( - "WB_REQ_DATA", - "Container", - [ - ("WB_INDEX", "UChar8", 0), - ( - "WB_REQ_SET_EXTERN", - "Container", - [ - ("WB_EXTERN_DATA", "ByteArray", barry), - ("WB_EXTERN_DATA_LEN", "UChar8", 6), - ], - ), - ], - ), - keepAlive=True, - ) + return self.__wallbox_set_extern(0, 1 if active else 2, keepAlive) def wallbox_set_schuko(self, on: bool, keepAlive=False): """Sets the Schuko of the wallbox via rscp protocol locally. @@ -1140,53 +1122,7 @@ def wallbox_set_schuko(self, on: bool, keepAlive=False): on (bool): True to activate the Schuko, otherwise false keepAlive (Optional[bool]): True to keep connection alive """ - barry = bytearray([0, 0, 0, 0, 0, 1 if on else 0]) - self.sendRequest( - ( - "WB_REQ_DATA", - "Container", - [ - ("WB_INDEX", "UChar8", 0), - ( - "WB_REQ_SET_EXTERN", - "Container", - [ - ("WB_EXTERN_DATA", "ByteArray", barry), - ("WB_EXTERN_DATA_LEN", "UChar8", 6), - ], - ), - ], - ), - keepAlive=keepAlive, - ) - - def wallbox_set_charging(self, active:bool, keepAlive=False): - """Toggles charging of the wallbox via rscp protocol locally. - - Args: - keepAlive (Optional[bool]): True to keep connection alive - """ - barry_on = bytearray([0, 0, 0, 0, 1, 0]) - barry_off = bytearray([0, 0, 0, 1, 0, 0]) - res = self.sendRequest( - ( - "WB_REQ_DATA", - "Container", - [ - ("WB_INDEX", "UChar8", 0), - ( - "WB_REQ_SET_EXTERN", - "Container", - [ - ("WB_EXTERN_DATA", "ByteArray", barry_on if active else barry_off), - ("WB_EXTERN_DATA_LEN", "UChar8", 6), - ], - ), - ], - ), - keepAlive=keepAlive, - ) - print(res) + return self.__wallbox_set_extern(5, 1 if on else 0, keepAlive) def wallbox_toggle(self, keepAlive=False): """Toggles charging of the wallbox via rscp protocol locally. @@ -1194,7 +1130,11 @@ def wallbox_toggle(self, keepAlive=False): Args: keepAlive (Optional[bool]): True to keep connection alive """ - barry = bytearray([0, 0, 0, 0, 1, 0]) + return self.__wallbox_set_extern(4, 1, keepAlive) + + def __wallbox_set_extern(self, index:int, value:int, keepAlive=False): + barry = bytearray([0, 0, 0, 0, 0, 0]) + barry[index] = value self.sendRequest( ( "WB_REQ_DATA", @@ -1214,6 +1154,7 @@ def wallbox_toggle(self, keepAlive=False): keepAlive=keepAlive, ) + def get_battery_data(self, batIndex=None, dcbs=None, keepAlive=False): """Polls the battery data via rscp protocol locally. From 53cfe893897666e12285a53d80a76545faacafbe Mon Sep 17 00:00:00 2001 From: Max Dhom Date: Sun, 6 Mar 2022 18:14:47 +0100 Subject: [PATCH 10/18] black formatting --- e3dc/_e3dc.py | 3 +-- 1 file changed, 1 insertion(+), 2 deletions(-) diff --git a/e3dc/_e3dc.py b/e3dc/_e3dc.py index 32815ef..dc83508 100644 --- a/e3dc/_e3dc.py +++ b/e3dc/_e3dc.py @@ -1132,7 +1132,7 @@ def wallbox_toggle(self, keepAlive=False): """ return self.__wallbox_set_extern(4, 1, keepAlive) - def __wallbox_set_extern(self, index:int, value:int, keepAlive=False): + def __wallbox_set_extern(self, index: int, value: int, keepAlive=False): barry = bytearray([0, 0, 0, 0, 0, 0]) barry[index] = value self.sendRequest( @@ -1154,7 +1154,6 @@ def __wallbox_set_extern(self, index:int, value:int, keepAlive=False): keepAlive=keepAlive, ) - def get_battery_data(self, batIndex=None, dcbs=None, keepAlive=False): """Polls the battery data via rscp protocol locally. From fd8aff67dfec9c7537324ca3a25d86c519c63e29 Mon Sep 17 00:00:00 2001 From: Max Dhom Date: Sun, 6 Mar 2022 18:35:22 +0100 Subject: [PATCH 11/18] reblack --- e3dc/_e3dc.py | 3 +-- e3dc/_rscpLib.py | 8 ++++---- 2 files changed, 5 insertions(+), 6 deletions(-) diff --git a/e3dc/_e3dc.py b/e3dc/_e3dc.py index dc83508..ab51004 100644 --- a/e3dc/_e3dc.py +++ b/e3dc/_e3dc.py @@ -725,8 +725,7 @@ def set_idle_periods(self, idlePeriods, keepAlive=False): + str(idlePeriod["day"]) + " in " + idle_type - + " is not between 00:00" - " and 23:59" + + " is not between 00:00 and 23:59" ) if ( idlePeriod["start"][0] * 60 + idlePeriod["start"][1] diff --git a/e3dc/_rscpLib.py b/e3dc/_rscpLib.py index 763855a..f8fac59 100644 --- a/e3dc/_rscpLib.py +++ b/e3dc/_rscpLib.py @@ -100,8 +100,8 @@ def rscpEncode(tagStr, typeStr=None, data=None): if type(data) is str: data = data.encode("utf-8") - packFmt = ( # format of header: little-endian, Uint32 tag, Uint8 type, Uint16 length - " Date: Sun, 6 Mar 2022 21:47:17 +0100 Subject: [PATCH 12/18] docstrings, renamed methods, --- e3dc/_e3dc.py | 44 +++++++++++++++++++++++++++++++------------- 1 file changed, 31 insertions(+), 13 deletions(-) diff --git a/e3dc/_e3dc.py b/e3dc/_e3dc.py index 0fb139e..52fc996 100644 --- a/e3dc/_e3dc.py +++ b/e3dc/_e3dc.py @@ -1083,17 +1083,30 @@ def get_system_status(self, keepAlive=False): outObj = {k: SystemStatusBools[v] for k, v in outObj.items()} return outObj - def get_wallbox_data(self, keepAlive=False): + def get_wallbox_data(self, wallbox_index=0, keepAlive=False): """Polls the wallbox status via rscp protocol locally. Args: + wallbox_index (Optional[int]): Index of the wallbox to poll data for keepAlive (Optional[bool]): True to keep connection alive Returns: dict: Dictionary containing the wallbox status structured as follows:: { - TODO!!! + "appSoftware": , + "chargingActive": , + "chargingCanceled": , + "consumptionNet": , + "consumptionSun": , + "energyAll": , + "energySolar": , + "maxChargeCurrent": , + "phases": , + "schukoOn": , + "soc": , + "sunModeOn": , + "wallboxIndex": } """ req = self.sendRequest( @@ -1101,7 +1114,7 @@ def get_wallbox_data(self, keepAlive=False): "WB_REQ_DATA", "Container", [ - ("WB_INDEX", "UChar8", 0), + ("WB_INDEX", "UChar8", wallbox_index), ("WB_REQ_ENERGY_ALL", "None", None), ("WB_REQ_ENERGY_SOLAR", "None", None), ("WB_REQ_SOC", "None", None), @@ -1136,47 +1149,52 @@ def get_wallbox_data(self, keepAlive=False): extern_data_sun = rscpFindTag(req, "WB_EXTERN_DATA_SUN") if extern_data_sun is not None: extern_data = rscpFindTagIndex(extern_data_sun, "WB_EXTERN_DATA") - outObj["sun"] = struct.unpack("h", extern_data[0:2])[0] + outObj["consumptionSun"] = struct.unpack("h", extern_data[0:2])[0] extern_data_net = rscpFindTag(req, "WB_EXTERN_DATA_NET") if extern_data_net is not None: extern_data = rscpFindTagIndex(extern_data_net, "WB_EXTERN_DATA") - outObj["net"] = struct.unpack("h", extern_data[0:2])[0] + outObj["consumptionNet"] = struct.unpack("h", extern_data[0:2])[0] rsp_param_1 = rscpFindTag(req, "WB_RSP_PARAM_1") if rsp_param_1 is not None: extern_data = rscpFindTagIndex(rsp_param_1, "WB_EXTERN_DATA") outObj["maxChargeCurrent"] = extern_data[2] + outObj = {k: v for k, v in sorted(outObj.items())} return outObj - def wallbox_set_sunmode(self, active: bool, keepAlive=False): + def set_wallbox_sunmode(self, active: bool, wallbox_index=0, keepAlive=False): """Sets the sun mode of the wallbox via rscp protocol locally. Args: active (bool): True to activate sun mode, otherwise false keepAlive (Optional[bool]): True to keep connection alive """ - return self.__wallbox_set_extern(0, 1 if active else 2, keepAlive) + return self.__wallbox_set_extern( + 0, 1 if active else 2, wallbox_index, keepAlive + ) - def wallbox_set_schuko(self, on: bool, keepAlive=False): + def set_wallbox_schuko(self, on: bool, wallbox_index=0, keepAlive=False): """Sets the Schuko of the wallbox via rscp protocol locally. Args: on (bool): True to activate the Schuko, otherwise false keepAlive (Optional[bool]): True to keep connection alive """ - return self.__wallbox_set_extern(5, 1 if on else 0, keepAlive) + return self.__wallbox_set_extern(5, 1 if on else 0, wallbox_index, keepAlive) - def wallbox_toggle(self, keepAlive=False): + def toggle_wallbox_charging(self, wallbox_index=0, keepAlive=False): """Toggles charging of the wallbox via rscp protocol locally. Args: keepAlive (Optional[bool]): True to keep connection alive """ - return self.__wallbox_set_extern(4, 1, keepAlive) + return self.__wallbox_set_extern(4, 1, wallbox_index, keepAlive) - def __wallbox_set_extern(self, index: int, value: int, keepAlive=False): + def __wallbox_set_extern( + self, index: int, value: int, wallbox_index, keepAlive=False + ): barry = bytearray([0, 0, 0, 0, 0, 0]) barry[index] = value self.sendRequest( @@ -1184,7 +1202,7 @@ def __wallbox_set_extern(self, index: int, value: int, keepAlive=False): "WB_REQ_DATA", "Container", [ - ("WB_INDEX", "UChar8", 0), + ("WB_INDEX", "UChar8", wallbox_index), ( "WB_REQ_SET_EXTERN", "Container", From 7eed623e046c7b738b7b68e872e78ffeb0a17745 Mon Sep 17 00:00:00 2001 From: Max Dhom Date: Sun, 6 Mar 2022 21:49:45 +0100 Subject: [PATCH 13/18] fixed args in docstrings --- e3dc/_e3dc.py | 5 ++++- 1 file changed, 4 insertions(+), 1 deletion(-) diff --git a/e3dc/_e3dc.py b/e3dc/_e3dc.py index 52fc996..6b87b48 100644 --- a/e3dc/_e3dc.py +++ b/e3dc/_e3dc.py @@ -1168,7 +1168,8 @@ def set_wallbox_sunmode(self, active: bool, wallbox_index=0, keepAlive=False): """Sets the sun mode of the wallbox via rscp protocol locally. Args: - active (bool): True to activate sun mode, otherwise false + active (bool): True to activate sun mode, otherwise false, + wallbox_index (Optional[int]): index of the requested wallbox, keepAlive (Optional[bool]): True to keep connection alive """ return self.__wallbox_set_extern( @@ -1180,6 +1181,7 @@ def set_wallbox_schuko(self, on: bool, wallbox_index=0, keepAlive=False): Args: on (bool): True to activate the Schuko, otherwise false + wallbox_index (Optional[int]): index of the requested wallbox, keepAlive (Optional[bool]): True to keep connection alive """ return self.__wallbox_set_extern(5, 1 if on else 0, wallbox_index, keepAlive) @@ -1188,6 +1190,7 @@ def toggle_wallbox_charging(self, wallbox_index=0, keepAlive=False): """Toggles charging of the wallbox via rscp protocol locally. Args: + wallbox_index (Optional[int]): index of the requested wallbox, keepAlive (Optional[bool]): True to keep connection alive """ return self.__wallbox_set_extern(4, 1, wallbox_index, keepAlive) From e4ed0df7120f93fbe3925b9f42f68595cefca2a5 Mon Sep 17 00:00:00 2001 From: Max Dhom Date: Sun, 6 Mar 2022 22:03:37 +0100 Subject: [PATCH 14/18] consistency fixes --- e3dc/_e3dc.py | 40 ++++++++++++++++++---------------------- 1 file changed, 18 insertions(+), 22 deletions(-) diff --git a/e3dc/_e3dc.py b/e3dc/_e3dc.py index 6b87b48..8eb1d4d 100644 --- a/e3dc/_e3dc.py +++ b/e3dc/_e3dc.py @@ -1083,11 +1083,11 @@ def get_system_status(self, keepAlive=False): outObj = {k: SystemStatusBools[v] for k, v in outObj.items()} return outObj - def get_wallbox_data(self, wallbox_index=0, keepAlive=False): + def get_wallbox_data(self, wbIndex=0, keepAlive=False): """Polls the wallbox status via rscp protocol locally. Args: - wallbox_index (Optional[int]): Index of the wallbox to poll data for + wbIndex (Optional[int]): Index of the wallbox to poll data for keepAlive (Optional[bool]): True to keep connection alive Returns: @@ -1101,12 +1101,12 @@ def get_wallbox_data(self, wallbox_index=0, keepAlive=False): "consumptionSun": , "energyAll": , "energySolar": , + "index": , "maxChargeCurrent": , "phases": , "schukoOn": , "soc": , - "sunModeOn": , - "wallboxIndex": + "sunModeOn": } """ req = self.sendRequest( @@ -1114,7 +1114,7 @@ def get_wallbox_data(self, wallbox_index=0, keepAlive=False): "WB_REQ_DATA", "Container", [ - ("WB_INDEX", "UChar8", wallbox_index), + ("WB_INDEX", "UChar8", wbIndex), ("WB_REQ_ENERGY_ALL", "None", None), ("WB_REQ_ENERGY_SOLAR", "None", None), ("WB_REQ_SOC", "None", None), @@ -1129,7 +1129,7 @@ def get_wallbox_data(self, wallbox_index=0, keepAlive=False): ) outObj = { - "wallboxIndex": rscpFindTagIndex(req, "WB_INDEX"), + "index": rscpFindTagIndex(req, "WB_INDEX"), "energyAll": rscpFindTagIndex(req, "WB_ENERGY_ALL"), "energySolar": rscpFindTagIndex(req, "WB_ENERGY_SOLAR"), "soc": rscpFindTagIndex(req, "WB_SOC"), @@ -1164,40 +1164,36 @@ def get_wallbox_data(self, wallbox_index=0, keepAlive=False): outObj = {k: v for k, v in sorted(outObj.items())} return outObj - def set_wallbox_sunmode(self, active: bool, wallbox_index=0, keepAlive=False): + def set_wallbox_sunmode(self, enable: bool, wbIndex=0, keepAlive=False): """Sets the sun mode of the wallbox via rscp protocol locally. Args: - active (bool): True to activate sun mode, otherwise false, - wallbox_index (Optional[int]): index of the requested wallbox, + enable (bool): True to enable sun mode, otherwise false, + wbIndex (Optional[int]): index of the requested wallbox, keepAlive (Optional[bool]): True to keep connection alive """ - return self.__wallbox_set_extern( - 0, 1 if active else 2, wallbox_index, keepAlive - ) + return self.__wallbox_set_extern(0, 1 if enable else 2, wbIndex, keepAlive) - def set_wallbox_schuko(self, on: bool, wallbox_index=0, keepAlive=False): + def set_wallbox_schuko(self, on: bool, wbIndex=0, keepAlive=False): """Sets the Schuko of the wallbox via rscp protocol locally. Args: on (bool): True to activate the Schuko, otherwise false - wallbox_index (Optional[int]): index of the requested wallbox, + wbIndex (Optional[int]): index of the requested wallbox, keepAlive (Optional[bool]): True to keep connection alive """ - return self.__wallbox_set_extern(5, 1 if on else 0, wallbox_index, keepAlive) + return self.__wallbox_set_extern(5, 1 if on else 0, wbIndex, keepAlive) - def toggle_wallbox_charging(self, wallbox_index=0, keepAlive=False): + def toggle_wallbox_charging(self, wbIndex=0, keepAlive=False): """Toggles charging of the wallbox via rscp protocol locally. Args: - wallbox_index (Optional[int]): index of the requested wallbox, + wbIndex (Optional[int]): index of the requested wallbox, keepAlive (Optional[bool]): True to keep connection alive """ - return self.__wallbox_set_extern(4, 1, wallbox_index, keepAlive) + return self.__wallbox_set_extern(4, 1, wbIndex, keepAlive) - def __wallbox_set_extern( - self, index: int, value: int, wallbox_index, keepAlive=False - ): + def __wallbox_set_extern(self, index: int, value: int, wbIndex, keepAlive=False): barry = bytearray([0, 0, 0, 0, 0, 0]) barry[index] = value self.sendRequest( @@ -1205,7 +1201,7 @@ def __wallbox_set_extern( "WB_REQ_DATA", "Container", [ - ("WB_INDEX", "UChar8", wallbox_index), + ("WB_INDEX", "UChar8", wbIndex), ( "WB_REQ_SET_EXTERN", "Container", From 446b8cfcc0c6ab1e54eb23c75e2ff59b0c106abe Mon Sep 17 00:00:00 2001 From: Max Dhom Date: Mon, 7 Mar 2022 08:40:53 +0100 Subject: [PATCH 15/18] set wallbox phases and max charge current --- e3dc/_e3dc.py | 42 ++++++++++++++++++++++++++++++++++++++++++ 1 file changed, 42 insertions(+) diff --git a/e3dc/_e3dc.py b/e3dc/_e3dc.py index 8eb1d4d..6d33dd5 100644 --- a/e3dc/_e3dc.py +++ b/e3dc/_e3dc.py @@ -1184,6 +1184,48 @@ def set_wallbox_schuko(self, on: bool, wbIndex=0, keepAlive=False): """ return self.__wallbox_set_extern(5, 1 if on else 0, wbIndex, keepAlive) + def set_wallbox_max_charge_current( + self, max_charge_current: int, wbIndex=0, keepAlive=False + ): + """Sets the maximum charge current of the wallbox via rscp protocol locally. + + Args: + max_charge_current (int): maximum allowed charge current in A + wbIndex (Optional[int]): index of the requested wallbox, + keepAlive (Optional[bool]): True to keep connection alive + """ + barry = bytearray([0, 0, max_charge_current, 0, 0, 0]) + return self.sendRequest( + ( + "WB_REQ_DATA", + "Container", + [ + ("WB_INDEX", "UChar8", wbIndex), + ( + "WB_REQ_SET_PARAM_1", + "Container", + [ + ("WB_EXTERN_DATA", "ByteArray", barry), + ("WB_EXTERN_DATA_LEN", "UChar8", 6), + ], + ), + ], + ), + keepAlive=keepAlive, + ) + + def set_wallbox_phases(self, phases: int, wbIndex=0, keepAlive=False): + """Sets the number of phases used for charging on the wallbox via rscp protocol locally. + + Args: + phases (int): number of phases used, valid values are 1 or 3 + wbIndex (Optional[int]): index of the requested wallbox, + keepAlive (Optional[bool]): True to keep connection alive + """ + if phases not in [1, 3]: + raise Exception("Invalid phase given, valid values are 1 or 3") + return self.__wallbox_set_extern(3, phases, wbIndex, keepAlive) + def toggle_wallbox_charging(self, wbIndex=0, keepAlive=False): """Toggles charging of the wallbox via rscp protocol locally. From 030bc41d45a2f9a9ef25c426f49a85250863692f Mon Sep 17 00:00:00 2001 From: Max Dhom <30362954+mdhom@users.noreply.github.com> Date: Sun, 1 May 2022 08:25:12 +0200 Subject: [PATCH 16/18] Apply suggestions from code review Co-authored-by: Michael Seibt <36601201+mstv@users.noreply.github.com> --- e3dc/_e3dc.py | 8 +++++--- 1 file changed, 5 insertions(+), 3 deletions(-) diff --git a/e3dc/_e3dc.py b/e3dc/_e3dc.py index 6d33dd5..103fcdb 100644 --- a/e3dc/_e3dc.py +++ b/e3dc/_e3dc.py @@ -1140,9 +1140,11 @@ def get_wallbox_data(self, wbIndex=0, keepAlive=False): if extern_data_alg is not None: extern_data = rscpFindTagIndex(extern_data_alg, "WB_EXTERN_DATA") status_byte = extern_data[2] - outObj["sunModeOn"] = (status_byte & 128) == 128 - outObj["chargingCanceled"] = (status_byte & 64) == 64 - outObj["chargingActive"] = (status_byte & 32) == 32 + outObj["sunModeOn"] = (status_byte & 128) != 0 + outObj["chargingCanceled"] = (status_byte & 64) != 0 + outObj["chargingActive"] = (status_byte & 32) != 0 + outObj["plugLocked"] = (status_byte & 16) != 0 + outObj["plugged"] = (status_byte & 8) != 0 outObj["phases"] = extern_data[1] outObj["schukoOn"] = extern_data[5] != 0 From 81e920bdc43f8e8e00408aee2c486727129e10a0 Mon Sep 17 00:00:00 2001 From: Max Dhom Date: Sun, 1 May 2022 08:44:39 +0200 Subject: [PATCH 17/18] only requesting necessary registers --- e3dc/_e3dc.py | 20 ++++++++------------ 1 file changed, 8 insertions(+), 12 deletions(-) diff --git a/e3dc/_e3dc.py b/e3dc/_e3dc.py index 103fcdb..c4a2fb4 100644 --- a/e3dc/_e3dc.py +++ b/e3dc/_e3dc.py @@ -1100,7 +1100,8 @@ def get_wallbox_data(self, wbIndex=0, keepAlive=False): "consumptionNet": , "consumptionSun": , "energyAll": , - "energySolar": , + "energySun": , + "energyNet": , "index": , "maxChargeCurrent": , "phases": , @@ -1115,13 +1116,9 @@ def get_wallbox_data(self, wbIndex=0, keepAlive=False): "Container", [ ("WB_INDEX", "UChar8", wbIndex), - ("WB_REQ_ENERGY_ALL", "None", None), - ("WB_REQ_ENERGY_SOLAR", "None", None), - ("WB_REQ_SOC", "None", None), ("WB_REQ_EXTERN_DATA_ALG", "None", None), ("WB_REQ_EXTERN_DATA_SUN", "None", None), ("WB_REQ_EXTERN_DATA_NET", "None", None), - ("WB_REQ_PARAM_1", "None", None), ("WB_REQ_APP_SOFTWARE", "None", None), ], ), @@ -1130,9 +1127,6 @@ def get_wallbox_data(self, wbIndex=0, keepAlive=False): outObj = { "index": rscpFindTagIndex(req, "WB_INDEX"), - "energyAll": rscpFindTagIndex(req, "WB_ENERGY_ALL"), - "energySolar": rscpFindTagIndex(req, "WB_ENERGY_SOLAR"), - "soc": rscpFindTagIndex(req, "WB_SOC"), "appSoftware": rscpFindTagIndex(req, "WB_APP_SOFTWARE"), } @@ -1145,23 +1139,25 @@ def get_wallbox_data(self, wbIndex=0, keepAlive=False): outObj["chargingActive"] = (status_byte & 32) != 0 outObj["plugLocked"] = (status_byte & 16) != 0 outObj["plugged"] = (status_byte & 8) != 0 + outObj["soc"] = extern_data[0] outObj["phases"] = extern_data[1] + outObj["maxChargeCurrent"] = extern_data[3] outObj["schukoOn"] = extern_data[5] != 0 extern_data_sun = rscpFindTag(req, "WB_EXTERN_DATA_SUN") if extern_data_sun is not None: extern_data = rscpFindTagIndex(extern_data_sun, "WB_EXTERN_DATA") outObj["consumptionSun"] = struct.unpack("h", extern_data[0:2])[0] + outObj["energySun"] = struct.unpack("i", extern_data[2:6])[0] extern_data_net = rscpFindTag(req, "WB_EXTERN_DATA_NET") if extern_data_net is not None: extern_data = rscpFindTagIndex(extern_data_net, "WB_EXTERN_DATA") outObj["consumptionNet"] = struct.unpack("h", extern_data[0:2])[0] + outObj["energyNet"] = struct.unpack("i", extern_data[2:6])[0] - rsp_param_1 = rscpFindTag(req, "WB_RSP_PARAM_1") - if rsp_param_1 is not None: - extern_data = rscpFindTagIndex(rsp_param_1, "WB_EXTERN_DATA") - outObj["maxChargeCurrent"] = extern_data[2] + if "energySun" in outObj and "energyNet" in outObj: + outObj["energyAll"] = outObj["energyNet"] + outObj["energySun"] outObj = {k: v for k, v in sorted(outObj.items())} return outObj From 36aefce2d430f12e5092ddcb3988c496c15a23d4 Mon Sep 17 00:00:00 2001 From: Max Dhom Date: Mon, 2 May 2022 13:54:37 +0200 Subject: [PATCH 18/18] reading wallbox keyswitch state --- e3dc/_e3dc.py | 8 +++++++- 1 file changed, 7 insertions(+), 1 deletion(-) diff --git a/e3dc/_e3dc.py b/e3dc/_e3dc.py index c4a2fb4..f362e9b 100644 --- a/e3dc/_e3dc.py +++ b/e3dc/_e3dc.py @@ -1100,9 +1100,10 @@ def get_wallbox_data(self, wbIndex=0, keepAlive=False): "consumptionNet": , "consumptionSun": , "energyAll": , - "energySun": , "energyNet": , + "energySun": , "index": , + "keyState": , "maxChargeCurrent": , "phases": , "schukoOn": , @@ -1120,6 +1121,7 @@ def get_wallbox_data(self, wbIndex=0, keepAlive=False): ("WB_REQ_EXTERN_DATA_SUN", "None", None), ("WB_REQ_EXTERN_DATA_NET", "None", None), ("WB_REQ_APP_SOFTWARE", "None", None), + ("WB_REQ_KEY_STATE", "None", None), ], ), keepAlive=keepAlive, @@ -1159,6 +1161,10 @@ def get_wallbox_data(self, wbIndex=0, keepAlive=False): if "energySun" in outObj and "energyNet" in outObj: outObj["energyAll"] = outObj["energyNet"] + outObj["energySun"] + key_state = rscpFindTag(req, "WB_KEY_STATE") + if key_state is not None: + outObj["keyState"] = rscpFindTagIndex(key_state, "WB_KEY_STATE") + outObj = {k: v for k, v in sorted(outObj.items())} return outObj