Draggable nodes and periodic (hourly) announce

This commit is contained in:
Jack Kingsman
2026-01-22 22:39:34 -08:00
parent 2f07ee3bd7
commit a0abcceec9
9 changed files with 141 additions and 26 deletions

View File

@@ -161,6 +161,26 @@ if is_polling_paused():
print("Polling is currently paused")
```
### Periodic Advertisement
The server automatically sends an advertisement every hour to announce presence on the mesh.
This helps maintain visibility to other nodes and refreshes routing information.
- Started automatically on radio connection
- Interval: 1 hour (3600 seconds)
- Uses flood mode for maximum reach
```python
from app.radio_sync import start_periodic_advert, stop_periodic_advert, send_advertisement
# Start/stop periodic advertising
start_periodic_advert() # Started automatically in lifespan
await stop_periodic_advert()
# Manual advertisement
await send_advertisement() # Returns True on success
```
## Database Schema
```sql

View File

@@ -14,8 +14,10 @@ from app.radio import radio_manager
from app.radio_sync import (
drain_pending_messages,
start_message_polling,
start_periodic_advert,
start_periodic_sync,
stop_message_polling,
stop_periodic_advert,
stop_periodic_sync,
sync_and_offload_all,
sync_radio_time,
@@ -69,6 +71,9 @@ async def lifespan(app: FastAPI):
advert_result = await radio_manager.meshcore.commands.send_advert(flood=True)
logger.info("Advertisement sent: %s", advert_result.type)
# Start periodic advertisement (every hour)
start_periodic_advert()
await radio_manager.meshcore.start_auto_message_fetching()
logger.info("Auto message fetching started")
@@ -90,6 +95,7 @@ async def lifespan(app: FastAPI):
logger.info("Shutting down")
await radio_manager.stop_connection_monitor()
await stop_message_polling()
await stop_periodic_advert()
await stop_periodic_sync()
if radio_manager.meshcore:
await radio_manager.meshcore.stop_auto_message_fetching()

View File

@@ -29,6 +29,12 @@ _message_poll_task: asyncio.Task | None = None
# Message poll interval in seconds
MESSAGE_POLL_INTERVAL = 5
# Periodic advertisement task handle
_advert_task: asyncio.Task | None = None
# Advertisement interval in seconds (1 hour)
ADVERT_INTERVAL = 3600
# Counter to pause polling during repeater operations (supports nested pauses)
_polling_pause_count: int = 0
@@ -329,6 +335,69 @@ async def stop_message_polling():
logger.info("Stopped periodic message polling")
async def send_advertisement() -> bool:
"""Send an advertisement to announce presence on the mesh.
Returns True if successful, False otherwise.
"""
if not radio_manager.is_connected or radio_manager.meshcore is None:
logger.debug("Cannot send advertisement: radio not connected")
return False
try:
result = await radio_manager.meshcore.commands.send_advert(flood=True)
if result.type == EventType.OK:
logger.info("Periodic advertisement sent successfully")
return True
else:
logger.warning("Failed to send advertisement: %s", result.payload)
return False
except Exception as e:
logger.warning("Error sending advertisement: %s", e)
return False
async def _periodic_advert_loop():
"""Background task that periodically sends advertisements."""
while True:
try:
await asyncio.sleep(ADVERT_INTERVAL)
if radio_manager.is_connected:
await send_advertisement()
except asyncio.CancelledError:
logger.info("Periodic advertisement task cancelled")
break
except Exception as e:
logger.error("Error in periodic advertisement loop: %s", e)
def start_periodic_advert():
"""Start the periodic advertisement background task."""
global _advert_task
if _advert_task is None or _advert_task.done():
_advert_task = asyncio.create_task(_periodic_advert_loop())
logger.info(
"Started periodic advertisement (interval: %ds / %d min)",
ADVERT_INTERVAL,
ADVERT_INTERVAL // 60,
)
async def stop_periodic_advert():
"""Stop the periodic advertisement background task."""
global _advert_task
if _advert_task and not _advert_task.done():
_advert_task.cancel()
try:
await _advert_task
except asyncio.CancelledError:
pass
_advert_task = None
logger.info("Stopped periodic advertisement")
async def sync_radio_time() -> bool:
"""Sync the radio's clock with the system time.

File diff suppressed because one or more lines are too long

File diff suppressed because one or more lines are too long

File diff suppressed because one or more lines are too long

File diff suppressed because one or more lines are too long

View File

@@ -13,8 +13,8 @@
<link rel="shortcut icon" href="/favicon.ico" />
<link rel="apple-touch-icon" sizes="180x180" href="/apple-touch-icon.png" />
<link rel="manifest" href="/site.webmanifest" />
<script type="module" crossorigin src="/assets/index-DC7wagyC.js"></script>
<link rel="stylesheet" crossorigin href="/assets/index-6X8xpvpN.css">
<script type="module" crossorigin src="/assets/index-CJZtvuFn.js"></script>
<link rel="stylesheet" crossorigin href="/assets/index-BH0I1D-N.css">
</head>
<body>
<div id="root"></div>

View File

@@ -274,6 +274,23 @@ ctx.setTransform(dpr * scale, 0, 0, dpr * scale, dpr * (x + panX), dpr * (y + pa
4. Draw nodes (circles with emojis)
5. Draw hover tooltip if applicable
## Mouse Interactions
| Action | Behavior |
| -------------------------- | ------------------------------------------------ |
| Click + drag on node | Move node to new position (temporarily fixes it) |
| Release dragged node | Node returns to force-directed layout |
| Click + drag on empty area | Pan the canvas |
| Scroll wheel | Zoom in/out |
| Hover over node | Shows node details, cursor changes to pointer |
**Node Dragging Implementation:**
- On mouse down over a node, sets `fx`/`fy` (D3 fixed position) to lock it
- On mouse move, updates the fixed position to follow cursor
- On mouse up, clears `fx`/`fy` so node rejoins the simulation
- Simulation is slightly reheated during drag for responsive feedback
## Configuration Options
| Option | Default | Description |
@@ -303,12 +320,15 @@ PacketVisualizer.tsx
│ ├── getPacketLabel()
│ ├── generatePacketKey()
│ ├── findContactBy*()
── dedupeConsecutive()
── dedupeConsecutive()
│ ├── analyzeRepeaterTraffic()
│ └── recordTrafficObservation()
├── DATA LAYER HOOK (useVisualizerData)
│ ├── Refs (nodes, links, particles, simulation, pending, timers)
│ ├── Refs (nodes, links, particles, simulation, pending, timers, trafficPatterns)
│ ├── Simulation initialization
│ ├── Node/link management (addNode, addLink, syncSimulation)
│ ├── Path building (resolveNode, buildPath)
│ ├── Traffic pattern analysis (for repeater disambiguation)
│ └── Packet processing & publishing
├── RENDERING FUNCTIONS
│ ├── renderLinks()