diff --git a/custom_components/easee/__init__.py b/custom_components/easee/__init__.py index 594af139..1a06103d 100644 --- a/custom_components/easee/__init__.py +++ b/custom_components/easee/__init__.py @@ -9,7 +9,6 @@ from .const import ( DOMAIN, - EASEE_ENTITIES, LISTENER_FN_CLOSE, MEASURED_CONSUMPTION_DAYS, VERSION, diff --git a/custom_components/easee/config_flow.py b/custom_components/easee/config_flow.py index 4c26bea8..434d010f 100644 --- a/custom_components/easee/config_flow.py +++ b/custom_components/easee/config_flow.py @@ -19,11 +19,13 @@ from .const import ( DOMAIN, + CONSUMPTION_DAYS_PREFIX, MEASURED_CONSUMPTION_DAYS, MEASURED_CONSUMPTION_OPTIONS, CUSTOM_UNITS, CUSTOM_UNITS_OPTIONS, - EASEE_ENTITIES, + OPTIONAL_EASEE_ENTITIES, + MANDATORY_EASEE_ENTITIES, EASEE_EQ_ENTITIES, CONF_MONITORED_SITES, CONF_MONITORED_EQ_CONDITIONS, @@ -101,14 +103,18 @@ def __init__(self, config_entry): async def async_step_init(self, user_input=None): """Manage the options.""" - if user_input is not None: - self.options.update(user_input) - return await self._update_options() - constroller = self.hass.data[DOMAIN]["controller"] - sensor_multi_select = {x: x for x in list(EASEE_ENTITIES)} + errors = {} + if user_input is not None: + if len(user_input[CONF_MONITORED_SITES]) == 0: + errors["base"] = "no_sites" + else: + self.options.update(user_input) + return await self._update_options() + controller = self.hass.data[DOMAIN]["controller"] + sensor_multi_select = {x: x for x in list(OPTIONAL_EASEE_ENTITIES)} sensor_eq_multi_select = {x: x for x in list(EASEE_EQ_ENTITIES)} - sites: List[Site] = constroller.get_sites() + sites: List[Site] = controller.get_sites() sites_multi_select = [] for site in sites: sites_multi_select.append(site["name"]) @@ -126,7 +132,7 @@ async def async_step_init(self, user_input=None): vol.Optional( CONF_MONITORED_CONDITIONS, default=self.config_entry.options.get( - CONF_MONITORED_CONDITIONS, ["status"] + CONF_MONITORED_CONDITIONS, [] ), ): cv.multi_select(sensor_multi_select), vol.Optional( @@ -147,9 +153,13 @@ async def async_step_init(self, user_input=None): ): cv.multi_select(CUSTOM_UNITS_OPTIONS), } ), + errors=errors, ) async def _update_options(self): + for x in self.options[CONF_MONITORED_CONDITIONS]: + if x in MANDATORY_EASEE_ENTITIES: + del self.options[CONF_MONITORED_CONDITIONS][x] """Update config entry options.""" self.hass.data[DOMAIN]["entities_to_remove"] = [cond for cond in self.prev_options.get(CONF_MONITORED_CONDITIONS, {}) if cond not in self.options[CONF_MONITORED_CONDITIONS]] @@ -157,6 +167,7 @@ async def _update_options(self): if cond not in self.options[CONF_MONITORED_EQ_CONDITIONS]] self.hass.data[DOMAIN]["sites_to_remove"] = [cond for cond in self.prev_options.get(CONF_MONITORED_SITES, {}) if cond not in self.options[CONF_MONITORED_SITES]] - self.hass.data[DOMAIN]["days_to_remove"] = [cond for cond in self.prev_options.get(MEASURED_CONSUMPTION_DAYS, {}) + self.hass.data[DOMAIN]["days_to_remove"] = [f"{CONSUMPTION_DAYS_PREFIX}{cond}" for cond in self.prev_options.get(MEASURED_CONSUMPTION_DAYS, {}) if cond not in self.options[MEASURED_CONSUMPTION_DAYS]] + _LOGGER.debug("Days_to_remove: %s", self.hass.data[DOMAIN]["days_to_remove"]) return self.async_create_entry(title="", data=self.options) diff --git a/custom_components/easee/const.py b/custom_components/easee/const.py index 2d7b2ac7..c1a383c9 100644 --- a/custom_components/easee/const.py +++ b/custom_components/easee/const.py @@ -27,6 +27,7 @@ CONF_MONITORED_SITES = "monitored_sites" CONF_MONITORED_EQ_CONDITIONS = "monitored_eq_conditions" CUSTOM_UNITS = "custom_units" +CONSUMPTION_DAYS_PREFIX = "consumption_days_" PLATFORMS = ("sensor", "switch", "binary_sensor") LISTENER_FN_CLOSE = "update_listener_close_fn" MEASURED_CONSUMPTION_OPTIONS = { @@ -44,7 +45,32 @@ POWER_KILO_WATT: POWER_WATT, ENERGY_KILO_WATT_HOUR: ENERGY_WATT_HOUR, } -EASEE_ENTITIES = { +MANDATORY_EASEE_ENTITIES = { + "status": { + "key": "state.chargerOpMode", + "attrs": [ + "config.phaseMode", + "state.outputPhase", + "state.ledMode", + "state.cableRating", + "config.authorizationRequired", + "config.limitToSinglePhaseCharging", + "config.localNodeType", + "config.localAuthorizationRequired", + "config.ledStripBrightness", + "site.id", + "site.name", + "site.siteKey", + "circuit.id", + "circuit.ratedCurrent", + ], + "units": None, + "convert_units_func": None, + "device_class": "easee_status", + "icon": "mdi:ev-station", + }, +} +OPTIONAL_EASEE_ENTITIES = { "smart_charging": { "type": "switch", "key": "state.smartCharging", @@ -81,29 +107,6 @@ "icon": "mdi:lock", "switch_func": "lockCablePermanently", }, - "status": { - "key": "state.chargerOpMode", - "attrs": [ - "config.phaseMode", - "state.outputPhase", - "state.ledMode", - "state.cableRating", - "config.authorizationRequired", - "config.limitToSinglePhaseCharging", - "config.localNodeType", - "config.localAuthorizationRequired", - "config.ledStripBrightness", - "site.id", - "site.name", - "site.siteKey", - "circuit.id", - "circuit.ratedCurrent", - ], - "units": None, - "convert_units_func": None, - "device_class": "easee_status", - "icon": "mdi:ev-station", - }, "total_power": { "key": "state.totalPower", "attrs": [], diff --git a/custom_components/easee/controller.py b/custom_components/easee/controller.py index 0369d371..74f45edf 100644 --- a/custom_components/easee/controller.py +++ b/custom_components/easee/controller.py @@ -35,8 +35,10 @@ from .const import ( CONF_MONITORED_SITES, CONF_MONITORED_EQ_CONDITIONS, - EASEE_ENTITIES, + OPTIONAL_EASEE_ENTITIES, + MANDATORY_EASEE_ENTITIES, EASEE_EQ_ENTITIES, + CONSUMPTION_DAYS_PREFIX, MEASURED_CONSUMPTION_DAYS, CUSTOM_UNITS, CUSTOM_UNITS_TABLE, @@ -83,7 +85,7 @@ def __init__(self, charger: Charger, circuit: Circuit, site: Site): async def schedules_async_refresh(self): try: self.schedule = await self.charger.get_basic_charge_plan() - except (TooManyRequestsException, ServerFailureException) as err: + except (TooManyRequestsException, ServerFailureException): _LOGGER.debug("Got server error while fetching schedule") except NotFoundException: self.schedule = None @@ -110,7 +112,7 @@ def __init__( self.equalizers_data: List[EqualizerData] = [] self.switch_entities = [] self.sensor_entities = [] - self.equalizer_entities = [] + self.equalizer_sensor_entities = [] self.next_consumption_sensor = 0 async def initialize(self): @@ -181,7 +183,7 @@ def update_ha_state(self): def update_equalizers_state(self): # Schedule an update for all equalizer entities - for entity in self.equalizer_entities: + for entity in self.equalizer_sensor_entities: entity.async_schedule_update_ha_state(True) async def add_schedulers(self): @@ -289,16 +291,16 @@ def get_sensor_entities(self): return ( self.sensor_entities + self.consumption_sensor_entities - + self.equalizer_entities + + self.equalizer_sensor_entities ) def get_switch_entities(self): return self.switch_entities def _create_entitites(self): - monitored_conditions = self.config.options.get( - CONF_MONITORED_CONDITIONS, ["status"] - ) + monitored_conditions = list(dict.fromkeys(self.config.options.get( + CONF_MONITORED_CONDITIONS, [] + ) + [x for x in MANDATORY_EASEE_ENTITIES])) monitored_eq_conditions = self.config.options.get( CONF_MONITORED_EQ_CONDITIONS, ["status"] ) @@ -309,12 +311,14 @@ def _create_entitites(self): self.consumption_sensor_entities = [] self.equalizer_sensor_entities = [] + all_easee_entities = {**MANDATORY_EASEE_ENTITIES, **OPTIONAL_EASEE_ENTITIES} + for charger_data in self.chargers_data: for key in monitored_conditions: # Fix renamed entities previously configured - if key not in EASEE_ENTITIES: + if key not in all_easee_entities: continue - data = EASEE_ENTITIES[key] + data = all_easee_entities[key] entity_type = data.get("type", "sensor") if entity_type == "sensor": @@ -403,8 +407,9 @@ def _create_entitites(self): _LOGGER.info("Will measure days: %s", interval) self.consumption_sensor_entities.append( ChargerConsumptionSensor( + self, charger_data.charger, - f"consumption_days_{interval}", + f"{CONSUMPTION_DAYS_PREFIX}{interval}", int(interval), consumption_unit, ) @@ -429,7 +434,7 @@ def _create_entitites(self): if data["units"] in custom_units: data["units"] = CUSTOM_UNITS_TABLE[data["units"]] - self.equalizer_entities.append( + self.equalizer_sensor_entities.append( EqualizerSensor( controller=self, charger_data=equalizer_data, diff --git a/custom_components/easee/entity.py b/custom_components/easee/entity.py index ab2340da..70a0c248 100644 --- a/custom_components/easee/entity.py +++ b/custom_components/easee/entity.py @@ -85,14 +85,24 @@ async def async_added_to_hass(self) -> None: async def async_will_remove_from_hass(self) -> None: """Disconnect object when removed.""" + if self in self.controller.sensor_entities: + self.controller.sensor_entities.remove(self) + if self in self.controller.binary_sensor_entities: + self.controller.binary_sensor_entities.remove(self) + if self in self.controller.switch_entities: + self.controller.switch_entities.remove(self) + if self in self.controller.equalizer_sensor_entities: + self.controller.equalizer_sensor_entities.remove(self) ent_reg = await entity_registry.async_get_registry(self.hass) entity_entry = ent_reg.async_get(self.entity_id) dev_reg = await device_registry.async_get_registry(self.hass) device_entry = dev_reg.async_get(entity_entry.device_id) + _LOGGER.debug("Removing _entity_name: %s", self._entity_name) if (self._entity_name in self.hass.data[DOMAIN]["entities_to_remove"] or - self.charger_data.charger.site["name"] in self.hass.data[DOMAIN]["sites_to_remove"]): + self._entity_name in self.hass.data[DOMAIN]["eq_entities_to_remove"] or + self.charger_data.site["name"] in self.hass.data[DOMAIN]["sites_to_remove"]): if len(async_entries_for_device(ent_reg, entity_entry.device_id)) == 1: dev_reg.async_remove_device(device_entry.id) return @@ -162,7 +172,7 @@ def icon(self): def device_class(self): """Device class of sensor.""" return self._device_class - + @property def should_poll(self): """No polling needed.""" diff --git a/custom_components/easee/manifest.json b/custom_components/easee/manifest.json index 40091831..fde83bfe 100644 --- a/custom_components/easee/manifest.json +++ b/custom_components/easee/manifest.json @@ -8,9 +8,6 @@ ], "homeassistant": "0.113.0", "config_flow": true, - "dependencies": [ - "configurator" - ], "codeowners": [ "@fondberg", "@tmjo", diff --git a/custom_components/easee/sensor.py b/custom_components/easee/sensor.py index f3310296..2434c4aa 100644 --- a/custom_components/easee/sensor.py +++ b/custom_components/easee/sensor.py @@ -18,7 +18,7 @@ DEVICE_CLASS_CURRENT, DEVICE_CLASS_ENERGY, DEVICE_CLASS_VOLTAGE, - DEVICE_CLASS_SIGNAL_STRENGTH, + DEVICE_CLASS_SIGNAL_STRENGTH, ) @@ -49,14 +49,34 @@ def state(self): class ChargerConsumptionSensor(Entity): """Implementation of Easee charger sensor.""" - def __init__(self, charger, name, days, units): + def __init__(self, controller, charger, name, days, units): """Initialize the sensor.""" + self.controller = controller self.charger = charger self._sensor_name = name self._days = days self._state = None self._units = units + async def async_will_remove_from_hass(self) -> None: + """Disconnect object when removed.""" + if self in self.controller.consumption_sensor_entities: + self.controller.consumption_sensor_entities.remove(self) + ent_reg = await entity_registry.async_get_registry(self.hass) + entity_entry = ent_reg.async_get(self.entity_id) + + dev_reg = await device_registry.async_get_registry(self.hass) + device_entry = dev_reg.async_get(entity_entry.device_id) + + _LOGGER.debug(">>>>>>>>>>>>>> Removing _sensor_name: %s", self._sensor_name) + if (self._sensor_name in self.hass.data[DOMAIN]["days_to_remove"] or + self.charger.site["name"] in self.hass.data[DOMAIN]["sites_to_remove"]): + if len(async_entries_for_device(ent_reg, entity_entry.device_id)) == 1: + dev_reg.async_remove_device(device_entry.id) + return + + ent_reg.async_remove(self.entity_id) + @property def name(self): """Return the name of the sensor.""" @@ -104,7 +124,7 @@ def state_attributes(self): def device_class(self): """Device class of sensor.""" return DEVICE_CLASS_ENERGY - + @property def should_poll(self): """No polling needed.""" @@ -151,22 +171,6 @@ def device_info(self) -> Dict[str, any]: "model": "Equalizer", } - async def async_will_remove_from_hass(self) -> None: - """Disconnect object when removed.""" - ent_reg = await entity_registry.async_get_registry(self.hass) - entity_entry = ent_reg.async_get(self.entity_id) - - dev_reg = await device_registry.async_get_registry(self.hass) - device_entry = dev_reg.async_get(entity_entry.device_id) - - if (self._entity_name in self.hass.data[DOMAIN]["eq_entities_to_remove"] or - self.charger_data.site["name"] in self.hass.data[DOMAIN]["sites_to_remove"]): - if len(async_entries_for_device(ent_reg, entity_entry.device_id)) == 1: - dev_reg.async_remove_device(device_entry.id) - return - - ent_reg.async_remove(self.entity_id) - async def async_update(self): """Get the latest data and update the state.""" _LOGGER.debug( diff --git a/custom_components/easee/translations/en.json b/custom_components/easee/translations/en.json index 521ff88a..22a2b3f0 100644 --- a/custom_components/easee/translations/en.json +++ b/custom_components/easee/translations/en.json @@ -32,6 +32,9 @@ "description": "Select options", "title": "Easee EV Charger" } + }, + "error": { + "no_sites": "No monitored sites selected" } }, "title": "Easee EV Charger" diff --git a/custom_components/easee/translations/nb.json b/custom_components/easee/translations/nb.json index 5ba2eff2..2238a6f3 100644 --- a/custom_components/easee/translations/nb.json +++ b/custom_components/easee/translations/nb.json @@ -32,6 +32,9 @@ "description": "Velg instillinger", "title": "Easee EV Charger" } + }, + "error": { + "no_sites": "Ingen lokasjon valgt" } }, "title": "Easee EV Charger" diff --git a/custom_components/easee/translations/sv.json b/custom_components/easee/translations/sv.json index b062b013..d283ceb1 100644 --- a/custom_components/easee/translations/sv.json +++ b/custom_components/easee/translations/sv.json @@ -4,7 +4,7 @@ "already_setup": "Endast en anslutning för Easee kan konfigureras." }, "error": { - "auth_failure": "Kan inte ansluta till Easee. Kontrollera användarnamn och lösenord.", + "auth_failure": "Autentisering misslyckades. Kontrollera användarnamn och lösenord.", "connection_failure": "Kan inte ansluta till Easee. Försök igen senare.", "refused_failure": "Anslutning nekades till Easee. Försök igen senare." }, @@ -32,6 +32,9 @@ "description": "Välj inställningar", "title": "Easee EV Charger" } + }, + "error": { + "no_sites": "Ingen monitorerad plats vald" } }, "title": "Easee EV Charger"