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.
main.cpp@4:0fd5e5e121ea, 2017-01-04 (annotated)
- Committer:
- Jan Jongboom
- Date:
- Wed Jan 04 13:45:49 2017 +0000
- Revision:
- 4:0fd5e5e121ea
- Parent:
- 3:ac5101a47080
Retry OTA join when fails during startup
Who changed what in which revision?
User | Revision | Line number | New contents of line |
---|---|---|---|
janjongboom | 0:20fbd6f66b11 | 1 | #include "mbed.h" |
janjongboom | 0:20fbd6f66b11 | 2 | #include "mDot.h" |
janjongboom | 0:20fbd6f66b11 | 3 | #include "MTSLog.h" |
janjongboom | 0:20fbd6f66b11 | 4 | #include <string> |
janjongboom | 0:20fbd6f66b11 | 5 | #include <vector> |
janjongboom | 0:20fbd6f66b11 | 6 | #include <algorithm> |
janjongboom | 0:20fbd6f66b11 | 7 | #include "mbed.h" |
janjongboom | 0:20fbd6f66b11 | 8 | #include "parse_keys.h" |
janjongboom | 0:20fbd6f66b11 | 9 | |
janjongboom | 0:20fbd6f66b11 | 10 | #define BUILTIN_LED_ON 0 |
janjongboom | 0:20fbd6f66b11 | 11 | #define BUILTIN_LED_OFF 1 |
janjongboom | 0:20fbd6f66b11 | 12 | |
Jan Jongboom |
2:ff17ce021cfb | 13 | static const char APP_EUI[] = "BE7A000000000393"; |
Jan Jongboom |
3:ac5101a47080 | 14 | static const char APP_KEY[] = "3FE0040E234141A08A583D5F508B5781"; |
janjongboom | 0:20fbd6f66b11 | 15 | |
janjongboom | 0:20fbd6f66b11 | 16 | static mDot* dot; |
janjongboom | 0:20fbd6f66b11 | 17 | |
janjongboom | 0:20fbd6f66b11 | 18 | // so we have some state |
janjongboom | 0:20fbd6f66b11 | 19 | static uint16_t counter = 0; // always persisted to non-volatile mem |
janjongboom | 0:20fbd6f66b11 | 20 | |
janjongboom | 0:20fbd6f66b11 | 21 | static bool woke_from_interrupt = false; |
janjongboom | 0:20fbd6f66b11 | 22 | |
janjongboom | 0:20fbd6f66b11 | 23 | static InterruptIn btn(PA_1); /* D6 */ |
janjongboom | 0:20fbd6f66b11 | 24 | static DigitalOut led(PC_13, BUILTIN_LED_OFF); /* D2 */ |
janjongboom | 0:20fbd6f66b11 | 25 | |
janjongboom | 0:20fbd6f66b11 | 26 | static void read_counter() { |
janjongboom | 0:20fbd6f66b11 | 27 | char buffer[2]; |
janjongboom | 0:20fbd6f66b11 | 28 | bool res = dot->readUserFile("counter", &buffer, 2); |
janjongboom | 0:20fbd6f66b11 | 29 | if (res) { |
janjongboom | 0:20fbd6f66b11 | 30 | counter = (buffer[0] << 8) + buffer[1]; |
janjongboom | 0:20fbd6f66b11 | 31 | } |
janjongboom | 0:20fbd6f66b11 | 32 | else { |
janjongboom | 0:20fbd6f66b11 | 33 | counter = 0; |
janjongboom | 0:20fbd6f66b11 | 34 | } |
janjongboom | 0:20fbd6f66b11 | 35 | } |
janjongboom | 0:20fbd6f66b11 | 36 | |
janjongboom | 0:20fbd6f66b11 | 37 | static void up_counter() { |
janjongboom | 0:20fbd6f66b11 | 38 | logInfo("up counter"); |
janjongboom | 0:20fbd6f66b11 | 39 | |
janjongboom | 0:20fbd6f66b11 | 40 | read_counter(); |
janjongboom | 0:20fbd6f66b11 | 41 | |
janjongboom | 0:20fbd6f66b11 | 42 | counter++; |
janjongboom | 0:20fbd6f66b11 | 43 | |
janjongboom | 0:20fbd6f66b11 | 44 | char buffer[2]; |
janjongboom | 0:20fbd6f66b11 | 45 | buffer[0] = counter >> 8 & 0xff; |
janjongboom | 0:20fbd6f66b11 | 46 | buffer[1] = counter & 0xff; |
janjongboom | 0:20fbd6f66b11 | 47 | |
janjongboom | 0:20fbd6f66b11 | 48 | logInfo("new counter value is: %d", counter); |
janjongboom | 0:20fbd6f66b11 | 49 | |
janjongboom | 0:20fbd6f66b11 | 50 | dot->saveUserFile("counter", &buffer, 2); |
janjongboom | 0:20fbd6f66b11 | 51 | } |
janjongboom | 0:20fbd6f66b11 | 52 | |
janjongboom | 0:20fbd6f66b11 | 53 | static void btn_rise() { |
janjongboom | 0:20fbd6f66b11 | 54 | woke_from_interrupt = true; // will be woken by STM32 |
janjongboom | 0:20fbd6f66b11 | 55 | } |
janjongboom | 0:20fbd6f66b11 | 56 | |
janjongboom | 0:20fbd6f66b11 | 57 | static bool send_data(void) { |
janjongboom | 0:20fbd6f66b11 | 58 | int32_t ret; |
janjongboom | 0:20fbd6f66b11 | 59 | |
Jan Jongboom |
4:0fd5e5e121ea | 60 | // check join state, @todo: add a timeout here, join is very expensive on the battery... |
Jan Jongboom |
4:0fd5e5e121ea | 61 | if (!dot->getNetworkJoinStatus()) { |
Jan Jongboom |
4:0fd5e5e121ea | 62 | logInfo("trying to send before joining, retry join..."); |
Jan Jongboom |
4:0fd5e5e121ea | 63 | if ((ret = dot->joinNetwork()) != mDot::MDOT_OK ) { |
Jan Jongboom |
4:0fd5e5e121ea | 64 | logError("failed to join network %d:%s", ret, mDot::getReturnCodeString(ret).c_str()); |
Jan Jongboom |
4:0fd5e5e121ea | 65 | return false; |
Jan Jongboom |
4:0fd5e5e121ea | 66 | } |
Jan Jongboom |
4:0fd5e5e121ea | 67 | else { |
Jan Jongboom |
4:0fd5e5e121ea | 68 | logInfo("joined network successfully"); |
Jan Jongboom |
4:0fd5e5e121ea | 69 | } |
Jan Jongboom |
4:0fd5e5e121ea | 70 | } |
Jan Jongboom |
4:0fd5e5e121ea | 71 | |
janjongboom | 0:20fbd6f66b11 | 72 | std::vector<uint8_t> data; |
janjongboom | 0:20fbd6f66b11 | 73 | data.push_back(counter >> 8 & 0xff); |
janjongboom | 0:20fbd6f66b11 | 74 | data.push_back(counter & 0xff); |
janjongboom | 0:20fbd6f66b11 | 75 | |
janjongboom | 0:20fbd6f66b11 | 76 | logInfo("sending %d bytes", data.size()); |
janjongboom | 0:20fbd6f66b11 | 77 | if ((ret = dot->send(data)) != mDot::MDOT_OK) { |
janjongboom | 0:20fbd6f66b11 | 78 | logError("failed to send %d:%s", ret, mDot::getReturnCodeString(ret).c_str()); |
janjongboom | 0:20fbd6f66b11 | 79 | return false; |
janjongboom | 0:20fbd6f66b11 | 80 | } else { |
janjongboom | 0:20fbd6f66b11 | 81 | logInfo("successfully sent data to gateway"); |
janjongboom | 0:20fbd6f66b11 | 82 | |
janjongboom | 0:20fbd6f66b11 | 83 | std::vector<uint8_t> recv_data; |
janjongboom | 0:20fbd6f66b11 | 84 | if ((ret = dot->recv(recv_data)) != mDot::MDOT_OK) { |
janjongboom | 0:20fbd6f66b11 | 85 | logError("failed to recv %d:%s", ret, mDot::getReturnCodeString(ret).c_str()); |
janjongboom | 0:20fbd6f66b11 | 86 | return true; // sending succeeded, just recv failed |
janjongboom | 0:20fbd6f66b11 | 87 | } |
Jan Jongboom |
2:ff17ce021cfb | 88 | |
janjongboom | 0:20fbd6f66b11 | 89 | if (recv_data.size() > 0) { |
janjongboom | 0:20fbd6f66b11 | 90 | printf("[INFO] received %d bytes:", recv_data.size()); |
janjongboom | 0:20fbd6f66b11 | 91 | for (size_t ix = 0; ix < recv_data.size(); ix++) { |
janjongboom | 0:20fbd6f66b11 | 92 | printf(" %02x", recv_data[ix]); |
janjongboom | 0:20fbd6f66b11 | 93 | } |
janjongboom | 0:20fbd6f66b11 | 94 | printf("\r\n"); |
Jan Jongboom |
2:ff17ce021cfb | 95 | |
janjongboom | 0:20fbd6f66b11 | 96 | if (recv_data[0] == 1) { |
janjongboom | 0:20fbd6f66b11 | 97 | led = BUILTIN_LED_ON; |
janjongboom | 0:20fbd6f66b11 | 98 | } |
janjongboom | 0:20fbd6f66b11 | 99 | else { |
janjongboom | 0:20fbd6f66b11 | 100 | led = BUILTIN_LED_OFF; |
janjongboom | 0:20fbd6f66b11 | 101 | } |
janjongboom | 0:20fbd6f66b11 | 102 | } |
Jan Jongboom |
2:ff17ce021cfb | 103 | |
janjongboom | 0:20fbd6f66b11 | 104 | return true; |
janjongboom | 0:20fbd6f66b11 | 105 | } |
janjongboom | 0:20fbd6f66b11 | 106 | } |
janjongboom | 0:20fbd6f66b11 | 107 | |
janjongboom | 0:20fbd6f66b11 | 108 | static void wakeUpCallback() { |
janjongboom | 0:20fbd6f66b11 | 109 | logInfo("woke up, fromInterrupt=%d", woke_from_interrupt); |
Jan Jongboom |
2:ff17ce021cfb | 110 | |
janjongboom | 0:20fbd6f66b11 | 111 | bool wfi = woke_from_interrupt; |
Jan Jongboom |
2:ff17ce021cfb | 112 | |
janjongboom | 0:20fbd6f66b11 | 113 | // if we were woken up by RTC_ALARM, first up the counter |
janjongboom | 0:20fbd6f66b11 | 114 | if (wfi) { |
janjongboom | 0:20fbd6f66b11 | 115 | // reset the interrupt var |
janjongboom | 0:20fbd6f66b11 | 116 | woke_from_interrupt = false; |
janjongboom | 0:20fbd6f66b11 | 117 | |
janjongboom | 0:20fbd6f66b11 | 118 | up_counter(); |
janjongboom | 0:20fbd6f66b11 | 119 | } |
Jan Jongboom |
2:ff17ce021cfb | 120 | |
janjongboom | 0:20fbd6f66b11 | 121 | |
janjongboom | 0:20fbd6f66b11 | 122 | bool sent = send_data(); |
janjongboom | 0:20fbd6f66b11 | 123 | // not sent? try again in 5 minutes... |
janjongboom | 0:20fbd6f66b11 | 124 | if (!sent) { |
janjongboom | 0:20fbd6f66b11 | 125 | uint32_t sleep_time = 5 * 60; |
Jan Jongboom |
2:ff17ce021cfb | 126 | |
janjongboom | 0:20fbd6f66b11 | 127 | // if woke from button press, check duty cycle first... |
janjongboom | 0:20fbd6f66b11 | 128 | if (wfi) { |
janjongboom | 0:20fbd6f66b11 | 129 | // hmm.. something went wrong. Probably duty cycle, see next Tx frame |
janjongboom | 0:20fbd6f66b11 | 130 | // get the next transmission frame (in whole seconds) |
janjongboom | 0:20fbd6f66b11 | 131 | sleep_time = ceil(static_cast<float>(dot->getNextTxMs()) / 1000.0f); |
Jan Jongboom |
2:ff17ce021cfb | 132 | |
janjongboom | 0:20fbd6f66b11 | 133 | // Tx window open, but no success? Try again in 30s. |
janjongboom | 0:20fbd6f66b11 | 134 | if (sleep_time == 0) sleep_time = 30; |
janjongboom | 0:20fbd6f66b11 | 135 | } |
Jan Jongboom |
2:ff17ce021cfb | 136 | |
janjongboom | 0:20fbd6f66b11 | 137 | logInfo("Going back to sleep (RTC_ALARM), time=%d", sleep_time); |
janjongboom | 0:20fbd6f66b11 | 138 | dot->sleep(sleep_time, mDot::RTC_ALARM, false); |
janjongboom | 0:20fbd6f66b11 | 139 | } |
janjongboom | 0:20fbd6f66b11 | 140 | else { |
janjongboom | 0:20fbd6f66b11 | 141 | logInfo("Going back to sleep (INTERRUPT)"); |
janjongboom | 0:20fbd6f66b11 | 142 | |
janjongboom | 0:20fbd6f66b11 | 143 | // go back to sleep (wait for an interrupt to happen) |
janjongboom | 0:20fbd6f66b11 | 144 | dot->sleep(0, mDot::INTERRUPT, false); |
janjongboom | 0:20fbd6f66b11 | 145 | } |
janjongboom | 0:20fbd6f66b11 | 146 | } |
janjongboom | 0:20fbd6f66b11 | 147 | |
janjongboom | 0:20fbd6f66b11 | 148 | |
janjongboom | 0:20fbd6f66b11 | 149 | int main() { |
janjongboom | 0:20fbd6f66b11 | 150 | int32_t ret; |
janjongboom | 0:20fbd6f66b11 | 151 | printf("Entering main()\r\n"); |
janjongboom | 0:20fbd6f66b11 | 152 | |
janjongboom | 0:20fbd6f66b11 | 153 | btn.rise(&btn_rise); |
janjongboom | 0:20fbd6f66b11 | 154 | |
janjongboom | 0:20fbd6f66b11 | 155 | // get a mDot handle |
janjongboom | 0:20fbd6f66b11 | 156 | dot = mDot::getInstance(); |
janjongboom | 0:20fbd6f66b11 | 157 | |
janjongboom | 0:20fbd6f66b11 | 158 | dot->setLogLevel(mts::MTSLog::DEBUG_LEVEL); |
janjongboom | 0:20fbd6f66b11 | 159 | |
janjongboom | 0:20fbd6f66b11 | 160 | // print library version information |
janjongboom | 0:20fbd6f66b11 | 161 | logInfo("version: %s", dot->getId().c_str()); |
janjongboom | 0:20fbd6f66b11 | 162 | |
Jan Jongboom |
2:ff17ce021cfb | 163 | std::vector<uint8_t> devEui = dot->getDeviceId(); |
Jan Jongboom |
2:ff17ce021cfb | 164 | logInfo("device eui: %02x:%02x:%02x:%02x:%02x:%02x:%02x:%02x", |
Jan Jongboom |
2:ff17ce021cfb | 165 | devEui[0], devEui[1], devEui[2], devEui[3], devEui[4], devEui[5], devEui[6], devEui[7]); |
Jan Jongboom |
2:ff17ce021cfb | 166 | |
janjongboom | 0:20fbd6f66b11 | 167 | //******************************************* |
janjongboom | 0:20fbd6f66b11 | 168 | // configuration |
janjongboom | 0:20fbd6f66b11 | 169 | //******************************************* |
janjongboom | 0:20fbd6f66b11 | 170 | // reset to default config so we know what state we're in |
janjongboom | 0:20fbd6f66b11 | 171 | dot->resetConfig(); |
janjongboom | 0:20fbd6f66b11 | 172 | |
janjongboom | 0:20fbd6f66b11 | 173 | logInfo("frequencyBand: %d", dot->getFrequencyBand()); |
janjongboom | 0:20fbd6f66b11 | 174 | |
janjongboom | 0:20fbd6f66b11 | 175 | // set up the mDot with our network information: frequency sub band, network name, and network password |
janjongboom | 0:20fbd6f66b11 | 176 | // these can all be saved in NVM so they don't need to be set every time - see mDot::saveConfig() |
janjongboom | 0:20fbd6f66b11 | 177 | |
janjongboom | 0:20fbd6f66b11 | 178 | logInfo("setting public network"); |
janjongboom | 0:20fbd6f66b11 | 179 | if ((ret = dot->setPublicNetwork(true)) != mDot::MDOT_OK) { |
janjongboom | 0:20fbd6f66b11 | 180 | logError("failed to set public network %d:%s", ret, mDot::getReturnCodeString(ret).c_str()); |
janjongboom | 0:20fbd6f66b11 | 181 | } |
janjongboom | 0:20fbd6f66b11 | 182 | |
janjongboom | 0:20fbd6f66b11 | 183 | logInfo("setting tx power to 20"); |
janjongboom | 0:20fbd6f66b11 | 184 | if ((ret = dot->setTxPower(18)) != mDot::MDOT_OK) { |
janjongboom | 0:20fbd6f66b11 | 185 | logError("failed to set tx power %d:%s", ret, mDot::getReturnCodeString(ret).c_str()); |
janjongboom | 0:20fbd6f66b11 | 186 | } |
janjongboom | 0:20fbd6f66b11 | 187 | |
janjongboom | 0:20fbd6f66b11 | 188 | // set up the network keys |
Jan Jongboom |
2:ff17ce021cfb | 189 | ParseKeys::initializeOta(dot, APP_EUI, APP_KEY); |
janjongboom | 0:20fbd6f66b11 | 190 | |
janjongboom | 0:20fbd6f66b11 | 191 | // a higher spreading factor allows for longer range but lower throughput |
janjongboom | 0:20fbd6f66b11 | 192 | // in the 915 (US) frequency band, spreading factors 7 - 10 are available |
janjongboom | 0:20fbd6f66b11 | 193 | // in the 868 (EU) frequency band, spreading factors 7 - 12 are available |
janjongboom | 0:20fbd6f66b11 | 194 | logInfo("setting TX spreading factor"); |
janjongboom | 0:20fbd6f66b11 | 195 | if ((ret = dot->setTxDataRate(mDot::SF_9)) != mDot::MDOT_OK) { |
janjongboom | 0:20fbd6f66b11 | 196 | logError("failed to set TX datarate %d:%s", ret, mDot::getReturnCodeString(ret).c_str()); |
janjongboom | 0:20fbd6f66b11 | 197 | } |
janjongboom | 0:20fbd6f66b11 | 198 | |
janjongboom | 0:20fbd6f66b11 | 199 | // request receive confirmation of packets from the gateway |
janjongboom | 0:20fbd6f66b11 | 200 | logInfo("enabling ACKs"); |
janjongboom | 0:20fbd6f66b11 | 201 | if ((ret = dot->setAck(1)) != mDot::MDOT_OK) { |
janjongboom | 0:20fbd6f66b11 | 202 | logError("failed to enable ACKs %d:%s", ret, mDot::getReturnCodeString(ret).c_str()); |
janjongboom | 0:20fbd6f66b11 | 203 | } |
janjongboom | 0:20fbd6f66b11 | 204 | |
janjongboom | 0:20fbd6f66b11 | 205 | logInfo("enabling ADR"); |
janjongboom | 0:20fbd6f66b11 | 206 | if ((ret = dot->setAdr(1)) != mDot::MDOT_OK) { |
janjongboom | 0:20fbd6f66b11 | 207 | logError("failed to enable ADR %d:%s", ret, mDot::getReturnCodeString(ret).c_str()); |
janjongboom | 0:20fbd6f66b11 | 208 | } |
janjongboom | 0:20fbd6f66b11 | 209 | |
janjongboom | 0:20fbd6f66b11 | 210 | // save this configuration to the mDot's NVM |
janjongboom | 0:20fbd6f66b11 | 211 | logInfo("saving config"); |
janjongboom | 0:20fbd6f66b11 | 212 | if (! dot->saveConfig()) { |
janjongboom | 0:20fbd6f66b11 | 213 | logError("failed to save configuration"); |
janjongboom | 0:20fbd6f66b11 | 214 | } |
janjongboom | 0:20fbd6f66b11 | 215 | |
Jan Jongboom |
2:ff17ce021cfb | 216 | // OTA JOIN sequence, remove when using personalized mode |
Jan Jongboom |
2:ff17ce021cfb | 217 | logInfo("joining network"); |
Jan Jongboom |
2:ff17ce021cfb | 218 | if ((ret = dot->joinNetwork()) != mDot::MDOT_OK ) { |
Jan Jongboom |
2:ff17ce021cfb | 219 | logError("failed to join network %d:%s", ret, mDot::getReturnCodeString(ret).c_str()); |
Jan Jongboom |
2:ff17ce021cfb | 220 | } |
Jan Jongboom |
4:0fd5e5e121ea | 221 | else { |
Jan Jongboom |
4:0fd5e5e121ea | 222 | logInfo("joined network successfully"); |
Jan Jongboom |
4:0fd5e5e121ea | 223 | } |
Jan Jongboom |
2:ff17ce021cfb | 224 | |
janjongboom | 0:20fbd6f66b11 | 225 | //******************************************* |
janjongboom | 0:20fbd6f66b11 | 226 | // end of configuration |
janjongboom | 0:20fbd6f66b11 | 227 | //******************************************* |
janjongboom | 0:20fbd6f66b11 | 228 | |
janjongboom | 0:20fbd6f66b11 | 229 | read_counter(); |
janjongboom | 0:20fbd6f66b11 | 230 | |
janjongboom | 0:20fbd6f66b11 | 231 | dot->setWakeupCallback(&wakeUpCallback); |
janjongboom | 0:20fbd6f66b11 | 232 | |
janjongboom | 0:20fbd6f66b11 | 233 | dot->sleep(0, mDot::INTERRUPT, false); |
janjongboom | 0:20fbd6f66b11 | 234 | |
janjongboom | 0:20fbd6f66b11 | 235 | while (true) { |
janjongboom | 0:20fbd6f66b11 | 236 | wait_ms(1000); |
janjongboom | 0:20fbd6f66b11 | 237 | } |
janjongboom | 0:20fbd6f66b11 | 238 | } |