From 4eb8762f392703d7528ff3583cf3030c49af8c77 Mon Sep 17 00:00:00 2001 From: danalvarez Date: Fri, 4 Feb 2022 01:53:43 -0600 Subject: [PATCH] Improve handling of needsHardReset flag and add method to check valid module connected to serial port (#282) * Removed reset of needsHardReset flag in reset() function. Added checkValidModuleConnected() to check for correct wiring and supported module. Added getVersion to obtain response of sys get ver. Updated documentation. Added CheckModule example to showcase usage of checkValidModuleConnected(). * Updated naming scheme to match TTN camelCase convention --- docs/TheThingsNetwork.md | 36 +++++++++++++- examples/CheckModule/CheckModule.ino | 73 ++++++++++++++++++++++++++++ src/TheThingsNetwork.cpp | 68 ++++++++++++++++++++++---- src/TheThingsNetwork.h | 6 ++- 4 files changed, 170 insertions(+), 13 deletions(-) create mode 100644 examples/CheckModule/CheckModule.ino 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