diff --git a/backend/systembridgebackend/__main__.py b/backend/systembridgebackend/__main__.py index e25afd5bb..9023a92b6 100644 --- a/backend/systembridgebackend/__main__.py +++ b/backend/systembridgebackend/__main__.py @@ -8,10 +8,10 @@ from systembridgeshared.logger import setup_logger from systembridgeshared.settings import Settings -from systembridgebackend.autostart import autostart_disable, autostart_enable -from systembridgebackend.modules.system import System -from systembridgebackend.server import Server -from systembridgebackend.shortcut import create_shortcuts +from .autostart import autostart_disable, autostart_enable +from .modules.system import System +from .server import Server +from .shortcut import create_shortcuts class Main(Base): diff --git a/backend/systembridgebackend/autostart/__init__.py b/backend/systembridgebackend/autostart/__init__.py index 52d2326e9..27227aa62 100644 --- a/backend/systembridgebackend/autostart/__init__.py +++ b/backend/systembridgebackend/autostart/__init__.py @@ -6,12 +6,12 @@ def autostart_disable(): """Disable autostart""" if "Windows" in platform.system(): # pylint: disable=import-error, import-outside-toplevel - from systembridgebackend.autostart.windows import autostart_windows_disable + from .windows import autostart_windows_disable autostart_windows_disable() elif "Linux" in platform.system(): # pylint: disable=import-error, import-outside-toplevel - from systembridgebackend.autostart.linux import autostart_linux_disable + from .linux import autostart_linux_disable autostart_linux_disable() @@ -20,11 +20,11 @@ def autostart_enable(): """Enable autostart""" if "Windows" in platform.system(): # pylint: disable=import-error, import-outside-toplevel - from systembridgebackend.autostart.windows import autostart_windows_enable + from .windows import autostart_windows_enable autostart_windows_enable() elif "Linux" in platform.system(): # pylint: disable=import-error, import-outside-toplevel - from systembridgebackend.autostart.linux import autostart_linux_enable + from .linux import autostart_linux_enable autostart_linux_enable() diff --git a/backend/systembridgebackend/data.py b/backend/systembridgebackend/data.py index b6d79ccf1..3aeba5f8a 100644 --- a/backend/systembridgebackend/data.py +++ b/backend/systembridgebackend/data.py @@ -6,7 +6,7 @@ from systembridgeshared.base import Base from systembridgeshared.database import Database -from systembridgebackend.modules.update import Update +from .modules.update import Update class UpdateThread(Thread): diff --git a/backend/systembridgebackend/modules/base.py b/backend/systembridgebackend/modules/base.py index 732611b93..345a97689 100644 --- a/backend/systembridgebackend/modules/base.py +++ b/backend/systembridgebackend/modules/base.py @@ -1,6 +1,6 @@ """System Bridge: Module Base""" + from systembridgeshared.base import Base -from systembridgeshared.const import COLUMN_KEY, COLUMN_TIMESTAMP, COLUMN_VALUE from systembridgeshared.database import Database @@ -10,19 +10,10 @@ class ModuleUpdateBase(Base): def __init__( self, database: Database, - table: str, ): super().__init__() self._database = database - self._database.create_table( - table, - [ - (COLUMN_KEY, "TEXT PRIMARY KEY"), - (COLUMN_VALUE, "TEXT"), - (COLUMN_TIMESTAMP, "DOUBLE"), - ], - ) async def update_all_data(self) -> None: """Update data""" diff --git a/backend/systembridgebackend/modules/battery/update.py b/backend/systembridgebackend/modules/battery/update.py index d8dac332c..3bc4497d2 100644 --- a/backend/systembridgebackend/modules/battery/update.py +++ b/backend/systembridgebackend/modules/battery/update.py @@ -3,9 +3,10 @@ from systembridgeshared.common import camel_to_snake from systembridgeshared.database import Database +from systembridgeshared.models.database_data import Battery as DatabaseModel -from systembridgebackend.modules.base import ModuleUpdateBase -from systembridgebackend.modules.battery import Battery +from . import Battery +from ..base import ModuleUpdateBase class BatteryUpdate(ModuleUpdateBase): @@ -16,7 +17,7 @@ def __init__( database: Database, ) -> None: """Initialize""" - super().__init__(database, "battery") + super().__init__(database) self._battery = Battery() async def update_sensors(self) -> None: @@ -26,12 +27,24 @@ async def update_sensors(self) -> None: # From status if key == "percent": continue - self._database.write("battery", f"sensors_{key}", value) + self._database.update_data( + DatabaseModel, + DatabaseModel( + key="sensors_{key}", + value=value, + ), + ) async def update_status(self) -> None: """Update Battery Status""" for key, value in self._battery.status().items(): - self._database.write("battery", camel_to_snake(key), value) + self._database.update_data( + DatabaseModel, + DatabaseModel( + key=camel_to_snake(key), + value=value, + ), + ) async def update_all_data(self) -> None: """Update data""" diff --git a/backend/systembridgebackend/modules/bridge/__init__.py b/backend/systembridgebackend/modules/bridge/__init__.py deleted file mode 100644 index a9882c685..000000000 --- a/backend/systembridgebackend/modules/bridge/__init__.py +++ /dev/null @@ -1,23 +0,0 @@ -"""System Bridge: Bridge""" -from collections.abc import Callable - -from systembridgeshared.base import Base -from zeroconf import InterfaceChoice, ServiceBrowser, Zeroconf - - -class Bridge(Base): - """Bridge""" - - def __init__( - self, - callback: Callable, - ): - super().__init__() - ServiceBrowser( - Zeroconf( - interfaces=InterfaceChoice.All, - unicast=True, - ), - "_system-bridge._tcp.local.", - handlers=[callback], - ) diff --git a/backend/systembridgebackend/modules/bridge/update.py b/backend/systembridgebackend/modules/bridge/update.py deleted file mode 100644 index 69c28d11d..000000000 --- a/backend/systembridgebackend/modules/bridge/update.py +++ /dev/null @@ -1,149 +0,0 @@ -"""System Bridge: Update Bridge""" -import time - -from systembridgeshared.base import Base -from systembridgeshared.const import COLUMN_TIMESTAMP -from systembridgeshared.database import Database -from zeroconf import ServiceStateChange, Zeroconf - -from systembridgebackend.modules.bridge import Bridge - -COLUMN_NAME = "name" -COLUMN_ADDRESS = "address" -COLUMN_FQDN = "fqdn" -COLUMN_HOST = "host" -COLUMN_IP = "ip" -COLUMN_MAC = "mac" -COLUMN_PORT = "port" -COLUMN_UUID = "uuid" -COLUMN_VERSION = "version" -COLUMN_WEBSOCKET_ADDRESS = "websocket_address" -COLUMN_WEBSOCKET_PORT = "websocket_port" -COLUMN_ACTIVE = "active" -COLUMN_LAST_ACTIVE_TIMESTAMP = "last_active_timestamp" - - -class BridgeUpdate(Base): - """Bridge Update""" - - def __init__( - self, - database: Database, - ) -> None: - """Initialize""" - super().__init__() - - self._database = database - self._database.create_table( - "bridge", - [ - (COLUMN_UUID, "TEXT PRIMARY KEY"), - (COLUMN_NAME, "TEXT"), - (COLUMN_ADDRESS, "TEXT"), - (COLUMN_FQDN, "TEXT"), - (COLUMN_HOST, "TEXT"), - (COLUMN_IP, "TEXT"), - (COLUMN_MAC, "TEXT"), - (COLUMN_PORT, "TEXT"), - (COLUMN_VERSION, "TEXT"), - (COLUMN_WEBSOCKET_ADDRESS, "TEXT"), - (COLUMN_WEBSOCKET_PORT, "TEXT"), - (COLUMN_ACTIVE, "INTEGER"), - (COLUMN_LAST_ACTIVE_TIMESTAMP, "INTEGER"), - (COLUMN_TIMESTAMP, "DOUBLE"), - ], - ) - self._bridge = Bridge(self.service_changed) - - def service_changed( - self, - zeroconf: Zeroconf, - service_type: str, - name: str, - state_change: ServiceStateChange, - ) -> None: - """Service changed""" - self._logger.info( - "service_changed: type=%s name=%s state_change=%s", - service_type, - name, - state_change, - ) - service_info = zeroconf.get_service_info(service_type, name) - self._logger.info( - "Service %s %s: %s - %s", name, state_change, service_type, service_info - ) - if service_info and service_info.properties: - if state_change in (ServiceStateChange.Added, ServiceStateChange.Updated): - self.write_bridge(name, service_info.properties) - elif state_change == ServiceStateChange.Removed: - self.deactivate_bridge(name, service_info.properties) - - def deactivate_bridge( - self, - name: str, - info: dict, - ) -> None: - """Deactivate bridge""" - self._logger.info("Deactivate bridge %s", name) - self._database.execute_sql( - f"""UPDATE bridge SET {COLUMN_ACTIVE} = 0, {COLUMN_TIMESTAMP} = {time.time()} - WHERE {COLUMN_UUID}={info[b"uuid"].decode("utf-8")}""".replace( - "\n", "" - ).replace( - " ", "" - ), - ) - - def write_bridge( - self, - name: str, - info: dict, - ) -> None: - """Write bridge""" - values = { - COLUMN_UUID: info[b"uuid"].decode("utf-8"), - COLUMN_NAME: name, - COLUMN_ADDRESS: info[b"address"].decode("utf-8"), - COLUMN_FQDN: info[b"fqdn"].decode("utf-8"), - COLUMN_HOST: info[b"host"].decode("utf-8"), - COLUMN_IP: info[b"ip"].decode("utf-8"), - COLUMN_MAC: info[b"mac"].decode("utf-8"), - COLUMN_PORT: info[b"port"].decode("utf-8"), - COLUMN_VERSION: info[b"version"].decode("utf-8"), - COLUMN_WEBSOCKET_ADDRESS: info[b"websocketAddress"].decode("utf-8"), - COLUMN_WEBSOCKET_PORT: info[b"wsPort"].decode("utf-8"), - COLUMN_ACTIVE: 1, - COLUMN_LAST_ACTIVE_TIMESTAMP: time.time(), - COLUMN_TIMESTAMP: time.time(), - } - - self._database.execute_sql( - f"""INSERT INTO bridge ({COLUMN_UUID}, {COLUMN_NAME}, {COLUMN_ADDRESS}, {COLUMN_FQDN}, - {COLUMN_HOST}, {COLUMN_IP}, {COLUMN_MAC}, {COLUMN_PORT}, {COLUMN_VERSION}, - {COLUMN_WEBSOCKET_ADDRESS}, {COLUMN_WEBSOCKET_PORT}, {COLUMN_ACTIVE}, - {COLUMN_LAST_ACTIVE_TIMESTAMP}, {COLUMN_TIMESTAMP}) - VALUES("{values[COLUMN_UUID]}", "{values[COLUMN_NAME]}", "{values[COLUMN_ADDRESS]}", - "{values[COLUMN_FQDN]}", "{values[COLUMN_HOST]}", "{values[COLUMN_IP]}", - "{values[COLUMN_MAC]}", "{values[COLUMN_PORT]}", "{values[COLUMN_VERSION]}", - "{values[COLUMN_WEBSOCKET_ADDRESS]}", "{values[COLUMN_WEBSOCKET_PORT]}", - {values[COLUMN_ACTIVE]}, {values[COLUMN_LAST_ACTIVE_TIMESTAMP]}, - {values[COLUMN_TIMESTAMP]}) - ON CONFLICT({COLUMN_UUID}) DO - UPDATE SET {COLUMN_ADDRESS} = "{values[COLUMN_ADDRESS]}", - {COLUMN_FQDN} = "{values[COLUMN_FQDN]}", {COLUMN_HOST} = "{values[COLUMN_HOST]}", - {COLUMN_IP} = "{values[COLUMN_IP]}", {COLUMN_MAC} = "{values[COLUMN_MAC]}", - {COLUMN_PORT} = "{values[COLUMN_PORT]}", - {COLUMN_VERSION} = "{values[COLUMN_VERSION]}", - {COLUMN_WEBSOCKET_ADDRESS} = "{values[COLUMN_WEBSOCKET_ADDRESS]}", - {COLUMN_WEBSOCKET_PORT} = "{values[COLUMN_WEBSOCKET_PORT]}", - {COLUMN_ACTIVE} = {values[COLUMN_ACTIVE]}, - {COLUMN_LAST_ACTIVE_TIMESTAMP} = {values[COLUMN_LAST_ACTIVE_TIMESTAMP]}, - {COLUMN_TIMESTAMP} = {values[COLUMN_TIMESTAMP]} - WHERE {COLUMN_UUID} = "{values[COLUMN_UUID]}" - """.replace( - "\n", "" - ).replace( - " ", "" - ) - ) diff --git a/backend/systembridgebackend/modules/cpu/__init__.py b/backend/systembridgebackend/modules/cpu/__init__.py index 8f4c4b693..9ca4eb75b 100644 --- a/backend/systembridgebackend/modules/cpu/__init__.py +++ b/backend/systembridgebackend/modules/cpu/__init__.py @@ -14,13 +14,10 @@ ) from psutil._common import pcputimes, scpufreq, scpustats from systembridgeshared.base import Base -from systembridgeshared.const import ( - COLUMN_HARDWARE_TYPE, - COLUMN_KEY, - COLUMN_TYPE, - COLUMN_VALUE, -) from systembridgeshared.database import Database +from systembridgeshared.models.database_data_sensors import ( + Sensors as SensorsDatabaseModel, +) class CPU(Base): @@ -53,23 +50,23 @@ def temperature( database: Database, ) -> Optional[float]: """CPU temperature""" - for item in database.read_table("sensors").to_dict(orient="records"): - if item[COLUMN_HARDWARE_TYPE] is not None and ( + for item in database.get_data(SensorsDatabaseModel): + if item.hardware_type is not None and ( ( - "cpu" in item[COLUMN_HARDWARE_TYPE].lower() - and "temperature" in item[COLUMN_TYPE].lower() + "cpu" in item.hardware_type.lower() + and "temperature" in item.type.lower() ) or ( - "k10temp" in item[COLUMN_HARDWARE_TYPE].lower() - and "current" in item[COLUMN_TYPE].lower() + "k10temp" in item.hardware_type.lower() + and "current" in item.type.lower() ) ): self._logger.debug( "Found CPU temperature: %s = %s", - item[COLUMN_KEY], - item[COLUMN_VALUE], + item.key, + item.value, ) - return item[COLUMN_VALUE] + return item.value return None def times(self) -> pcputimes: @@ -105,14 +102,12 @@ def voltage( database: Database, ) -> Optional[float]: """CPU voltage""" - for item in database.read_table("sensors").to_dict(orient="records"): + for item in database.get_data(SensorsDatabaseModel): if ( - item[COLUMN_HARDWARE_TYPE] is not None - and "cpu" in item[COLUMN_HARDWARE_TYPE].lower() - and "voltage" in item[COLUMN_TYPE].lower() + item.hardware_type is not None + and "cpu" in item.hardware_type.lower() + and "voltage" in item.type.lower() ): - self._logger.debug( - "Found CPU voltage: %s = %s", item[COLUMN_KEY], item[COLUMN_VALUE] - ) - return item[COLUMN_VALUE] + self._logger.debug("Found CPU voltage: %s = %s", item.key, item.value) + return item.value return None diff --git a/backend/systembridgebackend/modules/cpu/update.py b/backend/systembridgebackend/modules/cpu/update.py index f73ef6ec9..0618d4314 100644 --- a/backend/systembridgebackend/modules/cpu/update.py +++ b/backend/systembridgebackend/modules/cpu/update.py @@ -2,9 +2,10 @@ import asyncio from systembridgeshared.database import Database +from systembridgeshared.models.database_data import CPU as DatabaseModel -from systembridgebackend.modules.base import ModuleUpdateBase -from systembridgebackend.modules.cpu import CPU +from . import CPU +from ..base import ModuleUpdateBase class CPUUpdate(ModuleUpdateBase): @@ -15,59 +16,111 @@ def __init__( database: Database, ) -> None: """Initialize""" - super().__init__(database, "cpu") + super().__init__(database) self._cpu = CPU() async def update_count(self) -> None: """Update CPU count""" - self._database.write("cpu", "count", self._cpu.count()) + self._database.update_data( + DatabaseModel, + DatabaseModel( + key="count", + value=str(self._cpu.count()), + ), + ) async def update_frequency(self) -> None: """Update CPU frequency""" for key, value in self._cpu.freq()._asdict().items(): - self._database.write("cpu", f"frequency_{key}", value) + self._database.update_data( + DatabaseModel, + DatabaseModel( + key=f"frequency_{key}", + value=value, + ), + ) async def update_frequency_per_cpu(self) -> None: """Update CPU frequency per CPU""" count = 0 for data in [freq._asdict() for freq in self._cpu.freq_per_cpu()]: for key, value in data.items(): - self._database.write("cpu", f"frequency_{count}_{key}", value) + self._database.update_data( + DatabaseModel, + DatabaseModel( + key=f"frequency_{count}_{key}", + value=value, + ), + ) count += 1 async def update_load_average(self) -> None: """Update load average""" avg_tuple = self._cpu.load_average() result = sum([avg_tuple[0], avg_tuple[1], avg_tuple[2]]) / 3 - self._database.write("cpu", "load_average", result) + self._database.update_data( + DatabaseModel, + DatabaseModel( + key="load_average", + value=str(result), + ), + ) async def update_stats(self) -> None: """Update stats""" for key, value in self._cpu.stats()._asdict().items(): - self._database.write("cpu", f"stats_{key}", value) + self._database.update_data( + DatabaseModel, + DatabaseModel( + key=f"stats_{key}", + value=value, + ), + ) async def update_temperature(self) -> None: """Update temperature""" - self._database.write( - "cpu", "temperature", self._cpu.temperature(self._database) + self._database.update_data( + DatabaseModel, + DatabaseModel( + key="temperature", + value=str(self._cpu.temperature(self._database)), + ), ) async def update_times(self) -> None: """Update times""" for key, value in self._cpu.times()._asdict().items(): - self._database.write("cpu", f"times_{key}", value) + self._database.update_data( + DatabaseModel, + DatabaseModel( + key=f"times_{key}", + value=value, + ), + ) async def update_times_percent(self) -> None: """Update times percent""" for key, value in self._cpu.times_percent()._asdict().items(): - self._database.write("cpu", f"times_percent_{key}", value) + self._database.update_data( + DatabaseModel, + DatabaseModel( + key=f"times_percent_{key}", + value=value, + ), + ) async def update_times_per_cpu(self) -> None: """Update times per CPU""" count = 0 for data in [freq._asdict() for freq in self._cpu.times_per_cpu()]: for key, value in data.items(): - self._database.write("cpu", f"times_per_cpu_{count}_{key}", value) + self._database.update_data( + DatabaseModel, + DatabaseModel( + key=f"times_per_cpu_{count}_{key}", + value=value, + ), + ) count += 1 async def update_times_per_cpu_percent(self) -> None: @@ -75,25 +128,47 @@ async def update_times_per_cpu_percent(self) -> None: count = 0 for data in [freq._asdict() for freq in self._cpu.times_per_cpu_percent()]: for key, value in data.items(): - self._database.write( - "cpu", f"times_per_cpu_percent_{count}_{key}", value + self._database.update_data( + DatabaseModel, + DatabaseModel( + key=f"times_per_cpu_percent_{count}_{key}", + value=value, + ), ) count += 1 async def update_usage(self) -> None: """Update usage""" - self._database.write("cpu", "usage", self._cpu.usage()) + self._database.update_data( + DatabaseModel, + DatabaseModel( + key="usage", + value=str(self._cpu.usage()), + ), + ) async def update_usage_per_cpu(self) -> None: """Update usage per CPU""" count = 0 for value in self._cpu.usage_per_cpu(): - self._database.write("cpu", f"usage_{count}", value) + self._database.update_data( + DatabaseModel, + DatabaseModel( + key=f"usage_{count}", + value=str(value), + ), + ) count += 1 async def update_voltage(self) -> None: """Update voltage""" - self._database.write("cpu", "voltage", self._cpu.voltage(self._database)) + self._database.update_data( + DatabaseModel, + DatabaseModel( + key="voltage", + value=str(self._cpu.voltage(self._database)), + ), + ) async def update_all_data(self) -> None: """Update data""" diff --git a/backend/systembridgebackend/modules/disk/update.py b/backend/systembridgebackend/modules/disk/update.py index 3d64aedca..b25b41428 100644 --- a/backend/systembridgebackend/modules/disk/update.py +++ b/backend/systembridgebackend/modules/disk/update.py @@ -1,10 +1,12 @@ """System Bridge: Update Disk""" import asyncio +from json import dumps from systembridgeshared.database import Database +from systembridgeshared.models.database_data import Disk as DatabaseModel -from systembridgebackend.modules.base import ModuleUpdateBase -from systembridgebackend.modules.disk import Disk +from . import Disk +from ..base import ModuleUpdateBase class DiskUpdate(ModuleUpdateBase): @@ -15,20 +17,30 @@ def __init__( database: Database, ) -> None: """Initialize""" - super().__init__(database, "disk") + super().__init__(database) self._disk = Disk() async def update_io_counters(self) -> None: """Update IO counters""" for key, value in self._disk.io_counters()._asdict().items(): - self._database.write("disk", f"io_counters_{key}", value) + self._database.update_data( + DatabaseModel, + DatabaseModel( + key=f"io_counters_{key}", + value=value, + ), + ) async def update_io_counters_per_disk(self) -> None: """Update IO counters per disk""" for key, value in self._disk.io_counters_per_disk().items(): # type: ignore for subkey, subvalue in value._asdict().items(): - self._database.write( - "disk", f"io_counters_per_disk_{key}_{subkey}", subvalue + self._database.update_data( + DatabaseModel, + DatabaseModel( + key=f"io_counters_per_disk_{key}_{subkey}", + value=subvalue, + ), ) async def update_partitions(self) -> None: @@ -40,21 +52,39 @@ async def update_partitions(self) -> None: device_list.append(partition.device) partition_list.append(partition.mountpoint) for key, value in partition._asdict().items(): - self._database.write( - "disk", - f"partitions_{partition.mountpoint}_{key}", - value, + self._database.update_data( + DatabaseModel, + DatabaseModel( + key=f"partitions_{partition.mountpoint}_{key}", + value=value, + ), ) - self._database.write("disk", "devices", device_list) - self._database.write("disk", "partitions", partition_list) + self._database.update_data( + DatabaseModel, + DatabaseModel( + key="devices", + value=dumps(device_list), + ), + ) + self._database.update_data( + DatabaseModel, + DatabaseModel( + key="partitions", + value=dumps(partition_list), + ), + ) async def update_usage(self) -> None: """Update usage""" for partition in self._disk.partitions(): if data := self._disk.usage(partition.mountpoint): for key, value in data._asdict().items(): - self._database.write( - "disk", f"usage_{partition.mountpoint}_{key}", value + self._database.update_data( + DatabaseModel, + DatabaseModel( + key=f"usage_{partition.mountpoint}_{key}", + value=value, + ), ) async def update_all_data(self) -> None: diff --git a/backend/systembridgebackend/modules/display/__init__.py b/backend/systembridgebackend/modules/display/__init__.py index adc2e4924..b6052e53e 100644 --- a/backend/systembridgebackend/modules/display/__init__.py +++ b/backend/systembridgebackend/modules/display/__init__.py @@ -5,14 +5,10 @@ from systembridgeshared.base import Base from systembridgeshared.common import make_key -from systembridgeshared.const import ( - COLUMN_HARDWARE_NAME, - COLUMN_HARDWARE_TYPE, - COLUMN_KEY, - COLUMN_NAME, - COLUMN_VALUE, -) from systembridgeshared.database import Database +from systembridgeshared.models.database_data_sensors import ( + Sensors as SensorsDatabaseModel, +) class Display(Base): @@ -24,14 +20,14 @@ def get_displays( ) -> list[str]: """Get Displays""" displays = [] - for item in database.read_table("sensors").to_dict(orient="records"): + for item in database.get_data(SensorsDatabaseModel): if ( - item[COLUMN_HARDWARE_TYPE] is not None - and "display" in item[COLUMN_HARDWARE_TYPE].lower() - and item[COLUMN_HARDWARE_NAME] is not None - and item[COLUMN_HARDWARE_NAME] not in displays + item.hardware_type is not None + and "display" in item.hardware_type.lower() + and item.hardware_name is not None + and item.hardware_name not in displays ): - displays.append(item[COLUMN_HARDWARE_NAME]) + displays.append(item.hardware_name) return displays def pixel_clock( @@ -40,20 +36,20 @@ def pixel_clock( display_key: str, ) -> Optional[float]: """Display pixel clock""" - for item in database.read_table("sensors").to_dict(orient="records"): + for item in database.get_data(SensorsDatabaseModel): if ( - item[COLUMN_HARDWARE_TYPE] is not None - and "display" in item[COLUMN_HARDWARE_TYPE].lower() - and "pixel" in item[COLUMN_NAME].lower() - and "clock" in item[COLUMN_NAME].lower() - and make_key(item[COLUMN_HARDWARE_NAME]) == display_key + item.hardware_type is not None + and "display" in item.hardware_type.lower() + and "pixel" in item.name.lower() + and "clock" in item.name.lower() + and make_key(item.hardware_name) == display_key ): self._logger.debug( "Found display pixel clock: %s = %s", - item[COLUMN_KEY], - item[COLUMN_VALUE], + item.key, + item.value, ) - return item[COLUMN_VALUE] + return item.value return None def refresh_rate( @@ -62,20 +58,20 @@ def refresh_rate( display_key: str, ) -> Optional[float]: """Display refresh rate""" - for item in database.read_table("sensors").to_dict(orient="records"): + for item in database.get_data(SensorsDatabaseModel): if ( - item[COLUMN_HARDWARE_TYPE] is not None - and "display" in item[COLUMN_HARDWARE_TYPE].lower() - and "refresh" in item[COLUMN_NAME].lower() - and "rate" in item[COLUMN_NAME].lower() - and make_key(item[COLUMN_HARDWARE_NAME]) == display_key + item.hardware_type is not None + and "display" in item.hardware_type.lower() + and "refresh" in item.name.lower() + and "rate" in item.name.lower() + and make_key(item.hardware_name) == display_key ): self._logger.debug( "Found display refresh rate: %s = %s", - item[COLUMN_KEY], - item[COLUMN_VALUE], + item.key, + item.value, ) - return item[COLUMN_VALUE] + return item.value return None def resolution_horizontal( @@ -84,20 +80,20 @@ def resolution_horizontal( display_key: str, ) -> Optional[int]: """Display resolution horizontal""" - for item in database.read_table("sensors").to_dict(orient="records"): + for item in database.get_data(SensorsDatabaseModel): if ( - item[COLUMN_HARDWARE_TYPE] is not None - and "display" in item[COLUMN_HARDWARE_TYPE].lower() - and "resolution" in item[COLUMN_NAME].lower() - and "horizontal" in item[COLUMN_NAME].lower() - and make_key(item[COLUMN_HARDWARE_NAME]) == display_key + item.hardware_type is not None + and "display" in item.hardware_type.lower() + and "resolution" in item.name.lower() + and "horizontal" in item.name.lower() + and make_key(item.hardware_name) == display_key ): self._logger.debug( "Found display resolution horizontal: %s = %s", - item[COLUMN_KEY], - item[COLUMN_VALUE], + item.key, + item.value, ) - return item[COLUMN_VALUE] + return item.value return None def resolution_vertical( @@ -106,18 +102,18 @@ def resolution_vertical( display_key: str, ) -> Optional[int]: """Display resolution vertical""" - for item in database.read_table("sensors").to_dict(orient="records"): + for item in database.get_data(SensorsDatabaseModel): if ( - item[COLUMN_HARDWARE_TYPE] is not None - and "display" in item[COLUMN_HARDWARE_TYPE].lower() - and "resolution" in item[COLUMN_NAME].lower() - and "vertical" in item[COLUMN_NAME].lower() - and make_key(item[COLUMN_HARDWARE_NAME]) == display_key + item.hardware_type is not None + and "display" in item.hardware_type.lower() + and "resolution" in item.name.lower() + and "vertical" in item.name.lower() + and make_key(item.hardware_name) == display_key ): self._logger.debug( "Found display resolution vertical: %s = %s", - item[COLUMN_KEY], - item[COLUMN_VALUE], + item.key, + item.value, ) - return item[COLUMN_VALUE] + return item.value return None diff --git a/backend/systembridgebackend/modules/display/update.py b/backend/systembridgebackend/modules/display/update.py index b4dbb0373..e5d2c3de4 100644 --- a/backend/systembridgebackend/modules/display/update.py +++ b/backend/systembridgebackend/modules/display/update.py @@ -1,11 +1,13 @@ """System Bridge: Update Display""" import asyncio +from json import dumps from systembridgeshared.common import make_key from systembridgeshared.database import Database +from systembridgeshared.models.database_data import Display as DatabaseModel -from systembridgebackend.modules.base import ModuleUpdateBase -from systembridgebackend.modules.display import Display +from . import Display +from ..base import ModuleUpdateBase class DisplayUpdate(ModuleUpdateBase): @@ -16,7 +18,7 @@ def __init__( database: Database, ) -> None: """Initialize""" - super().__init__(database, "display") + super().__init__(database) self._display = Display() async def update_name( @@ -25,17 +27,26 @@ async def update_name( display_name: str, ) -> None: """Update name""" - self._database.write("display", f"{display_key}_name", display_name) + self._database.update_data( + DatabaseModel, + DatabaseModel( + key=f"{display_key}_name", + value=display_name, + ), + ) async def update_pixel_clock( self, display_key: str, ) -> None: """Update pixel clock""" - self._database.write( - "display", - f"{display_key}_pixel_clock", - self._display.pixel_clock(self._database, display_key), + value = self._display.pixel_clock(self._database, display_key) + self._database.update_data( + DatabaseModel, + DatabaseModel( + key=f"{display_key}_pixel_clock", + value=str(value) if value is not None else None, + ), ) async def update_refresh_rate( @@ -43,10 +54,13 @@ async def update_refresh_rate( display_key: str, ) -> None: """Update refresh rate""" - self._database.write( - "display", - f"{display_key}_refresh_rate", - self._display.refresh_rate(self._database, display_key), + value = self._display.refresh_rate(self._database, display_key) + self._database.update_data( + DatabaseModel, + DatabaseModel( + key=f"{display_key}_refresh_rate", + value=str(value) if value is not None else None, + ), ) async def update_resolution_horizontal( @@ -54,10 +68,13 @@ async def update_resolution_horizontal( display_key: str, ) -> None: """Update resolution horizontal""" - self._database.write( - "display", - f"{display_key}_resolution_horizontal", - self._display.resolution_horizontal(self._database, display_key), + value = self._display.resolution_horizontal(self._database, display_key) + self._database.update_data( + DatabaseModel, + DatabaseModel( + key=f"{display_key}_resolution_horizontal", + value=str(value) if value is not None else None, + ), ) async def update_resolution_vertical( @@ -65,17 +82,20 @@ async def update_resolution_vertical( display_key: str, ) -> None: """Update resolution vertical""" - self._database.write( - "display", - f"{display_key}_resolution_vertical", - self._display.resolution_vertical(self._database, display_key), + value = self._display.resolution_vertical(self._database, display_key) + self._database.update_data( + DatabaseModel, + DatabaseModel( + key=f"{display_key}_resolution_vertical", + value=str(value) if value is not None else None, + ), ) async def update_all_data(self) -> None: """Update data""" # Clear table in case of hardware changes since last run - self._database.clear_table("display") + self._database.clear_table(DatabaseModel) display_list = [] for display_name in self._display.get_displays(self._database): @@ -90,4 +110,10 @@ async def update_all_data(self) -> None: self.update_resolution_vertical(display_key), ] ) - self._database.write("display", "displays", display_list) + self._database.update_data( + DatabaseModel, + DatabaseModel( + key="displays", + value=dumps(display_list), + ), + ) diff --git a/backend/systembridgebackend/modules/gpu/__init__.py b/backend/systembridgebackend/modules/gpu/__init__.py index ec129070b..add64e3ff 100644 --- a/backend/systembridgebackend/modules/gpu/__init__.py +++ b/backend/systembridgebackend/modules/gpu/__init__.py @@ -5,15 +5,10 @@ from systembridgeshared.base import Base from systembridgeshared.common import make_key -from systembridgeshared.const import ( - COLUMN_HARDWARE_NAME, - COLUMN_HARDWARE_TYPE, - COLUMN_KEY, - COLUMN_NAME, - COLUMN_TYPE, - COLUMN_VALUE, -) from systembridgeshared.database import Database +from systembridgeshared.models.database_data_sensors import ( + Sensors as SensorsDatabaseModel, +) class GPU(Base): @@ -25,13 +20,13 @@ def get_gpus( ) -> list[str]: """Get GPUs""" gpus = [] - for item in database.read_table("sensors").to_dict(orient="records"): + for item in database.get_data(SensorsDatabaseModel): if ( - item[COLUMN_HARDWARE_TYPE] is not None - and "gpu" in item[COLUMN_HARDWARE_TYPE].lower() - and item[COLUMN_HARDWARE_NAME] not in gpus + item.hardware_type is not None + and "gpu" in item.hardware_type.lower() + and item.hardware_name not in gpus ): - gpus.append(item[COLUMN_HARDWARE_NAME]) + gpus.append(item.hardware_name) return gpus def core_clock( @@ -40,20 +35,20 @@ def core_clock( gpu_key: str, ) -> Optional[float]: """GPU core clock""" - for item in database.read_table("sensors").to_dict(orient="records"): + for item in database.get_data(SensorsDatabaseModel): if ( - item[COLUMN_HARDWARE_TYPE] is not None - and "gpu" in item[COLUMN_HARDWARE_TYPE].lower() - and "clock" in item[COLUMN_TYPE].lower() - and "core" in item[COLUMN_NAME].lower() - and make_key(item[COLUMN_HARDWARE_NAME]) == gpu_key + item.hardware_type is not None + and "gpu" in item.hardware_type.lower() + and "clock" in item.type.lower() + and "core" in item.name.lower() + and make_key(item.hardware_name) == gpu_key ): self._logger.debug( "Found GPU core clock: %s = %s", - item[COLUMN_KEY], - item[COLUMN_VALUE], + item.key, + item.value, ) - return item[COLUMN_VALUE] + return item.value return None def core_load( @@ -62,18 +57,16 @@ def core_load( gpu_key: str, ) -> Optional[float]: """GPU core load""" - for item in database.read_table("sensors").to_dict(orient="records"): + for item in database.get_data(SensorsDatabaseModel): if ( - item[COLUMN_HARDWARE_TYPE] is not None - and "gpu" in item[COLUMN_HARDWARE_TYPE].lower() - and "load" in item[COLUMN_TYPE].lower() - and "core" in item[COLUMN_NAME].lower() - and make_key(item[COLUMN_HARDWARE_NAME]) == gpu_key + item.hardware_type is not None + and "gpu" in item.hardware_type.lower() + and "load" in item.type.lower() + and "core" in item.name.lower() + and make_key(item.hardware_name) == gpu_key ): - self._logger.debug( - "Found GPU core load: %s = %s", item[COLUMN_KEY], item[COLUMN_VALUE] - ) - return item[COLUMN_VALUE] + self._logger.debug("Found GPU core load: %s = %s", item.key, item.value) + return item.value return None def fan_speed( @@ -82,17 +75,15 @@ def fan_speed( gpu_key: str, ) -> Optional[float]: """GPU fan speed""" - for item in database.read_table("sensors").to_dict(orient="records"): + for item in database.get_data(SensorsDatabaseModel): if ( - item[COLUMN_HARDWARE_TYPE] is not None - and "gpu" in item[COLUMN_HARDWARE_TYPE].lower() - and "fan" in item[COLUMN_TYPE].lower() - and make_key(item[COLUMN_HARDWARE_NAME]) == gpu_key + item.hardware_type is not None + and "gpu" in item.hardware_type.lower() + and "fan" in item.type.lower() + and make_key(item.hardware_name) == gpu_key ): - self._logger.debug( - "Found GPU fan speed: %s = %s", item[COLUMN_KEY], item[COLUMN_VALUE] - ) - return item[COLUMN_VALUE] + self._logger.debug("Found GPU fan speed: %s = %s", item.key, item.value) + return item.value return None def memory_clock( @@ -101,20 +92,20 @@ def memory_clock( gpu_key: str, ) -> Optional[float]: """GPU memory clock""" - for item in database.read_table("sensors").to_dict(orient="records"): + for item in database.get_data(SensorsDatabaseModel): if ( - item[COLUMN_HARDWARE_TYPE] is not None - and "gpu" in item[COLUMN_HARDWARE_TYPE].lower() - and "clock" in item[COLUMN_TYPE].lower() - and "memory" in item[COLUMN_NAME].lower() - and make_key(item[COLUMN_HARDWARE_NAME]) == gpu_key + item.hardware_type is not None + and "gpu" in item.hardware_type.lower() + and "clock" in item.type.lower() + and "memory" in item.name.lower() + and make_key(item.hardware_name) == gpu_key ): self._logger.debug( "Found GPU memory clock: %s = %s", - item[COLUMN_KEY], - item[COLUMN_VALUE], + item.key, + item.value, ) - return item[COLUMN_VALUE] + return item.value return None def memory_load( @@ -123,20 +114,20 @@ def memory_load( gpu_key: str, ) -> Optional[float]: """GPU memory load""" - for item in database.read_table("sensors").to_dict(orient="records"): + for item in database.get_data(SensorsDatabaseModel): if ( - item[COLUMN_HARDWARE_TYPE] is not None - and "gpu" in item[COLUMN_HARDWARE_TYPE].lower() - and "load" in item[COLUMN_TYPE].lower() - and "memory" in item[COLUMN_NAME].lower() - and make_key(item[COLUMN_HARDWARE_NAME]) == gpu_key + item.hardware_type is not None + and "gpu" in item.hardware_type.lower() + and "load" in item.type.lower() + and "memory" in item.name.lower() + and make_key(item.hardware_name) == gpu_key ): self._logger.debug( "Found GPU memory load: %s = %s", - item[COLUMN_KEY], - item[COLUMN_VALUE], + item.key, + item.value, ) - return item[COLUMN_VALUE] + return item.value return None def memory_free( @@ -145,20 +136,20 @@ def memory_free( gpu_key: str, ) -> Optional[float]: """GPU memory free""" - for item in database.read_table("sensors").to_dict(orient="records"): + for item in database.get_data(SensorsDatabaseModel): if ( - item[COLUMN_HARDWARE_TYPE] is not None - and "gpu" in item[COLUMN_HARDWARE_TYPE].lower() - and "memory" in item[COLUMN_NAME].lower() - and "free" in item[COLUMN_NAME].lower() - and make_key(item[COLUMN_HARDWARE_NAME]) == gpu_key + item.hardware_type is not None + and "gpu" in item.hardware_type.lower() + and "memory" in item.name.lower() + and "free" in item.name.lower() + and make_key(item.hardware_name) == gpu_key ): self._logger.debug( "Found GPU memory free: %s = %s", - item[COLUMN_KEY], - item[COLUMN_VALUE], + item.key, + item.value, ) - return item[COLUMN_VALUE] + return item.value return None def memory_used( @@ -167,20 +158,20 @@ def memory_used( gpu_key: str, ) -> Optional[float]: """GPU memory used""" - for item in database.read_table("sensors").to_dict(orient="records"): + for item in database.get_data(SensorsDatabaseModel): if ( - item[COLUMN_HARDWARE_TYPE] is not None - and "gpu" in item[COLUMN_HARDWARE_TYPE].lower() - and "memory" in item[COLUMN_NAME].lower() - and "used" in item[COLUMN_NAME].lower() - and make_key(item[COLUMN_HARDWARE_NAME]) == gpu_key + item.hardware_type is not None + and "gpu" in item.hardware_type.lower() + and "memory" in item.name.lower() + and "used" in item.name.lower() + and make_key(item.hardware_name) == gpu_key ): self._logger.debug( "Found GPU memory used: %s = %s", - item[COLUMN_KEY], - item[COLUMN_VALUE], + item.key, + item.value, ) - return item[COLUMN_VALUE] + return item.value return None def memory_total( @@ -189,20 +180,20 @@ def memory_total( gpu_key: str, ) -> Optional[float]: """GPU memory total""" - for item in database.read_table("sensors").to_dict(orient="records"): + for item in database.get_data(SensorsDatabaseModel): if ( - item[COLUMN_HARDWARE_TYPE] is not None - and "gpu" in item[COLUMN_HARDWARE_TYPE].lower() - and "memory" in item[COLUMN_NAME].lower() - and "total" in item[COLUMN_NAME].lower() - and make_key(item[COLUMN_HARDWARE_NAME]) == gpu_key + item.hardware_type is not None + and "gpu" in item.hardware_type.lower() + and "memory" in item.name.lower() + and "total" in item.name.lower() + and make_key(item.hardware_name) == gpu_key ): self._logger.debug( "Found GPU memory total: %s = %s", - item[COLUMN_KEY], - item[COLUMN_VALUE], + item.key, + item.value, ) - return item[COLUMN_VALUE] + return item.value return None def power( @@ -211,17 +202,15 @@ def power( gpu_key: str, ) -> Optional[float]: """GPU power usage""" - for item in database.read_table("sensors").to_dict(orient="records"): + for item in database.get_data(SensorsDatabaseModel): if ( - item[COLUMN_HARDWARE_TYPE] is not None - and "gpu" in item[COLUMN_HARDWARE_TYPE].lower() - and "power" in item[COLUMN_TYPE].lower() - and make_key(item[COLUMN_HARDWARE_NAME]) == gpu_key + item.hardware_type is not None + and "gpu" in item.hardware_type.lower() + and "power" in item.type.lower() + and make_key(item.hardware_name) == gpu_key ): - self._logger.debug( - "Found GPU power: %s = %s", item[COLUMN_KEY], item[COLUMN_VALUE] - ) - return item[COLUMN_VALUE] + self._logger.debug("Found GPU power: %s = %s", item.key, item.value) + return item.value return None def temperature( @@ -230,18 +219,18 @@ def temperature( gpu_key: str, ) -> Optional[float]: """GPU temperature""" - for item in database.read_table("sensors").to_dict(orient="records"): + for item in database.get_data(SensorsDatabaseModel): if ( - item[COLUMN_HARDWARE_TYPE] is not None - and "gpu" in item[COLUMN_HARDWARE_TYPE].lower() - and "temperature" in item[COLUMN_TYPE].lower() - and "core" in item[COLUMN_NAME].lower() - and make_key(item[COLUMN_HARDWARE_NAME]) == gpu_key + item.hardware_type is not None + and "gpu" in item.hardware_type.lower() + and "temperature" in item.type.lower() + and "core" in item.name.lower() + and make_key(item.hardware_name) == gpu_key ): self._logger.debug( "Found GPU temperature: %s = %s", - item[COLUMN_KEY], - item[COLUMN_VALUE], + item.key, + item.value, ) - return item[COLUMN_VALUE] + return item.value return None diff --git a/backend/systembridgebackend/modules/gpu/update.py b/backend/systembridgebackend/modules/gpu/update.py index d427b34ee..d152b1001 100644 --- a/backend/systembridgebackend/modules/gpu/update.py +++ b/backend/systembridgebackend/modules/gpu/update.py @@ -1,11 +1,13 @@ """System Bridge: Update GPU""" import asyncio +from json import dumps from systembridgeshared.common import make_key from systembridgeshared.database import Database +from systembridgeshared.models.database_data import GPU as DatabaseModel -from systembridgebackend.modules.base import ModuleUpdateBase -from systembridgebackend.modules.gpu import GPU +from . import GPU +from ..base import ModuleUpdateBase class GPUUpdate(ModuleUpdateBase): @@ -16,7 +18,7 @@ def __init__( database: Database, ) -> None: """Initialize""" - super().__init__(database, "gpu") + super().__init__(database) self._gpu = GPU() async def update_name( @@ -25,17 +27,26 @@ async def update_name( gpu_name: str, ) -> None: """Update name""" - self._database.write("gpu", f"{gpu_key}_name", gpu_name) + self._database.update_data( + DatabaseModel, + DatabaseModel( + key=f"{gpu_key}_name", + value=gpu_name, + ), + ) async def update_core_clock( self, gpu_key: str, ) -> None: """Update core clock""" - self._database.write( - "gpu", - f"{gpu_key}_core_clock", - self._gpu.core_clock(self._database, gpu_key), + value = self._gpu.core_clock(self._database, gpu_key) + self._database.update_data( + DatabaseModel, + DatabaseModel( + key=f"{gpu_key}_core_clock", + value=str(value) if value else None, + ), ) async def update_core_load( @@ -43,8 +54,13 @@ async def update_core_load( gpu_key: str, ) -> None: """Update core load""" - self._database.write( - "gpu", f"{gpu_key}_core_load", self._gpu.core_load(self._database, gpu_key) + value = self._gpu.core_load(self._database, gpu_key) + self._database.update_data( + DatabaseModel, + DatabaseModel( + key=f"{gpu_key}_core_load", + value=str(value) if value else None, + ), ) async def update_fan_speed( @@ -52,8 +68,13 @@ async def update_fan_speed( gpu_key: str, ) -> None: """Update fan speed""" - self._database.write( - "gpu", f"{gpu_key}_fan_speed", self._gpu.fan_speed(self._database, gpu_key) + value = self._gpu.fan_speed(self._database, gpu_key) + self._database.update_data( + DatabaseModel, + DatabaseModel( + key=f"{gpu_key}_fan_speed", + value=str(value) if value else None, + ), ) async def update_memory_clock( @@ -61,10 +82,13 @@ async def update_memory_clock( gpu_key: str, ) -> None: """Update memory clock""" - self._database.write( - "gpu", - f"{gpu_key}_memory_clock", - self._gpu.memory_clock(self._database, gpu_key), + value = self._gpu.memory_clock(self._database, gpu_key) + self._database.update_data( + DatabaseModel, + DatabaseModel( + key=f"{gpu_key}_memory_clock", + value=str(value) if value else None, + ), ) async def update_memory_load( @@ -72,10 +96,13 @@ async def update_memory_load( gpu_key: str, ) -> None: """Update memory load""" - self._database.write( - "gpu", - f"{gpu_key}_memory_load", - self._gpu.memory_load(self._database, gpu_key), + value = self._gpu.memory_load(self._database, gpu_key) + self._database.update_data( + DatabaseModel, + DatabaseModel( + key=f"{gpu_key}_memory_load", + value=str(value) if value else None, + ), ) async def update_memory_free( @@ -83,10 +110,13 @@ async def update_memory_free( gpu_key: str, ) -> None: """Update memory free""" - self._database.write( - "gpu", - f"{gpu_key}_memory_free", - self._gpu.memory_free(self._database, gpu_key), + value = self._gpu.memory_free(self._database, gpu_key) + self._database.update_data( + DatabaseModel, + DatabaseModel( + key=f"{gpu_key}_memory_free", + value=str(value) if value else None, + ), ) async def update_memory_used( @@ -94,10 +124,13 @@ async def update_memory_used( gpu_key: str, ) -> None: """Update memory used""" - self._database.write( - "gpu", - f"{gpu_key}_memory_used", - self._gpu.memory_used(self._database, gpu_key), + value = self._gpu.memory_used(self._database, gpu_key) + self._database.update_data( + DatabaseModel, + DatabaseModel( + key=f"{gpu_key}_memory_used", + value=str(value) if value else None, + ), ) async def update_memory_total( @@ -105,10 +138,13 @@ async def update_memory_total( gpu_key: str, ) -> None: """Update memory total""" - self._database.write( - "gpu", - f"{gpu_key}_memory_total", - self._gpu.memory_total(self._database, gpu_key), + value = self._gpu.memory_total(self._database, gpu_key) + self._database.update_data( + DatabaseModel, + DatabaseModel( + key=f"{gpu_key}_memory_total", + value=str(value) if value else None, + ), ) async def update_power( @@ -116,8 +152,13 @@ async def update_power( gpu_key: str, ) -> None: """Update power""" - self._database.write( - "gpu", f"{gpu_key}_power", self._gpu.power(self._database, gpu_key) + value = self._gpu.power(self._database, gpu_key) + self._database.update_data( + DatabaseModel, + DatabaseModel( + key=f"{gpu_key}_power", + value=str(value) if value else None, + ), ) async def update_temperature( @@ -125,17 +166,20 @@ async def update_temperature( gpu_key: str, ) -> None: """Update temperature""" - self._database.write( - "gpu", - f"{gpu_key}_temperature", - self._gpu.temperature(self._database, gpu_key), + value = self._gpu.temperature(self._database, gpu_key) + self._database.update_data( + DatabaseModel, + DatabaseModel( + key=f"{gpu_key}_temperature", + value=str(value) if value else None, + ), ) async def update_all_data(self) -> None: """Update data""" # Clear table in case of hardware changes since last run - self._database.clear_table("gpu") + self._database.clear_table(DatabaseModel) gpu_list = [] for gpu_name in self._gpu.get_gpus(self._database): @@ -156,4 +200,10 @@ async def update_all_data(self) -> None: self.update_temperature(gpu_key), ] ) - self._database.write("gpu", "gpus", gpu_list) + self._database.update_data( + DatabaseModel, + DatabaseModel( + key="gpus", + value=dumps(gpu_list), + ), + ) diff --git a/backend/systembridgebackend/modules/listeners.py b/backend/systembridgebackend/modules/listeners.py index 70a6c4cfa..bc7e57235 100644 --- a/backend/systembridgebackend/modules/listeners.py +++ b/backend/systembridgebackend/modules/listeners.py @@ -1,12 +1,12 @@ """System Bridge: Module Listeners""" import asyncio -from collections.abc import Callable -from collections.abc import Awaitable +from collections.abc import Awaitable, Callable from systembridgeshared.base import Base from systembridgeshared.const import MODEL_MAP -from systembridgeshared.database import Database +from systembridgeshared.database import TABLE_MAP, Database +from systembridgeshared.models.data import DataDict class Listener: @@ -15,7 +15,7 @@ class Listener: def __init__( self, listener_id: str, - data_changed_callback: Callable[[str, dict], Awaitable[None]], + data_changed_callback: Callable[[str, DataDict], Awaitable[None]], modules: list[str], # pylint: disable=unsubscriptable-object ) -> None: """Initialize""" @@ -34,8 +34,8 @@ def __init__( ) -> None: """Initialize""" super().__init__() - self._database = database - self._implemented_modules = implemented_modules + self._database: Database = database + self._implemented_modules: list[str] = implemented_modules self._data: dict = {module: {} for module in self._implemented_modules} # pylint: disable=unsubscriptable-object self._registered_listeners: list[Listener] = [] @@ -43,7 +43,7 @@ def __init__( async def add_listener( self, listener_id: str, - data_changed_callback: Callable[[str, dict], Awaitable[None]], + data_changed_callback: Callable[[str, DataDict], Awaitable[None]], modules: list[str], # pylint: disable=unsubscriptable-object ) -> bool: """Add modules to listener""" @@ -62,9 +62,6 @@ async def add_listener( async def refresh_data(self) -> None: """Refresh data""" - if not self._database.connected: - self._database.connect() - # Get modules from registered listeners modules = [] for listener in self._registered_listeners: @@ -94,14 +91,14 @@ async def refresh_data_by_module( self._logger.warning("Unknown model: %s", module) return - new_data = self._database.table_data_to_ordered_dict(module) + new_data = self._database.get_data_dict(TABLE_MAP[module]) if new_data is None: self._logger.warning("No data found for module: %s", module) return if new_data != self._data[module]: self._logger.info("Data changed for module: %s", module) - self._data[module] = model(**new_data).dict() + self._data[module] = model(**new_data.dict()) for listener in self._registered_listeners: self._logger.info("Listener: %s - %s", listener.id, listener.modules) if module in listener.modules: diff --git a/backend/systembridgebackend/modules/memory/update.py b/backend/systembridgebackend/modules/memory/update.py index 599b699ab..03f69e3ef 100644 --- a/backend/systembridgebackend/modules/memory/update.py +++ b/backend/systembridgebackend/modules/memory/update.py @@ -1,9 +1,11 @@ """System Bridge: Update Memory""" import asyncio + from systembridgeshared.database import Database +from systembridgeshared.models.database_data import Memory as DatabaseModel -from systembridgebackend.modules.base import ModuleUpdateBase -from systembridgebackend.modules.memory import Memory +from . import Memory +from ..base import ModuleUpdateBase class MemoryUpdate(ModuleUpdateBase): @@ -14,18 +16,30 @@ def __init__( database: Database, ) -> None: """Initialize""" - super().__init__(database, "memory") + super().__init__(database) self._memory = Memory() async def update_swap(self) -> None: """Update Swap Memory""" for key, value in self._memory.swap()._asdict().items(): - self._database.write("memory", f"swap_{key}", value) + self._database.update_data( + DatabaseModel, + DatabaseModel( + key=f"swap_{key}", + value=value, + ), + ) async def update_virtual(self) -> None: """Update Virtual Memory""" for key, value in self._memory.virtual()._asdict().items(): - self._database.write("memory", f"virtual_{key}", value) + self._database.update_data( + DatabaseModel, + DatabaseModel( + key=f"virtual_{key}", + value=value, + ), + ) async def update_all_data(self) -> None: """Update data""" diff --git a/backend/systembridgebackend/modules/network/update.py b/backend/systembridgebackend/modules/network/update.py index 9db3bbe42..b7ce6a3cf 100644 --- a/backend/systembridgebackend/modules/network/update.py +++ b/backend/systembridgebackend/modules/network/update.py @@ -2,9 +2,10 @@ import asyncio from systembridgeshared.database import Database +from systembridgeshared.models.database_data import Network as DatabaseModel -from systembridgebackend.modules.base import ModuleUpdateBase -from systembridgebackend.modules.network import Network +from . import Network +from ..base import ModuleUpdateBase class NetworkUpdate(ModuleUpdateBase): @@ -15,21 +16,31 @@ def __init__( database: Database, ) -> None: """Initialize""" - super().__init__(database, "network") + super().__init__(database) self._network = Network() async def update_stats(self) -> None: """Update stats""" for key, value in self._network.stats().items(): for subkey, subvalue in value._asdict().items(): - self._database.write( - "network", f"stat_{key.replace(' ', '')}_{subkey}", subvalue + self._database.update_data( + DatabaseModel, + DatabaseModel( + key=f"stat_{key.replace(' ', '')}_{subkey}", + value=subvalue, + ), ) async def update_io_counters(self) -> None: """Update IO counters""" for key, value in self._network.io_counters()._asdict().items(): - self._database.write("network", f"io_counters_{key}", value) + self._database.update_data( + DatabaseModel, + DatabaseModel( + key=f"io_counters_{key}", + value=value, + ), + ) async def update_all_data(self) -> None: """Update data""" diff --git a/backend/systembridgebackend/modules/sensors/update.py b/backend/systembridgebackend/modules/sensors/update.py index 110152be5..e4ee9476d 100644 --- a/backend/systembridgebackend/modules/sensors/update.py +++ b/backend/systembridgebackend/modules/sensors/update.py @@ -1,23 +1,15 @@ """System Bridge: Update Sensors""" import asyncio -from systembridgeshared.base import Base from systembridgeshared.common import make_key -from systembridgeshared.const import ( - COLUMN_HARDWARE_NAME, - COLUMN_HARDWARE_TYPE, - COLUMN_KEY, - COLUMN_NAME, - COLUMN_TIMESTAMP, - COLUMN_TYPE, - COLUMN_VALUE, -) from systembridgeshared.database import Database +from systembridgeshared.models.database_data_sensors import Sensors as DatabaseModel -from systembridgebackend.modules.sensors import Sensors +from . import Sensors +from ..base import ModuleUpdateBase -class SensorsUpdate(Base): +class SensorsUpdate(ModuleUpdateBase): """Sensors Update""" def __init__( @@ -25,21 +17,7 @@ def __init__( database: Database, ) -> None: """Initialize""" - super().__init__() - - self._database = database - self._database.create_table( - "sensors", - [ - (COLUMN_KEY, "TEXT PRIMARY KEY"), - (COLUMN_TYPE, "TEXT"), - (COLUMN_NAME, "TEXT"), - (COLUMN_HARDWARE_TYPE, "TEXT"), - (COLUMN_HARDWARE_NAME, "TEXT"), - (COLUMN_VALUE, "TEXT"), - (COLUMN_TIMESTAMP, "DOUBLE"), - ], - ) + super().__init__(database) self._sensors = Sensors() async def update_fans(self) -> None: @@ -48,14 +26,16 @@ async def update_fans(self) -> None: for key, value in data.items(): for item in value: for subkey, subvalue in item._asdict().items(): - self._database.write_sensor( - "sensors", - f"fans_{key}_{subkey}", - subkey, - subkey, - key, - key, - subvalue, + self._database.update_data( + DatabaseModel, + DatabaseModel( + key=f"fans_{key}_{subkey}", + type=subkey, + name=subkey, + hardware_type=key, + hardware_name=key, + value=subvalue, + ), ) async def update_temperatures(self) -> None: @@ -64,14 +44,16 @@ async def update_temperatures(self) -> None: for key, value in data.items(): for item in value: for subkey, subvalue in item._asdict().items(): - self._database.write_sensor( - "sensors", - f"temperatures_{key}_{subkey}", - subkey, - subkey, - key, - key, - subvalue, + self._database.update_data( + DatabaseModel, + DatabaseModel( + key=f"temperatures_{key}_{subkey}", + type=subkey, + name=subkey, + hardware_type=key, + hardware_name=key, + value=subvalue, + ), ) async def update_windows_sensors(self) -> None: @@ -87,14 +69,16 @@ async def update_windows_sensors(self) -> None: key_sensor_name = make_key(sensor["name"]) key_sensor_type = make_key(sensor["type"]) - self._database.write_sensor( - "sensors", - f"windows_hardware{key_hardware}_{key_sensor_name}_{key_sensor_type}", - sensor["type"], - sensor["name"], - hardware["type"], - hardware["name"], - sensor["value"] if "value" in sensor else None, + self._database.update_data( + DatabaseModel, + DatabaseModel( + key=f"windows_hardware{key_hardware}_{key_sensor_name}_{key_sensor_type}", + type=sensor["type"], + name=sensor["name"], + hardware_type=hardware["type"], + hardware_name=hardware["name"], + value=sensor["value"] if "value" in sensor else None, + ), ) if "nvidia" in data and data["nvidia"] is not None: @@ -117,34 +101,41 @@ async def update_windows_sensors(self) -> None: f"Display {name_hardware.split('DISPLAY')[1]}" ) for subkey, subvalue in hardware.items(): - self._database.write_sensor( - "sensors", - f"windows_nvidia{key_hardware}_{sensor}_{counter}_{subkey}", - sensor, - subkey, - type_hardware, - name_hardware, - subvalue, + self._database.update_data( + DatabaseModel, + DatabaseModel( + key=f"windows_nvidia{key_hardware}_{sensor}_{counter}_{subkey}", + type=sensor, + name=subkey, + hardware_type=type_hardware, + hardware_name=name_hardware, + value=subvalue, + ), ) counter += 1 else: for subkey, subvalue in value.items(): - self._database.write_sensor( - "sensors", - f"windows_nvidia_{sensor}_{subkey}", - sensor, - subkey, - value["type"] if "type" in value else "NVIDIA", - value["name"] if "name" in value else "NVIDIA", - subvalue, + self._database.update_data( + DatabaseModel, + DatabaseModel( + key=f"windows_nvidia_{sensor}_{subkey}", + type=sensor, + name=subkey, + hardware_type=value["type"] + if "type" in value + else "NVIDIA", + hardware_name=value["name"] + if "name" in value + else "NVIDIA", + value=subvalue, + ), ) async def update_all_data(self) -> None: """Update data""" # Clear table in case of hardware changes since last run - self._database.clear_table("sensors") - + self._database.clear_table(DatabaseModel) await asyncio.gather( *[ self.update_fans(), diff --git a/backend/systembridgebackend/modules/system/__init__.py b/backend/systembridgebackend/modules/system/__init__.py index 5f64c9ea0..10b2ac410 100644 --- a/backend/systembridgebackend/modules/system/__init__.py +++ b/backend/systembridgebackend/modules/system/__init__.py @@ -21,8 +21,9 @@ from psutil._common import suser from systembridgeshared.base import Base from systembridgeshared.database import Database +from systembridgeshared.models.database_data import System as DatabaseModel -from systembridgebackend._version import __version__ +from ..._version import __version__ class System(Base): @@ -98,12 +99,16 @@ def version_newer_available( database: Database, ) -> Optional[bool]: """Check if newer version is available""" - version = database.read_table_by_key("system", "version").to_dict( - orient="records" - )[0]["value"] - latest_version = database.read_table_by_key("system", "version_latest").to_dict( - orient="records" - )[0]["value"] + version_record = database.get_data_item_by_key(DatabaseModel, "version") + if version_record is None: + return None + version = version_record.value + latest_version_record = database.get_data_item_by_key( + DatabaseModel, "version_latest" + ) + if latest_version_record is None: + return None + latest_version = latest_version_record.value if version is not None and latest_version is not None: return parse_version(latest_version) > parse_version(version) return None diff --git a/backend/systembridgebackend/modules/system/update.py b/backend/systembridgebackend/modules/system/update.py index b75aec110..6186c75db 100644 --- a/backend/systembridgebackend/modules/system/update.py +++ b/backend/systembridgebackend/modules/system/update.py @@ -2,9 +2,10 @@ import asyncio from systembridgeshared.database import Database +from systembridgeshared.models.database_data import System as DatabaseModel -from systembridgebackend.modules.base import ModuleUpdateBase -from systembridgebackend.modules.system import System +from . import System +from ..base import ModuleUpdateBase class SystemUpdate(ModuleUpdateBase): @@ -15,75 +16,144 @@ def __init__( database: Database, ) -> None: """Initialize""" - super().__init__(database, "system") + super().__init__(database) self._system = System() async def update_boot_time(self) -> None: """Update boot time""" - self._database.write("system", "boot_time", self._system.boot_time()) + self._database.update_data( + DatabaseModel, + DatabaseModel( + key="boot_time", + value=str(self._system.boot_time()), + ), + ) async def update_fqdn(self) -> None: """Update FQDN""" - self._database.write("system", "fqdn", self._system.fqdn()) + self._database.update_data( + DatabaseModel, + DatabaseModel( + key="fqdn", + value=self._system.fqdn(), + ), + ) async def update_hostname(self) -> None: """Update hostname""" - self._database.write("system", "hostname", self._system.hostname()) + self._database.update_data( + DatabaseModel, + DatabaseModel( + key="hostname", + value=self._system.hostname(), + ), + ) async def update_ip_address_4(self) -> None: """Update IP address 4""" - self._database.write("system", "ip_address_4", self._system.ip_address_4()) + self._database.update_data( + DatabaseModel, + DatabaseModel( + key="ip_address_4", + value=self._system.ip_address_4(), + ), + ) async def update_mac_address(self) -> None: """Update MAC address""" - self._database.write("system", "mac_address", self._system.mac_address()) + self._database.update_data( + DatabaseModel, + DatabaseModel( + key="mac_address", + value=self._system.mac_address(), + ), + ) async def update_platform(self) -> None: """Update platform""" - self._database.write("system", "platform", self._system.platform()) + self._database.update_data( + DatabaseModel, + DatabaseModel( + key="platform", + value=self._system.platform(), + ), + ) async def update_platform_version(self) -> None: """Update platform version""" - self._database.write( - "system", "platform_version", self._system.platform_version() + self._database.update_data( + DatabaseModel, + DatabaseModel( + key="platform_version", + value=self._system.platform_version(), + ), ) async def update_uptime(self) -> None: """Update uptime""" - self._database.write("system", "uptime", self._system.uptime()) + self._database.update_data( + DatabaseModel, + DatabaseModel( + key="uptime", + value=str(self._system.uptime()), + ), + ) async def update_users(self) -> None: """Update users""" for user in self._system.users(): for key, value in user._asdict().items(): - self._database.write( - "system", f"user_{user.name.replace(' ','_').lower()}_{key}", value + self._database.update_data( + DatabaseModel, + DatabaseModel( + key=f"user_{user.name.replace(' ','_').lower()}_{key}", + value=value, + ), ) async def update_uuid(self) -> None: """Update UUID""" - self._database.write("system", "uuid", self._system.uuid()) + self._database.update_data( + DatabaseModel, + DatabaseModel( + key="uuid", + value=self._system.uuid(), + ), + ) async def update_version(self) -> None: """Update version""" - self._database.write("system", "version", self._system.version()) + self._database.update_data( + DatabaseModel, + DatabaseModel( + key="version", + value=self._system.version(), + ), + ) async def update_version_latest(self) -> None: """Update latest version""" release = await self._system.version_latest() if release and release.tag_name: - self._database.write( - "system", - "version_latest", - release.tag_name.replace("v", "") if release is not None else None, + self._database.update_data( + DatabaseModel, + DatabaseModel( + key="version_latest", + value=release.tag_name.replace("v", "") + if release is not None + else None, + ), ) async def update_version_newer_available(self) -> None: """Update newer version available""" - self._database.write( - "system", - "version_newer_available", - self._system.version_newer_available(self._database), + value = self._system.version_newer_available(self._database) + self._database.update_data( + DatabaseModel, + DatabaseModel( + key="version_newer_available", + value=str(value) if value else str(False), + ), ) async def update_all_data(self) -> None: diff --git a/backend/systembridgebackend/modules/update.py b/backend/systembridgebackend/modules/update.py index 99a6bc101..1a6e1cf0e 100644 --- a/backend/systembridgebackend/modules/update.py +++ b/backend/systembridgebackend/modules/update.py @@ -1,21 +1,19 @@ """System Bridge: Modules Update""" import asyncio -from collections.abc import Callable -from collections.abc import Awaitable +from collections.abc import Awaitable, Callable from systembridgeshared.base import Base from systembridgeshared.database import Database -from systembridgebackend.modules.battery.update import BatteryUpdate -from systembridgebackend.modules.bridge.update import BridgeUpdate -from systembridgebackend.modules.cpu.update import CPUUpdate -from systembridgebackend.modules.disk.update import DiskUpdate -from systembridgebackend.modules.display.update import DisplayUpdate -from systembridgebackend.modules.gpu.update import GPUUpdate -from systembridgebackend.modules.memory.update import MemoryUpdate -from systembridgebackend.modules.network.update import NetworkUpdate -from systembridgebackend.modules.sensors.update import SensorsUpdate -from systembridgebackend.modules.system.update import SystemUpdate +from .battery.update import BatteryUpdate +from .cpu.update import CPUUpdate +from .disk.update import DiskUpdate +from .display.update import DisplayUpdate +from .gpu.update import GPUUpdate +from .memory.update import MemoryUpdate +from .network.update import NetworkUpdate +from .sensors.update import SensorsUpdate +from .system.update import SystemUpdate class Update(Base): @@ -41,7 +39,6 @@ def __init__( {"name": "memory", "cls": MemoryUpdate(self._database)}, {"name": "network", "cls": NetworkUpdate(self._database)}, ] - BridgeUpdate(self._database) async def _update( self, @@ -58,8 +55,6 @@ async def update_data( ) -> None: """Update Data""" self._logger.info("Update data") - if not self._database.connected: - self._database.connect() tasks = [self._update(cls, updated_callback) for cls in self._classes] await asyncio.gather(*tasks) @@ -72,8 +67,6 @@ async def update_frequent_data( ) -> None: """Update Data""" self._logger.info("Update frequent data") - if not self._database.connected: - self._database.connect() sensors_update = SensorsUpdate(self._database) await sensors_update.update_all_data() diff --git a/backend/systembridgebackend/server/__init__.py b/backend/systembridgebackend/server/__init__.py index 0e47382ab..205de4212 100644 --- a/backend/systembridgebackend/server/__init__.py +++ b/backend/systembridgebackend/server/__init__.py @@ -11,25 +11,26 @@ from sanic.response import HTTPResponse, json from sanic_scheduler import SanicScheduler, task from systembridgeshared.base import Base +from systembridgeshared.common import convert_string_to_correct_type from systembridgeshared.const import ( QUERY_API_KEY, SECRET_API_KEY, SETTING_LOG_LEVEL, SETTING_PORT_API, ) -from systembridgeshared.database import Database +from systembridgeshared.database import TABLE_MAP, Database from systembridgeshared.models.media_play import MediaPlay from systembridgeshared.models.notification import Notification from systembridgeshared.settings import Settings -from systembridgebackend.data import Data -from systembridgebackend.gui import GUIAttemptsExceededException, start_gui_threaded -from systembridgebackend.modules.listeners import Listeners -from systembridgebackend.server.auth import ApiKeyAuthentication -from systembridgebackend.server.cors import add_cors_headers -from systembridgebackend.server.keyboard import handler_keyboard -from systembridgebackend.server.mdns import MDNSAdvertisement -from systembridgebackend.server.media import ( +from ..data import Data +from ..gui import GUIAttemptsExceededException, start_gui_threaded +from ..modules.listeners import Listeners +from ..server.auth import ApiKeyAuthentication +from ..server.cors import add_cors_headers +from ..server.keyboard import handler_keyboard +from ..server.mdns import MDNSAdvertisement +from ..server.media import ( handler_media_directories, handler_media_file, handler_media_file_data, @@ -37,10 +38,10 @@ handler_media_files, handler_media_play, ) -from systembridgebackend.server.notification import handler_notification -from systembridgebackend.server.open import handler_open -from systembridgebackend.server.options import setup_options -from systembridgebackend.server.power import ( +from ..server.notification import handler_notification +from ..server.open import handler_open +from ..server.options import setup_options +from ..server.power import ( handler_hibernate, handler_lock, handler_logout, @@ -48,8 +49,8 @@ handler_shutdown, handler_sleep, ) -from systembridgebackend.server.update import handler_update -from systembridgebackend.server.websocket import WebSocketHandler +from ..server.update import handler_update +from ..server.websocket import WebSocketHandler class ApplicationExitException(BaseException): @@ -125,9 +126,10 @@ async def _handler_data_all( table: str, ) -> HTTPResponse: """Data handler all""" - if table not in implemented_modules: + table_module = TABLE_MAP.get(table) + if table not in implemented_modules or table_module is None: return json({"message": f"Data module {table} not found"}, status=404) - return json(self._database.table_data_to_ordered_dict(table)) + return json(self._database.get_data_dict(table_module).dict()) @auth.key_required async def _handler_data_by_key( @@ -136,16 +138,18 @@ async def _handler_data_by_key( key: str, ) -> HTTPResponse: """Data handler by key""" - if table not in implemented_modules: + table_module = TABLE_MAP.get(table) + if table not in implemented_modules or table_module is None: return json({"message": f"Data module {table} not found"}, status=404) - data = self._database.read_table_by_key(table, key).to_dict( - orient="records" - )[0] + data = self._database.get_data_item_by_key(table_module, key) + if data is None: + return json({"message": f"Data item {key} not found"}, status=404) + return json( { - data["key"]: data["value"], - "last_updated": data["timestamp"], + data.key: convert_string_to_correct_type(data.value), + "last_updated": data.timestamp, } ) diff --git a/backend/systembridgebackend/server/mdns.py b/backend/systembridgebackend/server/mdns.py index c78e6d717..96c109f67 100644 --- a/backend/systembridgebackend/server/mdns.py +++ b/backend/systembridgebackend/server/mdns.py @@ -3,7 +3,7 @@ from systembridgeshared.settings import SETTING_PORT_API, Settings from zeroconf import InterfaceChoice, ServiceInfo, Zeroconf -from systembridgebackend.modules.system import System +from ..modules.system import System ZEROCONF_TYPE = "_system-bridge._tcp.local." diff --git a/backend/systembridgebackend/server/options.py b/backend/systembridgebackend/server/options.py index a7dca4e5e..e252050ab 100644 --- a/backend/systembridgebackend/server/options.py +++ b/backend/systembridgebackend/server/options.py @@ -4,7 +4,7 @@ from sanic import Sanic, response from sanic_routing.router import Route -from systembridgebackend.server.cors import _add_cors_headers +from .cors import _add_cors_headers def _compile_routes_needing_options(routes: dict[str, Route]) -> dict[str, frozenset]: diff --git a/backend/systembridgebackend/server/websocket.py b/backend/systembridgebackend/server/websocket.py index e37ad624e..495543216 100644 --- a/backend/systembridgebackend/server/websocket.py +++ b/backend/systembridgebackend/server/websocket.py @@ -82,7 +82,8 @@ TYPE_UNREGISTER_DATA_LISTENER, TYPE_UPDATE_SETTING, ) -from systembridgeshared.database import Database +from systembridgeshared.database import TABLE_MAP, Database +from systembridgeshared.models.data import DataDict from systembridgeshared.models.get_data import GetData from systembridgeshared.models.get_setting import GetSetting from systembridgeshared.models.keyboard_key import KeyboardKey @@ -100,20 +101,13 @@ from systembridgeshared.settings import SECRET_API_KEY, Settings from systembridgeshared.update import Update -from systembridgebackend.autostart import autostart_disable, autostart_enable -from systembridgebackend.gui import start_gui_threaded -from systembridgebackend.modules.listeners import Listeners -from systembridgebackend.server.keyboard import keyboard_keypress, keyboard_text -from systembridgebackend.server.media import get_directories, get_file, get_files -from systembridgebackend.server.open import open_path, open_url -from systembridgebackend.server.power import ( - hibernate, - lock, - logout, - restart, - shutdown, - sleep, -) +from ..autostart import autostart_disable, autostart_enable +from ..gui import start_gui_threaded +from ..modules.listeners import Listeners +from ..server.keyboard import keyboard_keypress, keyboard_text +from ..server.media import get_directories, get_file, get_files +from ..server.open import open_path, open_url +from ..server.power import hibernate, lock, logout, restart, shutdown, sleep class WebSocketHandler(Base): @@ -152,7 +146,7 @@ async def _send_response( async def _data_changed( self, module: str, - data: dict, + data: DataDict, ) -> None: """Data changed""" if module not in self._implemented_modules: @@ -609,7 +603,8 @@ async def _handler( ) for module in model.modules: - data = self._database.table_data_to_ordered_dict(module) + table = TABLE_MAP.get(module) + data = self._database.get_data_dict(table) if data is not None: await self._send_response( Response( diff --git a/backend/systembridgebackend/shortcut/__init__.py b/backend/systembridgebackend/shortcut/__init__.py index de9ae8bb8..30c14d723 100644 --- a/backend/systembridgebackend/shortcut/__init__.py +++ b/backend/systembridgebackend/shortcut/__init__.py @@ -6,11 +6,11 @@ def create_shortcuts(): """Create shortcuts""" if "Windows" in platform.system(): # pylint: disable=import-error, import-outside-toplevel - from systembridgebackend.shortcut.windows import create_windows_shortcuts + from .windows import create_windows_shortcuts create_windows_shortcuts() elif "Linux" in platform.system(): # pylint: disable=import-error, import-outside-toplevel - from systembridgebackend.shortcut.linux import create_linux_shortcuts + from .linux import create_linux_shortcuts create_linux_shortcuts() diff --git a/cli/systembridgecli/__main__.py b/cli/systembridgecli/__main__.py index 601bd53d6..c7e4eebd8 100644 --- a/cli/systembridgecli/__main__.py +++ b/cli/systembridgecli/__main__.py @@ -4,17 +4,18 @@ import os import subprocess import sys -from uuid import uuid4 from typing import Optional +from uuid import uuid4 from systembridgeshared.common import get_user_data_directory -from systembridgeshared.const import SECRET_API_KEY, SETTING_PORT_API, TABLE_SETTINGS -from systembridgeshared.database import Database +from systembridgeshared.const import SECRET_API_KEY, SETTING_PORT_API +from systembridgeshared.database import TABLE_MAP, Database +from systembridgeshared.models.database_data import Settings as SettingsDatabaseModule from systembridgeshared.settings import Settings from tabulate import tabulate import typer -from systembridgecli._version import __version__ +from ._version import __version__ app = typer.Typer() database = Database() @@ -39,10 +40,14 @@ def api_port() -> None: @app.command(name="data", short_help="Get data") def data(module: str, key=None) -> None: """Get data""" + table_module = TABLE_MAP.get(module) if key: - output = database.read_table_by_key(module, key) + result = database.get_data_by_key(table_module, key) else: - output = database.read_table(module) + result = database.get_data(table_module) + + output = [item.dict() for item in result] + table_data = tabulate(output, headers="keys", tablefmt="psql") typer.secho(table_data, fg=typer.colors.GREEN) @@ -53,17 +58,18 @@ def data_value( key: str, ) -> None: """Get data value""" - output = database.read_table_by_key(module, key).to_dict(orient="records")[0][ - "value" - ] - typer.secho(output, fg=typer.colors.GREEN) + table_module = TABLE_MAP.get(module) + output = database.get_data_item_by_key(table_module, key) + typer.secho(output.value if output else None, fg=typer.colors.GREEN) @app.command(name="settings", short_help="Get all settings") def settings_all(): """Get all Settings""" table_data = tabulate( - database.read_table(TABLE_SETTINGS), headers="keys", tablefmt="psql" + [item.dict() for item in database.get_data(SettingsDatabaseModule)], + headers="keys", + tablefmt="psql", ) typer.secho(table_data, fg=typer.colors.CYAN) diff --git a/connector/systembridgeconnector/const.py b/connector/systembridgeconnector/const.py index bed41d877..2c6d16bd0 100644 --- a/connector/systembridgeconnector/const.py +++ b/connector/systembridgeconnector/const.py @@ -1,24 +1,23 @@ """System Bridge Connector: Constants""" -from systembridgeconnector.models.battery import Battery -from systembridgeconnector.models.bridge import Bridge -from systembridgeconnector.models.cpu import Cpu -from systembridgeconnector.models.data import Data -from systembridgeconnector.models.disk import Disk -from systembridgeconnector.models.display import Display -from systembridgeconnector.models.generic import Generic -from systembridgeconnector.models.gpu import Gpu -from systembridgeconnector.models.keyboard_key import KeyboardKey -from systembridgeconnector.models.keyboard_text import KeyboardText -from systembridgeconnector.models.media_directories import MediaDirectories -from systembridgeconnector.models.media_files import File, MediaFiles -from systembridgeconnector.models.memory import Memory -from systembridgeconnector.models.network import Network -from systembridgeconnector.models.notification import Notification -from systembridgeconnector.models.open_path import OpenPath -from systembridgeconnector.models.open_url import OpenUrl -from systembridgeconnector.models.response import Response -from systembridgeconnector.models.sensors import Sensors -from systembridgeconnector.models.system import System +from .models.battery import Battery +from .models.cpu import Cpu +from .models.data import Data +from .models.disk import Disk +from .models.display import Display +from .models.generic import Generic +from .models.gpu import Gpu +from .models.keyboard_key import KeyboardKey +from .models.keyboard_text import KeyboardText +from .models.media_directories import MediaDirectories +from .models.media_files import File, MediaFiles +from .models.memory import Memory +from .models.network import Network +from .models.notification import Notification +from .models.open_path import OpenPath +from .models.open_url import OpenUrl +from .models.response import Response +from .models.sensors import Sensors +from .models.system import System # Settings SETTING_ADDITIONAL_MEDIA_DIRECTORIES = "additional_media_directories" @@ -138,7 +137,6 @@ # Model MODEL_BATTERY = "battery" -MODEL_BRIDGE = "bridge" MODEL_CPU = "cpu" MODEL_DATA = "data" MODEL_DISK = "disk" @@ -148,20 +146,21 @@ MODEL_KEYBOARD_KEY = "keyboard_key" MODEL_KEYBOARD_TEXT = "keyboard_text" MODEL_MEDIA_DIRECTORIES = "media_directories" -MODEL_MEDIA_FILES = "media_files" MODEL_MEDIA_FILE = "media_file" +MODEL_MEDIA_FILES = "media_files" MODEL_MEMORY = "memory" MODEL_NETWORK = "network" MODEL_NOTIFICATION = "notification" MODEL_OPEN_PATH = "open_path" MODEL_OPEN_URL = "open_url" MODEL_RESPONSE = "response" +MODEL_SECRETS = "secrets" MODEL_SENSORS = "sensors" +MODEL_SETTINGS = "settings" MODEL_SYSTEM = "system" MODEL_MAP = { MODEL_BATTERY: Battery, - MODEL_BRIDGE: Bridge, MODEL_CPU: Cpu, MODEL_DATA: Data, MODEL_DISK: Disk, diff --git a/connector/systembridgeconnector/http_client.py b/connector/systembridgeconnector/http_client.py index 078ad9600..29dcece56 100644 --- a/connector/systembridgeconnector/http_client.py +++ b/connector/systembridgeconnector/http_client.py @@ -8,11 +8,8 @@ from aiohttp.client_exceptions import ClientConnectorError, ServerDisconnectedError import async_timeout -from systembridgeconnector.base import Base -from systembridgeconnector.exceptions import ( - AuthenticationException, - ConnectionErrorException, -) +from .base import Base +from .exceptions import AuthenticationException, ConnectionErrorException BASE_HEADERS = { "Accept": "application/json", diff --git a/connector/systembridgeconnector/models/bridge.py b/connector/systembridgeconnector/models/bridge.py deleted file mode 100644 index 1afbefce5..000000000 --- a/connector/systembridgeconnector/models/bridge.py +++ /dev/null @@ -1,20 +0,0 @@ -# generated by datamodel-codegen: -# filename: bridge.json - -from __future__ import annotations - -from typing import Optional - -from pydantic import BaseModel, Extra, Field - - -class Bridge(BaseModel): - """ - Bridge - """ - - class Config: - extra = Extra.allow - - id: Optional[str] = Field(None, description="Event ID") - last_updated: dict[str, float] = Field(..., description="Last updated") diff --git a/connector/systembridgeconnector/models/data.py b/connector/systembridgeconnector/models/data.py index fbe44f3a6..6aebb3361 100644 --- a/connector/systembridgeconnector/models/data.py +++ b/connector/systembridgeconnector/models/data.py @@ -2,25 +2,25 @@ from __future__ import annotations -from pydantic import BaseModel, Field +from typing import Optional -from systembridgeconnector.models.battery import Battery -from systembridgeconnector.models.bridge import Bridge -from systembridgeconnector.models.cpu import Cpu -from systembridgeconnector.models.disk import Disk -from systembridgeconnector.models.display import Display -from systembridgeconnector.models.gpu import Gpu -from systembridgeconnector.models.memory import Memory -from systembridgeconnector.models.network import Network -from systembridgeconnector.models.sensors import Sensors -from systembridgeconnector.models.system import System +from pydantic import BaseModel, Extra, Field + +from .battery import Battery +from .cpu import Cpu +from .disk import Disk +from .display import Display +from .gpu import Gpu +from .memory import Memory +from .network import Network +from .sensors import Sensors +from .system import System class Data(BaseModel): """Data""" battery: Battery = Field(..., alias="battery") - bridge: Bridge = Field(..., alias="bridge") cpu: Cpu = Field(..., alias="cpu") disk: Disk = Field(..., alias="disk") display: Display = Field(..., alias="display") @@ -29,3 +29,10 @@ class Data(BaseModel): network: Network = Field(..., alias="network") sensors: Sensors = Field(..., alias="sensors") system: System = Field(..., alias="system") + + +class DataDict(BaseModel): + class Config: + extra = Extra.allow + + last_updated: dict[str, Optional[float]] = Field(..., description="Last updated") diff --git a/connector/systembridgeconnector/models/database_data.py b/connector/systembridgeconnector/models/database_data.py new file mode 100644 index 000000000..60f3e17ab --- /dev/null +++ b/connector/systembridgeconnector/models/database_data.py @@ -0,0 +1,55 @@ +"""System Bridge: Models - Database Data""" + +from __future__ import annotations + +from typing import Optional + +from sqlmodel import Field, SQLModel + + +class Data(SQLModel): + """Database Data""" + + key: str = Field(primary_key=True, nullable=False) + value: Optional[str] = Field(default=None, nullable=True) + timestamp: Optional[float] = Field(default=None, nullable=True) + + +class Battery(Data, table=True): + """Database Data Battery""" + + +class CPU(Data, table=True): + """Database Data CPU""" + + +class Disk(Data, table=True): + """Database Data Disk""" + + +class Display(Data, table=True): + """Database Data Display""" + + +class GPU(Data, table=True): + """Database Data GPU""" + + +class Memory(Data, table=True): + """Database Data Memory""" + + +class Network(Data, table=True): + """Database Data Network""" + + +class Secrets(Data, table=True): + """Database Data Secrets""" + + +class Settings(Data, table=True): + """Database Data Settings""" + + +class System(Data, table=True): + """System""" diff --git a/connector/systembridgeconnector/models/database_data_bridge.py b/connector/systembridgeconnector/models/database_data_bridge.py new file mode 100644 index 000000000..97ba48a6c --- /dev/null +++ b/connector/systembridgeconnector/models/database_data_bridge.py @@ -0,0 +1,26 @@ +"""System Bridge: Models - Database Data Bridge""" + +from __future__ import annotations + +from typing import Optional + +from sqlmodel import Field, SQLModel + + +class Bridge(SQLModel, table=True): + """Database Data Bridge""" + + uuid: str = Field(primary_key=True, nullable=False) + name: str = Field(nullable=False) + address: str = Field(nullable=False) + fqdn: Optional[str] = Field(default=None, nullable=True) + host: str = Field(default=None, nullable=True) + ip: str = Field(nullable=False) + mac: str = Field(nullable=False) + port: str = Field(nullable=False) + version: str = Field(nullable=False) + websocket_address: str = Field(nullable=False) + websocket_port: str = Field(nullable=False) + active: Optional[int] = Field(default=None, nullable=True) + last_active_timestamp: Optional[float] = Field(default=None, nullable=True) + timestamp: Optional[float] = Field(default=None, nullable=True) diff --git a/connector/systembridgeconnector/models/database_data_sensors.py b/connector/systembridgeconnector/models/database_data_sensors.py new file mode 100644 index 000000000..dfa11dd25 --- /dev/null +++ b/connector/systembridgeconnector/models/database_data_sensors.py @@ -0,0 +1,18 @@ +"""System Bridge: Models - Database Data Sensors""" + +from __future__ import annotations + +from typing import Optional + +from sqlmodel import Field + +from systembridgeshared.models.database_data import Data + + +class Sensors(Data, table=True): + """Database Data Sensors""" + + type: str = Field(nullable=False) + name: Optional[str] = Field(default=None, nullable=True) + hardware_type: Optional[str] = Field(default=None, nullable=True) + hardware_name: Optional[str] = Field(default=None, nullable=True) diff --git a/connector/systembridgeconnector/models/display.py b/connector/systembridgeconnector/models/display.py index c08be42f2..c668615d0 100644 --- a/connector/systembridgeconnector/models/display.py +++ b/connector/systembridgeconnector/models/display.py @@ -28,5 +28,5 @@ class Config: extra = Extra.allow id: Optional[str] = Field(None, description="Event ID") - displays: list - last_updated: LastUpdated = Field(..., description="Last updated") + displays: Optional[list] = None + last_updated: Optional[LastUpdated] = Field(None, description="Last updated") diff --git a/connector/systembridgeconnector/models/gpu.py b/connector/systembridgeconnector/models/gpu.py index 51e573f57..01ebccc6a 100644 --- a/connector/systembridgeconnector/models/gpu.py +++ b/connector/systembridgeconnector/models/gpu.py @@ -28,5 +28,5 @@ class Config: extra = Extra.allow id: Optional[str] = Field(None, description="Event ID") - gpus: list - last_updated: LastUpdated = Field(..., description="Last updated") + gpus: Optional[list] = None + last_updated: Optional[LastUpdated] = Field(None, description="Last updated") diff --git a/connector/systembridgeconnector/version.py b/connector/systembridgeconnector/version.py index aa692cc18..a26c927a9 100644 --- a/connector/systembridgeconnector/version.py +++ b/connector/systembridgeconnector/version.py @@ -1,14 +1,15 @@ """System Bridge Connector: Version""" from __future__ import annotations +from typing import Optional + from aiohttp import ClientSession from pkg_resources import parse_version -from typing import Optional -from systembridgeconnector.base import Base -from systembridgeconnector.exceptions import ConnectionErrorException -from systembridgeconnector.http_client import HTTPClient -from systembridgeconnector.models.system import System +from .base import Base +from .exceptions import ConnectionErrorException +from .http_client import HTTPClient +from .models.system import System SUPPORTED_VERSION = "3.1.2" diff --git a/connector/systembridgeconnector/websocket_client.py b/connector/systembridgeconnector/websocket_client.py index e831283a6..6716ddcee 100644 --- a/connector/systembridgeconnector/websocket_client.py +++ b/connector/systembridgeconnector/websocket_client.py @@ -9,8 +9,8 @@ import aiohttp -from systembridgeconnector.base import Base -from systembridgeconnector.const import ( +from .base import Base +from .const import ( EVENT_API_KEY, EVENT_DATA, EVENT_EVENT, @@ -43,26 +43,26 @@ TYPE_POWER_SLEEP, TYPE_REGISTER_DATA_LISTENER, ) -from systembridgeconnector.exceptions import ( +from .exceptions import ( AuthenticationException, BadMessageException, ConnectionClosedException, ConnectionErrorException, ) -from systembridgeconnector.models.get_data import GetData -from systembridgeconnector.models.keyboard_key import KeyboardKey -from systembridgeconnector.models.keyboard_text import KeyboardText -from systembridgeconnector.models.media_directories import MediaDirectories -from systembridgeconnector.models.media_files import File as MediaFile, MediaFiles -from systembridgeconnector.models.media_get_file import MediaGetFile -from systembridgeconnector.models.media_get_files import MediaGetFiles -from systembridgeconnector.models.notification import Notification -from systembridgeconnector.models.open_path import OpenPath -from systembridgeconnector.models.open_url import OpenUrl -from systembridgeconnector.models.register_data_listener import RegisterDataListener -from systembridgeconnector.models.request import Request -from systembridgeconnector.models.response import Response -from systembridgeconnector.models.update import Update +from .models.get_data import GetData +from .models.keyboard_key import KeyboardKey +from .models.keyboard_text import KeyboardText +from .models.media_directories import MediaDirectories +from .models.media_files import File as MediaFile, MediaFiles +from .models.media_get_file import MediaGetFile +from .models.media_get_files import MediaGetFiles +from .models.notification import Notification +from .models.open_path import OpenPath +from .models.open_url import OpenUrl +from .models.register_data_listener import RegisterDataListener +from .models.request import Request +from .models.response import Response +from .models.update import Update class WebSocketClient(Base): diff --git a/frontend/components/Data/Data.tsx b/frontend/components/Data/Data.tsx index 95b3a2dc1..69405ef23 100644 --- a/frontend/components/Data/Data.tsx +++ b/frontend/components/Data/Data.tsx @@ -15,7 +15,6 @@ import DataItems from "components/Data/DataItems"; const modules = [ "battery", - "bridge", "cpu", "disk", "display", @@ -28,7 +27,6 @@ const modules = [ const moduleMap: { [key: string]: string } = { battery: "Battery", - bridge: "Bridge", cpu: "CPU", disk: "Disk", display: "Display", diff --git a/gui/systembridgegui/__main__.py b/gui/systembridgegui/__main__.py index a95e0bd81..161a93822 100644 --- a/gui/systembridgegui/__main__.py +++ b/gui/systembridgegui/__main__.py @@ -24,12 +24,12 @@ from systembridgeshared.websocket_client import WebSocketClient from typer import Typer -from systembridgegui._version import __version__ -from systembridgegui.system_tray import SystemTray -from systembridgegui.widgets.timed_message_box import TimedMessageBox -from systembridgegui.window.main import MainWindow -from systembridgegui.window.notification import NotificationWindow -from systembridgegui.window.player import PlayerWindow +from ._version import __version__ +from .system_tray import SystemTray +from .widgets.timed_message_box import TimedMessageBox +from .window.main import MainWindow +from .window.notification import NotificationWindow +from .window.player import PlayerWindow class Main(Base): diff --git a/gui/systembridgegui/system_tray.py b/gui/systembridgegui/system_tray.py index beb301e52..961969554 100644 --- a/gui/systembridgegui/system_tray.py +++ b/gui/systembridgegui/system_tray.py @@ -12,6 +12,7 @@ from systembridgeshared.base import Base from systembridgeshared.common import get_user_data_directory from systembridgeshared.database import Database +from systembridgeshared.models.database_data import System as DatabaseSystem from systembridgeshared.settings import Settings PATH_BRIDGES_OPEN_ON = "/app/bridges/openon.html" @@ -70,19 +71,26 @@ def __init__( menu.addSeparator() latest_version_text = "Latest Version" - version_current = self._database.read_table_by_key("system", "version").to_dict( - orient="records" - )[0]["value"] - version_latest = self._database.read_table_by_key( - "system", "version_latest" - ).to_dict(orient="records")[0]["value"] - record_version_newer_available = self._database.read_table_by_key( - "system", "version_newer_available" + result_version_current = self._database.get_data_item_by_key( + DatabaseSystem, "version" ) - version_newer_available = ( - record_version_newer_available.to_dict(orient="records")[0]["value"] - if not record_version_newer_available.empty - else "False" + version_current = ( + result_version_current.value if result_version_current is not None else None + ) + result_version_latest = self._database.get_data_item_by_key( + DatabaseSystem, "version_latest" + ) + version_latest = ( + result_version_latest.value if result_version_latest is not None else None + ) + result_version_newer_available = self._database.get_data_item_by_key( + DatabaseSystem, "version_newer_available" + ) + version_newer_available: str = ( + result_version_newer_available.value + if result_version_newer_available is not None + and result_version_newer_available.value is not None + else "" ) if version_newer_available.lower() == "true": diff --git a/schemas/data/bridge.json b/schemas/data/bridge.json deleted file mode 100644 index 23ca76ce8..000000000 --- a/schemas/data/bridge.json +++ /dev/null @@ -1,22 +0,0 @@ -{ - "$schema": "http://json-schema.org/draft-04/schema#", - "type": "object", - "description": "Bridge", - "properties": { - "id": { - "type": "string", - "description": "Event ID" - }, - "last_updated": { - "type": "object", - "description": "Last updated", - "additionalProperties": { - "type": "number" - } - } - }, - "additionalProperties": { - "type": "string" - }, - "required": ["last_updated"] -} diff --git a/schemas/data/display.json b/schemas/data/display.json index 08e3277da..e4a322cc4 100644 --- a/schemas/data/display.json +++ b/schemas/data/display.json @@ -24,6 +24,5 @@ "required": ["displays"] } }, - "additionalProperties": {}, - "required": ["displays", "last_updated"] + "additionalProperties": {} } diff --git a/schemas/data/gpu.json b/schemas/data/gpu.json index 5046871e2..b86666626 100644 --- a/schemas/data/gpu.json +++ b/schemas/data/gpu.json @@ -24,6 +24,5 @@ "required": ["gpus"] } }, - "additionalProperties": {}, - "required": ["gpus", "last_updated"] + "additionalProperties": {} } diff --git a/shared/requirements.txt b/shared/requirements.txt index 4963349e8..9bcb730bd 100644 --- a/shared/requirements.txt +++ b/shared/requirements.txt @@ -5,3 +5,4 @@ cryptography==37.0.4 incremental==21.3.0 pandas==1.4.3 pydantic==1.9.1 +sqlmodel==0.0.6 diff --git a/shared/systembridgeshared/common.py b/shared/systembridgeshared/common.py index 34e956841..6515a085c 100644 --- a/shared/systembridgeshared/common.py +++ b/shared/systembridgeshared/common.py @@ -4,7 +4,7 @@ import json import os import re -from typing import Union +from typing import Any, Union from appdirs import AppDirs @@ -25,9 +25,11 @@ def camel_to_snake(name): def convert_string_to_correct_type( - value: str, -) -> Union[bool, float, int, str, list, dict, None]: + value: str | None, +) -> Union[bool, float, int, str, list[Any], dict[str, Any], None]: """Convert string to correct data type""" + if value is None: + return None try: if value.startswith("'") and value.endswith("'"): return convert_string_to_correct_type(value[1:-1]) diff --git a/shared/systembridgeshared/const.py b/shared/systembridgeshared/const.py index 601fccc5d..0b90022d8 100644 --- a/shared/systembridgeshared/const.py +++ b/shared/systembridgeshared/const.py @@ -1,24 +1,23 @@ """System Bridge Shared: Constants""" -from systembridgeshared.models.battery import Battery -from systembridgeshared.models.bridge import Bridge -from systembridgeshared.models.cpu import Cpu -from systembridgeshared.models.data import Data -from systembridgeshared.models.disk import Disk -from systembridgeshared.models.display import Display -from systembridgeshared.models.generic import Generic -from systembridgeshared.models.gpu import Gpu -from systembridgeshared.models.keyboard_key import KeyboardKey -from systembridgeshared.models.keyboard_text import KeyboardText -from systembridgeshared.models.media_directories import MediaDirectories -from systembridgeshared.models.media_files import File, MediaFiles -from systembridgeshared.models.memory import Memory -from systembridgeshared.models.network import Network -from systembridgeshared.models.notification import Notification -from systembridgeshared.models.open_path import OpenPath -from systembridgeshared.models.open_url import OpenUrl -from systembridgeshared.models.response import Response -from systembridgeshared.models.sensors import Sensors -from systembridgeshared.models.system import System +from .models.battery import Battery +from .models.cpu import Cpu +from .models.data import Data +from .models.disk import Disk +from .models.display import Display +from .models.generic import Generic +from .models.gpu import Gpu +from .models.keyboard_key import KeyboardKey +from .models.keyboard_text import KeyboardText +from .models.media_directories import MediaDirectories +from .models.media_files import File, MediaFiles +from .models.memory import Memory +from .models.network import Network +from .models.notification import Notification +from .models.open_path import OpenPath +from .models.open_url import OpenUrl +from .models.response import Response +from .models.sensors import Sensors +from .models.system import System # Logging DATE_FORMAT = "%Y-%m-%d %H:%M:%S" @@ -153,7 +152,6 @@ # Model MODEL_BATTERY = "battery" -MODEL_BRIDGE = "bridge" MODEL_CPU = "cpu" MODEL_DATA = "data" MODEL_DISK = "disk" @@ -163,20 +161,21 @@ MODEL_KEYBOARD_KEY = "keyboard_key" MODEL_KEYBOARD_TEXT = "keyboard_text" MODEL_MEDIA_DIRECTORIES = "media_directories" -MODEL_MEDIA_FILES = "media_files" MODEL_MEDIA_FILE = "media_file" +MODEL_MEDIA_FILES = "media_files" MODEL_MEMORY = "memory" MODEL_NETWORK = "network" MODEL_NOTIFICATION = "notification" MODEL_OPEN_PATH = "open_path" MODEL_OPEN_URL = "open_url" MODEL_RESPONSE = "response" +MODEL_SECRETS = "secrets" MODEL_SENSORS = "sensors" +MODEL_SETTINGS = "settings" MODEL_SYSTEM = "system" MODEL_MAP = { MODEL_BATTERY: Battery, - MODEL_BRIDGE: Bridge, MODEL_CPU: Cpu, MODEL_DATA: Data, MODEL_DISK: Disk, diff --git a/shared/systembridgeshared/database.py b/shared/systembridgeshared/database.py index f972af819..0409aec01 100644 --- a/shared/systembridgeshared/database.py +++ b/shared/systembridgeshared/database.py @@ -1,30 +1,79 @@ """System Bridge Shared: Database""" from __future__ import annotations -from collections import OrderedDict from collections.abc import Mapping -import json import os -from sqlite3 import OperationalError, connect from time import time -from typing import Optional, Union - -from pandas import DataFrame, read_sql_query - -from systembridgeshared.base import Base -from systembridgeshared.common import ( - convert_string_to_correct_type, - get_user_data_directory, +from typing import Any, Optional, Union + +from sqlmodel import Session, SQLModel, create_engine, select +from sqlmodel.sql.expression import Select, SelectOfScalar + +from .base import Base +from .common import convert_string_to_correct_type, get_user_data_directory +from .const import ( + MODEL_BATTERY, + MODEL_CPU, + MODEL_DISK, + MODEL_DISPLAY, + MODEL_GPU, + MODEL_MEMORY, + MODEL_NETWORK, + MODEL_SECRETS, + MODEL_SENSORS, + MODEL_SETTINGS, + MODEL_SYSTEM, ) -from systembridgeshared.const import ( - COLUMN_HARDWARE_NAME, - COLUMN_HARDWARE_TYPE, - COLUMN_KEY, - COLUMN_NAME, - COLUMN_TIMESTAMP, - COLUMN_TYPE, - COLUMN_VALUE, +from .models.data import DataDict +from .models.database_data import ( + CPU, + GPU, + Battery, + Data, + Disk, + Display, + Memory, + Network, + Secrets, + Settings, + System, ) +from .models.database_data_bridge import Bridge +from .models.database_data_sensors import Sensors + +TABLE_MAP: Mapping[str, Any] = { + MODEL_BATTERY: Battery, + MODEL_CPU: CPU, + MODEL_DISK: Disk, + MODEL_DISPLAY: Display, + MODEL_GPU: GPU, + MODEL_MEMORY: Memory, + MODEL_NETWORK: Network, + MODEL_SECRETS: Secrets, + MODEL_SENSORS: Sensors, + MODEL_SETTINGS: Settings, + MODEL_SYSTEM: System, +} + + +TableDataType = Union[ + Battery, + Bridge, + CPU, + Disk, + Display, + GPU, + Memory, + Network, + Secrets, + Sensors, + Settings, + System, +] + + +SelectOfScalar.inherit_cache = True # type: ignore +Select.inherit_cache = True # type: ignore class Database(Base): @@ -33,221 +82,85 @@ class Database(Base): def __init__(self): """Initialise""" super().__init__() - self._connection = connect( - os.path.join(get_user_data_directory(), "systembridge.db"), - check_same_thread=False, + self._engine = create_engine( + f"sqlite:///{os.path.join(get_user_data_directory(), 'systembridge.db')}" ) - - @property - def connected(self) -> bool: - """Check if connected""" - return self._connection is not None - - def execute_sql( - self, - sql: str, - ) -> None: - """Execute SQL""" - if not self.connected: - self.connect() - self._logger.debug("Executing SQL: %s", sql) - try: - self._connection.commit() - except OperationalError: - pass - try: - self._connection.execute(sql) - self._connection.commit() - except OperationalError as error: - self._logger.warning( - "Error executing SQL: %s\n%s", - sql, - error, - ) - - def execute_sql_with_params( - self, - sql: str, - params: Mapping, - ) -> None: - """Execute SQL""" - self._logger.debug("Executing SQL: %s\n%s", sql, params) - try: - self._connection.commit() - except OperationalError: - pass - try: - self._connection.execute(sql, params) - self._connection.commit() - except OperationalError as error: - self._logger.warning( - "Error executing SQL: %s\n%s", - sql, - error, - ) - - def connect(self) -> None: - """Connect to database""" - self._connection = connect( - os.path.join(get_user_data_directory(), "systembridge.db"), - check_same_thread=False, + SQLModel.metadata.create_all( + self._engine, + # tables=TABLES, ) - def close(self) -> None: - """Close connection""" - self._connection.close() - - def check_table_for_key( + def clear_table( self, - table_name: str, - key: str, - ) -> bool: - """Check if key exists in table""" - return self.read_table_by_key(table_name, key).empty + table: Any, + ) -> None: + """Clear table""" + with Session(self._engine, autoflush=True) as session: + for sensor in session.exec(select(table)).all(): + session.delete(sensor) + session.commit() - def read_table( + def get_data( self, - table_name: str, - ) -> DataFrame: - """Read table""" - return self.read_query( - f"SELECT * FROM {table_name}", - ) + table: Any, + ) -> list[Any]: + """Get data from database""" + with Session(self._engine, autoflush=True) as session: + return session.exec(select(table)).all() - def read_table_by_key( + def get_data_by_key( self, - table_name: str, + table: Any, key: str, - ) -> DataFrame: - """Read table by key""" - return self.read_query( - f"SELECT * FROM {table_name} WHERE {COLUMN_KEY} = '{key}'", - ) + ) -> list[Data]: + """Get data from database by key""" + with Session(self._engine, autoflush=True) as session: + return session.exec(select(table).where(table.key == key)).all() - def read_query( - self, - query: str, - ) -> DataFrame: - """Read SQL""" - if not self.connected: - self.connect() - self._logger.debug("Reading SQL: %s", query) - return read_sql_query(query, self._connection) - - def create_table( + def get_data_item_by_key( self, - table_name: str, - columns: list[tuple], - ) -> None: - """Create table""" - sql = f"CREATE TABLE IF NOT EXISTS {table_name} (" - for column in columns: - sql += f"{column[0]} {column[1]}," - sql = sql[:-1] + ")" - self.execute_sql(sql) - - def clear_table( - self, - table_name: str, - ) -> None: - """Clear table""" - self.execute_sql(f"DELETE FROM {table_name}") - - def clear_table_by_key( - self, - table_name: str, + table: Any, key: str, - ) -> None: - """Clear table by key""" - self.execute_sql(f"DELETE FROM {table_name} WHERE {COLUMN_KEY} = '{key}'") + ) -> Optional[Data]: + """Get data item from database by key""" + with Session(self._engine, autoflush=True) as session: + return session.exec(select(table).where(table.key == key)).first() - def write( + def get_data_dict( self, - table_name: str, - data_key: str, - data_value: Union[bool, float, int, str, list, dict, None], - data_timestamp: Optional[float] = None, - ) -> None: - """Write to table""" - if data_timestamp is None: - data_timestamp = time() - - # Convert list or dict to JSON - if isinstance(data_value, (dict, list)): - data_value = json.dumps(data_value) - else: - data_value = str(data_value) - - self.execute_sql( - f"""INSERT INTO {table_name} ({COLUMN_KEY}, {COLUMN_VALUE}, {COLUMN_TIMESTAMP}) - VALUES ('{data_key}', '{data_value}', {data_timestamp}) - ON CONFLICT({COLUMN_KEY}) DO - UPDATE SET {COLUMN_VALUE} = '{data_value}', {COLUMN_TIMESTAMP} = {data_timestamp} - WHERE {COLUMN_KEY} = '{data_key}' - """.replace( - "\n", "" - ).replace( - " ", "" - ), - ) - - def write_sensor( + table: Any, + ) -> DataDict: + """Get data from database as dictionary""" + data: dict[str, Any] = {} + data_last_updated: dict[str, float | None] = {} + result = self.get_data(table) + for item in result: + if item.value is None: + data[item.key] = None + else: + data[item.key] = convert_string_to_correct_type(item.value) + if item.timestamp is None: + data_last_updated[item.key] = None + else: + data_last_updated[item.key] = item.timestamp + + return DataDict(**data, last_updated=data_last_updated) + + def update_data( self, - table_name: str, - data_key: str, - data_type: str, - data_name: str, - data_hardware_type: str, - data_hardware_name: str, - data_value: Union[bool, float, int, str, list, dict, None], - data_timestamp: Optional[float] = None, + table, + data: Any, ) -> None: - """Write to table""" - if data_timestamp is None: - data_timestamp = time() - - # Convert list or dict to JSON - if isinstance(data_value, (dict, list)): - data_value = json.dumps(data_value) - else: - data_value = str(data_value) - - self.execute_sql( - f"""INSERT INTO {table_name} ({COLUMN_KEY}, {COLUMN_TYPE}, {COLUMN_NAME}, - {COLUMN_HARDWARE_TYPE}, {COLUMN_HARDWARE_NAME}, {COLUMN_VALUE}, {COLUMN_TIMESTAMP}) - VALUES ('{data_key}', '{data_type}', '{data_name}', '{data_hardware_type}', - '{data_hardware_name}', '{data_value}', {data_timestamp}) - ON CONFLICT({COLUMN_KEY}) DO - UPDATE SET {COLUMN_VALUE} = '{data_value}', {COLUMN_TIMESTAMP} = {data_timestamp} - WHERE {COLUMN_KEY} = '{data_key}' - """.replace( - "\n", "" - ).replace( - " ", "" - ), - ) - - def table_data_to_ordered_dict( - self, - table_name: str, - ) -> Optional[OrderedDict]: - """Convert table to Ordereddict""" - data_dict = self.read_table(table_name).to_dict(orient="records") - if len(data_dict) == 0: - return None - data: dict = { - "last_updated": {}, - } - for item in data_dict: - data = { - **data, - item[COLUMN_KEY]: convert_string_to_correct_type(item[COLUMN_VALUE]), - "last_updated": { - **data["last_updated"], # type: ignore - item[COLUMN_KEY]: item["timestamp"], - }, - } - output = OrderedDict(sorted(data.items())) - output.move_to_end("last_updated", last=True) - - return output + """Update data""" + with Session(self._engine, autoflush=True) as session: + result = session.exec(select(table).where(table.key == data.key)) + if (old_data := result.first()) is None: + data.timestamp = time() + session.add(data) + else: + old_data.value = data.value + old_data.timestamp = time() + session.add(old_data) + session.commit() + if old_data is not None: + session.refresh(old_data) diff --git a/shared/systembridgeshared/logger.py b/shared/systembridgeshared/logger.py index a06dd6078..5cb931814 100644 --- a/shared/systembridgeshared/logger.py +++ b/shared/systembridgeshared/logger.py @@ -5,8 +5,8 @@ from colorlog import ColoredFormatter -from systembridgeshared.common import get_user_data_directory -from systembridgeshared.const import DATE_FORMAT, FORMAT +from .common import get_user_data_directory +from .const import DATE_FORMAT, FORMAT def setup_logger( diff --git a/shared/systembridgeshared/models/bridge.py b/shared/systembridgeshared/models/bridge.py deleted file mode 100644 index 1afbefce5..000000000 --- a/shared/systembridgeshared/models/bridge.py +++ /dev/null @@ -1,20 +0,0 @@ -# generated by datamodel-codegen: -# filename: bridge.json - -from __future__ import annotations - -from typing import Optional - -from pydantic import BaseModel, Extra, Field - - -class Bridge(BaseModel): - """ - Bridge - """ - - class Config: - extra = Extra.allow - - id: Optional[str] = Field(None, description="Event ID") - last_updated: dict[str, float] = Field(..., description="Last updated") diff --git a/shared/systembridgeshared/models/data.py b/shared/systembridgeshared/models/data.py index 27ced2ca5..6aebb3361 100644 --- a/shared/systembridgeshared/models/data.py +++ b/shared/systembridgeshared/models/data.py @@ -2,25 +2,25 @@ from __future__ import annotations -from pydantic import BaseModel, Field +from typing import Optional -from systembridgeshared.models.battery import Battery -from systembridgeshared.models.bridge import Bridge -from systembridgeshared.models.cpu import Cpu -from systembridgeshared.models.disk import Disk -from systembridgeshared.models.display import Display -from systembridgeshared.models.gpu import Gpu -from systembridgeshared.models.memory import Memory -from systembridgeshared.models.network import Network -from systembridgeshared.models.sensors import Sensors -from systembridgeshared.models.system import System +from pydantic import BaseModel, Extra, Field + +from .battery import Battery +from .cpu import Cpu +from .disk import Disk +from .display import Display +from .gpu import Gpu +from .memory import Memory +from .network import Network +from .sensors import Sensors +from .system import System class Data(BaseModel): """Data""" battery: Battery = Field(..., alias="battery") - bridge: Bridge = Field(..., alias="bridge") cpu: Cpu = Field(..., alias="cpu") disk: Disk = Field(..., alias="disk") display: Display = Field(..., alias="display") @@ -29,3 +29,10 @@ class Data(BaseModel): network: Network = Field(..., alias="network") sensors: Sensors = Field(..., alias="sensors") system: System = Field(..., alias="system") + + +class DataDict(BaseModel): + class Config: + extra = Extra.allow + + last_updated: dict[str, Optional[float]] = Field(..., description="Last updated") diff --git a/shared/systembridgeshared/models/database_data.py b/shared/systembridgeshared/models/database_data.py new file mode 100644 index 000000000..60f3e17ab --- /dev/null +++ b/shared/systembridgeshared/models/database_data.py @@ -0,0 +1,55 @@ +"""System Bridge: Models - Database Data""" + +from __future__ import annotations + +from typing import Optional + +from sqlmodel import Field, SQLModel + + +class Data(SQLModel): + """Database Data""" + + key: str = Field(primary_key=True, nullable=False) + value: Optional[str] = Field(default=None, nullable=True) + timestamp: Optional[float] = Field(default=None, nullable=True) + + +class Battery(Data, table=True): + """Database Data Battery""" + + +class CPU(Data, table=True): + """Database Data CPU""" + + +class Disk(Data, table=True): + """Database Data Disk""" + + +class Display(Data, table=True): + """Database Data Display""" + + +class GPU(Data, table=True): + """Database Data GPU""" + + +class Memory(Data, table=True): + """Database Data Memory""" + + +class Network(Data, table=True): + """Database Data Network""" + + +class Secrets(Data, table=True): + """Database Data Secrets""" + + +class Settings(Data, table=True): + """Database Data Settings""" + + +class System(Data, table=True): + """System""" diff --git a/shared/systembridgeshared/models/database_data_bridge.py b/shared/systembridgeshared/models/database_data_bridge.py new file mode 100644 index 000000000..97ba48a6c --- /dev/null +++ b/shared/systembridgeshared/models/database_data_bridge.py @@ -0,0 +1,26 @@ +"""System Bridge: Models - Database Data Bridge""" + +from __future__ import annotations + +from typing import Optional + +from sqlmodel import Field, SQLModel + + +class Bridge(SQLModel, table=True): + """Database Data Bridge""" + + uuid: str = Field(primary_key=True, nullable=False) + name: str = Field(nullable=False) + address: str = Field(nullable=False) + fqdn: Optional[str] = Field(default=None, nullable=True) + host: str = Field(default=None, nullable=True) + ip: str = Field(nullable=False) + mac: str = Field(nullable=False) + port: str = Field(nullable=False) + version: str = Field(nullable=False) + websocket_address: str = Field(nullable=False) + websocket_port: str = Field(nullable=False) + active: Optional[int] = Field(default=None, nullable=True) + last_active_timestamp: Optional[float] = Field(default=None, nullable=True) + timestamp: Optional[float] = Field(default=None, nullable=True) diff --git a/shared/systembridgeshared/models/database_data_sensors.py b/shared/systembridgeshared/models/database_data_sensors.py new file mode 100644 index 000000000..fff2f54b1 --- /dev/null +++ b/shared/systembridgeshared/models/database_data_sensors.py @@ -0,0 +1,18 @@ +"""System Bridge: Models - Database Data Sensors""" + +from __future__ import annotations + +from typing import Optional + +from sqlmodel import Field + +from .database_data import Data + + +class Sensors(Data, table=True): + """Database Data Sensors""" + + type: str = Field(nullable=False) + name: Optional[str] = Field(default=None, nullable=True) + hardware_type: Optional[str] = Field(default=None, nullable=True) + hardware_name: Optional[str] = Field(default=None, nullable=True) diff --git a/shared/systembridgeshared/models/display.py b/shared/systembridgeshared/models/display.py index c08be42f2..c668615d0 100644 --- a/shared/systembridgeshared/models/display.py +++ b/shared/systembridgeshared/models/display.py @@ -28,5 +28,5 @@ class Config: extra = Extra.allow id: Optional[str] = Field(None, description="Event ID") - displays: list - last_updated: LastUpdated = Field(..., description="Last updated") + displays: Optional[list] = None + last_updated: Optional[LastUpdated] = Field(None, description="Last updated") diff --git a/shared/systembridgeshared/models/gpu.py b/shared/systembridgeshared/models/gpu.py index 51e573f57..01ebccc6a 100644 --- a/shared/systembridgeshared/models/gpu.py +++ b/shared/systembridgeshared/models/gpu.py @@ -28,5 +28,5 @@ class Config: extra = Extra.allow id: Optional[str] = Field(None, description="Event ID") - gpus: list - last_updated: LastUpdated = Field(..., description="Last updated") + gpus: Optional[list] = None + last_updated: Optional[LastUpdated] = Field(None, description="Last updated") diff --git a/shared/systembridgeshared/settings.py b/shared/systembridgeshared/settings.py index fb49cf0ba..444224a5f 100644 --- a/shared/systembridgeshared/settings.py +++ b/shared/systembridgeshared/settings.py @@ -4,27 +4,27 @@ import io import os from os.path import exists -from typing import Union +from typing import Any, Union from uuid import uuid4 from appdirs import AppDirs from cryptography.fernet import Fernet -from systembridgeshared.base import Base -from systembridgeshared.common import convert_string_to_correct_type -from systembridgeshared.const import ( - COLUMN_KEY, - COLUMN_TIMESTAMP, - COLUMN_VALUE, +from .base import Base +from .common import convert_string_to_correct_type +from .const import ( SECRET_API_KEY, SETTING_ADDITIONAL_MEDIA_DIRECTORIES, SETTING_AUTOSTART, SETTING_LOG_LEVEL, SETTING_PORT_API, - TABLE_SECRETS, - TABLE_SETTINGS, ) -from systembridgeshared.database import Database +from .database import Database +from .models.database_data import ( + Data as DatabaseData, + Secrets as DatabaseSecrets, + Settings as DatabaseSettings, +) class Settings(Base): @@ -36,28 +36,10 @@ def __init__( ) -> None: """Initialize""" super().__init__() - self._database = database - self._database.create_table( - TABLE_SECRETS, - [ - (COLUMN_KEY, "TEXT PRIMARY KEY"), - (COLUMN_VALUE, "TEXT"), - (COLUMN_TIMESTAMP, "DOUBLE"), - ], - ) - - self._database.create_table( - TABLE_SETTINGS, - [ - (COLUMN_KEY, "TEXT PRIMARY KEY"), - (COLUMN_VALUE, "TEXT"), - (COLUMN_TIMESTAMP, "DOUBLE"), - ], - ) # Generate default encryption key - self._encryption_key = None + self._encryption_key: str = "" secret_key_path = os.path.join( AppDirs("systembridge", "timmo001").user_data_dir, "secret.key" ) @@ -65,69 +47,82 @@ def __init__( with io.open(secret_key_path, encoding="utf-8") as file: self._encryption_key = file.read().splitlines()[0] if not self._encryption_key: - self._encryption_key = Fernet.generate_key() + self._encryption_key = Fernet.generate_key().decode() with io.open(secret_key_path, "w", encoding="utf-8") as file: - file.write(self._encryption_key.decode()) + file.write(self._encryption_key) # Default Secrets - if self._database.check_table_for_key(TABLE_SECRETS, SECRET_API_KEY): + if self._database.get_data_item_by_key(DatabaseSecrets, SECRET_API_KEY) is None: self.set_secret(SECRET_API_KEY, str(uuid4())) # Default Settings - if self._database.check_table_for_key(TABLE_SETTINGS, SETTING_AUTOSTART): - self.set(SETTING_AUTOSTART, False) - if self._database.check_table_for_key(TABLE_SETTINGS, SETTING_LOG_LEVEL): + if ( + self._database.get_data_item_by_key(DatabaseSettings, SETTING_AUTOSTART) + is None + ): + self.set(SETTING_AUTOSTART, str(False)) + if ( + self._database.get_data_item_by_key(DatabaseSettings, SETTING_LOG_LEVEL) + is None + ): self.set(SETTING_LOG_LEVEL, "INFO") - if self._database.check_table_for_key(TABLE_SETTINGS, SETTING_PORT_API): - self.set(SETTING_PORT_API, 9170) - if self._database.check_table_for_key( - TABLE_SETTINGS, SETTING_ADDITIONAL_MEDIA_DIRECTORIES + if ( + self._database.get_data_item_by_key(DatabaseSettings, SETTING_PORT_API) + is None ): - self.set(SETTING_ADDITIONAL_MEDIA_DIRECTORIES, []) + self.set(SETTING_PORT_API, str(9170)) + if ( + self._database.get_data_item_by_key( + DatabaseSettings, SETTING_ADDITIONAL_MEDIA_DIRECTORIES + ) + is None + ): + self.set(SETTING_ADDITIONAL_MEDIA_DIRECTORIES, str([])) - def get_all(self) -> list[dict]: + def get_all(self) -> list[DatabaseData]: """Get settings""" - records = self._database.read_table(TABLE_SETTINGS).to_dict(orient="records") - for record in records: - record[COLUMN_VALUE] = convert_string_to_correct_type(record[COLUMN_VALUE]) + records = self._database.get_data(DatabaseSettings) + # for record in records: + # if record.value is not None: + # record.value = convert_string_to_correct_type(record.value) return records def get( self, key: str, - ) -> Union[bool, float, int, str, list, dict, None]: + ) -> Union[bool, float, int, str, list[Any], dict[str, Any], None]: """Get setting""" - record = self._database.read_table_by_key(TABLE_SETTINGS, key).to_dict( - orient="records" - ) - return ( - convert_string_to_correct_type(record[0]["value"]) - if record and len(record) > 0 - else None - ) + record = self._database.get_data_item_by_key(DatabaseSettings, key) + if record is None or record.value is None: + return None + return convert_string_to_correct_type(record.value) def get_secret( self, key: str, ) -> str: """Get secret""" - record = self._database.read_table_by_key(TABLE_SECRETS, key).to_dict( - orient="records" - ) - if not record or len(record) < 1: + record = self._database.get_data_item_by_key(DatabaseSecrets, key) + if record is None or record.value is None: raise KeyError(f"Secret {key} not found") - secret = record[0]["value"] - fernet = Fernet(self._encryption_key) # type: ignore + secret = record.value + fernet = Fernet(self._encryption_key) return fernet.decrypt(secret.encode()).decode() def set( self, key: str, - value: Union[bool, float, int, str, list, dict, None], + value: str, ) -> None: """Set setting""" - self._database.write(TABLE_SETTINGS, key, value) + self._database.update_data( + DatabaseSettings, + DatabaseSettings( + key=key, + value=value, + ), + ) def set_secret( self, @@ -135,8 +130,12 @@ def set_secret( value: str, ) -> None: """Set secret""" - fernet = Fernet(self._encryption_key) # type: ignore - - self._database.write( - TABLE_SECRETS, key, fernet.encrypt(value.encode()).decode() + fernet = Fernet(self._encryption_key) + + self._database.update_data( + DatabaseSecrets, + DatabaseSecrets( + key=key, + value=fernet.encrypt(value.encode()).decode(), + ), ) diff --git a/shared/systembridgeshared/update.py b/shared/systembridgeshared/update.py index 846a94130..b13a99ab9 100644 --- a/shared/systembridgeshared/update.py +++ b/shared/systembridgeshared/update.py @@ -8,10 +8,10 @@ import platform import subprocess import sys -import urllib.request from typing import Optional +import urllib.request -from systembridgeshared.base import Base +from .base import Base class Update(Base): diff --git a/shared/systembridgeshared/websocket_client.py b/shared/systembridgeshared/websocket_client.py index 9e5468496..b32ef8778 100644 --- a/shared/systembridgeshared/websocket_client.py +++ b/shared/systembridgeshared/websocket_client.py @@ -9,8 +9,8 @@ import aiohttp -from systembridgeshared.base import Base -from systembridgeshared.const import ( +from .base import Base +from .const import ( EVENT_API_KEY, EVENT_DATA, EVENT_EVENT, @@ -45,27 +45,27 @@ TYPE_POWER_SLEEP, TYPE_REGISTER_DATA_LISTENER, ) -from systembridgeshared.exceptions import ( +from .exceptions import ( AuthenticationException, BadMessageException, ConnectionClosedException, ConnectionErrorException, ) -from systembridgeshared.models.get_data import GetData -from systembridgeshared.models.keyboard_key import KeyboardKey -from systembridgeshared.models.keyboard_text import KeyboardText -from systembridgeshared.models.media_directories import MediaDirectories -from systembridgeshared.models.media_files import File as MediaFile, MediaFiles -from systembridgeshared.models.media_get_file import MediaGetFile -from systembridgeshared.models.media_get_files import MediaGetFiles -from systembridgeshared.models.notification import Notification -from systembridgeshared.models.open_path import OpenPath -from systembridgeshared.models.open_url import OpenUrl -from systembridgeshared.models.register_data_listener import RegisterDataListener -from systembridgeshared.models.request import Request -from systembridgeshared.models.response import Response -from systembridgeshared.models.update import Update -from systembridgeshared.settings import Settings +from .models.get_data import GetData +from .models.keyboard_key import KeyboardKey +from .models.keyboard_text import KeyboardText +from .models.media_directories import MediaDirectories +from .models.media_files import File as MediaFile, MediaFiles +from .models.media_get_file import MediaGetFile +from .models.media_get_files import MediaGetFiles +from .models.notification import Notification +from .models.open_path import OpenPath +from .models.open_url import OpenUrl +from .models.register_data_listener import RegisterDataListener +from .models.request import Request +from .models.response import Response +from .models.update import Update +from .settings import Settings class WebSocketClient(Base):