Convert solarlog to coordinator (#55345)

This commit is contained in:
Paulus Schoutsen 2021-08-27 14:59:55 -07:00 committed by GitHub
parent eb458fb1d5
commit 46d0523f98
No known key found for this signature in database
GPG Key ID: 4AEE18F83AFDEB23
3 changed files with 108 additions and 117 deletions

View File

@ -1,12 +1,28 @@
"""Solar-Log integration.""" """Solar-Log integration."""
from datetime import timedelta
import logging
from urllib.parse import ParseResult, urlparse
from requests.exceptions import HTTPError, Timeout
from sunwatcher.solarlog.solarlog import SolarLog
from homeassistant.config_entries import ConfigEntry from homeassistant.config_entries import ConfigEntry
from homeassistant.const import CONF_HOST
from homeassistant.core import HomeAssistant from homeassistant.core import HomeAssistant
from homeassistant.helpers import update_coordinator
from .const import DOMAIN
_LOGGER = logging.getLogger(__name__)
PLATFORMS = ["sensor"] PLATFORMS = ["sensor"]
async def async_setup_entry(hass: HomeAssistant, entry: ConfigEntry) -> bool: async def async_setup_entry(hass: HomeAssistant, entry: ConfigEntry) -> bool:
"""Set up a config entry for solarlog.""" """Set up a config entry for solarlog."""
coordinator = SolarlogData(hass, entry)
await coordinator.async_config_entry_first_refresh()
hass.data.setdefault(DOMAIN, {})[entry.entry_id] = coordinator
hass.config_entries.async_setup_platforms(entry, PLATFORMS) hass.config_entries.async_setup_platforms(entry, PLATFORMS)
return True return True
@ -14,3 +30,73 @@ async def async_setup_entry(hass: HomeAssistant, entry: ConfigEntry) -> bool:
async def async_unload_entry(hass, entry): async def async_unload_entry(hass, entry):
"""Unload a config entry.""" """Unload a config entry."""
return await hass.config_entries.async_unload_platforms(entry, PLATFORMS) return await hass.config_entries.async_unload_platforms(entry, PLATFORMS)
class SolarlogData(update_coordinator.DataUpdateCoordinator):
"""Get and update the latest data."""
def __init__(self, hass: HomeAssistant, entry: ConfigEntry) -> None:
"""Initialize the data object."""
super().__init__(
hass, _LOGGER, name="SolarLog", update_interval=timedelta(seconds=60)
)
host_entry = entry.data[CONF_HOST]
url = urlparse(host_entry, "http")
netloc = url.netloc or url.path
path = url.path if url.netloc else ""
url = ParseResult("http", netloc, path, *url[3:])
self.unique_id = entry.entry_id
self.name = entry.title
self.host = url.geturl()
async def _async_update_data(self):
"""Update the data from the SolarLog device."""
try:
api = await self.hass.async_add_executor_job(SolarLog, self.host)
except (OSError, Timeout, HTTPError) as err:
raise update_coordinator.UpdateFailed(err)
if api.time.year == 1999:
raise update_coordinator.UpdateFailed(
"Invalid data returned (can happen after Solarlog restart)."
)
self.logger.debug(
"Connection to Solarlog successful. Retrieving latest Solarlog update of %s",
api.time,
)
data = {}
try:
data["TIME"] = api.time
data["powerAC"] = api.power_ac
data["powerDC"] = api.power_dc
data["voltageAC"] = api.voltage_ac
data["voltageDC"] = api.voltage_dc
data["yieldDAY"] = api.yield_day / 1000
data["yieldYESTERDAY"] = api.yield_yesterday / 1000
data["yieldMONTH"] = api.yield_month / 1000
data["yieldYEAR"] = api.yield_year / 1000
data["yieldTOTAL"] = api.yield_total / 1000
data["consumptionAC"] = api.consumption_ac
data["consumptionDAY"] = api.consumption_day / 1000
data["consumptionYESTERDAY"] = api.consumption_yesterday / 1000
data["consumptionMONTH"] = api.consumption_month / 1000
data["consumptionYEAR"] = api.consumption_year / 1000
data["consumptionTOTAL"] = api.consumption_total / 1000
data["totalPOWER"] = api.total_power
data["alternatorLOSS"] = api.alternator_loss
data["CAPACITY"] = round(api.capacity * 100, 0)
data["EFFICIENCY"] = round(api.efficiency * 100, 0)
data["powerAVAILABLE"] = api.power_available
data["USAGE"] = round(api.usage * 100, 0)
except AttributeError as err:
raise update_coordinator.UpdateFailed(
f"Missing details data in Solarlog response: {err}"
) from err
_LOGGER.debug("Updated Solarlog overview data: %s", data)
return data

View File

@ -2,7 +2,6 @@
from __future__ import annotations from __future__ import annotations
from dataclasses import dataclass from dataclasses import dataclass
from datetime import timedelta
from homeassistant.components.sensor import ( from homeassistant.components.sensor import (
STATE_CLASS_MEASUREMENT, STATE_CLASS_MEASUREMENT,
@ -23,13 +22,10 @@ from homeassistant.const import (
DOMAIN = "solarlog" DOMAIN = "solarlog"
"""Default config for solarlog.""" # Default config for solarlog.
DEFAULT_HOST = "http://solar-log" DEFAULT_HOST = "http://solar-log"
DEFAULT_NAME = "solarlog" DEFAULT_NAME = "solarlog"
"""Fixed constants."""
SCAN_INTERVAL = timedelta(seconds=60)
@dataclass @dataclass
class SolarlogRequiredKeysMixin: class SolarlogRequiredKeysMixin:

View File

@ -1,133 +1,42 @@
"""Platform for solarlog sensors.""" """Platform for solarlog sensors."""
import logging
from urllib.parse import ParseResult, urlparse
from requests.exceptions import HTTPError, Timeout
from sunwatcher.solarlog.solarlog import SolarLog
from homeassistant.components.sensor import SensorEntity from homeassistant.components.sensor import SensorEntity
from homeassistant.const import CONF_HOST from homeassistant.helpers import update_coordinator
from homeassistant.util import Throttle from homeassistant.helpers.entity import StateType
from .const import DOMAIN, SCAN_INTERVAL, SENSOR_TYPES, SolarLogSensorEntityDescription from . import SolarlogData
from .const import DOMAIN, SENSOR_TYPES, SolarLogSensorEntityDescription
_LOGGER = logging.getLogger(__name__)
async def async_setup_platform(hass, config, async_add_entities, discovery_info=None):
"""Set up the solarlog platform."""
_LOGGER.warning(
"Configuration of the solarlog platform in configuration.yaml is deprecated "
"in Home Assistant 0.119. Please remove entry from your configuration"
)
async def async_setup_entry(hass, entry, async_add_entities): async def async_setup_entry(hass, entry, async_add_entities):
"""Add solarlog entry.""" """Add solarlog entry."""
host_entry = entry.data[CONF_HOST] coordinator = hass.data[DOMAIN][entry.entry_id]
device_name = entry.title async_add_entities(
SolarlogSensor(coordinator, description) for description in SENSOR_TYPES
url = urlparse(host_entry, "http")
netloc = url.netloc or url.path
path = url.path if url.netloc else ""
url = ParseResult("http", netloc, path, *url[3:])
host = url.geturl()
try:
api = await hass.async_add_executor_job(SolarLog, host)
_LOGGER.debug("Connected to Solar-Log device, setting up entries")
except (OSError, HTTPError, Timeout):
_LOGGER.error(
"Could not connect to Solar-Log device at %s, check host ip address", host
) )
return
# Create solarlog data service which will retrieve and update the data.
data = await hass.async_add_executor_job(SolarlogData, hass, api, host)
# Create a new sensor for each sensor type.
entities = [
SolarlogSensor(entry.entry_id, device_name, data, description)
for description in SENSOR_TYPES
]
async_add_entities(entities, True)
return True
class SolarlogData: class SolarlogSensor(update_coordinator.CoordinatorEntity, SensorEntity):
"""Get and update the latest data."""
def __init__(self, hass, api, host):
"""Initialize the data object."""
self.api = api
self.hass = hass
self.host = host
self.update = Throttle(SCAN_INTERVAL)(self._update)
self.data = {}
def _update(self):
"""Update the data from the SolarLog device."""
try:
self.api = SolarLog(self.host)
response = self.api.time
_LOGGER.debug(
"Connection to Solarlog successful. Retrieving latest Solarlog update of %s",
response,
)
except (OSError, Timeout, HTTPError):
_LOGGER.error("Connection error, Could not retrieve data, skipping update")
return
try:
self.data["TIME"] = self.api.time
self.data["powerAC"] = self.api.power_ac
self.data["powerDC"] = self.api.power_dc
self.data["voltageAC"] = self.api.voltage_ac
self.data["voltageDC"] = self.api.voltage_dc
self.data["yieldDAY"] = self.api.yield_day / 1000
self.data["yieldYESTERDAY"] = self.api.yield_yesterday / 1000
self.data["yieldMONTH"] = self.api.yield_month / 1000
self.data["yieldYEAR"] = self.api.yield_year / 1000
self.data["yieldTOTAL"] = self.api.yield_total / 1000
self.data["consumptionAC"] = self.api.consumption_ac
self.data["consumptionDAY"] = self.api.consumption_day / 1000
self.data["consumptionYESTERDAY"] = self.api.consumption_yesterday / 1000
self.data["consumptionMONTH"] = self.api.consumption_month / 1000
self.data["consumptionYEAR"] = self.api.consumption_year / 1000
self.data["consumptionTOTAL"] = self.api.consumption_total / 1000
self.data["totalPOWER"] = self.api.total_power
self.data["alternatorLOSS"] = self.api.alternator_loss
self.data["CAPACITY"] = round(self.api.capacity * 100, 0)
self.data["EFFICIENCY"] = round(self.api.efficiency * 100, 0)
self.data["powerAVAILABLE"] = self.api.power_available
self.data["USAGE"] = round(self.api.usage * 100, 0)
_LOGGER.debug("Updated Solarlog overview data: %s", self.data)
except AttributeError:
_LOGGER.error("Missing details data in Solarlog response")
class SolarlogSensor(SensorEntity):
"""Representation of a Sensor.""" """Representation of a Sensor."""
entity_description: SolarLogSensorEntityDescription
def __init__( def __init__(
self, self,
entry_id: str, coordinator: SolarlogData,
device_name: str,
data: SolarlogData,
description: SolarLogSensorEntityDescription, description: SolarLogSensorEntityDescription,
) -> None: ) -> None:
"""Initialize the sensor.""" """Initialize the sensor."""
super().__init__(coordinator)
self.entity_description = description self.entity_description = description
self.data = data self._attr_name = f"{coordinator.name} {description.name}"
self._attr_name = f"{device_name} {description.name}" self._attr_unique_id = f"{coordinator.unique_id}_{description.key}"
self._attr_unique_id = f"{entry_id}_{description.key}"
self._attr_device_info = { self._attr_device_info = {
"identifiers": {(DOMAIN, entry_id)}, "identifiers": {(DOMAIN, coordinator.unique_id)},
"name": device_name, "name": coordinator.name,
"manufacturer": "Solar-Log", "manufacturer": "Solar-Log",
} }
def update(self): @property
"""Get the latest data from the sensor and update the state.""" def native_value(self) -> StateType:
self.data.update() """Return the native sensor value."""
self._attr_native_value = self.data.data[self.entity_description.json_key] return self.coordinator.data[self.entity_description.json_key]