diff --git a/docu/docs/modul/multicast.md b/docu/docs/modul/multicast.md index def5284..151c6e3 100644 --- a/docu/docs/modul/multicast.md +++ b/docu/docs/modul/multicast.md @@ -31,14 +31,14 @@ Das Modul unterstützt: - Mehrere Startmarker (Delimiter) - Mehrere Text-RICs -- Netzident-RIC zur Paketfilterung +- Netzident-RIC zur Paketmarkierung - Automatische Bereinigung alter Tone-RICs (Fehlerfall: Auto-Clear) - Active Trigger System zur verlustfreien Paketauslieferung - Wildcards für spätere Weiterverarbeitung - Frequenz-basierte Trennung - Multi-Instanz-Betrieb mit geteiltem Zustand -Hinweis: Der Delimiter-RIC (0123456) wird automatisch gefiltert und nicht ausgegeben. +Hinweis: Der Delimiter-RIC (0123456) wird mit multicastRole: delimiter markiert und durchgereicht. Downstream-Filter (z.B. filter.regexFilter) können ihn bei Bedarf ausfiltern. **Wichtig:** Die Text-RIC (Message-RIC) wird **nicht als separates Paket ausgegeben**. Sie dient nur als Nachrichtenträger, der seinen Text an alle gesammelten Tone-RICs verteilt. Falls keine Tone-RICs vorhanden sind, wird die Text-RIC als `multicastMode: single` ausgegeben. @@ -53,9 +53,9 @@ Hinweis: Der Delimiter-RIC (0123456) wird automatisch gefiltert und nicht ausgeg |Feld|Beschreibung|Default| |----|------------|-------| |autoClearTimeout|Auto-Clear Timeout in Sekunden - Nicht zugestellte Empfänger werden nach dieser Zeit als incomplete ausgegeben|10| -|delimiterRics|Komma-getrennte Liste von Startmarkern, die einen Multicast-Block beginnen (leert sofort vorherige Empfänger und werden automatisch gefiltert wenn konfiguriert)|leer| +|delimiterRics|Komma-getrennte Liste von Startmarkern, die einen Multicast-Block beginnen (leert sofort vorherige Empfänger und werden mit multicastRole: delimiter markiert)|leer| |textRics|Komma-getrennte Liste von RICs, die den Alarmtext tragen|leer| -|netIdentRics|Komma-getrennte Liste von Netzwerk-Identifikations-RICs (werden automatisch gefiltert wenn konfiguriert)|leer| +|netIdentRics|Komma-getrennte Liste von Netzwerk-Identifikations-RICs (werden mit multicastRole: netident markiert)|leer| |triggerRic|RIC für das Wakeup-Trigger-Paket (optional, bei leer: dynamisch = erste Tone-RIC)|leer| |triggerHost|IP-Adresse für Loopback-Trigger|127.0.0.1| |triggerPort|Port für Loopback-Trigger|8080| @@ -110,7 +110,7 @@ Verwendet eine feste RIC (9999999) für das interne Wakeup-Trigger-Paket. textRics: '0299001,0310001' netIdentRics: '0000001' ``` -Filtert Netzident-Pakete (RIC 0000001) automatisch aus der Weiterverarbeitung. +Markiert Netzident-Pakete (RIC 0000001) mit multicastRole: netident. Downstream-Filter können sie gezielt ausfiltern (z.B. RegEx-Filter). ## Integration in Router-Konfiguration @@ -241,10 +241,10 @@ router: - `multicastRole` (string): Beschreibt die Rolle dieses Pakets innerhalb des Multicast-Ablaufs, besitzt einen der Werte: - - `delimiter`: Startmarker-Paket (wird automatisch gefiltert wenn delimiterRics konfiguriert) + - `delimiter`: Startmarker-Paket - `recipient`: tatsächlicher Empfänger - `single`: Einzelner, "normaler" Alarm (Tone-RIC = Text-RIC) - - `netident`: Netzwerk-Identifikations-Paket (wird automatisch gefiltert wenn netIdentRics konfiguriert) + - `netident`: Netzwerk-Identifikations-Paket - `multicastSourceRic` (string): RIC des ursprünglichen Message-RICs - `multicastRecipientCount` (string): Anzahl der Empfänger insgesamt @@ -262,7 +262,7 @@ router: - `message`: Bei incomplete-Modus leer, sonst Text von Text-RIC ### Rückgabewerte: -- **False**: Paket wurde blockiert (z.B. Delimiter/Netident-Filterung), Router stoppt Verarbeitung +- **False**: Paket wurde intern konsumiert (z.B. Tone-RIC wurde in den Buffer aufgenommen), Router stoppt Verarbeitung für dieses Paket - **Liste von Paketen**: Multicast-Verteilung, Router verarbeitet jedes Paket einzeln - **None**: Normaler Alarm, Router fährt mit unveränderten Paket fort @@ -378,11 +378,34 @@ Ausgegebene Multicast-Pakete: → RIC: 0345678 SubRIC: 3 Message: "B3 WOHNHAUS" (behält SubRIC 3!) ``` -### Automatische Filterung +### Paketmarkierung statt interner Filterung -- **Delimiter-Pakete**: Werden automatisch gefiltert (nicht weitergegeben), wenn `delimiterRics` konfiguriert ist -- **Netzident-Pakete**: Werden automatisch gefiltert (nicht weitergegeben), wenn `netIdentRics` konfiguriert ist -- **Filterung nach multicastRole**: Ermöglicht saubere nachgelagerte Verarbeitung ohne manuelle Filter +Das Modul filtert keine inhaltlich relevanten Pakete. +Alle Pakete mit Alarminhalt werden mit `multicastRole` markiert und +weitergereicht. Die Filterung nach Bedarf erfolgt nachgelagert, +z.B. mit `filter.regexFilter`. + +Eine Ausnahme bilden **Tone-RICs** (leere Nachrichten): Diese werden +intern im Buffer gesammelt und bei einem complete-Alarm in die +Listenfelder aggregiert. Sie erscheinen nie als eigenständige Pakete +im Router. + +Pakete und ihre Rollen: +- **Delimiter-Pakete**: Erhalten `multicastRole: delimiter` +- **Netzident-Pakete**: Erhalten `multicastRole: netident` +- **Empfänger-Pakete**: Erhalten `multicastRole: recipient` +- **Einzelalarme**: Erhalten `multicastRole: single` + +Beispiel-Filter um Delimiter und Netident auszublenden: +```yaml +- type: module + res: filter.regexFilter + config: + - name: "Nur echte Alarme" + checks: + - field: multicastRole + regex: ^(recipient|single)$ +``` ### Multi-Instanz-Betrieb diff --git a/module/multicast.py b/module/multicast.py index 55f8665..4cecd54 100644 --- a/module/multicast.py +++ b/module/multicast.py @@ -10,7 +10,7 @@ r"""! by Bastian Schroll @file: multicast.py -@date: 26.01.2025 +@date: 28.03.2026 @author: Claus Schichl @description: multicast module """ @@ -70,7 +70,8 @@ class BoswatchModule(ModuleBase): @param None @return None""" self._my_frequencies = set() - self.name = "Multicast" + self.instance_id = hex(id(self))[-4:] + self.name = f"MCAST_{self.instance_id}" self._auto_clear_timeout = int(self.config.get("autoClearTimeout", default=10)) self._hard_timeout = self._auto_clear_timeout * 3 @@ -94,9 +95,6 @@ class BoswatchModule(ModuleBase): self._trigger_host = self.config.get("triggerHost", default=self._TRIGGER_HOST) self._trigger_port = int(self.config.get("triggerPort", default=self._TRIGGER_PORT)) - self._block_delimiter = bool(self._delimiter_rics) - self._block_netident = bool(self._netident_rics) - logging.info("[%s] Multicast module loaded", self.name) with BoswatchModule._lock: @@ -118,28 +116,44 @@ class BoswatchModule(ModuleBase): def doWork(self, bwPacket): r"""!Process an incoming packet and handle multicast logic. - @param bwPacket: A BOSWatch packet instance - @return bwPacket, a list of packets, or False if blocked""" + Enriches packets with multicast metadata (mode, role, source). + Does NOT filter - all packets pass through, downstream modules handle filtering. + + @param bwPacket: A BOSWatch packet instance or list of packets + @return bwPacket, a list of packets, or None if no processing""" + if isinstance(bwPacket, list): + result_packets = [] + for single_packet in bwPacket: + processed = self.doWork(single_packet) + if processed is not None and processed is not False: + if isinstance(processed, list): + result_packets.extend(processed) + else: + result_packets.append(processed) + return result_packets if result_packets else None + packet_dict = self._get_packet_data(bwPacket) msg = packet_dict.get("message") ric = packet_dict.get("ric") freq = packet_dict.get("frequency", "default") mode = packet_dict.get("mode") + # Handle wakeup triggers if msg == BoswatchModule._MAGIC_WAKEUP_MSG: if self._trigger_ric and ric != self._trigger_ric: - pass - else: - logging.debug("[%s] Wakeup trigger received (RIC=%s)", self.name, ric) - queued = self._get_queued_packets() - return queued if queued else False + return None + logging.debug("[%s] Wakeup trigger received (RIC=%s)", self.name, ric) + queued = self._get_queued_packets() + return queued if queued else None + # Only process POCSAG if mode != "pocsag": queued = self._get_queued_packets() return queued if queued else None self._my_frequencies.add(freq) + # Determine if this is a text-RIC is_text_ric = False if self._text_rics: is_text_ric = ric in self._text_rics and msg and msg.strip() @@ -155,21 +169,23 @@ class BoswatchModule(ModuleBase): queued_packets = self._get_queued_packets() incomplete_packets = None if is_text_ric else self._check_instance_auto_clear(freq) + # === CONTROL PACKETS (netident, delimiter) === + # Mark and pass through - no filtering! + if self._netident_rics and ric in self._netident_rics: self._set_mcast_metadata(bwPacket, "control", "netident", ric) - result = self._combine_results(incomplete_packets, queued_packets, [bwPacket]) - return self._filter_output(result) + return self._combine_results(incomplete_packets, queued_packets, [bwPacket]) if self._delimiter_rics and ric in self._delimiter_rics: delimiter_incomplete = self._handle_delimiter(freq, ric, bwPacket) - result = self._combine_results(delimiter_incomplete, incomplete_packets, queued_packets) - return self._filter_output(result) + return self._combine_results(delimiter_incomplete, incomplete_packets, queued_packets) + # === TONE-RICs (no message) === if not msg or not msg.strip(): self._add_tone_ric_packet(freq, packet_dict) - result = self._combine_results(incomplete_packets, queued_packets, False) - return self._filter_output(result) + return self._combine_results(incomplete_packets, queued_packets, False) + # === TEXT-RICs (with message) === if is_text_ric and msg: logging.info("[%s] Text-RIC received: RIC=%s", self.name, ric) alarm_packets = self._distribute_complete(freq, packet_dict) @@ -180,18 +196,16 @@ class BoswatchModule(ModuleBase): if not alarm_packets: logging.warning("[%s] No tone-RICs for text-RIC=%s", self.name, ric) normal = self._enrich_normal_alarm(bwPacket, packet_dict) - result = self._combine_results(normal, incomplete_packets, queued_packets) + return self._combine_results(normal, incomplete_packets, queued_packets) else: - result = self._combine_results(alarm_packets, incomplete_packets, queued_packets) - return self._filter_output(result) + return self._combine_results(alarm_packets, incomplete_packets, queued_packets) + # === SINGLE ALARM (message but no text-RICs configured) === if msg: normal = self._enrich_normal_alarm(bwPacket, packet_dict) - result = self._combine_results(normal, incomplete_packets, queued_packets) - return self._filter_output(result) + return self._combine_results(normal, incomplete_packets, queued_packets) - result = self._combine_results(incomplete_packets, queued_packets) - return self._filter_output(result) + return self._combine_results(incomplete_packets, queued_packets) # ============================================================ # PACKET PROCESSING HELPERS (called by doWork) @@ -220,31 +234,6 @@ class BoswatchModule(ModuleBase): logging.warning("[%s] Error: %s", self.name, e) return {} - def _filter_output(self, result): - r"""!Apply multicastRole filtering before output. - - @param result: Single packet, list of packets, None or False - @return Final packet(s) or False if blocked""" - if result is None or result is False: - return result - - def get_role(packet): - """Helper to extract multicastRole from Packet object""" - packet_dict = self._get_packet_data(packet) - return packet_dict.get("multicastRole") - - if isinstance(result, list): - filtered = [p for p in result if self._should_output_packet(get_role(p))] - if not filtered: - logging.debug("All packets filtered out by multicastRole") - return False - return filtered if len(filtered) > 1 else filtered[0] - else: - if self._should_output_packet(get_role(result)): - return result - logging.debug("Packet filtered out: multicastRole=%s", get_role(result)) - return False - def _combine_results(self, *results): r"""!Combine multiple result sources into a single list or status. @@ -266,17 +255,6 @@ class BoswatchModule(ModuleBase): return combined return False if has_false else None - def _should_output_packet(self, multicast_role): - r"""!Check if packet should be output based on role. - - @param multicast_role: The role string to check - @return bool: True if allowed""" - if self._block_delimiter and multicast_role == "delimiter": - return False - if self._block_netident and multicast_role == "netident": - return False - return True - # ============================================================ # TONE-RIC BUFFER MANAGEMENT # ============================================================