知微 知微·设备云

设备数据与控制,一处完成

MQTT 与 HTTPS 双接入,实时遥测、远程执行器、邮箱账号体系。

  • 邮箱验证码注册
  • 管理员审核后启用
  • 传感器与执行器分开管理
  • 开关、按钮、刻度、输入框
知微 知微·设备云
知微知微·设备云
我的设备
    还没有设备

    添加第一台设备

    选好接入方式,拿到凭据后设备就能开始上报。

    —

    离线

    还没有数据字段

    字段标识与设备上报 data 里的键名一致,就会自动显示。

    传感器 0
    名称类型最新值最近上报操作
    执行器 0
    名称操作类型当前值控制操作

    —

    知微
    知微 · 设备云

    开发者文档

    目录

    1. MQTT 接入 2. HTTPS 接入 3. TCP 接入 4. 物模型 5. OTA 远程升级 6. 应用端 API

    平台提供 MQTT、TCP 两种长连接方式(适合实时控制),以及 HTTPS 主动上报方式(适合无法保持连接的设备)。新建设备后凭据只显示一次,请立即保存。

    1MQTT 接入

    连接参数

    参数值
    地址 / 端口mqtts://8.148.67.96:8883(TLS 加密)
    用户名等于设备 ID,例如 ESP8266_MQTT
    密码创建设备时返回,仅显示一次
    Client ID任意,同设备重复连接会顶掉旧连接

    Topic 结构

    方向Topic说明
    上行dev/{id}/up遥测数据,周期上报
    上行dev/{id}/status在线状态(retained + 遗嘱),{"online":true|false}
    上行dev/{id}/ack指令回执
    下行dev/{id}/cmd平台下发的控制指令,需订阅

    上行消息

    // 遥测:dev/{id}/up
    {"ts":1768000000,"seq":42,"fw":"1.1.0","data":{"temp":22.5,"humi":57.4,"relay":1}}
    
    // 在线:dev/{id}/status(连接时 publish retained true,遗嘱设 offline)
    {"online":true}
    
    // 回执:dev/{id}/ack
    {"cmdId":"a1b2c3d4e5f6","ok":true,"err":null,"result":{"value":1}}

    下行指令

    // dev/{id}/cmd
    {"cmdId":"a1b2c3d4e5f6","type":"switch","value":1,"expireAt":1768000065}
    typevalue规则
    switch0 或 1开关型,只能是 0/1
    range整数刻度型,在 min~max 内且按 step 递增
    input字符串输入框型,长度 ≤ 64

    ESP8266 代码片段

    #include <ESP8266WiFi.h>
    #include <WiFiClientSecure.h>
    #include <PubSubClient.h>
    
    const char *MQTT_HOST = "8.148.67.96";
    const uint16_t MQTT_PORT = 8883;
    const char *DEVICE_ID = "ESP8266_MQTT";
    const char *MQTT_USER = DEVICE_ID;   // 用户名 = 设备 ID
    const char *MQTT_PASS = "创建时返回的密码";
    
    WiFiClientSecure net;
    PubSubClient mqtt(net);
    
    void setupMqtt() {
      mqtt.setServer(MQTT_HOST, MQTT_PORT);
      net.setInsecure();  // 自签证书,本地调试
      bool ok = mqtt.connect(DEVICE_ID, MQTT_USER, MQTT_PASS,
          "dev/ESP8266_MQTT/status", 1, true, "{\"online\":false}");
      if (ok) {
        mqtt.subscribe("dev/ESP8266_MQTT/cmd", 1);
        mqtt.publish("dev/ESP8266_MQTT/status", "{\"online\":true}", true);
      }
    }
    
    // 周期上报遥测
    void report() {
      char buf[256];
      snprintf(buf, sizeof(buf),
        "{\"ts\":%ld,\"seq\":%d,\"fw\":\"1.1.0\","
        "\"data\":{\"temp\":%.2f,\"humi\":%.1f,\"relay\":%d}}",
        time(nullptr), seq, temp, hum, relay);
      mqtt.publish("dev/ESP8266_MQTT/up", buf);
    }
    
    // 接收下行指令
    void onMessage(char *topic, byte *payload, unsigned int len) {
      JsonDocument doc;
      deserializeJson(doc, payload, len);
      const char *type = doc["type"] | "";
      if (strcmp(type, "switch") == 0) {
        int v = doc["value"] | 0;
        relay = (v == 1);
        char ack[128];
        snprintf(ack, sizeof(ack),
          "{\"cmdId\":\"%s\",\"ok\":true,\"result\":{\"value\":%d}}",
          (const char *)doc["cmdId"], relay);
        mqtt.publish("dev/ESP8266_MQTT/ack", ack);
      }
    }

    2HTTPS 接入

    适合无法保持长连接的设备。创建 HTTPS 设备后会返回一个 endpoint 和 token,每次请求带上 X-Device-Token 头。

    # 上报遥测(平台会在响应里带回排队的下行指令)
    curl -X POST "https://iot.wenbo2005.online/api/ingest/ESP8266_HTTP" \
      -H "X-Device-Token: 你的令牌" \
      -H "Content-Type: application/json" \
      -d '{"ts":1768000000,"seq":1,"data":{"temp":23.4}}'
    
    # 指令回执
    curl -X POST "https://iot.wenbo2005.online/api/ingest/ESP8266_HTTP/ack" \
      -H "X-Device-Token: 你的令牌" \
      -H "Content-Type: application/json" \
      -d '{"cmdId":"a1b2c3d4e5f6","ok":true}'

    3TCP 接入

    设备与平台保持一条 TLS 长连接,服务器可在毫秒级主动下发指令。连接成功后先发鉴权帧,之后每行一个 JSON。

    连接参数

    参数值
    地址 / 端口tcp://8.148.67.96:9443(TLS 加密)
    设备 ID创建设备时的 device_id
    令牌创建设备时返回,仅显示一次
    心跳设备每 20 秒发一次,60 秒无帧自动断线

    帧格式(每行一个 JSON,UTF-8)

    // 设备 → 平台:首帧鉴权
    {"t":1,"device":"ESP8266_TCP","key":"你的令牌","ver":"1.0"}
    
    // 平台 → 设备:鉴权结果 / 心跳应答
    {"t":2,"ok":true}
    {"t":6}
    
    // 设备 → 平台:遥测、指令回执、心跳
    {"t":3,"ts":1768000000,"seq":1,"fw":"1.0.0","data":{"temp":23.4}}
    {"t":4,"cmdId":"a1b2c3d4e5f6","ok":true,"result":{"value":1}}
    {"t":6}
    
    // 平台 → 设备:实时指令
    {"t":5,"cmd":{"cmdId":"a1b2c3d4e5f6","type":"switch","value":1}}

    鉴权失败平台会返回 {"t":2,"ok":false,"err":"..."} 并断开;同一设备重复连接会顶掉旧连接。

    4物模型

    传感器按 data_type 区分:number(数值)、bool(布尔)。字段标识(identifier)必须与上报 data 里的键名一致才会显示。

    执行器按操作类型区分,对应下行指令里的 type:开关型(0/1)、刻度型(整数 + 步进)、输入框型(字符串,≤64 字)。

    上报间隔建议 ≤ 60 秒,否则平台会判定离线(2 × keepalive)。

    5OTA 远程升级

    平台只负责下发升级指令,固件二进制由设备自己通过 HTTPS 下载并写入 flash,不经过 MQTT 或 TCP 网关。升级指令复用普通指令的下发通道,设备回执后开始下载,重启后上报的 fw 与目标版本一致才算成功。

    更新前的准备

    芯片 / FlashArduino IDE 选项可用 app 分区
    ESP8266 1MB(ESP-01S,25Q80)1MB (FS:none OTA:~502KB)约 480KB,固件必须小于它
    ESP8266 4MB(NodeMCU / ESP-12E)4M (1M SPIFFS)约 1.9MB
    ESP32min_spiffs(含两个 OTA 分区)约 1.9MB

    没有 OTA 分区时编译能过,但下载完 Update.end() 会报 no OTA partition。第一次必须用串口烧录带 OTA 功能的固件,之后才能走远程升级。平台上传时限制:ESP8266 ≤ 1000KB、ESP32 ≤ 1900KB,且首字节必须是 0xE9。

    上传固件

    控制台右上角用户名 → 远程升级:选开发板、填版本号(字母数字和 . _ -,1~24 位),选择编译好的 .bin。也可以直接调接口:

    # 上传(-F 字段不能用 JSON)
    curl -X POST "https://iot.wenbo2005.online/api/ota/firmwares" \
      -H "Cookie: 你的会话" \
      -F "file=@ESP8266_MQTT.ino.bin;type=application/octet-stream" \
      -F "board=esp8266" \
      -F "version=1.2.1" \
      -F "note=修复 OTA 下载"
    
    # 固件列表 / 删除
    curl "https://iot.wenbo2005.online/api/ota/firmwares?board=esp8266" -H "Cookie: 你的会话"
    curl -X DELETE "https://iot.wenbo2005.online/api/ota/firmwares/{firmware_id}" -H "Cookie: 你的会话"

    发起升级

    curl -X POST "https://iot.wenbo2005.online/api/devices/ESP8266_MQTT/ota" \
      -H "Cookie: 你的会话" \
      -H "Content-Type: application/json" \
      -d '{"firmware_id":"8f3c1a9b2d4e"}'
    状态码含义
    200指令已下发,设备已回执接受
    202设备离线或未连网关,指令已排队,上线后自动下发
    400设备已经是该版本
    404固件不存在或不属于当前账号
    409该设备已有升级任务在进行中(15 分钟无动作会自动判定超时)
    504指令已发出但设备没有回执

    MQTT 设备的下发走到 dev/{id}/cmd,TCP 设备收到 {"t":5,"cmd":...},HTTPS 设备在下次上报的响应里取走。三条链路的升级报文完全一致,ACL 已为 MQTT 设备额外放行 dev/{id}/ota 的写权限。

    升级报文

    // 平台 → 设备(cmd 通道)
    {
      "cmdId": "9a7c2e51f0b3",
      "type": "ota",
      "value": {
        "url": "https://iot.wenbo2005.online/api/ota/file/8f3c1a9b2d4e?d=ESP8266_MQTT&e=1768003600&s=...",
        "version": "1.2.1",
        "size": 612345,
        "sha256": "…",
        "md5": "…",
        "chip": "esp8266"
      },
      "expireAt": 1768000065
    }
    字段说明
    url带签名的临时下载地址,默认 1 小时有效
    version目标版本,重启后遥测 fw 与它一致即判定成功
    size固件字节数,下载前必须校验 HTTP Content-Length
    sha256 / md5固件摘要;ESP8266 端 Update.setMD5() 只支持 MD5
    chipesp8266 或 esp32,芯片不匹配必须回 ok:false

    设备回执与进度

    // 已接受(dev/{id}/ack 或 TCP t=4)
    {"cmdId":"9a7c2e51f0b3","ok":true,"err":null,"ts":1768000000,"result":{"state":"accepted"}}
    
    // 拒绝(芯片不匹配、URL 非法、空间不足等)
    {"cmdId":"9a7c2e51f0b3","ok":false,"err":"chip mismatch","ts":1768000000}
    
    // 下载进度(可选,发到 dev/{id}/ota)
    {"state":"downloading","progress":40}
    {"state":"failed","progress":0,"err":"md5 check failed"}

    进度一直是 0% 属正常现象(ESP8266):1MB 芯片只有约 80KB RAM,同时跑 MQTT 和下载两条 TLS 会 OOM,参考固件在下载前 mqtt.disconnect(),所以只能发一次 downloading 0,后续由重启后的 fw 判定成功。ESP32 内存更大,可以保持连接并持续上报进度。

    任务状态机

    状态触发条件
    queued设备离线 / 未连网关,指令进入排队
    sent指令已写出(MQTT publish、TCP 帧、HTTPS 取走)
    accepted设备回执 ok:true,开始下载
    downloading设备上报 downloading(可选)
    success重启后遥测 fw 等于目标版本,只有平台能置成功
    failed设备回执失败或上报 failed
    timeout15 分钟无动作(IOT_OTA_TASK_TIMEOUT)

    下载链接鉴权

    设备不持有账号,下载靠签名完成:HMAC-SHA256(SESSION_SECRET, "firmware_id:device_id:exp"),签名和过期时间都在 query 里,只对这台设备有效,默认 1 小时(IOT_OTA_URL_TTL,建议不要超过 1 小时,否则重放风险变大)。响应头带 X-SHA256 便于二次校验。

    ESP8266 下载要点

    mqtt.disconnect();                       // 先释放 TLS 连接,否则内存不够
    WiFiClientSecure dl;
    HTTPClient http;
    http.setTimeout(20000);                  // 没有 setConnectTimeout,只有 setTimeout
    http.begin(dl, otaUrl);
    int code = http.GET();
    if (code != 200 || http.getSize() != (size_t)otaSize) { ... }
    
    Update.begin(otaSize);                   // 必须在 begin 之后设置 MD5,begin 会清空
    Update.setMD5(otaMd5);
    while (http.connected() && Update.isRunning()) {
      uint8_t buf[512];
      int n = http.getStream().readBytes(buf, sizeof(buf));
      if (n <= 0) break;
      if (!Update.write(buf, n)) break;
    }
    Update.end();                            // 内部校验 MD5
    http.end();
    ESP.restart();                           // 重启后上报新 fw,平台判定成功

    常见错误

    现象原因与处理
    no OTA partition / not supported分区表没选 OTA 方案,按上文重新选 Flash 方案并用串口烧录一次
    size mismatch / not enough space固件大于 app 分区,裁剪代码或换更大的 Flash
    MD5 check failed / verify failed下载被截断或固件被替换;检查 Content-Length 与信号质量后重试
    下载 403 已过期 / 签名错误指令排队超过 1 小时,重新点一次升级即可
    409 已有任务上一任务未结束;等它超时(15 分钟)或确认设备已回执
    504 设备没有回执按顺序排查:安全组 / 防火墙 → 是否订阅 dev/{id}/cmd → ACL 是否放行 → 进程与连接状态
    进度一直 0% 然后变成功ESP8266 内存限制,不回报中间进度,属预期行为

    注意事项

    • 升级过程断电会保留旧 app 分区,设备照旧启动,不会变砖;但中途断电可能导致本次升级判为 timeout。
    • 升级前确认设备在线、WiFi 信号稳定,下载耗时与固件大小成正比。
    • 删除设备或删除用户会连带清理该设备的升级任务;固件文件请在本地留一份,平台不是版本仓库。
    • 服务器自签证书要在设备里装 CA(BearSSL::X509List)或临时 setInsecure(),否则握手失败表现为一直下载不动。

    6应用端 API

    应用端(App、小程序、后端服务)用 API Key 调用,不需要模拟网页登录。每个账号只有一个 Key,在右上角用户名 → 开发设置 中生成,可选择有效期(30/90/180/365/730 天),生成、重置、吊销都需要邮箱验证码。

    获取 API Key

    控制台「开发设置」会直接显示当前 Key,并提供复制按钮。也可以调接口(先点“获取验证码”,填到 code 里):

    # 生成 / 重置 Key,days 为有效期天数(1~730),code 为邮箱验证码
    curl -X POST "https://iot.wenbo2005.online/api/apikey" \
      -H "Content-Type: application/json" \
      -d '{"days":90,"code":"123456"}' -b cookies.txt
    
    # 查看状态与当前 Key
    curl "https://iot.wenbo2005.online/api/apikey" -b cookies.txt
    
    # 吊销(同样需要验证码)
    curl -X DELETE "https://iot.wenbo2005.online/api/apikey?code=123456" -b cookies.txt

    调用业务接口时把 Key 放在请求头,两种写法等价:

    X-API-Key: iot_xxxxxxxx
    Authorization: Bearer iot_xxxxxxxx

    Key 到期后所有接口返回 401 API Key 已过期,需要重新生成。控制台会直接显示当前 Key,方便随时复制。

    响应约定

    所有接口返回 JSON;HTTP 状态码表达结果:200 成功、401 未认证 / Key 无效、403 账号被禁用、404 设备不存在、409 冲突(如删除在线设备)、429 请求过于频繁。失败时 body 形如 {"detail": "原因"}。时间戳统一为 Unix 秒。

    接口一览

    方法路径说明
    GET/api/devices名下全部设备(含在线状态、最新数据)
    GET/api/devices/status?devIds=a,b批量在线状态;不传 devIds 返回全部
    GET/api/devices/datas?devIds=a,b批量最新遥测
    GET/api/devices/{id}单设备详情:设备 + 物模型 + 最新数据
    GET/api/devices/{id}/latest最新一条遥测
    GET/api/devices/{id}/telemetry?limit&offset&since&until历史遥测(分页)
    GET/api/devices/{id}/telemetry/count?since&until历史遥测总条数
    GET/api/devices/{id}/telemetry/aggregate按时间窗口聚合数值字段
    GET/api/devices/{id}/online-log?limit&offset&since上下线记录
    GET/api/devices/{id}/commands?limit&offset指令记录
    POST/api/devices/{id}/cmd下发指令 {"type":"switch","value":1}
    GET/api/devices/{id}/sensors物模型字段
    PUT/api/devices/{id}/sensors/{identifier}修改字段名称 / 单位 / 范围

    调用示例

    # 批量在线状态
    curl "https://iot.wenbo2005.online/api/devices/status?devIds=sensor_kitchen_01,sensor_living_02" \
      -H "X-API-Key: iot_xxxxxxxx"
    
    # 批量最新数据
    curl "https://iot.wenbo2005.online/api/devices/datas?devIds=sensor_kitchen_01" \
      -H "X-API-Key: iot_xxxxxxxx"
    
    # 最近 24 小时,按 5 分钟聚合 temp / humi
    curl "https://iot.wenbo2005.online/api/devices/sensor_kitchen_01/telemetry/aggregate?interval=300&fields=temp,humi" \
      -H "X-API-Key: iot_xxxxxxxx"
    
    # 下发指令(响应 200 表示已下发,202 表示设备离线已排队)
    curl -X POST "https://iot.wenbo2005.online/api/devices/ESP8266_TCP/cmd" \
      -H "X-API-Key: iot_xxxxxxxx" \
      -H "Content-Type: application/json" \
      -d '{"type":"switch","value":1}'

    本项目是个人服务器轻量实现,和新大陆平台一致的部分是设备、传感器、传感数据、指令与 OTA 升级;项目分组、策略引擎等企业级功能不在此实现。聚合接口单次最多读取 5 万条原始记录,时间范围过大时请调大 interval。

    知微
    历史记录

    —

    知微
    账号

    用户设置

    基本资料

    修改密码

    密码每月只能修改一次,需要邮箱验证码。

    API Key

    应用端调用该接口,放在请求头 X-API-Key 或 Authorization: Bearer 中。Key 到期后自动失效,需要重新生成。

    当前 API Key

    —

    生成、重置、吊销都需要邮箱验证码;重新生成后旧 Key 立即失效,正在使用旧 Key 的应用会收到 401。

    固件库 0

    上传 ESP32 / ESP8266 的 .bin 固件,只保存在服务器,通过临时签名地址下发给设备。

    设备升级 0

    选择目标版本后点升级;设备离线时指令会排队,上线后自动执行。

    用户管理