init commit

This commit is contained in:
zhaowei.huang
2026-06-14 11:19:15 +08:00
parent e79a3815a5
commit c8e540d272
174 changed files with 60830 additions and 39 deletions
File diff suppressed because it is too large Load Diff
+109
View File
@@ -0,0 +1,109 @@
using System;
using System.ComponentModel;
namespace StandardScene.Charge
{
/// <summary>
/// 报警配置数据模型
/// </summary>
public class AlarmConfig
{
/// <summary>
/// 报警编号(自动生成)
/// </summary>
[DisplayName("编号")]
public string AlarmId { get; set; }
/// <summary>
/// 报警编码值
/// </summary>
[DisplayName("报警编码")]
public int AlarmCode { get; set; }
/// <summary>
/// 报警内容描述
/// </summary>
[DisplayName("报警内容")]
public string AlarmContent { get; set; }
/// <summary>
/// 报警级别
/// </summary>
[DisplayName("报警级别")]
public AlarmLevel Level { get; set; }
/// <summary>
/// 是否启用
/// </summary>
[DisplayName("启用")]
public bool Enabled { get; set; }
/// <summary>
/// 备注
/// </summary>
[DisplayName("备注")]
public string Remarks { get; set; }
/// <summary>
/// 创建时间
/// </summary>
[DisplayName("创建时间")]
public DateTime CreatedTime { get; set; }
/// <summary>
/// 最后修改时间
/// </summary>
[DisplayName("修改时间")]
public DateTime ModifiedTime { get; set; }
public AlarmConfig()
{
AlarmId = GenerateAlarmId();
Level = AlarmLevel.Medium;
Enabled = true;
CreatedTime = DateTime.Now;
ModifiedTime = DateTime.Now;
}
/// <summary>
/// 生成报警编号
/// </summary>
private static string GenerateAlarmId()
{
return $"ALM{DateTime.Now:yyyyMMddHHmmss}{new Random().Next(100, 999)}";
}
/// <summary>
/// 验证数据有效性
/// </summary>
public bool IsValid(out string errorMessage)
{
if (string.IsNullOrWhiteSpace(AlarmId))
{
errorMessage = "报警编号不能为空";
return false;
}
if (AlarmCode < 0)
{
errorMessage = "报警编码不能为负数";
return false;
}
if (string.IsNullOrWhiteSpace(AlarmContent))
{
errorMessage = "报警内容不能为空";
return false;
}
errorMessage = string.Empty;
return true;
}
public override string ToString()
{
return $"[{AlarmCode}] {AlarmContent}";
}
}
}
@@ -0,0 +1,310 @@
using System;
using System.Collections.Generic;
using System.IO;
using System.Linq;
using Newtonsoft.Json;
namespace StandardScene.Charge
{
/// <summary>
/// 报警配置数据服务(单例模式)
/// </summary>
public class AlarmConfigDataService
{
private static AlarmConfigDataService _instance;
private static readonly object _lock = new object();
private List<AlarmConfig> _alarmConfigs;
private readonly string _dataFilePath;
private AlarmConfigDataService()
{
_dataFilePath = Path.Combine(AppDomain.CurrentDomain.BaseDirectory, "Config", "AlarmConfigs.json");
LoadData();
}
/// <summary>
/// 获取单例实例
/// </summary>
public static AlarmConfigDataService Instance
{
get
{
if (_instance == null)
{
lock (_lock)
{
if (_instance == null)
{
_instance = new AlarmConfigDataService();
}
}
}
return _instance;
}
}
/// <summary>
/// 从文件加载数据
/// </summary>
private void LoadData()
{
try
{
// 确保数据目录存在
var directory = Path.GetDirectoryName(_dataFilePath);
if (!string.IsNullOrEmpty(directory) && !Directory.Exists(directory))
{
Directory.CreateDirectory(directory);
}
if (File.Exists(_dataFilePath))
{
var json = File.ReadAllText(_dataFilePath);
if (!string.IsNullOrWhiteSpace(json))
{
_alarmConfigs = JsonConvert.DeserializeObject<List<AlarmConfig>>(json);
}
// 如果反序列化失败或为null,创建新列表
if (_alarmConfigs == null)
{
_alarmConfigs = new List<AlarmConfig>();
}
}
else
{
_alarmConfigs = new List<AlarmConfig>();
InitializeDefaultAlarms();
SaveData();
}
}
catch (Exception ex)
{
System.Diagnostics.Debug.WriteLine($"加载报警配置数据失败: {ex.Message}");
_alarmConfigs = new List<AlarmConfig>();
InitializeDefaultAlarms();
}
}
/// <summary>
/// 初始化默认报警配置
/// </summary>
private void InitializeDefaultAlarms()
{
//_alarmConfigs.Add(new AlarmConfig
//{
// AlarmCode = 1001,
// AlarmContent = "电压过高",
// Level = AlarmLevel.High,
// Enabled = true,
// Remarks = "电压超过额定值10%"
//});
//_alarmConfigs.Add(new AlarmConfig
//{
// AlarmCode = 1002,
// AlarmContent = "电压过低",
// Level = AlarmLevel.High,
// Enabled = true,
// Remarks = "电压低于额定值10%"
//});
//_alarmConfigs.Add(new AlarmConfig
//{
// AlarmCode = 1003,
// AlarmContent = "电流过大",
// Level = AlarmLevel.Critical,
// Enabled = true,
// Remarks = "电流超过额定值"
//});
//_alarmConfigs.Add(new AlarmConfig
//{
// AlarmCode = 2001,
// AlarmContent = "温度异常",
// Level = AlarmLevel.High,
// Enabled = true,
// Remarks = "温度超过安全范围"
//});
//_alarmConfigs.Add(new AlarmConfig
//{
// AlarmCode = 3001,
// AlarmContent = "通讯超时",
// Level = AlarmLevel.Medium,
// Enabled = true,
// Remarks = "通讯响应时间超过阈值"
//});
//_alarmConfigs.Add(new AlarmConfig
//{
// AlarmCode = 3002,
// AlarmContent = "连接断开",
// Level = AlarmLevel.Critical,
// Enabled = true,
// Remarks = "网络连接中断"
//});
}
/// <summary>
/// 保存数据到文件
/// </summary>
private void SaveData()
{
try
{
var directory = Path.GetDirectoryName(_dataFilePath);
if (!Directory.Exists(directory))
{
Directory.CreateDirectory(directory);
}
var json = JsonConvert.SerializeObject(_alarmConfigs, Formatting.Indented);
File.WriteAllText(_dataFilePath, json);
}
catch (Exception ex)
{
throw new Exception($"保存数据失败: {ex.Message}");
}
}
/// <summary>
/// 获取所有报警配置
/// </summary>
public List<AlarmConfig> GetAllAlarmConfigs()
{
lock (_lock)
{
if (_alarmConfigs == null)
{
_alarmConfigs = new List<AlarmConfig>();
}
return new List<AlarmConfig>(_alarmConfigs);
}
}
/// <summary>
/// 根据编号获取报警配置
/// </summary>
public AlarmConfig GetAlarmConfig(string alarmId)
{
lock (_lock)
{
return _alarmConfigs.FirstOrDefault(a => a.AlarmId == alarmId);
}
}
/// <summary>
/// 根据编号获取报警配置
/// </summary>
public AlarmConfig GetAlarmConfigAlarmCode(int alarmCode)
{
lock (_lock)
{
return _alarmConfigs.FirstOrDefault(a => a.AlarmCode == alarmCode);
}
}
/// <summary>
/// 根据报警编码获取报警配置
/// </summary>
public AlarmConfig GetAlarmConfigByCode(int alarmCode)
{
lock (_lock)
{
return _alarmConfigs.FirstOrDefault(a => a.AlarmCode == alarmCode);
}
}
/// <summary>
/// 添加报警配置
/// </summary>
public bool AddAlarmConfig(AlarmConfig alarmConfig, out string errorMessage)
{
lock (_lock)
{
if (!alarmConfig.IsValid(out errorMessage))
{
return false;
}
// 检查编码是否已存在
if (_alarmConfigs.Any(a => a.AlarmCode == alarmConfig.AlarmCode))
{
errorMessage = $"报警编码 {alarmConfig.AlarmCode} 已存在";
return false;
}
_alarmConfigs.Add(alarmConfig);
SaveData();
errorMessage = string.Empty;
return true;
}
}
/// <summary>
/// 更新报警配置
/// </summary>
public bool UpdateAlarmConfig(AlarmConfig alarmConfig, out string errorMessage)
{
lock (_lock)
{
if (!alarmConfig.IsValid(out errorMessage))
{
return false;
}
var index = _alarmConfigs.FindIndex(a => a.AlarmId == alarmConfig.AlarmId);
if (index == -1)
{
errorMessage = "报警配置不存在";
return false;
}
// 检查编码是否与其他配置冲突
if (_alarmConfigs.Any(a => a.AlarmId != alarmConfig.AlarmId && a.AlarmCode == alarmConfig.AlarmCode))
{
errorMessage = $"报警编码 {alarmConfig.AlarmCode} 已被其他配置使用";
return false;
}
alarmConfig.ModifiedTime = DateTime.Now;
_alarmConfigs[index] = alarmConfig;
SaveData();
errorMessage = string.Empty;
return true;
}
}
/// <summary>
/// 删除报警配置
/// </summary>
public bool DeleteAlarmConfig(string alarmId, out string errorMessage)
{
lock (_lock)
{
var alarmConfig = _alarmConfigs.FirstOrDefault(a => a.AlarmId == alarmId);
if (alarmConfig == null)
{
errorMessage = "报警配置不存在";
return false;
}
_alarmConfigs.Remove(alarmConfig);
SaveData();
errorMessage = string.Empty;
return true;
}
}
/// <summary>
/// 重新加载数据
/// </summary>
public void Reload()
{
lock (_lock)
{
LoadData();
}
}
}
}
@@ -0,0 +1,596 @@
namespace StandardScene.Charge
{
partial class AlarmConfigManagementForm
{
/// <summary>
/// Required designer variable.
/// </summary>
private System.ComponentModel.IContainer components = null;
/// <summary>
/// Clean up any resources being used.
/// </summary>
/// <param name="disposing">true if managed resources should be disposed; otherwise, false.</param>
protected override void Dispose(bool disposing)
{
if (disposing && (components != null))
{
components.Dispose();
}
base.Dispose(disposing);
}
#region Windows Form Designer generated code
/// <summary>
/// Required method for Designer support - do not modify
/// the contents of this method with the code editor.
/// </summary>
private void InitializeComponent()
{
System.Windows.Forms.DataGridViewCellStyle dataGridViewCellStyle3 = new System.Windows.Forms.DataGridViewCellStyle();
System.Windows.Forms.DataGridViewCellStyle dataGridViewCellStyle4 = new System.Windows.Forms.DataGridViewCellStyle();
this.splitContainer = new System.Windows.Forms.SplitContainer();
this.pnlList = new System.Windows.Forms.Panel();
this.dgvAlarmConfigs = new System.Windows.Forms.DataGridView();
this.pnlListButtons = new System.Windows.Forms.Panel();
this.lblStatistics = new System.Windows.Forms.Label();
this.btnClose = new System.Windows.Forms.Button();
this.btnRefresh = new System.Windows.Forms.Button();
this.pnlSearch = new System.Windows.Forms.Panel();
this.cmbLevelFilter = new System.Windows.Forms.ComboBox();
this.lblLevelFilter = new System.Windows.Forms.Label();
this.txtSearch = new System.Windows.Forms.TextBox();
this.lblSearch = new System.Windows.Forms.Label();
this.pnlEdit = new System.Windows.Forms.Panel();
this.grpEditInfo = new System.Windows.Forms.GroupBox();
this.txtRemarks = new System.Windows.Forms.TextBox();
this.lblRemarks = new System.Windows.Forms.Label();
this.chkEnabled = new System.Windows.Forms.CheckBox();
this.cmbLevel = new System.Windows.Forms.ComboBox();
this.lblLevel = new System.Windows.Forms.Label();
this.txtAlarmContent = new System.Windows.Forms.TextBox();
this.lblAlarmContent = new System.Windows.Forms.Label();
this.numAlarmCode = new System.Windows.Forms.NumericUpDown();
this.lblAlarmCode = new System.Windows.Forms.Label();
this.txtAlarmId = new System.Windows.Forms.TextBox();
this.lblAlarmId = new System.Windows.Forms.Label();
this.pnlEditButtons = new System.Windows.Forms.Panel();
this.btnCancel = new System.Windows.Forms.Button();
this.btnDelete = new System.Windows.Forms.Button();
this.btnSave = new System.Windows.Forms.Button();
this.colAlarmId = new System.Windows.Forms.DataGridViewTextBoxColumn();
this.colAlarmCode = new System.Windows.Forms.DataGridViewTextBoxColumn();
this.colAlarmContent = new System.Windows.Forms.DataGridViewTextBoxColumn();
this.colLevel = new System.Windows.Forms.DataGridViewTextBoxColumn();
this.colEnabled = new System.Windows.Forms.DataGridViewTextBoxColumn();
this.colRemarks = new System.Windows.Forms.DataGridViewTextBoxColumn();
((System.ComponentModel.ISupportInitialize)(this.splitContainer)).BeginInit();
this.splitContainer.Panel1.SuspendLayout();
this.splitContainer.Panel2.SuspendLayout();
this.splitContainer.SuspendLayout();
this.pnlList.SuspendLayout();
((System.ComponentModel.ISupportInitialize)(this.dgvAlarmConfigs)).BeginInit();
this.pnlListButtons.SuspendLayout();
this.pnlSearch.SuspendLayout();
this.pnlEdit.SuspendLayout();
this.grpEditInfo.SuspendLayout();
((System.ComponentModel.ISupportInitialize)(this.numAlarmCode)).BeginInit();
this.pnlEditButtons.SuspendLayout();
this.SuspendLayout();
//
// splitContainer
//
this.splitContainer.Dock = System.Windows.Forms.DockStyle.Fill;
this.splitContainer.Location = new System.Drawing.Point(0, 0);
this.splitContainer.Margin = new System.Windows.Forms.Padding(4);
this.splitContainer.Name = "splitContainer";
//
// splitContainer.Panel1
//
this.splitContainer.Panel1.Controls.Add(this.pnlList);
//
// splitContainer.Panel2
//
this.splitContainer.Panel2.Controls.Add(this.pnlEdit);
this.splitContainer.Size = new System.Drawing.Size(1400, 750);
this.splitContainer.SplitterDistance = 900;
this.splitContainer.SplitterWidth = 5;
this.splitContainer.TabIndex = 0;
//
// pnlList
//
this.pnlList.Controls.Add(this.dgvAlarmConfigs);
this.pnlList.Controls.Add(this.pnlListButtons);
this.pnlList.Controls.Add(this.pnlSearch);
this.pnlList.Dock = System.Windows.Forms.DockStyle.Fill;
this.pnlList.Location = new System.Drawing.Point(0, 0);
this.pnlList.Margin = new System.Windows.Forms.Padding(4);
this.pnlList.Name = "pnlList";
this.pnlList.Size = new System.Drawing.Size(900, 750);
this.pnlList.TabIndex = 0;
//
// dgvAlarmConfigs
//
this.dgvAlarmConfigs.AllowUserToAddRows = false;
this.dgvAlarmConfigs.AllowUserToDeleteRows = false;
this.dgvAlarmConfigs.AutoSizeColumnsMode = System.Windows.Forms.DataGridViewAutoSizeColumnsMode.Fill;
this.dgvAlarmConfigs.BackgroundColor = System.Drawing.Color.White;
this.dgvAlarmConfigs.BorderStyle = System.Windows.Forms.BorderStyle.None;
this.dgvAlarmConfigs.CellBorderStyle = System.Windows.Forms.DataGridViewCellBorderStyle.SingleHorizontal;
dataGridViewCellStyle3.Alignment = System.Windows.Forms.DataGridViewContentAlignment.MiddleLeft;
dataGridViewCellStyle3.BackColor = System.Drawing.Color.FromArgb(((int)(((byte)(63)))), ((int)(((byte)(81)))), ((int)(((byte)(181)))));
dataGridViewCellStyle3.Font = new System.Drawing.Font("微软雅黑", 9F, System.Drawing.FontStyle.Bold, System.Drawing.GraphicsUnit.Point, ((byte)(134)));
dataGridViewCellStyle3.ForeColor = System.Drawing.Color.White;
dataGridViewCellStyle3.SelectionBackColor = System.Drawing.Color.FromArgb(((int)(((byte)(63)))), ((int)(((byte)(81)))), ((int)(((byte)(181)))));
dataGridViewCellStyle3.SelectionForeColor = System.Drawing.SystemColors.HighlightText;
dataGridViewCellStyle3.WrapMode = System.Windows.Forms.DataGridViewTriState.True;
this.dgvAlarmConfigs.ColumnHeadersDefaultCellStyle = dataGridViewCellStyle3;
this.dgvAlarmConfigs.ColumnHeadersHeightSizeMode = System.Windows.Forms.DataGridViewColumnHeadersHeightSizeMode.AutoSize;
this.dgvAlarmConfigs.Columns.AddRange(new System.Windows.Forms.DataGridViewColumn[] {
this.colAlarmId,
this.colAlarmCode,
this.colAlarmContent,
this.colLevel,
this.colEnabled,
this.colRemarks});
dataGridViewCellStyle4.Alignment = System.Windows.Forms.DataGridViewContentAlignment.MiddleLeft;
dataGridViewCellStyle4.BackColor = System.Drawing.Color.White;
dataGridViewCellStyle4.Font = new System.Drawing.Font("微软雅黑", 9F, System.Drawing.FontStyle.Regular, System.Drawing.GraphicsUnit.Point, ((byte)(134)));
dataGridViewCellStyle4.ForeColor = System.Drawing.Color.FromArgb(((int)(((byte)(64)))), ((int)(((byte)(64)))), ((int)(((byte)(64)))));
dataGridViewCellStyle4.SelectionBackColor = System.Drawing.Color.FromArgb(((int)(((byte)(197)))), ((int)(((byte)(202)))), ((int)(((byte)(233)))));
dataGridViewCellStyle4.SelectionForeColor = System.Drawing.Color.FromArgb(((int)(((byte)(33)))), ((int)(((byte)(33)))), ((int)(((byte)(33)))));
dataGridViewCellStyle4.WrapMode = System.Windows.Forms.DataGridViewTriState.False;
this.dgvAlarmConfigs.DefaultCellStyle = dataGridViewCellStyle4;
this.dgvAlarmConfigs.Dock = System.Windows.Forms.DockStyle.Fill;
this.dgvAlarmConfigs.EnableHeadersVisualStyles = false;
this.dgvAlarmConfigs.GridColor = System.Drawing.Color.FromArgb(((int)(((byte)(224)))), ((int)(((byte)(224)))), ((int)(((byte)(224)))));
this.dgvAlarmConfigs.Location = new System.Drawing.Point(0, 62);
this.dgvAlarmConfigs.Margin = new System.Windows.Forms.Padding(4);
this.dgvAlarmConfigs.MultiSelect = false;
this.dgvAlarmConfigs.Name = "dgvAlarmConfigs";
this.dgvAlarmConfigs.ReadOnly = true;
this.dgvAlarmConfigs.RowHeadersVisible = false;
this.dgvAlarmConfigs.RowHeadersWidth = 30;
this.dgvAlarmConfigs.RowTemplate.Height = 35;
this.dgvAlarmConfigs.SelectionMode = System.Windows.Forms.DataGridViewSelectionMode.FullRowSelect;
this.dgvAlarmConfigs.Size = new System.Drawing.Size(900, 600);
this.dgvAlarmConfigs.TabIndex = 2;
this.dgvAlarmConfigs.CellDoubleClick += new System.Windows.Forms.DataGridViewCellEventHandler(this.dgvAlarmConfigs_CellDoubleClick);
//
// pnlListButtons
//
this.pnlListButtons.Controls.Add(this.lblStatistics);
this.pnlListButtons.Controls.Add(this.btnClose);
this.pnlListButtons.Controls.Add(this.btnRefresh);
this.pnlListButtons.Dock = System.Windows.Forms.DockStyle.Bottom;
this.pnlListButtons.Location = new System.Drawing.Point(0, 662);
this.pnlListButtons.Margin = new System.Windows.Forms.Padding(4);
this.pnlListButtons.Name = "pnlListButtons";
this.pnlListButtons.Padding = new System.Windows.Forms.Padding(13, 12, 13, 12);
this.pnlListButtons.Size = new System.Drawing.Size(900, 88);
this.pnlListButtons.TabIndex = 1;
//
// lblStatistics
//
this.lblStatistics.AutoSize = true;
this.lblStatistics.Font = new System.Drawing.Font("微软雅黑", 10F, System.Drawing.FontStyle.Bold, System.Drawing.GraphicsUnit.Point, ((byte)(134)));
this.lblStatistics.Location = new System.Drawing.Point(20, 31);
this.lblStatistics.Margin = new System.Windows.Forms.Padding(4, 0, 4, 0);
this.lblStatistics.Name = "lblStatistics";
this.lblStatistics.Size = new System.Drawing.Size(204, 24);
this.lblStatistics.TabIndex = 2;
this.lblStatistics.Text = "总数: 0 | 启用: 0 | 禁用: 0";
//
// btnClose
//
this.btnClose.Anchor = ((System.Windows.Forms.AnchorStyles)((System.Windows.Forms.AnchorStyles.Top | System.Windows.Forms.AnchorStyles.Right)));
this.btnClose.Font = new System.Drawing.Font("微软雅黑", 10F, System.Drawing.FontStyle.Regular, System.Drawing.GraphicsUnit.Point, ((byte)(134)));
this.btnClose.Location = new System.Drawing.Point(753, 19);
this.btnClose.Margin = new System.Windows.Forms.Padding(4);
this.btnClose.Name = "btnClose";
this.btnClose.Size = new System.Drawing.Size(120, 50);
this.btnClose.TabIndex = 1;
this.btnClose.Text = "关闭";
this.btnClose.UseVisualStyleBackColor = true;
this.btnClose.Click += new System.EventHandler(this.btnClose_Click);
//
// btnRefresh
//
this.btnRefresh.Anchor = ((System.Windows.Forms.AnchorStyles)((System.Windows.Forms.AnchorStyles.Top | System.Windows.Forms.AnchorStyles.Right)));
this.btnRefresh.Font = new System.Drawing.Font("微软雅黑", 10F, System.Drawing.FontStyle.Regular, System.Drawing.GraphicsUnit.Point, ((byte)(134)));
this.btnRefresh.Location = new System.Drawing.Point(620, 19);
this.btnRefresh.Margin = new System.Windows.Forms.Padding(4);
this.btnRefresh.Name = "btnRefresh";
this.btnRefresh.Size = new System.Drawing.Size(120, 50);
this.btnRefresh.TabIndex = 0;
this.btnRefresh.Text = "刷新";
this.btnRefresh.UseVisualStyleBackColor = true;
this.btnRefresh.Click += new System.EventHandler(this.btnRefresh_Click);
//
// pnlSearch
//
this.pnlSearch.Controls.Add(this.cmbLevelFilter);
this.pnlSearch.Controls.Add(this.lblLevelFilter);
this.pnlSearch.Controls.Add(this.txtSearch);
this.pnlSearch.Controls.Add(this.lblSearch);
this.pnlSearch.Dock = System.Windows.Forms.DockStyle.Top;
this.pnlSearch.Location = new System.Drawing.Point(0, 0);
this.pnlSearch.Margin = new System.Windows.Forms.Padding(4);
this.pnlSearch.Name = "pnlSearch";
this.pnlSearch.Padding = new System.Windows.Forms.Padding(13, 12, 13, 12);
this.pnlSearch.Size = new System.Drawing.Size(900, 62);
this.pnlSearch.TabIndex = 0;
//
// cmbLevelFilter
//
this.cmbLevelFilter.DropDownStyle = System.Windows.Forms.ComboBoxStyle.DropDownList;
this.cmbLevelFilter.Font = new System.Drawing.Font("微软雅黑", 10F, System.Drawing.FontStyle.Regular, System.Drawing.GraphicsUnit.Point, ((byte)(134)));
this.cmbLevelFilter.FormattingEnabled = true;
this.cmbLevelFilter.Location = new System.Drawing.Point(550, 16);
this.cmbLevelFilter.Margin = new System.Windows.Forms.Padding(4);
this.cmbLevelFilter.Name = "cmbLevelFilter";
this.cmbLevelFilter.Size = new System.Drawing.Size(150, 31);
this.cmbLevelFilter.TabIndex = 3;
this.cmbLevelFilter.SelectedIndexChanged += new System.EventHandler(this.cmbLevelFilter_SelectedIndexChanged);
//
// lblLevelFilter
//
this.lblLevelFilter.AutoSize = true;
this.lblLevelFilter.Font = new System.Drawing.Font("微软雅黑", 10F, System.Drawing.FontStyle.Regular, System.Drawing.GraphicsUnit.Point, ((byte)(134)));
this.lblLevelFilter.Location = new System.Drawing.Point(463, 21);
this.lblLevelFilter.Margin = new System.Windows.Forms.Padding(4, 0, 4, 0);
this.lblLevelFilter.Name = "lblLevelFilter";
this.lblLevelFilter.Size = new System.Drawing.Size(61, 23);
this.lblLevelFilter.TabIndex = 2;
this.lblLevelFilter.Text = "级别:";
//
// txtSearch
//
this.txtSearch.Font = new System.Drawing.Font("微软雅黑", 10F, System.Drawing.FontStyle.Regular, System.Drawing.GraphicsUnit.Point, ((byte)(134)));
this.txtSearch.Location = new System.Drawing.Point(100, 16);
this.txtSearch.Margin = new System.Windows.Forms.Padding(4);
this.txtSearch.Name = "txtSearch";
this.txtSearch.Size = new System.Drawing.Size(300, 29);
this.txtSearch.TabIndex = 1;
this.txtSearch.TextChanged += new System.EventHandler(this.txtSearch_TextChanged);
//
// lblSearch
//
this.lblSearch.AutoSize = true;
this.lblSearch.Font = new System.Drawing.Font("微软雅黑", 10F, System.Drawing.FontStyle.Regular, System.Drawing.GraphicsUnit.Point, ((byte)(134)));
this.lblSearch.Location = new System.Drawing.Point(13, 21);
this.lblSearch.Margin = new System.Windows.Forms.Padding(4, 0, 4, 0);
this.lblSearch.Name = "lblSearch";
this.lblSearch.Size = new System.Drawing.Size(61, 23);
this.lblSearch.TabIndex = 0;
this.lblSearch.Text = "搜索:";
//
// pnlEdit
//
this.pnlEdit.Controls.Add(this.grpEditInfo);
this.pnlEdit.Controls.Add(this.pnlEditButtons);
this.pnlEdit.Dock = System.Windows.Forms.DockStyle.Fill;
this.pnlEdit.Location = new System.Drawing.Point(0, 0);
this.pnlEdit.Margin = new System.Windows.Forms.Padding(4);
this.pnlEdit.Name = "pnlEdit";
this.pnlEdit.Size = new System.Drawing.Size(495, 750);
this.pnlEdit.TabIndex = 0;
//
// grpEditInfo
//
this.grpEditInfo.Controls.Add(this.txtRemarks);
this.grpEditInfo.Controls.Add(this.lblRemarks);
this.grpEditInfo.Controls.Add(this.chkEnabled);
this.grpEditInfo.Controls.Add(this.cmbLevel);
this.grpEditInfo.Controls.Add(this.lblLevel);
this.grpEditInfo.Controls.Add(this.txtAlarmContent);
this.grpEditInfo.Controls.Add(this.lblAlarmContent);
this.grpEditInfo.Controls.Add(this.numAlarmCode);
this.grpEditInfo.Controls.Add(this.lblAlarmCode);
this.grpEditInfo.Controls.Add(this.txtAlarmId);
this.grpEditInfo.Controls.Add(this.lblAlarmId);
this.grpEditInfo.Dock = System.Windows.Forms.DockStyle.Fill;
this.grpEditInfo.Font = new System.Drawing.Font("微软雅黑", 10F, System.Drawing.FontStyle.Bold, System.Drawing.GraphicsUnit.Point, ((byte)(134)));
this.grpEditInfo.Location = new System.Drawing.Point(0, 0);
this.grpEditInfo.Margin = new System.Windows.Forms.Padding(4);
this.grpEditInfo.Name = "grpEditInfo";
this.grpEditInfo.Padding = new System.Windows.Forms.Padding(20, 19, 20, 19);
this.grpEditInfo.Size = new System.Drawing.Size(495, 625);
this.grpEditInfo.TabIndex = 1;
this.grpEditInfo.TabStop = false;
this.grpEditInfo.Text = "报警配置信息";
//
// txtRemarks
//
this.txtRemarks.Font = new System.Drawing.Font("微软雅黑", 10F, System.Drawing.FontStyle.Regular, System.Drawing.GraphicsUnit.Point, ((byte)(134)));
this.txtRemarks.Location = new System.Drawing.Point(130, 350);
this.txtRemarks.Margin = new System.Windows.Forms.Padding(4);
this.txtRemarks.Multiline = true;
this.txtRemarks.Name = "txtRemarks";
this.txtRemarks.ScrollBars = System.Windows.Forms.ScrollBars.Vertical;
this.txtRemarks.Size = new System.Drawing.Size(330, 80);
this.txtRemarks.TabIndex = 10;
//
// lblRemarks
//
this.lblRemarks.Font = new System.Drawing.Font("微软雅黑", 10F, System.Drawing.FontStyle.Regular, System.Drawing.GraphicsUnit.Point, ((byte)(134)));
this.lblRemarks.Location = new System.Drawing.Point(27, 350);
this.lblRemarks.Margin = new System.Windows.Forms.Padding(4, 0, 4, 0);
this.lblRemarks.Name = "lblRemarks";
this.lblRemarks.Size = new System.Drawing.Size(100, 31);
this.lblRemarks.TabIndex = 9;
this.lblRemarks.Text = "备注:";
this.lblRemarks.TextAlign = System.Drawing.ContentAlignment.TopRight;
//
// chkEnabled
//
this.chkEnabled.AutoSize = true;
this.chkEnabled.Checked = true;
this.chkEnabled.CheckState = System.Windows.Forms.CheckState.Checked;
this.chkEnabled.Font = new System.Drawing.Font("微软雅黑", 10F, System.Drawing.FontStyle.Regular, System.Drawing.GraphicsUnit.Point, ((byte)(134)));
this.chkEnabled.Location = new System.Drawing.Point(130, 300);
this.chkEnabled.Margin = new System.Windows.Forms.Padding(4);
this.chkEnabled.Name = "chkEnabled";
this.chkEnabled.Size = new System.Drawing.Size(83, 27);
this.chkEnabled.TabIndex = 8;
this.chkEnabled.Text = "启用中";
this.chkEnabled.UseVisualStyleBackColor = true;
this.chkEnabled.Visible = false;
//
// cmbLevel
//
this.cmbLevel.DropDownStyle = System.Windows.Forms.ComboBoxStyle.DropDownList;
this.cmbLevel.Font = new System.Drawing.Font("微软雅黑", 10F, System.Drawing.FontStyle.Regular, System.Drawing.GraphicsUnit.Point, ((byte)(134)));
this.cmbLevel.FormattingEnabled = true;
this.cmbLevel.Location = new System.Drawing.Point(130, 244);
this.cmbLevel.Margin = new System.Windows.Forms.Padding(4);
this.cmbLevel.Name = "cmbLevel";
this.cmbLevel.Size = new System.Drawing.Size(330, 31);
this.cmbLevel.TabIndex = 7;
//
// lblLevel
//
this.lblLevel.Font = new System.Drawing.Font("微软雅黑", 10F, System.Drawing.FontStyle.Regular, System.Drawing.GraphicsUnit.Point, ((byte)(134)));
this.lblLevel.Location = new System.Drawing.Point(27, 244);
this.lblLevel.Margin = new System.Windows.Forms.Padding(4, 0, 4, 0);
this.lblLevel.Name = "lblLevel";
this.lblLevel.Size = new System.Drawing.Size(100, 31);
this.lblLevel.TabIndex = 6;
this.lblLevel.Text = "报警级别:";
this.lblLevel.TextAlign = System.Drawing.ContentAlignment.MiddleRight;
//
// txtAlarmContent
//
this.txtAlarmContent.Font = new System.Drawing.Font("微软雅黑", 10F, System.Drawing.FontStyle.Regular, System.Drawing.GraphicsUnit.Point, ((byte)(134)));
this.txtAlarmContent.Location = new System.Drawing.Point(130, 181);
this.txtAlarmContent.Margin = new System.Windows.Forms.Padding(4);
this.txtAlarmContent.Multiline = true;
this.txtAlarmContent.Name = "txtAlarmContent";
this.txtAlarmContent.Size = new System.Drawing.Size(330, 50);
this.txtAlarmContent.TabIndex = 5;
//
// lblAlarmContent
//
this.lblAlarmContent.Font = new System.Drawing.Font("微软雅黑", 10F, System.Drawing.FontStyle.Regular, System.Drawing.GraphicsUnit.Point, ((byte)(134)));
this.lblAlarmContent.Location = new System.Drawing.Point(27, 181);
this.lblAlarmContent.Margin = new System.Windows.Forms.Padding(4, 0, 4, 0);
this.lblAlarmContent.Name = "lblAlarmContent";
this.lblAlarmContent.Size = new System.Drawing.Size(100, 31);
this.lblAlarmContent.TabIndex = 4;
this.lblAlarmContent.Text = "报警内容:";
this.lblAlarmContent.TextAlign = System.Drawing.ContentAlignment.MiddleRight;
//
// numAlarmCode
//
this.numAlarmCode.Font = new System.Drawing.Font("微软雅黑", 10F, System.Drawing.FontStyle.Regular, System.Drawing.GraphicsUnit.Point, ((byte)(134)));
this.numAlarmCode.Location = new System.Drawing.Point(130, 119);
this.numAlarmCode.Margin = new System.Windows.Forms.Padding(4);
this.numAlarmCode.Maximum = new decimal(new int[] {
99999,
0,
0,
0});
this.numAlarmCode.Name = "numAlarmCode";
this.numAlarmCode.Size = new System.Drawing.Size(330, 29);
this.numAlarmCode.TabIndex = 3;
//
// lblAlarmCode
//
this.lblAlarmCode.Font = new System.Drawing.Font("微软雅黑", 10F, System.Drawing.FontStyle.Regular, System.Drawing.GraphicsUnit.Point, ((byte)(134)));
this.lblAlarmCode.Location = new System.Drawing.Point(27, 119);
this.lblAlarmCode.Margin = new System.Windows.Forms.Padding(4, 0, 4, 0);
this.lblAlarmCode.Name = "lblAlarmCode";
this.lblAlarmCode.Size = new System.Drawing.Size(100, 31);
this.lblAlarmCode.TabIndex = 2;
this.lblAlarmCode.Text = "报警编码:";
this.lblAlarmCode.TextAlign = System.Drawing.ContentAlignment.MiddleRight;
//
// txtAlarmId
//
this.txtAlarmId.BackColor = System.Drawing.Color.LightGray;
this.txtAlarmId.Font = new System.Drawing.Font("微软雅黑", 9F, System.Drawing.FontStyle.Regular, System.Drawing.GraphicsUnit.Point, ((byte)(134)));
this.txtAlarmId.Location = new System.Drawing.Point(130, 56);
this.txtAlarmId.Margin = new System.Windows.Forms.Padding(4);
this.txtAlarmId.Name = "txtAlarmId";
this.txtAlarmId.ReadOnly = true;
this.txtAlarmId.Size = new System.Drawing.Size(330, 27);
this.txtAlarmId.TabIndex = 1;
this.txtAlarmId.Visible = false;
//
// lblAlarmId
//
this.lblAlarmId.Font = new System.Drawing.Font("微软雅黑", 10F, System.Drawing.FontStyle.Regular, System.Drawing.GraphicsUnit.Point, ((byte)(134)));
this.lblAlarmId.Location = new System.Drawing.Point(27, 56);
this.lblAlarmId.Margin = new System.Windows.Forms.Padding(4, 0, 4, 0);
this.lblAlarmId.Name = "lblAlarmId";
this.lblAlarmId.Size = new System.Drawing.Size(100, 31);
this.lblAlarmId.TabIndex = 0;
this.lblAlarmId.Text = "编号:";
this.lblAlarmId.TextAlign = System.Drawing.ContentAlignment.MiddleRight;
this.lblAlarmId.Visible = false;
//
// pnlEditButtons
//
this.pnlEditButtons.Controls.Add(this.btnCancel);
this.pnlEditButtons.Controls.Add(this.btnDelete);
this.pnlEditButtons.Controls.Add(this.btnSave);
this.pnlEditButtons.Dock = System.Windows.Forms.DockStyle.Bottom;
this.pnlEditButtons.Location = new System.Drawing.Point(0, 625);
this.pnlEditButtons.Margin = new System.Windows.Forms.Padding(4);
this.pnlEditButtons.Name = "pnlEditButtons";
this.pnlEditButtons.Padding = new System.Windows.Forms.Padding(13, 12, 13, 12);
this.pnlEditButtons.Size = new System.Drawing.Size(495, 125);
this.pnlEditButtons.TabIndex = 0;
//
// btnCancel
//
this.btnCancel.Font = new System.Drawing.Font("微软雅黑", 11F, System.Drawing.FontStyle.Regular, System.Drawing.GraphicsUnit.Point, ((byte)(134)));
this.btnCancel.Location = new System.Drawing.Point(333, 25);
this.btnCancel.Margin = new System.Windows.Forms.Padding(4);
this.btnCancel.Name = "btnCancel";
this.btnCancel.Size = new System.Drawing.Size(133, 62);
this.btnCancel.TabIndex = 2;
this.btnCancel.Text = "取消";
this.btnCancel.UseVisualStyleBackColor = true;
this.btnCancel.Click += new System.EventHandler(this.btnCancel_Click);
//
// btnDelete
//
this.btnDelete.BackColor = System.Drawing.Color.LightCoral;
this.btnDelete.Font = new System.Drawing.Font("微软雅黑", 11F, System.Drawing.FontStyle.Bold, System.Drawing.GraphicsUnit.Point, ((byte)(134)));
this.btnDelete.Location = new System.Drawing.Point(180, 25);
this.btnDelete.Margin = new System.Windows.Forms.Padding(4);
this.btnDelete.Name = "btnDelete";
this.btnDelete.Size = new System.Drawing.Size(133, 62);
this.btnDelete.TabIndex = 1;
this.btnDelete.Text = "删除";
this.btnDelete.UseVisualStyleBackColor = false;
this.btnDelete.Click += new System.EventHandler(this.btnDelete_Click);
//
// btnSave
//
this.btnSave.BackColor = System.Drawing.Color.LightBlue;
this.btnSave.Font = new System.Drawing.Font("微软雅黑", 11F, System.Drawing.FontStyle.Bold, System.Drawing.GraphicsUnit.Point, ((byte)(134)));
this.btnSave.Location = new System.Drawing.Point(27, 25);
this.btnSave.Margin = new System.Windows.Forms.Padding(4);
this.btnSave.Name = "btnSave";
this.btnSave.Size = new System.Drawing.Size(133, 62);
this.btnSave.TabIndex = 0;
this.btnSave.Text = "新增";
this.btnSave.UseVisualStyleBackColor = false;
this.btnSave.Click += new System.EventHandler(this.btnSave_Click);
//
// colAlarmId
//
this.colAlarmId.HeaderText = "编号";
this.colAlarmId.MinimumWidth = 6;
this.colAlarmId.Name = "colAlarmId";
this.colAlarmId.ReadOnly = true;
this.colAlarmId.Visible = false;
//
// colAlarmCode
//
this.colAlarmCode.HeaderText = "报警编码";
this.colAlarmCode.MinimumWidth = 6;
this.colAlarmCode.Name = "colAlarmCode";
this.colAlarmCode.ReadOnly = true;
//
// colAlarmContent
//
this.colAlarmContent.HeaderText = "报警内容";
this.colAlarmContent.MinimumWidth = 6;
this.colAlarmContent.Name = "colAlarmContent";
this.colAlarmContent.ReadOnly = true;
//
// colLevel
//
this.colLevel.HeaderText = "级别";
this.colLevel.MinimumWidth = 6;
this.colLevel.Name = "colLevel";
this.colLevel.ReadOnly = true;
//
// colEnabled
//
this.colEnabled.HeaderText = "启用";
this.colEnabled.MinimumWidth = 6;
this.colEnabled.Name = "colEnabled";
this.colEnabled.ReadOnly = true;
//
// colRemarks
//
this.colRemarks.HeaderText = "备注";
this.colRemarks.MinimumWidth = 6;
this.colRemarks.Name = "colRemarks";
this.colRemarks.ReadOnly = true;
//
// AlarmConfigManagementForm
//
this.AutoScaleDimensions = new System.Drawing.SizeF(8F, 15F);
this.AutoScaleMode = System.Windows.Forms.AutoScaleMode.Font;
this.ClientSize = new System.Drawing.Size(1400, 750);
this.Controls.Add(this.splitContainer);
this.Margin = new System.Windows.Forms.Padding(4);
this.MinimumSize = new System.Drawing.Size(1200, 600);
this.Name = "AlarmConfigManagementForm";
this.StartPosition = System.Windows.Forms.FormStartPosition.CenterScreen;
this.Text = "报警配置管理";
this.splitContainer.Panel1.ResumeLayout(false);
this.splitContainer.Panel2.ResumeLayout(false);
((System.ComponentModel.ISupportInitialize)(this.splitContainer)).EndInit();
this.splitContainer.ResumeLayout(false);
this.pnlList.ResumeLayout(false);
((System.ComponentModel.ISupportInitialize)(this.dgvAlarmConfigs)).EndInit();
this.pnlListButtons.ResumeLayout(false);
this.pnlListButtons.PerformLayout();
this.pnlSearch.ResumeLayout(false);
this.pnlSearch.PerformLayout();
this.pnlEdit.ResumeLayout(false);
this.grpEditInfo.ResumeLayout(false);
this.grpEditInfo.PerformLayout();
((System.ComponentModel.ISupportInitialize)(this.numAlarmCode)).EndInit();
this.pnlEditButtons.ResumeLayout(false);
this.ResumeLayout(false);
}
#endregion
private System.Windows.Forms.SplitContainer splitContainer;
private System.Windows.Forms.Panel pnlList;
private System.Windows.Forms.DataGridView dgvAlarmConfigs;
private System.Windows.Forms.Panel pnlListButtons;
private System.Windows.Forms.Label lblStatistics;
private System.Windows.Forms.Button btnClose;
private System.Windows.Forms.Button btnRefresh;
private System.Windows.Forms.Panel pnlSearch;
private System.Windows.Forms.ComboBox cmbLevelFilter;
private System.Windows.Forms.Label lblLevelFilter;
private System.Windows.Forms.TextBox txtSearch;
private System.Windows.Forms.Label lblSearch;
private System.Windows.Forms.Panel pnlEdit;
private System.Windows.Forms.GroupBox grpEditInfo;
private System.Windows.Forms.TextBox txtRemarks;
private System.Windows.Forms.Label lblRemarks;
private System.Windows.Forms.CheckBox chkEnabled;
private System.Windows.Forms.ComboBox cmbLevel;
private System.Windows.Forms.Label lblLevel;
private System.Windows.Forms.TextBox txtAlarmContent;
private System.Windows.Forms.Label lblAlarmContent;
private System.Windows.Forms.NumericUpDown numAlarmCode;
private System.Windows.Forms.Label lblAlarmCode;
private System.Windows.Forms.TextBox txtAlarmId;
private System.Windows.Forms.Label lblAlarmId;
private System.Windows.Forms.Panel pnlEditButtons;
private System.Windows.Forms.Button btnCancel;
private System.Windows.Forms.Button btnDelete;
private System.Windows.Forms.Button btnSave;
private System.Windows.Forms.DataGridViewTextBoxColumn colAlarmId;
private System.Windows.Forms.DataGridViewTextBoxColumn colAlarmCode;
private System.Windows.Forms.DataGridViewTextBoxColumn colAlarmContent;
private System.Windows.Forms.DataGridViewTextBoxColumn colLevel;
private System.Windows.Forms.DataGridViewTextBoxColumn colEnabled;
private System.Windows.Forms.DataGridViewTextBoxColumn colRemarks;
}
}
@@ -0,0 +1,547 @@
using System;
using System.Drawing;
using System.Linq;
using System.Windows.Forms;
namespace StandardScene.Charge
{
/// <summary>
/// 报警配置管理窗体
/// </summary>
public partial class AlarmConfigManagementForm : Form
{
private readonly AlarmConfigDataService dataService;
private AlarmConfig selectedAlarmConfig;
public AlarmConfigManagementForm()
{
try
{
InitializeComponent();
dataService = AlarmConfigDataService.Instance;
// 订阅Load事件,确保所有控件都已初始化后再加载数据
this.Load += AlarmConfigManagementForm_Load;
}
catch (Exception ex)
{
MessageBox.Show($"初始化报警配置管理窗体失败: {ex.Message}\n\n详细信息:\n{ex.StackTrace}",
"错误", MessageBoxButtons.OK, MessageBoxIcon.Error);
}
}
/// <summary>
/// 窗体加载事件
/// </summary>
private void AlarmConfigManagementForm_Load(object sender, EventArgs e)
{
InitializeForm();
}
/// <summary>
/// 初始化窗体
/// </summary>
private void InitializeForm()
{
try
{
// 初始化报警级别下拉框
if (cmbLevel != null)
{
cmbLevel.Items.Clear();
cmbLevel.Items.Add("无");
cmbLevel.Items.Add("低");
cmbLevel.Items.Add("中");
cmbLevel.Items.Add("高");
cmbLevel.Items.Add("严重");
cmbLevel.SelectedIndex = 2; // 默认选择"中"
}
// 初始化级别筛选下拉框
if (cmbLevelFilter != null)
{
cmbLevelFilter.Items.Clear();
cmbLevelFilter.Items.Add("全部");
cmbLevelFilter.Items.Add("无");
cmbLevelFilter.Items.Add("低");
cmbLevelFilter.Items.Add("中");
cmbLevelFilter.Items.Add("高");
cmbLevelFilter.Items.Add("严重");
cmbLevelFilter.SelectedIndex = 0;
}
LoadAlarmConfigs();
ClearEditFields();
}
catch (Exception ex)
{
MessageBox.Show($"初始化窗体失败: {ex.Message}\n\n{ex.StackTrace}", "错误",
MessageBoxButtons.OK, MessageBoxIcon.Error);
}
}
/// <summary>
/// 加载报警配置列表
/// </summary>
private void LoadAlarmConfigs()
{
try
{
if (dgvAlarmConfigs == null)
{
return; // 控件还未初始化,直接返回
}
var alarmConfigs = dataService.GetAllAlarmConfigs();
if (alarmConfigs == null)
{
alarmConfigs = new System.Collections.Generic.List<AlarmConfig>();
}
// 根据级别筛选
if (cmbLevelFilter != null && cmbLevelFilter.SelectedIndex > 0)
{
var filterLevel = (AlarmLevel)(cmbLevelFilter.SelectedIndex - 1);
alarmConfigs = alarmConfigs.Where(a => a.Level == filterLevel).ToList();
}
// 根据搜索文本筛选
if (txtSearch != null && !string.IsNullOrWhiteSpace(txtSearch.Text))
{
var searchText = txtSearch.Text.Trim().ToLower();
alarmConfigs = alarmConfigs.Where(a =>
a.AlarmId.ToLower().Contains(searchText) ||
a.AlarmCode.ToString().Contains(searchText) ||
a.AlarmContent.ToLower().Contains(searchText)
).ToList();
}
dgvAlarmConfigs.Rows.Clear();
foreach (var alarm in alarmConfigs)
{
var index = dgvAlarmConfigs.Rows.Add(
alarm.AlarmId,
alarm.AlarmCode,
alarm.AlarmContent,
GetLevelText(alarm.Level),
alarm.Enabled ? "是" : "否",
alarm.Remarks
);
// 根据级别设置行颜色
var row = dgvAlarmConfigs.Rows[index];
switch (alarm.Level)
{
case AlarmLevel.Critical:
row.DefaultCellStyle.BackColor = Color.FromArgb(255, 235, 238); // 浅红色
row.DefaultCellStyle.ForeColor = Color.FromArgb(183, 28, 28);
// 安全地创建粗体字体
var baseFont = row.DefaultCellStyle.Font ?? dgvAlarmConfigs.DefaultCellStyle.Font ?? new Font("微软雅黑", 9F);
row.DefaultCellStyle.Font = new Font(baseFont, FontStyle.Bold);
break;
case AlarmLevel.High:
row.DefaultCellStyle.BackColor = Color.FromArgb(255, 243, 224); // 浅橙色
row.DefaultCellStyle.ForeColor = Color.FromArgb(230, 81, 0);
break;
case AlarmLevel.Medium:
row.DefaultCellStyle.BackColor = Color.FromArgb(255, 249, 196); // 浅黄色
row.DefaultCellStyle.ForeColor = Color.FromArgb(245, 127, 23);
break;
case AlarmLevel.Low:
row.DefaultCellStyle.BackColor = Color.FromArgb(232, 245, 233); // 浅绿色
row.DefaultCellStyle.ForeColor = Color.FromArgb(46, 125, 50);
break;
}
// 如果未启用,显示为灰色
if (!alarm.Enabled)
{
row.DefaultCellStyle.BackColor = Color.FromArgb(238, 238, 238);
row.DefaultCellStyle.ForeColor = Color.FromArgb(158, 158, 158);
}
}
UpdateStatistics();
UpdateTitleWithFilter(alarmConfigs.Count);
}
catch (Exception ex)
{
MessageBox.Show($"加载数据失败: {ex.Message}", "错误",
MessageBoxButtons.OK, MessageBoxIcon.Error);
}
}
/// <summary>
/// 更新统计信息
/// </summary>
private void UpdateStatistics()
{
try
{
if (lblStatistics == null)
{
return;
}
var alarmConfigs = dataService.GetAllAlarmConfigs();
if (alarmConfigs == null)
{
alarmConfigs = new System.Collections.Generic.List<AlarmConfig>();
}
var total = alarmConfigs.Count;
var enabled = alarmConfigs.Count(a => a.Enabled);
var disabled = total - enabled;
var critical = alarmConfigs.Count(a => a.Level == AlarmLevel.Critical);
var high = alarmConfigs.Count(a => a.Level == AlarmLevel.High);
lblStatistics.Text = $"总数: {total} | 启用: {enabled} | 禁用: {disabled} | 严重: {critical} | 高级: {high}";
}
catch (Exception ex)
{
System.Diagnostics.Debug.WriteLine($"更新统计信息失败: {ex.Message}");
}
}
/// <summary>
/// 更新标题显示筛选信息
/// </summary>
private void UpdateTitleWithFilter(int displayCount)
{
try
{
var allConfigs = dataService.GetAllAlarmConfigs();
var totalCount = allConfigs != null ? allConfigs.Count : 0;
if (cmbLevelFilter != null && cmbLevelFilter.SelectedIndex > 0)
{
this.Text = $"报警配置管理 - 显示: {displayCount}/{totalCount} ({cmbLevelFilter.Text})";
}
else
{
this.Text = $"报警配置管理 - 总数: {totalCount}";
}
}
catch (Exception ex)
{
System.Diagnostics.Debug.WriteLine($"更新标题失败: {ex.Message}");
this.Text = "报警配置管理";
}
}
/// <summary>
/// 获取级别文本
/// </summary>
private string GetLevelText(AlarmLevel level)
{
switch (level)
{
case AlarmLevel.None: return "无";
case AlarmLevel.Low: return "低";
case AlarmLevel.Medium: return "中";
case AlarmLevel.High: return "高";
case AlarmLevel.Critical: return "严重";
default: return "未知";
}
}
/// <summary>
/// 清空编辑字段
/// </summary>
private void ClearEditFields()
{
try
{
selectedAlarmConfig = null;
if (txtAlarmId != null)
{
txtAlarmId.Text = "";
txtAlarmId.Enabled = false; // 新增时编号自动生成
}
if (numAlarmCode != null)
{
numAlarmCode.Value = 0;
numAlarmCode.Enabled = true;
numAlarmCode.ReadOnly = false;
}
if (txtAlarmContent != null)
{
txtAlarmContent.Text = "";
txtAlarmContent.Enabled = true;
txtAlarmContent.ReadOnly = false;
}
if (cmbLevel != null)
{
cmbLevel.SelectedIndex = 2; // 中
cmbLevel.Enabled = true;
}
if (chkEnabled != null)
{
chkEnabled.Checked = true;
chkEnabled.Enabled = true;
}
if (txtRemarks != null)
{
txtRemarks.Text = "";
txtRemarks.Enabled = true;
txtRemarks.ReadOnly = false;
}
if (btnSave != null)
{
btnSave.Text = "新增";
btnSave.Enabled = true;
}
if (btnDelete != null)
{
btnDelete.Enabled = false;
}
}
catch (Exception ex)
{
System.Diagnostics.Debug.WriteLine($"清空编辑字段失败: {ex.Message}");
}
}
/// <summary>
/// 从字段创建报警配置
/// </summary>
private AlarmConfig CreateAlarmConfigFromFields()
{
var alarmConfig = selectedAlarmConfig ?? new AlarmConfig();
alarmConfig.AlarmCode = (int)numAlarmCode.Value;
alarmConfig.AlarmContent = txtAlarmContent.Text.Trim();
alarmConfig.Level = (AlarmLevel)cmbLevel.SelectedIndex;
alarmConfig.Enabled = chkEnabled.Checked;
alarmConfig.Remarks = txtRemarks.Text.Trim();
return alarmConfig;
}
/// <summary>
/// 加载报警配置到编辑区
/// </summary>
private void LoadAlarmConfigToFields(AlarmConfig alarmConfig)
{
try
{
selectedAlarmConfig = alarmConfig;
// 填充数据
if (txtAlarmId != null)
{
txtAlarmId.Text = alarmConfig.AlarmId;
txtAlarmId.Enabled = false; // 编号不可修改
}
if (numAlarmCode != null)
{
numAlarmCode.Value = alarmConfig.AlarmCode;
numAlarmCode.Enabled = true;
numAlarmCode.ReadOnly = false;
}
if (txtAlarmContent != null)
{
txtAlarmContent.Text = alarmConfig.AlarmContent;
txtAlarmContent.Enabled = true;
txtAlarmContent.ReadOnly = false;
}
if (cmbLevel != null)
{
cmbLevel.SelectedIndex = (int)alarmConfig.Level;
cmbLevel.Enabled = true;
}
if (chkEnabled != null)
{
chkEnabled.Checked = alarmConfig.Enabled;
chkEnabled.Enabled = true;
}
if (txtRemarks != null)
{
txtRemarks.Text = alarmConfig.Remarks ?? "";
txtRemarks.Enabled = true;
txtRemarks.ReadOnly = false;
}
// 设置按钮状态
if (btnSave != null)
{
btnSave.Text = "保存";
btnSave.Enabled = true;
}
if (btnDelete != null)
{
btnDelete.Enabled = true;
}
}
catch (Exception ex)
{
MessageBox.Show($"加载数据到编辑区失败: {ex.Message}\n\n{ex.StackTrace}", "错误",
MessageBoxButtons.OK, MessageBoxIcon.Error);
}
}
// ==================== 事件处理 ====================
private void btnSave_Click(object sender, EventArgs e)
{
try
{
// 验证报警编码
if (numAlarmCode.Value < 0)
{
MessageBox.Show("报警编码不能为负数", "验证失败",
MessageBoxButtons.OK, MessageBoxIcon.Warning);
numAlarmCode.Focus();
return;
}
// 验证报警内容
if (string.IsNullOrWhiteSpace(txtAlarmContent.Text))
{
MessageBox.Show("报警内容不能为空", "验证失败",
MessageBoxButtons.OK, MessageBoxIcon.Warning);
txtAlarmContent.Focus();
return;
}
var alarmConfig = CreateAlarmConfigFromFields();
string errorMessage;
bool success;
if (selectedAlarmConfig == null)
{
// 新增
success = dataService.AddAlarmConfig(alarmConfig, out errorMessage);
}
else
{
// 更新
success = dataService.UpdateAlarmConfig(alarmConfig, out errorMessage);
}
if (success)
{
MessageBox.Show("保存成功!", "提示",
MessageBoxButtons.OK, MessageBoxIcon.Information);
LoadAlarmConfigs();
ClearEditFields();
}
else
{
MessageBox.Show($"保存失败: {errorMessage}", "错误",
MessageBoxButtons.OK, MessageBoxIcon.Error);
}
}
catch (Exception ex)
{
MessageBox.Show($"保存失败: {ex.Message}", "错误",
MessageBoxButtons.OK, MessageBoxIcon.Error);
}
}
private void btnDelete_Click(object sender, EventArgs e)
{
if (selectedAlarmConfig == null)
{
MessageBox.Show("请先选择要删除的报警配置", "提示",
MessageBoxButtons.OK, MessageBoxIcon.Warning);
return;
}
var result = MessageBox.Show(
$"确定要删除报警配置 [{selectedAlarmConfig.AlarmCode}] {selectedAlarmConfig.AlarmContent} 吗?",
"确认删除",
MessageBoxButtons.YesNo,
MessageBoxIcon.Question);
if (result == DialogResult.Yes)
{
if (dataService.DeleteAlarmConfig(selectedAlarmConfig.AlarmId, out string errorMessage))
{
MessageBox.Show("删除成功!", "提示",
MessageBoxButtons.OK, MessageBoxIcon.Information);
LoadAlarmConfigs();
ClearEditFields();
}
else
{
MessageBox.Show($"删除失败: {errorMessage}", "错误",
MessageBoxButtons.OK, MessageBoxIcon.Error);
}
}
}
private void btnCancel_Click(object sender, EventArgs e)
{
ClearEditFields();
}
private void btnRefresh_Click(object sender, EventArgs e)
{
dataService.Reload();
LoadAlarmConfigs();
}
private void btnClose_Click(object sender, EventArgs e)
{
this.Close();
}
private void dgvAlarmConfigs_CellDoubleClick(object sender, DataGridViewCellEventArgs e)
{
try
{
if (e.RowIndex >= 0 && e.RowIndex < dgvAlarmConfigs.Rows.Count)
{
var row = dgvAlarmConfigs.Rows[e.RowIndex];
if (row.Cells[0].Value != null)
{
var alarmId = row.Cells[1].Value.ToString();
var alarmConfig = dataService.GetAlarmConfigAlarmCode(int.Parse(alarmId));
if (alarmConfig != null)
{
LoadAlarmConfigToFields(alarmConfig);
}
else
{
MessageBox.Show($"未找到报警配置: {alarmId}", "提示",
MessageBoxButtons.OK, MessageBoxIcon.Warning);
}
}
}
}
catch (Exception ex)
{
MessageBox.Show($"加载报警配置失败: {ex.Message}", "错误",
MessageBoxButtons.OK, MessageBoxIcon.Error);
}
}
private void txtSearch_TextChanged(object sender, EventArgs e)
{
LoadAlarmConfigs();
}
private void cmbLevelFilter_SelectedIndexChanged(object sender, EventArgs e)
{
LoadAlarmConfigs();
}
}
}
@@ -0,0 +1,120 @@
<?xml version="1.0" encoding="utf-8"?>
<root>
<!--
Microsoft ResX Schema
Version 2.0
The primary goals of this format is to allow a simple XML format
that is mostly human readable. The generation and parsing of the
various data types are done through the TypeConverter classes
associated with the data types.
Example:
... ado.net/XML headers & schema ...
<resheader name="resmimetype">text/microsoft-resx</resheader>
<resheader name="version">2.0</resheader>
<resheader name="reader">System.Resources.ResXResourceReader, System.Windows.Forms, ...</resheader>
<resheader name="writer">System.Resources.ResXResourceWriter, System.Windows.Forms, ...</resheader>
<data name="Name1"><value>this is my long string</value><comment>this is a comment</comment></data>
<data name="Color1" type="System.Drawing.Color, System.Drawing">Blue</data>
<data name="Bitmap1" mimetype="application/x-microsoft.net.object.binary.base64">
<value>[base64 mime encoded serialized .NET Framework object]</value>
</data>
<data name="Icon1" type="System.Drawing.Icon, System.Drawing" mimetype="application/x-microsoft.net.object.bytearray.base64">
<value>[base64 mime encoded string representing a byte array form of the .NET Framework object]</value>
<comment>This is a comment</comment>
</data>
There are any number of "resheader" rows that contain simple
name/value pairs.
Each data row contains a name, and value. The row also contains a
type or mimetype. Type corresponds to a .NET class that support
text/value conversion through the TypeConverter architecture.
Classes that don't support this are serialized and stored with the
mimetype set.
The mimetype is used for serialized objects, and tells the
ResXResourceReader how to depersist the object. This is currently not
extensible. For a given mimetype the value must be set accordingly:
Note - application/x-microsoft.net.object.binary.base64 is the format
that the ResXResourceWriter will generate, however the reader can
read any of the formats listed below.
mimetype: application/x-microsoft.net.object.binary.base64
value : The object must be serialized with
: System.Runtime.Serialization.Formatters.Binary.BinaryFormatter
: and then encoded with base64 encoding.
mimetype: application/x-microsoft.net.object.soap.base64
value : The object must be serialized with
: System.Runtime.Serialization.Formatters.Soap.SoapFormatter
: and then encoded with base64 encoding.
mimetype: application/x-microsoft.net.object.bytearray.base64
value : The object must be serialized into a byte array
: using a System.ComponentModel.TypeConverter
: and then encoded with base64 encoding.
-->
<xsd:schema id="root" xmlns="" xmlns:xsd="http://www.w3.org/2001/XMLSchema" xmlns:msdata="urn:schemas-microsoft-com:xml-msdata">
<xsd:import namespace="http://www.w3.org/XML/1998/namespace" />
<xsd:element name="root" msdata:IsDataSet="true">
<xsd:complexType>
<xsd:choice maxOccurs="unbounded">
<xsd:element name="metadata">
<xsd:complexType>
<xsd:sequence>
<xsd:element name="value" type="xsd:string" minOccurs="0" />
</xsd:sequence>
<xsd:attribute name="name" use="required" type="xsd:string" />
<xsd:attribute name="type" type="xsd:string" />
<xsd:attribute name="mimetype" type="xsd:string" />
<xsd:attribute ref="xml:space" />
</xsd:complexType>
</xsd:element>
<xsd:element name="assembly">
<xsd:complexType>
<xsd:attribute name="alias" type="xsd:string" />
<xsd:attribute name="name" type="xsd:string" />
</xsd:complexType>
</xsd:element>
<xsd:element name="data">
<xsd:complexType>
<xsd:sequence>
<xsd:element name="value" type="xsd:string" minOccurs="0" msdata:Ordinal="1" />
<xsd:element name="comment" type="xsd:string" minOccurs="0" msdata:Ordinal="2" />
</xsd:sequence>
<xsd:attribute name="name" type="xsd:string" use="required" msdata:Ordinal="1" />
<xsd:attribute name="type" type="xsd:string" msdata:Ordinal="3" />
<xsd:attribute name="mimetype" type="xsd:string" msdata:Ordinal="4" />
<xsd:attribute ref="xml:space" />
</xsd:complexType>
</xsd:element>
<xsd:element name="resheader">
<xsd:complexType>
<xsd:sequence>
<xsd:element name="value" type="xsd:string" minOccurs="0" msdata:Ordinal="1" />
</xsd:sequence>
<xsd:attribute name="name" type="xsd:string" use="required" />
</xsd:complexType>
</xsd:element>
</xsd:choice>
</xsd:complexType>
</xsd:element>
</xsd:schema>
<resheader name="resmimetype">
<value>text/microsoft-resx</value>
</resheader>
<resheader name="version">
<value>2.0</value>
</resheader>
<resheader name="reader">
<value>System.Resources.ResXResourceReader, System.Windows.Forms, Version=4.0.0.0, Culture=neutral, PublicKeyToken=b77a5c561934e089</value>
</resheader>
<resheader name="writer">
<value>System.Resources.ResXResourceWriter, System.Windows.Forms, Version=4.0.0.0, Culture=neutral, PublicKeyToken=b77a5c561934e089</value>
</resheader>
</root>
+429
View File
@@ -0,0 +1,429 @@
using System;
using System.ComponentModel;
using Newtonsoft.Json;
namespace StandardScene.Charge
{
/// <summary>
/// 充电桩数据模型
/// </summary>
public class ChargeStation
{
/// <summary>
/// 充电桩编号(唯一标识)
/// </summary>
[DisplayName("编号")]
public string StationId { get; set; } = "1";
/// <summary>
/// 充电桩名称
/// </summary>
[DisplayName("名称")]
public string Name { get; set; }
/// <summary>
/// 充电桩类型
/// </summary>
[DisplayName("类型")]
public ChargeStationType Type { get; set; }
/// <summary>
/// 充电方式
/// </summary>
[DisplayName("充电方式")]
public ChargeMethodType ChargeMethod { get; set; }
/// <summary>
/// IP地址
/// </summary>
[DisplayName("IP地址")]
public string IpAddress { get; set; }
/// <summary>
/// 端口号
/// </summary>
[DisplayName("端口")]
public int Port { get; set; }
/// <summary>
/// 通讯类型 (UDP/TCP)
/// </summary>
[DisplayName("通讯类型")]
public string CommunicationType { get; set; } = "TCP";
/// <summary>
/// 额定电压 (V)
/// </summary>
[DisplayName("电压(V)")]
public double SetVoltage { get; set; }
/// <summary>
/// 额定电流 (A)
/// </summary>
[DisplayName("电流(A)")]
public double SetElectricCurrent { get; set; }
/// <summary>
/// 实时电压 (V) - 当前充电时的实际电压
/// </summary>
[DisplayName("实时电压(V)")]
[JsonIgnore]
public double RealTimeVoltage { get; set; }
/// <summary>
/// 实时电流 (A) - 当前充电时的实际电流
/// </summary>
[DisplayName("实时电流(A)")]
[JsonIgnore]
public double RealTimeCurrent { get; set; }
/// <summary>
/// 最后发送数据时间
/// </summary>
[DisplayName("发送时间")]
[JsonIgnore]
public DateTime? LastSendTime { get; set; }
/// <summary>
/// 最后接收数据时间
/// </summary>
[DisplayName("接收时间")]
[JsonIgnore]
public DateTime? LastReceiveTime { get; set; }
/// <summary>
/// 是否有报警
/// </summary>
[DisplayName("报警")]
[JsonIgnore]
public bool HasAlarm { get; set; }
/// <summary>
/// 报警信息
/// </summary>
[DisplayName("报警信息")]
[JsonIgnore]
public string AlarmMessage { get; set; }
/// <summary>
/// 报警级别
/// </summary>
[DisplayName("报警级别")]
public AlarmLevel AlarmLevel { get; set; }
/// <summary>
/// 网络通讯状态
/// </summary>
[DisplayName("通讯状态")]
[JsonIgnore]
public CommunicationStatus CommStatus { get; set; }
/// <summary>
/// 最后通讯成功时间
/// </summary>
[JsonIgnore]
[DisplayName("最后通讯时间")]
public DateTime? LastCommunicationTime { get; set; }
/// <summary>
/// 机构伸缩状态
/// </summary>
[JsonIgnore]
[DisplayName("机构状态")]
public MechanismStatus MechanismStatus { get; set; }
[DisplayName("屏蔽机构状态交互")]
public bool ShieldSiteMechanismStatus { get; set; }
/// <summary>
/// 当前充电车辆编号
/// </summary>
[DisplayName("当前车辆")]
[JsonIgnore]
public string CurrentVehicle { get; set; }
/// <summary>
/// 当前电量百分比 (0-100)
/// </summary>
[DisplayName("电量")]
[JsonIgnore]
public double BatteryLevel { get; set; }
/// <summary>
/// 发送充电的状态
/// </summary>
[DisplayName("充电指令状态")]
[JsonIgnore]
public ChargeCommandStatus ChargeCommandStatus { get; set; }
/// <summary>
/// 充电桩状态
/// </summary>
[DisplayName("状态")]
[JsonIgnore]
public ChargeStationStatus Status { get; set; }
/// <summary>
/// 是否启用
/// </summary>
[DisplayName("启用")]
public bool Enabled { get; set; }
[DisplayName("停靠车辆类型")]
public ChargeStationCarType GroupCarType { get; set; }
/// <summary>
/// 关联的站点ID(可选)
/// </summary>
[DisplayName("站点ID")]
public int? SiteId { get; set; }
/// <summary>
/// 备注
/// </summary>
[DisplayName("备注")]
public string Remarks { get; set; }
/// <summary>
/// 创建时间
/// </summary>
[DisplayName("创建时间")]
public DateTime CreatedTime { get; set; }
/// <summary>
/// 最后修改时间
/// </summary>
[DisplayName("修改时间")]
public DateTime ModifiedTime { get; set; }
/// <summary>
/// 计算功率 (W)
/// </summary>
[JsonIgnore]
[DisplayName("功率(W)")]
public double Power => SetVoltage * SetElectricCurrent;
public ChargeStation()
{
StationId = GenerateStationId();
Type = ChargeStationType.FRLDTall; // 默认FRLD高款充电桩
ChargeMethod = ChargeMethodType.Ground; // 默认地充
Status = ChargeStationStatus.Idle;
Enabled = true;
CreatedTime = DateTime.Now;
ModifiedTime = DateTime.Now;
Port = 502; // 默认Modbus TCP端口
CommunicationType = "UDP"; // 默认UDP通讯
SetVoltage = 29.2;
SetElectricCurrent = 45.0;
}
/// <summary>
/// 生成充电桩编号
/// </summary>
private static string GenerateStationId()
{
return "1";
//return $"CS{DateTime.Now:yyyyMMddHHmmss}{new Random().Next(1000, 9999)}";
}
/// <summary>
/// 验证数据有效性
/// </summary>
public bool IsValid(out string errorMessage)
{
if (string.IsNullOrWhiteSpace(StationId))
{
errorMessage = "充电桩编号不能为空";
return false;
}
if (string.IsNullOrWhiteSpace(Name))
{
errorMessage = "充电桩名称不能为空";
return false;
}
if (string.IsNullOrWhiteSpace(IpAddress))
{
errorMessage = "IP地址不能为空";
return false;
}
// 验证IP格式
if (!System.Net.IPAddress.TryParse(IpAddress, out _))
{
errorMessage = "IP地址格式不正确";
return false;
}
// 验证端口范围
if (Port < 1 || Port > 65535)
{
errorMessage = "端口号必须在 1-65535 之间";
return false;
}
// 验证电压范围
if (SetVoltage <= 0 || SetVoltage > 64)
{
errorMessage = "电压必须在 0-64V 之间";
return false;
}
// 验证电流范围
if (SetElectricCurrent <= 0 || SetElectricCurrent > 101)
{
errorMessage = "电流必须在 0-101A 之间";
return false;
}
errorMessage = string.Empty;
return true;
}
public override string ToString()
{
return $"[{StationId}] {Name} ({IpAddress}:{Port}) - {Status}";
}
}
/// <summary>
/// 充电桩类型枚举
/// </summary>
public enum ChargeStationType
{
[Description("FRLD高款充电桩")]
FRLDTall = 0,
[Description("FRLD矮款充电桩")]
FRLDShort = 1,
[Description("牧星充电桩")]
MuXing = 2
// 后续可在此处添加其他充电桩类型
}
public enum ChargeStationCarType
{
[Description("FRLD充电")]
FRLD = 0,
[Description("牧星充电桩充电")]
MuXing = 1
// 后续可在此处添加其他充电桩类型
}
/// <summary>
/// 充电桩状态枚举
/// </summary>
public enum ChargeStationStatus
{
[Description("空闲")]
Idle = 0,
[Description("充电中")]
Charging = 1,
[Description("报警中")]
Fault = 2,
[Description("AGV电池已接入")]
Battery = 3
}
/// <summary>
/// 报警级别枚举
/// </summary>
public enum AlarmLevel
{
[Description("无")]
None = 0,
[Description("低")]
Low = 1,
[Description("中")]
Medium = 2,
[Description("高")]
High = 3,
[Description("严重")]
Critical = 4
}
/// <summary>
/// 机构伸缩状态枚举
/// </summary>
public enum MechanismStatus
{
[Description("伸出")]
Extended = 1,
[Description("缩回")]
Retracted = 2,
[Description("运动中")]
Extending = 3,
}
/// <summary>
/// 网络通讯状态枚举
/// </summary>
public enum CommunicationStatus
{
[Description("未知")]
Unknown = 0,
[Description("正常")]
Normal = 1,
[Description("延迟")]
Delayed = 2,
[Description("超时")]
Timeout = 3,
[Description("断开")]
Disconnected = 4,
[Description("错误")]
Error = 5
}
/// <summary>
/// 充电指令状态枚举
/// </summary>
public enum ChargeCommandStatus
{
[Description("停止")]
Stopped = 0,
[Description("启动")]
Started = 1
}
/// <summary>
/// 充电方式枚举
/// </summary>
public enum ChargeMethodType
{
[Description("地充")]
Ground = 0,
[Description("尾充")]
Rear = 1,
[Description("侧充")]
Side = 2
}
}
@@ -0,0 +1,386 @@
using DocumentFormat.OpenXml.Bibliography;
using Newtonsoft.Json;
using SimpleCore;
using SimpleCore.Library;
using System;
using System.Collections.Generic;
using System.IO;
using System.Linq;
namespace StandardScene.Charge
{
/// <summary>
/// 充电桩数据服务 - 负责数据的持久化和管理
/// </summary>
public class ChargeStationDataService
{
private static ChargeStationDataService _instance;
private static readonly object lockObj = new object();
private List<ChargeStation> chargeStations;
private readonly string dataFilePath;
// 单例模式
public static ChargeStationDataService Instance
{
get
{
if (_instance == null)
{
lock (lockObj)
{
if (_instance == null)
{
_instance = new ChargeStationDataService();
}
}
}
return _instance;
}
}
private ChargeStationDataService()
{
// 数据文件路径:项目根目录/Config/ChargeStations.json
var dataDir = Path.Combine(AppDomain.CurrentDomain.BaseDirectory, "Config");
if (!Directory.Exists(dataDir))
{
Directory.CreateDirectory(dataDir);
}
dataFilePath = Path.Combine(dataDir, "ChargeStations.json");
chargeStations = new List<ChargeStation>();
LoadData();
}
/// <summary>
/// 加载数据
/// </summary>
private void LoadData()
{
try
{
if (File.Exists(dataFilePath))
{
var json = File.ReadAllText(dataFilePath);
chargeStations = JsonConvert.DeserializeObject<List<ChargeStation>>(json)
?? new List<ChargeStation>();
Diagnosis.Log($"加载充电桩数据成功,共 {chargeStations.Count} 条记录", "ChargeStation");
}
else
{
chargeStations = new List<ChargeStation>();
Diagnosis.Log("充电桩数据文件不存在,已创建新列表", "ChargeStation");
}
}
catch (Exception ex)
{
Diagnosis.Log($"加载充电桩数据失败: {ExceptionFormatter.FormatEx(ex)}", "ChargeStation", true);
chargeStations = new List<ChargeStation>();
}
}
/// <summary>
/// 保存数据
/// </summary>
private bool SaveData()
{
try
{
lock (lockObj)
{
var json = JsonConvert.SerializeObject(chargeStations, Formatting.Indented);
File.WriteAllText(dataFilePath, json);
//Diagnosis.Log($"保存充电桩数据成功,共 {chargeStations.Count} 条记录", "ChargeStation");
return true;
}
}
catch (Exception ex)
{
Diagnosis.Log($"保存充电桩数据失败: {ExceptionFormatter.FormatEx(ex)}", "ChargeStation", true);
return false;
}
}
/// <summary>
/// 获取所有充电桩
/// </summary>
public List<ChargeStation> GetAllStations()
{
lock (lockObj)
{
return new List<ChargeStation>(chargeStations);
}
}
/// <summary>
/// 根据编号获取充电桩
/// </summary>
public ChargeStation GetStationById(string stationId)
{
lock (lockObj)
{
return chargeStations.FirstOrDefault(s => s.StationId == stationId);
}
}
/// <summary>
/// 根据IP地址获取充电桩
/// </summary>
public ChargeStation GetStationByIp(string ipAddress, int port)
{
lock (lockObj)
{
return chargeStations.FirstOrDefault(s => s.IpAddress == ipAddress && s.Port == port);
}
}
public ChargeStation GetStationByIp(string ipAddress)
{
lock (lockObj)
{
return chargeStations.FirstOrDefault(s => s.IpAddress == ipAddress);
}
}
/// <summary>
/// 添加充电桩
/// </summary>
public bool AddStation(ChargeStation station, out string errorMessage)
{
if (station == null)
{
errorMessage = "充电桩数据不能为空";
return false;
}
// 验证数据
if (!station.IsValid(out errorMessage))
{
return false;
}
lock (lockObj)
{
// 检查编号是否已存在
if (chargeStations.Any(s => s.StationId == station.StationId))
{
errorMessage = $"充电桩编号 {station.StationId} 已存在";
return false;
}
// 检查IP和端口是否已被使用
if (chargeStations.Any(s => s.IpAddress == station.IpAddress && s.Port == station.Port))
{
errorMessage = $"IP地址 {station.IpAddress}:{station.Port} 已被使用";
return false;
}
if (chargeStations.Any(s => s.SiteId == station.SiteId))
{
errorMessage = $"SiteID {station.SiteId} 已被使用";
return false;
}
station.CreatedTime = DateTime.Now;
station.ModifiedTime = DateTime.Now;
chargeStations.Add(station);
if (SaveData())
{
Diagnosis.Log($"添加充电桩成功: {station}", "ChargeStation", true);
errorMessage = string.Empty;
return true;
}
else
{
chargeStations.Remove(station);
errorMessage = "保存数据失败";
return false;
}
}
}
/// <summary>
/// 更新充电桩
/// </summary>
public bool UpdateStation(ChargeStation station, out string errorMessage, bool isSave = false)
{
if (station == null)
{
errorMessage = "充电桩数据不能为空";
return false;
}
// 验证数据
if (!station.IsValid(out errorMessage))
{
return false;
}
lock (lockObj)
{
var existingStation = chargeStations.FirstOrDefault(s => s.StationId == station.StationId);
if (existingStation == null)
{
errorMessage = $"充电桩编号 {station.StationId} 不存在";
return false;
}
// 检查IP和端口是否与其他充电桩冲突
if (chargeStations.Any(s => s.StationId != station.StationId &&
s.IpAddress == station.IpAddress &&
s.Port == station.Port))
{
errorMessage = $"IP地址 {station.IpAddress}:{station.Port} 已被其他充电桩使用";
return false;
}
if (chargeStations.Any(s => s.StationId != station.StationId && s.SiteId == station.SiteId))
{
errorMessage = $"SiteID {station.SiteId} 已被使用";
return false;
}
// 保留创建时间
station.CreatedTime = existingStation.CreatedTime;
station.ModifiedTime = DateTime.Now;
var index = chargeStations.IndexOf(existingStation);
//进行赋值
if (isSave)
{
existingStation.StationId = station.StationId;
existingStation.Name = station.Name;
existingStation.Type = station.Type;
existingStation.ChargeMethod = station.ChargeMethod;
existingStation.IpAddress = station.IpAddress;
existingStation.Port = station.Port;
existingStation.SetVoltage = station.SetVoltage;
existingStation.SetElectricCurrent = station.SetElectricCurrent;
existingStation.Enabled = station.Enabled;
existingStation.ShieldSiteMechanismStatus = station.ShieldSiteMechanismStatus;
existingStation.GroupCarType = station.GroupCarType;
existingStation.SiteId = station.SiteId;
existingStation.Remarks = station.Remarks;
station = existingStation;
station.ModifiedTime = DateTime.Now;
}
chargeStations[index] = station;
if (SaveData())
{
//Diagnosis.Log($"更新充电桩成功: {station}", "ChargeStation", true);
errorMessage = string.Empty;
return true;
}
else
{
chargeStations[index] = existingStation;
errorMessage = "保存数据失败";
return false;
}
}
}
/// <summary>
/// 删除充电桩
/// </summary>
public bool DeleteStation(string stationId, out string errorMessage)
{
lock (lockObj)
{
var station = chargeStations.FirstOrDefault(s => s.StationId == stationId);
if (station == null)
{
errorMessage = $"充电桩编号 {stationId} 不存在";
return false;
}
// 检查是否正在充电
//if (station.Status == ChargeStationStatus.Charging)
//{
// errorMessage = $"充电桩 {station.Name} 正在充电中,无法删除";
// return false;
//}
chargeStations.Remove(station);
if (SaveData())
{
Diagnosis.Log($"删除充电桩成功: {station}", "ChargeStation", true);
errorMessage = string.Empty;
return true;
}
else
{
chargeStations.Add(station);
errorMessage = "保存数据失败";
return false;
}
}
}
/// <summary>
/// 更新充电桩状态
/// </summary>
public bool UpdateStationStatus(string stationId, ChargeStationStatus status)
{
lock (lockObj)
{
var station = chargeStations.FirstOrDefault(s => s.StationId == stationId);
if (station == null)
{
return false;
}
station.Status = status;
station.ModifiedTime = DateTime.Now;
return SaveData();
}
}
/// <summary>
/// 获取空闲的充电桩
/// </summary>
public List<ChargeStation> GetIdleStations()
{
lock (lockObj)
{
return chargeStations
.Where(s => s.Enabled && s.Status == ChargeStationStatus.Idle)
.ToList();
}
}
/// <summary>
/// 获取充电中的充电桩数量
/// </summary>
public int GetChargingCount()
{
lock (lockObj)
{
return chargeStations.Count(s => s.Status == ChargeStationStatus.Charging);
}
}
/// <summary>
/// 重新加载数据
/// </summary>
public void Reload()
{
LoadData();
}
}
}
@@ -0,0 +1,448 @@
using System;
using System.Collections.Generic;
using System.Linq;
using System.Windows.Forms;
using SimpleCore;
using SimpleCore.Library;
namespace StandardScene.Charge
{
/// <summary>
/// 充电桩管理辅助类
/// 提供简化的静态方法用于快速访问充电桩功能
/// </summary>
public static class ChargeStationHelper
{
private static ChargeStationManagementForm _managementForm;
/// <summary>
/// 打开充电桩管理窗口(单例模式)
/// </summary>
public static void OpenManagementWindow()
{
if (_managementForm == null || _managementForm.IsDisposed)
{
_managementForm = new ChargeStationManagementForm();
_managementForm.FormClosed += (s, e) => _managementForm = null;
_managementForm.Show();
}
else
{
_managementForm.BringToFront();
_managementForm.Activate();
}
}
/// <summary>
/// 打开充电桩管理窗口(对话框模式)
/// </summary>
public static DialogResult OpenManagementDialog()
{
using (var form = new ChargeStationManagementForm())
{
return form.ShowDialog();
}
}
/// <summary>
/// 获取指定站点的充电桩
/// </summary>
/// <param name="siteId">站点ID</param>
/// <returns>充电桩对象,如果不存在则返回null</returns>
public static ChargeStation GetStationBySiteId(int siteId)
{
var dataService = ChargeStationDataService.Instance;
return dataService.GetAllStations()
.FirstOrDefault(s => s.SiteId == siteId);
}
/// <summary>
/// 获取指定IP的充电桩
/// </summary>
/// <param name="ipAddress">IP地址</param>
/// <returns>充电桩对象,如果不存在则返回null</returns>
public static ChargeStation GetStationByIp(string ipAddress)
{
var dataService = ChargeStationDataService.Instance;
return dataService.GetAllStations()
.FirstOrDefault(s => s.IpAddress == ipAddress);
}
/// <summary>
/// 获取所有充电桩配置
/// </summary>
/// <returns>所有充电桩配置列表</returns>
public static List<ChargeStation> GetAllStationConfigs()
{
var dataService = ChargeStationDataService.Instance;
return dataService.GetAllStations();
}
/// <summary>
/// 获取当前充电策略配置
/// </summary>
/// <returns>充电策略配置对象</returns>
public static ChargeStrategyConfig GetChargeStrategyConfig()
{
var configService = ChargeStrategyConfigService.Instance;
return configService.LoadConfig();
}
/// <summary>
/// 保存充电策略配置
/// </summary>
/// <param name="config">充电策略配置对象</param>
public static void SaveChargeStrategyConfig(ChargeStrategyConfig config)
{
var configService = ChargeStrategyConfigService.Instance;
configService.SaveConfig(config);
}
/// <summary>
/// 检查指定站点是否有可用的充电桩
/// </summary>
/// <param name="siteId">站点ID</param>
/// <returns>true表示有可用充电桩,false表示没有</returns>
public static bool IsSiteHasAvailableChargeStation(int siteId)
{
var station = GetStationBySiteId(siteId);
return station != null &&
station.Enabled &&
station.Status == ChargeStationStatus.Idle;
}
/// <summary>
/// 标记充电桩开始充电
/// </summary>
/// <param name="stationId">充电桩编号</param>
/// <param name="carId">车辆ID</param>
/// <returns>成功返回true,失败返回false</returns>
public static bool StartCharging(string stationId, int carId)
{
try
{
var dataService = ChargeStationDataService.Instance;
var station = dataService.GetStationById(stationId);
if (station == null)
{
Diagnosis.Log($"充电桩 {stationId} 不存在", "ChargeStation", true);
return false;
}
if (station.Status == ChargeStationStatus.Charging)
{
Diagnosis.Log($"充电桩 {station.Name} 已经在充电中", "ChargeStation", true);
return false;
}
bool success = dataService.UpdateStationStatus(stationId, ChargeStationStatus.Charging);
if (success)
{
Diagnosis.Log($"车辆 {carId} 开始在充电桩 {station.Name} 充电", "ChargeStation", true);
}
return success;
}
catch (Exception ex)
{
Diagnosis.Log($"启动充电失败: {ExceptionFormatter.FormatEx(ex)}", "ChargeStation", true);
return false;
}
}
/// <summary>
/// 标记充电桩停止充电
/// </summary>
/// <param name="stationId">充电桩编号</param>
/// <param name="carId">车辆ID</param>
/// <returns>成功返回true,失败返回false</returns>
public static bool StopCharging(string stationId, int carId)
{
try
{
var dataService = ChargeStationDataService.Instance;
var station = dataService.GetStationById(stationId);
if (station == null)
{
Diagnosis.Log($"充电桩 {stationId} 不存在", "ChargeStation", true);
return false;
}
bool success = dataService.UpdateStationStatus(stationId, ChargeStationStatus.Idle);
if (success)
{
Diagnosis.Log($"车辆 {carId} 在充电桩 {station.Name} 充电完成", "ChargeStation", true);
}
return success;
}
catch (Exception ex)
{
Diagnosis.Log($"停止充电失败: {ExceptionFormatter.FormatEx(ex)}", "ChargeStation", true);
return false;
}
}
/// <summary>
/// 标记充电桩为故障状态
/// </summary>
/// <param name="stationId">充电桩编号</param>
/// <param name="reason">故障原因</param>
/// <returns>成功返回true,失败返回false</returns>
public static bool MarkAsFault(string stationId, string reason = "")
{
try
{
var dataService = ChargeStationDataService.Instance;
bool success = dataService.UpdateStationStatus(stationId, ChargeStationStatus.Fault);
if (success)
{
var station = dataService.GetStationById(stationId);
var message = string.IsNullOrEmpty(reason)
? $"充电桩 {station.Name} 标记为故障"
: $"充电桩 {station.Name} 标记为故障: {reason}";
Diagnosis.Log(message, "ChargeStation", true);
}
return success;
}
catch (Exception ex)
{
Diagnosis.Log($"标记故障失败: {ExceptionFormatter.FormatEx(ex)}", "ChargeStation", true);
return false;
}
}
/// <summary>
/// 获取充电桩状态摘要信息
/// </summary>
/// <returns>格式化的状态字符串</returns>
public static string GetStatusSummary()
{
var dataService = ChargeStationDataService.Instance;
var stations = dataService.GetAllStations();
var total = stations.Count;
var idle = stations.Count(s => s.Status == ChargeStationStatus.Idle && s.Enabled);
var charging = stations.Count(s => s.Status == ChargeStationStatus.Charging);
var fault = stations.Count(s => s.Status == ChargeStationStatus.Fault);
var offline = stations.Count(s => s.Status == ChargeStationStatus.Battery);
return $"总数:{total} | 空闲:{idle} | 充电中:{charging} | 故障:{fault} | 离线:{offline}";
}
/// <summary>
/// 获取最近的空闲充电桩(基于站点ID)
/// </summary>
/// <param name="currentSiteId">当前站点ID</param>
/// <returns>最近的充电桩,如果没有则返回null</returns>
public static ChargeStation FindNearestIdleStation(int currentSiteId)
{
var dataService = ChargeStationDataService.Instance;
var idleStations = dataService.GetIdleStations();
if (idleStations.Count == 0)
return null;
// 优先选择同站点的充电桩
var sameStation = idleStations.FirstOrDefault(s => s.SiteId == currentSiteId);
if (sameStation != null)
return sameStation;
// 否则选择第一个可用的
return idleStations[0];
}
/// <summary>
/// 快速创建测试充电桩(用于测试)
/// </summary>
/// <param name="name">名称</param>
/// <param name="ip">IP地址</param>
/// <param name="siteId">站点ID</param>
/// <returns>创建成功返回true</returns>
public static bool QuickAddStation(string name, string ip, int? siteId = null)
{
var dataService = ChargeStationDataService.Instance;
var station = new ChargeStation
{
Name = name,
IpAddress = ip,
Port = 502,
SetVoltage = 220.0,
SetElectricCurrent = 32.0,
Status = ChargeStationStatus.Idle,
Enabled = true,
SiteId = siteId,
Remarks = $"快速创建于 {DateTime.Now}"
};
bool success = dataService.AddStation(station, out string errorMsg);
if (success)
{
Diagnosis.Log($"快速创建充电桩: {name}", "ChargeStation", true);
}
else
{
Diagnosis.Log($"快速创建充电桩失败: {errorMsg}", "ChargeStation", true);
}
return success;
}
/// <summary>
/// 显示充电桩选择对话框
/// </summary>
/// <param name="filterByStatus">按状态过滤(null表示显示全部)</param>
/// <returns>选中的充电桩,取消则返回null</returns>
public static ChargeStation ShowStationSelectionDialog(ChargeStationStatus? filterByStatus = null)
{
var dataService = ChargeStationDataService.Instance;
var stations = dataService.GetAllStations();
if (filterByStatus.HasValue)
{
stations = stations.Where(s => s.Status == filterByStatus.Value).ToList();
}
if (stations.Count == 0)
{
MessageBox.Show("没有符合条件的充电桩", "提示",
MessageBoxButtons.OK, MessageBoxIcon.Information);
return null;
}
// 创建简单的选择对话框
using (var dialog = new Form())
{
dialog.Text = "选择充电桩";
dialog.Size = new System.Drawing.Size(500, 400);
dialog.StartPosition = FormStartPosition.CenterParent;
var listBox = new ListBox
{
Dock = DockStyle.Fill,
Font = new System.Drawing.Font("微软雅黑", 10F)
};
foreach (var station in stations)
{
listBox.Items.Add($"[{station.StationId}] {station.Name} - {station.IpAddress}:{station.Port} - {GetStatusText(station.Status)}");
}
var btnOK = new Button
{
Text = "确定",
DialogResult = DialogResult.OK,
Dock = DockStyle.Bottom,
Height = 40
};
dialog.Controls.Add(listBox);
dialog.Controls.Add(btnOK);
dialog.AcceptButton = btnOK;
if (dialog.ShowDialog() == DialogResult.OK && listBox.SelectedIndex >= 0)
{
return stations[listBox.SelectedIndex];
}
return null;
}
}
/// <summary>
/// 获取状态文本
/// </summary>
private static string GetStatusText(ChargeStationStatus status)
{
switch (status)
{
case ChargeStationStatus.Idle: return "空闲";
case ChargeStationStatus.Charging: return "充电中";
case ChargeStationStatus.Fault: return "故障";
case ChargeStationStatus.Battery: return "离线";
default: return "未知";
}
}
/// <summary>
/// 批量更新充电桩在线状态(用于定期监控)
/// </summary>
/// <param name="timeout">超时时间(毫秒)</param>
/// <returns>更新的充电桩数量</returns>
public static int UpdateOnlineStatus(int timeout = 3000)
{
var dataService = ChargeStationDataService.Instance;
var stations = dataService.GetAllStations().Where(s => s.Enabled).ToList();
int updatedCount = 0;
foreach (var station in stations)
{
try
{
// 这里应该实际ping充电桩,此处仅演示
bool isOnline = PingStation(station.IpAddress, station.Port, timeout);
var expectedStatus = isOnline
? (station.Status == ChargeStationStatus.Battery ? ChargeStationStatus.Idle : station.Status)
: ChargeStationStatus.Battery;
if (station.Status != expectedStatus &&
(station.Status == ChargeStationStatus.Battery || expectedStatus == ChargeStationStatus.Battery))
{
if (dataService.UpdateStationStatus(station.StationId, expectedStatus))
{
updatedCount++;
Diagnosis.Log($"充电桩 {station.Name} 状态更新为: {GetStatusText(expectedStatus)}",
"ChargeStation", true);
}
}
}
catch (Exception ex)
{
Diagnosis.Log($"检查充电桩 {station.Name} 在线状态失败: {ex.Message}",
"ChargeStation");
}
}
return updatedCount;
}
/// <summary>
/// Ping 充电桩(检查连通性)
/// </summary>
private static bool PingStation(string ip, int port, int timeout)
{
try
{
using (var client = new System.Net.Sockets.TcpClient())
{
var result = client.BeginConnect(ip, port, null, null);
var success = result.AsyncWaitHandle.WaitOne(TimeSpan.FromMilliseconds(timeout));
if (success)
{
client.EndConnect(result);
return true;
}
return false;
}
}
catch
{
return false;
}
}
}
}
File diff suppressed because it is too large Load Diff
File diff suppressed because it is too large Load Diff
@@ -0,0 +1,120 @@
<?xml version="1.0" encoding="utf-8"?>
<root>
<!--
Microsoft ResX Schema
Version 2.0
The primary goals of this format is to allow a simple XML format
that is mostly human readable. The generation and parsing of the
various data types are done through the TypeConverter classes
associated with the data types.
Example:
... ado.net/XML headers & schema ...
<resheader name="resmimetype">text/microsoft-resx</resheader>
<resheader name="version">2.0</resheader>
<resheader name="reader">System.Resources.ResXResourceReader, System.Windows.Forms, ...</resheader>
<resheader name="writer">System.Resources.ResXResourceWriter, System.Windows.Forms, ...</resheader>
<data name="Name1"><value>this is my long string</value><comment>this is a comment</comment></data>
<data name="Color1" type="System.Drawing.Color, System.Drawing">Blue</data>
<data name="Bitmap1" mimetype="application/x-microsoft.net.object.binary.base64">
<value>[base64 mime encoded serialized .NET Framework object]</value>
</data>
<data name="Icon1" type="System.Drawing.Icon, System.Drawing" mimetype="application/x-microsoft.net.object.bytearray.base64">
<value>[base64 mime encoded string representing a byte array form of the .NET Framework object]</value>
<comment>This is a comment</comment>
</data>
There are any number of "resheader" rows that contain simple
name/value pairs.
Each data row contains a name, and value. The row also contains a
type or mimetype. Type corresponds to a .NET class that support
text/value conversion through the TypeConverter architecture.
Classes that don't support this are serialized and stored with the
mimetype set.
The mimetype is used for serialized objects, and tells the
ResXResourceReader how to depersist the object. This is currently not
extensible. For a given mimetype the value must be set accordingly:
Note - application/x-microsoft.net.object.binary.base64 is the format
that the ResXResourceWriter will generate, however the reader can
read any of the formats listed below.
mimetype: application/x-microsoft.net.object.binary.base64
value : The object must be serialized with
: System.Runtime.Serialization.Formatters.Binary.BinaryFormatter
: and then encoded with base64 encoding.
mimetype: application/x-microsoft.net.object.soap.base64
value : The object must be serialized with
: System.Runtime.Serialization.Formatters.Soap.SoapFormatter
: and then encoded with base64 encoding.
mimetype: application/x-microsoft.net.object.bytearray.base64
value : The object must be serialized into a byte array
: using a System.ComponentModel.TypeConverter
: and then encoded with base64 encoding.
-->
<xsd:schema id="root" xmlns="" xmlns:xsd="http://www.w3.org/2001/XMLSchema" xmlns:msdata="urn:schemas-microsoft-com:xml-msdata">
<xsd:import namespace="http://www.w3.org/XML/1998/namespace" />
<xsd:element name="root" msdata:IsDataSet="true">
<xsd:complexType>
<xsd:choice maxOccurs="unbounded">
<xsd:element name="metadata">
<xsd:complexType>
<xsd:sequence>
<xsd:element name="value" type="xsd:string" minOccurs="0" />
</xsd:sequence>
<xsd:attribute name="name" use="required" type="xsd:string" />
<xsd:attribute name="type" type="xsd:string" />
<xsd:attribute name="mimetype" type="xsd:string" />
<xsd:attribute ref="xml:space" />
</xsd:complexType>
</xsd:element>
<xsd:element name="assembly">
<xsd:complexType>
<xsd:attribute name="alias" type="xsd:string" />
<xsd:attribute name="name" type="xsd:string" />
</xsd:complexType>
</xsd:element>
<xsd:element name="data">
<xsd:complexType>
<xsd:sequence>
<xsd:element name="value" type="xsd:string" minOccurs="0" msdata:Ordinal="1" />
<xsd:element name="comment" type="xsd:string" minOccurs="0" msdata:Ordinal="2" />
</xsd:sequence>
<xsd:attribute name="name" type="xsd:string" use="required" msdata:Ordinal="1" />
<xsd:attribute name="type" type="xsd:string" msdata:Ordinal="3" />
<xsd:attribute name="mimetype" type="xsd:string" msdata:Ordinal="4" />
<xsd:attribute ref="xml:space" />
</xsd:complexType>
</xsd:element>
<xsd:element name="resheader">
<xsd:complexType>
<xsd:sequence>
<xsd:element name="value" type="xsd:string" minOccurs="0" msdata:Ordinal="1" />
</xsd:sequence>
<xsd:attribute name="name" type="xsd:string" use="required" />
</xsd:complexType>
</xsd:element>
</xsd:choice>
</xsd:complexType>
</xsd:element>
</xsd:schema>
<resheader name="resmimetype">
<value>text/microsoft-resx</value>
</resheader>
<resheader name="version">
<value>2.0</value>
</resheader>
<resheader name="reader">
<value>System.Resources.ResXResourceReader, System.Windows.Forms, Version=4.0.0.0, Culture=neutral, PublicKeyToken=b77a5c561934e089</value>
</resheader>
<resheader name="writer">
<value>System.Resources.ResXResourceWriter, System.Windows.Forms, Version=4.0.0.0, Culture=neutral, PublicKeyToken=b77a5c561934e089</value>
</resheader>
</root>
@@ -0,0 +1,309 @@
using System;
using System.ComponentModel;
namespace StandardScene.Charge
{
/// <summary>
/// 充电策略配置
/// </summary>
public class ChargeStrategyConfig
{
#region SOC
/// <summary>
/// 必充电量 (%)
/// </summary>
[Description("必充电量")]
[DisplayName("必充电量(%)")]
public double MustChargeSoc { get; set; }
/// <summary>
/// 空闲充电电量 (%)
/// </summary>
[Description("空闲充电电量")]
[DisplayName("空闲充电电量(%)")]
public double IdleChargeSoc { get; set; }
/// <summary>
/// 任务可用电量 (%)
/// </summary>
[Description("任务可用电量")]
[DisplayName("任务可用电量(%)")]
public double TaskAvailableSoc { get; set; }
/// <summary>
/// 满电电量 (%)
/// </summary>
[Description("满电电量")]
[DisplayName("满电电量(%)")]
public double FullChargeSoc { get; set; }
/// <summary>
/// 允许中断电量 (%)
/// </summary>
[Description("允许中断电量")]
[DisplayName("允许中断电量(%)")]
public double AllowInterruptSoc { get; set; }
#endregion
#region
/// <summary>
/// 空闲充电时间 (秒)
/// </summary>
[Description("空闲充电时间")]
[DisplayName("空闲充电时间(秒)")]
public double IdleChargeSeconds { get; set; }
/// <summary>
/// 空闲时间 (秒)
/// </summary>
[Description("空闲时间")]
[DisplayName("空闲时间(秒)")]
public double IdleSeconds { get; set; }
/// <summary>
/// 必充时间 (秒)
/// </summary>
[Description("必充时间")]
[DisplayName("必充时间(秒)")]
public double MustChargeSeconds { get; set; }
/// <summary>
/// 补电时间 (分钟)
/// </summary>
[Description("补电时间")]
[DisplayName("补电时间(分钟)")]
public double TopUpMinutes { get; set; }
#endregion
#region
/// <summary>
/// 允许空闲车充电的最小任务数
/// </summary>
[Description("允许空闲车充电的最小任务数")]
[DisplayName("最小任务数")]
public int MinAllowFreeCarToChargeTaskCnt { get; set; }
#endregion
#region
/// <summary>
/// 允许中断充电任务
/// </summary>
[Description("允许中断充电任务")]
[DisplayName("允许中断任务")]
public bool AllowInterruptTask { get; set; }
/// <summary>
/// 优先使用低电量车辆充电
/// </summary>
[Description("优先使用低电量车辆充电")]
[DisplayName("优先低电量充电")]
public bool UseLowerSocForCharge { get; set; }
/// <summary>
/// 启用充电错误检测
/// </summary>
[Description("启用充电错误检测")]
[DisplayName("错误检测")]
public bool EnableErrorChargeDetection { get; set; }
/// <summary>
/// 使用充电站点筛选
/// </summary>
[Description("使用充电站点筛选")]
[DisplayName("站点筛选")]
public bool UseChargeSiteFilter { get; set; }
#endregion
#region
public ChargeStrategyConfig()
{
// 使用默认值初始化
SetDefaults();
}
/// <summary>
/// 设置默认值
/// </summary>
private void SetDefaults()
{
// SOC 参数默认值
MustChargeSoc = 20;
IdleChargeSoc = 90;
TaskAvailableSoc = 60;
FullChargeSoc = 90;
AllowInterruptSoc = 45;
// 时间参数默认值
IdleChargeSeconds = 30;
IdleSeconds = 5;
MustChargeSeconds = 60;
TopUpMinutes = 5;
// 任务参数默认值
MinAllowFreeCarToChargeTaskCnt = 0;
// 开关参数默认值
AllowInterruptTask = false;
UseLowerSocForCharge = true;
EnableErrorChargeDetection = false;
UseChargeSiteFilter = false;
}
/// <summary>
/// 创建默认配置
/// </summary>
public static ChargeStrategyConfig CreateDefault()
{
return new ChargeStrategyConfig();
}
#endregion
#region
/// <summary>
/// 验证配置是否有效
/// </summary>
public bool Validate(out string errorMessage)
{
// 验证 SOC 范围
if (MustChargeSoc < 0 || MustChargeSoc > 100)
{
errorMessage = "必充电量必须在 0-100 之间";
return false;
}
if (IdleChargeSoc < 0 || IdleChargeSoc > 100)
{
errorMessage = "空闲充电电量必须在 0-100 之间";
return false;
}
if (TaskAvailableSoc < 0 || TaskAvailableSoc > 100)
{
errorMessage = "任务可用电量必须在 0-100 之间";
return false;
}
if (FullChargeSoc < 0 || FullChargeSoc > 100)
{
errorMessage = "满电电量必须在 0-100 之间";
return false;
}
if (AllowInterruptSoc < 0 || AllowInterruptSoc > 100)
{
errorMessage = "允许中断电量必须在 0-100 之间";
return false;
}
// 验证 SOC 逻辑关系
if (MustChargeSoc >= IdleChargeSoc)
{
errorMessage = "必充电量必须小于空闲充电电量";
return false;
}
if (TaskAvailableSoc <= MustChargeSoc)
{
errorMessage = "任务可用电量必须大于必充电量";
return false;
}
if (FullChargeSoc < IdleChargeSoc)
{
errorMessage = "满电电量必须大于等于空闲充电电量";
return false;
}
if (AllowInterruptSoc <= MustChargeSoc)
{
errorMessage = "允许中断电量必须大于必充电量";
return false;
}
// 验证时间参数
if (IdleChargeSeconds < 0)
{
errorMessage = "空闲充电时间不能为负数";
return false;
}
if (IdleSeconds < 0)
{
errorMessage = "空闲时间不能为负数";
return false;
}
if (MustChargeSeconds < 0)
{
errorMessage = "必充时间不能为负数";
return false;
}
if (TopUpMinutes < 0)
{
errorMessage = "补电时间不能为负数";
return false;
}
// 验证任务参数
if (MinAllowFreeCarToChargeTaskCnt < 0)
{
errorMessage = "最小任务数不能为负数";
return false;
}
errorMessage = string.Empty;
return true;
}
#endregion
#region
/// <summary>
/// 克隆配置
/// </summary>
public ChargeStrategyConfig Clone()
{
return new ChargeStrategyConfig
{
MustChargeSoc = this.MustChargeSoc,
IdleChargeSoc = this.IdleChargeSoc,
TaskAvailableSoc = this.TaskAvailableSoc,
FullChargeSoc = this.FullChargeSoc,
AllowInterruptSoc = this.AllowInterruptSoc,
IdleChargeSeconds = this.IdleChargeSeconds,
IdleSeconds = this.IdleSeconds,
MustChargeSeconds = this.MustChargeSeconds,
TopUpMinutes = this.TopUpMinutes,
MinAllowFreeCarToChargeTaskCnt = this.MinAllowFreeCarToChargeTaskCnt,
AllowInterruptTask = this.AllowInterruptTask,
UseLowerSocForCharge = this.UseLowerSocForCharge,
EnableErrorChargeDetection = this.EnableErrorChargeDetection,
UseChargeSiteFilter = this.UseChargeSiteFilter
};
}
/// <summary>
/// 转换为字符串
/// </summary>
public override string ToString()
{
return $"充电策略配置 [必充:{MustChargeSoc}%, 空闲充:{IdleChargeSoc}%, 任务可用:{TaskAvailableSoc}%]";
}
#endregion
}
}
@@ -0,0 +1,572 @@
namespace StandardScene.Charge
{
partial class ChargeStrategyConfigForm
{
private System.ComponentModel.IContainer components = null;
protected override void Dispose(bool disposing)
{
if (disposing && (components != null))
{
components.Dispose();
}
base.Dispose(disposing);
}
#region Windows Form Designer generated code
private void InitializeComponent()
{
// 创建所有控件实例
this.pnlMain = new System.Windows.Forms.Panel();
this.pnlBottom = new System.Windows.Forms.Panel();
this.grpSocParams = new System.Windows.Forms.GroupBox();
this.grpTimeParams = new System.Windows.Forms.GroupBox();
this.grpTaskParams = new System.Windows.Forms.GroupBox();
this.grpSwitchParams = new System.Windows.Forms.GroupBox();
// SOC 参数控件
this.lblMustChargeSoc = new System.Windows.Forms.Label();
this.numMustChargeSoc = new System.Windows.Forms.NumericUpDown();
this.lblIdleChargeSoc = new System.Windows.Forms.Label();
this.numIdleChargeSoc = new System.Windows.Forms.NumericUpDown();
this.lblTaskAvailableSoc = new System.Windows.Forms.Label();
this.numTaskAvailableSoc = new System.Windows.Forms.NumericUpDown();
this.lblFullChargeSoc = new System.Windows.Forms.Label();
this.numFullChargeSoc = new System.Windows.Forms.NumericUpDown();
this.lblAllowInterruptSoc = new System.Windows.Forms.Label();
this.numAllowInterruptSoc = new System.Windows.Forms.NumericUpDown();
// 时间参数控件
this.lblIdleChargeSeconds = new System.Windows.Forms.Label();
this.numIdleChargeSeconds = new System.Windows.Forms.NumericUpDown();
this.lblIdleSeconds = new System.Windows.Forms.Label();
this.numIdleSeconds = new System.Windows.Forms.NumericUpDown();
this.lblMustChargeSeconds = new System.Windows.Forms.Label();
this.numMustChargeSeconds = new System.Windows.Forms.NumericUpDown();
this.lblTopUpMinutes = new System.Windows.Forms.Label();
this.numTopUpMinutes = new System.Windows.Forms.NumericUpDown();
// 任务参数控件
this.lblMinAllowFreeCarToChargeTaskCnt = new System.Windows.Forms.Label();
this.numMinAllowFreeCarToChargeTaskCnt = new System.Windows.Forms.NumericUpDown();
// 开关参数控件
this.chkAllowInterruptTask = new System.Windows.Forms.CheckBox();
this.chkUseLowerSocForCharge = new System.Windows.Forms.CheckBox();
this.chkEnableErrorChargeDetection = new System.Windows.Forms.CheckBox();
this.chkUseChargeSiteFilter = new System.Windows.Forms.CheckBox();
// 底部控件
this.lblStatus = new System.Windows.Forms.Label();
this.btnSave = new System.Windows.Forms.Button();
this.btnApply = new System.Windows.Forms.Button();
this.btnRestoreDefaults = new System.Windows.Forms.Button();
this.btnCancel = new System.Windows.Forms.Button();
this.pnlMain.SuspendLayout();
this.grpSwitchParams.SuspendLayout();
this.grpTaskParams.SuspendLayout();
((System.ComponentModel.ISupportInitialize)(this.numMinAllowFreeCarToChargeTaskCnt)).BeginInit();
this.grpTimeParams.SuspendLayout();
((System.ComponentModel.ISupportInitialize)(this.numTopUpMinutes)).BeginInit();
((System.ComponentModel.ISupportInitialize)(this.numMustChargeSeconds)).BeginInit();
((System.ComponentModel.ISupportInitialize)(this.numIdleSeconds)).BeginInit();
((System.ComponentModel.ISupportInitialize)(this.numIdleChargeSeconds)).BeginInit();
this.grpSocParams.SuspendLayout();
((System.ComponentModel.ISupportInitialize)(this.numAllowInterruptSoc)).BeginInit();
((System.ComponentModel.ISupportInitialize)(this.numFullChargeSoc)).BeginInit();
((System.ComponentModel.ISupportInitialize)(this.numTaskAvailableSoc)).BeginInit();
((System.ComponentModel.ISupportInitialize)(this.numIdleChargeSoc)).BeginInit();
((System.ComponentModel.ISupportInitialize)(this.numMustChargeSoc)).BeginInit();
this.pnlBottom.SuspendLayout();
this.SuspendLayout();
//
// pnlMain
//
this.pnlMain.AutoScroll = true;
this.pnlMain.Controls.Add(this.grpSwitchParams);
this.pnlMain.Controls.Add(this.grpTaskParams);
this.pnlMain.Controls.Add(this.grpTimeParams);
this.pnlMain.Controls.Add(this.grpSocParams);
this.pnlMain.Dock = System.Windows.Forms.DockStyle.Fill;
this.pnlMain.Location = new System.Drawing.Point(0, 0);
this.pnlMain.Name = "pnlMain";
this.pnlMain.Padding = new System.Windows.Forms.Padding(10);
this.pnlMain.Size = new System.Drawing.Size(784, 631);
this.pnlMain.TabIndex = 0;
//
// grpSwitchParams
//
this.grpSwitchParams.Controls.Add(this.chkUseChargeSiteFilter);
this.grpSwitchParams.Controls.Add(this.chkEnableErrorChargeDetection);
this.grpSwitchParams.Controls.Add(this.chkUseLowerSocForCharge);
this.grpSwitchParams.Controls.Add(this.chkAllowInterruptTask);
this.grpSwitchParams.Dock = System.Windows.Forms.DockStyle.Top;
this.grpSwitchParams.Font = new System.Drawing.Font("微软雅黑", 9F, System.Drawing.FontStyle.Bold);
this.grpSwitchParams.Location = new System.Drawing.Point(10, 460);
this.grpSwitchParams.Name = "grpSwitchParams";
this.grpSwitchParams.Padding = new System.Windows.Forms.Padding(10);
this.grpSwitchParams.Size = new System.Drawing.Size(764, 150);
this.grpSwitchParams.TabIndex = 3;
this.grpSwitchParams.TabStop = false;
this.grpSwitchParams.Text = "开关参数";
//
// chkUseChargeSiteFilter
//
this.chkUseChargeSiteFilter.AutoSize = true;
this.chkUseChargeSiteFilter.Font = new System.Drawing.Font("微软雅黑", 9F);
this.chkUseChargeSiteFilter.Location = new System.Drawing.Point(400, 80);
this.chkUseChargeSiteFilter.Name = "chkUseChargeSiteFilter";
this.chkUseChargeSiteFilter.Size = new System.Drawing.Size(147, 24);
this.chkUseChargeSiteFilter.TabIndex = 3;
this.chkUseChargeSiteFilter.Text = "使用充电站点筛选";
this.chkUseChargeSiteFilter.UseVisualStyleBackColor = true;
//
// chkEnableErrorChargeDetection
//
this.chkEnableErrorChargeDetection.AutoSize = true;
this.chkEnableErrorChargeDetection.Font = new System.Drawing.Font("微软雅黑", 9F);
this.chkEnableErrorChargeDetection.Location = new System.Drawing.Point(30, 80);
this.chkEnableErrorChargeDetection.Name = "chkEnableErrorChargeDetection";
this.chkEnableErrorChargeDetection.Size = new System.Drawing.Size(147, 24);
this.chkEnableErrorChargeDetection.TabIndex = 2;
this.chkEnableErrorChargeDetection.Text = "启用充电错误检测";
this.chkEnableErrorChargeDetection.UseVisualStyleBackColor = true;
//
// chkUseLowerSocForCharge
//
this.chkUseLowerSocForCharge.AutoSize = true;
this.chkUseLowerSocForCharge.Font = new System.Drawing.Font("微软雅黑", 9F);
this.chkUseLowerSocForCharge.Location = new System.Drawing.Point(400, 40);
this.chkUseLowerSocForCharge.Name = "chkUseLowerSocForCharge";
this.chkUseLowerSocForCharge.Size = new System.Drawing.Size(195, 24);
this.chkUseLowerSocForCharge.TabIndex = 1;
this.chkUseLowerSocForCharge.Text = "优先使用低电量车辆充电";
this.chkUseLowerSocForCharge.UseVisualStyleBackColor = true;
//
// chkAllowInterruptTask
//
this.chkAllowInterruptTask.AutoSize = true;
this.chkAllowInterruptTask.Font = new System.Drawing.Font("微软雅黑", 9F);
this.chkAllowInterruptTask.Location = new System.Drawing.Point(30, 40);
this.chkAllowInterruptTask.Name = "chkAllowInterruptTask";
this.chkAllowInterruptTask.Size = new System.Drawing.Size(147, 24);
this.chkAllowInterruptTask.TabIndex = 0;
this.chkAllowInterruptTask.Text = "允许中断充电任务";
this.chkAllowInterruptTask.UseVisualStyleBackColor = true;
//
// grpTaskParams
//
this.grpTaskParams.Controls.Add(this.numMinAllowFreeCarToChargeTaskCnt);
this.grpTaskParams.Controls.Add(this.lblMinAllowFreeCarToChargeTaskCnt);
this.grpTaskParams.Dock = System.Windows.Forms.DockStyle.Top;
this.grpTaskParams.Font = new System.Drawing.Font("微软雅黑", 9F, System.Drawing.FontStyle.Bold);
this.grpTaskParams.Location = new System.Drawing.Point(10, 370);
this.grpTaskParams.Name = "grpTaskParams";
this.grpTaskParams.Padding = new System.Windows.Forms.Padding(10);
this.grpTaskParams.Size = new System.Drawing.Size(764, 90);
this.grpTaskParams.TabIndex = 2;
this.grpTaskParams.TabStop = false;
this.grpTaskParams.Text = "任务参数";
//
// numMinAllowFreeCarToChargeTaskCnt
//
this.numMinAllowFreeCarToChargeTaskCnt.Font = new System.Drawing.Font("微软雅黑", 9F);
this.numMinAllowFreeCarToChargeTaskCnt.Location = new System.Drawing.Point(250, 40);
this.numMinAllowFreeCarToChargeTaskCnt.Maximum = new decimal(new int[] {
100,
0,
0,
0});
this.numMinAllowFreeCarToChargeTaskCnt.Name = "numMinAllowFreeCarToChargeTaskCnt";
this.numMinAllowFreeCarToChargeTaskCnt.Size = new System.Drawing.Size(120, 27);
this.numMinAllowFreeCarToChargeTaskCnt.TabIndex = 1;
//
// lblMinAllowFreeCarToChargeTaskCnt
//
this.lblMinAllowFreeCarToChargeTaskCnt.AutoSize = true;
this.lblMinAllowFreeCarToChargeTaskCnt.Font = new System.Drawing.Font("微软雅黑", 9F);
this.lblMinAllowFreeCarToChargeTaskCnt.Location = new System.Drawing.Point(30, 42);
this.lblMinAllowFreeCarToChargeTaskCnt.Name = "lblMinAllowFreeCarToChargeTaskCnt";
this.lblMinAllowFreeCarToChargeTaskCnt.Size = new System.Drawing.Size(207, 20);
this.lblMinAllowFreeCarToChargeTaskCnt.TabIndex = 0;
this.lblMinAllowFreeCarToChargeTaskCnt.Text = "允许空闲车充电的最小任务数:";
//
// grpTimeParams
//
this.grpTimeParams.Controls.Add(this.numTopUpMinutes);
this.grpTimeParams.Controls.Add(this.lblTopUpMinutes);
this.grpTimeParams.Controls.Add(this.numMustChargeSeconds);
this.grpTimeParams.Controls.Add(this.lblMustChargeSeconds);
this.grpTimeParams.Controls.Add(this.numIdleSeconds);
this.grpTimeParams.Controls.Add(this.lblIdleSeconds);
this.grpTimeParams.Controls.Add(this.numIdleChargeSeconds);
this.grpTimeParams.Controls.Add(this.lblIdleChargeSeconds);
this.grpTimeParams.Dock = System.Windows.Forms.DockStyle.Top;
this.grpTimeParams.Font = new System.Drawing.Font("微软雅黑", 9F, System.Drawing.FontStyle.Bold);
this.grpTimeParams.Location = new System.Drawing.Point(10, 210);
this.grpTimeParams.Name = "grpTimeParams";
this.grpTimeParams.Padding = new System.Windows.Forms.Padding(10);
this.grpTimeParams.Size = new System.Drawing.Size(764, 160);
this.grpTimeParams.TabIndex = 1;
this.grpTimeParams.TabStop = false;
this.grpTimeParams.Text = "时间参数";
//
// numTopUpMinutes
//
this.numTopUpMinutes.DecimalPlaces = 1;
this.numTopUpMinutes.Font = new System.Drawing.Font("微软雅黑", 9F);
this.numTopUpMinutes.Location = new System.Drawing.Point(580, 100);
this.numTopUpMinutes.Maximum = new decimal(new int[] {
1000,
0,
0,
0});
this.numTopUpMinutes.Name = "numTopUpMinutes";
this.numTopUpMinutes.Size = new System.Drawing.Size(120, 27);
this.numTopUpMinutes.TabIndex = 7;
//
// lblTopUpMinutes
//
this.lblTopUpMinutes.AutoSize = true;
this.lblTopUpMinutes.Font = new System.Drawing.Font("微软雅黑", 9F);
this.lblTopUpMinutes.Location = new System.Drawing.Point(400, 102);
this.lblTopUpMinutes.Name = "lblTopUpMinutes";
this.lblTopUpMinutes.Size = new System.Drawing.Size(159, 20);
this.lblTopUpMinutes.TabIndex = 6;
this.lblTopUpMinutes.Text = "补电时间 (分钟,min):";
//
// numMustChargeSeconds
//
this.numMustChargeSeconds.DecimalPlaces = 1;
this.numMustChargeSeconds.Font = new System.Drawing.Font("微软雅黑", 9F);
this.numMustChargeSeconds.Location = new System.Drawing.Point(250, 100);
this.numMustChargeSeconds.Maximum = new decimal(new int[] {
10000,
0,
0,
0});
this.numMustChargeSeconds.Name = "numMustChargeSeconds";
this.numMustChargeSeconds.Size = new System.Drawing.Size(120, 27);
this.numMustChargeSeconds.TabIndex = 5;
//
// lblMustChargeSeconds
//
this.lblMustChargeSeconds.AutoSize = true;
this.lblMustChargeSeconds.Font = new System.Drawing.Font("微软雅黑", 9F);
this.lblMustChargeSeconds.Location = new System.Drawing.Point(30, 102);
this.lblMustChargeSeconds.Name = "lblMustChargeSeconds";
this.lblMustChargeSeconds.Size = new System.Drawing.Size(147, 20);
this.lblMustChargeSeconds.TabIndex = 4;
this.lblMustChargeSeconds.Text = "必充时间 (秒,sec):";
//
// numIdleSeconds
//
this.numIdleSeconds.DecimalPlaces = 1;
this.numIdleSeconds.Font = new System.Drawing.Font("微软雅黑", 9F);
this.numIdleSeconds.Location = new System.Drawing.Point(580, 40);
this.numIdleSeconds.Maximum = new decimal(new int[] {
10000,
0,
0,
0});
this.numIdleSeconds.Name = "numIdleSeconds";
this.numIdleSeconds.Size = new System.Drawing.Size(120, 27);
this.numIdleSeconds.TabIndex = 3;
//
// lblIdleSeconds
//
this.lblIdleSeconds.AutoSize = true;
this.lblIdleSeconds.Font = new System.Drawing.Font("微软雅黑", 9F);
this.lblIdleSeconds.Location = new System.Drawing.Point(400, 42);
this.lblIdleSeconds.Name = "lblIdleSeconds";
this.lblIdleSeconds.Size = new System.Drawing.Size(147, 20);
this.lblIdleSeconds.TabIndex = 2;
this.lblIdleSeconds.Text = "空闲时间 (秒,sec):";
//
// numIdleChargeSeconds
//
this.numIdleChargeSeconds.DecimalPlaces = 1;
this.numIdleChargeSeconds.Font = new System.Drawing.Font("微软雅黑", 9F);
this.numIdleChargeSeconds.Location = new System.Drawing.Point(250, 40);
this.numIdleChargeSeconds.Maximum = new decimal(new int[] {
10000,
0,
0,
0});
this.numIdleChargeSeconds.Name = "numIdleChargeSeconds";
this.numIdleChargeSeconds.Size = new System.Drawing.Size(120, 27);
this.numIdleChargeSeconds.TabIndex = 1;
//
// lblIdleChargeSeconds
//
this.lblIdleChargeSeconds.AutoSize = true;
this.lblIdleChargeSeconds.Font = new System.Drawing.Font("微软雅黑", 9F);
this.lblIdleChargeSeconds.Location = new System.Drawing.Point(30, 42);
this.lblIdleChargeSeconds.Name = "lblIdleChargeSeconds";
this.lblIdleChargeSeconds.Size = new System.Drawing.Size(171, 20);
this.lblIdleChargeSeconds.TabIndex = 0;
this.lblIdleChargeSeconds.Text = "空闲充电时间 (秒,sec):";
//
// grpSocParams
//
this.grpSocParams.Controls.Add(this.numAllowInterruptSoc);
this.grpSocParams.Controls.Add(this.lblAllowInterruptSoc);
this.grpSocParams.Controls.Add(this.numFullChargeSoc);
this.grpSocParams.Controls.Add(this.lblFullChargeSoc);
this.grpSocParams.Controls.Add(this.numTaskAvailableSoc);
this.grpSocParams.Controls.Add(this.lblTaskAvailableSoc);
this.grpSocParams.Controls.Add(this.numIdleChargeSoc);
this.grpSocParams.Controls.Add(this.lblIdleChargeSoc);
this.grpSocParams.Controls.Add(this.numMustChargeSoc);
this.grpSocParams.Controls.Add(this.lblMustChargeSoc);
this.grpSocParams.Dock = System.Windows.Forms.DockStyle.Top;
this.grpSocParams.Font = new System.Drawing.Font("微软雅黑", 9F, System.Drawing.FontStyle.Bold);
this.grpSocParams.Location = new System.Drawing.Point(10, 10);
this.grpSocParams.Name = "grpSocParams";
this.grpSocParams.Padding = new System.Windows.Forms.Padding(10);
this.grpSocParams.Size = new System.Drawing.Size(764, 200);
this.grpSocParams.TabIndex = 0;
this.grpSocParams.TabStop = false;
this.grpSocParams.Text = "SOC 参数 (电量百分比)";
//
// numAllowInterruptSoc
//
this.numAllowInterruptSoc.DecimalPlaces = 1;
this.numAllowInterruptSoc.Font = new System.Drawing.Font("微软雅黑", 9F);
this.numAllowInterruptSoc.Location = new System.Drawing.Point(250, 150);
this.numAllowInterruptSoc.Name = "numAllowInterruptSoc";
this.numAllowInterruptSoc.Size = new System.Drawing.Size(120, 27);
this.numAllowInterruptSoc.TabIndex = 9;
//
// lblAllowInterruptSoc
//
this.lblAllowInterruptSoc.AutoSize = true;
this.lblAllowInterruptSoc.Font = new System.Drawing.Font("微软雅黑", 9F);
this.lblAllowInterruptSoc.Location = new System.Drawing.Point(30, 152);
this.lblAllowInterruptSoc.Name = "lblAllowInterruptSoc";
this.lblAllowInterruptSoc.Size = new System.Drawing.Size(135, 20);
this.lblAllowInterruptSoc.TabIndex = 8;
this.lblAllowInterruptSoc.Text = "允许中断电量 (%):";
//
// numFullChargeSoc
//
this.numFullChargeSoc.DecimalPlaces = 1;
this.numFullChargeSoc.Font = new System.Drawing.Font("微软雅黑", 9F);
this.numFullChargeSoc.Location = new System.Drawing.Point(580, 95);
this.numFullChargeSoc.Name = "numFullChargeSoc";
this.numFullChargeSoc.Size = new System.Drawing.Size(120, 27);
this.numFullChargeSoc.TabIndex = 7;
//
// lblFullChargeSoc
//
this.lblFullChargeSoc.AutoSize = true;
this.lblFullChargeSoc.Font = new System.Drawing.Font("微软雅黑", 9F);
this.lblFullChargeSoc.Location = new System.Drawing.Point(400, 97);
this.lblFullChargeSoc.Name = "lblFullChargeSoc";
this.lblFullChargeSoc.Size = new System.Drawing.Size(99, 20);
this.lblFullChargeSoc.TabIndex = 6;
this.lblFullChargeSoc.Text = "满电电量 (%):";
//
// numTaskAvailableSoc
//
this.numTaskAvailableSoc.DecimalPlaces = 1;
this.numTaskAvailableSoc.Font = new System.Drawing.Font("微软雅黑", 9F);
this.numTaskAvailableSoc.Location = new System.Drawing.Point(250, 95);
this.numTaskAvailableSoc.Name = "numTaskAvailableSoc";
this.numTaskAvailableSoc.Size = new System.Drawing.Size(120, 27);
this.numTaskAvailableSoc.TabIndex = 5;
//
// lblTaskAvailableSoc
//
this.lblTaskAvailableSoc.AutoSize = true;
this.lblTaskAvailableSoc.Font = new System.Drawing.Font("微软雅黑", 9F);
this.lblTaskAvailableSoc.Location = new System.Drawing.Point(30, 97);
this.lblTaskAvailableSoc.Name = "lblTaskAvailableSoc";
this.lblTaskAvailableSoc.Size = new System.Drawing.Size(135, 20);
this.lblTaskAvailableSoc.TabIndex = 4;
this.lblTaskAvailableSoc.Text = "任务可用电量 (%):";
//
// numIdleChargeSoc
//
this.numIdleChargeSoc.DecimalPlaces = 1;
this.numIdleChargeSoc.Font = new System.Drawing.Font("微软雅黑", 9F);
this.numIdleChargeSoc.Location = new System.Drawing.Point(580, 40);
this.numIdleChargeSoc.Name = "numIdleChargeSoc";
this.numIdleChargeSoc.Size = new System.Drawing.Size(120, 27);
this.numIdleChargeSoc.TabIndex = 3;
//
// lblIdleChargeSoc
//
this.lblIdleChargeSoc.AutoSize = true;
this.lblIdleChargeSoc.Font = new System.Drawing.Font("微软雅黑", 9F);
this.lblIdleChargeSoc.Location = new System.Drawing.Point(400, 42);
this.lblIdleChargeSoc.Name = "lblIdleChargeSoc";
this.lblIdleChargeSoc.Size = new System.Drawing.Size(135, 20);
this.lblIdleChargeSoc.TabIndex = 2;
this.lblIdleChargeSoc.Text = "空闲充电电量 (%):";
//
// numMustChargeSoc
//
this.numMustChargeSoc.DecimalPlaces = 1;
this.numMustChargeSoc.Font = new System.Drawing.Font("微软雅黑", 9F);
this.numMustChargeSoc.Location = new System.Drawing.Point(250, 40);
this.numMustChargeSoc.Name = "numMustChargeSoc";
this.numMustChargeSoc.Size = new System.Drawing.Size(120, 27);
this.numMustChargeSoc.TabIndex = 1;
//
// lblMustChargeSoc
//
this.lblMustChargeSoc.AutoSize = true;
this.lblMustChargeSoc.Font = new System.Drawing.Font("微软雅黑", 9F);
this.lblMustChargeSoc.Location = new System.Drawing.Point(30, 42);
this.lblMustChargeSoc.Name = "lblMustChargeSoc";
this.lblMustChargeSoc.Size = new System.Drawing.Size(99, 20);
this.lblMustChargeSoc.TabIndex = 0;
this.lblMustChargeSoc.Text = "必充电量 (%):";
//
// pnlBottom
//
this.pnlBottom.BackColor = System.Drawing.Color.FromArgb(((int)(((byte)(250)))), ((int)(((byte)(250)))), ((int)(((byte)(250)))));
this.pnlBottom.Controls.Add(this.lblStatus);
this.pnlBottom.Controls.Add(this.btnApply);
this.pnlBottom.Controls.Add(this.btnRestoreDefaults);
this.pnlBottom.Controls.Add(this.btnCancel);
this.pnlBottom.Controls.Add(this.btnSave);
this.pnlBottom.Dock = System.Windows.Forms.DockStyle.Bottom;
this.pnlBottom.Location = new System.Drawing.Point(0, 631);
this.pnlBottom.Name = "pnlBottom";
this.pnlBottom.Size = new System.Drawing.Size(784, 70);
this.pnlBottom.TabIndex = 1;
//
// lblStatus
//
this.lblStatus.AutoSize = true;
this.lblStatus.Font = new System.Drawing.Font("微软雅黑", 9F);
this.lblStatus.Location = new System.Drawing.Point(20, 25);
this.lblStatus.Name = "lblStatus";
this.lblStatus.Size = new System.Drawing.Size(54, 20);
this.lblStatus.TabIndex = 4;
this.lblStatus.Text = "就绪...";
//
// btnApply
//
this.btnApply.Anchor = ((System.Windows.Forms.AnchorStyles)((System.Windows.Forms.AnchorStyles.Top | System.Windows.Forms.AnchorStyles.Right)));
this.btnApply.Font = new System.Drawing.Font("微软雅黑", 9F);
this.btnApply.Location = new System.Drawing.Point(564, 18);
this.btnApply.Name = "btnApply";
this.btnApply.Size = new System.Drawing.Size(100, 35);
this.btnApply.TabIndex = 3;
this.btnApply.Text = "应用";
this.btnApply.UseVisualStyleBackColor = true;
this.btnApply.Click += new System.EventHandler(this.btnApply_Click);
//
// btnRestoreDefaults
//
this.btnRestoreDefaults.Anchor = ((System.Windows.Forms.AnchorStyles)((System.Windows.Forms.AnchorStyles.Top | System.Windows.Forms.AnchorStyles.Right)));
this.btnRestoreDefaults.Font = new System.Drawing.Font("微软雅黑", 9F);
this.btnRestoreDefaults.Location = new System.Drawing.Point(344, 18);
this.btnRestoreDefaults.Name = "btnRestoreDefaults";
this.btnRestoreDefaults.Size = new System.Drawing.Size(100, 35);
this.btnRestoreDefaults.TabIndex = 2;
this.btnRestoreDefaults.Text = "恢复默认";
this.btnRestoreDefaults.UseVisualStyleBackColor = true;
this.btnRestoreDefaults.Click += new System.EventHandler(this.btnRestoreDefaults_Click);
//
// btnCancel
//
this.btnCancel.Anchor = ((System.Windows.Forms.AnchorStyles)((System.Windows.Forms.AnchorStyles.Top | System.Windows.Forms.AnchorStyles.Right)));
this.btnCancel.Font = new System.Drawing.Font("微软雅黑", 9F);
this.btnCancel.Location = new System.Drawing.Point(674, 18);
this.btnCancel.Name = "btnCancel";
this.btnCancel.Size = new System.Drawing.Size(100, 35);
this.btnCancel.TabIndex = 1;
this.btnCancel.Text = "取消";
this.btnCancel.UseVisualStyleBackColor = true;
this.btnCancel.Click += new System.EventHandler(this.btnCancel_Click);
//
// btnSave
//
this.btnSave.Anchor = ((System.Windows.Forms.AnchorStyles)((System.Windows.Forms.AnchorStyles.Top | System.Windows.Forms.AnchorStyles.Right)));
this.btnSave.Font = new System.Drawing.Font("微软雅黑", 9F);
this.btnSave.Location = new System.Drawing.Point(454, 18);
this.btnSave.Name = "btnSave";
this.btnSave.Size = new System.Drawing.Size(100, 35);
this.btnSave.TabIndex = 0;
this.btnSave.Text = "保存";
this.btnSave.UseVisualStyleBackColor = true;
this.btnSave.Click += new System.EventHandler(this.btnSave_Click);
//
// ChargeStrategyConfigForm
//
this.AutoScaleDimensions = new System.Drawing.SizeF(8F, 15F);
this.AutoScaleMode = System.Windows.Forms.AutoScaleMode.Font;
this.ClientSize = new System.Drawing.Size(784, 701);
this.Controls.Add(this.pnlMain);
this.Controls.Add(this.pnlBottom);
this.Name = "ChargeStrategyConfigForm";
this.Text = "充电策略配置";
this.pnlMain.ResumeLayout(false);
this.grpSwitchParams.ResumeLayout(false);
this.grpSwitchParams.PerformLayout();
this.grpTaskParams.ResumeLayout(false);
this.grpTaskParams.PerformLayout();
((System.ComponentModel.ISupportInitialize)(this.numMinAllowFreeCarToChargeTaskCnt)).EndInit();
this.grpTimeParams.ResumeLayout(false);
this.grpTimeParams.PerformLayout();
((System.ComponentModel.ISupportInitialize)(this.numTopUpMinutes)).EndInit();
((System.ComponentModel.ISupportInitialize)(this.numMustChargeSeconds)).EndInit();
((System.ComponentModel.ISupportInitialize)(this.numIdleSeconds)).EndInit();
((System.ComponentModel.ISupportInitialize)(this.numIdleChargeSeconds)).EndInit();
this.grpSocParams.ResumeLayout(false);
this.grpSocParams.PerformLayout();
((System.ComponentModel.ISupportInitialize)(this.numAllowInterruptSoc)).EndInit();
((System.ComponentModel.ISupportInitialize)(this.numFullChargeSoc)).EndInit();
((System.ComponentModel.ISupportInitialize)(this.numTaskAvailableSoc)).EndInit();
((System.ComponentModel.ISupportInitialize)(this.numIdleChargeSoc)).EndInit();
((System.ComponentModel.ISupportInitialize)(this.numMustChargeSoc)).EndInit();
this.pnlBottom.ResumeLayout(false);
this.pnlBottom.PerformLayout();
this.ResumeLayout(false);
}
#endregion
private System.Windows.Forms.Panel pnlMain;
private System.Windows.Forms.GroupBox grpSocParams;
private System.Windows.Forms.NumericUpDown numMustChargeSoc;
private System.Windows.Forms.Label lblMustChargeSoc;
private System.Windows.Forms.NumericUpDown numIdleChargeSoc;
private System.Windows.Forms.Label lblIdleChargeSoc;
private System.Windows.Forms.NumericUpDown numTaskAvailableSoc;
private System.Windows.Forms.Label lblTaskAvailableSoc;
private System.Windows.Forms.NumericUpDown numFullChargeSoc;
private System.Windows.Forms.Label lblFullChargeSoc;
private System.Windows.Forms.NumericUpDown numAllowInterruptSoc;
private System.Windows.Forms.Label lblAllowInterruptSoc;
private System.Windows.Forms.GroupBox grpTimeParams;
private System.Windows.Forms.NumericUpDown numIdleChargeSeconds;
private System.Windows.Forms.Label lblIdleChargeSeconds;
private System.Windows.Forms.NumericUpDown numIdleSeconds;
private System.Windows.Forms.Label lblIdleSeconds;
private System.Windows.Forms.NumericUpDown numMustChargeSeconds;
private System.Windows.Forms.Label lblMustChargeSeconds;
private System.Windows.Forms.NumericUpDown numTopUpMinutes;
private System.Windows.Forms.Label lblTopUpMinutes;
private System.Windows.Forms.GroupBox grpTaskParams;
private System.Windows.Forms.NumericUpDown numMinAllowFreeCarToChargeTaskCnt;
private System.Windows.Forms.Label lblMinAllowFreeCarToChargeTaskCnt;
private System.Windows.Forms.GroupBox grpSwitchParams;
private System.Windows.Forms.CheckBox chkAllowInterruptTask;
private System.Windows.Forms.CheckBox chkUseLowerSocForCharge;
private System.Windows.Forms.CheckBox chkEnableErrorChargeDetection;
private System.Windows.Forms.CheckBox chkUseChargeSiteFilter;
private System.Windows.Forms.Panel pnlBottom;
private System.Windows.Forms.Button btnSave;
private System.Windows.Forms.Button btnCancel;
private System.Windows.Forms.Button btnRestoreDefaults;
private System.Windows.Forms.Button btnApply;
private System.Windows.Forms.Label lblStatus;
}
}
@@ -0,0 +1,215 @@
using System;
using System.Drawing;
using System.Windows.Forms;
namespace StandardScene.Charge
{
/// <summary>
/// 充电策略配置窗体
/// </summary>
public partial class ChargeStrategyConfigForm : Form
{
private ChargeStrategyConfig config;
private ChargeStrategyConfigService configService;
public ChargeStrategyConfigForm()
{
InitializeComponent();
configService = ChargeStrategyConfigService.Instance;
InitializeForm();
}
private void InitializeForm()
{
this.Text = "充电策略配置";
this.Size = new Size(800, 700);
this.StartPosition = FormStartPosition.CenterScreen;
this.MinimumSize = new Size(700, 600);
this.FormBorderStyle = FormBorderStyle.FixedDialog;
this.MaximizeBox = false;
// 加载配置
LoadConfig();
}
/// <summary>
/// 加载配置到界面
/// </summary>
private void LoadConfig(bool isDef = false)
{
try
{
if (!isDef)
{
config = configService.LoadConfig();
}
// SOC 相关参数
numMustChargeSoc.Value = (decimal)config.MustChargeSoc;
numIdleChargeSoc.Value = (decimal)config.IdleChargeSoc;
numTaskAvailableSoc.Value = (decimal)config.TaskAvailableSoc;
numFullChargeSoc.Value = (decimal)config.FullChargeSoc;
numAllowInterruptSoc.Value = (decimal)config.AllowInterruptSoc;
// 时间相关参数
numIdleChargeSeconds.Value = (decimal)config.IdleChargeSeconds;
numIdleSeconds.Value = (decimal)config.IdleSeconds;
numMustChargeSeconds.Value = (decimal)config.MustChargeSeconds;
numTopUpMinutes.Value = (decimal)config.TopUpMinutes;
// 任务相关参数
numMinAllowFreeCarToChargeTaskCnt.Value = config.MinAllowFreeCarToChargeTaskCnt;
// 开关参数
chkAllowInterruptTask.Checked = config.AllowInterruptTask;
chkUseLowerSocForCharge.Checked = config.UseLowerSocForCharge;
chkEnableErrorChargeDetection.Checked = config.EnableErrorChargeDetection;
chkUseChargeSiteFilter.Checked = config.UseChargeSiteFilter;
lblStatus.Text = "配置加载成功";
lblStatus.ForeColor = Color.Green;
}
catch (Exception ex)
{
MessageBox.Show($"加载配置失败: {ex.Message}", "错误",
MessageBoxButtons.OK, MessageBoxIcon.Error);
lblStatus.Text = "配置加载失败";
lblStatus.ForeColor = Color.Red;
}
}
/// <summary>
/// 从界面保存配置
/// </summary>
private void SaveConfig()
{
try
{
// SOC 相关参数
config.MustChargeSoc = (double)numMustChargeSoc.Value;
config.IdleChargeSoc = (double)numIdleChargeSoc.Value;
config.TaskAvailableSoc = (double)numTaskAvailableSoc.Value;
config.FullChargeSoc = (double)numFullChargeSoc.Value;
config.AllowInterruptSoc = (double)numAllowInterruptSoc.Value;
// 时间相关参数
config.IdleChargeSeconds = (double)numIdleChargeSeconds.Value;
config.IdleSeconds = (double)numIdleSeconds.Value;
config.MustChargeSeconds = (double)numMustChargeSeconds.Value;
config.TopUpMinutes = (double)numTopUpMinutes.Value;
// 任务相关参数
config.MinAllowFreeCarToChargeTaskCnt = (int)numMinAllowFreeCarToChargeTaskCnt.Value;
// 开关参数
config.AllowInterruptTask = chkAllowInterruptTask.Checked;
config.UseLowerSocForCharge = chkUseLowerSocForCharge.Checked;
config.EnableErrorChargeDetection = chkEnableErrorChargeDetection.Checked;
config.UseChargeSiteFilter = chkUseChargeSiteFilter.Checked;
// 保存到文件
configService.SaveConfig(config);
lblStatus.Text = "配置保存成功";
lblStatus.ForeColor = Color.Green;
MessageBox.Show("充电策略配置保存成功!", "成功",
MessageBoxButtons.OK, MessageBoxIcon.Information);
}
catch (Exception ex)
{
MessageBox.Show($"保存配置失败: {ex.Message}", "错误",
MessageBoxButtons.OK, MessageBoxIcon.Error);
lblStatus.Text = "配置保存失败";
lblStatus.ForeColor = Color.Red;
}
}
/// <summary>
/// 恢复默认配置
/// </summary>
private void RestoreDefaults()
{
var result = MessageBox.Show(
"确定要恢复默认配置吗?当前配置将被覆盖。",
"确认恢复",
MessageBoxButtons.YesNo,
MessageBoxIcon.Question);
if (result == DialogResult.Yes)
{
config = ChargeStrategyConfig.CreateDefault();
LoadConfig(true);
lblStatus.Text = "已恢复默认配置(未保存)";
lblStatus.ForeColor = Color.Blue;
}
}
/// <summary>
/// 验证配置参数
/// </summary>
private bool ValidateConfig()
{
// 验证 SOC 范围
if (numMustChargeSoc.Value >= numIdleChargeSoc.Value)
{
MessageBox.Show("必充电量必须小于空闲充电电量", "验证失败",
MessageBoxButtons.OK, MessageBoxIcon.Warning);
return false;
}
if (numTaskAvailableSoc.Value <= numMustChargeSoc.Value)
{
MessageBox.Show("任务可用电量必须大于必充电量", "验证失败",
MessageBoxButtons.OK, MessageBoxIcon.Warning);
return false;
}
if (numFullChargeSoc.Value < numIdleChargeSoc.Value)
{
MessageBox.Show("满电电量必须大于等于空闲充电电量", "验证失败",
MessageBoxButtons.OK, MessageBoxIcon.Warning);
return false;
}
if (numAllowInterruptSoc.Value <= numMustChargeSoc.Value)
{
MessageBox.Show("允许中断电量必须大于必充电量", "验证失败",
MessageBoxButtons.OK, MessageBoxIcon.Warning);
return false;
}
return true;
}
// ==================== 事件处理 ====================
private void btnSave_Click(object sender, EventArgs e)
{
if (ValidateConfig())
{
SaveConfig();
}
}
private void btnCancel_Click(object sender, EventArgs e)
{
this.Close();
}
private void btnRestoreDefaults_Click(object sender, EventArgs e)
{
RestoreDefaults();
}
private void btnApply_Click(object sender, EventArgs e)
{
if (ValidateConfig())
{
SaveConfig();
}
}
}
}
@@ -0,0 +1,203 @@
using System;
using System.IO;
using Newtonsoft.Json;
namespace StandardScene.Charge
{
/// <summary>
/// 充电策略配置服务(单例模式)
/// </summary>
public class ChargeStrategyConfigService
{
private static ChargeStrategyConfigService _instance;
private static readonly object _lock = new object();
private readonly string configFilePath;
private const string ConfigFileName = "ChargeStrategyConfig.json";
/// <summary>
/// 获取单例实例
/// </summary>
public static ChargeStrategyConfigService Instance
{
get
{
if (_instance == null)
{
lock (_lock)
{
if (_instance == null)
{
_instance = new ChargeStrategyConfigService();
}
}
}
return _instance;
}
}
private ChargeStrategyConfigService()
{
// 配置文件保存在应用程序目录下的 Config 文件夹
string configDir = Path.Combine(AppDomain.CurrentDomain.BaseDirectory, "Config");
// 确保目录存在
if (!Directory.Exists(configDir))
{
Directory.CreateDirectory(configDir);
}
configFilePath = Path.Combine(configDir, ConfigFileName);
}
/// <summary>
/// 加载配置
/// </summary>
public ChargeStrategyConfig LoadConfig()
{
try
{
if (File.Exists(configFilePath))
{
string json = File.ReadAllText(configFilePath);
var config = JsonConvert.DeserializeObject<ChargeStrategyConfig>(json);
// 验证配置
if (config.Validate(out string errorMessage))
{
return config;
}
else
{
// 配置无效,返回默认配置
System.Diagnostics.Debug.WriteLine($"配置验证失败: {errorMessage},使用默认配置");
return ChargeStrategyConfig.CreateDefault();
}
}
else
{
// 文件不存在,创建默认配置并保存
var defaultConfig = ChargeStrategyConfig.CreateDefault();
SaveConfig(defaultConfig);
return defaultConfig;
}
}
catch (Exception ex)
{
System.Diagnostics.Debug.WriteLine($"加载配置失败: {ex.Message}");
// 加载失败,返回默认配置
return ChargeStrategyConfig.CreateDefault();
}
}
/// <summary>
/// 保存配置
/// </summary>
public void SaveConfig(ChargeStrategyConfig config)
{
try
{
// 验证配置
if (!config.Validate(out string errorMessage))
{
throw new InvalidOperationException($"配置验证失败: {errorMessage}");
}
// 序列化为 JSON
string json = JsonConvert.SerializeObject(config, Formatting.Indented);
// 保存到文件
File.WriteAllText(configFilePath, json);
System.Diagnostics.Debug.WriteLine($"配置保存成功: {configFilePath}");
}
catch (Exception ex)
{
System.Diagnostics.Debug.WriteLine($"保存配置失败: {ex.Message}");
throw new Exception($"保存配置失败: {ex.Message}", ex);
}
}
/// <summary>
/// 获取配置文件路径
/// </summary>
public string GetConfigFilePath()
{
return configFilePath;
}
/// <summary>
/// 检查配置文件是否存在
/// </summary>
public bool ConfigFileExists()
{
return File.Exists(configFilePath);
}
/// <summary>
/// 删除配置文件
/// </summary>
public void DeleteConfig()
{
try
{
if (File.Exists(configFilePath))
{
File.Delete(configFilePath);
System.Diagnostics.Debug.WriteLine($"配置文件已删除: {configFilePath}");
}
}
catch (Exception ex)
{
System.Diagnostics.Debug.WriteLine($"删除配置文件失败: {ex.Message}");
throw new Exception($"删除配置文件失败: {ex.Message}", ex);
}
}
/// <summary>
/// 导出配置到指定路径
/// </summary>
public void ExportConfig(string exportPath, ChargeStrategyConfig config)
{
try
{
string json = JsonConvert.SerializeObject(config, Formatting.Indented);
File.WriteAllText(exportPath, json);
System.Diagnostics.Debug.WriteLine($"配置导出成功: {exportPath}");
}
catch (Exception ex)
{
throw new Exception($"导出配置失败: {ex.Message}", ex);
}
}
/// <summary>
/// 从指定路径导入配置
/// </summary>
public ChargeStrategyConfig ImportConfig(string importPath)
{
try
{
if (!File.Exists(importPath))
{
throw new FileNotFoundException($"配置文件不存在: {importPath}");
}
string json = File.ReadAllText(importPath);
var config = JsonConvert.DeserializeObject<ChargeStrategyConfig>(json);
// 验证配置
if (!config.Validate(out string errorMessage))
{
throw new InvalidOperationException($"配置验证失败: {errorMessage}");
}
return config;
}
catch (Exception ex)
{
throw new Exception($"导入配置失败: {ex.Message}", ex);
}
}
}
}
@@ -0,0 +1,62 @@
using System;
using System.Collections.Generic;
using System.Linq;
using System.Net.Sockets;
using System.Text;
using System.Threading;
using System.Threading.Tasks;
using SimpleLite;
using SimpleCore;
using SimpleCore.Library;
using StandardScene.ChargeStationType;
namespace StandardScene.Charge
{
/// <summary>
/// 充电桩udp监听
/// </summary>
public class ChargeUdpService
{
public Thread ListenerThread;
public ChargeUdpService()
{
ListenerThread = new Thread(ListenerProcess);
ListenerThread.Start();
}
private static async void ListenerProcess()
{
var messageService = CommunicationMessageService.Instance;
using (UdpClient udpListener = new UdpClient(40001))
{
Diagnosis.Log($"Listening for UDP messages on port {40001}");
while (true)
{
try
{
var result = await udpListener.ReceiveAsync();
var remoteEndPoint = result.RemoteEndPoint;
var message = result.Buffer;
messageService.AddReceiveMessage(remoteEndPoint.Address.ToString(), 40001, BitConverter.ToString(message).Replace("-", " "), "FRLDShort");
Diagnosis.Log($"ChargeStation ADD:[{BitConverter.ToString(message).Replace("-", " ")}]","UDP返回报文信息",true);
var chargeMission = SimpleProject.proj.Missions.OfType<StandardChargeMission>().FirstOrDefault();
if (chargeMission == null) continue;
var chargeStation =
chargeMission.ChargeStations.FirstOrDefault(c =>
c.Value.Ip == remoteEndPoint.Address.ToString()).Value;
if (chargeStation == null) continue;
if (message.Length > 28)
chargeStation.IsSafe = message[28] == 1;
chargeStation.OnUdpMessage(message);
}
catch (Exception e)
{
// 单帧异常(含越界/半包)不得中断 UDP 监听线程
Diagnosis.Log($"充电UDP接收处理异常: {e.Message}", "UDP", true);
}
}
}
}
}
}
@@ -0,0 +1,89 @@
using System;
namespace StandardScene.Charge
{
/// <summary>
/// 通讯报文数据模型
/// </summary>
public class CommunicationMessage
{
/// <summary>
/// 报文ID(自动生成)
/// </summary>
public string MessageId { get; set; }
/// <summary>
/// 时间戳
/// </summary>
public DateTime Timestamp { get; set; }
/// <summary>
/// 方向(发送/接收)
/// </summary>
public MessageDirection Direction { get; set; }
/// <summary>
/// IP地址
/// </summary>
public string IpAddress { get; set; }
/// <summary>
/// 端口号
/// </summary>
public int Port { get; set; }
/// <summary>
/// 原始报文数据(十六进制字符串)
/// </summary>
public string RawData { get; set; }
/// <summary>
/// 报文长度(字节)
/// </summary>
public int Length { get; set; }
/// <summary>
/// 协议类型
/// </summary>
public string Type { get; set; }
/// <summary>
/// 关联的充电桩ID(可选)
/// </summary>
public string StationId { get; set; }
public CommunicationMessage()
{
MessageId = GenerateMessageId();
Timestamp = DateTime.Now;
}
private static string GenerateMessageId()
{
return $"MSG{DateTime.Now:yyyyMMddHHmmssfff}{new Random().Next(100, 999)}";
}
public override string ToString()
{
return $"[{Timestamp:HH:mm:ss.fff}] {Direction} {IpAddress}:{Port} - {Length}字节";
}
}
//解析后的数据
/// <summary>
/// 报文方向枚举
/// </summary>
public enum MessageDirection
{
/// <summary>
/// 发送
/// </summary>
Send = 0,
/// <summary>
/// 接收
/// </summary>
Receive = 1
}
}
@@ -0,0 +1,587 @@
using System;
using System.Collections.Generic;
using System.Globalization;
using System.Linq;
namespace StandardScene.Charge
{
/// <summary>
/// 通讯报文数据服务(单例模式)
/// </summary>
public class CommunicationMessageService
{
private static CommunicationMessageService _instance;
private static readonly object _lock = new object();
private readonly object _dataLock = new object();
private readonly AlarmConfigDataService dataService;
private readonly LinkedList<CommunicationMessage> _messages;
private const int MaxMessages = 100; // 最多保留100条
/// <summary>
/// 报文添加事件
/// </summary>
public event EventHandler<CommunicationMessage> MessageAdded;
/// <summary>
/// 获取单例实例
/// </summary>
public static CommunicationMessageService Instance
{
get
{
if (_instance == null)
{
lock (_lock)
{
if (_instance == null)
{
_instance = new CommunicationMessageService();
}
}
}
return _instance;
}
}
private CommunicationMessageService()
{
_messages = new LinkedList<CommunicationMessage>();
dataService = AlarmConfigDataService.Instance;
}
/// <summary>
/// 添加报文
/// </summary>
public void AddMessage(CommunicationMessage message)
{
if (message == null)
return;
lock (_dataLock)
{
// 添加到链表头部(最新的在前面)
_messages.AddFirst(message);
// 如果超过最大数量,移除最旧的
while (_messages.Count > MaxMessages)
{
_messages.RemoveLast();
}
}
// 触发事件
MessageAdded?.Invoke(this, message);
}
/// <summary>
/// 添加发送报文
/// </summary>
public void AddSendMessage(string ipAddress, int port, string rawData, string type, string stationId = null)
{
var message = new CommunicationMessage
{
Direction = MessageDirection.Send,
IpAddress = ipAddress,
Port = port,
RawData = rawData,
Length = rawData.Split(' ')?.Length ?? 0, // 假设是十六进制字符串
StationId = stationId,
Type = type
};
AddMessage(message);
// 发送报文后,解析并更新充电桩数据(发送方向)
ParseSendDataAndUpdateStation(ipAddress, port, rawData, type);
}
/// <summary>
/// 添加接收报文
/// </summary>
public void AddReceiveMessage(string ipAddress, int port, string rawData, string type, string stationId = null)
{
var message = new CommunicationMessage
{
Direction = MessageDirection.Receive,
IpAddress = ipAddress,
Port = port,
RawData = rawData,
Length = rawData.Split(' ')?.Length ?? 0,
StationId = stationId,
Type = type
};
AddMessage(message);
// 接收到报文后,解析并更新充电桩数据(接收方向)
ParseReceiveDataAndUpdateStation(ipAddress, port, rawData, type);
}
/// <summary>
/// 解析发送报文并更新充电桩数据
/// </summary>
private void ParseSendDataAndUpdateStation(string ipAddress, int port, string rawData, string type)
{
try
{
var dataService = ChargeStationDataService.Instance;
// 根据IP地址查找充电桩
var station = dataService.GetStationByIp(ipAddress, port);
if (station == null)
{
return; // 未找到对应充电桩,不处理
}
// 解析发送报文数据
var parsedData = ParseSendRawData(rawData, type);
if (parsedData == null)
{
return; // 解析失败,不处理
}
// 更新充电桩数据(发送方向)
UpdateStationFromSendData(station, parsedData);
// 更新到数据服务
dataService.UpdateStation(station, out string errorMessage);
}
catch
{
// 静默处理异常,不影响报文记录
}
}
/// <summary>
/// 解析接收报文并更新充电桩数据
/// </summary>
public void ParseReceiveDataAndUpdateStation(string ipAddress, int port, string rawData, string type)
{
try
{
var dataService = ChargeStationDataService.Instance;
// 根据IP地址查找充电桩
var station = dataService.GetStationByIp(ipAddress);
if (station == null)
{
return; // 未找到对应充电桩,不处理
}
// 解析接收报文数据
var parsedData = ParseReceiveRawData(rawData, type);
if (parsedData == null)
{
return; // 解析失败,不处理
}
// 更新充电桩数据(接收方向)
UpdateStationFromReceiveData(station, parsedData);
// 合并发送和接收数据,更新到数据服务
dataService.UpdateStation(station, out string errorMessage);
}
catch
{
// 静默处理异常,不影响报文记录
}
}
/// <summary>
/// 解析发送报文数据
/// </summary>
public ParsedSendData ParseSendRawData(string rawData, string type)
{
try
{
// 将逗号分隔的字符串转换为字节数组
var parts = rawData.Split(' ');
if (parts.Length < 10) // 发送报文至少10字节
{
return null;
}
var bytes = new byte[parts.Length];
for (int i = 0; i < parts.Length; i++)
{
if (!byte.TryParse(parts[i], NumberStyles.HexNumber, CultureInfo.InvariantCulture, out bytes[i]))
{
return null;
}
}
byte chargeCommand = 0;
double setVoltage = 0;
double setCurrent = 0;
short carId = 0;
int carSoc = 0;
double carVoltage = 0;
double carCurrent = 0;
if (type == "FRLDShort")
{
chargeCommand = bytes[2];
setCurrent = BitConverter.ToInt32(new byte[] { bytes[6], bytes[5], bytes[4], bytes[3] }, 0) / 10f;
setVoltage = BitConverter.ToInt32(new byte[] { bytes[10], bytes[9], bytes[8], bytes[7] }, 0) / 10f;
carId = BitConverter.ToInt16(new byte[] { bytes[14], bytes[13] }, 0);
carSoc = bytes[15];
carVoltage = BitConverter.ToInt32(new byte[] { bytes[19], bytes[18], bytes[17], bytes[16] }, 0);
carCurrent = BitConverter.ToInt32(new byte[] { bytes[23], bytes[22], bytes[21], bytes[20] }, 0);
}
else if (type == "FRLDTall")
{
chargeCommand = bytes[1];
setCurrent = BitConverter.ToSingle(new byte[] { bytes[5], bytes[4], bytes[3], bytes[2] }, 0);
setVoltage = BitConverter.ToSingle(new byte[] { bytes[9], bytes[8], bytes[7], bytes[6] }, 0);
carId = BitConverter.ToInt16(new byte[] { bytes[13], bytes[12] }, 0);
carSoc = BitConverter.ToInt16(new byte[] { bytes[15], bytes[14] }, 0);
carVoltage = BitConverter.ToSingle(new byte[] { bytes[19], bytes[18], bytes[17], bytes[16] }, 0);
carCurrent = BitConverter.ToSingle(new byte[] { bytes[23], bytes[22], bytes[21], bytes[20] }, 0);
}
// 解析发送报文(根据实际协议)
var parsed = new ParsedSendData
{
ChargeCommand = chargeCommand,
SetVoltage = setVoltage,
SetCurrent = setCurrent,
CurrentVehicleId = carId,
BatteryLevel = carSoc,
CarVoltage = carVoltage,
CarCurrent = carCurrent,
// 发送时间
SendTime = DateTime.Now
};
return parsed;
}
catch
{
return null;
}
}
/// <summary>
/// 解析接收报文数据
/// </summary>
public ParsedReceiveData ParseReceiveRawData(string rawData, string type)
{
try
{
// 将逗号分隔的字符串转换为字节数组
var parts = rawData.Split(' ');
if (parts.Length < 30) // 假设报文至少30字节
{
return null;
}
var bytes = new byte[parts.Length];
for (int i = 0; i < parts.Length; i++)
{
if (!byte.TryParse(parts[i], NumberStyles.HexNumber, CultureInfo.InvariantCulture, out bytes[i]))
{
return null;
}
}
double realTimeVoltage = 0;
double realTimeCurrent = 0;
byte chargeStationStatus = 0;
byte chargeId = 0;
short batteryAH = 0;
byte mechanismStatus = 0;
byte alarmValue = 0;
if (type == "FRLDShort")
{
realTimeCurrent = BitConverter.ToInt32(new byte[] { bytes[5], bytes[4], bytes[3], bytes[2] }, 0) / 10f;
realTimeVoltage = BitConverter.ToInt32(new byte[] { bytes[9], bytes[8], bytes[7], bytes[6] }, 0) / 10f;
chargeStationStatus = bytes[14];
chargeId = bytes[15];
batteryAH = BitConverter.ToInt16(new byte[] { bytes[17], bytes[16] }, 0);
mechanismStatus = bytes[28];
}
else if (type == "FRLDTall")
{
chargeStationStatus = bytes[14];
mechanismStatus = bytes[28];
alarmValue = bytes[15];
}
// 根据实际协议解析接收数据
var parsed = new ParsedReceiveData
{
RealTimeCurrent = realTimeCurrent,
RealTimeVoltage = realTimeVoltage,
Status = type == "FRLDTall" ? ParseStationStatusFRLDTall(chargeStationStatus) : ParseStationStatus(chargeStationStatus),
ChargeID = chargeId,
BatteryAH = batteryAH,
MechanismStatus = type == "FRLDTall" ? ParseMechanismStatusFRLDTall(mechanismStatus) : ParseMechanismStatus(mechanismStatus),
HasAlarm = chargeStationStatus == 2,
AlarmCode = alarmValue,
// AlarmLevel = ParseAlarmLevel(bytes[20])
ReceiveTime = DateTime.Now
};
return parsed;
}
catch
{
return null;
}
}
/// <summary>
/// 从发送报文更新充电桩数据
/// </summary>
private void UpdateStationFromSendData(ChargeStation station, ParsedSendData parsedData)
{
// 更新发送的设定值
//station.SetVoltage = parsedData.SetVoltage;
//station.SetElectricCurrent = parsedData.SetCurrent;
// 更新最后发送时间
station.LastSendTime = parsedData.SendTime;
// 根据发送的充电指令更新状态
if (parsedData.ChargeCommand == 1)
{
// 发送了启动充电指令
station.ChargeCommandStatus = ChargeCommandStatus.Started;
}
else if (parsedData.ChargeCommand == 0)
{
// 发送了停止充电指令
station.ChargeCommandStatus = ChargeCommandStatus.Stopped;
}
station.BatteryLevel = parsedData.BatteryLevel;
station.CurrentVehicle = parsedData.CurrentVehicleId.ToString();
}
/// <summary>
/// 从接收报文更新充电桩数据
/// </summary>
private void UpdateStationFromReceiveData(ChargeStation station, ParsedReceiveData parsedData)
{
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;
if (parsedData.HasAlarm)
{
var alarmInfo = dataService.GetAlarmConfigAlarmCode(parsedData.AlarmCode);
if (alarmInfo != null)
{
station.AlarmMessage = $"报警级别: {GetAlarmLevelText(alarmInfo.Level)}:{alarmInfo.AlarmContent}";
}
}
else
{
station.AlarmMessage = string.Empty;
}
}
/// <summary>
/// 解析机构状态
/// </summary>
private MechanismStatus ParseMechanismStatus(byte statusByte)
{
switch (statusByte)
{
case 1: return MechanismStatus.Retracted;
case 2: return MechanismStatus.Extended;
default: return MechanismStatus.Extending;
}
}
/// <summary>
/// 解析机构状态
/// </summary>
private MechanismStatus ParseMechanismStatusFRLDTall(byte statusByte)
{
switch (statusByte)
{
case 1: return MechanismStatus.Extended;
case 2: return MechanismStatus.Retracted;
default: return MechanismStatus.Extending;
}
}
/// <summary>
/// 解析报警级别
/// </summary>
private AlarmLevel ParseAlarmLevel(byte alarmByte)
{
if (alarmByte == 0) return AlarmLevel.None;
if (alarmByte <= 2) return AlarmLevel.Low;
if (alarmByte <= 5) return AlarmLevel.Medium;
if (alarmByte <= 8) return AlarmLevel.High;
return AlarmLevel.Critical;
}
/// <summary>
/// 解析充电桩状态
/// </summary>
private ChargeStationStatus ParseStationStatus(byte statusByte)
{
switch (statusByte)
{
case 0: return ChargeStationStatus.Idle;
case 1: return ChargeStationStatus.Charging;
case 2: return ChargeStationStatus.Fault;
case 3: return ChargeStationStatus.Battery;
default: return ChargeStationStatus.Idle;
}
}
private ChargeStationStatus ParseStationStatusFRLDTall(byte statusByte)
{
switch (statusByte)
{
case 0: return ChargeStationStatus.Idle;
case 2: return ChargeStationStatus.Idle;
case 3: return ChargeStationStatus.Charging;
case 4: return ChargeStationStatus.Fault;
default: return ChargeStationStatus.Idle;
}
}
/// <summary>
/// 获取报警级别文本
/// </summary>
private string GetAlarmLevelText(AlarmLevel level)
{
switch (level)
{
case AlarmLevel.None: return "无";
case AlarmLevel.Low: return "低";
case AlarmLevel.Medium: return "中";
case AlarmLevel.High: return "高";
case AlarmLevel.Critical: return "严重";
default: return "未知";
}
}
/// <summary>
/// 解析后的发送报文数据(内部类)
/// </summary>
public class ParsedSendData
{
public byte ChargeCommand { get; set; }
public double SetVoltage { get; set; }
public double SetCurrent { get; set; }
public double BatteryLevel { get; set; }
public int CurrentVehicleId { get; set; }
public double CarVoltage { get; set; }
public double CarCurrent { get; set; }
public DateTime SendTime { get; set; }
}
/// <summary>
/// 解析后的接收报文数据(内部类)
/// </summary>
public class ParsedReceiveData
{
public CommunicationStatus CommStatus { get; set; }
public ChargeCommandStatus ChargeCommandStatus { get; set; }
public MechanismStatus MechanismStatus { get; set; }
public double RealTimeVoltage { get; set; }
public double RealTimeCurrent { get; set; }
public int ChargeID { get; set; }
public float BatteryAH { get; set; }
public bool HasAlarm { get; set; }
public AlarmLevel AlarmLevel { get; set; }
public int AlarmCode { get; set; }
public ChargeStationStatus Status { get; set; }
public DateTime ReceiveTime { get; set; }
}
/// <summary>
/// 获取所有报文
/// </summary>
public List<CommunicationMessage> GetAllMessages()
{
lock (_dataLock)
{
return _messages.ToList();
}
}
/// <summary>
/// 根据IP筛选报文
/// </summary>
public List<CommunicationMessage> GetMessagesByIp(string ipAddress)
{
if (string.IsNullOrWhiteSpace(ipAddress))
return GetAllMessages();
lock (_dataLock)
{
return _messages.Where(m => m.IpAddress == ipAddress).ToList();
}
}
/// <summary>
/// 根据充电桩ID筛选报文
/// </summary>
public List<CommunicationMessage> GetMessagesByStationId(string stationId)
{
if (string.IsNullOrWhiteSpace(stationId))
return GetAllMessages();
lock (_dataLock)
{
return _messages.Where(m => m.StationId == stationId).ToList();
}
}
/// <summary>
/// 清空所有报文
/// </summary>
public void Clear()
{
lock (_dataLock)
{
_messages.Clear();
}
}
/// <summary>
/// 获取所有唯一IP地址列表
/// </summary>
public List<string> GetUniqueIpAddresses()
{
lock (_dataLock)
{
return _messages
.Select(m => m.IpAddress)
.Distinct()
.OrderBy(ip => ip)
.ToList();
}
}
}
}
@@ -0,0 +1,376 @@
namespace StandardScene.Charge
{
partial class CommunicationMonitorForm
{
private System.ComponentModel.IContainer components = null;
protected override void Dispose(bool disposing)
{
if (disposing && (components != null))
{
components.Dispose();
}
base.Dispose(disposing);
}
#region Windows Form Designer generated code
private void InitializeComponent()
{
System.Windows.Forms.DataGridViewCellStyle dataGridViewCellStyle1 = new System.Windows.Forms.DataGridViewCellStyle();
this.splitContainer = new System.Windows.Forms.SplitContainer();
this.pnlLeft = new System.Windows.Forms.Panel();
this.dgvMessages = new System.Windows.Forms.DataGridView();
this.colTime = new System.Windows.Forms.DataGridViewTextBoxColumn();
this.colDirection = new System.Windows.Forms.DataGridViewTextBoxColumn();
this.colIpAddress = new System.Windows.Forms.DataGridViewTextBoxColumn();
this.colPort = new System.Windows.Forms.DataGridViewTextBoxColumn();
this.colLength = new System.Windows.Forms.DataGridViewTextBoxColumn();
this.colRawData = new System.Windows.Forms.DataGridViewTextBoxColumn();
this.colStationId = new System.Windows.Forms.DataGridViewTextBoxColumn();
this.type = new System.Windows.Forms.DataGridViewTextBoxColumn();
this.pnlLeftTop = new System.Windows.Forms.Panel();
this.button1 = new System.Windows.Forms.Button();
this.btnClear = new System.Windows.Forms.Button();
this.btnRefresh = new System.Windows.Forms.Button();
this.lblStatistics = new System.Windows.Forms.Label();
this.cmbIpFilter = new System.Windows.Forms.ComboBox();
this.lblIpFilter = new System.Windows.Forms.Label();
this.pnlRight = new System.Windows.Forms.Panel();
this.txtParsedData = new System.Windows.Forms.TextBox();
this.pnlRightTop = new System.Windows.Forms.Panel();
this.btnClose = new System.Windows.Forms.Button();
this.lblParsedTitle = new System.Windows.Forms.Label();
((System.ComponentModel.ISupportInitialize)(this.splitContainer)).BeginInit();
this.splitContainer.Panel1.SuspendLayout();
this.splitContainer.Panel2.SuspendLayout();
this.splitContainer.SuspendLayout();
this.pnlLeft.SuspendLayout();
((System.ComponentModel.ISupportInitialize)(this.dgvMessages)).BeginInit();
this.pnlLeftTop.SuspendLayout();
this.pnlRight.SuspendLayout();
this.pnlRightTop.SuspendLayout();
this.SuspendLayout();
//
// splitContainer
//
this.splitContainer.Dock = System.Windows.Forms.DockStyle.Fill;
this.splitContainer.Location = new System.Drawing.Point(0, 0);
this.splitContainer.Name = "splitContainer";
//
// splitContainer.Panel1
//
this.splitContainer.Panel1.Controls.Add(this.pnlLeft);
//
// splitContainer.Panel2
//
this.splitContainer.Panel2.Controls.Add(this.pnlRight);
this.splitContainer.Size = new System.Drawing.Size(1400, 800);
this.splitContainer.SplitterDistance = 850;
this.splitContainer.TabIndex = 0;
//
// pnlLeft
//
this.pnlLeft.Controls.Add(this.dgvMessages);
this.pnlLeft.Controls.Add(this.pnlLeftTop);
this.pnlLeft.Dock = System.Windows.Forms.DockStyle.Fill;
this.pnlLeft.Location = new System.Drawing.Point(0, 0);
this.pnlLeft.Name = "pnlLeft";
this.pnlLeft.Size = new System.Drawing.Size(850, 800);
this.pnlLeft.TabIndex = 0;
//
// dgvMessages
//
this.dgvMessages.AllowUserToAddRows = false;
this.dgvMessages.AllowUserToDeleteRows = false;
this.dgvMessages.AutoSizeColumnsMode = System.Windows.Forms.DataGridViewAutoSizeColumnsMode.Fill;
this.dgvMessages.BackgroundColor = System.Drawing.Color.White;
this.dgvMessages.BorderStyle = System.Windows.Forms.BorderStyle.None;
dataGridViewCellStyle1.Alignment = System.Windows.Forms.DataGridViewContentAlignment.MiddleLeft;
dataGridViewCellStyle1.BackColor = System.Drawing.Color.FromArgb(((int)(((byte)(63)))), ((int)(((byte)(81)))), ((int)(((byte)(181)))));
dataGridViewCellStyle1.Font = new System.Drawing.Font("微软雅黑", 9F, System.Drawing.FontStyle.Bold, System.Drawing.GraphicsUnit.Point, ((byte)(134)));
dataGridViewCellStyle1.ForeColor = System.Drawing.Color.White;
dataGridViewCellStyle1.SelectionBackColor = System.Drawing.SystemColors.Highlight;
dataGridViewCellStyle1.SelectionForeColor = System.Drawing.SystemColors.HighlightText;
dataGridViewCellStyle1.WrapMode = System.Windows.Forms.DataGridViewTriState.True;
this.dgvMessages.ColumnHeadersDefaultCellStyle = dataGridViewCellStyle1;
this.dgvMessages.ColumnHeadersHeight = 35;
this.dgvMessages.Columns.AddRange(new System.Windows.Forms.DataGridViewColumn[] {
this.colTime,
this.colDirection,
this.colIpAddress,
this.colPort,
this.colLength,
this.colRawData,
this.colStationId,
this.type});
this.dgvMessages.Dock = System.Windows.Forms.DockStyle.Fill;
this.dgvMessages.EnableHeadersVisualStyles = false;
this.dgvMessages.GridColor = System.Drawing.Color.LightGray;
this.dgvMessages.Location = new System.Drawing.Point(0, 80);
this.dgvMessages.MultiSelect = false;
this.dgvMessages.Name = "dgvMessages";
this.dgvMessages.ReadOnly = true;
this.dgvMessages.RowHeadersVisible = false;
this.dgvMessages.RowHeadersWidth = 51;
this.dgvMessages.RowTemplate.Height = 30;
this.dgvMessages.SelectionMode = System.Windows.Forms.DataGridViewSelectionMode.FullRowSelect;
this.dgvMessages.Size = new System.Drawing.Size(850, 720);
this.dgvMessages.TabIndex = 1;
this.dgvMessages.SelectionChanged += new System.EventHandler(this.dgvMessages_SelectionChanged);
//
// colTime
//
this.colTime.FillWeight = 80F;
this.colTime.HeaderText = "时间";
this.colTime.MinimumWidth = 6;
this.colTime.Name = "colTime";
this.colTime.ReadOnly = true;
//
// colDirection
//
this.colDirection.FillWeight = 50F;
this.colDirection.HeaderText = "方向";
this.colDirection.MinimumWidth = 6;
this.colDirection.Name = "colDirection";
this.colDirection.ReadOnly = true;
//
// colIpAddress
//
this.colIpAddress.FillWeight = 80F;
this.colIpAddress.HeaderText = "IP地址";
this.colIpAddress.MinimumWidth = 6;
this.colIpAddress.Name = "colIpAddress";
this.colIpAddress.ReadOnly = true;
//
// colPort
//
this.colPort.FillWeight = 50F;
this.colPort.HeaderText = "端口";
this.colPort.MinimumWidth = 6;
this.colPort.Name = "colPort";
this.colPort.ReadOnly = true;
//
// colLength
//
this.colLength.FillWeight = 50F;
this.colLength.HeaderText = "长度";
this.colLength.MinimumWidth = 6;
this.colLength.Name = "colLength";
this.colLength.ReadOnly = true;
//
// colRawData
//
this.colRawData.FillWeight = 200F;
this.colRawData.HeaderText = "原始数据";
this.colRawData.MinimumWidth = 6;
this.colRawData.Name = "colRawData";
this.colRawData.ReadOnly = true;
//
// colStationId
//
this.colStationId.FillWeight = 80F;
this.colStationId.HeaderText = "充电桩";
this.colStationId.MinimumWidth = 6;
this.colStationId.Name = "colStationId";
this.colStationId.ReadOnly = true;
//
// type
//
this.type.HeaderText = "协议类型";
this.type.MinimumWidth = 6;
this.type.Name = "type";
this.type.ReadOnly = true;
//
// pnlLeftTop
//
this.pnlLeftTop.BackColor = System.Drawing.Color.FromArgb(((int)(((byte)(250)))), ((int)(((byte)(250)))), ((int)(((byte)(250)))));
this.pnlLeftTop.Controls.Add(this.button1);
this.pnlLeftTop.Controls.Add(this.btnClear);
this.pnlLeftTop.Controls.Add(this.btnRefresh);
this.pnlLeftTop.Controls.Add(this.lblStatistics);
this.pnlLeftTop.Controls.Add(this.cmbIpFilter);
this.pnlLeftTop.Controls.Add(this.lblIpFilter);
this.pnlLeftTop.Dock = System.Windows.Forms.DockStyle.Top;
this.pnlLeftTop.Location = new System.Drawing.Point(0, 0);
this.pnlLeftTop.Name = "pnlLeftTop";
this.pnlLeftTop.Padding = new System.Windows.Forms.Padding(10);
this.pnlLeftTop.Size = new System.Drawing.Size(850, 80);
this.pnlLeftTop.TabIndex = 0;
//
// button1
//
this.button1.Font = new System.Drawing.Font("微软雅黑", 9F, System.Drawing.FontStyle.Regular, System.Drawing.GraphicsUnit.Point, ((byte)(134)));
this.button1.Location = new System.Drawing.Point(546, 16);
this.button1.Name = "button1";
this.button1.Size = new System.Drawing.Size(80, 32);
this.button1.TabIndex = 5;
this.button1.Text = "暂停";
this.button1.UseVisualStyleBackColor = true;
this.button1.Click += new System.EventHandler(this.button1_Click);
//
// btnClear
//
this.btnClear.Font = new System.Drawing.Font("微软雅黑", 9F, System.Drawing.FontStyle.Regular, System.Drawing.GraphicsUnit.Point, ((byte)(134)));
this.btnClear.Location = new System.Drawing.Point(460, 15);
this.btnClear.Name = "btnClear";
this.btnClear.Size = new System.Drawing.Size(80, 32);
this.btnClear.TabIndex = 4;
this.btnClear.Text = "清空";
this.btnClear.UseVisualStyleBackColor = true;
this.btnClear.Click += new System.EventHandler(this.btnClear_Click);
//
// btnRefresh
//
this.btnRefresh.Font = new System.Drawing.Font("微软雅黑", 9F, System.Drawing.FontStyle.Regular, System.Drawing.GraphicsUnit.Point, ((byte)(134)));
this.btnRefresh.Location = new System.Drawing.Point(370, 15);
this.btnRefresh.Name = "btnRefresh";
this.btnRefresh.Size = new System.Drawing.Size(80, 32);
this.btnRefresh.TabIndex = 3;
this.btnRefresh.Text = "刷新";
this.btnRefresh.UseVisualStyleBackColor = true;
this.btnRefresh.Click += new System.EventHandler(this.btnRefresh_Click);
//
// lblStatistics
//
this.lblStatistics.AutoSize = true;
this.lblStatistics.Font = new System.Drawing.Font("微软雅黑", 9F, System.Drawing.FontStyle.Regular, System.Drawing.GraphicsUnit.Point, ((byte)(134)));
this.lblStatistics.ForeColor = System.Drawing.Color.FromArgb(((int)(((byte)(100)))), ((int)(((byte)(100)))), ((int)(((byte)(100)))));
this.lblStatistics.Location = new System.Drawing.Point(13, 52);
this.lblStatistics.Name = "lblStatistics";
this.lblStatistics.Size = new System.Drawing.Size(115, 20);
this.lblStatistics.TabIndex = 2;
this.lblStatistics.Text = "显示: 0 | 总数: 0";
//
// cmbIpFilter
//
this.cmbIpFilter.DropDownStyle = System.Windows.Forms.ComboBoxStyle.DropDownList;
this.cmbIpFilter.Font = new System.Drawing.Font("微软雅黑", 9F, System.Drawing.FontStyle.Regular, System.Drawing.GraphicsUnit.Point, ((byte)(134)));
this.cmbIpFilter.FormattingEnabled = true;
this.cmbIpFilter.Location = new System.Drawing.Point(100, 17);
this.cmbIpFilter.Name = "cmbIpFilter";
this.cmbIpFilter.Size = new System.Drawing.Size(250, 28);
this.cmbIpFilter.TabIndex = 1;
this.cmbIpFilter.SelectedIndexChanged += new System.EventHandler(this.cmbIpFilter_SelectedIndexChanged);
//
// lblIpFilter
//
this.lblIpFilter.AutoSize = true;
this.lblIpFilter.Font = new System.Drawing.Font("微软雅黑", 9F, System.Drawing.FontStyle.Regular, System.Drawing.GraphicsUnit.Point, ((byte)(134)));
this.lblIpFilter.Location = new System.Drawing.Point(13, 21);
this.lblIpFilter.Name = "lblIpFilter";
this.lblIpFilter.Size = new System.Drawing.Size(67, 20);
this.lblIpFilter.TabIndex = 0;
this.lblIpFilter.Text = "IP筛选:";
//
// pnlRight
//
this.pnlRight.Controls.Add(this.txtParsedData);
this.pnlRight.Controls.Add(this.pnlRightTop);
this.pnlRight.Dock = System.Windows.Forms.DockStyle.Fill;
this.pnlRight.Location = new System.Drawing.Point(0, 0);
this.pnlRight.Name = "pnlRight";
this.pnlRight.Size = new System.Drawing.Size(546, 800);
this.pnlRight.TabIndex = 0;
//
// txtParsedData
//
this.txtParsedData.BackColor = System.Drawing.Color.White;
this.txtParsedData.Dock = System.Windows.Forms.DockStyle.Fill;
this.txtParsedData.Font = new System.Drawing.Font("Consolas", 9F, System.Drawing.FontStyle.Regular, System.Drawing.GraphicsUnit.Point, ((byte)(0)));
this.txtParsedData.Location = new System.Drawing.Point(0, 60);
this.txtParsedData.Multiline = true;
this.txtParsedData.Name = "txtParsedData";
this.txtParsedData.ReadOnly = true;
this.txtParsedData.ScrollBars = System.Windows.Forms.ScrollBars.Both;
this.txtParsedData.Size = new System.Drawing.Size(546, 740);
this.txtParsedData.TabIndex = 1;
this.txtParsedData.WordWrap = false;
//
// pnlRightTop
//
this.pnlRightTop.BackColor = System.Drawing.Color.FromArgb(((int)(((byte)(250)))), ((int)(((byte)(250)))), ((int)(((byte)(250)))));
this.pnlRightTop.Controls.Add(this.btnClose);
this.pnlRightTop.Controls.Add(this.lblParsedTitle);
this.pnlRightTop.Dock = System.Windows.Forms.DockStyle.Top;
this.pnlRightTop.Location = new System.Drawing.Point(0, 0);
this.pnlRightTop.Name = "pnlRightTop";
this.pnlRightTop.Padding = new System.Windows.Forms.Padding(10);
this.pnlRightTop.Size = new System.Drawing.Size(546, 60);
this.pnlRightTop.TabIndex = 0;
//
// btnClose
//
this.btnClose.Anchor = ((System.Windows.Forms.AnchorStyles)((System.Windows.Forms.AnchorStyles.Top | System.Windows.Forms.AnchorStyles.Right)));
this.btnClose.Font = new System.Drawing.Font("微软雅黑", 9F, System.Drawing.FontStyle.Regular, System.Drawing.GraphicsUnit.Point, ((byte)(134)));
this.btnClose.Location = new System.Drawing.Point(446, 15);
this.btnClose.Name = "btnClose";
this.btnClose.Size = new System.Drawing.Size(80, 32);
this.btnClose.TabIndex = 1;
this.btnClose.Text = "关闭";
this.btnClose.UseVisualStyleBackColor = true;
this.btnClose.Click += new System.EventHandler(this.btnClose_Click);
//
// lblParsedTitle
//
this.lblParsedTitle.AutoSize = true;
this.lblParsedTitle.Font = new System.Drawing.Font("微软雅黑", 10F, System.Drawing.FontStyle.Bold, System.Drawing.GraphicsUnit.Point, ((byte)(134)));
this.lblParsedTitle.Location = new System.Drawing.Point(13, 20);
this.lblParsedTitle.Name = "lblParsedTitle";
this.lblParsedTitle.Size = new System.Drawing.Size(112, 24);
this.lblParsedTitle.TabIndex = 0;
this.lblParsedTitle.Text = "报文数据解析";
//
// CommunicationMonitorForm
//
this.AutoScaleDimensions = new System.Drawing.SizeF(8F, 15F);
this.AutoScaleMode = System.Windows.Forms.AutoScaleMode.Font;
this.ClientSize = new System.Drawing.Size(1400, 800);
this.Controls.Add(this.splitContainer);
this.Name = "CommunicationMonitorForm";
this.Text = "通讯监控";
this.Load += new System.EventHandler(this.CommunicationMonitorForm_Load);
this.splitContainer.Panel1.ResumeLayout(false);
this.splitContainer.Panel2.ResumeLayout(false);
((System.ComponentModel.ISupportInitialize)(this.splitContainer)).EndInit();
this.splitContainer.ResumeLayout(false);
this.pnlLeft.ResumeLayout(false);
((System.ComponentModel.ISupportInitialize)(this.dgvMessages)).EndInit();
this.pnlLeftTop.ResumeLayout(false);
this.pnlLeftTop.PerformLayout();
this.pnlRight.ResumeLayout(false);
this.pnlRight.PerformLayout();
this.pnlRightTop.ResumeLayout(false);
this.pnlRightTop.PerformLayout();
this.ResumeLayout(false);
}
#endregion
private System.Windows.Forms.SplitContainer splitContainer;
private System.Windows.Forms.Panel pnlLeft;
private System.Windows.Forms.DataGridView dgvMessages;
private System.Windows.Forms.Panel pnlLeftTop;
private System.Windows.Forms.ComboBox cmbIpFilter;
private System.Windows.Forms.Label lblIpFilter;
private System.Windows.Forms.Panel pnlRight;
private System.Windows.Forms.TextBox txtParsedData;
private System.Windows.Forms.Panel pnlRightTop;
private System.Windows.Forms.Label lblParsedTitle;
private System.Windows.Forms.Label lblStatistics;
private System.Windows.Forms.Button btnRefresh;
private System.Windows.Forms.Button btnClear;
private System.Windows.Forms.Button btnClose;
private System.Windows.Forms.DataGridViewTextBoxColumn colTime;
private System.Windows.Forms.DataGridViewTextBoxColumn colDirection;
private System.Windows.Forms.DataGridViewTextBoxColumn colIpAddress;
private System.Windows.Forms.DataGridViewTextBoxColumn colPort;
private System.Windows.Forms.DataGridViewTextBoxColumn colLength;
private System.Windows.Forms.DataGridViewTextBoxColumn colRawData;
private System.Windows.Forms.DataGridViewTextBoxColumn colStationId;
private System.Windows.Forms.DataGridViewTextBoxColumn type;
private System.Windows.Forms.Button button1;
}
}
@@ -0,0 +1,561 @@
using System;
using System.Collections.Generic;
using System.Drawing;
using System.Linq;
using System.Windows.Forms;
namespace StandardScene.Charge
{
/// <summary>
/// 通讯监控窗体
/// </summary>
public partial class CommunicationMonitorForm : Form
{
private readonly CommunicationMessageService messageService;
private bool isFormLoaded = false;
private bool isFormMessageStop = false;
private const int MaxDisplayRows = 100;
private const int UiBatchSize = 20;
private const int StatsRefreshMs = 500;
private readonly Queue<CommunicationMessage> pendingMessages = new Queue<CommunicationMessage>();
private readonly object pendingMessagesLock = new object();
private readonly Timer uiFlushTimer;
private readonly Timer statsRefreshTimer;
private bool pendingStatsRefresh = false;
private int lastDisplayCountForStats = 0;
public CommunicationMonitorForm()
{
InitializeComponent();
messageService = CommunicationMessageService.Instance;
uiFlushTimer = new Timer { Interval = 500 };
uiFlushTimer.Tick += UiFlushTimer_Tick;
statsRefreshTimer = new Timer { Interval = StatsRefreshMs };
statsRefreshTimer.Tick += StatsRefreshTimer_Tick;
// 订阅窗体关闭事件
this.FormClosing += CommunicationMonitorForm_FormClosing;
}
private void CommunicationMonitorForm_Load(object sender, EventArgs e)
{
try
{
InitializeForm();
LoadMessages();
// 标记窗体已加载完成
isFormLoaded = true;
uiFlushTimer.Start();
statsRefreshTimer.Start();
// 在窗体加载完成后再订阅报文添加事件(避免在初始化期间触发)
messageService.MessageAdded += OnMessageAdded;
}
catch (Exception ex)
{
MessageBox.Show($"窗体加载失败: {ex.Message}\r\n{ex.StackTrace}", "错误",
MessageBoxButtons.OK, MessageBoxIcon.Error);
}
}
private void InitializeForm()
{
this.Text = "通讯监控";
this.Size = new Size(1400, 800);
this.StartPosition = FormStartPosition.CenterScreen;
this.MinimumSize = new Size(1200, 600);
// 初始化IP筛选下拉框
RefreshIpFilter();
}
/// <summary>
/// 刷新IP筛选下拉框
/// </summary>
private void RefreshIpFilter()
{
try
{
if (cmbIpFilter == null || messageService == null)
return;
var selectedIp = cmbIpFilter.SelectedItem?.ToString();
cmbIpFilter.Items.Clear();
cmbIpFilter.Items.Add("全部");
var ipAddresses = messageService.GetUniqueIpAddresses();
if (ipAddresses != null)
{
foreach (var ip in ipAddresses)
{
if (!string.IsNullOrEmpty(ip))
{
cmbIpFilter.Items.Add(ip);
}
}
}
// 恢复选中项
if (!string.IsNullOrEmpty(selectedIp) && cmbIpFilter.Items.Contains(selectedIp))
{
cmbIpFilter.SelectedItem = selectedIp;
}
else if (cmbIpFilter.Items.Count > 0)
{
cmbIpFilter.SelectedIndex = 0;
}
}
catch (Exception ex)
{
System.Diagnostics.Debug.WriteLine($"刷新IP筛选失败: {ex.Message}");
}
}
/// <summary>
/// 加载报文列表
/// </summary>
private void LoadMessages()
{
var layoutSuspended = false;
try
{
if (dgvMessages == null|| isFormMessageStop)
return;
var selectedIp = cmbIpFilter?.SelectedItem?.ToString();
var messages = string.IsNullOrEmpty(selectedIp) || selectedIp == "全部"
? messageService.GetAllMessages()
: messageService.GetMessagesByIp(selectedIp);
dgvMessages.SuspendLayout();
layoutSuspended = true;
dgvMessages.Rows.Clear();
foreach (var msg in messages)
{
AddMessageRow(msg, false);
}
UpdateStatistics(messages.Count);
pendingStatsRefresh = false;
}
catch (Exception ex)
{
MessageBox.Show($"加载报文失败: {ex.Message}", "错误",
MessageBoxButtons.OK, MessageBoxIcon.Error);
}
finally
{
if (layoutSuspended && dgvMessages != null)
{
dgvMessages.ResumeLayout();
}
}
}
/// <summary>
/// 定时批量刷新UI,避免每条报文都抢占UI线程
/// </summary>
private void UiFlushTimer_Tick(object sender, EventArgs e)
{
if (!isFormLoaded || isFormMessageStop)
return;
List<CommunicationMessage> batch = null;
lock (pendingMessagesLock)
{
if (pendingMessages.Count == 0)
return;
int count = Math.Min(UiBatchSize, pendingMessages.Count);
batch = new List<CommunicationMessage>(count);
for (int i = 0; i < count; i++)
{
batch.Add(pendingMessages.Dequeue());
}
}
if (batch == null || batch.Count == 0)
return;
dgvMessages.SuspendLayout();
try
{
var selectedIp = cmbIpFilter?.SelectedItem?.ToString();
bool displayChanged = false;
foreach (var message in batch)
{
EnsureIpInFilter(message.IpAddress);
if (string.IsNullOrEmpty(selectedIp) || selectedIp == "全部" || selectedIp == message.IpAddress)
{
AddMessageRow(message, true);
displayChanged = true;
}
}
if (displayChanged)
{
RequestStatisticsRefresh(dgvMessages.Rows.Count);
}
}
finally
{
dgvMessages.ResumeLayout();
}
}
/// <summary>
/// 统计信息低频刷新(500ms
/// </summary>
private void StatsRefreshTimer_Tick(object sender, EventArgs e)
{
if (!isFormLoaded || isFormMessageStop || !pendingStatsRefresh)
return;
pendingStatsRefresh = false;
UpdateStatistics(lastDisplayCountForStats);
}
private void RequestStatisticsRefresh(int displayCount)
{
lastDisplayCountForStats = displayCount;
pendingStatsRefresh = true;
}
private void EnsureIpInFilter(string ipAddress)
{
if (cmbIpFilter == null || string.IsNullOrWhiteSpace(ipAddress))
return;
if (!cmbIpFilter.Items.Contains(ipAddress))
{
cmbIpFilter.Items.Add(ipAddress);
}
}
/// <summary>
/// 向表格新增一条报文行(支持头部插入)
/// </summary>
private void AddMessageRow(CommunicationMessage msg, bool insertAtTop = true)
{
if (msg == null || dgvMessages == null)
return;
DataGridViewRow row;
if (insertAtTop)
{
dgvMessages.Rows.Insert(0,
msg.Timestamp.ToString("HH:mm:ss.fff"),
msg.Direction == MessageDirection.Send ? "发送" : "接收",
msg.IpAddress,
msg.Port,
msg.Length,
msg.RawData,
msg.StationId ?? "-",
msg.Type);
row = dgvMessages.Rows[0];
}
else
{
var index = dgvMessages.Rows.Add(
msg.Timestamp.ToString("HH:mm:ss.fff"),
msg.Direction == MessageDirection.Send ? "发送" : "接收",
msg.IpAddress,
msg.Port,
msg.Length,
msg.RawData,
msg.StationId ?? "-",
msg.Type);
row = dgvMessages.Rows[index];
}
if (msg.Direction == MessageDirection.Send)
{
row.DefaultCellStyle.BackColor = Color.FromArgb(232, 245, 233);
row.DefaultCellStyle.ForeColor = Color.FromArgb(46, 125, 50);
}
else
{
row.DefaultCellStyle.BackColor = Color.FromArgb(227, 242, 253);
row.DefaultCellStyle.ForeColor = Color.FromArgb(13, 71, 161);
}
while (dgvMessages.Rows.Count > MaxDisplayRows)
{
dgvMessages.Rows.RemoveAt(dgvMessages.Rows.Count - 1);
}
}
/// <summary>
/// 更新统计信息
/// </summary>
private void UpdateStatistics(int displayCount)
{
try
{
if (lblStatistics == null || messageService == null)
return;
var allMessages = messageService.GetAllMessages();
if (allMessages == null)
return;
var sendCount = allMessages.Count(m => m.Direction == MessageDirection.Send);
var receiveCount = allMessages.Count(m => m.Direction == MessageDirection.Receive);
lblStatistics.Text = $"显示: {displayCount} | 总数: {allMessages.Count} | 发送: {sendCount} | 接收: {receiveCount}";
}
catch (Exception ex)
{
System.Diagnostics.Debug.WriteLine($"更新统计信息失败: {ex.Message}");
if (lblStatistics != null)
{
lblStatistics.Text = "统计信息加载失败";
}
}
}
/// <summary>
/// 新报文添加事件处理(线程安全)
/// </summary>
private void OnMessageAdded(object sender, CommunicationMessage message)
{
// 如果窗体还未加载完成,忽略此事件
if (!isFormLoaded || isFormMessageStop)
return;
try
{
if (message == null)
{
return;
}
lock (pendingMessagesLock)
{
pendingMessages.Enqueue(message);
}
}
catch (Exception ex)
{
System.Diagnostics.Debug.WriteLine($"处理新报文失败: {ex.Message}");
}
}
/// <summary>
/// 解析报文数据
/// </summary>
private void ParseMessage(CommunicationMessage message)
{
if (message == null || txtParsedData == null)
return;
try
{
var parsed = new System.Text.StringBuilder();
parsed.AppendLine("=== 报文解析 ===");
parsed.AppendLine($"时间: {message.Timestamp:yyyy-MM-dd HH:mm:ss.fff}");
parsed.AppendLine($"方向: {(message.Direction == MessageDirection.Send ? "" : "")}");
parsed.AppendLine($"地址: {message.IpAddress}:{message.Port}");
parsed.AppendLine($"站点: {message.StationId ?? ""}");
parsed.AppendLine($"长度: {message.Length} 字节");
parsed.AppendLine();
parsed.AppendLine("=== 原始数据 (HEX) ===");
parsed.AppendLine(message.RawData);
// parsed.AppendLine(FormatHexString(message.RawData));
parsed.AppendLine();
parsed.AppendLine("=== 数据解析 ===");
// TODO: 根据实际协议进行解析
parsed.AppendLine();
if (message.Direction== MessageDirection.Send)
{
var sendDate = messageService.ParseSendRawData(message.RawData, message.Type);
parsed.AppendLine("示例解析:");
parsed.AppendLine($"充电指令:{sendDate.ChargeCommand}");
parsed.AppendLine($"发送电压:{sendDate.SetVoltage}");
parsed.AppendLine($"发送电流:{sendDate.SetCurrent}");
parsed.AppendLine($"车辆ID{sendDate.CurrentVehicleId}");
parsed.AppendLine($"车辆电量:{sendDate.BatteryLevel}");
parsed.AppendLine($"车辆电压:{sendDate.CarVoltage}");
parsed.AppendLine($"车辆电流:{sendDate.CarCurrent}");
}
else
{
var recDate = messageService.ParseReceiveRawData(message.RawData, message.Type);
string mechanismStatus = (int)recDate.MechanismStatus == 1 ? "伸出" : (int)recDate.MechanismStatus == 2 ? "缩回" : (int)recDate.MechanismStatus == 3 ? "运动中" : recDate.MechanismStatus.ToString();
parsed.AppendLine("示例解析:");
parsed.AppendLine($"机构状态:{mechanismStatus}");
parsed.AppendLine($"实时电压:{recDate.RealTimeVoltage}");
parsed.AppendLine($"实时电流:{recDate.RealTimeCurrent}");
parsed.AppendLine($"充电量: {recDate.BatteryAH}");
parsed.AppendLine($"是否报警:{recDate.HasAlarm}");
parsed.AppendLine($"充电状态:{recDate.Status.ToString()}");
}
txtParsedData.Text = parsed.ToString();
}
catch (Exception ex)
{
txtParsedData.Text = $"解析失败: {ex.Message}";
}
}
/// <summary>
/// 格式化十六进制字符串
/// </summary>
private string FormatHexString(string hexData)
{
if (string.IsNullOrEmpty(hexData))
return string.Empty;
var formatted = new System.Text.StringBuilder();
for (int i = 0; i < hexData.Length; i += 2)
{
if (i > 0 && i % 32 == 0)
formatted.AppendLine();
else if (i > 0)
formatted.Append(" ");
if (i + 1 < hexData.Length)
formatted.Append(hexData.Substring(i, 2));
else
formatted.Append(hexData[i]);
}
return formatted.ToString();
}
// ==================== 事件处理 ====================
private void cmbIpFilter_SelectedIndexChanged(object sender, EventArgs e)
{
try
{
LoadMessages();
}
catch (Exception ex)
{
System.Diagnostics.Debug.WriteLine($"筛选改变失败: {ex.Message}");
}
}
private void dgvMessages_SelectionChanged(object sender, EventArgs e)
{
try
{
if (dgvMessages.SelectedRows.Count > 0)
{
var row = dgvMessages.SelectedRows[0];
var rawData = row.Cells[5].Value?.ToString();
var ipAddress = row.Cells[2].Value?.ToString();
var port = int.Parse(row.Cells[3].Value?.ToString() ?? "0");
var timeStr = row.Cells[0].Value?.ToString();
var directionStr = row.Cells[1].Value?.ToString();
var stationId = row.Cells[6].Value?.ToString();
var type = row.Cells[7].Value?.ToString();
// 构造消息对象用于解析
var message = new CommunicationMessage
{
RawData = rawData,
IpAddress = ipAddress,
Port = port,
Direction = directionStr == "发送" ? MessageDirection.Send : MessageDirection.Receive,
StationId = stationId == "-" ? null : stationId,
Length = rawData.Split(' ')?.Length ?? 0,
Type=type,
};
if (DateTime.TryParse(timeStr, out DateTime timestamp))
{
message.Timestamp = timestamp;
}
ParseMessage(message);
}
}
catch (Exception ex)
{
System.Diagnostics.Debug.WriteLine($"选择报文失败: {ex.Message}");
}
}
private void btnRefresh_Click(object sender, EventArgs e)
{
try
{
RefreshIpFilter();
LoadMessages();
RequestStatisticsRefresh(dgvMessages?.Rows.Count ?? 0);
}
catch (Exception ex)
{
MessageBox.Show($"刷新失败: {ex.Message}", "错误",
MessageBoxButtons.OK, MessageBoxIcon.Error);
}
}
private void btnClear_Click(object sender, EventArgs e)
{
try
{
var result = MessageBox.Show(
"确定要清空所有报文记录吗?",
"确认清空",
MessageBoxButtons.YesNo,
MessageBoxIcon.Question);
if (result == DialogResult.Yes)
{
messageService.Clear();
RefreshIpFilter();
LoadMessages();
if (txtParsedData != null)
{
txtParsedData.Clear();
}
dgvMessages.Rows.Clear();
RequestStatisticsRefresh(0);
}
}
catch (Exception ex)
{
MessageBox.Show($"清空报文失败: {ex.Message}", "错误",
MessageBoxButtons.OK, MessageBoxIcon.Error);
}
}
private void btnClose_Click(object sender, EventArgs e)
{
this.Close();
}
private void CommunicationMonitorForm_FormClosing(object sender, FormClosingEventArgs e)
{
// 取消订阅事件
messageService.MessageAdded -= OnMessageAdded;
uiFlushTimer.Stop();
uiFlushTimer.Dispose();
statsRefreshTimer.Stop();
statsRefreshTimer.Dispose();
}
private void button1_Click(object sender, EventArgs e)
{
isFormMessageStop = !isFormMessageStop;
if (sender is Button pauseButton)
{
pauseButton.Text = isFormMessageStop ? "继续" : "暂停";
}
}
}
}
@@ -0,0 +1,123 @@
<?xml version="1.0" encoding="utf-8"?>
<root>
<!--
Microsoft ResX Schema
Version 2.0
The primary goals of this format is to allow a simple XML format
that is mostly human readable. The generation and parsing of the
various data types are done through the TypeConverter classes
associated with the data types.
Example:
... ado.net/XML headers & schema ...
<resheader name="resmimetype">text/microsoft-resx</resheader>
<resheader name="version">2.0</resheader>
<resheader name="reader">System.Resources.ResXResourceReader, System.Windows.Forms, ...</resheader>
<resheader name="writer">System.Resources.ResXResourceWriter, System.Windows.Forms, ...</resheader>
<data name="Name1"><value>this is my long string</value><comment>this is a comment</comment></data>
<data name="Color1" type="System.Drawing.Color, System.Drawing">Blue</data>
<data name="Bitmap1" mimetype="application/x-microsoft.net.object.binary.base64">
<value>[base64 mime encoded serialized .NET Framework object]</value>
</data>
<data name="Icon1" type="System.Drawing.Icon, System.Drawing" mimetype="application/x-microsoft.net.object.bytearray.base64">
<value>[base64 mime encoded string representing a byte array form of the .NET Framework object]</value>
<comment>This is a comment</comment>
</data>
There are any number of "resheader" rows that contain simple
name/value pairs.
Each data row contains a name, and value. The row also contains a
type or mimetype. Type corresponds to a .NET class that support
text/value conversion through the TypeConverter architecture.
Classes that don't support this are serialized and stored with the
mimetype set.
The mimetype is used for serialized objects, and tells the
ResXResourceReader how to depersist the object. This is currently not
extensible. For a given mimetype the value must be set accordingly:
Note - application/x-microsoft.net.object.binary.base64 is the format
that the ResXResourceWriter will generate, however the reader can
read any of the formats listed below.
mimetype: application/x-microsoft.net.object.binary.base64
value : The object must be serialized with
: System.Runtime.Serialization.Formatters.Binary.BinaryFormatter
: and then encoded with base64 encoding.
mimetype: application/x-microsoft.net.object.soap.base64
value : The object must be serialized with
: System.Runtime.Serialization.Formatters.Soap.SoapFormatter
: and then encoded with base64 encoding.
mimetype: application/x-microsoft.net.object.bytearray.base64
value : The object must be serialized into a byte array
: using a System.ComponentModel.TypeConverter
: and then encoded with base64 encoding.
-->
<xsd:schema id="root" xmlns="" xmlns:xsd="http://www.w3.org/2001/XMLSchema" xmlns:msdata="urn:schemas-microsoft-com:xml-msdata">
<xsd:import namespace="http://www.w3.org/XML/1998/namespace" />
<xsd:element name="root" msdata:IsDataSet="true">
<xsd:complexType>
<xsd:choice maxOccurs="unbounded">
<xsd:element name="metadata">
<xsd:complexType>
<xsd:sequence>
<xsd:element name="value" type="xsd:string" minOccurs="0" />
</xsd:sequence>
<xsd:attribute name="name" use="required" type="xsd:string" />
<xsd:attribute name="type" type="xsd:string" />
<xsd:attribute name="mimetype" type="xsd:string" />
<xsd:attribute ref="xml:space" />
</xsd:complexType>
</xsd:element>
<xsd:element name="assembly">
<xsd:complexType>
<xsd:attribute name="alias" type="xsd:string" />
<xsd:attribute name="name" type="xsd:string" />
</xsd:complexType>
</xsd:element>
<xsd:element name="data">
<xsd:complexType>
<xsd:sequence>
<xsd:element name="value" type="xsd:string" minOccurs="0" msdata:Ordinal="1" />
<xsd:element name="comment" type="xsd:string" minOccurs="0" msdata:Ordinal="2" />
</xsd:sequence>
<xsd:attribute name="name" type="xsd:string" use="required" msdata:Ordinal="1" />
<xsd:attribute name="type" type="xsd:string" msdata:Ordinal="3" />
<xsd:attribute name="mimetype" type="xsd:string" msdata:Ordinal="4" />
<xsd:attribute ref="xml:space" />
</xsd:complexType>
</xsd:element>
<xsd:element name="resheader">
<xsd:complexType>
<xsd:sequence>
<xsd:element name="value" type="xsd:string" minOccurs="0" msdata:Ordinal="1" />
</xsd:sequence>
<xsd:attribute name="name" type="xsd:string" use="required" />
</xsd:complexType>
</xsd:element>
</xsd:choice>
</xsd:complexType>
</xsd:element>
</xsd:schema>
<resheader name="resmimetype">
<value>text/microsoft-resx</value>
</resheader>
<resheader name="version">
<value>2.0</value>
</resheader>
<resheader name="reader">
<value>System.Resources.ResXResourceReader, System.Windows.Forms, Version=4.0.0.0, Culture=neutral, PublicKeyToken=b77a5c561934e089</value>
</resheader>
<resheader name="writer">
<value>System.Resources.ResXResourceWriter, System.Windows.Forms, Version=4.0.0.0, Culture=neutral, PublicKeyToken=b77a5c561934e089</value>
</resheader>
<metadata name="type.UserAddedColumn" type="System.Boolean, mscorlib, Version=4.0.0.0, Culture=neutral, PublicKeyToken=b77a5c561934e089">
<value>True</value>
</metadata>
</root>
+413
View File
@@ -0,0 +1,413 @@
# 🚀 充电桩管理系统 - 快速启动指南
## 📦 文件清单
已创建的文件:
```
Charge/
├── ChargeStation.cs # 充电桩数据模型
├── ChargeStationDataService.cs # 数据服务(单例)
├── ChargeStationManagementForm.cs # 管理窗口主类
├── ChargeStationManagementForm.Designer.cs # 窗口UI设计
├── ChargeStationManagementExample.cs # 示例代码
├── README_ChargeStationManagement.md # 详细使用说明
└── QUICKSTART.md # 本文件
```
## ⚡ 5分钟快速上手
### 步骤1:在主窗口添加菜单(推荐方式)
如果您的主窗口有菜单栏,添加一个菜单项:
```csharp
// 在主窗口的 InitializeComponent() 或构造函数中添加
// 方法1: 如果有工具栏
var btnChargeManagement = new ToolStripButton("充电桩管理");
btnChargeManagement.Click += (s, e) => {
var form = new StandardScene.Charge.ChargeStationManagementForm();
form.Show();
};
toolStrip.Items.Add(btnChargeManagement);
// 方法2: 如果有菜单栏
var menuItemCharge = new ToolStripMenuItem("充电桩管理(&C)");
menuItemCharge.Click += (s, e) => {
var form = new StandardScene.Charge.ChargeStationManagementForm();
form.Show();
};
menuStrip.Items.Add(menuItemCharge);
// 方法3: 如果有按钮面板
var btnChargeManagement = new Button
{
Text = "充电桩管理",
Size = new Size(120, 40),
Location = new Point(10, 10)
};
btnChargeManagement.Click += (s, e) => {
var form = new StandardScene.Charge.ChargeStationManagementForm();
form.Show();
};
this.Controls.Add(btnChargeManagement);
```
### 步骤2:初始化测试数据(首次运行)
在程序启动时或通过菜单调用:
```csharp
// 在主窗口的 Load 事件或启动代码中
StandardScene.Charge.ChargeStationManagementExample.InitializeTestData();
```
### 步骤3:打开管理窗口
点击您添加的菜单项或按钮,即可打开充电桩管理窗口。
---
## 🎯 集成到 AbstractChargeMission
如果您想将充电桩数据与充电任务关联,在 `AbstractChargeMission.cs` 中添加:
### 1. 引用命名空间
```csharp
using StandardScene.Charge;
```
### 2. 在选择充电站点时使用充电桩数据
```csharp
// 在 Execute() 方法的充电决策部分
var dataService = ChargeStationDataService.Instance;
// 获取空闲的充电桩
var idleStations = dataService.GetIdleStations();
// 根据充电桩的站点ID筛选
targetPlan = Commons.GetNearestPlan((Car)car, site =>
site.fields.ContainsKey("group") &&
site.fields.ContainsKey("Charge") &&
GetChargeType(car).Contains(site.fields["group"]) &&
idleStations.Any(s => s.SiteId == site.id) // 确保站点有空闲充电桩
);
```
### 3. 在开始充电时更新充电桩状态
```csharp
// 在车辆到达充电站时
public override void ArriveAction(Car car, Site site)
{
var dataService = ChargeStationDataService.Instance;
var station = dataService.GetAllStations()
.FirstOrDefault(s => s.SiteId == site.id && s.Status == ChargeStationStatus.Idle);
if (station != null)
{
dataService.UpdateStationStatus(station.StationId, ChargeStationStatus.Charging);
car.tags.Add("chargingStationId", station.StationId);
Diagnosis.Log($"车辆 {car.name} 开始在充电桩 {station.Name} 充电",
"Charge", true);
}
}
```
### 4. 在离开充电站时更新充电桩状态
```csharp
// 在车辆离开充电站时
public override void LeaveAction(Car car, Site site)
{
if (car.tags.TryGetValue("chargingStationId", out var stationId))
{
var dataService = ChargeStationDataService.Instance;
dataService.UpdateStationStatus(stationId, ChargeStationStatus.Idle);
car.tags.Remove("chargingStationId");
Diagnosis.Log($"车辆 {car.name} 充电完成,充电桩 {stationId} 恢复空闲",
"Charge", true);
}
}
```
---
## 📊 在主界面显示充电桩统计
在主窗口添加实时统计显示:
```csharp
// 添加一个 Timer 定时更新统计信息
private Timer chargeStationStatusTimer;
private Label lblChargeStationStatus;
private void InitializeChargeStationMonitor()
{
// 创建状态标签
lblChargeStationStatus = new Label
{
Text = "充电桩: 加载中...",
AutoSize = true,
Location = new Point(10, 10),
Font = new Font("微软雅黑", 10F, FontStyle.Bold)
};
this.Controls.Add(lblChargeStationStatus);
// 创建定时器(每3秒更新一次)
chargeStationStatusTimer = new Timer
{
Interval = 3000,
Enabled = true
};
chargeStationStatusTimer.Tick += UpdateChargeStationStatus;
chargeStationStatusTimer.Start();
}
private void UpdateChargeStationStatus(object sender, EventArgs e)
{
try
{
var dataService = StandardScene.Charge.ChargeStationDataService.Instance;
var stations = dataService.GetAllStations();
var idle = stations.Count(s => s.Status == StandardScene.Charge.ChargeStationStatus.Idle);
var charging = stations.Count(s => s.Status == StandardScene.Charge.ChargeStationStatus.Charging);
var fault = stations.Count(s => s.Status == StandardScene.Charge.ChargeStationStatus.Fault);
lblChargeStationStatus.Text = $"充电桩: 总数 {stations.Count} | " +
$"空闲 {idle} | 充电中 {charging} | 故障 {fault}";
// 根据状态设置颜色
if (fault > 0)
lblChargeStationStatus.ForeColor = Color.Red;
else if (idle == 0 && charging > 0)
lblChargeStationStatus.ForeColor = Color.Orange;
else
lblChargeStationStatus.ForeColor = Color.Green;
}
catch (Exception ex)
{
lblChargeStationStatus.Text = $"充电桩: 获取状态失败 - {ex.Message}";
lblChargeStationStatus.ForeColor = Color.Gray;
}
}
```
---
## 🔧 配置充电桩与站点的映射
### 方式1: 在站点属性中添加充电桩编号
修改地图站点的 `fields`
```csharp
// 为站点添加充电桩编号
site.fields.Add("ChargeStationId", "CS20240115123456");
```
### 方式2: 在充电桩管理界面直接设置站点ID
在充电桩管理窗口中,编辑充电桩时填写"站点ID"字段。
### 方式3: 自动关联(代码实现)
```csharp
// 自动将充电桩与最近的充电站点关联
public void AutoAssignStationsToSites()
{
var dataService = ChargeStationDataService.Instance;
var allStations = dataService.GetAllStations();
var chargeSites = SimpleLib.GetAllSites()
.Where(s => s.fields.ContainsKey("Charge"))
.ToList();
foreach (var station in allStations)
{
if (station.SiteId == null || station.SiteId == 0)
{
// 根据名称或其他规则自动匹配站点
var matchedSite = chargeSites.FirstOrDefault(s =>
s.fields.ContainsKey("name") &&
s.fields["name"].Contains(station.Name)
);
if (matchedSite != null)
{
station.SiteId = matchedSite.id;
dataService.UpdateStation(station, out _);
Diagnosis.Log($"自动关联充电桩 {station.Name} 到站点 {matchedSite.id}",
"ChargeStation", true);
}
}
}
}
```
---
## 📱 添加快捷键
为管理窗口添加快捷键(在主窗口):
```csharp
protected override bool ProcessCmdKey(ref Message msg, Keys keyData)
{
// Ctrl+C 打开充电桩管理
if (keyData == (Keys.Control | Keys.C))
{
var form = new StandardScene.Charge.ChargeStationManagementForm();
form.Show();
return true;
}
return base.ProcessCmdKey(ref msg, keyData);
}
```
---
## 🎨 自定义界面样式
如果需要调整窗口样式,修改 `ChargeStationManagementForm.Designer.cs`
```csharp
// 修改窗口大小
this.Size = new Size(1400, 800);
// 修改按钮颜色
btnAdd.BackColor = Color.FromArgb(144, 238, 144); // 浅绿色
btnSave.BackColor = Color.FromArgb(135, 206, 250); // 浅蓝色
btnDelete.BackColor = Color.FromArgb(255, 182, 193); // 浅红色
// 修改字体
this.Font = new Font("微软雅黑", 9F);
```
---
## 🐛 常见问题
### Q1: 窗口打不开
**A**: 检查是否正确引用了命名空间:
```csharp
using StandardScene.Charge;
```
### Q2: 数据保存失败
**A**: 确保 `Data` 文件夹有写入权限:
```bash
# Windows
右键 Data 文件夹 -> 属性 -> 安全 -> 确保当前用户有"写入"权限
```
### Q3: 找不到充电桩数据
**A**: 首次运行时需要初始化数据:
```csharp
ChargeStationManagementExample.InitializeTestData();
```
### Q4: 充电桩状态不更新
**A**: 手动刷新数据:
```csharp
ChargeStationDataService.Instance.Reload();
```
---
## 📚 进阶功能
### 实时监控充电桩通信状态
```csharp
// 定期 Ping 充电桩 IP
private async Task<bool> PingChargeStation(string ip, int port)
{
try
{
using (var client = new System.Net.Sockets.TcpClient())
{
var result = client.BeginConnect(ip, port, null, null);
var success = result.AsyncWaitHandle.WaitOne(TimeSpan.FromSeconds(3));
if (success)
{
client.EndConnect(result);
return true;
}
return false;
}
}
catch
{
return false;
}
}
```
### 充电桩数据可视化
```csharp
// 在主界面添加充电桩状态图表
private void DrawChargeStationChart(Graphics g)
{
var dataService = ChargeStationDataService.Instance;
var stations = dataService.GetAllStations();
int x = 10, y = 10, size = 40;
foreach (var station in stations)
{
Color color = station.Status switch
{
ChargeStationStatus.Idle => Color.Green,
ChargeStationStatus.Charging => Color.Yellow,
ChargeStationStatus.Fault => Color.Red,
ChargeStationStatus.Offline => Color.Gray,
_ => Color.White
};
g.FillRectangle(new SolidBrush(color), x, y, size, size);
g.DrawString(station.Name, this.Font, Brushes.Black, x, y + size + 5);
x += size + 10;
if (x > this.Width - 100)
{
x = 10;
y += size + 30;
}
}
}
```
---
## 🎉 完成
现在您已经完成了充电桩管理系统的集成!
**下一步**
1. ✅ 添加菜单项或按钮
2. ✅ 初始化测试数据
3. ✅ 打开管理窗口测试
4. ✅ 将充电桩数据集成到充电任务
5. ✅ 添加实时监控和统计
**需要帮助?**
- 查看 `README_ChargeStationManagement.md` 获取详细文档
- 参考 `ChargeStationManagementExample.cs` 查看示例代码
- 检查日志中的 `ChargeStation` 标签
---
**版本**: 1.0.0
**最后更新**: 2024-01-15
@@ -0,0 +1,310 @@
# 充电桩管理系统使用说明
## 📋 概述
充电桩管理系统是一个基于 WinForms 的可视化管理工具,用于管理 AGV 系统中的充电桩设备。
## 🚀 快速开始
### 打开管理窗口
```csharp
// 在代码中打开充电桩管理窗口
var form = new StandardScene.Charge.ChargeStationManagementForm();
form.ShowDialog();
// 或者在按钮点击事件中
private void btnOpenChargeManagement_Click(object sender, EventArgs e)
{
var form = new StandardScene.Charge.ChargeStationManagementForm();
form.Show();
}
```
### 添加菜单项(推荐)
在主窗口的菜单栏中添加:
```csharp
// 在主窗口的初始化代码中
var menuItem = new ToolStripMenuItem("充电桩管理");
menuItem.Click += (s, e) => {
var form = new StandardScene.Charge.ChargeStationManagementForm();
form.Show();
};
// 将 menuItem 添加到主菜单
```
## 📖 功能说明
### 1. 充电桩列表(左侧面板)
#### 功能特性
- **实时显示**:显示所有充电桩的详细信息
- **颜色标识**
- 🟢 **绿色**:充电中
- 🔴 **红色**:故障
-**灰色**:离线
-**白色**:空闲/其他状态
- **搜索功能**:支持按编号、名称、IP地址搜索
- **双击编辑**:双击列表项可快速编辑
#### 列表字段
| 字段 | 说明 | 示例 |
|-----|------|------|
| 编号 | 充电桩唯一标识 | CS20240115123456 |
| 名称 | 充电桩名称 | 1号充电桩 |
| IP地址 | 设备IP | 192.168.1.100 |
| 端口 | 通信端口 | 502 |
| 电压(V) | 额定电压 | 220.0 |
| 电流(A) | 额定电流 | 32.0 |
| 功率(W) | 计算功率 | 7040.0 |
| 状态 | 当前状态 | 空闲/充电中/故障 |
| 启用 | 是否启用 | 是/否 |
| 站点ID | 关联站点 | 1001 |
| 备注 | 备注信息 | 南区1号充电桩 |
### 2. 充电桩编辑(右侧面板)
#### 必填字段
-**编号**:自动生成(格式:CS+时间戳+随机数)
-**名称**:充电桩名称,便于识别
-**IP地址**:设备IP,必须为有效IP格式
-**端口**:通信端口(1-65535
-**电压**:额定电压(0-1000V
-**电流**:额定电流(0-500A
#### 选填字段
- 📌 **状态**:空闲/充电中/故障/离线/维护中/预约中
- 📌 **启用**:是否启用该充电桩
- 📌 **站点ID**:关联的站点编号(与地图站点关联)
- 📌 **备注**:额外说明信息
#### 自动计算
-**功率**:自动计算(电压 × 电流)
### 3. 操作按钮
#### 右侧编辑区
- 🆕 **新增**:清空表单,准备添加新充电桩
- 💾 **保存**:保存当前充电桩信息(新增或更新)
- 🗑️ **删除**:删除当前选中的充电桩
-**取消**:清空表单
#### 左侧列表区
- 🔄 **刷新**:重新加载数据
- 📤 **导出**:导出充电桩数据为 JSON 或 CSV 文件
## 🔒 数据验证规则
### IP地址验证
```
✅ 有效:192.168.1.100, 10.0.0.1, 172.16.0.1
❌ 无效:192.168.1, 256.1.1.1, abc.def.ghi.jkl
```
### 端口验证
```
✅ 有效:502, 8080, 1234
❌ 无效:0, 70000, -1
```
### 电压验证
```
✅ 有效:220V, 380V, 110V
❌ 无效:-10V, 1500V, 0V
```
### 电流验证
```
✅ 有效:32A, 16A, 63A
❌ 无效:-5A, 600A, 0A
```
### 唯一性验证
- ❌ 编号不能重复
- ❌ IP地址+端口组合不能重复
## 💾 数据存储
### 存储位置
```
项目根目录/Data/ChargeStations.json
```
### 数据格式
```json
[
{
"StationId": "CS20240115123456",
"Name": "1号充电桩",
"IpAddress": "192.168.1.100",
"Port": 502,
"Voltage": 220.0,
"Current": 32.0,
"Status": 0,
"Enabled": true,
"SiteId": 1001,
"Remarks": "南区1号充电桩",
"CreatedTime": "2024-01-15T12:34:56",
"ModifiedTime": "2024-01-15T14:20:30"
}
]
```
## 📊 代码集成
### 获取充电桩数据
```csharp
using StandardScene.Charge;
// 获取数据服务实例
var dataService = ChargeStationDataService.Instance;
// 获取所有充电桩
var allStations = dataService.GetAllStations();
// 获取空闲充电桩
var idleStations = dataService.GetIdleStations();
// 根据编号获取充电桩
var station = dataService.GetStationById("CS20240115123456");
// 获取充电中的充电桩数量
int chargingCount = dataService.GetChargingCount();
```
### 添加/更新充电桩
```csharp
// 创建新充电桩
var newStation = new ChargeStation
{
Name = "2号充电桩",
IpAddress = "192.168.1.101",
Port = 502,
Voltage = 220.0,
Current = 32.0
};
// 添加
if (dataService.AddStation(newStation, out string errorMsg))
{
Console.WriteLine("添加成功");
}
else
{
Console.WriteLine($"添加失败: {errorMsg}");
}
// 更新状态
dataService.UpdateStationStatus("CS20240115123456", ChargeStationStatus.Charging);
```
### 删除充电桩
```csharp
// 删除充电桩
if (dataService.DeleteStation("CS20240115123456", out string errorMsg))
{
Console.WriteLine("删除成功");
}
else
{
Console.WriteLine($"删除失败: {errorMsg}");
}
```
## ⚠️ 注意事项
1. **数据持久化**:所有数据自动保存到 JSON 文件,重启后数据不会丢失
2. **线程安全**:数据服务使用单例模式和锁机制,支持多线程访问
3. **状态管理**:充电中的充电桩无法删除,需先停止充电
4. **IP冲突检测**:系统会自动检测IP和端口的冲突
5. **数据备份**:建议定期备份 `Data/ChargeStations.json` 文件
## 🔧 扩展功能建议
### 与充电任务集成
`AbstractChargeMission.cs` 中集成充电桩数据:
```csharp
// 在充电任务中获取充电桩信息
private void SelectChargeStation(Car car)
{
var dataService = ChargeStationDataService.Instance;
var idleStations = dataService.GetIdleStations();
if (idleStations.Count > 0)
{
var nearestStation = FindNearestStation(car, idleStations);
// 更新充电桩状态
dataService.UpdateStationStatus(
nearestStation.StationId,
ChargeStationStatus.Reserved
);
// 分配车辆到充电桩
AssignCarToStation(car, nearestStation);
}
}
// 充电完成后
private void OnChargeComplete(Car car, ChargeStation station)
{
var dataService = ChargeStationDataService.Instance;
dataService.UpdateStationStatus(
station.StationId,
ChargeStationStatus.Idle
);
}
```
### 监控充电桩状态
```csharp
// 定期检查充电桩在线状态
private void MonitorChargeStations()
{
var dataService = ChargeStationDataService.Instance;
var stations = dataService.GetAllStations();
foreach (var station in stations)
{
if (station.Enabled)
{
bool isOnline = PingStation(station.IpAddress, station.Port);
var newStatus = isOnline
? ChargeStationStatus.Idle
: ChargeStationStatus.Offline;
if (station.Status != newStatus)
{
dataService.UpdateStationStatus(station.StationId, newStatus);
Diagnosis.Log($"充电桩 {station.Name} 状态变更: {newStatus}",
"ChargeStation", true);
}
}
}
}
```
## 📞 技术支持
如有问题,请检查:
1. `Data` 文件夹是否有写入权限
2. JSON 文件格式是否正确
3. 日志中的错误信息(标签:`ChargeStation`
---
**版本**: 1.0.0
**最后更新**: 2024-01-15
**作者**: MDCS System
@@ -0,0 +1,785 @@
using System;
using System.Collections.Generic;
using System.Linq;
using System.Net;
using System.Text;
using System.Threading;
using System.Threading.Tasks;
using System.Windows.Forms;
using CommonUsage;
using LessokajiWeaverUtilities.Utilities;
using Newtonsoft.Json;
using SimpleLite;
using SimpleLite.RCS;
using SimpleLite.RCS.CarTypes;
using SimpleCore;
using SimpleCore.Library;
using SimpleCore.PropType;
using StandardScene.Chained;
using StandardScene.ChargeStationType;
using StandardScene.Model;
namespace StandardScene.Charge
{
/// <summary>
/// 标准充电进程状态
/// </summary>
public class StandardChargeMissionStatus : AbstractChargeMissionStatus
{
/// <summary>
/// 是否屏蔽充电桩交互(true=屏蔽,false=允许)
/// </summary>
public bool ShieldInterLock = false;
}
/// <summary>
/// 标准充电进程
/// 负责充电桩的初始化、通讯管理和充电业务逻辑处理
/// </summary>
[MissionType(Name = "充电进程", editor = typeof(StandardChargeMission))]
[I18N.DocumentTranslation(Name = "Charge Mission", locale = "en")]
public class StandardChargeMission : AbstractChargeLogiceMission
{
#region
/// <summary>
/// 充电站字典 Key=站点ID, Value=充电站对象
/// </summary>
[JsonIgnore]
public Dictionary<int, AbstractChargeStation> ChargeStations;
/// <summary>
/// 进程状态
/// </summary>
public override MissionStatus status { get; set; } = new StandardChargeMissionStatus();
/// <summary>
/// 进程是否已启动
/// </summary>
[JsonIgnore]
public bool myStarted = false;
/// <summary>
/// 时间同步服务是否已启动
/// </summary>
[JsonIgnore]
public bool tsStarted = false;
/// <summary>
/// 充电处理线程
/// </summary>
[JsonIgnore]
private Thread ChargeThread;
/// <summary>
/// 当前正在充电的车辆 Key=车辆ID, Value=充电次数
/// </summary>
[JsonIgnore]
public Dictionary<string, int> inChargeCar = new();
/// <summary>
/// 上一次充电的车辆记录
/// </summary>
[JsonIgnore]
public Dictionary<string, int> lastinChargeCar = new();
/// <summary>
/// 充电开始时间 Key=车辆ID, Value=开始时间
/// </summary>
[JsonIgnore]
public Dictionary<string, DateTime> BeginTime = new();
/// <summary>
/// 充电条件状态
/// </summary>
[JsonIgnore]
public Dictionary<string, bool> Condition = new();
/// <summary>
/// 上一次充电条件状态
/// </summary>
[JsonIgnore]
public Dictionary<string, bool> lastCondition = new();
/// <summary>
/// 充电超时时间(小时)
/// </summary>
[JsonIgnore]
public double outtimeOfCharge = 0.5;
/// <summary>
/// UDP通讯服务
/// </summary>
[JsonIgnore]
public ChargeUdpService UdpService;
#endregion
#region
/// <summary>
/// 获取最低电量车辆的SOC值
/// 用于充电策略判断,找到系统中电量最低的空闲车辆
/// </summary>
/// <param name="car">当前车辆</param>
/// <param name="allChargeSite">所有充电站点列表</param>
/// <returns>最低电量值</returns>
public override float LowerCarSoc(AbstractCar car, List<Site> allChargeSite)
{
try
{
// 查找符合条件的最低电量车辆:
// 1. 车辆在有效站点上(GetLastSite != -1
// 2. 车辆有坐标信息(haveCoordination
// 3. 车辆未被占用(!occupied
// 4. 车辆未在充电(!charging
// 5. 车辆不在充电站点上
// 6. 车辆在线
var lowCar = SimpleLib.GetAllCars()
.OfType<Car>()
.ToList()
.FindAll(p =>
p.GetLastSite() != -1 &&
p.haveCoordination &&
!p.tags.Contains("occupied") &&
!p.tags.Contains("charging") &&
!allChargeSite.Contains(SimpleLib.GetSite(p.GetLastSite())) &&
IsOnlineCar((Car)p))
.OrderBy(p => Commons.CarValue(p, "Soc"))
.FirstOrDefault();
// 如果没有找到符合条件的车辆,返回当前车辆的电量
if (lowCar == null)
return (float)Commons.CarValue((Car)car, "Soc");
return (float)Commons.CarValue(lowCar, "Soc");
}
catch (Exception e)
{
Diagnosis.Post($"获取最低电量车辆失败: {e.Message}", "error");
return (float)0;
}
}
/// <summary>
/// 获取车辆的充电类型
/// 根据车辆字段判断是FRLD还是MuXing类型
/// </summary>
/// <param name="car">车辆对象</param>
/// <returns>充电类型字符串</returns>
public override string GetChargeType(AbstractCar car)
{
if (car is null)
{
return "";
}
// 优先判断FRLD类型
if (car.fields.ContainsKey("FRLD"))
return "FRLD";
// 其次判断MuXing类型
if (car.fields.ContainsKey("MuXing"))
return "MuXing";
// 默认返回MuXing
return "MuXing";
}
/// <summary>
/// 判断车辆是否在线
/// 通过车辆是否在有效站点上来判断
/// </summary>
/// <param name="car">车辆对象</param>
/// <returns>true=在线, false=离线</returns>
public override bool IsOnlineCar(Car car)
{
return car.GetLastSite() != -1;
}
/// <summary>
/// 车辆到达站点时的处理
/// 记录车辆到达充电站点的日志
/// </summary>
/// <param name="car">车辆对象</param>
/// <param name="site">站点对象</param>
public override void ArriveAction(Car car, Site site)
{
// 判断是否为充电站点
if (site.fields.TryGetValue("Charge", out var strStationId))
{
// 记录到达日志(模拟车辆除外)
if (!car.name.Contains("模拟"))
{
Diagnosis.Post($"{car.name}({car.id})到达充电站点{site.id}");
}
}
else
{
Diagnosis.Post($"arrived, site{site.id} is not charge site");
}
}
/// <summary>
/// 车辆离开站点时的处理
/// 记录车辆离开充电站点的日志
/// </summary>
/// <param name="car">车辆对象</param>
/// <param name="site">站点对象</param>
public override void LeaveAction(Car car, Site site)
{
// 判断是否为充电站点
if (site.fields.TryGetValue("Charge", out var strStationId))
{
// 记录离开日志(模拟车辆除外)
if (!car.name.Contains("模拟"))
{
Diagnosis.Post($"{car.name}({car.id})离开充电站点{site.id}");
}
}
else
{
Diagnosis.Post($"left, site{site.id} is not charge site");
}
}
/// <summary>
/// 站点筛选器
/// 判断站点是否为充电站点
/// </summary>
/// <param name="siteId">站点ID</param>
/// <returns>true=充电站点, false=非充电站点</returns>
public override bool SiteFilter(int siteId)
{
// 如果未屏蔽充电桩交互,返回false
if (((StandardChargeMissionStatus)status).ShieldInterLock)
{
return false;
}
// 检查站点字段中是否包含"Charge"关键字
var site = SimpleLib.GetSite(siteId);
return site.fields.Keys.ToList().Any(p => p.Contains("Charge"));
}
#endregion
#region
/// <summary>
/// 启动充电进程
/// 初始化充电站、创建通讯连接、启动充电业务循环
/// </summary>
[MethodMember(Name = "启动进程", Description = "开始处理充电维护进程")]
public override void Execute()
{
// 防止重复启动
if (myStarted)
{
MessageBox.Show("充电进程已启动,不可重复启动");
return;
}
status.status = "已启动";
myStarted = true;
int iteration = 0;
ChargeStations = new Dictionary<int, AbstractChargeStation>();
// 创建充电处理线程
ChargeThread = new Thread(() =>
{
ChargeStations = new Dictionary<int, AbstractChargeStation>();
while (true)
{
try
{
if (status.status.Contains("已停止"))
{
break;
}
var shieldInterLock = ((StandardChargeMissionStatus)status).ShieldInterLock;
// ==================== 步骤1: 初始化充电站 ====================
// 从充电桩管理配置中获取所有充电桩配置
var allStationConfigs = ChargeStationHelper.GetAllStationConfigs();
// 遍历所有充电桩配置,创建充电站实例
foreach (var stationConfig in allStationConfigs)
{
// 1.1 检查充电桩是否启用
if (!stationConfig.Enabled)
{
Diagnosis.Log($"ChargeStation[{stationConfig.StationId}] is disabled, skip");
continue;
}
// 1.2 验证站点ID
if (!stationConfig.SiteId.HasValue || stationConfig.SiteId.Value <= 0)
{
Diagnosis.Log($"ChargeStation[{stationConfig.StationId}] has invalid SiteId, skip");
continue;
}
int siteId = stationConfig.SiteId.Value;
// 1.4 验证IP地址格式
if (string.IsNullOrWhiteSpace(stationConfig.IpAddress) ||
!IPAddress.TryParse(stationConfig.IpAddress, out var ipAddress))
{
Diagnosis.Log($"ERR:ChargeStation[{stationConfig.StationId}] IP[{stationConfig.IpAddress}] is invalid");
continue;
}
// 1.5 验证端口范围
int port = stationConfig.Port;
if (port <= 0 || port > 65535)
{
Diagnosis.Log($"ERR:ChargeStation[{stationConfig.StationId}] Port[{port}] is invalid");
continue;
}
// 1.3 检查站点是否已存在,以及IP/端口是否变更
if (ChargeStations.ContainsKey(siteId))
{
var existingStation = ChargeStations[siteId];
// 检查IP或端口是否变更
if (existingStation.Ip != ipAddress.ToString() ||
existingStation.Port != port)
{
Diagnosis.Log($"ChargeStation[{stationConfig.StationId}] IP/Port changed from [{existingStation.Ip}:{existingStation.Port}] to [{ipAddress}:{port}], recreating connection...");
// 先关闭旧连接
try
{
existingStation.CloseCommunication();
}
catch (Exception ex)
{
Diagnosis.Log($"WARN:ChargeStation[{stationConfig.StationId}] failed to close old connection: {ex.Message}");
}
// 更新IP和端口
existingStation.Ip = ipAddress.ToString();
existingStation.Port = port;
// 重新创建通讯连接
try
{
existingStation.CreateCommunication(ipAddress, port);
Diagnosis.Log($"ChargeStation[{stationConfig.StationId}] connection recreated successfully");
}
catch (Exception ex)
{
Diagnosis.Log($"ERR:ChargeStation[{stationConfig.StationId}] failed to recreate connection: {ex.Message}");
}
}
// 站点已存在且IP/端口未变更,跳过
continue;
}
// 1.6 根据充电桩类型创建对应的充电站对象
string chargeTypeString = GetChargeTypeString(stationConfig.Type);
// 跨程序集解析:充电桩具体类型可能位于卫星插件 dllStandardScene.Devices.Charge),
// 不能再用 Type.GetType(简单名,仅当前程序集)。改用内核同款全域类型发现。
string chargeTypeFullName = "StandardScene.ChargeStationType." + chargeTypeString;
Type type = SimpleLite.Utils.UiTypeDiscovery.AllTypes()
.FirstOrDefault(t => t.FullName == chargeTypeFullName);
if (type == null)
{
Diagnosis.Log($"ERR:ChargeStation[{stationConfig.StationId}] Type[{chargeTypeString}] not found");
continue;
}
// 1.7 创建充电站实例并配置基本信息
object chargeStation = Activator.CreateInstance(type);
((AbstractChargeStation)chargeStation).SiteId = siteId;
((AbstractChargeStation)chargeStation).Ip = ipAddress.ToString();
((AbstractChargeStation)chargeStation).Port = port;
// 1.8 设置通讯类型(从配置读取,默认TCP)
if (stationConfig.Type == ChargeStationType.FRLDShort)
{
((AbstractChargeStation)chargeStation).CommunicationType = "UDP";
}
else
{
((AbstractChargeStation)chargeStation).CommunicationType = stationConfig.CommunicationType;
}
//((AbstractChargeStation)chargeStation).CommunicationType =
// string.IsNullOrWhiteSpace(stationConfig.CommunicationType)
// ? "UDP"
// : stationConfig.CommunicationType.ToUpper();
// 1.9 创建通讯连接
((AbstractChargeStation)chargeStation).CreateCommunication(ipAddress, port);
// 1.10 添加到充电站字典
ChargeStations.Add(siteId, (AbstractChargeStation)chargeStation);
Diagnosis.Log($"ChargeStation ADD: StationId[{stationConfig.StationId}] SiteId[{siteId}] IP[{ipAddress}:{port}] Type[{chargeTypeString}] Comm[{((AbstractChargeStation)chargeStation).CommunicationType}]");
}
// ==================== 步骤2: 初始化UDP服务 ====================
// 如果有任意充电桩使用UDP通讯,则创建UDP服务
if (ChargeStations.Any(c => c.Value.CommunicationType == "UDP"))
{
UdpService ??= new ChargeUdpService();
}
status.status = $"已启动-循环{iteration++}";
// ==================== 步骤3: 充电业务处理循环 ====================
foreach (var item in ChargeStations.Keys.ToArray()) //移除配置
{
var chargeStationSetting = ChargeStationHelper.GetStationBySiteId(item);
if (chargeStationSetting == null)
{
try
{
ChargeStations[item].CloseCommunication();
}
catch (Exception ex)
{
Diagnosis.Log($"WARN:ChargeStationHelper [{item}] failed to close old connection: {ex.Message}");
}
ChargeStations.Remove(item);
}
}
//S站点存在配置里没有的,需要移除配置
var sites = SimpleLib.GetAllSites().Where(s => s.fields.ContainsKey("Charge"));
foreach (var item in sites)
{
if (!ChargeStations.Keys.Contains(item.id))
{
//移除站点的配置
item.fields.Remove("Charge");
item.fields.Remove("setVoltage");
item.fields.Remove("setElectricCurrent");
item.fields.Remove("group");
Diagnosis.Post($"Charge {item.name}-{item.id} 未在充电管理配置移除参数");
item.name = "NoName";
//并关闭对应的连接
}
}
// 遍历所有已添加的充电站,处理充电业务逻辑
foreach (var chargeStationEntry in ChargeStations)
{
var openCharge = 0;
int siteId = chargeStationEntry.Key;
var chargeStation = chargeStationEntry.Value;
var site = SimpleLib.GetSite(siteId);
if (site == null)
{
Console.WriteLine($"从地图中未获取到站点的信息 siteId {siteId}");
continue;
}
var chargeStationSetting = ChargeStationHelper.GetStationBySiteId(siteId);
if (!chargeStationSetting.Enabled)
{
continue;
}
//绑定group 添加charge
if (site != null)
{
site.name = chargeStationSetting.Name;
site.fields["Charge"] = "True";
site.fields["setVoltage"] = chargeStationSetting.SetVoltage.ToString("0.0");
site.fields["setElectricCurrent"] = chargeStationSetting.SetElectricCurrent.ToString("0.0");
if (chargeStationSetting.Enabled)
{
site.fields["group"] = chargeStationSetting.GroupCarType.ToString();
}
else
{
site.fields["group"] = "禁停";
}
}
// 3.1 设置站点访问权限(默认允许进入和离开)
if (chargeStationSetting.ChargeMethod == ChargeMethodType.Side)
{
SetAllowEnter(siteId, chargeStationSetting.ShieldSiteMechanismStatus || chargeStationSetting.MechanismStatus == MechanismStatus.Retracted);
SetAllowExit(siteId, chargeStationSetting.ShieldSiteMechanismStatus || chargeStationSetting.MechanismStatus == MechanismStatus.Retracted);
SetAcknowledgeLeave(siteId, true);
}
else
{
SetAllowEnter(siteId, true);
SetAllowExit(siteId, true);
SetAcknowledgeLeave(siteId, true);
}
// 3.2 查找当前在充电站点的车辆
// 条件:车辆在站点上 或 正在获取站点锁 或 持有站点锁
var car = SimpleLib
.GetAllCars()
.FirstOrDefault(c =>
c.GetLastSite() == siteId ||
c.status.aquiringLock == siteId ||
c.status.holdingLocks.Contains(siteId)
);
// 3.3 如果找到车辆,处理充电逻辑
if (car != null)
{
// 3.3.1 检查车辆状态是否正常
if (Commons.GetVehicleStatus((Car)car) != VehicleStatus.Normal&&car.fields.ContainsKey("SkipStatus"))
{
continue;
}
// 3.3.2 判断车辆是否正在充电
var charging = car.tags.Contains("charging");
// 3.3.3 检查充电条件
// 条件:未被占用 && 未获取其他锁 && 正在充电标记
if (!car.tags.Contains("occupied")
&& car.status.holdingLocks.Length == 1 && car.status.pendingLocks.Length == 0 &&
charging)
{
openCharge = 1; // 允许充电
//// 3.3.4 安全检查:验证叉齿是否升起
//var actualLiftPillar = 1; // 默认已升起
//if (car.status.enums.TryGetValue("actualLiftPillar", out var liftPillar))
//{
// actualLiftPillar = Convert.ToInt32(liftPillar);
//}
//// 如果叉齿未升起,禁止充电
//if (actualLiftPillar != 1)
//{
// openCharge = 0;
// Console.WriteLine($"{car.id} 小车不满足充电的安全条件 叉齿未抬升");
// // TODO: 触发报警,通知人员处理
//}
}
}
// 3.3.5 发送充电指令(如果未屏蔽充电桩交互)
if (!shieldInterLock)
{
Diagnosis.Log($"向充电站[{siteId}]发送充电指令, 车辆[{car?.id}], 指令[{openCharge}]","Charge",true);
chargeStation.SendToChargeStation(openCharge, (Car)car, site);
}
}
}
catch (Exception ex)
{
Diagnosis.Post(
$"StandardChargeMission Error: {ExceptionFormatter.FormatEx(ex)}",
"error"
);
}
// 等待500ms后进行下一次循环
Thread.Sleep(500);
}
})
{
Name = "StandardChargeMission",
IsBackground = true
};
// 启动充电处理线程
ChargeThread.Start();
// ==================== 步骤4: 启动站点禁用状态上传任务 ====================
// 定期将禁用站点信息上传到迷毂系统
Task.Factory.StartNew(() =>
{
HttpPostData httpPostData = new HttpPostData();
while (true)
{
try
{
if (status.status.Contains("已停止"))
{
break;
}
// 4.1 获取所有标记为"unavailable"的站点
var sites = SimpleLib
.GetAllSites()
.Where(site => site.tags.Contains("unavailable"))
.ToList();
// 4.2 收集所有需要禁用的站点ID
HashSet<int> disabledSites = new HashSet<int>();
foreach (var site in sites)
{
// 添加当前站点
disabledSites.Add(site.id);
// 4.3 检查并添加关联的必须释放站点(mustFree)
if (site.fields.ContainsKey("mustFree") &&
site.mustFree != null &&
site.mustFree.Length > 0)
{
for (int i = 0; i < site.mustFree.Length; i++)
{
disabledSites.Add(site.mustFree[i]);
}
}
}
// 4.4 上传禁用站点列表到迷毂系统
Diagnosis.Log($"向迷毂提供禁用站点,共 {disabledSites.Count} 个", "siteIsEnable", true);
httpPostData.UploadListNode(disabledSites);
// 等待500ms后进行下一次上传
Thread.Sleep(500);
}
catch (Exception e)
{
Diagnosis.Post($"上传禁用站点失败: {ExceptionFormatter.FormatEx(e)}", "禁用站点");
}
}
}, TaskCreationOptions.LongRunning);
// 调用基类Execute方法
base.Execute();
}
/// <summary>
/// 关闭充电进程
/// 停止所有相关线程
/// </summary>
[MethodMember(Name = "关闭进程", Description = "关闭充电进程")]
public void Stop()
{
try
{
started = false;
myStarted = false;
// 先置停止状态,循环体检测到“已停止”后会自行 break
status.status = "已停止";
// 协作式停止:等待工作线程在下一次循环检测标志后退出(不再使用 .NET8 已不支持的 Thread.Abort
myThread?.Join(2000);
ChargeThread?.Join(2000);
Diagnosis.Log("充电进程已停止");
foreach (var item in ChargeStations.Values)
{
item.CloseCommunication();
Diagnosis.Post($"充电进程已停止,关闭充电通讯连接{item.Ip}-{item.Port}");
}
}
catch (Exception ex)
{
Diagnosis.Post($"充电进程已停止 {ex.ToString()}");
}
}
/// <summary>
/// 切换充电桩交互屏蔽状态
/// true=屏蔽交互,false=允许交互
/// </summary>
[MethodMember(Name = "切换充电桩交互状态", Description = "屏蔽/允许充电桩交互")]
public void ShieldInterLock()
{
var currentStatus = ((StandardChargeMissionStatus)status).ShieldInterLock;
((StandardChargeMissionStatus)status).ShieldInterLock = !currentStatus;
string statusText = ((StandardChargeMissionStatus)status).ShieldInterLock ? "已屏蔽" : "已允许";
Console.WriteLine($"充电桩交互状态: {statusText}");
Diagnosis.Log($"充电桩交互状态切换为: {statusText}");
}
/// <summary>
/// 打开充电桩管理界面
/// 用于配置和监控充电桩
/// </summary>
[MethodMember(Name = "打开充电桩管理界面", Description = "打开充电桩管理界面")]
public void OpenManagementWindow()
{
ChargeStationHelper.OpenManagementWindow();
}
#endregion
#region
/// <summary>
/// 将充电桩类型枚举转换为类型字符串
/// 用于通过反射创建对应的充电站对象
/// </summary>
/// <param name="type">充电桩类型枚举</param>
/// <returns>充电站类名</returns>
private string GetChargeTypeString(ChargeStationType type)
{
switch (type)
{
case ChargeStationType.FRLDTall:
return "FLChargeStation";
case ChargeStationType.FRLDShort:
return "PCBChargeStation";
case ChargeStationType.MuXing:
return "MuXingChargeStation";
default:
// 默认使用FRLD矮款充电桩
return "PCBChargeStation";
}
}
/// <summary>
/// 给车辆下发充电任务
/// 为测试或调试用途,手动给车辆添加shouldCharge标签
/// </summary>
[MethodMember(Name = "小车下发充电任务", Description = "给小车下发充电任务")]
public void addtagshuldcharge()
{
// 查找第一个在有效站点上的车辆
var car = (Car)SimpleLib.GetAllCars()
.ToList()
.Find(c => c.GetLastSite() != -1);
if (car != null)
{
// 添加shouldCharge标签
Commons.AddOrUpdateTag(car.tags, "shouldCharge", "true");
Diagnosis.Log($"已为车辆 {car.id} 添加充电任务标签");
}
else
{
Diagnosis.Log("未找到在有效站点上的车辆");
}
}
#endregion
}
}