diff --git a/README.md b/README.md index ec363b4d..e05089ea 100644 --- a/README.md +++ b/README.md @@ -3,7 +3,7 @@ This is an [Arduino Library](https://www.arduino.cc/en/Guide/Libraries) for Arduino devices like [The Things Uno](https://www.thethingsnetwork.org/docs/devices/uno/) and [Node](https://www.thethingsnetwork.org/docs/devices/node/) to communicate via [The Things Network](https://www.thethingsnetwork.org). -> At the moment this library requires devices to feature a [Microchip RN2xx3 module](http://www.microchip.com/design-centers/wireless-connectivity/embedded-wireless/lora-technology). +> At the moment this library requires devices to feature a [Microchip RN2xx3 module](http://www.microchip.com/design-centers/wireless-connectivity/embedded-wireless/lora-technology). You may also use a `SAMR34`-based board, for more information on that see [SAM34 Usage](#user-content-samr34-usage). ## Installation @@ -17,6 +17,16 @@ This is an [Arduino Library](https://www.arduino.cc/en/Guide/Libraries) for Ardu * [TheThingsNetwork](docs/TheThingsNetwork.md) * [TheThingsMessage](docs/TheThingsMessage.md) +## SAM34 Usage + +Compatibility between this library and the `SAMR34`-based boards is, at the moment, experimental. Boards that can be used are the [SAMR34 Xplained Pro](https://www.microchip.com/en-us/development-tool/dm320111), the [WLR089 Xplained Pro](https://www.microchip.com/en-us/development-tool/EV23M25A) or a proprietary board containing the `SAMR34`. + +Before usage, please note the following: + +1. If using a `SAMR34`-based board, you must first program your board with the [RN Parser](https://github.com/MicrochipTech/atsamr34_lorawan_rn_parser) firmware. This firmware emulates the behaviour of the `RN2xx3` devices on the `SAMR34`. Specifically, you **must** use the `MLS 1_0_P_6 (Parser_ECC608)` firmware contained [here](https://github.com/MicrochipTech/atsamr34_lorawan_rn_parser/tree/master/software/MLS_1_0_P_6/Parser_ECC608), since several bugs that made the firmware unusable where fixed for this release. +2. Some commands available in the `RN2xx3` modems are not implemented in the RN Parser firmware for `SAMR34`. For a complete list of the implemented commands and their possible differences, see the [Command User Guide](https://github.com/MicrochipTech/atsamr34_lorawan_rn_parser/blob/master/02_command_guide/README.md#top) for the RN Parser firmware. +3. The `autoBaud` feature is not available in the RN parser firmware, but this is easily circumvented by manually modifying the default baud rate in the `conf_sio2host.h` file within the firmware's source code. Default baud rate is `115200`. + ## Examples The library comes with [examples](examples). After installing the library you need to restart the Arduino IDE before they can be found under **File > Examples > TheThingsNetwork**. diff --git a/docs/TheThingsNetwork.md b/docs/TheThingsNetwork.md index 4511a6eb..f953b082 100644 --- a/docs/TheThingsNetwork.md +++ b/docs/TheThingsNetwork.md @@ -38,6 +38,16 @@ void hardReset(uint8_t resetPin); - `uint8_t resetPin`: The output pin that is connected to the module's reset pin. The output pin should be configured as output and set to high by the user. +## Method: `macReset` + +Resets the LoRaWAN stack and initializes it with the parameters for the selected band. + +```c +void macReset() +``` + +Note that, for `SAMR34`-based devices, where this command requires a `` parameter, it will use the configured FP in the initialization of the TTN object (e.g. `TTN_FP_US915`). + ## Method: `getHardwareEui` Gets the unique hardware EUI, often used as the DevEUI. @@ -82,6 +92,8 @@ RX Delay 1: 1000 RX Delay 2: 2000 ``` +Note that for `SAMR34`-based boards, it will not print out the `Battery` parameter, since `sys get vdd` is not implemented in the RN parser firmware for these modems. + See the [DeviceInfo](https://github.com/TheThingsNetwork/arduino-device-lib/blob/master/examples/DeviceInfo/DeviceInfo.ino) example. ## Method: `onMessage` @@ -122,14 +134,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 reset_first); +bool personalize(const char *devAddr, const char *nwkSKey, const char *appSKey, bool resetFirst); 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`. +- `bool resetFirst`: Soft reset the module before performing any other action. Only performed on RN2xx3 modules. Default is `true`. Returns `true` or `false` depending on whether the activation was successful. @@ -201,12 +213,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 reset_first); +bool provision(const char *appEui, const char *appKey, bool resetFirst); ``` - `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`. +- `bool resetFirst`: Soft reset the module before performing any other action. Only performed on RN2xx3 modules. Default is `true`. ## Method: `sleep` @@ -216,16 +228,21 @@ Sleep the LoRa module for a specified number of milliseconds. void sleep(unsigned long mseconds); ``` -- `unsigned long mseconds`: number of milliseconds to sleep. +- `unsigned long mseconds`: number of milliseconds to sleep. Must be >= 100 ms for `RN2xx3` modems, >= 1000 ms for `SAMR34`-based boards. + +Note that, for `SAMR34`-based boards, this command will send `sys sleep standby ` to the modem. For all other `RN2xx3` modems, it will only send `sys sleep `. ## Method: `wake` Wake up the LoRa module from sleep before the expiration of the defined time. ```c -void wake(); +void wake(uint8_t interruptPin); ``` +- `uint8_t interruptPin`: Only required for `SAMR34`-based boards. Will not be used for `RN2XX3` modems, as these are woken up by a call to `autoBaud()`. Default value is `3`. + - On `SAMR34`-based boards, this pin must be set to `OUTPUT` and `HIGH` by the user, and provided in the call to `wake(interruptPin)`. More information on how this works available [here](https://github.com/MicrochipTech/atsamr34_lorawan_rn_parser/blob/master/02_command_guide/README.md#sys-sleep-mode-length). + ## Method: `linkCheck` Sets the time interval for the link check process to be triggered. The next uplink will include a Link Check Request MAC command when the interval expires. This method should be called after joining has been completed. @@ -449,10 +466,10 @@ bool setRX1Delay(uint16_t delay); 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 checkValidModuleConnected(bool autoBaudFirst); ``` -- `bool autobaud_first`: Perform a call to `autoBaud()` before checking connection. Default is `false`. +- `bool autoBaudFirst`: Perform a call to `autoBaud()` before checking connection. Default is `false`. Returns: @@ -462,9 +479,31 @@ Returns: * `RN2483A` * `RN2903` * `RN2903AS` + * `SAMR34` (or boards based on this device) * `true` if the module responded (i.e. `needsHardReset` is `false`) and is valid (supported). + * Also sets the `modemType` attribute to either `TTN_MODEM_TYPE_RN` (for all `RN2xx3` devices) or `TTN_MODEM_TYPE_SAMR34`, depending on the detected modem. + +See the [DeviceInfo](https://github.com/TheThingsNetwork/arduino-device-lib/blob/master/examples/DeviceInfo/DeviceInfo.ino) example. + +## Method: `setModemType` -See the [CheckModule](https://github.com/TheThingsNetwork/arduino-device-lib/blob/master/examples/CheckModule/CheckModule.ino) example. +Sets the `modemType` attribute. It is best to call `checkValidModuleConnected()`, as this automatically sets the modem type depending on the modem's response. + +```c +void setModemType(ttn_modem_type_t modemType); +``` + +- `ttn_modem_type_t modemType`: The modem type. Can be `TTN_MODEM_TYPE_RN` (for all `RN2xx3` devices) or `TTN_MODEM_TYPE_SAMR34`. + +## Method: `getModemType` + +Returns the `modemType` attribute. Can be used to check what modem is connected (make sure to call `checkValidModuleConnected()` before to automatically set `modemType`). + +```c +ttn_modem_type_t getModemType(); +``` + +See the [DeviceInfo](https://github.com/TheThingsNetwork/arduino-device-lib/blob/master/examples/DeviceInfo/DeviceInfo.ino) example. # Additional for statistics diff --git a/examples/CheckModule/CheckModule.ino b/examples/CheckModule/CheckModule.ino deleted file mode 100644 index c67a6fc1..00000000 --- a/examples/CheckModule/CheckModule.ino +++ /dev/null @@ -1,73 +0,0 @@ -#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/examples/DeviceInfo/DeviceInfo.ino b/examples/DeviceInfo/DeviceInfo.ino index 5cc525ac..fd0e2b09 100644 --- a/examples/DeviceInfo/DeviceInfo.ino +++ b/examples/DeviceInfo/DeviceInfo.ino @@ -10,8 +10,38 @@ TheThingsNetwork ttn(loraSerial, debugSerial, freqPlan); void setup() { + // For SAMR34, configure baudrate to the speed set in RN parser firmware (default is 115200) loraSerial.begin(57600); debugSerial.begin(9600); + + // RN2XX3/SAMR34 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(1000); + // 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, SAMR34 + } + while(true) // stop code execution + { + ; + } + } + // init LoRaWAN stack (only necessary in SAMR34) + if(ttn.getModemType() == TTN_MODEM_TYPE_SAMR34) + ttn.macReset(); } void loop() diff --git a/examples/Receive/Receive.ino b/examples/Receive/Receive.ino index 3dd95ed2..da191b45 100644 --- a/examples/Receive/Receive.ino +++ b/examples/Receive/Receive.ino @@ -14,6 +14,7 @@ TheThingsNetwork ttn(loraSerial, debugSerial, freqPlan); void setup() { + // For SAMR34, configure baudrate to the speed set in RN parser firmware (default is 115200) loraSerial.begin(57600); debugSerial.begin(9600); @@ -21,6 +22,15 @@ void setup() while (!debugSerial && millis() < 10000) ; + pinMode(12, OUTPUT); // RN2XX3/SAMR34 reset pin connected to Arduino pin 12 + digitalWrite(12, HIGH); + debugSerial.println("-- CHECK COMM"); + ttn.resetHard(12); // hard-reset the module + delay(1000); // wait for module startup + ttn.checkValidModuleConnected(true); // check a valid module is connected (RN2xx3 or SAMR34) + if(ttn.getModemType() == TTN_MODEM_TYPE_SAMR34) // if SAMR34, init LoRaWAN stack + ttn.macReset(); + // Set callback for incoming messages ttn.onMessage(message); diff --git a/examples/SendABP/SendABP.ino b/examples/SendABP/SendABP.ino index f8f22f04..7b0eadd1 100644 --- a/examples/SendABP/SendABP.ino +++ b/examples/SendABP/SendABP.ino @@ -15,6 +15,7 @@ TheThingsNetwork ttn(loraSerial, debugSerial, freqPlan); void setup() { + // For SAMR34, configure baudrate to the speed set in RN parser firmware (default is 115200) loraSerial.begin(57600); debugSerial.begin(9600); @@ -22,6 +23,15 @@ void setup() while (!debugSerial && millis() < 10000) ; + pinMode(12, OUTPUT); // RN2XX3/SAMR34 reset pin connected to Arduino pin 12 + digitalWrite(12, HIGH); + debugSerial.println("-- CHECK COMM"); + ttn.resetHard(12); // hard-reset the module + delay(1000); // wait for module startup + ttn.checkValidModuleConnected(true); // check a valid module is connected (RN2xx3 or SAMR34) + if(ttn.getModemType() == TTN_MODEM_TYPE_SAMR34) // if SAMR34, init LoRaWAN stack + ttn.macReset(); + debugSerial.println("-- PERSONALIZE"); ttn.personalize(devAddr, nwkSKey, appSKey); diff --git a/examples/SendOTAA/SendOTAA.ino b/examples/SendOTAA/SendOTAA.ino index 033874cd..936ef075 100644 --- a/examples/SendOTAA/SendOTAA.ino +++ b/examples/SendOTAA/SendOTAA.ino @@ -14,6 +14,7 @@ TheThingsNetwork ttn(loraSerial, debugSerial, freqPlan); void setup() { + // For SAMR34, configure baudrate to the speed set in RN parser firmware (default is 115200) loraSerial.begin(57600); debugSerial.begin(9600); @@ -21,6 +22,15 @@ void setup() while (!debugSerial && millis() < 10000) ; + pinMode(12, OUTPUT); // RN2XX3/SAMR34 reset pin connected to Arduino pin 12 + digitalWrite(12, HIGH); + debugSerial.println("-- CHECK COMM"); + ttn.resetHard(12); // hard-reset the module + delay(1000); // wait for module startup + ttn.checkValidModuleConnected(true); // check a valid module is connected (RN2xx3 or SAMR34) + if(ttn.getModemType() == TTN_MODEM_TYPE_SAMR34) // if SAMR34, init LoRaWAN stack + ttn.macReset(); + debugSerial.println("-- STATUS"); ttn.showStatus(); diff --git a/src/TheThingsNetwork.cpp b/src/TheThingsNetwork.cpp index d0888e94..ce432d34 100644 --- a/src/TheThingsNetwork.cpp +++ b/src/TheThingsNetwork.cpp @@ -14,7 +14,8 @@ debugStream->print(__VA_ARGS__); \ } -#define TTN_HEX_CHAR_TO_NIBBLE(c) ((c >= 'A') ? (c - 'A' + 0x0A) : (c - '0')) +#define TTN_HEX_CHAR_DETECT_CASE(c) ((c >= 'a') ? (c - 'a' + 0x0A) : (c - 'A' + 0x0A)) +#define TTN_HEX_CHAR_TO_NIBBLE(c) ((c >= 'A') ? (TTN_HEX_CHAR_DETECT_CASE(c)) : (c - '0')) #define TTN_HEX_PAIR_TO_BYTE(h, l) ((TTN_HEX_CHAR_TO_NIBBLE(h) << 4) + TTN_HEX_CHAR_TO_NIBBLE(l)) const char ok[] PROGMEM = "ok"; @@ -28,8 +29,9 @@ const char rn2483[] PROGMEM = "RN2483"; const char rn2483a[] PROGMEM = "RN2483A"; const char rn2903[] PROGMEM = "RN2903"; const char rn2903as[] PROGMEM = "RN2903AS"; +const char samr34[] PROGMEM = "SAMR34"; -const char *const compare_table[] PROGMEM = {ok, on, off, accepted, mac_tx_ok, mac_rx, mac_err, rn2483, rn2483a, rn2903, rn2903as}; +const char *const compare_table[] PROGMEM = {ok, on, off, accepted, mac_tx_ok, mac_rx, mac_err, rn2483, rn2483a, rn2903, rn2903as, samr34}; #define CMP_OK 0 #define CMP_ON 1 @@ -42,6 +44,7 @@ const char *const compare_table[] PROGMEM = {ok, on, off, accepted, mac_tx_ok, m #define CMP_RN2483A 8 #define CMP_RN2903 9 #define CMP_RN2903AS 10 +#define CMP_SAMR34 11 // CMP OK const char busy[] PROGMEM = "busy"; @@ -115,7 +118,7 @@ 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 invalid_module[] PROGMEM = "Invalid module (must be RN2xx3[xx]/SAMR34)."; 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}; @@ -190,8 +193,9 @@ const char sys_get_vdd[] PROGMEM = "vdd"; const char sys_get_hweui[] PROGMEM = "hweui"; const char sys_set_get_nvm[] PROGMEM = "nvm"; const char sys_set_pindig[] PROGMEM = "pindig"; +const char sys_sleep_standby[] PROGMEM = "standby"; -const char *const sys_table[] PROGMEM = {sys_prefix, sys_sleep, sys_reset, sys_erase_fw, sys_factory_rst, sys_set, sys_get, sys_get_ver, sys_get_vdd, sys_get_hweui, sys_set_get_nvm, sys_set_pindig}; +const char *const sys_table[] PROGMEM = {sys_prefix, sys_sleep, sys_reset, sys_erase_fw, sys_factory_rst, sys_set, sys_get, sys_get_ver, sys_get_vdd, sys_get_hweui, sys_set_get_nvm, sys_set_pindig, sys_sleep_standby}; #define SYS_PREFIX 0 #define SYS_SLEEP 1 @@ -205,6 +209,7 @@ const char *const sys_table[] PROGMEM = {sys_prefix, sys_sleep, sys_reset, sys_e #define SYS_GET_HWEUI 9 #define SYS_SET_GET_NVM 10 #define SYS_SET_PINDIG 11 +#define SYS_SLEEP_STANDBY 12 const char mac_prefix[] PROGMEM = "mac"; const char mac_reset[] PROGMEM = "reset"; @@ -230,6 +235,44 @@ const char *const mac_table[] PROGMEM = {mac_prefix, mac_reset, mac_tx, mac_join #define MAC_SET 8 #define MAC_GET 9 +const char mac_reset_868[] PROGMEM = "868"; // EU868 +const char mac_reset_433[] PROGMEM = "433"; // EU433 +const char mac_reset_na915[] PROGMEM = "na915"; // NA915 +const char mac_reset_au915[] PROGMEM = "au915"; // AU915 +const char mac_reset_kr920[] PROGMEM = "kr920"; // KR920 +const char mac_reset_jpn923[] PROGMEM = "jpn923"; // JPN932 +const char mac_reset_brn923[] PROGMEM = "brn923"; // AS923 +const char mac_reset_cmb923[] PROGMEM = "cmb923"; // AS923 +const char mac_reset_ins923[] PROGMEM = "ins923"; // AS923 +const char mac_reset_laos923[] PROGMEM = "laos923"; // AS923 +const char mac_reset_nz923[] PROGMEM = "nz923"; // AS923 +const char mac_reset_sp923[] PROGMEM = "sp923"; // AS923 +const char mac_reset_twn923[] PROGMEM = "twn923"; // AS923 +const char mac_reset_thai923[] PROGMEM = "thai923"; // AS923 +const char mac_reset_vtm923[] PROGMEM = "vtm923"; // AS923 +const char mac_reset_ind865[] PROGMEM = "ind865"; // IND865 + +const char *const mac_reset_table[] PROGMEM = {mac_reset_868, mac_reset_433, mac_reset_na915, mac_reset_au915, mac_reset_kr920, + mac_reset_jpn923, mac_reset_brn923, mac_reset_cmb923, mac_reset_ins923, mac_reset_laos923, mac_reset_nz923, mac_reset_sp923, + mac_reset_twn923, mac_reset_thai923, mac_reset_vtm923, mac_reset_ind865}; + +#define MAC_RESET_868 0 +#define MAC_RESET_433 1 +#define MAC_RESET_NA915 2 +#define MAC_RESET_AU915 3 +#define MAC_RESET_KR920 4 +#define MAC_RESET_JPN923 5 +#define MAC_RESET_BRN923 6 +#define MAC_RESET_CMB923 7 +#define MAC_RESET_INS923 8 +#define MAC_RESET_LAOS923 9 +#define MAC_RESET_NZ923 10 +#define MAC_RESET_SP923 11 +#define MAC_RESET_TWN923 12 +#define MAC_RESET_THAI923 13 +#define MAC_RESET_VTM923 14 +#define MAC_RESET_IND865 15 + const char mac_devaddr[] PROGMEM = "devaddr"; const char mac_deveui[] PROGMEM = "deveui"; const char mac_appeui[] PROGMEM = "appeui"; @@ -254,9 +297,11 @@ const char mac_class[] PROGMEM = "class"; const char mac_status[] PROGMEM = "status"; const char mac_upctr[] PROGMEM = "upctr"; const char mac_dnctr[] PROGMEM = "dnctr"; +const char mac_joineui[] PROGMEM = "joineui"; +const char mac_edclass[] PROGMEM = "edclass"; const char *const mac_options[] PROGMEM = {mac_devaddr, mac_deveui, mac_appeui, mac_nwkskey, mac_appskey, mac_appkey, mac_pwridx, mac_dr, mac_adr, mac_bat, mac_retx, mac_linkchk, mac_rxdelay1, mac_rxdelay2, mac_band, - mac_ar, mac_rx2, mac_ch, mac_gwnb, mac_mrgn, mac_class, mac_status, mac_upctr, mac_dnctr}; + mac_ar, mac_rx2, mac_ch, mac_gwnb, mac_mrgn, mac_class, mac_status, mac_upctr, mac_dnctr, mac_joineui, mac_edclass}; #define MAC_DEVADDR 0 #define MAC_DEVEUI 1 @@ -282,6 +327,8 @@ const char *const mac_options[] PROGMEM = {mac_devaddr, mac_deveui, mac_appeui, #define MAC_STATUS 21 #define MAC_UPCTR 22 #define MAC_DNCTR 23 +#define MAC_JOINEUI 24 +#define MAC_EDCLASS 25 const char mac_join_mode_otaa[] PROGMEM = "otaa"; const char mac_join_mode_abp[] PROGMEM = "abp"; @@ -322,6 +369,7 @@ const char *const mac_tx_table[] PROGMEM = {mac_tx_type_cnf, mac_tx_type_ucnf}; #define SUCCESS_MESSAGE 8 #define CMP_TABLE 9 #define CMP_ERR_TABLE 10 +#define MAC_RESET_TABLE 11 int pgmstrcmp(const char *str1, uint8_t str2Index, uint8_t table = CMP_TABLE) { @@ -383,7 +431,16 @@ TheThingsNetwork::TheThingsNetwork(Stream &modemStream, Stream &debugStream, ttn size_t TheThingsNetwork::getAppEui(char *buffer, size_t size) { - return readResponse(MAC_TABLE, MAC_GET_SET_TABLE, MAC_APPEUI, buffer, size); + // for RN2xx3 + if(getModemType() == TTN_MODEM_TYPE_RN) + { + return readResponse(MAC_TABLE, MAC_GET_SET_TABLE, MAC_APPEUI, buffer, size); + } + // for SAMR34 + else + { + return readResponse(MAC_TABLE, MAC_GET_SET_TABLE, MAC_JOINEUI, buffer, size); + } } size_t TheThingsNetwork::getHardwareEui(char *buffer, size_t size) @@ -645,6 +702,50 @@ void TheThingsNetwork::autoBaud() baudDetermined = true; } +bool TheThingsNetwork::macReset() +{ + clearReadBuffer(); + sendCommand(MAC_TABLE, MAC_PREFIX, true, false); + // only for SAMR34-based boards + if(getModemType() == TTN_MODEM_TYPE_SAMR34) + { + sendCommand(MAC_TABLE, MAC_RESET, true, false); + // check which default FP will be set + switch (fp) + { + case TTN_FP_EU868: + sendCommand(MAC_RESET_TABLE, MAC_RESET_868, false, false); + break; + case TTN_FP_US915: + sendCommand(MAC_RESET_TABLE, MAC_RESET_NA915, false, false); + break; + case TTN_FP_AU915: + sendCommand(MAC_RESET_TABLE, MAC_RESET_AU915, false, false); + break; + case TTN_FP_KR920_923: + sendCommand(MAC_RESET_TABLE, MAC_RESET_KR920, false, false); + break; + case TTN_FP_AS920_923: + case TTN_FP_AS923_925: + sendCommand(MAC_RESET_TABLE, MAC_RESET_BRN923, false, false); // TODO: fix, SAMR34 implements individual FPs for each AS923 country, while TTN only sets a general AS923/AS925 FP + break; + case TTN_FP_IN865_867: + sendCommand(MAC_RESET_TABLE, MAC_RESET_IND865, false, false); + break; + default: + debugPrintMessage(ERR_MESSAGE, ERR_INVALID_FP); + break; + } + } + // for RN2XX3-based boards + else + { + sendCommand(MAC_TABLE, MAC_RESET, false, false); + } + modemStream->write(SEND_MSG); + return waitForOk(); +} + void TheThingsNetwork::reset(bool adr) { // autobaud and send "sys reset" @@ -691,7 +792,9 @@ void TheThingsNetwork::onMessage(void (*cb)(const uint8_t *payload, size_t size, bool TheThingsNetwork::personalize(const char *devAddr, const char *nwkSKey, const char *appSKey, bool resetFirst) { - if(resetFirst) { + // a sys reset before personalize should only be done in RN2xx3 modules + // for SAMR34, best not to do it because a mac reset would be required afterwards + if(resetFirst && getModemType() != TTN_MODEM_TYPE_SAMR34) { reset(adr); } if (strlen(devAddr) != 8 || strlen(appSKey) != 32 || strlen(nwkSKey) != 32) @@ -725,7 +828,9 @@ bool TheThingsNetwork::personalize() bool TheThingsNetwork::provision(const char *appEui, const char *appKey, bool resetFirst) { - if(resetFirst) { + // a sys reset before provision should only be done in RN2xx3 modules + // for SAMR34, best not to do it because a mac reset would be required afterwards + if(resetFirst && getModemType() != TTN_MODEM_TYPE_SAMR34) { reset(adr); } if (strlen(appEui) != 16 || strlen(appKey) != 32) @@ -735,9 +840,17 @@ bool TheThingsNetwork::provision(const char *appEui, const char *appKey, bool re } readResponse(SYS_TABLE, SYS_TABLE, SYS_GET_HWEUI, buffer, sizeof(buffer)); sendMacSet(MAC_DEVEUI, buffer); - sendMacSet(MAC_APPEUI, appEui); + // send 'mac get appeui' on RN modules + if(getModemType() == TTN_MODEM_TYPE_RN) + sendMacSet(MAC_APPEUI, appEui); + // send 'mac get joineui' on SAMR34-based modules + else + sendMacSet(MAC_JOINEUI, appEui); sendMacSet(MAC_APPKEY, appKey); - saveState(); + // 'mac save' is not implemented (and reduntant) in SAMR34 RN parser firmware + // see: https://github.com/MicrochipTech/atsamr34_lorawan_rn_parser/blob/master/02_command_guide/README.md#limitations + if(getModemType() == TTN_MODEM_TYPE_RN) + saveState(); return true; } @@ -780,14 +893,19 @@ bool TheThingsNetwork::setClass(lorawan_class_t p_lw_class) case CLASS_A: { lw_class = p_lw_class; - return sendMacSet(MAC_CLASS, "a"); + // 'mac set class ' command is for RN2xx3 + // 'mac set edclass ' command is for SAMR34 + if(getModemType() == TTN_MODEM_TYPE_RN) + return sendMacSet(MAC_CLASS, "a"); + else + return sendMacSet(MAC_EDCLASS, "a"); } // case CLASS_B: // Not yet supported. Use default case. case CLASS_C: { - bool result = sendMacSet(MAC_CLASS, "c"); + bool result = (getModemType() == TTN_MODEM_TYPE_RN) ? sendMacSet(MAC_CLASS, "c") : sendMacSet(MAC_EDCLASS, "c"); // Older firmware does not support Class C. If setting change fails, keep on using Class A. if(result) lw_class = p_lw_class; return result; @@ -925,9 +1043,13 @@ void TheThingsNetwork::showStatus() { readResponse(SYS_TABLE, SYS_TABLE, SYS_GET_HWEUI, buffer, sizeof(buffer)); debugPrintIndex(SHOW_EUI, buffer); - readResponse(SYS_TABLE, SYS_TABLE, SYS_GET_VDD, buffer, sizeof(buffer)); - debugPrintIndex(SHOW_BATTERY, buffer); - readResponse(MAC_TABLE, MAC_GET_SET_TABLE, MAC_APPEUI, buffer, sizeof(buffer)); + // 'sys get vdd' not implemented in RN parser firmware for SAMR34/WLR089 + if(getModemType() == TTN_MODEM_TYPE_RN) + { + readResponse(SYS_TABLE, SYS_TABLE, SYS_GET_VDD, buffer, sizeof(buffer)); + debugPrintIndex(SHOW_BATTERY, buffer); + } + getAppEui(buffer, sizeof(buffer)); debugPrintIndex(SHOW_APPEUI, buffer); readResponse(MAC_TABLE, MAC_GET_SET_TABLE, MAC_DEVEUI, buffer, sizeof(buffer)); debugPrintIndex(SHOW_DEVEUI, buffer); @@ -939,6 +1061,16 @@ void TheThingsNetwork::showStatus() debugPrintIndex(SHOW_RX_DELAY_2, buffer); } +void TheThingsNetwork::setModemType(ttn_modem_type_t modemType) +{ + this->modemType = modemType; +} + +ttn_modem_type_t TheThingsNetwork::getModemType() +{ + return this->modemType; +} + bool TheThingsNetwork::checkValidModuleConnected(bool autoBaudFirst) { // check if we want to autobaud first @@ -957,12 +1089,19 @@ bool TheThingsNetwork::checkValidModuleConnected(bool autoBaudFirst) // 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) + // check if module is valid (must be RN2483, RN2483A, RN2903, RN2903AS or SAMR34) if(pgmstrcmp(model, CMP_RN2483) == 0 || pgmstrcmp(model, CMP_RN2483A) == 0 || pgmstrcmp(model, CMP_RN2903) == 0 || pgmstrcmp(model, CMP_RN2903AS) == 0) { + setModemType(TTN_MODEM_TYPE_RN); debugPrintMessage(SUCCESS_MESSAGE, SCS_VALID_MODULE); return true; // module responded and is valid (recognized/supported) } + else if(pgmstrcmp(model, CMP_SAMR34) == 0) + { + setModemType(TTN_MODEM_TYPE_SAMR34); + debugPrintMessage(SUCCESS_MESSAGE, SCS_VALID_MODULE); // module responded and is valid (recognized/supported) + return true; + } debugPrintMessage(ERR_MESSAGE, ERR_INVALID_MODULE); return false; // module responded but is invalid (unrecognized/unsupported) } @@ -1338,6 +1477,9 @@ void TheThingsNetwork::sendCommand(uint8_t table, uint8_t index, bool appendSpac case RADIO_TABLE: strcpy_P(command, (char *)pgm_read_word(&(radio_table[index]))); break; + case MAC_RESET_TABLE: + strcpy_P(command, (char *)pgm_read_word(&(mac_reset_table[index]))); + break; default: return; } @@ -1486,7 +1628,8 @@ bool TheThingsNetwork::sendPayload(uint8_t mode, uint8_t port, uint8_t *payload, void TheThingsNetwork::sleep(uint32_t mseconds) { - if (mseconds < 100) + uint16_t minSleepTime = (getModemType() == TTN_MODEM_TYPE_SAMR34) ? 1000 : 100; // min sleep time for SAMR34 is 1000 ms + if (mseconds < minSleepTime) { return; } @@ -1494,6 +1637,11 @@ void TheThingsNetwork::sleep(uint32_t mseconds) debugPrint(F(SENDING)); sendCommand(SYS_TABLE, SYS_PREFIX, true); sendCommand(SYS_TABLE, SYS_SLEEP, true); + // send standby for SAMR34-based boards + if(getModemType() == TTN_MODEM_TYPE_SAMR34) + { + sendCommand(SYS_TABLE, SYS_SLEEP_STANDBY, true); + } sprintf(buffer, "%lu", mseconds); modemStream->write(buffer); @@ -1501,9 +1649,20 @@ void TheThingsNetwork::sleep(uint32_t mseconds) debugPrintLn(buffer); } -void TheThingsNetwork::wake() +void TheThingsNetwork::wake(uint8_t interruptPin) { - autoBaud(); + // only for SAMR34-based boards + if(getModemType() == TTN_MODEM_TYPE_SAMR34) + { + digitalWrite(interruptPin, LOW); + delay(1000); + digitalWrite(interruptPin, HIGH); + } + // for RN2XX3-based boards + else + { + autoBaud(); + } } void TheThingsNetwork::linkCheck(uint16_t seconds) diff --git a/src/TheThingsNetwork.h b/src/TheThingsNetwork.h index ebcc59f7..b0b42f9b 100644 --- a/src/TheThingsNetwork.h +++ b/src/TheThingsNetwork.h @@ -87,6 +87,12 @@ enum ttn_modem_status_t TTN_MODEM_C_RX2 }; +enum ttn_modem_type_t +{ + TTN_MODEM_TYPE_RN, + TTN_MODEM_TYPE_SAMR34 +}; + class TheThingsNetwork { private: @@ -133,11 +139,15 @@ class TheThingsNetwork public: bool needsHardReset = false; + ttn_modem_type_t modemType = TTN_MODEM_TYPE_RN; // assume RN modem, this can be changed using checkValidModuleConnected TheThingsNetwork(Stream &modemStream, Stream &debugStream, ttn_fp_t fp, uint8_t sf = TTN_DEFAULT_SF, uint8_t fsb = TTN_DEFAULT_FSB); + bool macReset(); void reset(bool adr = true); void resetHard(uint8_t resetPin); void showStatus(); + void setModemType(ttn_modem_type_t modemType); + ttn_modem_type_t getModemType(); size_t getHardwareEui(char *buffer, size_t size); size_t getAppEui(char *buffer, size_t size); size_t getVersion(char *buffer, size_t size); @@ -166,7 +176,7 @@ class TheThingsNetwork ttn_response_t sendBytes(const uint8_t *payload, size_t length, port_t port = 1, bool confirm = false, uint8_t sf = 0); ttn_response_t poll(port_t port = 1, bool confirm = false, bool modem_only = false); void sleep(uint32_t mseconds); - void wake(); + void wake(uint8_t interruptPin = 3); void saveState(); void linkCheck(uint16_t seconds); uint8_t getLinkCheckGateways();