init commit
This commit is contained in:
File diff suppressed because it is too large
Load Diff
@@ -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>
|
||||
@@ -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>
|
||||
@@ -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);
|
||||
// 跨程序集解析:充电桩具体类型可能位于卫星插件 dll(StandardScene.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
|
||||
|
||||
|
||||
}
|
||||
}
|
||||
Reference in New Issue
Block a user