Skip to content

Commit

Permalink
Merge pull request #56 from Xitee1/change-coordinator
Browse files Browse the repository at this point in the history
Adjust code design more towards HA standards
  • Loading branch information
weltmeyer committed May 19, 2024
2 parents 1062a7c + 4bbd7cd commit 22483c9
Show file tree
Hide file tree
Showing 9 changed files with 973 additions and 755 deletions.
3 changes: 1 addition & 2 deletions custom_components/sonnenbatterie/__init__.py
Original file line number Diff line number Diff line change
Expand Up @@ -3,7 +3,6 @@
from .const import *
import json

# from homeassistant.helpers import config_validation as cv
# from homeassistant.helpers.entity import Entity
from homeassistant.const import (
CONF_PASSWORD,
Expand All @@ -27,7 +26,7 @@ async def async_setup(hass, config):
async def async_setup_entry(hass, config_entry):
LOGGER.info("setup_entry: " + json.dumps(dict(config_entry.data)))

hass.async_add_job(
await hass.async_create_task(
hass.config_entries.async_forward_entry_setup(config_entry, "sensor")
)
config_entry.add_update_listener(update_listener)
Expand Down
1 change: 1 addition & 0 deletions custom_components/sonnenbatterie/config_flow.py
Original file line number Diff line number Diff line change
Expand Up @@ -8,6 +8,7 @@
# import voluptuous as vol
from homeassistant import config_entries, core
from homeassistant.core import callback
from homeassistant.helpers import config_validation as cv

# pylint: disable=unused-wildcard-import
from .const import * #
Expand Down
2 changes: 0 additions & 2 deletions custom_components/sonnenbatterie/const.py
Original file line number Diff line number Diff line change
@@ -1,8 +1,6 @@
import logging
import voluptuous as vol

from datetime import timedelta
from homeassistant.helpers import config_validation as cv
from homeassistant.const import (
CONF_PASSWORD,
CONF_USERNAME,
Expand Down
160 changes: 160 additions & 0 deletions custom_components/sonnenbatterie/coordinator.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,160 @@
"""The data update coordinator for OctoPrint."""

import traceback

from .const import DOMAIN, LOGGER, logging
from datetime import timedelta

from homeassistant.helpers.entity import DeviceInfo
from homeassistant.helpers.update_coordinator import (
CoordinatorEntity,
DataUpdateCoordinator,
)

from sonnenbatterie import sonnenbatterie
from homeassistant.core import HomeAssistant

_LOGGER = logging.getLogger(__name__)


class SonnenBatterieCoordinator(DataUpdateCoordinator):
"""The SonnenBatterieCoordinator class."""

def __init__(
self,
hass: HomeAssistant,
sb_inst: sonnenbatterie,
update_interval_seconds: int,
ip_address,
debug_mode,
device_id,
):
"""Initialize my coordinator."""
super().__init__(
hass,
_LOGGER,
# Name of the data. For logging purposes.
name=f"sonnenbatterie-{device_id}",
# Polling interval. Will only be polled if there are subscribers.
update_interval=timedelta(seconds=update_interval_seconds),
)
self.sensor = None
self.hass = hass
self.latestData = {}
self.disabledSensors = []
self.device_id = device_id

self.stopped = False

self.sbInst: sonnenbatterie = sb_inst
self.meterSensors = {}
self.update_interval_seconds = update_interval_seconds
self.ip_address = ip_address
self.debug = debug_mode
self.fullLogsAlreadySent = False

# fixed value, percentage of total installed power reserved for
# internal battery system purposes
self.batt_reserved_factor = 7.0

# placeholders, will be filled later
self.serial = ""

@property
def device_info(self) -> DeviceInfo:
system_data = self.latestData["system_data"]

return DeviceInfo(
identifiers={(DOMAIN, self.device_id)},
configuration_url=f"http://{self.ip_address}/",
manufacturer="Sonnen",
model=system_data.get("ERP_ArticleName", "unknown"),
name=f"{DOMAIN} {system_data.get('DE_Ticket_Number', 'unknown')}",
sw_version=system_data.get("software_version", "unknown"),
)

async def _async_update_data(self):
"""Fetch data from API endpoint.
This is the place to pre-process the data to lookup tables
so entities can quickly look up their data.
"""
try: # ignore errors here, may be transient
self.latestData["battery"] = await self.hass.async_add_executor_job(
self.sbInst.get_battery
)
self.latestData["battery_system"] = await self.hass.async_add_executor_job(
self.sbInst.get_batterysystem
)
self.latestData["inverter"] = await self.hass.async_add_executor_job(
self.sbInst.get_inverter
)
self.latestData["powermeter"] = await self.hass.async_add_executor_job(
self.sbInst.get_powermeter
)
self.latestData["status"] = await self.hass.async_add_executor_job(
self.sbInst.get_status
)
self.latestData["system_data"] = await self.hass.async_add_executor_job(
self.sbInst.get_systemdata
)

except:
e = traceback.format_exc()
LOGGER.error(e)

if self.debug:
self.send_all_data_to_log()

if self.serial == "":
if (
serial := self.latestData.get("system_data", {}).get("DE_Ticket_Number")
) is not None:
self.serial = serial
else:
LOGGER.warning("Unable to retrieve sonnenbatterie serial number.")
self.serial = "UNKNOWN"

""" some manually calculated values """
batt_module_capacity = int(
self.latestData["battery_system"]["battery_system"]["system"][
"storage_capacity_per_module"
]
)
batt_module_count = int(self.latestData["battery_system"]["modules"])

if self.latestData["status"]["BatteryCharging"]:
battery_current_state = "charging"
elif self.latestData["status"]["BatteryDischarging"]:
battery_current_state = "discharging"
else:
battery_current_state = "standby"

self.latestData["battery_info"] = {}
self.latestData["battery_info"]["current_state"] = battery_current_state
self.latestData["battery_info"][
"total_installed_capacity"
] = total_installed_capacity = int(batt_module_count * batt_module_capacity)
self.latestData["battery_info"]["reserved_capacity"] = reserved_capacity = int(
total_installed_capacity * (self.batt_reserved_factor / 100.0)
)
self.latestData["battery_info"]["remaining_capacity"] = remaining_capacity = (
int(total_installed_capacity * self.latestData["status"]["RSOC"]) / 100.0
)
self.latestData["battery_info"]["remaining_capacity_usable"] = max(
0, int(remaining_capacity - reserved_capacity)
)

def send_all_data_to_log(self):
"""
Since we're in "debug" mode, send all data to the log, so we don't have to search for the
variable we're looking for if it's not where we expect it to be
"""
if not self.fullLogsAlreadySent:
LOGGER.warning(f"Powermeter data:\n{self.latestData['powermeter']}")
LOGGER.warning(f"Battery system data:\n{self.latestData['battery_system']}")
LOGGER.warning(f"Inverted:\n{self.latestData['inverter']}")
LOGGER.warning(f"System data:\n{self.latestData['system_data']}")
LOGGER.warning(f"Status:\n{self.latestData['status']}")
LOGGER.warning(f"Battery:\n{self.latestData['battery']}")
self.fullLogsAlreadySent = True
Loading

0 comments on commit 22483c9

Please sign in to comment.