diff --git a/.gitignore b/.gitignore index 4cacd56..03a1ce0 100644 --- a/.gitignore +++ b/.gitignore @@ -65,3 +65,4 @@ syncpi.sh # Docker /data +Installing diff --git a/repeater/data_acquisition/storage_utils.py b/repeater/data_acquisition/storage_utils.py index 1fd4ab2..42cf9ad 100644 --- a/repeater/data_acquisition/storage_utils.py +++ b/repeater/data_acquisition/storage_utils.py @@ -70,7 +70,7 @@ class PacketRecord: origin_id=origin_id, timestamp=dt.isoformat(), type="PACKET", - direction="rx", + direction="tx" if bool(packet_record.get("transmitted", False)) else "rx", time=dt.strftime("%H:%M:%S"), date=dt.strftime("%-d/%-m/%Y"), len=str(len(packet_record["raw_packet"]) // 2), diff --git a/repeater/main.py b/repeater/main.py index 7fc87a5..8b62d8c 100644 --- a/repeater/main.py +++ b/repeater/main.py @@ -1083,8 +1083,15 @@ class RepeaterDaemon: route_type="flood", ) - # Send via dispatcher - await self.dispatcher.send_packet(packet, wait_for_ack=False) + # Route advert TX through router injection so it flows through engine/storage/MQTT. + if not self.router: + logger.error("Cannot send advert: router not initialized") + return False + + send_ok = await self.router.inject_packet(packet, wait_for_ack=False) + if not send_ok: + logger.error("Failed to inject advert packet for TX") + return False if self.repeater_handler: self.repeater_handler.mark_seen(packet) diff --git a/tests/test_main_py_coverage.py b/tests/test_main_py_coverage.py index ec7330f..13566a8 100644 --- a/tests/test_main_py_coverage.py +++ b/tests/test_main_py_coverage.py @@ -212,7 +212,7 @@ async def test_register_identity_everywhere_calls_helpers_and_respects_collision @pytest.mark.asyncio -async def test_send_advert_branches_and_success_path(): +async def test_send_advert_branches_and_success_paths(): daemon = RepeaterDaemon(_base_config(), radio=object()) # Missing dispatcher/local identity @@ -231,12 +231,19 @@ async def test_send_advert_branches_and_success_path(): get_repeater_location=lambda: {"latitude": 9.1, "longitude": 8.2, "source": "gps"} ) + # Router is now required for advert TX processing path. + daemon.router = None + assert await daemon.send_advert() is False + + daemon.router = SimpleNamespace(inject_packet=AsyncMock(return_value=True)) + packet = SimpleNamespace(calculate_packet_hash=lambda: b"\xab" * 16) with patch("pymc_core.protocol.PacketBuilder.create_advert", return_value=packet): ok = await daemon.send_advert() assert ok is True - daemon.dispatcher.send_packet.assert_awaited_once_with(packet, wait_for_ack=False) + daemon.router.inject_packet.assert_awaited_once_with(packet, wait_for_ack=False) + daemon.dispatcher.send_packet.assert_not_called() daemon.repeater_handler.mark_seen.assert_called_once_with(packet) daemon.dispatcher.packet_filter.track_packet.assert_called_once() diff --git a/tests/test_mqtt_publish_integration.py b/tests/test_mqtt_publish_integration.py index ef3ee7e..204a2f8 100644 --- a/tests/test_mqtt_publish_integration.py +++ b/tests/test_mqtt_publish_integration.py @@ -236,3 +236,40 @@ def test_mqtt_published_packet_legacy_mqtt_format_uses_singular_packet_topic(): payload_dict = json.loads(captured[0]["payload"]) # Duration still flows through correctly even on the legacy topic assert int(payload_dict["duration"]) > 0 + + +def test_mqtt_published_packet_direction_uses_transmitted_flag_for_tx(): + """When engine marks packet_record.transmitted=True, published direction must be tx.""" + config = _make_config(format_value="letsmesh", iata_code="LAX") + public_key_hex = "11" * 32 + identity = _FakeIdentity(public_key_hex) + + pusher = MeshCoreToMqttPusher(local_identity=identity, config=config) + captured = _attach_capturing_client(pusher.connections[0]) + + raw_bytes = bytes(range(12)) + airtime_mgr = AirtimeManager(config) + packet_record = { + "timestamp": 1700000000.0, + "type": 8, + "route": 1, + "rssi": -88, + "snr": 6.0, + "score": 0.4, + "payload_length": 6, + "packet_hash": "FACEFEED" + "00" * 4, + "raw_packet": raw_bytes.hex(), + "airtime_ms": airtime_mgr.calculate_airtime(len(raw_bytes)), + "transmitted": True, + } + + record = PacketRecord.from_packet_record( + packet_record, origin="test-node", origin_id=public_key_hex.upper() + ) + assert record is not None + + pusher.publish_packet(record.to_dict()) + + assert len(captured) == 1 + payload_dict = json.loads(captured[0]["payload"]) + assert payload_dict["direction"] == "tx"