Files
2026-06-14 11:19:15 +08:00

988 lines
44 KiB
Markdown
Raw Permalink Blame History

This file contains ambiguous Unicode characters
This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.
# 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 发送报文]
```