[feat/multicast]: add multi-instance multicast module with active trigger 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.
This commit is contained in:
KoenigMjr 2025-11-25 16:25:28 +01:00
parent 7d4cb57a6e
commit 0b9387af08
5 changed files with 1069 additions and 1 deletions

View file

@ -56,6 +56,32 @@ class ModuleBase(ABC):
@param bwPacket: A BOSWatch packet instance
@return bwPacket or False"""
# --- FIX: Multicast list support for Module ---
if isinstance(bwPacket, list):
result_packets = []
for single_packet in bwPacket:
# Recursive call for single packet
processed = self._run(single_packet)
# new logic:
if processed is False:
# filter called 'False' -> packet discarded
continue
elif processed is None:
# module returned None -> keep packet unchanged
result_packets.append(single_packet)
elif isinstance(processed, list):
# module returned new list -> extend
result_packets.extend(processed)
else:
# module returned modified packet -> add
result_packets.append(processed)
# if list is not empty, return it. else False (filter all).
return result_packets if result_packets else False
# -----------------------------------------------
self._runCount += 1
logging.debug("[%s] run #%d", self._moduleName, self._runCount)