How-To-Tutorials · September 23, 2025

How to Implement Wi-Fi MQTT Logging in Zephyr on nRF52840 with JSON Payload

how to implement wi fi mqtt logging in zephyr on nrf52840 with json payload

The Setup: nRF52840, Wi-Fi, and MQTT

The nRF52840 is a BLE powerhouse, but it doesn't have built-in Wi-Fi. To get MQTT over Wi-Fi, you'll pair it with a companion module — typically the nRF7002 Wi-Fi companion IC on Nordic's development kits (like the nRF7002 DK or an nRF7002 EK shield). Zephyr RTOS (v3.7+) has native drivers for this combo, plus a solid MQTT client library and JSON support baked in.

This guide walks through building a Zephyr application that connects to Wi-Fi, formats sensor readings as JSON, and publishes them to an MQTT broker. It's a pattern you'll use constantly for IoT logging and monitoring.

Prerequisites

  • C programming experience and basic embedded systems knowledge
  • Familiarity with Zephyr RTOS concepts (devicetree, Kconfig, west build system)
  • nRF52840 DK with nRF7002 EK shield (or nRF7002 DK)
  • Wi-Fi network credentials
  • An MQTT broker running and accessible on your network (Mosquitto is the easy choice for local testing)
  • nRF Connect SDK v2.7+ installed (includes Zephyr v3.7+) with the nRF toolchain

Parts/Tools

  • nRF52840 DK + nRF7002 EK shield (or nRF7002 DK)
  • USB cable for programming and serial output
  • Computer with nRF Connect SDK installed (includes west, Zephyr, and the nRF toolchain)
  • MQTT broker (Mosquitto for local testing, or a cloud broker like HiveMQ or AWS IoT)

Steps

  1. Set Up Your Workspace

    If you haven't already, install the nRF Connect SDK (v2.7+), which bundles Zephyr v3.7+ and all the Nordic-specific drivers. The easiest path is through the nRF Connect for VS Code extension — it handles the SDK installation, toolchain, and west initialization for you.

    Create a new application directory inside your workspace:

    mkdir mqtt_json_logger
    cd mqtt_json_logger

    You'll need three files: prj.conf for Kconfig, CMakeLists.txt for the build, and src/main.c for the application code.

  2. Configure the Project (prj.conf)

    This is where you enable the subsystems you need. Here's a working config for Wi-Fi + MQTT + JSON:

    # Networking stack
    CONFIG_NETWORKING=y
    CONFIG_NET_TCP=y
    CONFIG_NET_SOCKETS=y
    CONFIG_NET_CONNECTION_MANAGER=y
    
    # Wi-Fi (nRF7002)
    CONFIG_WIFI=y
    CONFIG_WIFI_NRF700X=y
    CONFIG_NET_L2_WIFI_MGMT=y
    CONFIG_NET_DHCPV4=y
    
    # MQTT
    CONFIG_MQTT_LIB=y
    CONFIG_MQTT_CLEAN_SESSION=y
    
    # JSON
    CONFIG_CJSON_LIB=y
    
    # Logging
    CONFIG_LOG=y
    CONFIG_LOG_DEFAULT_LEVEL=3
    
    # DNS (needed to resolve broker hostname)
    CONFIG_DNS_RESOLVER=y

    A couple of notes: CONFIG_WIFI_NRF700X is the current Kconfig symbol for the nRF7002 Wi-Fi driver in nRF Connect SDK. If you're using a different Wi-Fi module (like an ESP32 as a Wi-Fi co-processor), the Kconfig symbols will differ. Also, we're enabling cJSON here — Zephyr ships it as an optional module and it's the most convenient way to build JSON payloads.

  3. Write the Application Code

    Create src/main.c with the following structure. This is simplified to show the flow — a production app would need better error handling and reconnection logic.

    Start with the includes and MQTT client setup:

    #include <zephyr/kernel.h>
    #include <zephyr/net/mqtt.h>
    #include <zephyr/net/socket.h>
    #include <zephyr/net/wifi_mgmt.h>
    #include <zephyr/net/net_mgmt.h>
    #include <cJSON.h>
    
    #define MQTT_BROKER_ADDR "192.168.1.100"
    #define MQTT_BROKER_PORT 1883
    #define MQTT_TOPIC "sensors/data"
    
    static struct mqtt_client client;
    static uint8_t rx_buffer[256];
    static uint8_t tx_buffer[256];
    static struct sockaddr_storage broker;

    Next, the Wi-Fi connection handler. Zephyr's net management API fires events when the connection state changes:

    static K_SEM_DEFINE(wifi_connected_sem, 0, 1);
    
    static void wifi_mgmt_handler(struct net_mgmt_event_callback *cb,
                                  uint32_t mgmt_event, struct net_if *iface) {
        if (mgmt_event == NET_EVENT_WIFI_CONNECT_RESULT) {
            k_sem_give(&wifi_connected_sem);
        }
    }
    
    static void connect_wifi(void) {
        struct net_if *iface = net_if_get_default();
        struct wifi_connect_req_params params = {
            .ssid = "YourSSID",
            .ssid_length = strlen("YourSSID"),
            .psk = "YourPassword",
            .psk_length = strlen("YourPassword"),
            .security = WIFI_SECURITY_TYPE_PSK,
        };
        net_mgmt(NET_REQUEST_WIFI_CONNECT, iface, &params, sizeof(params));
        k_sem_take(&wifi_connected_sem, K_FOREVER);
    }

    The JSON payload builder — this is where cJSON shines over hand-rolling snprintf'd JSON strings:

    static char *create_json_payload(const char *sensor_name, float value) {
        cJSON *root = cJSON_CreateObject();
        cJSON_AddStringToObject(root, "sensor", sensor_name);
        cJSON_AddNumberToObject(root, "value", value);
        cJSON_AddNumberToObject(root, "timestamp", k_uptime_get() / 1000);
        char *json_str = cJSON_PrintUnformatted(root);  // No whitespace, saves bytes
        cJSON_Delete(root);
        return json_str;
    }

    Use cJSON_PrintUnformatted() instead of cJSON_Print() on embedded targets. The pretty-printed version wastes RAM on whitespace nobody will see.

    MQTT client initialization and publish function:

    static void mqtt_evt_handler(struct mqtt_client *c,
                                 const struct mqtt_evt *evt) {
        switch (evt->type) {
        case MQTT_EVT_CONNACK:
            printk("MQTT connected\n");
            break;
        case MQTT_EVT_DISCONNECT:
            printk("MQTT disconnected\n");
            break;
        default:
            break;
        }
    }
    
    static void init_mqtt(void) {
        struct sockaddr_in *broker4 = (struct sockaddr_in *)&broker;
        broker4->sin_family = AF_INET;
        broker4->sin_port = htons(MQTT_BROKER_PORT);
        net_addr_pton(AF_INET, MQTT_BROKER_ADDR, &broker4->sin_addr);
    
        mqtt_client_init(&client);
        client.broker = &broker;
        client.evt_cb = mqtt_evt_handler;
        client.rx_buf = rx_buffer;
        client.rx_buf_size = sizeof(rx_buffer);
        client.tx_buf = tx_buffer;
        client.tx_buf_size = sizeof(tx_buffer);
        client.protocol_version = MQTT_VERSION_3_1_1;
    }
    
    static void publish_data(const char *sensor, float value) {
        char *payload = create_json_payload(sensor, value);
        struct mqtt_publish_param param = {
            .message.topic.qos = MQTT_QOS_1_AT_LEAST_ONCE,
            .message.topic.topic.utf8 = MQTT_TOPIC,
            .message.topic.topic.size = strlen(MQTT_TOPIC),
            .message.payload.data = payload,
            .message.payload.len = strlen(payload),
            .message_id = sys_rand32_get(),
        };
        mqtt_publish(&client, &param);
        cJSON_free(payload);
    }

    Finally, the main function ties it all together:

    int main(void) {
        static struct net_mgmt_event_callback wifi_cb;
        net_mgmt_init_event_callback(&wifi_cb, wifi_mgmt_handler,
                                     NET_EVENT_WIFI_CONNECT_RESULT);
        net_mgmt_add_event_callback(&wifi_cb);
    
        connect_wifi();
        printk("Wi-Fi connected\n");
    
        init_mqtt();
        mqtt_connect(&client);
    
        while (1) {
            mqtt_input(&client);   // Process incoming MQTT data
            mqtt_live(&client);    // Keep-alive maintenance
    
            float temp = 23.5f;    // Replace with actual sensor read
            publish_data("temperature", temp);
    
            k_sleep(K_SECONDS(30));
        }
        return 0;
    }

    In a real project, you'd replace that hardcoded 23.5 with an actual sensor driver call, add reconnection logic for both Wi-Fi and MQTT, and probably use a dedicated thread for the MQTT event loop.

  4. Build and Flash

    Build for the nRF52840 DK with the nRF7002 EK shield:

    west build -b nrf52840dk/nrf52840 -- -DSHIELD=nrf7002ek

    Flash it:

    west flash

    Open a serial terminal (115200 baud) to see the log output. You should see Wi-Fi association, DHCP address assignment, MQTT connection, and then periodic publish messages.

Troubleshooting

  • Wi-Fi won't connect: Check your SSID and password. Also verify the nRF7002 EK shield is properly seated and that you passed the -DSHIELD=nrf7002ek flag during build. Without the shield overlay, the Wi-Fi driver won't initialize.
  • MQTT broker connection refused: Make sure your broker is listening on the right port and that there are no firewall rules blocking the connection. Test from your PC first with mosquitto_pub to rule out broker issues.
  • JSON parsing errors or garbled output: Validate your JSON with a tool like jq on the receiving end. If you're seeing truncated payloads, increase your TX buffer size.
  • Build errors about missing Kconfig symbols: You're probably using an older nRF Connect SDK version. CONFIG_WIFI_NRF700X replaced the older CONFIG_WIFI_NRF7002 symbol. Update to nRF Connect SDK v2.7+ or check the migration guide for your version.
  • Enable more verbose logging by adding these to prj.conf:
    CONFIG_NET_LOG=y
    CONFIG_MQTT_LOG_LEVEL_DBG=y
    CONFIG_WIFI_LOG_LEVEL_DBG=y

Where to Go Next

You've got a working MQTT JSON logger on Zephyr. For production, you'll want to add TLS 1.3 to the MQTT connection (Zephyr supports this via the mbedTLS subsystem — enable CONFIG_MQTT_LIB_TLS and configure certificates). Add reconnection logic with exponential backoff for both Wi-Fi and MQTT. And consider switching from cJSON to Zephyr's built-in json library for lower memory usage if you're working with predictable, fixed-schema payloads. The cJSON approach is more flexible but allocates from the heap, which isn't ideal on memory-constrained devices.