17 KiB
17 KiB
AGENTS.md - 充电柜项目工作手册
项目简介
充电柜矩阵系统(Power Matrix System / PMS) 产品名:安知充(anzhizhichong),已有充电站平台在运行,充电柜是新增产品线。 仓数不固定:1柜控板+N仓控板,每仓控板管6仓,最小6仓。
设备标识
- 平台抽象ID:
10-00000000(绑定柜控板,二维码对应此ID) - 设备ID:4G SIM卡15位IMEI(10进制)
- 仓体全局ID:
平台层ID-仓板ID-仓体ID - 扫码链接:
https://app.anzhizhichong.com/pms/{抽象ID}
技术栈
- 后端:Rust + Axum
- 前端:React + Arco Design Pro(React版)
- 数据库:MySQL(阿里云RDS),开发/生产共用
- 缓存:Redis(阿里云Redis),开发/生产共用
- 设备通讯:TCP长连接(柜控板4G模块,Air780E Cat.1)
- 权限:RBAC,精确到按钮级别(权限码如 device🚪unlock)
项目结构
D:\charging-cabinet\
├── tasks/ # 任务文件(opencode执行)
├── docs/ # 开发文档、协议文档
├── deploy/ # 部署配置文件
│ ├── nginx/ # Nginx配置
│ ├── *.service # systemd服务单元
│ └── deploy-*.sh # 部署脚本
├── software/
│ ├── api-server/ # Rust API服务
│ ├── device-server/ # Rust TCP设备服务
│ └── web/ # React前端
├── hardware/ # 硬件资料(原理图/PCB等)
└── test/ # 测试用例/报告
部署架构
环境规划
| 环境 | 域名 | API服务 | TCP服务 | Redis DB | 部署路径 | URL路径 |
|---|---|---|---|---|---|---|
| 正式环境 | app.anzhizhichong.com | :9000 | :9001 | db5 | /data/pms | /pms/ |
| 测试环境 | test.anzhizhichong.com | :3000 | :3001 | db6 | /data/pms_dev | /pms/ |
服务端口分配:
- 测试环境:API 3000,TCP 3001
- 正式环境:API 9000,TCP 9001
部署路径:
- 产品介绍页:/data/landing
- 正式环境:/data/pms(api-server、device-server、web)
- 测试环境:/data/pms_dev(api-server、device-server、web)
Nginx配置:
- 正式: /etc/nginx/conf.d/app.anzhizhichong.com.conf
- 测试: /etc/nginx/conf.d/test.anzhizhichong.com.conf
服务器信息
- 外网IP: 118.31.119.206
- 内网IP: 172.25.42.239
- 系统: CentOS Stream 9(2核/3.5G/49G)
- ZeroTier: 网络ID
63e843dd42f54b9c,服务器节点12a91dd47d,IP10.8.0.252 - 本地电脑ZeroTier IP:
10.8.0.248 - 已装软件: Nginx 1.20.1、ZeroTier 1.16.2、socat(开机自启)
- 服务: mysql-proxy(3306→RDS)、redis-proxy(6379→Redis)、Gitea(3080)、acme.sh
- 不装: Docker/Node/Rust(本地编译传二进制)
- SSH: plink连接,首次需
echo y接受host key
正式环境数据库
- MySQL:
rm-bp1bb7yiv5n8z4uvp.mysql.rds.aliyuncs.com,user: root,password: Hbhyg731024,database:pms - Redis:
r-bp1lsezvgzmgtwhbtg.redis.rds.aliyuncs.com,password: Hbhyg731024,db: 5
本地开发环境
数据库连接(通过ZeroTier):
MySQL: 10.8.0.252:3306
user: root
password: Hbhyg731024@
database: pms_dev (新建,不动AZZCWeChat*)
Redis: 10.8.0.252:6379
password: Hbhyg731024@
db: 6 (测试环境用db6,不动db1-5)
代理说明:
- 服务器nginx stream转发MySQL(3306)和Redis(6379)到阿里云RDS
- systemd自启,
Restart=always自动恢复 - 本地直连
10.8.0.252,无需SSH隧道
证书
- 阿里云免费SSL证书(acme.sh自动续期)
- 存放路径:
/etc/nginx/ssl/{域名}.pem+/etc/nginx/ssl/{域名}.key - 已配置: app.anzhizhichong.com、test.anzhizhichong.com
内部服务
- Gitea:
https://app.anzhizhichong.com/doc/(端口3080)- 组织: anzhizhichong
- 仓库: charging-cabinet
- 用户: admin / skyp76
开发约束
禁止操作
- 禁止在服务器上编译Rust — 2核/3.5G会OOM,本地编译传二进制
- 禁止使用socat fork模式 — 进程泄漏会耗尽内存,用nginx stream代理
编码规范
- 任务需求写到
tasks/目录的md文件 - 通过Agent子代理包裹opencode执行,异步通知
- 任务要拆的尽量细
- 质量约束模板固定追加到每个任务文件
- opencode超时必须报告用户,不能fallback手动执行
- 审查用不同模型(不用开发同款)
编码任务下发
- 任务需求写到
tasks/目录的md文件 - 通过Agent子代理包裹opencode执行,异步通知
- 质量约束模板固定追加到每个任务文件
质量约束模板
所有编码任务必须追加以下约束:
- 完成代码后执行对应栈静态校验(Rust→cargo check,TS→tsc --noEmit),零报错零警告
- 分层拆分,单函数≤80行,命名语义化,完整注释
- 所有外部IO/网络请求异常捕获,禁止裸panic
- 分支逻辑全覆盖,不遗漏兜底分支
- 常量抽离,不使用废弃API
- Rust:内存安全,合理管理所有权,禁用unsafe无合理理由
- React:函数式组件+Hooks,不写Class组件
验收流程
- 静态校验命令通过
- 跑起来看效果
- 抽查关键逻辑(异常处理、权限校验、协议解析)
部署与维护
SSH连接
# 外网IP(推荐)
echo y | plink -ssh root@118.31.119.206 -pw "Hbhyg731024@"
# ZeroTier内网IP
echo y | plink -ssh root@10.8.0.252 -pw "Hbhyg731024@"
数据库连接
# MySQL(通过ZeroTier代理)
mysql -h 10.8.0.252 -u root -p
# 密码:Hbhyg731024@
# 测试环境库:pms_dev
# 正式环境库:pms
# Redis(通过ZeroTier代理)
redis-cli -h 10.8.0.252 -a "Hbhyg731024@"
# 测试环境:db6
# 正式环境:db5
Nginx配置
正式环境 (/etc/nginx/conf.d/app.anzhizhichong.com.conf):
server {
listen 80;
server_name app.anzhizhichong.com;
return 301 https://$server_name$request_uri;
}
server {
listen 443 ssl http2;
server_name app.anzhizhichong.com;
ssl_certificate /etc/nginx/ssl/app.anzhizhichong.com.pem;
ssl_certificate_key /etc/nginx/ssl/app.anzhizhichong.com.key;
ssl_protocols TLSv1.2 TLSv1.3;
ssl_ciphers HIGH:!aNULL:!MD5;
# 产品介绍页
root /data/landing;
index index.html;
location /doc/ {
proxy_pass http://127.0.0.1:3080/;
proxy_set_header Host $host;
proxy_set_header X-Real-IP $remote_addr;
proxy_set_header X-Forwarded-For $proxy_add_x_forwarded_for;
proxy_set_header X-Forwarded-Proto $scheme;
client_max_body_size 500m;
}
location /pms/api/ {
proxy_pass http://127.0.0.1:9000/;
proxy_set_header Host $host;
proxy_set_header X-Real-IP $remote_addr;
proxy_set_header X-Forwarded-For $proxy_add_x_forwarded_for;
proxy_set_header X-Forwarded-Proto $scheme;
}
# PMS管理前端
location /pms/ {
alias /data/pms/web/;
try_files $uri $uri/ /pms/index.html;
}
}
测试环境 (/etc/nginx/conf.d/test.anzhizhichong.com.conf):
server {
listen 80;
server_name test.anzhizhichong.com;
return 301 https://$server_name$request_uri;
}
server {
listen 443 ssl http2;
server_name test.anzhizhichong.com;
ssl_certificate /etc/nginx/ssl/test.anzhizhichong.com.pem;
ssl_certificate_key /etc/nginx/ssl/test.anzhizhichong.com.key;
ssl_protocols TLSv1.2 TLSv1.3;
ssl_ciphers HIGH:!aNULL:!MD5;
root /data/pms_dev;
index index.html;
location /pms/api/ {
proxy_pass http://127.0.0.1:3000/;
proxy_set_header Host $host;
proxy_set_header X-Real-IP $remote_addr;
proxy_set_header X-Forwarded-For $proxy_add_x_forwarded_for;
proxy_set_header X-Forwarded-Proto $scheme;
}
location /pms/ {
try_files $uri $uri/ /pms/index.html;
}
}
测试环境SSL证书与正式环境共用(test是app证书的SAN域名),部署时复制pem+key即可。
部署步骤
1. 本地编译
# 后端(Rust)
cd software/api-server
cargo build --release
cd software/device-server
cargo build --release
# 前端(React)
cd software/web
npm run build
2. 传输到服务器
# 传输后端二进制(测试环境)
scp software/api-server/target/release/api-server root@118.31.119.206:/data/pms_dev/api-server/
scp software/device-server/target/release/device-server root@118.31.119.206:/data/pms_dev/device-server/
# 传输后端二进制(正式环境)
scp software/api-server/target/release/api-server root@118.31.119.206:/data/pms/api-server/
scp software/device-server/target/release/device-server root@118.31.119.206:/data/pms/device-server/
# 传输前端静态文件
scp -r software/web/dist/* root@118.31.119.206:/data/pms_dev/web/ # 测试环境
scp -r software/web/dist/* root@118.31.119.206:/data/pms/web/ # 正式环境
3. 服务器重启服务
# SSH到服务器
echo y | plink -ssh root@118.31.119.206 -pw "Hbhyg731024@"
# 重启测试环境
systemctl restart pms-api-test
systemctl restart pms-device-test
# 重启正式环境
systemctl restart pms-api-prod
systemctl restart pms-device-prod
# 重启Nginx
systemctl restart nginx
服务管理
# 查看服务状态
systemctl status pms-api-test # 测试环境API
systemctl status pms-api-prod # 正式环境API
systemctl status nginx
# 查看日志
journalctl -u pms-api-test -f
journalctl -u pms-api-prod -f
tail -f /var/log/nginx/error.log
硬件架构
- 柜控板(ID128)×1 + 仓控板(ID1-N)×N,每仓控板管6个仓体
- 仓控板与电源:1TN RS485协议(翌工)
- 仓控板与BMS:星恒Modbus / 天能485 / 无协议(平台可配置)
- 仓控板与柜控板:自定义485协议(14+功能码)
- 柜控板与后台:4G TCP长连接(Air780E模块)
开发范围(当前阶段)
做
- 后台管理系统(Web端)
- 用户端H5页面(
/pms/{id},微信扫码可访问) - 设备TCP通讯服务(协议文档已就绪)
不做
- 微信小程序(名额暂不可用,先用H5)
- 支付/计费(本系统不涉及)
组织架构
组织(企业) → 项目(放置地点) → 设备(充电柜)
设备卖给哪个企业就划拨给该组织,设备放置的具体地点称为项目,设备必须划拨到项目下。
数据模型
核心关系
organization(组织/企业)
├── project(项目/放置地点)
│ └── cabinet(充电柜)
│ ├── cabin_board(仓控板×N)
│ │ └── compartment(仓体×6)
│ ├── charge_record(充电记录)
│ └── energy_stat(能耗统计)
├── user(用户)
└── device_log(设备日志)
operation_log(操作日志)
数据库表
| 表名 | 说明 |
|---|---|
| organizations | 组织表 |
| projects | 项目表 |
| cabinets | 充电柜表(IMEI、abstract_id、auth_str) |
| cabin_boards | 仓控板表 |
| compartments | 仓体表 |
| users | 用户表(手机号登录) |
| charge_records | 充电记录表 |
| device_logs | 设备日志表 |
| energy_stats | 能耗统计表 |
| operation_logs | 操作日志表 |
TCP通讯协议
- 长连接,无需PING包
- 空闲1分钟上报,充电15秒上报
- 超时5000ms
- JSON去空格回车省流量,LF处理粘包
dev_id:IMEI(10进制),sub_device_id:485 IDmsg_id:自增会话ID- 全局通道号:
dev_id-sub_device_id-通道号(如123456789012345-01-6)
签名机制
- 设备首次连接调
auth_str接口获取8位安全码(仅一次) sign = SHA256(dev_id + auth_str + timestamp).substring(56, 63)- 时间戳秒级,平台校验±60秒
指令清单
| 动作 | 方向 | id格式 | 说明 |
|---|---|---|---|
login |
设备→平台 | 无 | 登录(含sign+timestamp) |
auth_str |
设备→平台 | 无 | 获取安全码(仅首次) |
status_post |
设备→平台 | 无 | 定时上报(rssi+status字典) |
pow_fail |
设备→平台 | 无 | 12V电源掉电上报 |
bat_in |
设备→平台 | IMEI-仓控板-通道 |
电池插入(含BMS数据,字段待定) |
bat_out |
设备→平台 | IMEI-仓控板-通道 |
电池拔出 |
on |
双向 | IMEI-仓控板-通道 |
开始充电(平台下发/设备上报) |
off |
双向 | IMEI-仓控板-通道 |
停止供电(平台手动/设备自动) |
open |
平台→设备 | IMEI |
开门 |
pow_on |
平台→设备 | IMEI |
接触器合闸(恢复380V供电) |
pow_off |
平台→设备 | IMEI |
接触器分闸(切断380V供电) |
reset |
平台→设备 | IMEI 或 IMEI-仓控板 |
重启整柜或仓控板 |
指令详情
login(设备→平台)
设备登录,验证签名。
// 请求
{"act":"login","msg_id":1,"dev_id":"123456789012345","sign":"abc12345","timestamp":1719800000,"h_ver":"1.0.0","s_ver":"1.0.0"}
// 响应
{"suc":1,"msg_id":1,"cell":6}
字段说明:
sign— 签名(SHA256(dev_id+auth_str+timestamp)后8位)timestamp— 秒级时间戳(平台校验±60秒)h_ver— 硬件版本s_ver— 软件版本cell— 仓控板数量(响应)
auth_str(设备→平台)
获取8位安全码,仅首次连接调用。
// 请求
{"act":"auth_str","msg_id":2,"dev_id":"123456789012345","ccid":"89860000000000000000"}
// 响应
{"suc":1,"msg_id":2,"data":{"auth_str":"abcd1234"}}
字段说明:
ccid— SIM卡ICCID号auth_str— 8位随机安全码(响应,仅返回一次)
status_post(设备→平台)
定时上报,空闲60秒/充电15秒。
// 请求
{"act":"status_post","msg_id":10,"dev_id":"123456789012345","rssi":85,"status":{"123456789012345-01":"000100020003000400050006"}}
// 响应
{"suc":1,"msg_id":10}
status字段说明:
- key:
IMEI-仓控板ID - value: 6个通道状态拼接(每通道2字节=4个HEX字符)
pow_fail(设备→平台)
电源掉电上报。
// 请求
{"act":"pow_fail","msg_id":15,"dev_id":"123456789012345","info":"0"}
// 响应
{"suc":1,"msg_id":15}
字段说明:
info— 停电类型:0=直流停电(12V掉电),1=交流停电(380V掉电)
pow_on(平台→设备)
接触器合闸,恢复380V充电电源供电。
// 请求
{"act":"pow_on","msg_id":16,"dev_id":"123456789012345"}
// 响应
{"suc":1,"msg_id":16}
pow_off(平台→设备)
接触器分闸,切断380V充电电源供电。
// 请求
{"act":"pow_off","msg_id":16,"dev_id":"123456789012345"}
// 响应
{"suc":1,"msg_id":16}
bat_in(设备→平台)
电池插入事件,立即上报。
// 请求
{"act":"bat_in","msg_id":20,"id":"123456789012345-01-6","bms":{"soc":45,"voltage":52.3}}
// 响应
{"suc":1,"msg_id":20}
平台处理:
- 记录电池信息
- 验证电池合规性
- 自动下发
on指令
bat_out(设备→平台)
电池拔出事件,立即上报。
// 请求
{"act":"bat_out","msg_id":21,"id":"123456789012345-01-6"}
// 响应
{"suc":1,"msg_id":21}
on(双向)
平台→设备: 开始充电
// 请求
{"act":"on","msg_id":30,"id":"123456789012345-01-6"}
// 响应
{"suc":1,"msg_id":30}
设备→平台: 充电开始确认(自动充电模式)
// 上报
{"act":"on","msg_id":30,"id":"123456789012345-01-6"}
off(双向)
平台→设备: 手动停止充电
// 请求
{"act":"off","msg_id":31,"id":"123456789012345-01-6"}
// 响应
{"suc":1,"msg_id":31}
设备→平台: 充满自动停止
// 上报
{"act":"off","msg_id":31,"id":"123456789012345-01-6"}
open(平台→设备)
开门,只需IMEI。
// 请求
{"act":"open","msg_id":40,"id":"123456789012345"}
// 响应
{"suc":1,"msg_id":40}
错误码63 = 门锁故障。
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 — 通信超时 |
H5用户端
- 路径:
/pms/{抽象ID} - 功能:显示柜子每个通道状态,可开始/停止充电
- 无支付/计费功能