diff --git a/server/dashboard/.formatter.exs b/dashboard/.formatter.exs similarity index 100% rename from server/dashboard/.formatter.exs rename to dashboard/.formatter.exs diff --git a/server/dashboard/.gitignore b/dashboard/.gitignore similarity index 100% rename from server/dashboard/.gitignore rename to dashboard/.gitignore diff --git a/server/dashboard/README.md b/dashboard/README.md similarity index 100% rename from server/dashboard/README.md rename to dashboard/README.md diff --git a/server/dashboard/assets/css/app.css b/dashboard/assets/css/app.css similarity index 100% rename from server/dashboard/assets/css/app.css rename to dashboard/assets/css/app.css diff --git a/server/dashboard/assets/js/app.js b/dashboard/assets/js/app.js similarity index 100% rename from server/dashboard/assets/js/app.js rename to dashboard/assets/js/app.js diff --git a/server/dashboard/assets/tailwind.config.js b/dashboard/assets/tailwind.config.js similarity index 100% rename from server/dashboard/assets/tailwind.config.js rename to dashboard/assets/tailwind.config.js diff --git a/server/dashboard/assets/vendor/topbar.js b/dashboard/assets/vendor/topbar.js similarity index 100% rename from server/dashboard/assets/vendor/topbar.js rename to dashboard/assets/vendor/topbar.js diff --git a/server/dashboard/config/config.exs b/dashboard/config/config.exs similarity index 100% rename from server/dashboard/config/config.exs rename to dashboard/config/config.exs diff --git a/server/dashboard/config/dev.exs b/dashboard/config/dev.exs similarity index 100% rename from server/dashboard/config/dev.exs rename to dashboard/config/dev.exs diff --git a/server/dashboard/config/prod.exs b/dashboard/config/prod.exs similarity index 100% rename from server/dashboard/config/prod.exs rename to dashboard/config/prod.exs diff --git a/server/dashboard/config/runtime.exs b/dashboard/config/runtime.exs similarity index 100% rename from server/dashboard/config/runtime.exs rename to dashboard/config/runtime.exs diff --git a/server/dashboard/config/test.exs b/dashboard/config/test.exs similarity index 100% rename from server/dashboard/config/test.exs rename to dashboard/config/test.exs diff --git a/server/dashboard/lib/dashboard.ex b/dashboard/lib/dashboard.ex similarity index 100% rename from server/dashboard/lib/dashboard.ex rename to dashboard/lib/dashboard.ex diff --git a/server/dashboard/lib/dashboard/application.ex b/dashboard/lib/dashboard/application.ex similarity index 100% rename from server/dashboard/lib/dashboard/application.ex rename to dashboard/lib/dashboard/application.ex diff --git a/server/dashboard/lib/dashboard/mailer.ex b/dashboard/lib/dashboard/mailer.ex similarity index 100% rename from server/dashboard/lib/dashboard/mailer.ex rename to dashboard/lib/dashboard/mailer.ex diff --git a/server/dashboard/lib/dashboard_web.ex b/dashboard/lib/dashboard_web.ex similarity index 100% rename from server/dashboard/lib/dashboard_web.ex rename to dashboard/lib/dashboard_web.ex diff --git a/server/dashboard/lib/dashboard_web/components/core_components.ex b/dashboard/lib/dashboard_web/components/core_components.ex similarity index 100% rename from server/dashboard/lib/dashboard_web/components/core_components.ex rename to dashboard/lib/dashboard_web/components/core_components.ex diff --git a/server/dashboard/lib/dashboard_web/components/layouts.ex b/dashboard/lib/dashboard_web/components/layouts.ex similarity index 100% rename from server/dashboard/lib/dashboard_web/components/layouts.ex rename to dashboard/lib/dashboard_web/components/layouts.ex diff --git a/server/dashboard/lib/dashboard_web/components/layouts/app.html.heex b/dashboard/lib/dashboard_web/components/layouts/app.html.heex similarity index 100% rename from server/dashboard/lib/dashboard_web/components/layouts/app.html.heex rename to dashboard/lib/dashboard_web/components/layouts/app.html.heex diff --git a/server/dashboard/lib/dashboard_web/components/layouts/root.html.heex b/dashboard/lib/dashboard_web/components/layouts/root.html.heex similarity index 100% rename from server/dashboard/lib/dashboard_web/components/layouts/root.html.heex rename to dashboard/lib/dashboard_web/components/layouts/root.html.heex diff --git a/server/dashboard/lib/dashboard_web/controllers/error_html.ex b/dashboard/lib/dashboard_web/controllers/error_html.ex similarity index 100% rename from server/dashboard/lib/dashboard_web/controllers/error_html.ex rename to dashboard/lib/dashboard_web/controllers/error_html.ex diff --git a/server/dashboard/lib/dashboard_web/controllers/error_json.ex b/dashboard/lib/dashboard_web/controllers/error_json.ex similarity index 100% rename from server/dashboard/lib/dashboard_web/controllers/error_json.ex rename to dashboard/lib/dashboard_web/controllers/error_json.ex diff --git a/server/dashboard/lib/dashboard_web/controllers/page_controller.ex b/dashboard/lib/dashboard_web/controllers/page_controller.ex similarity index 100% rename from server/dashboard/lib/dashboard_web/controllers/page_controller.ex rename to dashboard/lib/dashboard_web/controllers/page_controller.ex diff --git a/server/dashboard/lib/dashboard_web/controllers/page_html.ex b/dashboard/lib/dashboard_web/controllers/page_html.ex similarity index 100% rename from server/dashboard/lib/dashboard_web/controllers/page_html.ex rename to dashboard/lib/dashboard_web/controllers/page_html.ex diff --git a/server/dashboard/lib/dashboard_web/controllers/page_html/home.html.heex b/dashboard/lib/dashboard_web/controllers/page_html/home.html.heex similarity index 100% rename from server/dashboard/lib/dashboard_web/controllers/page_html/home.html.heex rename to dashboard/lib/dashboard_web/controllers/page_html/home.html.heex diff --git a/server/dashboard/lib/dashboard_web/endpoint.ex b/dashboard/lib/dashboard_web/endpoint.ex similarity index 100% rename from server/dashboard/lib/dashboard_web/endpoint.ex rename to dashboard/lib/dashboard_web/endpoint.ex diff --git a/server/dashboard/lib/dashboard_web/gettext.ex b/dashboard/lib/dashboard_web/gettext.ex similarity index 100% rename from server/dashboard/lib/dashboard_web/gettext.ex rename to dashboard/lib/dashboard_web/gettext.ex diff --git a/server/dashboard/lib/dashboard_web/router.ex b/dashboard/lib/dashboard_web/router.ex similarity index 100% rename from server/dashboard/lib/dashboard_web/router.ex rename to dashboard/lib/dashboard_web/router.ex diff --git a/server/dashboard/lib/dashboard_web/telemetry.ex b/dashboard/lib/dashboard_web/telemetry.ex similarity index 100% rename from server/dashboard/lib/dashboard_web/telemetry.ex rename to dashboard/lib/dashboard_web/telemetry.ex diff --git a/server/dashboard/mix.exs b/dashboard/mix.exs similarity index 100% rename from server/dashboard/mix.exs rename to dashboard/mix.exs diff --git a/server/dashboard/mix.lock b/dashboard/mix.lock similarity index 100% rename from server/dashboard/mix.lock rename to dashboard/mix.lock diff --git a/server/dashboard/priv/gettext/en/LC_MESSAGES/errors.po b/dashboard/priv/gettext/en/LC_MESSAGES/errors.po similarity index 100% rename from server/dashboard/priv/gettext/en/LC_MESSAGES/errors.po rename to dashboard/priv/gettext/en/LC_MESSAGES/errors.po diff --git a/server/dashboard/priv/gettext/errors.pot b/dashboard/priv/gettext/errors.pot similarity index 100% rename from server/dashboard/priv/gettext/errors.pot rename to dashboard/priv/gettext/errors.pot diff --git a/server/dashboard/priv/static/favicon.ico b/dashboard/priv/static/favicon.ico similarity index 100% rename from server/dashboard/priv/static/favicon.ico rename to dashboard/priv/static/favicon.ico diff --git a/server/dashboard/priv/static/images/logo.svg b/dashboard/priv/static/images/logo.svg similarity index 100% rename from server/dashboard/priv/static/images/logo.svg rename to dashboard/priv/static/images/logo.svg diff --git a/server/dashboard/priv/static/robots.txt b/dashboard/priv/static/robots.txt similarity index 100% rename from server/dashboard/priv/static/robots.txt rename to dashboard/priv/static/robots.txt diff --git a/server/dashboard/test/dashboard_web/controllers/error_html_test.exs b/dashboard/test/dashboard_web/controllers/error_html_test.exs similarity index 100% rename from server/dashboard/test/dashboard_web/controllers/error_html_test.exs rename to dashboard/test/dashboard_web/controllers/error_html_test.exs diff --git a/server/dashboard/test/dashboard_web/controllers/error_json_test.exs b/dashboard/test/dashboard_web/controllers/error_json_test.exs similarity index 100% rename from server/dashboard/test/dashboard_web/controllers/error_json_test.exs rename to dashboard/test/dashboard_web/controllers/error_json_test.exs diff --git a/server/dashboard/test/dashboard_web/controllers/page_controller_test.exs b/dashboard/test/dashboard_web/controllers/page_controller_test.exs similarity index 100% rename from server/dashboard/test/dashboard_web/controllers/page_controller_test.exs rename to dashboard/test/dashboard_web/controllers/page_controller_test.exs diff --git a/server/dashboard/test/support/conn_case.ex b/dashboard/test/support/conn_case.ex similarity index 100% rename from server/dashboard/test/support/conn_case.ex rename to dashboard/test/support/conn_case.ex diff --git a/server/dashboard/test/test_helper.exs b/dashboard/test/test_helper.exs similarity index 100% rename from server/dashboard/test/test_helper.exs rename to dashboard/test/test_helper.exs diff --git a/dev-config/.HA_VERSION b/dev-config/.HA_VERSION deleted file mode 100644 index 434122a..0000000 --- a/dev-config/.HA_VERSION +++ /dev/null @@ -1 +0,0 @@ -2025.7.4 \ No newline at end of file diff --git a/dev-config/automations.yaml b/dev-config/automations.yaml deleted file mode 100644 index dfe1128..0000000 --- a/dev-config/automations.yaml +++ /dev/null @@ -1,2 +0,0 @@ -# Development automations for testing Systant integration -[] \ No newline at end of file diff --git a/dev-config/blueprints/automation/homeassistant/motion_light.yaml b/dev-config/blueprints/automation/homeassistant/motion_light.yaml deleted file mode 100644 index 1190070..0000000 --- a/dev-config/blueprints/automation/homeassistant/motion_light.yaml +++ /dev/null @@ -1,58 +0,0 @@ -blueprint: - name: Motion-activated Light - description: Turn on a light when motion is detected. - domain: automation - source_url: https://github.com/home-assistant/core/blob/dev/homeassistant/components/automation/blueprints/motion_light.yaml - author: Home Assistant - input: - motion_entity: - name: Motion Sensor - selector: - entity: - filter: - - device_class: occupancy - domain: binary_sensor - - device_class: motion - domain: binary_sensor - light_target: - name: Light - selector: - target: - entity: - domain: light - no_motion_wait: - name: Wait time - description: Time to leave the light on after last motion is detected. - default: 120 - selector: - number: - min: 0 - max: 3600 - unit_of_measurement: seconds - -# If motion is detected within the delay, -# we restart the script. -mode: restart -max_exceeded: silent - -triggers: - trigger: state - entity_id: !input motion_entity - from: "off" - to: "on" - -actions: - - alias: "Turn on the light" - action: light.turn_on - target: !input light_target - - alias: "Wait until there is no motion from device" - wait_for_trigger: - trigger: state - entity_id: !input motion_entity - from: "on" - to: "off" - - alias: "Wait the number of seconds that has been set" - delay: !input no_motion_wait - - alias: "Turn off the light" - action: light.turn_off - target: !input light_target diff --git a/dev-config/blueprints/automation/homeassistant/notify_leaving_zone.yaml b/dev-config/blueprints/automation/homeassistant/notify_leaving_zone.yaml deleted file mode 100644 index e072aad..0000000 --- a/dev-config/blueprints/automation/homeassistant/notify_leaving_zone.yaml +++ /dev/null @@ -1,50 +0,0 @@ -blueprint: - name: Zone Notification - description: Send a notification to a device when a person leaves a specific zone. - domain: automation - source_url: https://github.com/home-assistant/core/blob/dev/homeassistant/components/automation/blueprints/notify_leaving_zone.yaml - author: Home Assistant - input: - person_entity: - name: Person - selector: - entity: - filter: - domain: person - zone_entity: - name: Zone - selector: - entity: - filter: - domain: zone - notify_device: - name: Device to notify - description: Device needs to run the official Home Assistant app to receive notifications. - selector: - device: - filter: - integration: mobile_app - -triggers: - trigger: state - entity_id: !input person_entity - -variables: - zone_entity: !input zone_entity - # This is the state of the person when it's in this zone. - zone_state: "{{ states[zone_entity].name }}" - person_entity: !input person_entity - person_name: "{{ states[person_entity].name }}" - -conditions: - condition: template - # The first case handles leaving the Home zone which has a special state when zoning called 'home'. - # The second case handles leaving all other zones. - value_template: "{{ zone_entity == 'zone.home' and trigger.from_state.state == 'home' and trigger.to_state.state != 'home' or trigger.from_state.state == zone_state and trigger.to_state.state != zone_state }}" - -actions: - - alias: "Notify that a person has left the zone" - domain: mobile_app - type: notify - device_id: !input notify_device - message: "{{ person_name }} has left {{ zone_state }}" diff --git a/dev-config/blueprints/script/homeassistant/confirmable_notification.yaml b/dev-config/blueprints/script/homeassistant/confirmable_notification.yaml deleted file mode 100644 index 0106a4e..0000000 --- a/dev-config/blueprints/script/homeassistant/confirmable_notification.yaml +++ /dev/null @@ -1,86 +0,0 @@ -blueprint: - name: Confirmable Notification - description: >- - A script that sends an actionable notification with a confirmation before - running the specified action. - domain: script - source_url: https://github.com/home-assistant/core/blob/master/homeassistant/components/script/blueprints/confirmable_notification.yaml - author: Home Assistant - input: - notify_device: - name: Device to notify - description: Device needs to run the official Home Assistant app to receive notifications. - selector: - device: - filter: - integration: mobile_app - title: - name: "Title" - description: "The title of the button shown in the notification." - default: "" - selector: - text: - message: - name: "Message" - description: "The message body" - selector: - text: - confirm_text: - name: "Confirmation Text" - description: "Text to show on the confirmation button" - default: "Confirm" - selector: - text: - confirm_action: - name: "Confirmation Action" - description: "Action to run when notification is confirmed" - default: [] - selector: - action: - dismiss_text: - name: "Dismiss Text" - description: "Text to show on the dismiss button" - default: "Dismiss" - selector: - text: - dismiss_action: - name: "Dismiss Action" - description: "Action to run when notification is dismissed" - default: [] - selector: - action: - -mode: restart - -sequence: - - alias: "Set up variables" - variables: - action_confirm: "{{ 'CONFIRM_' ~ context.id }}" - action_dismiss: "{{ 'DISMISS_' ~ context.id }}" - - alias: "Send notification" - domain: mobile_app - type: notify - device_id: !input notify_device - title: !input title - message: !input message - data: - actions: - - action: "{{ action_confirm }}" - title: !input confirm_text - - action: "{{ action_dismiss }}" - title: !input dismiss_text - - alias: "Awaiting response" - wait_for_trigger: - - trigger: event - event_type: mobile_app_notification_action - event_data: - action: "{{ action_confirm }}" - - trigger: event - event_type: mobile_app_notification_action - event_data: - action: "{{ action_dismiss }}" - - choose: - - conditions: "{{ wait.trigger.event.data.action == action_confirm }}" - sequence: !input confirm_action - - conditions: "{{ wait.trigger.event.data.action == action_dismiss }}" - sequence: !input dismiss_action diff --git a/dev-config/configuration.yaml b/dev-config/configuration.yaml deleted file mode 100644 index a60be7c..0000000 --- a/dev-config/configuration.yaml +++ /dev/null @@ -1,30 +0,0 @@ -# Home Assistant Development Configuration for Systant Integration - -homeassistant: - name: Systant Development - latitude: 0 - longitude: 0 - elevation: 0 - unit_system: metric - time_zone: UTC - -# Basic required components only -http: - server_host: 0.0.0.0 - server_port: 8123 - -# Enable API -api: - -# Enable logging -logger: - default: info - logs: - custom_components.systant: debug - -# MQTT Configuration (for our integration testing) -mqtt: - - broker: mqtt.home - port: 1883 - discovery: true - discovery_prefix: homeassistant \ No newline at end of file diff --git a/dev-config/scenes.yaml b/dev-config/scenes.yaml deleted file mode 100644 index 491c356..0000000 --- a/dev-config/scenes.yaml +++ /dev/null @@ -1,2 +0,0 @@ -# Development scenes for testing Systant integration -[] \ No newline at end of file diff --git a/dev-config/scripts.yaml b/dev-config/scripts.yaml deleted file mode 100644 index dcff63e..0000000 --- a/dev-config/scripts.yaml +++ /dev/null @@ -1,2 +0,0 @@ -# Development scripts for testing Systant integration -{} \ No newline at end of file diff --git a/home-assistant-integration/custom_components/systant/__init__.py b/home-assistant-integration/custom_components/systant/__init__.py deleted file mode 100644 index 47e0f8c..0000000 --- a/home-assistant-integration/custom_components/systant/__init__.py +++ /dev/null @@ -1,42 +0,0 @@ -"""The Systant System Monitor integration.""" -from __future__ import annotations - -import logging - -from homeassistant.config_entries import ConfigEntry -from homeassistant.core import HomeAssistant -from homeassistant.helpers.typing import ConfigType - -from .const import DOMAIN - -_LOGGER = logging.getLogger(__name__) - -PLATFORMS = ["sensor", "binary_sensor"] - - -async def async_setup(hass: HomeAssistant, config: ConfigType) -> bool: - """Set up the Systant integration.""" - return True - - -async def async_setup_entry(hass: HomeAssistant, entry: ConfigEntry) -> bool: - """Set up Systant from a config entry.""" - hass.data.setdefault(DOMAIN, {}) - - # Store the config entry data - hass.data[DOMAIN][entry.entry_id] = entry.data - - # Forward the setup to the sensor platform - await hass.config_entries.async_forward_entry_setups(entry, PLATFORMS) - - return True - - -async def async_unload_entry(hass: HomeAssistant, entry: ConfigEntry) -> bool: - """Unload a config entry.""" - unload_ok = await hass.config_entries.async_unload_platforms(entry, PLATFORMS) - - if unload_ok: - hass.data[DOMAIN].pop(entry.entry_id) - - return unload_ok \ No newline at end of file diff --git a/home-assistant-integration/custom_components/systant/config_flow.py b/home-assistant-integration/custom_components/systant/config_flow.py deleted file mode 100644 index e985ec5..0000000 --- a/home-assistant-integration/custom_components/systant/config_flow.py +++ /dev/null @@ -1,63 +0,0 @@ -"""Config flow for Systant integration.""" -from __future__ import annotations - -import logging -from typing import Any - -import voluptuous as vol - -from homeassistant import config_entries -from homeassistant.core import HomeAssistant -from homeassistant.data_entry_flow import FlowResult -from homeassistant.exceptions import HomeAssistantError - -from .const import DOMAIN - -_LOGGER = logging.getLogger(__name__) - -STEP_USER_DATA_SCHEMA = vol.Schema( - { - vol.Required("mqtt_prefix", default="systant"): str, - } -) - - -class ConfigFlow(config_entries.ConfigFlow, domain=DOMAIN): - """Handle a config flow for Systant.""" - - VERSION = 1 - - async def async_step_user( - self, user_input: dict[str, Any] | None = None - ) -> FlowResult: - """Handle the initial step.""" - errors: dict[str, str] = {} - - if user_input is not None: - try: - await self.async_set_unique_id(user_input["mqtt_prefix"]) - self._abort_if_unique_id_configured() - - return self.async_create_entry( - title=f"Systant ({user_input['mqtt_prefix']})", - data=user_input, - ) - except CannotConnect: - errors["base"] = "cannot_connect" - except InvalidConfig: - errors["base"] = "invalid_config" - except Exception: # pylint: disable=broad-except - _LOGGER.exception("Unexpected exception") - errors["base"] = "unknown" - - return self.async_show_form( - step_id="user", data_schema=STEP_USER_DATA_SCHEMA, errors=errors - ) - - -class CannotConnect(HomeAssistantError): - """Error to indicate we cannot connect.""" - - -class InvalidConfig(HomeAssistantError): - """Error to indicate there is invalid configuration.""" \ No newline at end of file diff --git a/home-assistant-integration/custom_components/systant/const.py b/home-assistant-integration/custom_components/systant/const.py deleted file mode 100644 index 4c4a4fd..0000000 --- a/home-assistant-integration/custom_components/systant/const.py +++ /dev/null @@ -1,11 +0,0 @@ -"""Constants for the Systant integration.""" - -DOMAIN = "systant" - -# MQTT topics -MQTT_STATS_TOPIC = "systant/+/stats" -MQTT_COMMANDS_TOPIC = "systant/+/commands" - -# Default configuration -DEFAULT_NAME = "Systant" -DEFAULT_MQTT_PREFIX = "systant" \ No newline at end of file diff --git a/home-assistant-integration/custom_components/systant/manifest.json b/home-assistant-integration/custom_components/systant/manifest.json deleted file mode 100644 index 3c8e9b0..0000000 --- a/home-assistant-integration/custom_components/systant/manifest.json +++ /dev/null @@ -1,13 +0,0 @@ -{ - "domain": "systant", - "name": "Systant System Monitor", - "codeowners": ["@ryanpandya"], - "config_flow": true, - "dependencies": ["mqtt"], - "documentation": "https://github.com/ryanpandya/systant", - "integration_type": "service", - "iot_class": "local_push", - "mqtt": ["systant/+/stats", "systant/+/commands"], - "requirements": [], - "version": "0.1.0" -} \ No newline at end of file diff --git a/home-assistant-integration/custom_components/systant/sensor.py b/home-assistant-integration/custom_components/systant/sensor.py deleted file mode 100644 index 8fae732..0000000 --- a/home-assistant-integration/custom_components/systant/sensor.py +++ /dev/null @@ -1,161 +0,0 @@ -"""Support for Systant sensors.""" -from __future__ import annotations - -import json -import logging -from typing import Any - -from homeassistant.components import mqtt -from homeassistant.components.sensor import SensorEntity -from homeassistant.config_entries import ConfigEntry -from homeassistant.core import HomeAssistant, callback -from homeassistant.helpers.entity_platform import AddEntitiesCallback -from homeassistant.util import dt as dt_util - -from .const import DOMAIN, MQTT_STATS_TOPIC - -_LOGGER = logging.getLogger(__name__) - - -async def async_setup_entry( - hass: HomeAssistant, - config_entry: ConfigEntry, - async_add_entities: AddEntitiesCallback, -) -> None: - """Set up Systant sensors from config entry.""" - mqtt_prefix = config_entry.data.get("mqtt_prefix", "systant") - - # Create a coordinator that will manage discovered hosts - coordinator = SystantCoordinator(hass, mqtt_prefix) - await coordinator.async_setup() - - # Store coordinator in hass data - hass.data[DOMAIN][config_entry.entry_id] = coordinator - - -class SystantCoordinator: - """Coordinate discovery and management of Systant hosts.""" - - def __init__(self, hass: HomeAssistant, mqtt_prefix: str) -> None: - """Initialize the coordinator.""" - self.hass = hass - self.mqtt_prefix = mqtt_prefix - self.hosts: dict[str, SystantHost] = {} - self._unsubscribe_mqtt = None - - async def async_setup(self) -> None: - """Set up MQTT subscription for host discovery.""" - topic = f"{self.mqtt_prefix}/+/stats" - - self._unsubscribe_mqtt = await mqtt.async_subscribe( - self.hass, topic, self._message_received, 0 - ) - - _LOGGER.info("Subscribed to %s for Systant host discovery", topic) - - @callback - def _message_received(self, message: mqtt.ReceiveMessage) -> None: - """Handle received MQTT message.""" - try: - # Extract hostname from topic: systant/hostname/stats - topic_parts = message.topic.split("/") - if len(topic_parts) != 3: - return - - hostname = topic_parts[1] - - # Parse message payload - payload = json.loads(message.payload) - - # Create or update host - if hostname not in self.hosts: - _LOGGER.info("Discovered new Systant host: %s", hostname) - self.hosts[hostname] = SystantHost(self.hass, hostname, self.mqtt_prefix) - - # Update host with new data - self.hosts[hostname].update_data(payload) - - except json.JSONDecodeError: - _LOGGER.warning("Invalid JSON received from %s", message.topic) - except Exception as err: - _LOGGER.error("Error processing message from %s: %s", message.topic, err) - - async def async_unload(self) -> None: - """Unload the coordinator.""" - if self._unsubscribe_mqtt: - self._unsubscribe_mqtt() - - -class SystantHost: - """Represent a Systant host with its sensors.""" - - def __init__(self, hass: HomeAssistant, hostname: str, mqtt_prefix: str) -> None: - """Initialize the host.""" - self.hass = hass - self.hostname = hostname - self.mqtt_prefix = mqtt_prefix - self.sensors: list[SystantSensor] = [] - self.last_seen = None - - # Create sensors for this host - self._create_sensors() - - def _create_sensors(self) -> None: - """Create sensor entities for this host.""" - # Last seen sensor - last_seen_sensor = SystantLastSeenSensor(self.hostname, self.mqtt_prefix) - self.sensors.append(last_seen_sensor) - - # Add sensors to Home Assistant - self.hass.async_create_task( - self.hass.helpers.entity_platform.async_add_entities( - self.sensors, update_before_add=True - ) - ) - - def update_data(self, data: dict[str, Any]) -> None: - """Update sensors with new data.""" - self.last_seen = dt_util.utcnow() - - # Update all sensors - for sensor in self.sensors: - sensor.update_from_data(data) - - -class SystantSensor(SensorEntity): - """Base class for Systant sensors.""" - - def __init__(self, hostname: str, mqtt_prefix: str, sensor_type: str) -> None: - """Initialize the sensor.""" - self._hostname = hostname - self._mqtt_prefix = mqtt_prefix - self._sensor_type = sensor_type - self._attr_unique_id = f"{mqtt_prefix}_{hostname}_{sensor_type}" - self._attr_name = f"Systant {hostname} {sensor_type.replace('_', ' ').title()}" - self._attr_device_info = { - "identifiers": {(DOMAIN, f"{mqtt_prefix}_{hostname}")}, - "name": f"Systant {hostname}", - "manufacturer": "Systant", - "model": "System Monitor", - "via_device": (DOMAIN, mqtt_prefix), - } - - def update_from_data(self, data: dict[str, Any]) -> None: - """Update sensor from MQTT data.""" - # To be implemented by subclasses - pass - - -class SystantLastSeenSensor(SystantSensor): - """Sensor for last seen timestamp.""" - - def __init__(self, hostname: str, mqtt_prefix: str) -> None: - """Initialize the last seen sensor.""" - super().__init__(hostname, mqtt_prefix, "last_seen") - self._attr_device_class = "timestamp" - self._attr_icon = "mdi:clock-outline" - - def update_from_data(self, data: dict[str, Any]) -> None: - """Update with current timestamp.""" - self._attr_native_value = dt_util.utcnow() - self.async_write_ha_state() \ No newline at end of file