router-status: publish missing values to HA as 'unavailable'

HA renders JSON null as 'None'; map None to the string 'unavailable' for
the WAN, AP, Starlink, failover, thermal and battery payloads.

Co-Authored-By: Claude Sonnet 5.5 <noreply@anthropic.com>
This commit is contained in:
2026-10-22 17:41:50 -04:00
co-authored by Claude Sonnet 5.5
parent 11d3fccf75
commit f53b6bbc4b
+18 -6
View File
@@ -99,6 +99,18 @@ DEVICE_INFO = {"identifiers": [device_id], "name": "Van Router", "manufacturer":
WAN_NAMES = ("wifi", "starlink", "cellular")
def mark_unavailable(obj):
"""Copy of obj with every None replaced by 'unavailable' -- HA treats that string
as a missing value, whereas null renders as 'None'."""
if obj is None:
return "unavailable"
if isinstance(obj, dict):
return {k: mark_unavailable(v) for k, v in obj.items()}
if isinstance(obj, list):
return [mark_unavailable(v) for v in obj]
return obj
def sh(args, timeout=10):
try:
return subprocess.run(args, capture_output=True, text=True, timeout=timeout)
@@ -510,23 +522,23 @@ def publish_state(mqc):
client_dir = collect_client_dir()
wan_rows = collect_wan()
mqc.publish(f"{state_root}/wan", json.dumps({"rows": wan_rows}))
mqc.publish(f"{state_root}/wan", json.dumps(mark_unavailable({"rows": wan_rows})))
for ap, meta in zip(aps, APS):
mqc.publish(f"{state_root}/ap/{meta['id']}", json.dumps(ap))
mqc.publish(f"{state_root}/ap/{meta['id']}", json.dumps(mark_unavailable(ap)))
mqc.publish(f"{state_root}/clients", json.dumps({"rows": collect_clients(aps, client_dir)}))
mqc.publish(f"{state_root}/starlink", json.dumps(collect_starlink()))
mqc.publish(f"{state_root}/starlink", json.dumps(mark_unavailable(collect_starlink())))
mqc.publish(f"{state_root}/deploywarn", json.dumps(collect_deploywarn()))
mqc.publish(f"{state_root}/wan_prefer", json.dumps({"prefer": read_prefer() or "auto"}))
failover = read_json_file(FAILOVER_STATE)
if failover is not None:
mqc.publish(f"{state_root}/failover", json.dumps(failover))
mqc.publish(f"{state_root}/failover", json.dumps(mark_unavailable(failover)))
thermal = read_json_file(THERMAL_STATE)
if thermal is not None:
mqc.publish(f"{state_root}/thermal", json.dumps(thermal))
mqc.publish(f"{state_root}/thermal", json.dumps(mark_unavailable(thermal)))
battery = read_json_file(BATTERY_STATE)
if battery is not None:
mqc.publish(f"{state_root}/battery", json.dumps(battery))
mqc.publish(f"{state_root}/battery", json.dumps(mark_unavailable(battery)))
return wan_rows