Files

988 lines
44 KiB
Markdown
Raw Permalink Normal View History

2026-06-14 11:19:15 +08:00
# StandardScene - Charge 模块逻辑文档
本文档用于梳理 `StandardScene/Charge/` 充电桩管理与充电业务的整体逻辑,重点包含:
- 充电桩配置(数据模型与持久化)
- 通信报文解析与 `ChargeStation` 状态落库
- `StandardChargeMission` 的充电业务循环
- 相关 WinForms 界面如何展示与交互
---
## 1. 目录/模块职责速览(Charge/ 内)
### 数据与配置层
- `ChargeStation.cs`:充电桩数据模型(`ChargeStation`)及相关枚举(`ChargeStationStatus``CommunicationStatus``ChargeCommandStatus` 等)
- `ChargeStationDataService.cs``ChargeStation` 的持久化与查询/更新(JSON 文件存储)
- `AlarmConfig.cs`:报警配置模型(`AlarmConfig`
- `AlarmConfigDataService.cs`:报警配置的持久化与查询/更新(JSON 文件存储)
- `ChargeStrategyConfig.cs`:充电策略配置模型
- `ChargeStrategyConfigService.cs`:充电策略配置的持久化(JSON 文件存储)
### 运行时与通信层
- `CommunicationMessageService.cs`:通信报文“记录 + 解析 + 更新 ChargeStation”的核心服务
- `ChargeUdpService.cs`:UDP 监听入口,将 UDP 收到的数据转为 `CommunicationMessageService.AddReceiveMessage(...)`
- `StandardChargeMission.cs`:主业务进程(初始化充电站实例、500ms 循环下发充电指令)
### WinForms 界面层
- `ChargeStationManagementForm.cs`:充电桩管理窗口(列表、增删改、跳转其它窗口)
- `ChargeStrategyConfigForm.cs`:充电策略参数配置窗口
- `CommunicationMonitorForm.cs`:通信报文监控窗口(订阅 `MessageAdded` 并刷新表格)
- `AlarmConfigManagementForm.cs`:报警配置管理窗口(增删改、筛选与搜索)
---
## 2. 数据模型(ChargeStation / AlarmConfig / 策略)
### 2.1 `ChargeStation``Charge/ChargeStation.cs`
`ChargeStation` 是所有界面展示与通信落库的核心对象。与本模块强相关的字段包括:
- 身份与配置
- `StationId`:充电桩编号(用于唯一标识,UI 校验 1-99)
- `Name``Type`(充电桩类型:`FRLDTall` / `FRLDShort` / `MuXing`
- `ChargeMethod`(地充/尾充/侧充)
- `IpAddress``Port`:通信地址
- `SetVoltage``SetElectricCurrent`:设定值
- `Enabled`:是否启用
- `GroupCarType``SiteId`:与调度系统站点配置绑定
- 通信与状态(用于 UI 展示)
- `Status``ChargeStationStatus`):`Idle` / `Charging` / `Fault` / `Battery`
- `CommStatus``CommunicationStatus`):UI 中展示用的通讯状态(通常由 UI Ping 计算)
- `ChargeCommandStatus``ChargeCommandStatus`):最近一次“启动/停止充电指令”的状态
- `MechanismStatus`:机构伸缩状态
- `HasAlarm``AlarmLevel``AlarmMessage`:报警相关
- 实时数值
- `LastSendTime``LastReceiveTime`
- `RealTimeVoltage``RealTimeCurrent`
- `BatteryLevel``CurrentVehicle`
### 2.2 枚举含义(`ChargeStation.cs`
主要枚举:
- `ChargeStationStatus`:空闲/充电中/报警中/AGV电池已接入
- `CommunicationStatus`:未知/正常/延迟/超时/断开/错误
- `ChargeCommandStatus`:停止/启动
- `MechanismStatus`:伸出/缩回/运动中
- `AlarmLevel`:无/低/中/高/严重
- `ChargeMethodType`:地充/尾充/侧充
### 2.3 报警配置 `AlarmConfig``Charge/AlarmConfig.cs`
报警配置用于 UI 管理与展示(`AlarmConfigManagementForm` 管理)。字段包括:
- `AlarmId``AlarmCode``AlarmContent`
- `Level`(报警级别)、`Enabled`
- `Remarks`
### 2.4 策略配置 `ChargeStrategyConfig``Charge/ChargeStrategyConfig.cs`
策略配置包含 SOC 阈值、时间参数、以及开关项(例如 `AllowInterruptTask``UseLowerSocForCharge` 等),由 `ChargeStrategyConfigForm` 编辑、由 `ChargeStrategyConfigService` 持久化。
---
## 3. 持久化与服务层(DataService
### 3.1 充电桩数据持久化:`ChargeStationDataService`
入口与关键能力(来自实现):
- 获取:`GetAllStations()``GetStationById(...)``GetStationByIp(ip,port)`
- 增加:`AddStation(...)`
- 更新:`UpdateStation(...)`(可选 `isSave`)、`UpdateStationStatus(...)`
- 删除:`DeleteStation(...)`
- 刷新:`Reload()`
落库逻辑特点:
- 通信解析后会调用 `ChargeStationDataService.UpdateStation(station, out errorMessage)`,最终把 `ChargeStation` 新状态写回 JSON。
### 3.2 报警配置持久化:`AlarmConfigDataService`
入口与关键能力:
- 获取:`GetAllAlarmConfigs()``GetAlarmConfig(alarmId)``GetAlarmConfigByCode(...)`
- 增加:`AddAlarmConfig(...)`
- 更新:`UpdateAlarmConfig(...)`
- 删除:`DeleteAlarmConfig(...)`
- 刷新:`Reload()`(实现中一般会重新从文件加载)
---
## 4. 通信报文解析与落库:CommunicationMessageService
`Charge/CommunicationMessageService.cs` 是本模块最核心的“桥梁”:
1. 把发送/接收报文记录到内存队列(`LinkedList`
2. 根据报文原始 hex 字符串与协议类型 `type` 解析出结构化数据
3. 更新对应的 `ChargeStation` 字段
4. 调用 `ChargeStationDataService.UpdateStation(...)` 落库到 JSON,并触发 UI 展示更新
### 4.1 报文记录与订阅
- `MessageAdded` 事件:当新报文加入时触发
- UI 通信监控窗体(`CommunicationMonitorForm`)订阅该事件,并在 UI 线程刷新表格
### 4.2 发送报文路径(AddSendMessage -> 更新 ChargeCommandStatus 等)
- 外部调用:`AddSendMessage(ipAddress, port, rawData, type, stationId?)`
- 内部流程:
- `ParseSendRawData(rawData, type)` 解析
- `UpdateStationFromSendData(station, parsedData)` 更新:
- `LastSendTime = SendTime`
- 根据 `ChargeCommand`(启动/停止)更新 `ChargeCommandStatus`
- 更新 `BatteryLevel``CurrentVehicle`
- `ChargeStationDataService.UpdateStation(station, out errorMessage)` 落库
### 4.3 接收报文路径(AddReceiveMessage -> 更新状态/机构/告警)
- 外部调用:`AddReceiveMessage(ipAddress, port, rawData, type, stationId?)`
- 内部流程:
- `ParseReceiveRawData(rawData, type)` 解析
- `UpdateStationFromReceiveData(station, parsedData)` 更新:
- `LastReceiveTime`
- `MechanismStatus``RealTimeVoltage``RealTimeCurrent`
- `Status``Idle/Charging/Fault/Battery`
- `HasAlarm``AlarmLevel``AlarmMessage`
- `ChargeStationDataService.UpdateStation(...)` 落库
### 4.4 协议类型 `type`
解析分支中常见类型示例:
- `FRLDShort`
- `FRLDTall`
不同类型会使用不同索引位置从报文字节数组中解析字段。
---
## 5. 通信接入入口
### 5.1 UDP 接入:ChargeUdpService
`Charge/ChargeUdpService.cs`
- 创建线程监听 UDP`UdpClient(40001)`
- 循环接收并转发:
- `CommunicationMessageService.AddReceiveMessage(remoteIp, 40001, hexString, "FRLDShort")`
- 同时会通过 `SimpleProject.proj.Missions` 找到 `StandardChargeMission` 实例,并在 `chargeMission.ChargeStations` 中按 IP 找到对应站点
- 对特定站点类型(例如 `PCBChargeStation`)进一步更新站点字段(例如 `IsSafe``IndexReceive`
### 5.2 TCP 接入:以 FLChargeStation 为例(ChargeStationType
`ChargeStationType/FLChargeStation.cs` 为例:
- `OnPlaintextReceived(...)` 在收到 TCP 明文后:
- 提取报文字节(示例中 `Take(35)`
- 更新站点内的一些运行时字段(例如 `IsSafe`
- 调用 `CommunicationMessageService.AddReceiveMessage(...)`,并把 `type` 传为对应协议类型(例如 `"FRLDTall"`
> 说明:具体 TCP 断连/重连机制由底层 TCP 客户端与对应站点实现决定;无论 TCP/UDP,最终都会汇聚到 `CommunicationMessageService` 完成解析与落库。
---
## 6. 运行时充电业务循环:StandardChargeMission
`Charge/StandardChargeMission.cs` 负责把“调度系统中的车的状态 + 充电策略 + 站点配置”组合成周期性的充电指令下发。
### 6.1 初始化充电桩实例(创建 station 对象)
关键步骤(来自实现片段):
1. 遍历系统 `Site` 中带有 `fields["Charge"]` 的站点,构建 station 配置
2. 根据 `ChargeStationType` 使用反射创建 `AbstractChargeStation` 实例
3. 给站点对象赋值:
- `SiteId``Ip``Port`
- `CommunicationType`
- 示例:`FRLDShort` 时设置为 `"UDP"`;否则使用配置中的 `CommunicationType`(默认走 TCP
4. 调用 `chargeStation.CreateCommunication(ipAddress, port)` 建立通信通道
5. 把站点对象加入 `ChargeStations` 字典:`Dictionary<int, AbstractChargeStation>`
如果存在任何 UDP 站点,会创建 `UdpService ??= new ChargeUdpService()`
### 6.2 500ms 业务循环(选择车辆 -> 下发指令)
主循环(实现中包含 `Thread.Sleep(500)`)逻辑大致如下:
1. 对每个 `chargeStationEntry`(按站点遍历):
- 通过 `SimpleLib.GetAllCars()` 查找:
- 车辆当前所在站点 `c.GetLastSite() == siteId`
- 或车辆正在竞争锁/持有锁(`aquiringLock == siteId``holdingLocks.Contains(siteId)`
2. 若找到车辆:
- 判断车辆状态:`Commons.GetVehicleStatus((Car)car) == VehicleStatus.Normal`
- 判断是否正在“充电标记”(`car.tags.Contains("charging")`
- 结合锁状态与 tag 状态计算 `openCharge`0/1
3. 当未屏蔽交互(`shieldInterLock == false`)时下发指令:
- `chargeStation.SendToChargeStation(openCharge, (Car)car)`
### 6.3 Stop/ShieldInterLock/管理界面入口
- `Stop()`:中止 mission 线程,并对每个 station 调用 `CloseCommunication()`
- `ShieldInterLock()`:切换“是否屏蔽充电桩交互”
- `OpenManagementWindow()`:打开 `ChargeStationHelper.OpenManagementWindow()`
---
## 7. WinForms 界面与交互细节
### 7.1 充电桩管理:ChargeStationManagementForm
文件:`Charge/ChargeStationManagementForm.cs`
#### 核心展示数据来源
- 列表数据来源:`ChargeStationDataService.GetAllStations()`
- UI 侧通讯状态:
-`LoadStations()` 中对每个 station 执行 `Ping.Send(station.IpAddress, 1000)`
- Ping 成功则 `station.CommStatus = CommunicationStatus.Normal`,否则 `CommunicationStatus.Error`
- 电气/运行时信息来源:
- `ChargeCommandStatus``Status``MechanismStatus``HasAlarm/AlarmLevel/AlarmMessage``RealTimeVoltage/Current` 等都来自 `CommunicationMessageService` 解析并落库后的 `ChargeStation` 字段
#### 自动刷新
- `autoRefreshTimer.Interval = 3000`
- `AutoRefreshTimer_Tick`
- 保存当前选中行的 `StationId`
- 调用 `LoadStations()` 重绘
- 恢复选中行
#### 关键编辑与保存逻辑(btnSave)
- `btnSave.Text == "修改"`:先切换为编辑模式 `SetEditMode(true)`
- 新增/保存时校验:
- `StationId` 不能为空且必须是 1-99 范围整数
- 新增时禁止重复 `StationId`
- `SiteId` 必须存在于调度系统站点集合(`SimpleLib.GetSite((int)numSiteId.Value)`
- 保存调用:
- 新增:`dataService.AddStation(...)`
- 更新:`dataService.UpdateStation(..., isSave:true)`
- 同步到调度系统 `Site.fields`
- `setVoltage``setElectricCurrent`
- `group`:根据 `Enabled` 设置为 `"禁用"``GroupCarType`
#### 删除逻辑(btnDelete
- 调用 `dataService.DeleteStation(stationId, out errorMessage)`
- 同步清理 `Site.fields`
- 移除 `setVoltage``setElectricCurrent``Charge``group`
#### 列表交互
- `dgvStations_CellDoubleClick`
- 根据 `StationId` 查找 `ChargeStation`
- 调用 `LoadStationToFields(station)`
- 进入编辑模式 `SetEditMode(true, true)`
#### 其它窗口入口按钮
- `btnStrategyConfig_Click`:打开 `ChargeStrategyConfigForm`
- `btnCommMonitor_Click`:打开 `CommunicationMonitorForm`
- `btnAlarmConfig_Click`:打开 `AlarmConfigManagementForm`
- `btnExport_Click`:导出 JSON 或 CSV(从 `GetAllStations()` 读取)
### 7.2 策略配置:ChargeStrategyConfigForm
文件:`Charge/ChargeStrategyConfigForm.cs`
- 初始化:`config = configService.LoadConfig()`
- 保存:把 UI 控件值写入 `ChargeStrategyConfig` 后调用 `configService.SaveConfig(config)`
- 恢复默认:调用 `ChargeStrategyConfig.CreateDefault()` 并重新加载到界面
### 7.3 通信监控:CommunicationMonitorForm
文件:`Charge/CommunicationMonitorForm.cs`
- 初始化:
- `messageService = CommunicationMessageService.Instance`
- 窗体加载完成后订阅:`messageService.MessageAdded += OnMessageAdded`
- 新报文到达:`OnMessageAdded(...)`
-`InvokeRequired``BeginInvoke` 回 UI 线程
- 根据当前 IP 筛选条件刷新消息列表(调用 `LoadMessages()`
- 消息列表展示:
-`messageService.GetAllMessages()``GetMessagesByIp(ip)` 取出数据
- 根据 `Direction`(发送/接收)设置行颜色
- 统计信息:
- `lblStatistics.Text = $"显示: {displayCount} | 总数: ... | 发送: ... | 接收: ..."`
### 7.4 报警配置管理:AlarmConfigManagementForm
文件:`Charge/AlarmConfigManagementForm.cs`
- 界面加载:
- 初始化级别下拉框与筛选下拉框
- 调用 `LoadAlarmConfigs()`
- 列表加载逻辑:
-`AlarmConfigDataService.GetAllAlarmConfigs()` 获取全量
- 按筛选条件(等级 `cmbLevelFilter`、搜索框 `txtSearch`)过滤
- 填充 `dgvAlarmConfigs` 并根据 `AlarmLevel` 设置行颜色
- 保存:
- `selectedAlarmConfig == null` -> 新增 `dataService.AddAlarmConfig`
- 否则 -> 更新 `dataService.UpdateAlarmConfig`
- 删除:
- `dataService.DeleteAlarmConfig(selectedAlarmConfig.AlarmId, out ...)`
- 双击列表:
- `dgvAlarmConfigs_CellDoubleClick` 读取 `AlarmId` 并加载到编辑区
---
## 7(代码一致性修订):UI 窗体导航与更新流
### 7.1 `ChargeStationManagementForm`(充电桩管理)
入口/导航
- 通过 `ChargeStationHelper.OpenManagementWindow()`(单例 `Show()`)或 `ChargeStationHelper.OpenManagementDialog()``ShowDialog()`)打开。
- 窗体内通过按钮打开:
- `btnStrategyConfig_Click` -> `ChargeStrategyConfigForm.ShowDialog()`
- `btnCommMonitor_Click` -> `CommunicationMonitorForm.Show()`
- `btnAlarmConfig_Click` -> `AlarmConfigManagementForm.ShowDialog()`
更新/刷新
- 列表自动刷新:`autoRefreshTimer.Interval = 3000``AutoRefreshTimer_Tick` 会保存当前选中 `StationId`、重建 `dgvStations``LoadStations()`)、再恢复选中行。
- 关闭窗体:`OnFormClosing` 停止并释放 `autoRefreshTimer`
- `LoadStations()` 的状态刷新点:
- 数据:`ChargeStationDataService.GetAllStations()` + 按 `cmbStatusFilter` 过滤。
- 通讯状态:逐个对站点执行 `Ping.Send(station.IpAddress, 1000)`,成功/失败分别写入 `station.CommStatus`,再刷新行颜色。
- 搜索/筛选:`txtSearch_TextChanged``cmbStatusFilter_SelectedIndexChanged` 都会触发 `ApplyFilters()`,清空并重建 `dgvStations`(包含行颜色规则)。
- 手动刷新:`btnRefresh_Click` -> `dataService.Reload()` -> `LoadStations()`
编辑与保存
- 双击列表:`dgvStations_CellDoubleClick` -> `LoadStationToFields(station)` -> `SetEditMode(false)`(查看模式,`btnSave.Text="修改"`)。
- `btnSave_Click` 两段式:
- `btnSave.Text=="修改"`:仅切到编辑模式 `SetEditMode(true)`
- 否则执行保存:校验 `StationId`1-99)、新增时校验唯一性、校验 `SiteId` 存在,然后调用 `AddStation` / `UpdateStation(..., isSave:true)`
- 保存成功后同步调度系统 `Site.fields``setVoltage``setElectricCurrent``group`(启用写 `GroupCarType`,禁用写 `"禁用"`),再刷新列表并清空编辑区。
- 删除:`btnDelete_Click` 确认后 `DeleteStation`,并同步清理 `Site.fields``setVoltage``setElectricCurrent``Charge``group`)。
### 7.2 `ChargeStrategyConfigForm`(充电策略配置)
入口/导航
- 通常由管理窗体打开:`ChargeStationManagementForm``btnStrategyConfig_Click` 使用 `ShowDialog()`
更新/刷新
- 初始化:`configService = ChargeStrategyConfigService.Instance`,构造时 `LoadConfig()` 把文件配置加载到界面控件。
- 保存/应用:`btnSave_Click``btnApply_Click` 都会先 `ValidateConfig()` 校验阈值关系,再把控件值写回 `config` 并调用 `configService.SaveConfig(config)`
- 恢复默认:`btnRestoreDefaults_Click` 确认后 `config = ChargeStrategyConfig.CreateDefault()`,调用 `LoadConfig(true)` 刷新界面,但不自动保存(状态提示“未保存”)。
- 取消:`btnCancel_Click` -> `Close()`
### 7.3 `CommunicationMonitorForm`(通信监控)
入口/导航
- 由管理窗体 `btnCommMonitor_Click` 打开:`Show()`(非阻塞)。
更新/刷新(事件驱动)
- 构造中拿到 `messageService = CommunicationMessageService.Instance``FormClosing` 退订 `MessageAdded`
- `CommunicationMonitorForm_Load`
- `InitializeForm()` + `LoadMessages()` 后设置 `isFormLoaded=true`
- 再订阅 `messageService.MessageAdded += OnMessageAdded`
- `OnMessageAdded`
- `InvokeRequired``BeginInvoke` 回 UI 线程
- 新 IP 则刷新 `cmbIpFilter``RefreshIpFilter()`
- 若当前筛选匹配(“全部”或等于当前消息 IP)则调用 `LoadMessages()` 重建消息列表
- 手动操作:
- `cmbIpFilter_SelectedIndexChanged` -> `LoadMessages()`
- `btnRefresh_Click` -> `RefreshIpFilter()` + `LoadMessages()`
- `btnClear_Click`:确认 -> `messageService.Clear()` -> 刷新列表并清空 `txtParsedData`
- 列表选择与解析展示:
- `dgvMessages_SelectionChanged` 根据所选行构造临时 `CommunicationMessage`,再调用 `ParseMessage()`,并将解析结果写入 `txtParsedData`
### 7.4 `AlarmConfigManagementForm`(报警配置管理)
入口/导航
- 由管理窗体 `btnAlarmConfig_Click` 打开:`ShowDialog()`
更新/刷新(加载 + 筛选/搜索)
- 构造:`dataService = AlarmConfigDataService.Instance`,并订阅 `this.Load += AlarmConfigManagementForm_Load`
- `InitializeForm()`
- 初始化 `cmbLevel``cmbLevelFilter`
- 调用 `LoadAlarmConfigs()` 加载列表
- 调用 `ClearEditFields()` 初始化编辑区(默认新增态)
- `LoadAlarmConfigs()`
- 数据源:`dataService.GetAllAlarmConfigs()`
- 过滤:`cmbLevelFilter`(映射到 `AlarmLevel`)与 `txtSearch`(匹配 `AlarmId/AlarmCode/AlarmContent`
- 填充 `dgvAlarmConfigs` 并按 `AlarmLevel` + `Enabled` 设置行颜色/样式,同时更新统计与标题
- 实时刷新:`txtSearch_TextChanged``cmbLevelFilter_SelectedIndexChanged` 都直接调用 `LoadAlarmConfigs()``btnRefresh_Click``dataService.Reload()` 后重新加载。
编辑与保存
- 双击列表:`dgvAlarmConfigs_CellDoubleClick` 读取 `AlarmId` -> `dataService.GetAlarmConfig(alarmId)` -> `LoadAlarmConfigToFields()`(编号不可编辑,切为编辑态)。
- 保存:`btnSave_Click` 校验 `numAlarmCode >= 0``txtAlarmContent` 非空;根据是否选中项决定 `AddAlarmConfig``UpdateAlarmConfig`;成功后刷新列表并清空编辑区。
- 删除:`btnDelete_Click` 确认后 `DeleteAlarmConfig(selectedAlarmConfig.AlarmId)`,成功后刷新列表并清空编辑区。
- 取消/关闭:`btnCancel_Click` 清空编辑区,`btnClose_Click` 关闭窗体。
---
## 8. 关键调用链(建议排查/理解用)
### 8.1 周期循环下发充电指令 -> 发送报文记录 -> ChargeCommandStatus 更新
```mermaid
flowchart TD
A[StandardChargeMission 500ms循环] --> B[chargeStation.SendToChargeStation(openCharge, car)]
B --> C[chargeStation 内部构造发送报文]
C --> D[CommunicationMessageService.AddSendMessage(...)]
D --> E[ParseSendRawData(type)]
E --> F[UpdateStationFromSendData]
F --> G[ChargeStationDataService.UpdateStation]
G --> H[ChargeStation 字段落库]
H --> I[ChargeStationManagementForm(3s刷新) 展示]
```
### 8.2 TCP/UDP 接收报文 -> 解析 -> ChargeStation 状态与告警更新 -> UI 展示
```mermaid
flowchart TD
A[TCP 收到明文 或 UDP 收到报文] --> B[CommunicationMessageService.AddReceiveMessage(...)]
B --> C[ParseReceiveRawData(type)]
C --> D[UpdateStationFromReceiveData]
D --> E[ChargeStationDataService.UpdateStation]
E --> F[ChargeStation 字段落库]
F --> G[ChargeStationManagementForm(3s刷新) 展示 Status/告警/电压电流]
```
### 8.3 通信监控界面订阅报文事件
```mermaid
flowchart TD
A[CommunicationMessageService.AddMessage/MessageAdded] --> B[CommunicationMonitorForm.OnMessageAdded]
B --> C[BeginInvoke 切到UI线程]
C --> D[LoadMessages 刷新 dgvMessages]
```
---
## 9. 常用调试点(建议)
- 通信解析落库:
-`CommunicationMessageService``UpdateStationFromSendData/ReceiveData` 更新了哪些字段
- UI 展示:
- `ChargeStationManagementForm.LoadStations()` 中的 `Ping.Send(...)` 会影响 `CommStatus` 展示
- 如果“列表里状态不变”:
- 优先确认报文是否真的进入 `CommunicationMessageService.AddSendMessage/AddReceiveMessage`
- 再确认解析是否返回非 null(解析失败会直接 `return null`
# StandardScene/Charge:充电桩数据模型与持久化(仅数据层)
本页聚焦 `StandardScene/Charge/` 中与“数据模型 + DataService 持久化/更新 API”相关的部分,覆盖:
1. `ChargeStation`:充电桩配置/运行时状态字段含义与 `JsonIgnore` 持久化边界
2. `ChargeStationDataService``Config/ChargeStations.json` 的读取/保存、增删改与状态更新
3. `AlarmConfig``AlarmConfigDataService``Config/AlarmConfigs.json` 的读取/保存、增删改
---
## 1. 数据模型:`ChargeStation`
文件:`Charge/ChargeStation.cs`
### 1.1 配置/计算字段说明(按 `JsonIgnore` 区分)
`ChargeStation` 的下列字段用于“充电桩配置”,在 JSON 里会被序列化(即:未标注 `JsonIgnore`):
- `StationId`:充电桩编号(唯一标识)
- `Name`:充电桩名称
- `Type`:充电桩类型(`ChargeStationType`
- `ChargeMethod`:充电方式(`ChargeMethodType`
- `IpAddress`IP 地址
- `Port`:端口号
- `CommunicationType`:通讯类型(属性初始值为 `"TCP"`,但构造函数会覆盖为 `"UDP"`
- `SetVoltage`:额定电压(V
- `SetElectricCurrent`:额定电流(A
- `Enabled`:是否启用
- `GroupCarType`:停靠车辆类型(`ChargeStationCarType`
- `SiteId`:关联站点 ID(可选)
- `ShieldSiteMechanismStatus`:屏蔽机构状态交互
- `Remarks`:备注
- `CreatedTime`:创建时间
- `ModifiedTime`:最后修改时间
- `Power`:计算属性(`SetVoltage * SetElectricCurrent`),标注了 `[JsonIgnore]`,不会写入 JSON
### 1.2 运行时状态字段(不会被持久化到 JSON)
以下字段标注了 `[JsonIgnore]`,因此不会写入 `Config/ChargeStations.json`(重启后这些运行时状态通常会丢失):
- `RealTimeVoltage``RealTimeCurrent`:实时电压/电流
- `LastSendTime``LastReceiveTime`:最后发送/接收时间
- `HasAlarm``AlarmMessage``AlarmLevel`:报警标记/报警文本/报警级别
- `CommStatus``LastCommunicationTime`:通讯状态/最后通讯时间(注意:当前代码里通讯状态字段的更新路径不在本节展开)
- `MechanismStatus`:机构伸缩状态
- `CurrentVehicle`:当前充电车辆编号
- `BatteryLevel`:当前电量百分比
- `ChargeCommandStatus`:发送充电指令状态(停止/启动)
- `Status`:充电桩状态(空闲/充电中/报警中/AGV电池已接入)
### 1.3 校验:`IsValid(out errorMessage)`
`ChargeStation.IsValid()` 约束:
- `StationId``Name``IpAddress` 不能为空
- `IpAddress` 需为可解析的 IP
- `Port` 必须在 `1-65535`
- `SetVoltage` 必须在 `(0, 64]`
- `SetElectricCurrent` 必须在 `(0, 101]`
---
## 2. 数据服务:`ChargeStationDataService`
文件:`Charge/ChargeStationDataService.cs`
### 2.1 单例与持久化文件
- 单例:`ChargeStationDataService.Instance`
- 内部数据:`private List<ChargeStation> chargeStations`
- JSON 文件路径:基于运行目录写入
- `AppDomain.CurrentDomain.BaseDirectory/Config/ChargeStations.json`
- 构造函数会确保 `Config/` 目录存在,并执行 `LoadData()`
### 2.2 读取:`LoadData()`
行为:
- 若文件存在:读取文本并 `JsonConvert.DeserializeObject<List<ChargeStation>>(json)`
- 若文件不存在:初始化为空列表(并不会自动生成默认样例)
- 异常:记录诊断日志并回退到空列表
### 2.3 保存:`SaveData()`
行为:
- 在锁 `lockObj` 下序列化整个 `chargeStations` 列表
- 写入文件 `Config/ChargeStations.json``Formatting.Indented`
- 保存失败:返回 `false` 并由调用方回滚内存状态(部分方法会回滚)
### 2.4 查询 API
- `List<ChargeStation> GetAllStations()`:返回列表副本(拷贝)
- `ChargeStation GetStationById(string stationId)`:按 `StationId` 查找
- `ChargeStation GetStationByIp(string ipAddress, int port)`:按 `IpAddress + Port` 查找
- `List<ChargeStation> GetIdleStations()`:过滤 `Enabled && Status == Idle`
- `int GetChargingCount()`:统计 `Status == Charging`
- `void Reload()`:重新执行 `LoadData()`
### 2.5 新增:`AddStation(ChargeStation station, out string errorMessage)`
关键点:
- `station == null` 返回失败
- 先执行 `station.IsValid(out errorMessage)`
- 唯一性校验:
- `StationId` 不可重复
- `IpAddress + Port` 组合不可重复
- 写入字段:
- 设置 `CreatedTime` / `ModifiedTime` 为当前时间
- 成功后:`SaveData()`;失败则将新增对象从内存移除
### 2.6 更新:`UpdateStation(ChargeStation station, out string errorMessage, bool isSave = false)`
该方法同时被用作“配置更新”与“运行时状态合并后再落盘”的入口之一(不同调用方会用不同的 `isSave` 值)。
核心流程:
- 校验:`station.IsValid(out errorMessage)`
- 找到原对象:`existingStation = chargeStations.FirstOrDefault(s => s.StationId == station.StationId)`
- 冲突校验:`IpAddress + Port` 不能被其它站点占用
- 时间处理:
- 保留 `existingStation.CreatedTime`
- 更新 `station.ModifiedTime = DateTime.Now`
- 赋值策略取决于 `isSave`
- `isSave == true`:仅将“配置类字段”拷贝到 `existingStation`(并令 `station = existingStation`
- `isSave == false`:不进行字段级拷贝,直接用传入的 `station` 替换列表里的对应项
- 之后无论 `isSave` 为何都会执行 `SaveData()` 并落盘整个列表
- 保存失败:回滚为 `existingStation`
持久化边界提醒(结合 `ChargeStation``JsonIgnore`):
- 因为 `Status / Alarm / 实时电压电流 等运行时字段` 都是 `JsonIgnore`,即使 `UpdateStation` 被用于合并运行时字段,重启后这些运行时字段仍不会出现在 JSON 中
-`ModifiedTime`(未 `JsonIgnore`)会被写入,因此会出现“通信上报频繁导致 JSON 文件 `ModifiedTime` 刷新”的现象
### 2.7 删除:`DeleteStation(string stationId, out string errorMessage)`
-`stationId` 找到对象并移除
- 成功后保存;失败则将对象重新加入内存
- 代码中原本有“如果正在充电则禁止删除”的检查,但被注释掉了
### 2.8 状态更新(运行时):`UpdateStationStatus(string stationId, ChargeStationStatus status)`
- 修改内存对象的 `Status``ModifiedTime`
- 然后 `SaveData()`
- 由于 `Status` 标注了 `JsonIgnore`,因此重启后站点 `Status` 通常不会从 JSON 恢复(但 `ModifiedTime` 会更新)
---
## 3. 数据模型:`AlarmConfig`
文件:`Charge/AlarmConfig.cs`
### 3.1 字段含义(会被持久化)
`AlarmConfig` 没有 `JsonIgnore`,因此以下字段都能写入 `Config/AlarmConfigs.json`
- `AlarmId`:报警编号(构造函数自动生成,格式类似 `ALMyyyyMMddHHmmssxxx`
- `AlarmCode`:报警编码值(int
- `AlarmContent`:报警内容描述(文本)
- `Level`:报警级别(`AlarmLevel`None/Low/Medium/High/Critical
- `Enabled`:是否启用
- `Remarks`:备注
- `CreatedTime` / `ModifiedTime`:创建与修改时间
### 3.2 校验:`IsValid(out errorMessage)`
- `AlarmId` 不能为空
- `AlarmCode >= 0`
- `AlarmContent` 不能为空
---
## 4. 数据服务:`AlarmConfigDataService`
文件:`Charge/AlarmConfigDataService.cs`
### 4.1 单例与持久化文件
- 单例:`AlarmConfigDataService.Instance`
- 数据文件路径:`AppDomain.CurrentDomain.BaseDirectory/Config/AlarmConfigs.json`
- 构造函数调用 `LoadData()`;若目录不存在则创建
### 4.2 读取:`LoadData()`
行为:
- 文件存在:读取并反序列化为 `List<AlarmConfig>`
- 若反序列化结果为 `null`,回退为空列表
- 文件不存在:初始化默认报警配置 `InitializeDefaultAlarms()`,随后 `SaveData()`
- 异常:记录 `Debug.WriteLine`,回退到空列表并初始化默认报警配置
默认报警包含(示例):
- 1001:电压过高
- 1002:电压过低
- 1003:电流过大
- 2001:温度异常
- 3001:通讯超时
- 3002:连接断开
### 4.3 保存:`SaveData()`
- 序列化整个 `_alarmConfigs` 并写入 `AlarmConfigs.json`
- 保存失败会抛出异常(不只是返回 `false`
### 4.4 查询 API
- `List<AlarmConfig> GetAllAlarmConfigs()`:返回列表副本
- `AlarmConfig GetAlarmConfig(string alarmId)`:按 `AlarmId` 查找
- `AlarmConfig GetAlarmConfigByCode(int alarmCode)`:按 `AlarmCode` 查找
### 4.5 新增:`AddAlarmConfig(AlarmConfig alarmConfig, out string errorMessage)`
- 先执行 `alarmConfig.IsValid(out errorMessage)`
- 校验 `AlarmCode` 唯一性(不允许重复)
- 添加到列表后 `SaveData()`
### 4.6 更新:`UpdateAlarmConfig(AlarmConfig alarmConfig, out string errorMessage)`
- 校验:`IsValid`
- 查找目标:按 `AlarmId` 找到索引;不存在则失败
- 冲突校验:`AlarmCode` 不能被其它报警配置占用
- 设置 `alarmConfig.ModifiedTime = DateTime.Now`
- 替换列表项并 `SaveData()`
### 4.7 删除:`DeleteAlarmConfig(string alarmId, out string errorMessage)`
-`AlarmId` 找到并移除
- 然后 `SaveData()`
### 4.8 重新加载:`Reload()`
- 在锁下重新执行 `LoadData()`
---
## 5. 与“更新路径”的关系(为何运行时变化也会触发落盘)
虽然本页主要讲 DataService,但为了说明“哪些字段会/不会出现在 JSON 里”,需要点到调用关系:
- 通讯层(`Charge/CommunicationMessageService.cs`)在解析发送/接收报文后,会:
- 更新 `ChargeStation` 的运行时字段(例如 `HasAlarm``Status``RealTimeVoltage/Current` 等)
- 然后调用 `ChargeStationDataService.UpdateStation(station, out errorMessage)`(使用默认 `isSave=false`
- UI 保存站点配置(`Charge/ChargeStationManagementForm.cs`)在“保存/修改配置”时会调用:
- `ChargeStationDataService.UpdateStation(station, out errorMessage, true)`
- 报警配置的 UI 增删改(`Charge/AlarmConfigManagementForm.cs`)直接调用:
- `AddAlarmConfig / UpdateAlarmConfig / DeleteAlarmConfig`
因此你会观察到:
- `ChargeStations.json` 中的“运行时字段”不会被写入(因为它们带 `JsonIgnore`
-`ModifiedTime` 这类未忽略字段会被写入,所以文件仍会频繁变化
---
## 6. 通讯报文解析:`CommunicationMessageService` 如何更新 `ChargeStation`
本节重点解释 `Charge/CommunicationMessageService.cs` 中“报文解析 -> 更新充电桩运行时字段”的完整链路(并说明当前实现里哪些字段没有被真正落到 `ChargeStation`)。
### 6.1 入口与站点匹配规则
`CommunicationMessageService` 通过两个入口接收外部报文,并在内部完成“解析 + 更新 + 落盘(通过 DataService)”:
- 发送报文入口:`AddSendMessage(ipAddress, port, rawData, type, stationId)`
- 接收报文入口:`AddReceiveMessage(ipAddress, port, rawData, type, stationId)`
两条链路在解析前都会做同样的站点匹配:
- 先拿到单例:`ChargeStationDataService.Instance`
- 通过 `GetStationByIp(ipAddress, port)` 找到对应 `ChargeStation`
- 找不到站点直接返回(此时只会记录报文,不会更新该站点运行时字段)
解析成功后才会调用:
- `ChargeStationDataService.UpdateStation(station, out errorMessage)`(该调用在当前代码里使用默认参数,最终会落盘整个 `ChargeStations.json`;但由于运行时字段多为 `JsonIgnore`,重启后这些运行时值不会恢复)
异常处理方面:
- `ParseSendDataAndUpdateStation` / `ParseReceiveDataAndUpdateStation` 都使用 `try/catch` 并“静默吞掉异常”,因此解析失败通常表现为:报文列表有记录,但充电桩字段没有变化。
### 6.2 发送报文解析与字段更新(`UpdateStationFromSendData`
发送报文完整调用链如下:
`AddSendMessage` -> `ParseSendDataAndUpdateStation`
-> `ParseSendRawData(rawData, type)`
-> `UpdateStationFromSendData(station, parsedData)`
-> `ChargeStationDataService.UpdateStation(...)`
#### 6.2.1 `ParseSendRawData` 输入格式与 `type` 支持
`ParseSendRawData` 的输入要求:
- `rawData` 以空格分隔字节 token(例如:`"BB 01 42 ..."`
- 每个 token 会按十六进制解析:`byte.TryParse(token, NumberStyles.HexNumber, ...)`
- 发送报文最少 token 数:`parts.Length >= 10`
当前实现里,`type` 仅对以下两种有明确字节位映射:
- `FRLDShort`
- `FRLDTall`
其他 `type`(例如 `MuXing`)不会命中映射分支,此时解析出来的数值保持默认值,然后仍可能触发 `UpdateStationFromSendData` 的“默认覆盖”逻辑(见下文“已知限制”)。
#### 6.2.2 从发送报文写入哪些 `ChargeStation` 字段
`UpdateStationFromSendData` 实际更新的字段如下(直接对应代码赋值):
- `station.LastSendTime = parsedData.SendTime`
- `station.ChargeCommandStatus`
- `parsedData.ChargeCommand == 1` -> `ChargeCommandStatus.Started`
- `parsedData.ChargeCommand == 0` -> `ChargeCommandStatus.Stopped`
- `station.BatteryLevel = parsedData.BatteryLevel`
- `station.CurrentVehicle = parsedData.CurrentVehicleId.ToString()`
注意:
- `UpdateStationFromSendData``SetVoltage` / `SetElectricCurrent` 的赋值被注释掉了(即:发送报文不会更新 `ChargeStation.SetVoltage` / `ChargeStation.SetElectricCurrent` 的配置目标值)。
### 6.3 接收报文解析与字段更新(`UpdateStationFromReceiveData`
接收报文完整调用链如下:
`AddReceiveMessage` -> `ParseReceiveDataAndUpdateStation`
-> `ParseReceiveRawData(rawData, type)`
-> `UpdateStationFromReceiveData(station, parsedData)`
-> `ChargeStationDataService.UpdateStation(...)`
#### 6.3.1 `ParseReceiveRawData` 输入格式与 `type` 支持
`ParseReceiveRawData` 的输入要求:
- `rawData` 以空格分隔字节 token`rawData.Split(' ')`
- 接收报文最少 token 数:`parts.Length >= 30`
- 每个 token 的解析使用的是 `byte.TryParse(parts[i], out bytes[i])`(没有显式 `NumberStyles.HexNumber`
因此当 `rawData` token 形如十六进制字节(例如 `0A``FF`)时,可能出现解析失败导致 `parsedData == null`(从而不会更新站点字段)的情况。
`type` 的字节位映射同样只实现了两种:
- `FRLDShort`
- `FRLDTall`
#### 6.3.2 从接收报文写入哪些 `ChargeStation` 字段
`UpdateStationFromReceiveData` 实际更新的字段如下:
- `station.LastReceiveTime = parsedData.ReceiveTime`
- `station.MechanismStatus = parsedData.MechanismStatus`
- `station.RealTimeVoltage = parsedData.RealTimeVoltage`
- `station.RealTimeCurrent = parsedData.RealTimeCurrent`
- `station.Status = parsedData.Status`
- `station.HasAlarm = parsedData.HasAlarm`
- `station.AlarmLevel = parsedData.AlarmLevel`
- `station.AlarmMessage`
- `parsedData.HasAlarm == true` -> `报警级别: {GetAlarmLevelText(parsedData.AlarmLevel)}`
- 否则 -> `string.Empty`
与报警相关的映射:
- `ParseReceiveRawData``HasAlarm = chargeStationStatus == 2`
- `ParseStationStatus``statusByte == 2` 映射为 `ChargeStationStatus.Fault`
当前实现里 `AlarmLevel` 的来源有一个明显限制:
- `ParseReceiveRawData``AlarmLevel = ParseAlarmLevel(bytes[20])` 被注释掉了
- 因此 `parsedData.AlarmLevel` 多半保持默认值(`AlarmLevel.None`),但只要 `HasAlarm == true``AlarmMessage` 仍会按默认 `AlarmLevel` 生成文本
同时,`ParsedReceiveData` 中的以下字段虽然会解析出来,但 `UpdateStationFromReceiveData` 没有把它们写入 `ChargeStation`
- `ParsedReceiveData.CommStatus`
- `ParsedReceiveData.ChargeCommandStatus`
- `ParsedReceiveData.ChargeID`
- `ParsedReceiveData.BatteryAH`
### 6.4 已知限制/行为总结(影响“字段是否更新”)
1. 解析失败只影响“字段更新”,不影响“报文记录与 UI 列表展示”
- 报文一定会先进入 `_messages`(并触发 `MessageAdded`
- 但解析函数返回 `null` / 站点找不到 / 异常时,字段更新不会发生
2. 站点匹配使用 `IP + Port`
- `GetStationByIp(ipAddress, port)` 找不到对应 `ChargeStation` 时,不会更新该站点运行时字段
3. `type` 只对 `FRLDShort` / `FRLDTall` 完成了映射
- 发送侧对未知 `type` 仍会返回默认 `ParsedSendData`,从而可能覆盖 `ChargeCommandStatus` / `BatteryLevel` / `CurrentVehicle` 为默认值
- 接收侧未知 `type` 也可能产生默认 `ParsedReceiveData`,但前提是 `rawData.Split(' ')` 后仍满足 `parts.Length >= 30`
4. 接收侧 token 解析方式可能与输入十六进制格式不一致
- `ParseReceiveRawData` 未使用 `NumberStyles.HexNumber`
- 如果 `rawData` token 是十六进制字节(如 `0A`),可能导致 `parsedData == null`,进而不更新实时字段
## 7. 运行时充电业务:StandardChargeMission
本节聚焦 `Charge/StandardChargeMission.cs` 中的“充电进程启动 + 500ms 充电业务循环”,并跟踪 `SendToChargeStation(...)` 的真实调用路径到具体充电桩实现类。
### 7.1 启动入口:`Execute()`
`StandardChargeMission.Execute()` 负责启动充电进程,核心流程:
- 设置进程状态:`status.status = "已启动"`
- 防重复启动:通过 `myStarted` 判断,避免重复创建线程
- 初始化运行时字典:`ChargeStations = new Dictionary<int, AbstractChargeStation>()`
- 创建后台线程:`ChargeThread = new Thread(() => { ... })`
- 在线程内部完成“充电站初始化 + 500ms 业务循环”
- 启动辅助任务:定期上传带 `unavailable` 标签的站点到迷毂系统(同样是 `Thread.Sleep(500)` 周期)
- 最后调用 `base.Execute()`,让基类调度/联锁逻辑继续工作
### 7.2 初始化:后台线程 Step1(创建/重建 `AbstractChargeStation`
`ChargeThread``while (true)` 内部,每一轮都会先执行“步骤1:初始化充电站”:
- 读取配置:`ChargeStationHelper.GetAllStationConfigs()`
- 底层来自 `ChargeStationDataService.Instance.GetAllStations()`
- 遍历每个充电桩配置项,执行校验与创建:
- `Enabled == false`:跳过
- 校验 `SiteId > 0``IpAddress` 可解析、`Port``1-65535`
- 若字典里已存在相同 `siteId` 的站点:
- 当 IP/Port 发生变化:`existingStation.CloseCommunication()` 后更新 `Ip/Port` 并重新 `CreateCommunication(...)`
- IP/Port 未变化:直接 `continue`(复用原连接)
- 若不存在:
- 使用 `GetChargeTypeString(stationConfig.Type)` 映射到具体站点类名:
- `FRLDTall` -> `FLChargeStation`
- `FRLDShort` -> `PCBChargeStation`
- `MuXing` -> `MuXingChargeStation`
- 默认回退 -> `PCBChargeStation`
- `Activator.CreateInstance(type)` 创建对象,设置:
- `SiteId / Ip / Port`
- `CommunicationType``FRLDShort` 强制 `UDP`,其它使用配置里的 `CommunicationType`
- 调用 `CreateCommunication(ipAddress, port)` 建立通信连接
- 放入字典:`ChargeStations.Add(siteId, stationInstance)`
同时,线程内部还会做 UDP 服务初始化:
- 若存在任意站点 `CommunicationType == "UDP"`
- `UdpService ??= new ChargeUdpService();`
### 7.3 500ms 业务循环:后台线程 Step3 + `SendToChargeStation(...)`
`ChargeThread` 的主循环结构(简化):
1. 读取互锁开关:`var shieldInterLock = ((StandardChargeMissionStatus)status).ShieldInterLock`
2. 更新/清理配置绑定:
-`ChargeStationHelper.GetStationBySiteId(siteId) == null`:从 `ChargeStations` 移除该站点
-`SimpleLib.GetAllSites()` 中仍带 `fields["Charge"]` 但不在 `ChargeStations` 配置里的站点:
- 移除 `Charge / setVoltage / setElectricCurrent / group` 等字段
3. 遍历每个站点,执行“车辆搜索 -> openCharge 计算 -> 下发”:
- 取站点配置:`chargeStationSetting = ChargeStationHelper.GetStationBySiteId(siteId)`
-`!chargeStationSetting.Enabled`:跳过
- 将站点配置绑定回 `site.fields`
- `site.fields["Charge"] = "True"`
- `site.fields["setVoltage"] = chargeStationSetting.SetVoltage.ToString("0.0")`
- `site.fields["setElectricCurrent"] = chargeStationSetting.SetElectricCurrent.ToString("0.0")`
- `site.fields["group"]`:启用时写 `GroupCarType`,禁用时写 `"禁用"`
- 设置站点进入/离开权限:
- `ChargeMethodType.Side` 分支:`SetAllowEnter / SetAllowExit``ShieldSiteMechanismStatus` / `MechanismStatus == Retracted` 联动
-`Side`:直接 `SetAllowEnter(true) / SetAllowExit(true) / SetAcknowledgeLeave(true)`
- 查找与该站点相关的车辆(在站/获取锁/持有锁):
- `GetLastSite() == siteId``aquiringLock == siteId``holdingLocks.Contains(siteId)`
- 计算 `openCharge`
- 默认 `0`
- 仅当车辆存在且 `Commons.GetVehicleStatus((Car)car) == VehicleStatus.Normal`
- 并且满足充电条件:
- `charging` 标记存在
- 未被占用:`!car.tags.Contains("occupied")`
- 锁状态匹配:`holdingLocks.Length == 1``pendingLocks.Length == 0`
-`openCharge = 1`
- 互锁门控后下发指令:
-`!shieldInterLock`
- `chargeStation.SendToChargeStation(openCharge, (Car)car);`
4. 循环尾部固定节拍:`Thread.Sleep(500)`
### 7.4 `SendToChargeStation` 调用链(下发路径)
在 500ms 循环中,下发的调用路径是:
`StandardChargeMission(ChargeThread 500ms loop)`
-> `AbstractChargeStation` 子类 `SendToChargeStation(int isCharge, Car car)`
-> 子类内部组包 + 记录发送报文:`CommunicationMessageService.Instance.AddSendMessage(...)`
-> 通过 TCP/UDP 通道真正发送报文
各站点实现类的“发送端”关键点:
- `FLChargeStation.SendToChargeStation`
- 依赖 `IsConnected && Client != null`,否则不发送
- 读取 `Car``Soc/Voltage/ElectricCurrent`,并可覆盖 `site.fields["setVoltage"]/["setElectricCurrent"]`
- `AddSendMessage(..., "FRLDTall", site?.name)``Client.Send(msg)`
- `MuXingChargeStation.SendToChargeStation`
- 计算 `openChargePort = (isCharge == 1 ? 2 : 3)` 并组包(包含时间戳与 CRC
- `AddSendMessage(..., "MuXing")` 后写入 TCP `stream`
- `PCBChargeStation.SendToChargeStation``FRLDShort`
- 使用 `UdpClient` 发送
- `AddSendMessage(..., "FRLDShort", site?.name)``udpClient.SendAsync(msg, msg.Length, _endPoint)`
```mermaid
flowchart TD
A[StandardChargeMission.Execute\n启动 ChargeThread] --> B[ChargeThread while(true)]
B --> C[Step1 初始化/重建 ChargeStations]
B --> D[Step3 遍历每个站点]
D --> E[计算 openCharge(0/1)]
E --> F{!ShieldInterLock}
F -->|false| Z[跳过下发]
F -->|true| G[chargeStation.SendToChargeStation(openCharge, car)]
G --> H[站点子类组包]
H --> I[CommunicationMessageService.AddSendMessage]
I --> J[TCP/UDP 发送报文]
```