@rocketscream
I’m sending a packet every 5 min (approx), so I don’t think I’m violating the duty cyle. In fact, the Watchdog wake me 37 times on which I’m going back to sleep immediately. the 38 wake, I’m powering sensors, do measure, send packet and going to sleep again
For those asked here a skeleton of my code, I removed lot of debug and sensor management, but you’ve got the main concept. Note that push button help me to do different actions depending on how much time I press it
// Schedule TX every this many seconds (multiple of 8 due to watchdog).
// Takr care of to duty cycle limitations).
#define TX_INTERVAL 300
// Watchdog count between transmit
#define WDT_WAKE_COUNT (TX_INTERVAL/8)
// Some counter demo used in IRQ
volatile uint32_t iWakeCounter = 0;
volatile uint32_t iSwitchCounter = 0 ;
volatile uint32_t iWatchdogCounter = 0 ;
volatile uint8_t iIrq=0;
bool timeToSleep = false;
// give ULPNode instance
ULPNode ulpn;
/* ======================================================================
Function: wakeInterruptHandler
Purpose : IRQ Handler called when external device wake us
Input : -
Output : -
Comments: once fired this interrupt disable itself
====================================================================== */
void wakeInterruptHandler(void)
{
// Inc counter and set flag for main loop
iWakeCounter++;
iIrq |= SLEEP_WAKE_EXT;
}
/* ======================================================================
Function: switchInterruptHandler
Purpose : IRQ Handler called when switch is pressed/released (for wake)
Input : -
Output : -
Comments: once fired this interrupt disable itself
====================================================================== */
void switchInterruptHandler(void)
{
// Inc counter and set flag for main loop
iSwitchCounter++;
iIrq |= SLEEP_WAKE_SWITCH;
}
/* ======================================================================
Function: watchdogInterruptHandler
Purpose : IRQ Handler called when watchdog IRQ occurs
Input : -
Output : -
Comments: once fired this interrupt disable the watchdog
====================================================================== */
void watchdogInterruptHandler(void)
{
// Inc counter and set flag for main loop
iWatchdogCounter++;
iIrq |= SLEEP_WAKE_WATCHDOG ;
}
/* ======================================================================
Function: onEvent
Purpose : called my LMIC stack on event received
Input : event type
Output : -
Comments: -
====================================================================== */
void onEvent (ev_t ev) {
static unsigned long last_time=0;
unsigned long now = millis() / 1000;
showTime(now);
DebugF(" ("); showTime(now-last_time); DebugF(") ");
last_time = now;
switch(ev) {
case EV_SCAN_TIMEOUT: DebuglnF("EV_SCAN_TIMEOUT"); break;
case EV_BEACON_FOUND: DebuglnF("EV_BEACON_FOUND"); break;
case EV_BEACON_MISSED: DebuglnF("EV_BEACON_MISSED"); break;
case EV_BEACON_TRACKED: DebuglnF("EV_BEACON_TRACKED"); break;
case EV_JOINING: DebuglnF("EV_JOINING"); break;
case EV_RFU1: DebuglnF("EV_RFU1"); break;
case EV_JOIN_FAILED: DebuglnF("EV_JOIN_FAILED"); break;
case EV_REJOIN_FAILED: DebuglnF("EV_REJOIN_FAILED"); break;
case EV_LOST_TSYNC: DebuglnF("EV_LOST_TSYNC"); break;
case EV_RESET: DebuglnF("EV_RESET"); break;
case EV_RXCOMPLETE: DebuglnF("EV_RXCOMPLETE"); break;
case EV_LINK_DEAD: DebuglnF("EV_LINK_DEAD"); break;
case EV_LINK_ALIVE: DebuglnF("EV_LINK_ALIVE"); break;
case EV_SCAN_FOUND: DebuglnF("EV_SCAN_FOUND"); break;
case EV_TXSTART: DebuglnF("EV_TXSTART"); break;
case EV_TXCOMPLETE:
DebugF("EV_TXCOMPLETE ");
// Remove timeout job;
os_clearCallback(&timeoutjob);
if (LMIC.txrxFlags & TXRX_ACK) {
DebugF("with ACK");
DebugFlush();
ulpn.RGBBlink(2, RGB_GREEN, WDTO_120MS);
} else {
// Needed ACK didn't received it ?
if ( send_packet_ack) {
ulpn.RGBBlink(1, RGB_RED, WDTO_120MS);
}
}
Debugln();
if (LMIC.dataLen) {
DebugF("Received ");
Debugln(LMIC.dataLen);
DebuglnF(" bytes");
DebugFlush();
ulpn.RGBBlink(2, RGB_BLUE, WDTO_120MS);
}
ulpn.RGBShow(RGB_OFF);
// we done
timeToSleep = true;
break;
case EV_JOINED: {
// Disable link check validation (automatically enabled
// during join, but not supported by TTN at this time).
LMIC_setLinkCheckMode(0);
// Ok send our first data in 10 ms
os_setTimedCallback(&sendjob, os_getTime() + ms2osticks(10), do_send);
}
break;
default:
DebugF("Unknown event #");
Debugln(ev);
break;
}
}
/* ======================================================================
Function: do_send
Purpose : send LoraWAN packet
Input :
Output : -
Comments: -
====================================================================== */
void do_send(osjob_t* j)
{
static uint16_t frameCounter=0;
// Check if there is not a current TX/RX job running
if (LMIC.opmode & OP_TXRXPEND) {
#if DEBUG > 1
showTime(millis() / 1000);
DebuglnF(" OP_TXRXPEND, not sending");
#endif
} else if (LMIC.opmode & OP_JOINING) {
#if DEBUG > 1
showTime(millis() / 1000);
DebuglnF(" OP_JOINING, not sending");
#endif
} else {
uint8_t len = 0;
uint8_t payload[32] ; // Max, not all will be used, len is calculated on each data added
uint8_t *p=&payload[0];
// sensors reading + payload creation
// ...
// ...
ulpn.setDevice(DEVICE_SENSORS_OFF);
// calculate Len of packet we created
len = p - &payload[0];
// Send Data
LMIC_setTxData2(1, payload, len, send_packet_ack);
}
}
/* ======================================================================
Function: setup
Purpose : setup initial config
Input :
Output : -
Comments: -
====================================================================== */
void setup() {
uint8_t tmp;
// Init ULPNode I/O, Radio; Vbat
ulpn.init();
// Enable global watchdog to avoid lockups
ulpn.setWatchdog(APP_WATCHDOG_TO);
ulpn.RGBShow(RGB_OFF);
// Define IRQ callbacks we need in "user space"
// Here we want callbacks of
// watchdog, wake and switch push button
ulpn.attachWakeInterrupt( wakeInterruptHandler );
ulpn.attachSwitchInterrupt( switchInterruptHandler );
ulpn.attachWatchdogInterrupt( watchdogInterruptHandler );
// Give power to sensors
ulpn.setDevice(DEVICE_SENSORS_ON);
SERIAL_DEBUG.begin(SERIAL_PORT_SPEED);
// Do a I2C scan, this will look for known devices and
// set the accordings flags to global status
// You can use it for debug
if ( (tmp=ulpn.i2cScan()) > 0 ) {
ulpn.RGBBlink(tmp, RGB_PINK, WDTO_120MS);
}
os_getBattLevel();
// LMIC init
os_init();
// Reset the MAC state. Session and pending data transfers will be discarded.
LMIC_reset();
// Enable data rate adaptation
LMIC_setAdrMode(1);
// Increase RX1 Windows by 1% in case of clock error on board (crystal shift)
// This clearly increase son OTAA Join request to works first time even with SF7
LMIC_setClockError(MAX_CLOCK_ERROR * 1 / 100);
// Join the network, sending will be
// started after the event "Joined"
LMIC_startJoining();
timeToSleep = false;
}
/* ======================================================================
Function: loop
Purpose : main loop
Input : -
Output : -
Comments: -
====================================================================== */
void loop() {
static uint8_t wdt_period = APP_WATCHDOG_8S;// in 8S (set to APP_WATCHDOG_NONE for external wake only)
static uint8_t wdt_count = WDT_WAKE_COUNT; // number of WDT wake between transmit
static bool led_state ;
bool new_led_state ;
int16_t send_packet_ms = 0; // Delay sending packet in xxx ms
uint32_t WakeCounter = 0;
uint32_t SwitchCounter = 0 ;
uint32_t WatchdogCounter = 0;
uint8_t IrqTrigger = 0;
// action to be done with button, default none
btn_action_e SwitchAction = BTN_NONE;
// Need to go sleeping ?
if (timeToSleep) {
// Wait n WDT wake to do things
while (wdt_count--) {
goSleeping( SLEEP_BOD_OFF | SLEEP_WAKE_EXT | SLEEP_WAKE_SWITCH, wdt_period );
// IRQ are disabled, it's safe to get these values
WakeCounter = iWakeCounter;
SwitchCounter = iSwitchCounter;
WatchdogCounter = iWatchdogCounter;
IrqTrigger = iIrq;
// Only Watchdog
if (IrqTrigger == SLEEP_WAKE_WATCHDOG && wdt_count) {
// Ack this IRQ
IrqTrigger &= ~SLEEP_WAKE_WATCHDOG;
} else {
// Break of while exit sleep mode if it's other IRQ
wdt_count =0;
}
}
// Restart out watchdog count counter
wdt_count = WDT_WAKE_COUNT;
// Set to true for next loop
// will be reset in case ne need to transmit
timeToSleep = true;
}
// Enable global watchdog to avoid lockups
ulpn.setWatchdog(APP_WATCHDOG_TO);
// Ok loop in case we've been triggered by differents IRQ
// we need to proccess all IRQ
// I don't think this could happen, but does not hurt to check
while (IrqTrigger) {
// Reset RGB default color to none
ulpn.RGBSetColor(RGB_OFF);
// Waked by external Wake
if (IrqTrigger & SLEEP_WAKE_EXT ) {
// Ack this IRQ
IrqTrigger &= ~SLEEP_WAKE_EXT;
ulpn.RGBShow(RGB_PINK);
// Need to send a packet in 100 ms
send_packet_ms = 100;
// Waked by push button
} else if (IrqTrigger & SLEEP_WAKE_SWITCH ) {
// Get switch port state
uint8_t button_port = digitalRead(SWITCH_PIN);
// Ack this IRQ
IrqTrigger &= ~SLEEP_WAKE_SWITCH;
// Button pressed
if (button_port==BTN_PRESSED) {
btn_state_e btn_state;
// we enter into the loop to manage
// the function that will be done
// depending on button press duration
do {
// keep watching the push button:
btn_state = ulpn.buttonManageState(button_port);
if (btn_state == BTN_WAIT_LONG_RELEASE)
ulpn.setDevice(DEVICE_LED_OFF);
// read new state button
button_port = digitalRead(SWITCH_PIN);
// Pat the dog, this loop can be as long
// as button is pressed
wdt_reset();
}
// we loop until button state machine finished
while (btn_state != BTN_WAIT_PUSH);
// Get and save action we need to do after button analyze
SwitchAction = ulpn.buttonAction();
// If button still pressed
}
} else if (IrqTrigger & SLEEP_WAKE_WATCHDOG ) {
// Waked by watchdog
// Ack this IRQ
IrqTrigger &= ~SLEEP_WAKE_WATCHDOG;
// Need to send a packet in 100 ms
send_packet_ms = 100;
} else if ( IrqTrigger ) {
// Another Wake ? weird !!!
// ACK all other parasite IRQ, except the one we're dealing on
IrqTrigger &= ( SLEEP_WAKE_SWITCH | SLEEP_WAKE_WATCHDOG ) ;
}
// On button timeout we do absolutely nothing
if ( SwitchAction != BTN_TIMEOUT) {
// What action we want to do depending on button press ?
if (SwitchAction != BTN_NONE ) {
if (SwitchAction==BTN_BAD_PRESS) {
}
if (SwitchAction==BTN_QUICK_PRESS) {
// Will send a packet
send_packet_ms = 10;
}
// Button pressed between 1 and 2 seconds
if (SwitchAction==BTN_PRESSED_12) {
// Invert ACK Mode
if (send_packet_ack) {
send_packet_ack = false;
ulpn.RGBBlink(2, RGB_RED, WDTO_120MS);
} else {
send_packet_ack = true;
ulpn.RGBBlink(2, RGB_GREEN, WDTO_120MS);
}
}
// Button pressed between 2 and 3 seconds ?
if ( SwitchAction==BTN_PRESSED_23) {
// disable watchdog wake (now only external interrupts
wdt_period = APP_WATCHDOG_NONE;
}
if (SwitchAction==BTN_PRESSED_34) {
// enable watchdog wake
wdt_period = APP_WATCHDOG_8S;
}
if (SwitchAction==BTN_PRESSED_45) {
}
if (SwitchAction==BTN_TIMEOUT) {
}
} // we had a button press
} // if not button time out
// Pat the dog
wdt_reset();
} // While IrqTrigger
// something to send
if (send_packet_ms) {
timeToSleep = false;
os_setTimedCallback(&sendjob, os_getTime()+ ms2osticks(send_packet_ms), do_send);
send_packet_ms = 0;
}
// We've done all our IRQ, ACK them !!!
cli();
iIrq = 0;
sei();
// Pat the dog
wdt_reset();
// Don't forget LMIC STACK
os_runloop_once();
// All follow is Led management
// Let join at the begining of if sequence,
// is prior to send because joining state send data
// Joining Quick blink 50ms on each 1/5 second
if ( LMIC.opmode & (OP_JOINING | OP_REJOIN) ) {
//new_led_state = ((millis() % 200) < 50) ? HIGH : LOW;
new_led_state = ((millis() % 150) < 10) ? HIGH : LOW;
// If sensors detected
if (ulpn.status() & ( RF_NODE_STATE_SENSOR) ) {
// Join deal with GREEN
ulpn.RGBSetColor(RGB_GREEN);
} else {
// Join deal with RED
ulpn.RGBSetColor(RGB_RED);
}
}
// Small blink 100ms on each 1/2sec
if (LMIC.opmode & (OP_TXDATA | OP_TXRXPEND)) {
// Sending and not joining else keep join speed
if ( !(LMIC.opmode & (OP_JOINING | OP_REJOIN)) ) {
new_led_state = ((millis() % 500) < 10) ? HIGH : LOW;
}
// If sensors detected
if (ulpn.status() & ( RF_NODE_STATE_SENSOR) ) {
// Send deal with BLUE + GREEN
ulpn.RGBSetColor(RGB_CYAN);
} else {
// Send deal with BLUE + RED
ulpn.RGBSetColor(RGB_PINK);
}
}
// This should not happen but blink yellow to see
if ( LMIC.opmode & (OP_TXDATA | OP_TXRXPEND | OP_JOINING | OP_REJOIN) == 0 ) {
new_led_state = ((millis() % 2000) < 200) ? HIGH : LOW;
// Other all is off RED + GREEN
ulpn.RGBSetColor(RGB_YELLOW);
}
// led need to change state ?
// avoid digitalWrite() for nothing
if (led_state != new_led_state) {
if (new_led_state == HIGH) {
ulpn.RGBShow();
} else {
ulpn.RGBShow(RGB_OFF);
}
led_state = new_led_state;
}
}