Smart Power Box Project Tech Stack Analysis (Key Points: Thread Mailbox, FrameBuffer, MQTT)

This project is a smart power monitoring device based on an embedded Linux system. Its core implementation covers the entire process of data collection → internal communication → local display → remote reporting. Below, we break down the fundamental principles and project implementation of the core technical modules in conjunction with the code.

I. Overview of the Tech Stack

Core technical tools/frameworks used in the project:

  • Language/Foundation: C, POSIX threads (pthread), synchronization/mutex (mutex locks/condition variables);
  • Local Display: Linux FrameBuffer (framebuffer), UTF-8 font library;
  • Thread Communication: “Thread Mailbox” based on a ring buffer;
  • Remote Communication: MQTT protocol (paho-mqtt3c library), SQLite3 (offline message caching);
  • Data Processing: Mean filtering, median filtering (to reduce noise in collected data);
  • Build Tool: Makefile (compile and link management).

II. Thread Mailbox (Mailbox): Core of Asynchronous Multi-thread Communication

1. Basic Concepts

The thread mailbox is a classic implementation of the producer-consumer model, solving the problem of secure data transfer between threads using a combination of a ring buffer + mutex lock + condition variables:

  • Ring Buffer: A fixed-size buffer with head and tail pointers that cycle, efficiently utilizing memory;
  • Mutex Lock (pthread_mutex_t): Ensures atomic operations (enqueue/dequeue) on the queue, preventing thread contention;
  • Condition Variable (pthread_cond_t): Implements blocking logic for “wait if empty” or “wait if full,” replacing busy-waiting and reducing CPU usage.

2. Implementation in the Project (Core Code Snippets)

(1) Data Structure Definition (mailbox.h)
// Message type enum (adapts to different collected data)  
typedef enum {  
    MSG_METER_DATA = 1,  // Meter data (voltage/current)  
    MSG_TEMP_DATA,       // Temperature data  
    MSG_ALARM,           // Alarm data  
    MSG_NET_STATE        // Network status  
} msg_type_t;  

// Generic message body: Supports multiple data types  
typedef struct {  
    msg_type_t type;      // Message type  
    time_t     timestamp; // Timestamp  
    union {               // Union type: Saves memory by storing only one type of data  
        struct { float voltage; float current; } meter; // Meter  
        struct { float temperature; } temp;             // Temperature  
        struct { int level; } alarm;                    // Alarm  
        struct { int online; } net;                     // Network  
    } data;  
} message_t;  
(2) Core Ring Buffer Implementation (mailbox.c)
#define MAILBOX_SIZE 64          // Queue capacity (fixed size, adapted to embedded memory constraints)  
static message_t mailbox[MAILBOX_SIZE]; // Queue buffer  
static int head = 0, tail = 0, count = 0; // Head pointer (dequeue), tail pointer (enqueue), element count  
static pthread_mutex_t mtx;              // Mutex lock  
static pthread_cond_t  not_empty;        // Queue not empty condition (consumer waits)  
static pthread_cond_t  not_full;         // Queue not full condition (producer waits)  

// Producer: Sends a message (called by threads like meter/temperature collection)  
int mailbox_send(const message_t *msg) {  
    pthread_mutex_lock(&mtx);  
    // If queue is full, block until space is available  
    while (count == MAILBOX_SIZE) {  
        pthread_cond_wait(&not_full, &mtx);  
    }  
    // Enqueue message  
    memcpy(&mailbox[tail], msg, sizeof(message_t));  
    tail = (tail + 1) % MAILBOX_SIZE;  
    count++;  
    // Wake up waiting consumers  
    pthread_cond_signal(&not_empty);  
    pthread_mutex_unlock(&mtx);  
    return 0;  
}  

// Consumer: Receives a message (called by the dispatcher thread)  
int mailbox_recv(message_t *msg) {  
    pthread_mutex_lock(&mtx);  
    // If queue is empty, block until a message arrives  
    while (count == 0) {  
        pthread_cond_wait(&not_empty, &mtx);  
    }  
    // Dequeue message  
    memcpy(msg, &mailbox[head], sizeof(message_t));  
    head = (head + 1) % MAILBOX_SIZE;  
    count--;  
    // Wake up waiting producers  
    pthread_cond_signal(&not_full);  
    pthread_mutex_unlock(&mtx);  
    return 0;  
}  
(3) Project Use Cases
  • Producer Threads: meter_task (simulates voltage/current collection), temp_task (simulates temperature collection), periodically generate messages and send them to the mailbox;
  • Consumer Thread: dispatcher_task, continuously receives messages from the mailbox, processes filtering and alarm logic, and updates the global system state.

3. Extended Knowledge

(1) Core Optimization Directions
  • Non-blocking Mode: Add timeout parameters (e.g., mailbox_send_timeout) to avoid indefinite thread blocking;
  • Dynamic Queue: Replace fixed MAILBOX_SIZE with dynamic memory allocation for queue expansion;
  • Priority Queue: Assign high priority to alarm messages for faster processing;
  • Exception Handling: Support “discard old messages” or “overwrite latest message” when the queue is full, adapting to different business scenarios.
(2) Applicable Scenarios

Asynchronous multi-thread communication in embedded systems (e.g., decoupling between sensor collection, data processing, and device control threads). Compared to pipes/message queues, it is lighter and avoids kernel-mode switching overhead.

III. FrameBuffer: Core of Linux Local Graphics Display

1. Basic Concepts

FrameBuffer is a graphics hardware abstraction layer provided by the Linux kernel, abstracting display devices (LCD/screen) as a memory region that can be directly read/written:

  • Memory Mapping: Maps video memory to user space via mmap, eliminating the need to manipulate hardware registers;
  • Pixel Format Agnostic: Supports formats like RGB888 (32-bit), RGB565 (16-bit), adapting to different screens;
  • Efficient Operations: Directly modifies pixel values in memory for graphics/text rendering.

2. Implementation in the Project (Core Code Snippets)

(1) Initialization and Video Memory Mapping (framebuffer.c)
void *pmem;                  // User-space pointer after video memory mapping  
struct fb_var_screeninfo vinf; // Screen parameters (resolution, bit depth)  

int init_fb(char *devname) {  
    // 1. Open the framebuffer device (e.g., /dev/fb0)  
    int fd = open(devname, O_RDWR);  
    // 2. Get screen parameters (resolution, bit depth)  
    ioctl(fd, FBIOGET_VSCREENINFO, &vinf);  
    // 3. Map video memory to user space  
    size_t len = vinf.xres_virtual * vinf.yres_virtual * vinf.bits_per_pixel / 8;  
    pmem = mmap(NULL, len, PROT_READ | PROT_WRITE, MAP_SHARED, fd, 0);  
    return fd;  
}  
(2) Core Drawing Functions
  • Pixel Drawing: draw_point (adapts to RGB888/RGB565 formats);
  • Basic Shapes: draw_rectangle (rectangle), draw_circle (circle), draw_h_line (horizontal line);
  • Text/Images: draw_bmp (BMP image display), draw_utf8_str (UTF-8 Chinese display, using a font library);
  • Business Logic: The fb_task thread renders real-time device status (temperature, voltage, current), with “ALARM” text blinking during alerts.
(3) Business-Level Rendering (fb_task in main.c)
// Draw temperature data (red for alarm, green for normal)  
snprintf(buf, sizeof(buf), "TEMP: %.1f C", g_state.temperature);  
draw_utf8_str(&utf8_info, 50, 120, buf,  
              g_state.temp_alarm ? 0x00FF0000 : 0x0000FF00, 0);  

// Alarm state blinking  
if (alarm_any) {  
    if (alarm_blink_on()) {  
        draw_utf8_str(&utf8_info, 50, 320, "STATUS: ALARM", 0x00FF0000, 0);  
    }  
} else {  
    draw_utf8_str(&utf8_info, 50, 320, "STATUS: NORMAL", 0x0000FF00, 0);  
}  

3. Extended Knowledge

(1) Performance Optimization
  • Double Buffering: Front buffer (display) + back buffer (rendering), avoiding screen tearing;
  • Partial Refresh: Only updates changed regions (e.g., temperature values instead of the entire screen), reducing CPU usage;
  • Hardware Acceleration: Uses GPU/2D acceleration modules (e.g., libdrm) to improve complex graphics rendering efficiency.
(2) Advanced Feature Extensions
  • Transparency Support: Extends pixel format to RGBA for semi-transparent effects;
  • Touchscreen Interaction: Integrates with Linux input subsystem (/dev/input/eventX) for touch controls;
  • Multi-Resolution Adaptation: Dynamically adjusts drawing coordinates via vinf.xres/vinf.yres to fit different screens.

IV. MQTT: The Core of IoT Remote Communication

1. Basic Concepts

MQTT (Message Queuing Telemetry Transport) is a lightweight publish/subscribe protocol based on TCP/IP, designed for low-bandwidth, high-latency, and unreliable networks (IoT scenarios):

  • Publish/Subscribe Model: Clients publish messages to “Topics,” and clients subscribed to those topics receive the messages.
  • QoS (Quality of Service): 0 (at most once), 1 (at least once), 2 (exactly once).
  • Keep-Alive Mechanism: Clients periodically send heartbeat packets to maintain the connection with the server.
  • Lightweight: The minimum packet header is 2 bytes, making it suitable for embedded devices.

2. Implementation in the Project (Core Code Snippets)

(1) Basic Configuration and Initialization (mqtt_cfg.h + mqtt_client.c)
// MQTT Server Configuration (Aliyun IoT Platform)  
#define MQTT_ADDRESS   "tcp://183.230.40.96:1883"  
#define PRODUCT_ID     "Uherm6f889"  
#define DEVICE_NAME    "power_box"  

// Client Initialization  
int mqtt_client_init(void) {  
    MQTTClient_create(&client, MQTT_ADDRESS, CLIENT_ID, MQTTCLIENT_PERSISTENCE_NONE, NULL);  
    // Set Callbacks (Connection Lost, Message Arrived, Delivery Complete)  
    MQTTClient_setCallbacks(client, NULL, connlost, msgarrvd, delivered);  
    // Connect to Server (Set Username/Password, Keep-Alive Time)  
    MQTTClient_connect(client, &conn_opts);  
    // Subscribe to Topic (Receive Platform Replies)  
    MQTTClient_subscribe(client, sub_topic, MQTT_QOS);  
    return 0;  
}  
(2) Message Publishing and Offline Caching
// Publish Device Status (Voltage/Current/Temperature/Alarm)  
int mqtt_client_publish_state(const system_state_t *st) {  
    // Format JSON Payload  
    snprintf(payload, sizeof(payload),  
        "{\"id\":\"%d\",\"version\":\"1.0\",\"params\":{"  
        "\"voltage\":{\"value\":%.1f,\"time\":%lld},"  
        "\"temperature\":{\"value\":%.1f,\"time\":%lld},"  
        "\"temp_alarm\":{\"value\":%s,\"time\":%lld}"  
        "}}", msg_id++, st->voltage, ts_ms, st->temperature, ts_ms,  
        st->temp_alarm ? "true" : "false", ts_ms);  
    // Publish Message  
    int rc = MQTTClient_publishMessage(client, pub_topic, &msg, NULL);  
    // Publish Failure (Offline): Write to SQLite Cache  
    if (rc != MQTTCLIENT_SUCCESS) {  
        mqtt_cache_db_push(payload);  
        mqtt_connected = 0;  
        return -1;  
    }  
    return 0;  
}  

// SQLite Cache Reconnection Retransmission (mqtt_sqlite_cache.c)  
static void mqtt_cache_flush_sqlite(void) {  
    char payload[MQTT_PAYLOAD_MAX];  
    while (1) {  
        // Retrieve Oldest Message from Cache  
        int ret = mqtt_cache_db_pop(payload, sizeof(payload));  
        if (ret != 0) break;  
        // Retransmit Message  
        MQTTClient_publishMessage(client, pub_topic, &msg, &token);  
    }  
}  
(3) Reconnection
int mqtt_reconnect(void) {  
    // Reconnect to Server  
    int rc = MQTTClient_connect(client, &conn_opts);  
    if (rc != MQTTCLIENT_SUCCESS) return -1;  
    // Retransmit Cached Messages After Reconnection  
    mqtt_cache_flush_sqlite();  
    return 0;  
}  

3. Extended Knowledge

(1) Project Extension: Offline Cache Strategy

Implemented message persistence locally using SQLite3:

  • Offline: mqtt_cache_db_push writes messages to the database, automatically clearing old data exceeding 1000 entries.
  • Reconnection: mqtt_cache_flush_sqlite retrieves and retransmits cached messages one by one to ensure no message loss.
(2) Advanced Features
  • QoS Upgrade: The project uses QoS 0 but can upgrade to QoS 1 (at least once) to ensure message delivery.
  • TLS Encryption: Use mqtts:// (MQTT over TLS) to prevent data eavesdropping/tampering.
  • Last Will Message: The server automatically sends an “offline” message if the client disconnects abnormally.
  • Batch Publishing: Combine multiple data points into a single transmission to reduce network interactions.
(3) IoT Platform Integration

The project integrates with Alibaba Cloud IoT Platform (topic format: $sys/{PRODUCT_ID}/{DEVICE_NAME}/thing/property/post). It can be extended to Huawei Cloud or Tencent Cloud IoT platforms by modifying the topic format and authentication parameters.

V. Summary

This project is a typical case study for embedded IoT devices, with technology choices tailored to the characteristics of embedded systems—“limited resources, low power consumption, high reliability”:

  • Thread Mailbox: Lightweight decoupling for multi-thread communication, with no kernel-mode overhead.
  • FrameBuffer: Hardware-independent local display solution, compatible with embedded screens.
  • MQTT + SQLite: Balances IoT low-bandwidth requirements with offline message reliability.
Logo

有“AI”的1024 = 2048,欢迎大家加入2048 AI社区

更多推荐