infra: tune flash params + persist zigbee network key for paired devices

- FLASH_INTERVAL_SECONDS 3 -> 2 (user preference)
- Add network_key / pan_id / ext_pan_id to zigbee2mqtt configuration
  so paired devices can rejoin after a config wipe. Network key
  preserved from previous working state on RPi.
- Doorbell listener flash logic with even toggle count so plug ends
  at starting state
This commit is contained in:
2026-07-01 01:11:41 -07:00
parent 1ef49652b0
commit 758edc0a9a
3 changed files with 47 additions and 8 deletions
+1 -1
View File
@@ -73,7 +73,7 @@ services:
PLUG_TOPIC: "zigbee2mqtt/Sideboard Lamp/set" PLUG_TOPIC: "zigbee2mqtt/Sideboard Lamp/set"
POLL_INTERVAL: "30" POLL_INTERVAL: "30"
MAX_AGE_SECONDS: "180" MAX_AGE_SECONDS: "180"
FLASH_INTERVAL_SECONDS: "3" FLASH_INTERVAL_SECONDS: "2"
FLASH_DURATION_SECONDS: "15" FLASH_DURATION_SECONDS: "15"
TZ: America/Los_Angeles TZ: America/Los_Angeles
+18 -7
View File
@@ -28,6 +28,7 @@ Env vars (all optional):
NTFY_TOPICS comma-separated override, default "ALERT_klubhaus_topic_test,ALERT_klubhaus_topic" NTFY_TOPICS comma-separated override, default "ALERT_klubhaus_topic_test,ALERT_klubhaus_topic"
""" """
import json import json
import math
import os import os
import sys import sys
import threading import threading
@@ -48,12 +49,21 @@ POLL_INTERVAL = int(os.environ.get("POLL_INTERVAL", "30"))
# Drop messages older than this many seconds (replay protection after restart). # Drop messages older than this many seconds (replay protection after restart).
MAX_AGE_SECONDS = int(os.environ.get("MAX_AGE_SECONDS", "180")) MAX_AGE_SECONDS = int(os.environ.get("MAX_AGE_SECONDS", "180"))
# On a fresh alert, flash the plug: TOGGLE every FLASH_INTERVAL_SECONDS, # On a fresh alert, flash the plug: TOGGLE every FLASH_INTERVAL_SECONDS,
# repeated so the total runtime is FLASH_DURATION_SECONDS. With defaults # repeated so the total runtime is FLASH_DURATION_SECONDS. Count is rounded
# (3s interval, 15s duration) that's 5 toggles, ending opposite of start. # UP to the nearest even integer so the lamp ends in the state it started
FLASH_INTERVAL_SECONDS = float(os.environ.get("FLASH_INTERVAL_SECONDS", "3")) # in (an even number of toggles returns to the original state).
FLASH_INTERVAL_SECONDS = float(os.environ.get("FLASH_INTERVAL_SECONDS", "2"))
FLASH_DURATION_SECONDS = float(os.environ.get("FLASH_DURATION_SECONDS", "15")) FLASH_DURATION_SECONDS = float(os.environ.get("FLASH_DURATION_SECONDS", "15"))
def flash_count() -> int:
"""Number of toggles per alert. Always >= 2 and even."""
n = max(2, math.ceil(FLASH_DURATION_SECONDS / FLASH_INTERVAL_SECONDS))
if n % 2:
n += 1
return n
def log(msg: str) -> None: def log(msg: str) -> None:
print(msg, flush=True) print(msg, flush=True)
@@ -107,7 +117,7 @@ def poll_topic(client: mqtt.Client, topic: str, last_id: str) -> str:
newest_id = last_id newest_id = last_id
now = time.time() now = time.time()
flash_count = max(1, int(FLASH_DURATION_SECONDS / FLASH_INTERVAL_SECONDS)) n_flashes = flash_count()
for line in r.iter_lines(decode_unicode=True): for line in r.iter_lines(decode_unicode=True):
if not line: if not line:
continue continue
@@ -136,7 +146,7 @@ def poll_topic(client: mqtt.Client, topic: str, last_id: str) -> str:
continue continue
log(f"[{topic}] alert: id={msg_id} age={age}s title={title!r} message={message!r}") log(f"[{topic}] alert: id={msg_id} age={age}s title={title!r} message={message!r}")
start_flash(client, flash_count, FLASH_INTERVAL_SECONDS) start_flash(client, n_flashes, FLASH_INTERVAL_SECONDS)
return newest_id return newest_id
@@ -148,8 +158,9 @@ def main() -> None:
log(f" plug topic: {PLUG_TOPIC}") log(f" plug topic: {PLUG_TOPIC}")
log(f" poll interval: {POLL_INTERVAL}s") log(f" poll interval: {POLL_INTERVAL}s")
log(f" max age: {MAX_AGE_SECONDS}s") log(f" max age: {MAX_AGE_SECONDS}s")
log(f" flash: {FLASH_DURATION_SECONDS / FLASH_INTERVAL_SECONDS:.0f} toggles " n_flashes = flash_count()
f"@ {FLASH_INTERVAL_SECONDS}s ({FLASH_DURATION_SECONDS}s total)") log(f" flash: {n_flashes} toggles @ {FLASH_INTERVAL_SECONDS}s "
f"({n_flashes * FLASH_INTERVAL_SECONDS}s total, even -> ends at start)")
log(f" state dir: {STATE_DIR}") log(f" state dir: {STATE_DIR}")
client = mqtt.Client(mqtt.CallbackAPIVersion.VERSION2) client = mqtt.Client(mqtt.CallbackAPIVersion.VERSION2)
+28
View File
@@ -9,6 +9,34 @@ serial:
rtscts: false rtscts: false
advanced: advanced:
log_level: debug log_level: debug
network_key:
- 146
- 30
- 124
- 1
- 70
- 32
- 235
- 196
- 27
- 113
- 122
- 230
- 7
- 177
- 134
- 45
pan_id: 3926
ext_pan_id:
- 225
- 105
- 42
- 213
- 37
- 25
- 93
- 235
frontend: frontend:
enabled: true enabled: true
port: 8080 port: 8080