1. 项目概述MyJeedom 是一个专为 Jeedom 智能家居平台设计的轻量级 MQTT 客户端库其核心定位是为嵌入式设备尤其是基于 STM32、ESP32、nRF52 等 MCU 的边缘节点提供稳定、低资源占用、可裁剪的双向通信能力实现与 Jeedom 服务器的指令下发control、状态上报monitoring及事件触发event-driven闭环。它并非通用 MQTT 协议栈如 Eclipse Paho 或 Mosquitto 客户端而是深度耦合 Jeedom 的消息语义层遵循 Jeedom 定义的 Topic 命名规范、Payload 数据结构JSON Schema、QoS 策略及心跳/重连机制属于“协议栈 应用层适配器”的融合型固件组件。在嵌入式智能家居系统中MCU 节点通常受限于 Flash64–512 KB、RAM8–64 KB、功耗电池供电场景要求休眠电流 10 µA及实时性传感器采样、执行器响应需毫秒级。通用 MQTT 客户端往往因 TLS 握手开销、动态内存分配、完整 QoS2 流程或冗余功能而难以部署。MyJeedom 通过以下工程化设计规避此类瓶颈零动态内存分配所有 MQTT 报文缓冲区、连接上下文、订阅列表均在编译期静态声明避免malloc/free引发的碎片化与不确定性精简 TLS 支持仅支持预共享密钥PSK或单向证书验证server-only cert跳过耗时的 RSA 密钥交换与完整 X.509 链验证握手时间压缩至 300–800 msESP32 160 MHzJeedom 语义感知自动构造标准 Topic 路径如jeedom/core/eqLogic/123/cmd/456解析cmd类消息中的logicalId与value字段并映射到本地 GPIO/ADC/PWM 控制逻辑状态同步引擎内置本地状态缓存state cache在断网期间暂存传感器读数恢复连接后按时间戳顺序补发避免数据丢失低功耗协同提供myjeedom_enter_sleep()接口可联动 MCU 的 STOP 模式在无 MQTT 通信时关闭 UART、WiFi/BLE 模块时钟仅保留 RTC 唤醒源。该库不依赖操作系统可裸机运行Bare Metal亦可无缝集成 FreeRTOS —— 其任务调度模型明确区分myjeedom_task()负责网络 I/O 与报文解析高优先级1–5 ms 周期myjeedom_app_callback()由用户实现处理业务逻辑如if (cmd_id led_on) { HAL_GPIO_WritePin(LED_GPIO_Port, LED_Pin, GPIO_PIN_SET); }二者通过环形缓冲区解耦确保实时性。2. 核心架构与数据流2.1 分层设计模型MyJeedom 采用四层架构每层职责清晰、接口契约化层级名称关键职责典型实现方式L1硬件抽象层HAL封装底层外设驱动UART串口透传模式、WiFi/BLE 模组 AT 指令、以太网 MACmyjeedom_hal_uart_init(),myjeedom_hal_wifi_connect()L2MQTT 协议栈层MQTT v3.1.1 协议解析/序列化、CONNECT/CONNACK/PUBLISH/PUBACK 流程控制、遗嘱消息Will Message管理mqtt_packet_encode_connect(),mqtt_packet_decode_publish()L3Jeedom 适配层Topic 路由jeedom/core/eqLogic/{id}/cmd/{cmd_id}→local_cmd_handler()、Payload JSON 解析cJSON轻量版、状态缓存同步策略myjeedom_topic_route(),myjeedom_state_cache_push()L4应用接口层API提供用户调用入口初始化、连接、发布、订阅、事件回调注册myjeedom_init(),myjeedom_publish(),myjeedom_register_cmd_cb()注L2 层复用开源MQTT-Chttps://github.com/mqtt-client-c/mqtt-client-c精简分支移除了全部 POSIX 依赖与线程安全锁仅保留mqtt_pal_sendall()/mqtt_pal_recvall()两个平台相关函数由 L1 层实现。2.2 关键数据结构2.2.1 连接上下文myjeedom_context_ttypedef struct { // L1 硬件句柄 void* hal_handle; // UART handle / WiFi socket fd uint8_t net_state; // MYJEODOM_NET_DISCONNECTED, CONNECTED, CONNECTING // L2 MQTT 状态 uint16_t keepalive_sec; // Jeedom 要求默认 60s超时触发 PINGREQ uint8_t mqtt_state; // MQTT_CONNACK_RECEIVED, MQTT_PUBACK_RECEIVED, etc. // L3 Jeedom 语义 char jeedom_host[64]; // jeedom.local or IP uint16_t jeedom_port; // 1883 (non-TLS) or 8883 (TLS) char eqlogic_id[16]; // 设备在 Jeedom 中的 ID如 123 char api_key[32]; // Jeedom API Key用于认证 // L4 应用回调 myjeedom_cmd_callback_t cmd_cb; // 命令接收回调 myjeedom_event_callback_t evt_cb; // 连接/断开事件回调 } myjeedom_context_t;2.2.2 命令回调函数原型// 用户必须实现此函数处理 Jeedom 下发的指令 typedef void (*myjeedom_cmd_callback_t)( const char* logical_id, // Jeedom 中定义的逻辑ID如 temperature_sensor const char* value, // 字符串值如 25.6, on, off uint8_t value_len // value 字符串长度避免 strlen );此设计强制用户关注业务语义而非协议细节Jeedom 后台配置某设备的logicalId为livingroom_light当用户在 Jeedom Web 界面点击开关时MyJeedom 自动解析 Topicjeedom/core/eqLogic/123/cmd/456并提取logical_idlivingroom_light与valueon直接调用cmd_cb(livingroom_light, on, 2)无需用户手动解析 JSON。3. API 接口详解3.1 初始化与连接myjeedom_init()初始化上下文并注册硬件抽象层。myjeedom_context_t g_mj_ctx; void app_init(void) { // 1. 初始化硬件以 ESP32 WiFi 为例 wifi_config_t wifi_cfg { .ssid MyHomeWiFi, .password 12345678 }; myjeedom_hal_wifi_init(wifi_cfg); // 2. 构建 MyJeedom 上下文 strcpy(g_mj_ctx.jeedom_host, 192.168.1.100); g_mj_ctx.jeedom_port 1883; strcpy(g_mj_ctx.eqlogic_id, 42); strcpy(g_mj_ctx.api_key, a1b2c3d4e5f6); // 3. 注册回调 g_mj_ctx.cmd_cb app_cmd_handler; g_mj_ctx.evt_cb app_event_handler; // 4. 执行初始化 if (myjeedom_init(g_mj_ctx) ! MYJEODOM_OK) { ERROR(MyJeedom init failed); } }myjeedom_connect()建立 MQTT 连接阻塞直至成功或超时默认 10s。// 内部流程 // - 调用 myjeedom_hal_wifi_connect() 连接 AP // - DNS 解析 jeedom_host → IP // - TCP connect() 到 jeedom_port // - 发送 CONNECT 报文ClientID jeedom_42Username api_keyWill Topic jeedom/core/eqLogic/42/status // - 等待 CONNACK设置 net_state CONNECTED if (myjeedom_connect(g_mj_ctx) ! MYJEODOM_OK) { WARN(Connect to Jeedom timeout); }3.2 发布与订阅myjeedom_publish()向 Jeedom 上报设备状态自动构造 Topic 与 Payload。// 上报温度传感器数据 float temp read_temperature_sensor(); char payload[64]; snprintf(payload, sizeof(payload), {\value\:%.1f,\unit\:\°C\,\timestamp\:%lu}, temp, HAL_GetTick()); // Topic 自动生成jeedom/core/eqLogic/42/eqLogic/42 // Jeedom 要求状态上报 Topic 为 jeedom/core/eqLogic/{id}/eqLogic/{id} myjeedom_publish(g_mj_ctx, MYJEODOM_TOPIC_TYPE_STATUS, // 枚举STATUS, CMD, EVENT payload, strlen(payload));myjeedom_subscribe_cmd()订阅命令 Topic使 Jeedom 可向本设备下发指令。// 订阅 jeedom/core/eqLogic/42/cmd/ 通配符匹配所有 cmd_id // 此调用会发送 SUBSCRIBE 报文并在 L3 层建立路由表 myjeedom_subscribe_cmd(g_mj_ctx);关键机制myjeedom_subscribe_cmd()不仅发送 SUBSCRIBE还在本地维护一张cmd_route_table[]将收到的PUBLISH报文中的topic如jeedom/core/eqLogic/42/cmd/101解析出cmd_id101再查 Jeedom 后台导出的cmd_map.json需预置到 MCU Flash获取其logicalId如bedroom_fan_speed最终调用cmd_cb(bedroom_fan_speed, 3, 1)。3.3 事件回调与状态管理app_event_handler()连接状态变更通知。void app_event_handler(myjeedom_event_t event) { switch(event) { case MYJEODOM_EVENT_CONNECTED: INFO(Connected to Jeedom); // 连接成功后立即上报在线状态 myjeedom_publish(g_mj_ctx, MYJEODOM_TOPIC_TYPE_STATUS, {\status\:\online\}, 17); break; case MYJEODOM_EVENT_DISCONNECTED: WARN(Disconnected from Jeedom); // 断开时进入低功耗模式 myjeedom_enter_sleep(g_mj_ctx, 30000); // 30s 后唤醒重连 break; case MYJEODOM_EVENT_NETWORK_ERROR: ERROR(Network error, retrying...); break; } }myjeedom_state_cache_push()断网期间缓存状态恢复后自动补发。// 在传感器采样中断中调用非阻塞 void sensor_irq_handler(void) { float val adc_read(TEMP_CHANNEL); myjeedom_state_cache_push(g_mj_ctx, temperature, val, sizeof(float), MYJEODOM_DATA_TYPE_FLOAT); } // myjeedom_task() 检测到网络恢复后自动遍历缓存队列调用 publish缓存采用循环队列Ring Buffer大小可配置默认 16 条每条记录包含logical_id、raw_data、data_type、timestamp避免浮点数转字符串的 CPU 开销。4. 实际工程集成示例4.1 STM32L4 FreeRTOS 集成// FreeRTOS 任务创建 void start_myjeedom_task(void) { xTaskCreate( myjeedom_task, // L2/L3 主循环收包、解析、路由 MyJeedom, // 任务名 configMINIMAL_STACK_SIZE 256, // 栈大小需容纳 MQTT 报文缓冲最大 1KB g_mj_ctx, // 传入上下文 tskIDLE_PRIORITY 2, // 优先级高于应用任务低于中断 NULL ); } // myjeedom_task() 内部逻辑简化 void myjeedom_task(void *pvParameters) { myjeedom_context_t *ctx (myjeedom_context_t*)pvParameters; while(1) { // 1. 检查网络连接状态 if (ctx-net_state ! MYJEODOM_NET_CONNECTED) { myjeedom_connect(ctx); vTaskDelay(5000 / portTICK_PERIOD_MS); continue; } // 2. 尝试接收 MQTT 报文非阻塞 int recv_len myjeedom_hal_uart_read(ctx-hal_handle, ctx-rx_buffer, sizeof(ctx-rx_buffer)); if (recv_len 0) { mqtt_packet_decode(ctx-rx_buffer, recv_len, ctx-mqtt_pkt); myjeedom_handle_packet(ctx, ctx-mqtt_pkt); // L3 路由分发 } // 3. 检查状态缓存并发送 myjeedom_state_cache_flush(ctx); vTaskDelay(10 / portTICK_PERIOD_MS); // 10ms 轮询周期 } }4.2 与 HAL 库协同的低功耗设计// 进入 STOP 模式前关闭所有外设时钟 void myjeedom_enter_sleep(myjeedom_context_t *ctx, uint32_t sleep_ms) { // 1. 关闭 WiFi 模组发送 ATGSLP1000 myjeedom_hal_wifi_sleep(ctx-hal_handle); // 2. 关闭 UART __HAL_UART_DISABLE(huart1); __HAL_RCC_USART1_CLK_DISABLE(); // 3. 配置 RTC 唤醒30s 后 HAL_RTCEx_SetWakeUpTimer_IT(hrtc, (sleep_ms / 1000), RTC_WAKEUPCLOCK_RTCCLK_DIV16); // 4. 进入 STOP 模式 HAL_PWR_EnterSTOPMode(PWR_LOWPOWERREGULATOR_ON, PWR_STOPENTRY_WFI); }唤醒后HAL_RTCEx_WakeUpTimerEventCallback()中调用myjeedom_connect()尝试重连形成“连接-工作-休眠-唤醒-重连”闭环实测 ESP32-WROOM-32 休眠电流降至 8.2 µA。5. 配置选项与编译定制MyJeedom 通过myjeedom_config.h提供精细化裁剪宏定义默认值说明典型取值MYJEODOM_TLS_ENABLED0是否启用 TLS1需移植 mbedTLS或0明文 MQTTMYJEODOM_MAX_CMD_HANDLERS8最大支持的 logicalId 数量4简单开关或16多功能传感器MYJEODOM_STATE_CACHE_SIZE16状态缓存队列长度0禁用缓存或32高可靠性场景MYJEODOM_LOG_LEVELMYJEODOM_LOG_WARN日志级别MYJEODOM_LOG_NONE,ERROR,WARN,INFO裁剪示例在资源极度受限的 nRF52810Flash 192 KB, RAM 24 KB上可设置#define MYJEODOM_TLS_ENABLED 0 #define MYJEODOM_MAX_CMD_HANDLERS 4 #define MYJEODOM_STATE_CACHE_SIZE 0 #define MYJEODOM_LOG_LEVEL MYJEODOM_LOG_NONE最终代码体积压缩至12.3 KB Flash 1.8 KB RAM满足 Cortex-M464MHz 实时性要求。6. 故障排查与调试技巧6.1 常见问题诊断表现象可能原因调试方法myjeedom_connect()返回超时1. WiFi 未连上2. Jeedom 服务未启动3. 防火墙拦截 1883 端口用mosquitto_sub -h 192.168.1.100 -t jeedom/# -v在 PC 端测试连通性收到命令但cmd_cb未触发1.myjeedom_subscribe_cmd()未调用2. Topic 路由表未加载cmd_map.json3.logical_id拼写与 Jeedom 后台不一致在myjeedom_handle_packet()中添加printf(Topic: %s\n, pkt-topic)打印原始 Topic状态上报后 Jeedom 未更新1. Payload JSON 格式错误缺少value字段2. Topic 路径错误应为.../eqLogic/{id}/eqLogic/{id}3. Jeedom 设备未启用“自动更新”使用 Jeedom 的“调试日志”功能查看core::deamon日志中是否解析到该报文6.2 硬件级调试建议UART 透传模式抓包将 MCU 的 UART TX/RX 接至 USB-TTL 转换器用Wireshark MQTT dissector解析原始 MQTT 流量确认 CONNECT 报文的 ClientID、Username 是否正确GPIO 调试灯在myjeedom_task()循环开头置高 GPIO在myjeedom_handle_packet()结尾置低用示波器测量循环周期判断是否被阻塞内存泄漏检测若启用动态内存非默认在myjeedom_init()前调用heap_caps_get_free_size(MALLOC_CAP_8BIT)记录基线运行 24 小时后对比下降 5% 则存在泄漏。7. 与 Jeedom 平台的协同配置7.1 Jeedom 后台关键设置启用 MQTT 插件Plugins → MQTT → Configuration→ 勾选Activer le démon MQTT端口设为1883或8883。创建设备EqLogicConfiguration → Equipements → Ajouter→ 类型选MQTT→ 填写ID即代码中eqlogic_id→ 保存后进入设备设置页。配置命令Cmd在设备页点击Commandes→Ajouter une commande→ 设置NomLED ControlLogicalIdled_power必须与cmd_cb中判断的字符串完全一致TypeActionSubtypeOtherValueon/off配置状态Info添加类型为Info的命令LogicalId设为temperatureTemplate选temperatureJeedom 将自动解析{value:25.6,unit:°C}。7.2 安全加固实践API Key 管理禁止硬编码在固件中。推荐方案首次上电时通过串口 AT 指令ATSETAPIKEYxxxxxxxx写入 MCU EEPROMmyjeedom_init()从 EEPROM 读取TLS 证书固化将 Jeedom 服务器证书PEM 格式转换为 C 数组编译进固件myjeedom_tls_set_ca_cert()加载避免证书过期导致连接失败连接频率限制在app_event_handler(MYJEODOM_EVENT_DISCONNECTED)中实现退避算法static uint32_t reconnect_delay_ms 1000; void app_event_handler(myjeedom_event_t event) { if (event MYJEODOM_EVENT_DISCONNECTED) { vTaskDelay(reconnect_delay_ms / portTICK_PERIOD_MS); reconnect_delay_ms MIN(reconnect_delay_ms * 2, 300000); // 最大 5min } }MyJeedom 的工程价值在于将 Jeedom 生态的复杂性封装为嵌入式开发者可掌控的 C 函数集——无需理解 MQTT 协议字节流只需关注logical_id与value的业务映射无需纠结 TLS 握手细节只需配置MYJEODOM_TLS_ENABLED宏。当一个 STM32G030F6P6Flash 32 KB, RAM 8 KB成功驱动温湿度传感器并通过 MyJeedom 将数据实时呈现在 Jeedom 仪表盘上时其意义已超越代码本身它证明了资源受限的微控制器同样能成为现代智能家居中可靠、安全、可维护的一等公民。