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/syslog/notify.py
homeassistant/components/system_bridge/__init__.py
homeassistant/components/system_bridge/const.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/systemmonitor/sensor.py
homeassistant/components/tado/*

View file

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

View file

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

View file

@ -8,8 +8,6 @@ import async_timeout
from systembridge import Bridge
from systembridge.client import BridgeClient
from systembridge.exceptions import BridgeAuthenticationException
from systembridge.objects.os import Os
from systembridge.objects.system import System
import voluptuous as vol
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]
try:
async with async_timeout.timeout(30):
bridge_os: Os = await bridge.async_get_os()
if bridge_os.hostname is not None:
hostname = bridge_os.hostname
bridge_system: System = await bridge.async_get_system()
await bridge.async_get_information()
if (
bridge.information is not None
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:
_LOGGER.info(exception)
raise InvalidAuth from exception
@ -58,7 +60,7 @@ async def validate_input(hass: HomeAssistant, data: dict[str, Any]) -> dict[str,
_LOGGER.info(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):

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",
"config_flow": true,
"documentation": "https://www.home-assistant.io/integrations/system_bridge",
"requirements": ["systembridge==1.1.5"],
"requirements": ["systembridge==2.0.6"],
"codeowners": ["@timmo001"],
"zeroconf": ["_system-bridge._udp.local."],
"after_dependencies": ["zeroconf"],
"quality_scale": "silver",
"iot_class": "local_polling"
"iot_class": "local_push"
}

View file

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

View file

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

View file

@ -1232,7 +1232,7 @@ sunwatcher==0.2.1
surepy==0.7.0
# homeassistant.components.system_bridge
systembridge==1.1.5
systembridge==2.0.6
# homeassistant.components.tellduslive
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_NETWORK = {
"connections": [],
"gatewayDefault": "192.168.1.1",
"interfaceDefault": "wlp2s0",
"interfaces": {
"wlp2s0": {
"iface": "wlp2s0",
"ifaceName": "wlp2s0",
"ip4": "1.1.1.1",
"mac": FIXTURE_MAC_ADDRESS,
},
},
"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],
FIXTURE_INFORMATION = {
"address": "http://test-bridge:9170",
"apiPort": 9170,
"fqdn": "test-bridge",
"host": "test-bridge",
"ip": "1.1.1.1",
"mac": FIXTURE_MAC_ADDRESS,
"updates": {
"available": False,
"newer": False,
"url": "https://github.com/timmo001/system-bridge/releases/tag/v2.3.2",
"version": {"current": "2.3.2", "new": "2.3.2"},
},
"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["errors"] is None
aioclient_mock.get(f"{FIXTURE_BASE_URL}/os", json=FIXTURE_OS)
aioclient_mock.get(f"{FIXTURE_BASE_URL}/network", json=FIXTURE_NETWORK)
aioclient_mock.get(f"{FIXTURE_BASE_URL}/system", json=FIXTURE_SYSTEM)
aioclient_mock.get(
f"{FIXTURE_BASE_URL}/information",
headers={"Content-Type": "application/json"},
json=FIXTURE_INFORMATION,
)
with patch(
"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["errors"] is None
aioclient_mock.get(f"{FIXTURE_BASE_URL}/os", exc=BridgeAuthenticationException)
aioclient_mock.get(f"{FIXTURE_BASE_URL}/network", exc=BridgeAuthenticationException)
aioclient_mock.get(f"{FIXTURE_BASE_URL}/system", exc=BridgeAuthenticationException)
aioclient_mock.get(
f"{FIXTURE_BASE_URL}/information", exc=BridgeAuthenticationException
)
result2 = await hass.config_entries.flow.async_configure(
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["errors"] is None
aioclient_mock.get(f"{FIXTURE_BASE_URL}/os", exc=ClientConnectionError)
aioclient_mock.get(f"{FIXTURE_BASE_URL}/network", exc=ClientConnectionError)
aioclient_mock.get(f"{FIXTURE_BASE_URL}/system", exc=ClientConnectionError)
aioclient_mock.get(f"{FIXTURE_BASE_URL}/information", exc=ClientConnectionError)
result2 = await hass.config_entries.flow.async_configure(
result["flow_id"], FIXTURE_USER_INPUT
@ -220,7 +165,7 @@ async def test_form_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
) -> None:
"""Test we handle unknown error."""
@ -232,10 +177,9 @@ async def test_form_unknow_error(
assert result["errors"] is None
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"),
):
result2 = await hass.config_entries.flow.async_configure(
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["step_id"] == "authenticate"
aioclient_mock.get(f"{FIXTURE_BASE_URL}/network", exc=BridgeAuthenticationException)
aioclient_mock.get(f"{FIXTURE_BASE_URL}/os", exc=BridgeAuthenticationException)
aioclient_mock.get(f"{FIXTURE_BASE_URL}/system", exc=BridgeAuthenticationException)
aioclient_mock.get(
f"{FIXTURE_BASE_URL}/information", exc=BridgeAuthenticationException
)
result2 = await hass.config_entries.flow.async_configure(
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["step_id"] == "authenticate"
aioclient_mock.get(f"{FIXTURE_BASE_URL}/os", exc=ClientConnectionError)
aioclient_mock.get(f"{FIXTURE_BASE_URL}/network", exc=ClientConnectionError)
aioclient_mock.get(f"{FIXTURE_BASE_URL}/system", exc=ClientConnectionError)
aioclient_mock.get(f"{FIXTURE_BASE_URL}/information", exc=ClientConnectionError)
result2 = await hass.config_entries.flow.async_configure(
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["step_id"] == "authenticate"
aioclient_mock.get(f"{FIXTURE_BASE_URL}/os", json=FIXTURE_OS)
aioclient_mock.get(f"{FIXTURE_BASE_URL}/network", json=FIXTURE_NETWORK)
aioclient_mock.get(f"{FIXTURE_BASE_URL}/system", json=FIXTURE_SYSTEM)
aioclient_mock.get(
f"{FIXTURE_BASE_URL}/information",
headers={"Content-Type": "application/json"},
json=FIXTURE_INFORMATION,
)
with patch(
"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 not result["errors"]
aioclient_mock.get(f"{FIXTURE_ZEROCONF_BASE_URL}/os", json=FIXTURE_OS)
aioclient_mock.get(f"{FIXTURE_ZEROCONF_BASE_URL}/network", json=FIXTURE_NETWORK)
aioclient_mock.get(f"{FIXTURE_ZEROCONF_BASE_URL}/system", json=FIXTURE_SYSTEM)
aioclient_mock.get(
f"{FIXTURE_ZEROCONF_BASE_URL}/information",
headers={"Content-Type": "application/json"},
json=FIXTURE_INFORMATION,
)
with patch(
"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 not result["errors"]
aioclient_mock.get(f"{FIXTURE_ZEROCONF_BASE_URL}/os", exc=ClientConnectionError)
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(
result["flow_id"], FIXTURE_AUTH_INPUT