# MicroPython lora reliable_delivery example - synchronous receiver program # MIT license; Copyright (c) 2023 Angus Gratton import struct import time import machine from machine import SPI, Pin from micropython import const from lora import RxPacket 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_modem(): # from lora import SX1276 # return SX1276( # 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(): print("Initializing...") modem = get_modem() print("Main loop started") receiver = Receiver(modem) while True: # With wait=True, this function blocks until something is received and always # returns non-None sender_id, data = receiver.recv(wait=True) # Do something with the data! print(f"Received {data} from {sender_id:#x}") class Receiver: def __init__(self, modem): self.modem = modem self.last_counters = {} # Track the last counter value we got from each sender ID self.rx_packet = None # Reuse RxPacket object when possible, save allocation self.ack_buffer = bytearray(ACK_LENGTH) # reuse the same buffer for ACK packets self.skipped_packets = 0 # Counter of skipped packets modem.calibrate() # Start receiving immediately. We expect the modem to receive continuously self.will_irq = modem.start_recv(continuous=True) print("Modem initialized and started receive...") def recv(self, wait=True): # Receive a packet from the sender, including sending an ACK. # # Returns a tuple of the 16-bit sender id and the sensor data payload. # # This function should be called very frequently from the main loop (at # least every ACK_DELAY_MS milliseconds), to avoid not sending ACKs in time. # # If 'wait' argument is True (default), the function blocks indefinitely # until a packet is received. If False then it will return None # if no packet is available. # # Note that because we called start_recv(continuous=True), the modem # will keep receiving on its own - even if when we call send() to # send an ACK. while True: rx = self.modem.poll_recv(rx_packet=self.rx_packet) if isinstance(rx, RxPacket): # value will be True or an RxPacket instance decoded = self._handle_rx(rx) if decoded: return decoded # valid LoRa packet and valid for this application if not wait: return None # Otherwise, wait for an IRQ (or have a short sleep) and then poll recv again # (receiver is not a low power node, so don't bother with sleep modes.) if self.will_irq: while not self.modem.irq_triggered(): machine.idle() else: time.sleep_ms(1) def _handle_rx(self, rx): # Internal function to handle a received packet and either send an ACK # and return the sender and the payload, or return None if packet # payload is invalid or a duplicate. if len(rx) < 5: # 4 byte header plus 1 byte checksum print("Invalid packet length") return None sender_id, counter, data_len = struct.unpack(" {tx_done}ms took {tx_time}ms expected {expected}") # Check if the data we received is fresh or stale if sender_id not in self.last_counters: print(f"New device id {sender_id:#x}") elif self.last_counters[sender_id] == counter: print(f"Duplicate packet received from {sender_id:#x}") return None 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 - self.last_counters[sender_id]) & 0xFF if delta: print(f"Skipped/lost {delta} packets from {sender_id:#x}") self.skipped_packets += delta self.last_counters[sender_id] = counter return sender_id, rx[4:-1] if __name__ == "__main__": main()