charging-cabinet/AGENTS.md

17 KiB
Raw Blame History

AGENTS.md - 充电柜项目工作手册

项目简介

充电柜矩阵系统Power Matrix System / PMS 产品名安知充anzhizhichong已有充电站平台在运行充电柜是新增产品线。 仓数不固定1柜控板+N仓控板每仓控板管6仓最小6仓。

设备标识

  • 平台抽象ID10-00000000绑定柜控板二维码对应此ID
  • 设备ID4G SIM卡15位IMEI10进制
  • 仓体全局ID平台层ID-仓板ID-仓体ID
  • 扫码链接https://app.anzhizhichong.com/h5/{抽象ID}

技术栈

  • 后端Rust + Axum
  • 前端React + Arco Design ProReact版
  • 数据库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 3000TCP 3001
  • 正式环境API 9000TCP 9001

部署路径:

  • 产品介绍页:/data/landing
  • 正式环境:/data/pmsapi-server、device-server、web
  • 测试环境:/data/pms_devapi-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 92核/3.5G/49G
  • ZeroTier: 网络ID 63e843dd42f54b9c,服务器节点 12a91dd47dIP 10.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.comuser: rootpassword: Hbhyg731024database: pms
  • Redis: r-bp1lsezvgzmgtwhbtg.redis.rds.aliyuncs.compassword: Hbhyg731024db: 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执行异步通知
  • 质量约束模板固定追加到每个任务文件

质量约束模板

所有编码任务必须追加以下约束:

  1. 完成代码后执行对应栈静态校验Rust→cargo checkTS→tsc --noEmit零报错零警告
  2. 分层拆分单函数≤80行命名语义化完整注释
  3. 所有外部IO/网络请求异常捕获禁止裸panic
  4. 分支逻辑全覆盖,不遗漏兜底分支
  5. 常量抽离不使用废弃API
  6. Rust内存安全合理管理所有权禁用unsafe无合理理由
  7. React函数式组件+Hooks不写Class组件

验收流程

  1. 静态校验命令通过
  2. 跑起来看效果
  3. 抽查关键逻辑(异常处理、权限校验、协议解析)

部署与维护

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_idIMEI10进制sub_device_id485 ID
  • msg_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 平台→设备 IMEIIMEI-仓控板 重启整柜或仓控板

指令详情

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}

平台处理:

  1. 记录电池信息
  2. 验证电池合规性
  3. 自动下发 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}
  • 功能:显示柜子每个通道状态,可开始/停止充电
  • 无支付/计费功能