The TheThingsNetwork
class enables Arduino devices with supported LoRaWAN modules to communicate via The Things Network. Currently supported LoRaWAN modules are the Microchip RN2483 and the RN2903.
Include and instantiate the TheThingsNetwork class. The constructor initializes the library with the Streams it should communicate with. It also sets the value of the spreading factor, the frequency plan, and the frequency sub-band.
#include <TheThingsNetwork.h>
TheThingsNetwork ttn(Stream& modemStream, Stream& debugStream, fp_ttn_t fp, uint8_t sf = 7, uint8_t fsb = 2);
Stream& modemStream
: Stream for the LoRa modem (see notes).Stream& debugStream
: Stream to write debug logs to (see notes).fp_ttn_fp fp
: The frequency plan:TTN_FP_EU868
,TTN_FP_US915
,TTN_FP_AS920_923
,TTN_FP_AS923_925
orTTN_FP_KR920_923
depending on the region you deploy in. See the wiki.uint8_t sf = 7
: Optional custom spreading factor. Can be7
to10
forTTN_FP_US915
and7
to12
for other frequency plans. Defaults to7
.uint8_t fsb = 2
: Optional custom frequency subband. Can be1
to8
. Defaults to2
(for US915).
Performs a software reset of the RN module. This does not clear saved state, e.g. provisioned keys.
void reset(bool adr);
bool adr
: Enable/disable Adaptive Data Rate.
Performs a hardware reset of the RN module. Input parameter is the pin to which the reset pin from the module is connected. This does clear saved state, e.g., provisioned keys.
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.
Gets the unique hardware EUI, often used as the DevEUI.
size_t getHardwareEui(char *buffer, size_t size);
Gets the provisioned AppEUI. The AppEUI is set using provision()
or join()
.
size_t getAppEui(char *buffer, size_t size);
Gets the response from a sys get ver
command (i.e. the hardware model, the the software version, etc.).
size_t getVersion(char *buffer, size_t size);
Writes information about the device and LoRa module to debugStream
.
void showStatus();
Will write something like:
EUI: 0004A30B001B7AD2
Battery: 3223
AppEUI: 70B3D57EF000001C
DevEUI: 0004A30B001B7AD2
Data Rate: 5
RX Delay 1: 1000
RX Delay 2: 2000
See the DeviceInfo example.
Sets a function that will be called to process incoming messages. You'll want to do this in your setup()
function and then define a void (*cb)(const byte* payload, size_t length, port_t port)
function somewhere else in your sketch.
void onMessage(void (*cb)(const byte* payload, size_t length, port_t port));
const byte* payload
: Bytes received.size_t length
: Number of bytes.port_t port
: The port addressed.
See the Receive example.
Activate the device via OTAA (default).
bool join(const char *appEui, const char *appKey, int8_t retries = -1, uint32_t retryDelay = 10000, lorawan_class = CLASS_A);
bool join(int8_t retries = -1, uint32_t retryDelay = 10000);
const char *appEui
: Application EUI the device is registered to.const char *appKey
: Application Key assigned to the device.int8_t retries = -1
: Number of times to retry after failed or unconfirmed join. Defaults to-1
, which means infinite.uint32_t retryDelay = 10000
: Delay in ms between attempts. Defaults to 10 seconds.lorawan_class = CLASS_A
: The LoRaWAN class to use for downlink message reception.
Returns true
or false
depending on whether it received confirmation that the activation was successful before the maximum number of attempts.
Call the method without the first two arguments if the device's LoRa module comes with pre-flashed values.
Activate the device via ABP.
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 istrue
.
Returns true
or false
depending on whether the activation was successful.
Call the method with no arguments if the device's LoRa module comes with pre-flashed values.
See the SendABP example.
Change the downlink receive LoRaWAN Class. Class C is only supported in firmware version 1.0.5 and up. For other firmware versions, this method will have no effect.
bool setClass(lorawan_class p_lw_class);
lorawan_class p_lw_class
: The LoRaWAN class. EitherCLASS_A
orCLASS_C
.
Returns true
if the change was successful, or false
if not successful.
The receive window only opens after a transmit. Therefore Class C receive will only start after calling sendBytes()
.
See the ReceiveClassC example.
Send a message to the application using raw bytes.
ttn_response_t sendBytes(const byte* payload, size_t length, port_t port = 1, bool confirm = false, uint8_t sf = 0);
const byte* payload
: Bytes to send.size_t length
: The number of bytes. Usesizeof(payload)
to get it.port_t port = 1
: The port to address. Defaults to1
.bool confirm = false
: Whether to ask for confirmation—defaults tofalse
. If confirmation fails, the method will return the error codeTTN_ERROR_UNEXPECTED_RESPONSE
.uint8_t sf = 0
: Override the spreading factor (SF). If the default value0
is passed, the SF is not changed from the constructor or previous value.
Returns a success or error code and logs the related error message:
TTN_ERROR_SEND_COMMAND_FAILED
: Send command failed.TTN_SUCCESSFUL_TRANSMISSION
: Successful transmission.TTN_SUCCESSFUL_RECEIVE
: Successful transmission. Received <N> bytesTTN_UNSUCCESSFUL_RECEIVE
: if we sent a confirmed message but did not get an acknowledgmentTTN_ERROR_UNEXPECTED_RESPONSE
: Unexpected response: <Response>
See the SendOTAA example.
Calls sendBytes()
with { 0x00 }
as payload to poll for incoming messages.
int8_t poll(port_t port = 1, bool confirm = false);
port_t port = 1
: The port to address. Defaults to1
.bool confirm = false
: Whether to ask for confirmation.bool modem_only = false
: (only class A) Whether to avoid sending an empty frame and only listen to the modem's incoming message.
Returns the result of sendBytes()
, with one difference:
TTN_UNSUCCESSFUL_RECEIVE
: In class C or class Amodem_only
poll, we only listened, and there was no incoming message.
The modem_only
option for poll may be useful if the sending procedure did not terminate yet, e.g., we sent a confirmed message and did not get a response. In such a case, the modem will independently retry. With modem_only
we thus just listen to the modem output to verify the status of the further attempts.
See the Receive example.
Sets the information needed to activate the device via OTAA, without actually activating. Call join() without the first 2 arguments to activate.
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 istrue
.
Sleep the LoRa module for a specified number of milliseconds.
void sleep(unsigned long mseconds);
unsigned long mseconds
: number of milliseconds to sleep.
Wake up the LoRa module from sleep before the expiration of the defined time.
void wake();
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.
void linkCheck(uint16_t seconds);
uint16_t seconds
: the time interval in seconds. A value of 0 will disable the link check process.
Gets the number of gateways that successfully received the last Link Check Request frame.
uint8_t getLinkCheckGateways();
Gets the demodulation margin as received in the last Link Check Answer frame.
uint8_t getLinkCheckMargin();
Returns the voltage in millivolt (mV) measured by the RN2xxx LoRa module. It's for information only since we don't know how it's calculated but looks accurate.
uint16_t getVDD();
Returns the status of the RN2xxx modem's state machine in one of the nine possible states of ttn_modem_status_t
. Unfortunately, due to a firmware bug, this does not work for RN2493 modems with firmware < 1.05. If unsuccessful, the method returns TTN_MODEM_READ_ERROR
.
enum ttn_modem_status_t getStatus()
Possible return codes are:
TTN_MODEM_READ_ERR
could not read out the statusTTN_MODEM_IDLE
modem idle, ready for next txTTN_MODEM_TX
modem transmittingTTN_MODEM_BEFORE_RX
waiting time between tx and rx1 windowsTTN_MODEM_RX1
RX1 window openTTN_MODEM_BEFORE_RX2
waiting time between the two rx windowsTTN_MODEM_RETRY_DELAY
waiting before retry againTTN_MODEM_APB_DELAY
APB join delayTTN_MODEM_C_RX1
RX1 window open in class CTTN_MODEM_C_RX2
RX2 window open in class C
Returns the last error code encountered by the RN2xxx modem. The error code may be one of the twelve possible errors of ttn_response_code_t
. Call this method after an unsuccessful execution.
ttn_response_code_t getLastError();
For example, suppose you perform a send via sendbytes
and get a TTN_ERROR_SEND_COMMAND_FAILED
. In that case, you can call this method and get a TTN_ERR_BUSY
, which means the modem already has a message to send in the buffer.
Possible error codes are:
TTN_OK
nothing actually went wrong.TTN_ERROR_BUSY
busy sending a messageTTN_ERROR_FRAME_COUNTER_ERROR
if the frame counter rolled overTTN_ERROR_INVALID_CLASS
selected LoRaWan class is invalidTTN_ERROR_INVALID_LENGTH
data length is not available for the current configurationTTN_ERROR_INVALID_PARAMETER
invalid parameter specifiedTTN_ERROR_NO_KEY_INTITIALIZED
network keys not initializedTTN_ERROR_MAC_PAUSE
mac level has been pausedTTN_ERROR_NO_KEY_MULTICAST
multicast keys not setTTN_ERROR_NO_FREE_CHANNEL
all channels exhausted their duty cycle, so we can not sendTTN_ERROR_NOT_JOINED
modem did not join a networkTTN_ERROR_SILENT
if the module is in a Silent Immediately stateTTN_ERROR_ERR
other unspecified error
Returns the next used uplink frame counter. In LoRaWan, this counter must match on both sides, server, and end-node, for a transmission to be successful. Please note that although the counter is 32bit, the frame itself contains only the lowest 16bits of the message. Nonetheless, the message is encrypted using all 32bits of the FCU in the key. Therefore, it must be up to date and in sync. If the readout is unsuccessful, the method returns 0. However, the first frame after a reset is also set to 0.
uint32_t getFCU();
Sets the FCU as of above. The method returns true if successful.
bool setFCU(uint32_t fcu);
Returns the next expected downlink frame counter. In LoRaWan, this counter must match on both sides, server, and end-node, for a transmission to be successful. Please note that although the counter is 32bit, the frame itself contains only the lowest 16bits of the message. Nonetheless, the message is encrypted using all 32bits of the FCD in the key. Therefore, it must be up to date and in sync. If the readout is unsuccessful, the method returns 0. However, the first frame after a reset is also sent with frame number 0.
uint32_t getFCD();
Sets the FCD as of above. The method returns true if successful.
bool setFCD(uint32_t fcd);
Returns the data rate that the modem will use for the next uplink frame. If the readout is unsuccessful, the method returns -1.
int8_t getDR();
Sets the data rate to use for the next uplink frame; see getDR
. This is mainly useful for operating with adaptive data rate (ADR) in off
; see setADR
. If ADR is on, it will set the DR for the next uplink, but the value for the successive transmissions may change according to the ADR protocol. The range of valid DR values depends on the frequency plan. The method returns true if successful.
bool setDR(uint8_t dr);
Returns the power index that the LoRa modem will use for the next uplink frame. If the readout is unsuccessful, the method returns -1.
int8_t getPowerIndex();
Sets the power index to use for the next uplink frame; see getPowerIndex
. This is mainly useful for operating with adaptive data rate (ADR) in off
; see setADR
. If ADR is on, it will set the power index for the next uplink. Still, the value for the following transmissions may change according to the ADR protocol. The range of valid values depends on modem hardware and the frequency plan. The method returns true if successful.
bool setPowerIndex(uint8_t index);
The TTN module can store multiple channel configurations. With this method, you can configure a logical channel of the modem by defining frequency in Hz
, minimum and maximum usable data rates for the channel. Data rates depend on the used frequency plan and must be between 0 and 15. The method returns true if a configuration is successful.
bool setChannel(uint8_t channel, uint32_t frequency = 0l, uint8_t dr_min = 255, uint8_t dr_max = 255);
Alternatively, you can only re-set the data rates for the channel by leaving (omitting) frequency to 0.
setChannel(2, dr_min = 0, dr_max = 4);
Or, you can only re-set the frequency for the channel by leaving (omitting) dr_* to 255.
setChannel(2, 863500000);
However, the whole configuration must have been set at least once to be successful in both cases.
In some plans, the TTN module stores a common RX2 downlink channel, which is used for RX2 window communication. With this method, you can configure this channel by defining frequency in Hz
and its data rates. The valid data rates depend on the used frequency plan. The method returns true if a configuration is successful.
bool setRx2Channel(uint32_t frequency, uint8_t dr);
The TTN module can store multiple channel configurations. This method returns true if the channel is enabled for transmission and/or receiving.
bool getChannelStatus (uint8_t channel);
Sets the channel status of the selected channel number; see getChannelStatus
. If set to true and the channel is configured, the modem may use the channel for transmission. The method returns true if successful.
bool setChannelStatus (uint8_t channel, bool status);
Sets the channel maximum allowed duty cycle of a selected channel number in %
. Depending on regional regulations, the duty cycle may limit the maximum frequency use to a percentage in time. With this method, we set the ratio of transmission time for the selected channel, e.g., 1%
with 1 second transmission time means the radio has to pause for 99 seconds on this channel before retransmitting. The method returns true if successful.
bool setChannelDCycle (uint8_t channel, float duty_cycle);
We can manually disable or enable the adaptive data rate (ADR) algorithm on the device with this method. It independently adapts the transmission data rate (and power) in runtime if active. If disabled, we must select power index and data rate manually, see setDR
and setPowerIndex
. The method returns true if successful.
bool setADR(bool adr);
When transmitting in LoRaWan, we usually operate on a TX window and two RX windows. This setting defines how long the modem has to wait before opening the channel for listening for RX window 1. RX window two is hardcoded to be one second after RX1. This parameter is configurable between 1 and 15 seconds in ms
. By default, we have a delay of 1000 ms, and you should not change this if you do not change the network server accordingly. Longer window delays may be helpful with slow, unresponsive Internet connectivity. The method returns true if successful.
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.
bool checkValidModuleConnected(bool autobaud_first);
bool autobaud_first
: Perform a call toautoBaud()
before checking connection. Default isfalse
.
Returns:
false
if no response was received (i.e.needsHardReset
istrue
)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
isfalse
) and is valid (supported).
See the CheckModule example.
Returns the relative signal strength of the last received frame in dBm
. Typical values are between -70dBm (very strong) and -136dBm (weak/sensitivity threshold). If the readout is unsuccessful, the method returns -255.
int16_t getRSSI();
Returns the signal to noise ratio of the last received frame in dB
. Note that LoRa also allows negative values for SNR. Typical values are between +20dB (very clear) and -20dB (weak/sensitivity threshold). If the readout is unsuccessful, the method returns -128.
int8_t getSNR();
Returns the channel frequency of the last received frame in Hz
. If the readout is unsuccessful, the method returns 0.
uint32_t getFrequency();
Returns the LoRa modem's internal watch-dog timer in ms
. If this time is exceeded, the modem resets. If instead, the readout is unsuccessful, the method returns 0.
uint32_t getWatchDogTimer();
Returns the used channel bandwidth of the last received frame in kHz
. If the readout is unsuccessful, the method returns 0.
uint8_t getBW();
Returns the used code rate of the last received frame in 4/x
. If the readout is unsuccessful, the method returns 0.
uint8_t getCR();
Returns the power of the last incoming transmission in dBm
. If the readout is unsuccessful, the method returns -128.
int8_t getPower();