diff --git a/README.md b/README.md index 372ca6e..de19dca 100644 --- a/README.md +++ b/README.md @@ -27,6 +27,7 @@ In particular, if HA seems to not receive any data, a first step is to validate ## Changelog: +- 1.7 - 09/01/2023: Fixed issue [#36] sensors not updating after HA update to 2024.1. Updated sensor types to address deprecation warnings for combinarions of device and state classes [@shortbloke] - 1.6 - 05/05/2022: ported Energy support to latest HA core releases [@shortbloke] * 1.5 - 20/09/2021: added support for the Energy feature in HA, and included [@shortbloke] a templated sensor for the Grid consumption * 1.4 - 05/05/2019: added resources.json and updated code layout following 'the great migration' diff --git a/custom_components/owlintuition/owl_intuition.yaml b/custom_components/owlintuition/owl_intuition.yaml index 8434a5d..85c011a 100644 --- a/custom_components/owlintuition/owl_intuition.yaml +++ b/custom_components/owlintuition/owl_intuition.yaml @@ -1,7 +1,12 @@ #################################################### -# Owl Intution - Grid Energy Sensor # +# Owl Intuition - Energy Sensor # #################################################### + template: + # State ensures: + # - value is not set until both solar and electricity readings have been received and are no longer unknown + # - reads the values in simple variables for elec and solar, to make if statements easier to read + # - protects value going negative if one sensor resets to zero before the other at around midnight - trigger: - platform: state entity_id: sensor.owl_intuition_electricity_power @@ -23,6 +28,11 @@ template: {% endif %} {% endif %} + # State ensures: + # - value is not set until both solar and electricity readings have been received and are no longer unknown + # - reads the values in simple variables for elec, solar and last_grid_today, to make if statements easier to read + # - protects value going negative if one sensor resets to zero before the other at around midnight + # - if elec - solar would be negative it uses the last value from the sensor, stored in last_grid_today - trigger: - platform: state entity_id: sensor.owl_intuition_electricity_today @@ -37,7 +47,7 @@ template: {% else %} {% set elec = states('sensor.owl_intuition_electricity_today') | float %} {% set solar = states('sensor.owl_intuition_solar_generated_today') | float %} - {% set last_grid_today = states('sensor.owl_grid_energy_today') | float %} + {% set last_grid_today = states('sensor.owl_grid_energy_today') | float(default=0) %} {% if (float(elec) - float(solar)) >= 0 %} {% if ((float(elec) - float(solar)) > float(last_grid_today)) or ((float(elec) - float(solar)) < 1 ) %} {{ float(elec) - float(solar) }} @@ -45,4 +55,4 @@ template: {{ float(last_grid_today) }} {% endif %} {% endif %} - {% endif %} \ No newline at end of file + {% endif %} diff --git a/custom_components/owlintuition/sensor.py b/custom_components/owlintuition/sensor.py index e574174..e9b9842 100644 --- a/custom_components/owlintuition/sensor.py +++ b/custom_components/owlintuition/sensor.py @@ -20,19 +20,19 @@ ) from homeassistant.config_entries import ConfigEntry from homeassistant.const import ( + CONF_BROADCAST_ADDRESS, + CONF_BROADCAST_PORT, CONF_HOST, CONF_MODE, CONF_MONITORED_CONDITIONS, CONF_NAME, CONF_PORT, - ELECTRIC_POTENTIAL_VOLT, - ENERGY_KILO_WATT_HOUR, PERCENTAGE, - POWER_WATT, SIGNAL_STRENGTH_DECIBELS_MILLIWATT, - TEMP_CELSIUS, - CONF_BROADCAST_ADDRESS, - CONF_BROADCAST_PORT, + UnitOfElectricPotential, + UnitOfEnergy, + UnitOfPower, + UnitOfTemperature, ) from homeassistant.core import HomeAssistant from homeassistant.helpers.entity_platform import AddEntitiesCallback @@ -45,7 +45,7 @@ CONF_COST_ICON = 'cost_icon' # OWL-specific constants -VERSION = '1.6.0' +VERSION = '1.7.0' DEFAULT_NAME = 'OWL Intuition' MODE_MONO = 'monophase' MODE_TRI = 'triphase' @@ -98,29 +98,29 @@ OWLCLASS_RELAYS ] SENSOR_TYPES = { - SENSOR_ELECTRICITY_BATTERY: ['Electricity Battery', None, 'mdi:battery', OWLCLASS_ELECTRICITY, SensorDeviceClass.ENUM, SensorStateClass.MEASUREMENT], + SENSOR_ELECTRICITY_BATTERY: ['Electricity Battery', None, 'mdi:battery', OWLCLASS_ELECTRICITY, SensorDeviceClass.ENUM, None], SENSOR_ELECTRICITY_BATTERY_LVL: ['Electricity Battery Level', PERCENTAGE, 'mdi:battery', OWLCLASS_ELECTRICITY, SensorDeviceClass.BATTERY, SensorStateClass.MEASUREMENT], SENSOR_ELECTRICITY_RADIO: ['Electricity Radio', SIGNAL_STRENGTH_DECIBELS_MILLIWATT, 'mdi:signal', OWLCLASS_ELECTRICITY, SensorDeviceClass.SIGNAL_STRENGTH, SensorStateClass.MEASUREMENT], - SENSOR_ELECTRICITY_POWER: ['Electricity Power', POWER_WATT, 'mdi:flash', OWLCLASS_ELECTRICITY, SensorDeviceClass.POWER, SensorStateClass.MEASUREMENT], - SENSOR_ELECTRICITY_ENERGY_TODAY: ['Electricity Today', ENERGY_KILO_WATT_HOUR, 'mdi:flash', OWLCLASS_ELECTRICITY, SensorDeviceClass.ENERGY, SensorStateClass.TOTAL_INCREASING], - SENSOR_ELECTRICITY_COST_TODAY: ['Cost Today', None, 'mdi:coin', OWLCLASS_ELECTRICITY, SensorDeviceClass.MONETARY, SensorStateClass.TOTAL_INCREASING], - SENSOR_SOLAR_GPOWER: ['Solar Generating', POWER_WATT, 'mdi:flash', OWLCLASS_SOLAR, SensorDeviceClass.POWER, SensorStateClass.MEASUREMENT], - SENSOR_SOLAR_GENERGY_TODAY: ['Solar Generated Today', ENERGY_KILO_WATT_HOUR, 'mdi:flash', OWLCLASS_SOLAR, SensorDeviceClass.ENERGY, SensorStateClass.TOTAL_INCREASING], - SENSOR_SOLAR_EPOWER: ['Solar Exporting', POWER_WATT, 'mdi:flash', OWLCLASS_SOLAR, SensorDeviceClass.POWER, SensorStateClass.MEASUREMENT], - SENSOR_SOLAR_EENERGY_TODAY: ['Solar Exported Today', ENERGY_KILO_WATT_HOUR, 'mdi:flash', OWLCLASS_SOLAR, SensorDeviceClass.ENERGY, SensorStateClass.TOTAL_INCREASING], - SENSOR_HOTWATER_BATTERY: ['Hotwater Battery', None, 'mdi:battery', OWLCLASS_HOTWATER, SensorDeviceClass.ENUM, SensorStateClass.MEASUREMENT], - SENSOR_HOTWATER_BATTERY_LVL: ['Hotwater Battery Level', ELECTRIC_POTENTIAL_VOLT, 'mdi:battery', OWLCLASS_HOTWATER, SensorDeviceClass.VOLTAGE, SensorStateClass.MEASUREMENT], + SENSOR_ELECTRICITY_POWER: ['Electricity Power', UnitOfPower.WATT, 'mdi:flash', OWLCLASS_ELECTRICITY, SensorDeviceClass.POWER, SensorStateClass.MEASUREMENT], + SENSOR_ELECTRICITY_ENERGY_TODAY: ['Electricity Today', UnitOfEnergy.KILO_WATT_HOUR, 'mdi:flash', OWLCLASS_ELECTRICITY, SensorDeviceClass.ENERGY, SensorStateClass.TOTAL_INCREASING], + SENSOR_ELECTRICITY_COST_TODAY: ['Cost Today', None, 'mdi:coin', OWLCLASS_ELECTRICITY, SensorDeviceClass.MONETARY, None], + SENSOR_SOLAR_GPOWER: ['Solar Generating', UnitOfPower.WATT, 'mdi:flash', OWLCLASS_SOLAR, SensorDeviceClass.POWER, SensorStateClass.MEASUREMENT], + SENSOR_SOLAR_GENERGY_TODAY: ['Solar Generated Today', UnitOfEnergy.KILO_WATT_HOUR, 'mdi:flash', OWLCLASS_SOLAR, SensorDeviceClass.ENERGY, SensorStateClass.TOTAL_INCREASING], + SENSOR_SOLAR_EPOWER: ['Solar Exporting', UnitOfPower.WATT, 'mdi:flash', OWLCLASS_SOLAR, SensorDeviceClass.POWER, SensorStateClass.MEASUREMENT], + SENSOR_SOLAR_EENERGY_TODAY: ['Solar Exported Today', UnitOfEnergy.KILO_WATT_HOUR, 'mdi:flash', OWLCLASS_SOLAR, SensorDeviceClass.ENERGY, SensorStateClass.TOTAL_INCREASING], + SENSOR_HOTWATER_BATTERY: ['Hotwater Battery', None, 'mdi:battery', OWLCLASS_HOTWATER, SensorDeviceClass.ENUM, None], + SENSOR_HOTWATER_BATTERY_LVL: ['Hotwater Battery Level', UnitOfElectricPotential.VOLT, 'mdi:battery', OWLCLASS_HOTWATER, SensorDeviceClass.VOLTAGE, SensorStateClass.MEASUREMENT], SENSOR_HOTWATER_RADIO: ['Hotwater Radio', SIGNAL_STRENGTH_DECIBELS_MILLIWATT, 'mdi:signal', OWLCLASS_HOTWATER, SensorDeviceClass.SIGNAL_STRENGTH, SensorStateClass.MEASUREMENT], - SENSOR_HOTWATER_CURRENT: ['Hotwater Temperature', TEMP_CELSIUS, 'mdi:thermometer', OWLCLASS_HOTWATER, SensorDeviceClass.TEMPERATURE, SensorStateClass.MEASUREMENT], - SENSOR_HOTWATER_REQUIRED: ['Hotwater Required', TEMP_CELSIUS, 'mdi:thermostat', OWLCLASS_HOTWATER, SensorDeviceClass.TEMPERATURE, SensorStateClass.MEASUREMENT], - SENSOR_HOTWATER_AMBIENT: ['Hotwater Ambient', TEMP_CELSIUS, 'mdi:thermometer', OWLCLASS_HOTWATER, SensorDeviceClass.TEMPERATURE, SensorStateClass.MEASUREMENT], - SENSOR_HOTWATER_STATE: ['Hotwater State', None, 'mdi:information-outline', OWLCLASS_HOTWATER, SensorDeviceClass.ENUM, SensorStateClass.MEASUREMENT], - SENSOR_HEATING_BATTERY: ['Heating Battery', None, 'mdi:battery', OWLCLASS_HEATING, SensorDeviceClass.ENUM, SensorStateClass.MEASUREMENT], - SENSOR_HEATING_BATTERY_LVL: ['Heating Battery Level', ELECTRIC_POTENTIAL_VOLT, 'mdi:battery', OWLCLASS_HEATING, SensorDeviceClass.VOLTAGE, SensorStateClass.MEASUREMENT], + SENSOR_HOTWATER_CURRENT: ['Hotwater Temperature', UnitOfTemperature.CELSIUS, 'mdi:thermometer', OWLCLASS_HOTWATER, SensorDeviceClass.TEMPERATURE, SensorStateClass.MEASUREMENT], + SENSOR_HOTWATER_REQUIRED: ['Hotwater Required', UnitOfTemperature.CELSIUS, 'mdi:thermostat', OWLCLASS_HOTWATER, SensorDeviceClass.TEMPERATURE, SensorStateClass.MEASUREMENT], + SENSOR_HOTWATER_AMBIENT: ['Hotwater Ambient', UnitOfTemperature.CELSIUS, 'mdi:thermometer', OWLCLASS_HOTWATER, SensorDeviceClass.TEMPERATURE, SensorStateClass.MEASUREMENT], + SENSOR_HOTWATER_STATE: ['Hotwater State', None, 'mdi:information-outline', OWLCLASS_HOTWATER, SensorDeviceClass.ENUM, None], + SENSOR_HEATING_BATTERY: ['Heating Battery', None, 'mdi:battery', OWLCLASS_HEATING, SensorDeviceClass.ENUM, None], + SENSOR_HEATING_BATTERY_LVL: ['Heating Battery Level', UnitOfElectricPotential.VOLT, 'mdi:battery', OWLCLASS_HEATING, SensorDeviceClass.VOLTAGE, SensorStateClass.MEASUREMENT], SENSOR_HEATING_RADIO: ['Heating Radio', SIGNAL_STRENGTH_DECIBELS_MILLIWATT, 'mdi:signal', OWLCLASS_HEATING, SensorDeviceClass.SIGNAL_STRENGTH, SensorStateClass.MEASUREMENT], - SENSOR_HEATING_CURRENT: ['Heating Temperature', TEMP_CELSIUS, 'mdi:thermometer', OWLCLASS_HEATING, SensorDeviceClass.TEMPERATURE, SensorStateClass.MEASUREMENT], - SENSOR_HEATING_REQUIRED: ['Heating Required', TEMP_CELSIUS, 'mdi:thermostat', OWLCLASS_HEATING, SensorDeviceClass.TEMPERATURE, SensorStateClass.MEASUREMENT], - SENSOR_HEATING_STATE: ['Heating State', None, 'mdi:information-outline', OWLCLASS_HEATING, SensorDeviceClass.ENUM, SensorStateClass.MEASUREMENT], + SENSOR_HEATING_CURRENT: ['Heating Temperature', UnitOfTemperature.CELSIUS, 'mdi:thermometer', OWLCLASS_HEATING, SensorDeviceClass.TEMPERATURE, SensorStateClass.MEASUREMENT], + SENSOR_HEATING_REQUIRED: ['Heating Required', UnitOfTemperature.CELSIUS, 'mdi:thermostat', OWLCLASS_HEATING, SensorDeviceClass.TEMPERATURE, SensorStateClass.MEASUREMENT], + SENSOR_HEATING_STATE: ['Heating State', None, 'mdi:information-outline', OWLCLASS_HEATING, SensorDeviceClass.ENUM, None], SENSOR_RELAYS_RADIO: ['Relays Radio', SIGNAL_STRENGTH_DECIBELS_MILLIWATT, 'mdi:signal', OWLCLASS_RELAYS, SensorDeviceClass.SIGNAL_STRENGTH, SensorStateClass.MEASUREMENT], } @@ -283,33 +283,18 @@ def __init__(self, owldata, sensor_name, sensor_type, phase=0, zone=1, zones_cou self._owldata = owldata self._sensor_type = sensor_type self._phase = phase - self._name = f'{sensor_name} {SENSOR_TYPES[sensor_type][0]}' + self._attr_name = f'{sensor_name} {SENSOR_TYPES[sensor_type][0]}' if phase > 0: - self._name += f' P{phase}' + self._attr_name += f' P{phase}' self._zone = zone self._name_zone_updated = (zones_count == 1) - self._state = None self._attr_attribution = POWERED_BY self._attr_native_unit_of_measurement = SENSOR_TYPES[sensor_type][1] + self._attr_icon = SENSOR_TYPES[sensor_type][2] self._owl_class = SENSOR_TYPES[sensor_type][3] self._attr_device_class = SENSOR_TYPES[sensor_type][4] self._attr_state_class = SENSOR_TYPES[sensor_type][5] - @property - def _attr_name(self): - """Return the current name for this sensor.""" - return self._name - - @property - def _attr_native_value(self): - """Return the current value for this sensor.""" - return self._state - - @property - def icon(self): - """Return the icon for this entity.""" - return SENSOR_TYPES[self._sensor_type][2] - def update(self): """Retrieve the latest value for this sensor.""" self._owldata.update() @@ -329,107 +314,108 @@ def update(self): # fallback to the first one xml = xml.find('zones/zone') + # Update the state of the current sensor: we use _attr_native_value and not _state because of #36 # Radio sensors if self._attr_device_class == SensorDeviceClass.SIGNAL_STRENGTH: - self._state = int(xml.find('signal').attrib['rssi']) + self._attr_native_value = int(xml.find('signal').attrib['rssi']) elif self._sensor_type == SENSOR_ELECTRICITY_BATTERY_LVL: # Battery level in % for OWLCLASS_ELECTRICITY, mV for others - self._state = int(xml.find("battery").attrib['level'][:-1]) + self._attr_native_value = int(xml.find("battery").attrib['level'][:-1]) elif self._sensor_type in [SENSOR_HOTWATER_BATTERY_LVL, SENSOR_HEATING_BATTERY_LVL]: - self._state = round(float(xml.find("battery").attrib['level'])/1000, 2) + self._attr_native_value = round(float(xml.find("battery").attrib['level'])/1000, 2) elif self._sensor_type == SENSOR_ELECTRICITY_BATTERY: batt_lvl = int(xml.find("battery").attrib['level'][:-1]) if batt_lvl > 90: - self._state = 'High' + self._attr_native_value = 'High' elif batt_lvl > 30: - self._state = 'Medium' + self._attr_native_value = 'Medium' elif batt_lvl > 10: - self._state = 'Low' + self._attr_native_value = 'Low' else: - self._state = 'Very Low' + self._attr_native_value = 'Very Low' elif self._sensor_type in [SENSOR_HOTWATER_BATTERY, SENSOR_HEATING_BATTERY]: # 2670mV = 66% # 2780mV = 76% batt_lvl = int(xml.find("battery").attrib['level']) if batt_lvl > 2900: - self._state = 'High' + self._attr_native_value = 'High' elif batt_lvl > 2750: - self._state = 'Medium' + self._attr_native_value = 'Medium' elif batt_lvl > 2600: - self._state = 'Low' + self._attr_native_value = 'Low' else: - self._state = 'Very Low' + self._attr_native_value = 'Very Low' # Electricity sensors elif self._sensor_type == SENSOR_ELECTRICITY_POWER: if self._phase == 0: # xml_ver undefined for older version if xml_ver is None: - self._state = int(float(xml.find('chan/curr').text)) + self._attr_native_value = int(float(xml.find('chan/curr').text)) else: - self._state = int(float(xml.find('property/current/watts').text)) + self._attr_native_value = int(float(xml.find('property/current/watts').text)) else: if xml_ver is None: - self._state = int(float(xml.findall('chan')[self._phase-1]. + self._attr_native_value = int(float(xml.findall('chan')[self._phase-1]. find('curr').text)) else: - self._state = int(float(xml.find('channels'). + self._attr_native_value = int(float(xml.find('channels'). findall('chan')[self._phase-1]. find('curr').text)) elif self._sensor_type == SENSOR_ELECTRICITY_ENERGY_TODAY: if self._phase == 0: # xml_ver undefined for older version if xml_ver is None: - self._state = round(float(xml.find('chan/day').text)/1000,2) + self._attr_native_value = round(float(xml.find('chan/day').text)/1000,2) else: - self._state = round(float(xml.find('property/day/wh').text)/1000, 2) + self._attr_native_value = round(float(xml.find('property/day/wh').text)/1000, 2) else: if xml_ver is None: - self._state = round(float(xml.findall('chan')[self._phase-1]. + self._attr_native_value = round(float(xml.findall('chan')[self._phase-1]. find('day').text)/1000, 2) else: - self._state = round(float(xml.find('channels'). + self._attr_native_value = round(float(xml.find('channels'). findall('chan')[self._phase-1]. find('day').text)/1000, 2) elif self._sensor_type == SENSOR_ELECTRICITY_COST_TODAY: # xml_ver undefined for older version if xml_ver is None: - self._state = 0 + self._attr_native_value = 0 else: # the measure comes in cent. of the configured currency - self._state = round(float(xml.find('property/day/cost').text)/100, 3) + self._attr_native_value = round(float(xml.find('property/day/cost').text)/100, 3) # Solar sensors elif self._sensor_type == SENSOR_SOLAR_GPOWER: - self._state = int(float(xml.find('current/generating').text)) + self._attr_native_value = int(float(xml.find('current/generating').text)) elif self._sensor_type == SENSOR_SOLAR_EPOWER: - self._state = int(float(xml.find('current/exporting').text)) + self._attr_native_value = int(float(xml.find('current/exporting').text)) elif self._sensor_type == SENSOR_SOLAR_GENERGY_TODAY: - self._state = round(float(xml.find('day/generated').text)/1000, 2) + self._attr_native_value = round(float(xml.find('day/generated').text)/1000, 2) elif self._sensor_type == SENSOR_SOLAR_EENERGY_TODAY: - self._state = round(float(xml.find('day/exported').text)/1000, 2) + self._attr_native_value = round(float(xml.find('day/exported').text)/1000, 2) # Hot water sensors if self._sensor_type == SENSOR_HOTWATER_CURRENT: - self._state = round(float(xml.find('temperature/current').text),1) + self._attr_native_value = round(float(xml.find('temperature/current').text),1) elif self._sensor_type == SENSOR_HOTWATER_REQUIRED: - self._state = float(xml.find('temperature/required').text) + self._attr_native_value = float(xml.find('temperature/required').text) elif self._sensor_type == SENSOR_HOTWATER_AMBIENT: - self._state = float(xml.find('temperature/ambient').text) + self._attr_native_value = float(xml.find('temperature/ambient').text) elif self._sensor_type == SENSOR_HOTWATER_STATE: # Heating state reported in version 2 and up if xml_ver is not None: - self._state = HOTWATER_STATE[int(xml.find('temperature').attrib['state'])] + self._attr_native_value = HOTWATER_STATE[int(xml.find('temperature').attrib['state'])] # Heating Sensors elif self._sensor_type == SENSOR_HEATING_CURRENT: - self._state = round(float(xml.find('temperature/current').text),1) + self._attr_native_value = round(float(xml.find('temperature/current').text),1) elif self._sensor_type == SENSOR_HEATING_REQUIRED: - self._state = float(xml.find('temperature/required').text) + self._attr_native_value = float(xml.find('temperature/required').text) elif self._sensor_type == SENSOR_HEATING_STATE: # Heating state reported in version 2 and up if xml_ver is not None: - self._state = HEATING_STATE[int(xml.find('temperature').attrib['state'])] + self._attr_native_value = HEATING_STATE[int(xml.find('temperature').attrib['state'])] class OwlStateUpdater(asyncio.DatagramProtocol):