diff --git a/.travis.yml b/.travis.yml index d1f465c..930c7a4 100644 --- a/.travis.yml +++ b/.travis.yml @@ -11,7 +11,7 @@ before_install: - sudo ln -s /usr/local/share/arduino/arduino /usr/local/bin/arduino install: - ln -s $PWD /usr/local/share/arduino/libraries/TheThingsNode - - arduino --install-library "DHT sensor library:1.3.0,Adafruit Unified Sensor:1.0.2,TheThingsNetwork:2.5.6" + - arduino --install-library "DHT sensor library:1.3.0,Adafruit Unified Sensor:1.0.2,TheThingsNetwork:2.5.7" - git clone https://github.com/sparkfun/arduino_boards /tmp/sparkfun - mv /tmp/sparkfun/sparkfun /usr/local/share/arduino/hardware/sparkfun before_script: diff --git a/docs/TheThingsNode.md b/docs/TheThingsNode.md index 7fd8b47..920ef82 100644 --- a/docs/TheThingsNode.md +++ b/docs/TheThingsNode.md @@ -38,14 +38,41 @@ This will: > Don't add any other code in your `loop()` function, but use [`onWake()`](#method-onwake), [`onInterval()`](#method-oninterval) and [`onSleep()`](#method-onsleep) instead, to prevent unpredictable behavior. ## Method: onWake -Set a callback that will run first thing every time the Node wakes up, which is when an interrupt changes because of interaction with the button, motion sensor or temperature sensor. The device also wakes up every 8 seconds, which is the longest we can make it sleep. +Set a callback that will run first thing every time the Node wakes up, which is when an interrupt changes because of interaction with the button, motion sensor, temperature sensor or LoRa device wake. +Depending on [`configInterval()`](#method-configinterval) setup, this method can be called: + +- When you instructed the LoRa RN2xxx device to wake up after the specific interval. +- Every 8 seconds (Watchdog), which is the longest we can make it sleep. + +the parameter wakeReason is indicating what waked the node and can be a bit field of the following description + +| Bit | 7 | 6 | 5 | 4 | 3 | 2 | 1 | 0 | +| :--- | :---: | :---: | :---: | :---: | :---: | :---: | :---: | :---: | +| Wake by | Interval | Watchdog | LoRa | Btn Release | Btn Press | Motion Stop | Motion Start | Temperature | + +Is declared into the library as follow: ```c -void onWake(void(*callback)(void)); +#define TTN_WAKE_TEMPERATURE 0x01 +#define TTN_WAKE_MOTION_START 0x02 +#define TTN_WAKE_MOTION_STOP 0x04 +#define TTN_WAKE_BTN_PRESS 0x08 +#define TTN_WAKE_BTN_RELEASE 0x10 +#define TTN_WAKE_LORA 0x20 +#define TTN_WAKE_WATCHDOG 0x40 +#define TTN_WAKE_INTERVAL 0x80 +``` + +Note that of course, wake by TTN_WAKE_LORA also setup the TTN_WAKE_INTERVAL because we program the LoRa module to wake at the specified interval. + + +```c +void onWake(void(*callback)(uint8_t wakeReason)); ``` Usage: + ```c void setup() { node = TheThingsNode::setup(); @@ -53,8 +80,34 @@ void setup() { node->onWake(wake); } -void wake() { - node->setColor(TTN_GREEN); +void wake(uint8_t wakeReason) { + debugSerial.print(F("Wake Reason: 0x")); + debugSerial.println(wakeReason, HEX); + + if (wakeReason & TTN_WAKE_WATCHDOG) { + debugSerial.print(F("Watchdog ")); + } + if (wakeReason & TTN_WAKE_INTERVAL) { + debugSerial.print(F("Interval ")); + } + if (wakeReason & TTN_WAKE_LORA) { + debugSerial.print(F("LoRa_RN2xxx ")); + } + if (wakeReason & TTN_WAKE_BTN_PRESS) { + debugSerial.print(F("Button_Press ")); + } + if (wakeReason & TTN_WAKE_BTN_RELEASE) { + debugSerial.print(F("Button_release ")); + } + if (wakeReason & TTN_WAKE_MOTION_START) { + debugSerial.print(F("Motion_Start ")); + } + if (wakeReason & TTN_WAKE_MOTION_STOP) { + debugSerial.print(F("Motion_Stop ")); + } + if (wakeReason & TTN_WAKE_TEMPERATURE) { + debugSerial.print(F("Temperature ")); + } } ``` @@ -81,12 +134,10 @@ void sleep() { } ``` -Pay attention, this internal sleep method of the library does not put the LoRa module (RN2483 or RN2903) into sleep mode and thus your node may consume 3mA even in sleep mode. You need to manually set the LoRa module to sleep and wake. -Check the example [BatteryMonitorLPP](../examples/BatteryMonitorLPP/) - ## Interval Instead of using your `loop()` function, use `configInterval()` and `onInterval()` to set a function to be called on a certain interval: + ```c void setup() { node = TheThingsNode::setup(); @@ -97,31 +148,37 @@ void setup() { node->onInterval(interval); } -void interval() { +void interval(uint8_t wakeReason) { node->showStatus(); } ``` +See [`onWake()`](#method-onwake) for definition of wakeReason: + ### Method: onInterval Set a callback that will run on a certain interval. This will automatically enable the interval. ```c -void onInterval(void(*callback)(void)); +void onInterval(void(*callback)(uint8_t wakeReason)); ``` -- `void(*callback)(void)`: Function to be called, with no arguments nor return value. +- `void(*callback)(uint8_t wakeReason)`: Function to be called. + +See [`onWake()`](#method-onwake) for definition of wakeReason: ### Method: configInterval ```c void configInterval(bool enabled, uint32_t ms); void configInterval(bool enabled); +void configInterval(TheThingsNetwork *pttn, uint32_t ms); ``` - `bool enabled`: Enable or disable the interval callback. Enabled automatically by `onInterval()`, but you can use this method to temporarily disable it. Defaults to `false`. +- `TheThingsNetwork * pttn`: This enable the interval callback but in this mode, the interval is passed to RN2483 or RN2903 module (this is why we need to pass pointer to object) with the command `sys sleep ms` and then it's the LoRa module that wake up the node. This is the most advanced Low Power Mode. In this mode, the watchdog is disabled and consumption again reduced. - `uint32_t ms`: Minimal time between calling the interval callback. Defaults to `60000` or 60 seconds. -> If the Node has no USB data connection or is configured to also sleep in that case, it will only wake up every 8 seconds to check if the interval callback should run. This means setting `ms` to less than `8000` makes no sense. It also means that the maximum time between calls can be up to 8 seconds longer than `ms` if it wakes up to handle button or sensor interaction in between. +> If the Node has no USB data connection or is configured to also sleep in that case, it will only wake up every 8 seconds to check if the interval callback should run. This means setting `ms` to less than `8000` makes no sense. It also means that the maximum time between calls can be up to 8 seconds longer than `ms` if it wakes up to handle button or sensor interaction in between. This does not apply when wake up by LoRa module. ## Method: showStatus Writes information about the device and sensors to `Serial`. @@ -196,7 +253,7 @@ void configTemperature(bool enabled, MCP9804_Resolution resolution); void configTemperature(bool enabled); ``` -- `bool enabled `: Enable or disable the temperature sensor. Enabled automatically by `onTemperature()`, but you can use this method to temporarily disable the sensor and therefore the alerts. Defaults to `false`. +- `bool enabled `: Enable or disable the temperature sensor ALERTS. Enabled automatically by `onTemperature()`, but you can use this method to temporarily disable the alerts. Defaults to `false`. - `MCP9804_Resolution resolution = R_DEGREES_0_0625 `: Set the resolution (precision) of the sensor. One of: - `R_DEGREES_0_5000`: +0.5 C - `R_DEGREES_0_2500`: +0.25 C @@ -457,9 +514,29 @@ When disabled, the [`loop()`](#method-loop) method will delay 100ms until an int > When the Node goes into sleep, the Serial Monitor will loose its connection. The Node will try to reopen the connection when it wakes up, but Serial Monitor might not always be able to pick it up. -## Method: getBattery -Returns the battery level in millivolt (mV) as a unsigned integer of 2 bytes. +## Battery +Different methods to get battery level + +### Method: getBattery +Returns the battery level in millivolt (mV) as a unsigned integer of 2 bytes. This method measure battery with a resistor voltage divider (located before 3.3V ob board regulator.) and connected to an analog input pin. The ADC pin as voltage reference of 2.52V ```c uint16_t getBattery(); ``` + +### Method: getVCC +Returns the AT32U4 chip measured VCC voltage millivolt (mV) using the 1.1V internal Analog Reference voltage. This method is prefered when voltage before regulator comes to and below 3.3V. Moreover, if voltage is below 2.52V, using the method `getBattery` could return wrong values due to the main voltage < ADC reference voltage. + +```c +uint16_t getVCC(); +``` + +### Alternative Method: getVDD +You can also use the getVDD from [TTN Device Library](https://github.com/TheThingsNetwork/arduino-device-lib). + +Returns the voltage in millivolt (mV) measured by the RN2xxx LoRa module. It's for information only since we don't know how it's measured. + +```c +uint16_t bat = ttn.getVDD(); +``` + diff --git a/examples/Basic/Basic.ino b/examples/Basic/Basic.ino index 97e6259..a880eba 100644 --- a/examples/Basic/Basic.ino +++ b/examples/Basic/Basic.ino @@ -80,7 +80,7 @@ void loop() node->loop(); } -void interval() +void interval(uint8_t wakeReason) { node->setColor(TTN_BLUE); @@ -88,7 +88,7 @@ void interval() sendData(PORT_INTERVAL); } -void wake() +void wake(uint8_t wakeReason) { node->setColor(TTN_GREEN); } diff --git a/examples/BatteryMonitorLPP/BatteryMonitorLPP.ino b/examples/BatteryMonitorLPP/BatteryMonitorLPP.ino index 6dd23fb..f50b709 100644 --- a/examples/BatteryMonitorLPP/BatteryMonitorLPP.ino +++ b/examples/BatteryMonitorLPP/BatteryMonitorLPP.ino @@ -1,3 +1,11 @@ +// This sketch use advanced Ultra Low Power techniques +// for this it disable all unneeded peripherals during sleep mode, including +// USB Management, this mean you won't be able to upload anymore if the node is sleeping +// when wake up, Lora transmission is approx 3s (including receive windows) this means +// that you have 3 seconds windows to upload, so unless you're lucky, it's almost impossible +// to sync Arduino compilation and upload. But to avoid this, you can press the node button +// for more than 2s, then the led will yellow blink quickly for 60s, letting you time to upload + #include #include #include @@ -12,41 +20,101 @@ const char *appKey = "00000000000000000000000000000000"; #define loraSerial Serial1 #define debugSerial Serial +#define PORT_SETUP 1 +#define PORT_BTN_PRESS 10 +#define PORT_BTN_RELEASE 11 +#define PORT_MOTION_START 20 +#define PORT_MOTION_END 21 +#define PORT_WATCHDOG 30 +#define PORT_INTERVAL 31 +#define PORT_LORA 32 +#define PORT_TEMPERATURE 33 + +// Interval between send in seconds, so 300s = 5min +#define CONFIG_INTERVAL ((uint32_t)300) + TheThingsNetwork ttn(loraSerial, debugSerial, freqPlan); TheThingsNode *node; -CayenneLPP lpp(16); - -#define PORT_SETUP 1 -#define PORT_INTERVAL 2 -#define PORT_MOTION 3 -#define PORT_BUTTON 4 +CayenneLPP lpp(24); -// Interval between send in seconds, so 300s = 5min -#define CONFIG_INTERVAL ((uint32_t) 300) +uint8_t fport = PORT_SETUP; // LoRaWAN port used -void sendData(uint8_t port = PORT_SETUP, uint32_t duration = 0); +void sendData(uint8_t port=PORT_SETUP, uint32_t duration=0); // This is called on each interval we defined so mainly // this is where we need to do our job -void interval() +void interval(uint8_t wakeReason) { - node->setColor(TTN_BLUE); - debugSerial.println(F("-- SEND: INTERVAL")); - sendData(PORT_INTERVAL); + uint8_t fport = PORT_INTERVAL; + + debugSerial.print(F("-- SEND: INTERVAL 0x")); + debugSerial.print(wakeReason, HEX); + + if (wakeReason&TTN_WAKE_LORA) + { + fport = PORT_LORA; + } + + sendData(fport); } // This is called on each wake, every 8S or by an sensor/button interrupt // if it's watchdog, we mainly do nothing (core IRQ, we don't have choice) // but if it's an interupt it will ne bone ine loop -void wake() +void wake(uint8_t wakeReason) { - debugSerial.println(F("-- WAKE")); - - // Just if you want to see this IRQ with a LED, remove for LOW power - //node->setColor(TTN_GREEN); - //delay(50); - //node->setColor(TTN_BLACK); - //delay(100); + ttn_color ledcolor = TTN_BLACK; + uint8_t ledblink = 0 ; + + debugSerial.print(F("-- WAKE: 0x")); + debugSerial.println(wakeReason, HEX); + + if (wakeReason & (TTN_WAKE_WATCHDOG | TTN_WAKE_INTERVAL)) { + ledcolor = TTN_YELLOW; + ledblink = 1; + if (wakeReason & TTN_WAKE_WATCHDOG) { + debugSerial.print(F(" Watchdog")); + } + if (wakeReason & TTN_WAKE_INTERVAL) { + debugSerial.print(F(" INTERVAL")); + ledblink++; + } + } + if (wakeReason & TTN_WAKE_LORA) { + debugSerial.print(F(" LoRa RNxxxx")); + ledblink = 1; + ledcolor = TTN_GREEN; + } + if (wakeReason & TTN_WAKE_BTN_PRESS) { + debugSerial.print(F(" PRESS")); + } + if (wakeReason & TTN_WAKE_BTN_RELEASE) { + debugSerial.print(F(" RELEASE")); + } + if (wakeReason & TTN_WAKE_MOTION_START) { + ledblink = 1; + ledcolor = TTN_RED; + debugSerial.print(F(" MOTION_START")); + } + if (wakeReason & TTN_WAKE_MOTION_STOP) { + ledblink = 2; + ledcolor = TTN_RED; + debugSerial.print(F(" MOTION_STOP")); + } + if (wakeReason & TTN_WAKE_TEMPERATURE) { + debugSerial.print(F(" TEMPERATURE")); + } + + debugSerial.println(); + + // Just if you want to see this IRQ with a LED + // just uncomment, but take care, not LOW power + //while (ledblink--) { + // node->setColor(ledcolor); + // delay(50); + // node->setColor(TTN_BLACK); + // delay(333); + //} } void sleep() @@ -62,52 +130,76 @@ void sleep() void onButtonRelease(unsigned long duration) { - uint16_t adc_value; uint32_t timepressed = (uint32_t) duration; - node->setColor(TTN_BLUE); - - debugSerial.print(F("-- SEND: BUTTON ")); + debugSerial.print(F("-- SEND: BUTTON: " )); debugSerial.print(timepressed); debugSerial.println(F(" ms")); - sendData(PORT_BUTTON, timepressed); + // If button was pressed for more then 2 seconds + if (timepressed > 2000) { + // blink yellow led for 60 seconds + // this will let us to upload new sketch if needed + for (uint8_t i=0 ; i<60 ; i++) { + node->setColor(TTN_YELLOW); + delay(30); + node->setColor(TTN_BLACK); + delay(470); + } + } + + // then send data + sendData(PORT_BTN_RELEASE, timepressed); } void sendData(uint8_t port, uint32_t value) { + uint16_t volt; + // Wake RN2483 ttn.wake(); - // Read battery voltage - uint16_t vbat = node->getBattery(); + // Prepare cayenne payload + lpp.reset(); - debugSerial.print(F("Battery:\t")); - debugSerial.print(vbat); + // Read battery voltage + volt = node->getBattery(); + debugSerial.print(F("Bat:\t")); + debugSerial.print(volt); debugSerial.println(F("mV")); + lpp.addAnalogInput(4, volt/1000.0); + + // This one is usefull when battery < 2.5V below reference ADC 2.52V + // because in this case reading are wrong, but you can use it + // as soon as VCC < 3.3V, + // when above 3.3V, since regulator fix 3.3V you should read 3300mV + volt = node->getVCC(); + debugSerial.print(F("Vcc:\t")); + debugSerial.print(volt); + debugSerial.println(F("mV")); + lpp.addAnalogInput(5, volt/1000.0); - // Just send battery voltage - lpp.reset(); - lpp.addAnalogInput(4, vbat/1000.0); + // Read value returned by RN2483 module + volt = ttn.getVDD(); + debugSerial.print(F("Vcc:\t")); + debugSerial.print(volt); + debugSerial.println(F("mV")); + lpp.addAnalogInput(6, volt/1000.0); // If button pressed, send press duration // please myDeviceCayenne add counter value type to // avoid us using analog values to send counters - if (port == PORT_BUTTON) { - debugSerial.print(F("Button:\t")); + if (value) + { + debugSerial.print(F("Button pressed for ")); debugSerial.print(value); debugSerial.println(F("ms")); - lpp.addAnalogInput(7, value / 1000.0); + lpp.addAnalogInput(10, value/1000.0); } + node->setColor(TTN_BLUE); + // Send data ttn.sendBytes(lpp.getBuffer(), lpp.getSize(), port); - - // Set RN2483 to sleep mode - ttn.sleep(CONFIG_INTERVAL * 1000); - - // This one is not optionnal, remove it - // and say bye bye to RN2983 sleep mode - delay(50); } void setup() @@ -115,17 +207,19 @@ void setup() loraSerial.begin(57600); debugSerial.begin(9600); - // Wait a maximum of 10s for Serial Monitor - while (!debugSerial && millis() < 10000) - ; + // Wait a maximum of 5s for Serial Monitor + while (!debugSerial && millis() < 5000) { + node->setColor(TTN_RED); + delay(20); + node->setColor(TTN_BLACK); + delay(480); + }; // Config Node, Disable all sensors // Check node schematics here // https://github.com/TheThingsProducts/node node = TheThingsNode::setup(); - // Each interval - node->configInterval(true, CONFIG_INTERVAL*1000); node->onWake(wake); node->onInterval(interval); node->onSleep(sleep); @@ -133,18 +227,35 @@ void setup() // We monitor just button release node->onButtonRelease(onButtonRelease); - // Test sensors and set LED to GREEN if it works + // Test sensors node->showStatus(); - node->setColor(TTN_GREEN); debugSerial.println(F("-- TTN: STATUS")); ttn.showStatus(); + // Each interval (with watchdog) + //node->configInterval(true, CONFIG_INTERVAL*1000); + + // Each interval (with Lora Module and Serial IRQ) + // Take care this one need to be called after any + // first call to ttn.* so object has been instancied + // if not &ttn will be null and watchdog will wake us + node->configInterval(&ttn, CONFIG_INTERVAL*1000); + + // Magenta during join, is joined then green else red debugSerial.println(F("-- TTN: JOIN")); - ttn.join(appEui, appKey); + node->setColor(TTN_BLUE); + if (ttn.join(appEui, appKey)) { + node->setColor(TTN_GREEN); + } else { + node->setColor(TTN_RED); + } debugSerial.println(F("-- SEND: SETUP")); sendData(PORT_SETUP); + + // Enable sleep even connected with USB cable + // node->configUSB(true); } void loop() diff --git a/examples/CayenneLPP/CayenneLPP.ino b/examples/CayenneLPP/CayenneLPP.ino index 91d8b47..339e60d 100644 --- a/examples/CayenneLPP/CayenneLPP.ino +++ b/examples/CayenneLPP/CayenneLPP.ino @@ -1,3 +1,12 @@ +// This sketch sends sensors dta tp Cayenne with LPP format +// but it use advanced Ultra Low Power techniques +// for this it disable all unneeded peripherals during sleep mode, including +// USB Management, this mean you won't be able to upload anymore if the node is sleeping +// when wake up, Lora transmission is approx 3s (including receive windows) this means +// that you have 3 seconds windows to upload, so unless you're lucky, it's almost impossible +// to sync Arduino compilation and upload. But to avoid this, you can press the node button +// for more than 2s, then the led will yellow blink quickly for 60s, letting you time to upload + #include #include @@ -5,12 +14,12 @@ const char *appEui = "0000000000000000"; const char *appKey = "00000000000000000000000000000000"; -#define loraSerial Serial1 -#define debugSerial Serial - // Replace REPLACE_ME with TTN_FP_EU868 or TTN_FP_US915 #define freqPlan REPLACE_ME +#define loraSerial Serial1 +#define debugSerial Serial + TheThingsNetwork ttn(loraSerial, debugSerial, freqPlan); TheThingsNode *node; CayenneLPP lpp(51); @@ -20,38 +29,66 @@ CayenneLPP lpp(51); #define PORT_MOTION 3 #define PORT_BUTTON 4 +// Interval between send in seconds, so 300s = 5min +#define CONFIG_INTERVAL ((uint32_t)300) + +void sendData(uint8_t port=PORT_SETUP, uint32_t duration=0); + void setup() { loraSerial.begin(57600); debugSerial.begin(9600); // Wait a maximum of 10s for Serial Monitor - while (!debugSerial && millis() < 10000) - ; + while (!debugSerial && millis() < 10000) { + node->setColor(TTN_RED); + delay(20); + node->setColor(TTN_BLACK); + delay(480); + }; // Config Node node = TheThingsNode::setup(); node->configLight(true); - node->configInterval(true, 60000); - node->configTemperature(true); + node->configTemperature(false); // Just read temp, no alert monitoring (Low Power) + + // If you want alerts (200uA to 400uA more consumption) + // node->configTemperature(true); // Alerts below 0C and over 30C and critical to 55C + // node->configTemperature(true, R_DEGREES_0_0625, 15, 25, 50); // Alerts below 15C and over 25C and critical to 50C + // node->onTemperatureAlert(yourAlertCallback); // Your alert callback + node->onWake(wake); node->onInterval(interval); node->onSleep(sleep); node->onMotionStart(onMotionStart); node->onButtonRelease(onButtonRelease); - // Test sensors and set LED to GREEN if it works + // Test sensors node->showStatus(); - node->setColor(TTN_GREEN); debugSerial.println("-- TTN: STATUS"); ttn.showStatus(); - debugSerial.println("-- TTN: JOIN"); - ttn.join(appEui, appKey); + // Each interval (with Lora Module and Serial IRQ) + // Take care this one need to be called after any + // first call to ttn.* so object has been instancied + // if not &ttn will be null and watchdog will wake us + node->configInterval(&ttn, CONFIG_INTERVAL*1000); + + // Magenta during join, if joined then green else red + debugSerial.println(F("-- TTN: JOIN")); + node->setColor(TTN_MAGENTA); + if (ttn.join(appEui, appKey)) { + node->setColor(TTN_GREEN); + } else { + node->setColor(TTN_RED); + } debugSerial.println("-- SEND: SETUP"); sendData(PORT_SETUP); + + // Enable sleep even connected with USB cable + // node->configUSB(true); } void loop() @@ -59,16 +96,18 @@ void loop() node->loop(); } -void interval() +void interval(uint8_t wakeReason) { - node->setColor(TTN_BLUE); - - debugSerial.println("-- SEND: INTERVAL"); + debugSerial.print(F("-- SEND: INTERVAL 0x")); + debugSerial.print(wakeReason, HEX); + debugSerial.println(F("ms")); sendData(PORT_INTERVAL); } -void wake() +void wake(uint8_t wakeReason) { + debugSerial.print(F("-- WAKE: 0x")); + debugSerial.println(wakeReason, HEX); node->setColor(TTN_GREEN); } @@ -79,26 +118,59 @@ void sleep() void onMotionStart() { - node->setColor(TTN_BLUE); + node->setColor(TTN_RED); - debugSerial.print("-- SEND: MOTION"); - sendData(PORT_MOTION); + // This move has already been detected + // We need to detect stop motion before sending any new motion + // this avoid flooding network when the device is all time moving + if (node->isMoving()) + { + debugSerial.print("-- STILL MOVING"); + } + else + { + debugSerial.print("-- SEND: START MOTION"); + sendData(PORT_MOTION); + } } void onButtonRelease(unsigned long duration) { - node->setColor(TTN_BLUE); - - debugSerial.print("-- SEND: BUTTON"); - debugSerial.println(duration); + uint32_t timepressed = (uint32_t) duration; + + debugSerial.print(F("-- SEND: BUTTON ")); + debugSerial.print(timepressed); + debugSerial.println(F("ms")); + + // If button was pressed for more then 2 seconds + if (timepressed > 2000) { + // blink yellow led for 60 seconds + // this will let us to upload new sketch if needed + for (uint8_t i=0 ; i<60 ; i++) { + node->setColor(TTN_YELLOW); + delay(30); + node->setColor(TTN_BLACK); + delay(470); + } + } - sendData(PORT_BUTTON); + // then send data + sendData(PORT_BUTTON, timepressed); } -void sendData(uint8_t port) +void sendData(uint8_t port, uint32_t duration) { + uint16_t bat = node->getBattery(); - // Wake RN2483 + // when battery < 3.3V (regulator does not regule output, so + // we use another method to read VCC of the device and this will + // also avoid error if VCC < ADC reference (2.52V) + if (bat <= 3300) + { + bat = node->getVCC(); + } + + // Wake Lora Module ttn.wake(); printSensors(); @@ -107,18 +179,27 @@ void sendData(uint8_t port) lpp.addDigitalInput(1, node->isButtonPressed()); lpp.addDigitalInput(2, node->isUSBConnected()); lpp.addDigitalInput(3, node->isMoving()); - lpp.addAnalogInput(4, node->getBattery()/1000.0); + lpp.addAnalogInput(4, bat/1000.0); lpp.addTemperature(5, node->getTemperatureAsFloat()); lpp.addLuminosity(6, node->getLight()); float x,y,z; node->getAcceleration(&x, &y, &z); lpp.addAccelerometer(7, x, y, z); + + // If button pressed, send press duration + // please myDeviceCayenne add counter value type to + // avoid us using analog values to send counters + if (duration) + { + debugSerial.print(F("Button pressed for ")); + debugSerial.print(duration); + debugSerial.println(F("ms")); + lpp.addAnalogInput(10, duration/1000.0); + } + node->setColor(TTN_BLUE); ttn.sendBytes(lpp.getBuffer(), lpp.getSize(), port); - - // Set RN module to sleep mode - ttn.sleep(60000); } void printSensors() diff --git a/src/TheThingsNode.cpp b/src/TheThingsNode.cpp index b1ce333..96b2108 100644 --- a/src/TheThingsNode.cpp +++ b/src/TheThingsNode.cpp @@ -4,95 +4,91 @@ #include #include -#define TTN_LDR_INPUT 10 -#define TTN_LDR_GAIN1 12 -#define TTN_LDR_GAIN2 4 -#define TTN_RED_LED 13 -#define TTN_GREEN_LED 5 -#define TTN_BLUE_LED 6 -#define TTN_BUTTON 16 -#define TTN_LORA_RESET 21 -#define TTN_VBAT_MEAS_EN A2 -#define TTN_VBAT_MEAS 1 -#define TTN_TEMPERATURE_SENSOR_ADDRESS 0x18 -#define TTN_TEMPERATURE_ALERT 14 -#define TTN_ACCELEROMETER_INT2 9 - -#define TTN_ADDR_ACC 0x1D -#define TTN_DR 5 // active data rate -#define TTN_SR 3 // sleep data rate -#define TTN_SC 4 // sleep delay -#define TTN_MT 4 // 0.063g/LSB -#define TTN_MDC 2 // debounce delay in samples -#define TTN_SYSMOD 0x0B -#define TTN_FF_MT_CFG 0x15 -#define TTN_FF_MT_SRC 0x16 -#define TTN_FF_MT_THS 0x17 -#define TTN_FF_MT_COUNT 0x18 -#define TTN_TRANSIENT_CFG 0x1D -#define TTN_TRANSIENT_SRC 0x1E -#define TTN_TRANSIENT_THS 0x1F -#define TTN_TRANSIENT_COUNT 0x20 -#define TTN_ASLP_CNT 0x29 -#define TTN_CTRL_REG1 0x2A -#define TTN_CTRL_REG2 0x2B -#define TTN_CTRL_REG3 0x2C -#define TTN_CTRL_REG4 0x2D -#define TTN_CTRL_REG5 0x2E - Hackscribble_MCP9804 TTN_TEMPERATURE_SENSOR(TTN_TEMPERATURE_SENSOR_ADDRESS); -volatile bool TTN_TEMPERATURE = false; -volatile bool TTN_MOTION_START = false; -volatile bool TTN_MOTION_STOP = false; -volatile bool TTN_BUTTON_PRESS = false; -volatile bool TTN_BUTTON_RELEASE = false; +volatile uint16_t wakeStatus; volatile uint32_t TTN_INTERVAL = 0; +volatile uint8_t adcIRQCnt; void TTN_TEMPERATURE_FN() { - TTN_TEMPERATURE = true; + wakeStatus |= TTN_WAKE_TEMPERATURE; TTN_TEMPERATURE_SENSOR.clearAlert(); } void TTN_MOTION_FN() { uint8_t trigger = getPinChangeInterruptTrigger(digitalPinToPCINT(TTN_ACCELEROMETER_INT2)); + // Reset according bits + wakeStatus &= ~ (TTN_WAKE_MOTION_START | TTN_WAKE_MOTION_STOP); + if (trigger == RISING) { - TTN_MOTION_START = true; + wakeStatus |= TTN_WAKE_MOTION_START; } else if (trigger == FALLING) { - TTN_MOTION_STOP = true; + wakeStatus |= TTN_WAKE_MOTION_STOP; } } void TTN_BUTTON_FN() { - uint8_t trigger = getPinChangeInterruptTrigger(digitalPinToPCINT(TTN_BUTTON)); - if (trigger == FALLING) + uint16_t i=2000; // approx 16ms (measured) + int16_t btn=0; + + // This loop duration is about 16 ms + // we are in IRQ, no millis() please, but as this IRQ just wake us + // it does not mind if we take some time, do go for software debouncing + while (i-- > 0 ) { - TTN_BUTTON_PRESS = true; + // If button read as press we add 1 else we remove 1 + btn += (digitalRead(TTN_BUTTON) == LOW) ? 1 : -1 ; } - // Be sure main loop ACK press button before rising the release - if (TTN_BUTTON_PRESS == false && trigger == RISING ) + // Assume button ok if we have enouhgt press/release + if (btn > 500) + { + wakeStatus |= TTN_WAKE_BTN_PRESS; + } + else if (btn < 500) + { + wakeStatus |= TTN_WAKE_BTN_RELEASE; + } + else { - TTN_BUTTON_RELEASE = true; + // Bad press, bounce, interference, ... } } +void TTN_SERIAL_LORA_FN() +{ + // Just used to wake up we can now mask this + // interrupt because serial will become verbose + wakeStatus |= TTN_WAKE_LORA; + EIMSK &= ~(1 << INT2); +} + ISR(WDT_vect) { + wakeStatus |= TTN_WAKE_WATCHDOG; TTN_INTERVAL = TTN_INTERVAL + 8000; } +ISR(ADC_vect) +{ + // Increment ADC counter + adcIRQCnt++; +} + + + + /****************************************************************************** * PUBLIC *****************************************************************************/ -void TheThingsNode::onWake(void (*callback)(void)) +void TheThingsNode::onWake(void (*callback)(uint8_t wakeStatus)) { this->wakeCallback = callback; } @@ -127,26 +123,31 @@ void TheThingsNode::loop() } } - if (this->wakeCallback) + // We sure to be called only once when button still pressed + // Button management will start only below this code + if (this->wakeCallback && !this->buttonPressed) { - this->wakeCallback(); + this->wakeCallback(wakeStatus); } - if (TTN_BUTTON_PRESS) + if (wakeStatus & TTN_WAKE_BTN_PRESS) { if (!this->buttonPressed) { + this->buttonPressed = true; + this->buttonPressedAt = millis(); if (this->buttonEnabled && this->buttonPressCallback) { this->buttonPressCallback(); } - this->buttonPressed = true; } - TTN_BUTTON_PRESS = false; - } - - if (TTN_BUTTON_RELEASE) + // ACK our IRQ press, now all is in this-> + wakeStatus &= ~TTN_WAKE_BTN_PRESS; + } + + // At least one loop instance between press and release so else if here + if (wakeStatus & TTN_WAKE_BTN_RELEASE) { if (this->buttonPressed) { @@ -154,12 +155,13 @@ void TheThingsNode::loop() { this->buttonReleaseCallback(millis() - this->buttonPressedAt); } + this->buttonPressedAt = 0; this->buttonPressed = false; } - TTN_BUTTON_RELEASE = false; + wakeStatus &= ~TTN_WAKE_BTN_RELEASE; } - if (TTN_MOTION_START) + if (wakeStatus & TTN_WAKE_MOTION_START) { if (!this->motionStarted) { @@ -170,10 +172,11 @@ void TheThingsNode::loop() } this->motionStarted = true; } - TTN_MOTION_START = false; + // ACK our interrupt + wakeStatus &= ~TTN_WAKE_MOTION_START; } - if (TTN_MOTION_STOP) + if (wakeStatus & TTN_WAKE_MOTION_STOP) { if (this->motionStarted) { @@ -181,56 +184,91 @@ void TheThingsNode::loop() { this->motionStopCallback(millis() - this->motionStartedAt); } + this->motionStartedAt = 0; this->motionStarted = false; } - TTN_MOTION_STOP = false; + // ACK our interrupt + wakeStatus &= ~TTN_WAKE_MOTION_STOP; } - if (TTN_TEMPERATURE) + if (wakeStatus & TTN_WAKE_TEMPERATURE) { if (this->temperatureEnabled && this->temperatureCallback) { this->temperatureCallback(); } - TTN_TEMPERATURE = false; + // ACK our interrupt + wakeStatus &= ~TTN_WAKE_TEMPERATURE; + } + + if (wakeStatus & TTN_WAKE_LORA) + { + // This is mainly our interval + wakeStatus |= TTN_WAKE_INTERVAL; + + // ACK our interrupt + wakeStatus &= ~TTN_WAKE_LORA; + } + + if (wakeStatus & TTN_WAKE_WATCHDOG) + { + if (TTN_INTERVAL >= this->intervalMs) { + wakeStatus |= TTN_WAKE_INTERVAL; + } + // ACK our interrupt + wakeStatus &= ~TTN_WAKE_WATCHDOG; } - if (TTN_INTERVAL >= this->intervalMs) + if (wakeStatus & TTN_WAKE_INTERVAL) { if (this->intervalEnabled && this->intervalCallback) { - this->intervalCallback(); + this->intervalCallback(wakeStatus); } + // Ack our interval interrupt + wakeStatus &= ~TTN_WAKE_INTERVAL; TTN_INTERVAL = 0; } - if (this->sleepCallback) - { - this->sleepCallback(); - } + // If button pushed manage loop faster + uint16_t dly = this->buttonPressed ? 10 : 100; + // USB is connected and so sleep on USB if (this->isUSBConnected() && !this->USBDeepSleep) { + if (!this->buttonPressed) { + setColor(TTN_BLACK); + } - while (!TTN_BUTTON_PRESS && !TTN_BUTTON_RELEASE && !TTN_MOTION_START && !TTN_MOTION_STOP && !TTN_TEMPERATURE && TTN_INTERVAL < this->intervalMs) + // Loop until pseudo wake event (because we're not sleeping) or interval + while (!(wakeStatus & TTN_WAKE_ANY) && this->intervalMs > TTN_INTERVAL) { - delay(100); - TTN_INTERVAL = TTN_INTERVAL + 100; + delay(dly); + TTN_INTERVAL = TTN_INTERVAL + dly; } } else { // Don't go to sleep mode while button is still pressed // because if so, timer of ms will be stopped and duration - // of pressed button will not work + // of pressed button will not work, this acting like a debounce if (this->buttonPressed) { - delay(100); - TTN_INTERVAL = TTN_INTERVAL + 100; - } else { + delay(dly); + TTN_INTERVAL = TTN_INTERVAL + dly; + } + else + { + if (this->sleepCallback) + { + this->sleepCallback(); + } + Serial.flush(); deepSleep(); } } + + } void TheThingsNode::onSleep(void (*callback)(void)) @@ -293,16 +331,24 @@ void TheThingsNode::showStatus() void TheThingsNode::configInterval(bool enabled, uint32_t ms) { this->intervalMs = ms; + this->pttn = NULL; + this->intervalEnabled = enabled; +} - configInterval(enabled); +void TheThingsNode::configInterval(TheThingsNetwork *ttn, uint32_t ms) +{ + this->intervalMs = ms; + this->pttn = ttn; + this->intervalEnabled = true; } void TheThingsNode::configInterval(bool enabled) { + this->pttn = NULL; this->intervalEnabled = enabled; } -void TheThingsNode::onInterval(void (*callback)(void)) +void TheThingsNode::onInterval(void (*callback)(uint8_t wakeStatus)) { this->intervalCallback = callback; @@ -327,7 +373,23 @@ void TheThingsNode::configLight(bool enabled) return; } - if (enabled) + // Ok be sure to set it to low power mode + if (!enabled) + { + digitalWrite(TTN_LDR_GAIN1, LOW); + digitalWrite(TTN_LDR_GAIN2, LOW); + // Just to be sure, see datasheet, at least 1.5ms to enable Low Power + delay(2); + } + + this->lightEnabled = enabled; +} + +uint16_t TheThingsNode::getLight() +{ + uint16_t value = 0; + + if ( this->lightEnabled) { switch (this->lightGain) { @@ -348,21 +410,20 @@ void TheThingsNode::configLight(bool enabled) digitalWrite(TTN_LDR_GAIN2, HIGH); break; } - } - else - { + // Wait to settle + delay(1); + + // Read value + value = analogRead(TTN_LDR_INPUT); + + // Go back to sleep mode digitalWrite(TTN_LDR_GAIN1, LOW); digitalWrite(TTN_LDR_GAIN2, LOW); + // Just to be sure, see datasheet, at least 1.5ms to enable Low Power + delay(2); } - this->lightEnabled = enabled; -} - -uint16_t TheThingsNode::getLight() -{ - // TODO: if (this->lightEnabled) - - return analogRead(TTN_LDR_INPUT); + return value; } /****************************************************************************** @@ -691,6 +752,104 @@ void TheThingsNode::setColor(ttn_color color) /****************************************************************************** * BATTERY */ +uint16_t TheThingsNode::readADCLowNoise(bool average) +{ + uint8_t low, high; + uint16_t sum = 0; + + // Start 1st Conversion, but ignore it, can be hazardous + ADCSRA |= _BV(ADSC); + + // wait for first dummy conversion + while (bit_is_set(ADCSRA,ADSC)) + { + }; + + // Init our measure counter + adcIRQCnt = 0; + + // We want to have a interrupt when the conversion is done + ADCSRA |= _BV(ADIE); + + // Loop thru samples + // 8 samples (we don't take the 1st one) + do { + // Enable Noise Reduction Sleep Mode + set_sleep_mode(SLEEP_MODE_ADC); + sleep_enable(); + + // Wait until conversion is finished + do { + // enabled IRQ before sleeping + sei(); + sleep_cpu(); + cli(); + } + // Check is done with interrupts disabled to avoid a race condition + while (bit_is_set(ADCSRA, ADSC)); + + // No more sleeping + sleep_disable(); + sei(); + + // read low first + low = ADCL; + high = ADCH; + + // Sum the total + sum += ((high << 8) | low); + } + while (adcIRQCnt<8); + + // No more interrupts needed for this + // we finished the job + ADCSRA &= ~ _BV( ADIE ); + + // Return the average divided by 8 (8 samples) + return (average ? sum >> 3 : sum); +} + +uint16_t TheThingsNode::getVCC() +{ + uint16_t value; + uint16_t vcc; + + // Enable ADC (just in case going out of low power) + power_adc_enable(); + ADCSRA |= _BV(ADEN); + + // Read 1.1V reference against AVcc + // REFS1 REFS0 --> 0 1, AVcc internal ref. -Selects AVcc external reference + // MUX4 MUX3 MUX2 MUX1 MUX0 --> 011110 1.1V (VBG) -Selects channel 14, bandgap voltage, to measure + ADMUX = (0< 5500) + { + vcc = 5500; + } + + // Vcc in millivolts + return vcc; +} + uint16_t TheThingsNode::getBattery() { @@ -701,6 +860,10 @@ uint16_t TheThingsNode::getBattery() return batteryVoltage; } + + + + /****************************************************************************** * USB */ @@ -748,14 +911,13 @@ TheThingsNode::TheThingsNode() pinMode(TTN_BLUE_LED, OUTPUT); setColor(TTN_BLACK); - // hardware reset of LoRa module, so module is reset on sketch upload - #ifdef TTN_LORA_RESET + // reset RN2xx3 module, this allow to reset module on sketch upload also +#ifdef TTN_LORA_RESET pinMode(TTN_LORA_RESET, OUTPUT); digitalWrite(TTN_LORA_RESET, LOW); delay(100); digitalWrite(TTN_LORA_RESET, HIGH); - #endif - +#endif // TODO: Can we enable/disable this at will to save memory? USBCON |= (1 << OTGPADE); @@ -790,6 +952,14 @@ void TheThingsNode::wakeTemperature() TTN_TEMPERATURE_SENSOR.setMode(MODE_CONTINUOUS); + // If was in powerdown mode, let time to convert temperature + if (!this->temperatureEnabled) + { + // See datasheet 5.2.4 (added 5ms each for security) + uint8_t dly[4] = { 35, 70, 135, 255}; + delay( dly[TTN_TEMPERATURE_SENSOR.getResolution()]); + } + this->temperatureSleep = false; } @@ -864,7 +1034,7 @@ void TheThingsNode::WDT_start() cli(); MCUSR &= ~(1 << WDRF); WDTCSR |= (1 << WDCE) | (1 << WDE); - WDTCSR = 1 << WDP0 | 0 << WDP1 | 0 << WDP2 | 1 << WDP3; /* 2.0 seconds */ + WDTCSR = 1 << WDP0 | 0 << WDP1 | 0 << WDP2 | 1 << WDP3; /* 8.0 seconds */ WDTCSR |= _BV(WDIE); sei(); @@ -890,20 +1060,56 @@ void TheThingsNode::WDT_stop() void TheThingsNode::deepSleep(void) { - ADCSRA &= ~_BV(ADEN); + // We want to be awake by LoRa module ? + if (this->pttn) { + // watchdog Not needed, avoid wake every 8S + WDT_stop(); + + // Set LoRa module sleep mode + this->pttn->sleep(this->intervalMs); + // This one is not optionnal, remove it + // and say bye bye to RN2483 or RN2903 sleep mode + delay(50); + + // Module need to wake us with interrupt + attachInterrupt(TTN_LORA_SERIAL_RX_INT, TTN_SERIAL_LORA_FN, FALLING); + + // switch all interrupts off while messing with their settings + cli(); + bitSet(EIFR,INTF2); // clear any pending interrupts for serial RX pin (INT2 D0) + sei(); + } else { + // watchdog needed for wakeup + WDT_start(); + } + + bitClear(ADCSRA, ADEN); + bitSet(MCUCR, JTD); + bitSet(USBCON,FRZCLK); // Disable USB clock + bitClear(PLLCSR, PLLE); // Disable USB PLL + bitClear(USBCON, USBE); // Disable USB + bitClear(UHWCON, UVREGE); // Disable USB Regulator set_sleep_mode(SLEEP_MODE_PWR_DOWN); - MCUCR |= (1 << JTD); - USBCON |= (1 << FRZCLK); - //USBCON &= ~_BV(USBE); - PLLCSR &= ~_BV(PLLE); sleep_enable(); sleep_mode(); //Sweet dreams! //wake up, after ISR we arrive here -> sleep_disable(); - PLLCSR |= (1 << PLLE); power_all_enable(); - //USBCON |= (1 << USBE); - USBCON &= ~_BV(FRZCLK); - ADCSRA |= (1 << ADEN); -} \ No newline at end of file + + // Be sure any com to LoRa module don't fire a IRQ + cli(); + EIMSK &= ~(1 << INT2); // Mask interrupt line + bitSet(EIFR,INTF2); // clear any pending interrupts for serial RX pin (INT2 D0) + sei(); + + bitSet(ADCSRA, ADEN); + + // We need to enable back USB there, because we have + // some debug print in code and if not enabled it will + // lock the program, but since it's only when we're awake + // it's not a real Low Power issue + USBDevice.attach(); +} + + diff --git a/src/TheThingsNode.h b/src/TheThingsNode.h index ee60f5f..0c55542 100644 --- a/src/TheThingsNode.h +++ b/src/TheThingsNode.h @@ -16,6 +16,58 @@ #include #include "TheThingsNetwork.h" +#define TTN_LDR_INPUT 10 +#define TTN_LDR_GAIN1 12 +#define TTN_LDR_GAIN2 4 +#define TTN_RED_LED 13 +#define TTN_GREEN_LED 5 +#define TTN_BLUE_LED 6 +#define TTN_BUTTON 16 +#define TTN_LORA_RESET 21 /* Hardware reset pin of LoRa module */ +#define TTN_LORA_SERIAL_RX_INT INT2 /* Serial RX Interrupt number (to be waked by RN2483 or RN2903 module) */ +#define TTN_VBAT_MEAS_EN A2 +#define TTN_VBAT_MEAS 1 +#define TTN_TEMPERATURE_SENSOR_ADDRESS 0x18 +#define TTN_TEMPERATURE_ALERT 14 +#define TTN_ACCELEROMETER_INT2 9 + +#define TTN_ADDR_ACC 0x1D +#define TTN_DR 5 // active data rate +#define TTN_SR 3 // sleep data rate +#define TTN_SC 4 // sleep delay +#define TTN_MT 4 // 0.063g/LSB +#define TTN_MDC 2 // debounce delay in samples +#define TTN_SYSMOD 0x0B +#define TTN_FF_MT_CFG 0x15 +#define TTN_FF_MT_SRC 0x16 +#define TTN_FF_MT_THS 0x17 +#define TTN_FF_MT_COUNT 0x18 +#define TTN_TRANSIENT_CFG 0x1D +#define TTN_TRANSIENT_SRC 0x1E +#define TTN_TRANSIENT_THS 0x1F +#define TTN_TRANSIENT_COUNT 0x20 +#define TTN_ASLP_CNT 0x29 +#define TTN_CTRL_REG1 0x2A +#define TTN_CTRL_REG2 0x2B +#define TTN_CTRL_REG3 0x2C +#define TTN_CTRL_REG4 0x2D +#define TTN_CTRL_REG5 0x2E + +#define TTN_WAKE_TEMPERATURE 0x0001 +#define TTN_WAKE_MOTION_START 0x0002 +#define TTN_WAKE_MOTION_STOP 0x0004 +#define TTN_WAKE_BTN_PRESS 0x0008 +#define TTN_WAKE_BTN_RELEASE 0x0010 +#define TTN_WAKE_LORA 0x0020 +#define TTN_WAKE_WATCHDOG 0x0040 +#define TTN_WAKE_INTERVAL 0x0080 + +#define TTN_WAKE_ANY ( TTN_WAKE_TEMPERATURE | \ + TTN_WAKE_MOTION_START | TTN_WAKE_MOTION_STOP | \ + TTN_WAKE_BTN_PRESS | TTN_WAKE_BTN_RELEASE | \ + TTN_WAKE_LORA | \ + TTN_WAKE_WATCHDOG ) + enum ttn_color : byte { TTN_RED, @@ -35,6 +87,8 @@ class TheThingsNode TheThingsNode(TheThingsNode const &); void operator=(TheThingsNode const &); + TheThingsNetwork *pttn; + bool intervalEnabled; uint32_t intervalMs; uint32_t intervalSince; @@ -52,14 +106,14 @@ class TheThingsNode bool USBDeepSleep; bool wdtStarted; - void (*wakeCallback)(void); + void (*wakeCallback)(uint8_t wakeReason); void (*sleepCallback)(void); void (*temperatureCallback)(void); void (*motionStartCallback)(void); void (*motionStopCallback)(unsigned long duration); void (*buttonPressCallback)(void); void (*buttonReleaseCallback)(unsigned long duration); - void (*intervalCallback)(void); + void (*intervalCallback)(uint8_t wakeReason); void wakeTemperature(); void sleepTemperature(); @@ -78,15 +132,16 @@ class TheThingsNode return &node; }; - void onWake(void (*callback)(void)); + void onWake(void (*callback)(uint8_t wakeReason)); void loop(); void onSleep(void (*callback)(void)); void showStatus(); - void onInterval(void (*callback)(void)); + void onInterval(void (*callback)(uint8_t wakeReason)); void configInterval(bool enabled, uint32_t ms); void configInterval(bool enabled); + void configInterval(TheThingsNetwork *pttn, uint32_t ms); void configLight(bool enabled, uint8_t gain); void configLight(bool enabled); @@ -126,6 +181,8 @@ class TheThingsNode void configUSB(bool deepSleep); uint16_t getBattery(); + uint16_t readADCLowNoise(bool average); + uint16_t getVCC(); }; #endif diff --git a/test/TheThingsNode/TheThingsNode.ino b/test/TheThingsNode/TheThingsNode.ino index f010173..4b1e10f 100644 --- a/test/TheThingsNode/TheThingsNode.ino +++ b/test/TheThingsNode/TheThingsNode.ino @@ -4,7 +4,7 @@ TheThingsNode *node; void setup() { node = TheThingsNode::setup(); - node->onWake(voidFn); + node->onWake(wakeFn); node->onSleep(voidFn); node->onInterval(onInterval); node->configInterval(false, 60000); @@ -37,6 +37,7 @@ void setup() { } void voidFn() {} +void wakeFn(uint8_t wakeStatus) {} void durationFn(unsigned long duration) {} @@ -44,7 +45,7 @@ void loop() { node->loop(); } -void onInterval() { +void onInterval(uint8_t wakeStatus) { node->showStatus(); uint16_t light = node->getLight(); int8_t tempFloat = node->getTemperatureAsFloat();