From 79ee58d91a8c388665c82c61386e91856046a180 Mon Sep 17 00:00:00 2001 From: MarekWo Date: Tue, 14 Jul 2026 19:08:37 +0200 Subject: [PATCH] feat(observer): scheduled flood adverts Observer-owned daemon thread checks every 10 minutes whether advert_interval_hours has elapsed since the persisted last-advert timestamp and sends a flood advert via the existing DeviceManager wrapper. Timestamp persists across restarts so redeploys don't re-advertise early; failures and disconnected device skip the send without touching the timestamp. Co-Authored-By: Claude Fable 5 --- app/main.py | 1 + app/observer.py | 27 ++++++++++++++++++++++++--- 2 files changed, 25 insertions(+), 3 deletions(-) diff --git a/app/main.py b/app/main.py index 42a6206..8950ff8 100644 --- a/app/main.py +++ b/app/main.py @@ -300,6 +300,7 @@ def create_app(): device_manager = DeviceManager(config, db, socketio, observer=observer_manager) app.device_manager = device_manager observer_manager.device_manager = device_manager + observer_manager.start_advert_scheduler() # Start device connection in background (non-blocking) device_manager.start() diff --git a/app/observer.py b/app/observer.py index 01e1728..971c0ca 100644 --- a/app/observer.py +++ b/app/observer.py @@ -187,6 +187,7 @@ class ObserverManager: LAST_ADVERT_KEY = 'observer_last_advert_at' DEFAULT_SETTINGS = {'enabled': False, 'iata': '', 'advert_interval_hours': 0} STATUS_EMIT_MIN_INTERVAL = 2.0 # seconds between observer_status socket emits + ADVERT_CHECK_INTERVAL_SEC = 600 # how often the advert loop re-checks the schedule def __init__(self, db, socketio=None): self.db = db @@ -204,6 +205,8 @@ class ObserverManager: self._packets_seen = 0 self._packets_published = 0 self._last_status_emit = 0.0 + self._advert_thread = None + self._advert_stop = None # ---------------- settings ---------------- @@ -537,13 +540,31 @@ class ObserverManager: except Exception as e: logger.debug(f"Observer: status emit failed: {e}") - # ---------------- advert scheduler (APScheduler job) ---------------- + # ---------------- advert scheduler ---------------- + + def start_advert_scheduler(self): + """Start the periodic advert check thread (idempotent). + + The thread always runs; maybe_send_advert() gates on the observer + being enabled and advert_interval_hours > 0, so settings changes + take effect on the next check without thread management. + """ + if self._advert_thread and self._advert_thread.is_alive(): + return + self._advert_stop = threading.Event() + self._advert_thread = threading.Thread( + target=self._advert_loop, name='observer-advert', daemon=True) + self._advert_thread.start() + + def _advert_loop(self): + while not self._advert_stop.wait(self.ADVERT_CHECK_INTERVAL_SEC): + self.maybe_send_advert() def maybe_send_advert(self): """Send a flood advert when the configured interval has elapsed. - Called every few minutes by APScheduler; the last-advert timestamp - persists in app_settings so restarts don't re-advertise early. + The last-advert timestamp persists in app_settings so restarts + don't re-advertise early. """ try: settings = self.get_settings()