feat: mqtt adapter monitor daemon
This commit is contained in:
parent
54e0b5df6c
commit
1a61dfb260
@ -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.
|
||||
|
||||
@ -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).
|
||||
|
||||
@ -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.
|
||||
|
||||
@ -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.
|
||||
|
||||
@ -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.
|
||||
|
||||
@ -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
|
||||
]);
|
||||
|
||||
@ -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'
|
||||
|
||||
12
luci-app-mqtt-bridge/root/etc/init.d/mqtt-bridge
Executable file
12
luci-app-mqtt-bridge/root/etc/init.d/mqtt-bridge
Executable 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
|
||||
}
|
||||
@ -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() {
|
||||
|
||||
149
luci-app-mqtt-bridge/root/usr/sbin/mqtt-bridge-monitor
Executable file
149
luci-app-mqtt-bridge/root/usr/sbin/mqtt-bridge-monitor
Executable 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
|
||||
Loading…
Reference in New Issue
Block a user