feat: mqtt adapter monitor daemon

This commit is contained in:
CyberMind-FR 2025-12-29 14:44:49 +01:00
parent 54e0b5df6c
commit 1a61dfb260
10 changed files with 267 additions and 19 deletions

View File

@ -4,6 +4,7 @@
- Introduced SecuBox cascade layout helper (CSS + JS) and migrated SecuNav + MQTT tabs to the new layered system.
- MQTT Bridge now exposes Zigbee/SMSC USB2134B presets with dmesg hints, tty detection, and documentation updates.
- New `mqtt-bridge-monitor` daemon keeps adapter metadata (port/bus/health) synced and logs detection events for SecuBox.
- Unified Monitoring + Modules filters and Help view with SecuNav styling.
- Added Bonus tab to navbar, refreshed alerts action buttons, removed legacy hero blocks.
- Verified on router (scp + cache reset) and tagged release v0.5.0-A.

View File

@ -13,5 +13,5 @@
- Provide rules to forward payloads into SecuBox Alerts.
4. **Profiles**
- Promote detected presets into editable device entries (auto-populate `/etc/config/mqtt-bridge`).
- Support multiple adapters simultaneously and expose health metrics per profile.
- Allow LuCI to edit adapter entries (enable/disable, rename, override serial port).
- Surface per-adapter health metrics/uptime graphs and expose actions (rescan, reset).

View File

@ -4,6 +4,7 @@
- Scaffolded `luci-app-mqtt-bridge` with SecuBox-themed views (overview/devices/settings).
- Added RPC backend (`luci.mqtt-bridge`) and UCI defaults for broker/bridge stats.
- Added Zigbee/SMSC USB2134B preset detection (USB VID/PID scan, tty hinting, LuCI cards + docs).
- Added `/usr/sbin/mqtt-bridge-monitor` + init.d service to keep adapter sections (port/bus/health) in sync.
## In Progress
- Flesh out real USB discovery and MQTT client integration.

View File

@ -67,6 +67,17 @@ Match the reported Bus/Device numbers with `/sys/bus/usb/devices/*/busnum` and `
Once the tty node is confirmed, update `/etc/config/mqtt-bridge` and restart the bridge service to bind Zigbee traffic to the MQTT topics defined in the Settings tab.
## Adapter monitor daemon
The package now installs a lightweight watcher (`/usr/sbin/mqtt-bridge-monitor`) that keeps SecuBox informed about attached adapters:
- Configured via `config monitor 'monitor'` (interval in seconds) and `config adapter '...'` sections inside `/etc/config/mqtt-bridge`.
- Managed with the standard init script: `service mqtt-bridge start|stop|status`.
- Writes state transitions to the system log (`logread -e mqtt-bridge-monitor`).
- Updates each adapter section with `detected`, `port`, `bus`, `device`, `health`, and `last_seen`, which the LuCI Devices tab now surfaces.
Use `uci show mqtt-bridge.adapter` to inspect the persisted metadata, or `ubus call luci.mqtt-bridge status` to see the JSON payload consumed by the UI.
## Next steps
- Add real daemon integration with Mosquitto.

View File

@ -20,6 +20,10 @@ USB-aware MQTT orchestrator for SecuBox routers. The application discovers USB s
The LuCI views depend on the SecuBox theme bundle included in `luci-theme-secubox`.
## Daemon / Monitor
`/usr/sbin/mqtt-bridge-monitor` (started via `/etc/init.d/mqtt-bridge`) polls configured adapter presets, logs plug/unplug events, and updates `/etc/config/mqtt-bridge` with `detected`, `port`, `bus`, `device`, and `health` metadata. The Devices view consumes those values to surface Zigbee/serial presets along with `dmesg` hints for `/dev/tty*` alignment.
## Development Notes
See `.codex/apps/mqtt-bridge/WIP.md` for current tasks and `.codex/apps/mqtt-bridge/TODO.md` for backlog/high-level goals.

View File

@ -26,7 +26,7 @@ return view.extend({
E('link', { 'rel': 'stylesheet', 'href': L.resource('mqtt-bridge/common.css') }),
Nav.renderTabs('devices'),
this.renderStats(status),
this.renderProfiles(status.profiles || []),
this.renderProfiles(status.adapters || [], status.profiles || []),
E('div', { 'class': 'mb-card' }, [
E('div', { 'class': 'mb-card-header' }, [
E('div', { 'class': 'mb-card-title' }, [E('span', {}, '🔌'), _('USB & Sensors')]),
@ -41,8 +41,10 @@ return view.extend({
]);
},
renderProfiles: function(profiles) {
var items = profiles || [];
renderProfiles: function(adapters, liveProfiles) {
var primary = (adapters && adapters.length) ? adapters : [];
var fallback = (liveProfiles && liveProfiles.length) ? liveProfiles : [];
var items = primary.length ? primary : fallback;
var cards = items.length ? items.map(this.renderProfile.bind(this)) :
[E('p', { 'style': 'color:var(--mb-muted);' },
_('No presets detected yet. Connect a Zigbee adapter or review the documentation below.'))];
@ -62,13 +64,22 @@ return view.extend({
},
renderProfile: function(profile) {
var detected = profile.detected;
var detected = profile.detected === true || profile.detected === 1 || profile.detected === '1';
var meta = [
(profile.vendor && profile.product) ? _('VID:PID ') + profile.vendor + ':' + profile.product : null,
profile.bus ? _('Bus ') + profile.bus : null,
profile.device ? _('Device ') + profile.device : null,
profile.port ? _('Port ') + profile.port : null
].filter(Boolean);
var statusParts = [];
if (detected)
statusParts.push(_('Detected'));
else
statusParts.push(_('Waiting'));
if (profile.health)
statusParts.push(profile.health);
if (profile.last_seen)
statusParts.push(_('Last seen ') + profile.last_seen);
return E('div', { 'class': 'mb-profile-card' }, [
E('div', { 'class': 'mb-profile-header' }, [
@ -80,7 +91,7 @@ return view.extend({
]),
E('span', {
'class': 'mb-profile-status' + (detected ? ' online' : '')
}, detected ? _('Detected') : _('Waiting'))
}, statusParts.join(' • '))
]),
profile.notes ? E('p', { 'class': 'mb-profile-notes' }, profile.notes) : null
]);

View File

@ -8,6 +8,22 @@ config bridge 'bridge'
option base_topic 'secubox/+/state'
option retention '7'
config monitor 'monitor'
option interval '10'
config adapter 'zigbee_usb2134'
option enabled '1'
option preset 'zigbee_usb2134'
option title 'SMSC USB2134B'
option vendor '0424'
option product '2134'
option notes 'Bus 003 Device 002: ID 0424:2134 SMSC USB2134B'
option detected '0'
option port ''
option bus ''
option device ''
option health 'unknown'
config stats 'stats'
option clients '0'
option mps '0'

View File

@ -0,0 +1,12 @@
#!/bin/sh /etc/rc.common
START=95
USE_PROCD=1
SERVICE_NAME="mqtt-bridge-monitor"
start_service() {
procd_open_instance
procd_set_param command /usr/sbin/mqtt-bridge-monitor
procd_set_param respawn 0 5 5
procd_close_instance
}

View File

@ -72,6 +72,47 @@ append_zigbee_profile() {
fi
}
add_adapter_json() {
local section="$1"
local enabled vendor product title preset notes detected port bus device health last_seen
config_get enabled "$section" enabled "1"
config_get vendor "$section" vendor
config_get product "$section" product
config_get title "$section" title
config_get preset "$section" preset
config_get notes "$section" notes
config_get detected "$section" detected
config_get port "$section" port
config_get bus "$section" bus
config_get device "$section" device
config_get health "$section" health
config_get last_seen "$section" last_seen
json_add_object
json_add_string "id" "$section"
[ -n "$title" ] && json_add_string "label" "$title"
[ -n "$preset" ] && json_add_string "preset" "$preset"
[ -n "$vendor" ] && json_add_string "vendor" "$vendor"
[ -n "$product" ] && json_add_string "product" "$product"
json_add_boolean "enabled" "${enabled:-0}"
json_add_boolean "detected" "${detected:-0}"
[ -n "$port" ] && json_add_string "port" "$port"
[ -n "$bus" ] && json_add_string "bus" "$bus"
[ -n "$device" ] && json_add_string "device" "$device"
[ -n "$health" ] && json_add_string "health" "$health"
[ -n "$last_seen" ] && json_add_string "last_seen" "$last_seen"
[ -n "$notes" ] && json_add_string "notes" "$notes"
json_close_object
}
append_configured_adapters() {
json_add_array "adapters"
config_load mqtt-bridge
config_foreach add_adapter_json adapter
json_close_array
}
status() {
json_init
json_add_string "broker" "$(uci -q get mqtt-bridge.broker.host || echo 'localhost')"
@ -112,20 +153,22 @@ status() {
done
json_close_array
json_add_object "settings"
json_add_string "host" "$(uci -q get mqtt-bridge.broker.host || echo '127.0.0.1')"
json_add_int "port" "$(uci -q get mqtt-bridge.broker.port || echo 1883)"
json_add_string "username" "$(uci -q get mqtt-bridge.broker.username || echo '')"
json_add_string "password" ""
json_add_string "base_topic" "$(uci -q get mqtt-bridge.bridge.base_topic || echo 'secubox/+/state')"
json_add_int "retention" "$(uci -q get mqtt-bridge.bridge.retention || echo 7)"
json_close_object
json_add_object "settings"
json_add_string "host" "$(uci -q get mqtt-bridge.broker.host || echo '127.0.0.1')"
json_add_int "port" "$(uci -q get mqtt-bridge.broker.port || echo 1883)"
json_add_string "username" "$(uci -q get mqtt-bridge.broker.username || echo '')"
json_add_string "password" ""
json_add_string "base_topic" "$(uci -q get mqtt-bridge.bridge.base_topic || echo 'secubox/+/state')"
json_add_int "retention" "$(uci -q get mqtt-bridge.bridge.retention || echo 7)"
json_close_object
json_add_array "profiles"
append_zigbee_profile
json_close_array
json_add_array "profiles"
append_zigbee_profile
json_close_array
json_dump
append_configured_adapters
json_dump
}
list_devices() {

View File

@ -0,0 +1,149 @@
#!/bin/sh
#
# MQTT Bridge monitor daemon
# Scans configured USB adapters/presets and updates UCI with live metadata.
. /lib/functions.sh
UCI_NAMESPACE="mqtt-bridge"
LOGTAG="mqtt-bridge-monitor"
SCAN_INTERVAL=10
COMMIT_NEEDED=0
log_msg() {
logger -t "$LOGTAG" "$*"
}
find_usb_device() {
local vendor="$1"
local product="$2"
local dev
for dev in /sys/bus/usb/devices/*; do
[ -f "$dev/idVendor" ] || continue
[ -f "$dev/idProduct" ] || continue
local idVendor idProduct
idVendor="$(cat "$dev/idVendor" 2>/dev/null)"
idProduct="$(cat "$dev/idProduct" 2>/dev/null)"
[ "$idVendor" = "$vendor" ] || continue
[ "$idProduct" = "$product" ] || continue
echo "$dev"
return 0
done
return 1
}
find_usb_tty() {
local base="$1"
local path node
for path in "$base" "$base"/* "$base"/*/*; do
[ -d "$path/tty" ] || continue
for node in "$path"/tty/*; do
[ -e "$node" ] || continue
local tty
tty="$(basename "$node")"
[ -e "/dev/$tty" ] && { echo "/dev/$tty"; return 0; }
done
done
return 1
}
set_option_if_changed() {
local section="$1"
local key="$2"
local value="$3"
local current
current="$(uci -q get ${UCI_NAMESPACE}.adapter.${section}.${key} 2>/dev/null)"
[ "$current" = "$value" ] && return
uci set ${UCI_NAMESPACE}.adapter.${section}.${key}="$value"
COMMIT_NEEDED=1
}
clear_option_if_needed() {
local section="$1"
local key="$2"
local current
current="$(uci -q get ${UCI_NAMESPACE}.adapter.${section}.${key} 2>/dev/null)"
[ -z "$current" ] && return
uci delete ${UCI_NAMESPACE}.adapter.${section}.${key}
COMMIT_NEEDED=1
}
update_adapter_section() {
local section="$1"
local enabled vendor product title preset
config_get enabled "$section" enabled "1"
config_get vendor "$section" vendor
config_get product "$section" product
config_get preset "$section" preset
config_get title "$section" title
if [ "$enabled" != "1" ]; then
set_option_if_changed "$section" detected "0"
set_option_if_changed "$section" health "disabled"
return
fi
if [ -z "$vendor" ] || [ -z "$product" ]; then
set_option_if_changed "$section" detected "0"
set_option_if_changed "$section" health "unknown"
return
fi
local dev_path
dev_path="$(find_usb_device "$vendor" "$product")" || dev_path=""
local prev_detected
prev_detected="$(uci -q get ${UCI_NAMESPACE}.adapter.${section}.detected 2>/dev/null)"
if [ -n "$dev_path" ]; then
local bus devnum port ts
bus="$(cat "$dev_path/busnum" 2>/dev/null)"
devnum="$(cat "$dev_path/devnum" 2>/dev/null)"
port="$(find_usb_tty "$dev_path")"
ts="$(date -Iseconds)"
set_option_if_changed "$section" detected "1"
set_option_if_changed "$section" health "online"
[ -n "$bus" ] && set_option_if_changed "$section" bus "$bus"
[ -n "$devnum" ] && set_option_if_changed "$section" device "$devnum"
if [ -n "$port" ]; then
set_option_if_changed "$section" port "$port"
else
clear_option_if_needed "$section" port
fi
set_option_if_changed "$section" last_seen "$ts"
if [ "$prev_detected" != "1" ]; then
log_msg "Adapter $section ($title) detected on bus $bus dev $devnum $port"
fi
else
set_option_if_changed "$section" detected "0"
set_option_if_changed "$section" health "missing"
clear_option_if_needed "$section" port
clear_option_if_needed "$section" bus
clear_option_if_needed "$section" device
if [ "$prev_detected" = "1" ]; then
log_msg "Adapter $section ($title) disconnected"
fi
fi
}
scan_loop() {
while true; do
COMMIT_NEEDED=0
config_load "$UCI_NAMESPACE"
local interval
config_get interval monitor interval
[ -n "$interval" ] && SCAN_INTERVAL="$interval"
config_foreach update_adapter_section adapter
if [ "$COMMIT_NEEDED" -eq 1 ]; then
uci commit "$UCI_NAMESPACE"
fi
sleep "$SCAN_INTERVAL"
done
}
scan_loop