Cellular and WiFi Stack Management on a custom nRF9160 board

Hello!
We have developed a custom board featuring an nRF9160 and an ESP32-C3-MINI-1 co-processor, which is flashed with AT-firmware (version 3.2.0.0). Our current application runs an LWM2M client that connects to an LWM2M server and sends data.

We currently have two firmware builds, each one only compiles in only one network stack (either LTE or WiFi):
1. LTE with nRF9160: Utilizes socket offloading, securely connects to LWM2M with DTLS and COAPS.
2. WiFi with ESP32-C3-MINI-1: Utilizes the Espressif AT Command offloaded WiFi driver, and connects to LWM2M insecurely with COAP (unresolved issues).

Our current objective is to modify our application to support management of both cellular and WiFi network stacks at runtime.
Specifically, the application should be capable of:

- Initializing both network stacks.
- Connecting to both networks.
- Handle switching between networks in case of the disconnection. For example, if WiFi connection becomes unavailable, application should switch to completely using the LTE connection.

Before implementing this in the actual application we created a small sample with shell commands to test the dual stack setup by running both interfaces and sending data to some server.
As a starting point, we took the NCS net/udp sample which can be used on an nRF9160 and nRF7002, but we did not use network management (net_mgmt) to handle initialization and event management of both interfaces.

In this sample the initialization and simultaneous operation of both cellular and WiFi stacks was successful, ESP connected to an access point, nRF9160 connected to LTE.
But when we tried to send data using the socket API we encountered issues when trying to use each interface separately.
Sending only worked over LTE, but not over WiFi. To make sending over WiFi possible we had to:

- enable NET_SHELL
- connect using our wifi commands and
- send the data with the net udp send command,

That way we were able to send a UDP payload through the WiFi interface, with LTE connection still present at that moment.
We were not able to figure out how to switch between the interface, without using the entire net_shell.c logic.
While doing some experimenting, it seemed that either interface wanted to use only one offload implementation.

Main questions:

- Is the simultaneous operation of both interfaces (cellular and WiFi) possible with our current setup?
- Is dynamic switching between network interfaces possible via net_if_* calls?
- Is socket offloading preventing the WiFi interface from using its own implementation?

Development setup:

- OS: Ubuntu 20.04
- Zephyr: 3.5.0
- NCS: 2.5.0

Sample prj.conf:

# Enable console
CONFIG_USE_SEGGER_RTT=y
CONFIG_RTT_CONSOLE=y
CONFIG_SHELL=y
CONFIG_SHELL_BACKEND_RTT=y
CONFIG_SHELL_TAB=y

# Serial
CONFIG_SERIAL=y

# Modem library
CONFIG_MODEM=y
CONFIG_NRF_MODEM_LIB=y
CONFIG_MODEM_INFO=y

# AT monitor library
CONFIG_AT_MONITOR_HEAP_SIZE=4096
CONFIG_AT_MONITOR=y

# PDN
CONFIG_PDN=y

# LTE
CONFIG_LTE_LINK_CONTROL=y # Enables LTE custom commands
CONFIG_LTE_NETWORK_MODE_DEFAULT=y
CONFIG_LTE_NETWORK_USE_FALLBACK=n
CONFIG_LTE_SHELL=n # Disable LTE shell

# Networking
CONFIG_NETWORKING=y

CONFIG_NET_SHELL=y
CONFIG_NET_L2_WIFI_SHELL=n # Enables net wifi shell

CONFIG_NET_NATIVE=y
CONFIG_NET_OFFLOAD=n

CONFIG_NET_SOCKETS=y
CONFIG_NET_SOCKETS_OFFLOAD=y
CONFIG_NET_SOCKETS_POSIX_NAMES=y # Expose Zephyr socket API without zsock_ prefix
CONFIG_NET_SOCKETS_LOG_LEVEL_DBG=y

# Net event management and other logging
CONFIG_NET_LOG=y
CONFIG_NET_MGMT_EVENT_MONITOR=y
CONFIG_NET_OFFLOAD_LOG_LEVEL_DBG=y

# Network buffers
CONFIG_NET_TX_STACK_SIZE=2048
CONFIG_NET_RX_STACK_SIZE=2048
CONFIG_NET_PKT_RX_COUNT=10
CONFIG_NET_PKT_TX_COUNT=10
CONFIG_NET_BUF_RX_COUNT=20
CONFIG_NET_BUF_TX_COUNT=20
CONFIG_NET_MAX_CONTEXTS=10

# IPv
CONFIG_NET_IPV4=y
CONFIG_NET_IF_MAX_IPV4_COUNT=5
CONFIG_NET_IPV6=y
CONFIG_NET_IF_MAX_IPV6_COUNT=5

# Protocols
CONFIG_NET_UDP=y

# ESP wifi configs
CONFIG_WIFI=y
CONFIG_WIFI_DRIVER=y # Custom WiFi driver with helper functions
CONFIG_WIFI_LOG_LEVEL_DBG=y

# AT Command offloaded WiFi driver.
CONFIG_WIFI_ESP_AT=y
CONFIG_WIFI_ESP_AT_PASSIVE_MODE=y


Cellular commands:

#include <zephyr/net/socket.h>
#include <zephyr/shell/shell.h>
#include <modem/lte_lc.h>

LOG_MODULE_REGISTER(lte_commands, LOG_LEVEL_DBG);

static int client_fd;
static struct sockaddr_storage host_addr;

static void prv_lte_handler(const struct lte_lc_evt *const evt)
{
    switch (evt->type) {
    case LTE_LC_EVT_NW_REG_STATUS:
        if ((evt->nw_reg_status != LTE_LC_NW_REG_REGISTERED_HOME) &&
            (evt->nw_reg_status != LTE_LC_NW_REG_REGISTERED_ROAMING)) {
            break;
        }

        printk("Network registration status: %s\n",
               evt->nw_reg_status == LTE_LC_NW_REG_REGISTERED_HOME ? "Connected - home"
                                       : "Connected - roaming");
        break;
    case LTE_LC_EVT_PSM_UPDATE:
        printk("PSM parameter update: TAU: %d s, Active time: %d s\n", evt->psm_cfg.tau,
               evt->psm_cfg.active_time);
        break;
    case LTE_LC_EVT_EDRX_UPDATE:
        printf("eDRX parameter update: eDRX: %.2f s, PTW: %.2f s\n", evt->edrx_cfg.edrx,
               evt->edrx_cfg.ptw);
        break;
    case LTE_LC_EVT_RRC_UPDATE:
        printk("RRC mode: %s\n",
               evt->rrc_mode == LTE_LC_RRC_MODE_CONNECTED ? "Connected" : "Idle\n");
        break;
    case LTE_LC_EVT_CELL_UPDATE:
        printk("LTE cell changed: Cell ID: %d, Tracking area: %d\n", evt->cell.id,
               evt->cell.tac);
        break;
    default:
        break;
    }
}

/**
 * @brief Connect to LTE network.
 *
 * This function connects to the LTE network. The function will return
 * immediately and the connection process will be handled by the event handler.
 *
 * @param[in] sh        Pointer to the shell instance.
 * @param[in] argc        Number of arguments.
 * @param[in] argv        Arguments.
 *
 * @retval 0 If the command was successful.
 * @retval -EINVAL If no event handler was registered.
 * @retval -EFAULT If the command was successful.
 */
static int cmd_lte_init_and_connect(const struct shell *sh, size_t argc, char **argv)
{
    int err;

    err = lte_lc_init();
    if (err) {
        shell_error(sh, "lte_lc_init, err: %d", err);
        return err;
    }

    /* Reister handler */
    lte_lc_register_handler(prv_lte_handler);

    err = lte_lc_connect_async(NULL);
    if (err) {
        shell_error(sh, "lte_lc_connect_async, err: %d", err);
        return err;
    }

    return 0;
}

/**
 * @brief Disconnect from LTE network.
 *
 * @param[in] sh         Pointer to the shell instance.
 * @param[in] argc        Number of arguments.
 * @param[in] argv        Arguments.
 *
 * @retval 0 If the command was successful.
 * @retval -EFAULT If functional mode could not be configured.
 */
static int cmd_lte_offline(const struct shell *sh, size_t argc, char **argv)
{
    ARG_UNUSED(argc);
    ARG_UNUSED(argv);

    int err;

    err = lte_lc_offline();
    if (err) {
        shell_error(sh, "Error: lte_lc_offline() returned %d", err);
        return err;
    }

    return 0;
}

/**
 * @brief Power off LTE modem.
 *
 * @param[in] sh         Pointer to the shell instance.
 * @param[in] argc         Number of arguments.
 * @param[in] argv         Arguments.
 *
 * @retval 0 If the command was successful.
 * @retval -EFAULT If functional mode could not be configured.
 */
static int cmd_lte_off(const struct shell *sh, size_t argc, char **argv)
{
    ARG_UNUSED(argc);
    ARG_UNUSED(argv);

    int err;

    err = lte_lc_power_off();
    if (err) {
        shell_error(sh, "Error: lte_lc_power_off() returned %d", err);
        return err;
    }

    return 0;
}

/**
 * @brief Put LTE modem into normal mode.
 *
 * @param[in] sh         Pointer to the shell instance.
 * @param[in] argc         Number of arguments.
 * @param[in] argv         Arguments.
 *
 * @retval 0 If the command was successful.
 * @retval -EFAULT If functional mode could not be configured.
 */
static int cmd_lte_normal(const struct shell *sh, size_t argc, char **argv)
{
    ARG_UNUSED(argc);
    ARG_UNUSED(argv);

    int err;

    err = lte_lc_normal();
    if (err) {
        shell_error(sh, "Error: lte_lc_normal() returned %d", err);
        return -ENOEXEC;
    }
    return 0;
}

/**
 * @brief Deinitialize LTE library.
 *
 * @param[in] sh        Pointer to the shell instance.
 * @param[in] argc        Number of arguments.
 * @param[in] argv        Arguments.
 *
 * @retval 0 If the command was successful.
 * @retval -EFAULT If the command failed.
 */
static int cmd_lte_deinit(const struct shell *sh, size_t argc, char **argv)
{
    ARG_UNUSED(argc);
    ARG_UNUSED(argv);

    int err;
    err = lte_lc_deinit();
    if (err) {
        shell_error(sh, "Error: lte_lc_deinit() returned %d", err);
        return -EFAULT;
    }

    return 0;
}

static void prv_socket_send(void)
{
    int err;
    char buffer[10] = {"hello"};

    printk("Transmitting UDP payload");

    err = send(client_fd, buffer, sizeof(buffer), 0);
    if (err < 0) {
        printk("Failed to transmit UDP packet, error: %d\n", errno);
    }
}

static int prv_socket_connect(void)
{
    int err;

    struct sockaddr_in *server4 = ((struct sockaddr_in *)&host_addr);

    server4->sin_family = AF_INET;
    server4->sin_port = htons(PORT);
    inet_pton(AF_INET, server_ip, &server4->sin_addr);

    client_fd = socket(AF_INET, SOCK_DGRAM, IPPROTO_UDP);
    if (client_fd < 0) {
        printk("Failed to create UDP socket, error: %d\n", errno);
        err = -errno;
        return err;
    }

    err = connect(client_fd, (struct sockaddr *)&host_addr, sizeof(struct sockaddr_in));
    if (err < 0) {
        printk("Failed to connect socket, error: %d\n", errno);
        close(client_fd);
        return err;
    }

    return 0;
}

/**
 * @brief Send some something with UDP.
 *
 * @param[in] sh        Pointer to the shell instance.
 * @param[in] argc        Number of arguments.
 * @param[in] argv        Arguments.
 *
 * @retval 0 If the command was successful.
 * @retval -EFAULT If the command failed.
 */
static int cmd_lte_send(const struct shell *sh, size_t argc, char **argv)
{
    ARG_UNUSED(argc);
    ARG_UNUSED(argv);

    int err = prv_socket_connect();
    if (err) {
        shell_error(sh, "Error: prv_socket_connect() returned %d", err);
        return -EFAULT;
    }

    prv_socket_send();

    return 0;
}

SHELL_STATIC_SUBCMD_SET_CREATE(
    lte,
    SHELL_CMD_ARG(conn, NULL, "Init and connect to LTE network\n", cmd_lte_init_and_connect, 1,
              0),
    SHELL_CMD_ARG(dis, NULL, "Disconnect from LTE network if connected\n", cmd_lte_offline, 1,
              0),
    SHELL_CMD_ARG(power_off, NULL, "Power off LTE modem\n", cmd_lte_off, 1, 0),
    SHELL_CMD_ARG(normal, NULL, "Power off LTE modem\n", cmd_lte_normal, 1, 0),
    SHELL_CMD_ARG(deinit, NULL, "Power off LTE modem\n", cmd_lte_deinit, 1, 0),
    SHELL_CMD_ARG(send, NULL, "Power off LTE modem\n", cmd_lte_send, 1, 0),
    SHELL_SUBCMD_SET_END);

SHELL_CMD_REGISTER(lte, &lte, "Custom LTE commands", NULL);


WiFi commands:

#include <zephyr/net/socket.h>
#include <zephyr/shell/shell.h>
#include <wifi_driver.h>

static int client_fd;
static struct sockaddr_storage host_addr;

/**
 * @brief Scan for WiFi access points.
 *
 * @param[in] sh         Pointer to the shell instance.
 * @param[in] argc         Number of arguments.
 * @param[in] argv         Arguments.
 * @return
 */
static int cmd_wifi_scan(const struct shell *sh, size_t argc, char **argv)
{
    ARG_UNUSED(argc);
    ARG_UNUSED(argv);

    int err;

    err = wifi_scan();
    if (err) {
        shell_error(sh, "Failed to scan WiFi");
        return -1;
    }
    return 0;
}

/**
 * @brief Connect to WiFi access point.
 *
 * @param[in] sh        Pointer to the shell instance.
 * @param[in] argc         Number of arguments.
 * @param[in] argv         Arguments.
 * @return
 */
static int cmd_wifi_connect(const struct shell *sh, size_t argc, char **argv)
{
    ARG_UNUSED(argc);
    ARG_UNUSED(argv);

    int err;

    err = wifi_connect(WIFI_AP, WIFI_PSK);
    if (err) {
        shell_error(sh, "Failed to connect to WiFi");
        return -1;
    }
    return 0;
}

/**
 * @brief Disconnect from a WiFi access point.
 *
 * @param[in] sh        Pointer to the shell instance.
 * @param[in] argc         Number of arguments.
 * @param[in] argv         Arguments.
 * @return
 */
static int cmd_wifi_disconnect(const struct shell *sh, size_t argc, char **argv)
{
    ARG_UNUSED(argc);
    ARG_UNUSED(argv);

    int err;

    err = wifi_disconnect();
    if (err) {
        shell_error(sh, "Failed to disconnect from WiFi");
        return -1;
    }
    return 0;
}

static void prv_socket_send(void)
{
    int err;
    char buffer[10] = {"hello"};

    printk("Transmitting UDP payload");

    err = send(client_fd, buffer, sizeof(buffer), 0);
    if (err < 0) {
        printk("Failed to transmit UDP packet, error: %d\n", errno);
    }
}

static int prv_socket_connect(void)
{
    int err;

    struct sockaddr_in *server4 = ((struct sockaddr_in *)&host_addr);

    server4->sin_family = AF_INET;
    server4->sin_port = htons(PORT);
    inet_pton(AF_INET, server_ip, &server4->sin_addr);

    client_fd = socket(AF_INET, SOCK_DGRAM, IPPROTO_UDP);
    if (client_fd < 0) {
        printk("Failed to create UDP socket, error: %d\n", errno);
        err = -errno;
        return err;
    }

    err = connect(client_fd, (struct sockaddr *)&host_addr, sizeof(struct sockaddr_in));
    if (err < 0) {
        printk("Failed to connect socket, error: %d\n", errno);
        close(client_fd);
        return err;
    }

    return 0;
}

/**
 * @brief Send some something with UDP.
 *
 * @param[in] sh        Pointer to the shell instance.
 * @param[in] argc        Number of arguments.
 * @param[in] argv        Arguments.
 *
 * @retval 0 If the command was successful.
 * @retval -EFAULT If the command failed.
 */
static int cmd_wifi_send_udp(const struct shell *sh, size_t argc, char **argv)
{
    ARG_UNUSED(argc);
    ARG_UNUSED(argv);

    int err = prv_socket_connect();
    if (err) {
        shell_error(sh, "Error: prv_socket_connect() returned %d", err);
        return -EFAULT;
    }

    prv_socket_send();

    return 0;
}

SHELL_STATIC_SUBCMD_SET_CREATE(wifi,
                   SHELL_CMD_ARG(scan, NULL,
                         "Scan WiFi for access points\n"
                         "usage: scan",
                         cmd_wifi_scan, 1, 0),
                   SHELL_CMD_ARG(conn, NULL,
                         "Connect to WiFi access point (pre-set ssid and psk)\n"
                         "usage: connect",
                         cmd_wifi_connect, 1, 0),
                   SHELL_CMD_ARG(dis, NULL,
                         "Disconnect from WiFi access point if connected\n"
                         "usage: disconnect",
                         cmd_wifi_disconnect, 1, 0),
                   SHELL_CMD_ARG(send, NULL,
                         "Send 'hello' to nodered\n"
                         "usage: send",
                         cmd_wifi_send_udp, 1, 0),
                   SHELL_SUBCMD_SET_END);


Thanks for your help!

  • Hi

    On the LTE side, you can use the AT command AT%COEX, and I assume you can use the MPSL timeslots for the multiprotocol service layer. For the ESP32 co-processor we here at Nordic don't have any experience with, so I don't have much in terms of advice here.

    Best regards,

    Simon

  • Hi Simon.

    Thanks for your reply, the problem is not RF co-existance but network stack co-existance.

    We also looked at the cellular/modem_shell sample for the nRF9160dk where there is support for the WiFi configuration with the ESP8266 chip running AT firmware.

    This setup seems to be quite similar to ours, however we can't understand how we could adapt this to our use-case.
    Could your provide some details on that? Specifically, what network stack configurations are required and what APIs should we use to accomplish this?

  • Hi

    I'm sorry, but we don't have much more than the networking documentation on setting up our devices. We can leave the case open if someone in the community would like to chime in though.

    Best regards,

    Simon

  • Hi Simon, we made some progress on this.

    We are now able to use the WiFi and LTE interfaces simultaneously, at least with non-TLS sockets.

    After reading through the Zephyr documentation, we learned about the
    socket dispatcher, which enables the application to specify which network implementation a socket should be bound to.

    As shown in the first example in the above-linked documentation, a socket first needs to be created
    Then, the SO_BINDTODEVICE option, with the interface name, is set with setsockopt.

    The names are not arbitrary, but rather the names of the network interfaces. For WiFi, the name is
    wlan0 and for LTE, the name is net0.

    With that information, we started integrating the offloaded socket dispatcher into the
    nrf/samples/net/udp sample, as it seemed to be the simplest sample to begin with.

    I will post the code below, but here are the important changes:

    - CONFIG_NET_SOCKETS_OFFLOAD and CONFIG_NET_SOCKETS_OFFLOAD_DISPATCHER must be enabled.
    - The CONFIG_NET_IF_MAX_IPV4_COUNT must be set to 2, since we have two interfaces which require
      their IPs.
    - Various CONFIG_WIFI_* and CONFIG_WIFI_ESP_AT_* options must be enabled.
    - You can't use the NET_EVENT_L4_CONNECTED event as a trigger to create both WiFi and LTE
      sockets, since it is emitted only once when the first interface is connected. Instead the
      NET_EVENT_IF_UP event must be used, as it is emitted for each interface.

    We thought that the above changes would be enough, however, during our initial tests we saw that:

    - If only esp32 wifi and socket dispatcher are enabled, then the setsockopt and the following
      connect calls are successful.
    - If only nRF91's LTE and socket dispatcher are enabled, then the setsockopt and the following
      connect calls are successful.
    - If all three are enabled and the setsockopt is given the wlan0 interface, then the nRF91
      socket implementation is used instead.

    The last case was very tricky to figure out and we had to dig into the Zephyr source code to find
    out what exactly was happening.

    By using the debugger and inspecting the
    socket_dispatcher.c we found out that:

    - When the setsockopt is called with the SO_BINDTODEVICE option and the wlan0 interface name
      the following functions get called:

      - sock_dispatch_setsockopt_vmeth
      - sock_dispatch_native
      - sock_dispatch_find

    - sock_dispatch_find iterates through registered sockets. The order of the sockets is determined
      by their priority that was given when they were registered with NET_SOCKET_REGISTER.
    - nRF91's LTE uses an offloaded socket interface, which has a default priority of 40.
    - ESP32 AT uses a native Zephyr socket interface, which has a default priority of 50.

    This means that sock_dispatch_find will always return the nRF91's LTE socket implementation first.

    The solution was to set the CONFIG_NET_SOCKETS_PRIORITY_DEFAULT to a value lower than 40, so that
    the native Zephyr socket implementation is used by default.

    I find it interesting that around line 300 there is a check that decides how to handle the socket
    creation based on whether the interface is native or offloaded. Even if we are requesting a socket
    with a native interface, the offloaded interfaces can be used if their priority is higher.

    I feel like this is a bug, but I am not sure.

    We will attempt to do the same with secure sockets (TLS/DTLS).

    Below are all the relevant files, if someone uses this don't forget to adjust the overlay and the
    WiFi SSID and password. Keep in mind, we used the nrf/samples/net/udp (NCS version 2.5.0) as a basis for this.

    main.c:

    #include <zephyr/kernel.h>
    #include <zephyr/logging/log.h>
    #include <zephyr/logging/log_ctrl.h>
    #include <zephyr/sys/reboot.h>
    
    #include <zephyr/net/conn_mgr_connectivity.h>
    #include <zephyr/net/conn_mgr_monitor.h>
    #include <zephyr/net/net_if.h>
    #include <zephyr/net/socket.h>
    #include <zephyr/net/wifi_mgmt.h>
    
    #include <stdio.h>
    
    LOG_MODULE_REGISTER(udp_sample, CONFIG_UDP_SAMPLE_LOG_LEVEL);
    
    #define UDP_IP_HEADER_SIZE 28
    
    #define CONN_LAYER_EVENT_MASK (NET_EVENT_CONN_IF_FATAL_ERROR)
    
    static struct net_mgmt_event_callback conn_cb;
    static struct net_mgmt_event_callback iface_up_cb;
    
    static int lte_fd;
    static int wifi_fd;
    
    static struct sockaddr_storage host_addr;
    
    /* Forward declarations */
    static void wifi_work_fn(struct k_work *work);
    
    /* Work item used to schedule periodic UDP transmissions. */
    static K_WORK_DELAYABLE_DEFINE(wifi_work, wifi_work_fn);
    
    static void lte_work_fn(struct k_work *work);
    
    static K_WORK_DELAYABLE_DEFINE(lte_work, lte_work_fn);
    
    static void wifi_work_fn(struct k_work *work)
    {
    	int err;
    	char buffer[] = "wifi";
    
    	LOG_INF("WIFI: Transmitting UDP/IP payload of %d bytes to the "
    		"IP address %s, port number %d",
    		sizeof(buffer) + UDP_IP_HEADER_SIZE, CONFIG_SERVER_ADDRESS, CONFIG_SERVER_PORT);
    
    	err = send(wifi_fd, buffer, sizeof(buffer), 0);
    	if (err < 0) {
    		LOG_ERR("Failed to transmit UDP packet, %d", -errno);
    		return;
    	}
    
    	(void)k_work_reschedule(&wifi_work, K_SECONDS(5));
    }
    
    static void lte_work_fn(struct k_work *work)
    {
    	int err;
    	char buffer[] = "lte";
    
    	LOG_INF("LTE: Transmitting UDP/IP payload of %d bytes to the "
    		"IP address %s, port number %d",
    		sizeof(buffer) + UDP_IP_HEADER_SIZE, CONFIG_SERVER_ADDRESS, CONFIG_SERVER_PORT);
    
    	err = send(lte_fd, buffer, sizeof(buffer), 0);
    	if (err < 0) {
    		LOG_ERR("Failed to transmit UDP packet, %d", -errno);
    		return;
    	}
    
    	(void)k_work_reschedule(&lte_work, K_SECONDS(5));
    }
    
    static int server_addr_construct(void)
    {
    	int err;
    
    	struct sockaddr_in *server4 = ((struct sockaddr_in *)&host_addr);
    
    	server4->sin_family = AF_INET;
    	server4->sin_port = htons(CONFIG_SERVER_PORT);
    
    	err = inet_pton(AF_INET, CONFIG_SERVER_ADDRESS, &server4->sin_addr);
    	if (err < 0) {
    		LOG_ERR("inet_pton, error: %d", -errno);
    		return err;
    	}
    
    	return 0;
    }
    
    static int prv_socket_create(struct net_if *iface)
    {
    	LOG_WRN("Create socket for iface %s", iface->config.name);
    
    	int fd = socket(AF_INET, SOCK_DGRAM, IPPROTO_UDP);
    	if (fd < 0) {
    		LOG_ERR("Failed to create UDP socket: %d", -errno);
    		return -1;
    	}
    
    	struct ifreq ifreq = {.ifr_name = {0}};
    
    	strncpy(ifreq.ifr_name, iface->config.name, sizeof(ifreq.ifr_name));
    
    	setsockopt(fd, SOL_SOCKET, SO_BINDTODEVICE, &ifreq, sizeof(ifreq));
    
    	return fd;
    }
    
    static int prv_connect(int fd)
    {
    	int err;
    
    	err = connect(fd, (struct sockaddr *)&host_addr, sizeof(struct sockaddr_in));
    	if (err < 0) {
    		LOG_ERR("Connect, error: %d", -errno);
    		return err;
    	}
    
    	return 0;
    }
    
    static void iface_up_handler(struct net_mgmt_event_callback *cb, uint32_t mgmt_event,
    			     struct net_if *iface)
    {
    	LOG_WRN("Iface name %s", iface->config.name);
    
    	int fd = prv_socket_create(iface);
    
    	if (mgmt_event == NET_EVENT_IF_UP) {
    		if (strcmp(iface->config.name, "wlan0") == 0) {
    			wifi_fd = fd;
    			prv_connect(wifi_fd);
    			k_work_reschedule(&wifi_work, K_NO_WAIT);
    		} else if (strcmp(iface->config.name, "net0") == 0) {
    			lte_fd = fd;
    			prv_connect(lte_fd);
    			k_work_reschedule(&lte_work, K_NO_WAIT);
    		}
    	}
    }
    
    static void connectivity_event_handler(struct net_mgmt_event_callback *cb, uint32_t event,
    				       struct net_if *iface)
    {
    	if (event == NET_EVENT_CONN_IF_FATAL_ERROR) {
    		LOG_ERR("NET_EVENT_CONN_IF_FATAL_ERROR");
    		return;
    	}
    }
    
    int wifi_connect(char *ap_name, char *psk)
    {
    	int err;
    	struct net_if *iface = net_if_get_default();
    
    	/* Set up connection parameters */
    	static struct wifi_connect_req_params cnx_params;
    	cnx_params.ssid = (uint8_t *)ap_name;
    	cnx_params.ssid_length = strlen(ap_name);
    
    	/* No need to select channel, so pass any */
    	cnx_params.channel = WIFI_CHANNEL_ANY;
    
    	/* if passkey is NULL, the AP is unsecured */
    	if (!psk) {
    		cnx_params.security = WIFI_SECURITY_TYPE_NONE;
    	} else {
    		cnx_params.psk = (uint8_t *)psk;
    		cnx_params.psk_length = strlen(psk);
    		cnx_params.security = WIFI_SECURITY_TYPE_PSK;
    	}
    
    	err = net_mgmt(NET_REQUEST_WIFI_CONNECT, iface, &cnx_params,
    		       sizeof(struct wifi_connect_req_params));
    	if (err) {
    		LOG_ERR("Connection request failed, err: %d", err);
    		return -ENOEXEC;
    	}
    
    	return 0;
    }
    
    int main(void)
    {
    	int err;
    
    	LOG_INF("UDP sample has started");
    
    	net_mgmt_init_event_callback(&conn_cb, connectivity_event_handler, CONN_LAYER_EVENT_MASK);
    	net_mgmt_add_event_callback(&conn_cb);
    
    	net_mgmt_init_event_callback(&iface_up_cb, iface_up_handler, NET_EVENT_IF_UP);
    	net_mgmt_add_event_callback(&iface_up_cb);
    
    	err = server_addr_construct();
    	if (err) {
    		LOG_INF("server_addr_construct, error: %d", err);
    		return err;
    	}
    
    	LOG_INF("Bringing network interface up and connecting to the network");
    
    	wifi_connect(CONFIG_AP_SSID, CONFIG_AP_PSK);
    
    	err = conn_mgr_all_if_up(true);
    	if (err) {
    		LOG_ERR("conn_mgr_all_if_up, error: %d", err);
    		return err;
    	}
    
    	return 0;
    }
    


    prj.conf:
    # General configurations
    CONFIG_LOG=y
    CONFIG_LOG_INFO_COLOR_GREEN=y
    CONFIG_CONSOLE=y
    CONFIG_RTT_CONSOLE=y
    CONFIG_USE_SEGGER_RTT=y
    
    # CONFIG_SHELL=y
    # CONFIG_SHELL_LOG_BACKEND=y
    # CONFIG_SHELL_BACKEND_RTT=y
    # CONFIG_SEGGER_RTT_BUFFER_SIZE_DOWN=128
    
    CONFIG_DEBUG_OPTIMIZATIONS=y
    CONFIG_DEBUG_THREAD_INFO=y
    CONFIG_DEBUG_INFO=y
    
    # For communication with ESP-32
    CONFIG_SERIAL=y
    
    # Network
    CONFIG_NETWORKING=y
    CONFIG_NET_NATIVE=y
    CONFIG_NET_IPV4=y
    CONFIG_NET_SOCKETS=y
    CONFIG_NET_CONNECTION_MANAGER=y
    CONFIG_NET_CONNECTION_MANAGER_MONITOR_STACK_SIZE=4096
    # Increase this to avoid stack overflow in net_mgmt
    CONFIG_NET_MGMT_EVENT_STACK_SIZE=4096
    
    # Increase this to allow more than one IPv4 address
    CONFIG_NET_IF_MAX_IPV4_COUNT=2
    
    # Heap and stacks
    CONFIG_HEAP_MEM_POOL_SIZE=1024
    CONFIG_MAIN_STACK_SIZE=4096
    CONFIG_SYSTEM_WORKQUEUE_STACK_SIZE=2048
    
    # ESP specific flags
    CONFIG_WIFI_LOG_LEVEL_DBG=y
    CONFIG_WIFI=y
    CONFIG_WIFI_ESP_AT=y
    CONFIG_WIFI_ESP_AT_SCAN_RESULT_RSSI_ORDERED=y
    CONFIG_WIFI_ESP_AT_PASSIVE_MODE=y
    CONFIG_WIFI_ESP_AT_IP_DHCP=y
    CONFIG_WIFI_ESP_AT_VERSION_2_0=y
    CONFIG_WIFI_ESP_AT_DNS_USE=y
    # For ESP-AT DNS
    CONFIG_DNS_RESOLVER=y
    
    # Offload and dispatcher
    CONFIG_NET_SOCKETS_OFFLOAD=y
    CONFIG_NET_SOCKETS_OFFLOAD_DISPATCHER=y
    
    # Setting the priority of native sockets to less than the offloaded sockets,
    # so that ESP does NOT use nrf91 socket offloading.
    # Default priority is 50
    CONFIG_NET_SOCKETS_PRIORITY_DEFAULT=2
    
    # Modem LIB forces nrf91 socket offloading
    CONFIG_NRF_MODEM_LIB=y
    
    CONFIG_NET_L2_ETHERNET=y
    CONFIG_NET_UDP=y
    CONFIG_NET_DHCPV4=y
    
    ############################################################# LTE #############################################################
    # Modem related configurations
    CONFIG_LTE_CONNECTIVITY_LOG_LEVEL_DBG=y
    CONFIG_NRF_MODEM_LIB_NET_IF_AUTO_START=n
    CONFIG_NRF_MODEM_LIB_ON_FAULT_APPLICATION_SPECIFIC=y
    
    # Disable Duplicate Address Detection (DAD)
    # due to not being properly implemented for offloaded interfaces.
    CONFIG_NET_IPV6_NBR_CACHE=n
    CONFIG_NET_IPV6_MLD=n
    
    # Zephyr NET Connection Manager and Connectivity layer.
    CONFIG_LTE_CONNECTIVITY=y
    CONFIG_LTE_CONNECTIVITY_AUTO_DOWN=n
    
    # PSM
    CONFIG_LTE_PSM_REQ=y
    CONFIG_LTE_PSM_REQ_RPTAU="00100001"
    CONFIG_LTE_PSM_REQ_RAT="00000000"
    
    # eDRX
    CONFIG_LTE_EDRX_REQ=n
    CONFIG_LTE_EDRX_REQ_VALUE_LTE_M="1001"
    
    # RAI
    CONFIG_LTE_RAI_REQ=n
    CONFIG_LTE_RAI_REQ_VALUE="4"
    
    CONFIG_AT_MONITOR_HEAP_SIZE=1024
    


    Parameters such as WiFi AP, SSID, server address, and port are defined in Kconfig.

  • Additionally, we received an answer from rlubos on Zephyr's discord:

    What you described in the devzone ticket did sound like a bug , as with SO_BINDTODEVICE being used with dispatcher, socket priorities should not be used at all, given proper interface names are provided (which seems to be true in your case), so it shouldn't be needed to change priorities.

    It turned out that in NCS 2.5.0 we indeed had a bug, incorrect macro was used to register the offload socket implementation, so the dispatcher wrongly treated the nRF91 LTE interface as a native one. This was fixed a few months ago in sdk-nrf:
    github.com/.../f26d29161dd3553ee68ca7ed77704933a58ad994

    Cherry-picking this commit into v2.5.0-branch (or switching to NCS 2.6.0) should resolve the issue you see.


Related