Add MQTT diagnostics, broker connection probe, and switch to Debian base

The MQTT publisher now tracks connection state, publish counters, discovery
and availability timestamps, and the last error, exposed at /api/mqtt and on
the dashboard as an MQTT card and a Home Assistant section. Connection
failures, disconnects, and dropped publishes are logged; paho's
on_connect_fail callback was not registered before, so failed attempts were
silent. MQTT connect, disconnect, and unreachable events go to the timeline.

When a connection attempt fails the service runs a probe from inside the
container: system resolver, each nameserver from resolv.conf queried directly,
/etc/hosts, and a TCP connect to every address found. The result is shown as
a summary and raw JSON on the dashboard and can be rerun via
POST /api/mqtt/probe.

The image base moves from Alpine to Debian slim. musl queries all
nameservers in parallel and accepts the first reply, so a fast public
NXDOMAIN beats a slower local server that knows the name. glibc asks the
nameservers in order.

Co-Authored-By: Claude Fable 5.1 <noreply@anthropic.com>
This commit is contained in:
Milan PandurovandClaude Fable 5.1 committed 2026-09-29 12:08:48 +02:00
1 parent d80c40065e
commit 62b57a7e56
8 files changed
+737 -14

No files matched your search

+17 -1
View File
@@ -13,6 +13,7 @@ log = logging.getLogger("app")
storage = Storage(cfg.db_path)
state = State()
publisher = None
app = Flask(__name__)
WIFI_KEYS = ("wifi_interface", "wifi_interfaces", "wifi_status", "wifi_error",
@@ -51,6 +52,20 @@ def api_wifi():
return jsonify(result)
@app.route('/api/mqtt')
def api_mqtt():
result = publisher.diagnostics() if publisher else {"enabled": False}
result["now"] = time.time()
return jsonify(result)
@app.route('/api/mqtt/probe', methods=['POST'])
def api_mqtt_probe():
if not publisher:
return jsonify({"error": "MQTT is not configured"}), 404
return jsonify(publisher.run_probe())
@app.route('/api/events')
def api_events():
limit = min(max(request.args.get("limit", 200, type=int), 1), 1000)
@@ -67,12 +82,13 @@ def api_samples():
def start_background():
global publisher
storage.add_event("service_started", "Health check service started")
storage.prune_samples(cfg.sample_retention_days)
if cfg.mqtt_host:
from hamqtt import HomeAssistantPublisher
publisher = HomeAssistantPublisher(cfg)
publisher = HomeAssistantPublisher(cfg, storage)
state.on_change(publisher.publish_state)
publisher.start()