Cleaner discovery topics, ability to specify own node_id

This commit is contained in:
2026-05-04 20:33:10 -04:00
parent 4bb99de7db
commit b3cba0f556
2 changed files with 21 additions and 8 deletions
+1
View File
@@ -9,6 +9,7 @@ mqtt:
monitor: monitor:
interval: 15 # seconds between stat publishes interval: 15 # seconds between stat publishes
device_name: "" # display name in HA; defaults to hostname device_name: "" # display name in HA; defaults to hostname
node_id: "" # MQTT topic slug and unique_id prefix; defaults to hostname if empty
sensors: sensors:
cpu: true cpu: true
+20 -8
View File
@@ -62,6 +62,7 @@ def load_config(path: Path = CONFIG_PATH) -> dict:
mn = cfg["monitor"] mn = cfg["monitor"]
mn.setdefault("interval", 30) mn.setdefault("interval", 30)
mn.setdefault("device_name", "") mn.setdefault("device_name", "")
mn.setdefault("node_id", "")
s = cfg["sensors"] s = cfg["sensors"]
s.setdefault("cpu", True) s.setdefault("cpu", True)
@@ -711,14 +712,14 @@ class DiscoveryPublisher:
def __init__( def __init__(
self, self,
client: mqtt.Client, client: mqtt.Client,
hostname: str, node_id: str,
device_name: str, device_name: str,
state_topic: str, state_topic: str,
availability_topic: str, availability_topic: str,
discovery_prefix: str = "homeassistant", discovery_prefix: str = "homeassistant",
): ):
self._client = client self._client = client
self._hostname = hostname self._node_id = node_id
self._device_name = device_name self._device_name = device_name
self._state_topic = state_topic self._state_topic = state_topic
self._availability_topic = availability_topic self._availability_topic = availability_topic
@@ -726,7 +727,7 @@ def __init__(
def _device_block(self) -> dict: def _device_block(self) -> dict:
return { return {
"identifiers": [f"{self._hostname}_monitor"], "identifiers": [f"{self._node_id}_monitor"],
"name": self._device_name, "name": self._device_name,
"manufacturer": "monitor.py", "manufacturer": "monitor.py",
"model": "Linux System Monitor", "model": "Linux System Monitor",
@@ -740,7 +741,7 @@ def publish_all(self, sensor_defs: list[dict]) -> None:
def _publish_one(self, defn: dict) -> None: def _publish_one(self, defn: dict) -> None:
key = defn["key"] key = defn["key"]
unique_id = f"{self._hostname}_{key}" unique_id = f"{self._node_id}_{key}"
object_id = unique_id object_id = unique_id
payload: dict[str, Any] = { payload: dict[str, Any] = {
@@ -762,7 +763,7 @@ def _publish_one(self, defn: dict) -> None:
payload["icon"] = defn["icon"] payload["icon"] = defn["icon"]
discovery_topic = ( discovery_topic = (
f"{self._discovery_prefix}/sensor/{object_id}/config" f"{self._discovery_prefix}/sensor/{self._node_id}/{object_id}/config"
) )
self._client.publish( self._client.publish(
discovery_topic, discovery_topic,
@@ -836,22 +837,24 @@ def __init__(self, cfg: dict):
self._cfg = cfg self._cfg = cfg
self._hostname = socket.gethostname() self._hostname = socket.gethostname()
self._device_name = cfg["monitor"].get("device_name") or self._hostname self._device_name = cfg["monitor"].get("device_name") or self._hostname
raw_node_id = cfg["monitor"].get("node_id", "").strip()
self._node_id = self._make_node_id(raw_node_id or self._hostname)
self._interval = max(5, int(cfg["monitor"].get("interval", 30))) self._interval = max(5, int(cfg["monitor"].get("interval", 30)))
prefix = cfg["mqtt"]["topic_prefix"] prefix = cfg["mqtt"]["topic_prefix"]
self._state_topic, self._avail_topic = make_topics(prefix, self._hostname) self._state_topic, self._avail_topic = make_topics(prefix, self._node_id)
self._collectors = self._build_collectors() self._collectors = self._build_collectors()
self._sensor_defs = all_sensor_defs(self._collectors, self._hostname) self._sensor_defs = all_sensor_defs(self._collectors, self._hostname)
self._client = build_client(cfg, self._hostname) self._client = build_client(cfg, self._node_id)
self._client.on_connect = self._on_connect self._client.on_connect = self._on_connect
self._client.on_disconnect = self._on_disconnect self._client.on_disconnect = self._on_disconnect
self._client.on_message = self._on_message self._client.on_message = self._on_message
self._discovery = DiscoveryPublisher( self._discovery = DiscoveryPublisher(
self._client, self._client,
self._hostname, self._node_id,
self._device_name, self._device_name,
self._state_topic, self._state_topic,
self._avail_topic, self._avail_topic,
@@ -862,6 +865,15 @@ def __init__(self, cfg: dict):
# Warm up cpu_percent (first call always returns 0.0) # Warm up cpu_percent (first call always returns 0.0)
psutil.cpu_percent(interval=None) psutil.cpu_percent(interval=None)
@staticmethod
def _make_node_id(name: str) -> str:
"""Sanitize a string for use in MQTT topics and HA unique_ids."""
safe = "".join(c if c.isalnum() or c in "-_" else "-" for c in name.lower())
# Collapse consecutive hyphens and strip leading/trailing separators
while "--" in safe:
safe = safe.replace("--", "-")
return safe.strip("-_") or "monitor"
def _build_collectors(self) -> list: def _build_collectors(self) -> list:
s = self._cfg["sensors"] s = self._cfg["sensors"]
collectors = [] collectors = []