System Bridge v2.3.0+ - Data from WebSocket (#53443)

Co-authored-by: Martin Hjelmare <marhje52@gmail.com>
This commit is contained in:
Aidan Timson 2021-08-02 21:11:26 +01:00 committed by GitHub
parent 938ec27a86
commit 18f4d125c3
No known key found for this signature in database
GPG Key ID: 4AEE18F83AFDEB23
10 changed files with 327 additions and 245 deletions

View File

@ -1007,8 +1007,9 @@ omit =
homeassistant/components/synology_srm/device_tracker.py homeassistant/components/synology_srm/device_tracker.py
homeassistant/components/syslog/notify.py homeassistant/components/syslog/notify.py
homeassistant/components/system_bridge/__init__.py homeassistant/components/system_bridge/__init__.py
homeassistant/components/system_bridge/const.py
homeassistant/components/system_bridge/binary_sensor.py homeassistant/components/system_bridge/binary_sensor.py
homeassistant/components/system_bridge/const.py
homeassistant/components/system_bridge/coordinator.py
homeassistant/components/system_bridge/sensor.py homeassistant/components/system_bridge/sensor.py
homeassistant/components/systemmonitor/sensor.py homeassistant/components/systemmonitor/sensor.py
homeassistant/components/tado/* homeassistant/components/tado/*

View File

@ -2,7 +2,6 @@
from __future__ import annotations from __future__ import annotations
import asyncio import asyncio
from datetime import timedelta
import logging import logging
import shlex import shlex
@ -22,20 +21,17 @@ from homeassistant.const import (
CONF_PORT, CONF_PORT,
) )
from homeassistant.core import HomeAssistant from homeassistant.core import HomeAssistant
from homeassistant.exceptions import ConfigEntryAuthFailed from homeassistant.exceptions import ConfigEntryAuthFailed, ConfigEntryNotReady
from homeassistant.helpers import ( from homeassistant.helpers import (
aiohttp_client, aiohttp_client,
config_validation as cv, config_validation as cv,
device_registry as dr, device_registry as dr,
) )
from homeassistant.helpers.entity import DeviceInfo from homeassistant.helpers.entity import DeviceInfo
from homeassistant.helpers.update_coordinator import ( from homeassistant.helpers.update_coordinator import CoordinatorEntity
CoordinatorEntity,
DataUpdateCoordinator,
UpdateFailed,
)
from .const import BRIDGE_CONNECTION_ERRORS, DOMAIN from .const import BRIDGE_CONNECTION_ERRORS, DOMAIN
from .coordinator import SystemBridgeDataUpdateCoordinator
_LOGGER = logging.getLogger(__name__) _LOGGER = logging.getLogger(__name__)
@ -61,51 +57,55 @@ SERVICE_OPEN_SCHEMA = vol.Schema(
async def async_setup_entry(hass: HomeAssistant, entry: ConfigEntry) -> bool: async def async_setup_entry(hass: HomeAssistant, entry: ConfigEntry) -> bool:
"""Set up System Bridge from a config entry.""" """Set up System Bridge from a config entry."""
bridge = Bridge(
client = Bridge(
BridgeClient(aiohttp_client.async_get_clientsession(hass)), BridgeClient(aiohttp_client.async_get_clientsession(hass)),
f"http://{entry.data[CONF_HOST]}:{entry.data[CONF_PORT]}", f"http://{entry.data[CONF_HOST]}:{entry.data[CONF_PORT]}",
entry.data[CONF_API_KEY], entry.data[CONF_API_KEY],
) )
async def async_update_data() -> Bridge: try:
"""Fetch data from Bridge.""" async with async_timeout.timeout(30):
try: await bridge.async_get_information()
async with async_timeout.timeout(60): except BridgeAuthenticationException as exception:
await asyncio.gather( raise ConfigEntryAuthFailed(
*[ f"Authentication failed for {entry.title} ({entry.data[CONF_HOST]})"
client.async_get_battery(), ) from exception
client.async_get_cpu(), except BRIDGE_CONNECTION_ERRORS as exception:
client.async_get_filesystem(), raise ConfigEntryNotReady(
client.async_get_memory(), f"Could not connect to {entry.title} ({entry.data[CONF_HOST]})."
client.async_get_network(), ) from exception
client.async_get_os(),
client.async_get_processes(),
client.async_get_system(),
]
)
return client
except BridgeAuthenticationException as exception:
raise ConfigEntryAuthFailed from exception
except BRIDGE_CONNECTION_ERRORS as exception:
raise UpdateFailed("Could not connect to System Bridge.") from exception
coordinator = DataUpdateCoordinator( coordinator = SystemBridgeDataUpdateCoordinator(hass, bridge, _LOGGER, entry=entry)
hass, await coordinator.async_config_entry_first_refresh()
_LOGGER,
# Name of the data. For logging purposes. # Wait for initial data
name=f"{DOMAIN}_coordinator", try:
update_method=async_update_data, async with async_timeout.timeout(60):
# Polling interval. Will only be polled if there are subscribers. while (
update_interval=timedelta(seconds=60), coordinator.bridge.battery is None
) or coordinator.bridge.cpu is None
or coordinator.bridge.filesystem is None
or coordinator.bridge.information is None
or coordinator.bridge.memory is None
or coordinator.bridge.network is None
or coordinator.bridge.os is None
or coordinator.bridge.processes is None
or coordinator.bridge.system is None
):
_LOGGER.debug(
"Waiting for initial data from %s (%s)",
entry.title,
entry.data[CONF_HOST],
)
await asyncio.sleep(1)
except asyncio.TimeoutError as exception:
raise ConfigEntryNotReady(
f"Timed out waiting for {entry.title} ({entry.data[CONF_HOST]})."
) from exception
hass.data.setdefault(DOMAIN, {}) hass.data.setdefault(DOMAIN, {})
hass.data[DOMAIN][entry.entry_id] = coordinator hass.data[DOMAIN][entry.entry_id] = coordinator
# Fetch initial data so we have data when entities subscribe
await coordinator.async_config_entry_first_refresh()
hass.config_entries.async_setup_platforms(entry, PLATFORMS) hass.config_entries.async_setup_platforms(entry, PLATFORMS)
if hass.services.has_service(DOMAIN, SERVICE_SEND_COMMAND): if hass.services.has_service(DOMAIN, SERVICE_SEND_COMMAND):
@ -128,8 +128,8 @@ async def async_setup_entry(hass: HomeAssistant, entry: ConfigEntry) -> bool:
for entry in hass.config_entries.async_entries(DOMAIN) for entry in hass.config_entries.async_entries(DOMAIN)
if entry.entry_id in device_entry.config_entries if entry.entry_id in device_entry.config_entries
) )
coordinator: DataUpdateCoordinator = hass.data[DOMAIN][entry_id] coordinator: SystemBridgeDataUpdateCoordinator = hass.data[DOMAIN][entry_id]
bridge: Bridge = coordinator.data bridge: Bridge = coordinator.bridge
_LOGGER.debug( _LOGGER.debug(
"Command payload: %s", "Command payload: %s",
@ -166,8 +166,8 @@ async def async_setup_entry(hass: HomeAssistant, entry: ConfigEntry) -> bool:
for entry in hass.config_entries.async_entries(DOMAIN) for entry in hass.config_entries.async_entries(DOMAIN)
if entry.entry_id in device_entry.config_entries if entry.entry_id in device_entry.config_entries
) )
coordinator: DataUpdateCoordinator = hass.data[DOMAIN][entry_id] coordinator: SystemBridgeDataUpdateCoordinator = hass.data[DOMAIN][entry_id]
bridge: Bridge = coordinator.data bridge: Bridge = coordinator.bridge
_LOGGER.debug("Open payload: %s", {CONF_PATH: path}) _LOGGER.debug("Open payload: %s", {CONF_PATH: path})
try: try:
@ -190,14 +190,26 @@ async def async_setup_entry(hass: HomeAssistant, entry: ConfigEntry) -> bool:
schema=SERVICE_OPEN_SCHEMA, schema=SERVICE_OPEN_SCHEMA,
) )
# Reload entry when its updated.
entry.async_on_unload(entry.add_update_listener(async_reload_entry))
return True return True
async def async_unload_entry(hass: HomeAssistant, entry: ConfigEntry): async def async_unload_entry(hass: HomeAssistant, entry: ConfigEntry) -> bool:
"""Unload a config entry.""" """Unload a config entry."""
unload_ok = await hass.config_entries.async_unload_platforms(entry, PLATFORMS) unload_ok = await hass.config_entries.async_unload_platforms(entry, PLATFORMS)
if unload_ok: if unload_ok:
hass.data[DOMAIN].pop(entry.entry_id) coordinator: SystemBridgeDataUpdateCoordinator = hass.data[DOMAIN][
entry.entry_id
]
# Ensure disconnected and cleanup stop sub
await coordinator.bridge.async_close_websocket()
if coordinator.unsub:
coordinator.unsub()
del hass.data[DOMAIN][entry.entry_id]
if not hass.data[DOMAIN]: if not hass.data[DOMAIN]:
hass.services.async_remove(DOMAIN, SERVICE_SEND_COMMAND) hass.services.async_remove(DOMAIN, SERVICE_SEND_COMMAND)
@ -206,13 +218,17 @@ async def async_unload_entry(hass: HomeAssistant, entry: ConfigEntry):
return unload_ok return unload_ok
class BridgeEntity(CoordinatorEntity): async def async_reload_entry(hass: HomeAssistant, entry: ConfigEntry) -> None:
"""Reload the config entry when it changed."""
await hass.config_entries.async_reload(entry.entry_id)
class SystemBridgeEntity(CoordinatorEntity):
"""Defines a base System Bridge entity.""" """Defines a base System Bridge entity."""
def __init__( def __init__(
self, self,
coordinator: DataUpdateCoordinator, coordinator: SystemBridgeDataUpdateCoordinator,
bridge: Bridge,
key: str, key: str,
name: str, name: str,
icon: str | None, icon: str | None,
@ -220,14 +236,13 @@ class BridgeEntity(CoordinatorEntity):
) -> None: ) -> None:
"""Initialize the System Bridge entity.""" """Initialize the System Bridge entity."""
super().__init__(coordinator) super().__init__(coordinator)
self._key = f"{bridge.os.hostname}_{key}" bridge: Bridge = coordinator.data
self._name = f"{bridge.os.hostname} {name}" self._key = f"{bridge.information.host}_{key}"
self._name = f"{bridge.information.host} {name}"
self._icon = icon self._icon = icon
self._enabled_default = enabled_by_default self._enabled_default = enabled_by_default
self._hostname = bridge.os.hostname self._hostname = bridge.information.host
self._default_interface = bridge.network.interfaces[ self._mac = bridge.information.mac
bridge.network.interfaceDefault
]
self._manufacturer = bridge.system.system.manufacturer self._manufacturer = bridge.system.system.manufacturer
self._model = bridge.system.system.model self._model = bridge.system.system.model
self._version = bridge.system.system.version self._version = bridge.system.system.version
@ -253,16 +268,14 @@ class BridgeEntity(CoordinatorEntity):
return self._enabled_default return self._enabled_default
class BridgeDeviceEntity(BridgeEntity): class SystemBridgeDeviceEntity(SystemBridgeEntity):
"""Defines a System Bridge device entity.""" """Defines a System Bridge device entity."""
@property @property
def device_info(self) -> DeviceInfo: def device_info(self) -> DeviceInfo:
"""Return device information about this System Bridge instance.""" """Return device information about this System Bridge instance."""
return { return {
"connections": { "connections": {(dr.CONNECTION_NETWORK_MAC, self._mac)},
(dr.CONNECTION_NETWORK_MAC, self._default_interface["mac"])
},
"manufacturer": self._manufacturer, "manufacturer": self._manufacturer,
"model": self._model, "model": self._model,
"name": self._hostname, "name": self._hostname,

View File

@ -9,30 +9,29 @@ from homeassistant.components.binary_sensor import (
) )
from homeassistant.config_entries import ConfigEntry from homeassistant.config_entries import ConfigEntry
from homeassistant.core import HomeAssistant from homeassistant.core import HomeAssistant
from homeassistant.helpers.update_coordinator import DataUpdateCoordinator
from . import BridgeDeviceEntity from . import SystemBridgeDeviceEntity
from .const import DOMAIN from .const import DOMAIN
from .coordinator import SystemBridgeDataUpdateCoordinator
async def async_setup_entry( async def async_setup_entry(
hass: HomeAssistant, entry: ConfigEntry, async_add_entities hass: HomeAssistant, entry: ConfigEntry, async_add_entities
) -> None: ) -> None:
"""Set up System Bridge binary sensor based on a config entry.""" """Set up System Bridge binary sensor based on a config entry."""
coordinator: DataUpdateCoordinator = hass.data[DOMAIN][entry.entry_id] coordinator: SystemBridgeDataUpdateCoordinator = hass.data[DOMAIN][entry.entry_id]
bridge: Bridge = coordinator.data bridge: Bridge = coordinator.data
if bridge.battery.hasBattery: if bridge.battery and bridge.battery.hasBattery:
async_add_entities([BridgeBatteryIsChargingBinarySensor(coordinator, bridge)]) async_add_entities([SystemBridgeBatteryIsChargingBinarySensor(coordinator)])
class BridgeBinarySensor(BridgeDeviceEntity, BinarySensorEntity): class SystemBridgeBinarySensor(SystemBridgeDeviceEntity, BinarySensorEntity):
"""Defines a System Bridge binary sensor.""" """Defines a System Bridge binary sensor."""
def __init__( def __init__(
self, self,
coordinator: DataUpdateCoordinator, coordinator: SystemBridgeDataUpdateCoordinator,
bridge: Bridge,
key: str, key: str,
name: str, name: str,
icon: str | None, icon: str | None,
@ -42,7 +41,7 @@ class BridgeBinarySensor(BridgeDeviceEntity, BinarySensorEntity):
"""Initialize System Bridge binary sensor.""" """Initialize System Bridge binary sensor."""
self._device_class = device_class self._device_class = device_class
super().__init__(coordinator, bridge, key, name, icon, enabled_by_default) super().__init__(coordinator, key, name, icon, enabled_by_default)
@property @property
def device_class(self) -> str | None: def device_class(self) -> str | None:
@ -50,14 +49,13 @@ class BridgeBinarySensor(BridgeDeviceEntity, BinarySensorEntity):
return self._device_class return self._device_class
class BridgeBatteryIsChargingBinarySensor(BridgeBinarySensor): class SystemBridgeBatteryIsChargingBinarySensor(SystemBridgeBinarySensor):
"""Defines a Battery is charging binary sensor.""" """Defines a Battery is charging binary sensor."""
def __init__(self, coordinator: DataUpdateCoordinator, bridge: Bridge) -> None: def __init__(self, coordinator: SystemBridgeDataUpdateCoordinator) -> None:
"""Initialize System Bridge binary sensor.""" """Initialize System Bridge binary sensor."""
super().__init__( super().__init__(
coordinator, coordinator,
bridge,
"battery_is_charging", "battery_is_charging",
"Battery Is Charging", "Battery Is Charging",
None, None,

View File

@ -8,8 +8,6 @@ import async_timeout
from systembridge import Bridge from systembridge import Bridge
from systembridge.client import BridgeClient from systembridge.client import BridgeClient
from systembridge.exceptions import BridgeAuthenticationException from systembridge.exceptions import BridgeAuthenticationException
from systembridge.objects.os import Os
from systembridge.objects.system import System
import voluptuous as vol import voluptuous as vol
from homeassistant import config_entries, exceptions from homeassistant import config_entries, exceptions
@ -47,10 +45,14 @@ async def validate_input(hass: HomeAssistant, data: dict[str, Any]) -> dict[str,
hostname = data[CONF_HOST] hostname = data[CONF_HOST]
try: try:
async with async_timeout.timeout(30): async with async_timeout.timeout(30):
bridge_os: Os = await bridge.async_get_os() await bridge.async_get_information()
if bridge_os.hostname is not None: if (
hostname = bridge_os.hostname bridge.information is not None
bridge_system: System = await bridge.async_get_system() and bridge.information.host is not None
and bridge.information.uuid is not None
):
hostname = bridge.information.host
uuid = bridge.information.uuid
except BridgeAuthenticationException as exception: except BridgeAuthenticationException as exception:
_LOGGER.info(exception) _LOGGER.info(exception)
raise InvalidAuth from exception raise InvalidAuth from exception
@ -58,7 +60,7 @@ async def validate_input(hass: HomeAssistant, data: dict[str, Any]) -> dict[str,
_LOGGER.info(exception) _LOGGER.info(exception)
raise CannotConnect from exception raise CannotConnect from exception
return {"hostname": hostname, "uuid": bridge_system.uuid.os} return {"hostname": hostname, "uuid": uuid}
class ConfigFlow(config_entries.ConfigFlow, domain=DOMAIN): class ConfigFlow(config_entries.ConfigFlow, domain=DOMAIN):

View File

@ -0,0 +1,139 @@
"""DataUpdateCoordinator for System Bridge."""
from __future__ import annotations
import asyncio
from datetime import timedelta
import logging
from typing import Callable
from systembridge import Bridge
from systembridge.exceptions import (
BridgeAuthenticationException,
BridgeConnectionClosedException,
BridgeException,
)
from systembridge.objects.events import Event
from homeassistant.config_entries import ConfigEntry
from homeassistant.const import CONF_HOST, EVENT_HOMEASSISTANT_STOP
from homeassistant.core import HomeAssistant
from homeassistant.exceptions import ConfigEntryAuthFailed
from homeassistant.helpers.update_coordinator import DataUpdateCoordinator, UpdateFailed
from .const import BRIDGE_CONNECTION_ERRORS, DOMAIN
class SystemBridgeDataUpdateCoordinator(DataUpdateCoordinator[Bridge]):
"""Class to manage fetching System Bridge data from single endpoint."""
def __init__(
self,
hass: HomeAssistant,
bridge: Bridge,
LOGGER: logging.Logger,
*,
entry: ConfigEntry,
) -> None:
"""Initialize global System Bridge data updater."""
self.bridge = bridge
self.title = entry.title
self.host = entry.data[CONF_HOST]
self.unsub: Callable | None = None
super().__init__(
hass, LOGGER, name=DOMAIN, update_interval=timedelta(seconds=30)
)
def update_listeners(self) -> None:
"""Call update on all listeners."""
for update_callback in self._listeners:
update_callback()
async def async_handle_event(self, event: Event):
"""Handle System Bridge events from the WebSocket."""
# No need to update anything, as everything is updated in the caller
self.logger.debug(
"New event from %s (%s): %s", self.title, self.host, event.name
)
self.async_set_updated_data(self.bridge)
async def _listen_for_events(self) -> None:
"""Listen for events from the WebSocket."""
try:
await self.bridge.async_send_event(
"get-data",
[
"battery",
"cpu",
"filesystem",
"memory",
"network",
"os",
"processes",
"system",
],
)
await self.bridge.listen_for_events(callback=self.async_handle_event)
except BridgeConnectionClosedException as exception:
self.last_update_success = False
self.logger.info(
"Websocket Connection Closed for %s (%s). Will retry: %s",
self.title,
self.host,
exception,
)
except BridgeException as exception:
self.last_update_success = False
self.update_listeners()
self.logger.warning(
"Exception occurred for %s (%s). Will retry: %s",
self.title,
self.host,
exception,
)
async def _setup_websocket(self) -> None:
"""Use WebSocket for updates."""
try:
self.logger.debug(
"Connecting to ws://%s:%s",
self.host,
self.bridge.information.websocketPort,
)
await self.bridge.async_connect_websocket(
self.host, self.bridge.information.websocketPort
)
except BridgeAuthenticationException as exception:
if self.unsub:
self.unsub()
self.unsub = None
raise ConfigEntryAuthFailed() from exception
except (*BRIDGE_CONNECTION_ERRORS, ConnectionRefusedError) as exception:
if self.unsub:
self.unsub()
self.unsub = None
raise UpdateFailed(
f"Could not connect to {self.title} ({self.host})."
) from exception
asyncio.create_task(self._listen_for_events())
async def close_websocket(_) -> None:
"""Close WebSocket connection."""
await self.bridge.async_close_websocket()
# Clean disconnect WebSocket on Home Assistant shutdown
self.unsub = self.hass.bus.async_listen_once(
EVENT_HOMEASSISTANT_STOP, close_websocket
)
async def _async_update_data(self) -> Bridge:
"""Update System Bridge data from WebSocket."""
self.logger.debug(
"_async_update_data - WebSocket Connected: %s",
self.bridge.websocket_connected,
)
if not self.bridge.websocket_connected:
await self._setup_websocket()
return self.bridge

View File

@ -3,10 +3,10 @@
"name": "System Bridge", "name": "System Bridge",
"config_flow": true, "config_flow": true,
"documentation": "https://www.home-assistant.io/integrations/system_bridge", "documentation": "https://www.home-assistant.io/integrations/system_bridge",
"requirements": ["systembridge==1.1.5"], "requirements": ["systembridge==2.0.6"],
"codeowners": ["@timmo001"], "codeowners": ["@timmo001"],
"zeroconf": ["_system-bridge._udp.local."], "zeroconf": ["_system-bridge._udp.local."],
"after_dependencies": ["zeroconf"], "after_dependencies": ["zeroconf"],
"quality_scale": "silver", "quality_scale": "silver",
"iot_class": "local_polling" "iot_class": "local_push"
} }

View File

@ -20,10 +20,10 @@ from homeassistant.const import (
TEMP_CELSIUS, TEMP_CELSIUS,
) )
from homeassistant.core import HomeAssistant from homeassistant.core import HomeAssistant
from homeassistant.helpers.update_coordinator import DataUpdateCoordinator
from . import BridgeDeviceEntity from . import SystemBridgeDeviceEntity
from .const import DOMAIN from .const import DOMAIN
from .coordinator import SystemBridgeDataUpdateCoordinator
ATTR_AVAILABLE = "available" ATTR_AVAILABLE = "available"
ATTR_FILESYSTEM = "filesystem" ATTR_FILESYSTEM = "filesystem"
@ -41,40 +41,38 @@ async def async_setup_entry(
hass: HomeAssistant, entry: ConfigEntry, async_add_entities hass: HomeAssistant, entry: ConfigEntry, async_add_entities
) -> None: ) -> None:
"""Set up System Bridge sensor based on a config entry.""" """Set up System Bridge sensor based on a config entry."""
coordinator: DataUpdateCoordinator = hass.data[DOMAIN][entry.entry_id] coordinator: SystemBridgeDataUpdateCoordinator = hass.data[DOMAIN][entry.entry_id]
bridge: Bridge = coordinator.data
entities = [ entities = [
BridgeCpuSpeedSensor(coordinator, bridge), SystemBridgeCpuSpeedSensor(coordinator),
BridgeCpuTemperatureSensor(coordinator, bridge), SystemBridgeCpuTemperatureSensor(coordinator),
BridgeCpuVoltageSensor(coordinator, bridge), SystemBridgeCpuVoltageSensor(coordinator),
*( *(
BridgeFilesystemSensor(coordinator, bridge, key) SystemBridgeFilesystemSensor(coordinator, key)
for key, _ in bridge.filesystem.fsSize.items() for key, _ in coordinator.data.filesystem.fsSize.items()
), ),
BridgeMemoryFreeSensor(coordinator, bridge), SystemBridgeMemoryFreeSensor(coordinator),
BridgeMemoryUsedSensor(coordinator, bridge), SystemBridgeMemoryUsedSensor(coordinator),
BridgeMemoryUsedPercentageSensor(coordinator, bridge), SystemBridgeMemoryUsedPercentageSensor(coordinator),
BridgeKernelSensor(coordinator, bridge), SystemBridgeKernelSensor(coordinator),
BridgeOsSensor(coordinator, bridge), SystemBridgeOsSensor(coordinator),
BridgeProcessesLoadSensor(coordinator, bridge), SystemBridgeProcessesLoadSensor(coordinator),
BridgeBiosVersionSensor(coordinator, bridge), SystemBridgeBiosVersionSensor(coordinator),
] ]
if bridge.battery.hasBattery: if coordinator.data.battery.hasBattery:
entities.append(BridgeBatterySensor(coordinator, bridge)) entities.append(SystemBridgeBatterySensor(coordinator))
entities.append(BridgeBatteryTimeRemainingSensor(coordinator, bridge)) entities.append(SystemBridgeBatteryTimeRemainingSensor(coordinator))
async_add_entities(entities) async_add_entities(entities)
class BridgeSensor(BridgeDeviceEntity, SensorEntity): class SystemBridgeSensor(SystemBridgeDeviceEntity, SensorEntity):
"""Defines a System Bridge sensor.""" """Defines a System Bridge sensor."""
def __init__( def __init__(
self, self,
coordinator: DataUpdateCoordinator, coordinator: SystemBridgeDataUpdateCoordinator,
bridge: Bridge,
key: str, key: str,
name: str, name: str,
icon: str | None, icon: str | None,
@ -86,7 +84,7 @@ class BridgeSensor(BridgeDeviceEntity, SensorEntity):
self._device_class = device_class self._device_class = device_class
self._unit_of_measurement = unit_of_measurement self._unit_of_measurement = unit_of_measurement
super().__init__(coordinator, bridge, key, name, icon, enabled_by_default) super().__init__(coordinator, key, name, icon, enabled_by_default)
@property @property
def device_class(self) -> str | None: def device_class(self) -> str | None:
@ -99,14 +97,13 @@ class BridgeSensor(BridgeDeviceEntity, SensorEntity):
return self._unit_of_measurement return self._unit_of_measurement
class BridgeBatterySensor(BridgeSensor): class SystemBridgeBatterySensor(SystemBridgeSensor):
"""Defines a Battery sensor.""" """Defines a Battery sensor."""
def __init__(self, coordinator: DataUpdateCoordinator, bridge: Bridge) -> None: def __init__(self, coordinator: SystemBridgeDataUpdateCoordinator) -> None:
"""Initialize System Bridge sensor.""" """Initialize System Bridge sensor."""
super().__init__( super().__init__(
coordinator, coordinator,
bridge,
"battery", "battery",
"Battery", "Battery",
None, None,
@ -122,14 +119,13 @@ class BridgeBatterySensor(BridgeSensor):
return bridge.battery.percent return bridge.battery.percent
class BridgeBatteryTimeRemainingSensor(BridgeSensor): class SystemBridgeBatteryTimeRemainingSensor(SystemBridgeSensor):
"""Defines the Battery Time Remaining sensor.""" """Defines the Battery Time Remaining sensor."""
def __init__(self, coordinator: DataUpdateCoordinator, bridge: Bridge) -> None: def __init__(self, coordinator: SystemBridgeDataUpdateCoordinator) -> None:
"""Initialize System Bridge sensor.""" """Initialize System Bridge sensor."""
super().__init__( super().__init__(
coordinator, coordinator,
bridge,
"battery_time_remaining", "battery_time_remaining",
"Battery Time Remaining", "Battery Time Remaining",
None, None,
@ -147,14 +143,13 @@ class BridgeBatteryTimeRemainingSensor(BridgeSensor):
return str(datetime.now() + timedelta(minutes=bridge.battery.timeRemaining)) return str(datetime.now() + timedelta(minutes=bridge.battery.timeRemaining))
class BridgeCpuSpeedSensor(BridgeSensor): class SystemBridgeCpuSpeedSensor(SystemBridgeSensor):
"""Defines a CPU speed sensor.""" """Defines a CPU speed sensor."""
def __init__(self, coordinator: DataUpdateCoordinator, bridge: Bridge) -> None: def __init__(self, coordinator: SystemBridgeDataUpdateCoordinator) -> None:
"""Initialize System Bridge sensor.""" """Initialize System Bridge sensor."""
super().__init__( super().__init__(
coordinator, coordinator,
bridge,
"cpu_speed", "cpu_speed",
"CPU Speed", "CPU Speed",
"mdi:speedometer", "mdi:speedometer",
@ -170,14 +165,13 @@ class BridgeCpuSpeedSensor(BridgeSensor):
return bridge.cpu.currentSpeed.avg return bridge.cpu.currentSpeed.avg
class BridgeCpuTemperatureSensor(BridgeSensor): class SystemBridgeCpuTemperatureSensor(SystemBridgeSensor):
"""Defines a CPU temperature sensor.""" """Defines a CPU temperature sensor."""
def __init__(self, coordinator: DataUpdateCoordinator, bridge: Bridge) -> None: def __init__(self, coordinator: SystemBridgeDataUpdateCoordinator) -> None:
"""Initialize System Bridge sensor.""" """Initialize System Bridge sensor."""
super().__init__( super().__init__(
coordinator, coordinator,
bridge,
"cpu_temperature", "cpu_temperature",
"CPU Temperature", "CPU Temperature",
None, None,
@ -193,14 +187,13 @@ class BridgeCpuTemperatureSensor(BridgeSensor):
return bridge.cpu.temperature.main return bridge.cpu.temperature.main
class BridgeCpuVoltageSensor(BridgeSensor): class SystemBridgeCpuVoltageSensor(SystemBridgeSensor):
"""Defines a CPU voltage sensor.""" """Defines a CPU voltage sensor."""
def __init__(self, coordinator: DataUpdateCoordinator, bridge: Bridge) -> None: def __init__(self, coordinator: SystemBridgeDataUpdateCoordinator) -> None:
"""Initialize System Bridge sensor.""" """Initialize System Bridge sensor."""
super().__init__( super().__init__(
coordinator, coordinator,
bridge,
"cpu_voltage", "cpu_voltage",
"CPU Voltage", "CPU Voltage",
None, None,
@ -216,17 +209,16 @@ class BridgeCpuVoltageSensor(BridgeSensor):
return bridge.cpu.cpu.voltage return bridge.cpu.cpu.voltage
class BridgeFilesystemSensor(BridgeSensor): class SystemBridgeFilesystemSensor(SystemBridgeSensor):
"""Defines a filesystem sensor.""" """Defines a filesystem sensor."""
def __init__( def __init__(
self, coordinator: DataUpdateCoordinator, bridge: Bridge, key: str self, coordinator: SystemBridgeDataUpdateCoordinator, key: str
) -> None: ) -> None:
"""Initialize System Bridge sensor.""" """Initialize System Bridge sensor."""
uid_key = key.replace(":", "") uid_key = key.replace(":", "")
super().__init__( super().__init__(
coordinator, coordinator,
bridge,
f"filesystem_{uid_key}", f"filesystem_{uid_key}",
f"{key} Space Used", f"{key} Space Used",
"mdi:harddisk", "mdi:harddisk",
@ -260,14 +252,13 @@ class BridgeFilesystemSensor(BridgeSensor):
} }
class BridgeMemoryFreeSensor(BridgeSensor): class SystemBridgeMemoryFreeSensor(SystemBridgeSensor):
"""Defines a memory free sensor.""" """Defines a memory free sensor."""
def __init__(self, coordinator: DataUpdateCoordinator, bridge: Bridge) -> None: def __init__(self, coordinator: SystemBridgeDataUpdateCoordinator) -> None:
"""Initialize System Bridge sensor.""" """Initialize System Bridge sensor."""
super().__init__( super().__init__(
coordinator, coordinator,
bridge,
"memory_free", "memory_free",
"Memory Free", "Memory Free",
"mdi:memory", "mdi:memory",
@ -287,14 +278,13 @@ class BridgeMemoryFreeSensor(BridgeSensor):
) )
class BridgeMemoryUsedSensor(BridgeSensor): class SystemBridgeMemoryUsedSensor(SystemBridgeSensor):
"""Defines a memory used sensor.""" """Defines a memory used sensor."""
def __init__(self, coordinator: DataUpdateCoordinator, bridge: Bridge) -> None: def __init__(self, coordinator: SystemBridgeDataUpdateCoordinator) -> None:
"""Initialize System Bridge sensor.""" """Initialize System Bridge sensor."""
super().__init__( super().__init__(
coordinator, coordinator,
bridge,
"memory_used", "memory_used",
"Memory Used", "Memory Used",
"mdi:memory", "mdi:memory",
@ -314,14 +304,13 @@ class BridgeMemoryUsedSensor(BridgeSensor):
) )
class BridgeMemoryUsedPercentageSensor(BridgeSensor): class SystemBridgeMemoryUsedPercentageSensor(SystemBridgeSensor):
"""Defines a memory used percentage sensor.""" """Defines a memory used percentage sensor."""
def __init__(self, coordinator: DataUpdateCoordinator, bridge: Bridge) -> None: def __init__(self, coordinator: SystemBridgeDataUpdateCoordinator) -> None:
"""Initialize System Bridge sensor.""" """Initialize System Bridge sensor."""
super().__init__( super().__init__(
coordinator, coordinator,
bridge,
"memory_used_percentage", "memory_used_percentage",
"Memory Used %", "Memory Used %",
"mdi:memory", "mdi:memory",
@ -341,14 +330,13 @@ class BridgeMemoryUsedPercentageSensor(BridgeSensor):
) )
class BridgeKernelSensor(BridgeSensor): class SystemBridgeKernelSensor(SystemBridgeSensor):
"""Defines a kernel sensor.""" """Defines a kernel sensor."""
def __init__(self, coordinator: DataUpdateCoordinator, bridge: Bridge) -> None: def __init__(self, coordinator: SystemBridgeDataUpdateCoordinator) -> None:
"""Initialize System Bridge sensor.""" """Initialize System Bridge sensor."""
super().__init__( super().__init__(
coordinator, coordinator,
bridge,
"kernel", "kernel",
"Kernel", "Kernel",
"mdi:devices", "mdi:devices",
@ -364,14 +352,13 @@ class BridgeKernelSensor(BridgeSensor):
return bridge.os.kernel return bridge.os.kernel
class BridgeOsSensor(BridgeSensor): class SystemBridgeOsSensor(SystemBridgeSensor):
"""Defines an OS sensor.""" """Defines an OS sensor."""
def __init__(self, coordinator: DataUpdateCoordinator, bridge: Bridge) -> None: def __init__(self, coordinator: SystemBridgeDataUpdateCoordinator) -> None:
"""Initialize System Bridge sensor.""" """Initialize System Bridge sensor."""
super().__init__( super().__init__(
coordinator, coordinator,
bridge,
"os", "os",
"Operating System", "Operating System",
"mdi:devices", "mdi:devices",
@ -387,14 +374,13 @@ class BridgeOsSensor(BridgeSensor):
return f"{bridge.os.distro} {bridge.os.release}" return f"{bridge.os.distro} {bridge.os.release}"
class BridgeProcessesLoadSensor(BridgeSensor): class SystemBridgeProcessesLoadSensor(SystemBridgeSensor):
"""Defines a Processes Load sensor.""" """Defines a Processes Load sensor."""
def __init__(self, coordinator: DataUpdateCoordinator, bridge: Bridge) -> None: def __init__(self, coordinator: SystemBridgeDataUpdateCoordinator) -> None:
"""Initialize System Bridge sensor.""" """Initialize System Bridge sensor."""
super().__init__( super().__init__(
coordinator, coordinator,
bridge,
"processes_load", "processes_load",
"Load", "Load",
"mdi:percent", "mdi:percent",
@ -429,14 +415,13 @@ class BridgeProcessesLoadSensor(BridgeSensor):
return attrs return attrs
class BridgeBiosVersionSensor(BridgeSensor): class SystemBridgeBiosVersionSensor(SystemBridgeSensor):
"""Defines a bios version sensor.""" """Defines a bios version sensor."""
def __init__(self, coordinator: DataUpdateCoordinator, bridge: Bridge) -> None: def __init__(self, coordinator: SystemBridgeDataUpdateCoordinator) -> None:
"""Initialize System Bridge sensor.""" """Initialize System Bridge sensor."""
super().__init__( super().__init__(
coordinator, coordinator,
bridge,
"bios_version", "bios_version",
"BIOS Version", "BIOS Version",
"mdi:chip", "mdi:chip",

View File

@ -2234,7 +2234,7 @@ swisshydrodata==0.1.0
synology-srm==0.2.0 synology-srm==0.2.0
# homeassistant.components.system_bridge # homeassistant.components.system_bridge
systembridge==1.1.5 systembridge==2.0.6
# homeassistant.components.tahoma # homeassistant.components.tahoma
tahoma-api==0.0.16 tahoma-api==0.0.16

View File

@ -1232,7 +1232,7 @@ sunwatcher==0.2.1
surepy==0.7.0 surepy==0.7.0
# homeassistant.components.system_bridge # homeassistant.components.system_bridge
systembridge==1.1.5 systembridge==2.0.6
# homeassistant.components.tellduslive # homeassistant.components.tellduslive
tellduslive==0.10.11 tellduslive==0.10.11

View File

@ -55,79 +55,24 @@ FIXTURE_ZEROCONF_BAD = {
}, },
} }
FIXTURE_OS = {
"platform": "linux",
"distro": "Ubuntu",
"release": "20.10",
"codename": "Groovy Gorilla",
"kernel": "5.8.0-44-generic",
"arch": "x64",
"hostname": "test-bridge",
"fqdn": "test-bridge.local",
"codepage": "UTF-8",
"logofile": "ubuntu",
"serial": "abcdefghijklmnopqrstuvwxyz",
"build": "",
"servicepack": "",
"uefi": True,
"users": [],
}
FIXTURE_INFORMATION = {
FIXTURE_NETWORK = { "address": "http://test-bridge:9170",
"connections": [], "apiPort": 9170,
"gatewayDefault": "192.168.1.1", "fqdn": "test-bridge",
"interfaceDefault": "wlp2s0", "host": "test-bridge",
"interfaces": { "ip": "1.1.1.1",
"wlp2s0": { "mac": FIXTURE_MAC_ADDRESS,
"iface": "wlp2s0", "updates": {
"ifaceName": "wlp2s0", "available": False,
"ip4": "1.1.1.1", "newer": False,
"mac": FIXTURE_MAC_ADDRESS, "url": "https://github.com/timmo001/system-bridge/releases/tag/v2.3.2",
}, "version": {"current": "2.3.2", "new": "2.3.2"},
},
"stats": {},
}
FIXTURE_SYSTEM = {
"baseboard": {
"manufacturer": "System manufacturer",
"model": "Model",
"version": "Rev X.0x",
"serial": "1234567",
"assetTag": "",
"memMax": 134217728,
"memSlots": 4,
},
"bios": {
"vendor": "System vendor",
"version": "12345",
"releaseDate": "2019-11-13",
"revision": "",
},
"chassis": {
"manufacturer": "Default string",
"model": "",
"type": "Desktop",
"version": "Default string",
"serial": "Default string",
"assetTag": "",
"sku": "",
},
"system": {
"manufacturer": "System manufacturer",
"model": "System Product Name",
"version": "System Version",
"serial": "System Serial Number",
"uuid": "abc123-def456",
"sku": "SKU",
"virtual": False,
},
"uuid": {
"os": FIXTURE_UUID,
"hardware": "abc123-def456",
"macs": [FIXTURE_MAC_ADDRESS],
}, },
"uuid": FIXTURE_UUID,
"version": "2.3.2",
"websocketAddress": "ws://test-bridge:9172",
"websocketPort": 9172,
} }
@ -151,9 +96,11 @@ async def test_user_flow(
assert result["type"] == data_entry_flow.RESULT_TYPE_FORM assert result["type"] == data_entry_flow.RESULT_TYPE_FORM
assert result["errors"] is None assert result["errors"] is None
aioclient_mock.get(f"{FIXTURE_BASE_URL}/os", json=FIXTURE_OS) aioclient_mock.get(
aioclient_mock.get(f"{FIXTURE_BASE_URL}/network", json=FIXTURE_NETWORK) f"{FIXTURE_BASE_URL}/information",
aioclient_mock.get(f"{FIXTURE_BASE_URL}/system", json=FIXTURE_SYSTEM) headers={"Content-Type": "application/json"},
json=FIXTURE_INFORMATION,
)
with patch( with patch(
"homeassistant.components.system_bridge.async_setup_entry", "homeassistant.components.system_bridge.async_setup_entry",
@ -181,9 +128,9 @@ async def test_form_invalid_auth(
assert result["type"] == data_entry_flow.RESULT_TYPE_FORM assert result["type"] == data_entry_flow.RESULT_TYPE_FORM
assert result["errors"] is None assert result["errors"] is None
aioclient_mock.get(f"{FIXTURE_BASE_URL}/os", exc=BridgeAuthenticationException) aioclient_mock.get(
aioclient_mock.get(f"{FIXTURE_BASE_URL}/network", exc=BridgeAuthenticationException) f"{FIXTURE_BASE_URL}/information", exc=BridgeAuthenticationException
aioclient_mock.get(f"{FIXTURE_BASE_URL}/system", exc=BridgeAuthenticationException) )
result2 = await hass.config_entries.flow.async_configure( result2 = await hass.config_entries.flow.async_configure(
result["flow_id"], FIXTURE_USER_INPUT result["flow_id"], FIXTURE_USER_INPUT
@ -206,9 +153,7 @@ async def test_form_cannot_connect(
assert result["type"] == data_entry_flow.RESULT_TYPE_FORM assert result["type"] == data_entry_flow.RESULT_TYPE_FORM
assert result["errors"] is None assert result["errors"] is None
aioclient_mock.get(f"{FIXTURE_BASE_URL}/os", exc=ClientConnectionError) aioclient_mock.get(f"{FIXTURE_BASE_URL}/information", exc=ClientConnectionError)
aioclient_mock.get(f"{FIXTURE_BASE_URL}/network", exc=ClientConnectionError)
aioclient_mock.get(f"{FIXTURE_BASE_URL}/system", exc=ClientConnectionError)
result2 = await hass.config_entries.flow.async_configure( result2 = await hass.config_entries.flow.async_configure(
result["flow_id"], FIXTURE_USER_INPUT result["flow_id"], FIXTURE_USER_INPUT
@ -220,7 +165,7 @@ async def test_form_cannot_connect(
assert result2["errors"] == {"base": "cannot_connect"} assert result2["errors"] == {"base": "cannot_connect"}
async def test_form_unknow_error( async def test_form_unknown_error(
hass, aiohttp_client, aioclient_mock, current_request_with_host hass, aiohttp_client, aioclient_mock, current_request_with_host
) -> None: ) -> None:
"""Test we handle unknown error.""" """Test we handle unknown error."""
@ -232,10 +177,9 @@ async def test_form_unknow_error(
assert result["errors"] is None assert result["errors"] is None
with patch( with patch(
"homeassistant.components.system_bridge.config_flow.Bridge.async_get_os", "homeassistant.components.system_bridge.config_flow.Bridge.async_get_information",
side_effect=Exception("Boom"), side_effect=Exception("Boom"),
): ):
result2 = await hass.config_entries.flow.async_configure( result2 = await hass.config_entries.flow.async_configure(
result["flow_id"], FIXTURE_USER_INPUT result["flow_id"], FIXTURE_USER_INPUT
) )
@ -257,9 +201,9 @@ async def test_reauth_authorization_error(
assert result["type"] == data_entry_flow.RESULT_TYPE_FORM assert result["type"] == data_entry_flow.RESULT_TYPE_FORM
assert result["step_id"] == "authenticate" assert result["step_id"] == "authenticate"
aioclient_mock.get(f"{FIXTURE_BASE_URL}/network", exc=BridgeAuthenticationException) aioclient_mock.get(
aioclient_mock.get(f"{FIXTURE_BASE_URL}/os", exc=BridgeAuthenticationException) f"{FIXTURE_BASE_URL}/information", exc=BridgeAuthenticationException
aioclient_mock.get(f"{FIXTURE_BASE_URL}/system", exc=BridgeAuthenticationException) )
result2 = await hass.config_entries.flow.async_configure( result2 = await hass.config_entries.flow.async_configure(
result["flow_id"], FIXTURE_AUTH_INPUT result["flow_id"], FIXTURE_AUTH_INPUT
@ -282,9 +226,7 @@ async def test_reauth_connection_error(
assert result["type"] == data_entry_flow.RESULT_TYPE_FORM assert result["type"] == data_entry_flow.RESULT_TYPE_FORM
assert result["step_id"] == "authenticate" assert result["step_id"] == "authenticate"
aioclient_mock.get(f"{FIXTURE_BASE_URL}/os", exc=ClientConnectionError) aioclient_mock.get(f"{FIXTURE_BASE_URL}/information", exc=ClientConnectionError)
aioclient_mock.get(f"{FIXTURE_BASE_URL}/network", exc=ClientConnectionError)
aioclient_mock.get(f"{FIXTURE_BASE_URL}/system", exc=ClientConnectionError)
result2 = await hass.config_entries.flow.async_configure( result2 = await hass.config_entries.flow.async_configure(
result["flow_id"], FIXTURE_AUTH_INPUT result["flow_id"], FIXTURE_AUTH_INPUT
@ -312,9 +254,11 @@ async def test_reauth_flow(
assert result["type"] == data_entry_flow.RESULT_TYPE_FORM assert result["type"] == data_entry_flow.RESULT_TYPE_FORM
assert result["step_id"] == "authenticate" assert result["step_id"] == "authenticate"
aioclient_mock.get(f"{FIXTURE_BASE_URL}/os", json=FIXTURE_OS) aioclient_mock.get(
aioclient_mock.get(f"{FIXTURE_BASE_URL}/network", json=FIXTURE_NETWORK) f"{FIXTURE_BASE_URL}/information",
aioclient_mock.get(f"{FIXTURE_BASE_URL}/system", json=FIXTURE_SYSTEM) headers={"Content-Type": "application/json"},
json=FIXTURE_INFORMATION,
)
with patch( with patch(
"homeassistant.components.system_bridge.async_setup_entry", "homeassistant.components.system_bridge.async_setup_entry",
@ -345,9 +289,11 @@ async def test_zeroconf_flow(
assert result["type"] == data_entry_flow.RESULT_TYPE_FORM assert result["type"] == data_entry_flow.RESULT_TYPE_FORM
assert not result["errors"] assert not result["errors"]
aioclient_mock.get(f"{FIXTURE_ZEROCONF_BASE_URL}/os", json=FIXTURE_OS) aioclient_mock.get(
aioclient_mock.get(f"{FIXTURE_ZEROCONF_BASE_URL}/network", json=FIXTURE_NETWORK) f"{FIXTURE_ZEROCONF_BASE_URL}/information",
aioclient_mock.get(f"{FIXTURE_ZEROCONF_BASE_URL}/system", json=FIXTURE_SYSTEM) headers={"Content-Type": "application/json"},
json=FIXTURE_INFORMATION,
)
with patch( with patch(
"homeassistant.components.system_bridge.async_setup_entry", "homeassistant.components.system_bridge.async_setup_entry",
@ -378,11 +324,9 @@ async def test_zeroconf_cannot_connect(
assert result["type"] == data_entry_flow.RESULT_TYPE_FORM assert result["type"] == data_entry_flow.RESULT_TYPE_FORM
assert not result["errors"] assert not result["errors"]
aioclient_mock.get(f"{FIXTURE_ZEROCONF_BASE_URL}/os", exc=ClientConnectionError)
aioclient_mock.get( aioclient_mock.get(
f"{FIXTURE_ZEROCONF_BASE_URL}/network", exc=ClientConnectionError f"{FIXTURE_ZEROCONF_BASE_URL}/information", exc=ClientConnectionError
) )
aioclient_mock.get(f"{FIXTURE_ZEROCONF_BASE_URL}/system", exc=ClientConnectionError)
result2 = await hass.config_entries.flow.async_configure( result2 = await hass.config_entries.flow.async_configure(
result["flow_id"], FIXTURE_AUTH_INPUT result["flow_id"], FIXTURE_AUTH_INPUT