# 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:door:unlock) ## 项目结构 ``` D:\charging-cabinet\ ├── tasks/ # 任务文件(opencode执行) ├── docs/ # 开发文档、协议文档 ├── software/ │ ├── server/ # Rust后端 │ └── web/ # React前端 ├── hardware/ # 硬件资料(原理图/PCB等) └── test/ # 测试用例/报告 ``` ## 部署架构 ### 环境规划 | 环境 | 域名 | 反代指向 | Nginx配置 | |------|------|---------|----------| | 正式环境 | app.anzhizhichong.com | 127.0.0.1:3000(服务器本地) | /etc/nginx/conf.d/app.anzhizhichong.com.conf | | 测试环境 | test.anzhizhichong.com | 10.8.0.248:3001(本地电脑) | /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`,IP `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 ### 本地开发环境 **数据库连接(通过ZeroTier):** ``` MySQL: 10.8.0.252:3306 user: root password: Hbhyg731024@ database: pms_dev (新建,不动AZZCWeChat*) Redis: 10.8.0.252:6379 password: Hbhyg731024@ db: 5 (新建,不动db1-4) ``` **代理说明:** - 服务器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 check,TS→tsc --noEmit),零报错零警告 2. 分层拆分,单函数≤80行,命名语义化,完整注释 3. 所有外部IO/网络请求异常捕获,禁止裸panic 4. 分支逻辑全覆盖,不遗漏兜底分支 5. 常量抽离,不使用废弃API 6. Rust:内存安全,合理管理所有权,禁用unsafe无合理理由 7. React:函数式组件+Hooks,不写Class组件 ### 验收流程 1. 静态校验命令通过 2. 跑起来看效果 3. 抽查关键逻辑(异常处理、权限校验、协议解析) ## 硬件架构 - 柜控板(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 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` | 设备→平台 | 无 | 停电上报 | | `bat_in` | 设备→平台 | `IMEI-仓控板-通道` | 电池插入(含BMS数据,字段待定) | | `bat_out` | 设备→平台 | `IMEI-仓控板-通道` | 电池拔出 | | `on` | **双向** | `IMEI-仓控板-通道` | 开始充电(平台下发/设备上报) | | `off` | **双向** | `IMEI-仓控板-通道` | 停止供电(平台手动/设备自动) | | `open` | 平台→设备 | `IMEI` | 开门 | | `status_get` | 平台→设备 | `IMEI-仓控板` | 查仓板状态 | | `reset` | 平台→设备 | `IMEI` 或 `IMEI-仓控板` | 重启整柜或仓控板 | ### status_post格式 ```json { "act": "status_post", "msg_id": 42, "dev_id": "123456789012345", "rssi": 85, "status": { "123456789012345-01": "000100020003000400050006", "123456789012345-02": "000100020003000400050006" } } ``` - key: `IMEI-仓控板ID` - value: 6个通道状态拼接(每通道2字节=4个HEX字符) - 平台从key解析仓控板数量 ### 充电结束原因(平台判断) | 平台收到 | 判断原因 | |---------|---------| | `off` (设备→平台) | `normal` — 正常充满 | | `off` (平台→设备) | `user_stop` — 用户手动停止 | | `bat_out` | `normal` — 电池拔出 | | `bat_in` (通道已有电池) | `forced` — 新电池插入,旧电池强制结束 | | 超时未响应 | `timeout` — 通信超时 | ## H5用户端 - 路径:`/pms/{抽象ID}` - 功能:显示柜子每个通道状态,可开始/停止充电 - 无支付/计费功能