CTBot:ESP32/ESP8266轻量级Telegram物联网通信库
1. 项目概述CTBot 是一款专为 ESP8266 和 ESP32 平台设计的轻量级 Arduino Telegram Bot 客户端库。其核心定位是“简单、直接、易用”不依赖复杂框架或中间件以最小化资源开销和最大化学以致用为目标服务于嵌入式物联网终端设备与 Telegram 消息平台的快速集成。该库并非对 Telegram Bot API 的全量封装而是聚焦于工业现场最常使用的通信原语文本消息收发、结构化交互Inline Keyboard / Reply Keyboard、地理位置上报、联系人交换、群组元信息获取等。所有功能均基于裸机 HTTP(S) 请求构建底层使用 ESP-IDF 或 Arduino Core 提供的 WiFiClientSecureESP32或 BearSSLESP8266进行 TLS 1.2 加密通信严格遵循 Telegram Bot API v6.9 的 JSON-RPC 协议规范。CTBot 的工程价值在于其嵌入式友好性全部字符串常量存储于 FlashPROGMEM动态内存分配仅发生在 JSON 解析阶段且支持 ArduinoJson v5.x 与 v6.x经验证兼容至 v6.19.4避免因第三方库版本升级导致的编译断裂。在 ESP32-WROOM-32 典型配置下空闲堆内存稳定维持在 120KB 以上在 ESP8266-12F 上启用getNewMessage()非阻塞轮询模式后Heap 峰值占用低于 28KB满足长期运行的稳定性要求。2. 系统架构与依赖关系2.1 整体分层模型CTBot 采用清晰的四层架构设计层级组件职责工程考量硬件抽象层HALWiFiClientSecureESP32BearSSL::WiFiClientSecureESP8266封装 TLS 握手、证书校验、HTTP 连接管理ESP8266 自 v2.1.12 起弃用硬编码指纹校验改用系统级证书链验证ESP32 自 v2.1.5 起启用 SSL 证书验证提升安全性协议适配层CTBotHttpClient构造 HTTPS GET/POST 请求处理 URL 编码、Content-Type 设置、响应状态码解析所有请求 URL 使用完整域名如https://api.telegram.org/bot{token}/getUpdates规避 DNS 解析失败风险JSON 处理层ArduinoJsonv5/v6解析 Telegram 返回的 JSON 响应序列化发送参数通过模板特化适配不同版本 APIv5 使用DynamicJsonBufferv6 使用StaticJsonDocumentN关键字段如chat_id,message_id强制声明为int64_t类型符合 Telegram API 对 64 位整数的要求应用接口层CTBotclass提供面向用户的高阶 API屏蔽底层细节所有方法设计为可重入reentrant支持 FreeRTOS 多任务环境getNewMessage()默认非阻塞返回true表示新消息到达false表示无更新2.2 关键依赖项配置CTBot 的正常运行依赖三项外部组件其安装与配置需严格遵循以下规范1ArduinoJson 库版本要求v5.13.5 — v6.19.4严禁使用 v6.20.0存在deserializeJson()内存越界缺陷配置建议// 对于 ESP32RAM 充足推荐使用 StaticJsonDocument #define CTBOT_JSON_DOC_SIZE 1024 StaticJsonDocumentCTBOT_JSON_DOC_SIZE doc; // 对于 ESP8266RAM 紧张可启用动态分配需确保 Heap 32KB // #define CTBOT_USE_DYNAMIC_JSON工程意义JSON 文档大小直接影响可解析消息的复杂度。1024 字节足以处理含 Inline Keyboard 的长文本消息约 300 字符 3×3 按钮若需支持大文件上传回调则需扩大至 2048 字节。2ESP8266/ESP32 Arduino CoreESP8266 Core≥ v3.0.0启用NONOSDK模式禁用RTOS SDK以降低中断延迟ESP32 Core≥ v2.0.9启用PSRAM支持时需在sdkconfig中设置CONFIG_SPIRAM_CACHE_WORKAROUNDy关键编译选项# platformio.ini 示例 [env:esp32dev] platform espressif32 board esp32dev framework arduino build_flags -DCONFIG_ARDUINO_LOOP_STACK_SIZE8192 -DARDUINOJSON_ENABLE_ARDUINO_STRING13WiFi 连接管理CTBot 内置CTBotWifiSetup工具类提供两种连接模式自动连接推荐调用bot.wifiConnect(ssid, password)内部执行 DHCP 获取 IP并设置 DNS 服务器为8.8.8.8静态 IP工业场景IPAddress local_ip(192, 168, 1, 100); IPAddress gateway(192, 168, 1, 1); IPAddress subnet(255, 255, 255, 0); bot.wifiConnectStatic(ssid, password, local_ip, gateway, subnet);注意静态 IP 模式下必须手动调用WiFi.config(local_ip, gateway, subnet)否则WiFiClientSecure无法建立 TLS 连接。3. 核心 API 接口详解CTBot 的 API 设计遵循“一个方法一个职责”原则所有函数均返回布尔值表示操作成功与否错误信息通过串口日志输出需启用Serial.begin(115200)。3.1 初始化与连接管理方法签名参数说明返回值典型用法begin()bool begin(const String token)token: Telegram Bot Token格式123456789:ABCdefGhIJKlmNoPQRsTUVwxyZtrue初始化成功falseToken 格式错误或内存不足bot.begin(123456789:ABCdefGhIJKlmNoPQRsTUVwxyZ);wifiConnect()bool wifiConnect(const char* ssid, const char* password)ssid/password: WiFi 凭据true连接成功并获取 IPfalse超时默认 30s或认证失败bot.wifiConnect(MyIoT, password123);isConnected()bool isConnected()无参数trueWiFi 已连接且 IP 可用false断连或 DHCP 未完成if (bot.isConnected()) { /* 发送消息 */ }3.2 消息收发核心方法getNewMessage(TBMessage message)作用轮询 Telegram 服务器获取最新未读消息非阻塞式自 v2.1.12 起实现逻辑构造getUpdates请求携带offset参数记录上次处理的消息 ID 1发送 HTTPS GET 请求至https://api.telegram.org/bot{token}/getUpdates?offset{offset}timeout0解析 JSON 响应提取result[0]中的message对象若存在新消息填充TBMessage结构体并更新offset否则返回falseTBMessage 关键字段struct TBMessage { int64_t chat_id; // 群组/用户唯一 ID64 位整数 int64_t message_id; // 消息唯一 ID String text; // 消息文本UTF-8 编码 String from_first_name; // 发送者名 String from_last_name; // 发送者姓 String from_username; // 发送者用户名xxx int64_t date; // Unix 时间戳秒级 bool isGroup; // 是否为群组消息 String group_title; // 群组标题仅群组消息有效 };sendMessage()签名bool sendMessage(int64_t chat_id, const String text, const String parse_mode , bool disable_web_page_preview false, int64_t reply_to_message_id 0)参数详解参数类型说明工程建议chat_idint64_t目标会话 ID用户 ID 或群组 ID从getNewMessage()获取不可转为 int32_ttextString消息内容含特殊字符时需启用parse_modeHTML并转义parse_modeString解析模式HTML支持b,a href或MarkdownV2需双重转义_,*,[disable_web_page_previewbool禁用链接预览工业设备发送传感器数据时建议设为true避免生成无关缩略图reply_to_message_idint64_t回复指定消息 ID实现对话上下文追踪底层请求构造POST /bot{token}/sendMessage HTTP/1.1 Content-Type: application/json { chat_id: 123456789, text: Temperature: 25.3°C, parse_mode: HTML, disable_web_page_preview: true, reply_to_message_id: 987654321 }3.3 交互式控件支持Inline Keyboard内联键盘用于在消息下方添加可点击按钮触发callback_query事件// 构造 2×2 键盘 TKeyboardButton btn1(️ 温度, GET_TEMP); TKeyboardButton btn2( 湿度, GET_HUMI); TKeyboardButton btn3( 电量, GET_BAT); TKeyboardButton btn4( 刷新, REFRESH); TKeyboard keyboard; keyboard.addButton(btn1).addButton(btn2).addButton(btn3).addButton(btn4); // 发送带键盘的消息 bot.sendMessage(chat_id, 请选择操作, , false, 0, keyboard);回调处理当用户点击按钮Telegram 发送callback_query需通过bot.getCallbackQuery()获取data字段如GET_TEMP再调用bot.answerCallbackQuery()消除客户端加载状态。Reply Keyboard回复键盘将键盘固定显示在输入框上方适合高频操作TReplyKeyboard reply_kb; reply_kb.addKey(ON).addKey(OFF).addKey(STATUS); bot.sendMessage(chat_id, 控制设备, , false, 0, reply_kb);工程优势相比 Inline KeyboardReply Keyboard 不产生callback_query直接作为普通消息发送降低服务端解析复杂度。3.4 高级消息类型支持消息类型API 方法关键参数典型场景地理位置sendLocation(int64_t chat_id, float latitude, float longitude)latitude/longitude: WGS84 坐标车载终端上报实时位置联系人sendContact(int64_t chat_id, const String phone_number, const String first_name, const String last_name )phone_number: E.164 格式如8613800138000设备维护人员信息共享群组信息getChat(int64_t chat_id, TBChat chat)TBChat结构体包含title,type,description动态获取群组名称用于日志记录4. 典型应用场景与代码实现4.1 工业传感器数据上报FreeRTOS 集成在 ESP32 上构建双任务系统sensor_task采集数据telegram_task负责通信通过队列传递消息。// 全局定义 CTBot bot; QueueHandle_t xMsgQueue; // 传感器任务 void sensor_task(void *pvParameters) { while(1) { float temp read_dht22_temperature(); float humi read_dht22_humidity(); // 构造消息结构体 struct SensorMsg { float temperature; float humidity; uint32_t timestamp; }; SensorMsg msg {temp, humi, millis()}; xQueueSend(xMsgQueue, msg, portMAX_DELAY); vTaskDelay(5000 / portTICK_PERIOD_MS); // 每5秒上报一次 } } // Telegram 任务 void telegram_task(void *pvParameters) { bot.begin(YOUR_BOT_TOKEN); bot.wifiConnect(Factory_WiFi, secure_password); while(1) { if (bot.isConnected()) { SensorMsg sensor_data; if (xQueueReceive(xMsgQueue, sensor_data, 0) pdTRUE) { String payload String(️ ) sensor_data.temperature °C | sensor_data.humidity %; bot.sendMessage(CHAT_ID, payload, HTML); } } vTaskDelay(100 / portTICK_PERIOD_MS); // 高频轮询 Telegram } } // 初始化 void setup() { Serial.begin(115200); xMsgQueue xQueueCreate(5, sizeof(SensorMsg)); xTaskCreate(sensor_task, SENSOR, 2048, NULL, 5, NULL); xTaskCreate(telegram_task, TELEGRAM, 4096, NULL, 5, NULL); }4.2 远程设备控制命令解析解析用户发送的文本命令映射到 GPIO 控制动作void handleCommand(const TBMessage msg) { String cmd msg.text; cmd.trim(); cmd.toLowerCase(); if (cmd /start) { bot.sendMessage(msg.chat_id, ✅ 设备已上线\n发送 /status 查看状态); } else if (cmd /status) { String status 电源: String(digitalRead(POWER_PIN) ? ON : OFF) \n; status WiFi: String(bot.isConnected() ? CONNECTED : DISCONNECTED); bot.sendMessage(msg.chat_id, status, HTML); } else if (cmd /power_on) { digitalWrite(POWER_PIN, HIGH); bot.sendMessage(msg.chat_id, ⚡ 电源已开启); } else if (cmd /power_off) { digitalWrite(POWER_PIN, LOW); bot.sendMessage(msg.chat_id, 电源已关闭); } else { bot.sendMessage(msg.chat_id, ❌ 未知命令\n可用命令/start /status /power_on /power_off); } } // 主循环 void loop() { TBMessage msg; if (bot.getNewMessage(msg)) { handleCommand(msg); } delay(1000); }4.3 群组告警通知多目标推送利用TBMessage.isGroup字段识别群组消息实现设备异常广播void loop() { TBMessage msg; if (bot.getNewMessage(msg)) { // 仅处理群组消息中的告警指令 if (msg.isGroup msg.text.startsWith(/alert )) { String alert_text msg.text.substring(7); // 向预设的多个群组广播 int64_t groups[] {GROUP_ID_1, GROUP_ID_2, GROUP_ID_3}; for (int i 0; i 3; i) { bot.sendMessage(groups[i], [ msg.group_title ] alert_text, HTML); } bot.sendMessage(msg.chat_id, ✅ 告警已广播至所有监控群组); } } delay(1000); }5. 调试与故障排除5.1 常见错误码与解决方案错误现象日志特征根本原因解决方案WiFi not connected串口持续打印WiFi not connectedwifiConnect()超时或密码错误检查ssid/password大小写确认路由器未启用 MAC 过滤尝试wifiConnectStatic()JSON parsing failedgetNewMessage()返回false无其他日志ArduinoJson 版本不兼容或doc尺寸不足降级至 v6.19.4增大CTBOT_JSON_DOC_SIZE启用#define CTBOT_DEBUG_HTTP查看原始响应SSL connection failedHTTPS request failedESP32 证书验证失败或 ESP8266 旧版指纹失效ESP32检查sdkconfig中CONFIG_MBEDTLS_CERTIFICATE_BUNDLE是否启用ESP8266升级 CTBot 至 ≥ v2.1.12Invalid chat_idsendMessage()返回falseTelegram 响应Bad Request: chat_id is emptychat_id被截断为 32 位整数确保所有chat_id变量声明为int64_t禁止使用longESP32 上为 32 位5.2 关键调试宏在CTBot.h中启用以下宏可获取深度诊断信息// 启用 HTTP 流量日志慎用影响性能 #define CTBOT_DEBUG_HTTP // 启用 JSON 解析过程日志 #define CTBOT_DEBUG_JSON // 启用 WiFi 状态轮询日志 #define CTBOT_DEBUG_WIFI启用后串口将输出[HTTP] GET https://api.telegram.org/bot123.../getUpdates?offset12345timeout0 [HTTP] Response: {ok:true,result:[]} [JSON] Parsed 0 messages6. 性能优化实践6.1 内存占用控制Flash 优化所有提示字符串如WiFi connecting...自动存储于 Flash减少 RAM 占用ESP8266 1KB HeapHeap 管理避免在loop()中频繁创建String对象改用char buffer[64]snprintf()JSON 文档复用全局声明StaticJsonDocument1024 doc在getNewMessage()和sendMessage()中复用同一实例6.2 延迟敏感场景优化对于需要亚秒级响应的场景如紧急停机指令禁用getUpdates的timeout参数// 修改 CTBot.cpp 中 getUpdates 请求构造 // 原始String url /bot m_token /getUpdates?offset String(m_offset) timeout30; // 优化String url /bot m_token /getUpdates?offset String(m_offset) timeout0;此修改使轮询变为纯 HTTP GET消除 Telegram 服务端 30 秒等待端到端延迟从 30s 降至 200ms 内实测 ESP32 100Mbps 网络。7. 安全加固建议Token 保护切勿将 Bot Token 硬编码于源码应通过#define BOT_TOKEN xxx方式隔离或使用 PlatformIO 的build_flags -DBOT_TOKEN\xxx\群组白名单在handleCommand()中校验msg.chat_id是否在预设白名单内拒绝非法群组消息const int64_t ALLOWED_GROUPS[] {-1001234567890, -1009876543210}; bool isAllowed false; for (auto id : ALLOWED_GROUPS) { if (msg.chat_id id) { isAllowed true; break; } } if (!isAllowed) return;命令频率限制使用millis()记录上次命令时间防止暴力刷屏static uint32_t last_cmd_time 0; if (millis() - last_cmd_time 5000) { // 5秒内限1次 bot.sendMessage(msg.chat_id, ⏳ 操作过于频繁请稍后再试); return; } last_cmd_time millis();CTBot 的设计哲学是“做少而精的事”。它不追求覆盖 Telegram Bot API 的全部 50 接口而是将getUpdates、sendMessage、answerCallbackQuery等 7 个核心原语打磨至嵌入式可用的极致——在 20KB Flash、28KB RAM 的资源约束下稳定支撑工业现场 7×24 小时消息收发。这种克制正是嵌入式工程师对可靠性的终极承诺。