star-mesh LoRa network

Dependencies:   sx12xx_hal

start-mesh

radio chip selection

Radio chip driver is not included, because options are available.
If you're using SX1272 or SX1276, then import sx127x driver into your program.
if you're using SX1261 or SX1262, then import sx126x driver into your program.
If you're using NAmote72 or Murata discovery, then you must import only sx127x driver.

In this network, devices repeat messages to/from devices out of range of central gateway device. Appropriate for use when slightly larger batteries cost less than extra LoRaWAN gateways. This network uses LoRa transceiver directly and is not LoRaWAN. This network is appropriate for use where extra latency added from store-and-forward is of minimal consequence.

network implementation

Network achieves low-power operation by device waking up at regular intervals to check if LoRa preamble exists. If so, packet is received, otherwise devices sleeps. In this type of operation, trade-off is made between transmitter sending long preamble, and receiver waking up to receive this preamble along with associate message at the end of preamble. This long preamble is only used for request packets: the reply packet will have normal 8-symbol preamble length. This is known as asynchronous low-power operation, permitting an arbitrary number of devices to operate.

Devices start operation on the network by sending a discovery request to any devices that can hear it. Any devices which hears this discovery request will then send a discovery reply at randomized time offset relative to the request. After a pre-established time limit, the discovering device will decide which device to attach to depending on signal quality and how many hops away from the central gateway the device resides.

After device has attached to network, downlinks and uplinks can be sent to/from device. To facilitate downlinks, the devices closer to central gateway will be sent a new-device-notification to inform all devices between the central gateway and the newly attached device, which devices the new device can be reached via.

All devices have two logical interfaces to the network: An upstream interface, and a downstream interface. However, the central gateway device only has a downstream interface, because the "upstream" is only a UART interface to the user handling the user payloads on central gateway.

All devices on network are programmed with same firmware, except for gateway. In main.h #define GATEWAY is commented-out for devices on network, or is defined for central gateway device. Only one central gateway must exist on this network. The unique identifying address of device is derived from CPU unique ID registers, of which 4 byte ID number is used on this network. Using this CPU serial number permits the same binary file to be programmed into any number of devices.

network configuration

Network is configured in main.h

define in main.h
spreading factorSPREADING_FACTOR
bandwidthBW_KHZ
operating radio frequencyCF_MHZ
gateway or deviceGATEWAY
transmit powerTX_DBM

MAC layer timing scales according to LoRa symbol period. When spreading factor and/or bandwidth is changed, all network timing is scaled accordingly by MAC layer.
The transceivers used with the project operate at one datarate. This datarate is fixed, and must be defined at compile time for all devices and gateway.

low power operation

This MAC layer uses mbed eventqueue for scheduling. To enable low power operation, events.use-lowpower-timer-ticker is defined in mbed_app.json. This requires bare-metal operation to have eventqueue use low power timer, permitting deep sleep. LoRa applications such as this do not require RTOS: bare-metal mode is preferred for typical LoRa use.

application layer

User payloads are handled in app_endDevice.cpp. Uplinks are send from application layer by calling uplink(uint8_t *buffer_ptr, uint8_t length) Downlinks are handled in callback function app_downlink()

For gateway, app_gateway.cpp handles user payloads. For downlinks, an example is provided in cmd_downlink() where destination and payload is entered on serial port. All uplinks are handled in callback function gateway_uplink().

Header file app.h contains definitions common to application layer on both network central control and end device.

Note

This page describes how to use the network, for more detailed description of implementation, see details page.

serial terminal user interface

The STDIO UART is used to send and receive user-payload on the gateway, but is also available on end-devices. This serial port is configured at 115200 : 8,N,1.

commandargumentsdescription
?list commands
dldestID byte0 byte1 etcsend downlink to device (from gateway)
lslist downstream devices attached
opdBmmanually change transmit power

For the list of downstream devices, on start each row is printed directly attached device. If devices are attached further downstream, they will be subsequently printed on the same row.

testing / evaluation

Use 3 devices for testing: one gateway and two devices.
Three devices required for checking message repeating (relaying) function. The gateway device must be installed at some distance to prevent both devices from connecting directly to gateway.
Gateway needs to be located far enough away, so signal strength preference overrides hop count from gateway.

main.h

Committer:
Wayne Roberts
Date:
2019-12-03
Revision:
0:6015834e4279

File content as of revision 0:6015834e4279:

#include "radio.h" 


//#define GATEWAY

//#define MESH_DEBUG

#define DISCOVERY_ANS_LENGTH        13
#define ANS_PAD_MS                  5

#define ANS_SIZE_BYTE       9   /* */

#define RETRY_LIMIT     5       /* maximum count of reqest retries until giving up */
/********************************************************/

#define SPREADING_FACTOR            8
#ifdef SX128x_H
    #define CF_MHZ                      2487.0
    #define BW_KHZ                      400
    #define TX_DBM                      12
#else
    #define CF_MHZ                      917.6
    #define BW_KHZ                      500
    #define TX_DBM                      17
#endif

#define SP_US               ((1<<SPREADING_FACTOR) / (BW_KHZ/1000.0))
#define N_PRE_SYMB          (((WAKEUP_INTERVAL_MS * 1000) / SP_US) + 8)

#define N_DISCOVERY_ANS             180   // enough time slots for randomized collision avoidance
#define N_HALF_DISCOVERY_ANS             (N_DISCOVERY_ANS / 2)

#define WAKEUP_INTERVAL_MS                  1000
#define CHANNEL_VACANT_REQUIRED_COUNT       3

#define ID_NONE             0x00000000
#define ANY_ID              0xffffffff

typedef enum {
    /*  0 */ CMD_UNUSED = 0,
    /*  1 */ CMD_ANS,
    /*  2 */ CMD_DISCOVERY_REQ,
    /*  3 */ CMD_DISCOVERY_ANS,
    /*  4 */ CMD_ATTACH_REQ,
    /*  5 */ CMD_USER_PAYLOAD_UP_REQ,
    /*  6 */ CMD_USER_PAYLOAD_DN_REQ,
    /*  7 */ CMD_NEW_DEVICE_ATTACHED_REQ,
    /*  8 */ CMD_REMOVE_DEVICE_REQ,
    /*  9 */ CMD_DOWNSTREAM_NOT_RESPONDING,
} cmd_e;

typedef enum {
    /* 0 */ NOT_ANSWERING = 0,
    /* 1 */ ANSWER_OK,
    /* 2 */ ANSWER_BUSY,
    /* 3 */ ANSWER_UNATTACHED,
} ans_e;

typedef union {
    struct {
        uint8_t currentOp : 5; // 0,1,2,3,4
        uint8_t txAns     : 3; // 5,6,7
    } bits;
    uint16_t octet;
} reqflags_t;
extern reqflags_t reqFlags;

typedef struct {
    uint8_t discoverAnswering : 1; // 0
    uint8_t sending_req       : 1; // 1
    uint8_t firstDiscoverAns  : 1; // 2
    uint8_t CallTXRequest     : 1; // 3
    uint8_t unused            : 1; // 4
    uint8_t getAns            : 1; // 5
    uint8_t vacantCheck       : 1; // 6
    uint8_t deferred_send     : 1; // 7
} flags_t;

extern volatile flags_t flags;

typedef struct local lid_list_t;
typedef struct children cid_list_t;

struct children {
    uint32_t id;
    children* next;
};

struct local {
    uint32_t id;    // device directly attached
    children* attachedList; // devices attached to id
    local* next;
};

struct _fwd_ {
    uint8_t buf[247];
    int len;
    uint32_t A_id;
    uint32_t B_id;
    uint32_t tx_dest_id;    // pkt destination
};
extern struct _fwd_ fwd;

struct _nr_ {
    uint32_t reporting_id;
    uint32_t device_not_respoding_id;
};
extern struct _nr_ notResponding;

/* from main.cpp: */
#define HFG_UNATTACHED          0xff
#ifdef GATEWAY
    extern const uint8_t hops_from_gateway;
#else
    extern uint8_t hops_from_gateway;
#endif
extern EventQueue queue;
extern RawSerial pc; 
extern char pcbuf[64]; /* local user terminal */
extern uint32_t my_id;
extern unsigned discovery_ans_time_step_us;
extern unsigned discovery_ans_time_total_us;
void setPreambleSize(bool wakesize, uint8_t by);
uint32_t getu32FromBuf(const uint8_t* in);
void putu32ToBuf(uint8_t* out, uint32_t v);
void putu16ToBuf(uint8_t* out, uint16_t v);
uint16_t getu16FromBuf(const uint8_t* in);
uint16_t crc16( uint8_t *buffer, uint16_t length );
void txBuf_send(bool sendingReq);
bool remove_directlyAttached_device(uint32_t id);
void remove_childDevice(uint32_t id, uint32_t* attachedTo);
uint32_t find_dest_id(uint32_t reqid);
#ifdef MESH_DEBUG
int _rx_log_printf(const char *format, ...);
#endif /* MESH_DEBUG */
extern uint32_t tx_dest_id;
extern uint8_t txBuf[];
extern uint8_t txBuf_idx;
extern uint16_t dbg_plCur;
extern uint8_t dbg_plSetBy;
extern const char* const cmdStrs[];
void start_periodic_rxing(uint8_t by);

/* radio specific */
void radio_print_status(void);
void cmd_op(uint8_t);
void radio_printOpMode(void);
bool isRadioRxing(void);

#ifndef GATEWAY
/* upstream interface */
extern uint32_t id_newDeviceNotification;
typedef struct {
    uint32_t id, cnt;
    int preference;
    uint8_t hfg;
} upstream_t;
extern upstream_t attUp;
void upstream_init(void);
void discovery_rx_end(void);
void upstream_print_status(void);
int uplink(const uint8_t* userPayload, uint8_t userPayloadSize);
void upstream_new_device_notify(void);
void upstream_ans_rxDoneCB(float rssi, float snr, uint8_t* idx, uint32_t, uint8_t);
#endif /* !GATEWAY */
void upstream_req_rxDoneCB(float rssi, float snr, uint8_t* idx, uint32_t, uint8_t);
void upstream_signal_check(float rssi, float snr, uint8_t rx_hfg, uint32_t rx_id);
void init_attached_upstream(void);
void upstream_attached_check(uint32_t);

/* downstream interface */
struct remove {
    uint32_t destID;
    uint32_t removeID;
};
extern struct remove downRemove;
extern lid_list_t* attachedDevices;
#ifdef GATEWAY
typedef struct {
    uint8_t len;
    uint32_t originating_src_id;
    uint8_t rxBufIdx;
} upInfo_t;
void downstream_req_rxDoneCB(float rssi, float snr, uint8_t* idx, uint32_t, uint8_t, upInfo_t*);
#else
void downstream_req_rxDoneCB(float rssi, float snr, uint8_t* idx, uint32_t, uint8_t);
#endif
void downstream_ans_rxDoneCB(float rssi, float snr, uint8_t* idx, uint32_t, uint8_t);
void request_remove_device(void);


void cmd_downlink(uint8_t argsAt);

/* application layer: */
void app_init(void);
void gateway_uplink(uint8_t len, uint32_t, const uint8_t* payload);   /* uplink handler */
void app_downlink(uint8_t len, const uint8_t* payload); /* downlink handler */
void app_uplink_complete(void);

#ifdef MESH_DEBUG
    #define Mdbg_printf(fmt, ...)       pc.printf((fmt), ##__VA_ARGS__)
    #define mdbg_putc(x)                pc.putc(x)
    #define Rx_log_printf(fmt, ...)     _rx_log_printf((fmt), ##__VA_ARGS__)
#else
    #define Mdbg_printf(fmt, ...)       
    #define mdbg_putc(x)
    #define Rx_log_printf(fmt, ...)    
#endif