From 8bfc642ae4a0f95c3f6365b3dc9d1c092c843aab Mon Sep 17 00:00:00 2001 From: Tuen Lee Date: Sat, 12 Aug 2023 18:38:37 +0200 Subject: [PATCH 1/2] fix #40 show display error, fix #41 add and rename some entities --- custom_components/alfen_wallbox/alfen.py | 4 +-- custom_components/alfen_wallbox/const.py | 2 ++ custom_components/alfen_wallbox/number.py | 28 +++++++++++---- custom_components/alfen_wallbox/select.py | 44 +++++++++++++++++++++++ custom_components/alfen_wallbox/sensor.py | 44 +++++++++++++++++++---- custom_components/alfen_wallbox/switch.py | 4 +-- 6 files changed, 109 insertions(+), 17 deletions(-) diff --git a/custom_components/alfen_wallbox/alfen.py b/custom_components/alfen_wallbox/alfen.py index 55d2273..3101471 100644 --- a/custom_components/alfen_wallbox/alfen.py +++ b/custom_components/alfen_wallbox/alfen.py @@ -5,7 +5,7 @@ from datetime import timedelta from homeassistant.util import Throttle -from .const import CAT, CAT_GENERIC, CAT_GENERIC2, CAT_METER1, CAT_OCPP, CAT_STATES, CAT_TEMP, CMD, DOMAIN, ALFEN_PRODUCT_MAP, ID, METHOD_GET, METHOD_POST, OFFSET, INFO, LOGIN, LOGOUT, PARAM_COMMAND, PARAM_PASSWORD, PARAM_USERNAME, PROP, PROPERTIES, TOTAL, VALUE +from .const import CAT, CAT_GENERIC, CAT_GENERIC2, CAT_METER1, CAT_METER4, CAT_OCPP, CAT_STATES, CAT_TEMP,CAT_MBUS_TCP, CMD, DOMAIN, ALFEN_PRODUCT_MAP, ID, METHOD_GET, METHOD_POST, OFFSET, INFO, LOGIN, LOGOUT, PARAM_COMMAND, PARAM_PASSWORD, PARAM_USERNAME, PROP, PROPERTIES, TOTAL, VALUE HEADER_JSON = {"content-type": "alfen/json; charset=utf-8"} POST_HEADER_JSON = {"content-type": "application/json"} @@ -124,7 +124,7 @@ async def _do_update(self): await self.login() properties = [] - for cat in (CAT_GENERIC, CAT_GENERIC2, CAT_METER1, CAT_STATES, CAT_TEMP, CAT_OCPP): + for cat in (CAT_GENERIC, CAT_GENERIC2, CAT_METER1, CAT_STATES, CAT_TEMP, CAT_OCPP, CAT_METER4, CAT_MBUS_TCP): nextRequest = True offset = 0 while (nextRequest): diff --git a/custom_components/alfen_wallbox/const.py b/custom_components/alfen_wallbox/const.py index 51775d2..95876e7 100644 --- a/custom_components/alfen_wallbox/const.py +++ b/custom_components/alfen_wallbox/const.py @@ -24,9 +24,11 @@ CAT_GENERIC = "generic" CAT_GENERIC2 = "generic2" CAT_METER1 = "meter1" +CAT_METER4= "meter4" CAT_STATES = "states" CAT_TEMP = "temp" CAT_OCPP = "ocpp" +CAT_MBUS_TCP = "MbusTCP" COMMAND_REBOOT = "reboot" diff --git a/custom_components/alfen_wallbox/number.py b/custom_components/alfen_wallbox/number.py index aca8645..df246c4 100644 --- a/custom_components/alfen_wallbox/number.py +++ b/custom_components/alfen_wallbox/number.py @@ -41,7 +41,7 @@ class AlfenNumberDescription(NumberEntityDescription, AlfenNumberDescriptionMixi ALFEN_NUMBER_TYPES: Final[tuple[AlfenNumberDescription, ...]] = ( AlfenNumberDescription( key="alb_safe_current", - name="ALB Safe Current", + name="Load Balancing Safe Current", state=None, icon="mdi:current-ac", assumed_state=False, @@ -54,8 +54,8 @@ class AlfenNumberDescription(NumberEntityDescription, AlfenNumberDescriptionMixi api_param="2068_0", ), AlfenNumberDescription( - key="current_limit", - name="Current Limit", + key="ps_connector_max_limit", + name="Power Connector Max Current", state=None, icon="mdi:current-ac", assumed_state=False, @@ -82,8 +82,8 @@ class AlfenNumberDescription(NumberEntityDescription, AlfenNumberDescriptionMixi api_param="2062_0", ), AlfenNumberDescription( - key="max_smart_meter_current", - name="Max. Meter Current", + key="lb_max_smart_meter_current", + name="Load Balancing Max. Meter Current", state=None, icon="mdi:current-ac", assumed_state=False, @@ -138,8 +138,8 @@ class AlfenNumberDescription(NumberEntityDescription, AlfenNumberDescriptionMixi api_param="2061_2", ), AlfenNumberDescription( - key="lb_max_imbalance_current", - name="Max. Imbalance Current between phases", + key="ps_installation_max_imbalance_current", + name="Installation Max. Imbalance Current between phases", state=None, icon="mdi:current-ac", assumed_state=False, @@ -151,6 +151,20 @@ class AlfenNumberDescription(NumberEntityDescription, AlfenNumberDescriptionMixi unit_of_measurement=UnitOfElectricCurrent.AMPERE, api_param="2174_0", ), + AlfenNumberDescription( + key="lb_Charging_profiles_random_delay", + name="Load Balancing Charging profiles random delay", + state=None, + icon="mdi:timer-sand", + assumed_state=False, + device_class=NumberDeviceClass.POWER_FACTOR, + native_min_value=0, + native_max_value=30, + native_step=1, + custom_mode=None, + unit_of_measurement=UnitOfElectricCurrent.AMPERE, + api_param="21B9_0", + ), ) diff --git a/custom_components/alfen_wallbox/select.py b/custom_components/alfen_wallbox/select.py index ca4fe47..9486152 100644 --- a/custom_components/alfen_wallbox/select.py +++ b/custom_components/alfen_wallbox/select.py @@ -80,6 +80,7 @@ class AlfenSelectDescription(SelectEntityDescription, AlfenSelectDescriptionMixi LOAD_BALANCE_DATA_SOURCE_DICT: Final[dict[str, int]] = { "Meter": 0, + "Meter + EMS Monitoring": 1, "Energy Management System": 3 } @@ -135,6 +136,17 @@ class AlfenSelectDescription(SelectEntityDescription, AlfenSelectDescriptionMixi "4G": 2, } +DSMR_SMR_INTERFACE_DICT: Final[dict[str, int]] = { + "Serial" : 0, + "Telnet" : 1, + "HomeWizard Wi-Fi P1": 2, +} + +DIRECT_EXTERNAL_SUSPEND_SIGNAL: Final[dict[str, int]] = { + "Not allowed": 0, + "Allowed, suspend when closed": 1, + "Allowed, suspend when open": 2 +} ALFEN_SELECT_TYPES: Final[tuple[AlfenSelectDescription, ...]] = ( AlfenSelectDescription( @@ -236,6 +248,38 @@ class AlfenSelectDescription(SelectEntityDescription, AlfenSelectDescriptionMixi options_dict=GPRS_TECHNOLOGY_DICT, api_param="2114_0", ), + AlfenSelectDescription( + key="lb_dsmr_smr_interface", + name="Load Balancing DSMR/SMR Interface", + icon="mdi:scale-balance", + options=list(DSMR_SMR_INTERFACE_DICT), + options_dict=DSMR_SMR_INTERFACE_DICT, + api_param="2191_1", + ), + AlfenSelectDescription( + key="lb_data_source", + name="Load Balancing Data Source", + icon="mdi:scale-balance", + options=list(LOAD_BALANCE_DATA_SOURCE_DICT), + options_dict=LOAD_BALANCE_DATA_SOURCE_DICT, + api_param="2530_1", + ), + AlfenSelectDescription( + key="ps_installation_max_allowed_phase", + name="Installation Max. Allowed Phases", + icon="mdi:scale-balance", + options=list(ALLOWED_PHASE_DICT), + options_dict=ALLOWED_PHASE_DICT, + api_param="2189_0", + ), + AlfenSelectDescription( + key="ps_installation_direct external suspend signal", + name="Installation Direct External Suspend Signal", + icon="mdi:scale-balance", + options=list(DIRECT_EXTERNAL_SUSPEND_SIGNAL), + options_dict=DIRECT_EXTERNAL_SUSPEND_SIGNAL, + api_param="2189_0", + ), ) diff --git a/custom_components/alfen_wallbox/sensor.py b/custom_components/alfen_wallbox/sensor.py index f76533e..81cfefa 100644 --- a/custom_components/alfen_wallbox/sensor.py +++ b/custom_components/alfen_wallbox/sensor.py @@ -100,6 +100,37 @@ class AlfenSensorDescription( 43: "Partial Solar Charging", } +DISPLAY_ERROR_DICT: Final[dict[int, str]] = { + 0: "No Error", + 1: "Not able to charge. Please call for support.", + 101: "One moment please. Your charging session will resume shortly.", + 102: "Not able to charge. Please call for support", + 104: "Not able to charge. Please call for support.", + 105: "Not able to charge. Please call for support.", + 106: "Not able to charge. Please call for support.", + 108: "Not displayed", + 109: "Not displayed", + 201: "Error in installation. Please check installation or call for support.", + 202: "Input voltage too low, not able to charge. Please call your installer.", + 206: "Temporary set to unavailable. Contact CPO or try again later.", + 208: "Not displayed", + 209: "Not displayed", + 210: "Not displayed", + 211: "Not able to lock cable. Please call for support.", + 212: "Error in installation. Please check installation or call for support.", + 213: "Not displayed", + 301: "One moment please your charging session will resume shortly.", + 302: "One moment please your charging session will resume shortly.", + 303: "One moment please your charging session will resume shortly.", + 304: "Charging not started yet to continue please reconnect cable.", + 401: "Inside temperature high. Charging will resume shortly.", + 402: "Inside temperature low. Charging will resume shortly.", + 404: "Not able to lock cable. Please reconnect cable.", + 405: "Cable not supported. Please try connecting your cable again.", + 406: "No communication with vehicle. Please check your charging cable.", + 407: "Not displayed" +} + ALLOWED_PHASE_DICT: Final[dict[int, str]] = { 1: "1 Phase", 3: "3 Phases" @@ -535,8 +566,8 @@ class AlfenSensorDescription( round_digits=1, ), AlfenSensorDescription( - key="lb_max_allowed_phase_socket_1", - name="LB Max Allowed of Phases Socket 1", + key="ps_connector_1_max_allowed_phase", + name="Connector 1 Max Allowed of Phases", icon="mdi:scale-balance", unit=None, api_param="312E_0", @@ -560,15 +591,13 @@ class AlfenSensorDescription( ), # 2 Socket devices # AlfenSensorDescription( - # key="lb_max_allowed_phase_socket_2", - # name="Load Balancing Max Allowed of Phases Socket 2", + # key="ps_connector_2_max_allowed_phase", + # name="Connector 2 Max Allowed of Phases", # icon="mdi:scale-balance", # unit=None, # api_param="312F_0", # round_digits=None, # ), - - ) @@ -756,6 +785,9 @@ def state(self) -> StateType: if self.entity_description.round_digits is not None: return round(prop[VALUE], self.entity_description.round_digits) + if self.entity_description.api_param == "3190_2": + return str(prop[VALUE]) + ': ' + DISPLAY_ERROR_DICT.get(prop[VALUE], 'Unknown') + return prop[VALUE] @property diff --git a/custom_components/alfen_wallbox/switch.py b/custom_components/alfen_wallbox/switch.py index 152a073..6fceafd 100644 --- a/custom_components/alfen_wallbox/switch.py +++ b/custom_components/alfen_wallbox/switch.py @@ -33,8 +33,8 @@ class AlfenSwitchDescription(SwitchEntityDescription, AlfenSwitchDescriptionMixi ALFEN_BINARY_SENSOR_TYPES: Final[tuple[AlfenSwitchDescription, ...]] = ( AlfenSwitchDescription( - key="enable_phase_switching", - name="Enable Phase Switching", + key="lb_enable_phase_switching", + name="Load Balancing Enable Phase Switching", api_param="2185_0", ), AlfenSwitchDescription( From 5636719da43996ab80b0d6e3743d7c3f8704316e Mon Sep 17 00:00:00 2001 From: Tuen Lee Date: Sat, 12 Aug 2023 23:09:04 +0200 Subject: [PATCH 2/2] fix #42: autorization part and plug&chargeID --- custom_components/alfen_wallbox/__init__.py | 3 +- custom_components/alfen_wallbox/alfen.py | 4 +- custom_components/alfen_wallbox/const.py | 1 + custom_components/alfen_wallbox/number.py | 30 ++++++++ custom_components/alfen_wallbox/select.py | 32 ++------ custom_components/alfen_wallbox/switch.py | 20 +++++ custom_components/alfen_wallbox/text.py | 84 +++++++++++++++++++++ 7 files changed, 144 insertions(+), 30 deletions(-) create mode 100644 custom_components/alfen_wallbox/text.py diff --git a/custom_components/alfen_wallbox/__init__.py b/custom_components/alfen_wallbox/__init__.py index dd19f2c..4a1301b 100644 --- a/custom_components/alfen_wallbox/__init__.py +++ b/custom_components/alfen_wallbox/__init__.py @@ -32,7 +32,8 @@ Platform.BINARY_SENSOR, Platform.SWITCH, Platform.NUMBER, - Platform.BUTTON + Platform.BUTTON, + Platform.TEXT ] SCAN_INTERVAL = timedelta(seconds=60) diff --git a/custom_components/alfen_wallbox/alfen.py b/custom_components/alfen_wallbox/alfen.py index 3101471..5f4ab63 100644 --- a/custom_components/alfen_wallbox/alfen.py +++ b/custom_components/alfen_wallbox/alfen.py @@ -5,7 +5,7 @@ from datetime import timedelta from homeassistant.util import Throttle -from .const import CAT, CAT_GENERIC, CAT_GENERIC2, CAT_METER1, CAT_METER4, CAT_OCPP, CAT_STATES, CAT_TEMP,CAT_MBUS_TCP, CMD, DOMAIN, ALFEN_PRODUCT_MAP, ID, METHOD_GET, METHOD_POST, OFFSET, INFO, LOGIN, LOGOUT, PARAM_COMMAND, PARAM_PASSWORD, PARAM_USERNAME, PROP, PROPERTIES, TOTAL, VALUE +from .const import CAT, CAT_GENERIC, CAT_GENERIC2, CAT_METER1, CAT_METER4, CAT_OCPP, CAT_STATES, CAT_TEMP,CAT_MBUS_TCP,CAT_COMM, CMD, DOMAIN, ALFEN_PRODUCT_MAP, ID, METHOD_GET, METHOD_POST, OFFSET, INFO, LOGIN, LOGOUT, PARAM_COMMAND, PARAM_PASSWORD, PARAM_USERNAME, PROP, PROPERTIES, TOTAL, VALUE HEADER_JSON = {"content-type": "alfen/json; charset=utf-8"} POST_HEADER_JSON = {"content-type": "application/json"} @@ -124,7 +124,7 @@ async def _do_update(self): await self.login() properties = [] - for cat in (CAT_GENERIC, CAT_GENERIC2, CAT_METER1, CAT_STATES, CAT_TEMP, CAT_OCPP, CAT_METER4, CAT_MBUS_TCP): + for cat in (CAT_GENERIC, CAT_GENERIC2, CAT_METER1, CAT_STATES, CAT_TEMP, CAT_OCPP, CAT_METER4, CAT_MBUS_TCP, CAT_COMM): nextRequest = True offset = 0 while (nextRequest): diff --git a/custom_components/alfen_wallbox/const.py b/custom_components/alfen_wallbox/const.py index 95876e7..2f52f17 100644 --- a/custom_components/alfen_wallbox/const.py +++ b/custom_components/alfen_wallbox/const.py @@ -29,6 +29,7 @@ CAT_TEMP = "temp" CAT_OCPP = "ocpp" CAT_MBUS_TCP = "MbusTCP" +CAT_COMM = "comm" COMMAND_REBOOT = "reboot" diff --git a/custom_components/alfen_wallbox/number.py b/custom_components/alfen_wallbox/number.py index df246c4..3a3629d 100644 --- a/custom_components/alfen_wallbox/number.py +++ b/custom_components/alfen_wallbox/number.py @@ -14,6 +14,7 @@ PERCENTAGE, UnitOfElectricCurrent, UnitOfPower, + UnitOfTime, ) import voluptuous as vol @@ -165,6 +166,35 @@ class AlfenNumberDescription(NumberEntityDescription, AlfenNumberDescriptionMixi unit_of_measurement=UnitOfElectricCurrent.AMPERE, api_param="21B9_0", ), + AlfenNumberDescription( + key="auth_re_authorize_after_power_outage", + name="Auth. Re-authorize after Power Outage (s)", + state=None, + icon="mdi:timer-sand", + assumed_state=False, + device_class=None, + native_min_value=0, + native_max_value=30, + native_step=1, + custom_mode=None, + unit_of_measurement=UnitOfTime.SECONDS, + api_param="2169_0", + ), + AlfenNumberDescription( + key="auth_connection_timeout", + name="Auth. Connection Timeout (s)", + state=None, + icon="mdi:timer-sand", + assumed_state=False, + device_class=None, + native_min_value=0, + native_max_value=30, + native_step=1, + custom_mode=None, + unit_of_measurement=UnitOfTime.SECONDS, + api_param="2169_0", + ), + ) diff --git a/custom_components/alfen_wallbox/select.py b/custom_components/alfen_wallbox/select.py index 9486152..e07426b 100644 --- a/custom_components/alfen_wallbox/select.py +++ b/custom_components/alfen_wallbox/select.py @@ -55,19 +55,6 @@ class AlfenSelectDescription(SelectEntityDescription, AlfenSelectDescriptionMixi "L3,L2,L1": "L3L2L1", } -SAFE_AMPS_DICT: Final[dict[str, int]] = { - "1 A": 1, - "2 A": 2, - "3 A": 3, - "4 A": 4, - "5 A": 5, - "6 A": 6, - "7 A": 7, - "8 A": 8, - "9 A": 9, - "10 A": 10, -} - AUTH_MODE_DICT: Final[dict[str, int]] = { "Plug and Charge": 0, "RFID": 2 @@ -159,25 +146,16 @@ class AlfenSelectDescription(SelectEntityDescription, AlfenSelectDescriptionMixi ), AlfenSelectDescription( - key="alb_phase_connection", - name="Active Load Balancing Phase Connection", + key="lb_phase_connection", + name="Load Balancing Phase Connection", icon=None, options=list(PHASE_ROTATION_DICT), options_dict=PHASE_ROTATION_DICT, api_param="2069_0", ), - # AlfenSelectDescription( - # key="alb_safe_current", - # name="Active Load Balancing Safe Current", - # icon="mdi:current-ac", - # options=list(SAFE_AMPS_DICT), - # options_dict=SAFE_AMPS_DICT, - # api_param="2068_0", - # ), - AlfenSelectDescription( key="auth_mode", - name="Authorization Mode", + name="Auth. Mode", icon="mdi:key", options=list(AUTH_MODE_DICT), options_dict=AUTH_MODE_DICT, @@ -273,12 +251,12 @@ class AlfenSelectDescription(SelectEntityDescription, AlfenSelectDescriptionMixi api_param="2189_0", ), AlfenSelectDescription( - key="ps_installation_direct external suspend signal", + key="ps_installation_direct_external_suspend_signal", name="Installation Direct External Suspend Signal", icon="mdi:scale-balance", options=list(DIRECT_EXTERNAL_SUSPEND_SIGNAL), options_dict=DIRECT_EXTERNAL_SUSPEND_SIGNAL, - api_param="2189_0", + api_param="216C_0", ), ) diff --git a/custom_components/alfen_wallbox/switch.py b/custom_components/alfen_wallbox/switch.py index 6fceafd..e2b4b67 100644 --- a/custom_components/alfen_wallbox/switch.py +++ b/custom_components/alfen_wallbox/switch.py @@ -47,6 +47,26 @@ class AlfenSwitchDescription(SwitchEntityDescription, AlfenSwitchDescriptionMixi name="Solar Charging Boost", api_param="3280_4", ), + AlfenSwitchDescription( + key="auth_white_list", + name="Auth. Whitelist", + api_param="213B_0", + ), + AlfenSwitchDescription( + key="auth_local_list", + name="Auth. Whitelist", + api_param="213D_0", + ), + AlfenSwitchDescription( + key="auth_restart_after_power_outage", + name="Auth. Restart after Power Outage", + api_param="215E_0", + ), + AlfenSwitchDescription( + key="auth_remote_transaction_request", + name="Auth. Remote Transaction requests", + api_param="209B_0", + ), ) diff --git a/custom_components/alfen_wallbox/text.py b/custom_components/alfen_wallbox/text.py new file mode 100644 index 0000000..2ff3ec6 --- /dev/null +++ b/custom_components/alfen_wallbox/text.py @@ -0,0 +1,84 @@ +from __future__ import annotations +import logging + +from dataclasses import dataclass +from typing import Final + +from .const import ID +from homeassistant.components.counter import VALUE + +from .alfen import AlfenDevice +from .entity import AlfenEntity + + +from homeassistant.components.text import TextEntity, TextEntityDescription, TextMode +from homeassistant.config_entries import ConfigEntry +from homeassistant.core import HomeAssistant, callback +from homeassistant.helpers.entity_platform import AddEntitiesCallback + +from . import DOMAIN as ALFEN_DOMAIN +from . import DOMAIN + +_LOGGER = logging.getLogger(__name__) + +@dataclass +class AlfenTextDescriptionMixin: + """Define an entity description mixin for text entities.""" + + api_param: str + +@dataclass +class AlfenTextDescription(TextEntityDescription, AlfenTextDescriptionMixin): + """Class to describe an Alfen text entity.""" + +ALFEN_TEXT_TYPES: Final[tuple[AlfenTextDescription,...]] = ( + AlfenTextDescription( + key="auth_plug_and_charge_id", + name="Auth. Plug & Charge ID", + icon="mdi:key", + api_param="2063_0" + ), +) + +async def async_setup_entry( + hass: HomeAssistant, entry: ConfigEntry, async_add_entities: AddEntitiesCallback +) -> None: + """Add Alfen Select from a config_entry""" + + device = hass.data[ALFEN_DOMAIN][entry.entry_id] + texts = [AlfenText(device, description) + for description in ALFEN_TEXT_TYPES] + + async_add_entities(texts) + +class AlfenText(AlfenEntity, TextEntity): + """Representation of a Alfen text entity.""" + + def __init__( + self, device: AlfenDevice, description: AlfenTextDescription + )->None: + super().__init__(device) + self._device = device + self._attr_name = f"{device.name} {description.name}" + + self._attr_unique_id = f"{self._device.id}_{description.key}" + self.entity_description = description + self._async_update_attrs() + + @callback + def _async_update_attrs(self) -> None: + """Update text attributes.""" + self._attr_native_value = self._get_current_value() + + def _get_current_value(self) -> str | None: + """Return the current value.""" + for prop in self._device.properties: + if prop[ID] == self.entity_description.api_param: + return prop[VALUE] + return None + + async def async_set_value(self, value: str) -> None: + """Update the value.""" + self._attr_native_value = value + await self.update_state(self.entity_description.api_param,value) + self.async_write_ha_state() \ No newline at end of file