Repository navigation
micropython/lora: Add async-capable LoRa modem driver - #501
Conversation
|
Hello @projectgus Great. This is a great lib for the MicroPython system!
Thank you very much. |
|
Sorry, I see above now that is specific for the SX1276. |
|
I was questioning that because I would like to use in the feature the 2.4Ghz LORA https://www.semtech.com/products/wireless-rf/lora-24ghz for indoor and outdoor location - It has ~3 meters accuracy, like as the SX1280 |
|
@beyonlo The idea is to have a generic LoRA modem API that can be extended to other modem models in the future, such as the SX1280. However at least for now the focus is on messaging and supporting LoRAWAN nodes, rather than ToF range measurement. |
|
@projectgus No problem. To have a Micropython driver for messaging on LORA nodes, will be great :) |
2096d88 to
0664189
Compare
0664189 to
48ea643
Compare
|
I've had a play with this driver using 2x SX1276 modules connected to a PYBLITEv1.0 and a PYBD-SF2. Then they run the It works! Both devices can send and receive to/from each other. For the power consumption, it seems quite high. I measure about 13mA when in RX mode (on top of the pyboard current), and about 30-45mA when transmitting a message (again, on top of pyboard current). Is that expected? |
I think this would be very useful, for the driver to automatically manage switching between TX and RX. For LoRaWAN, or for a custom LoRa protocol, is the idea to have RX constantly on (except when transmitting), or for RX only to be active periodically to reduce power consumption? |
That sounded high to me also, but SX1276 datasheet says "Supply current in receive mode" between 10.8 and 11.5mA for AU915 depending on which antenna is used. Transmit current 20-120mA depending on transmit power level. So probably to be expected.
LoRaWAN specifies particular time slices when the node has to receive. Given how much of a power drain constant receiving is, it won't be desirable for battery-powered devices. It's likely most custom LoRa setups will have at least one end on battery power, so it's probably not that useful to have seamless RX/TX switching in the driver. Will pull it out. |
Yeah, I came to the same conclusion reading the SX datasheet.
OK. We can always put it back in when we have more experience with this driver. For now, best to keep it simple/minimal. |
ff1da3d to
95204e4
Compare
|
@dpgeorge Thanks for the review, I've updated based on your comments and did some additional memory usage optimisations (especially in sx1262.py). The stm32wl5 support has now been split into an sx1262.py driver, tested with a local module, and I've moved the small amount of additional changes needed for the stm32wl5 into another branch for a separate PR. This means this PR no longer requires any MicroPython changes, and @jimmo your modules should now work properly with it! |
|
Looking good! One thing that's needed is a Probably the directory |
|
@dpgeorge Can do! Sorry, I did know I needed to do this but I hadn't put it on the TODO list. Will crib from
[*] It does mostly implement the synchronous methods that using the AbstractModem() wrapper removes the need for, but I can't think of a better hierarchy that doesn't include multiple inheritance. And I think probably best to avoid multiple inheritance! |
4f6864d to
d65b630
Compare
There was a problem hiding this comment.
Nice code overall!
I'm having a hard time picturing how to use this driver, there are a bunch of things missing and few that make usage difficult. If this driver is supposed to be a low level driver that can be used for different high-level LoRa uses then I would expect it to look somewhat different:
- break all sub-parts of
config()out into separate functions so they can be called when needed, examples are changing BW/SF and TX power in response to ACKs (or lack thereof) or changing channels (freq) (see https://github.com/lemariva/uPyLoRaWAN/blob/master/sx127x.py for example where the init is a collection of calls to these broken out functions) - ability to have continuous RX as the default mode as opposed to switching back to standby (that's what I tend to use for powered devices)
- provide functionality to check for RX in-progress before TX
- provide a function to calculate packet duration (with LoRa it's not that trivial and often needed since air time can be quite long)
provide timing information, in particular when a packet was received (I find this essential to sync nodes e.g. to define an ACK window when there is no other shared clock, such as GPS)I take that back, there's no significant difference between calling ticks_ms in the driver irq handler vs. the upper layer's irq handler, except for the fact that the upper layer doesn't immediately know why it's called- provide functions to enter and leave sleep mode, incl. setting all pins to high impedance
- provide a function to read the FEI (frequency error indication), I find it pretty useful to tune the frequency of sensor nodes when using FSK mode; I don't know whether LoRa is more robust WRT freq deviation (hmmm, certainly not in the very narrow band modes) and the FEI is an easy way to ensure the nodes agree with a central gateway (I'm not using LoRaWAN)
I find the way the IRQ flags are managed rather fragile. All the masking/unmasking only works if start_xx/end_xx are always correctly called in the right sequence. If they're not, one is guaranteed odd and difficult to troubleshoot results. I'm used to clear all flags before switching to TX/RX mode so I'm guaranteed I'm starting from a known state for that phase. Also, I've never used the IRQ masks on the semtech chips and rather do a dispatch in the IRQ handler to figure out what happened. You seem to experience some spurious interrupt according tot he comments and I wouldn't be surprised if it had to do with changing mode, changing IRQ masks, and changing DIO Mapping in back-to-back SPI transactions. There's a wimpy little uC in that sx1276 that has to keep up and do the right thing kepping in mind that the mode change may take way longer than the subsequent SPI transactions (we know that little uC has bugs...)
WRT polling for RX I would not provide any examples or high level code that polls the radio for RX complete. It's a noise disaster in my experience. (Maybe different in stm32wle.) I believe the only appropriate non-interrupt usage pattern for RX is to have an RX window, start the RX at the beginning of the window, wait, and then check whether something arrived once at the end of the window.
I did not see high-level code to operate the driver in always-RX mode and I believe it's rather tricky. Specifically because the IRQ handler gets no info about why it got called and there's an issue with calling RX when no RX-complete indication has been provided (e.g. because one wants to TX).
Documentation???
There was a problem hiding this comment.
[Oh weird, looks like github submitted a second copy of my comments just because I added another comment... Ignore...]
Nice code overall!
I'm having a hard time picturing how to use this driver, there are a bunch of things missing and few that make usage difficult. If this driver is supposed to be a low level driver that can be used for different high-level LoRa uses then I would expect it to look somewhat different:
- break all sub-parts of
config()out into separate functions so they can be called when needed, examples are changing BW/SF and TX power in response to ACKs (or lack thereof) or changing channels (freq) (see https://github.com/lemariva/uPyLoRaWAN/blob/master/sx127x.py for example where the init is a collection of calls to these broken out functions) - ability to have continuous RX as the default mode as opposed to switching back to standby (that's what I tend to use for powered devices)
- provide functionality to check for RX in-progress before TX
- provide a function to calculate packet duration (with LoRa it's not that trivial and often needed since air time can be quite long)
- provide timing information, in particular when a packet was received (I find this essential to sync nodes e.g. to define an ACK window when there is no other shared clock, such as GPS)
- provide functions to enter and leave sleep mode, incl. setting all pins to high impedance~
I find the way the IRQ flags are managed rather fragile. All the masking/unmasking only works if start_xx/end_xx are always correctly called in the right sequence. If they're not, one is guaranteed odd difficult to troubleshoot results. I'm more used to clear all flags before switching to TX/RX mode so I'm guaranteed I'm starting from a known state for that phase. Also, I've never used the IRQ masks on the semtech chips and rather do a dispatch in the IRQ handler to figure out what happened. You seem to experience some spurious interrupt according tot he comments and I wouldn't be surprised if it had to do with changing mode, changing IRQ masks, and changing DIO Mapping in back-to-back SPI transactions. There's a wimpy little uC in that sx1276 that has to keep up and do the right thing kepping in mind that the mode change may take way longer than the subsequent SPI transactions (we know that little uC has bugs...)
WRT polling for RX I would not provide any examples or high level code that polls the radio for RX complete. It's a noise disaster in my experience. (Maybe different in stm32wle.) I believe the only appropriate non-interrupt usage pattern for RX is to have an RX window, start the RX at the beginning of the window, wait, and then check whether something arrived once at the end of the window.
I did not see high-level code to operate the driver in always-RX mode and I believe it's rather tricky. Specifically because the IRQ handler gets no info about why it got called and there's an issue with calling RX when no RX-complete indication has been provided (e.g. because one wants to TX).
Documentation???
e42edd8 to
b6cf4ab
Compare
4672387 to
64d41c4
Compare
4c5395b to
acb379d
Compare
|
One thing @jimmo and I just discussed was splitting out the sync-modem methods (namely Benefits:
Drawbacks:
@projectgus what are your thoughts on that? |
@tve also asked about the possibility of a change like this, so it's clearly a logical layout for folks. I agree about the benefits, it's a more symmetrical set of relationships. The reasons I avoided it until now was a general idea of "keep the simple case - Sync - as simple as possible", but also that I don't know how to logically describe these classes if we split them into two types of "thing":
... not knowing what names to give them makes me think it's a code smell. (I guess the current code has this problem as well regarding AsyncModem, and maybe if keeping the current structure then AsyncModem should be called AsyncAdapter or something to disambiguate... this is all starting to feel very Java-y!) The other way around this would be to use multiple inheritance, so each "public" class in the package inherits from either SyncModem or AsyncModem and also from a chip-specific modem base (SX1276, SX1262, etc.), meaning you can do something like |
acb379d to
5d67b21
Compare
|
Method renames have been implemented!
@dpgeorge @jimmo Please take a look at the head commit in this branch. I'm not 100% sure this is a good idea, but I think it's still preferable to having SyncModem/AsyncModem wrappers that take chip-specific objects as arguments. I put some reasons in the previous comment on this PR, but the final challenge was telling the user which object to call which method on. For example, do you call If you think the "mixin-like class" approach in that commit is too fiddly then I fully understand though, I'm not 100% convinced on it myself - the best I could say for it is that it might be the "least bad" way. Am happy to drop thjs commit and try something else instead if you're not convinced. |
acc1550 to
17b74cd
Compare
|
(Rebased, tweaked some wording in the README a tiny bit.) |
|
Rebased, pushed a bugfix with CRC as reported by Barry on Discord and another bug fix with async wait timeout on send (only relevant if IRQ doesn't trigger as expected). |
|
Please make it configure the pins in the constructor (using Also rename u-module to module. |
|
@dpgeorge @jimmo Thanks for the review of a large and complex PR! Have applied the requested changes (see 30214ee, 247597b), re-tested the two major modem models with sync and async code, squashed commits and rebased on master. EDIT: Oh well, looks like when I rebased my individual fix commits were lost from GitHub. But the fixes are in there. :) |
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 <angus@redyak.com.au>
This work was funded through GitHub Sponsors. Signed-off-by: Angus Gratton <angus@redyak.com.au>
|
I tested the (I had to fix the |
|
Thanks for the huge effort on this @projectgus ! |
|
Congrats and thanks to everyone involved! |
Working LoRa drivers for:
Eventual goal (not in this PR) is to have enough support for MicroPython to be a LoRaWAN Class A device with a fully async API available.
How to Test this PR
The lora README added in the PR branch has instructions for how to use the LoRa drivers.
However, until the PR is merged you need some additional steps to install the lora MicroPython packages.
Installing with mip
The
--indexargument of mip install is needed to install packages from a PR branch.Example:
(Replace
/dev/ttyUSB0with the serial port you are using, and replacelora-sx127xandlora-syncwith the appropriate packages for your LoRa hardware and programming style - see the lora README for explanation.)Or from a REPL:
(Replace the package name as needed.)
Freezing lora into the firmware
If your board doesn't support mip, you can freeze the lora modules into the firmware.
micropython-libgit submodule, as found in thelib/micropython-libdirectory of the micropython source, to the branch for this PR. GitHub CLI client is the easiest way to check out a PR branch from the command line: Running commands similar tocd lib/micropython; gh pr checkout 501should do it.TODOs
modem = # TODO put your modem config here, but haven't made this change yet._irq_enas a workaround)receive()while another task sometimes calledtransmit(), and have the modem driver handle the state changes internally without the caller needing to do anything. This isn't necessary for LoRAWAN, but it would make some other types of application logic much simpler. (Implemented.)rx_continuousmode. If the previous dot point can be implemented then it might not be needed to explicitly support putting the modem into continuous RX mode. (Implemented.)assertin the code - some places are probably ValueErrors, some places may be unnecessary.Follow-up work
Intended for future PRs:
Document theant_swfeature that allows callbacks for switching off-modem antenna switches for RX, TX.calibrate()andcalibrate_image()functions.Document the modemcrc_errorscounter field, and maybe break out CRC checking to its own doc section.SX126x radios with DIO2 configured as "enable TCXO" line currently take around 18ms to start the TCXO, which can be made configurable.We can also add a configuration option to not automatically stop the TCXO (and go to "RC Standby") when a receive or transmit ends, which allows tighter receive/transmit timing.Example API usage
This PR adds two examples, each with synchronous and async API versions:
simple_rxtxexample that just transmits and receives in a loopreliable_deliverywhich implements a relatively simple "ACK or retry" scheme to receive packets reliably from low power nodes.This work was funded through GitHub Sponsors.