协议(唯一权威文档:docs/柜控板固件对接文档.md) - 应答必带 act(与请求一致)、msg_id 与请求一致;判"应答 vs 指令"只看有没有 suc - dev_id 只在 auth_str/login 携带;其余报文用内部字段 _dev_id 路由(下发前由 device-server 摘除) - id 只表示动作目标(柜级省略 / 一段=单板 / 两段=仓体级),不再含 IMEI - msg_id 每连接从 1 起自增;60 秒交互超时;status_post 每通道 18 字符 hex 全量上报 - 未登录连接 30 秒内未 login 即断开;未登录只放行 auth_str/login 平台侧实现 - 上行解析按新结构重写(cabinet/boards/battery),删除废弃的 status/parsed_status - 订单:唯一入口 = on;bat_in 只登记电池台账;4 条结束路径统一走 finalize_order(含充电记录落库) - 新增设备日志保留策略(90 天,分批删除);未登记 IMEI 静默丢弃;停电标记按 status1 bit0 恢复清除 - 待确认第 13 项兜底:平台侧按 电压 × 电流 × 时间 积分计算订单电量(workers/energy.rs) - 待确认第 18 项兜底:status_post bit7 连续 2 次无电池 → 判定电池被拔走并提前结单 修复(均为部署后才暴露的问题) - 队列消费禁用 BRPOP:无限阻塞会因代理掐连接永久卡死(零错误日志);有限超时也只是 "超时才醒",下行指令延迟 15~45 秒 → 改为 RPOP 非阻塞轮询,延迟降到 0.1~0.2 秒 - sync_iccid 把可空列 iccid 按 Option<String> 解码 → 整条 login 失败(ICCID 未上报的新柜子永远登录不上) - 迁移读 information_schema.COLUMN_COMMENT 需 CAST(... AS CHAR)(MySQL 8 驱动报 VARBINARY,否则迁移 panic) - 部署注意事项:.env 不支持行内注释;pscp 上传需 chmod +x;运行中二进制需 .new + mv 原子替换 部署 - 生产迁到 API 9000 / TCP 9001(systemd 单元补 EnvironmentFile、nginx 同步指向) - 新增 deploy/deploy-prod.ps1(本地一键:构建→校验→备份→原子替换→重启→验收→无副作用探测) - 废弃 deploy-prod.sh / deploy-test.sh(原脚本会在 target/release 找产物、装重复 systemd 单元、 并用本地 .env 覆盖线上 .env) 质量 - 单测:api-server 39 个 / device-server 13 个(含协议示例逐值断言、跨模块 Redis key 契约) - 两服务 cargo check 零错误零告警;web tsc --noEmit 通过 - test/tcp_e2e_smoke.py 端到端联调 13/13 通过
27 KiB
API 接口文档
充电柜矩阵系统(PMS)接口规范。PC 管理端与微信小程序用户端共用同一套 API。
基础信息
- 测试环境:
https://test.anzhizhichong.com/pms/api/ - 正式环境:
https://app.anzhizhichong.com/pms/api/ - 认证方式:Bearer Token(JWT)
- 响应格式:
{ "code": 0, "data": ..., "message": "ok" }
统一端点清单
| 方法 | 路径 | 说明 | 权限 |
|---|---|---|---|
| POST | /api/auth/login |
登录 | 免认证 |
| GET | /api/auth/logout |
登出 | 需认证 |
| GET | /api/auth/me |
当前用户信息 | 需认证 |
| GET | /api/dashboard |
仪表盘数据 | 需认证 |
| GET | /api/cabinets |
设备列表(含 RSSI) | device:view |
| POST | /api/cabinets |
批量创建柜子 | device:create |
| GET | /api/cabinets/:id |
设备详情(含仓板+仓体) | device:view |
| PUT | /api/cabinets/:id |
更新柜子 | device:edit |
| DELETE | /api/cabinets/:id |
删除柜子 | device:delete |
| POST | /api/cabinets/:id/regenerate-auth |
重置安全码(仅总管理员) | device:auth_str:reset |
| POST | /api/cabinets/:id/bind |
绑定设备到项目 | device:edit |
| POST | /api/cabinets/:id/replace-imei |
更换 IMEI | device:edit |
| GET | /api/cabinets/abstract/:abstract_id |
扫码查设备(免认证) | 免认证 |
| GET | /api/cabinets/scan/:abstract_id |
扫码验证(需认证) | 需认证 |
| GET | /api/cabinets/filter |
设备列表(管理端) | 需认证 |
| GET | /api/cabinets/options |
设备下拉选项 | 需认证 |
| GET | /api/cabin-boards/options |
仓控板下拉选项 | 需认证 |
| GET | /api/compartments/options |
仓体下拉选项 | 需认证 |
| GET | /api/cabinets/realtime-status |
设备实时状态 | 需认证 |
| GET | /api/compartments/:id |
仓体/通道详情 | 需认证 |
| POST | /api/charge/start |
开始充电 | charge:start |
| POST | /api/charge/stop |
停止充电 | charge:stop |
| POST | /api/door/open |
开门(柜级:整柜共用一扇门,只解锁需人拉开) | charge:open_door |
| POST | /api/cabinets/{id}/open |
解锁柜门(柜级,同上;管理端设备详情页用) | charge:open_door |
| POST | /api/cabinets/{id}/pow-on |
接触器合闸(柜级) | device:operate |
| POST | /api/cabinets/{id}/pow-off |
接触器分闸(柜级) | device:operate |
| POST | /api/cabinets/{id}/reset |
重启整柜(柜级) | device:operate |
| POST | /api/cabin-boards/{id}/reset |
重启单块仓控板 | device:operate |
| GET | /api/organizations |
组织列表 | org:view |
| POST | /api/organizations |
创建组织 | org:create |
| PUT | /api/organizations/:id |
更新组织 | org:edit |
| DELETE | /api/organizations/:id |
删除组织 | org:delete |
| GET | /api/organizations/:org_id/projects |
项目列表 | 需认证 |
| POST | /api/organizations/:org_id/projects |
创建项目 | project:create |
| PUT | /api/projects/:id |
更新项目 | project:edit |
| DELETE | /api/projects/:id |
删除项目 | project:delete |
| GET | /api/organization-tree |
组织树(含项目/设备) | 需认证 |
| GET | /api/charge-records |
充电记录 | charge_record:view |
| GET | /api/charge-records/export |
导出充电记录 | charge_record:export |
| GET | /api/charge-orders |
充电订单列表 | charge_record:view |
| GET | /api/batteries |
电池台账列表 | device:view |
| GET | /api/energy-stats/summary |
能耗概览 | energy:view |
| GET | /api/energy-stats/cabinets |
柜子能耗排行 | energy:view |
| GET | /api/energy-stats/projects |
项目能耗统计 | energy:view |
| GET | /api/energy-stats/trend |
能耗趋势 | energy:view |
| GET | /api/energy-stats/export |
导出能耗数据 | energy:view |
| GET | /api/device-logs |
设备日志 | device:view |
| GET | /api/operation-logs |
操作日志 | system:log |
| GET | /api/users |
用户列表 | user:view |
| POST | /api/users |
创建用户 | user:create |
| PUT | /api/users/:id |
更新用户 | user:edit |
| DELETE | /api/users/:id |
删除用户 | user:delete |
| PUT | /api/users/:id/reset-password |
重置密码 | user:edit |
| PUT | /api/users/:id/status |
设置用户状态 | user:edit |
| GET | /api/users/:id/permissions |
用户权限 | user:view |
| PUT | /api/users/:id/roles |
分配角色 | user:edit |
| GET | /api/users/:id/scopes |
用户数据范围 | user:view |
| PUT | /api/users/:id/scopes |
设置数据范围 | user:edit |
| GET | /api/roles |
角色列表 | role:view |
| POST | /api/roles |
创建角色 | role:create |
| PUT | /api/roles/:id |
更新角色 | role:edit |
| DELETE | /api/roles/:id |
删除角色 | role:delete |
| PUT | /api/roles/:id/permissions |
角色赋权 | role:edit |
| GET | /api/permissions |
权限码列表 | role:view |
| GET | /api/downloads |
下载中心 | 需认证 |
| GET | /api/downloads/:id |
下载详情 | 需认证 |
| GET | /api/downloads/:id/file |
下载文件 | 需认证 |
| POST | /api/downloads/cleanup |
清理过期下载 | 需认证 |
| POST | /api/devices/:dev_id/command |
下发设备指令 | device:operate |
| GET | /api/devices/stats |
设备服务统计 | 需认证 |
| GET | /api/health |
健康检查 | 免认证 |
认证接口
POST /api/auth/login
请求:
{
"phone": "13800138000",
"password": "admin123"
}
响应:
{
"code": 0,
"data": {
"token": "eyJ0eXAi...",
"user": {
"id": 1,
"name": "管理员",
"phone": "13800138000",
"role_level": 2,
"organization_id": null,
"permissions": ["device:view", "device:create", "..."]
}
},
"message": "登录成功"
}
角色等级: 0=操作员,1=企业管理员,2=总管理员
小程序专用接口
GET /api/dashboard
小程序首页仪表盘数据。支持 org_id 参数按组织过滤。
响应:
{
"code": 0,
"data": {
"online_cabinets": 68,
"charging_count": 214,
"idle_channels": 722,
"fault_channels": 0,
"full_count": 0,
"today_charge_count": 0,
"today_energy_kwh": 0,
"today_charge_hours": 0,
"alerts": [],
"projects": [
{ "id": 1, "name": "无锡太湖新城换电站", "cabinet_count": 2, "charging": 16, "idle": 56, "fault": 0 }
]
}
}
GET /api/cabinets
设备列表,支持 project_id 和 org_id 过滤。响应包含实时 RSSI。
响应字段:
{
"id": 1,
"abstract_id": "10-00000001",
"name": "太湖新城换电柜A",
"status": 1,
"project_id": 1,
"rssi": 85,
"channel_count": 36,
"idle_count": 30,
"charging_count": 6,
"full_count": 0,
"fault_count": 0
}
GET /api/cabinets/abstract/:abstract_id
扫码入口,免认证。通过二维码中的抽象ID查找设备。
响应:
{
"code": 0,
"data": {
"id": 1,
"abstract_id": "10-00000001",
"imei": "864720562557021",
"iccid": "8986012520983901841",
"name": "太湖新城换电柜A",
"status": 1,
"cabin_boards": [
{
"id": 1,
"board_id": 1,
"status": 0,
"compartments": [
{ "id": 1, "channel_id": 1, "status": 0 }
]
}
]
}
}
GET /api/compartments/:id
仓体/通道详情,含实时 BMS 数据和充电记录。
响应字段:
voltage— mV(整数)current— mA(整数)energy— Wh(整数)soc— ‰(0-10000,前端已自动转为 %)cell_voltages— mV 数组
POST /api/charge/start
请求: { "compartment_id": 123 }
权限:
charge:start+ 组织隔离(用户只能操作所属组织的设备)
POST /api/charge/stop
请求: { "compartment_id": 123 }
权限:
charge:stop+ 组织隔离
POST /api/door/open
请求: { "compartment_id": 123 }
权限:
charge:open_door+ 组织隔离
GET /api/cabinets/scan/:abstract_id
扫码验证,需认证。验证当前用户是否有权访问该设备。
管理端接口
GET /api/charge-records
充电记录列表,支持按柜子/时间过滤,响应含组织数据隔离。
查询参数:
| 参数 | 类型 | 说明 |
|---|---|---|
page |
int | 页码(默认 1) |
page_size |
int | 每页条数(默认 20,最大 100) |
cabinet_id |
int | 按柜子过滤 |
cabin_board_id |
int | 按仓控板过滤 |
compartment_id |
int | 按仓体过滤 |
start_time |
string | 开始时间(含),格式 YYYY-MM-DD HH:MM:SS |
end_time |
string | 结束时间(含),格式同上 |
响应:
{
"code": 0,
"data": {
"list": [
{
"id": 1,
"cabinet_id": 1,
"cabinet_name": "太湖新城换电柜A",
"cabin_board_id": 1,
"board_id": 1,
"compartment_id": 1,
"channel_id": 1,
"start_time": "2026-07-05 10:00:00",
"end_time": "2026-07-05 12:00:00",
"energy": 1200,
"max_temperature": 30435,
"status": 1,
"status_text": "正常结束"
}
],
"total": 50,
"page": 1,
"page_size": 20
},
"message": "ok"
}
状态码: 0=充电中,1=正常结束,2=异常中断,3=手动停止
GET /api/charge-records/export
异步导出充电记录 Excel。创建导出任务后自行去下载中心取文件。
查询参数: 同 GET /api/charge-records
响应:
{ "code": 0, "data": { "task_id": 1 }, "message": "导出任务已创建" }
GET /api/charge-orders
充电订单列表。订单记录一次充电事件,含电池ID、SOC变化、结束原因。
查询参数:
| 参数 | 类型 | 说明 |
|---|---|---|
page |
int | 页码(默认 1) |
page_size |
int | 每页条数(默认 20,最大 100) |
cabinet_id |
int | 按柜子过滤 |
compartment_id |
int | 按仓体过滤 |
battery_id |
string | 电池ID(模糊匹配) |
status |
int | 状态过滤 |
start_time |
string | 开始时间(含) |
end_time |
string | 结束时间(含) |
响应:
{
"code": 0,
"data": {
"list": [
{
"id": 1,
"cabinet_id": 1,
"cabinet_name": "太湖新城换电柜A",
"compartment_id": 1,
"battery_id": "BAT001",
"start_time": "2026-07-05 10:00:00",
"end_time": "2026-07-05 12:00:00",
"start_soc": 20,
"end_soc": 95,
"energy": 1200,
"reason": null,
"reason_text": "-",
"status": 1,
"status_text": "正常结束",
"created_at": "2026-07-05 10:00:05"
}
],
"total": 50,
"page": 1,
"page_size": 20
},
"message": "ok"
}
订单状态: 0=充电中,1=正常结束,2=手动停止,3=异常终止,4=拔出结束
GET /api/batteries
电池台账列表。记录每块电池的插入/拔出事件和当前状态。
查询参数:
| 参数 | 类型 | 说明 |
|---|---|---|
page |
int | 页码(默认 1) |
page_size |
int | 每页条数(默认 20,最大 100) |
status |
int | 状态过滤 |
battery_id |
string | 电池ID(模糊匹配) |
响应:
{
"code": 0,
"data": {
"list": [
{
"id": 1,
"battery_id": "BAT001",
"brand": "星恒",
"model": "XH-48V",
"voltage": 52000,
"soc": 45,
"status": 1,
"cabinet_id": 1,
"compartment_id": 1,
"last_seen": "2026-07-05 12:00:00",
"created_at": "2026-07-01 08:00:00"
}
],
"total": 50,
"page": 1,
"page_size": 20
},
"message": "ok"
}
GET /api/energy-stats/summary
能耗概览,按组织数据隔离返回今日/本月能耗数据。
响应:
{
"code": 0,
"data": {
"today_energy": 45000,
"month_energy": 1200000,
"estimated_cost": 840.00,
"cabinet_count": 36,
"online_count": 34
},
"message": "ok"
}
today_energy/month_energy— Wh(整数)estimated_cost— 预估费用(float,元),按COST_PER_WH单价计算
数据单位规范
核心原则:所有数值字段使用整数存储,禁止浮点类型 (f32/f64/DECIMAL/FLOAT)
单位转换仅在前端: API 始终返回原始单位值,前端负责转换为用户友好单位显示。后端禁止做任何单位换算。
标量字段
| 字段 | 类型 | 单位 | 前端显示 | 转换公式 |
|---|---|---|---|---|
energy / total_energy / today_energy / month_energy / energy_kwh¹ |
i64 | Wh | kWh | / 1000 |
voltage / cell_voltages[] |
i64 | mV | V | / 1000 |
current |
i64 | mA | A | / 1000 |
soc / start_soc / end_soc |
i64 | ‰ | % | / 10 |
temp / cell_temps[] / max_temperature |
i64 | K×100 | °C | / 100 - 273.15 |
rssi |
i64 | 0–99 信号等级 | 直接显示 | 不转换(不是 dBm) |
cost / estimated_cost |
i64/f64 | 元 | ¥ | 不转换(后端业务计算) |
today_charge_count / today_charge_hours |
i64 | 计数值 | 直接显示 | 不转换 |
¹ 注意:energy_kwh / today_energy_kwh 为历史遗留命名,实际值仍为 Wh,前端仍需 / 1000。
示例
| 原始值 | 含义 | 前端显示 |
|---|---|---|
52000 |
电压(mV) | 52.0 V |
10000 |
电流(mA) | 10.0 A |
120000 |
功率(mW) | 120.0 W |
1000 |
充电量(Wh) | 1.000 kWh |
850 |
SOC(‰) | 85.0% |
29865 |
温度(K×100) | 25.5°C |
85 |
RSSI 信号等级(0–99) | 85 |
[32456,32500,32300] |
单体电压(mV) | 32.456V, 32.500V, 32.300V |
[29865,29900] |
单体温度(K×100) | 25.5°C, 25.9°C |
TCP 设备通讯协议
⛔ 本节(自本行起至下一个
##标题之前)全部内容已废弃,禁止作为实现依据。 唯一权威文档:柜控板固件对接文档.md(v1.1) —— 所有 TCP 报文格式、字段名、act清单、签名算法、保活/超时规则、时序与异常处理,一律以该文档为准。本节保留仅为历史参考。已知与定稿规范的重大冲突(仅列关键几处,避免误读):
→ 只有dev_id上行全部报文必带auth_str/login带,其余报文不带(设备身份由 TCP 连接确定)→id=IMEI/IMEI-仓控板-通道id不含 IMEI:柜级省略、单板一段"01"、仓体级两段"01-3"空闲 1 分钟 / 充电 15 秒上报→status_post固定每 60 秒一次、每次全量→ 返回login应答返回cellspec(柜子规格,如"0606")与work_mode→ 改为pow_fail带infopow_fail_dc/pow_fail_ac两条独立指令,无附加字段→ 改为status_post的statusHEX 字典 /bat_in的bms块cabinet+boards[].channels[](每通道 18 字符 hex 透传) 与battery块→bat_in建单bat_in与订单无关,订单只由on建立- 内部字段已由
id改为_dev_id(供 device-server 路由,下发前摘除)
设计原则
MCU(Air780E)资源有限,TCP 协议遵循以下原则:
- 响应扁平化:字段直接放在顶层,不包
data/result等嵌套层- 字段最小化:需要几个字段传几个,不传空值、默认值
- JSON 紧凑:去空格回车,不传多余引号和逗号(可用
serde_json::to_string紧凑模式)- 响应必达:所有上行报文平台都会回复;但设备回执(带
suc)平台不再回包(防误配对)
通信规则(⚠️ 下列旧描述已废弃,见节首说明)
- TCP 长连接,无需 PING 包 ✅(保活由
status_post承担) 空闲 1 分钟上报,充电 15 秒上报❌ → 固定每 60 秒全量超时 5000ms❌ → 每个交互 60 秒超时,超时作废、迟到应答丢弃、不重试- JSON 去空格回车省流量,LF 处理粘包 ✅
❌ → 仅dev_id:设备上报时使用auth_str/login携带❌ → 不含 IMEI:柜级省略 / 单板id:IMEI-仓控板-通道/IMEI"01"/ 仓体级"01-3"msg_id:两个方向各自独立计数,都每连接从1起自增,重连归零;判"应答 vs 指令"看有无suc
签名机制
- 设备首次连接调
auth_str接口获取8位安全码(仅一次) sign = SHA256(dev_id + auth_str + timestamp).substring(56, 63)- 时间戳秒级,平台校验±60秒
指令清单
⛔ 以下整段(含本表与其后的报文示例、
cell/pow_fail/bms字段说明)均为废弃的历史内容,请勿据此实现。 现行清单与报文以柜控板固件对接文档.md§4 / §5 为准。act共 13 个:auth_str/login/status_post/pow_fail_dc/pow_fail_ac/bat_in/bat_out/on/off/open/pow_on/pow_off/reset(已无pow_fail,拆成pow_fail_dc/pow_fail_ac)
| 动作 | 方向 | 说明(⚠️ 历史,见上方说明) |
|---|---|---|
login |
设备→平台 | 登录(含 sign+timestamp+iccid);成功应答返回 spec + work_mode(不再是 cell) |
auth_str |
设备→平台 | 获取安全码(首次 / 重置后;失败统一回 invalid signature 之外的两条见权威文档) |
status_post |
设备→平台 | 每 60 秒全量;结构为 cabinet + boards[].channels[](每通道 18 字符 hex),不再是 status 字典 |
pow_fail |
设备→平台 | 已废弃 → 改为 pow_fail_dc / pow_fail_ac(无附加字段) |
pow_fail_dc / pow_fail_ac |
设备→平台 | 直流停电 / 交流停电(事件上报) |
bat_in |
设备→平台 | 电池插入(battery 块:属性 + info 18hex + cells hex);不建单 |
bat_out |
设备→平台 | 电池拔出(结束订单 user_stop) |
on |
双向 | 开始充电(上行两形态:带 suc=回执 / 不带=上报;建单唯一入口) |
off |
双向 | 充电停止(结束订单 normal) |
open |
平台→设备 | 柜门解锁(柜级:整柜共用一扇门,需人拉开;无"关门"指令) |
pow_on / pow_off |
平台→设备 | 接触器合闸 / 分闸(pow_off 后仓控板随之下电 → 上报 pow_fail_dc) |
reset |
平台→设备 | 重启整柜(id 省略)或单块仓控板(id="01");先回执再重启 |
⚠️ 所有设备上报消息平台都会回复
{"suc":1,"msg_id":N}确认。响应字段扁平化,不加data包裹。
全局单位规范
所有数值字段使用整数存储,禁止浮点类型:
| 字段 | 单位 | 精度 | 说明 |
|---|---|---|---|
voltage |
mV | 1 | 52000 = 52.000V |
current |
mA | 1 | 10000 = 10.000A |
temp |
K×100 | 0.01K | 29865 = 298.65K = 25.5°C |
soc |
‰ | 0.1% | 4500 = 45.00% |
服务架构
设备 ─TCP─→ device-server ─LPUSH→ Redis ─BRPOP→ api-server
设备 ←TCP─ device-server ←BRPOP─ Redis ←LPUSH─ api-server
职责分层:
| 层 | 职责 |
|---|---|
| device-server | TCP连接管理、协议解析(HEX→结构化)、消息转发 |
| api-server | 业务逻辑:签名验证、状态持久化、充电事件、告警、指令下发 |
Redis 队列:
| 队列 | 方向 | 说明 |
|---|---|---|
device:reports |
设备→平台 | device-server 转发设备上报 |
device:commands |
平台→设备 | api-server 下发指令/响应 |
全局单位规范
login(设备→平台)
{"act":"login","msg_id":1,"dev_id":"123456789012345","sign":"abc12345","timestamp":1719800000,"iccid":"89860000000000000000"}
// 响应:{"suc":1,"msg_id":1,"cell":6}
iccid— SIM卡ICCID(变更时写 SIM 变更日志)cell— 仓控板数量(柜子配置属性)
auth_str(设备→平台)
{"act":"auth_str","msg_id":2,"dev_id":"123456789012345"}
// 响应:{"suc":1,"msg_id":2,"auth_str":"abcd1234"}
auth_str— 8位随机安全码(仅返回一次,标记后不可重复获取)
status_post(设备→平台)
{"act":"status_post","msg_id":10,"dev_id":"123456789012345","rssi":85,
"status":{"00":"0001","01":"000100020003000400050006","02":"000100020003000400050006"}}
// 响应:{"suc":1,"msg_id":10}
status 字段:
- key:
"00"=柜子级,"01"~"N"=仓控板级(N = cell-1) - value: 6通道状态拼接(每通道4个HEX字符=2字节)
- device-server 解析后附加
parsed_status字段,api-server 直接写入 Redis
HEX 解析(device-server 负责,格式待硬件团队最终确认):
- 字节1 bit1=充电中, bit2=充满, bit3=故障
- 当前实现:每通道4字符,取字节1解析
⚠️ HEX 编码格式待硬件团队最终确认,当前为预留实现。
pow_fail(设备→平台)
{"act":"pow_fail","msg_id":15,"dev_id":"123456789012345","info":"0"}
// 响应:{"suc":1,"msg_id":15}
info— 停电类型:0=直流停电(12V掉电),1=交流停电(380V掉电)
bat_in / bat_out(设备→平台)
{"act":"bat_in","msg_id":20,"id":"123456789012345-01-01","bms":{
"soc":4500,"voltage":52000,"current":10000,"temp":29865,
"battery_id":"BAT001","brand":"星恒","model":"XH-48V","sn":"SN123456"
}}
// 响应:{"suc":1,"msg_id":20}
{"act":"bat_out","msg_id":21,"id":"123456789012345-01-01","bms":{
"battery_id":"BAT001","soc":4500
}}
// 响应:{"suc":1,"msg_id":21}
BMS 字段(全局整数单位):
| 字段 | 单位 | 精度 | 说明 |
|---|---|---|---|
soc |
‰ | 0.1% | 4500 = 45.00% |
voltage |
mV | 1 | 52000 = 52.000V |
current |
mA | 1 | 10000 = 10.000A |
temp |
K×100 | 0.01K | 29865 = 298.65K = 25.5°C |
battery_id |
— | — | 电池唯一标识 |
brand |
— | — | BMS品牌 |
model |
— | — | BMS型号 |
sn |
— | — | BMS序列号 |
cell_voltages |
mV | 1 | 单体电芯电压数组 |
bat_out只需上传battery_id、soc(其他数据不变)。
on / off(双向)
// 平台→设备:下发充电
{"act":"on","msg_id":30,"id":"123456789012345-01-01"}
// 设备响应:{"suc":1,"msg_id":30}
// 设备→平台:充电开始确认
{"act":"on","msg_id":30,"id":"123456789012345-01-01"}
// 平台响应:{"suc":1,"msg_id":30}
// 平台→设备:下发停止
{"act":"off","msg_id":31,"id":"123456789012345-01-01"}
// 设备响应:{"suc":1,"msg_id":31}
// 设备→平台:充满自动停止
{"act":"off","msg_id":31,"id":"123456789012345-01-01"}
// 平台响应:{"suc":1,"msg_id":31}
on必须由平台下发,设备不可主动充电。MCU 保证off→bat_out顺序。
open(平台→设备)
{"act":"open","msg_id":40,"id":"123456789012345"}
响应: {"suc":1,"msg_id":40}
pow_on / pow_off(平台→设备)
{"act":"pow_on","msg_id":50,"id":"123456789012345"}
{"act":"pow_off","msg_id":51,"id":"123456789012345"}
响应: {"suc":1,"msg_id":50}
reset(平台→设备)
// 重启整柜
{"act":"reset","msg_id":60,"id":"123456789012345"}
// 重启仓控板
{"act":"reset","msg_id":61,"id":"123456789012345-01"}
响应: {"suc":1,"msg_id":60}
充电结束原因
| 平台收到 | 判断原因 |
|---|---|
off (设备→平台) |
normal — 正常充满 |
off (平台→设备) |
user_stop — 用户手动停止 |
bat_out |
normal — 电池拔出 |
bat_in (通道已有电池) |
forced — 新电池插入强制结束 |
| 超时未响应 | timeout — 通信超时 |
Redis 实时状态存储
层级结构
device:{imei} # 设备级 Hash
├─ online = "1" # 在线标记(TTL 300秒)
├─ rssi = "85" # 信号强度
├─ pow_fail_dc = "0/1" # 直流停电
├─ pow_fail_ac = "0/1" # 交流停电
└─ device:{imei}:board:{board_idx} # 板级 Hash
├─ status_hex = "00010002..." # 原始hex状态
└─ device:{imei}:board:{board_idx}:ch:{ch} # 通道级 Hash(1~6)
├─ on = "0/1" # 充电中
├─ full = "0/1" # 充满
├─ fault = "0/1" # 故障
├─ hex = "0001" # 原始hex(4字符)
├─ battery_id = "BAT001" # 电池ID(bat_in 写入,bat_out 清除)
├─ soc = "4500" # 电量 ‰
├─ voltage = "52000" # 电压 mV
├─ current = "10000" # 电流 mA
├─ temp = "29865" # 温度 K×100
├─ brand = "星恒" # BMS品牌
├─ model = "XH-48V" # BMS型号
└─ sn = "SN123456" # BMS序列号
HEX 解析由 device-server 负责,api-server 只接收结构化数据写入 Redis。
BMS 字段(全局整数单位)
| 字段 | 单位 | 精度 | 说明 |
|---|---|---|---|
soc |
‰ | 0.1% | 4500 = 45.00% |
voltage |
mV | 1 | 52000 = 52.000V |
current |
mA | 1 | 10000 = 10.000A |
energy |
Wh | 1 | 累计充电量 |
temp |
K×100 | 0.01K | 29865 = 298.65K = 25.5°C |
battery_id |
— | — | 电池唯一标识 |
brand |
— | — | BMS品牌 |
model |
— | — | BMS型号 |
sn |
— | — | BMS序列号 |
cell_voltages |
mV | 1 | 单体电芯电压数组 |
权限码列表
以
software/api-server/src/db/migrate.rs::seed_permissions的种子表为准(下表与之对齐)。 ⚠️ 历史上代码里曾误用一个不存在的权限码device:door:unlock(种子里从来没有它)→ 导致开门对所有角色 403,已修正为charge:open_door。
| 权限码 | 说明 |
|---|---|
device:view |
查看设备 |
device:operate |
操作设备(开合闸 / 重启 / 开门等柜级控制) |
device:create |
添加设备 |
device:edit |
编辑设备(含修改充电工作模式) |
device:delete |
删除设备 |
device:auth_str:reset |
重置安全码(仅总管理员) |
charge:start |
开始充电 |
charge:stop |
停止充电 |
charge:open_door |
开门(柜门解锁) |
charge_record:view |
查看充电记录 |
charge_record:export |
导出充电记录 |
device_log:view |
查看设备日志 |
energy:view |
查看能耗 |
energy:export |
导出能耗 |
user:view / user:create / user:edit / user:delete |
用户管理 |
role:view / role:create / role:edit / role:delete |
角色管理 |
operation_log:view |
查看操作日志 |
org:view / org:create / org:edit / org:delete |
组织管理 |