This is an old revision of the document!
Buying a LoRaWAN gateway for an end device can be a little expensive. You'd need an 8-channel LoRa concentrator (e.g. for a Raspberry Pi) or an all-in-one gateway unit.
Then you could build a simple sensor end device and have the gateway route your packets to something like the The Things Network.
The better option, and if you are lucky, there might be a gateway in your area that will pick up your sensor data for you.
That is certainly the case for me being out of town. Thankfully I did discover via the TTN map that there is gateway not very far away, just over the hill.
If a gateway is in range, that gives you the opportunity to experiment without the cost and maintenance of a gateway. Now you could probably just start by making an end device and registering it on the TTN, and hope for the best that a gateway may pick it up, but how about we make a sniffer device that scans the downlink channels for LoRaWAN packets?
Note that this sniffer only listens to gateway downlinks.
In our case, we'll be working with the AU915 LoRaWAN downlink channels DR8–DR13 used by gateways to talk to end devices. This is the combination of Frequencies and Spreading Factors. We'll listen for a period of time on each channel before switching to the next. This way we increase the amount of packets that can be heard from any nearby gateway. The table of AU915 downlink channels (gateway to end device) are as follows:
| Channel index | Frequency (MHz) | Data Rate | SF | BW (kHz) | CR | Preamble (symbols) |
|---|---|---|---|---|---|---|
| 0 | 923.3 | DR8 | SF12 | 500 | 4/5 | 8 |
| 1 | 923.9 | DR9 | SF11 | 500 | 4/5 | 8 |
| 2 | 924.5 | DR10 | SF10 | 500 | 4/5 | 8 |
| 3 | 925.1 | DR11 | SF9 | 500 | 4/5 | 8 |
| 4 | 925.7 | DR12 | SF8 | 500 | 4/5 | 8 |
| 5 | 926.3 | DR13 | SF7 | 500 | 4/5 | 8 |
| 6 | 926.9 | DR13 | SF7 | 500 | 4/5 | 8 |
| 7 | 927.5 | DR13 | SF7 | 500 | 4/5 | 8 |
Note that each channel has the bandwidth of 500 kHz, the Coding Rate of 4/5 and Preamble of 8.
1. Open ESP-IDF 5.3.2 PowerShell Create a blank project with:
idf.py create-project lorawan-sniffer cd lorawan-sniffer
If you're using an esp32 set it here. Ensure you set the right esp32 hardware, eg: esp32, esp32s2, etc
idf.py set-target esp32
Ensure you know how much memory is on board your esp32. Set the memory on board, and the frequency for the FreeRTOS tick:
idf.py menuconfig Serial Flasher config > Flash size > 4MB Component > FreeRTOS > Kernel > configTICK_RATE_HZ
Quick test build:
idf.py build
Add the LoRa library component to the project by following section on their website here: https://github.com/nopnop2002/esp-idf-sx127x#how-to-use-this-component-in-your-project
Then configure again with:
idf.py menuconfig
There will be a new menu called: LoRa Configuration. Head into this menu and set up which points on the ESP32 are connected to SPI MISO, SCK, MOSI, NSS and RESET.
On this ESP32 pictured above it will be:
(19) -> MISO GPIO (18) -> SCK GPIO (23) -> MOSI GPIO (5) -> NSS GPIO (22) -> RST GPIO
Save and rebuild with:
idf.py build
Get your COM port from the Device Manager. If it was COM5, you can flash with:
idf.py -p COM5 flash
I'll dump the entire routine here. Essentially there are two FreeRTOS tasks, one to discover packets over the air on each channel for a set amount of attempts before moving to the next channel. The sniffer will move round-robin over all eight.
If a packet is discovered, the LED will blink off and on, and the resulting packet is sent to the serial terminal via USB.
/* Scan LoRaWAN channels for raw packets */ #include <stdio.h> #include "esp_log.h" #include "driver/gpio.h" #include "freertos/FreeRTOS.h" #include "freertos/task.h" #include "lora.h" #define BUILT_IN_LED 2 #define ON 1 #define OFF 0 #define MAX_CHANNEL_ATTEMPTS 2048 #define CODING_RATE_4_5 1 #define PREAMBLE_SYMBOLS 8 #define BW_500_KHZ 9 int blinkRequest = 0; // | Channel index | Frequency (MHz) | Data Rate | SF | BW (kHz) | CR | Preamble (symbols) | // | ------------- | --------------- | --------- | ---- | -------- | --- | ------------------ | // | 0 | 923.3 | DR8 | SF12 | 500 | 4/5 | 8 | // | 1 | 923.9 | DR9 | SF11 | 500 | 4/5 | 8 | // | 2 | 924.5 | DR10 | SF10 | 500 | 4/5 | 8 | // | 3 | 925.1 | DR11 | SF9 | 500 | 4/5 | 8 | // | 4 | 925.7 | DR12 | SF8 | 500 | 4/5 | 8 | // | 5 | 926.3 | DR13 | SF7 | 500 | 4/5 | 8 | // | 6 | 926.9 | DR13 | SF7 | 500 | 4/5 | 8 | // | 7 | 927.5 | DR13 | SF7 | 500 | 4/5 | 8 | long frequencies[] = { 923300, 923900, 924500, 925100, 925700, 926300, 926900, 927500 }; int factors[] = { 12, 11, 10, 9, 8, 7, 7, 7 }; void print_buffer_hex(const uint8_t *buf, size_t len) { for (int i = 0; i < len; ++i) { printf("%02X", buf[i]); } printf("\n"); } void blink(){ gpio_set_level(BUILT_IN_LED, OFF); //^^^ First time lit is a signal found. vTaskDelay(50); gpio_set_level(BUILT_IN_LED, ON); //^^^ Stay lit default as a battery deployment test. //Temporary dip shows a new signal ESP_LOGI(pcTaskGetName(NULL), "LED has been lit."); } void receiveTask(void *pvParameters) { int channelAttempts = 0; int channel = 0; ESP_LOGI(pcTaskGetName(NULL), "Start"); uint8_t buf[255]; // Maximum Payload size of SX1276/77/78/79 is 255 while(1) { lora_receive(); // put into receive mode if (lora_received()) { int rxLen = lora_receive_packet(buf, sizeof(buf)); if (rxLen > 0){ ESP_LOGI(pcTaskGetName(NULL), "%d byte packet received:[%.*s]", rxLen, rxLen, buf); print_buffer_hex(buf, rxLen); blinkRequest = 1; } else { ESP_LOGI(pcTaskGetName(NULL), "Likely noise."); } } channelAttempts++; if (channelAttempts > MAX_CHANNEL_ATTEMPTS){ channelAttempts = 0; channel++; if (channel >= 8 ){ channel = 0; } ESP_LOGI(pcTaskGetName(NULL), "Switching to Frequency %.3f MHz and SF %d", (double)frequencies[channel]/1000, factors[channel]); lora_set_frequency(frequencies[channel]); lora_set_spreading_factor(factors[channel]); } vTaskDelay(1); } } void blinkerTask(void *pvParameters) { while(1) { if (blinkRequest == 1){ blinkRequest = 0; blink(); } vTaskDelay(50); } } void app_main(void) { gpio_set_direction(BUILT_IN_LED, GPIO_MODE_OUTPUT); // Initialize LoRa if (lora_init() == 0) { ESP_LOGE(pcTaskGetName(NULL), "Does not recognize the module"); while(1) { vTaskDelay(1); } } lora_set_frequency(frequencies[0]); ESP_LOGI(pcTaskGetName(NULL), "Frequency is %.3f MHz", (double)frequencies[0]/1000); lora_enable_crc(); lora_set_coding_rate(CODING_RATE_4_5); ESP_LOGI(pcTaskGetName(NULL), "coding_rate=%d", CODING_RATE_4_5); lora_set_bandwidth(BW_500_KHZ); ESP_LOGI(pcTaskGetName(NULL), "bandwidth=%d", BW_500_KHZ); lora_set_spreading_factor(factors[0]); ESP_LOGI(pcTaskGetName(NULL), "spreading_factor=%d", factors[0]); lora_set_preamble_length(PREAMBLE_SYMBOLS); ESP_LOGI(pcTaskGetName(NULL), "preamble: %d", PREAMBLE_SYMBOLS); lora_set_sync_word(0x34); lora_explicit_header_mode(); xTaskCreate(&receiveTask, "RX", 1024*3, NULL, 5, NULL); xTaskCreate(&blinkerTask, "BLINKER", 1024*2, NULL, 6, NULL); }
Note that Sync Word of 0x34 is important for LoRaWAN packets.
Build and flash to your device and watch for the LED to light. Eventually you should strike gold with something like this:
I (4569637) RX: Switching to Frequency 927.500 MHz and SF 7 I (4590127) RX: Switching to Frequency 923.300 MHz and SF 12 I (4610617) RX: Switching to Frequency 923.900 MHz and SF 11 I (4631107) RX: Switching to Frequency 924.500 MHz and SF 10 I (4651597) RX: Switching to Frequency 925.100 MHz and SF 9 I (4672087) RX: Switching to Frequency 925.700 MHz and SF 8 I (4692577) RX: Switching to Frequency 926.300 MHz and SF 7 I (4708937) RX: 197 byte packet received:[w▒▒X▒gԆ▒&▒j▒m▒hs▒[▒2N▒R▒+_▒SC▒▒HE[▒▒▒=V▒▒▒▒- 6▒˓й="g+CB▒J▒jR ▒▒▒L] 77939258F567D486D126D26A916DAF6873FD5BA2324EE7529F2B5FE55343E1F648455BEDE7DE3D56B982A9CF012D0C36B3CB93D0B93D22672B43421DD94AA16A140252091BAFD6CEE4FB1A07084C0002A1ABEBD8CE3A8DB37FF013C963F23186AFEDFD8D962782DF75BD16D4ED776FF50FC21AD28030685379C4DA15811083515C2A3B2AE58C2B0CC9CD2114538688F126ADFC652C976B24E055FE43C0B2B5916221D0674CD368DB7A9289DE2B67805EF46A2A655BE060DF6F093C32A388F098AF47B948DD I (4709847) BLINKER: LED has been lit. I (4713097) RX: Switching to Frequency 926.900 MHz and SF 7 I (4733587) RX: Switching to Frequency 927.500 MHz and SF 7 I (4754077) RX: Switching to Frequency 923.300 MHz and SF 12 I (4774567) RX: Switching to Frequency 923.900 MHz and SF 11 I (4795057) RX: Switching to Frequency 924.500 MHz and SF 10 I (4815547) RX: Switching to Frequency 925.100 MHz and SF 9
You can copy the hex string out of the terminal output and pop it into a LoRaWAN packet decoder like Alien Fusion's LoRa Packet Decoder and check that it's legit.
In the example hex above we can decode that it is:
Unconfirmed Data Down (Type 3) LoRaWAN R4 177 bytes of payload.
That's paydirt, and that shows there is a gateway somewhere around that you can hear. Ensuring it can hear you will come down to choosing a good antenna and decent position for your end device. But that's for next time.
Have fun.