diff --git a/docs/TheThingsNetwork.md b/docs/TheThingsNetwork.md index 766825e3..4511a6eb 100644 --- a/docs/TheThingsNetwork.md +++ b/docs/TheThingsNetwork.md @@ -54,6 +54,14 @@ Gets the provisioned AppEUI. The AppEUI is set using `provision()` or `join()`. size_t getAppEui(char *buffer, size_t size); ``` +## Method: `getVersion` + +Gets the response from a `sys get ver` command (i.e. the hardware model, the the software version, etc.). + +```c +size_t getVersion(char *buffer, size_t size); +``` + ## Method: `showStatus` Writes information about the device and LoRa module to `debugStream`. @@ -114,13 +122,14 @@ Call the method without the first two arguments if the device's LoRa module come Activate the device via ABP. ```c -bool personalize(const char *devAddr, const char *nwkSKey, const char *appSKey); +bool personalize(const char *devAddr, const char *nwkSKey, const char *appSKey, bool reset_first); bool personalize(); ``` - `const char *devAddr`: Device Address assigned to the device. - `const char *nwkSKey`: Network Session Key assigned to the device for identification. - `const char *appSKey`: Application Session Key assigned to the device for encryption. +- `bool reset_first`: Soft reset the module before performing any other action. Default is `true`. Returns `true` or `false` depending on whether the activation was successful. @@ -192,11 +201,12 @@ See the [Receive](https://github.com/TheThingsNetwork/arduino-device-lib/blob/ma Sets the information needed to activate the device via OTAA, without actually activating. Call join() without the first 2 arguments to activate. ```c -bool provision(const char *appEui, const char *appKey); +bool provision(const char *appEui, const char *appKey, bool reset_first); ``` - `const char *appEui`: Application Identifier for the device. - `const char *appKey`: Application Key assigned to the device. +- `bool reset_first`: Soft reset the module before performing any other action. Default is `true`. ## Method: `sleep` @@ -434,6 +444,28 @@ When transmitting in LoRaWan, we usually operate on a TX window and two RX windo bool setRX1Delay(uint16_t delay); ``` +## Method: `checkValidModuleConnected` + +Checks if a valid module is connected to the configured serial port. Useful to check for connectivity with a supported module before performing any other actions. + +```c +bool checkValidModuleConnected(bool autobaud_first); +``` + +- `bool autobaud_first`: Perform a call to `autoBaud()` before checking connection. Default is `false`. + +Returns: + +* `false` if no response was received (i.e. `needsHardReset` is `true`) +* `false` if the module is invalid (unsupported), i.e. **not** one of the following: + * `RN2483` + * `RN2483A` + * `RN2903` + * `RN2903AS` +* `true` if the module responded (i.e. `needsHardReset` is `false`) and is valid (supported). + +See the [CheckModule](https://github.com/TheThingsNetwork/arduino-device-lib/blob/master/examples/CheckModule/CheckModule.ino) example. + # Additional for statistics ## Method: `getRSSI` diff --git a/examples/CheckModule/CheckModule.ino b/examples/CheckModule/CheckModule.ino new file mode 100644 index 00000000..c67a6fc1 --- /dev/null +++ b/examples/CheckModule/CheckModule.ino @@ -0,0 +1,73 @@ +#include + +// Set your DevAddr, NwkSKey, AppSKey and the frequency plan +const char *devAddr = "00000000"; +const char *nwkSKey = "00000000000000000000000000000000"; +const char *appSKey = "00000000000000000000000000000000"; + +#define loraSerial Serial1 +#define debugSerial Serial + +// Replace REPLACE_ME with TTN_FP_EU868 or TTN_FP_US915 +#define freqPlan REPLACE_ME + +TheThingsNetwork ttn(loraSerial, debugSerial, freqPlan); + +void setup() +{ + loraSerial.begin(57600); + debugSerial.begin(9600); + + // Wait a maximum of 10s for Serial Monitor + while (!debugSerial && millis() < 10000) + ; + + // RN2XX3 reset pin connected to Arduino pin 12 + pinMode(12, OUTPUT); + digitalWrite(12, HIGH); + // hard reset module and wait for startup + debugSerial.println("-- CHECK COMM"); + ttn.resetHard(12); + delay(100); + // check if a valid module responded + // (if no module is connected or wiring is bad, checkValidModuleConnected() will + // take about ~30s to return (another ~30s if autobaud_first is true)) + if(!ttn.checkValidModuleConnected(true)) + { + if(ttn.needsHardReset) + { + debugSerial.println("Module unresponsive, please power cycle or hard reset board!"); + } + else + { + debugSerial.println("Module unsupported!"); // module must be RN2483, RN2483A, RN2903, RN2903AS + } + while(true) // stop code execution + { + ; + } + } + + // do an ABP join + debugSerial.println("-- PERSONALIZE"); + // false is added as fourth argument to the personalize() call so that it + // does not perform a soft reset, because the module was already hard reset before via pin 12. + ttn.personalize(devAddr, nwkSKey, appSKey, false); + + debugSerial.println("-- STATUS"); + ttn.showStatus(); +} + +void loop() +{ + debugSerial.println("-- LOOP"); + + // Prepare payload of 1 byte to indicate LED status + byte payload[1]; + payload[0] = (digitalRead(LED_BUILTIN) == HIGH) ? 1 : 0; + + // Send it off + ttn.sendBytes(payload, sizeof(payload)); + + delay(10000); +} diff --git a/src/TheThingsNetwork.cpp b/src/TheThingsNetwork.cpp index ec6f3804..d0888e94 100644 --- a/src/TheThingsNetwork.cpp +++ b/src/TheThingsNetwork.cpp @@ -25,8 +25,11 @@ const char mac_tx_ok[] PROGMEM = "mac_tx_ok"; const char mac_rx[] PROGMEM = "mac_rx"; const char mac_err[] PROGMEM = "mac_err"; const char rn2483[] PROGMEM = "RN2483"; +const char rn2483a[] PROGMEM = "RN2483A"; +const char rn2903[] PROGMEM = "RN2903"; +const char rn2903as[] PROGMEM = "RN2903AS"; -const char *const compare_table[] PROGMEM = {ok, on, off, accepted, mac_tx_ok, mac_rx, mac_err, rn2483}; +const char *const compare_table[] PROGMEM = {ok, on, off, accepted, mac_tx_ok, mac_rx, mac_err, rn2483, rn2483a, rn2903, rn2903as}; #define CMP_OK 0 #define CMP_ON 1 @@ -36,6 +39,9 @@ const char *const compare_table[] PROGMEM = {ok, on, off, accepted, mac_tx_ok, m #define CMP_MAC_RX 5 #define CMP_MAC_ERR 6 #define CMP_RN2483 7 +#define CMP_RN2483A 8 +#define CMP_RN2903 9 +#define CMP_RN2903AS 10 // CMP OK const char busy[] PROGMEM = "busy"; @@ -109,8 +115,9 @@ const char response_is_not_ok[] PROGMEM = "Response is not OK: "; const char error_key_length[] PROGMEM = "One or more keys are of invalid length."; const char check_configuration[] PROGMEM = "Check your coverage, keys and backend status."; const char no_response[] PROGMEM = "No response from RN module."; +const char invalid_module[] PROGMEM = "Invalid module (must be RN2xx3[xx])."; -const char *const error_msg[] PROGMEM = {invalid_sf, invalid_fp, unexpected_response, send_command_failed, join_failed, join_not_accepted, personalize_not_accepted, response_is_not_ok, error_key_length, check_configuration, no_response}; +const char *const error_msg[] PROGMEM = {invalid_sf, invalid_fp, unexpected_response, send_command_failed, join_failed, join_not_accepted, personalize_not_accepted, response_is_not_ok, error_key_length, check_configuration, no_response, invalid_module}; #define ERR_INVALID_SF 0 #define ERR_INVALID_FP 1 @@ -123,18 +130,21 @@ const char *const error_msg[] PROGMEM = {invalid_sf, invalid_fp, unexpected_resp #define ERR_KEY_LENGTH 8 #define ERR_CHECK_CONFIGURATION 9 #define ERR_NO_RESPONSE 10 +#define ERR_INVALID_MODULE 11 const char personalize_accepted[] PROGMEM = "Personalize accepted. Status: "; const char join_accepted[] PROGMEM = "Join accepted. Status: "; const char successful_transmission[] PROGMEM = "Successful transmission"; const char successful_transmission_received[] PROGMEM = "Successful transmission. Received "; +const char valid_module[] PROGMEM = "Valid module connected."; -const char *const success_msg[] PROGMEM = {personalize_accepted, join_accepted, successful_transmission, successful_transmission_received}; +const char *const success_msg[] PROGMEM = {personalize_accepted, join_accepted, successful_transmission, successful_transmission_received, valid_module}; #define SCS_PERSONALIZE_ACCEPTED 0 #define SCS_JOIN_ACCEPTED 1 #define SCS_SUCCESSFUL_TRANSMISSION 2 #define SCS_SUCCESSFUL_TRANSMISSION_RECEIVED 3 +#define SCS_VALID_MODULE 4 const char radio_prefix[] PROGMEM = "radio"; const char radio_set[] PROGMEM = "set"; @@ -381,6 +391,11 @@ size_t TheThingsNetwork::getHardwareEui(char *buffer, size_t size) return readResponse(SYS_TABLE, SYS_TABLE, SYS_GET_HWEUI, buffer, size); } +size_t TheThingsNetwork::getVersion(char *buffer, size_t size) +{ + return readResponse(SYS_TABLE, SYS_TABLE, SYS_GET_VER, buffer, size); +} + uint16_t TheThingsNetwork::getVDD() { if (readResponse(SYS_TABLE, SYS_TABLE, SYS_GET_VDD, buffer, sizeof(buffer)) > 0) { @@ -632,11 +647,13 @@ void TheThingsNetwork::autoBaud() void TheThingsNetwork::reset(bool adr) { + // autobaud and send "sys reset" autoBaud(); readResponse(SYS_TABLE, SYS_RESET, buffer, sizeof(buffer)); + // autobaud (again, because baudrate was reset with "sys reset") and get HW model and SW version autoBaud(); - readResponse(SYS_TABLE, SYS_TABLE, SYS_GET_VER, buffer, sizeof(buffer)); + getVersion(buffer, sizeof(buffer)); // buffer contains "RN2xx3[xx] x.x.x ...", splitting model from version char *model = strtok(buffer, " "); @@ -644,10 +661,11 @@ void TheThingsNetwork::reset(bool adr) char *version = strtok(NULL, " "); debugPrintIndex(SHOW_VERSION, version); + // set DEVEUI as HWEUI readResponse(SYS_TABLE, SYS_TABLE, SYS_GET_HWEUI, buffer, sizeof(buffer)); sendMacSet(MAC_DEVEUI, buffer); + // set ADR setADR(adr); - this->needsHardReset = false; } void TheThingsNetwork::resetHard(uint8_t resetPin){ @@ -671,9 +689,11 @@ void TheThingsNetwork::onMessage(void (*cb)(const uint8_t *payload, size_t size, messageCallback = cb; } -bool TheThingsNetwork::personalize(const char *devAddr, const char *nwkSKey, const char *appSKey) +bool TheThingsNetwork::personalize(const char *devAddr, const char *nwkSKey, const char *appSKey, bool resetFirst) { - reset(adr); + if(resetFirst) { + reset(adr); + } if (strlen(devAddr) != 8 || strlen(appSKey) != 32 || strlen(nwkSKey) != 32) { debugPrintMessage(ERR_MESSAGE, ERR_KEY_LENGTH); @@ -703,9 +723,11 @@ bool TheThingsNetwork::personalize() return true; } -bool TheThingsNetwork::provision(const char *appEui, const char *appKey) +bool TheThingsNetwork::provision(const char *appEui, const char *appKey, bool resetFirst) { - reset(adr); + if(resetFirst) { + reset(adr); + } if (strlen(appEui) != 16 || strlen(appKey) != 32) { debugPrintMessage(ERR_MESSAGE, ERR_KEY_LENGTH); @@ -917,6 +939,34 @@ void TheThingsNetwork::showStatus() debugPrintIndex(SHOW_RX_DELAY_2, buffer); } +bool TheThingsNetwork::checkValidModuleConnected(bool autoBaudFirst) +{ + // check if we want to autobaud first + if(autoBaudFirst) + { + autoBaud(); + } + // send "sys get ver" to check if (and what) module is connected + getVersion(buffer, sizeof(buffer)); + // check if we got a response (whatever it might be) + // needsHardReset flag is set by readLine() (called at some point down the line by getVersion()) + if(this->needsHardReset) + { + return false; // no response + } + // buffer contains "RN2xx3[xx] x.x.x ...", getting only model (RN2xx3[xx]) + char *model = strtok(buffer, " "); + debugPrintIndex(SHOW_MODEL, model); + // check if module is valid (must be RN2483, RN2483A, RN2903 or RN2903AS) + if(pgmstrcmp(model, CMP_RN2483) == 0 || pgmstrcmp(model, CMP_RN2483A) == 0 || pgmstrcmp(model, CMP_RN2903) == 0 || pgmstrcmp(model, CMP_RN2903AS) == 0) + { + debugPrintMessage(SUCCESS_MESSAGE, SCS_VALID_MODULE); + return true; // module responded and is valid (recognized/supported) + } + debugPrintMessage(ERR_MESSAGE, ERR_INVALID_MODULE); + return false; // module responded but is invalid (unrecognized/unsupported) +} + void TheThingsNetwork::configureEU868() { sendMacSet(MAC_RX2, 3, 869525000); diff --git a/src/TheThingsNetwork.h b/src/TheThingsNetwork.h index c1f52874..ebcc59f7 100644 --- a/src/TheThingsNetwork.h +++ b/src/TheThingsNetwork.h @@ -140,6 +140,7 @@ class TheThingsNetwork void showStatus(); size_t getHardwareEui(char *buffer, size_t size); size_t getAppEui(char *buffer, size_t size); + size_t getVersion(char *buffer, size_t size); enum ttn_modem_status_t getStatus(); uint16_t getVDD(); int16_t getRSSI(); @@ -156,10 +157,10 @@ class TheThingsNetwork bool getChannelStatus (uint8_t channel); ttn_response_code_t getLastError(); void onMessage(void (*cb)(const uint8_t *payload, size_t size, port_t port)); - bool provision(const char *appEui, const char *appKey); + bool provision(const char *appEui, const char *appKey, bool resetFirst = true); bool join(const char *appEui, const char *appKey, int8_t retries = -1, uint32_t retryDelay = 10000, lorawan_class_t = CLASS_A); bool join(int8_t retries = -1, uint32_t retryDelay = 10000); - bool personalize(const char *devAddr, const char *nwkSKey, const char *appSKey); + bool personalize(const char *devAddr, const char *nwkSKey, const char *appSKey, bool resetFirst = true); bool personalize(); bool setClass(lorawan_class_t p_lw_class); ttn_response_t sendBytes(const uint8_t *payload, size_t length, port_t port = 1, bool confirm = false, uint8_t sf = 0); @@ -180,6 +181,7 @@ class TheThingsNetwork bool setRX1Delay(uint16_t delay); bool setFCU(uint32_t fcu); bool setFCD(uint32_t fcd); + bool checkValidModuleConnected(bool autoBaudFirst = false); }; #endif