Two way data over LoRaWAN using Multitech mDot
This is example firmware for the Multitech mDot. It demonstrates how to:
- Do two-way data.
- Sleep aggressively and only wake up when the wake-up pin is triggered.
- Handle errors, retries and duty cycle errors.
- Cache data in non-volatile storage.
Based on mbed OS 5.1, hard faults against mbed OS 5.3 unfortunately. Can be compiled with GCC and ARMCC (but not IAR).
To do a new transmission, short pin D6 / PA_1.
Diff: main.cpp
- Revision:
- 0:20fbd6f66b11
- Child:
- 2:ff17ce021cfb
diff -r 000000000000 -r 20fbd6f66b11 main.cpp --- /dev/null Thu Jan 01 00:00:00 1970 +0000 +++ b/main.cpp Tue Jan 03 13:20:36 2017 +0000 @@ -0,0 +1,220 @@ +#include "mbed.h" +#include "mDot.h" +#include "MTSLog.h" +#include <string> +#include <vector> +#include <algorithm> +#include "mbed.h" +#include "parse_keys.h" + +#define BUILTIN_LED_ON 0 +#define BUILTIN_LED_OFF 1 + +static const char LORIOT_DEV_ADDR[] = "01F83E96"; // Use the big endian version here +static const char LORIOT_NWK_S_KEY[] = "6B11E4D0814BE7BC5707920B4B3545D6"; // NWKSKEY +static const char LORIOT_APP_S_KEY[] = "5C79DCD7E2CCB93A83730BC7A9BC6CFE"; // APPSKEY + +static mDot* dot; + +// so we have some state +static uint16_t counter = 0; // always persisted to non-volatile mem + +static bool woke_from_interrupt = false; + +static InterruptIn btn(PA_1); /* D6 */ +static DigitalOut led(PC_13, BUILTIN_LED_OFF); /* D2 */ + +static void read_counter() { + char buffer[2]; + bool res = dot->readUserFile("counter", &buffer, 2); + if (res) { + counter = (buffer[0] << 8) + buffer[1]; + } + else { + counter = 0; + } +} + +static void up_counter() { + logInfo("up counter"); + + read_counter(); + + counter++; + + char buffer[2]; + buffer[0] = counter >> 8 & 0xff; + buffer[1] = counter & 0xff; + + logInfo("new counter value is: %d", counter); + + dot->saveUserFile("counter", &buffer, 2); +} + +static void btn_rise() { + woke_from_interrupt = true; // will be woken by STM32 +} + +static bool send_data(void) { + int32_t ret; + + std::vector<uint8_t> data; + data.push_back(counter >> 8 & 0xff); + data.push_back(counter & 0xff); + + logInfo("sending %d bytes", data.size()); + if ((ret = dot->send(data)) != mDot::MDOT_OK) { + logError("failed to send %d:%s", ret, mDot::getReturnCodeString(ret).c_str()); + return false; + } else { + logInfo("successfully sent data to gateway"); + + std::vector<uint8_t> recv_data; + if ((ret = dot->recv(recv_data)) != mDot::MDOT_OK) { + logError("failed to recv %d:%s", ret, mDot::getReturnCodeString(ret).c_str()); + return true; // sending succeeded, just recv failed + } + + if (recv_data.size() > 0) { + printf("[INFO] received %d bytes:", recv_data.size()); + for (size_t ix = 0; ix < recv_data.size(); ix++) { + printf(" %02x", recv_data[ix]); + } + printf("\r\n"); + + if (recv_data[0] == 1) { + led = BUILTIN_LED_ON; + } + else { + led = BUILTIN_LED_OFF; + } + } + + return true; + } +} + +static void wakeUpCallback() { + logInfo("woke up, fromInterrupt=%d", woke_from_interrupt); + + bool wfi = woke_from_interrupt; + + // if we were woken up by RTC_ALARM, first up the counter + if (wfi) { + // reset the interrupt var + woke_from_interrupt = false; + + up_counter(); + } + + + bool sent = send_data(); + // not sent? try again in 5 minutes... + if (!sent) { + uint32_t sleep_time = 5 * 60; + + // if woke from button press, check duty cycle first... + if (wfi) { + // hmm.. something went wrong. Probably duty cycle, see next Tx frame + // get the next transmission frame (in whole seconds) + sleep_time = ceil(static_cast<float>(dot->getNextTxMs()) / 1000.0f); + + // Tx window open, but no success? Try again in 30s. + if (sleep_time == 0) sleep_time = 30; + } + + logInfo("Going back to sleep (RTC_ALARM), time=%d", sleep_time); + dot->sleep(sleep_time, mDot::RTC_ALARM, false); + } + else { + logInfo("Going back to sleep (INTERRUPT)"); + + // go back to sleep (wait for an interrupt to happen) + dot->sleep(0, mDot::INTERRUPT, false); + } +} + + +int main() { + int32_t ret; + printf("Entering main()\r\n"); + + btn.rise(&btn_rise); + + // get a mDot handle + dot = mDot::getInstance(); + + dot->setLogLevel(mts::MTSLog::DEBUG_LEVEL); + + // print library version information + logInfo("version: %s", dot->getId().c_str()); + + //******************************************* + // configuration + //******************************************* + // reset to default config so we know what state we're in + dot->resetConfig(); + + logInfo("frequencyBand: %d", dot->getFrequencyBand()); + + // set up the mDot with our network information: frequency sub band, network name, and network password + // these can all be saved in NVM so they don't need to be set every time - see mDot::saveConfig() + + // set join mode to MANUAL so the mDot doesn't have to rejoin after sleeping + logInfo("setting join mode to MANUAL"); + if ((ret = dot->setJoinMode(mDot::MANUAL)) != mDot::MDOT_OK) { + logError("failed to set join mode %d:%s", ret, mDot::getReturnCodeString(ret).c_str()); + } + + logInfo("setting public network"); + if ((ret = dot->setPublicNetwork(true)) != mDot::MDOT_OK) { + logError("failed to set public network %d:%s", ret, mDot::getReturnCodeString(ret).c_str()); + } + + logInfo("setting tx power to 20"); + if ((ret = dot->setTxPower(18)) != mDot::MDOT_OK) { + logError("failed to set tx power %d:%s", ret, mDot::getReturnCodeString(ret).c_str()); + } + + // set up the network keys + ParseKeys::initialize(dot, LORIOT_DEV_ADDR, LORIOT_NWK_S_KEY, LORIOT_APP_S_KEY); + + // a higher spreading factor allows for longer range but lower throughput + // in the 915 (US) frequency band, spreading factors 7 - 10 are available + // in the 868 (EU) frequency band, spreading factors 7 - 12 are available + logInfo("setting TX spreading factor"); + if ((ret = dot->setTxDataRate(mDot::SF_9)) != mDot::MDOT_OK) { + logError("failed to set TX datarate %d:%s", ret, mDot::getReturnCodeString(ret).c_str()); + } + + // request receive confirmation of packets from the gateway + logInfo("enabling ACKs"); + if ((ret = dot->setAck(1)) != mDot::MDOT_OK) { + logError("failed to enable ACKs %d:%s", ret, mDot::getReturnCodeString(ret).c_str()); + } + + logInfo("enabling ADR"); + if ((ret = dot->setAdr(1)) != mDot::MDOT_OK) { + logError("failed to enable ADR %d:%s", ret, mDot::getReturnCodeString(ret).c_str()); + } + + // save this configuration to the mDot's NVM + logInfo("saving config"); + if (! dot->saveConfig()) { + logError("failed to save configuration"); + } + + //******************************************* + // end of configuration + //******************************************* + + read_counter(); + + dot->setWakeupCallback(&wakeUpCallback); + + dot->sleep(0, mDot::INTERRUPT, false); + + while (true) { + wait_ms(1000); + } +}