You cannot select more than 25 topics
Topics must start with a letter or number, can include dashes ('-') and can be up to 35 characters long.
458 lines
12 KiB
C++
458 lines
12 KiB
C++
#include <Arduino.h>
|
|
#include <avr/sleep.h>
|
|
#include <avr/interrupt.h>
|
|
#include <SPI.h>
|
|
#include <Wire.h>
|
|
#include <EEPROM.h>
|
|
|
|
#include <lmic.h>
|
|
#include <hal/hal.h>
|
|
|
|
// 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
|
|
#ifdef HAS_SG112A
|
|
#include <SG112A.h>
|
|
#endif
|
|
#ifdef HAS_SENSAIRS8
|
|
#include <SENSAIRS8.h>
|
|
#endif
|
|
#ifdef HAS_SCD30
|
|
#include <SCD30.h>
|
|
#endif
|
|
#ifdef HAS_BME280
|
|
#include <BME280.h>
|
|
#endif
|
|
#ifdef HAS_SHT21
|
|
#include <SHT21.h>
|
|
#endif
|
|
#ifdef HAS_SPS30
|
|
#include <SPS30.h>
|
|
#endif
|
|
#ifdef HAS_HM330x
|
|
#include <HM330x.h>
|
|
#endif
|
|
#ifdef HAS_BRIGHTNESS
|
|
#include <Brightness.h>
|
|
#endif
|
|
#ifdef HAS_DS18B20
|
|
#include <DS18B20.h>
|
|
#endif
|
|
#endif
|
|
|
|
// Define the blink function and BLINK_LED Macro depending
|
|
// on the definition of LED_PIN
|
|
#ifdef LED_PIN
|
|
void blink(uint8_t num) {
|
|
pinMode(LED_PIN, OUTPUT);
|
|
digitalWrite(LED_PIN, 0);
|
|
for (uint8_t i = 0; i < num * 2; i++) {
|
|
digitalWrite(LED_PIN, !digitalRead(LED_PIN));
|
|
delay(100);
|
|
}
|
|
digitalWrite(LED_PIN, 0);
|
|
}
|
|
#define BLINK_LED(COUNT) blink(COUNT);
|
|
#else
|
|
#define BLINK_LED(COUNT)
|
|
#endif
|
|
|
|
// WS2812B RGB LEDs on the CO2 Addon Board
|
|
// Defines the Macro Function WS2812B_SETLED so we don't need #ifdefs everywhere
|
|
#ifdef WS2812B_PIN
|
|
#include <tinyNeoPixel_Static.h>
|
|
byte pixels[WS2812B_NUM * 3];
|
|
tinyNeoPixel leds = tinyNeoPixel(WS2812B_NUM, WS2812B_PIN, NEO_GRB, pixels);
|
|
#define WS2812B_SETLED(led,r,g,b) leds.setPixelColor(led,r,g,b); leds.show()
|
|
#define WS2812B_BLINK(led,r,g,b,ms) leds.setPixelColor(led,r,g,b); leds.show(); delay(ms); leds.setPixelColor(led,0,0,0); leds.show()
|
|
#else
|
|
#define WS2812B_SETLED(led,r,g,b)
|
|
#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
|
|
void os_getArtEui (u1_t* buf) {
|
|
memcpy_P(buf, APPEUI, 8);
|
|
}
|
|
void os_getDevEui (u1_t* buf) {
|
|
memcpy_P(buf, DEVEUI, 8);
|
|
}
|
|
void os_getDevKey (u1_t* buf) {
|
|
memcpy_P(buf, APPKEY, 16);
|
|
}
|
|
static osjob_t sendjob;
|
|
void do_send(osjob_t* j);
|
|
|
|
// Pin-Mapping for ATTNode v3
|
|
const lmic_pinmap lmic_pins = {
|
|
.nss = PIN_PA5,
|
|
.rxtx = LMIC_UNUSED_PIN,
|
|
.rst = LMIC_UNUSED_PIN,
|
|
.dio = {PIN_PA4, PIN_PA6, LMIC_UNUSED_PIN},
|
|
};
|
|
|
|
// List of unused Pins - will be disabled for Power Saving
|
|
#if defined DEBUG || defined HAS_SG112A || defined HAS_MHZ19C || defined HAS_SENSAIRS8
|
|
const int disabledPins[] = {PIN_PB5, PIN_PB4, PIN_PB1, PIN_PB0, PIN_PC3, PIN_PC2, PIN_PC1, PIN_PC0};
|
|
#else
|
|
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;
|
|
|
|
// ISR Routine for Button
|
|
void btn_press() {
|
|
btn_pressed = 1;
|
|
btn_millis = millis();
|
|
delayMicroseconds(250000);
|
|
}
|
|
#endif
|
|
|
|
// Interrupt Routine for Sleep
|
|
ISR(RTC_PIT_vect)
|
|
{
|
|
/* Clear interrupt flag by writing '1' (required) */
|
|
RTC.PITINTFLAGS = RTC_PI_bm;
|
|
}
|
|
|
|
// Sleep Routine, Sleep for 32 Seconds
|
|
void sleep_32s() {
|
|
cli();
|
|
while (RTC.PITSTATUS > 0) {}
|
|
RTC.PITINTCTRL = RTC_PI_bm;
|
|
// 32 Seconds
|
|
RTC.PITCTRLA = RTC_PERIOD_CYC32768_gc | RTC_PITEN_bm;
|
|
while (RTC.PITSTATUS & RTC_CTRLBUSY_bm) {}
|
|
set_sleep_mode(SLEEP_MODE_PWR_DOWN);
|
|
sleep_enable();
|
|
sei();
|
|
sleep_cpu();
|
|
sleep_disable();
|
|
sei();
|
|
}
|
|
|
|
// LMIC Callback Handling
|
|
void onEvent(ev_t ev) {
|
|
switch (ev) {
|
|
case EV_JOINED:
|
|
// Disable LinkCheck
|
|
LMIC_setLinkCheckMode(0);
|
|
BLINK_LED(2);
|
|
#if WS2812B_NUM > 1
|
|
WS2812B_BLINK(1,0,127,0,1000);
|
|
#endif
|
|
DEBUG_PRINTLN("OTAA Join Succeeded");
|
|
break;
|
|
case EV_TXCOMPLETE:
|
|
// Check for Downlink
|
|
DEBUG_PRINTLN("LoRa Packet Sent");
|
|
#if WS2812B_NUM > 1
|
|
WS2812B_BLINK(1,0,127,0,1000);
|
|
#endif
|
|
if ((int)LMIC.dataLen > 0) {
|
|
// Check for Downlinks
|
|
// Function based in Ports:
|
|
// Port 1
|
|
// Set Sending Interval
|
|
// Port 2
|
|
// Do Calibration
|
|
switch((uint8_t)LMIC.frame[LMIC.dataBeg-1]) {
|
|
case 1:
|
|
if ((int)LMIC.dataLen == 2) {
|
|
// We got a Packet with the right size - lets assemble it into a uint16_t
|
|
DEBUG_PRINTLN("Received Downlink")
|
|
uint16_t tmpslp = (LMIC.frame[LMIC.dataBeg] << 8) | LMIC.frame[LMIC.dataBeg+1];
|
|
DEBUG_PRINT("Setting Sleep Time to: ");
|
|
DEBUG_PRINTLN(tmpslp);
|
|
sleep_time = tmpslp;
|
|
EEPROM.put(ADDR_SLP, tmpslp);
|
|
#if WS2812B_NUM > 1
|
|
WS2812B_BLINK(1,0,0,127,250);
|
|
#endif
|
|
}
|
|
break;
|
|
case 2:
|
|
for (uint8_t i=0; i<NUM_SENSORS; i++)
|
|
sensors[i]->calibrate();
|
|
BLINK_LED(3);
|
|
break;
|
|
}
|
|
}
|
|
|
|
// Got to sleep for specified Time
|
|
DEBUG_PRINT("Going to Sleep for ");
|
|
DEBUG_PRINT(sleep_time*64);
|
|
DEBUG_PRINTLN(" Seconds");
|
|
for (uint16_t i = 0; i < sleep_time*2; i++) {
|
|
// Cancel sleep Cycle if Button was Pressed
|
|
#ifdef BTN_PIN
|
|
if (btn_pressed && digitalRead(BTN_PIN) == HIGH) {
|
|
DEBUG_PRINTLN("Button Pressed, waking up");
|
|
i = sleep_time*2;
|
|
btn_pressed = 0;
|
|
} else {
|
|
#endif
|
|
sleep_32s();
|
|
#ifdef BTN_PIN
|
|
}
|
|
#endif
|
|
}
|
|
|
|
// Schedule Next Transmit
|
|
do_send(&sendjob);
|
|
break;
|
|
}
|
|
}
|
|
|
|
// Get Battery Voltage
|
|
#ifdef EXT_BAT_PIN
|
|
uint16_t readSupplyVoltage() {
|
|
pinMode(EXT_BAT_PIN, INPUT);
|
|
analogReference(INTERNAL2V5);
|
|
ADC0.CTRLD = ADC_INITDLY_DLY256_gc;
|
|
ADC0_CTRLB = ADC_SAMPNUM_ACC64_gc;
|
|
uint16_t reading = analogRead(EXT_BAT_PIN);
|
|
reading = reading/64;
|
|
DEBUG_PRINT("ADC Reading: ");
|
|
DEBUG_PRINTLN(reading);
|
|
reading = (int16_t)(reading * (2500/1024.0) * EXT_BAT_RATIO);
|
|
return reading;
|
|
}
|
|
#else
|
|
uint16_t readSupplyVoltage() { //returns value in millivolts to avoid floating point
|
|
uint16_t temp = 0;
|
|
analogReference(VDD);
|
|
VREF.CTRLA = VREF_ADC0REFSEL_1V5_gc;
|
|
ADC0.CTRLD = ADC_INITDLY_DLY256_gc;
|
|
ADC0_CTRLB = ADC_SAMPNUM_ACC64_gc;
|
|
uint16_t reading = analogRead(ADC_INTREF);
|
|
temp = reading / 64;
|
|
uint32_t intermediate = 1534500;
|
|
reading = 0;
|
|
reading = intermediate / temp;
|
|
return reading;
|
|
}
|
|
#endif
|
|
|
|
// Read Sensors and Send Data
|
|
void do_send(osjob_t* j) {
|
|
// 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)
|
|
uint32_t batv = readSupplyVoltage();
|
|
payload[curByte] = (uint8_t)(batv / 20);
|
|
if (batv % 20 > 9)
|
|
payload[curByte] += 1;
|
|
curByte++;
|
|
|
|
#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
|
|
// < 1800 ppm yellow
|
|
// > 1000 ppm red
|
|
|
|
// Get PPM from Payload:
|
|
uint16_t ppm = word(payload[2], payload[1]);
|
|
|
|
// Set WS2812B-LED accodring to PPM Value
|
|
if (ppm > 0 && ppm <= 1000) {
|
|
WS2812B_SETLED(0,0,127,0);
|
|
} else if (ppm > 1000 && ppm <= 1800) {
|
|
WS2812B_SETLED(0,127,127,0);
|
|
} else if (ppm > 1800) {
|
|
WS2812B_SETLED(0,127,0,0);
|
|
} else {
|
|
WS2812B_SETLED(0,0,0,0);
|
|
}
|
|
#endif // WS2812B
|
|
#endif // HAS_NO_SENSOR
|
|
|
|
// Queue Packet for Sending
|
|
DEBUG_PRINTLN("LoRa-Packet Queued");
|
|
LMIC_setTxData2(1, payload, sizeof(payload), 0);
|
|
}
|
|
}
|
|
|
|
void setup()
|
|
{
|
|
// Initialize Serial if Debug is enabled
|
|
#ifdef DEBUG
|
|
Serial.begin(115200);
|
|
// Wait 2 seconds for Monitor to connect
|
|
delay(2000);
|
|
#endif
|
|
|
|
// Initialize SPI and I2C
|
|
Wire.begin();
|
|
SPI.begin();
|
|
|
|
// Disable unused Pins (for power saving)
|
|
for (int i = 0; i < (sizeof(disabledPins) / sizeof(disabledPins[0])) - 1; i++)
|
|
pinMode(disabledPins[i], INPUT_PULLUP);
|
|
|
|
#ifdef EXT_BAT_PIN
|
|
|
|
#endif
|
|
|
|
// 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 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();
|
|
i++;
|
|
#endif
|
|
#ifdef HAS_SG112A
|
|
sensors[i] = new SG112A();
|
|
i++;
|
|
#endif
|
|
#ifdef HAS_SENSAIRS8
|
|
sensors[i] = new SENSAIRS8();
|
|
i++;
|
|
#endif
|
|
#ifdef HAS_SCD30
|
|
sensors[i] = new SCD30();
|
|
i++;
|
|
#endif
|
|
#ifdef HAS_BME280
|
|
sensors[i] = new BME280();
|
|
i++;
|
|
#endif
|
|
#ifdef HAS_SHT21
|
|
sensors[i] = new SHT21();
|
|
i++;
|
|
#endif
|
|
#ifdef HAS_SPS30
|
|
sensors[i] = new SPS30();
|
|
i++;
|
|
#endif
|
|
#ifdef HAS_HM330x
|
|
sensors[i] = new HM330x(HM330x_SLEEP_PIN);
|
|
i++;
|
|
#endif
|
|
#ifdef HAS_BRIGHTNESS
|
|
sensors[i] = new Brightness(BRIGHTNESS_LED_A, BRIGHTNESS_LED_C);
|
|
i++;
|
|
#endif
|
|
#ifdef HAS_DS18B20
|
|
sensors[i] = new DS18B20(DS18B20_PIN, DS18B20_RES);
|
|
i++;
|
|
#endif
|
|
|
|
// Initialize all Sensors
|
|
for (i = 0; i < NUM_SENSORS; i++) {
|
|
sensors[i]->initialize();
|
|
payloadBytes += sensors[i]->numBytes();
|
|
}
|
|
|
|
#endif
|
|
|
|
// Setup RTC
|
|
while (RTC.STATUS > 0) {}
|
|
RTC.CLKSEL = RTC_CLKSEL_INT1K_gc;
|
|
while (RTC.PITSTATUS > 0) {}
|
|
|
|
// Setup LMIC
|
|
DEBUG_PRINT("Initializing LMIC...")
|
|
os_init();
|
|
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
|
|
DEBUG_PRINTLN("Done");
|
|
|
|
// Check if Sending Interval is set in EEPROM
|
|
// if we get 65535 (0xFFFF) EEPROM was not written
|
|
uint16_t tmpsleep = 0;
|
|
EEPROM.get(ADDR_SLP, tmpsleep);
|
|
if (tmpsleep < 65535) {
|
|
DEBUG_PRINT("Setting Sleep Time from EEPROM to ");
|
|
DEBUG_PRINTLN(tmpsleep);
|
|
sleep_time = tmpsleep;
|
|
}
|
|
|
|
DEBUG_PRINTLN("Setup Finished");
|
|
|
|
// Set WS2812B to Yellow for "Joining" (if enabled)
|
|
#if WS2812B_NUM > 1
|
|
WS2812B_SETLED(1,127,127,0);
|
|
#endif
|
|
// Schedule First Send (Triggers OTAA Join as well)
|
|
do_send(&sendjob);
|
|
}
|
|
|
|
void loop()
|
|
{
|
|
// Handle long Button Press for Calibration with MH-Z19C Sensor
|
|
#ifdef BTN_PIN
|
|
if (digitalRead(BTN_PIN) == LOW) {
|
|
// Press Button longer than 4 Seconds -> Start Sensor Calibration Routine (if applicable)
|
|
unsigned long loop_millis = millis();
|
|
if ((unsigned long)(loop_millis - btn_millis) >= 4000) {
|
|
WS2812B_SETLED(0,153,0,153);
|
|
BLINK_LED(3);
|
|
delay(1000);
|
|
for (uint8_t i=0; i<NUM_SENSORS; i++)
|
|
sensors[i]->calibrate();
|
|
BLINK_LED(1);
|
|
WS2812B_SETLED(0,0,0,0);
|
|
} else {
|
|
delay(500);
|
|
}
|
|
} else {
|
|
#endif
|
|
// Only Run the LMIC loop here. Actual Sending Code is in do_send()
|
|
os_runloop_once();
|
|
#ifdef BTN_PIN
|
|
}
|
|
#endif
|
|
} |