Fix ble_alerter: MAC name filter, Matrix 429 backoff, active scanner
- Reject dash/colon MAC-format device names (e.g. 45-48-39-7F-73-C7)
from open mode — were slipping past is_generic_name()
- Add exponential backoff (1s, 2s) on Matrix 429/5xx, max 3 attempts —
startup burst was rate-limiting and dropping alerts
- Commit active scanner flag (scanning_mode=active) that was in working
tree but not in prior commit 8686eb5 despite the commit message
- Add 3 tests: MAC format filter, 429 retry success, 429 give-up
Fixes DevTrack #518
This commit is contained in:
+45
-27
@@ -11,6 +11,7 @@ import asyncio
|
||||
import json
|
||||
import logging
|
||||
import os
|
||||
import re
|
||||
import signal
|
||||
import sys
|
||||
import time
|
||||
@@ -115,6 +116,9 @@ def load_env():
|
||||
logging.error(f"Failed to load .env: {e}")
|
||||
|
||||
|
||||
_MAC_RE = re.compile(r'^[0-9A-Fa-f]{2}[:\-][0-9A-Fa-f]{2}[:\-][0-9A-Fa-f]{2}[:\-][0-9A-Fa-f]{2}[:\-][0-9A-Fa-f]{2}[:\-][0-9A-Fa-f]{2}$')
|
||||
|
||||
|
||||
def is_generic_name(name):
|
||||
"""
|
||||
Filter out unhelpful device names (generic manufacturer defaults, hex-only, too short).
|
||||
@@ -122,17 +126,21 @@ def is_generic_name(name):
|
||||
"""
|
||||
if not name or not isinstance(name, str):
|
||||
return True
|
||||
|
||||
|
||||
name = name.strip()
|
||||
|
||||
|
||||
# Too short
|
||||
if len(name) < 3:
|
||||
return True
|
||||
|
||||
|
||||
# All hex characters (looks like MAC address fragment)
|
||||
if all(c in "0123456789ABCDEFabcdef" for c in name):
|
||||
return True
|
||||
|
||||
|
||||
# MAC address format with colons or dashes (e.g. 45-48-39-7F-73-C7 or 45:48:39:7F:73:C7)
|
||||
if _MAC_RE.match(name):
|
||||
return True
|
||||
|
||||
# Common generic patterns
|
||||
generic_patterns = [
|
||||
"LE-", "BLE-", "BT-", "BT ", "Bluetooth", "Unknown",
|
||||
@@ -141,7 +149,7 @@ def is_generic_name(name):
|
||||
for pattern in generic_patterns:
|
||||
if pattern.lower() in name.lower():
|
||||
return True
|
||||
|
||||
|
||||
return False
|
||||
|
||||
|
||||
@@ -160,34 +168,44 @@ def fmt_duration(secs):
|
||||
|
||||
|
||||
def send_alert(msg):
|
||||
"""Send alert to Matrix room."""
|
||||
"""Send alert to Matrix room. Retries up to 3 times with exponential backoff on 429/5xx."""
|
||||
if not MATRIX_ACCESS_TOKEN:
|
||||
logging.warning("No MATRIX_ACCESS_TOKEN set, skipping alert")
|
||||
return
|
||||
|
||||
|
||||
url = (
|
||||
f"{MATRIX_HOMESERVER}/_matrix/client/v3/rooms/"
|
||||
f"{urllib.parse.quote(MATRIX_ROOM_ID, safe='')}/send/m.room.message"
|
||||
)
|
||||
payload = json.dumps({"msgtype": "m.text", "body": msg}).encode()
|
||||
req = urllib.request.Request(
|
||||
url,
|
||||
data=payload,
|
||||
headers={
|
||||
"Authorization": f"Bearer {MATRIX_ACCESS_TOKEN}",
|
||||
"Content-Type": "application/json",
|
||||
"User-Agent": "Mozilla/5.0 (X11; Linux x86_64) AppleWebKit/537.36",
|
||||
},
|
||||
method="POST",
|
||||
)
|
||||
try:
|
||||
with urllib.request.urlopen(req, timeout=10) as r:
|
||||
pass
|
||||
logging.debug(f"Alert sent to Matrix")
|
||||
except urllib.error.HTTPError as e:
|
||||
logging.error(f"Matrix send failed with HTTP {e.code}")
|
||||
except Exception as e:
|
||||
logging.error(f"Matrix send exception: {e}")
|
||||
|
||||
for attempt in range(3):
|
||||
req = urllib.request.Request(
|
||||
url,
|
||||
data=payload,
|
||||
headers={
|
||||
"Authorization": f"Bearer {MATRIX_ACCESS_TOKEN}",
|
||||
"Content-Type": "application/json",
|
||||
"User-Agent": "Mozilla/5.0 (X11; Linux x86_64) AppleWebKit/537.36",
|
||||
},
|
||||
method="POST",
|
||||
)
|
||||
try:
|
||||
with urllib.request.urlopen(req, timeout=10) as r:
|
||||
pass
|
||||
logging.debug("Alert sent to Matrix")
|
||||
return
|
||||
except urllib.error.HTTPError as e:
|
||||
if e.code in (429, 500, 502, 503, 504) and attempt < 2:
|
||||
wait = 2 ** attempt # 1s, 2s
|
||||
logging.warning(f"Matrix HTTP {e.code}, retrying in {wait}s")
|
||||
time.sleep(wait)
|
||||
else:
|
||||
logging.error(f"Matrix send failed with HTTP {e.code}")
|
||||
return
|
||||
except Exception as e:
|
||||
logging.error(f"Matrix send exception: {e}")
|
||||
return
|
||||
|
||||
|
||||
async def on_arrival(name, mac, rssi):
|
||||
@@ -256,8 +274,8 @@ async def scan_loop():
|
||||
|
||||
await on_arrival(name, mac, rssi)
|
||||
|
||||
# Active scanner — sends SCAN_REQ to get full advertisement data including device names
|
||||
async with BleakScanner(detection_callback=detection_callback) as scanner:
|
||||
# Active scanner — explicitly required; bleak 0.20 BlueZ backend defaults to passive
|
||||
async with BleakScanner(detection_callback=detection_callback, scanning_mode="active") as scanner:
|
||||
try:
|
||||
while not shutdown_event.is_set():
|
||||
await asyncio.sleep(1)
|
||||
|
||||
Reference in New Issue
Block a user