Last active
May 12, 2019 06:08
-
-
Save huntc/6ea9c7b138063b0d5c9d09575d404643 to your computer and use it in GitHub Desktop.
LoRaWAN Arduino integration for the Hyquest rain water sensor
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
/* | |
* Copyright 2018 Titan Class Pty Ltd | |
* | |
* Licensed under the Apache License, Version 2.0 (the "License"); | |
* you may not use this file except in compliance with the License. | |
* You may obtain a copy of the License at | |
* | |
* http://www.apache.org/licenses/LICENSE-2.0 | |
* | |
* Unless required by applicable law or agreed to in writing, software | |
* distributed under the License is distributed on an "AS IS" BASIS, | |
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. | |
* See the License for the specific language governing permissions and | |
* limitations under the License. | |
*/ | |
#include <lmic.h> | |
#include <hal/hal.h> | |
#include <SPI.h> | |
#ifdef COMPILE_REGRESSION_TEST | |
# define FILLMEIN 0 | |
#else | |
# warning "You must replace the values marked FILLMEIN with real values from the TTN control panel!" | |
# define FILLMEIN (#dont edit this, edit the lines that use FILLMEIN) | |
#endif | |
// This EUI must be in little-endian format, so least-significant-byte | |
// first. When copying an EUI from ttnctl output, this means to reverse | |
// the bytes. For TTN issued EUIs the last bytes should be 0xD5, 0xB3, | |
// 0x70. | |
static const u1_t PROGMEM APPEUI[8] = { 0xA1, 0x93, 0x01, 0xD0, 0x7E, 0xD5, 0xB3, 0x70 }; | |
void os_getArtEui (u1_t* buf) { | |
memcpy_P(buf, APPEUI, 8); | |
} | |
// This should also be in little endian format, see above. | |
static const u1_t PROGMEM DEVEUI[8] = { 0xDA, 0xA4, 0x48, 0xB5, 0x48, 0xC5, 0x95, 0x00 }; | |
void os_getDevEui (u1_t* buf) { | |
memcpy_P(buf, DEVEUI, 8); | |
} | |
// This key should be in big endian format (or, since it is not really a | |
// number but a block of memory, endianness does not really apply). In | |
// practice, a key taken from ttnctl can be copied as-is. | |
static const u1_t PROGMEM APPKEY[16] = { 0x0A, 0x11, 0x29, 0x0C, 0x7C, 0xFE, 0x23, 0x08, 0xE3, 0x10, 0x65, 0x50, 0x17, 0x0F, 0xAD, 0xC9 }; | |
void os_getDevKey (u1_t* buf) { | |
memcpy_P(buf, APPKEY, 16); | |
} | |
static osjob_t sendjob; | |
// Pin mapping | |
const lmic_pinmap lmic_pins = { | |
.nss = 10, | |
.rxtx = LMIC_UNUSED_PIN, | |
.rst = 9, | |
.dio = {2, 6, 7}, | |
}; | |
// Schedule TX every this many seconds (might become longer due to duty | |
// cycle limitations). | |
const unsigned TX_INTERVAL = 10; | |
const unsigned TIP_COUNT_INTERVAL = 5 * 60; // The amount of seconds pertaining to each bucket of tip counts | |
const unsigned MAX_TIP_COUNTS = 12; // This will become the length of our packet | |
static osjob_t rolloverjob; | |
static unsigned char tipCounts[MAX_TIP_COUNTS]; | |
static int currentTipCountIndex = MAX_TIP_COUNTS - 1; // Cause a reset of the tip counts for the first time in | |
const int REED_PIN = 3; // Pin connected to reed switch | |
void onEvent (ev_t ev) { | |
Serial.print(os_getTime()); | |
Serial.print(": "); | |
switch (ev) { | |
case EV_SCAN_TIMEOUT: | |
Serial.println(F("EV_SCAN_TIMEOUT")); | |
break; | |
case EV_BEACON_FOUND: | |
Serial.println(F("EV_BEACON_FOUND")); | |
break; | |
case EV_BEACON_MISSED: | |
Serial.println(F("EV_BEACON_MISSED")); | |
break; | |
case EV_BEACON_TRACKED: | |
Serial.println(F("EV_BEACON_TRACKED")); | |
break; | |
case EV_JOINING: | |
Serial.println(F("EV_JOINING")); | |
break; | |
case EV_JOINED: | |
Serial.println(F("EV_JOINED")); | |
{ | |
u4_t netid = 0; | |
devaddr_t devaddr = 0; | |
u1_t nwkKey[16]; | |
u1_t artKey[16]; | |
LMIC_getSessionKeys(&netid, &devaddr, nwkKey, artKey); | |
Serial.print("netid: "); | |
Serial.println(netid, DEC); | |
Serial.print("devaddr: "); | |
Serial.println(devaddr, HEX); | |
Serial.print("artKey: "); | |
for (int i = 0; i < sizeof(artKey); ++i) { | |
Serial.print(artKey[i], HEX); | |
} | |
Serial.println(""); | |
Serial.print("nwkKey: "); | |
for (int i = 0; i < sizeof(nwkKey); ++i) { | |
Serial.print(nwkKey[i], HEX); | |
} | |
Serial.println(""); | |
} | |
// Disable link check validation (automatically enabled | |
// during join, but because slow data rates change max TX | |
// size, we don't use it in this example. | |
LMIC_setLinkCheckMode(0); | |
break; | |
case EV_JOIN_FAILED: | |
Serial.println(F("EV_JOIN_FAILED")); | |
if (LMIC.dataLen) { | |
Serial.print(F("Received ")); | |
Serial.print(LMIC.dataLen); | |
Serial.println(F(" bytes of payload")); | |
} | |
break; | |
case EV_REJOIN_FAILED: | |
Serial.println(F("EV_REJOIN_FAILED")); | |
break; | |
case EV_TXCOMPLETE: | |
Serial.println(F("EV_TXCOMPLETE (includes waiting for RX windows)")); | |
if (LMIC.txrxFlags & TXRX_ACK) | |
Serial.println(F("Received ack")); | |
if (LMIC.dataLen) { | |
Serial.print(F("Received ")); | |
Serial.print(LMIC.dataLen); | |
Serial.println(F(" bytes of payload")); | |
} | |
// Schedule next transmission | |
os_setTimedCallback(&sendjob, os_getTime() + sec2osticks(TX_INTERVAL), do_send); | |
break; | |
case EV_LOST_TSYNC: | |
Serial.println(F("EV_LOST_TSYNC")); | |
break; | |
case EV_RESET: | |
Serial.println(F("EV_RESET")); | |
break; | |
case EV_RXCOMPLETE: | |
// data received in ping slot | |
Serial.println(F("EV_RXCOMPLETE")); | |
break; | |
case EV_LINK_DEAD: | |
Serial.println(F("EV_LINK_DEAD")); | |
break; | |
case EV_LINK_ALIVE: | |
Serial.println(F("EV_LINK_ALIVE")); | |
break; | |
case EV_TXSTART: | |
Serial.println(F("EV_TXSTART")); | |
break; | |
default: | |
Serial.print(F("Unknown event: ")); | |
Serial.println((unsigned) ev); | |
break; | |
} | |
} | |
void do_send(osjob_t* j) { | |
// Check if there is not a current TX/RX job running | |
if (LMIC.opmode & OP_TXRXPEND) { | |
Serial.println(F("OP_TXRXPEND, not sending")); | |
} else { | |
// Prepare upstream data transmission at the next possible time. | |
LMIC_setTxData2(1, tipCounts, currentTipCountIndex + 1, 0); | |
Serial.print(F("Packet queued with ")); | |
for (int i = 0; i <= currentTipCountIndex; ++i) { | |
Serial.print(i); | |
Serial.print('='); | |
Serial.print(tipCounts[i]); | |
Serial.print(' '); | |
} | |
Serial.println(); | |
} | |
// Next TX is scheduled after TX_COMPLETE event. | |
} | |
void recordTip() { | |
++tipCounts[currentTipCountIndex]; | |
} | |
void rollTipCountIndex(osjob_t* j) { | |
os_setTimedCallback(&rolloverjob, os_getTime() + sec2osticks(TIP_COUNT_INTERVAL), rollTipCountIndex); | |
int newTipCountIndex = currentTipCountIndex + 1; | |
if (newTipCountIndex == MAX_TIP_COUNTS) { | |
memset(tipCounts, 0, MAX_TIP_COUNTS); | |
currentTipCountIndex = 0; | |
} else { | |
currentTipCountIndex = newTipCountIndex; | |
} | |
} | |
void setup() { | |
Serial.begin(9600); | |
Serial.println(F("Starting")); | |
#ifdef VCC_ENABLE | |
// For Pinoccio Scout boards | |
pinMode(VCC_ENABLE, OUTPUT); | |
digitalWrite(VCC_ENABLE, HIGH); | |
delay(1000); | |
#endif | |
// LMIC init | |
os_init(); | |
// Reset the MAC state. Session and pending data transfers will be discarded. | |
LMIC_reset(); | |
// Setup sensing | |
rollTipCountIndex(&rolloverjob); | |
pinMode(REED_PIN, INPUT_PULLUP); | |
attachInterrupt(digitalPinToInterrupt(REED_PIN), recordTip, FALLING); | |
// Start job (sending automatically starts OTAA too) | |
do_send(&sendjob); | |
} | |
void loop() { | |
os_runloop_once(); | |
} |
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
function transform(time, nwkAddr, fPort, payload) { | |
var tipCountIntervalMs = 5 * 60 * 1000; // ms represented per bucket (byte of payload) | |
var tipBucketSize = 1; // The volume represented by a tip bucket - in mm | |
var epochTimeMs = (new Date(time)).getTime(); | |
var payloadBytes = atob(payload); | |
var tipCountSpanMs = 0; | |
var result = []; | |
for (var i = payloadBytes.length - 1; i >= 0; --i) { | |
var observation = { | |
outlet: 0, | |
data: { | |
time: (new Date(epochTimeMs - tipCountSpanMs)).toISOString(), | |
nwkAddr: nwkAddr, | |
temperature: parseInt(payloadBytes.charCodeAt(i)) * tipBucketSize | |
} | |
}; | |
result.push(observation); | |
tipCountSpanMs += tipCountIntervalMs; | |
} | |
return result; | |
} | |
function test() { | |
var result = transform("2019-03-29T06:02:04.539Z", 65959, 15, "AAEAAgD/") | |
return ( | |
result.length===6 && | |
result[1].outlet===0 && | |
result[1].data.temperature===0 && | |
result[0].outlet===0 && | |
result[0].data.temperature===255 | |
); | |
} |
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment