mirror of
https://github.com/jkingsman/Remote-Terminal-for-MeshCore.git
synced 2026-03-28 17:43:05 +01:00
Draggable nodes and periodic (hourly) announce
This commit is contained in:
@@ -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
|
||||
|
||||
@@ -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()
|
||||
|
||||
@@ -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
1
frontend/dist/assets/index-CJZtvuFn.js.map
vendored
Normal file
1
frontend/dist/assets/index-CJZtvuFn.js.map
vendored
Normal file
File diff suppressed because one or more lines are too long
1
frontend/dist/assets/index-DC7wagyC.js.map
vendored
1
frontend/dist/assets/index-DC7wagyC.js.map
vendored
File diff suppressed because one or more lines are too long
4
frontend/dist/index.html
vendored
4
frontend/dist/index.html
vendored
@@ -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>
|
||||
|
||||
@@ -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()
|
||||
|
||||
Reference in New Issue
Block a user