User-friendly library for using the Arduino LMIC library with The Things Network and LoRaWAN™ networks.
Contents:
- Overview
- Required libraries
- How To Use
- APIs
- Starting operation
- Poll and update the LMIC
- Reset the LMIC
- Shut down the LMIC
- Register an event listener
- Send an event to all listeners
- Manipulate the Debug Mask
- Output a formatted log message
- Get the configured LoRaWAN region, country code, and network name
- Set link-check mode
- Send a buffer
- Register a Receive-Buffer Callback
- Get DevEUI, AppEUI, AppKey
- Test provisioning state
- Release History
- Notes
The arduino-lorawan library provides a structured way of using the arduino-lmic library to send sensor data over The Things Network or a similar LoRaWAN-based data network.
This version targets v2.3.0 or later of the arduino-lmic library.
It targets devices that are reasonably capable, consisting of:
- A 32-bit processor (ARM, XTENSA, etc.);
- An SX1276-based LoRa radio; and
- An Arduino run-time environment.
The reference target for SAMD21G deployments is Adafruit Feather M0 LoRa. In addition to the basic Feather M0 LoRa, other products are supported. The MCCI Catena 4450, Catena 4460, and Catena 4470 products are upward compatible with the Feather M0 LoRa and therefore also can be used with this library.
The reference target for STM32L0 deployments is the Murata CMWX1ZZABZ-078, as deployed in the MCCI Catena 4610, Catena 4612, Catena 4801, Catena 4617, Catena 4618, Catena 4630 etc., with the MCCI Arduino board support package. Note that for proper TCXO control, you must use v2.3.0 or later of the arduino-lmic library.
arduino-lorawan attempts to solve three problems.
- It separates network maintenance code from your application.
- It separates the common logic of your sensor app from the details about each individual device, allowing you to have a common source base that's used for all sensors.
- It provides a simple framework for doing low-power programming.
- It includes a framework for managing non-volatile storage (particularly FRAM) in a stable and atomic way. (However, you have to provide the non-volatile storage handling).
The resulting programming environment is just a little more complicated than basic Arduino, but we intend that it will be almost as easy to use for prototyping, and not too tedious to use when moving to small pilot runs.
MCCI tends to use the this library wrapped by the Catena Arduino Platform library, but it can be used stand-alone as described below.
Library | Version | Comments |
---|---|---|
arduino-lmic | 2.3.0 | Earlier versions will fail to compile due to missing arduino_lmic_hal_boards.h and arduino_lmic_hal_configuration.h |
Catena-mcciadk | 0.1.1 | Needed for miscellaneous definitions |
The classes in this library are normally intended to be used inside a class that overrides one or more of the virtual methods.
The stand-alone use pattern is as follows, targeting The Things Network V2. This code can be found in the example/simple_feather/simple.ino
sketch. Note that this isn't complete, as you have to add code in the indicated places.
#include <Arduino_LoRaWAN_ttn.h>
class cMyLoRaWAN : public Arduino_LoRaWAN_ttn {
public:
cMyLoRaWAN() {};
protected:
// you'll need to provide implementations for each of the following.
virtual bool GetOtaaProvisioningInfo(Arduino_LoRaWAN::OtaaProvisioningInfo*) override;
virtual void NetSaveFCntUp(uint32_t uFCntUp) override;
virtual void NetSaveFCntDown(uint32_t uFCntDown) override;
virtual void NetSaveSessionInfo(const SessionInfo &Info, const uint8_t *pExtraInfo, size_t nExtraInfo) override;
};
// set up the data structures.
cMyLoRaWAN myLoRaWAN {};
void setup() {
myLoRaWAN.begin();
}
void loop() {
myLoRaWAN.loop();
}
// this method is called when the LMIC needs OTAA info.
// return false to indicate "no provisioning", otherwise
// fill in the data and return true.
bool
cMyLoRaWAN::GetOtaaProvisioningInfo(
OtaaProvisioningInfo *pInfo
) {
return false;
}
void
cMyLoRaWAN::NetSaveFCntDown(uint32_t uFCntDown) {
// save uFcntDown somwwhere
}
void
cMyLoRaWAN::NetSaveFCntUp(uint32_t uFCntUp) {
// save uFCntUp somewhere
}
void
cMyLoRaWAN::NetSaveSessionInfo(
const SessionInfo &Info,
const uint8_t *pExtraInfo,
size_t nExtraInfo
) {
// save Info somewhere.
}
If the LMIC library doesn't have a pin-map for your board, and you don't want to add one to the library, you can supply your own. Simply prepare a pin-map, and pass it to the begin()
method.
A full example can be found in the example/simple_feather/simple_feather.ino
sketch, but here are the relevant differences.
// The pin map. This form is convenient if the LMIC library
// doesn't support your board and you don't want to add the
// configuration to the library (perhaps you're just testing).
// This pinmap matches the FeatherM0 LoRa. See the arduino-lmic
// docs for more info on how to set this up.
const cMyLoRaWAN::lmic_pinmap myPinMap = {
.nss = 8,
.rxtx = cMyLoRaWAN::lmic_pinmap::LMIC_UNUSED_PIN,
.rst = 4,
.dio = { 3, 6, cMyLoRaWAN::lmic_pinmap::LMIC_UNUSED_PIN },
.rxtx_rx_active = 0,
.rssi_cal = 0,
.spi_freq = 8000000,
};
void setup() {
// simply pass the pinmap to the begin() method.
myLoRaWAN.begin(myPinMap);
}
-
Define a class to define your concrete LoRaWAN instance. This is how you'll provide the out-calls, by defining virtual method functions. We'll call this
cMyLoRaWAN
, beginning withc
to indicate that this is a class name. -
Create an instance of your class. We'll call this
myLoRaWAN
. -
Determine whether you need a pin-map, or whether your arduino-lmic library already directly supports your board. If directly supported, you can call
myLoRaWAN.begin()
without any arguments, and the LMIC default pin-map for your board will be used. Otherwise, you can allocate a pin-map. If you name itmyPinmap
, you can callmyLoRaWAN.begin(myPinmap);
, as in the example. -
Implement the required methods.
The begin()
APIs are used to start the LMIC engine. There are three forms. See "Details on use," above.
void Arduino_LoRaWAN::loop(void);
This method must be called periodically in order to keep the LMIC operating. For class-A devices, this need only be called while actively pushing an uplink, or while a task is pending in the LMIC's time-driven queue.
void Arduino_LoRaWAN::reset(void);
Cancel any pending operations and reinitialize all internal state.
void Arduino_LoRaWAN::Shutdown(void);
Shut down the LMIC. Any attempt to transmit while shut down will fail.
typedef void ARDUINO_LORAWAN_EVENT_FN(
void *pUserData,
uint32_t eventCode
);
bool Arduino_LoRaWAN::RegisterListener(
ARDUINO_LORAWAN_EVENT_FN *pEvent,
void *pUserData
);
Clients may register event functions using RegisterListener
. The event function is called on each event from the LMIC. Up to four listeners may be registered. There's no way to cancel a registration.
void Arduino_LoRaWAN::DispatchEvent(uint32_t eventCode);
This routine causes each registered event listener to be called with the specified event code. This is mostly for internal use, and may become protected
in future releases.
To be documented. This feature is currently only in the header files and not used.
To be documented. This feature is currently only used by the macro ARDUINO_LORAWAN_PRINTF
, which in turn is only used in one place.
const char *Arduino_LoRaWAN::GetRegionString(char *pBuf, size_t size) const;
Set the buffer at *pBuf
to the configured network region. At most size-1
characters will be copied to the target buffer.
The result is guaranteed to be non-NULL, and is a pointer to a string. If pBuf
is nullptr
or size
is zero, then the result is a constant string "<<null>>"
. Otherwise, the result is pBuf
. Since the result might be an immutable string, the result is declared as const char *
. The result is guaranteed to be a well-formed string. If the buffer is too small to contain the region string, the region string will be truncated to the right as needed.
Arduino_LoRaWAN::Region Arduino_LoRaWAN::GetRegion() const;
Return the region code. Arduino_LoRaWAN::Region
contains the following values: unknown
, eu868
, us915
, cn783
, eu433
, au921
, cn490
, as923
, kr920
, and in866
.
Arduino_LoRaWAN::CountryCode Arduino_LoRaWAN::GetCountryCode() const;
Return the country code, which might be relevant to the region definition. The defined values are none
(in case there are no relevant country-specific variations), and JP
(which means we must follow Japan listen-before-talk rules).
const char *GetNetworkName() const;
Return the network name. Current values include "The Things Network"
and "machineQ"
.
bool Arduino_LoRaWAN::SetLinkCheckMode(
bool fEnable
);
Enable (or disable) link check mode, based on the value of fEnabled
. If disabled, the device will not try to maintain a connection to the network. If enabled, the device watches for downlinks. If no downlink is seen for 64 messages, the device starts setting the network ADR request in uplinks. If there's no response after 32 messages, the device reduces the data rate and increases power, and continues this loop until there are no more data rates to try. At that point, the device will start inserting join attempts after every uplink without a downlink.
If disabled, the device doesn't try to reduce data rate automatically and won't ever automatically detect network loss or change.
Disabled was formerly the preferred mode of operation, but as of early 2019, it is clear that enabled is the preferred mode for The Things Network.
typedef void Arduino_LoRaWAN::SendBufferCbFn(
void *pClientData,
bool fSuccess
);
bool Arduino_LoRaWAN::SendBuffer(
const uint8_t *pBuffer,
size_t nBuffer,
SendBufferCbFn *pDoneFn = nullptr,
void *pClientData = nullptr,
bool fConfirmed = false,
uint8_t port = 1
);
Send message from pBuffer
; call pDoneFn(pClientData, status)
when the message has either been transmitted or abandoned.
typedef void
Arduino_LoRaWAN::ReceivePortBufferCbFn(
void *pClientData,
uint8_t uPort,
const uint8_t *pBuffer,
size_t nBuffer
);
void Arduino_LoRaWAN::SetReceiveBufferCb(
Arduino_LoRaWAN::ReceivePortBufferCbFn *pReceiveBufferFn,
void *pUserData = nullptr
);
The specified function is called whenever a downlink message is received. nBuffer
might be zero, and uPort
might be zero for MAC messages.
bool Arduino_LoRaWAN::GetDevEUI(uint8_t *pBuf);
bool Arduino_LoRaWAN::GetAppEUI(uint8_t *pBuf);
bool Arduino_LoRaWAN::GetAppKey(uint8_t *pBuf);
These three routines fetch the provisioned DevEUI, AppEUI, and AppKey. pBuf
points to an 8-byte (DevEUI and AppEUI) or 16-byte (AppKey) buffer. The values are returned in network byte order: DevEUI and AppEUI are returned in little-endian byte order, and AppKey is returned in big-endian byte order.
bool Arduino_LoRaWAN::IsProvisioned(void);
Return true
if the LoRaWAN stack seems to be properly provisioned (provided with a valid DevEui, AppEUI and AppKey for OTAA; or provided with valid DevAddr, AppSKey and NwkSKey for ABP). Returns false
otherwise.
-
HEAD has the following changes
- #116 adds KR920 support. Vestigial / unused uses of
KR921
were changed to match the officialKR920
name. Cleanup typos in this file. Version is 0.6.0.10, and this requiresarduino-lmic
library version 2.3.2.60 or greater.
- #116 adds KR920 support. Vestigial / unused uses of
-
v0.6.0 has the following changes.
- #110 tweak initial power settings for US.
- #106, #107, #108, #104 CI improvements
- #88 use new LMIC APIs for SendBuffer
- #97 add
ARDUINO_LORAWAN_VERSION
macro. - #98 check LMIC version at compile time.
- #96 properly restores the NetID from a saved session.
- #93 adds EV_TXCANCELED support.
- #92, #84, #85, #87 handles transmit completion status correctly.
- #91 removes a redundant call to
UpdateFCntDown()
. - #89 adds new LMIC event codes added as part of the certification push.
- #5 enables link-check-mode by default.
- #83 add `SetLinkCheckMode() method.
- #81 allows uplinks to arbitrary ports.
-
v0.5.3 is a patch release. It fixes a PlatformIO compile warning, and also fixes another missing return for
Arduino_LoRaWAN::begin()
(this time in an overload in the header file.) -
v0.5.2 incorporates the fix for issue #68, missing return in
Arduino_LoRaWAN::begin()
. -
v0.5.1 fixes compilation errors when the library manager installs arduino-lmic in a renamed directory (issue #65).
-
v0.5.0 has necessary changes to support the LMIC built-in pin-maps, while retaining support for user-supplied pin-maps. We moved the pin-map parameter from compile-time initialization to an argument to Arduino_LoRaWAN::begin(). This is, unfortunately, a breaking change. Either do as we did in the example -- move the pinmap to the
begin()
call -- or add anm_pinmap
field in your concretecMyLoRaWAN
, and initialize it in yourcMyLoRaWAN::cMyLoRaWAN()
constructor. In addition, we added a few example programs (issue #49), and fixed handling of downlink messages with port numbers but no payloads (issue #50). -
v0.4.0 adds preliminary machineQ support, continuous integration for SAMD and STM32 L0, better PlatformIO support, improved as923jp support, and fixes a defect in the receive-message API.
-
v0.3.4 adds a few simple compile tests, improves the library name in
library.properties
, and further improves documentation. -
v0.3.3 adds PlatformIO support and fixes
library.properties
. -
v0.3.2 is just documentation changes.
-
v0.3.1 adds documentation (in this file, in the Required Libraries section) describing the need for the catena-mcciadk library. No code changes.
-
v0.3.0 adds support for the Murata module. It requires V2.1.0 of the arduino-lmic library.
-
v0.2.5 added support for the extended band plans, and requires V2.0.2 of the arduino-lmic library.
- Terry Moore of MCCI was the principal author of arduino-lorawan.
- Many thanks to Bob Fendrick for assistance in preparing initial test units.
- MCCI and Catena are registered trademarks of MCCI Corporation. LoRaWAN is a trademark of the LoRa Alliance. All other trademarks are the properties of their respective owners.
- This document initially composed with StackEdit; now maintained using Visual Studio Code.