From 6e5c2d3e7397649040de7eb26cd490565ba16ed5 Mon Sep 17 00:00:00 2001 From: Rightup Date: Mon, 17 Aug 2026 14:45:35 +0100 Subject: [PATCH] feat(advert): enhance advert sending with mode selection and routing options --- repeater/main.py | 10 ++++++++-- repeater/utils_packet.py | 19 +++++++++++-------- repeater/web/api_endpoints.py | 19 +++++++++++++++++-- repeater/web/openapi.yaml | 12 ++++++++++++ 4 files changed, 48 insertions(+), 12 deletions(-) diff --git a/repeater/main.py b/repeater/main.py index d281848..3375935 100644 --- a/repeater/main.py +++ b/repeater/main.py @@ -1544,6 +1544,7 @@ class RepeaterDaemon: mesh_config = self.config.get("mesh", {}) default_region = mesh_config.get("default_region") + advert_route_type = "direct" if advert_kind == "direct" else "flood" packet, scoped_region_name = create_scoped_advert_packet( local_identity=identity, node_name=node_name, @@ -1552,6 +1553,7 @@ class RepeaterDaemon: flags=flags, default_region=default_region, scope_label="room server advert", + route_type=advert_route_type, ) injector = getattr(getattr(self, "router", None), "inject_packet", None) @@ -1569,8 +1571,9 @@ class RepeaterDaemon: logger.debug("Marked room server advert '%s' as seen in duplicate cache", node_name) logger.info( - "Sent %s room advert (flood packet) '%s' at (%.6f, %.6f)", + "Sent %s room advert (%s packet) '%s' at (%.6f, %.6f)", advert_kind, + advert_route_type, node_name, latitude, longitude, @@ -1733,6 +1736,7 @@ class RepeaterDaemon: mesh_config = self.config.get("mesh", {}) default_region = mesh_config.get("default_region") + advert_route_type = "direct" if advert_kind == "direct" else "flood" packet, scoped_region_name = create_scoped_advert_packet( local_identity=self.local_identity, node_name=node_name, @@ -1741,6 +1745,7 @@ class RepeaterDaemon: flags=flags, default_region=default_region, scope_label="advert", + route_type=advert_route_type, ) injector = getattr(getattr(self, "router", None), "inject_packet", None) @@ -1760,8 +1765,9 @@ class RepeaterDaemon: logger.debug("Marked own advert as seen in duplicate cache") logger.info( - "Sent %s advert (flood packet) '%s' at (%.6f, %.6f) source=%s", + "Sent %s advert (%s packet) '%s' at (%.6f, %.6f) source=%s", advert_kind, + advert_route_type, node_name, latitude, longitude, diff --git a/repeater/utils_packet.py b/repeater/utils_packet.py index bde3379..3dbfd86 100644 --- a/repeater/utils_packet.py +++ b/repeater/utils_packet.py @@ -1,5 +1,5 @@ import logging -from typing import Optional, Tuple +from typing import Literal, Optional, Tuple from openhop_core.protocol import PacketBuilder from openhop_core.protocol.constants import ROUTE_TYPE_TRANSPORT_FLOOD @@ -16,8 +16,9 @@ def create_scoped_advert_packet( flags: int, default_region, scope_label: str, + route_type: Literal["flood", "direct"] = "flood", ) -> Tuple[object, Optional[str]]: - """Create a flood advert packet and apply default-region transport scope when configured.""" + """Create an advert packet and apply flood scoping when configured.""" packet = PacketBuilder.create_advert( local_identity=local_identity, name=node_name, @@ -26,14 +27,16 @@ def create_scoped_advert_packet( feature1=0, feature2=0, flags=flags, - route_type="flood", + route_type=route_type, ) - scoped_region_name = _apply_default_region_scope( - packet=packet, - default_region=default_region, - scope_label=scope_label, - ) + scoped_region_name = None + if route_type == "flood": + scoped_region_name = _apply_default_region_scope( + packet=packet, + default_region=default_region, + scope_label=scope_label, + ) return packet, scoped_region_name diff --git a/repeater/web/api_endpoints.py b/repeater/web/api_endpoints.py index e2c1cb7..d09abdc 100644 --- a/repeater/web/api_endpoints.py +++ b/repeater/web/api_endpoints.py @@ -1906,6 +1906,7 @@ class APIEndpoints: @cherrypy.expose @cherrypy.tools.json_out() + @cherrypy.tools.json_in() def send_advert(self): # Enable CORS for this endpoint self._set_cors_headers() @@ -1920,12 +1921,26 @@ class APIEndpoints: return self._error("Send advert function not configured") if self.event_loop is None: return self._error("Event loop not available") + + data = cherrypy.request.json or {} + mode = str(data.get("mode", "flood")).strip().lower() + if mode not in ("flood", "direct"): + return self._error("Invalid mode. Must be 'flood' or 'direct'") + import asyncio - future = asyncio.run_coroutine_threadsafe(self.send_advert_func(), self.event_loop) + async def _call_send_advert(): + try: + return await self.send_advert_func(advert_kind=mode) + except TypeError: + # Backward compatibility for older callbacks that don't + # accept advert_kind yet. + return await self.send_advert_func() + + future = asyncio.run_coroutine_threadsafe(_call_send_advert(), self.event_loop) result = future.result(timeout=10) return ( - self._success("Advert sent successfully") + self._success(f"{mode.title()} advert sent successfully") if result else self._error("Failed to send advert") ) diff --git a/repeater/web/openapi.yaml b/repeater/web/openapi.yaml index 90e9d0e..ca8912d 100644 --- a/repeater/web/openapi.yaml +++ b/repeater/web/openapi.yaml @@ -454,6 +454,18 @@ paths: tags: [System] summary: Send repeater advertisement description: Manually trigger sending a repeater advertisement packet + requestBody: + required: false + content: + application/json: + schema: + type: object + properties: + mode: + type: string + enum: [flood, direct] + default: flood + description: Advert send mode label for UI/workflow selection. responses: '200': description: Advertisement sent