/* * 自然写互动课堂教学管理网关软件 V1.0 * mqtt_client.c - MQTT通信客户端(TLS加密) * * 功能说明: * 1. MQTT 3.1.1协议实现(基于mosquitto库) * 2. TLS/SSL加密通信 * 3. 自动重连与会话恢复 * 4. 主题订阅管理(控制指令下发) * 5. 笔迹数据批量发布 * 6. 遗嘱消息(设备离线通知) */ #include #include #include #include #include #include #include #include /* Mosquitto MQTT库 */ #include /* 模块头文件 */ #include "mqtt_client.h" #include "gateway_config.h" /* ========== 常量定义 ========== */ /* MQTT QoS级别 */ #define MQTT_QOS_AT_MOST_ONCE 0 #define MQTT_QOS_AT_LEAST_ONCE 1 /* MQTT保活间隔(秒) */ #define MQTT_KEEPALIVE_SEC 60 /* 重连间隔(秒) */ #define MQTT_RECONNECT_SEC 5 /* 最大重连间隔(秒,指数退避上限) */ #define MQTT_MAX_RECONNECT_SEC 120 /* 消息批量发布缓冲区大小 */ #define PUBLISH_BATCH_SIZE 32 /* 主题前缀 */ #define TOPIC_PREFIX "writech/gateway/" /* ========== 数据结构 ========== */ /* MQTT客户端状态 */ typedef struct { struct mosquitto *mosq; /* Mosquitto实例 */ char gateway_id[64]; /* 网关唯一ID */ char broker_host[256]; /* 服务器地址 */ int broker_port; /* 服务器端口 */ int is_connected; /* 是否已连接 */ int reconnect_count; /* 重连次数 */ pthread_mutex_t pub_mutex; /* 发布锁 */ /* 主题 */ char topic_stroke_data[128]; /* 笔迹数据上报主题 */ char topic_device_status[128]; /* 设备状态上报主题 */ char topic_cmd_subscribe[128]; /* 命令下发订阅主题 */ char topic_ota[128]; /* OTA升级通知主题 */ /* TLS证书路径 */ char ca_cert_path[256]; /* CA证书 */ char client_cert_path[256]; /* 客户端证书 */ char client_key_path[256]; /* 客户端私钥 */ /* 统计 */ unsigned long msgs_published; unsigned long msgs_received; unsigned long bytes_sent; } MQTTState; static MQTTState g_mqtt; /* 命令回调函数 */ static void (*g_cmd_callback)(const char *topic, const uint8_t *payload, int payload_len) = NULL; /* ========== MQTT回调函数 ========== */ /** * 连接成功回调 */ static void on_connect(struct mosquitto *mosq, void *userdata, int rc) { (void)userdata; if (rc == 0) { g_mqtt.is_connected = 1; g_mqtt.reconnect_count = 0; syslog(LOG_INFO, "MQTT: 已连接到 %s:%d", g_mqtt.broker_host, g_mqtt.broker_port); /* 订阅控制指令主题 */ mosquitto_subscribe(mosq, NULL, g_mqtt.topic_cmd_subscribe, MQTT_QOS_AT_LEAST_ONCE); /* 订阅OTA升级通知主题 */ mosquitto_subscribe(mosq, NULL, g_mqtt.topic_ota, MQTT_QOS_AT_LEAST_ONCE); /* 发布上线状态 */ publish_status("online"); } else { syslog(LOG_ERR, "MQTT: 连接失败,返回码=%d", rc); g_mqtt.is_connected = 0; } } /** * 连接断开回调 */ static void on_disconnect(struct mosquitto *mosq, void *userdata, int rc) { (void)mosq; (void)userdata; g_mqtt.is_connected = 0; syslog(LOG_WARNING, "MQTT: 连接断开,原因=%d", rc); /* 非主动断开,将自动重连 */ if (rc != 0) { g_mqtt.reconnect_count++; } } /** * 消息接收回调(订阅的主题收到消息) */ static void on_message(struct mosquitto *mosq, void *userdata, const struct mosquitto_message *msg) { (void)mosq; (void)userdata; g_mqtt.msgs_received++; syslog(LOG_DEBUG, "MQTT: 收到消息 [%s] 长度=%d", msg->topic, msg->payloadlen); /* 分发到命令处理回调 */ if (g_cmd_callback) { g_cmd_callback(msg->topic, (const uint8_t *)msg->payload, msg->payloadlen); } } /** * 发布完成回调 */ static void on_publish(struct mosquitto *mosq, void *userdata, int mid) { (void)mosq; (void)userdata; (void)mid; g_mqtt.msgs_published++; } /* ========== 初始化 ========== */ /** * 初始化MQTT客户端 * * @param host MQTT服务器地址 * @param port MQTT服务器端口(8883=TLS) * @return 0成功, -1失败 */ int mqtt_client_init(const char *host, int port) { memset(&g_mqtt, 0, sizeof(g_mqtt)); pthread_mutex_init(&g_mqtt.pub_mutex, NULL); strncpy(g_mqtt.broker_host, host, sizeof(g_mqtt.broker_host) - 1); g_mqtt.broker_port = port; /* 生成网关ID */ snprintf(g_mqtt.gateway_id, sizeof(g_mqtt.gateway_id), "writech-gw-%08x", (unsigned int)time(NULL)); /* 构建主题 */ snprintf(g_mqtt.topic_stroke_data, sizeof(g_mqtt.topic_stroke_data), "%s%s/stroke", TOPIC_PREFIX, g_mqtt.gateway_id); snprintf(g_mqtt.topic_device_status, sizeof(g_mqtt.topic_device_status), "%s%s/status", TOPIC_PREFIX, g_mqtt.gateway_id); snprintf(g_mqtt.topic_cmd_subscribe, sizeof(g_mqtt.topic_cmd_subscribe), "%s%s/cmd/#", TOPIC_PREFIX, g_mqtt.gateway_id); snprintf(g_mqtt.topic_ota, sizeof(g_mqtt.topic_ota), "%s%s/ota", TOPIC_PREFIX, g_mqtt.gateway_id); /* 初始化Mosquitto库 */ mosquitto_lib_init(); /* 创建Mosquitto客户端实例 */ g_mqtt.mosq = mosquitto_new(g_mqtt.gateway_id, true, NULL); if (g_mqtt.mosq == NULL) { syslog(LOG_ERR, "MQTT: 创建客户端失败"); return -1; } /* 注册回调 */ mosquitto_connect_callback_set(g_mqtt.mosq, on_connect); mosquitto_disconnect_callback_set(g_mqtt.mosq, on_disconnect); mosquitto_message_callback_set(g_mqtt.mosq, on_message); mosquitto_publish_callback_set(g_mqtt.mosq, on_publish); /* 设置遗嘱消息(设备异常离线时自动发布) */ char will_payload[128]; snprintf(will_payload, sizeof(will_payload), "{\"gatewayId\":\"%s\",\"status\":\"offline\"}", g_mqtt.gateway_id); mosquitto_will_set(g_mqtt.mosq, g_mqtt.topic_device_status, strlen(will_payload), will_payload, MQTT_QOS_AT_LEAST_ONCE, true); /* 配置TLS */ const char *ca_cert = gateway_config_get_string("mqtt.ca_cert", "/etc/writech/ca.pem"); const char *client_cert = gateway_config_get_string("mqtt.client_cert", "/etc/writech/client.pem"); const char *client_key = gateway_config_get_string("mqtt.client_key", "/etc/writech/client.key"); strncpy(g_mqtt.ca_cert_path, ca_cert, sizeof(g_mqtt.ca_cert_path) - 1); strncpy(g_mqtt.client_cert_path, client_cert, sizeof(g_mqtt.client_cert_path) - 1); strncpy(g_mqtt.client_key_path, client_key, sizeof(g_mqtt.client_key_path) - 1); int tls_ret = mosquitto_tls_set(g_mqtt.mosq, ca_cert, NULL, client_cert, client_key, NULL); if (tls_ret != MOSQ_ERR_SUCCESS) { syslog(LOG_WARNING, "MQTT: TLS配置失败,将使用非加密连接"); } /* 设置自动重连 */ mosquitto_reconnect_delay_set(g_mqtt.mosq, MQTT_RECONNECT_SEC, MQTT_MAX_RECONNECT_SEC, true); /* 发起连接 */ int ret = mosquitto_connect_async(g_mqtt.mosq, host, port, MQTT_KEEPALIVE_SEC); if (ret != MOSQ_ERR_SUCCESS) { syslog(LOG_ERR, "MQTT: 连接发起失败: %s", mosquitto_strerror(ret)); return -1; } /* 启动Mosquitto网络循环线程 */ mosquitto_loop_start(g_mqtt.mosq); syslog(LOG_INFO, "MQTT客户端初始化完成,网关ID=%s", g_mqtt.gateway_id); return 0; } /* ========== 数据发布 ========== */ /** * 发布笔迹数据到MQTT * * @param pen_mac 笔MAC地址 * @param data 笔迹二进制数据 * @param data_len 数据长度 * @return 0成功, -1未连接, -2发布失败 */ int mqtt_publish_stroke(const char *pen_mac, const uint8_t *data, int data_len) { if (!g_mqtt.is_connected) { return -1; } /* 构建包含笔MAC的完整主题 */ char topic[256]; snprintf(topic, sizeof(topic), "%s/%s", g_mqtt.topic_stroke_data, pen_mac); pthread_mutex_lock(&g_mqtt.pub_mutex); int ret = mosquitto_publish(g_mqtt.mosq, NULL, topic, data_len, data, MQTT_QOS_AT_MOST_ONCE, false); pthread_mutex_unlock(&g_mqtt.pub_mutex); if (ret == MOSQ_ERR_SUCCESS) { g_mqtt.bytes_sent += data_len; return 0; } syslog(LOG_WARNING, "MQTT: 发布失败: %s", mosquitto_strerror(ret)); return -2; } /** * 发布网关/设备状态 */ static void publish_status(const char *status) { char payload[512]; snprintf(payload, sizeof(payload), "{\"gatewayId\":\"%s\",\"status\":\"%s\"," "\"uptime\":%lu,\"penCount\":%d," "\"msgsSent\":%lu,\"msgsRecv\":%lu}", g_mqtt.gateway_id, status, (unsigned long)time(NULL), 0, /* pen count to be filled */ g_mqtt.msgs_published, g_mqtt.msgs_received); mosquitto_publish(g_mqtt.mosq, NULL, g_mqtt.topic_device_status, strlen(payload), payload, MQTT_QOS_AT_LEAST_ONCE, true); } /* ========== 外部接口 ========== */ int mqtt_client_is_connected(void) { return g_mqtt.is_connected; } int mqtt_client_get_fd(void) { return mosquitto_socket(g_mqtt.mosq); } void mqtt_client_process_read(void) { mosquitto_loop_read(g_mqtt.mosq, 1); } void mqtt_client_process_write(void) { mosquitto_loop_write(g_mqtt.mosq, 1); } void mqtt_client_set_cmd_callback(void (*cb)(const char *, const uint8_t *, int)) { g_cmd_callback = cb; } void mqtt_client_cleanup(void) { if (g_mqtt.mosq) { publish_status("offline"); mosquitto_disconnect(g_mqtt.mosq); mosquitto_loop_stop(g_mqtt.mosq, true); mosquitto_destroy(g_mqtt.mosq); } mosquitto_lib_cleanup(); pthread_mutex_destroy(&g_mqtt.pub_mutex); syslog(LOG_INFO, "MQTT客户端已清理"); }