added basic cryptography, advertising, and forwarding functionality to the LoRa handler. Also added a UUID class for generating unique IDs for each device.

also, i have 2 radios now! :D
This commit is contained in:
2026-05-15 10:24:49 +10:00
parent 8abf447d66
commit 6f8083fdc2
8 changed files with 201 additions and 55 deletions

View File

@@ -57,6 +57,7 @@ class BluetoothHandler:
def deserialize_msg(self, s: bytes): def deserialize_msg(self, s: bytes):
# returns packet type (int) and deserialized data # returns packet type (int) and deserialized data
print(s[1:].decode())
return s[0], eval(s[1:].decode()) return s[0], eval(s[1:].decode())
def _get_mac_address(self): def _get_mac_address(self):

16
relay/connection.py Normal file
View File

@@ -0,0 +1,16 @@
from crypt_random import secure_random
class Connection:
def load_public_constants(self):
with open("public_constants.txt", "r") as f:
self.P = int(f.readline(), 16)
self.G = int(f.readline(), 16)
def __init__(self):
self.load_public_constants()
self.private_key = int.from_bytes(secure_random(1024), "big")
self.public_key = pow(self.G, self.private_key) % self.P

39
relay/crypt_random.py Normal file
View File

@@ -0,0 +1,39 @@
from machine import ADC
import utime
import uhashlib
adc = ADC(26)
def get_entropy(samples: int = 512):
entropy = bytearray()
for _ in range(samples):
value = adc.read_u16()
mixed = (
value ^
(value >> 2) ^
(value >> 5) ^
utime.ticks_cpu()
) & 0xFF
entropy.append(mixed)
utime.sleep_us(5)
return entropy
def secure_random(num_bytes: int = 32):
entropy = get_entropy()
digest = uhashlib.sha256(entropy).digest()
output = bytearray()
while len(output) < num_bytes:
digest = uhashlib.sha256(digest).digest()
output.extend(digest)
return bytes(output[:num_bytes])

View File

@@ -1,17 +1,124 @@
import time
import uuid
from sx1262 import SX1262
from machine import Pin
led = Pin(25, Pin.OUT)
class FixedDeque:
def __init__(self, maxlen):
self.maxlen = maxlen
self.data = []
def append(self, x):
if len(self.data) >= self.maxlen:
self.data.pop(0)
self.data.append(x)
def __iter__(self):
return iter(self.data)
class LoRaMessage:
def __init__(self, sender_id: str, recipient_id: str, payload: bytes):
self.sender_id = sender_id
self.recipient_id = recipient_id
self.payload = payload
self.message_id = str(uuid.uuid4())
self.lifetime = 7
def encode(self) -> bytes:
encoded = bytearray()
encoded.append(len(self.sender_id))
encoded.extend(self.sender_id.encode("utf-8"))
encoded.append(len(self.recipient_id))
encoded.extend(self.recipient_id.encode("utf-8"))
encoded.extend(self.payload)
return bytes(encoded)
@staticmethod
def decode(data: bytes):
sender_id_len = data[0]
sender_id = data[1:1+sender_id_len].decode("utf-8")
recipient_id_len = data[1+sender_id_len]
recipient_id = data[2+sender_id_len:2+sender_id_len+recipient_id_len].decode("utf-8")
payload = data[2+sender_id_len+recipient_id_len:]
return LoRaMessage(sender_id=sender_id, recipient_id=recipient_id, payload=payload)
class LoRaHandler: class LoRaHandler:
def __init__(self): def __init__(self):
print("Initializing LoRa...") print("Initializing LoRa...")
self.sender_id = str(uuid.uuid4())
# initialize our radio, im using the HAT SX1262 hat for the pico # initialize our radio, im using the HAT SX1262 hat for the pico
self.radio = SX1262(spi_bus=1, clk=10, mosi=11, miso=12, cs=3, irq=20, rst=15, gpio=2) self.radio = SX1262(spi_bus=1, clk=10, mosi=11, miso=12, cs=3, irq=20, rst=15, gpio=2)
self.radio.begin(freq=915, bw=125, power=22) self.radio.begin(freq=915, bw=125, power=22)
self.radio.setBlockingCallback(False, self.irq) self.radio.setBlockingCallback(False, self.irq)
self.recent_messages = FixedDeque(32)
self.neighbours = set()
def send(self, data: bytes, recipient_id: str):
msg_bytes = LoRaMessage(sender_id=self.sender_id, recipient_id=recipient_id, payload=data).encode()
print(msg_bytes)
self.radio.send(msg_bytes)
def forward(self, msg: LoRaMessage):
msg_bytes = msg.encode()
self.radio.send(msg_bytes)
def advertise(self):
# send an advertisement message to let neighbors know we're here
self.send(b"ADVERTISE", recipient_id="BROADCAST")
def on_receive(self, msg: LoRaMessage):
# ensure we don't process a message twice
if msg.message_id in self.recent_messages:
return
self.recent_messages.append(msg.message_id)
# messages has a lifetime to prevent infinite loops.
# if we receive a message with lifetime <= 0 we just drop it,
# otherwise we decrement the lifetime and forward it to all
# neighbors except the sender
if msg.lifetime <= 0:
return
msg.lifetime -= 1
# forward messages if they aren't meant for us
if (msg.recipient_id != self.sender_id) and msg.recipient_id != "BROADCAST":
print("Forwarding message...")
self.forward(msg)
return
if msg.payload == b"ADVERTISE":
if not msg.sender_id in self.neighbours:
print(f"Neighbour discovered: {msg.sender_id}")
self.neighbours.add(msg.sender_id)
return
print(f"Received message from {msg.sender_id}: {msg.payload}")
def irq(self, events): def irq(self, events):
print(f"LORA EVENT: {events}") #print(f"LORA EVENT: {events}")
if events & SX1262.RX_DONE: if events & SX1262.RX_DONE:
msg, err = sx.recv() msg, err = self.radio.recv()
error = SX1262.STATUS[err] error = SX1262.STATUS[err]
print('Receive: {}, {}'.format(msg, error))
if error != "ERR_NONE":
print(f"Error receiving message: {error}")
return
self.on_receive(LoRaMessage.decode(msg))
elif events & SX1262.TX_DONE: elif events & SX1262.TX_DONE:
print('TX done.') #print('TX done.')
pass

View File

@@ -1,23 +1,28 @@
from sx1262 import SX1262 from sx1262 import SX1262
from _sx126x import * from _sx126x import *
import time import time
import crypt_random
from bluetooth_handler import BluetoothHandler
from lora_handler import LoRaHandler from lora_handler import LoRaHandler
LORA_ENABLED = False LORA_ENABLED = True
BLUETOOTH_ENABLED = False
def main(): def main():
bluetooth_handler = None
if BLUETOOTH_ENABLED:
from bluetooth_handler import BluetoothHandler
bluetooth_handler = BluetoothHandler() bluetooth_handler = BluetoothHandler()
lora_handler = None lora_handler = None
if LORA_ENABLED: if LORA_ENABLED:
lora_handler = LoRaHandler() lora_handler = LoRaHandler()
print("Halting Pico...") print("Ready!")
while True: while True:
pass lora_handler.advertise()
time.sleep(5)
if __name__ == "__main__": if __name__ == "__main__":

View File

@@ -0,0 +1,2 @@
FFFFFFFFFFFFFFFFC90FDAA22168C234C4C6628B80DC1CD129024E088A67CC74020BBEA63B139B22514A08798E3404DDEF9519B3CD3A431B302B0A6DF25F14374FE1356D6D51C245E485B576625E7EC6F44C42E9A637ED6B0BFF5CB6F406B7EDEE386BFB5A899FA5AE9F24117C4B1FE649286651ECE45B3DC2007CB8A163BF0598DA48361C55D39A69163FA8FD24CF5F83655D23DCA3AD961C62F356208552BB9ED529077096966D670C354E4ABC9804F1746C08CA18217C32905E462E36CE3BE39E772C180E86039B2783A2EC07A28FB5C55DF06F4C52C9DE2BCBF6955817183995497CEA956AE515D2261898FA051015728E5A8AAAC42DAD33170D04507A33A85521ABDF1CBA64ECFB850458DBEF0A8AEA71575D060C7DB3970F85A6E1E4C7ABF5AE8CDB0933D71E8C94E04A25619DCEE3D2261AD2EE6BF12FFA06D98A0864D87602733EC86A64521F2B18177B200CBBE117577A615D6C770988C0BAD946E208E24FA074E5AB3143DB5BFCE0FD108E4B82D120A92108011A723C12A787E6D788719A10BDBA5B2699C327186AF4E23C1A946834B6150BDA2583E9CA2AD44CE8DBBBC2DB04DE8EF92E8EFC141FBECAA6287C59474E6BC05D99B2964FA090C3A2233BA186515BE7ED1F612970CEE2D7AFB81BDD762170481CD0069127D5B05AA993B4EA988D8FDDC186FFB7DC90A6C08F4DF435C934063199FFFFFFFFFFFFFFFF
2

22
relay/uuid.py Normal file
View File

@@ -0,0 +1,22 @@
import urandom
def uuid4():
b = bytearray(urandom.getrandbits(8) for _ in range(16))
# Set version (4) -> bits 12-15 of time_hi_and_version
b[6] = (b[6] & 0x0F) | 0x40
# Set variant (RFC 4122)
b[8] = (b[8] & 0x3F) | 0x80
def to_hex(x):
return '{:02x}'.format(x)
return (
to_hex(b[0]) + to_hex(b[1]) + to_hex(b[2]) + to_hex(b[3]) + '-' +
to_hex(b[4]) + to_hex(b[5]) + '-' +
to_hex(b[6]) + to_hex(b[7]) + '-' +
to_hex(b[8]) + to_hex(b[9]) + '-' +
to_hex(b[10]) + to_hex(b[11]) + to_hex(b[12]) +
to_hex(b[13]) + to_hex(b[14]) + to_hex(b[15])
)

46
test.py
View File

@@ -1,46 +0,0 @@
from bleak import BleakScanner, BleakClient
import asyncio
async def main():
devices = await BleakScanner.discover(service_uuids=["E1898FF7-5063-4441-a6eb-526073B00001"])
for device in devices:
print()
print(f"Name: {device.name}")
print(f"Address: {device.address}")
print(f"Details: {device.details}")
for device in devices:
try:
this_device = await BleakScanner.find_device_by_address(device.address, timeout=20)
async with BleakClient(this_device) as client:
print(f'Services found for device')
print(f'\tDevice address:{device.address}')
print(f'\tDevice name:{device.name}')
client.write_gatt_char()
print('\tServices:')
for service in client.services:
print()
print(f'\t\tDescription: {service.description}')
print(f'\t\tService: {service}')
print('\t\tCharacteristics:')
for c in service.characteristics:
print()
print(f'\t\t\tUUID: {c.uuid}'),
print(f'\t\t\tDescription: {c.uuid}')
print(f'\t\t\tHandle: {c.uuid}'),
print(f'\t\t\tProperties: {c.uuid}')
print('\t\tDescriptors:')
for descrip in c.descriptors:
print(f'\t\t\t{descrip}')
except Exception as e:
print(f"Could not connect to device with info: {device}")
print(f"Error: {e}")
asyncio.run(main())