# 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` 如果存在任何 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 chargeStations` - JSON 文件路径:基于运行目录写入 - `AppDomain.CurrentDomain.BaseDirectory/Config/ChargeStations.json` - 构造函数会确保 `Config/` 目录存在,并执行 `LoadData()` ### 2.2 读取:`LoadData()` 行为: - 若文件存在:读取文本并 `JsonConvert.DeserializeObject>(json)` - 若文件不存在:初始化为空列表(并不会自动生成默认样例) - 异常:记录诊断日志并回退到空列表 ### 2.3 保存:`SaveData()` 行为: - 在锁 `lockObj` 下序列化整个 `chargeStations` 列表 - 写入文件 `Config/ChargeStations.json`(`Formatting.Indented`) - 保存失败:返回 `false` 并由调用方回滚内存状态(部分方法会回滚) ### 2.4 查询 API - `List GetAllStations()`:返回列表副本(拷贝) - `ChargeStation GetStationById(string stationId)`:按 `StationId` 查找 - `ChargeStation GetStationByIp(string ipAddress, int port)`:按 `IpAddress + Port` 查找 - `List 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` - 若反序列化结果为 `null`,回退为空列表 - 文件不存在:初始化默认报警配置 `InitializeDefaultAlarms()`,随后 `SaveData()` - 异常:记录 `Debug.WriteLine`,回退到空列表并初始化默认报警配置 默认报警包含(示例): - 1001:电压过高 - 1002:电压过低 - 1003:电流过大 - 2001:温度异常 - 3001:通讯超时 - 3002:连接断开 ### 4.3 保存:`SaveData()` - 序列化整个 `_alarmConfigs` 并写入 `AlarmConfigs.json` - 保存失败会抛出异常(不只是返回 `false`) ### 4.4 查询 API - `List 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()` - 创建后台线程:`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 发送报文] ```