From 8656968f4846ebdf1ec1541d2fccefc072348f62 Mon Sep 17 00:00:00 2001 From: Stefan Brand Date: Thu, 18 Mar 2021 17:57:00 +0100 Subject: [PATCH] Added README Section for Multisensor Mode Added More Comments Cleanup and Formatting --- README.md | 30 ++++++++++++++++ src/config.h.example | 85 ++++++++++++++++++++++++++++---------------- src/main.cpp | 50 +++++++++++++++++--------- 3 files changed, 119 insertions(+), 46 deletions(-) diff --git a/README.md b/README.md index db6017a..519a9e5 100644 --- a/README.md +++ b/README.md @@ -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. diff --git a/src/config.h.example b/src/config.h.example index d5e3345..e854089 100644 --- a/src/config.h.example +++ b/src/config.h.example @@ -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! diff --git a/src/main.cpp b/src/main.cpp index cc65c66..9c5af9b 100644 --- a/src/main.cpp +++ b/src/main.cpp @@ -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 #endif @@ -30,8 +33,9 @@ #ifdef HAS_SHT21 #include #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) @@ -206,12 +217,13 @@ void do_send(osjob_t* j) { if (batv % 20 > 9) 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