From 8491fc7308afb578a7daffecd1fd9f770433efff Mon Sep 17 00:00:00 2001 From: Jan Moser Date: Wed, 14 Sep 2016 20:02:05 +0200 Subject: [PATCH] inital draft --- HackZurich_LoraWan.ino | 119 +++++++++ Sodaq_RN2483.cpp | 535 +++++++++++++++++++++++++++++++++++++++++ Sodaq_RN2483.h | 199 +++++++++++++++ StringLiterals.h | 63 +++++ Switchable_Device.cpp | 63 +++++ Switchable_Device.h | 24 ++ Utils.h | 34 +++ 7 files changed, 1037 insertions(+) create mode 100644 HackZurich_LoraWan.ino create mode 100644 Sodaq_RN2483.cpp create mode 100644 Sodaq_RN2483.h create mode 100644 StringLiterals.h create mode 100644 Switchable_Device.cpp create mode 100644 Switchable_Device.h create mode 100644 Utils.h diff --git a/HackZurich_LoraWan.ino b/HackZurich_LoraWan.ino new file mode 100644 index 0000000..9560729 --- /dev/null +++ b/HackZurich_LoraWan.ino @@ -0,0 +1,119 @@ +#include "Sodaq_RN2483.h" +#include "Arduino.h" + +#define debugSerial SerialUSB +#define loraSerial Serial1 + + + +void RED() { + digitalWrite(LED_RED, LOW); + digitalWrite(LED_GREEN, HIGH); + digitalWrite(LED_BLUE, HIGH); +} + +void CLEAR() { + digitalWrite(LED_RED, HIGH); + digitalWrite(LED_GREEN, HIGH); + digitalWrite(LED_BLUE, HIGH); +} + +// OTAA +​// I took 0x02 here as device Id... +uint8_t DevEUI[8] = { 0x9c, 0xd9, 0x0b, 0xb5, 0x2b, 0x6a, 0x1d, 0x02 }; +uint8_t AppEUI[8] = { 0xd4, 0x16, 0xcd, 0x0b, 0x7b, 0xcf, 0x2d, 0x5c }; +uint8_t AppKey[16] = {0xa9, 0xbc, 0x8b, 0x6a, 0x81, 0x75, 0xf6, 0x33, +0xe0, 0xd6, 0x64, 0xd9, 0x2b, 0xcb, 0x13, 0x78 }; + +void setupLoRaOTAA(){ + if (LoRaBee.initOTA(loraSerial, DevEUI, AppEUI, AppKey, true)) + { + debugSerial.println("Communication to LoRaBEE successful."); + } + else + { + debugSerial.println("OTAA Setup failed!"); + } +} + +void setup() { + //Power up the LoRaBEE + pinMode(ENABLE_PIN_IO, OUTPUT); // ONE + digitalWrite(ENABLE_PIN_IO, HIGH); // ONE + pinMode(LED_RED, OUTPUT); + pinMode(LED_GREEN, OUTPUT); + pinMode(LED_BLUE, OUTPUT); + delay(3000); + + while ((!SerialUSB) && (millis() < 10000)){ + // Wait 10 seconds for the Serial Monitor + } + + //Set baud rate + debugSerial.begin(57600); + loraSerial.begin(LoRaBee.getDefaultBaudRate()); + + // Debug output from LoRaBee + // LoRaBee.setDiag(debugSerial); // optional + + //connect to the LoRa Network + setupLoRa(); +} + +void setupLoRa(){ + // ABP +// setupLoRaABP(); + // OTAA + setupLoRaOTAA(); +} + +void sendPacket(String packet){ + switch (LoRaBee.sendReqAck(1, (uint8_t*)packet.c_str(), packet.length(), 8)) + { + case NoError: + debugSerial.println("Successful transmission."); + break; + case NoResponse: + debugSerial.println("There was no response from the device."); + setupLoRa(); + break; + case Timeout: + debugSerial.println("Connection timed-out. Check your serial connection to the device! Sleeping for 20sec."); + delay(20000); + break; + case PayloadSizeError: + debugSerial.println("The size of the payload is greater than allowed. Transmission failed!"); + break; + case InternalError: + debugSerial.println("Oh No! This shouldn't happen. Something is really wrong! Try restarting the device!\r\nThe network connection will reset."); + setupLoRa(); + break; + case Busy: + debugSerial.println("The device is busy. Sleeping for 10 extra seconds."); + delay(10000); + break; + case NetworkFatalError: + debugSerial.println("There is a non-recoverable error with the network connection. You should re-connect.\r\nThe network connection will reset."); + setupLoRa(); + break; + case NotConnected: + debugSerial.println("The device is not connected to the network. Please connect to the network before attempting to send data.\r\nThe network connection will reset."); + setupLoRa(); + break; + case NoAcknowledgment: + debugSerial.println("There was no acknowledgment sent back!"); + // When you this message you are probaly out of range of the network. + break; + default: + break; + } +} + +void loop() { + // put your main code here, to run repeatedly: + String packet = "SODAQ"; + sendPacket(packet); +RED(); + delay(5000); + CLEAR(); +} diff --git a/Sodaq_RN2483.cpp b/Sodaq_RN2483.cpp new file mode 100644 index 0000000..3cda2a0 --- /dev/null +++ b/Sodaq_RN2483.cpp @@ -0,0 +1,535 @@ +/* +* Copyright (c) 2015 SODAQ. All rights reserved. +* +* This file is part of Sodaq_RN2483. +* +* Sodaq_RN2483 is free software: you can redistribute it and/or modify +* it under the terms of the GNU Lesser General Public License as +* published by the Free Software Foundation, either version 3 of +* the License, or(at your option) any later version. +* +* Sodaq_RN2483 is distributed in the hope that it will be useful, +* but WITHOUT ANY WARRANTY; without even the implied warranty of +* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the +* GNU Lesser General Public License for more details. +* +* You should have received a copy of the GNU Lesser General Public +* License along with Sodaq_RN2483. If not, see +* . +*/ + +#include "Sodaq_RN2483.h" +#include "StringLiterals.h" +#include "Utils.h" + +#ifdef DEBUG +#define debugPrintLn(...) { if (this->diagStream) this->diagStream->println(__VA_ARGS__); } +#define debugPrint(...) { if (this->diagStream) this->diagStream->print(__VA_ARGS__); } +#warning "Debug mode is ON" +#else +#define debugPrintLn(...) +#define debugPrint(...) +#endif + +// Structure for mapping error response strings and error codes. +typedef struct StringEnumPair +{ + const char* stringValue; + uint8_t enumValue; +} StringEnumPair_t; + +Sodaq_RN2483 LoRaBee; + +// Creates a new Sodaq_RN2483 instance. +Sodaq_RN2483::Sodaq_RN2483() : + loraStream(0), + diagStream(0), + inputBufferSize(DEFAULT_INPUT_BUFFER_SIZE), + receivedPayloadBufferSize(DEFAULT_RECEIVED_PAYLOAD_BUFFER_SIZE), + packetReceived(false) +{ +#ifdef USE_DYNAMIC_BUFFER + this->isBufferInitialized = false; +#endif +} + +// Takes care of the init tasks common to both initOTA() and initABP. +void Sodaq_RN2483::init(Stream& stream) +{ + debugPrintLn("[init]"); + + this->loraStream = &stream; + +#ifdef USE_DYNAMIC_BUFFER + // make sure the buffers are only initialized once + if (!isBufferInitialized) { + this->inputBuffer = static_cast(malloc(this->inputBufferSize)); + this->receivedPayloadBuffer = static_cast(malloc(this->receivedPayloadBufferSize)); + + isBufferInitialized = true; + } +#endif +} + +// Initializes the device and connects to the network using Over-The-Air Activation. +// Returns true on successful connection. +bool Sodaq_RN2483::initOTA(Stream& stream, const uint8_t devEUI[8], const uint8_t appEUI[8], const uint8_t appKey[16], bool adr) +{ + debugPrintLn("[initOTA]"); + + init(stream); + + return resetDevice() && + setMacParam(STR_DEV_EUI, devEUI, 8) && + setMacParam(STR_APP_EUI, appEUI, 8) && + setMacParam(STR_APP_KEY, appKey, 16) && + setMacParam(STR_ADR, BOOL_TO_ONOFF(adr)) && + joinNetwork(STR_OTAA); +} + +// Initializes the device and connects to the network using Activation By Personalization. +// Returns true on successful connection. +bool Sodaq_RN2483::initABP(Stream& stream, const uint8_t devAddr[4], const uint8_t appSKey[16], const uint8_t nwkSKey[16], bool adr) +{ + debugPrintLn("[initABP]"); + + init(stream); + + return resetDevice() && + setMacParam(STR_DEV_ADDR, devAddr, 4) && + setMacParam(STR_APP_SESSION_KEY, appSKey, 16) && + setMacParam(STR_NETWORK_SESSION_KEY, nwkSKey, 16) && + setMacParam(STR_ADR, BOOL_TO_ONOFF(adr)) && + joinNetwork(STR_ABP); +} + +// Sends the given payload without acknowledgement. +// Returns 0 (NoError) when transmission is successful or one of the MacTransmitErrorCodes otherwise. +uint8_t Sodaq_RN2483::send(uint8_t port, const uint8_t* payload, uint8_t size) +{ + debugPrintLn("[send]"); + + return macTransmit(STR_UNCONFIRMED, port, payload, size); +} + +// Sends the given payload with acknowledgement. +// Returns 0 (NoError) when transmission is successful or one of the MacTransmitErrorCodes otherwise. +uint8_t Sodaq_RN2483::sendReqAck(uint8_t port, const uint8_t* payload, + uint8_t size, uint8_t maxRetries) +{ + debugPrintLn("[sendReqAck]"); + + if (!setMacParam(STR_RETRIES, maxRetries)) { + // not a fatal error -just show a debug message + debugPrintLn("[sendReqAck] Non-fatal error: setting number of retries failed."); + } + + return macTransmit(STR_CONFIRMED, port, payload, size); +} + +// Copies the latest received packet (optionally starting from the "payloadStartPosition" +// position of the payload) into the given "buffer", up to "size" number of bytes. +// Returns the number of bytes written or 0 if no packet is received since last transmission. +uint16_t Sodaq_RN2483::receive(uint8_t* buffer, uint16_t size, + uint16_t payloadStartPosition) +{ + debugPrintLn("[receive]"); + + if (!this->packetReceived) { + debugPrintLn("[receive]: There is no packet received!"); + return 0; + } + + uint16_t inputIndex = payloadStartPosition * 2; // payloadStartPosition is in bytes, not hex char pairs + uint16_t outputIndex = 0; + + // check that the asked starting position is within bounds + if (inputIndex >= this->receivedPayloadBufferSize) { + debugPrintLn("[receive]: Out of bounds start position!"); + return 0; + } + + // stop at the first string termination char, or if output buffer is over, or if payload buffer is over + while (outputIndex < size + && inputIndex + 1 < this->receivedPayloadBufferSize + && this->receivedPayloadBuffer[inputIndex] != 0 + && this->receivedPayloadBuffer[inputIndex + 1] != 0) { + buffer[outputIndex] = HEX_PAIR_TO_BYTE( + this->receivedPayloadBuffer[inputIndex], + this->receivedPayloadBuffer[inputIndex + 1]); + + inputIndex += 2; + outputIndex++; + } + + // Note: if the payload has an odd length, the last char is discarded + + buffer[outputIndex] = 0; // terminate the string + + debugPrintLn("[receive]: Done"); + return outputIndex; +} + +// Reads a line from the device stream into the "buffer" starting at the "start" position of the buffer. +// Returns the number of bytes read. +uint16_t Sodaq_RN2483::readLn(char* buffer, uint16_t size, uint16_t start) +{ + int len = this->loraStream->readBytesUntil('\n', buffer + start, size); + this->inputBuffer[start + len - 1] = 0; // bytes until \n always end with \r, so get rid of it (-1) + + return len; +} + +// Waits for the given string. Returns true if the string is received before a timeout. +// Returns false if a timeout occurs or if another string is received. +bool Sodaq_RN2483::expectString(const char* str, uint16_t timeout) +{ + debugPrint("[expectString] expecting "); debugPrint(str); + + unsigned long start = millis(); + while (millis() < start + timeout) { + debugPrint("."); + + if (readLn() > 0) { + debugPrint("("); debugPrint(this->inputBuffer); debugPrint(")"); + + if (strstr(this->inputBuffer, str) != NULL) { + debugPrintLn(" found a match!"); + + return true; + } + + return false; + } + } + + return false; +} + +bool Sodaq_RN2483::expectOK() +{ + return expectString(STR_RESULT_OK); +} + +// Sends a reset command to the module and waits for the success response (or timeout). +// Returns true on success. +bool Sodaq_RN2483::resetDevice() +{ + debugPrintLn("[resetDevice]"); + + this->loraStream->print(STR_CMD_RESET); + this->loraStream->print(CRLF); + + return expectString(STR_DEVICE_TYPE); +} + +// Sends a join network command to the device and waits for the response (or timeout). +// Returns true on success. +bool Sodaq_RN2483::joinNetwork(const char* type) +{ + debugPrintLn("[joinNetwork]"); + + this->loraStream->print(STR_CMD_JOIN); + this->loraStream->print(type); + this->loraStream->print(CRLF); + + return expectOK() && expectString(STR_ACCEPTED, 30000); +} + +// Sends the given mac command together with the given paramValue +// to the device and awaits for the response. +// Returns true on success. +// NOTE: paramName should include a trailing space +bool Sodaq_RN2483::setMacParam(const char* paramName, const uint8_t* paramValue, uint16_t size) +{ + debugPrint("[setMacParam] "); debugPrint(paramName); debugPrint("= [array]"); + + this->loraStream->print(STR_CMD_SET); + this->loraStream->print(paramName); + + for (uint16_t i = 0; i < size; ++i) { + this->loraStream->print(static_cast(NIBBLE_TO_HEX_CHAR(HIGH_NIBBLE(paramValue[i])))); + this->loraStream->print(static_cast(NIBBLE_TO_HEX_CHAR(LOW_NIBBLE(paramValue[i])))); + } + + this->loraStream->print(CRLF); + + return expectOK(); +} + +// Sends the given mac command together with the given paramValue +// to the device and awaits for the response. +// Returns true on success. +// NOTE: paramName should include a trailing space +bool Sodaq_RN2483::setMacParam(const char* paramName, uint8_t paramValue) +{ + debugPrint("[setMacParam] "); + debugPrint(paramName); + debugPrint("= "); + debugPrintLn(paramValue); + + this->loraStream->print(STR_CMD_SET); + this->loraStream->print(paramName); + this->loraStream->print(paramValue); + this->loraStream->print(CRLF); + + return expectOK(); +} + +// Sends the given mac command together with the given paramValue +// to the device and awaits for the response. +// Returns true on success. +// NOTE: paramName should include a trailing space +bool Sodaq_RN2483::setMacParam(const char* paramName, const char* paramValue) +{ + debugPrint("[setMacParam] "); + debugPrint(paramName); + debugPrint("= "); + debugPrintLn(paramValue); + + this->loraStream->print(STR_CMD_SET); + this->loraStream->print(paramName); + this->loraStream->print(paramValue); + this->loraStream->print(CRLF); + + return expectOK(); +} + +// Returns the enum that is mapped to the given "error" message. +uint8_t Sodaq_RN2483::lookupMacTransmitError(const char* error) +{ + debugPrint("[lookupMacTransmitError]: "); + debugPrintLn(error); + + if (error[0] == 0) { + debugPrintLn("[lookupMacTransmitError]: The string is empty!"); + return NoResponse; + } + + StringEnumPair_t errorTable[] = + { + { STR_RESULT_INVALID_PARAM, InternalError }, + { STR_RESULT_NOT_JOINED, NotConnected }, + { STR_RESULT_NO_FREE_CHANNEL, Busy }, + { STR_RESULT_SILENT, Busy }, + { STR_RESULT_FRAME_COUNTER_ERROR, NetworkFatalError }, + { STR_RESULT_BUSY, Busy }, + { STR_RESULT_MAC_PAUSED, InternalError }, + { STR_RESULT_INVALID_DATA_LEN, PayloadSizeError }, + { STR_RESULT_MAC_ERROR, NoAcknowledgment }, + }; + + for (StringEnumPair_t * p = errorTable; p->stringValue != NULL; ++p) { + if (strcmp(p->stringValue, error) == 0) { + debugPrint("[lookupMacTransmitError]: found "); + debugPrintLn(p->enumValue); + + return p->enumValue; + } + } + + debugPrintLn("[lookupMacTransmitError]: not found!"); + return NoResponse; +} + +uint8_t Sodaq_RN2483::macTransmit(const char* type, uint8_t port, const uint8_t* payload, uint8_t size) +{ + debugPrintLn("[macTransmit]"); + + this->loraStream->print(STR_CMD_MAC_TX); + this->loraStream->print(type); + this->loraStream->print(port); + this->loraStream->print(" "); + + for (int i = 0; i < size; ++i) { + this->loraStream->print(static_cast(NIBBLE_TO_HEX_CHAR(HIGH_NIBBLE(payload[i])))); + this->loraStream->print(static_cast(NIBBLE_TO_HEX_CHAR(LOW_NIBBLE(payload[i])))); + } + + this->loraStream->print(CRLF); + + if (!expectOK()) { + return lookupMacTransmitError(this->inputBuffer); // inputBuffer still has the last line read + } + + this->packetReceived = false; // prepare for receiving a new packet + + debugPrint("Waiting for server response"); + unsigned long timeout = millis() + RECEIVE_TIMEOUT; // hard timeout + while (millis() < timeout) { + debugPrint("."); + if (readLn() > 0) { + debugPrintLn(".");debugPrint("(");debugPrint(this->inputBuffer);debugPrintLn(")"); + + if (strstr(this->inputBuffer, " ") != NULL) // to avoid double delimiter search + { + // there is a splittable line -only case known is mac_rx + debugPrintLn("Splittable response found"); + return onMacRX(); + } else if (strstr(this->inputBuffer, STR_RESULT_MAC_TX_OK)) { + // done + debugPrintLn("Received mac_tx_ok"); + return NoError; + } else { + // lookup the error message + debugPrintLn("Some other string received (error)"); + return lookupMacTransmitError(this->inputBuffer); + } + } + } + + debugPrintLn("Timed-out waiting for a response!"); + return Timeout; +} + +// Parses the input buffer and copies the received payload into the "received payload" buffer +// when a "mac rx" message has been received. It is called internally by macTransmit(). +// Returns 0 (NoError) or otherwise one of the MacTransmitErrorCodes. +uint8_t Sodaq_RN2483::onMacRX() +{ + debugPrintLn("[onMacRX]"); + + // parse inputbuffer, put payload into packet buffer + char* token = strtok(this->inputBuffer, " "); + + // sanity check + if (strcmp(token, STR_RESULT_MAC_RX) != 0) { + debugPrintLn("[onMacRX]: mac_rx keyword not found!"); + return InternalError; + } + + // port + token = strtok(NULL, " "); + + // payload + token = strtok(NULL, " "); // until end of string + + uint16_t len = strlen(token) + 1; // include termination char + memcpy(this->receivedPayloadBuffer, token, len <= this->receivedPayloadBufferSize ? len : this->receivedPayloadBufferSize); + + this->packetReceived = true; // enable receive() again + return NoError; +} + +#ifdef DEBUG +// Provides a quick test of several methods as a pseudo-unit test. +void Sodaq_RN2483::runTestSequence(Stream& stream) +{ + debugPrint("free ram: "); + debugPrintLn(freeRam()); + + init(stream); + + this->loraStream = &stream; + this->diagStream = &stream; + + // expectString + debugPrintLn("write \"testString\" and then CRLF"); + if (expectString("testString", 5000)) { + debugPrintLn("[expectString] positive case works!"); + } + + debugPrintLn(""); + debugPrintLn("write something other than \"testString\" and then CRLF"); + if (!expectString("testString", 5000)) { + debugPrintLn("[expectString] negative case works!"); + } + + debugPrint("free ram: "); + debugPrintLn(freeRam()); + + // setMacParam(array) + debugPrintLn(""); + debugPrintLn(""); + uint8_t testValue[] = {0x01, 0x02, 0xDE, 0xAD, 0xBE, 0xEF}; + setMacParam("testParam ", testValue, ARRAY_SIZE(testValue)); + + // macTransmit + debugPrintLn(""); + debugPrintLn(""); + uint8_t testValue2[] = {0x01, 0x02, 0xDE, 0xAD, 0xBE, 0xEF}; + macTransmit(STR_CONFIRMED, 1, testValue2, ARRAY_SIZE(testValue2)); + + debugPrint("free ram: "); + debugPrintLn(freeRam()); + + // receive + debugPrintLn(""); + debugPrintLn("==== receive"); + char mockResult[] = "303132333435363738"; + memcpy(this->receivedPayloadBuffer, mockResult, strlen(mockResult) + 1); + uint8_t payload[64]; + debugPrintLn("* without having received packet"); + uint8_t length = receive(payload, sizeof(payload)); + debugPrintLn(reinterpret_cast(payload)); + debugPrint("Length: "); + debugPrintLn(length); + debugPrintLn("* having received packet"); + this->packetReceived = true; + length = receive(payload, sizeof(payload)); + debugPrintLn(reinterpret_cast(payload)); + debugPrint("Length: "); + debugPrintLn(length); + + // onMacRX + debugPrintLn(""); + debugPrintLn("==== onMacRX"); + char mockRx[] = "mac_rx 1 303132333435363738"; + memcpy(this->inputBuffer, mockRx, strlen(mockRx) + 1); + this->packetReceived = false;// reset + debugPrint("Input buffer now is: "); + debugPrintLn(this->inputBuffer); + debugPrint("onMacRX result code: "); + debugPrintLn(onMacRX()); + uint8_t payload2[64]; + if (receive(payload2, sizeof(payload2)) != 9) { + debugPrintLn("len is wrong!"); + } + debugPrintLn(reinterpret_cast(payload2)); + if (receive(payload2, sizeof(payload2), 2) != 7) { + debugPrintLn("len is wrong!"); + } + debugPrintLn(reinterpret_cast(payload2)); + if (receive(payload2, sizeof(payload2), 3) != 6) { + debugPrintLn("len is wrong!"); + } + debugPrintLn(reinterpret_cast(payload2)); + + debugPrint("free ram: "); + debugPrintLn(freeRam()); + + // lookup error + debugPrintLn(""); + debugPrintLn(""); + + debugPrint("empty string: "); + debugPrintLn((lookupMacTransmitError("") == NoResponse) ? "passed" : "wrong"); + + debugPrint("\"random\": "); + debugPrintLn((lookupMacTransmitError("random") == NoResponse) ? "passed" : "wrong"); + + debugPrint("\"invalid_param\": "); + debugPrintLn((lookupMacTransmitError("invalid_param") == InternalError) ? "passed" : "wrong"); + + debugPrint("\"not_joined\": "); + debugPrintLn((lookupMacTransmitError("not_joined") == NotConnected) ? "passed" : "wrong"); + + debugPrint("\"busy\": "); + debugPrintLn((lookupMacTransmitError("busy") == Busy) ? "passed" : "wrong"); + + debugPrint("\"invalid_param\": "); + debugPrintLn((lookupMacTransmitError("invalid_param") == InternalError) ? "passed" : "wrong"); + + debugPrint("free ram: "); + debugPrintLn(freeRam()); +} + +int Sodaq_RN2483::freeRam() +{ + extern int __heap_start; + extern int *__brkval; + int v; + return (int)&v - (__brkval == 0 ? (int)&__heap_start : (int)__brkval); +} + +#endif diff --git a/Sodaq_RN2483.h b/Sodaq_RN2483.h new file mode 100644 index 0000000..7b7ba2b --- /dev/null +++ b/Sodaq_RN2483.h @@ -0,0 +1,199 @@ +/* +* Copyright (c) 2015 SODAQ. All rights reserved. +* +* This file is part of Sodaq_RN2483. +* +* Sodaq_RN2483 is free software: you can redistribute it and/or modify +* it under the terms of the GNU Lesser General Public License as +* published by the Free Software Foundation, either version 3 of +* the License, or(at your option) any later version. +* +* Sodaq_RN2483 is distributed in the hope that it will be useful, +* but WITHOUT ANY WARRANTY; without even the implied warranty of +* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the +* GNU Lesser General Public License for more details. +* +* You should have received a copy of the GNU Lesser General Public +* License along with Sodaq_RN2483. If not, see +* . +*/ + +#ifndef _Sodaq_RN2483_h +#define _Sodaq_RN2483_h + +#include + +#include +#include +#include "Switchable_Device.h" + +/** + + Notes: + + - uint16_t is preferred over size_t because long is never needed by the + size of the packets or the buffers of this application. + (Kees Bakker does not agree with this. size_t is not the same as long. + On AVR a size_t is uint16_t. On SAMD we don't care too much about the + data size, a long is fine.) + - Currently, only one received packet is supported. Every time a packet is + received, the previous one is overwritten. + - Also multiple responses from the server (with Frame Pending Bit set) are + not supported. + - The port of the received packet is not returned. + + */ + +//#define USE_DYNAMIC_BUFFER +//#define DEBUG + +#define DEFAULT_INPUT_BUFFER_SIZE 64 +#define DEFAULT_RECEIVED_PAYLOAD_BUFFER_SIZE 32 +#define DEFAULT_TIMEOUT 120 +#define RECEIVE_TIMEOUT 60000 + +// Available error codes. +enum MacTransmitErrorCodes +{ + NoError = 0, + NoResponse = 1, + Timeout = 2, + PayloadSizeError = 3, + InternalError = 4, + Busy = 5, + NetworkFatalError = 6, + NotConnected = 7, + NoAcknowledgment = 8, +}; + +// Provides a simple, abstracted interface to Microchip's RN2483 LoRaWAN module. +// Implements SwitchableDevice for turning the device On/Off on supported boards. +// +// It is strongly suggested to use the static instance that is included with the library (LoRaBee) +// and not to create a new instance. +class Sodaq_RN2483 : public SwitchableDevice +{ +public: + // Creates a new Sodaq_RN2483 instance. + Sodaq_RN2483(); + + // Returns the correct baudrate for the serial port that connects to the device. + uint32_t getDefaultBaudRate() { return 57600; }; + + // Initializes the device and connects to the network using Over-The-Air Activation. + // Returns true on successful connection. + bool initOTA(Stream& stream, const uint8_t devEUI[8], const uint8_t appEUI[8], const uint8_t appKey[16], bool adr = true); + + // Initializes the device and connects to the network using Activation By Personalization. + // Returns true on successful connection. + bool initABP(Stream& stream, const uint8_t devAddr[4], const uint8_t appSKey[16], const uint8_t nwkSKey[16], bool adr = true); + + // Sets the optional "Diagnostics and Debug" stream. + void setDiag(Stream& stream) { diagStream = &stream; }; + + // Sends the given payload without acknowledgement. + // Returns 0 (NoError) when transmission is successful or one of the MacTransmitErrorCodes otherwise. + uint8_t send(uint8_t port, const uint8_t* payload, uint8_t size); + + // Sends the given payload with acknowledgement. + // Returns 0 (NoError) when transmission is successful or one of the MacTransmitErrorCodes otherwise. + uint8_t sendReqAck(uint8_t port, const uint8_t* payload, uint8_t size, uint8_t maxRetries); + + // Copies the latest received packet (optionally starting from the "payloadStartPosition" + // position of the payload) into the given "buffer", up to "size" number of bytes. + // Returns the number of bytes written or 0 if no packet is received since last transmission. + uint16_t receive(uint8_t* buffer, uint16_t size, uint16_t payloadStartPosition = 0); + +#ifdef USE_DYNAMIC_BUFFER + // Sets the size of the input buffer. + // Needs to be called before initOTA()/initABP(). + void setInputBufferSize(uint16_t value) { this->inputBufferSize = value; }; + + // Sets the size of the "Received Payload" buffer. + // Needs to be called before initOTA()/initABP(). + void setReceivedPayloadBufferSize(uint16_t value) { this->receivedPayloadBufferSize = value; }; +#endif + +#ifdef DEBUG + // Provides a quick test of several methods as a pseudo-unit test. + void runTestSequence(Stream& stream); + int freeRam(); +#endif + +private: + // The stream that communicates with the device. + Stream* loraStream; + + // The (optional) stream to show debug information. + Stream* diagStream; + + // The size of the input buffer. Equals DEFAULT_INPUT_BUFFER_SIZE + // by default or (optionally) a user-defined value when using USE_DYNAMIC_BUFFER. + uint16_t inputBufferSize; + + // The size of the received payload buffer. Equals DEFAULT_RECEIVED_PAYLOAD_BUFFER_SIZE + // by default or (optionally) a user-defined value when using USE_DYNAMIC_BUFFER. + uint16_t receivedPayloadBufferSize; + + // Flag used to make sure the received payload buffer is + // current with the latest transmission. + bool packetReceived; + +#ifdef USE_DYNAMIC_BUFFER + // Flag to make sure the buffers are not allocated more than once. + bool isBufferInitialized; + + char* inputBuffer; + char* receivedPayloadBuffer; +#else + char inputBuffer[DEFAULT_INPUT_BUFFER_SIZE]; + char receivedPayloadBuffer[DEFAULT_RECEIVED_PAYLOAD_BUFFER_SIZE]; +#endif + // Takes care of the init tasks common to both initOTA() and initABP. + inline void init(Stream& stream); + + // Reads a line from the device stream into the "buffer" starting at the "start" position of the buffer. + // Returns the number of bytes read. + uint16_t readLn(char* buffer, uint16_t size, uint16_t start = 0); + + // Reads a line from the device stream into the input buffer. + // Returns the number of bytes read. + uint16_t readLn() { return readLn(this->inputBuffer, this->inputBufferSize); }; + + // Waits for the given string. Returns true if the string is received before a timeout. + // Returns false if a timeout occurs or if another string is received. + bool expectString(const char* str, uint16_t timeout = DEFAULT_TIMEOUT); + bool expectOK(); + + // Sends a reset command to the module and waits for the success response (or timeout). + // Returns true on success. + bool resetDevice(); + + // Sends a join network command to the device and waits for the response (or timeout). + // Returns true on success. + bool joinNetwork(const char* type); + + // Sends the given mac command together with the given paramValue + // to the device and awaits for the response. + // Returns true on success. + // NOTE: paramName should include a trailing space + bool setMacParam(const char* paramName, const uint8_t* paramValue, uint16_t size); + bool setMacParam(const char* paramName, uint8_t paramValue); + bool setMacParam(const char* paramName, const char* paramValue); + + // Returns the enum that is mapped to the given "error" message. + uint8_t lookupMacTransmitError(const char* error); + + // Sends a a payload and blocks until there is a response back, or the receive windows have closed, + // or the hard timeout has passed. + uint8_t macTransmit(const char* type, uint8_t port, const uint8_t* payload, uint8_t size); + + // Parses the input buffer and copies the received payload into the "received payload" buffer + // when a "mac rx" message has been received. It is called internally by macTransmit(). + // Returns 0 (NoError) or otherwise one of the MacTransmitErrorCodes. + uint8_t onMacRX(); +}; + +extern Sodaq_RN2483 LoRaBee; + +#endif // Sodaq_RN2483 diff --git a/StringLiterals.h b/StringLiterals.h new file mode 100644 index 0000000..bcbb061 --- /dev/null +++ b/StringLiterals.h @@ -0,0 +1,63 @@ +/* +* Copyright (c) 2015 SODAQ. All rights reserved. +* +* This file is part of MicrochipLoRaWAN. +* +* MicrochipLoRaWAN is free software: you can redistribute it and/or modify +* it under the terms of the GNU Lesser General Public License as +* published by the Free Software Foundation, either version 3 of +* the License, or(at your option) any later version. +* +* MicrochipLoRaWAN is distributed in the hope that it will be useful, +* but WITHOUT ANY WARRANTY; without even the implied warranty of +* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the +* GNU Lesser General Public License for more details. +* +* You should have received a copy of the GNU Lesser General Public +* License along with MicrochipLoRaWAN. If not, see +* . +*/ + +#ifndef _STRINGLITERALS_h +#define _STRINGLITERALS_h + +#define CRLF "\r\n" + +#define STR_RESULT_OK "ok" +#define STR_RESULT_INVALID_PARAM "invalid_param" +#define STR_RESULT_MAC_ERROR "mac_err" +#define STR_RESULT_MAC_RX "mac_rx" +#define STR_RESULT_MAC_TX_OK "mac_tx_ok" + +#define STR_RESULT_NOT_JOINED "not_joined" +#define STR_RESULT_NO_FREE_CHANNEL "no_free_ch" +#define STR_RESULT_SILENT "silent" +#define STR_RESULT_FRAME_COUNTER_ERROR "frame_counter_err_rejoin_needed" +#define STR_RESULT_BUSY "busy" +#define STR_RESULT_MAC_PAUSED "mac_paused" +#define STR_RESULT_INVALID_DATA_LEN "invalid_data_len" + +#define STR_CMD_RESET "sys reset" +#define STR_DEVICE_TYPE "RN2483" + +#define STR_CMD_SET "mac set " +#define STR_RETRIES "retx " +#define STR_DEV_ADDR "devaddr " +#define STR_APP_SESSION_KEY "appskey " +#define STR_NETWORK_SESSION_KEY "nwkskey " +#define STR_DEV_EUI "deveui " +#define STR_APP_EUI "appeui " +#define STR_APP_KEY "appkey " +#define STR_ADR "adr " + +#define STR_CMD_JOIN "mac join " +#define STR_OTAA "otaa" +#define STR_ABP "abp" +#define STR_ACCEPTED "accepted" + +#define STR_CMD_MAC_TX "mac tx " +#define STR_CONFIRMED "cnf " +#define STR_UNCONFIRMED "uncnf " + + +#endif diff --git a/Switchable_Device.cpp b/Switchable_Device.cpp new file mode 100644 index 0000000..4cfb645 --- /dev/null +++ b/Switchable_Device.cpp @@ -0,0 +1,63 @@ +/* +* Copyright (c) 2015 SODAQ. All rights reserved. +* +* This file is part of MicrochipLoRaWAN. +* +* MicrochipLoRaWAN is free software: you can redistribute it and/or modify +* it under the terms of the GNU Lesser General Public License as +* published by the Free Software Foundation, either version 3 of +* the License, or(at your option) any later version. +* +* MicrochipLoRaWAN is distributed in the hope that it will be useful, +* but WITHOUT ANY WARRANTY; without even the implied warranty of +* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the +* GNU Lesser General Public License for more details. +* +* You should have received a copy of the GNU Lesser General Public +* License along with MicrochipLoRaWAN. If not, see +* . +*/ + +#include "Switchable_Device.h" + +SwitchableDevice::SwitchableDevice() +{ + clearSwitchMethods(); +} + +void SwitchableDevice::setOnMethod(voidFuncPtr onMethod) +{ + _onMethod = onMethod; +} + +void SwitchableDevice::setOffMethod(voidFuncPtr offMethod) +{ + _offMethod = offMethod; +} + +void SwitchableDevice::setSwitchMethods(voidFuncPtr onMethod, voidFuncPtr offMethod) +{ + _onMethod = onMethod; + _offMethod = offMethod; +} + +void SwitchableDevice::clearSwitchMethods() +{ + _onMethod = _offMethod = 0; +} + +void SwitchableDevice::on() +{ + if (_onMethod != 0) + { + _onMethod(); + } +} + +void SwitchableDevice::off() +{ + if (_offMethod != 0) + { + _offMethod(); + } +} diff --git a/Switchable_Device.h b/Switchable_Device.h new file mode 100644 index 0000000..6c19748 --- /dev/null +++ b/Switchable_Device.h @@ -0,0 +1,24 @@ +#ifndef SWITCHABLE_DEVICE_H +#define SWITCHABLE_DEVICE_H + +typedef void(*voidFuncPtr)(void); + +class SwitchableDevice { +private: + voidFuncPtr _onMethod; + voidFuncPtr _offMethod; + +public: + SwitchableDevice(); + + void setOnMethod(voidFuncPtr onMethod); + void setOffMethod(voidFuncPtr offMethod); + + void setSwitchMethods(voidFuncPtr onMethod, voidFuncPtr offMethod); + void clearSwitchMethods(); + + void on(); + void off(); +}; + +#endif // SWITCHABLE_DEVICE_H diff --git a/Utils.h b/Utils.h new file mode 100644 index 0000000..ed692d2 --- /dev/null +++ b/Utils.h @@ -0,0 +1,34 @@ +/* +* Copyright (c) 2015 SODAQ. All rights reserved. +* +* This file is part of MicrochipLoRaWAN. +* +* MicrochipLoRaWAN is free software: you can redistribute it and/or modify +* it under the terms of the GNU Lesser General Public License as +* published by the Free Software Foundation, either version 3 of +* the License, or(at your option) any later version. +* +* MicrochipLoRaWAN is distributed in the hope that it will be useful, +* but WITHOUT ANY WARRANTY; without even the implied warranty of +* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the +* GNU Lesser General Public License for more details. +* +* You should have received a copy of the GNU Lesser General Public +* License along with MicrochipLoRaWAN. If not, see +* . +*/ + +#ifndef _UTILS_h +#define _UTILS_h + +#define BOOL_TO_ONOFF(b) (b ? "on" : "off") +#define NIBBLE_TO_HEX_CHAR(i) ((i <= 9) ? ('0' + i) : ('A' - 10 + i)) +#define HIGH_NIBBLE(i) ((i >> 4) & 0x0F) +#define LOW_NIBBLE(i) (i & 0x0F) + +#define HEX_CHAR_TO_NIBBLE(c) ((c >= 'A') ? (c - 'A' + 0x0A) : (c - '0')) +#define HEX_PAIR_TO_BYTE(h, l) ((HEX_CHAR_TO_NIBBLE(h) << 4) + HEX_CHAR_TO_NIBBLE(l)) + +#define ARRAY_SIZE(a) (sizeof(a)/sizeof(a[0])) + +#endif