Two way data over LoRaWAN using Multitech mDot

Dependencies:   libmDot-mbed5

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.

main.cpp

Committer:
Jan Jongboom
Date:
2017-01-04
Revision:
4:0fd5e5e121ea
Parent:
3:ac5101a47080

File content as of revision 4:0fd5e5e121ea:

#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 APP_EUI[] = "BE7A000000000393";
static const char APP_KEY[] = "3FE0040E234141A08A583D5F508B5781";

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;

    // check join state, @todo: add a timeout here, join is very expensive on the battery...
    if (!dot->getNetworkJoinStatus()) {
        logInfo("trying to send before joining, retry join...");
        if ((ret = dot->joinNetwork()) != mDot::MDOT_OK ) {
            logError("failed to join network %d:%s", ret, mDot::getReturnCodeString(ret).c_str());
            return false;
        }
        else {
            logInfo("joined network successfully");
        }
    }

    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());

    std::vector<uint8_t> devEui = dot->getDeviceId();
    logInfo("device eui: %02x:%02x:%02x:%02x:%02x:%02x:%02x:%02x",
        devEui[0], devEui[1], devEui[2], devEui[3], devEui[4], devEui[5], devEui[6], devEui[7]);

    //*******************************************
    // 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()

    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::initializeOta(dot, APP_EUI, APP_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");
    }

    // OTA JOIN sequence, remove when using personalized mode
    logInfo("joining network");
    if ((ret = dot->joinNetwork()) != mDot::MDOT_OK ) {
        logError("failed to join network %d:%s", ret, mDot::getReturnCodeString(ret).c_str());
    }
    else {
        logInfo("joined network successfully");
    }

    //*******************************************
    // end of configuration
    //*******************************************

    read_counter();

    dot->setWakeupCallback(&wakeUpCallback);

    dot->sleep(0, mDot::INTERRUPT, false);

    while (true) {
        wait_ms(1000);
    }
}