Skip to content

Commit

Permalink
Clean removal of entities (#74)
Browse files Browse the repository at this point in the history
* Fixed error when config contains sensor names that have been removed

* Introduced intention bug fixed

* binary_sensor added to links.sh

* Merge diff

* Correct spelling mistake in HACS json file

* Remove unused dependency

* Proof of concept - Removal of entities

* Validate options: minimun one monitored site

* Fix nb translation

* Clean up after deleting consumption sensors

* Remove duplicate sensors, if any

* Fixed equalizer entity removal

Co-authored-by: Ola Lidholm <[email protected]>
Co-authored-by: Ola Lidholm <[email protected]>
  • Loading branch information
3 people authored Oct 29, 2020
1 parent 038815c commit 48db981
Show file tree
Hide file tree
Showing 10 changed files with 109 additions and 71 deletions.
1 change: 0 additions & 1 deletion custom_components/easee/__init__.py
Original file line number Diff line number Diff line change
Expand Up @@ -9,7 +9,6 @@

from .const import (
DOMAIN,
EASEE_ENTITIES,
LISTENER_FN_CLOSE,
MEASURED_CONSUMPTION_DAYS,
VERSION,
Expand Down
29 changes: 20 additions & 9 deletions custom_components/easee/config_flow.py
Original file line number Diff line number Diff line change
Expand Up @@ -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,
Expand Down Expand Up @@ -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"])
Expand All @@ -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(
Expand All @@ -147,16 +153,21 @@ 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]]
self.hass.data[DOMAIN]["eq_entities_to_remove"] = [cond for cond in self.prev_options.get(CONF_MONITORED_EQ_CONDITIONS, {})
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)
51 changes: 27 additions & 24 deletions custom_components/easee/const.py
Original file line number Diff line number Diff line change
Expand Up @@ -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 = {
Expand All @@ -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",
Expand Down Expand Up @@ -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": [],
Expand Down
29 changes: 17 additions & 12 deletions custom_components/easee/controller.py
Original file line number Diff line number Diff line change
Expand Up @@ -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,
Expand Down Expand Up @@ -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
Expand All @@ -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):
Expand Down Expand Up @@ -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):
Expand Down Expand Up @@ -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"]
)
Expand All @@ -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":
Expand Down Expand Up @@ -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,
)
Expand All @@ -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,
Expand Down
14 changes: 12 additions & 2 deletions custom_components/easee/entity.py
Original file line number Diff line number Diff line change
Expand Up @@ -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
Expand Down Expand Up @@ -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."""
Expand Down
3 changes: 0 additions & 3 deletions custom_components/easee/manifest.json
Original file line number Diff line number Diff line change
Expand Up @@ -8,9 +8,6 @@
],
"homeassistant": "0.113.0",
"config_flow": true,
"dependencies": [
"configurator"
],
"codeowners": [
"@fondberg",
"@tmjo",
Expand Down
42 changes: 23 additions & 19 deletions custom_components/easee/sensor.py
Original file line number Diff line number Diff line change
Expand Up @@ -18,7 +18,7 @@
DEVICE_CLASS_CURRENT,
DEVICE_CLASS_ENERGY,
DEVICE_CLASS_VOLTAGE,
DEVICE_CLASS_SIGNAL_STRENGTH,
DEVICE_CLASS_SIGNAL_STRENGTH,
)


Expand Down Expand Up @@ -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."""
Expand Down Expand Up @@ -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."""
Expand Down Expand Up @@ -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(
Expand Down
3 changes: 3 additions & 0 deletions custom_components/easee/translations/en.json
Original file line number Diff line number Diff line change
Expand Up @@ -32,6 +32,9 @@
"description": "Select options",
"title": "Easee EV Charger"
}
},
"error": {
"no_sites": "No monitored sites selected"
}
},
"title": "Easee EV Charger"
Expand Down
3 changes: 3 additions & 0 deletions custom_components/easee/translations/nb.json
Original file line number Diff line number Diff line change
Expand Up @@ -32,6 +32,9 @@
"description": "Velg instillinger",
"title": "Easee EV Charger"
}
},
"error": {
"no_sites": "Ingen lokasjon valgt"
}
},
"title": "Easee EV Charger"
Expand Down
Loading

0 comments on commit 48db981

Please sign in to comment.