def _update_cost(self) -> None: """Update incurred costs.""" energy_state = self.hass.states.get( cast(str, self._config[self._adapter.entity_energy_key])) if energy_state is None: return state_class = energy_state.attributes.get(ATTR_STATE_CLASS) if state_class not in SUPPORTED_STATE_CLASSES: if not self._wrong_state_class_reported: self._wrong_state_class_reported = True _LOGGER.warning( "Found unexpected state_class %s for %s", state_class, energy_state.entity_id, ) return # last_reset must be set if the sensor is STATE_CLASS_MEASUREMENT if (state_class == STATE_CLASS_MEASUREMENT and ATTR_LAST_RESET not in energy_state.attributes): return try: energy = float(energy_state.state) except ValueError: return # Determine energy price if self._config["entity_energy_price"] is not None: energy_price_state = self.hass.states.get( self._config["entity_energy_price"]) if energy_price_state is None: return try: energy_price = float(energy_price_state.state) except ValueError: return if energy_price_state.attributes.get( ATTR_UNIT_OF_MEASUREMENT, "").endswith(f"/{ENERGY_WATT_HOUR}"): energy_price *= 1000.0 if energy_price_state.attributes.get( ATTR_UNIT_OF_MEASUREMENT, "").endswith(f"/{ENERGY_MEGA_WATT_HOUR}"): energy_price /= 1000.0 else: energy_price_state = None energy_price = cast(float, self._config["number_energy_price"]) if self._last_energy_sensor_state is None: # Initialize as it's the first time all required entities are in place. self._reset(energy_state) return energy_unit = energy_state.attributes.get(ATTR_UNIT_OF_MEASUREMENT) if self._adapter.source_type == "grid": if energy_unit not in VALID_ENERGY_UNITS: energy_unit = None elif self._adapter.source_type == "gas": if energy_unit not in VALID_ENERGY_UNITS_GAS: energy_unit = None if energy_unit == ENERGY_WATT_HOUR: energy_price /= 1000 elif energy_unit == ENERGY_MEGA_WATT_HOUR: energy_price *= 1000 if energy_unit is None: if not self._wrong_unit_reported: self._wrong_unit_reported = True _LOGGER.warning( "Found unexpected unit %s for %s", energy_state.attributes.get(ATTR_UNIT_OF_MEASUREMENT), energy_state.entity_id, ) return if state_class != STATE_CLASS_TOTAL_INCREASING and energy_state.attributes.get( ATTR_LAST_RESET ) != self._last_energy_sensor_state.attributes.get(ATTR_LAST_RESET): # Energy meter was reset, reset cost sensor too energy_state_copy = copy.copy(energy_state) energy_state_copy.state = "0.0" self._reset(energy_state_copy) elif state_class == STATE_CLASS_TOTAL_INCREASING and reset_detected( self.hass, cast(str, self._config[self._adapter.entity_energy_key]), energy, float(self._last_energy_sensor_state.state), self._last_energy_sensor_state, ): # Energy meter was reset, reset cost sensor too energy_state_copy = copy.copy(energy_state) energy_state_copy.state = "0.0" self._reset(energy_state_copy) # Update with newly incurred cost old_energy_value = float(self._last_energy_sensor_state.state) cur_value = cast(float, self._attr_native_value) self._attr_native_value = cur_value + (energy - old_energy_value) * energy_price self._last_energy_sensor_state = energy_state
class EnergyCostSensor(SensorEntity): """Calculate costs incurred by consuming energy. This is intended as a fallback for when no specific cost sensor is available for the utility. """ _wrong_state_class_reported = False _wrong_unit_reported = False def __init__( self, adapter: SourceAdapter, config: dict, ) -> None: """Initialize the sensor.""" super().__init__() self._adapter = adapter self.entity_id = ( f"{config[adapter.entity_energy_key]}_{adapter.entity_id_suffix}") self._attr_device_class = DEVICE_CLASS_MONETARY self._attr_state_class = STATE_CLASS_TOTAL_INCREASING self._config = config self._last_energy_sensor_state: StateType | None = None self._cur_value = 0.0 def _reset(self, energy_state: StateType) -> None: """Reset the cost sensor.""" self._attr_native_value = 0.0 self._cur_value = 0.0 self._last_energy_sensor_state = energy_state self.async_write_ha_state() @callback def _update_cost(self) -> None: """Update incurred costs.""" energy_state = self.hass.states.get( cast(str, self._config[self._adapter.entity_energy_key])) if energy_state is None: return if (state_class := energy_state.attributes.get(ATTR_STATE_CLASS) ) != STATE_CLASS_TOTAL_INCREASING: if not self._wrong_state_class_reported: self._wrong_state_class_reported = True _LOGGER.warning( "Found unexpected state_class %s for %s", state_class, energy_state.entity_id, ) return try: energy = float(energy_state.state) except ValueError: return # Determine energy price if self._config["entity_energy_price"] is not None: energy_price_state = self.hass.states.get( self._config["entity_energy_price"]) if energy_price_state is None: return try: energy_price = float(energy_price_state.state) except ValueError: return if (self._adapter.source_type == "grid" and energy_price_state.attributes.get( ATTR_UNIT_OF_MEASUREMENT, "").endswith(f"/{ENERGY_WATT_HOUR}")): energy_price *= 1000.0 else: energy_price_state = None energy_price = cast(float, self._config["number_energy_price"]) if self._last_energy_sensor_state is None: # Initialize as it's the first time all required entities are in place. self._reset(energy_state.state) return energy_unit = energy_state.attributes.get(ATTR_UNIT_OF_MEASUREMENT) if self._adapter.source_type == "grid": if energy_unit == ENERGY_WATT_HOUR: energy_price /= 1000 elif energy_unit != ENERGY_KILO_WATT_HOUR: energy_unit = None elif self._adapter.source_type == "gas": if energy_unit != VOLUME_CUBIC_METERS: energy_unit = None if energy_unit is None: if not self._wrong_unit_reported: self._wrong_unit_reported = True _LOGGER.warning( "Found unexpected unit %s for %s", energy_state.attributes.get(ATTR_UNIT_OF_MEASUREMENT), energy_state.entity_id, ) return if reset_detected( self.hass, cast(str, self._config[self._adapter.entity_energy_key]), energy, float(self._last_energy_sensor_state), ): # Energy meter was reset, reset cost sensor too self._reset(0) # Update with newly incurred cost old_energy_value = float(self._last_energy_sensor_state) self._cur_value += (energy - old_energy_value) * energy_price self._attr_native_value = round(self._cur_value, 2) self._last_energy_sensor_state = energy_state.state