Implement Multisensor Capabilities #4

Merged
seiichiro merged 3 commits from multisensor into master 2021-03-18 16:58:24 +00:00
3 changed files with 119 additions and 46 deletions
Showing only changes of commit 8656968f48 - Show all commits

View file

@ -16,6 +16,36 @@ Before programming a node, copy src/config.h.example to src/config.h and set the
Programming is done using a [MicroUPDI Programmer](https://github.com/MCUdude/microUPDI), settings in platformio.ini are set to use it. For other pogrammer options see the PlatformIO Documentation
## Multisensor Mode
The Firmware can be configured for multiple sensors at once (see comments in config.h.example). In this case the default payload decoder from the website will not be able to correctly determine the used sensors. You **must** define a specific decoder in this case. In the TTN v3 Stack a decoder can be set per device. Use the following as an example, and uncomment the parts for each enabled sensor, then make sure the placeholder for the byte index (**ii**)
is filled in ascending order, starting with the first enabled sensor from left to right, beginning with 1
function decodeUplink(input) {
var decoded = {};
// Battery Voltage, always enabled
decoded.v = (input.bytes[0] * 20) / 1000.0;
// CO2-Sensor (SG112A, MH-Z19C, Sensair S8)
// decoded.ppm = ((input.bytes[ii]) | (input.bytes[ii] << 8 ));
// Temperature and Humidity (BME280 / SHT21)
// decoded.t = ((input.bytes[ii]) | (input.bytes[ii] << 8 ) | (input.bytes[ii] << 16 ) | (input.bytes[ii] << 24)) / 100.0;
// decoded.h = ((input.bytes[ii]) | (input.bytes[ii] << 8 ) | (input.bytes[ii] << 16 ) | (input.bytes[ii] << 24)) / 100.0;
// Atmospheric Pressure (BME280)
// decoded.p = ((input.bytes[ii]) | (input.bytes[ii] << 8 ) | (input.bytes[ii] << 16 ) | (input.bytes[ii] << 24)) / 100.0;
// Leave this part as is
return {
data: decoded,
warnings: [],
errors: []
};
}
Please be also aware, that not all sensor combinations are valid. Some might use the same interface and interfere with each others readings. Also keep in mind that RAM- and Flash-Space are limited, which might lead to crashes or the code not compiling/flashing correctly if to many sensors are enabled at the same time.
## Configuring via Downlink
It is possible to change the sending interval via Downlink-Packets at runtime. The time between Transmits is specified in minutes (or more exactly, 64 Second intervals) and has to be sent as a 2 byte value, which will be interpreted as an uint. so for example 0x0001 means 1 Minute, 0x0002 means 2 Minutes and so on. Sending 0xFFFF resets the value to the compiled in default.

View file

@ -1,55 +1,80 @@
#ifndef CONFIG_H
#define CONFIG_H
// ALL EDITS BELOW THIS LINE!
// ATTNode v3 Onboard LED is on PIN_PA7
/**************************************************************************************************************************
* Use a Single Color LED for Status Signaling
* At Startup the LED will blink twice to signal a sucessfull OTAA Join
*************************************************************************************************************************/
#define LED_PIN PIN_PA7
// Enable WS2812B RGB LED Support for the CO2 Addon Board
// * First LED shows CO2-Level (green: <1000, yellow: 1000-1800, red: >=1800)
// * Second LED shows LoRa Status (yellow: Joining, green 1s: Joined, green 100ms: Sending, blue 250ms: Received Downlink)
// WS2812B_BRIGHT can be set to the desired brightness value for the LEDs (0=off, 255=brightest)
// Uncomment the 3 following lines to get the default behaviour
/**************************************************************************************************************************
* Enable WS2812B RGB LED Support for the CO2 Addon Board
* * First LED shows CO2-Level (green: <1000, yellow: 1000-1800, red: >=1800)
* * Second LED shows LoRa Status (yellow: Joining, green 1s: Joined, green 100ms: Sending, blue 250ms: Received Downlink)
* WS2812B_BRIGHT can be set to the desired brightness value for the LEDs (0=off, 255=brightest)
* Uncomment the 3 following lines to get the default behaviour
*************************************************************************************************************************/
// #define WS2812B_PIN PIN_PC1
// #define WS2812B_NUM 2
// #define WS2812B_BRIGHT 32
// Enable Sending on Button Press, as well as Calibration by Pressing Button for 4s with MH-Z19C Addon
// The Button has to be connected to a pin that is capable of Fully Asynchronus Interrupts.
// For The ATTNode this means Pin PC2 if you don't want to block any other Interfaces
/**************************************************************************************************************************
* Enable Sending on Button Press, as well as CO2 Background Level Calibration by Pressing Button for 4s with MH-Z19C Addon
* The Button has to be connected to a pin that is capable of Fully Asynchronus Interrupts.
* For The ATTNode this means Pin PC2 if you don't want to block any other Interfaces
*************************************************************************************************************************/
// #define BTN_PIN PIN_PC2
// Enable Serial Debugging. Parameters for the Serial Port are 115200
// Please be aware that the SG112A/B CO2 Sensor uses the HW-UART, so
// Serial Debug Output is not available with this Sensor.
/**************************************************************************************************************************
* Enable Serial Debugging. Parameters for the Serial Port are 115200 Baud 8n1
* By default the Firmware will output some messages about LoRa-Status and indicate sending
* The Macros DEBUG_PRINT() and DEBUG_PRINTLN() can be used to produce debug output depending on this define
* Please be aware that this will not work when a Serial Sensor like the SG112A/MH-Z19C or Sensair S8 ist used
*************************************************************************************************************************/
// #define DEBUG
// Number of Active Sensors (used as long as HAS_NO_SENSOR is not enabled)
// Set to the correct number of enabled sensors below
// Not doing so will lead to unpredictable results or not work at all
/**************************************************************************************************************************
* Number of active Sensors (used as long as HAS_NO_SENSOR is not enabled)
* Set to the correct number of enabled sensors below
* Not doing so will lead to unpredictable results or not work at all
* The default payload decoder will NOT WORK if more than one sensor is enabled, so you MUST define a specific
* decoder for this case. See the README.md for details
*************************************************************************************************************************/
#define NUM_SENSORS 1
// Define which Sensor is installed
/**************************************************************************************************************************
* Define which Sensors are installed
* Not all sensors can be used at the same time
* For example you can only have one sensor using serial UART
* If HAS_NO_SENSOR is selected all other activated sensor will be ignored
*************************************************************************************************************************/
#define HAS_NO_SENSOR
// #define HAS_MHZ19C
// #define HAS_SG112A
// #define HAS_SENSAIRS8
// #define HAS_BME280
// #define HAS_SHT21
// #define HAS_SG112A
// #define HAS_MHZ19C
// #define HAS_SENSAIRS8
// How many minutes to sleep between Measuring/Sending
// Since this is a 2-byte value internally, intervals between 1 and 65536 are possible
// This is the default interval to use, which can be overwritten via DownLink. If an interval
// is set via DownLink it will be saved in EEPROM and the time specified here will no longer be used.
// Actual Sleep Time is SLEEP_TIME*2*32 Seconds due to the 32s sleep intervals of the ATTiny3216
/**************************************************************************************************************************
* How many minutes to sleep between Measuring/Sending
* Since this is a 2-byte value internally, intervals between 1 and 65536 are possible
* This is the default interval to use, which can be overwritten via DownLink. If an interval
* is set via DownLink it will be saved in EEPROM and the time specified here will no longer be used.
* Actual sleep Time is sleep_time*2*32 seconds due to the 32s sleep intervals of the ATTiny3216
*************************************************************************************************************************/
uint16_t sleep_time = 10;
// Keys for OTAA Mode
// APPEUI and DEVEUI from TTN, LSB!
static const u1_t PROGMEM APPEUI[8]={ 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00 };
static const u1_t PROGMEM DEVEUI[8]={ 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00 };
// APPKey from TTN, MSB!
/**************************************************************************************************************************
* LoRa Keys for OTAA Mode
* Please make sure to use the correct byte order when copying these from the TTN-Console!
* APPEUI and DEVEUI need to be inserted in LSB order
* APPKEY needs to be inserted in MSB order
* in some cases there is no APPEUI, for example when using Chirpstack. leave it set to all 0x00 in these cases
*************************************************************************************************************************/
static const u1_t PROGMEM APPEUI[8] = { 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00 };
static const u1_t PROGMEM DEVEUI[8] = { 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00 };
static const u1_t PROGMEM APPKEY[16] = { 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00 };
// ALL EDITS ABOVE THIS LINE!

View file

@ -11,10 +11,13 @@
// Keep Track of used EEPROM Addresses
#define ADDR_SLP 0 // Sleep Interval, 2 Bytes
// Include Config and Helpers
#include "config.h"
#include "debug.h"
#include "attsensor.h"
// Include All Sensors Activated in config.h
#ifndef HAS_NO_SENSOR
#ifdef HAS_MHZ19C
#include <MHZ19C.h>
#endif
@ -30,8 +33,9 @@
#ifdef HAS_SHT21
#include <SHT21.h>
#endif
#endif
// define the blink function and BLINK_LED Macro depending
// Define the blink function and BLINK_LED Macro depending
// on the definition of LED_PIN
#ifdef LED_PIN
void blink(uint8_t num) {
@ -61,9 +65,12 @@ void blink(uint8_t num) {
#define WS2812B_BLINK(led,r,g,b,ms)
#endif
// Create Array for the Sensor Objects
#ifndef HAS_NO_SENSORS
AttSensor* sensors[NUM_SENSORS];
#endif
// Track Length of Payload (Depends on Active Sensors)
int payloadBytes = 1;
// Define some LMIC Callbacks and Variables
@ -94,6 +101,7 @@ const int disabledPins[] = {PIN_PB5, PIN_PB4, PIN_PB1, PIN_PB0, PIN_PC3, PIN_PC2
const int disabledPins[] = {PIN_PB5, PIN_PB4, PIN_PB3, PIN_PB2, PIN_PB1, PIN_PB0, PIN_PC3, PIN_PC2, PIN_PC1, PIN_PC0};
#endif
// Helper variables and Interrupt Routine for Button
#ifdef BTN_PIN
volatile bool btn_pressed = 0;
volatile unsigned long btn_millis = 0;
@ -106,19 +114,19 @@ void btn_press() {
}
#endif
// ISR Routine for Sleep
// Interrupt Routine for Sleep
ISR(RTC_PIT_vect)
{
/* Clear interrupt flag by writing '1' (required) */
RTC.PITINTFLAGS = RTC_PI_bm;
}
// Sleep Routine
// Sleep Routine, Sleep for 32 Seconds
void sleep_32s() {
cli();
while (RTC.PITSTATUS > 0) {}
RTC.PITINTCTRL = RTC_PI_bm;
// 32 Sekunden
// 32 Seconds
RTC.PITCTRLA = RTC_PERIOD_CYC32768_gc | RTC_PITEN_bm;
while (RTC.PITSTATUS & RTC_CTRLBUSY_bm) {}
set_sleep_mode(SLEEP_MODE_PWR_DOWN);
@ -156,6 +164,7 @@ void onEvent(ev_t ev) {
// Got to sleep for specified Time
DEBUG_PRINTLN("Going to Sleep");
for (uint16_t i = 0; i < sleep_time*2; i++) {
// Cancel sleep Cycle if Button was Pressed
#ifdef BTN_PIN
if (btn_pressed) {
i = sleep_time*2;
@ -191,13 +200,15 @@ uint16_t readSupplyVoltage() { //returns value in millivolts to avoid floating p
// Read Sensors and Send Data
void do_send(osjob_t* j) {
// Prepare LoRa Data Packet
// The struct is defined in the sensor class (or above for use without a sensor)
// Array of Bytes for the Payload
// Length is defined by the Enabled Sensors
char payload[payloadBytes];
if (LMIC.opmode & OP_TXRXPEND) {
// Wayt if LMIC is busy
delay(1);
} else {
// Track Current Position in Payload Array
uint8_t curByte = 0;
// Add Battery Voltage (0.2V Accuracy stored in 1 byte)
@ -207,11 +218,12 @@ void do_send(osjob_t* j) {
payload[curByte] += 1;
curByte++;
// Get Sensor Readings Into Data Paket
#ifndef HAS_NO_SENSOR
// Put Sensor Readings into the Payload Array
for (int i=0; i < NUM_SENSORS; i++)
curByte = sensors[i]->getSensorData(payload, curByte);
// If CO2 Addon Boards with RGB-LEDS is installed, set LED according to the current CO2 Reading
#if defined WS2812B_PIN && (defined HAS_SG112A || defined HAS_MHZ19C || defined HAS_SENSAIRS8)
// CO2 PPM Levels and LED Colors
// < 1000 ppm green
@ -232,7 +244,6 @@ void do_send(osjob_t* j) {
WS2812B_SETLED(0,0,0,0);
}
#endif // WS2812B
#endif // HAS_NO_SENSOR
// Queue Packet for Sending
@ -243,9 +254,11 @@ void do_send(osjob_t* j) {
void setup()
{
// Initialize Serial if Debug is enabled
#ifdef DEBUG
Serial.begin(115200);
#endif
// Initialize SPI and I2C
Wire.begin();
SPI.begin();
@ -254,18 +267,21 @@ void setup()
for (int i = 0; i < (sizeof(disabledPins) / sizeof(disabledPins[0])) - 1; i++)
pinMode(disabledPins[i], INPUT_PULLUP);
// Setup WS2812B LEDs
#ifdef WS2812B_PIN
pinMode(WS2812B_PIN, OUTPUT);
leds.setBrightness(WS2812B_BRIGHT);
#endif
// Setup Button Interrupt
#ifdef BTN_PIN
pinMode(BTN_PIN, INPUT_PULLUP);
attachInterrupt(digitalPinToInterrupt(BTN_PIN), btn_press, FALLING);
#endif
// Setup all Sensors
// Setup all Sensors and Calculate the Payload Length
// Order of the Sensors here is Order in the Payload
#ifndef HAS_NO_SENSOR
uint8_t i = 0;
#ifdef HAS_MHZ19C
sensors[i] = new MHZ19C();
@ -292,7 +308,7 @@ void setup()
payloadBytes += sensors[i]->numBytes();
i++;
#endif
#endif // HAS_NO_SENSOR
// Initialize all Sensors
#ifndef HAS_NO_SENSOR
@ -300,7 +316,7 @@ void setup()
sensors[i]->initialize();
#endif
// Set RTC
// Setup RTC
while (RTC.STATUS > 0) {}
RTC.CLKSEL = RTC_CLKSEL_INT1K_gc;
while (RTC.PITSTATUS > 0) {}
@ -308,10 +324,10 @@ void setup()
// Setup LMIC
DEBUG_PRINT("Initializing LMIC...")
os_init();
LMIC_reset(); // Reset LMIC state and cancel all queued transmissions
LMIC_reset(); // Reset LMIC state and cancel all queued transmissions
LMIC_setClockError(MAX_CLOCK_ERROR * 10 / 100); // Compensate for Clock Skew
LMIC.dn2Dr = DR_SF9; // Downlink Band
LMIC_setDrTxpow(DR_SF7, 14); // Default to SF7
LMIC.dn2Dr = DR_SF9; // Downlink Band
LMIC_setDrTxpow(DR_SF7, 14); // Default to SF7
DEBUG_PRINTLN("Done");
// Check if Sending Interval is set in EEPROM
@ -326,13 +342,15 @@ void setup()
DEBUG_PRINTLN("Setup Finished");
// Schedule First Send (Triggers OTAA Join as well)
// Set WS2812B to Yellow for "Joining" (if enabled)
WS2812B_SETLED(1,127,127,0);
// Schedule First Send (Triggers OTAA Join as well)
do_send(&sendjob);
}
void loop()
{
// Handle long Button Press for Calibration with MH-Z19C Sensor
#if defined HAS_MHZ19C && defined BTN_PIN
if (digitalRead(BTN_PIN) == LOW) {
// Press Button longer than 4 Seconds -> Start MH-Z19C Calibration Routine