feat: Add mDNS, watchdog, human-readable uptime, esp-fleet tool

Firmware:
- mDNS announcement as <hostname>.local (configurable via Kconfig)
- Task watchdog with 30s timeout and auto-reboot on hang
- STATUS now returns human-readable uptime (e.g., 3d2h15m) and hostname

Pi-side tools:
- esp-cmd: mDNS hostname resolution (esp-cmd amber-maple.local STATUS)
- esp-fleet: parallel command to all sensors (esp-fleet status)

Tested on amber-maple — mDNS resolves, watchdog active, fleet tool works.
This commit is contained in:
user
2026-02-04 15:59:18 +01:00
parent 6c4c17d740
commit 44bd549761
7 changed files with 163 additions and 25 deletions

View File

@@ -8,7 +8,9 @@ DEFAULT_PORT = 5501
TIMEOUT = 2.0
USAGE = """\
Usage: esp-cmd <ip> <command> [args...]
Usage: esp-cmd <host> <command> [args...]
Host can be an IP address or mDNS hostname (e.g., amber-maple.local).
Commands:
STATUS Query device state (uptime, heap, RSSI, tx_power, rate)
@@ -18,11 +20,19 @@ Commands:
POWER <2-20> Set TX power in dBm (saved to NVS)
Examples:
esp-cmd 192.168.1.50 STATUS
esp-cmd 192.168.1.50 RATE 50
esp-cmd 192.168.1.50 POWER 10
esp-cmd 192.168.1.50 REBOOT
esp-cmd 192.168.1.50 IDENTIFY"""
esp-cmd amber-maple.local STATUS
esp-cmd 192.168.129.30 RATE 50
esp-cmd amber-maple.local IDENTIFY"""
def resolve(host):
"""Resolve hostname to IP address (supports mDNS .local)."""
try:
result = socket.getaddrinfo(host, DEFAULT_PORT, socket.AF_INET, socket.SOCK_DGRAM)
return result[0][4][0]
except socket.gaierror as e:
print(f"ERR: cannot resolve {host}: {e}", file=sys.stderr)
sys.exit(1)
def main():
@@ -30,8 +40,9 @@ def main():
print(USAGE)
sys.exit(0 if sys.argv[1:] and sys.argv[1] in ("-h", "--help") else 2)
ip = sys.argv[1]
host = sys.argv[1]
cmd = " ".join(sys.argv[2:]).strip()
ip = resolve(host)
sock = socket.socket(socket.AF_INET, socket.SOCK_DGRAM)
sock.settimeout(TIMEOUT)
@@ -41,7 +52,7 @@ def main():
data, _ = sock.recvfrom(512)
print(data.decode().strip())
except socket.timeout:
print(f"ERR: no reply from {ip}:{DEFAULT_PORT} (timeout {TIMEOUT}s)", file=sys.stderr)
print(f"ERR: no reply from {host} ({ip}:{DEFAULT_PORT}), timeout {TIMEOUT}s", file=sys.stderr)
sys.exit(1)
except OSError as e:
print(f"ERR: {e}", file=sys.stderr)