Skip to content

[feat/multicast]: Add multi-instance multicast module with active trigger system#154

Open
KoenigMjr wants to merge 6 commits into
BOSWatch:developfrom
KoenigMjr:feat/multicast
Open

[feat/multicast]: Add multi-instance multicast module with active trigger system#154
KoenigMjr wants to merge 6 commits into
BOSWatch:developfrom
KoenigMjr:feat/multicast

Conversation

@KoenigMjr
Copy link
Copy Markdown
Contributor

🇩🇪 DE

Ich habe die Idee von thierry3000 aufgeschnappt und in den letzten Monaten ein Multicast-Modul geschrieben.
Sollte somit das Issue #87 und auch den PR #88 schließen.

Kurz und bündig
Dieses PR implementiert ein vollständiges Multicast-Modul für BOSWatch 3, das es ermöglicht, Alarmnachrichten automatisch an mehrere Empfänger (RICs) zu verteilen. Das Modul integriert sich nahtlos in das bestehende System und erweitert die Kernmodule um die Fähigkeit, Paketlisten zu verarbeiten.

Detaillierte Änderungen

1. Kernmodul module/multicast.py

  • Vollständige Multicast-Alarmverarbeitung mit automatischer Verteilung
  • Active Trigger System: Garantiert verlustfreie Paketauslieferung via Loopback-Socket
  • Multi-Instanz-Betrieb: Mehrere Modulinstanzen mit gemeinsamer Thread-Safe-State
  • Frequenz-Trennung: Verhindert Vermischung von Alarmen verschiedener Sender
  • Automatische Filterung: Delimiter- und Netzident-RICs werden intelligent herausgefiltert
  • Konfigurierbare Parameter:
    • autoClearTimeout: Auto-Clear nach X Sekunden (Default: 10s)
    • delimiterRics: Startmarker für neue Multicast-Blöcke
    • textRics: RICs mit eigentlicher Alarmnachricht
    • netIdentRics: Netzwerk-ID-RICs (Filterung)
    • triggerRic/Host/Port: Wakeup-Trigger-Konfiguration -> (Standardmäßig wird das RIC vom hängengebliebenen Alarm verwendet)

2. Ergänzungen am bestehenden Programmcode

module/moduleBase.py

  • List-Support für Module: Neue Logik zur Verarbeitung von Paketlisten
  • Rekursive Verarbeitung einzelner Pakete mit intelligenter Resultat-Kombination
  • Unterstützt: False (gefiltert), None (unverändert), Liste, einzelnes Paket
  • Ermöglicht Multicast-Verteilung durch Module ohne Bruch der bestehenden Architektur

plugin/pluginBase.py

  • ist-Support für Plugins: Vollständige Lifecycle-Unterstützung für Paketlisten
  • Jedes Paket durchläuft vollständig: Setup → Alarm → Teardown
  • Garantiert konsistente Plugin-Verarbeitung für Multi-Pakete-Szenarien

3. Dokumentation

  • Ausführliche Deutsche Dokumentation mit Beispielen
  • Funktionsweise in 3 Phasen erklärt (Delimiter, Tone-RICs, Text-RIC)
  • 4 praktische Konfigurationsbeispiele
  • Detaillierte Erklärung aller neuen Paketfelder (multicastMode, multicastRole, etc.)
  • 7 neue Wildcards für Plugin-Verarbeitung
  • Technische Details: Trigger-System, Frequenz-Trennung, SubRIC-Erhaltung
  • Konfigurationsfelder in Config-Editor integriert & UI-Unterstützung für alle Parameter mit Defaults

Wichtige Punkte

  • Abwärtskompatibel: Existierende Module/Plugins funktionieren ohne Änderungen
  • Thread-Safe: Alle Operationen mit Locks geschützt

🇬🇧 EN

I picked up the idea from thierry3000 and developed a multicast module over the past few months.
This should resolve Issue #87 as well as PR #88.

In a nutshell
This PR implements a complete multicast module for BOSWatch 3, enabling alarm messages to be automatically distributed to multiple recipients (RICs). The module integrates seamlessly into the existing system and extends the core modules with the ability to process packet lists.

Detailed Changes

1. Core Module module/multicast.py

  • Full multicast alarm processing with automatic distribution

  • Active Trigger System: Ensures lossless packet delivery via loopback socket

  • Multi-instance operation: Multiple module instances with shared thread-safe state

  • Frequency separation: Prevents mixing of alarms from different transmitters

  • Automatic filtering: Delimiter and network identifier RICs are intelligently filtered out

  • Configurable parameters:

    • autoClearTimeout: Auto-clear after X seconds (default: 10s)
    • delimiterRics: Start markers for new multicast blocks
    • textRics: RICs containing the actual alarm message
    • netIdentRics: Network ID RICs (filtered)
    • triggerRic/Host/Port: Wakeup trigger configuration → (by default, the RIC of the stuck alarm is used)

2. Enhancements to Existing Codebase

module/moduleBase.py

  • List support for modules: New logic for processing packet lists
  • Recursive processing of individual packets with intelligent result combination
  • Supports: False (filtered), None (unchanged), list, single packet
  • Enables multicast distribution through modules without breaking existing architecture

plugin/pluginBase.py

  • List support for plugins: Full lifecycle support for packet lists
  • Each packet goes through the full cycle: Setup → Alarm → Teardown
  • Ensures consistent plugin processing for multi-packet scenarios

3. Documentation

  • Comprehensive English documentation with examples
  • Functionality explained in 3 phases (Delimiter, Tone RICs, Text RIC)
  • 4 practical configuration examples
  • Detailed explanation of all new packet fields (multicastMode, multicastRole, etc.)
  • 7 new wildcards for plugin processing
  • Technical details: Trigger system, frequency separation, SubRIC preservation
  • Configuration fields integrated into the config editor & UI support for all parameters with defaults

Key Points

  • Backward compatible: Existing modules/plugins work without changes
  • Thread-safe: All operations are protected with locks

…gger system

Introduce a robust multicast processing module for POCSAG that correlates
empty tone-RICs (recipients) with subsequent text-RICs (content).

Key Features:
- Four Output Modes: Internally supports 'complete', 'incomplete', 'single',
  and 'control'. Functional alarms are delivered as the first three, while
  technical 'control' packets (Delimiters/NetIdent) are filtered by default.
- Active Trigger System: Implements a loss-free deferred delivery mechanism
  using a loopback socket (TCP) to re-inject wakeup packets, flushing the
  internal queue during auto-clear timeouts.
- Shared State & Multi-Instance: State is shared across instances but
  separated by frequency to prevent crosstalk in multi-frequency setups.
- Data Aggregation: Automatically generates '{FIELD}_list' wildcards (e.g.,
  RIC_LIST, DESCRIPTION_LIST) for all collected recipients, enabling
  consolidated notifications in downstream plugins.
- Dynamic Filtering: Automatically blocks Delimiter and NetIdent RICs from
  reaching subsequent plugins if they are defined in the configuration.

Infrastructural Changes:
- ModuleBase: Expanded return semantics to support:
  * False: Explicitly blocks/drops a packet.
  * List: Allows a module to expand one input into multiple output packets.
- PluginBase: Updated to handle lists of packets, ensuring a full
  setup->alarm->teardown lifecycle for every individual element.
- Remove redundant list-handling block in doWork() - already handled by moduleBase._run()
- Fix bare except to except (ValueError, TypeError) in _copy_packet_dict_to_packet()
- Replace f-string logging with lazy %-style in _enrich_normal_alarm() and _set_mcast_metadata()
- Remove unused _trigger_ric_mode variable
- Simplify instance name from dynamic MCAST_{id} to static "Multicast" (no debug value)
- Update doWork() docstring to reflect single-packet-only parameter
- Add extension hook comment to _perform_instance_tick()
@KoenigMjr KoenigMjr marked this pull request as draft March 28, 2026 14:23
…tream

Remove internal filtering of delimiter and netident packets from the
multicast module. All packets are now passed through with multicastRole
metadata set, allowing downstream filters (e.g. filter.regexFilter) to
handle filtering as needed.

Tone-RICs remain internally consumed as they carry no alarm-relevant
information outside the module.

Update documentation to reflect new behavior and add regexFilter
example for filtering by multicastRole.
@KoenigMjr
Copy link
Copy Markdown
Contributor Author

KoenigMjr commented Mar 28, 2026

🇩🇪 DE
Ich habe mich noch entschieden, (fast) alle Filterungen aus dem Modul herauszunehmen und das Modul nur den Job machen zu lassen, welcher im Name steht (Schuster bleib bei deinen Leisten). Einzig die Text-RIC wird verarbeitet und nicht weitergegeben, wenn korrekterweise ein Multicast-Alarm daraus entstanden ist.
Alle anderen werden nun nur gekennzeichnet und weitergeleitet.


🇬🇧 EN
I decided to remove (almost) all filtering from the module and let it only do the job its name implies (Cobbler, stick to your last). The only exception is the text-RIC: it is consumed and not forwarded when a multicast alarm has been successfully generated from it.
All other packets are now only tagged and passed through.

@KoenigMjr KoenigMjr marked this pull request as ready for review April 5, 2026 08:30
@Schrolli91
Copy link
Copy Markdown
Member

Ich finde das mit den Listen in den Basis Klassen etwas "unschön", würde ich es wohl nennen :-D

Bin mir nicht ganz sicher ob man das auch irgendwie besser lösen kann. Habe da aber leider gerade auch keine Zeit tiefer rein zu gehen.

Kannst du mir nochmal kurz umreisen, wieso dies nötig ist?

@KoenigMjr
Copy link
Copy Markdown
Contributor Author

Du hast absolut recht! Vielen herzlichen Dank für deinen Input!
Ist ein Relikt aus dem steten Umbau und Bugfixing im Development-Prozess.
Ich glaube, man kann die Class-Variablen zu Instanz-Variablen umbauen ohne viel Kummer, da ja die Frequenz der Schlüssel in der Zuordnung ist (Multi-Setup möglich) und somit auch mehr "Python-Standard" und verbessert auch die Kapselung.
Ich sehe mir den Code nochmal im Detail an und werde dann einen neuen Commit zum reviewen posten, sobald ich eine Lösung parat habe.

Auf den ersten Blick macht mir der _cleanup_thread bisschen Kummer, aber ich versuche mich und bin zuversichtlich, dass ich das irgendwie gelöst bekomme!

@KoenigMjr KoenigMjr marked this pull request as draft April 16, 2026 18:46
…ables to instance scope

This commit refactors the internal state management to ensure true
multi-instance capability. While previous commits shared state via class
variables, this change encapsulates all buffers and flags within the
individual instance.

Key changes:
- Migration of state: Moved `_tone_ric_packets`, `_last_tone_ric_time`,
  and processing flags from class variables to instance variables (`self`).
- Thread Isolation: Shifted the cleanup logic to a per-instance
  `_cleanup_worker` thread, ensuring that timeouts are managed
  independently for each route/configuration.
- Wildcard Safety: Isolated `_wildcards_registered` to prevent
  registration conflicts between multiple multicast instances.
- Robust Hard-Timeout: Simplified `_cleanup_hard_timeout` to act
  strictly on the instance's own state.

This refactoring resolves the "architectural dinosaur" of shared class
state, making the module fully thread-safe and reliable for complex
multi-route and multi-frequency deployments.
@KoenigMjr KoenigMjr marked this pull request as ready for review April 28, 2026 05:49
@KoenigMjr
Copy link
Copy Markdown
Contributor Author

Sooo...
hab es nochmal überarbeitet..

Wesentliche Änderungen:

  • Zustandsverwaltung: _tone_ric_packets, _last_tone_ric_time und
    Processing-Flags von Klassen- in Instanz-Variablen (self) migriert
  • Thread-Isolation: Cleanup-Logik zu eigenständigem _cleanup_worker
    Thread pro Instanz verschoben → unabhängige Timeout-Verwaltung
  • Wildcard-Sicherheit: _wildcards_registered isoliert um
    Registrierungskonflikte zu verhindern
  • Robuster Hard-Timeout: Vereinfacht auf reine Instanz-Verwaltung

Dokumentation erweitert:

  • Detaillierte Erklärung der Multicast-Phasen (4 Stufen)
  • Ausführliche Tabellen zur Paket-Modifikation und Metadaten
  • Praktische Szenarien mit vollständigen Beispiel-Tabellen
  • Klarstellung: Tone-RICs werden gepuffert und nicht sofort ausgegeben

Bitte nochmal kritisch reviewen :)

@Schrolli91 Schrolli91 self-requested a review May 5, 2026 06:50
@Schrolli91
Copy link
Copy Markdown
Member

Verstehe das Listen Handling in den Base Klassen immer noch nicht so ganz.
Wieso brauchen wir hier die Listen? Können die Pakete nicht schon vorher aufgesplittet werden?
Hab leider keine Zeit aktuell, mir das wirklich im Detail anzusehen.

@KoenigMjr
Copy link
Copy Markdown
Contributor Author

Guten Morgen!
Die Idee hinter den Listen ist, das die Multicast-Alarme sowohl einzeln (Direkt) oder auch gebündelt (Listen) verarbeitet werden können.
Als Beispiel kann FF A-Dorf auf seine RIC zugreifen und bekommt da auch den Text hingespiegelt.
Will FF A-Dorf aber noch am Alarmmonitor z.B. wissen, wer außer A-Dorf zu dem Ereignis alarmiert worden ist (oder Sondereinheiten von A-Dorf), dann sind die Listen das mittel der Wahl, die ja sogar an deren ihrer RIC schon dranhängen ({RIC_LIST})

Gibts einen schöneren Weg (nativ?) den ich übersehen habe?

@Schrolli91
Copy link
Copy Markdown
Member

Schrolli91 commented May 18, 2026

Ehrlich gesagt verstehe ich das "Feature" nicht wirklich.
Hat das direkt was mit Multicast zu tun (also die Paketlisten-Funktion)?

@KoenigMjr
Copy link
Copy Markdown
Contributor Author

Vielleicht liegt ein Missverständnis im Wording "liste" vor.

Um alle abzuholen, fange ich weiter hinten mit dem Erklären an, ich vermute, du wirst jetzt die Basics nicht brauchen, aber um sicher zu gehen, How does it work:

1. Warum können wir die Pakete nicht vorher aufsplitten?
Die Alarme kommen an:

  • 21:00:00 Uhr -> RIC 1 (Tone) -> Kein Text vorhanden
  • 21:00:01 Uhr -> RIC 2 (Tone) -> Kein Text vorhanden
  • 21:00:05 Uhr -> RIC 3 (Text) -> Hier kommt erst der Text ("B3 Wohnhaus")

Wir können sie vorher nicht verarbeiten oder splitten, weil den ersten beiden RICs der Text fehlt. Das Multicast-Modul muss sie also im RAM sammeln (Buffer) und den Router blockieren (return False), bis die Text-RIC eintrifft.


2. Warum geben wir eine Python-Liste von Paketen zurück?
Kurzum: Kompatibilität
Sobald die Text-RIC eintrifft, haben wir im RAM z.B. 3 fertige Alarme. Jetzt gibt es IMHO zwei Möglichkeiten, wie wir das an BOSWatch übergeben:

Möglichkeit A (Ein einziges Paket mit einer Liste von RICs darin):
--> Machen wir NICHT.
Wir senden ein Paket an den Router, in dem im Feld ric eine Liste steht: ric: [111, 222, 333].
Problem: Alle Standard-Plugins (Telegram, MySQL, Mail) werden crashen oder ignorieren das, weil sie ein Textfeld mit einer RIC erwarten. So z.B. MySQL würde nur einen Eintrag schreiben, Telegram nur an eine Gruppe senden.

Möglichkeit B (Eine Python-Liste aus einzelnen, vollwertigen Paketen)
--> So ist es aktuell implementiert.
Wir "explodieren" die gesammelten Daten im Moment des Text-Eingangs in eine echte Python-Liste von eigenständigen Paketen ([Paket_für_RIC_1, Paket_für_RIC_2]).
Der Router nimmt diese Liste und arbeitet sie für die nachfolgenden Plugins nacheinander ab.
Vorteil: Volle native Kompatibilität. Telegram, MySQL und alle anderen Plugins merken gar nicht, dass das ein Multicast war. Sie verarbeiten einfach 3 saubere, einzelne Alarme mit jeweils korrekter RIC und Text.


3. Was hat es mit der ric_list (dem Feature) auf sich?
Das ist der Mehrwert. Da wir im RAM ohnehin gerade wissen, welche Einheiten zusammen alarmiert wurden, schreiben wir diese Info als reines Textfeld (ric_list: "111, 222, 333") in jedes der drei Pakete.

Wer das nicht braucht, ignoriert es. Wer aber einen Alarmmonitor betreibt, kann über die Wildcard {RIC_LIST} auf einen Blick sehen: "Ah, meine RIC hat ausgelöst, und ich sehe im Monitor direkt, dass die Nachbarwehr auch alarmiert wurde." Das ist das eigentliche "Multicast-Feature" für den Endanwender.

Ich hoffe, das macht den Ansatz verständlicher!
Ohne das Zurückgeben der Paket-Liste an den Router müssten sämtliche Core-Plugins von BOSWatch umgeschrieben werden, damit sie mit Multicast-Gruppen umgehen können.
--> NO-GO

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Projects

None yet

Development

Successfully merging this pull request may close these issues.

2 participants