Programming Model and API

The central component of the 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 2
#define LMIC_VERSION_MINOR 1

Warning

The design of the LMiC core allows very powerful access to all functionality of the stack. This allows for easy implementation of new experimental features, tweaking of internals for regression, functional, and compliance tests, and many more advanced use cases. However, it also means that you must take extreme care when using the APIs. For this reason we recommended taking 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 what are 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. This control identifies the job and stores context information.

Note

To ensure seamless operation, jobs must not be long-running. 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 doesn’t 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 mentioned 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)

Prepares 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)

Schedules 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)

Cancels a run-time job. A previously-scheduled run-time job is removed from the 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()

Executes run-time jobs from the timer and from the run queues. This function is the main action dispatcher. It must be run on the main thread and doesn’t return.

ostime_t os_getTime()

Queries absolute system time (in ticks).

Application Callbacks

The LMiC core requires that a few callback functions are implemented. 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 must provide the device EUI and copy it to the given buffer. The device EUI is 8 bytes long 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 must 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 long).

void os_getArtEui(u1_t* buf)

The implementation of this callback function must provide the application EUI and copy it to the given buffer. The application EUI is 8 bytes long 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 to 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 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

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

EV_RESET

The session reset due to a rollover of sequence counters. The network will be rejoined automatically to acquire a new session.

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 described 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_t 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_t structure are used only internally.

The most important fields to examine upon 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 acknowledged (mutually exclusive with TXRX_NACK)

TXRX_NACK

confirmed UP frame was not acknowledged (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()

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

bit_t LMIC_startJoining()

Immediately starts joining the network. Will be called implicitly 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)

Enables or disables data rate adaptation. Should be turned off if the device is mobile.

void LMIC_setLinkCheckMode(bit_t enabled)

Enables or disables link-check validation. The link-check mode is enabled by default and is used to periodically verify network connectivity. This must only be called if a session is established.

void LMIC_setDrTxpow(dr_t dr, s1_t txpow)

Sets the data rate and transmit power. This should only be called if data-rate adaptation is disabled.

void LMIC_setTxData()

Prepares 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)

Prepares upstream data transmission at the next possible time. This is a 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 starts scanning for the beacon immediately. A non-zero value specifies the number of attempts to make when when querying 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()

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

void LMIC_setPingable(u1_t intvExp)

Enables pinging and sets 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. In addition 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()

Stops 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()

Sends 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()

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