—
离线| 名称 | 类型 | 最新值 | 最近上报 | 操作 |
|---|
| 名称 | 操作类型 | 当前值 | 控制 | 操作 |
|---|
知微·设备云
知微·设备云选好接入方式,拿到凭据后设备就能开始上报。
字段标识与设备上报 data 里的键名一致,就会自动显示。
| 名称 | 类型 | 最新值 | 最近上报 | 操作 |
|---|
| 名称 | 操作类型 | 当前值 | 控制 | 操作 |
|---|
平台提供 MQTT、TCP 两种长连接方式(适合实时控制),以及 HTTPS 主动上报方式(适合无法保持连接的设备)。新建设备后凭据只显示一次,请立即保存。
| 参数 | 值 |
|---|---|
| 地址 / 端口 | mqtts://8.148.67.96:8883(TLS 加密) |
| 用户名 | 等于设备 ID,例如 ESP8266_MQTT |
| 密码 | 创建设备时返回,仅显示一次 |
| Client ID | 任意,同设备重复连接会顶掉旧连接 |
| 方向 | 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}
| type | value | 规则 |
|---|---|---|
switch | 0 或 1 | 开关型,只能是 0/1 |
range | 整数 | 刻度型,在 min~max 内且按 step 递增 |
input | 字符串 | 输入框型,长度 ≤ 64 |
#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);
}
}
适合无法保持长连接的设备。创建 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}'
设备与平台保持一条 TLS 长连接,服务器可在毫秒级主动下发指令。连接成功后先发鉴权帧,之后每行一个 JSON。
| 参数 | 值 |
|---|---|
| 地址 / 端口 | tcp://8.148.67.96:9443(TLS 加密) |
| 设备 ID | 创建设备时的 device_id |
| 令牌 | 创建设备时返回,仅显示一次 |
| 心跳 | 设备每 20 秒发一次,60 秒无帧自动断线 |
// 设备 → 平台:首帧鉴权
{"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":"..."} 并断开;同一设备重复连接会顶掉旧连接。
传感器按 data_type 区分:number(数值)、bool(布尔)。字段标识(identifier)必须与上报 data 里的键名一致才会显示。
执行器按操作类型区分,对应下行指令里的 type:开关型(0/1)、刻度型(整数 + 步进)、输入框型(字符串,≤64 字)。
上报间隔建议 ≤ 60 秒,否则平台会判定离线(2 × keepalive)。
平台只负责下发升级指令,固件二进制由设备自己通过 HTTPS 下载并写入 flash,不经过 MQTT 或 TCP 网关。升级指令复用普通指令的下发通道,设备回执后开始下载,重启后上报的 fw 与目标版本一致才算成功。
| 芯片 / Flash | Arduino IDE 选项 | 可用 app 分区 |
|---|---|---|
| ESP8266 1MB(ESP-01S,25Q80) | 1MB (FS:none OTA:~502KB) | 约 480KB,固件必须小于它 |
| ESP8266 4MB(NodeMCU / ESP-12E) | 4M (1M SPIFFS) | 约 1.9MB |
| ESP32 | min_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 |
chip | esp8266 或 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 |
timeout | 15 分钟无动作(IOT_OTA_TASK_TIMEOUT) |
设备不持有账号,下载靠签名完成:HMAC-SHA256(SESSION_SECRET, "firmware_id:device_id:exp"),签名和过期时间都在 query 里,只对这台设备有效,默认 1 小时(IOT_OTA_URL_TTL,建议不要超过 1 小时,否则重放风险变大)。响应头带 X-SHA256 便于二次校验。
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 内存限制,不回报中间进度,属预期行为 |
BearSSL::X509List)或临时 setInsecure(),否则握手失败表现为一直下载不动。应用端(App、小程序、后端服务)用 API Key 调用,不需要模拟网页登录。每个账号只有一个 Key,在右上角用户名 → 开发设置 中生成,可选择有效期(30/90/180/365/730 天),生成、重置、吊销都需要邮箱验证码。
控制台「开发设置」会直接显示当前 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。
密码每月只能修改一次,需要邮箱验证码。
应用端调用该接口,放在请求头 X-API-Key 或 Authorization: Bearer 中。Key 到期后自动失效,需要重新生成。
当前 API Key
—
生成、重置、吊销都需要邮箱验证码;重新生成后旧 Key 立即失效,正在使用旧 Key 的应用会收到 401。
上传 ESP32 / ESP8266 的 .bin 固件,只保存在服务器,通过临时签名地址下发给设备。
选择目标版本后点升级;设备离线时指令会排队,上线后自动执行。