Skip to content

Commit

Permalink
Add mode indicator sensor and force discharging
Browse files Browse the repository at this point in the history
  • Loading branch information
Hamish Findlay committed Oct 16, 2022
1 parent 1fe74cd commit cc29f95
Show file tree
Hide file tree
Showing 3 changed files with 125 additions and 7 deletions.
24 changes: 22 additions & 2 deletions custom_components/battery_sim/__init__.py
Original file line number Diff line number Diff line change
Expand Up @@ -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__)
Expand Down Expand Up @@ -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
Expand Down Expand Up @@ -314,13 +323,15 @@ 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)
amount_to_discharge = 0.0
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]:
Expand All @@ -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:
Expand All @@ -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

Expand All @@ -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
Expand Down
2 changes: 1 addition & 1 deletion custom_components/battery_sim/const.py
Original file line number Diff line number Diff line change
Expand Up @@ -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"
Expand Down
106 changes: 102 additions & 4 deletions custom_components/battery_sim/sensor.py
Original file line number Diff line number Diff line change
Expand Up @@ -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,
Expand Down Expand Up @@ -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):
Expand All @@ -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."""
Expand All @@ -110,15 +111,24 @@ 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:
dispatcher_send(self.hass, f"{self._name}-BatteryResetExportSim")

async def async_update_state():
"""Update sensor state."""
self._available = True
await self.async_update_ha_state(True)

async_dispatcher_connect(
Expand Down Expand Up @@ -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"""

Expand Down Expand Up @@ -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),
Expand Down Expand Up @@ -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"""

0 comments on commit cc29f95

Please sign in to comment.