From 46c7194cb358dea3168da19ead9b5a5a894e8ddd Mon Sep 17 00:00:00 2001 From: AJ Quick Date: Sat, 8 Aug 2026 09:23:33 -0600 Subject: [PATCH 1/8] Change device name retrieval to use observer name Updated method to retrieve device name for PacketCapture reporting to use observer name instead of bot name. --- .../service_plugins/packet_capture_service.py | 21 +++++++++++++++++-- 1 file changed, 19 insertions(+), 2 deletions(-) diff --git a/modules/service_plugins/packet_capture_service.py b/modules/service_plugins/packet_capture_service.py index 509d535..f11567d 100644 --- a/modules/service_plugins/packet_capture_service.py +++ b/modules/service_plugins/packet_capture_service.py @@ -1212,7 +1212,7 @@ class PacketCaptureService(BaseServicePlugin): payload_len = str(max(0, packet_len - 1 - transport_bytes - 1 - path_bytes)) # Get device name and public key - device_name = self._get_bot_name() + device_name = self._get_observer_name() if not device_name: device_name = "MeshCore Device" @@ -1556,6 +1556,23 @@ class PacketCaptureService(BaseServicePlugin): self.logger.debug(f"Decode error traceback: {traceback.format_exc()}") return None + def _get_observer_name(self) -> str: + """Get observer name for PacketCapture MQTT reporting. + + Allows the observer/analyzer identity to differ from the + MeshCore RF node and bot name. + """ + observer_name = self.bot.config.get( + "PacketCapture", + "observer_name", + fallback="" + ).strip() + + if observer_name: + return observer_name + + return self._get_bot_name() + def _get_bot_name(self) -> str: """Get bot name from device or config. @@ -2767,7 +2784,7 @@ class PacketCaptureService(BaseServicePlugin): firmware_info = await self.get_firmware_info() # Get device name and public key - device_name = self._get_bot_name() + device_name = self._get_observer_name() if not device_name: device_name = "MeshCore Device" From 8ea806c0745ae92bc5795f6056d1d290569c1fd2 Mon Sep 17 00:00:00 2001 From: AJ Quick Date: Sat, 8 Aug 2026 09:25:57 -0600 Subject: [PATCH 2/8] Add observer_name option to config.ini.example Added optional observer_name configuration for MQTT. --- config.ini.example | 4 ++++ 1 file changed, 4 insertions(+) diff --git a/config.ini.example b/config.ini.example index 64caa2f..ccf0fda 100644 --- a/config.ini.example +++ b/config.ini.example @@ -1860,6 +1860,10 @@ auth_token_method = device # Default: XYZ (invalid if not set) iata = XYZ +# Optional observer name used in MQTT packet/status payloads. +# If unset, the connected device/bot name is used. +observer_name = + # MQTT Broker Configuration # You can configure multiple MQTT brokers by using mqtt1_*, mqtt2_*, mqtt3_*, etc. # Each broker can have independent settings for transport, TLS, authentication, and topics. From 9eeb226def472f1d9d89187d59df6aacbad545f0 Mon Sep 17 00:00:00 2001 From: AJ Quick Date: Sat, 8 Aug 2026 09:26:56 -0600 Subject: [PATCH 3/8] Add observer_name configuration to packet-capture Add optional observer name configuration for MQTT. --- docs/packet-capture.md | 4 ++++ 1 file changed, 4 insertions(+) diff --git a/docs/packet-capture.md b/docs/packet-capture.md index 462c42d..0bc1d43 100644 --- a/docs/packet-capture.md +++ b/docs/packet-capture.md @@ -50,6 +50,10 @@ mqtt_skip_unparseable_packets = true # Skip MQTT when content hash is all zero # Optional: skip MQTT for ADVERT packets whose Ed25519 signature does not verify (damaged or spoofed mesh payload). # Does not affect file/JSONL capture. advert_require_valid_signature = false + +# Optional name reported as the MQTT observer "origin". +# Defaults to the connected MeshCore device/bot name. +observer_name = CustomObserverBot ``` ### Authentication From 064f098ac32cf6c92246e2aab0b17bd7c2993ce7 Mon Sep 17 00:00:00 2001 From: AJ Quick Date: Sat, 8 Aug 2026 09:44:30 -0600 Subject: [PATCH 4/8] Add tests for PacketCapture observer_name functionality This test suite verifies the behavior of the PacketCaptureService regarding the observer_name functionality, ensuring it correctly overrides and falls back to the device name as expected. --- .../unit/test_packet_capture_observer_name.py | 108 ++++++++++++++++++ 1 file changed, 108 insertions(+) create mode 100644 tests/unit/test_packet_capture_observer_name.py diff --git a/tests/unit/test_packet_capture_observer_name.py b/tests/unit/test_packet_capture_observer_name.py new file mode 100644 index 0000000..9708403 --- /dev/null +++ b/tests/unit/test_packet_capture_observer_name.py @@ -0,0 +1,108 @@ +"""Tests for PacketCapture observer_name override.""" + +import configparser +import logging +import types +from unittest.mock import MagicMock + +from modules.service_plugins.packet_capture_service import PacketCaptureService + + +LOGGER = logging.getLogger("test-packet-capture-observer-name") +DEVICE_KEY = "ab" * 32 + + +def build_service( + observer_name=None, + device_name="DeviceNode", + bot_name="BotNode", +): + """Build a minimal PacketCaptureService without running __init__.""" + config = configparser.ConfigParser() + + config["Bot"] = { + "bot_name": bot_name, + } + + config["PacketCapture"] = {} + + if observer_name is not None: + config["PacketCapture"]["observer_name"] = observer_name + + bot = MagicMock() + bot.config = config + + service = object.__new__(PacketCaptureService) + service.bot = bot + service.logger = LOGGER + service.meshcore = types.SimpleNamespace( + self_info={ + "name": device_name, + "public_key": DEVICE_KEY, + } + ) + + service.debug = False + service.decode_payloads = False + service.channel_key_store = None + + return service + + +def test_observer_name_override(): + service = build_service( + observer_name="ObserverStation", + device_name="DeviceNode", + ) + + assert service._get_observer_name() == "ObserverStation" + + +def test_observer_name_falls_back_to_device_name(): + service = build_service( + device_name="DeviceNode", + ) + + assert service._get_observer_name() == "DeviceNode" + + +def test_blank_observer_name_falls_back_to_device_name(): + service = build_service( + observer_name=" ", + device_name="DeviceNode", + ) + + assert service._get_observer_name() == "DeviceNode" + + +def test_packet_payload_uses_observer_name(): + service = build_service( + observer_name="ObserverStation", + device_name="DeviceNode", + ) + + packet_info = { + "route_type": "FLOOD", + "payload_type": "ADVERT", + "payload_type_value": 4, + "payload_bytes": 1, + "path_len": 0, + "path_byte_length": 0, + "path": [], + "packet_hash": "0123456789ABCDEF", + "has_transport_codes": False, + } + + payload = { + "snr": 5.0, + "rssi": -90, + } + + result = service._format_packet_data( + "00AA", + packet_info, + payload, + ) + + assert result["origin"] == "ObserverStation" + assert result["origin_id"] == DEVICE_KEY.upper() From 71264f47937cd0d3007e3246d57aee4d87970667 Mon Sep 17 00:00:00 2001 From: AJ Quick Date: Sat, 8 Aug 2026 09:49:02 -0600 Subject: [PATCH 5/8] Organize imports in test_packet_capture_observer_name.py Refactor imports in test_packet_capture_observer_name.py --- tests/unit/test_packet_capture_observer_name.py | 9 ++++++--- 1 file changed, 6 insertions(+), 3 deletions(-) diff --git a/tests/unit/test_packet_capture_observer_name.py b/tests/unit/test_packet_capture_observer_name.py index 9708403..3dcaec4 100644 --- a/tests/unit/test_packet_capture_observer_name.py +++ b/tests/unit/test_packet_capture_observer_name.py @@ -1,12 +1,15 @@ """Tests for PacketCapture observer_name override.""" +from __future__ import annotations + import configparser -import logging -import types from unittest.mock import MagicMock -from modules.service_plugins.packet_capture_service import PacketCaptureService +import logging +import types +import pytest +from modules.service_plugins.packet_capture_service import PacketCaptureService LOGGER = logging.getLogger("test-packet-capture-observer-name") DEVICE_KEY = "ab" * 32 From 54503b3a7e32556e1a83df02565b46df41164ad0 Mon Sep 17 00:00:00 2001 From: AJ Quick Date: Sat, 8 Aug 2026 09:51:28 -0600 Subject: [PATCH 6/8] Update test_packet_capture_observer_name.py --- tests/unit/test_packet_capture_observer_name.py | 9 +++++---- 1 file changed, 5 insertions(+), 4 deletions(-) diff --git a/tests/unit/test_packet_capture_observer_name.py b/tests/unit/test_packet_capture_observer_name.py index 3dcaec4..1e8e7a7 100644 --- a/tests/unit/test_packet_capture_observer_name.py +++ b/tests/unit/test_packet_capture_observer_name.py @@ -35,15 +35,16 @@ def build_service( bot = MagicMock() bot.config = config - service = object.__new__(PacketCaptureService) - service.bot = bot - service.logger = LOGGER - service.meshcore = types.SimpleNamespace( + bot.meshcore = types.SimpleNamespace( self_info={ "name": device_name, "public_key": DEVICE_KEY, } ) + + service = object.__new__(PacketCaptureService) + service.bot = bot + service.logger = LOGGER service.debug = False service.decode_payloads = False From 9c50aaa7ba8b5b4467725c47185ad7362e331dd9 Mon Sep 17 00:00:00 2001 From: AJ Quick Date: Sun, 9 Aug 2026 18:13:57 +0000 Subject: [PATCH 7/8] Apply file.patch changes --- CHANGELOG.md | 10 ++ file.patch | 108 ++++++++++++++++++ .../service_plugins/packet_capture_service.py | 2 +- .../unit/test_packet_capture_observer_name.py | 6 +- .../test_packet_capture_payload_decode.py | 5 +- 5 files changed, 125 insertions(+), 6 deletions(-) create mode 100644 file.patch diff --git a/CHANGELOG.md b/CHANGELOG.md index caa1a3f..38cf9f7 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -4,6 +4,16 @@ All notable changes to this project are documented here. The format loosely foll [Keep a Changelog](https://keepachangelog.com/en/1.1.0/), and the project adheres to semantic versioning. +## [Unreleased] + +### Added + +- `[PacketCapture] observer_name` — an optional name reported as the `origin` of + MQTT packet and status payloads. It lets the observer/analyzer identity differ + from the MeshCore RF node, which is useful when one bot name is already taken + by the radio's advertised name. Unset (the default) keeps the previous + behavior: the connected device name, falling back to `[Bot] bot_name`. + ## [1.0.0] — 2026-08-07 v1.0.0 marks the first stable release. It adds zero-hop neighbor discovery, a diff --git a/file.patch b/file.patch new file mode 100644 index 0000000..142bcdc --- /dev/null +++ b/file.patch @@ -0,0 +1,108 @@ +From d774fad9855a5b14dac3965af4fec6151246bcf6 Mon Sep 17 00:00:00 2001 +From: ajquick +Date: Sun, 9 Aug 2026 17:57:25 +0000 +Subject: [PATCH] fix(packet-capture): make observer_name change pass lint and + tests + +The observer_name work broke three existing payload-decode tests and +tripped ruff: + +- tests/unit/test_packet_capture_payload_decode.py built a bare service + with `bot = None` and stubbed `_get_bot_name`. `_format_packet_data` + now resolves the origin through `_get_observer_name`, which reads + `[PacketCapture] observer_name` off `self.bot.config` before falling + back, so the stub no longer intercepted the call and the three + `_format_packet_data` tests raised on `None.config`. Stub the new + entry point instead, keeping those tests about payload decoding. +- ruff: trailing whitespace on a blank line in + packet_capture_service.py, plus unsorted imports, an unused `pytest` + import, and another whitespace-only line in the new observer_name + test. + +Also record the new option under CHANGELOG's [Unreleased]. +--- + CHANGELOG.md | 10 ++++++++++ + modules/service_plugins/packet_capture_service.py | 2 +- + tests/unit/test_packet_capture_observer_name.py | 6 ++---- + tests/unit/test_packet_capture_payload_decode.py | 5 ++++- + 4 files changed, 17 insertions(+), 6 deletions(-) + +diff --git a/CHANGELOG.md b/CHANGELOG.md +index caa1a3f..38cf9f7 100644 +--- a/CHANGELOG.md ++++ b/CHANGELOG.md +@@ -4,6 +4,16 @@ All notable changes to this project are documented here. The format loosely foll + [Keep a Changelog](https://keepachangelog.com/en/1.1.0/), and the project adheres to + semantic versioning. + ++## [Unreleased] ++ ++### Added ++ ++- `[PacketCapture] observer_name` — an optional name reported as the `origin` of ++ MQTT packet and status payloads. It lets the observer/analyzer identity differ ++ from the MeshCore RF node, which is useful when one bot name is already taken ++ by the radio's advertised name. Unset (the default) keeps the previous ++ behavior: the connected device name, falling back to `[Bot] bot_name`. ++ + ## [1.0.0] — 2026-08-07 + + v1.0.0 marks the first stable release. It adds zero-hop neighbor discovery, a +diff --git a/modules/service_plugins/packet_capture_service.py b/modules/service_plugins/packet_capture_service.py +index f11567d..1c683c1 100644 +--- a/modules/service_plugins/packet_capture_service.py ++++ b/modules/service_plugins/packet_capture_service.py +@@ -1572,7 +1572,7 @@ class PacketCaptureService(BaseServicePlugin): + return observer_name + + return self._get_bot_name() +- ++ + def _get_bot_name(self) -> str: + """Get bot name from device or config. + +diff --git a/tests/unit/test_packet_capture_observer_name.py b/tests/unit/test_packet_capture_observer_name.py +index 1e8e7a7..67c346f 100644 +--- a/tests/unit/test_packet_capture_observer_name.py ++++ b/tests/unit/test_packet_capture_observer_name.py +@@ -3,11 +3,9 @@ + from __future__ import annotations + + import configparser +-from unittest.mock import MagicMock +- + import logging + import types +-import pytest ++from unittest.mock import MagicMock + + from modules.service_plugins.packet_capture_service import PacketCaptureService + +@@ -41,7 +39,7 @@ def build_service( + "public_key": DEVICE_KEY, + } + ) +- ++ + service = object.__new__(PacketCaptureService) + service.bot = bot + service.logger = LOGGER +diff --git a/tests/unit/test_packet_capture_payload_decode.py b/tests/unit/test_packet_capture_payload_decode.py +index c0cc1a8..c45d360 100644 +--- a/tests/unit/test_packet_capture_payload_decode.py ++++ b/tests/unit/test_packet_capture_payload_decode.py +@@ -132,7 +132,10 @@ def _service_for_format(decode_payloads: bool) -> PacketCaptureService: + svc.debug = False + svc.logger = logging.getLogger("test-packet-capture") + svc.bot = None +- svc._get_bot_name = lambda: "TestBot" # type: ignore[method-assign] ++ # _format_packet_data resolves the reported origin through _get_observer_name, ++ # which falls back to _get_bot_name; stub the entry point so these tests stay ++ # about payload decoding and not name resolution. ++ svc._get_observer_name = lambda: "TestBot" # type: ignore[method-assign] + if decode_payloads: + svc.channel_key_store = ChannelKeyStore() + svc.channel_key_store.add_secret(BOT_KEY, "#bot") +-- +2.43.0 + diff --git a/modules/service_plugins/packet_capture_service.py b/modules/service_plugins/packet_capture_service.py index f11567d..1c683c1 100644 --- a/modules/service_plugins/packet_capture_service.py +++ b/modules/service_plugins/packet_capture_service.py @@ -1572,7 +1572,7 @@ class PacketCaptureService(BaseServicePlugin): return observer_name return self._get_bot_name() - + def _get_bot_name(self) -> str: """Get bot name from device or config. diff --git a/tests/unit/test_packet_capture_observer_name.py b/tests/unit/test_packet_capture_observer_name.py index 1e8e7a7..67c346f 100644 --- a/tests/unit/test_packet_capture_observer_name.py +++ b/tests/unit/test_packet_capture_observer_name.py @@ -3,11 +3,9 @@ from __future__ import annotations import configparser -from unittest.mock import MagicMock - import logging import types -import pytest +from unittest.mock import MagicMock from modules.service_plugins.packet_capture_service import PacketCaptureService @@ -41,7 +39,7 @@ def build_service( "public_key": DEVICE_KEY, } ) - + service = object.__new__(PacketCaptureService) service.bot = bot service.logger = LOGGER diff --git a/tests/unit/test_packet_capture_payload_decode.py b/tests/unit/test_packet_capture_payload_decode.py index c0cc1a8..c45d360 100644 --- a/tests/unit/test_packet_capture_payload_decode.py +++ b/tests/unit/test_packet_capture_payload_decode.py @@ -132,7 +132,10 @@ def _service_for_format(decode_payloads: bool) -> PacketCaptureService: svc.debug = False svc.logger = logging.getLogger("test-packet-capture") svc.bot = None - svc._get_bot_name = lambda: "TestBot" # type: ignore[method-assign] + # _format_packet_data resolves the reported origin through _get_observer_name, + # which falls back to _get_bot_name; stub the entry point so these tests stay + # about payload decoding and not name resolution. + svc._get_observer_name = lambda: "TestBot" # type: ignore[method-assign] if decode_payloads: svc.channel_key_store = ChannelKeyStore() svc.channel_key_store.add_secret(BOT_KEY, "#bot") From 9186e9e65bfc08abd9d5261f97d01a35e7e6f6dd Mon Sep 17 00:00:00 2001 From: AJ Quick Date: Sun, 9 Aug 2026 12:15:04 -0600 Subject: [PATCH 8/8] Delete file.patch --- file.patch | 108 ----------------------------------------------------- 1 file changed, 108 deletions(-) delete mode 100644 file.patch diff --git a/file.patch b/file.patch deleted file mode 100644 index 142bcdc..0000000 --- a/file.patch +++ /dev/null @@ -1,108 +0,0 @@ -From d774fad9855a5b14dac3965af4fec6151246bcf6 Mon Sep 17 00:00:00 2001 -From: ajquick -Date: Sun, 9 Aug 2026 17:57:25 +0000 -Subject: [PATCH] fix(packet-capture): make observer_name change pass lint and - tests - -The observer_name work broke three existing payload-decode tests and -tripped ruff: - -- tests/unit/test_packet_capture_payload_decode.py built a bare service - with `bot = None` and stubbed `_get_bot_name`. `_format_packet_data` - now resolves the origin through `_get_observer_name`, which reads - `[PacketCapture] observer_name` off `self.bot.config` before falling - back, so the stub no longer intercepted the call and the three - `_format_packet_data` tests raised on `None.config`. Stub the new - entry point instead, keeping those tests about payload decoding. -- ruff: trailing whitespace on a blank line in - packet_capture_service.py, plus unsorted imports, an unused `pytest` - import, and another whitespace-only line in the new observer_name - test. - -Also record the new option under CHANGELOG's [Unreleased]. ---- - CHANGELOG.md | 10 ++++++++++ - modules/service_plugins/packet_capture_service.py | 2 +- - tests/unit/test_packet_capture_observer_name.py | 6 ++---- - tests/unit/test_packet_capture_payload_decode.py | 5 ++++- - 4 files changed, 17 insertions(+), 6 deletions(-) - -diff --git a/CHANGELOG.md b/CHANGELOG.md -index caa1a3f..38cf9f7 100644 ---- a/CHANGELOG.md -+++ b/CHANGELOG.md -@@ -4,6 +4,16 @@ All notable changes to this project are documented here. The format loosely foll - [Keep a Changelog](https://keepachangelog.com/en/1.1.0/), and the project adheres to - semantic versioning. - -+## [Unreleased] -+ -+### Added -+ -+- `[PacketCapture] observer_name` — an optional name reported as the `origin` of -+ MQTT packet and status payloads. It lets the observer/analyzer identity differ -+ from the MeshCore RF node, which is useful when one bot name is already taken -+ by the radio's advertised name. Unset (the default) keeps the previous -+ behavior: the connected device name, falling back to `[Bot] bot_name`. -+ - ## [1.0.0] — 2026-08-07 - - v1.0.0 marks the first stable release. It adds zero-hop neighbor discovery, a -diff --git a/modules/service_plugins/packet_capture_service.py b/modules/service_plugins/packet_capture_service.py -index f11567d..1c683c1 100644 ---- a/modules/service_plugins/packet_capture_service.py -+++ b/modules/service_plugins/packet_capture_service.py -@@ -1572,7 +1572,7 @@ class PacketCaptureService(BaseServicePlugin): - return observer_name - - return self._get_bot_name() -- -+ - def _get_bot_name(self) -> str: - """Get bot name from device or config. - -diff --git a/tests/unit/test_packet_capture_observer_name.py b/tests/unit/test_packet_capture_observer_name.py -index 1e8e7a7..67c346f 100644 ---- a/tests/unit/test_packet_capture_observer_name.py -+++ b/tests/unit/test_packet_capture_observer_name.py -@@ -3,11 +3,9 @@ - from __future__ import annotations - - import configparser --from unittest.mock import MagicMock -- - import logging - import types --import pytest -+from unittest.mock import MagicMock - - from modules.service_plugins.packet_capture_service import PacketCaptureService - -@@ -41,7 +39,7 @@ def build_service( - "public_key": DEVICE_KEY, - } - ) -- -+ - service = object.__new__(PacketCaptureService) - service.bot = bot - service.logger = LOGGER -diff --git a/tests/unit/test_packet_capture_payload_decode.py b/tests/unit/test_packet_capture_payload_decode.py -index c0cc1a8..c45d360 100644 ---- a/tests/unit/test_packet_capture_payload_decode.py -+++ b/tests/unit/test_packet_capture_payload_decode.py -@@ -132,7 +132,10 @@ def _service_for_format(decode_payloads: bool) -> PacketCaptureService: - svc.debug = False - svc.logger = logging.getLogger("test-packet-capture") - svc.bot = None -- svc._get_bot_name = lambda: "TestBot" # type: ignore[method-assign] -+ # _format_packet_data resolves the reported origin through _get_observer_name, -+ # which falls back to _get_bot_name; stub the entry point so these tests stay -+ # about payload decoding and not name resolution. -+ svc._get_observer_name = lambda: "TestBot" # type: ignore[method-assign] - if decode_payloads: - svc.channel_key_store = ChannelKeyStore() - svc.channel_key_store.add_secret(BOT_KEY, "#bot") --- -2.43.0 -