Skip to content
12 changes: 11 additions & 1 deletion README.md
Original file line number Diff line number Diff line change
Expand Up @@ -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

Expand All @@ -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**.
27 changes: 23 additions & 4 deletions docs/TheThingsNetwork.md
Original file line number Diff line number Diff line change
Expand Up @@ -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 `<band>` 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.
Expand Down Expand Up @@ -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` and `AppEUI` parameters, since the `sys get vdd` and `mac get appeui` commands are not currently 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`
Expand Down Expand Up @@ -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 <mseconds>` to the modem. For all other `RN2xx3` modems, it will only send `sys sleep <mseconds>`.

## 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.
Expand Down Expand Up @@ -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:

Expand All @@ -462,7 +479,9 @@ 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 [CheckModule](https://github.com/TheThingsNetwork/arduino-device-lib/blob/master/examples/CheckModule/CheckModule.ino) example.

Expand Down
143 changes: 131 additions & 12 deletions src/TheThingsNetwork.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -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";
Expand All @@ -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
Expand All @@ -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";
Expand Down Expand Up @@ -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};

Expand Down Expand Up @@ -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
Expand All @@ -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";
Expand All @@ -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";
Expand Down Expand Up @@ -322,6 +365,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)
{
Expand Down Expand Up @@ -645,6 +689,50 @@ void TheThingsNetwork::autoBaud()
baudDetermined = true;
}

bool TheThingsNetwork::macReset()
{
clearReadBuffer();
sendCommand(MAC_TABLE, MAC_PREFIX, true, false);
// only for SAMR34-based boards
if(this->modemType == 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"
Expand Down Expand Up @@ -925,10 +1013,14 @@ 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));
debugPrintIndex(SHOW_APPEUI, buffer);
// these commands are not implemented for RN parser firmware for SAMR34/WLR089
if(this->modemType != TTN_MODEM_TYPE_SAMR34)
{
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));
debugPrintIndex(SHOW_APPEUI, buffer);
}
readResponse(MAC_TABLE, MAC_GET_SET_TABLE, MAC_DEVEUI, buffer, sizeof(buffer));
debugPrintIndex(SHOW_DEVEUI, buffer);
readResponse(MAC_TABLE, MAC_GET_SET_TABLE, MAC_DR, buffer, sizeof(buffer));
Expand Down Expand Up @@ -957,12 +1049,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)
{
this->modemType = 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)
{
this->modemType = 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)
}
Expand Down Expand Up @@ -1338,6 +1437,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;
}
Expand Down Expand Up @@ -1486,24 +1588,41 @@ bool TheThingsNetwork::sendPayload(uint8_t mode, uint8_t port, uint8_t *payload,

void TheThingsNetwork::sleep(uint32_t mseconds)
{
if (mseconds < 100)
uint16_t minSleepTime = (this->modemType == TTN_MODEM_TYPE_SAMR34) ? 1000 : 100; // min sleep time for SAMR34 is 1000 ms
if (mseconds < minSleepTime)
{
return;
}

debugPrint(F(SENDING));
sendCommand(SYS_TABLE, SYS_PREFIX, true);
sendCommand(SYS_TABLE, SYS_SLEEP, true);
// send standby for SAMR34-based boards
if(this->modemType == TTN_MODEM_TYPE_SAMR34)
{
sendCommand(SYS_TABLE, SYS_SLEEP_STANDBY, true);
}

sprintf(buffer, "%lu", mseconds);
modemStream->write(buffer);
modemStream->write(SEND_MSG);
debugPrintLn(buffer);
}

void TheThingsNetwork::wake()
void TheThingsNetwork::wake(uint8_t interruptPin)
{
autoBaud();
// only for SAMR34-based boards
if(this->modemType == TTN_MODEM_TYPE_SAMR34)
{
digitalWrite(interruptPin, LOW);
delay(1000);
digitalWrite(interruptPin, HIGH);
}
// for RN2XX3-based boards
else
{
autoBaud();
}
}

void TheThingsNetwork::linkCheck(uint16_t seconds)
Expand Down
10 changes: 9 additions & 1 deletion src/TheThingsNetwork.h
Original file line number Diff line number Diff line change
Expand Up @@ -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:
Expand Down Expand Up @@ -133,8 +139,10 @@ 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();
Expand Down Expand Up @@ -166,7 +174,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();
Expand Down