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.
|
- 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.
|
- 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.
|
- Unified Monitoring + Modules filters and Help view with SecuNav styling.
|
||||||
- Added Bonus tab to navbar, refreshed alerts action buttons, removed legacy hero blocks.
|
- 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.
|
- 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.
|
- Provide rules to forward payloads into SecuBox Alerts.
|
||||||
|
|
||||||
4. **Profiles**
|
4. **Profiles**
|
||||||
- Promote detected presets into editable device entries (auto-populate `/etc/config/mqtt-bridge`).
|
- Allow LuCI to edit adapter entries (enable/disable, rename, override serial port).
|
||||||
- Support multiple adapters simultaneously and expose health metrics per profile.
|
- 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).
|
- 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 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 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
|
## In Progress
|
||||||
- Flesh out real USB discovery and MQTT client integration.
|
- 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.
|
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
|
## Next steps
|
||||||
|
|
||||||
- Add real daemon integration with Mosquitto.
|
- 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`.
|
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
|
## Development Notes
|
||||||
|
|
||||||
See `.codex/apps/mqtt-bridge/WIP.md` for current tasks and `.codex/apps/mqtt-bridge/TODO.md` for backlog/high-level goals.
|
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') }),
|
E('link', { 'rel': 'stylesheet', 'href': L.resource('mqtt-bridge/common.css') }),
|
||||||
Nav.renderTabs('devices'),
|
Nav.renderTabs('devices'),
|
||||||
this.renderStats(status),
|
this.renderStats(status),
|
||||||
this.renderProfiles(status.profiles || []),
|
this.renderProfiles(status.adapters || [], status.profiles || []),
|
||||||
E('div', { 'class': 'mb-card' }, [
|
E('div', { 'class': 'mb-card' }, [
|
||||||
E('div', { 'class': 'mb-card-header' }, [
|
E('div', { 'class': 'mb-card-header' }, [
|
||||||
E('div', { 'class': 'mb-card-title' }, [E('span', {}, '🔌'), _('USB & Sensors')]),
|
E('div', { 'class': 'mb-card-title' }, [E('span', {}, '🔌'), _('USB & Sensors')]),
|
||||||
@ -41,8 +41,10 @@ return view.extend({
|
|||||||
]);
|
]);
|
||||||
},
|
},
|
||||||
|
|
||||||
renderProfiles: function(profiles) {
|
renderProfiles: function(adapters, liveProfiles) {
|
||||||
var items = profiles || [];
|
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)) :
|
var cards = items.length ? items.map(this.renderProfile.bind(this)) :
|
||||||
[E('p', { 'style': 'color:var(--mb-muted);' },
|
[E('p', { 'style': 'color:var(--mb-muted);' },
|
||||||
_('No presets detected yet. Connect a Zigbee adapter or review the documentation below.'))];
|
_('No presets detected yet. Connect a Zigbee adapter or review the documentation below.'))];
|
||||||
@ -62,13 +64,22 @@ return view.extend({
|
|||||||
},
|
},
|
||||||
|
|
||||||
renderProfile: function(profile) {
|
renderProfile: function(profile) {
|
||||||
var detected = profile.detected;
|
var detected = profile.detected === true || profile.detected === 1 || profile.detected === '1';
|
||||||
var meta = [
|
var meta = [
|
||||||
(profile.vendor && profile.product) ? _('VID:PID ') + profile.vendor + ':' + profile.product : null,
|
(profile.vendor && profile.product) ? _('VID:PID ') + profile.vendor + ':' + profile.product : null,
|
||||||
profile.bus ? _('Bus ') + profile.bus : null,
|
profile.bus ? _('Bus ') + profile.bus : null,
|
||||||
profile.device ? _('Device ') + profile.device : null,
|
profile.device ? _('Device ') + profile.device : null,
|
||||||
profile.port ? _('Port ') + profile.port : null
|
profile.port ? _('Port ') + profile.port : null
|
||||||
].filter(Boolean);
|
].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' }, [
|
return E('div', { 'class': 'mb-profile-card' }, [
|
||||||
E('div', { 'class': 'mb-profile-header' }, [
|
E('div', { 'class': 'mb-profile-header' }, [
|
||||||
@ -80,7 +91,7 @@ return view.extend({
|
|||||||
]),
|
]),
|
||||||
E('span', {
|
E('span', {
|
||||||
'class': 'mb-profile-status' + (detected ? ' online' : '')
|
'class': 'mb-profile-status' + (detected ? ' online' : '')
|
||||||
}, detected ? _('Detected') : _('Waiting'))
|
}, statusParts.join(' • '))
|
||||||
]),
|
]),
|
||||||
profile.notes ? E('p', { 'class': 'mb-profile-notes' }, profile.notes) : null
|
profile.notes ? E('p', { 'class': 'mb-profile-notes' }, profile.notes) : null
|
||||||
]);
|
]);
|
||||||
|
|||||||
@ -8,6 +8,22 @@ config bridge 'bridge'
|
|||||||
option base_topic 'secubox/+/state'
|
option base_topic 'secubox/+/state'
|
||||||
option retention '7'
|
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'
|
config stats 'stats'
|
||||||
option clients '0'
|
option clients '0'
|
||||||
option mps '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
|
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() {
|
status() {
|
||||||
json_init
|
json_init
|
||||||
json_add_string "broker" "$(uci -q get mqtt-bridge.broker.host || echo 'localhost')"
|
json_add_string "broker" "$(uci -q get mqtt-bridge.broker.host || echo 'localhost')"
|
||||||
@ -112,20 +153,22 @@ status() {
|
|||||||
done
|
done
|
||||||
json_close_array
|
json_close_array
|
||||||
|
|
||||||
json_add_object "settings"
|
json_add_object "settings"
|
||||||
json_add_string "host" "$(uci -q get mqtt-bridge.broker.host || echo '127.0.0.1')"
|
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_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 "username" "$(uci -q get mqtt-bridge.broker.username || echo '')"
|
||||||
json_add_string "password" ""
|
json_add_string "password" ""
|
||||||
json_add_string "base_topic" "$(uci -q get mqtt-bridge.bridge.base_topic || echo 'secubox/+/state')"
|
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_add_int "retention" "$(uci -q get mqtt-bridge.bridge.retention || echo 7)"
|
||||||
json_close_object
|
json_close_object
|
||||||
|
|
||||||
json_add_array "profiles"
|
json_add_array "profiles"
|
||||||
append_zigbee_profile
|
append_zigbee_profile
|
||||||
json_close_array
|
json_close_array
|
||||||
|
|
||||||
json_dump
|
append_configured_adapters
|
||||||
|
|
||||||
|
json_dump
|
||||||
}
|
}
|
||||||
|
|
||||||
list_devices() {
|
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