diff --git a/custom_components/battery_sim/__init__.py b/custom_components/battery_sim/__init__.py index 2e466eb..2c06b5b 100644 --- a/custom_components/battery_sim/__init__.py +++ b/custom_components/battery_sim/__init__.py @@ -42,7 +42,15 @@ GRID_EXPORT_SIM, GRID_IMPORT_SIM, ATTR_MONEY_SAVED, - FORCE_DISCHARGE + FORCE_DISCHARGE, + BATTERY_MODE, + MODE_IDLE, + MODE_CHARGING, + MODE_DISCHARGING, + MODE_FORCE_CHARGING, + MODE_FORCE_DISCHARGING, + MODE_FULL, + MODE_EMPTY ) _LOGGER = logging.getLogger(__name__) @@ -154,7 +162,8 @@ def __init__( DISCHARGING_RATE: 0.0, GRID_EXPORT_SIM: 0.0, GRID_IMPORT_SIM: 0.0, - ATTR_MONEY_SAVED: 0.0 + ATTR_MONEY_SAVED: 0.0, + BATTERY_MODE: MODE_IDLE } self._energy_saved_today = 0.0 self._energy_saved_week = 0.0 @@ -314,6 +323,7 @@ def updateBattery(self, import_amount, export_amount): amount_to_discharge = 0.0 net_export = export_amount net_import = import_amount + self._sensors[BATTERY_MODE] = MODE_IDLE elif self._switches[OVERIDE_CHARGING]: _LOGGER.debug("Battery (%s) overide charging.", self._name) amount_to_charge = min(max_charge, available_capacity_to_charge) @@ -321,6 +331,7 @@ def updateBattery(self, import_amount, export_amount): net_export = max(export_amount - amount_to_charge, 0) net_import = max(amount_to_charge - export_amount, 0) + import_amount self._charging = True + self._sensors[BATTERY_MODE] = MODE_FORCE_CHARGING if self._tariff_sensor_id is not None: net_money_saved = -1*amount_to_charge*float(self._hass.states.get(self._tariff_sensor_id).state) elif self._switches[FORCE_DISCHARGE]: @@ -330,6 +341,7 @@ def updateBattery(self, import_amount, export_amount): net_export = max(amount_to_discharge - import_amount, 0) + export_amount net_import = max(import_amount - amount_to_discharge, 0) self._charging = False + self._sensors[BATTERY_MODE] = MODE_FORCE_DISCHARGING if self._tariff_sensor_id is not None: net_money_saved = -1*amount_to_charge*float(self._hass.states.get(self._tariff_sensor_id).state) else: @@ -344,8 +356,10 @@ def updateBattery(self, import_amount, export_amount): net_money_saved = amount_to_discharge*float(self._hass.states.get(self._tariff_sensor_id).state) if amount_to_charge > amount_to_discharge: self._charging = True + self._sensors[BATTERY_MODE] = MODE_CHARGING else: self._charging = False + self._sensors[BATTERY_MODE] = MODE_DISCHARGING self._charge_state = float(self._charge_state) + amount_to_charge - amount_to_discharge @@ -358,6 +372,12 @@ def updateBattery(self, import_amount, export_amount): self._sensors[DISCHARGING_RATE] = amount_to_discharge/(time_since_last_battery_update/3600) self._charge_percentage = round(100*self._charge_state/self._battery_size) + + if self._charge_percentage < 2: + self._sensors[BATTERY_MODE] = MODE_EMPTY + elif self._charge_percentage >98: + self._sensors[BATTERY_MODE] = MODE_FULL + if self._tariff_sensor_id is not None: self._sensors[ATTR_MONEY_SAVED] += net_money_saved self._energy_saved_today += amount_to_discharge diff --git a/custom_components/battery_sim/const.py b/custom_components/battery_sim/const.py index 47b3bb3..16b11d0 100644 --- a/custom_components/battery_sim/const.py +++ b/custom_components/battery_sim/const.py @@ -58,7 +58,7 @@ RESET_BATTERY = "reset_battery" PERCENTAGE_ENERGY_IMPORT_SAVED = "percentage_import_energy_saved" -BATTERY_MODE = "Battery_mode" +BATTERY_MODE = "Battery_mode_now" MODE_IDLE = "Idle/Paused" MODE_CHARGING = "Charging" MODE_DISCHARGING = "Discharging" diff --git a/custom_components/battery_sim/sensor.py b/custom_components/battery_sim/sensor.py index 2e81675..9c24eed 100644 --- a/custom_components/battery_sim/sensor.py +++ b/custom_components/battery_sim/sensor.py @@ -36,8 +36,7 @@ ATTR_ENERGY_SAVED_WEEK, ATTR_ENERGY_SAVED_MONTH, ATTR_DATE_RECORDING_STARTED, - CHARGING, - DISCHARGING, + BATTERY_MODE, ATTR_CHARGE_PERCENTAGE, ATTR_ENERGY_BATTERY_OUT, ATTR_ENERGY_BATTERY_IN, @@ -84,6 +83,7 @@ async def define_sensors(hass, handle): if handle._tariff_sensor_id != "none": sensors.append(DisplayOnlySensor(handle, ATTR_MONEY_SAVED, DEVICE_CLASS_MONETARY, hass.config.currency)) sensors.append(SimulatedBattery(handle)) + sensors.append(BatteryStatus(handle, BATTERY_MODE)) return sensors class DisplayOnlySensor(RestoreEntity, SensorEntity): @@ -102,6 +102,7 @@ def __init__( self._sensor_type = sensor_name self._type_of_sensor = type_of_sensor self._last_reset = dt_util.utcnow() + self._available = False async def async_added_to_hass(self): """Subscribe for update from the battery.""" @@ -110,8 +111,16 @@ async def async_added_to_hass(self): state = await self.async_get_last_state() if state: - self._handle._sensors[self._sensor_type] = float(state.state) + try: + self._handle._sensors[self._sensor_type] = float(state.state) + except: + _LOGGER.debug("Sensor state not restored properly.") + if self._sensor_type == GRID_IMPORT_SIM: + dispatcher_send(self.hass, f"{self._name}-BatteryResetImportSim") + elif self._sensor_type == GRID_EXPORT_SIM: + dispatcher_send(self.hass, f"{self._name}-BatteryResetExportSim") else: + _LOGGER.debug("No sensor state - presume new battery.") if self._sensor_type == GRID_IMPORT_SIM: dispatcher_send(self.hass, f"{self._name}-BatteryResetImportSim") elif self._sensor_type == GRID_EXPORT_SIM: @@ -119,6 +128,7 @@ async def async_added_to_hass(self): async def async_update_state(): """Update sensor state.""" + self._available = True await self.async_update_ha_state(True) async_dispatcher_connect( @@ -219,6 +229,11 @@ def last_reset(self): """Return the time when the sensor was last reset.""" return self._last_reset + @property + def available(self) -> bool: + """Return True if entity is available. Needed to avoid spikes in energy dashboard on startup""" + return self._available + class SimulatedBattery(RestoreEntity, SensorEntity): """Representation of the battery itself""" @@ -309,7 +324,7 @@ def should_poll(self): def extra_state_attributes(self): """Return the state attributes of the sensor.""" state_attr = { - ATTR_STATUS: CHARGING if self.handle._charging else DISCHARGING, + ATTR_STATUS: self.handle._sensors[BATTERY_MODE], ATTR_CHARGE_PERCENTAGE: int(self.handle._charge_percentage), ATTR_DATE_RECORDING_STARTED: self.handle._date_recording_started, ATTR_ENERGY_SAVED_TODAY: round(float(self.handle._energy_saved_today),3), @@ -338,3 +353,86 @@ def state(self): def update(self): """Not used""" + +class BatteryStatus(SensorEntity): + """Representation of the battery itself""" + + def __init__( + self, + handle, + sensor_name + ): + self.handle = handle + self._date_recording_started = time.asctime() + self._name = handle._name + " - " + sensor_name + self._device_name = handle._name + self._sensor_type = sensor_name + + + async def async_added_to_hass(self): + """Handle entity which will be added.""" + await super().async_added_to_hass() + + async def async_update_state(): + """Update sensor state.""" + await self.async_update_ha_state(True) + + async_dispatcher_connect( + self.hass, f"{self.handle._name}-BatteryUpdateMessage", async_update_state + ) + + @property + def name(self): + """Return the name of the sensor.""" + return self._name + + @property + def unique_id(self): + """Return uniqueid.""" + return self._name + + @property + def device_info(self): + return { + "name": self._device_name, + "identifiers": { + (DOMAIN, self._device_name) + } + } + + @property + def native_value(self): + """Return the state of the sensor.""" + return self.handle._sensors[BATTERY_MODE] + + @property + def device_class(self): + """Return the device class of the sensor.""" + return DEVICE_CLASS_ENERGY + + @property + def should_poll(self): + """No polling needed.""" + return False + + @property + def extra_state_attributes(self): + """Return the state attributes of the sensor.""" + state_attr = {} + return state_attr + + @property + def icon(self): + """Return the icon to use in the frontend, if any.""" + if self.handle._charging: + return ICON_CHARGING + else: + return ICON_DISCHARGING + + @property + def state(self): + """Return the state of the sensor.""" + return self.handle._sensors[BATTERY_MODE] + + def update(self): + """Not used""" \ No newline at end of file