Tools
HOME » RESOURCES » TOOLS » LoRa Basics MAC » Programming Model and API

LoRa Basics MAC

Programming Model and API

The central component of Basic MAC is the LMiC core. It consists of the LoRaWAN state engine and the run-time environment. This core can be accessed via a set of API functions, run-time functions, callback functions, and the global LMIC data structure. The interface is defined in a single header file “lmic.h” which all applications should include.

#include "lmic.h"

To identify the version of the library two constants are defined in this header file.

#define LMIC_VERSION_MAJOR 1
#define LMIC_VERSION_MINOR 9

Warning

The design of the LMiC core allows very powerful access to all functionality of the stack. While this allows for easy implementation of new experimental features, tweaking of internals for regression, functional, and compliance tests, and many more advanced use cases, it also means that extreme care must be taken when making use of the APIs. For this reason, it is recommended to take advantage of the provided services that build on top of the LMiC core and abstract its powerful functionality from the developer.

Programming Model

The LMiC core offers a simple event-based programming model where all protocol events are dispatched to the application’s onEvent() callback function. In order to free the application of details like timings or interrupts, the library has a built-in run-time environment to take care of timer queues and job management.

Application Jobs

In this model, all application code is run in so-called jobs which are executed on the main thread by the run-time scheduler function os_runloop(). These application jobs are coded as normal C functions and can be managed using the run-time API functions described below. For job management an additional per job control struct osjob_t is required which identifies the job and stores context information. Jobs must not be long-running in order to ensure seamless operation! They should only update state and schedule actions, which will trigger new job or event callbacks.

Main Event Loop

Note

It is recommended that applications make use of the Application Startup (appstart) service to provide the correct initialization for the run-time environment.

All an application has to do is to initialize the run-time environment using the os_init() function and to run the job scheduler function os_runloop(), which does not return. In order to bootstrap protocol actions and generate events, an initial job needs to be set up. Therefore, a startup job is usually scheduled using the os_setCallback() function.

void main () {
    osjob_t initjob;
    // initialize run-time environment
    os_init();
    // setup initial job
    os_setCallback(&initjob, initfunc);
    // execute scheduled jobs and events
    os_runloop();
    // (not reached)
}

The startup code shown in the initfunc() function below initializes the LoRaWAN stack and starts joining the network:

// initial job
static void initfunc (osjob_t* j) {
    // reset MAC state
    LMIC_reset();
    // start joining
    LMIC_startJoining();
    // init done - onEvent() callback will be invoked…
}

The initfunc() function will return immediately, and the onEvent() callback function will be invoked by the scheduler later on for the events EV_JOINING, EV_JOINED or EV_JOIN_FAILED.

Run-time API Functions

The run-time functions menitioned before are used to control the run-time environment. This includes initialization, scheduling and execution of the run-time jobs.

void os_setCallback(osjob_t* job, osjobcb_t cb)

Prepare an immediately runnable job. This function can be called at any time, including from interrupt handler contexts (e.g. if a new sensor value has become available).

void os_setTimedCallback(osjob_t* job, ostime_t time, osjobcb_t cb)

Schedule a timed job to run at the given timestamp (absolute system time). This function can be called at any time, including from interrupt handler contexts.

void os_clearCallback(osjob_t* job)

Cancel a run-time job. A previously scheduled run-time job is removed from timer and run queues. The job is identified by the address of the job struct. The function has no effect if the specified job is not yet scheduled.

void os_runloop()

Execute run-time jobs from the timer and from the run queues. This function is the main action dispatcher. It does not return and must be run on the main thread.

ostime_t os_getTime()

Query absolute system time (in ticks).

Application Callbacks

The LMiC core requires the implementation of a few callback functions. These functions will be called by the state engine to query application-specific information and to deliver state events to the application.

Note

In most cases, these callback functions are implemented and provided by higher-level service modules. For example, the LoRaWAN Muxer (lwmux) service provides the onEvent() callback and appropriate hooks for applications to receive the events they are interested in.

void os_getDevEui(u1_t* buf)

The implementation of this callback function has to provide the device EUI and copy it to the given buffer. The device EUI is 8 bytes in length and is stored in little-endian format, that is, least-significant byte first (LSBF).

void os_getDevKey(u1_t* buf)

The implementation of this callback function has to provide the device-specific cryptographic application key and copy it to the given buffer. The device-specific application key is a 128-bit AES key (16 bytes in length).

void os_getArtEui(u1_t* buf)

The implementation of this callback function has to provide the application EUI and copy it to the given buffer. The application EUI is 8 bytes in length and is stored in little-endian format, that is, least-significant byte first (LSBF).

void onEvent(ev_t ev)

The implementation of this callback function may react on certain events and trigger new actions based on the event and the LMIC state. Typically, an implementation processes the events it is interested in and schedules further protocol actions using the LMIC API. The following events will be reported:

EV_JOINING

The node has started joining the network.

EV_JOINED

The node has successfully joined the network and is now ready for data exchanges.

EV_JOIN_FAILED

The node could not join the network (after retrying).

EV_TXCOMPLETE

The data prepared via LMIC_setTxData() has been sent, and eventually downstream data has been received in return. If confirmation was requested, the acknowledgement has been received.

EV_RXCOMPLETE

Downstream data has been received.

EV_SCAN_TIMEOUT

After a call to LMIC_enableTracking() no beacon was received within the beacon interval. Tracking needs to be restarted.

EV_BEACON_FOUND

After a call to LMIC_enableTracking() the first beacon has been received within the beacon interval.

EV_BEACON_TRACKED

The next beacon has been received at the expected time.

EV_BEACON_MISSED

No beacon was received at the expected time.

EV_LOST_TSYNC

Beacon was missed repeatedly and time synchronization has been lost. Tracking or pinging needs to be restarted.

EV_RESET

Session reset due to rollover of sequence counters. Network will be rejoined automatically to acquire new session.

EV_LINK_DEAD

No confirmation has been received from the network server for an extended period of time. Transmissions are still possible but their reception is uncertain.

Details for specific events can be obtained from the global LMIC structure decribed in the next section.

The LMIC Struct

Instead of passing numerous parameters back and forth between API and callback functions, information about the protocol state can be accessed via a global LMIC structure as shown below. All fields besides the ones explicitly mentioned below are read-only and should not be modified.

struct lmic_t {
    u1_t frame[MAX_LEN_FRAME];
    u1_t dataLen;    // 0 no data or zero length data, >0 byte count of data
    u1_t dataBeg;    // 0 or start of data (dataBeg-1 is port)

    u1_t txCnt;
    u1_t txrxFlags;  // transaction flags (TX-RX combo)

    u1_t pendTxPort;
    u1_t pendTxConf; // confirmed data
    u1_t pendTxLen;
    u1_t pendTxData[MAX_LEN_PAYLOAD];

    u1_t bcnChnl;
    u1_t bcnRxsyms;
    ostime_t bcnRxtime;
    bcninfo_t bcninfo; // Last received beacon info

    ...
    ...
};

This document does not describe the full struct in detail since most of the fields of the LMIC struct are used internally only.

The most important fields to examine on reception (event EV_RXCOMPLETE or EV_TXCOMPLETE) are the txrxFlags for status information and frame[] and dataLen / dataBeg for the received application payload data.

For data transmission the most important fields are pendTxData[], pendTxLen, pendTxPort and pendTxConf, which are used as input to the LMIC_setTxData() API function. For the EV_RXCOMPLETE and EV_TXCOMPLETE events, the txrxFlags field sould be evaluated and the following flags are defined:

TXRX_ACK

confirmed UP frame was acked (mutually exclusive with TXRX_NACK)

TXRX_NACK

confirmed UP frame was not acked (mutually exclusive with TXRX_ACK)

TXRX_PORT

a port field is contained in the received frame

TXRX_DNW1

received in first DOWN slot (mutually exclusive with TXRX_DNW2)

TXRX_DNW2

received in second DOWN slot (mutually exclusive with TXRX_DNW1)

TXRX_PING

received in a scheduled RX slot

For the EV_TXCOMPLETE event the fields have the following values:

Received Frame LMIC.txrxFlags LMIC.dataLen LMIC.dataBeg
ACK NACK PORT DNW1 DNW2 PING
nothing 0 0 0 0 0 0 0 0
empty frame x x 0 x x 0 0 x
port only x x 1 x x 0 0 x
port + payload x x 1 x x 0 x x

For the EV_RXCOMPLETE event the fields have the following values:

Received Frame LMIC.txrxFlags LMIC.dataLen LMIC.dataBeg
ACK NACK PORT DNW1 DNW2 PING
empty frame 0 0 0 0 0 1 0 x
port only 0 0 1 0 0 1 0 x
port + payload 0 0 1 0 0 1 x x

API Functions

The LMiC library offers a set of API functions to control the MAC state and to trigger protocol actions.

void LMIC_reset()

Reset the MAC state. Session and pending data transfers will be discarded.

bit_t LMIC_startJoining()

Immediately start joining the network. Will be called implicitely by other API functions if no session has been established yet. The events EV_JOINING and EV_JOINED or EV_JOIN_FAILED will be generated.

void LMIC_setSession(u4_t netid, devaddr_t devaddr, u1_t* nwkKey, u1_t* artKey)

Set static session parameters. Instead of dynamically establishing a session by joining the network, precomputed session parameters can be provided. To resume a session with precomputed parameters, the frame sequence counters (LMIC.seqnoUp and LMIC.seqnoDn) must be restored to their latest values.

void LMIC_setAdrMode(bit_t enabled)

Enable or disable data rate adaptation. Should be turned off if the device is mobile.

void LMIC_setLinkCheckMode(bit_t enabled)

Enable/disable link check validation. Link check mode is enabled by default and is used to periodically verify network connectivity. Must be called only if a session is established.

void LMIC_setDrTxpow(dr_t dr, s1_t txpow)

Set data rate and transmit power. Should only be used if data rate adaptation is disabled.

void LMIC_setTxData()

Prepare upstream data transmission at the next possible time. It is assumed, that LMIC.pendTxData, LMIC.pendTxLen, LMIC.pendTxPort and LMIC.pendTxConf have already been set. Data of length LMIC.pendTxLen from the array LMIC.pendTxData[] will be sent to port LMIC.pendTxPort. If LMIC.pendTxConf is true, confirmation by the server will be requested. The event EV_TXCOMPLETE will be generated when the transaction is complete, i.e. after the data has been sent and both RX1 and RX2 have expired.

int LMIC_setTxData2(u1_t port, xref2u1_t data, u1_t dlen, u1_t confirmed)

Prepare upstream data transmission at the next possible time. Convenience function for LMIC_setTxData(). If data is NULL, the data in LMIC.pendTxData[] will be used.

void LMIC_clrTxData()

Remove data previously prepared for upstream transmission.

bit_t LMIC_enableTracking(u1_t tryBcnInfo)

Enable beacon tracking. A value of 0 for tryBcnInfo indicates to start scanning for the beacon immediately. A non-zero value specifies the number of attempts to query the server for the exact beacon arrival time. The query requests will be sent within the next upstream frames (no frame will be generated). If no answer is received scanning will be started. The events EV_BEACON_FOUND or EV_SCAN_TIMEOUT will be generated for the first beacon, and the events EV_BEACON_TRACKED, EV_BEACON_MISSED or EV_LOST_TSYNC will be generated for subsequent beacons.

void LMIC_disableTracking()

Disable beacon tracking. The beacon will be no longer tracked and, therefore, also pinging will be disabled.

void LMIC_setPingable(u1_t intvExp)

Enable pinging and set the downstream listen interval. Pinging will be enabled with the next upstream frame (no frame will be generated). The listen interval is 2intvExp seconds, valid values for intvExp are 0-7. This API function requires a valid session established with the network server either via LMIC_startJoining() or LMIC_setSession() functions. If beacon tracking is not yet enabled, scanning will be started immediately. In order to avoid scanning, the beacon can be located more efficiently by a preceding call to LMIC_enableTracking() with a non- zero parameter. Additionally to the events mentioned for LMIC_enableTracking(), the event EV_RXCOMPLETE will be generated whenever downstream data has been received in a ping slot.

void LMIC_stopPingable()

Stop listening for downstream data. Periodical reception is disabled, but beacons will still be tracked. In order to stop tracking, the beacon a call to LMIC_disableTracking() is required.

void LMIC_sendAlive()>

Send one empty upstream MAC frame as soon as possible. Might be used to signal liveness or to transport pending MAC options, and to open a receive window.

void LMIC_shutdown()

Stop all MAC activity. Subsequently, the MAC needs to be reset via a call to LMIC_reset() and new protocol actions need to be initiated.