Build LoRaWAN Gateway with Ci40 and iC880a

In this post I will show you how to setup an OpenWRT based LoRaWAN gateway with Ci40 and iC880a. It has a user-friedly interface to config parameters.


  1. IMST iC880a concentrator board
  2. Pigtail for antenna
  3. Ci40
  4. Power Supply 2A with micro USB
  5. MicroSD card
  6. RPi to iC8880a interface

Ci40 has a 40-pin header which is though not 100% compatible with Raspberry Pi. The feature make it possible to connect lots of Raspberry Pi HATs such as this backplane from Tindie.

OpenWrt Preparation

At first, please following the instruction to prepare the SD card. I prefer the SD card boot not normal because I want to save lifecycles of NAND FLASH on my board.

Posted in Ci40, LEDE, LoRa, OpenWRT | Leave a comment

Meet /etc/preinit again

*/etc/preinit is the init program after Linux kernel booted. It is briefly introduced in OpenWRT/LEDE: System Boot Sequence. This time I will meet it again and dive into it to search some internals.

As a reference, the code of preinit is shown below. And it is assumed that $PREINIT is already initialized.

# Copyright (C) 2006-2016
# Copyright (C) 2010 Vertical Communications

[ -z "$PREINIT" ] && exec /sbin/init

export PATH="%PATH%"





. /lib/
. /lib/functions/
. /lib/functions/

boot_hook_init preinit_essential
boot_hook_init preinit_main
boot_hook_init failsafe
boot_hook_init initramfs
boot_hook_init preinit_mount_root

for pi_source_file in /lib/preinit/*; do
    . $pi_source_file

boot_run_hook preinit_essential


boot_run_hook preinit_main

The first part includes three group of parameters. They are initilized for the functions which are located in hook list.

Then we come to five hook lists which are initilized by boot_hook_init which is located in /lib/function/ The code of boot_hook_init is show below.

boot_hook_init() {          
    local hook="${1}_hook" # add suffix _hook to every hook lister name
    export -n "PI_STACK_LIST=${PI_STACK_LIST:+$PI_STACK_LIST }$hook"
    export -n "$hook="

The point of this function is the command export -n. The option -n means remove the export property from each NAME. It marks each NAME not export to the environment and child-env, but other functions in this shell can still use this variable.

for pi_source_file in /lib/preinit/*; do
    . $pi_source_file

preinit is then looking for and excute every files located /lib/preinit/. The name of such file is starting with a number and then the name of function. Each file has similar structure as

function_name1() {
function_name2() {
boot_hook_add hook_name function_name1
boot_hook_add hook_name function_name2

There can be multiple functions and later using boot_hook_add to add into a certain hook linker. So what happens in boot_hook_add is shown below

boot_hook_add() {
    local hook="${1}_hook${PI_HOOK_SPLICE:+_splice}"
    local func="${2}"

    [ -n "$func" ] && {
        local v; eval "v=\$$hook"
        export -n "$hook=${v:+$v }$func" # add function to responding hook list

After adding functions, preinit excutes the functions located in preinit_essential by

boot_run_hook preinit_essential

here the definition of boot_run_hook is shown below

boot_run_hook() {
    local hook="$1"
    local func
    while boot_hook_shift "$hook" func; do
        local ran; eval "ran=\$PI_RAN_$func"
        [ -n "$ran" ] || {
            export -n "PI_RAN_$func=1"
            $func "$1" "$2"  # excute the function

Here boot_hook_shift loops every functions in “$hook” until the last one. It returns 0 as true when there is still functions in “$hook” and returns 1 as false when reaching the end. The code of boot_hook_shift is shown below:

boot_hook_shift() {
    local hook="${1}_hook"
    local rvar="${2}"

    local v; eval "v=\$$hook" # $$hook includes all the registered functions
    [ -n "$v" ] && {
        local first="${v%% *}"  # take the first function every time

        [ "$v" != "${v#* }" ] && \
            export -n "$hook=${v#* }" || \
            export -n "$hook="

        export -n "$rvar=$first" # export the first function for other ones
        return 0

    return 1
Posted in Ci40, LEDE, OpenWRT | Leave a comment

iC880A LoRaWAN Gateway with Ci40

2017-06-27 15.13.19

with my lora-feed

I am working on LuCi web interface to config the gateway. I will show it later.

Posted in Ci40, LEDE, LoRa, OpenWRT, 未分类 | Leave a comment

[Dissecting OpenWRT]#2: netifd – main()

netifd is an RPC-capable daemon written in C for better access to kernel APIs with the ability to listen on netlink events.

In this series serveral articles will be written introduce the internal of netifd which is a core componet of OpenWRT/LEDE system for network management and its support for 802.11. As a normal Linux program, standing at main() function as a start point is a good choice.

The main() function of netidf is rather short and simple. It hides all the details in functions

int main(int argc, char **argv)
    const char *socket = NULL;
    int ch;

    global_argv = argv;

    while ((ch = getopt(argc, argv, "d:s:p:c:h:r:l:S")) != -1) {
        switch(ch) {
        case 'd':
            debug_mask = strtoul(optarg, NULL, 0);
        case 's':
            socket = optarg;
        case 'p':
            main_path = optarg;
        case 'c':
            config_path = optarg;
        case 'h':
            hotplug_cmd_path = optarg;
        case 'r':
            resolv_conf = optarg;
        case 'l':
            log_level = atoi(optarg);
            if (log_level >= ARRAY_SIZE(log_class))
                log_level = ARRAY_SIZE(log_class) - 1;
#ifndef DUMMY_MODE
        case 'S':
            use_syslog = false;
            return usage(argv[0]);

    if (use_syslog)
        openlog("netifd", 0, LOG_DAEMON);

    if (netifd_ubus_init(socket) < 0) {
        fprintf(stderr, "Failed to connect to ubus\n");
        return 1;


    if (system_init()) {
        fprintf(stderr, "Failed to initialize system control\n");
        return 1;




    if (use_syslog)

    return 0;

A brief flow chart is shown below

netifd main function


Posted in 未分类 | Leave a comment

Semtech LoRa packages for OpenWRT/LEDE

This article is still under construction

Recently I made a repo for latest Semtech Lora packages.


A donation of SX1301 gateway board is appreciate. With this I could do further development of UCI and Luci for Lora.


Posted in LEDE, LoRa, OpenWRT | Leave a comment

[Dissecting OpenWRT]#1: Preface

From today I would like to start a new series (挖坑) called “Dissecting OpenWRT” (深入浅出 OpenWRT). Unlike other tutorials about OpenWRT, this new series will try to dissect OpenWRT from a developer’s point of view. It will focus on the implementation of OpenWRT core components, such as ubus, procd, netifd and luci. I hope that with the help of this new series, people will not only understand the insider but also know how to extend OpenWRT.



Posted in OpenWRT | Leave a comment

WPAN support for netifd

netifd is an RPC-capable daemon written in C for better access to kernel APIs with the ability to listen on netlink events. Netifd has replaced the old OpenWrt-network configuration scripts, the actual scripts that configured the network e.g.,


The WPAN support is now a simple clone of 802.11 support which include C code for netifd and Ash scripts to handle wpan interface.

The hardware platform I use is ci40 from Imagination, which has ca2810 802.15.4 radio chip. For most router without 802.15.4 hardware chips, fakelb is a good choice for testing. here is a simple tutorial how to setup a 6lowpan test network.

The OpenWRT source code is here

Posted in 6lowpan for linux, 802.15.4, Ci40, netifd, OpenWRT | Leave a comment