├── .gitattributes ├── .gitignore ├── EEPROM_routines.h ├── MailboxNotifier.ino ├── MailboxNotifier1284P └── MailboxNotifier1284P.ino ├── MailboxNotifier1284P_V2 ├── MailboxNotifier1284P_V2.ino └── State Diagram.vsdx ├── MailboxNotifier1284P_V3 └── MailboxNotifier1284P_V3.ino ├── MailboxNotifierCubeCell └── MailboxNotifierCubeCell.ino ├── Node-Red flow.txt └── README.md /.gitattributes: -------------------------------------------------------------------------------- 1 | # Auto detect text files and perform LF normalization 2 | * text=auto 3 | -------------------------------------------------------------------------------- /.gitignore: -------------------------------------------------------------------------------- 1 | *credentials*.* 2 | *Credentials*.* 3 | TTN_Credentials*.* 4 | -------------------------------------------------------------------------------- /EEPROM_routines.h: -------------------------------------------------------------------------------- 1 | lmic_t EEPROM_LMIC; 2 | #define EEPROM_SIZE 1000 3 | 4 | 5 | int writeEEPROM(const lmic_t & value) { 6 | const byte * p = (const byte*) &value; 7 | int i; 8 | for (i = 0; i < sizeof value; i++) { 9 | byte EE = (*p++); 10 | EEPROM.write(i, EE); 11 | Serial.print(EE); 12 | } 13 | Serial.println(); 14 | Serial.println(i); 15 | EEPROM.commit(); 16 | return i; 17 | } 18 | 19 | unsigned int readEEPROM(lmic_t & value) { 20 | byte * p = (byte*) &value; 21 | unsigned int i; 22 | byte _hi; 23 | for (i = 1; i < sizeof value; i++) { 24 | _hi = EEPROM.read(i); 25 | *p++ = _hi; 26 | Serial.print(_hi); 27 | } 28 | Serial.println(); 29 | Serial.println(i); 30 | return i; 31 | } 32 | 33 | void SaveLMICtoEEPROM(int deepsleep_sec) { 34 | EEPROM_LMIC = LMIC; 35 | // EU Like Bands 36 | 37 | // System time is resetted after sleep. So we need to calculate the dutycycle 38 | // with a resetted system time 39 | unsigned long now = millis(); 40 | 41 | #if defined(CFG_LMIC_EU_like) 42 | for (int i = 0; i < MAX_BANDS; i++) { 43 | ostime_t correctedAvail = 44 | EEPROM_LMIC.bands[i].avail - 45 | ((now / 1000.0 + deepsleep_sec) * OSTICKS_PER_SEC); 46 | if (correctedAvail < 0) { 47 | correctedAvail = 0; 48 | } 49 | EEPROM_LMIC.bands[i].avail = correctedAvail; 50 | } 51 | 52 | EEPROM_LMIC.globalDutyAvail = EEPROM_LMIC.globalDutyAvail - 53 | ((now / 1000.0 + deepsleep_sec) * OSTICKS_PER_SEC); 54 | if (EEPROM_LMIC.globalDutyAvail < 0) { 55 | EEPROM_LMIC.globalDutyAvail = 0; 56 | } 57 | #else 58 | Serial 59 | .println("No DutyCycle recalculation function!") 60 | #endif 61 | EEPROM.put(0, EEPROM_LMIC); 62 | EEPROM.commit(); 63 | Serial.println(); 64 | Serial.println("EEPROM Saved"); 65 | } 66 | 67 | // Load LMIC structure from EEPROM to avoid re-joining 68 | void LoadLMICfromEEPROM() { 69 | EEPROM.get(0, EEPROM_LMIC); 70 | if (EEPROM_LMIC.seqnoUp != 0) { 71 | Serial.println("Retrieving LMICdata"); 72 | LMIC = EEPROM_LMIC; 73 | } 74 | else { 75 | LMIC.seqnoUp = 0; 76 | Serial.println("No valid EEPROM data found"); 77 | } 78 | } 79 | -------------------------------------------------------------------------------- /MailboxNotifier.ino: -------------------------------------------------------------------------------- 1 | /* 2 | 3 | Based on this example: https://github.com/JackGruber/ESP32-LMIC-DeepSleep-example 4 | https://jackgruber.github.io/2020-04-13-ESP32-DeepSleep-and-LoraWAN-OTAA-join/ 5 | 6 | Use TTGO LoRa V1 Board 7 | 8 | */ 9 | 10 | #define TTN_APPEUI { 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00 } // TTN Application EUI with "lsb" 11 | #define TTN_DEVEUI { 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00 } // TTN Device EUI with "lsb" 12 | #define TTN_APPKEY { 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00 } // TTN App Key with "msb" 13 | 14 | #define POWERPIN 4 15 | #define BATTPIN 39 16 | #define BATTFACTOR 0.0857 17 | 18 | 19 | #include 20 | #include 21 | #include 22 | #include 23 | #include 24 | #include "EEPROM_routines.h" 25 | //#include 26 | 27 | #define EEPROM_SIZE 1000 28 | 29 | #define POWEROFF // power off instead of deep sleep 30 | 31 | 32 | volatile bool enableSleep_ = false; 33 | unsigned long entry; 34 | 35 | // rename ttn_credentials.h.example to ttn_credentials.h and add you keys 36 | static const u1_t PROGMEM APPEUI[8] = TTN_APPEUI; 37 | static const u1_t PROGMEM DEVEUI[8] = TTN_DEVEUI; 38 | static const u1_t PROGMEM APPKEY[16] = TTN_APPKEY; 39 | void os_getArtEui(u1_t *buf) { 40 | memcpy_P(buf, APPEUI, 8); 41 | } 42 | void os_getDevEui(u1_t *buf) { 43 | memcpy_P(buf, DEVEUI, 8); 44 | } 45 | void os_getDevKey(u1_t *buf) { 46 | memcpy_P(buf, APPKEY, 16); 47 | } 48 | 49 | static uint8_t mydata[] = "Test"; 50 | static osjob_t sendjob; 51 | 52 | // Schedule TX every this many seconds 53 | // Respect Fair Access Policy and Maximum Duty Cycle! 54 | // https://www.thethingsnetwork.org/docs/lorawan/duty-cycle.html 55 | // https://www.loratools.nl/#/airtime 56 | const unsigned TX_INTERVAL = 3600; 57 | 58 | // Saves the LMIC structure during DeepSleep 59 | RTC_DATA_ATTR lmic_t RTC_LMIC; 60 | 61 | // Pin mapping for TTGO V2 62 | const lmic_pinmap lmic_pins = { 63 | .nss = 18, 64 | .rxtx = LMIC_UNUSED_PIN, 65 | .rst = LMIC_UNUSED_PIN, 66 | .dio = {26, 33, LMIC_UNUSED_PIN}, 67 | }; 68 | 69 | // opmode def 70 | // https://github.com/mcci-catena/arduino-lmic/blob/89c28c5888338f8fc851851bb64968f2a493462f/src/lmic/lmic.h#L233 71 | void LoraWANPrintLMICOpmode(void) 72 | { 73 | Serial.print(F("LMIC.opmode: ")); 74 | if (LMIC.opmode & OP_NONE) 75 | { 76 | Serial.print(F("OP_NONE ")); 77 | } 78 | if (LMIC.opmode & OP_SCAN) 79 | { 80 | Serial.print(F("OP_SCAN ")); 81 | } 82 | if (LMIC.opmode & OP_TRACK) 83 | { 84 | Serial.print(F("OP_TRACK ")); 85 | } 86 | if (LMIC.opmode & OP_JOINING) 87 | { 88 | Serial.print(F("OP_JOINING ")); 89 | } 90 | if (LMIC.opmode & OP_TXDATA) 91 | { 92 | Serial.print(F("OP_TXDATA ")); 93 | } 94 | if (LMIC.opmode & OP_POLL) 95 | { 96 | Serial.print(F("OP_POLL ")); 97 | } 98 | if (LMIC.opmode & OP_REJOIN) 99 | { 100 | Serial.print(F("OP_REJOIN ")); 101 | } 102 | if (LMIC.opmode & OP_SHUTDOWN) 103 | { 104 | Serial.print(F("OP_SHUTDOWN ")); 105 | } 106 | if (LMIC.opmode & OP_TXRXPEND) 107 | { 108 | Serial.print(F("OP_TXRXPEND ")); 109 | } 110 | if (LMIC.opmode & OP_RNDTX) 111 | { 112 | Serial.print(F("OP_RNDTX ")); 113 | } 114 | if (LMIC.opmode & OP_PINGINI) 115 | { 116 | Serial.print(F("OP_PINGINI ")); 117 | } 118 | if (LMIC.opmode & OP_PINGABLE) 119 | { 120 | Serial.print(F("OP_PINGABLE ")); 121 | } 122 | if (LMIC.opmode & OP_NEXTCHNL) 123 | { 124 | Serial.print(F("OP_NEXTCHNL ")); 125 | } 126 | if (LMIC.opmode & OP_LINKDEAD) 127 | { 128 | Serial.print(F("OP_LINKDEAD ")); 129 | } 130 | if (LMIC.opmode & OP_LINKDEAD) 131 | { 132 | Serial.print(F("OP_LINKDEAD ")); 133 | } 134 | if (LMIC.opmode & OP_TESTMODE) 135 | { 136 | Serial.print(F("OP_TESTMODE ")); 137 | } 138 | if (LMIC.opmode & OP_UNJOIN) 139 | { 140 | Serial.print(F("OP_UNJOIN ")); 141 | } 142 | } 143 | 144 | void LoraWANDebug(lmic_t lmic_check) 145 | { 146 | Serial.println(""); 147 | Serial.println(""); 148 | 149 | LoraWANPrintLMICOpmode(); 150 | Serial.println(""); 151 | 152 | Serial.print(F("LMIC.seqnoUp = ")); 153 | Serial.println(lmic_check.seqnoUp); 154 | 155 | Serial.print(F("LMIC.globalDutyRate = ")); 156 | Serial.print(lmic_check.globalDutyRate); 157 | Serial.print(F(" osTicks, ")); 158 | Serial.print(osticks2ms(lmic_check.globalDutyRate) / 1000); 159 | Serial.println(F(" sec")); 160 | 161 | Serial.print(F("LMIC.globalDutyAvail = ")); 162 | Serial.print(lmic_check.globalDutyAvail); 163 | Serial.print(F(" osTicks, ")); 164 | Serial.print(osticks2ms(lmic_check.globalDutyAvail) / 1000); 165 | Serial.println(F(" sec")); 166 | 167 | Serial.print(F("LMICbandplan_nextTx = ")); 168 | Serial.print(LMICbandplan_nextTx(os_getTime())); 169 | Serial.print(F(" osTicks, ")); 170 | Serial.print(osticks2ms(LMICbandplan_nextTx(os_getTime())) / 1000); 171 | Serial.println(F(" sec")); 172 | 173 | Serial.print(F("os_getTime = ")); 174 | Serial.print(os_getTime()); 175 | Serial.print(F(" osTicks, ")); 176 | Serial.print(osticks2ms(os_getTime()) / 1000); 177 | Serial.println(F(" sec")); 178 | 179 | Serial.print(F("LMIC.txend = ")); 180 | Serial.println(lmic_check.txend); 181 | Serial.print(F("LMIC.txChnl = ")); 182 | Serial.println(lmic_check.txChnl); 183 | 184 | Serial.println(F("Band \tavail \t\tavail_sec\tlastchnl \ttxcap")); 185 | for (u1_t bi = 0; bi < MAX_BANDS; bi++) 186 | { 187 | Serial.print(bi); 188 | Serial.print("\t"); 189 | Serial.print(lmic_check.bands[bi].avail); 190 | Serial.print("\t\t"); 191 | Serial.print(osticks2ms(lmic_check.bands[bi].avail) / 1000); 192 | Serial.print("\t\t"); 193 | Serial.print(lmic_check.bands[bi].lastchnl); 194 | Serial.print("\t\t"); 195 | Serial.println(lmic_check.bands[bi].txcap); 196 | } 197 | Serial.println(""); 198 | Serial.println(""); 199 | } 200 | 201 | void PrintRuntime() 202 | { 203 | long seconds = millis() / 1000; 204 | Serial.print("Runtime: "); 205 | Serial.print(seconds); 206 | Serial.println(" seconds"); 207 | } 208 | 209 | void PrintLMICVersion() 210 | { 211 | Serial.print(F("LMIC: ")); 212 | Serial.print(ARDUINO_LMIC_VERSION_GET_MAJOR(ARDUINO_LMIC_VERSION)); 213 | Serial.print(F(".")); 214 | Serial.print(ARDUINO_LMIC_VERSION_GET_MINOR(ARDUINO_LMIC_VERSION)); 215 | Serial.print(F(".")); 216 | Serial.print(ARDUINO_LMIC_VERSION_GET_PATCH(ARDUINO_LMIC_VERSION)); 217 | Serial.print(F(".")); 218 | Serial.println(ARDUINO_LMIC_VERSION_GET_LOCAL(ARDUINO_LMIC_VERSION)); 219 | } 220 | 221 | void onEvent(ev_t ev) 222 | { 223 | Serial.print(os_getTime()); 224 | Serial.print(": "); 225 | switch (ev) 226 | { 227 | case EV_SCAN_TIMEOUT: 228 | Serial.println(F("EV_SCAN_TIMEOUT")); 229 | break; 230 | case EV_BEACON_FOUND: 231 | Serial.println(F("EV_BEACON_FOUND")); 232 | break; 233 | case EV_BEACON_MISSED: 234 | Serial.println(F("EV_BEACON_MISSED")); 235 | break; 236 | case EV_BEACON_TRACKED: 237 | Serial.println(F("EV_BEACON_TRACKED")); 238 | break; 239 | case EV_JOINING: 240 | Serial.println(F("EV_JOINING")); 241 | break; 242 | case EV_JOINED: 243 | Serial.println(F("EV_JOINED")); 244 | { 245 | u4_t netid = 0; 246 | devaddr_t devaddr = 0; 247 | u1_t nwkKey[16]; 248 | u1_t artKey[16]; 249 | LMIC_getSessionKeys(&netid, &devaddr, nwkKey, artKey); 250 | Serial.print("netid: "); 251 | Serial.println(netid, DEC); 252 | Serial.print("devaddr: "); 253 | Serial.println(devaddr, HEX); 254 | Serial.print("artKey: "); 255 | for (size_t i = 0; i < sizeof(artKey); ++i) 256 | { 257 | Serial.print(artKey[i], HEX); 258 | } 259 | Serial.println(""); 260 | Serial.print("nwkKey: "); 261 | for (size_t i = 0; i < sizeof(nwkKey); ++i) 262 | { 263 | Serial.print(nwkKey[i], HEX); 264 | } 265 | Serial.println(""); 266 | } 267 | // Disable link check validation (automatically enabled 268 | // during join, but because slow data rates change max TX 269 | // size, we don't use it in this example. 270 | LMIC_setLinkCheckMode(0); 271 | break; 272 | /* 273 | || This event is defined but not used in the code. No 274 | || point in wasting codespace on it. 275 | || 276 | || case EV_RFU1: 277 | || Serial.println(F("EV_RFU1")); 278 | || break; 279 | */ 280 | case EV_JOIN_FAILED: 281 | Serial.println(F("EV_JOIN_FAILED")); 282 | break; 283 | case EV_REJOIN_FAILED: 284 | Serial.println(F("EV_REJOIN_FAILED")); 285 | break; 286 | case EV_TXCOMPLETE: 287 | // Transmit completed, includes waiting for RX windows. 288 | Serial.println(F("EV_TXCOMPLETE (includes waiting for RX windows)")); 289 | // setTxIndicatorsOn(false); 290 | // printEvent(timestamp, ev); 291 | // printFrameCounters(); 292 | // Check if downlink was received 293 | if (LMIC.dataLen != 0 || LMIC.dataBeg != 0) 294 | { 295 | uint8_t fPort = 0; 296 | if (LMIC.txrxFlags & TXRX_PORT) 297 | { 298 | fPort = LMIC.frame[LMIC.dataBeg - 1]; 299 | } 300 | // printDownlinkInfo(); 301 | // processDownlink(timestamp, fPort, LMIC.frame + LMIC.dataBeg, LMIC.dataLen); 302 | } 303 | enableSleep_ = true; 304 | break; 305 | case EV_LOST_TSYNC: 306 | Serial.println(F("EV_LOST_TSYNC")); 307 | break; 308 | case EV_RESET: 309 | Serial.println(F("EV_RESET")); 310 | break; 311 | case EV_RXCOMPLETE: 312 | // data received in ping slot 313 | Serial.println(F("EV_RXCOMPLETE")); 314 | break; 315 | case EV_LINK_DEAD: 316 | Serial.println(F("EV_LINK_DEAD")); 317 | break; 318 | case EV_LINK_ALIVE: 319 | Serial.println(F("EV_LINK_ALIVE")); 320 | break; 321 | /* 322 | || This event is defined but not used in the code. No 323 | || point in wasting codespace on it. 324 | || 325 | || case EV_SCAN_FOUND: 326 | || Serial.println(F("EV_SCAN_FOUND")); 327 | || break; 328 | */ 329 | case EV_TXSTART: 330 | Serial.println(F("EV_TXSTART")); 331 | break; 332 | case EV_TXCANCELED: 333 | Serial.println(F("EV_TXCANCELED")); 334 | break; 335 | case EV_RXSTART: 336 | /* do not print anything -- it wrecks timing */ 337 | break; 338 | case EV_JOIN_TXCOMPLETE: 339 | Serial.println(F("EV_JOIN_TXCOMPLETE: no JoinAccept")); 340 | break; 341 | default: 342 | Serial.print(F("Unknown event: ")); 343 | Serial.println((unsigned)ev); 344 | break; 345 | } 346 | } 347 | 348 | void do_send(osjob_t *j) 349 | { 350 | // Check if there is not a current TX/RX job running 351 | if (LMIC.opmode & OP_TXRXPEND) 352 | { 353 | Serial.println(F("OP_TXRXPEND, not sending")); 354 | } 355 | else 356 | { 357 | // Prepare upstream data transmission at the next possible time. 358 | int _adc = analogRead(BATTPIN); 359 | Serial.print("ADC "); 360 | Serial.println(_adc); 361 | float voltage = (float)_adc * BATTFACTOR; 362 | int _hi = (int)voltage; 363 | Serial.print("Voltage "); 364 | Serial.println(_hi/ 100.0); 365 | mydata[0] = _hi & 0xFF; 366 | mydata[1] = _hi >> 8 & 0xFF; 367 | LMIC_setTxData2(1, mydata, 2, 0); 368 | Serial.println(F("Packet queued")); 369 | } 370 | // Next TX is scheduled after TX_COMPLETE event. 371 | } 372 | 373 | void SaveLMICToRTC(int deepsleep_sec) 374 | { 375 | Serial.println(F("Save LMIC to RTC")); 376 | RTC_LMIC = LMIC; 377 | 378 | // ESP32 can't track millis during DeepSleep and no option to advanced millis after DeepSleep. 379 | // Therefore reset DutyCyles 380 | 381 | unsigned long now = millis(); 382 | 383 | // EU Like Bands 384 | #if defined(CFG_LMIC_EU_like) 385 | Serial.println(F("Reset CFG_LMIC_EU_like band avail")); 386 | for (int i = 0; i < MAX_BANDS; i++) 387 | { 388 | ostime_t correctedAvail = RTC_LMIC.bands[i].avail - ((now / 1000.0 + deepsleep_sec) * OSTICKS_PER_SEC); 389 | if (correctedAvail < 0) 390 | { 391 | correctedAvail = 0; 392 | } 393 | RTC_LMIC.bands[i].avail = correctedAvail; 394 | } 395 | 396 | RTC_LMIC.globalDutyAvail = RTC_LMIC.globalDutyAvail - ((now / 1000.0 + deepsleep_sec) * OSTICKS_PER_SEC); 397 | if (RTC_LMIC.globalDutyAvail < 0) 398 | { 399 | RTC_LMIC.globalDutyAvail = 0; 400 | } 401 | #else 402 | Serial.println(F("No DutyCycle recalculation function!")); 403 | #endif 404 | } 405 | 406 | void LoadLMICFromRTC() { 407 | if (RTC_LMIC.seqnoUp != 0) { 408 | Serial.println(F("Load LMIC from RTC")); 409 | LMIC = RTC_LMIC; 410 | } 411 | } 412 | 413 | void GoDeepSleep() 414 | { 415 | Serial.println(F("Go DeepSleep")); 416 | PrintRuntime(); 417 | Serial.flush(); 418 | esp_sleep_enable_timer_wakeup(TX_INTERVAL * 1000000); 419 | esp_deep_sleep_start(); 420 | } 421 | 422 | void switchOff() { 423 | Serial.println("---------POWER DOWN"); 424 | digitalWrite(POWERPIN, LOW); // Switch board off 425 | // These lines are only for test. They are never reached 426 | // during normal operation 427 | esp_sleep_enable_timer_wakeup(TX_INTERVAL * 1000000); 428 | esp_deep_sleep_start(); 429 | } 430 | 431 | void setup() 432 | { 433 | #ifdef POWEROFF 434 | pinMode(POWERPIN, OUTPUT); 435 | digitalWrite(POWERPIN, HIGH); 436 | #endif 437 | Serial.begin(115200); 438 | EEPROM.begin(EEPROM_SIZE); 439 | 440 | Serial.println(F("Starting DeepSleep test")); 441 | PrintLMICVersion(); 442 | 443 | // LMIC init 444 | os_init(); 445 | 446 | // Reset the MAC state. Session and pending data transfers will be discarded. 447 | LMIC_reset(); 448 | 449 | #ifdef POWEROFF 450 | LoadLMICfromEEPROM(); 451 | #else 452 | LoadLMICfromRTC(); 453 | #endif 454 | 455 | LoraWANDebug(LMIC); 456 | 457 | // Start job (sending automatically starts OTAA too) 458 | do_send(&sendjob); 459 | entry = millis(); 460 | } 461 | 462 | void loop() 463 | { 464 | static unsigned long lastPrintTime = 0; 465 | 466 | os_runloop_once(); 467 | 468 | const bool timeCriticalJobs = os_queryTimeCriticalJobs(ms2osticksRound((TX_INTERVAL * 1000))); 469 | if (!timeCriticalJobs && enableSleep_ == true && !(LMIC.opmode & OP_TXRXPEND)) 470 | { 471 | Serial.print(F("Can go sleep ")); 472 | LoraWANPrintLMICOpmode(); 473 | #ifdef POWEROFF 474 | SaveLMICtoEEPROM(TX_INTERVAL); 475 | switchOff(); 476 | #else 477 | SaveLMICtoRTC(TX_INTERVAL); 478 | GoDeepSleep(); 479 | #endif 480 | } 481 | else if (lastPrintTime + 2000 < millis()) 482 | { 483 | Serial.print(F("Cannot sleep ")); 484 | Serial.print(F("TimeCriticalJobs: ")); 485 | Serial.print(timeCriticalJobs); 486 | Serial.print(" "); 487 | 488 | LoraWANPrintLMICOpmode(); 489 | PrintRuntime(); 490 | lastPrintTime = millis(); 491 | } 492 | if (millis() - entry > 30000) { 493 | Serial.println("Reset LMIC"); 494 | LMIC.seqnoUp = 0; 495 | #ifdef POWEROFF 496 | SaveLMICtoEEPROM(TX_INTERVAL); 497 | switchOff(); 498 | #else 499 | SaveLMICtoRTC(TX_INTERVAL); 500 | GoDeepSleep(); 501 | #endif 502 | } 503 | } 504 | -------------------------------------------------------------------------------- /MailboxNotifier1284P/MailboxNotifier1284P.ino: -------------------------------------------------------------------------------- 1 | // This sketch demonstrates how to use the TPL5010 timer to wakeup the Atmgea 1284P from the most low power sleep for a transmissions to reduce power consumption 2 | 3 | /******************************************************************************* 4 | Copyright (c) 2015 Thomas Telkamp and Matthijs Kooijman 5 | Copyright (c) 2018 Terry Moore, MCCI 6 | 7 | Permission is hereby granted, free of charge, to anyone 8 | obtaining a copy of this document and accompanying files, 9 | to do whatever they want with them without any restriction, 10 | including, but not limited to, copying, modification and redistribution. 11 | NO WARRANTY OF ANY KIND IS PROVIDED. 12 | 13 | This example sends a valid LoRaWAN packet with payload "Hello, 14 | world!", using frequency and encryption settings matching those of 15 | the The Things Network. 16 | 17 | This uses OTAA (Over-the-air activation), where where a DevEUI and 18 | application key is configured, which are used in an over-the-air 19 | activation procedure where a DevAddr and session keys are 20 | assigned/generated for use with all further communication. 21 | 22 | Note: LoRaWAN per sub-band duty-cycle limitation is enforced (1% in 23 | g1, 0.1% in g2), but not the TTN fair usage policy (which is probably 24 | violated by this sketch when left running for longer)! 25 | 26 | To use this sketch, first register your application and device with 27 | the things network, to set or generate an AppEUI, DevEUI and AppKey. 28 | Multiple devices can use the same AppEUI, but each device has its own 29 | DevEUI and AppKey. 30 | 31 | Do not forget to define the radio type correctly in 32 | arduino-lmic/project_config/lmic_project_config.h or from your BOARDS.txt. 33 | 34 | *******************************************************************************/ 35 | 36 | #include 37 | #include 38 | #include 39 | #include 40 | #include "PinChangeInterrupt.h" // https://github.com/NicoHood/PinChangeInterrupt // you can also use the attachInterrupt() function from Arduino, to be consistent through all examples this lib is here used too 41 | 42 | 43 | // Create a file TTN_Credentials.h and add the following lines with your keys from TTN or fill in your keys below 44 | #define FILLMEIN_APPEUI 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00 45 | #define FILLMEIN_DEVEUI 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00 46 | #define FILLMEIN_APPKEY 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00 47 | #include "TTN_Credentials_Test.h" 48 | 49 | 50 | #define DEBUG 51 | 52 | #ifdef DEBUG 53 | #define DEBUGPRINT(x) Serial.print(x) 54 | #define DEBUGPRINTLN(x) Serial.println(x) 55 | #else 56 | #define DEBUGPRINT(x) 57 | #define DEBUGPRINTLN(x) 58 | #endif 59 | 60 | bool next = false; 61 | const int donePin = 3; // define done pin where TPL5010 is connected to, in our case pin 3 62 | const int TPLWakePin = 2; // define WDT pin where TPL5010 is connected to, in our case pin 2 63 | const int doorSwitch = 4; // define wakeup pin, in our case pin 4 which is pulled up to VCC with a 10K resistor on the board 64 | const int lidSwitch = 17; // lid switch connected to SDA pin 65 | uint8_t wakeupReason = 0; // set to 0 means startup, set to 1 means wakeup from TPL5010, set to 2 means wakeup from user pin, set to 3 means unknown wakeup reason 66 | int batteryReadPin = A0; // set the input pin for the battery measurement 67 | int voltageDividerPin = 0; // set the pin to enable the voltage divider 68 | const float AREF = 1.104; // internal reference votlage, for better accuracy use the printed value on the sticker! 69 | const float RGND = 20.02; // voltage divider resistor from A0 to GND, for better accuracy use the printed value on the sticker! Value in K ohms 70 | const float RSUP = 99.9; // voltage divider resistor from A0 to VCC or UREG depending on solder jumper, for better accuracy use the printed value on the sticker! Value in K ohms 71 | 72 | // 73 | // For normal use, we require that you edit the sketch to replace FILLMEIN 74 | // with values assigned by the TTN console. However, for regression tests, 75 | // we want to be able to compile these scripts. The regression tests define 76 | // COMPILE_REGRESSION_TEST, and in that case we define FILLMEIN to a non- 77 | // working but innocuous value. 78 | // 79 | //# define COMPILE_REGRESSION_TEST //comment this out if you want to compile this sketch with 'FILLMEIN' values for APPEUI, DEVEUI and APPKEY 80 | #ifdef COMPILE_REGRESSION_TEST 81 | # define FILLMEIN 0 82 | #else 83 | # warning "You must replace the values marked FILLMEIN with real values from the TTN control panel!" 84 | # define FILLMEIN (#dont edit this, edit the lines that use FILLMEIN) 85 | #endif 86 | 87 | // This EUI must be in little-endian format, so least-significant-byte 88 | // first. When copying an EUI from ttnctl output, this means to reverse 89 | // the bytes. For TTN issued EUIs the last bytes should be 0xD5, 0xB3, 90 | // 0x70. 91 | static const u1_t PROGMEM APPEUI[8] = { FILLMEIN_APPEUI }; 92 | void os_getArtEui(u1_t* buf) { 93 | memcpy_P(buf, APPEUI, 8); 94 | } 95 | 96 | // This should also be in little endian format, see above. 97 | static const u1_t PROGMEM DEVEUI[8] = { FILLMEIN_DEVEUI }; 98 | void os_getDevEui(u1_t* buf) { 99 | memcpy_P(buf, DEVEUI, 8); 100 | } 101 | 102 | // This key should be in big endian format (or, since it is not really a 103 | // number but a block of memory, endianness does not really apply). In 104 | // practice, a key taken from ttnctl can be copied as-is. 105 | static const u1_t PROGMEM APPKEY[16] = { FILLMEIN_APPKEY }; 106 | void os_getDevKey(u1_t* buf) { 107 | memcpy_P(buf, APPKEY, 16); 108 | } 109 | 110 | static osjob_t sendjob; 111 | 112 | // This value is only used to hack timer0, set it to the programmed time configuration of the TPL5010 113 | const unsigned TX_INTERVAL = 120; 114 | 115 | // Pin mapping 116 | const lmic_pinmap lmic_pins = { 117 | .nss = 14, 118 | .rxtx = LMIC_UNUSED_PIN, 119 | .rst = 13, 120 | .dio = {10, 11, 12}, 121 | }; 122 | 123 | void printHex2(unsigned v) { 124 | v &= 0xff; 125 | #ifdef DEBUG 126 | if (v < 16) 127 | Serial.print('0'); 128 | Serial.print(v, HEX); 129 | #endif 130 | } 131 | 132 | void onEvent (ev_t ev) { 133 | DEBUGPRINT(os_getTime()); 134 | DEBUGPRINT(": "); 135 | switch (ev) { 136 | case EV_SCAN_TIMEOUT: 137 | DEBUGPRINTLN(F("EV_SCAN_TIMEOUT")); 138 | break; 139 | case EV_BEACON_FOUND: 140 | DEBUGPRINTLN(F("EV_BEACON_FOUND")); 141 | break; 142 | case EV_BEACON_MISSED: 143 | DEBUGPRINTLN(F("EV_BEACON_MISSED")); 144 | break; 145 | case EV_BEACON_TRACKED: 146 | DEBUGPRINTLN(F("EV_BEACON_TRACKED")); 147 | break; 148 | case EV_JOINING: 149 | DEBUGPRINTLN(F("EV_JOINING")); 150 | break; 151 | case EV_JOINED: 152 | DEBUGPRINTLN(F("EV_JOINED")); 153 | { 154 | u4_t netid = 0; 155 | devaddr_t devaddr = 0; 156 | u1_t nwkKey[16]; 157 | u1_t artKey[16]; 158 | LMIC_getSessionKeys(&netid, &devaddr, nwkKey, artKey); 159 | #ifdef DEBUG 160 | Serial.print("netid: "); 161 | Serial.println(netid, DEC); 162 | Serial.print("devaddr: "); 163 | Serial.println(devaddr, HEX); 164 | Serial.print("AppSKey: "); 165 | #endif 166 | for (size_t i = 0; i < sizeof(artKey); ++i) { 167 | if (i != 0) 168 | DEBUGPRINT("-"); 169 | printHex2(artKey[i]); 170 | } 171 | DEBUGPRINTLN(""); 172 | DEBUGPRINT("NwkSKey: "); 173 | for (size_t i = 0; i < sizeof(nwkKey); ++i) { 174 | if (i != 0) 175 | DEBUGPRINT("-"); 176 | printHex2(nwkKey[i]); 177 | } 178 | DEBUGPRINTLN(); 179 | } 180 | // Disable link check validation (automatically enabled 181 | // during join, but because slow data rates change max TX 182 | // size, we don't use it in this example. 183 | LMIC_setLinkCheckMode(0); 184 | break; 185 | /* 186 | || This event is defined but not used in the code. No 187 | || point in wasting codespace on it. 188 | || 189 | || case EV_RFU1: 190 | || DEBUGPRINTLN(F("EV_RFU1")); 191 | || break; 192 | */ 193 | case EV_JOIN_FAILED: 194 | DEBUGPRINTLN(F("EV_JOIN_FAILED")); 195 | break; 196 | case EV_REJOIN_FAILED: 197 | DEBUGPRINTLN(F("EV_REJOIN_FAILED")); 198 | break; 199 | case EV_TXCOMPLETE: 200 | DEBUGPRINTLN(F("EV_TXCOMPLETE (includes waiting for RX windows)")); 201 | if (LMIC.txrxFlags & TXRX_ACK) 202 | DEBUGPRINTLN(F("Received ack")); 203 | if (LMIC.dataLen) { 204 | DEBUGPRINT(F("Received ")); 205 | DEBUGPRINT(LMIC.dataLen); 206 | DEBUGPRINTLN(F(" bytes of payload")); 207 | } 208 | // Schedule next transmission 209 | next = true; 210 | //os_setTimedCallback(&sendjob, os_getTime()+sec2osticks(TX_INTERVAL), do_send); 211 | break; 212 | case EV_LOST_TSYNC: 213 | DEBUGPRINTLN(F("EV_LOST_TSYNC")); 214 | break; 215 | case EV_RESET: 216 | DEBUGPRINTLN(F("EV_RESET")); 217 | break; 218 | case EV_RXCOMPLETE: 219 | // data received in ping slot 220 | DEBUGPRINTLN(F("EV_RXCOMPLETE")); 221 | break; 222 | case EV_LINK_DEAD: 223 | DEBUGPRINTLN(F("EV_LINK_DEAD")); 224 | break; 225 | case EV_LINK_ALIVE: 226 | DEBUGPRINTLN(F("EV_LINK_ALIVE")); 227 | break; 228 | /* 229 | || This event is defined but not used in the code. No 230 | || point in wasting codespace on it. 231 | || 232 | || case EV_SCAN_FOUND: 233 | || DEBUGPRINTLN(F("EV_SCAN_FOUND")); 234 | || break; 235 | */ 236 | case EV_TXSTART: 237 | DEBUGPRINTLN(F("EV_TXSTART")); 238 | break; 239 | case EV_TXCANCELED: 240 | DEBUGPRINTLN(F("EV_TXCANCELED")); 241 | break; 242 | case EV_RXSTART: 243 | /* do not print anything -- it wrecks timing */ 244 | break; 245 | case EV_JOIN_TXCOMPLETE: 246 | DEBUGPRINTLN(F("EV_JOIN_TXCOMPLETE: no JoinAccept")); 247 | break; 248 | 249 | default: 250 | DEBUGPRINT(F("Unknown event: ")); 251 | DEBUGPRINTLN((unsigned) ev); 252 | break; 253 | } 254 | } 255 | 256 | void do_send(osjob_t* j) { 257 | // Check if there is not a current TX/RX job running 258 | if (LMIC.opmode & OP_TXRXPEND) { 259 | DEBUGPRINTLN(F("OP_TXRXPEND, not sending")); 260 | } else { 261 | // Prepare upstream data transmission at the next possible time. 262 | int voltage = readBattVoltage(); 263 | 264 | DEBUGPRINT("reason "); 265 | DEBUGPRINTLN(wakeupReason); 266 | 267 | DEBUGPRINT("voltage "); 268 | DEBUGPRINTLN(voltage); 269 | 270 | //Prepare uplink data 271 | byte mydata[3]; 272 | mydata[0] = wakeupReason; 273 | mydata[1] = voltage >> 8; 274 | mydata[2] = voltage; 275 | 276 | /* 277 | 278 | //TTNv3 Payload decoder 279 | function decodeUplink(input) { 280 | var data = {}; 281 | if (input.fPort == 1) { 282 | data.voltage = ((input.bytes[1] << 8) | input.bytes[2]) / 100.00; 283 | data.reason = (input.bytes[0]); 284 | } 285 | return { 286 | data: data, 287 | warnings: [], 288 | errors: [] 289 | }; 290 | } 291 | 292 | */ 293 | 294 | LMIC_setTxData2(1, mydata, sizeof(mydata), 0); 295 | DEBUGPRINTLN(F("Packet queued")); 296 | 297 | // Set wakeupReason value to 99, if we ever transmit the value '99', then the device woke up because of an unknown reason 298 | wakeupReason = 99; 299 | } 300 | // Next TX is scheduled after TX_COMPLETE event. 301 | } 302 | 303 | void wakeupFromTimer(void) { 304 | wakeupReason = 1; 305 | DEBUGPRINTLN("Timer Interrupt"); 306 | } 307 | 308 | void wakeupFromDoorSwitch(void) { 309 | if (digitalRead(doorSwitch) == HIGH) wakeupReason = 2; 310 | DEBUGPRINTLN("Door"); 311 | } 312 | 313 | void wakeupFromLidSwitch(void) { 314 | if (digitalRead(lidSwitch) == HIGH) wakeupReason = 3; 315 | DEBUGPRINTLN("Lid"); 316 | } 317 | 318 | uint16_t analogOversample(int pin) { 319 | uint16_t reading = 0; 320 | int i = 0; 321 | while (i < 8) 322 | { 323 | i++; 324 | reading = reading + analogRead(pin); 325 | } 326 | return reading >> 3; 327 | } 328 | 329 | int readBattVoltage() { 330 | //Enable voltage divider, warm up ADC, read voltage, disable voltage divider 331 | digitalWrite(voltageDividerPin, HIGH); 332 | for (int i = 0; i < 16; i++) analogRead(batteryReadPin); // first readings are not accurate 333 | uint16_t ADCreading = analogOversample(batteryReadPin); 334 | digitalWrite(voltageDividerPin, LOW); 335 | 336 | //Convert analog ADC reading to millivolts 337 | int _voltage = (((ADCreading * AREF / 1024.0) * (RSUP + RGND) / RGND) * 100); 338 | return _voltage; 339 | } 340 | void do_sendmac(osjob_t* j) { 341 | // This function is called if we need to process a MAC message 342 | // Check if there is not a current TX/RX job running 343 | if (LMIC.opmode & OP_TXRXPEND) { 344 | DEBUGPRINTLN(F("OP_TXRXPEND, not sending")); 345 | } 346 | else { 347 | // Prepare upstream data transmission at the next possible time. 348 | byte mydata[1]; 349 | LMIC_setTxData2(0, mydata, sizeof(mydata), 0); 350 | DEBUGPRINTLN(F("Packet queued (MAC command)")); 351 | } 352 | // Next TX is scheduled after TX_COMPLETE event. 353 | } 354 | 355 | void setup() { 356 | Serial.begin(115200); 357 | DEBUGPRINTLN("Starting"); 358 | 359 | // Pin settings for TPL5010 usage 360 | pinMode(TPLWakePin, INPUT); 361 | pinMode(donePin, OUTPUT); 362 | 363 | // Pin settings for User interrupt pin 364 | pinMode(doorSwitch, INPUT); 365 | pinMode(lidSwitch, INPUT); 366 | 367 | // Attach TPL5010 interrupt 368 | attachPCINT(digitalPinToPCINT(TPLWakePin), wakeupFromTimer, CHANGE); 369 | 370 | // Attach user interrupt pin 371 | attachPCINT(digitalPinToPCINT(lidSwitch), wakeupFromLidSwitch, CHANGE); 372 | 373 | // Attach user interrupt pin 374 | attachPCINT(digitalPinToPCINT(doorSwitch), wakeupFromDoorSwitch, CHANGE); 375 | 376 | // Pin settings to read supply voltage 377 | analogReference(INTERNAL1V1); // Set internal reference to 1.1V 378 | pinMode(batteryReadPin, INPUT); // Set analog pin to input 379 | pinMode(voltageDividerPin, OUTPUT); // Set pin which controls the voltage divider to output 380 | 381 | // LMIC init 382 | os_init(); 383 | // Reset the MAC state. Session and pending data transfers will be discarded. 384 | LMIC_reset(); 385 | 386 | // Start job (sending automatically starts OTAA too) 387 | do_send(&sendjob); 388 | } 389 | 390 | void loop() { 391 | extern volatile unsigned long timer0_overflow_count; 392 | if (next == false) { 393 | os_runloop_once(); 394 | 395 | // If we've to wait for the RFM95 module to complete TX or wait for RX window can't powerDown the MCU, 396 | // as LMIC controls RX timing and LMIC uses micros(), powerDown would stop micros() from running. 397 | // Instead we can put the MCU into idle state with TIMER0_ON. Use here the lowest possible sleeptime (SLEEP_15MS), 398 | // as TIMER0 will overflow every 1ms, MCU will wake up every ms again. Also, MCU will be waken up after the 399 | // selected time, in this case 15ms. We can save about 60% of the power during waiting with this simple trick. 400 | 401 | // Check if any time critical jobs is due in the next 1ms 402 | if (os_queryTimeCriticalJobs(ms2osticks(1)) == 0) { 403 | // If no time critical job is due, enter idle mode for 1ms 404 | LowPower.idle(SLEEP_15MS, ADC_OFF, TIMER2_OFF, TIMER1_OFF, TIMER0_ON, SPI_OFF, USART1_OFF, USART0_OFF, TWI_OFF); 405 | } 406 | } 407 | else { 408 | if (LMIC.pendMacLen > 0) { 409 | DEBUGPRINTLN("Pending MAC message"); 410 | next = false; 411 | do_sendmac(&sendjob); 412 | } 413 | else { 414 | DEBUGPRINTLN("No Pending MAC message"); 415 | Serial.flush(); // give the serial print chance to complete 416 | delay(20); // We need here some delay to prevent wakeup from WDT we used before. 20ms seems to be safe. 417 | 418 | // set DONE high for 100 micro seconds to tell the TPL5010 we're done 419 | // if DONE was not high between TPL5010 interrupts, TPL5010 will reset the MCU through the reset pin if connected, otherwise it will do nothing until 'DONE' was set high once 420 | digitalWrite(donePin, HIGH); 421 | delayMicroseconds(100); 422 | digitalWrite(donePin, LOW); 423 | LowPower.powerDown(SLEEP_FOREVER, ADC_OFF, BOD_OFF); 424 | 425 | // wakeupReason 99 means that the interrupt was called on a falling edge. We only want to trigger the transmit on the rising edge 426 | if (wakeupReason < 99) { 427 | // LMIC uses micros() to keep track of the duty cycle, so 428 | // hack timer0_overflow for a rude adjustment: 429 | timer0_overflow_count += TX_INTERVAL * 64 * clockCyclesPerMicrosecond(); 430 | next = false; 431 | do_send(&sendjob); 432 | } 433 | } 434 | } 435 | } 436 | -------------------------------------------------------------------------------- /MailboxNotifier1284P_V2/MailboxNotifier1284P_V2.ino: -------------------------------------------------------------------------------- 1 | // This sketch demonstrates how to use the TPL5010 timer to wakeup the Atmgea 1284P from the most low power sleep for a transmissions to reduce power consumption 2 | 3 | /******************************************************************************* 4 | Copyright (c) 2015 Thomas Telkamp and Matthijs Kooijman 5 | Copyright (c) 2018 Terry Moore, MCCI 6 | 7 | Permission is hereby granted, free of charge, to anyone 8 | obtaining a copy of this document and accompanying files, 9 | to do whatever they want with them without any restriction, 10 | including, but not limited to, copying, modification and redistribution. 11 | NO WARRANTY OF ANY KIND IS PROVIDED. 12 | 13 | This example sends a valid LoRaWAN packet with payload "Hello, 14 | world!", using frequency and encryption settings matching those of 15 | the The Things Network. 16 | 17 | This uses OTAA (Over-the-air activation), where where a DevEUI and 18 | application key is configured, which are used in an over-the-air 19 | activation procedure where a DevAddr and session keys are 20 | assigned/generated for use with all further communication. 21 | 22 | Note: LoRaWAN per sub-band duty-cycle limitation is enforced (1% in 23 | g1, 0.1% in g2), but not the TTN fair usage policy (which is probably 24 | violated by this sketch when left running for longer)! 25 | 26 | To use this sketch, first register your application and device with 27 | the things network, to set or generate an AppEUI, DevEUI and AppKey. 28 | Multiple devices can use the same AppEUI, but each device has its own 29 | DevEUI and AppKey. 30 | 31 | Do not forget to define the radio type correctly in 32 | arduino-lmic/project_config/lmic_project_config.h or from your BOARDS.txt. 33 | 34 | *******************************************************************************/ 35 | 36 | #include 37 | #include 38 | #include 39 | #include 40 | #include "PinChangeInterrupt.h" // https://github.com/NicoHood/PinChangeInterrupt // you can also use the attachInterrupt() function from Arduino, to be consistent through all examples this lib is here used too 41 | 42 | 43 | // Create a file TTN_Credentials.h and add the following lines with your keys from TTN or fill in your keys below 44 | #define FILLMEIN_APPEUI 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00 45 | #define FILLMEIN_DEVEUI 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00 46 | #define FILLMEIN_APPKEY 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00 47 | //#include "TTN_Credentials.h" 48 | 49 | enum wakeupReasons { 50 | unknown, 51 | door, 52 | lid 53 | }; 54 | 55 | #define DEBUG 56 | 57 | #ifdef DEBUG 58 | #define DEBUGPRINT(x) Serial.print(x) 59 | #define DEBUGPRINTLN(x) Serial.println(x) 60 | #else 61 | #define DEBUGPRINT(x) 62 | #define DEBUGPRINTLN(x) 63 | #endif 64 | 65 | bool next = false; 66 | const int donePin = 3; // define done pin where TPL5010 is connected to, in our case pin 3 67 | const int TPLWakePin = 2; // define WDT pin where TPL5010 is connected to, in our case pin 2 68 | const int doorSwitch = 4; // define wakeup pin, in our case pin 4 which is pulled up to VCC with a 10K resistor on the board 69 | const int lidSwitch = 17; // lid switch connected to SDA pin 70 | uint8_t wakeupReason = 0; // set to 0 means startup, set to 1 means wakeup from TPL5010, set to 2 means wakeup from user pin, set to 3 means unknown wakeup reason 71 | int batteryReadPin = A0; // set the input pin for the battery measurement 72 | int voltageDividerPin = 0; // set the pin to enable the voltage divider 73 | const float AREF = 1.0833; // internal reference votlage, for better accuracy use the printed value on the sticker! 74 | const float RGND = 20.051; // voltage divider resistor from A0 to GND, for better accuracy use the printed value on the sticker! Value in K ohms 75 | const float RSUP = 99.98; // voltage divider resistor from A0 to VCC or UREG depending on solder jumper, for better accuracy use the printed value on the sticker! Value in K ohms 76 | 77 | bool mailAvailable = false; 78 | 79 | // 80 | // For normal use, we require that you edit the sketch to replace FILLMEIN 81 | // with values assigned by the TTN console. However, for regression tests, 82 | // we want to be able to compile these scripts. The regression tests define 83 | // COMPILE_REGRESSION_TEST, and in that case we define FILLMEIN to a non- 84 | // working but innocuous value. 85 | // 86 | //# define COMPILE_REGRESSION_TEST //comment this out if you want to compile this sketch with 'FILLMEIN' values for APPEUI, DEVEUI and APPKEY 87 | #ifdef COMPILE_REGRESSION_TEST 88 | # define FILLMEIN 0 89 | #else 90 | # warning "You must replace the values marked FILLMEIN with real values from the TTN control panel!" 91 | # define FILLMEIN (#dont edit this, edit the lines that use FILLMEIN) 92 | #endif 93 | 94 | // This EUI must be in little-endian format, so least-significant-byte 95 | // first. When copying an EUI from ttnctl output, this means to reverse 96 | // the bytes. For TTN issued EUIs the last bytes should be 0xD5, 0xB3, 97 | // 0x70. 98 | static const u1_t PROGMEM APPEUI[8] = { FILLMEIN_APPEUI }; 99 | void os_getArtEui(u1_t* buf) { 100 | memcpy_P(buf, APPEUI, 8); 101 | } 102 | 103 | // This should also be in little endian format, see above. 104 | static const u1_t PROGMEM DEVEUI[8] = { FILLMEIN_DEVEUI }; 105 | void os_getDevEui(u1_t* buf) { 106 | memcpy_P(buf, DEVEUI, 8); 107 | } 108 | 109 | // This key should be in big endian format (or, since it is not really a 110 | // number but a block of memory, endianness does not really apply). In 111 | // practice, a key taken from ttnctl can be copied as-is. 112 | static const u1_t PROGMEM APPKEY[16] = { FILLMEIN_APPKEY }; 113 | void os_getDevKey(u1_t* buf) { 114 | memcpy_P(buf, APPKEY, 16); 115 | } 116 | 117 | static osjob_t sendjob; 118 | 119 | // This value is only used to hack timer0, set it to the programmed time configuration of the TPL5010 120 | const unsigned TX_INTERVAL = 120; 121 | 122 | // Pin mapping 123 | const lmic_pinmap lmic_pins = { 124 | .nss = 14, 125 | .rxtx = LMIC_UNUSED_PIN, 126 | .rst = 13, 127 | .dio = {10, 11, 12}, 128 | }; 129 | 130 | void printHex2(unsigned v) { 131 | v &= 0xff; 132 | #ifdef DEBUG 133 | if (v < 16) 134 | Serial.print('0'); 135 | Serial.print(v, HEX); 136 | #endif 137 | } 138 | 139 | void onEvent (ev_t ev) { 140 | DEBUGPRINT(os_getTime()); 141 | DEBUGPRINT(": "); 142 | switch (ev) { 143 | case EV_SCAN_TIMEOUT: 144 | DEBUGPRINTLN(F("EV_SCAN_TIMEOUT")); 145 | break; 146 | case EV_BEACON_FOUND: 147 | DEBUGPRINTLN(F("EV_BEACON_FOUND")); 148 | break; 149 | case EV_BEACON_MISSED: 150 | DEBUGPRINTLN(F("EV_BEACON_MISSED")); 151 | break; 152 | case EV_BEACON_TRACKED: 153 | DEBUGPRINTLN(F("EV_BEACON_TRACKED")); 154 | break; 155 | case EV_JOINING: 156 | DEBUGPRINTLN(F("EV_JOINING")); 157 | break; 158 | case EV_JOINED: 159 | DEBUGPRINTLN(F("EV_JOINED")); 160 | { 161 | u4_t netid = 0; 162 | devaddr_t devaddr = 0; 163 | u1_t nwkKey[16]; 164 | u1_t artKey[16]; 165 | LMIC_getSessionKeys(&netid, &devaddr, nwkKey, artKey); 166 | #ifdef DEBUG 167 | Serial.print("netid: "); 168 | Serial.println(netid, DEC); 169 | Serial.print("devaddr: "); 170 | Serial.println(devaddr, HEX); 171 | Serial.print("AppSKey: "); 172 | #endif 173 | for (size_t i = 0; i < sizeof(artKey); ++i) { 174 | if (i != 0) 175 | DEBUGPRINT("-"); 176 | printHex2(artKey[i]); 177 | } 178 | DEBUGPRINTLN(""); 179 | DEBUGPRINT("NwkSKey: "); 180 | for (size_t i = 0; i < sizeof(nwkKey); ++i) { 181 | if (i != 0) 182 | DEBUGPRINT("-"); 183 | printHex2(nwkKey[i]); 184 | } 185 | DEBUGPRINTLN(); 186 | } 187 | // Disable link check validation (automatically enabled 188 | // during join, but because slow data rates change max TX 189 | // size, we don't use it in this example. 190 | LMIC_setLinkCheckMode(0); 191 | break; 192 | /* 193 | || This event is defined but not used in the code. No 194 | || point in wasting codespace on it. 195 | || 196 | || case EV_RFU1: 197 | || DEBUGPRINTLN(F("EV_RFU1")); 198 | || break; 199 | */ 200 | case EV_JOIN_FAILED: 201 | DEBUGPRINTLN(F("EV_JOIN_FAILED")); 202 | break; 203 | case EV_REJOIN_FAILED: 204 | DEBUGPRINTLN(F("EV_REJOIN_FAILED")); 205 | break; 206 | case EV_TXCOMPLETE: 207 | DEBUGPRINTLN(F("EV_TXCOMPLETE (includes waiting for RX windows)")); 208 | if (LMIC.txrxFlags & TXRX_ACK) 209 | DEBUGPRINTLN(F("Received ack")); 210 | if (LMIC.dataLen) { 211 | DEBUGPRINT(F("Received ")); 212 | DEBUGPRINT(LMIC.dataLen); 213 | DEBUGPRINTLN(F(" bytes of payload")); 214 | } 215 | // Schedule next transmission 216 | next = true; 217 | //os_setTimedCallback(&sendjob, os_getTime()+sec2osticks(TX_INTERVAL), do_send); 218 | break; 219 | case EV_LOST_TSYNC: 220 | DEBUGPRINTLN(F("EV_LOST_TSYNC")); 221 | break; 222 | case EV_RESET: 223 | DEBUGPRINTLN(F("EV_RESET")); 224 | break; 225 | case EV_RXCOMPLETE: 226 | // data received in ping slot 227 | DEBUGPRINTLN(F("EV_RXCOMPLETE")); 228 | break; 229 | case EV_LINK_DEAD: 230 | DEBUGPRINTLN(F("EV_LINK_DEAD")); 231 | break; 232 | case EV_LINK_ALIVE: 233 | DEBUGPRINTLN(F("EV_LINK_ALIVE")); 234 | break; 235 | /* 236 | || This event is defined but not used in the code. No 237 | || point in wasting codespace on it. 238 | || 239 | || case EV_SCAN_FOUND: 240 | || DEBUGPRINTLN(F("EV_SCAN_FOUND")); 241 | || break; 242 | */ 243 | case EV_TXSTART: 244 | DEBUGPRINTLN(F("EV_TXSTART")); 245 | break; 246 | case EV_TXCANCELED: 247 | DEBUGPRINTLN(F("EV_TXCANCELED")); 248 | break; 249 | case EV_RXSTART: 250 | /* do not print anything -- it wrecks timing */ 251 | break; 252 | case EV_JOIN_TXCOMPLETE: 253 | DEBUGPRINTLN(F("EV_JOIN_TXCOMPLETE: no JoinAccept")); 254 | break; 255 | 256 | default: 257 | DEBUGPRINT(F("Unknown event: ")); 258 | DEBUGPRINTLN((unsigned) ev); 259 | break; 260 | } 261 | } 262 | 263 | void do_send(osjob_t* j) { 264 | // Check if there is not a current TX/RX job running 265 | if (LMIC.opmode & OP_TXRXPEND) { 266 | DEBUGPRINTLN(F("OP_TXRXPEND, not sending")); 267 | } else { 268 | // Prepare upstream data transmission at the next possible time. 269 | int voltage = readBattVoltage(); 270 | 271 | DEBUGPRINT("mailAvalable "); 272 | Serial.println(mailAvailable); 273 | 274 | DEBUGPRINT("reason "); 275 | Serial.println(wakeupReason); 276 | 277 | DEBUGPRINT("voltage "); 278 | DEBUGPRINTLN(voltage); 279 | 280 | //Prepare uplink data 281 | byte mydata[3]; 282 | mydata[0] = wakeupReason | mailAvailable << 7; 283 | mydata[1] = voltage >> 8; 284 | mydata[2] = voltage; 285 | 286 | /* 287 | 288 | //TTNv3 Payload decoder 289 | function decodeUplink(input) { 290 | var data = {}; 291 | if (input.fPort == 1) { 292 | data.voltage = ((input.bytes[1] << 8) | input.bytes[2]) / 100.00; 293 | data.reason = (input.bytes[0])& 0x0F; 294 | data.mailAvailable = (input.bytes[0]>>7) 295 | } 296 | return { 297 | data: data, 298 | warnings: [], 299 | errors: [] 300 | }; 301 | } 302 | 303 | */ 304 | 305 | LMIC_setTxData2(1, mydata, sizeof(mydata), 0); 306 | DEBUGPRINTLN(F("Packet queued")); 307 | 308 | // Set wakeupReason value to 99, if we ever transmit the value '99', then the device woke up because of an unknown reason 309 | wakeupReason = 99; 310 | } 311 | // Next TX is scheduled after TX_COMPLETE event. 312 | } 313 | 314 | void wakeupFromTimer(void) { 315 | wakeupReason = 1; 316 | DEBUGPRINTLN("Timer Interrupt"); 317 | } 318 | 319 | void wakeupFromLidSwitch(void) { 320 | if (digitalRead(lidSwitch) == HIGH) { 321 | wakeupReason = 2; 322 | mailAvailable = true; 323 | } 324 | DEBUGPRINTLN("Lid"); 325 | } 326 | 327 | void wakeupFromDoorSwitch(void) { 328 | if (digitalRead(doorSwitch) == HIGH) { 329 | wakeupReason = 3; 330 | mailAvailable = true; 331 | } 332 | DEBUGPRINTLN("Door"); 333 | } 334 | 335 | uint16_t analogOversample(int pin) { 336 | uint16_t reading = 0; 337 | int i = 0; 338 | while (i < 8) 339 | { 340 | i++; 341 | reading = reading + analogRead(pin); 342 | } 343 | return reading >> 3; 344 | } 345 | 346 | int readBattVoltage() { 347 | //Enable voltage divider, warm up ADC, read voltage, disable voltage divider 348 | digitalWrite(voltageDividerPin, HIGH); 349 | for (int i = 0; i < 16; i++) analogRead(batteryReadPin); // first readings are not accurate 350 | uint16_t ADCreading = analogOversample(batteryReadPin); 351 | digitalWrite(voltageDividerPin, LOW); 352 | 353 | //Convert analog ADC reading to millivolts 354 | int _voltage = (((ADCreading * AREF / 1024.0) * (RSUP + RGND) / RGND) * 100); 355 | return _voltage; 356 | } 357 | void do_sendmac(osjob_t* j) { 358 | // This function is called if we need to process a MAC message 359 | // Check if there is not a current TX/RX job running 360 | if (LMIC.opmode & OP_TXRXPEND) { 361 | DEBUGPRINTLN(F("OP_TXRXPEND, not sending")); 362 | } 363 | else { 364 | // Prepare upstream data transmission at the next possible time. 365 | byte mydata[1]; 366 | LMIC_setTxData2(0, mydata, sizeof(mydata), 0); 367 | DEBUGPRINTLN(F("Packet queued (MAC command)")); 368 | } 369 | // Next TX is scheduled after TX_COMPLETE event. 370 | } 371 | 372 | void setup() { 373 | Serial.begin(115200); 374 | DEBUGPRINTLN("Starting"); 375 | 376 | // Pin settings for TPL5010 usage 377 | pinMode(TPLWakePin, INPUT); 378 | pinMode(donePin, OUTPUT); 379 | 380 | // Pin settings for User interrupt pin 381 | pinMode(doorSwitch, INPUT); 382 | pinMode(lidSwitch, INPUT); 383 | 384 | // Attach TPL5010 interrupt 385 | attachPCINT(digitalPinToPCINT(TPLWakePin), wakeupFromTimer, CHANGE); 386 | 387 | // Attach user interrupt pin 388 | attachPCINT(digitalPinToPCINT(lidSwitch), wakeupFromLidSwitch, CHANGE); 389 | 390 | // Attach user interrupt pin 391 | attachPCINT(digitalPinToPCINT(doorSwitch), wakeupFromDoorSwitch, CHANGE); 392 | 393 | // Pin settings to read supply voltage 394 | analogReference(INTERNAL1V1); // Set internal reference to 1.1V 395 | pinMode(batteryReadPin, INPUT); // Set analog pin to input 396 | pinMode(voltageDividerPin, OUTPUT); // Set pin which controls the voltage divider to output 397 | 398 | // LMIC init 399 | os_init(); 400 | // Reset the MAC state. Session and pending data transfers will be discarded. 401 | LMIC_reset(); 402 | 403 | // Start job (sending automatically starts OTAA too) 404 | do_send(&sendjob); 405 | } 406 | 407 | void loop() { 408 | extern volatile unsigned long timer0_overflow_count; 409 | if (next == false) { 410 | os_runloop_once(); 411 | 412 | // If we've to wait for the RFM95 module to complete TX or wait for RX window can't powerDown the MCU, 413 | // as LMIC controls RX timing and LMIC uses micros(), powerDown would stop micros() from running. 414 | // Instead we can put the MCU into idle state with TIMER0_ON. Use here the lowest possible sleeptime (SLEEP_15MS), 415 | // as TIMER0 will overflow every 1ms, MCU will wake up every ms again. Also, MCU will be waken up after the 416 | // selected time, in this case 15ms. We can save about 60% of the power during waiting with this simple trick. 417 | 418 | // Check if any time critical jobs is due in the next 1ms 419 | if (os_queryTimeCriticalJobs(ms2osticks(1)) == 0) { 420 | // If no time critical job is due, enter idle mode for 1ms 421 | LowPower.idle(SLEEP_15MS, ADC_OFF, TIMER2_OFF, TIMER1_OFF, TIMER0_ON, SPI_OFF, USART1_OFF, USART0_OFF, TWI_OFF); 422 | } 423 | } 424 | else { 425 | if (LMIC.pendMacLen > 0) { 426 | DEBUGPRINTLN("Pending MAC message"); 427 | next = false; 428 | do_sendmac(&sendjob); 429 | } 430 | else { 431 | DEBUGPRINTLN("No Pending MAC message"); 432 | Serial.flush(); // give the serial print chance to complete 433 | delay(20); // We need here some delay to prevent wakeup from WDT we used before. 20ms seems to be safe. 434 | 435 | // set DONE high for 100 micro seconds to tell the TPL5010 we're done 436 | // if DONE was not high between TPL5010 interrupts, TPL5010 will reset the MCU through the reset pin if connected, otherwise it will do nothing until 'DONE' was set high once 437 | digitalWrite(donePin, HIGH); 438 | delayMicroseconds(100); 439 | digitalWrite(donePin, LOW); 440 | LowPower.powerDown(SLEEP_FOREVER, ADC_OFF, BOD_OFF); 441 | 442 | // wakeupReason 99 means that the interrupt was called on a falling edge. We only want to trigger the transmit on the rising edge 443 | if (wakeupReason < 99) { 444 | // LMIC uses micros() to keep track of the duty cycle, so 445 | // hack timer0_overflow for a rude adjustment: 446 | timer0_overflow_count += TX_INTERVAL * 64 * clockCyclesPerMicrosecond(); 447 | next = false; 448 | do_send(&sendjob); 449 | } 450 | } 451 | } 452 | } 453 | -------------------------------------------------------------------------------- /MailboxNotifier1284P_V2/State Diagram.vsdx: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/SensorsIot/MailboxNotifier/2df06d6e7f159d251d29580f0fc5488e32c1229e/MailboxNotifier1284P_V2/State Diagram.vsdx -------------------------------------------------------------------------------- /MailboxNotifier1284P_V3/MailboxNotifier1284P_V3.ino: -------------------------------------------------------------------------------- 1 | // This sketch demonstrates how to use the TPL5010 timer to wakeup the Atmgea 1284P from the most low power sleep for a transmissions to reduce power consumption 2 | 3 | /******************************************************************************* 4 | Copyright (c) 2015 Thomas Telkamp and Matthijs Kooijman 5 | Copyright (c) 2018 Terry Moore, MCCI 6 | 7 | Permission is hereby granted, free of charge, to anyone 8 | obtaining a copy of this document and accompanying files, 9 | to do whatever they want with them without any restriction, 10 | including, but not limited to, copying, modification and redistribution. 11 | NO WARRANTY OF ANY KIND IS PROVIDED. 12 | 13 | This example sends a valid LoRaWAN packet with payload "Hello, 14 | world!", using frequency and encryption settings matching those of 15 | the The Things Network. 16 | 17 | This uses OTAA (Over-the-air activation), where where a DevEUI and 18 | application key is configured, which are used in an over-the-air 19 | activation procedure where a DevAddr and session keys are 20 | assigned/generated for use with all further communication. 21 | 22 | Note: LoRaWAN per sub-band duty-cycle limitation is enforced (1% in 23 | g1, 0.1% in g2), but not the TTN fair usage policy (which is probably 24 | violated by this sketch when left running for longer)! 25 | 26 | To use this sketch, first register your application and device with 27 | the things network, to set or generate an AppEUI, DevEUI and AppKey. 28 | Multiple devices can use the same AppEUI, but each device has its own 29 | DevEUI and AppKey. 30 | 31 | Do not forget to define the radio type correctly in 32 | arduino-lmic/project_config/lmic_project_config.h or from your BOARDS.txt. 33 | 34 | *******************************************************************************/ 35 | 36 | #include 37 | #include 38 | #include 39 | #include 40 | #include "PinChangeInterrupt.h" // https://github.com/NicoHood/PinChangeInterrupt // you can also use the attachInterrupt() function from Arduino, to be consistent through all examples this lib is here used too 41 | 42 | 43 | // Create a file TTN_Credentials.h and add the following lines with your keys from TTN or fill in your keys below 44 | #define FILLMEIN_APPEUI 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00 45 | #define FILLMEIN_DEVEUI 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00 46 | #define FILLMEIN_APPKEY 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00 47 | #include "TTN_Credentials.h" 48 | 49 | #define TIMERINTERVALS 20 50 | 51 | enum reasons { 52 | undefined, 53 | timer, 54 | lid, 55 | door, 56 | unknown 57 | }; 58 | 59 | enum boxStatus { 60 | empty, 61 | full 62 | }; 63 | 64 | //uint16_t userChannelsMask[6]={0x0001,0x0000,0x0000,0x0000,0x0000,0x0000}; 65 | 66 | #define DEBUG 67 | // #define DEBUGINTERRUPT 68 | 69 | #ifdef DEBUG 70 | #define DEBUGPRINT(x) Serial.print(x) 71 | #define DEBUGPRINTLN(x) Serial.println(x) 72 | #else 73 | #define DEBUGPRINT(x) 74 | #define DEBUGPRINTLN(x) 75 | #endif 76 | 77 | bool transmitting = false; 78 | const int donePin = 3; // define done pin where TPL5010 is connected to, in our case pin 3 79 | const int TPLWakePin = 2; // define WDT pin where TPL5010 is connected to, in our case pin 2 80 | const int lidSwitch = 4; // define wakeup pin, in our case pin 4 which is pulled up to VCC with a 10K resistor on the board 81 | const int doorSwitch = 17; // lid switch connected to SDA pin 82 | reasons wakeupReason = undefined; // set to 0 means startup, set to 1 means wakeup from TPL5010, set to 2 means wakeup from user pin, set to 3 means unknown wakeup reason 83 | int batteryReadPin = A0; // set the input pin for the battery measurement 84 | int voltageDividerPin = 0; // set the pin to enable the voltage divider 85 | const float AREF = 1.1041; // internal reference votlage, for better accuracy use the printed value on the sticker! 86 | const float RGND = 20.020; // voltage divider resistor from A0 to GND, for better accuracy use the printed value on the sticker! Value in K ohms 87 | const float RSUP = 99.90; // voltage divider resistor from A0 to VCC or UREG depending on solder jumper, for better accuracy use the printed value on the sticker! Value in K ohms 88 | volatile int timerCounts = 0; 89 | bool joined = false; 90 | unsigned long joinEntry; 91 | 92 | volatile boxStatus mailboxStatus = empty; 93 | 94 | // 95 | // For normal use, we require that you edit the sketch to replace FILLMEIN 96 | // with values assigned by the TTN console. However, for regression tests, 97 | // we want to be able to compile these scripts. The regression tests define 98 | // COMPILE_REGRESSION_TEST, and in that case we define FILLMEIN to a non- 99 | // working but innocuous value. 100 | // 101 | //# define COMPILE_REGRESSION_TEST //comment this out if you want to compile this sketch with 'FILLMEIN' values for APPEUI, DEVEUI and APPKEY 102 | #ifdef COMPILE_REGRESSION_TEST 103 | # define FILLMEIN 0 104 | #else 105 | # warning "You must replace the values marked FILLMEIN with real values from the TTN control panel!" 106 | # define FILLMEIN (#dont edit this, edit the lines that use FILLMEIN) 107 | #endif 108 | 109 | // This EUI must be in little-endian format, so least-significant-byte 110 | // first. When copying an EUI from ttnctl output, this means to reverse 111 | // the bytes. For TTN issued EUIs the last bytes should be 0xD5, 0xB3, 112 | // 0x70. 113 | static const u1_t PROGMEM APPEUI[8] = { FILLMEIN_APPEUI }; 114 | void os_getArtEui(u1_t* buf) { 115 | memcpy_P(buf, APPEUI, 8); 116 | } 117 | 118 | // This should also be in little endian format, see above. 119 | static const u1_t PROGMEM DEVEUI[8] = { FILLMEIN_DEVEUI }; 120 | void os_getDevEui(u1_t* buf) { 121 | memcpy_P(buf, DEVEUI, 8); 122 | } 123 | 124 | // This key should be in big endian format (or, since it is not really a 125 | // number but a block of memory, endianness does not really apply). In 126 | // practice, a key taken from ttnctl can be copied as-is. 127 | static const u1_t PROGMEM APPKEY[16] = { FILLMEIN_APPKEY }; 128 | void os_getDevKey(u1_t* buf) { 129 | memcpy_P(buf, APPKEY, 16); 130 | } 131 | 132 | static osjob_t sendjob; 133 | 134 | // This value is only used to hack timer0, set it to the programmed time configuration of the TPL5010 135 | const unsigned TX_INTERVAL = 120; 136 | 137 | // Pin mapping 138 | const lmic_pinmap lmic_pins = { 139 | .nss = 14, 140 | .rxtx = LMIC_UNUSED_PIN, 141 | .rst = 13, 142 | .dio = {10, 11, 12}, 143 | }; 144 | 145 | void printHex2(unsigned v) { 146 | v &= 0xff; 147 | #ifdef DEBUG 148 | if (v < 16) 149 | Serial.print('0 '); 150 | Serial.print(v, HEX); 151 | #endif 152 | } 153 | 154 | void onEvent (ev_t ev) { 155 | DEBUGPRINT(os_getTime()); 156 | DEBUGPRINT(": "); 157 | switch (ev) { 158 | case EV_SCAN_TIMEOUT: 159 | DEBUGPRINTLN(F("EV_SCAN_TIMEOUT")); 160 | break; 161 | case EV_BEACON_FOUND: 162 | DEBUGPRINTLN(F("EV_BEACON_FOUND")); 163 | break; 164 | case EV_BEACON_MISSED: 165 | DEBUGPRINTLN(F("EV_BEACON_MISSED")); 166 | break; 167 | case EV_BEACON_TRACKED: 168 | DEBUGPRINTLN(F("EV_BEACON_TRACKED")); 169 | break; 170 | case EV_JOINING: 171 | DEBUGPRINTLN(F("EV_JOINING")); 172 | break; 173 | case EV_JOINED: 174 | DEBUGPRINTLN(F("EV_JOINED")); 175 | joined = true; 176 | { 177 | u4_t netid = 0; 178 | devaddr_t devaddr = 0; 179 | u1_t nwkKey[16]; 180 | u1_t artKey[16]; 181 | LMIC_getSessionKeys(&netid, &devaddr, nwkKey, artKey); 182 | #ifdef DEBUG 183 | Serial.print("netid: "); 184 | Serial.println(netid, DEC); 185 | Serial.print("devaddr: "); 186 | Serial.println(devaddr, HEX); 187 | Serial.print("AppSKey: "); 188 | #endif 189 | for (size_t i = 0; i < sizeof(artKey); ++i) { 190 | if (i != 0) 191 | DEBUGPRINT("-"); 192 | printHex2(artKey[i]); 193 | } 194 | DEBUGPRINTLN(""); 195 | DEBUGPRINT("NwkSKey: "); 196 | for (size_t i = 0; i < sizeof(nwkKey); ++i) { 197 | if (i != 0) 198 | DEBUGPRINT("-"); 199 | printHex2(nwkKey[i]); 200 | } 201 | DEBUGPRINTLN(); 202 | } 203 | // Disable link check validation (automatically enabled 204 | // during join, but because slow data rates change max TX 205 | // size, we don't use it in this example. 206 | LMIC_setLinkCheckMode(0); 207 | break; 208 | /* 209 | || This event is defined but not used in the code. No 210 | || point in wasting codespace on it. 211 | || 212 | || case EV_RFU1: 213 | || DEBUGPRINTLN(F("EV_RFU1")); 214 | || break; 215 | */ 216 | case EV_JOIN_FAILED: 217 | DEBUGPRINTLN(F("EV_JOIN_FAILED")); 218 | break; 219 | case EV_REJOIN_FAILED: 220 | DEBUGPRINTLN(F("EV_REJOIN_FAILED")); 221 | break; 222 | case EV_TXCOMPLETE: 223 | Serial.println(F("EV_TXCOMPLETE (includes waiting for RX windows)")); 224 | if (LMIC.txrxFlags & TXRX_ACK) 225 | DEBUGPRINTLN(F("Received ack")); 226 | if (LMIC.dataLen) { 227 | DEBUGPRINT(F("Received ")); 228 | DEBUGPRINT(LMIC.dataLen); 229 | DEBUGPRINTLN(F(" bytes of payload")); 230 | } 231 | // ready for next transmission 232 | transmitting = false; 233 | //os_setTimedCallback(&sendjob, os_getTime()+sec2osticks(TX_INTERVAL), do_send); 234 | break; 235 | case EV_LOST_TSYNC: 236 | DEBUGPRINTLN(F("EV_LOST_TSYNC")); 237 | break; 238 | case EV_RESET: 239 | DEBUGPRINTLN(F("EV_RESET")); 240 | break; 241 | case EV_RXCOMPLETE: 242 | // data received in ping slot 243 | DEBUGPRINTLN(F("EV_RXCOMPLETE")); 244 | break; 245 | case EV_LINK_DEAD: 246 | DEBUGPRINTLN(F("EV_LINK_DEAD")); 247 | break; 248 | case EV_LINK_ALIVE: 249 | DEBUGPRINTLN(F("EV_LINK_ALIVE")); 250 | break; 251 | /* 252 | || This event is defined but not used in the code. No 253 | || point in wasting codespace on it. 254 | || 255 | || case EV_SCAN_FOUND: 256 | || DEBUGPRINTLN(F("EV_SCAN_FOUND")); 257 | || break; 258 | */ 259 | case EV_TXSTART: 260 | DEBUGPRINTLN(F("EV_TXSTART")); 261 | break; 262 | case EV_TXCANCELED: 263 | DEBUGPRINTLN(F("EV_TXCANCELED")); 264 | break; 265 | case EV_RXSTART: 266 | /* do not print anything -- it wrecks timing */ 267 | break; 268 | case EV_JOIN_TXCOMPLETE: 269 | DEBUGPRINTLN(F("EV_JOIN_TXCOMPLETE: no JoinAccept")); 270 | break; 271 | 272 | default: 273 | DEBUGPRINT(F("Unknown event: ")); 274 | DEBUGPRINTLN((unsigned) ev); 275 | break; 276 | } 277 | } 278 | 279 | uint16_t analogOversample(int pin) { 280 | uint16_t reading = 0; 281 | int i = 0; 282 | while (i < 8) 283 | { 284 | i++; 285 | reading = reading + analogRead(pin); 286 | } 287 | return reading >> 3; 288 | } 289 | 290 | int readBattVoltage() { 291 | //Enable voltage divider, warm up ADC, read voltage, disable voltage divider 292 | digitalWrite(voltageDividerPin, HIGH); 293 | for (int i = 0; i < 16; i++) analogRead(batteryReadPin); // first readings are not accurate 294 | uint16_t ADCreading = analogOversample(batteryReadPin); 295 | digitalWrite(voltageDividerPin, LOW); 296 | 297 | //Convert analog ADC reading to millivolts 298 | int _voltage = (((ADCreading * AREF / 1024.0) * (RSUP + RGND) / RGND) * 100); 299 | return _voltage; 300 | } 301 | 302 | void do_send(osjob_t* j) { 303 | // Check if there is not a current TX/RX job running 304 | if (LMIC.opmode & OP_TXRXPEND) { 305 | DEBUGPRINTLN(F("OP_TXRXPEND, not sending")); 306 | } else { 307 | // Prepare upstream data transmission at the next possible time. 308 | int voltage = readBattVoltage(); 309 | 310 | DEBUGPRINT("Reason "); 311 | DEBUGPRINT(wakeupReason); 312 | 313 | DEBUGPRINT(" mailAvalable "); 314 | DEBUGPRINT(mailboxStatus); 315 | 316 | DEBUGPRINT(" Voltage "); 317 | DEBUGPRINTLN(voltage); 318 | 319 | //Prepare uplink data 320 | byte mydata[3]; 321 | mydata[0] = wakeupReason | mailboxStatus << 7; 322 | mydata[1] = voltage >> 8; 323 | mydata[2] = voltage; 324 | 325 | /* 326 | 327 | //TTNv3 Payload decoder 328 | function decodeUplink(input) { 329 | var data = {}; 330 | if (input.fPort == 1) { 331 | data.voltage = ((input.bytes[1] << 8) | input.bytes[2]) / 100.00; 332 | data.reason = (input.bytes[0])& 0x0F; 333 | data.boxStatus = (input.bytes[0]>>7) 334 | } 335 | return { 336 | data: data, 337 | warnings: [], 338 | errors: [] 339 | }; 340 | } 341 | 342 | */ 343 | 344 | LMIC_setTxData2(1, mydata, sizeof(mydata), 0); 345 | DEBUGPRINTLN(F("Packet queued")); 346 | transmitting = true; 347 | 348 | // Set wakeupReason value to unknown, (the device woke up because of an unknown reason) 349 | wakeupReason = unknown; 350 | } 351 | // Next TX is scheduled after TX_COMPLETE event. 352 | } 353 | 354 | void wakeupFromTimer(void) { 355 | if (digitalRead(TPLWakePin) == HIGH) { 356 | feedWatchdog(); 357 | wakeupReason = timer; 358 | timerCounts++; 359 | } 360 | } 361 | 362 | void wakeupFromLidSwitch(void) { 363 | delay(10); 364 | if (digitalRead(lidSwitch) == LOW) { 365 | wakeupReason = lid; 366 | DEBUGPRINTLN("Lid defined"); 367 | } else wakeupReason = undefined; 368 | } 369 | 370 | void wakeupFromDoorSwitch(void) { 371 | delay(10); 372 | if (digitalRead(doorSwitch) == LOW) { 373 | wakeupReason = door; 374 | DEBUGPRINTLN("Door defined"); 375 | } else wakeupReason = undefined; 376 | } 377 | 378 | void do_sendmac(osjob_t* j) { 379 | // This function is called if we need to process a MAC message 380 | // Check if there is not a current TX/RX job running 381 | if (LMIC.opmode & OP_TXRXPEND) { 382 | DEBUGPRINTLN(F("OP_TXRXPEND, not sending")); 383 | } 384 | else { 385 | // Prepare upstream data transmission at the next possible time. 386 | byte mydata[1]; 387 | LMIC_setTxData2(0, mydata, sizeof(mydata), 0); 388 | DEBUGPRINTLN(F("Packet queued (MAC command)")); 389 | transmitting = true; 390 | } 391 | // Next TX is scheduled after TX_COMPLETE event. 392 | } 393 | 394 | void setup() { 395 | Serial.begin(115200); 396 | DEBUGPRINTLN("Starting"); 397 | 398 | // Pin settings for TPL5010 usage 399 | pinMode(TPLWakePin, INPUT); 400 | pinMode(donePin, OUTPUT); 401 | digitalWrite(donePin, LOW); 402 | 403 | // Pin settings for User interrupt pin 404 | pinMode(doorSwitch, INPUT); 405 | pinMode(lidSwitch, INPUT); 406 | 407 | // Pin settings to read supply voltage 408 | analogReference(INTERNAL1V1); // Set internal reference to 1.1V 409 | pinMode(batteryReadPin, INPUT); // Set analog pin to input 410 | pinMode(voltageDividerPin, OUTPUT); // Set pin which controls the voltage divider to output 411 | while (joined == false) { 412 | joinEntry = millis(); 413 | // LMIC init 414 | os_init(); 415 | // Reset the MAC state. Session and pending data transfers will be discarded. 416 | LMIC_reset(); 417 | 418 | // Start job (sending automatically starts OTAA too) 419 | do_send(&sendjob); 420 | while (millis() - joinEntry < 20000) os_runloop_once(); // wait for join 421 | } 422 | feedWatchdog(); 423 | DEBUGPRINTLN("Attach Interrupts"); 424 | // Attach TPL5010 interrupt 425 | attachPCINT(digitalPinToPCINT(TPLWakePin), wakeupFromTimer, CHANGE); 426 | 427 | // Attach user interrupt pin 428 | attachPCINT(digitalPinToPCINT(lidSwitch), wakeupFromLidSwitch, CHANGE); 429 | 430 | // Attach user interrupt pin 431 | attachPCINT(digitalPinToPCINT(doorSwitch), wakeupFromDoorSwitch, CHANGE); 432 | DEBUGPRINTLN("Initialized"); 433 | } 434 | 435 | void loop() { 436 | extern volatile unsigned long timer0_overflow_count; 437 | os_runloop_once(); 438 | if (transmitting == true) { 439 | // If we've to wait for the RFM95 module to complete TX or wait for RX window can't powerDown the MCU, 440 | // as LMIC controls RX timing and LMIC uses micros(), powerDown would stop micros() from running. 441 | // Instead we can put the MCU into idle state with TIMER0_ON. Use here the lowest possible sleeptime (SLEEP_15MS), 442 | // as TIMER0 will overflow every 1ms, MCU will wake up every ms again. Also, MCU will be waken up after the 443 | // selected time, in this case 15ms. We can save about 60% of the power during waiting with this simple trick. 444 | 445 | // Check if any time critical jobs is due in the next 1ms 446 | if (os_queryTimeCriticalJobs(ms2osticks(1)) == 0) { 447 | // If no time critical job is due, enter idle mode for 1ms 448 | sleep_short(); 449 | } 450 | } 451 | else { 452 | if (LMIC.pendMacLen > 0) { 453 | DEBUGPRINTLN("Pending MAC message"); 454 | do_sendmac(&sendjob); 455 | } 456 | else { 457 | DEBUGPRINTLN("Sleep long"); 458 | Serial.flush(); // give the serial print chance to complete 459 | delay(20); // We need here some delay to prevent wakeup from WDT we used before. 20ms seems to be safe. 460 | 461 | // set DONE high for 100 micro seconds to tell the TPL5010 we're done 462 | // if DONE was not high between TPL5010 interrupts, TPL5010 will reset the MCU through the reset pin if connected, otherwise it will do nothing until 'DONE' was set high once 463 | sleep_long(); 464 | // interrupt happened 465 | switch (wakeupReason) { 466 | case undefined: 467 | break; 468 | case timer: 469 | Serial.println("Timer"); 470 | if (timerCounts >= TIMERINTERVALS) { 471 | Serial.println("Timer transmitted"); 472 | do_send(&sendjob); 473 | timerCounts = 0; 474 | break; 475 | case lid: 476 | mailboxStatus = full; 477 | do_send(&sendjob); 478 | timerCounts = 0; // timer reset 479 | break; 480 | case door: 481 | mailboxStatus = empty; 482 | do_send(&sendjob); 483 | timerCounts = 0; // timer reset 484 | break; 485 | case unknown: 486 | break; 487 | } 488 | // LMIC uses micros() to keep track of the duty cycle, so 489 | // hack timer0_overflow for a rude adjustment: 490 | timer0_overflow_count += TX_INTERVAL * 64 * clockCyclesPerMicrosecond(); 491 | } 492 | wakeupReason = undefined; 493 | } 494 | } 495 | } 496 | 497 | void sleep_long() { 498 | #ifdef DEBUGINTERRUPT 499 | DEBUGPRINTLN("Sleep long"); 500 | while ((wakeupReason != door) && (wakeupReason != lid) && (wakeupReason != timer)) { 501 | // LowPower.powerDown(SLEEP_FOREVER, ADC_OFF, BOD_OFF); 502 | } 503 | #else 504 | LowPower.powerDown(SLEEP_FOREVER, ADC_OFF, BOD_OFF); 505 | #endif 506 | DEBUGPRINTLN("Sleep long finished"); 507 | } 508 | 509 | int i = 0; 510 | void sleep_short() { 511 | #ifdef DEBUG 512 | if (i > 300) { 513 | DEBUGPRINT("."); 514 | i = 0; 515 | } 516 | i++; 517 | delay(5); 518 | #else 519 | LowPower.idle(SLEEP_15MS, ADC_OFF, TIMER2_OFF, TIMER1_OFF, TIMER0_ON, SPI_OFF, USART1_OFF, USART0_OFF, TWI_OFF); 520 | #endif 521 | } 522 | 523 | void feedWatchdog() { 524 | digitalWrite(donePin, HIGH); 525 | delayMicroseconds(200); 526 | digitalWrite(donePin, LOW); 527 | } 528 | -------------------------------------------------------------------------------- /MailboxNotifierCubeCell/MailboxNotifierCubeCell.ino: -------------------------------------------------------------------------------- 1 | #include "LoRaWan_APP.h" 2 | #include "Arduino.h" 3 | 4 | #define FILLMEIN_APPEUI 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00 5 | #define FILLMEIN_DEVEUI 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00 6 | #define FILLMEIN_APPKEY 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00 7 | #include "TTN_Credentials.h" 8 | 9 | /* OTAA para*/ 10 | uint8_t devEui[] = { FILLMEIN_DEVEUI }; 11 | uint8_t appEui[] = { FILLMEIN_APPEUI }; 12 | uint8_t appKey[] = { FILLMEIN_APPKEY }; 13 | 14 | /* ABP para*/ 15 | uint8_t nwkSKey[] = { 0x15, 0xb1, 0xd0, 0xef, 0xa4, 0x63, 0xdf, 0xbe, 0x3d, 0x11, 0x18, 0x1e, 0x1e, 0xc7, 0xda, 0x85 }; 16 | uint8_t appSKey[] = { 0xd7, 0x2c, 0x78, 0x75, 0x8c, 0xdc, 0xca, 0xbf, 0x55, 0xee, 0x4a, 0x77, 0x8d, 0x16, 0xef, 0x67 }; 17 | uint32_t devAddr = ( uint32_t )0x007e6ae1; 18 | 19 | /*LoraWan channelsmask, default channels 0-7*/ 20 | uint16_t userChannelsMask[6] = { 0x00FF, 0x0000, 0x0000, 0x0000, 0x0000, 0x0000 }; 21 | 22 | // The interrupt pin is attached to USER_KEY 23 | #define INT_PIN USER_KEY 24 | 25 | /* Application port */ 26 | #define DEVPORT 2 27 | #define APPPORT 1 28 | 29 | bool accelWoke = false; 30 | int interruptPin = INT_PIN; 31 | 32 | /*LoraWan region, select in arduino IDE tools*/ 33 | LoRaMacRegion_t loraWanRegion = ACTIVE_REGION; 34 | 35 | /*LoraWan Class, Class A and Class C are supported*/ 36 | DeviceClass_t loraWanClass = LORAWAN_CLASS; 37 | 38 | /*the application data transmission duty cycle. value in [ms].*/ 39 | /*For this example, this is the frequency of the device status packets */ 40 | uint32_t appTxDutyCycle = (1 * 1 * 30 * 1000); // ever hour; 41 | 42 | /*OTAA or ABP*/ 43 | bool overTheAirActivation = LORAWAN_NETMODE; 44 | 45 | /*ADR enable*/ 46 | bool loraWanAdr = LORAWAN_ADR; 47 | 48 | /* set LORAWAN_Net_Reserve ON, the node could save the network info to flash, when node reset not need to join again */ 49 | bool keepNet = LORAWAN_NET_RESERVE; 50 | 51 | /* Indicates if the node is sending confirmed or unconfirmed messages */ 52 | bool isTxConfirmed = LORAWAN_UPLINKMODE; 53 | 54 | /* Application port */ 55 | uint8_t appPort = DEVPORT; 56 | /*! 57 | Number of trials to transmit the frame, if the LoRaMAC layer did not 58 | receive an acknowledgment. The MAC performs a datarate adaptation, 59 | according to the LoRaWAN Specification V1.0.2, chapter 18.4, according 60 | to the following table: 61 | 62 | Transmission nb | Data Rate 63 | ----------------|----------- 64 | 1 (first) | DR 65 | 2 | DR 66 | 3 | max(DR-1,0) 67 | 4 | max(DR-1,0) 68 | 5 | max(DR-2,0) 69 | 6 | max(DR-2,0) 70 | 7 | max(DR-3,0) 71 | 8 | max(DR-3,0) 72 | 73 | Note, that if NbTrials is set to 1 or 2, the MAC will not decrease 74 | the datarate, in case the LoRaMAC layer did not receive an acknowledgment 75 | */ 76 | uint8_t confirmedNbTrials = 4; 77 | 78 | 79 | /* Prepares the payload of the frame */ 80 | static bool prepareTxFrame( uint8_t port ) 81 | { 82 | int head; 83 | uint16_t voltage; 84 | appDataSize = 3;//AppDataSize max value is 64 85 | appPort = port; 86 | voltage = (int)getBatteryVoltage()/10; 87 | Serial.println(voltage); 88 | Serial.println(voltage, HEX); 89 | appDataSize = 3;//AppDataSize max value is 64 90 | appData[1] = voltage >> 8; 91 | appData[2] = voltage; 92 | Serial.println(appData[1], HEX); 93 | Serial.println(appData[2], HEX); 94 | switch (port) { 95 | case APPPORT: // woke up from interrupt 96 | appData[0] = 0x02; // set to something useful 97 | break; 98 | case DEVPORT: // daily wake up 99 | Serial.println("Sending dev status packet"); 100 | appData[0] = 0x01; // set to something else useful 101 | break; 102 | } 103 | return true; 104 | } 105 | 106 | void accelWakeup() 107 | { 108 | delay(10); 109 | if (digitalRead(INT_PIN) == HIGH) 110 | { 111 | accelWoke = true; 112 | } 113 | } 114 | 115 | void setup() { 116 | Serial.begin(115200); 117 | #if(AT_SUPPORT) 118 | enableAt(); 119 | #endif 120 | deviceState = DEVICE_STATE_INIT; 121 | LoRaWAN.ifskipjoin(); 122 | 123 | accelWoke = false; 124 | pinMode(INT_PIN, INPUT); 125 | attachInterrupt(INT_PIN, accelWakeup, RISING); 126 | Serial.println("Interrupts attached"); 127 | } 128 | 129 | void loop() 130 | { 131 | if (accelWoke) { 132 | uint32_t now = TimerGetCurrentTime(); 133 | Serial.print(now); Serial.println("accel woke"); 134 | } 135 | 136 | switch ( deviceState ) 137 | { 138 | case DEVICE_STATE_INIT: 139 | { 140 | #if(LORAWAN_DEVEUI_AUTO) 141 | LoRaWAN.generateDeveuiByChipID(); 142 | #endif 143 | #if(AT_SUPPORT) 144 | getDevParam(); 145 | #endif 146 | printDevParam(); 147 | LoRaWAN.init(loraWanClass, loraWanRegion); 148 | deviceState = DEVICE_STATE_JOIN; 149 | break; 150 | } 151 | case DEVICE_STATE_JOIN: 152 | { 153 | LoRaWAN.join(); 154 | break; 155 | } 156 | case DEVICE_STATE_SEND: 157 | { 158 | prepareTxFrame( DEVPORT ); 159 | // LoRaWAN.setDataRateForNoADR(2); 160 | LoRaWAN.send(); 161 | deviceState = DEVICE_STATE_CYCLE; 162 | break; 163 | } 164 | case DEVICE_STATE_CYCLE: 165 | { 166 | // Schedule next packet transmission 167 | txDutyCycleTime = appTxDutyCycle + randr( 0, APP_TX_DUTYCYCLE_RND ); 168 | LoRaWAN.cycle(txDutyCycleTime); 169 | deviceState = DEVICE_STATE_SLEEP; 170 | break; 171 | } 172 | case DEVICE_STATE_SLEEP: 173 | { 174 | if (accelWoke) { 175 | if (IsLoRaMacNetworkJoined) { 176 | if (prepareTxFrame(APPPORT)) { 177 | LoRaWAN.send(); 178 | } 179 | } 180 | accelWoke = false; 181 | } 182 | LoRaWAN.sleep(); 183 | break; 184 | } 185 | default: 186 | { 187 | deviceState = DEVICE_STATE_INIT; 188 | break; 189 | } 190 | } 191 | } 192 | -------------------------------------------------------------------------------- /Node-Red flow.txt: -------------------------------------------------------------------------------- 1 | [{"id":"de021e5f.ed678","type":"debug","z":"a0cf871.496b078","name":"","active":true,"tosidebar":true,"console":false,"tostatus":false,"complete":"false","statusVal":"","statusType":"auto","x":510,"y":40,"wires":[]},{"id":"dbeeb6bd.9256e8","type":"function","z":"a0cf871.496b078","name":"Mail arrived","func":"\nvar batt=msg.payload.uplink_message.decoded_payload.voltage;\nvar event=msg.payload.uplink_message.decoded_payload.event;\nvar rssi=msg.payload.uplink_message.rx_metadata[0].rssi;\nvar now = new Date();\nvar n = now.toLocaleString()\nvar beg=n.indexOf(\",\")+2\nvar end=n.indexOf(\"M\")+1\n \n msg.payload=\"Mail arrived at \"+ n.substring(beg, end) + \" Voltage: \" + batt+\" RSSI: \"+rssi;\nnode.warn(msg.payload);\nnode.status({fill:\"green\",shape:\"ring\",text:batt});\nreturn msg;","outputs":1,"noerr":0,"initialize":"","finalize":"","x":1070,"y":120,"wires":[["ca84b4a7.cc19c8","d244c78c.263158"]]},{"id":"ca84b4a7.cc19c8","type":"debug","z":"a0cf871.496b078","name":"","active":false,"tosidebar":true,"console":false,"tostatus":false,"complete":"false","statusVal":"","statusType":"auto","x":1370,"y":180,"wires":[]},{"id":"948fea06.f1a0a8","type":"telegram sender","z":"a0cf871.496b078","name":"MailboxBot","bot":"f46ee038.b7ff8","x":1750,"y":120,"wires":[[]]},{"id":"d244c78c.263158","type":"function","z":"a0cf871.496b078","name":"Create Message","func":"hi= msg.payload;\npayload={chatId:876235944,\ntype:\"message\",\ncontent: hi\n};\nnode.status({fill:\"green\",shape:\"ring\",text:msg.payload});\nreturn {payload};","outputs":1,"noerr":0,"initialize":"","finalize":"","x":1380,"y":120,"wires":[["948fea06.f1a0a8"]]},{"id":"ceebe47d.b2af18","type":"function","z":"a0cf871.496b078","name":"Mapping","func":"var dat= Date.now()\nvar batt=msg.payload.uplink_message.decoded_payload.voltage;\nvar boxStatus=msg.payload.uplink_message.decoded_payload.boxStatus;\nvar reason=msg.payload.uplink_message.decoded_payload.reason;\nvar rssi=msg.payload.uplink_message.rx_metadata[0].rssi;\nmsg.payload = [\n{\n Time: dat.toString(),\n status: boxStatus,\n event: reason,\n port: msg.payload.uplink_message.f_port,\n BAT: batt,\n RSSI: rssi,\n count: msg.payload.uplink_message.f_cnt |0\n},{\n name: \"Mailbox\"\n \n}]\nnode.status({fill:\"green\",shape:\"ring\",text:rssi});\nreturn msg;","outputs":1,"noerr":0,"initialize":"","finalize":"","x":1060,"y":600,"wires":[["e0e13023.651a7","c4308dd4.b06cd"]]},{"id":"e0e13023.651a7","type":"debug","z":"a0cf871.496b078","name":"","active":false,"tosidebar":true,"console":false,"tostatus":false,"complete":"false","x":1290,"y":660,"wires":[]},{"id":"c4308dd4.b06cd","type":"influxdb out","z":"a0cf871.496b078","influxdb":"5a0c58b0.d9e658","name":"Mailbox","measurement":"Deliveries","precision":"s","retentionPolicy":"","database":"","retentionPolicyV18Flux":"","org":"","bucket":"","x":1280,"y":600,"wires":[]},{"id":"85e48340.90f2b","type":"trigger","z":"a0cf871.496b078","name":"","op1":"ON","op2":"OFF","op1type":"str","op2type":"str","duration":"500","extend":false,"overrideDelay":false,"units":"ms","reset":"","bytopic":"all","topic":"topic","outputs":1,"x":1120,"y":280,"wires":[["d4b10815.7e9a08","9ac2bf8e.b908c"]]},{"id":"88ab5f96.5f5d2","type":"inject","z":"a0cf871.496b078","name":"","props":[{"p":"payload"},{"p":"topic","vt":"str"}],"repeat":"","crontab":"","once":false,"onceDelay":0.1,"topic":"","payload":"","payloadType":"date","x":800,"y":280,"wires":[["85e48340.90f2b"]]},{"id":"d4b10815.7e9a08","type":"mqtt out","z":"a0cf871.496b078","name":"","topic":"/Bell/cmnd/Power1","qos":"0","retain":"","broker":"3ca313b8.542abc","x":1390,"y":300,"wires":[]},{"id":"9ac2bf8e.b908c","type":"debug","z":"a0cf871.496b078","name":"","active":true,"tosidebar":true,"console":false,"tostatus":false,"complete":"false","statusVal":"","statusType":"auto","x":1370,"y":360,"wires":[]},{"id":"b1e9676e.ff9de8","type":"switch","z":"a0cf871.496b078","name":"","property":"payload.uplink_message.decoded_payload.reason","propertyType":"msg","rules":[{"t":"eq","v":"0","vt":"str"},{"t":"eq","v":"1","vt":"str"},{"t":"eq","v":"2","vt":"str"},{"t":"eq","v":"3","vt":"str"}],"checkall":"true","repair":false,"outputs":4,"x":730,"y":120,"wires":[["4b8000d.b9a3e"],["820ec2c.333774","9f34f62b.246838"],["dbeeb6bd.9256e8","9db90220.c6d7d","85e48340.90f2b"],["a4c08db9.4ad48","714ceeb.a6a811"]]},{"id":"4b8000d.b9a3e","type":"debug","z":"a0cf871.496b078","name":"","active":false,"tosidebar":true,"console":false,"tostatus":false,"complete":"false","statusVal":"","statusType":"auto","x":930,"y":20,"wires":[]},{"id":"576ec7d5.f62e88","type":"function","z":"a0cf871.496b078","name":"Create Message","func":"hi= msg.payload;\npayload={chatId:876235944,\ntype:\"message\",\ncontent: hi\n};\nreturn {payload};","outputs":1,"noerr":0,"initialize":"","finalize":"","x":1380,"y":60,"wires":[["c603c805.494968"]]},{"id":"c603c805.494968","type":"telegram sender","z":"a0cf871.496b078","name":"MailboxBot","bot":"f46ee038.b7ff8","x":1750,"y":60,"wires":[[]]},{"id":"820ec2c.333774","type":"timeout","z":"a0cf871.496b078","name":"Mailbox Timeout","outtopic":"alarm","outsafe":"","outwarning":"","outunsafe":"Mailbox stopped","warning":"1","timer":"26000","repeat":false,"again":true,"x":1080,"y":60,"wires":[["576ec7d5.f62e88"]]},{"id":"5c96f044.3e037","type":"inject","z":"a0cf871.496b078","name":"1","props":[{"p":"payload.uplink_message.decoded_payload.event","v":"1","vt":"str"},{"p":"topic","vt":"str"}],"repeat":"","crontab":"","once":false,"onceDelay":0.1,"topic":"","x":230,"y":240,"wires":[["b1e9676e.ff9de8"]]},{"id":"5be5732d.764a3c","type":"inject","z":"a0cf871.496b078","name":"2","props":[{"p":"payload.uplink_message.decoded_payload.event","v":"2","vt":"str"},{"p":"topic","vt":"str"}],"repeat":"","crontab":"","once":false,"onceDelay":0.1,"topic":"","x":230,"y":300,"wires":[["b1e9676e.ff9de8"]]},{"id":"7e6b73a2.62ebdc","type":"inject","z":"a0cf871.496b078","name":"3","props":[{"p":"payload.uplink_message.decoded_payload.event","v":"3","vt":"str"},{"p":"topic","vt":"str"}],"repeat":"","crontab":"","once":false,"onceDelay":0.1,"topic":"","x":230,"y":360,"wires":[["b1e9676e.ff9de8"]]},{"id":"a4c08db9.4ad48","type":"function","z":"a0cf871.496b078","name":"","func":"\nvar batt=msg.payload.uplink_message.decoded_payload.voltage;\nvar event=msg.payload.uplink_message.decoded_payload.event;\nvar rssi=msg.payload.uplink_message.rx_metadata[0].rssi;\nvar now = new Date();\nvar n = now.toLocaleString()\nvar beg=n.indexOf(\",\")+2\nvar end=n.indexOf(\"M\")+1\n\nmsg.payload=\"Mailbox emptied \"+ n.substring(beg, end) + \" Voltage: \" + batt +\" RSSI: \"+rssi;\nnode.warn(msg.payload);\nnode.status({fill:\"green\",shape:\"ring\",text:batt});\nreturn msg;","outputs":1,"noerr":0,"initialize":"","finalize":"","x":1060,"y":480,"wires":[["6d6d192.f09fee8","cd7d24c1.919dd8"]]},{"id":"6d6d192.f09fee8","type":"debug","z":"a0cf871.496b078","name":"","active":false,"tosidebar":true,"console":false,"tostatus":false,"complete":"false","statusVal":"","statusType":"auto","x":1350,"y":520,"wires":[]},{"id":"ab042e12.79423","type":"telegram sender","z":"a0cf871.496b078","name":"MailboxBot","bot":"f46ee038.b7ff8","x":1610,"y":460,"wires":[[]]},{"id":"cd7d24c1.919dd8","type":"function","z":"a0cf871.496b078","name":"Create Message","func":"hi= msg.payload;\npayload={chatId:876235944,\ntype:\"message\",\ncontent: hi\n};\nnode.status({fill:\"green\",shape:\"ring\",text:msg.payload});\nreturn {payload};","outputs":1,"noerr":0,"initialize":"","finalize":"","x":1360,"y":460,"wires":[["ab042e12.79423"]]},{"id":"dc484e62.506e7","type":"mqtt in","z":"a0cf871.496b078","name":"","topic":"v3/mailbox-notifier-sensorsiot@ttn/devices/#","qos":"0","datatype":"json","broker":"f3f5e84c.b41648","x":170,"y":140,"wires":[["de021e5f.ed678","ceebe47d.b2af18","b1e9676e.ff9de8"]]},{"id":"ae1b9b6e.df07f8","type":"mqtt out","z":"a0cf871.496b078","name":"","topic":"Notifier/cmd","qos":"0","retain":"","broker":"3ca313b8.542abc","x":1410,"y":800,"wires":[]},{"id":"9db90220.c6d7d","type":"function","z":"a0cf871.496b078","name":"s4 Mailbox full","func":"msg.payload=\"s4\"\nreturn msg;","outputs":1,"noerr":0,"initialize":"","finalize":"","x":1080,"y":800,"wires":[["ae1b9b6e.df07f8","e6d9f4f8.8d02c8"]]},{"id":"e6d9f4f8.8d02c8","type":"debug","z":"a0cf871.496b078","name":"","active":false,"tosidebar":true,"console":false,"tostatus":false,"complete":"false","statusVal":"","statusType":"auto","x":1290,"y":740,"wires":[]},{"id":"aa3a01bd.122e5","type":"mqtt out","z":"a0cf871.496b078","name":"","topic":"Notifier/cmd","qos":"0","retain":"","broker":"3ca313b8.542abc","x":1410,"y":920,"wires":[]},{"id":"714ceeb.a6a811","type":"function","z":"a0cf871.496b078","name":"c4 Mailbox empty","func":"msg.payload=\"c4\"\nreturn msg;","outputs":1,"noerr":0,"initialize":"","finalize":"","x":1090,"y":920,"wires":[["aa3a01bd.122e5","9c3e5a62.8548e8"]]},{"id":"9c3e5a62.8548e8","type":"debug","z":"a0cf871.496b078","name":"","active":false,"tosidebar":true,"console":false,"tostatus":false,"complete":"false","statusVal":"","statusType":"auto","x":1290,"y":860,"wires":[]},{"id":"dc037bbd.5e86a8","type":"switch","z":"a0cf871.496b078","name":"Mailbox full","property":"payload","propertyType":"msg","rules":[{"t":"eq","v":"0","vt":"str"},{"t":"eq","v":"1","vt":"str"}],"checkall":"true","repair":false,"outputs":2,"x":590,"y":500,"wires":[["714ceeb.a6a811"],["9db90220.c6d7d"]]},{"id":"9f34f62b.246838","type":"function","z":"a0cf871.496b078","name":"","func":"msg.payload=msg.payload.uplink_message.decoded_payload.boxStatus;\nreturn msg;","outputs":1,"noerr":0,"initialize":"","finalize":"","x":390,"y":500,"wires":[["dc037bbd.5e86a8"]]},{"id":"f46ee038.b7ff8","type":"telegram bot","botname":"sensorsIOTMailboxBot","usernames":"","chatids":"","baseapiurl":"","updatemode":"polling","pollinterval":"300","usesocks":false,"sockshost":"","socksport":"6667","socksusername":"anonymous","sockspassword":"","bothost":"","localbotport":"8443","publicbotport":"8443","privatekey":"","certificate":"","useselfsignedcertificate":false,"sslterminated":false,"verboselogging":false},{"id":"5a0c58b0.d9e658","type":"influxdb","hostname":"influxdb","port":"8086","protocol":"http","database":"mailbox","name":"Mailbox","usetls":false,"tls":"c149cddf.15d68","influxdbVersion":"1.x","url":"http://localhost:8086","rejectUnauthorized":true},{"id":"3ca313b8.542abc","type":"mqtt-broker","name":"Hub","broker":"mosquitto","port":"1883","clientid":"","usetls":false,"compatmode":true,"keepalive":"60","cleansession":true,"birthTopic":"","birthQos":"0","birthRetain":"false","birthPayload":"","closeTopic":"","closeQos":"0","closePayload":"","willTopic":"","willQos":"0","willPayload":""},{"id":"f3f5e84c.b41648","type":"mqtt-broker","name":"sensorsiot-mailbox-notifier@ttn","broker":"eu1.cloud.thethings.network","port":"8883","tls":"c149cddf.15d68","clientid":"","usetls":true,"compatmode":false,"keepalive":"60","cleansession":true,"birthTopic":"","birthQos":"0","birthPayload":"","closeTopic":"","closeQos":"0","closePayload":"","willTopic":"","willQos":"0","willPayload":""},{"id":"c149cddf.15d68","type":"tls-config","name":"","cert":"","key":"","ca":"","certname":"","keyname":"","caname":"","servername":"","verifyservercert":false}] -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 | # MailboxNotifier 2 | 3 | Video: https://youtu.be/Y2zJ5dqDKBk 4 | --------------------------------------------------------------------------------