Skip to content
Open
Show file tree
Hide file tree
Changes from 1 commit
Commits
Show all changes
104 commits
Select commit Hold shift + click to select a range
78900af
aioble: Add short name support to scan results.
FlantasticDan Feb 16, 2023
52fcb8e
cbor2: Add cbor2 library.
iabdalkader Nov 29, 2022
9ee0257
senml: Add SenML library.
iabdalkader Sep 27, 2022
1eb282a
tools/ci.sh: Support publishing package and index files to GitHub Pages.
projectgus Feb 20, 2023
40dfc5f
github/workflows: Attach built packages to GitHub workflow artifacts.
projectgus Feb 24, 2023
b9741f6
cbor2: Remove u-module prefix from imports.
iabdalkader Mar 2, 2023
295a9e3
senml: Remove u-module prefix from imports.
iabdalkader Mar 2, 2023
c860319
senml: Fix data record encoding to use binascii instead of base64.
iabdalkader Mar 3, 2023
ea21cb3
iperf3: Support devices without os.urandom().
dpgeorge Mar 21, 2023
f672353
unittest-discover: Print results when no tests are found/run.
pi-anl Mar 16, 2023
386ab99
python-ecosys: Add pypi= to metadata.
jimmo Apr 6, 2023
afc9d0a
micropython: Add missing metadata for packages.
jimmo Apr 6, 2023
9b5f4d7
tools/makepyproject.py: Add tool to generate PyPI package.
jimmo Mar 31, 2023
01db3da
senml: Allow publishing to PyPI as micropython-senml.
jimmo Mar 31, 2023
c113611
aioble: Fix descriptor flag handling.
jimmo Apr 11, 2023
a1b9aa9
aioespnow: Add library providing asyncio support for espnow module.
glenn20 Apr 7, 2023
7128d42
utarfile: Support creating/appending tar files.
dpwe May 14, 2023
1957f24
lora: Add lora modem drivers for SX127x and SX126x.
projectgus Jun 28, 2022
2fba6b8
lora: Workaround SX1262 bug with GetStatus.
projectgus Jan 16, 2023
da5ddfc
hashlib: Refactor, split, and optimise.
jimmo Jun 1, 2023
0a5b635
utarfile: Fix read/write handling of nulls in tar header.
dpgeorge Jul 21, 2023
fe3e0a2
cmd: Remove comments about using the string module.
jimmo Jul 21, 2023
66924d9
xmltok: Change StopIteration to EOFError due to PEP-479.
ThunderEX Feb 3, 2021
c48b17d
aiorepl/README.md: More info about globals.
jimmo Jul 21, 2023
c2b44ea
types: Add manifest file.
pi-anl Apr 19, 2023
0f768c9
bisect: Add manifest file.
pi-anl Apr 20, 2023
b95deb3
json: Add manifest file.
pi-anl Apr 20, 2023
028a369
keyword: Add manifest file.
pi-anl Apr 20, 2023
5329ef5
logging: Add full support for logging exception tracebacks.
pi-anl May 18, 2023
0e68c7d
copy: Declare dependency on types.
pi-anl Jun 13, 2023
ff84231
aiorepl: Replace f-string with str.format.
jimmo Jun 20, 2023
6103823
all: Remove __version__ from .py files.
jimmo Jun 22, 2023
e45a7f6
fnmatch: Fix compatibility with ure -> re.
pi-anl Jul 6, 2023
5004436
tarfile: Rename from utarfile.
jimmo Jul 21, 2023
8513bfb
requests: Rename urequests to requests.
jimmo Jul 21, 2023
87b4cda
aiorepl: Bump patch version.
jimmo Jul 21, 2023
97b7a30
xmltok: Bump patch version.
jimmo Jul 21, 2023
a19d2a3
copy: Bump patch version.
jimmo Jul 21, 2023
ebbb78e
logging: Bump minor version.
jimmo Jul 21, 2023
8fc9eda
all: Standardise x.y.z versioning for all packages.
jimmo Jul 21, 2023
752ce66
github/workflows: Build all example .py files as part of CI.
dpgeorge Jul 24, 2023
4da6e6f
all: Lint Python code with ruff.
cclauss May 2, 2023
36e74c1
zlib: Add zlib module.
jimmo Jun 26, 2023
2328592
tools/codeformat.py: Remove git state detection.
jimmo Jul 25, 2023
5cdfe71
top: Add pre-commit config.
jimmo Jul 25, 2023
efa0402
tools/codeformat.py: Fix ruff warnings.
jimmo Jul 25, 2023
ce3f282
github/workflows: Split ruff into its own action.
jimmo Jul 25, 2023
01ab7ba
iperf3: Add compatibility for servers pre version 3.2.
graeme-winter May 21, 2023
674e734
drivers/display/lcd160cr: Use isinstance() for type checking.
projectgus Aug 9, 2023
86050c3
bmm150: Remove broken reset function.
projectgus Aug 9, 2023
2d16f21
lsm6dsox: Add missing time import.
projectgus Aug 9, 2023
1f3002b
wm8960: Add missing self reference for sample table.
projectgus Aug 9, 2023
786c0ea
all: Add missing const imports
projectgus Aug 9, 2023
c6a72c7
cbor2: Improve decoder to pass Ruff F821 undefined-name.
projectgus Aug 9, 2023
991ac98
iperf3: Pre-declare some variables set in the loop.
projectgus Aug 9, 2023
b46306c
uaiohttpclient: Fix missing name in unreachable example code.
projectgus Aug 9, 2023
5b6fb2b
top: Enable Ruff linter to check undefined-name (F821).
projectgus Aug 9, 2023
1b557ee
lsm6dsox: Bump patch version.
dpgeorge Aug 23, 2023
dc765ad
wm8960: Bump patch version.
dpgeorge Aug 23, 2023
93bf707
lora: Remove the pin parameter from IRQ callback.
projectgus Aug 8, 2023
ed688cf
lora: Add STM32WL55 subghz LoRa modem class.
projectgus Nov 10, 2022
0bdecbc
lora: Note known issue with STM32WL5 HP antenna.
projectgus Aug 9, 2023
7fcc728
lora/sx126x: Fix busy timeout handling.
projectgus Aug 23, 2023
e6b89ea
all: Remove unnecessary start argument in range.
dpgeorge Aug 31, 2023
55d1d23
__future__: Add "annotations".
smurfix Sep 24, 2023
e5ba864
aioble/server.py: Add data arg for indicate.
jimmo Sep 14, 2023
46748d2
aioble/server.py: Allow BufferedCharacteristic to support all ops.
jimmo Sep 14, 2023
e025c84
requests: Fix detection of iterators in chunked data requests.
bwhitman May 30, 2023
0620d02
.github/workflows/ruff.yml: Pin to 0.1.0.
jimmo Oct 17, 2023
d8e163b
unix-ffi/re: Convert to PCRE2.
Ansuel Sep 28, 2023
ad0a259
tools/verifygitlog.py: Add git commit message checking.
jimmo Oct 26, 2023
cee0945
all: Replace "black" with "ruff format".
jimmo Oct 17, 2023
83f3991
lcd160cr: Remove support for options in manifest.
jimmo Nov 10, 2023
340243e
time: Add README to explain the purpose of the time extension library.
mattytrentini Sep 30, 2023
41aa257
base64: Implement custom maketrans and translate methods.
magixyu Nov 12, 2023
e051a12
aiorepl: Update import of asyncio.
pi-anl Oct 24, 2023
d41851c
aiorepl: Add support for paste mode (ctrl-e).
pi-anl Oct 24, 2023
10c9281
aiorepl: Add cursor left/right support.
pi-anl Oct 24, 2023
f672baa
aiorepl: Add support for raw mode (ctrl-a).
pi-anl Oct 24, 2023
ae8ea8d
os-path: Implement os.path.isfile().
scivision Sep 14, 2023
149226d
uaiohttpclient: Fix hard coded port 80.
bulletmark Nov 7, 2023
9d09cdd
uaiohttpclient: Make flake8 inspired improvements.
bulletmark Nov 7, 2023
05efdd0
uaiohttpclient: Update "yield from" to "await".
bulletmark Nov 7, 2023
9ceda53
uaiohttpclient: Update example client code.
bulletmark Nov 7, 2023
57ce3ba
aioble: Fix advertising variable name to use us not ms.
bhavesh-k Nov 20, 2023
7cdf708
aiohttp: Add new aiohttp package.
Carglglz Sep 5, 2023
803452a
umqtt.simple: Simplify check for user being unused.
felixdoerre Feb 1, 2024
35d41db
ssl: Restructure micropython SSL interface to a new tls module.
felixdoerre Feb 1, 2024
ddb1a27
hmac: Fix passing in a string for digestmod argument.
Pharkie Jan 20, 2024
56f514f
aiohttp: Fix binary data treatment.
Carglglz Jan 3, 2024
8058b29
tarfile-write: Fix permissions when adding to archive.
ubidefeo Feb 5, 2024
4cc6706
tools/ci.sh: Add unix-ffi library when testing unix-ffi subdirectory.
projectgus Feb 13, 2024
b712103
lora-sx126x: Fix invalid default configuration after reset.
projectgus Feb 13, 2024
ad6ab5a
lora-sync: Fix race with fast or failed send().
projectgus Feb 13, 2024
5462848
lora-sx127x: Implement missing syncword support.
projectgus Feb 13, 2024
35bb795
lora-sx126x: Fix syncword setting.
projectgus Feb 13, 2024
2242465
lora-sx126x: Clean up some struct formatting.
projectgus Feb 13, 2024
ffb07db
gzip: Fix recursion error in open() function.
dpgeorge Feb 29, 2024
23df50d
unix-ffi: Remove "unix_ffi" argument from require().
dpgeorge Mar 17, 2024
5c7e3fc
json: Move to unix-ffi.
jimmo Feb 22, 2024
8ee876d
cbor2: Deprecate decoder and encoder modules.
iabdalkader Mar 6, 2024
661efa4
senml: Use the updated cbor2 API.
iabdalkader Mar 6, 2024
744f8c4
usb: Add USB device support packages.
projectgus Mar 27, 2024
dfdedeb
The new lines 105 and 106 were necessary to ensure the proper handlin…
wzab Jun 10, 2024
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
Prev Previous commit
Next Next commit
lora: Add lora modem drivers for SX127x and SX126x.
Includes:
- component oriented driver, to only install the parts that are needed
- synchronous operation
- async wrapper class for asynchronous operation
- two examples with async & synchronous versions
- documentation

This work was funded through GitHub Sponsors.

Signed-off-by: Angus Gratton <[email protected]>
  • Loading branch information
projectgus authored and dpgeorge committed Jul 20, 2023
commit 1957f240206e4379023be2109f9ccc8cce0e9a20
1,156 changes: 1,156 additions & 0 deletions micropython/lora/README.md

Large diffs are not rendered by default.

93 changes: 93 additions & 0 deletions micropython/lora/examples/reliable_delivery/README.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,93 @@
# LoRa Reliable Delivery Example

This example shows a basic custom protocol for reliable one way communication
from low-power remote devices to a central base device:

- A single "receiver" device, running on mains power, listens continuously for
messages from one or more "sender" devices. Messages are payloads inside LoRa packets,
with some additional framing and address in the LoRa packet payload.
- "Sender" devices are remote sensor nodes, possibly battery powered. These wake
up periodically, read some data from a sensor, and send it in a message to the receiver.
- Messages are transmitted "reliably" with some custom header information,
meaning the receiver will acknowledge it received each message and the sender
will retry sending if it doesn't receive the acknowledgement.

## Source Files

* `lora_rd_settings.py` contains some common settings that are imported by
sender and receiver. These settings will need to be modified for the correct
frequency and other settings, before running the examples.
* `receiver.py` and `receiver_async.py` contain a synchronous (low-level API)
and asynchronous (iterator API) implementation of the same receiver program,
respectively. These two programs should work the same, they are intended show
different ways the driver can be used.
* `sender.py` and `sender_async.py` contain a synchronous (simple API) and
asynchronous (async API) implementation of the same sender program,
respectively. Because the standard async API resembles the Simple API, these
implementations are *very* similar. The two programs should work the same,
they are intended to show different ways the driver can be used.

## Running the examples

One way to run this example interactively:

1. Install or "freeze in" the necessary lora modem driver package (`lora-sx127x`
or `lora-sx126x`) and optionally the `lora-async` package if using the async
examples (see main lora `README.md` in the above directory for details).
2. Edit the `lora_rd_settings.py` file to set the frequency and other protocol
settings for your region and hardware (see main lora `README.md`).
3. Edit the program you plan to run and fill in the `get_modem()` function with
the correct modem type, pin assignments, etc. for your board (see top-level
README). Note the `get_modem()` function should use the existing `lora_cfg`
variable, which holds the settings imported from `lora_rd_settings.py`.
4. Change to this directory in a terminal.
5. Run `mpremote mount . exec receiver.py` on one board and `mpremote mount
. exec sender.py` on another (or swap in `receiver_async.py` and/or
`sender_async.py` as desired).

Consult the [mpremote
documentation](https://docs.micropython.org/en/latest/reference/mpremote.html)
for an explanation of these commands and the options needed to run two copies of
`mpremote` on different serial ports at the same time.

## Automatic Performance Tuning

- When sending an ACK, the receiver includes the RSSI of the received
packet. Senders will automatically modify their output_power to minimize the
power consumption required to reach the receiver. Similarly, if no ACK is
received then they will increase their output power and also re-run Image
calibration in order to maximize RX performance.

## Message payloads

Messages are LoRa packets, set up as follows:

LoRA implicit header mode, CRCs enabled.

* Each remote device has a unique sixteen-bit ID (range 00x0000 to 0xFFFE). ID
0xFFFF is reserved for the single receiver device.
* An eight-bit message counter is used to identify duplicate messages

* Data message format is:
- Sender ID (two bytes, little endian)
- Counter byte (incremented on each new message, not incremented on retry).
- Message length (1 byte)
- Message (variable length)
- Checksum byte (sum of all proceeding bytes in message, modulo 256). The LoRa
packet has its own 16-bit CRC, this is included as an additional way to
disambiguate other LoRa packets that might appear the same.

* After receiving a valid data message, the receiver device should send
an acknowledgement message 25ms after the modem receive completed.

Acknowledgement message format:
- 0xFFFF (receiver station ID as two bytes)
- Sender's Device ID from received message (two bytes, little endian)
- Counter byte from received message
- Checksum byte from received message
- RSSI value as received by radio (one signed byte)

* If the remote device doesn't receive a packet with the acknowledgement
message, it retries up to a configurable number of times (default 4) with a
basic exponential backoff formula.

38 changes: 38 additions & 0 deletions micropython/lora/examples/reliable_delivery/lora_rd_settings.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,38 @@
# MicroPython lora reliable_delivery example - common protocol settings
# MIT license; Copyright (c) 2023 Angus Gratton

#
######
# To be able to be able to communicate, most of these settings need to match on both radios.
# Consult the example README for more information about how to use the example.
######

# LoRa protocol configuration
#
# Currently configured for relatively slow & low bandwidth settings, which
# gives more link budget and possible range.
#
# These settings should match on receiver.
#
# Check the README and local regulations to know what configuration settings
# are available.
lora_cfg = {
"freq_khz": 916000,
"sf": 10,
"bw": "62.5", # kHz
"coding_rate": 8,
"preamble_len": 12,
"output_power": 10, # dBm
}

# Single receiver has a fixed 16-bit ID value (senders each have a unique value).
RECEIVER_ID = 0xFFFF

# Length of an ACK message in bytes.
ACK_LENGTH = 7

# Send the ACK this many milliseconds after receiving a valid message
#
# This can be quite a bit lower (25ms or so) if wakeup times are short
# and _DEBUG is turned off on the modems (logging to UART delays everything).
ACK_DELAY_MS = 100
163 changes: 163 additions & 0 deletions micropython/lora/examples/reliable_delivery/receiver.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,163 @@
# 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("<HBB", rx)
csum = rx[-1]

if len(rx) != data_len + 5:
print("Invalid length in payload header")
return None

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}")
return None

# 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", self.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 = self.modem.send(self.ack_buffer, tx_at_ms=tx_at_ms)

if _DEBUG:
tx_time = time.ticks_diff(tx_done, tx_at_ms)
expected = self.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 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()
Loading