A Lesson in LoRa Module P2P Standards (or the Lack Thereof)

I got a handful of LoRa modules from Reyax a while back, the RYLR896 model based on Semtech SX1276 chips. Instead of using an SPI interface they operate over UART using a small set of AT commands. This made them easier to work with since I didn't have to dig too deeply into a bunch of SPI registers and Semtech specs and they communicate between one another really well. My Espruino JS module for them is available here, which I've used in a few of my YouTube videos. And more recently I've written a MicroPython module for them here.

 
(A pair of Reyax RYLR896  modules)

But, always being on the lookout for different boards and platforms I eventually ended up with a few Maduino LoRa boards. These are cool because they have an Arduino-compatible ATmega328 and the same Semtech LoRa chip (via an RFM95) both integrated on one board. They weren't compatible with Espruino or MicroPython though, and they used the SPI interface instead of AT commands so I knew I would need to look elsewhere for code to control them. Luckily, the popular Arduino RadioHead library has support for RFM95s and the API is surprisingly easy to use. Only, one problem...

 
(A pair of Makerfabs Maduino LoRa boards)

Even when I set all of the LoRa parameters to be the same the boards didn't seem capable of communicating! So I took a peak at the signals using a Software Defined Radio device and they looked really similar. Except that the first portion of the message was different. So I went digging through the RadioHead source code to see if any data was being prepended to the packets or to see if any packet validation was rejecting them. Sure enough, I found the method "validateRxBuf":

 
(RadioHead RH_RF95.cpp source code)

The RadioHead library prepends four bytes to each packet (to, from, id, and flags) and it filters packets based on the "to" address. Somehow I'd overlooked the fact that the API also provides basic addressing (as does the Reyax AT commands). Probably because it's obscured by three layers of inheritance: RH_RF95 inherits RHSPIDriver which inherits RHGenericDriver which is where those methods are found.

So I enabled the "promiscuous" mode to stop the packet filtering and low and behold... I was receiving packets from the Reyax module using the Maduino LoRa board! But something still wasn't right. Every packet started with a garbage byte for some reason. After some trial and error experimenting (since I didn't have the source code to the Reyax AT controller) I realized that this garbage byte was the length of the packet.

For some reason the Reyax modules prepend the length of the message in front of the message itself. So while the RadioHead library sends [4 byte header] + [message] the Reyax modules send [4 byte header] + [1 byte message length] + [message]. This is a pretty easy problem to solve though, but it has to be done on the Maduino LoRa side because there is no way to change how the Reyax modules handled things behind the AT command API.

The devices allow for two forms of addressing. You can send messages directly to certain addresses where all devices that don't match the address will filter it out (unless they're in promiscuous mode). Or you can send messages to a "broadcast" address that all devices will accept. To use them properly in a project I wanted to disable promiscuous mode and that's where I hit another snag. For some reason broadcasting wouldn't work. So I dug through the documentation for the Reyax AT commands and the RadioHead source code and realized that they use different "broadcast" addresses.

Reyax modules use 0x00.

RadioHead library uses 0xff.

But again, this was a trivial fix (either by patching RadioHead to use 0x00 or by explicitly sending to 0x00 in the user code) and now everything was finally working. Until...

Still wanting to try new devices I got my hands on another Makerfabs board, this time their MakePython LoRa gateway. This one is even cooler because the main board is an ESP32 and the LoRa module attachment has two RFM95 modems! And it comes with MicroPython preinstalled, which is what I wanted to program them with since I'll be writing something a bit more on the complex side that integrates the ESP WiFi with the LoRa module.

 
(Makerfabs MakerPython ESP32 + LoRa gateway)

Not wanting to write something from scratch to deal with the RFM95 over SPI I looked for some example code. Makerfabs had a couple of example projects that used this MicroPython library. Now that I knew what to look for I dug into that code to see which header structure their implementation was using... And it turns out they weren't using one at all! It doesn't prepend or validate packets based on any headers and instead just sends the raw packet as-is.

So now I have to write some helper functions to deal with the headers in the user code (I also ended up rewriting that MicroPython library to fit my projects better here so I could also fork that and use the Reyax structure) but otherwise everything still works.

At the end of all this, what I learned is that there is no one standard protocol for p2p LoRa radio communication and it seems like every library takes their own liberties to enforce different packet structures. So if you ever find yourself in this situation hopefully you'll be able to fix it right away instead of having to do all of this sleuthing.

Now I have all three different models of devices communicating with one another, so if you'll excuse me I have an Internet of Things to build!