This has been posted as a response to several other threads, but I thought it would be easier to find the example sketch along with TTN JavaScirpt payload decoder if I posted them to a new thread dedicated to this purpose. Each of my posts will include the model of the Adafruit breakout sensor board that the sketch has been functional with. In each case, I am posting them only once I have a sketch that writes debugging information to serial AND sends the data over LoRa with decoded data confirmed within the TTN Console.
Heltex LoRa32 V3 with I2C Adafruit boards
Adafruit VCNL4040
Here is the funtional sketch for Arduino IDE
/**
* This sketch was written starting with the "LoRAWAN_TTN" example
* from the "Heltec_ESP32_LoRa_V3" Library. It also uses portions of the
* "vcnl4040_test" example from the "Adafruit VNCL4040 Library". Modifications
* have been made to both contributions by Brandon J. Winters.
*
*
* FOR THIS EXAMPLE TO WORK, YOU MUST INSTALL THE "LoRaWAN_ESP32" LIBRARY USING
* THE LIBRARY MANAGER IN THE ARDUINO IDE.
*
* This code will send a two-byte LoRaWAN message every 15 minutes. The first
* byte is a simple 8-bit counter, the second is the ESP32 chip temperature
* directly after waking up from its 15 minute sleep in degrees celsius + 100.
*
* If your NVS partition does not have stored TTN / LoRaWAN provisioning
* information in it yet, you will be prompted for them on the serial port and
* they will be stored for subsequent use.
*
* See https://github.com/ropg/LoRaWAN_ESP32
*/
// Pause between sends in seconds, so this is every 15 minutes. (Delay will be
// longer if regulatory or TTN Fair Use Policy requires it.)
#define MINIMUM_DELAY 300
#include <heltec_unofficial.h>
#include <LoRaWAN_ESP32.h>
#include <Wire.h>
#include <SPI.h>
#include <Adafruit_Sensor.h>
#include <Adafruit_VCNL4040.h>
LoRaWANNode* node;
//create VCNL4040 Proximity sensor and give it the nickname "vcnl"
Adafruit_VCNL4040 vcnl = Adafruit_VCNL4040();
//Setup sequatial counting for data transmissions
RTC_DATA_ATTR uint8_t count = 0; //for LoRa transmissions
RTC_DATA_ATTR uint8_t trans = 0; //for serial debugging
void setup() {
//Not sure about the ordering of the next few things, but this works. It starts the heltec board
//along with serial debugging and the VCNL4040
Wire1.begin(41, 42); //SDA is pin 41 and SCL is pin 42 for second I2C bus
Serial.begin(115200); //begin serial communication at the defined baud rate
vcnl.begin(0x60, &Wire1); //begin vcnl sensor at 0x60 adress on Wire1
Serial.println("Adafruit VCNL4040 Config demo");
if (!vcnl.begin()) {
Serial.println("Couldn't find VCNL4040 chip");
while (1);
}
Serial.println("Found VCNL4040 chip");
// Obtain directly after deep sleep
// May or may not reflect room temperature, some correction needed
float temp = heltec_temperature();
Serial.println();
Serial.println(F("-------------------------"));
Serial.print("Consecutive Transmissions: ");
Serial.print(trans++);
Serial.println();
Serial.printf("Heltec Temperature: %.1f °C\n", temp);
//vcnl4040.setProximityLEDCurrent(VCNL4040_LED_CURRENT_200MA);
Serial.print("Proximity LED current set to: ");
switch(vcnl.getProximityLEDCurrent()) {
case VCNL4040_LED_CURRENT_50MA: Serial.println("50 mA"); break;
case VCNL4040_LED_CURRENT_75MA: Serial.println("75 mA"); break;
case VCNL4040_LED_CURRENT_100MA: Serial.println("100 mA"); break;
case VCNL4040_LED_CURRENT_120MA: Serial.println("120 mA"); break;
case VCNL4040_LED_CURRENT_140MA: Serial.println("140 mA"); break;
case VCNL4040_LED_CURRENT_160MA: Serial.println("160 mA"); break;
case VCNL4040_LED_CURRENT_180MA: Serial.println("180 mA"); break;
case VCNL4040_LED_CURRENT_200MA: Serial.println("200 mA"); break;
}
//vcnl4040.setProximityLEDDutyCycle(VCNL4040_LED_DUTY_1_40);
Serial.print("Proximity LED duty cycle set to: ");
switch(vcnl.getProximityLEDDutyCycle()) {
case VCNL4040_LED_DUTY_1_40: Serial.println("1/40"); break;
case VCNL4040_LED_DUTY_1_80: Serial.println("1/80"); break;
case VCNL4040_LED_DUTY_1_160: Serial.println("1/160"); break;
case VCNL4040_LED_DUTY_1_320: Serial.println("1/320"); break;
}
//vcnl4040.setAmbientIntegrationTime(VCNL4040_AMBIENT_INTEGRATION_TIME_80MS);
Serial.print("Ambient light integration time set to: ");
switch(vcnl.getAmbientIntegrationTime()) {
case VCNL4040_AMBIENT_INTEGRATION_TIME_80MS: Serial.println("80 ms"); break;
case VCNL4040_AMBIENT_INTEGRATION_TIME_160MS: Serial.println("160 ms"); break;
case VCNL4040_AMBIENT_INTEGRATION_TIME_320MS: Serial.println("320 ms"); break;
case VCNL4040_AMBIENT_INTEGRATION_TIME_640MS: Serial.println("640 ms"); break;
}
//vcnl4040.setProximityIntegrationTime(VCNL4040_PROXIMITY_INTEGRATION_TIME_8T);
Serial.print("Proximity integration time set to: ");
switch(vcnl.getProximityIntegrationTime()) {
case VCNL4040_PROXIMITY_INTEGRATION_TIME_1T: Serial.println("1T"); break;
case VCNL4040_PROXIMITY_INTEGRATION_TIME_1_5T: Serial.println("1.5T"); break;
case VCNL4040_PROXIMITY_INTEGRATION_TIME_2T: Serial.println("2T"); break;
case VCNL4040_PROXIMITY_INTEGRATION_TIME_2_5T: Serial.println("2.5T"); break;
case VCNL4040_PROXIMITY_INTEGRATION_TIME_3T: Serial.println("3T"); break;
case VCNL4040_PROXIMITY_INTEGRATION_TIME_3_5T: Serial.println("3.5T"); break;
case VCNL4040_PROXIMITY_INTEGRATION_TIME_4T: Serial.println("4T"); break;
case VCNL4040_PROXIMITY_INTEGRATION_TIME_8T: Serial.println("8T"); break;
}
//vcnl4040.setProximityHighResolution(false);
Serial.print("Proximity measurement high resolution? ");
Serial.println(vcnl.getProximityHighResolution() ? "True" : "False");
Serial.println("");
Serial.print("Proximity:"); Serial.println(vcnl.getProximity());
Serial.print("Ambient light:"); Serial.println(vcnl.getLux());
Serial.print("Raw white light:"); Serial.println(vcnl.getWhiteLight());
Serial.println("--------------------------");
//This seems to be needed to initialize the radio,
//but does not work when added at start of void setup
heltec_setup();
// initialize radio
Serial.println("Radio init");
int16_t state = radio.begin();
if (state != RADIOLIB_ERR_NONE) {
Serial.println("Radio did not initialize. We'll try again later.");
goToSleep();
}
node = persist.manage(&radio);
if (!node->isActivated()) {
Serial.println("Could not join network. We'll try again later.");
goToSleep();
}
// If we're still here, it means we joined, and we can send something
// Manages uplink intervals to the TTN Fair Use Policy
node->setDutyCycle(true, 1250);
//create LoRa data uplink structure and packet
uint8_t uplinkData[4];
uplinkData[0] = (count++); //sequantially adds one intiger to the value 'count' for each transmission, resets when RST button pushed
uplinkData[1] = (vcnl.getProximity()); //no math done here
uplinkData[2] = (vcnl.getLux()); //no math done here
uplinkData[3] = (vcnl.getWhiteLight()); //no math done here
//define LoRa downlink size
uint8_t downlinkData[256];
size_t lenDown = sizeof(downlinkData);
//Send of the LoRa data and establish wait for downlink
state = node->sendReceive(uplinkData, sizeof(uplinkData), 1, downlinkData, &lenDown);
if(state == RADIOLIB_ERR_NONE) {
Serial.println("Message sent, no downlink received.");
} else if (state > 0) {
Serial.println("Message sent, downlink received.");
} else {
Serial.printf("sendReceive returned error %d, we'll try again later.\n", state);
}
goToSleep(); // Does not return, program starts over next round
}
void loop() {
// This is never called. There is no repetition: we always go back to
// deep sleep one way or the other at the end of setup()
}
void goToSleep() {
Serial.println("Going to deep sleep now");
// allows recall of the session after deepsleep
persist.saveSession(node);
// Calculate minimum duty cycle delay (per FUP & law!)
uint32_t interval = node->timeUntilUplink();
// And then pick it or our MINIMUM_DELAY, whichever is greater
uint32_t delayMs = max(interval, (uint32_t)MINIMUM_DELAY * 1000);
Serial.printf("Next TX in %i s\n", delayMs/1000);
Serial.println(F("*****-----*-----*-----*-----*-----*-----*-----*-----*-----*-----*-----*****"));
Serial.println();
delay(100); // So message prints
// and off to bed we go
heltec_deep_sleep(delayMs/1000);
}
Here is the functional payload decoder in JavaScript for TheThingNetwork (TTN)
function Decoder(bytes, port) {
var decoded = {};
// Decode bytes to int
var testShort0 = (bytes[0]);
var testShort1 = ((1 / bytes[1]) * 20);
var testShort2 = (bytes[2]);
var testShort3 = (bytes[3]);
// Decode int
decoded.count = testShort0;
decoded.proximity = testShort1;
decoded.lux = testShort2;
decoded.whiteLight = testShort3;
return decoded;
}
The math done on the proximity is included only to VERY rughly correlate to the distance in cm.
Adafruit CCS811
Here is the functional sketch for Arduino IDE
/**
* This sketch was written starting with the "LoRAWAN_TTN" example
* from the "Heltec_ESP32_LoRa_V3" Library. It also uses portions of the
* "CCS811_test" example from the "Adafruit CCS811 Library". Modifications
* have been made to both contributions by Brandon J. Winters.
*
*
* FOR THIS EXAMPLE TO WORK, YOU MUST INSTALL THE "LoRaWAN_ESP32" LIBRARY USING
* THE LIBRARY MANAGER IN THE ARDUINO IDE.
*
* This code will send a two-byte LoRaWAN message every 15 minutes. The first
* byte is a simple 8-bit counter, the second is the ESP32 chip temperature
* directly after waking up from its 15 minute sleep in degrees celsius + 100.
*
* If your NVS partition does not have stored TTN / LoRaWAN provisioning
* information in it yet, you will be prompted for them on the serial port and
* they will be stored for subsequent use.
*
* See https://github.com/ropg/LoRaWAN_ESP32
*/
// Pause between sends in seconds, so this is every 15 minutes. (Delay will be
// longer if regulatory or TTN Fair Use Policy requires it.)
#define MINIMUM_DELAY 300
#include <heltec_unofficial.h>
#include <LoRaWAN_ESP32.h>
#include <Wire.h>
#include <SPI.h>
#include <Adafruit_Sensor.h>
#include <Adafruit_CCS811.h>
LoRaWANNode* node;
extern TwoWire Wire1; //Setup Wire1 as second I2C bus
//create CCS811 sensor and give it the nickname "ccs"
Adafruit_CCS811 ccs;
//Setup sequatial counting for data transmissions
RTC_DATA_ATTR uint8_t count = 0; //for LoRa transmissions
RTC_DATA_ATTR uint8_t trans = 0; //for serial debugging
void setup() {
// put your setup code here, to run once:
Wire1.begin(41, 42); //SDA is pin 41 and SCL is pin 42 for second I2C bus
Serial.begin(115200); //begin serial communication at the defined baud rate
ccs.begin(0x5A, &Wire1); //start the ccs sensor at the indicated address on Wire1
Serial.println("CCS811 Test");
if(!ccs.begin()){
Serial.println("Failed to start sensor! Please check your wiring.");
while(1);
}
// Wait for the sensor to be ready
while(!ccs.available());
Serial.println("CCS811 found after wait");
// Obtain directly after deep sleep
// May or may not reflect room temperature, some correction needed
float temp = heltec_temperature();
Serial.println();
Serial.println(F("-------------------------"));
Serial.print("Consecutive Transmissions: ");
Serial.print(trans++);
Serial.println();
Serial.printf("Heltec Temperature: %.1f °C\n", temp);
if(ccs.available()){
if(!ccs.readData()){
Serial.print("CO2: ");
Serial.print(ccs.geteCO2());
Serial.print(" ppm");
Serial.println();
Serial.print("TVOC: ");
Serial.print(ccs.getTVOC());
Serial.print(" TVOC Units");
Serial.println();
Serial.println(F("-------------------------"));
}
else{
Serial.println("ERROR!");
while(1);
}
}
heltec_setup();
// initialize radio
Serial.println("Radio init");
int16_t state = radio.begin();
if (state != RADIOLIB_ERR_NONE) {
Serial.println("Radio did not initialize. We'll try again later.");
goToSleep();
}
node = persist.manage(&radio);
if (!node->isActivated()) {
Serial.println("Could not join network. We'll try again later.");
goToSleep();
}
// If we're still here, it means we joined, and we can send something
// Manages uplink intervals to the TTN Fair Use Policy
node->setDutyCycle(true, 1250);
//create LoRa data uplink structure and packet
uint8_t uplinkData[4];
uplinkData[0] = (count++); //sequantially adds one intiger to the value 'count' for each transmission, resets when RST button pushed
uplinkData[1] = (ccs.geteCO2() / 5); //max value to multiply by and keep under cap of 256 for uint8_t value
uplinkData[2] = (ccs.getTVOC() * 10); //max value to multiply by and keep under cap of 256 for uint8_t value
uplinkData[3] = (ccs.calculateTemperature() + 273.15);
//define LoRa downlink size
uint8_t downlinkData[256];
size_t lenDown = sizeof(downlinkData);
//Send of the LoRa data and establish wait for downlink
state = node->sendReceive(uplinkData, sizeof(uplinkData), 1, downlinkData, &lenDown);
if(state == RADIOLIB_ERR_NONE) {
Serial.println("Message sent, no downlink received.");
} else if (state > 0) {
Serial.println("Message sent, downlink received.");
} else {
Serial.printf("sendReceive returned error %d, we'll try again later.\n", state);
}
goToSleep(); // Does not return, program starts over next round
}
void loop()
{
// put your main code here, to run repeatedly:
}
void goToSleep() {
Serial.println("Going to deep sleep now");
// allows recall of the session after deepsleep
persist.saveSession(node);
// Calculate minimum duty cycle delay (per FUP & law!)
uint32_t interval = node->timeUntilUplink();
// And then pick it or our MINIMUM_DELAY, whichever is greater
uint32_t delayMs = max(interval, (uint32_t)MINIMUM_DELAY * 1000);
Serial.printf("Next TX in %i s\n", delayMs/1000);
Serial.println(F("*****-----*-----*-----*-----*-----*-----*-----*-----*-----*-----*-----*****"));
Serial.println();
delay(100); // So message prints
// and off to bed we go
heltec_deep_sleep(delayMs/1000);
}
Along with the TTN payload decoder in JavaScript
function Decoder(bytes, port) {
var decoded = {};
// Decode bytes to int
var testShort0 = (bytes[0]);
var testShort1 = (bytes[1] * 5);
var testShort2 = (bytes[2] / 10);
var testShort3 = (bytes[3]);
// Decode int
decoded.count = testShort0;
decoded.eCO2 = testShort1;
decoded.TVOC = testShort2;
decoded.temperature = testShort3;
return decoded;
}
Adafruit BME680
Here is the funtional sketch for Arduino IDE
/**
* This sketch was written starting with the "LoRAWAN_TTN" example
* from the "Heltec_ESP32_LoRa_V3" Library. It also uses portions of the
* "bme680test" example from the "Adafruit BME680 Library". Modifications
* have been made to both contributions by Brandon J. Winters.
*
*
* FOR THIS EXAMPLE TO WORK, YOU MUST INSTALL THE "LoRaWAN_ESP32" LIBRARY USING
* THE LIBRARY MANAGER IN THE ARDUINO IDE.
*
* This code will send a two-byte LoRaWAN message every 15 minutes. The first
* byte is a simple 8-bit counter, the second is the ESP32 chip temperature
* directly after waking up from its 15 minute sleep in degrees celsius + 100.
*
* If your NVS partition does not have stored TTN / LoRaWAN provisioning
* information in it yet, you will be prompted for them on the serial port and
* they will be stored for subsequent use.
*
* See https://github.com/ropg/LoRaWAN_ESP32
*/
// Pause between sends in seconds, so this is every 15 minutes. (Delay will be
// longer if regulatory or TTN Fair Use Policy requires it.)
#define MINIMUM_DELAY 300
#include <heltec_unofficial.h>
#include <LoRaWAN_ESP32.h>
#include <Wire.h>
#include <SPI.h>
#include <Adafruit_Sensor.h>
#include "Adafruit_BME680.h"
LoRaWANNode* node;
//create BME sensor
Adafruit_BME680 bme(&Wire1); // I2C on second Wire channel (Wire1)
#define SEALEVELPRESSURE_HPA (1013.25)
//Define the I2C pins for Wire1
#define BME_SDA 41
#define BME_SCL 42
//Setup sequatial counting for data transmissions
RTC_DATA_ATTR uint8_t count = 0; //for LoRa transmissions
RTC_DATA_ATTR uint8_t trans = 0; //for serial debugging
void setup() {
//Not sure about the ordering of the next few things, but this works. It starts the heltec board
//along with serial debugging and the BME680 board
heltec_setup();
Wire.begin();
bool wireStatus = Wire1.begin(BME_SDA, BME_SCL);
bme.begin(0x77, &Wire1);
Serial.begin(115200);
//Debugging at 115200 baud for BME680
if (!bme.begin()) {
Serial.println("Could not find a valid BME680 sensor, check wiring!");
while (1);
}else{
Serial.print("BME680 found");
}
// Set up oversampling and filter initialization for BME680
bme.setTemperatureOversampling(BME680_OS_1X);
bme.setHumidityOversampling(BME680_OS_2X);
bme.setPressureOversampling(BME680_OS_16X);
bme.setIIRFilterSize(BME680_FILTER_SIZE_3);
bme.setGasHeater(320, 150); // 320*C for 150 ms
// Obtain directly after deep sleep
// May or may not reflect room temperature, some correction needed
float temp = heltec_temperature();
Serial.println();
Serial.println(F("-------------------------"));
Serial.print("Consecutive Transmissions: ");
Serial.print(trans++);
Serial.println();
Serial.printf("Heltec Temperature: %.1f °C\n", temp);
//create floats and nicknames for the BME680 values
float bmet = bme.readTemperature();
float bmeh = bme.readHumidity();
float bmep = bme.readPressure();
float bmea = bme.readAltitude(SEALEVELPRESSURE_HPA);
float bmeg = bme.readGas();
//Perform reading of BME680 sensor to make data available for transmision
bme.performReading();
//Print BME680 data to serial for debugging, math to get values into indicated units
Serial.printf("BME Temperature: %.1f °C\n", bmet);
Serial.print("Pressure = ");
Serial.print((bme.pressure) * 0.000010197162, 4);
Serial.println(" atm");
Serial.print("Humidity = ");
Serial.print(bme.humidity);
Serial.println(" %");
Serial.print("Gas = ");
Serial.print(bme.gas_resistance / 10000.0);
Serial.println(" KOhms");
Serial.print("Approx. Altitude = ");
Serial.print(bme.readAltitude(SEALEVELPRESSURE_HPA));
Serial.println(" m");
Serial.println(F("-------------------------"));
// initialize radio
Serial.println("Radio init");
int16_t state = radio.begin();
if (state != RADIOLIB_ERR_NONE) {
Serial.println("Radio did not initialize. We'll try again later.");
goToSleep();
}
node = persist.manage(&radio);
if (!node->isActivated()) {
Serial.println("Could not join network. We'll try again later.");
goToSleep();
}
// If we're still here, it means we joined, and we can send something
// Manages uplink intervals to the TTN Fair Use Policy
node->setDutyCycle(true, 1250);
//create LoRa data uplink structure and packet
uint8_t uplinkData[7];
uplinkData[0] = (count++); //sequantially adds one intiger to the value 'count' for each transmission, resets when RST button pushed
uplinkData[1] = (temp * 4); //max value to multiply by and keep under cap of 256 for uint8_t value
uplinkData[2] = (bmet * 5); //max value to multiply by and keep under cap of 256 for uint8_t value
uplinkData[3] = (bmeh); //no math needed
uplinkData[4] = (bmep * 0.0010197162); //converts to atm * 100 to keep two decimals
uplinkData[5] = (bmea / 16.09); //converts to miles * 100 to keep decimals
uplinkData[6] = (bmeg / 10000.0); //converts to kOhms
//define LoRa downlink size
uint8_t downlinkData[256];
size_t lenDown = sizeof(downlinkData);
//Send of the LoRa data and establish wait for downlink
state = node->sendReceive(uplinkData, sizeof(uplinkData), 1, downlinkData, &lenDown);
if(state == RADIOLIB_ERR_NONE) {
Serial.println("Message sent, no downlink received.");
} else if (state > 0) {
Serial.println("Message sent, downlink received.");
} else {
Serial.printf("sendReceive returned error %d, we'll try again later.\n", state);
}
goToSleep(); // Does not return, program starts over next round
}
void loop() {
// This is never called. There is no repetition: we always go back to
// deep sleep one way or the other at the end of setup()
}
void goToSleep() {
Serial.println("Going to deep sleep now");
// allows recall of the session after deepsleep
persist.saveSession(node);
// Calculate minimum duty cycle delay (per FUP & law!)
uint32_t interval = node->timeUntilUplink();
// And then pick it or our MINIMUM_DELAY, whichever is greater
uint32_t delayMs = max(interval, (uint32_t)MINIMUM_DELAY * 1000);
Serial.printf("Next TX in %i s\n", delayMs/1000);
Serial.println(F("*****-----*-----*-----*-----*-----*-----*-----*-----*-----*-----*-----*****"));
Serial.println();
delay(100); // So message prints
// and off to bed we go
heltec_deep_sleep(delayMs/1000);
}
Along with the TTN payload decoder in JavaScript
function Decoder(bytes, port) {
var decoded = {};
// Decode bytes to int
var testShort0 = (bytes[0]);
var testShort1 = ((bytes[1] / 4) + 21);
var testShort2 = (((bytes[2] / 5) + (9/5)) + 32);
var testShort3 = (bytes[3]);
var testShort4 = (bytes[4] / 100);
var testShort5 = ((bytes[5] * 16.09) * 3.28084);
var testShort6 = (bytes[6]);
// Decode int
decoded.count = testShort0;
decoded.temperature = testShort1;
decoded.bme_temp = testShort2;
decoded.bme_hum = testShort3;
decoded.bme_pres = testShort4;
decoded.bme_alt = testShort5;
decoded.bme_gas = testShort6;
return decoded;
}
Adafruit PM25 AQI Sensor
Here is the functioning sketch for Arduino IDE, though the raw particle counts are values that are too large to transmit as it is currently written, if you have knowledge about how to change these values so they can be transmitted as uint_8 data, that would be great!
/**
* This sketch was written starting with the "LoRAWAN_TTN" example
* from the "Heltec_ESP32_LoRa_V3" Library. It also uses portions of the
* "PM_test" example from the "Adafruit PM25 AQI Sensor" Library. Modifications
* have been made to both contributions by Brandon J. Winters.
*
*
* FOR THIS EXAMPLE TO WORK, YOU MUST INSTALL THE "LoRaWAN_ESP32" LIBRARY USING
* THE LIBRARY MANAGER IN THE ARDUINO IDE.
*
* This code will send a two-byte LoRaWAN message every 15 minutes. The first
* byte is a simple 8-bit counter, the second is the ESP32 chip temperature
* directly after waking up from its 15 minute sleep in degrees celsius + 100.
*
* If your NVS partition does not have stored TTN / LoRaWAN provisioning
* information in it yet, you will be prompted for them on the serial port and
* they will be stored for subsequent use.
*
* See https://github.com/ropg/LoRaWAN_ESP32
*/
// Pause between sends in seconds, so this is every 15 minutes. (Delay will be
// longer if regulatory or TTN Fair Use Policy requires it.)
#define MINIMUM_DELAY 300
#include <heltec_unofficial.h>
#include <LoRaWAN_ESP32.h>
#include <Wire.h>
#include <SPI.h>
#include <Adafruit_Sensor.h>
#include <Adafruit_PM25AQI.h>
LoRaWANNode* node;
extern TwoWire Wire1; //Setup Wire1 as second I2C bus
//create PM25 sensor and give it the nickname "aqi"
Adafruit_PM25AQI aqi = Adafruit_PM25AQI();
//Setup sequatial counting for data transmissions
RTC_DATA_ATTR uint8_t count = 0; //for LoRa transmissions
RTC_DATA_ATTR uint8_t trans = 0; //for serial debugging
void setup() {
// put your setup code here, to run once:
Wire1.begin(41, 42); //SDA is pin 41 and SCL is pin 42 for second I2C bus
Serial.begin(115200); //begin serial communication at the defined baud rate
aqi.begin_I2C(); //start the ccs sensor at the indicated address on Wire1
Serial.println("PM25 AQI Test");
if(!aqi.begin_I2C()){
Serial.println("Failed to start sensor! Please check your wiring.");
while(1);
}
// Obtain directly after deep sleep
// May or may not reflect room temperature, some correction needed
float temp = heltec_temperature();
Serial.println();
Serial.println(F("-------------------------"));
Serial.print("Consecutive Transmissions: ");
Serial.print(trans++);
Serial.println();
Serial.printf("Heltec Temperature: %.1f °C\n", temp);
PM25_AQI_Data data;
if (! aqi.read(&data)) {
Serial.println("Could not read from AQI");
delay(500); // try again in a bit!
return;
}
Serial.println("AQI reading success");
Serial.println(F("---------------------------------------"));
Serial.println(F("Concentration Units (standard)"));
Serial.print(F("PM 1.0: ")); Serial.print(data.pm10_standard);
Serial.print(F("\t\tPM 2.5: ")); Serial.print(data.pm25_standard);
Serial.print(F("\t\tPM 10: ")); Serial.println(data.pm100_standard);
Serial.println(F("---------------------------------------"));
Serial.println(F("Concentration Units (environmental)"));
Serial.print(F("PM 1.0: ")); Serial.print(data.pm10_env);
Serial.print(F("\t\tPM 2.5: ")); Serial.print(data.pm25_env);
Serial.print(F("\t\tPM 10: ")); Serial.println(data.pm100_env);
Serial.println(F("---------------------------------------"));
Serial.print(F("Particles > 0.3um / 0.1L air:")); Serial.println(data.particles_03um);
Serial.print(F("Particles > 0.5um / 0.1L air:")); Serial.println(data.particles_05um);
Serial.print(F("Particles > 1.0um / 0.1L air:")); Serial.println(data.particles_10um);
Serial.print(F("Particles > 2.5um / 0.1L air:")); Serial.println(data.particles_25um);
Serial.print(F("Particles > 5.0um / 0.1L air:")); Serial.println(data.particles_50um);
Serial.print(F("Particles > 10 um / 0.1L air:")); Serial.println(data.particles_100um);
Serial.println(F("---------------------------------------"));
Serial.println(F("AQI"));
Serial.print(F("PM2.5 AQI US: ")); Serial.print(data.aqi_pm25_us);
Serial.print(F("\tPM10 AQI US: ")); Serial.println(data.aqi_pm100_us);
Serial.println(F("---------------------------------------"));
Serial.println();
heltec_setup();
// initialize radio
Serial.println("Radio init");
int16_t state = radio.begin();
if (state != RADIOLIB_ERR_NONE) {
Serial.println("Radio did not initialize. We'll try again later.");
goToSleep();
}
node = persist.manage(&radio);
if (!node->isActivated()) {
Serial.println("Could not join network. We'll try again later.");
goToSleep();
}
// If we're still here, it means we joined, and we can send something
// Manages uplink intervals to the TTN Fair Use Policy
node->setDutyCycle(true, 1250);
//create LoRa data uplink structure and packet
uint8_t uplinkData[6];
uplinkData[0] = (count++); //sequantially adds one intiger to the value 'count' for each transmission, resets when RST button pushed
uplinkData[1] = (data.pm10_standard); //PM1.0 Standard in units of micrograms per cubic meter
uplinkData[2] = (data.pm25_standard); //PM2.5 Standard in units of micrograms per cubic meter
uplinkData[3] = (data.pm100_standard); //PM10 Standard in units of micrograms per cubic meter
uplinkData[4] = (data.aqi_pm25_us); //Air Quality Index for PM2.5
uplinkData[5] = (data.aqi_pm100_us); //Air Quality Index for PM10.0
uplinkData[6] = (temp); //Heltec board temperature
//define LoRa downlink size
uint8_t downlinkData[256];
size_t lenDown = sizeof(downlinkData);
//Send of the LoRa data and establish wait for downlink
state = node->sendReceive(uplinkData, sizeof(uplinkData), 1, downlinkData, &lenDown);
if(state == RADIOLIB_ERR_NONE) {
Serial.println("Message sent, no downlink received.");
} else if (state > 0) {
Serial.println("Message sent, downlink received.");
} else {
Serial.printf("sendReceive returned error %d, we'll try again later.\n", state);
}
goToSleep(); // Does not return, program starts over next round
}
void loop()
{
// put your main code here, to run repeatedly:
}
void goToSleep() {
Serial.println("Going to deep sleep now");
// allows recall of the session after deepsleep
persist.saveSession(node);
// Calculate minimum duty cycle delay (per FUP & law!)
uint32_t interval = node->timeUntilUplink();
// And then pick it or our MINIMUM_DELAY, whichever is greater
uint32_t delayMs = max(interval, (uint32_t)MINIMUM_DELAY * 1000);
Serial.printf("Next TX in %i s\n", delayMs/1000);
Serial.println(F("*****-----*-----*-----*-----*-----*-----*-----*-----*-----*-----*-----*****"));
Serial.println();
delay(100); // So message prints
// and off to bed we go
heltec_deep_sleep(delayMs/1000);
}
Here is the TTN payload decoded in JavaScript
function Decoder(bytes, port) {
var decoded = {};
// Decode bytes to int
var testShort0 = (bytes[0]);
var testShort1 = (bytes[1]);
var testShort2 = (bytes[2]);
var testShort3 = (bytes[3]);
var testShort4 = (bytes[4]);
var testShort5 = (bytes[5]);
var testShort6 = (bytes[6]);
// Decode int
decoded.count = testShort0;
decoded.PM10STD = testShort1;
decoded.PM25STD = testShort2;
decoded.PM100STD = testShort3;
decoded.AQI25 = testShort4;
decoded.AQI100 = testShort5;
decoded.temp = testShort6;
return decoded;
}
Digital Button
Here is the funtioning sketch for a push botton, or toggle switch, on pin 7.
/**
* This sketch was written starting with the "LoRAWAN_TTN" example
* from the "Heltec_ESP32_LoRa_V3" Library. It was modified to include a
* bush button on pin 7 that will send digital signal of 0 (normal) or
* 1 (pushed), this requires a pull-down resistor from pin 7 to ground.
* Modications were made by by Brandon J. Winters.
*
*
* FOR THIS EXAMPLE TO WORK, YOU MUST INSTALL THE "LoRaWAN_ESP32" LIBRARY USING
* THE LIBRARY MANAGER IN THE ARDUINO IDE.
*
* This code will send a two-byte LoRaWAN message every 15 minutes. The first
* byte is a simple 8-bit counter, the second is the ESP32 chip temperature
* directly after waking up from its 15 minute sleep in degrees celsius + 100.
*
* If your NVS partition does not have stored TTN / LoRaWAN provisioning
* information in it yet, you will be prompted for them on the serial port and
* they will be stored for subsequent use.
*
* See https://github.com/ropg/LoRaWAN_ESP32
*/
// Pause between sends in seconds, so this is every 15 minutes. (Delay will be
// longer if regulatory or TTN Fair Use Policy requires it.)
#define MINIMUM_DELAY 300
#include <heltec_unofficial.h>
#include <LoRaWAN_ESP32.h>
#include <Wire.h>
#include <SPI.h>
LoRaWANNode* node;
const int button1 = 7;
//Setup sequatial counting for data transmissions
RTC_DATA_ATTR uint8_t count = 0; //for LoRa transmissions
RTC_DATA_ATTR uint8_t trans = 0; //for serial debugging
void setup() {
//Not sure about the ordering of the next few things, but this works. It starts the heltec board
//along with serial debugging and the BME680 board
heltec_setup();
Wire.begin();
pinMode(button1, INPUT);
Serial.begin(115200);
// Obtain directly after deep sleep
// May or may not reflect room temperature, some correction needed
float temp = heltec_temperature();
Serial.println();
Serial.println(F("-------------------------"));
Serial.print("Consecutive Transmissions: ");
Serial.print(trans++);
Serial.println();
Serial.printf("Heltec Temperature: %.1f °C\n", temp);
//Read and print current state of button1
Serial.print("State of button 1: ");
Serial.println(digitalRead(button1));
// initialize radio
Serial.println("Radio init");
int16_t state = radio.begin();
if (state != RADIOLIB_ERR_NONE) {
Serial.println("Radio did not initialize. We'll try again later.");
goToSleep();
}
node = persist.manage(&radio);
if (!node->isActivated()) {
Serial.println("Could not join network. We'll try again later.");
goToSleep();
}
// If we're still here, it means we joined, and we can send something
// Manages uplink intervals to the TTN Fair Use Policy
node->setDutyCycle(true, 1250);
//create LoRa data uplink structure and packet
uint8_t uplinkData[3];
uplinkData[0] = (count++); //sequantially adds one intiger to the value 'count' for each transmission, resets when RST button pushed
uplinkData[1] = (temp * 4); //max value to multiply by and keep under cap of 256 for uint8_t value
uplinkData[2] = (digitalRead(button1)); //State of button 1
//define LoRa downlink size
uint8_t downlinkData[256];
size_t lenDown = sizeof(downlinkData);
//Send of the LoRa data and establish wait for downlink
state = node->sendReceive(uplinkData, sizeof(uplinkData), 1, downlinkData, &lenDown);
if(state == RADIOLIB_ERR_NONE) {
Serial.println("Message sent, no downlink received.");
} else if (state > 0) {
Serial.println("Message sent, downlink received.");
} else {
Serial.printf("sendReceive returned error %d, we'll try again later.\n", state);
}
goToSleep(); // Does not return, program starts over next round
}
void loop() {
// This is never called. There is no repetition: we always go back to
// deep sleep one way or the other at the end of setup()
}
void goToSleep() {
Serial.println("Going to deep sleep now");
// allows recall of the session after deepsleep
persist.saveSession(node);
// Calculate minimum duty cycle delay (per FUP & law!)
uint32_t interval = node->timeUntilUplink();
// And then pick it or our MINIMUM_DELAY, whichever is greater
uint32_t delayMs = max(interval, (uint32_t)MINIMUM_DELAY * 1000);
Serial.printf("Next TX in %i s\n", delayMs/1000);
Serial.println(F("*****-----*-----*-----*-----*-----*-----*-----*-----*-----*-----*-----*****"));
Serial.println();
delay(100); // So message prints
// and off to bed we go
heltec_deep_sleep(delayMs/1000);
}
Here is the TTN payload decoder in JavaScript
function Decoder(bytes, port) {
var decoded = {};
// Decode bytes to int
var testShort0 = (bytes[0]);
var testShort1 = (bytes[1] / 4); //readjusted from the math done in Arduino
var testShort2 = (bytes[2]);
// Decode int
decoded.count = testShort0;
decoded.temp = testShort1;
decoded.button = testShort2;
return decoded;
}
Useful stuff, would be worth considering using RadioLib library directly so that the RL developers that are on this forum can help with any issues that people implementing & extending these examples might have.
Additionally, the TTS v3 JS PF function definition is now:
function decodeUplink(input)
Sure, the v2 still works as legacy, but as RL above, supporting is easier if it ties in with current docs.
@nmcc thank you for your reply. I have not used RadioLib, but I will look into that and see if I can sort it out.
As for the payload decoder, the version I shared is based on the template that TTN Console provides. I’m assuming you mean for me to replace the first line with what you have indicated, but I’m not clear about what else to change. Do you have a short example you could share with me, even with only one uplink byte of data?
You are using RadioLib, just wrapped up in another layer of someone else’s library.
Not in the last three years. This is what it provides as a starter:
function decodeUplink(input) {
return {
data: {
bytes: input.bytes
},
warnings: [],
errors: []
};
}
And the docs align with that which give some examples.
The reason for the change is that input is now an object which allows them to add extra fields for free - like the recvTime
which wasn’t in the original TTS v3 release.