micropython-lib/micropython/lora/examples/reliable_delivery/receiver_async.py

122 wiersze
4.2 KiB
Python

# MicroPython lora reliable_delivery example - asynchronous receiver program
# MIT license; Copyright (c) 2023 Angus Gratton
import struct
import time
import asyncio
from machine import SPI, Pin
from micropython import const
from lora_rd_settings import RECEIVER_ID, ACK_LENGTH, ACK_DELAY_MS, lora_cfg
# Change _DEBUG to const(True) to get some additional debugging output
# about timing, RSSI, etc.
#
# For a lot more debugging detail, go to the modem driver and set _DEBUG there to const(True)
_DEBUG = const(False)
# Keep track of the last counter value we got from each known sender
# this allows us to tell if packets are being lost
last_counters = {}
def get_async_modem():
# from lora import AsyncSX1276
# return AsyncSX1276(
# spi=SPI(1, baudrate=2000_000, polarity=0, phase=0,
# miso=Pin(19), mosi=Pin(27), sck=Pin(5)),
# cs=Pin(18),
# dio0=Pin(26),
# dio1=Pin(35),
# reset=Pin(14),
# lora_cfg=lora_cfg,
# )
raise NotImplementedError("Replace this function with one that returns a lora modem instance")
def main():
# Initializing the modem.
#
print("Initializing...")
modem = get_async_modem()
asyncio.run(recv_continuous(modem, rx_callback))
async def rx_callback(sender_id, data):
# Do something with the data!
print(f"Received {data} from {sender_id:#x}")
async def recv_continuous(modem, callback):
# Async task which receives packets from the AsyncModem recv_continuous()
# iterator, checks if they are valid, and send back an ACK if needed.
#
# On each successful message, we await callback() to allow the application
# to do something with the data. Callback args are sender_id (as int) and the bytes
# of the message payload.
last_counters = {} # Track the last counter value we got from each sender ID
ack_buffer = bytearray(ACK_LENGTH) # reuse the same buffer for ACK packets
skipped_packets = 0 # Counter of skipped packets
modem.calibrate()
async for rx in modem.recv_continuous():
# Filter 'rx' packet to determine if it's valid for our application
if len(rx) < 5: # 4 byte header plus 1 byte checksum
print("Invalid packet length")
continue
sender_id, counter, data_len = struct.unpack("<HBB", rx)
csum = rx[-1]
if len(rx) != data_len + 5:
print("Invalid length in payload header")
continue
calc_csum = sum(b for b in rx[:-1]) & 0xFF
if csum != calc_csum:
print(f"Invalid checksum. calc={calc_csum:#x} received={csum:#x}")
continue
# Packet is valid!
if _DEBUG:
print(f"RX {data_len} byte message RSSI {rx.rssi} at timestamp {rx.ticks_ms}")
# Send the ACK
struct.pack_into("<HHBBb", ack_buffer, 0, RECEIVER_ID, sender_id, counter, csum, rx.rssi)
# Time send to start as close to ACK_DELAY_MS after message was received as possible
tx_at_ms = time.ticks_add(rx.ticks_ms, ACK_DELAY_MS)
tx_done = await modem.send(ack_buffer, tx_at_ms=tx_at_ms)
if _DEBUG:
tx_time = time.ticks_diff(tx_done, tx_at_ms)
expected = modem.get_time_on_air_us(ACK_LENGTH) / 1000
print(f"ACK TX {tx_at_ms}ms -> {tx_done}ms took {tx_time}ms expected {expected}")
# Check if the data we received is fresh or stale
if sender_id not in last_counters:
print(f"New device id {sender_id:#x}")
elif last_counters[sender_id] == counter:
print(f"Duplicate packet received from {sender_id:#x}")
continue
elif counter != 1:
# If the counter from this sender has gone up by more than 1 since
# last time we got a packet, we know there is some packet loss.
#
# (ignore the case where the new counter is 1, as this probably
# means a reset.)
delta = (counter - 1 - last_counters[sender_id]) & 0xFF
if delta:
print(f"Skipped/lost {delta} packets from {sender_id:#x}")
skipped_packets += delta
last_counters[sender_id] = counter
await callback(sender_id, rx[4:-1])
if __name__ == "__main__":
main()