Change Home Assistant device name and manufacturer/model

This commit is contained in:
ryan 2025-08-08 21:17:46 -07:00
parent bc7ac0c854
commit 8bbf91f25f

View File

@ -1,17 +1,17 @@
defmodule Systant.HaDiscovery do defmodule Systant.HaDiscovery do
@moduledoc """ @moduledoc """
Home Assistant MQTT Discovery integration for Systant. Home Assistant MQTT Discovery integration for Systant.
Publishes device and entity discovery configurations to Home Assistant Publishes device and entity discovery configurations to Home Assistant
via MQTT following the HA discovery protocol. via MQTT following the HA discovery protocol.
Discovery topic format: homeassistant/<component>/<node_id>/<object_id>/config Discovery topic format: homeassistant/<component>/<node_id>/<object_id>/config
""" """
require Logger require Logger
@manufacturer "Systant" @manufacturer "Systant"
@model "System Monitor" @model "Systant"
@doc """ @doc """
Publish all discovery configurations for a host Publish all discovery configurations for a host
@ -19,17 +19,17 @@ defmodule Systant.HaDiscovery do
def publish_discovery(client_pid, hostname, config \\ nil) do def publish_discovery(client_pid, hostname, config \\ nil) do
app_config = config || Systant.Config.load_config() app_config = config || Systant.Config.load_config()
ha_config = Systant.Config.get(app_config, ["homeassistant"]) || %{} ha_config = Systant.Config.get(app_config, ["homeassistant"]) || %{}
if ha_config["discovery_enabled"] != false do if ha_config["discovery_enabled"] != false do
discovery_prefix = ha_config["discovery_prefix"] || "homeassistant" discovery_prefix = ha_config["discovery_prefix"] || "homeassistant"
device_config = build_device_config(hostname) device_config = build_device_config(hostname)
# Publish device discovery first # Publish device discovery first
publish_device_discovery(client_pid, hostname, device_config, discovery_prefix) publish_device_discovery(client_pid, hostname, device_config, discovery_prefix)
# Publish sensor discoveries # Publish sensor discoveries
publish_sensor_discoveries(client_pid, hostname, device_config, discovery_prefix) publish_sensor_discoveries(client_pid, hostname, device_config, discovery_prefix)
Logger.info("Published Home Assistant discovery for #{hostname}") Logger.info("Published Home Assistant discovery for #{hostname}")
else else
Logger.info("Home Assistant discovery disabled in configuration") Logger.info("Home Assistant discovery disabled in configuration")
@ -43,15 +43,15 @@ defmodule Systant.HaDiscovery do
app_config = config || Systant.Config.load_config() app_config = config || Systant.Config.load_config()
ha_config = Systant.Config.get(app_config, ["homeassistant"]) || %{} ha_config = Systant.Config.get(app_config, ["homeassistant"]) || %{}
discovery_prefix = ha_config["discovery_prefix"] || "homeassistant" discovery_prefix = ha_config["discovery_prefix"] || "homeassistant"
# Remove by publishing empty payloads to discovery topics # Remove by publishing empty payloads to discovery topics
sensors = get_sensor_definitions(hostname) sensors = get_sensor_definitions(hostname)
Enum.each(sensors, fn {component, object_id, _config} -> Enum.each(sensors, fn {component, object_id, _config} ->
topic = "#{discovery_prefix}/#{component}/#{hostname}/#{object_id}/config" topic = "#{discovery_prefix}/#{component}/#{hostname}/#{object_id}/config"
Tortoise.publish(client_pid, topic, "", retain: true) Tortoise.publish(client_pid, topic, "", retain: true)
end) end)
Logger.info("Removed Home Assistant discovery for #{hostname}") Logger.info("Removed Home Assistant discovery for #{hostname}")
end end
@ -63,21 +63,21 @@ defmodule Systant.HaDiscovery do
device: device_config, device: device_config,
components: build_all_components(hostname, device_config) components: build_all_components(hostname, device_config)
} }
topic = "#{discovery_prefix}/device/#{hostname}/config" topic = "#{discovery_prefix}/device/#{hostname}/config"
payload = Jason.encode!(components_config) payload = Jason.encode!(components_config)
Tortoise.publish(client_pid, topic, payload, retain: true) Tortoise.publish(client_pid, topic, payload, retain: true)
end end
defp publish_sensor_discoveries(client_pid, hostname, device_config, discovery_prefix) do defp publish_sensor_discoveries(client_pid, hostname, device_config, discovery_prefix) do
sensors = get_sensor_definitions(hostname) sensors = get_sensor_definitions(hostname)
Enum.each(sensors, fn {component, object_id, config} -> Enum.each(sensors, fn {component, object_id, config} ->
full_config = Map.merge(config, %{device: device_config}) full_config = Map.merge(config, %{device: device_config})
topic = "#{discovery_prefix}/#{component}/#{hostname}/#{object_id}/config" topic = "#{discovery_prefix}/#{component}/#{hostname}/#{object_id}/config"
payload = Jason.encode!(full_config) payload = Jason.encode!(full_config)
Tortoise.publish(client_pid, topic, payload, retain: true) Tortoise.publish(client_pid, topic, payload, retain: true)
end) end)
end end
@ -85,7 +85,7 @@ defmodule Systant.HaDiscovery do
defp build_device_config(hostname) do defp build_device_config(hostname) do
%{ %{
identifiers: ["systant_#{hostname}"], identifiers: ["systant_#{hostname}"],
name: "Systant #{hostname}", name: hostname |> String.capitalize(),
manufacturer: @manufacturer, manufacturer: @manufacturer,
model: @model, model: @model,
sw_version: Application.spec(:systant, :vsn) |> to_string() sw_version: Application.spec(:systant, :vsn) |> to_string()
@ -102,61 +102,196 @@ defmodule Systant.HaDiscovery do
defp get_sensor_definitions(hostname) do defp get_sensor_definitions(hostname) do
base_topic = "systant/#{hostname}/stats" base_topic = "systant/#{hostname}/stats"
[ [
# CPU Sensors # CPU Sensors
{"sensor", "cpu_load_1m", build_sensor_config("CPU Load 1m", "#{base_topic}", "cpu.avg1", "load", "mdi:speedometer")}, {"sensor", "cpu_load_1m",
{"sensor", "cpu_load_5m", build_sensor_config("CPU Load 5m", "#{base_topic}", "cpu.avg5", "load", "mdi:speedometer")}, build_sensor_config("CPU Load 1m", "#{base_topic}", "cpu.avg1", "load", "mdi:speedometer")},
{"sensor", "cpu_load_15m", build_sensor_config("CPU Load 15m", "#{base_topic}", "cpu.avg15", "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 # Memory Sensors
{"sensor", "memory_used_percent", build_sensor_config("Memory Used", "#{base_topic}", "memory.used_percent", "%", "mdi:memory")}, {"sensor", "memory_used_percent",
{"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) }}")}, build_sensor_config(
{"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) }}")}, "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 # 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", "uptime_hours",
{"sensor", "kernel_version", build_sensor_config("Kernel Version", "#{base_topic}", "system.kernel_version", nil, "mdi:linux")}, 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 # 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 # 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_utilization",
{"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 }}")}, build_sensor_config(
{"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 }}")}, "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 # 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_utilization",
{"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 }}")}, 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 # 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_root_usage",
{"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 }}")}, 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 # 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_rx_throughput",
{"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 }}")}, 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 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 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 = %{ base_config = %{
name: name, name: name,
state_topic: state_topic, state_topic: state_topic,
value_template: custom_template || "{{ value_json.#{value_template_path} }}", value_template: custom_template || "{{ value_json.#{value_template_path} }}",
icon: icon, 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: %{ origin: %{
name: "Systant", name: "Systant",
sw_version: Application.spec(:systant, :vsn) |> to_string(), sw_version: Application.spec(:systant, :vsn) |> to_string(),
support_url: "https://github.com/user/systant" support_url: "https://github.com/user/systant"
} }
} }
if unit do if unit do
Map.put(base_config, :unit_of_measurement, unit) Map.put(base_config, :unit_of_measurement, unit)
else else
@ -179,4 +314,4 @@ defmodule Systant.HaDiscovery do
} }
} }
end end
end end