From 8bbf91f25f0653cc8a05cd9abb07573526b587fc Mon Sep 17 00:00:00 2001 From: ryan Date: Fri, 8 Aug 2025 21:17:46 -0700 Subject: [PATCH] Change Home Assistant device name and manufacturer/model --- server/lib/systant/ha_discovery.ex | 229 +++++++++++++++++++++++------ 1 file changed, 182 insertions(+), 47 deletions(-) diff --git a/server/lib/systant/ha_discovery.ex b/server/lib/systant/ha_discovery.ex index f23ff41..530ff27 100644 --- a/server/lib/systant/ha_discovery.ex +++ b/server/lib/systant/ha_discovery.ex @@ -1,17 +1,17 @@ defmodule Systant.HaDiscovery do @moduledoc """ Home Assistant MQTT Discovery integration for Systant. - + Publishes device and entity discovery configurations to Home Assistant via MQTT following the HA discovery protocol. - + Discovery topic format: homeassistant////config """ require Logger @manufacturer "Systant" - @model "System Monitor" + @model "Systant" @doc """ Publish all discovery configurations for a host @@ -19,17 +19,17 @@ defmodule Systant.HaDiscovery do def publish_discovery(client_pid, hostname, config \\ nil) do app_config = config || Systant.Config.load_config() ha_config = Systant.Config.get(app_config, ["homeassistant"]) || %{} - + if ha_config["discovery_enabled"] != false do discovery_prefix = ha_config["discovery_prefix"] || "homeassistant" device_config = build_device_config(hostname) - + # Publish device discovery first publish_device_discovery(client_pid, hostname, device_config, discovery_prefix) - + # Publish sensor discoveries publish_sensor_discoveries(client_pid, hostname, device_config, discovery_prefix) - + Logger.info("Published Home Assistant discovery for #{hostname}") else Logger.info("Home Assistant discovery disabled in configuration") @@ -43,15 +43,15 @@ defmodule Systant.HaDiscovery do app_config = config || Systant.Config.load_config() ha_config = Systant.Config.get(app_config, ["homeassistant"]) || %{} discovery_prefix = ha_config["discovery_prefix"] || "homeassistant" - + # Remove by publishing empty payloads to discovery topics sensors = get_sensor_definitions(hostname) - + Enum.each(sensors, fn {component, object_id, _config} -> topic = "#{discovery_prefix}/#{component}/#{hostname}/#{object_id}/config" Tortoise.publish(client_pid, topic, "", retain: true) end) - + Logger.info("Removed Home Assistant discovery for #{hostname}") end @@ -63,21 +63,21 @@ defmodule Systant.HaDiscovery do device: device_config, components: build_all_components(hostname, device_config) } - + topic = "#{discovery_prefix}/device/#{hostname}/config" payload = Jason.encode!(components_config) - + Tortoise.publish(client_pid, topic, payload, retain: true) end defp publish_sensor_discoveries(client_pid, hostname, device_config, discovery_prefix) do sensors = get_sensor_definitions(hostname) - + Enum.each(sensors, fn {component, object_id, config} -> full_config = Map.merge(config, %{device: device_config}) topic = "#{discovery_prefix}/#{component}/#{hostname}/#{object_id}/config" payload = Jason.encode!(full_config) - + Tortoise.publish(client_pid, topic, payload, retain: true) end) end @@ -85,7 +85,7 @@ defmodule Systant.HaDiscovery do defp build_device_config(hostname) do %{ identifiers: ["systant_#{hostname}"], - name: "Systant #{hostname}", + name: hostname |> String.capitalize(), manufacturer: @manufacturer, model: @model, sw_version: Application.spec(:systant, :vsn) |> to_string() @@ -102,61 +102,196 @@ defmodule Systant.HaDiscovery do defp get_sensor_definitions(hostname) do base_topic = "systant/#{hostname}/stats" - + [ # CPU Sensors - {"sensor", "cpu_load_1m", build_sensor_config("CPU Load 1m", "#{base_topic}", "cpu.avg1", "load", "mdi:speedometer")}, - {"sensor", "cpu_load_5m", build_sensor_config("CPU Load 5m", "#{base_topic}", "cpu.avg5", "load", "mdi:speedometer")}, - {"sensor", "cpu_load_15m", build_sensor_config("CPU Load 15m", "#{base_topic}", "cpu.avg15", "load", "mdi:speedometer")}, - + {"sensor", "cpu_load_1m", + build_sensor_config("CPU Load 1m", "#{base_topic}", "cpu.avg1", "load", "mdi:speedometer")}, + {"sensor", "cpu_load_5m", + build_sensor_config("CPU Load 5m", "#{base_topic}", "cpu.avg5", "load", "mdi:speedometer")}, + {"sensor", "cpu_load_15m", + build_sensor_config( + "CPU Load 15m", + "#{base_topic}", + "cpu.avg15", + "load", + "mdi:speedometer" + )}, + # Memory Sensors - {"sensor", "memory_used_percent", build_sensor_config("Memory Used", "#{base_topic}", "memory.used_percent", "%", "mdi:memory")}, - {"sensor", "memory_used_gb", build_sensor_config("Memory Used GB", "#{base_topic}", "memory.used_kb", "GB", "mdi:memory", "{{ (value_json.memory.used_kb | float / 1024 / 1024) | round(2) }}")}, - {"sensor", "memory_total_gb", build_sensor_config("Memory Total GB", "#{base_topic}", "memory.total_kb", "GB", "mdi:memory", "{{ (value_json.memory.total_kb | float / 1024 / 1024) | round(2) }}")}, - + {"sensor", "memory_used_percent", + build_sensor_config( + "Memory Used", + "#{base_topic}", + "memory.used_percent", + "%", + "mdi:memory" + )}, + {"sensor", "memory_used_gb", + build_sensor_config( + "Memory Used GB", + "#{base_topic}", + "memory.used_kb", + "GB", + "mdi:memory", + "{{ (value_json.memory.used_kb | float / 1024 / 1024) | round(2) }}" + )}, + {"sensor", "memory_total_gb", + build_sensor_config( + "Memory Total GB", + "#{base_topic}", + "memory.total_kb", + "GB", + "mdi:memory", + "{{ (value_json.memory.total_kb | float / 1024 / 1024) | round(2) }}" + )}, + # System Sensors - {"sensor", "uptime_hours", build_sensor_config("Uptime", "#{base_topic}", "system.uptime_seconds", "h", "mdi:clock-outline", "{{ (value_json.system.uptime_seconds | float / 3600) | round(1) }}")}, - {"sensor", "kernel_version", build_sensor_config("Kernel Version", "#{base_topic}", "system.kernel_version", nil, "mdi:linux")}, - + {"sensor", "uptime_hours", + build_sensor_config( + "Uptime", + "#{base_topic}", + "system.uptime_seconds", + "h", + "mdi:clock-outline", + "{{ (value_json.system.uptime_seconds | float / 3600) | round(1) }}" + )}, + {"sensor", "kernel_version", + build_sensor_config( + "Kernel Version", + "#{base_topic}", + "system.kernel_version", + nil, + "mdi:linux" + )}, + # Temperature Sensors - {"sensor", "cpu_temperature", build_sensor_config("CPU Temperature", "#{base_topic}", "temperature.cpu", "°C", "mdi:thermometer")}, - + {"sensor", "cpu_temperature", + build_sensor_config( + "CPU Temperature", + "#{base_topic}", + "temperature.cpu", + "°C", + "mdi:thermometer" + )}, + # GPU Sensors - NVIDIA - {"sensor", "gpu_nvidia_utilization", build_sensor_config("NVIDIA GPU Utilization", "#{base_topic}", "gpu.nvidia[0].utilization_percent", "%", "mdi:expansion-card", "{{ value_json.gpu.nvidia[0].utilization_percent if value_json.gpu.nvidia and value_json.gpu.nvidia|length > 0 else 0 }}")}, - {"sensor", "gpu_nvidia_temperature", build_sensor_config("NVIDIA GPU Temperature", "#{base_topic}", "gpu.nvidia[0].temperature_c", "°C", "mdi:thermometer", "{{ value_json.gpu.nvidia[0].temperature_c if value_json.gpu.nvidia and value_json.gpu.nvidia|length > 0 else none }}")}, - {"sensor", "gpu_nvidia_memory", build_sensor_config("NVIDIA GPU Memory", "#{base_topic}", "gpu.nvidia[0].memory_used_mb", "MB", "mdi:memory", "{{ value_json.gpu.nvidia[0].memory_used_mb if value_json.gpu.nvidia and value_json.gpu.nvidia|length > 0 else none }}")}, - + {"sensor", "gpu_nvidia_utilization", + build_sensor_config( + "NVIDIA GPU Utilization", + "#{base_topic}", + "gpu.nvidia[0].utilization_percent", + "%", + "mdi:expansion-card", + "{{ value_json.gpu.nvidia[0].utilization_percent if value_json.gpu.nvidia and value_json.gpu.nvidia|length > 0 else 0 }}" + )}, + {"sensor", "gpu_nvidia_temperature", + build_sensor_config( + "NVIDIA GPU Temperature", + "#{base_topic}", + "gpu.nvidia[0].temperature_c", + "°C", + "mdi:thermometer", + "{{ value_json.gpu.nvidia[0].temperature_c if value_json.gpu.nvidia and value_json.gpu.nvidia|length > 0 else none }}" + )}, + {"sensor", "gpu_nvidia_memory", + build_sensor_config( + "NVIDIA GPU Memory", + "#{base_topic}", + "gpu.nvidia[0].memory_used_mb", + "MB", + "mdi:memory", + "{{ value_json.gpu.nvidia[0].memory_used_mb if value_json.gpu.nvidia and value_json.gpu.nvidia|length > 0 else none }}" + )}, + # GPU Sensors - AMD - {"sensor", "gpu_amd_utilization", build_sensor_config("AMD GPU Utilization", "#{base_topic}", "gpu.amd[0].utilization_percent", "%", "mdi:expansion-card", "{{ value_json.gpu.amd[0].utilization_percent if value_json.gpu.amd and value_json.gpu.amd|length > 0 else 0 }}")}, - {"sensor", "gpu_amd_temperature", build_sensor_config("AMD GPU Temperature", "#{base_topic}", "gpu.amd[0].temperature_c", "°C", "mdi:thermometer", "{{ value_json.gpu.amd[0].temperature_c if value_json.gpu.amd and value_json.gpu.amd|length > 0 else none }}")}, - + {"sensor", "gpu_amd_utilization", + build_sensor_config( + "AMD GPU Utilization", + "#{base_topic}", + "gpu.amd[0].utilization_percent", + "%", + "mdi:expansion-card", + "{{ value_json.gpu.amd[0].utilization_percent if value_json.gpu.amd and value_json.gpu.amd|length > 0 else 0 }}" + )}, + {"sensor", "gpu_amd_temperature", + build_sensor_config( + "AMD GPU Temperature", + "#{base_topic}", + "gpu.amd[0].temperature_c", + "°C", + "mdi:thermometer", + "{{ value_json.gpu.amd[0].temperature_c if value_json.gpu.amd and value_json.gpu.amd|length > 0 else none }}" + )}, + # Disk Sensors - Main filesystem usage - {"sensor", "disk_root_usage", build_sensor_config("Root Disk Usage", "#{base_topic}", "disk.disks", "%", "mdi:harddisk", "{{ (value_json.disk.disks | selectattr('mounted_on', 'equalto', '/') | list | first).use_percent if value_json.disk.disks else 0 }}")}, - {"sensor", "disk_home_usage", build_sensor_config("Home Disk Usage", "#{base_topic}", "disk.disks", "%", "mdi:harddisk", "{{ (value_json.disk.disks | selectattr('mounted_on', 'equalto', '/home') | list | first).use_percent if (value_json.disk.disks | selectattr('mounted_on', 'equalto', '/home') | list) else 0 }}")}, - + {"sensor", "disk_root_usage", + build_sensor_config( + "Root Disk Usage", + "#{base_topic}", + "disk.disks", + "%", + "mdi:harddisk", + "{{ (value_json.disk.disks | selectattr('mounted_on', 'equalto', '/') | list | first).use_percent if value_json.disk.disks else 0 }}" + )}, + {"sensor", "disk_home_usage", + build_sensor_config( + "Home Disk Usage", + "#{base_topic}", + "disk.disks", + "%", + "mdi:harddisk", + "{{ (value_json.disk.disks | selectattr('mounted_on', 'equalto', '/home') | list | first).use_percent if (value_json.disk.disks | selectattr('mounted_on', 'equalto', '/home') | list) else 0 }}" + )}, + # Network Sensors - Primary interface throughput - {"sensor", "network_rx_throughput", build_sensor_config("Network RX Throughput", "#{base_topic}", "network.rx_throughput", "MB/s", "mdi:download-network", "{{ (value_json.network[0].rx_throughput_bps | float / 1024 / 1024) | round(2) if value_json.network and value_json.network|length > 0 else 0 }}")}, - {"sensor", "network_tx_throughput", build_sensor_config("Network TX Throughput", "#{base_topic}", "network.tx_throughput", "MB/s", "mdi:upload-network", "{{ (value_json.network[0].tx_throughput_bps | float / 1024 / 1024) | round(2) if value_json.network and value_json.network|length > 0 else 0 }}")}, - + {"sensor", "network_rx_throughput", + build_sensor_config( + "Network RX Throughput", + "#{base_topic}", + "network.rx_throughput", + "MB/s", + "mdi:download-network", + "{{ (value_json.network[0].rx_throughput_bps | float / 1024 / 1024) | round(2) if value_json.network and value_json.network|length > 0 else 0 }}" + )}, + {"sensor", "network_tx_throughput", + build_sensor_config( + "Network TX Throughput", + "#{base_topic}", + "network.tx_throughput", + "MB/s", + "mdi:upload-network", + "{{ (value_json.network[0].tx_throughput_bps | float / 1024 / 1024) | round(2) if value_json.network and value_json.network|length > 0 else 0 }}" + )}, + # Binary Sensors for status - {"binary_sensor", "system_online", build_binary_sensor_config("System Online", "#{base_topic}", "mdi:server", "connectivity")} + {"binary_sensor", "system_online", + build_binary_sensor_config("System Online", "#{base_topic}", "mdi:server", "connectivity")} ] end - defp build_sensor_config(name, state_topic, value_template_path, unit, icon, custom_template \\ nil) do + defp build_sensor_config( + name, + state_topic, + value_template_path, + unit, + icon, + custom_template \\ nil + ) do base_config = %{ name: name, state_topic: state_topic, value_template: custom_template || "{{ value_json.#{value_template_path} }}", icon: icon, - unique_id: "systant_#{String.replace(state_topic, "/", "_")}_#{String.replace(value_template_path, ".", "_")}", + unique_id: + "systant_#{String.replace(state_topic, "/", "_")}_#{String.replace(value_template_path, ".", "_")}", origin: %{ name: "Systant", sw_version: Application.spec(:systant, :vsn) |> to_string(), support_url: "https://github.com/user/systant" } } - + if unit do Map.put(base_config, :unit_of_measurement, unit) else @@ -179,4 +314,4 @@ defmodule Systant.HaDiscovery do } } end -end \ No newline at end of file +end