Skip to main content

SenseCAP Indicator - LoRa Application Development

Introduction

LoRa® is a long-range wireless communication technology optimized for sending small amounts of data over far distances. It works by modulating radio signals in the sub-GHz spectrum using a method called Chirp Spread Spectrum (CSS).

The SenseCAP Indicator(version D1L and D1Pro) by Seeed Studio includes a built-in LoRa transceiver module(Semtech SX1262 LoRa® chip), making it easy to add low-power wireless connectivity to your projects. In this post, we'll walk through how to set up LoRa communication between two SenseCAP Indicator boards.

Overview

This demonstration showcases how to establish a basic LoRa communication between the SenseCAP Indicator and the XIAO board using the Wio-E5 as an intermediary. The SenseCAP Indicator retrieves sensor data from the XIAO, which is then transmitted via Wio-E5. The transmitted payload is subsequently received by the SenseCAP Indicator, which deciphers and outputs. the result and displays the data on its screen.

No LVGL Code: Code · GitHub

Hardware

SenseCAP Indicator

From the page, Dive_into_the_Hardware, we can see that the LoRa transceiver is connected to the ESP32-S3 MCU via the SPI interface.

The key components are:

  • Semtech SX1262 radio front-end
  • ESP32-S3 MCU

The LoRa transceiver handles all the low-level modulation and demodulation of LoRa signals. We can communicate with it using the SPI interface from the ESP32-S3.

XIAO

The XIAO in this demo is required to collect sensor data and transmit it to the SenseCAP Indicator via the Wio-E5. The XIAO is connected to the Wio-E5 via the UART interface.

  • XIAO
  • Wio-E5
  • SEN5x

Software

As the SenseCAP_Indicator_ESP32 SDK has already provided the LoRa library, we can use it directly, you can quickly review the page LoRa® to see how to use the LoRa library.

Getting Started

this demo demonstrates how to set up Local LoRa® Hub for IoT Connectivity.

Prerequisites

Please follow the instructions provided to set up the Development environment.

Step 1: Download the Demo Code

Clone or download the demo code from this link. This code will serve as the starting point for your LoRa application.

Step 2: Implement the Payload Encoder (XIAO;Arduino)

Step 2.1: Implement Your Payload Structure and Encoder

#ifndef _FRAME_H
#define _FRAME_H
#include <Arduino.h>
#include <vector>

/** payload format
* | topic | dataLength | Data Payload | CRC |
* | 1byte | 1byte | n byte | 2byte |
* example:
* | 0x01 | 0x0E | 14 bytes | 2byte | for SEN54
* | 0x01 | 0x10 | 16 bytes | 2byte | for SEN55
*/

#pragma pack(1)
enum topics {
TOPICS_MIN = 0x00,
TOPICS_SEN5x = 0x01,
TOPIC_MAX,
};

#pragma pack(1)
typedef struct
{
enum topics topic; /*msg type*/
uint8_t dataLength;
std::vector<uint8_t> data; /*actual data of payload*/
uint16_t crc;
} Frame_t;
String packFrame(Frame_t frame);
void deleteFrame(Frame_t *frame);
uint16_t crc16_ccitt(const uint8_t *data, size_t length);
#endif

Step 2.2: Implement sensor data structrue and adapt to the Payload Encoder

#ifndef PAYLOAD_SEN5X_H
#define PAYLOAD_SEN5X_H
#include "Frame.h"
#include "SensorPayload.h"
#include <SensirionI2CSen5x.h>

#define DEVICE_SEN54

#if defined(DEVICE_SEN54)
#elif defined(DEVICE_SEN55)
#else
#error "Please define a device in the compiler options."
#endif

class PayloadSEN5x : public SensorPayload<SensirionI2CSen5x> {
public:
PayloadSEN5x(SensirionI2CSen5x handler);
uint16_t init() override;
String toPayloadString() override;

private:
uint16_t massConcentrationPm1p0;
uint16_t massConcentrationPm2p5;
uint16_t massConcentrationPm4p0;
uint16_t massConcentrationPm10p0;
int16_t ambientHumidity;
int16_t ambientTemperature;
int16_t vocIndex;
#ifdef DEVICE_SEN55
// int16_t noxIndex; // Sensor SEN54 does not support NOx
#endif
SensirionI2CSen5x _sen5x;
};
#endif // PAYLOAD_SEN5X_H

Function toPayloadString will serialize the data into a string, and the string will be sent to the SenseCAP Indicator via Wio-E5.

Step 2.3: Compile and Upload the Code to the XIAO

#include "sensor_sen5x.h"
#include "wio_e5_at.h"
#include <Arduino.h>
#include <SensirionI2CSen5x.h>
#include <Wire.h>
SoftwareSerial serial_lora( D2, D3 );
Radio radio( serial_lora, RF_FREQUENCY, LORA_SF12, LORA_BW_125, 15, 15, 14, LORA_CRC_ON, LORA_IQ_NORMAL, LORA_PUBLIC_OFF );

SensirionI2CSen5x sen5x;
PayloadSEN5x payloadSEN5x( sen5x );

void setup() {
delay( 2000 );
wait_serial();
Serial.println( "Starting..." );

radio.begin();

Wire.begin();
payloadSEN5x.init();

Serial.println( "APP begin" );
}

void loop() {
static int count = 0;
static unsigned long task_time = 0;
static String test_string;

if ( millis() - task_time > 10000 ) {
task_time = millis();

radio.sendPayload( payloadSEN5x.toPayloadString() );

Serial.printf( "Send data %d\r\n", count++ );
}
}

Complete the Payload, now we will dive into SenseCAP Indicator to programme the payload decoder.

Step 3: Implement the Payload Decoder (SenseCAP Indicator;ESP-IDF)

The payload decoder is a function that converts the binary payload received from the LoRa transceiver into a human-readable format. The payload decoder is specific to your application and must be implemented by you. The payload decoder for this demo is provided in the demo code.

Step 3.1: Implement Your Payload Decoder

  #ifndef __SIMPLE_FRAME_H
#define __SIMPLE_FRAME_H
#include <stdint.h>
#include <stdio.h>
#include <stdlib.h>

/** payload format
* | topic | dataLength | Data Payload | CRC |
* | 1byte | 1byte | n byte | 2byte |
* example:
* | 0x01 | 0x0E | 14 bytes | 2byte | for SEN54
* | 0x01 | 0x10 | 16 bytes | 2byte | for SEN55
*/

#pragma pack(1)
enum topics {
TOPICS_MIN = 0x00,
TOPICS_SEN5x = 0x01,
TOPIC_MAX,
};
typedef struct
{
enum topics topic; /*msg type or DataId*/
uint8_t dataLength;
uint8_t *data; /*actual data of payload*/
uint16_t crc;
} Frame_t;
Frame_t *parsePayload( uint8_t *payload, uint8_t length );
void deleteFrame( Frame_t *frame );
uint16_t crc16_ccitt( const uint8_t *data, size_t length );
#endif

Step 3.2: Implement Sensor Data Structure

  #ifndef PAYLOAD_SEN5X_H
#define PAYLOAD_SEN5X_H
#include "SensorPayload.h"

#define DEVICE_SEN54

#if defined( DEVICE_SEN54 )
#elif defined( DEVICE_SEN55 )
#else
#error "Please define a device in the compiler options."
#endif
#pragma pack(push, 1)
typedef union {
struct
{
uint16_t massConcentrationPm1p0;
uint16_t massConcentrationPm2p5;
uint16_t massConcentrationPm4p0;
uint16_t massConcentrationPm10p0;
int16_t ambientHumidity;
int16_t ambientTemperature;
int16_t vocIndex;
#ifdef DEVICE_SEN55
int16_t noxIndex;
#endif
};

#ifdef DEVICE_SEN55
int16_t data[8];
#else
int16_t data[7];
#endif
} SEN5xData_t;
#pragma pack(pop)
void phraseSEN5xData( uint8_t *data_arry, SEN5xData_t *SEN5x );
void prinSEN5xData( const SEN5xData_t *SEN5x );
#endif // PAYLOAD_SEN5X_H

Step 3.3: Configure the LoRa

Set up LoRa Parameters

set the necessary LoRa parameters such as frequency, spreading factor, and bandwidth. These settings must match between the two LoRa channel for successful communication.

#define RF_FREQUENCY               868000000 // Hz
#define LORA_BANDWIDTH 0 // [0: 125 kHz, 1: 250 kHz, 2: 500 kHz, 3: Reserved]
#define LORA_SPREADING_FACTOR 12 // [SF7..SF12]
#define LORA_CODINGRATE 1 // [1: 4/5, 2: 4/6, 3: 4/7, 4: 4/8]
#define LORA_PREAMBLE_LENGTH 15 // Same for Tx and Rx
#define LORA_SYMBOL_TIMEOUT 5 // Symbols
#define LORA_FIX_LENGTH_PAYLOAD_ON false
#define LORA_IQ_INVERSION_ON false
Set up the LoRa Transceiver Reciever
void OnRxDone( uint8_t *payload, uint16_t size, int16_t rssi, int8_t snr )
{
SEN5xData_t sen5x_data;
Frame_t *frame = parsePayload( payload, size );
if ( frame == NULL ) {
ESP_LOGE( TAG, "parsePayload error" );
return;
}
ESP_LOGI( TAG, "frame->type: %s", dataIDToString( frame->topic ) );

switch ( frame->topic ) {
case TOPICS_SEN5x:
phraseSEN5xData( frame->data, &sen5x_data );
break;
default:
break;
}
deleteFrame( frame );
}
Initinzie the LoRa Transceiver
RadioEvents.RxDone = OnRxDone;
Radio.Init( &RadioEvents );

Radio.SetChannel( RF_FREQUENCY );

Radio.SetRxConfig( MODEM_LORA, LORA_BANDWIDTH, LORA_SPREADING_FACTOR,
LORA_CODINGRATE, 0, LORA_PREAMBLE_LENGTH,
LORA_SYMBOL_TIMEOUT, LORA_FIX_LENGTH_PAYLOAD_ON,
0, true, 0, 0, LORA_IQ_INVERSION_ON, true );
Radio.SetMaxPayloadLength( MODEM_LORA, 255 );

Radio.Rx( 0 ); // Continuous Rx

Step 3.4: Compile and Flash the Code to the SenseCAP Indicator

/**
* @source: https://github.com/Seeed-Solution/indicator_lora_commu/blob/29624d10643a41ae5e1e24124b81e93b5e3cd3bb/Indicator/main/main.c
*/
#include "bsp_board.h"
#include "esp_log.h"
#include "frame.h"
#include "radio.h"
#include "sen5x.h"

static const char *TAG = "app_main";

#define VERSION "v0.0.1"

#define SENSECAP "\n\
_____ _________ ____ \n\
/ ___/___ ____ ________ / ____/ | / __ \\ \n\
\\__ \\/ _ \\/ __ \\/ ___/ _ \\/ / / /| | / /_/ / \n\
___/ / __/ / / (__ ) __/ /___/ ___ |/ ____/ \n\
/____/\\___/_/ /_/____/\\___/\\____/_/ |_/_/ \n\
--------------------------------------------------------\n\
Version: %s %s %s\n\
--------------------------------------------------------\n\
"

#define RF_FREQUENCY 868000000 // Hz
#define LORA_BANDWIDTH 0 // [0: 125 kHz, 1: 250 kHz, 2: 500 kHz, 3: Reserved]
#define LORA_SPREADING_FACTOR 12 // [SF7..SF12]
#define LORA_CODINGRATE 1 // [1: 4/5, 2: 4/6, 3: 4/7, 4: 4/8]
#define LORA_PREAMBLE_LENGTH 15 // Same for Tx and Rx
#define LORA_SYMBOL_TIMEOUT 5 // Symbols
#define LORA_FIX_LENGTH_PAYLOAD_ON false
#define LORA_IQ_INVERSION_ON false

static RadioEvents_t RadioEvents;

SEN5xData_t sen5x_data;

void OnRxDone( uint8_t *payload, uint16_t size, int16_t rssi, int8_t snr ) {
int i = 0;
ESP_LOGI( TAG, "rssi:%d dBm, snr:%d dB, len:%d, payload:", rssi, snr, size );
for ( i = 0; i < size; i++ ) {
printf( "0x%x ", payload[i] );
}
printf( "\n" );

Frame_t *frame = parsePayload( payload, size );
if ( frame == NULL ) {
ESP_LOGE( TAG, "parsePayload error" );
return;
}
ESP_LOGI( TAG, "frame->type: %s", dataIDToString( frame->topic ) );

switch ( frame->topic ) {
case TOPICS_SEN5x:
phraseSEN5xData( frame->data, &sen5x_data );
prinSEN5xData( &sen5x_data );
break;

default:
break;
}

deleteFrame( frame );
}

void app_main( void ) {
ESP_LOGI( "", SENSECAP, VERSION, __DATE__, __TIME__ );

ESP_ERROR_CHECK( bsp_board_init() );

ESP_LOGI( TAG, "APP MAIN START" );

RadioEvents.RxDone = OnRxDone;
Radio.Init( &RadioEvents );

Radio.SetChannel( RF_FREQUENCY );

Radio.SetRxConfig( MODEM_LORA, LORA_BANDWIDTH, LORA_SPREADING_FACTOR,
LORA_CODINGRATE, 0, LORA_PREAMBLE_LENGTH,
LORA_SYMBOL_TIMEOUT, LORA_FIX_LENGTH_PAYLOAD_ON,
0, true, 0, 0, LORA_IQ_INVERSION_ON, true );
Radio.SetMaxPayloadLength( MODEM_LORA, 255 );

Radio.Rx( 0 ); // Continuous Rx

while ( 1 ) {
vTaskDelay( pdMS_TO_TICKS( 10000 ) );
}
}

Step 4: Test the Communication

Power on both SenseCAP Indicator boards and open the serial monitor. You should see messages being sent and received between the two boards. Congratulations! You have successfully set up LoRa communication using the SenseCAP Indicator.

Serial Monitor of XIAO
String: 76,80,81,81,5389,5990,980
0 4C 0 50 0 51 0 51 15 D 17 66 3 D4
CRC: 629
<<<AT+TEST=TXLRPKT,"010E004C005000510051150D176603D40629"
>>>+TEST: TX DONE
+TEST: TXLRPKT

Send payload successfully
Send data 1
Serial Monitor of SenseCAP Indicator
I (95490) app_main: rssi:-22 dBm, snr:5 dB, len:18, payload:
0x1 0xe 0x0 0x4c 0x0 0x50 0x0 0x51 0x0 0x51 0x15 0xd 0x17 0x66 0x3 0xd4 0x6 0x29
W (95541) parsePayload: topic: 1
W (95541) parsePayload: dataLength: 14
W (95545) parsePayload: payload[0]: 00
W (95549) parsePayload: payload[1]: 4C
W (95554) parsePayload: payload[2]: 00
W (95558) parsePayload: payload[3]: 50
W (95563) parsePayload: payload[4]: 00
W (95567) parsePayload: payload[5]: 51
W (95572) parsePayload: payload[6]: 00
W (95576) parsePayload: payload[7]: 51
W (95580) parsePayload: payload[8]: 15
W (95585) parsePayload: payload[9]: 0D
W (95589) parsePayload: payload[10]: 17
W (95594) parsePayload: payload[11]: 66
W (95598) parsePayload: payload[12]: 03
W (95603) parsePayload: payload[13]: D4
I (95607) app_main: frame->type: SEN5X
I (95612) sen5x_: massConcentrationPm1p0: 76
I (95617) sen5x_: massConcentrationPm2p5: 80
I (95622) sen5x_: massConcentrationPm4p0: 81
I (95627) sen5x_: massConcentrationPm10p0: 81
I (95632) sen5x_: ambientHumidity: 5389
I (95636) sen5x_: ambientTemperature: 5990
I (95641) sen5x_: vocIndex: 980

Resources

name Function
Beep Control Receive the String "ON" or "OFF", can excute the corresponding functions
PingPong establishes a ping-pong communication pattern between a master and a slave device.
Multi-Sensor Data Upload XIAOS3 collects data and utilizes Wio-E5 (with LoRa module and AT Commands) to upload sensor data to the Indicator.

For more details, see the README file.

Tech Support

Need help with your SenseCAP Indicator? We're here to assist you!

If you encounter any issues or have any questions while following this tutorial, please feel free to reach out to our tech support. We are always here to help!

Visit our Seeed Official Discord Channel to ask your questions or the GitHub discussions to share all you want!

Loading Comments...