init commit
This commit is contained in:
@@ -0,0 +1,987 @@
|
||||
# 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 发送报文]
|
||||
```
|
||||
|
||||
Reference in New Issue
Block a user