BME680 sensor connected it to a Heltec 32 LoRa V3 using VSPI for LoRaWAN OTAA communication and HSPI for the BME680
file main.ino
// Heltec LoRa V3 SX1276 LoRaWAN OTAA test BME680 sensor test
// displat BME680 data on OLED and transmit to LoRaWAN TTN V3 server
// using library https://github.com/jgromes/RadioLib/tree/master
// modified from File>Examples>RadioLib>LoRaWAN>LoRaWAN_Starter
/*
RadioLib LoRaWAN Starter Example
! Please refer to the included notes to get started !
This example joins a LoRaWAN network and will send
uplink packets. Before you start, you will have to
register your device at https://www.thethingsnetwork.org/
After your device is registered, you can run this example.
The device will join the network and start uploading data.
Running this examples REQUIRES you to check "Resets DevNonces"
on your LoRaWAN dashboard. Refer to the network's
documentation on how to do this.
For default module settings, see the wiki page
https://github.com/jgromes/RadioLib/wiki/Default-configuration
For full API reference, see the GitHub Pages
https://jgromes.github.io/RadioLib/
For LoRaWAN details, see the wiki page
https://github.com/jgromes/RadioLib/wiki/LoRaWAN
*/
#include "config.h"
#include <Adafruit_GFX.h>
#include <Adafruit_SSD1306.h>
#define SCREEN_WIDTH 128 // OLED display1 width, in pixels
#define SCREEN_HEIGHT 64 // OLED display1 height, in pixels
// Declaration for an SSD1306 display1 connected to I2C (SDA, SCL pins)
#define SCREEN_ADDRESS 0x3C //0x3D ///< See datasheet for Address; 0x3D for 128x64, 0x3C for 128x32
// use Wire1 for OLED display as BMP280 is using Wire and would conflict (only one works)
Adafruit_SSD1306 display1(SCREEN_WIDTH, SCREEN_HEIGHT, &Wire1, RST_OLED);
void setup() {
Serial.begin(115200);
delay(2000);
// setup OLED display
Wire1.begin(SDA_OLED, SCL_OLED);
// SSD1306_SWITCHCAPVCC = generate display1 voltage from 3.3V internally
if (!display1.begin(SSD1306_SWITCHCAPVCC, SCREEN_ADDRESS)) {
Serial.println(F("SSD1306 allocation failed"));
for (;;)
; // Don't proceed, loop forever
}
Serial.println(F("SSD1306 allocation OK"));
delay(2000); // Pause for 2 seconds
display1.clearDisplay();
display1.setTextSize(2); // Normal 1:1 pixel scale
display1.setTextColor(SSD1306_WHITE); // Draw white text
display1.setCursor(0, 0); // Start at top-left corner
display1.println(F(" EPAS\n LoRaWAN\n node"));
display1.display();
delay(2000);
setupBME680(); // setup BME680 sensor
Serial.println(F("\n\nHeltec LoRa V3 SX1262 Setup ... "));
Serial.println(F("Initialise the radio")); // setup LoRaWAN
int16_t state = radio.begin();
debug(state != RADIOLIB_ERR_NONE, F("Initialise radio failed"), state, true);
// Setup the OTAA session information
state = node.beginOTAA(joinEUI, devEUI, nwkKey, appKey);
debug(state != RADIOLIB_ERR_NONE, F("Initialise node failed"), state, true);
Serial.println(F("Join ('login') the LoRaWAN Network"));
state = node.activateOTAA();
debug(state != RADIOLIB_LORAWAN_NEW_SESSION, F("Join failed"), state, true);
Serial.println(F("Ready!\n"));
}
extern float temperature, pressure, humidity, gas; // BME680 sensor data
void loop() {
Serial.print(F("Sending uplink "));
loopBME680(); // read BME680 sensor data
display1.clearDisplay(); // Dsiplay information
display1.setTextSize(1);
display1.setCursor(0, 0);
display1.println("LoRaWAN BMP280\n");
//display1.setCursor(0, 20);
display1.printf("Temperature %.2f\n", temperature);
// display1.setCursor(0, 40);
display1.printf("Pressure %.2f\n", pressure);
display1.printf("Pressure %.2f\n", humidity);
display1.printf("gas %.2f", gas);
display1.display();
// This is the place to gather the sensor inputs - Build payload byte array
static uint8_t uplinkPayload[8] = { 0x61, 0x2f, 0xea, 0x03, 0xac, 0x12, 0x9d, 0x08 };
Serial.printf("temp = %f, pressure = %f humidity = %f gas=%f\n", temperature, pressure, humidity, gas);
int temp = (int)((temperature + 100) * 100.0), press = (int)(pressure), hum = humidity * 100, igas = (int)(gas * 100);
uplinkPayload[0] = (temp & 0xff);
uplinkPayload[1] = (temp >> 8) & 0xff;
uplinkPayload[2] = press & 0xFF;
uplinkPayload[3] = (press >> 8) & 0xff;
uplinkPayload[4] = hum & 0xFF;
uplinkPayload[5] = (hum >> 8) & 0xff;
uplinkPayload[6] = igas & 0xFF;
uplinkPayload[7] = (igas >> 8) & 0xff;
Serial.print("LoRaWAN transmission ");
for (int i = 0; i < 8; i++) // ,,,, added print
Serial.printf("%x ", uplinkPayload[i]);
Serial.println();
// Perform an uplink
//int16_t state = node.sendReceive(uplinkPayload, sizeof(uplinkPayload));
// perform uplink passing array for received data
uint8_t dataDown[8] = { 0 };
size_t lenDown = 0;
int16_t state = node.sendReceive(uplinkPayload, sizeof(uplinkPayload), 1, dataDown, &lenDown);
debug(state < RADIOLIB_ERR_NONE, F("Error in sendReceive"), state, false);
// Check if a downlink was received
// (state 0 = no downlink, state 1/2 = downlink in window Rx1/Rx2)
if (state > 0) {
Serial.print(F("Received a downlink "));
Serial.printf("lenDown %d data ", lenDown); // print downlink data
for (int i = 0; i < lenDown; i++)
Serial.printf("%x ", dataDown[i]);
Serial.println();
} else {
Serial.println(F("No downlink received"));
}
Serial.print(F("Next uplink in "));
Serial.print(uplinkIntervalSeconds);
Serial.println(F(" seconds\n"));
// Wait until next uplink - observing legal & TTN FUP constraints
delay(uplinkIntervalSeconds * 1000UL); // delay needs milli-seconds
}
file config.h (in same directory as above main.ino)
#ifndef _RADIOLIB_EX_LORAWAN_CONFIG_H
#define _RADIOLIB_EX_LORAWAN_CONFIG_H
#include <RadioLib.h>
// first you have to set your radio model and pin configuration
// this is provided just as a default example
//SX1276 radio = new Module(10, 2, 9, 3);
// Heltec LoRa V3 SX1262 has the following connections:
// NSS pin: 8
// DIO1 pin: 14
// NRST pin: 12
// BUSY pin: 13
SX1262 radio = new Module(8, 14, 12, 13);
// if you have RadioBoards (https://github.com/radiolib-org/RadioBoards)
// and are using one of the supported boards, you can do the following:
/*
#define RADIO_BOARD_AUTO
#include <RadioBoards.h>
Radio radio = new RadioModule();
*/
// how often to send an uplink - consider legal & FUP constraints - see notes
const uint32_t uplinkIntervalSeconds = 1UL * 60UL; // minutes x seconds
// joinEUI - previous versions of LoRaWAN called this AppEUI
// for development purposes you can use all zeros - see wiki for details
#define RADIOLIB_LORAWAN_JOIN_EUI 0x0000000000000000
// the Device EUI & two keys can be generated on the TTN console
#ifndef RADIOLIB_LORAWAN_DEV_EUI // Replace with your Device EUI
#define RADIOLIB_LORAWAN_DEV_EUI 0xZZZZZZZZZZ
#endif
#ifndef RADIOLIB_LORAWAN_APP_KEY // Replace with your App Key
#define RADIOLIB_LORAWAN_APP_KEY 0x0E-------------------------------------
#endif
#ifndef RADIOLIB_LORAWAN_NWK_KEY // Put your Nwk Key here
#define RADIOLIB_LORAWAN_NWK_KEY 0x86 ------------------------------------------
#endif
// for the curious, the #ifndef blocks allow for automated testing &/or you can
// put your EUI & keys in to your platformio.ini - see wiki for more tips
// regional choices: EU868, US915, AU915, AS923, AS923_2, AS923_3, AS923_4, IN865, KR920, CN470
const LoRaWANBand_t Region = EU868;
// subband choice: for US915/AU915 set to 2, for CN470 set to 1, otherwise leave on 0
const uint8_t subBand = 0;
// ============================================================================
// Below is to support the sketch - only make changes if the notes say so ...
// copy over the EUI's & keys in to the something that will not compile if incorrectly formatted
uint64_t joinEUI = RADIOLIB_LORAWAN_JOIN_EUI;
uint64_t devEUI = RADIOLIB_LORAWAN_DEV_EUI;
uint8_t appKey[] = { RADIOLIB_LORAWAN_APP_KEY };
uint8_t nwkKey[] = { RADIOLIB_LORAWAN_NWK_KEY };
// create the LoRaWAN node
LoRaWANNode node(&radio, &Region, subBand);
// result code to text - these are error codes that can be raised when using LoRaWAN
// however, RadioLib has many more - see https://jgromes.github.io/RadioLib/group__status__codes.html for a complete list
String stateDecode(const int16_t result) {
switch (result) {
case RADIOLIB_ERR_NONE:
return "ERR_NONE";
case RADIOLIB_ERR_CHIP_NOT_FOUND:
return "ERR_CHIP_NOT_FOUND";
case RADIOLIB_ERR_PACKET_TOO_LONG:
return "ERR_PACKET_TOO_LONG";
case RADIOLIB_ERR_RX_TIMEOUT:
return "ERR_RX_TIMEOUT";
case RADIOLIB_ERR_MIC_MISMATCH:
return "ERR_MIC_MISMATCH";
case RADIOLIB_ERR_INVALID_BANDWIDTH:
return "ERR_INVALID_BANDWIDTH";
case RADIOLIB_ERR_INVALID_SPREADING_FACTOR:
return "ERR_INVALID_SPREADING_FACTOR";
case RADIOLIB_ERR_INVALID_CODING_RATE:
return "ERR_INVALID_CODING_RATE";
case RADIOLIB_ERR_INVALID_FREQUENCY:
return "ERR_INVALID_FREQUENCY";
case RADIOLIB_ERR_INVALID_OUTPUT_POWER:
return "ERR_INVALID_OUTPUT_POWER";
case RADIOLIB_ERR_NETWORK_NOT_JOINED:
return "RADIOLIB_ERR_NETWORK_NOT_JOINED";
case RADIOLIB_ERR_DOWNLINK_MALFORMED:
return "RADIOLIB_ERR_DOWNLINK_MALFORMED";
case RADIOLIB_ERR_INVALID_REVISION:
return "RADIOLIB_ERR_INVALID_REVISION";
case RADIOLIB_ERR_INVALID_PORT:
return "RADIOLIB_ERR_INVALID_PORT";
case RADIOLIB_ERR_NO_RX_WINDOW:
return "RADIOLIB_ERR_NO_RX_WINDOW";
case RADIOLIB_ERR_INVALID_CID:
return "RADIOLIB_ERR_INVALID_CID";
case RADIOLIB_ERR_UPLINK_UNAVAILABLE:
return "RADIOLIB_ERR_UPLINK_UNAVAILABLE";
case RADIOLIB_ERR_COMMAND_QUEUE_FULL:
return "RADIOLIB_ERR_COMMAND_QUEUE_FULL";
case RADIOLIB_ERR_COMMAND_QUEUE_ITEM_NOT_FOUND:
return "RADIOLIB_ERR_COMMAND_QUEUE_ITEM_NOT_FOUND";
case RADIOLIB_ERR_JOIN_NONCE_INVALID:
return "RADIOLIB_ERR_JOIN_NONCE_INVALID";
case RADIOLIB_ERR_DWELL_TIME_EXCEEDED:
return "RADIOLIB_ERR_DWELL_TIME_EXCEEDED";
case RADIOLIB_ERR_CHECKSUM_MISMATCH:
return "RADIOLIB_ERR_CHECKSUM_MISMATCH";
case RADIOLIB_ERR_NO_JOIN_ACCEPT:
return "RADIOLIB_ERR_NO_JOIN_ACCEPT";
case RADIOLIB_LORAWAN_SESSION_RESTORED:
return "RADIOLIB_LORAWAN_SESSION_RESTORED";
case RADIOLIB_LORAWAN_NEW_SESSION:
return "RADIOLIB_LORAWAN_NEW_SESSION";
case RADIOLIB_ERR_NONCES_DISCARDED:
return "RADIOLIB_ERR_NONCES_DISCARDED";
case RADIOLIB_ERR_SESSION_DISCARDED:
return "RADIOLIB_ERR_SESSION_DISCARDED";
}
return "See https://jgromes.github.io/RadioLib/group__status__codes.html";
}
// helper function to display any issues
void debug(bool failed, const __FlashStringHelper* message, int state, bool halt) {
if(failed) {
Serial.print(message);
Serial.print(" - ");
Serial.print(stateDecode(state));
Serial.print(" (");
Serial.print(state);
Serial.println(")");
while(halt) { delay(1); }
}
}
// helper function to display a byte array
void arrayDump(uint8_t *buffer, uint16_t len) {
for(uint16_t c = 0; c < len; c++) {
char b = buffer[c];
if(b < 0x10) { Serial.print('0'); }
Serial.print(b, HEX);
}
Serial.println();
}
#endif
file bme680.ino (in same directory as above main.ino)
// Heltec 32 LoRa V3 BME280 test using HSPI
// modified si this can be called from another file
// e.g. setup() become setupBME680() and loop() becomes loopBME680()
// using library https://github.com/adafruit/Adafruit_BME680
// File>Examples>Adafruit BME680 Library>bme680test
/***************************************************************************
This is a library for the BME680 gas, humidity, temperature & pressure sensor
Designed specifically to work with the Adafruit BME680 Breakout
----> http://www.adafruit.com/products/3660
These sensors use I2C or SPI to communicate, 2 or 4 pins are required
to interface.
Adafruit invests time and resources providing this open source code,
please support Adafruit and open-source hardware by purchasing products
from Adafruit!
Written by Limor Fried & Kevin Townsend for Adafruit Industries.
BSD license, all text above must be included in any redistribution
***************************************************************************/
//#include <Wire.h>
#include <SPI.h>
#include <Adafruit_Sensor.h>
#include "Adafruit_BME680.h"
SPIClass hspi(HSPI); // HSPI object used by BME680
// Heltec 32 LoRa V3 HSPI
#define BME_SCK 40
#define BME_MISO 42
#define BME_MOSI 39
#define BME_CS 41
#define SEALEVELPRESSURE_HPA (1013.25)
//Adafruit_BME680 bme(&Wire); // I2C
//Adafruit_BME680 bme(&Wire1); // example of I2C on another bus
Adafruit_BME680 bme(BME_CS, &hspi); // hardware SPI
//Adafruit_BME680 bme(BME_CS, BME_MOSI, BME_MISO, BME_SCK);
void setupBME680() {
Serial.begin(115200);
delay(2000);
while (!Serial)
;
Serial.println(F("BME680 test"));
hspi.begin(BME_SCK, BME_MISO, BME_MOSI, BME_CS);
if (!bme.begin()) {
Serial.println("Could not find a valid BME680 sensor, check wiring!");
while (1)
;
}
// Set up oversampling and filter initialization
bme.setTemperatureOversampling(BME680_OS_8X);
bme.setHumidityOversampling(BME680_OS_2X);
bme.setPressureOversampling(BME680_OS_4X);
bme.setIIRFilterSize(BME680_FILTER_SIZE_3);
bme.setGasHeater(320, 150); // 320*C for 150 ms
}
// global variables enables extern access from other files
float temperature, pressure, humidity, gas;
void loopBME680() {
if (!bme.performReading()) {
Serial.println("Failed to perform reading :(");
return;
}
Serial.print("Temperature = ");
Serial.print(temperature=bme.temperature);
Serial.print(" *C ");
Serial.print("Pressure = ");
Serial.print(pressure=bme.pressure / 100.0);
Serial.print(" hPa ");
Serial.print("Humidity = ");
Serial.print(humidity=bme.humidity);
Serial.println(" %");
Serial.print("Gas = ");
Serial.print(gas=bme.gas_resistance / 1000.0);
Serial.print(" KOhms ");
Serial.print("Approx. Altitude = ");
Serial.print(bme.readAltitude(SEALEVELPRESSURE_HPA));
Serial.println(" m");
Serial.println();
delay(2000);
}
Heltec V3 reads BME680 sensor data, displays it on OLED and transmits it to TTN V3 server over LoRaWAN
serial monitor output
SSD1306 allocation OK
BME680 test
Heltec LoRa V3 SX1262 Setup ...
Initialise the radio
Join ('login') the LoRaWAN Network
Ready!
Sending uplink Temperature = 21.67 *C Pressure = 1001.47 hPa Humidity = 46.31 %
Gas = 40.04 KOhms Approx. Altitude = 98.37 m
temp = 21.670380, pressure = 1001.469971 humidity = 46.307465 gas=40.037998
LoRaWAN transmission 87 2f e9 3 16 12 a3 f
Received a downlink lenDown 0 data
Next uplink in 60 seconds
Sending uplink Temperature = 21.47 *C Pressure = 1001.48 hPa Humidity = 46.55 %
Gas = 28.70 KOhms Approx. Altitude = 98.46 m
temp = 21.466648, pressure = 1001.479980 humidity = 46.551521 gas=28.701000
LoRaWAN transmission 72 2f e9 3 2f 12 36 b
No downlink received
Next uplink in 60 seconds
Sending uplink Temperature = 21.36 *C Pressure = 1001.48 hPa Humidity = 46.49 %
Gas = 26.07 KOhms Approx. Altitude = 98.54 m
temp = 21.360659, pressure = 1001.479980 humidity = 46.490311 gas=26.066999
LoRaWAN transmission 68 2f e9 3 29 12 2e a
Received a downlink lenDown 5 data 99 88 77 66 55
Next uplink in 60 seconds
TTNV3 payload formatter
function decodeUplink(input) {
var data = {};
data.temperature = ((input.bytes[1] <<8 | input.bytes[0])/100.0)-100.0;
data.pressure = (input.bytes[3] <<8 | input.bytes[2]);
data.humidity = (input.bytes[5] <<8 | input.bytes[4])/100.0;
data.gas = (input.bytes[7] <<8 | input.bytes[6])/100.0;
return {
data: data
};
}
TTN V2 applications live data display
test setup