Skip to content

Commit 6d69924

Browse files
authored
Merge pull request #29 from rightup/dev
Anonymous authentication + LBT feedback metrics
2 parents 099e6c2 + 91f916d commit 6d69924

36 files changed

+3288
-644
lines changed

examples/discover_nodes.py

Lines changed: 186 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,186 @@
1+
#!/usr/bin/env python3
2+
"""
3+
Minimal example: Discover nearby mesh nodes.
4+
5+
This example demonstrates how to broadcast a discovery request
6+
and collect responses from nearby repeaters and nodes in the mesh network.
7+
8+
The discovery request is sent as a zero-hop broadcast, and nearby nodes
9+
will respond with their public key and signal strength information.
10+
11+
Features:
12+
- Asynchronous callback-based response collection
13+
- Configurable discovery filter (node types to discover)
14+
- Signal strength data (SNR and RSSI) for each discovered node
15+
- Automatic timeout after specified duration
16+
"""
17+
18+
import asyncio
19+
import random
20+
import time
21+
22+
from common import create_mesh_node
23+
24+
from pymc_core.protocol.packet_builder import PacketBuilder
25+
26+
# ADV_TYPE_REPEATER = 2, so filter mask is (1 << 2) = 0x04
27+
FILTER_REPEATERS = 0x04 # Bit 2 set for repeater node type
28+
29+
30+
async def discover_nodes(
31+
radio_type: str = "waveshare",
32+
serial_port: str = "/dev/ttyUSB0",
33+
timeout: float = 5.0,
34+
filter_mask: int = FILTER_REPEATERS,
35+
):
36+
"""
37+
Discover nearby mesh nodes using control packets.
38+
39+
Args:
40+
radio_type: Radio hardware type ("waveshare", "uconsole", etc.)
41+
serial_port: Serial port for KISS TNC
42+
timeout: How long to wait for responses (seconds)
43+
filter_mask: Node types to discover (bitmask of ADV_TYPE values, e.g., ADV_TYPE_REPEATER = 2, so mask = 0x04 for repeaters)
44+
"""
45+
mesh_node, identity = create_mesh_node("DiscoveryNode", radio_type, serial_port)
46+
47+
# Dictionary to store discovered nodes
48+
discovered_nodes = {}
49+
50+
# Create callback to collect discovery responses
51+
def on_discovery_response(response_data: dict):
52+
"""Handle discovery response callback."""
53+
tag = response_data.get("tag", 0)
54+
node_type = response_data.get("node_type", 0)
55+
inbound_snr = response_data.get("inbound_snr", 0.0) # Their RX of our request
56+
response_snr = response_data.get("response_snr", 0.0) # Our RX of their response
57+
rssi = response_data.get("rssi", 0)
58+
pub_key = response_data.get("pub_key", "")
59+
timestamp = response_data.get("timestamp", 0)
60+
61+
# Get node type name
62+
node_type_names = {1: "Chat Node", 2: "Repeater", 3: "Room Server"}
63+
node_type_name = node_type_names.get(node_type, f"Unknown({node_type})")
64+
65+
# Store node info
66+
node_id = pub_key[:16] # Use first 8 bytes as ID
67+
if node_id not in discovered_nodes:
68+
discovered_nodes[node_id] = {
69+
"pub_key": pub_key,
70+
"node_type": node_type_name,
71+
"inbound_snr": inbound_snr,
72+
"response_snr": response_snr,
73+
"rssi": rssi,
74+
"timestamp": timestamp,
75+
}
76+
77+
print(
78+
f"✓ Discovered {node_type_name}: {node_id}... "
79+
f"(TX→RX SNR: {inbound_snr:+.1f}dB, RX←TX SNR: {response_snr:+.1f}dB, "
80+
f"RSSI: {rssi}dBm)"
81+
)
82+
83+
# Get the control handler and set up callback
84+
control_handler = mesh_node.dispatcher.control_handler
85+
if not control_handler:
86+
print("Error: Control handler not available")
87+
return
88+
89+
# Generate random tag for this discovery request
90+
discovery_tag = random.randint(0, 0xFFFFFFFF)
91+
92+
# Set up callback for responses matching this tag
93+
control_handler.set_response_callback(discovery_tag, on_discovery_response)
94+
95+
# Create discovery request packet
96+
# filter_mask: 0x04 = bit 2 set (1 << ADV_TYPE_REPEATER where ADV_TYPE_REPEATER=2)
97+
# since: 0 = discover all nodes regardless of modification time
98+
pkt = PacketBuilder.create_discovery_request(
99+
tag=discovery_tag, filter_mask=filter_mask, since=0, prefix_only=False
100+
)
101+
102+
print(f"Sending discovery request (tag: 0x{discovery_tag:08X})...")
103+
print(f"Filter mask: 0x{filter_mask:02X} (node types to discover)")
104+
print(f"Waiting {timeout} seconds for responses...\n")
105+
106+
# Send as zero-hop broadcast (no routing path)
107+
success = await mesh_node.dispatcher.send_packet(pkt, wait_for_ack=False)
108+
109+
if success:
110+
print("Discovery request sent successfully")
111+
112+
# Wait for responses
113+
start_time = time.time()
114+
while time.time() - start_time < timeout:
115+
await asyncio.sleep(0.1)
116+
117+
# Display results
118+
print(f"\n{'='*60}")
119+
print(f"Discovery complete - found {len(discovered_nodes)} node(s)")
120+
print(f"{'='*60}\n")
121+
122+
if discovered_nodes:
123+
for node_id, info in discovered_nodes.items():
124+
print(f"Node: {node_id}...")
125+
print(f" Type: {info['node_type']}")
126+
print(f" TX→RX SNR: {info['inbound_snr']:+.1f} dB (our request at their end)")
127+
print(f" RX←TX SNR: {info['response_snr']:+.1f} dB (their response at our end)")
128+
print(f" RSSI: {info['rssi']} dBm")
129+
print(f" Public Key: {info['pub_key']}")
130+
print()
131+
else:
132+
print("No nodes discovered.")
133+
print("This could mean:")
134+
print(" - No nodes are within range")
135+
print(" - No nodes match the filter criteria")
136+
print(" - Radio configuration mismatch")
137+
138+
else:
139+
print("Failed to send discovery request")
140+
141+
# Clean up callback
142+
control_handler.clear_response_callback(discovery_tag)
143+
144+
145+
def main():
146+
"""Main function for running the discovery example."""
147+
import argparse
148+
149+
parser = argparse.ArgumentParser(description="Discover nearby mesh nodes")
150+
parser.add_argument(
151+
"--radio-type",
152+
choices=["waveshare", "uconsole", "meshadv-mini", "kiss-tnc"],
153+
default="waveshare",
154+
help="Radio hardware type (default: waveshare)",
155+
)
156+
parser.add_argument(
157+
"--serial-port",
158+
default="/dev/ttyUSB0",
159+
help="Serial port for KISS TNC (default: /dev/ttyUSB0)",
160+
)
161+
parser.add_argument(
162+
"--timeout",
163+
type=float,
164+
default=5.0,
165+
help="Discovery timeout in seconds (default: 5.0)",
166+
)
167+
parser.add_argument(
168+
"--filter",
169+
type=lambda x: int(x, 0),
170+
default=FILTER_REPEATERS,
171+
help="Node type filter mask (default: 0x04 for repeaters, bit position = node type)",
172+
)
173+
174+
args = parser.parse_args()
175+
176+
print(f"Using {args.radio_type} radio configuration")
177+
if args.radio_type == "kiss-tnc":
178+
print(f"Serial port: {args.serial_port}")
179+
180+
asyncio.run(
181+
discover_nodes(args.radio_type, args.serial_port, args.timeout, args.filter)
182+
)
183+
184+
185+
if __name__ == "__main__":
186+
main()

0 commit comments

Comments
 (0)