ble-reticulum/examples/ble_minimal_test.py

182 lines
4.8 KiB
Python
Raw Normal View History

#!/usr/bin/env python3
"""
Minimal BLE Interface Test
This script demonstrates basic BLE interface functionality without
requiring a full Reticulum installation. Use this for development
and testing of the BLE interface itself.
Usage:
python ble_minimal_test.py [scan|test]
Commands:
scan - Scan for BLE devices and show what's nearby
test - Test fragmentation without BLE radio
"""
import sys
import os
import asyncio
# Add parent directory to path
sys.path.insert(0, os.path.join(os.path.dirname(__file__), '../src'))
from ble_reticulum.BLEFragmentation import BLEFragmenter, BLEReassembler
def test_fragmentation():
"""Test fragmentation and reassembly without BLE radio"""
print("=" * 60)
print("BLE Fragmentation Test")
print("=" * 60)
# Create fragmenter and reassembler
mtu = 185 # Typical BLE 4.2 MTU
fragmenter = BLEFragmenter(mtu=mtu)
reassembler = BLEReassembler()
# Test different packet sizes
test_cases = [
(50, "Small packet (no fragmentation)"),
(185, "Exact MTU size"),
(300, "Medium packet (2 fragments)"),
(500, "Large packet (3 fragments)"),
]
for size, description in test_cases:
print(f"\n{description}:")
print(f" Packet size: {size} bytes")
# Create test packet
packet = bytes([0x41 + (i % 26) for i in range(size)]) # A-Z pattern
# Fragment
fragments = fragmenter.fragment_packet(packet)
print(f" Fragments: {len(fragments)}")
# Calculate overhead
num_frags, overhead, pct = fragmenter.get_fragment_overhead(size)
print(f" Overhead: {overhead} bytes ({pct:.1f}%)")
# Show fragment details
for i, frag in enumerate(fragments):
frag_type = {1: "START", 2: "CONTINUE", 3: "END"}.get(frag[0], "UNKNOWN")
print(f" Fragment {i}: {len(frag)} bytes, type={frag_type}")
# Reassemble
result = None
for frag in fragments:
result = reassembler.receive_fragment(frag, "test_device")
if result is not None:
break
# Verify
if result == packet:
print(f" ✓ Reassembly successful!")
else:
print(f" ✗ Reassembly failed!")
return False
# Show statistics
print(f"\nReassembler Statistics:")
stats = reassembler.get_statistics()
for key, value in stats.items():
print(f" {key}: {value}")
print("\n" + "=" * 60)
print("All tests passed! ✓")
print("=" * 60)
return True
async def scan_ble_devices():
"""Scan for nearby BLE devices"""
print("=" * 60)
print("BLE Device Scanner")
print("=" * 60)
print("Scanning for BLE devices...")
print("(This will take a few seconds)")
print()
try:
from bleak import BleakScanner
devices = await BleakScanner.discover(timeout=5.0)
if not devices:
print("No BLE devices found.")
return
print(f"Found {len(devices)} device(s):\n")
for i, device in enumerate(devices, 1):
print(f"{i}. {device.name or 'Unknown'}")
print(f" Address: {device.address}")
# Get RSSI (API varies by bleak version)
rssi = getattr(device, 'rssi', device.metadata.get('rssi', 'N/A') if hasattr(device, 'metadata') else 'N/A')
print(f" RSSI: {rssi} dBm")
# Get UUIDs (API varies by bleak version)
uuids = getattr(device, 'uuids', device.metadata.get("uuids", []) if hasattr(device, 'metadata') else [])
if uuids:
print(f" Services: {len(uuids)} advertised")
for uuid in uuids[:3]: # Show first 3
print(f" - {uuid}")
print()
except ImportError:
print("ERROR: bleak library not installed")
print("Install with: pip install bleak>=0.21.0")
return
except Exception as e:
print(f"ERROR: {e}")
return
print("=" * 60)
def show_help():
"""Show usage information"""
print("""
BLE Interface Minimal Test
Usage:
python ble_minimal_test.py [command]
Commands:
scan - Scan for nearby BLE devices
test - Test fragmentation logic (no BLE radio needed)
help - Show this help message
Examples:
# Test fragmentation
python ble_minimal_test.py test
# Scan for BLE devices
python ble_minimal_test.py scan
""")
def main():
"""Main entry point"""
if len(sys.argv) < 2:
command = "test" # Default command
else:
command = sys.argv[1].lower()
if command == "test":
test_fragmentation()
elif command == "scan":
asyncio.run(scan_ble_devices())
elif command == "help":
show_help()
else:
print(f"Unknown command: {command}")
show_help()
sys.exit(1)
if __name__ == "__main__":
main()