417 lines
17 KiB
C#
417 lines
17 KiB
C#
using System;
|
|
using System.Collections.Generic;
|
|
using System.IO;
|
|
using System.Linq;
|
|
using System.Net;
|
|
using System.Reflection;
|
|
using System.Text;
|
|
using System.Text.RegularExpressions;
|
|
using System.Threading.Tasks;
|
|
using FairyView;
|
|
using StandardScene.Utils;
|
|
|
|
namespace StandardScene.ExtendDevice.Door
|
|
{
|
|
/// <summary>
|
|
/// 门控制器配置管理(CycleGUI 版,替代原 WinForms <c>DoorManager</c> 窗体)。
|
|
/// </summary>
|
|
public class DoorManager
|
|
{
|
|
private const string DataFileName = "DoorConfig.json";
|
|
private string DataFilePath => Path.Combine(AppDomain.CurrentDomain.BaseDirectory, DataFileName);
|
|
private readonly object SaveLock = new object();
|
|
|
|
private Panel _panel;
|
|
private List<DoorControllerModel> _controllers = new List<DoorControllerModel>();
|
|
private int _selectedCtrlIdx = -1;
|
|
private int _selectedDoorIdx = -1;
|
|
private string _status = "";
|
|
|
|
private string _ctrlIp = "";
|
|
private string _ctrlPort = "502";
|
|
private string _ctrlIndex = "";
|
|
private int _typeIdx;
|
|
private string[] _typeNames = Array.Empty<string>();
|
|
|
|
private string _doorIndex = "";
|
|
private string _doorCtrlAddr = "";
|
|
private string _doorOpenAddr = "";
|
|
private bool _doorNoControl;
|
|
|
|
public static void OpenViewer() => Open();
|
|
|
|
public static void Open() => (_instance ??= new DoorManager()).OpenCore();
|
|
|
|
private static DoorManager _instance;
|
|
|
|
private void OpenCore()
|
|
{
|
|
if (_panel != null)
|
|
{
|
|
try { _panel.BringToFront(); return; }
|
|
catch { _panel = null; }
|
|
}
|
|
|
|
_typeNames = DiscoverTypes();
|
|
LoadData();
|
|
_selectedCtrlIdx = -1;
|
|
_selectedDoorIdx = -1;
|
|
ClearControllerFields();
|
|
ClearDoorFields();
|
|
|
|
var panel = GUI.DeclarePanel()
|
|
.ShowTitle("门控制器管理")
|
|
.ForceFloat()
|
|
.InitSize(1100, 720)
|
|
.InitPos(false, 0, 0, 0.5f, 0.5f, 0.5f, 0.5f);
|
|
_panel = panel;
|
|
panel.IfTerminalQuit(() => { if (_panel == panel) _panel = null; });
|
|
|
|
panel.Define(pb =>
|
|
{
|
|
if (pb.Closing())
|
|
{
|
|
SaveData();
|
|
panel.Exit();
|
|
_panel = null;
|
|
return;
|
|
}
|
|
|
|
pb.SeparatorText("门控制器");
|
|
pb.Table("door-ctrl-list", new[] { "控制器编码", "IP", "端口", "类型", "Ctrl操作" },
|
|
_controllers.Count, (row, i) =>
|
|
{
|
|
var c = _controllers[i];
|
|
row.Label($"{c.Index}");
|
|
row.Label(c.Ip);
|
|
row.Label($"{c.Port}");
|
|
row.Label(c.Type);
|
|
if (row.ButtonGroup(new[] { "选控制器" }, new[] { "选择该控制器" }) == 0)
|
|
{
|
|
_selectedCtrlIdx = i;
|
|
_selectedDoorIdx = -1;
|
|
LoadControllerFields(c);
|
|
ClearDoorFields();
|
|
}
|
|
}, height: CycleUiHelper.TableHeightPx(8), enableSearch: true);
|
|
|
|
pb.SeparatorText("控制器编辑");
|
|
var (ip, _) = pb.TextInput("1. IP", _ctrlIp, alwaysReturnString: true);
|
|
_ctrlIp = ip;
|
|
pb.SameLine(12);
|
|
var (port, _) = pb.TextInput("2. 端口", _ctrlPort, alwaysReturnString: true);
|
|
_ctrlPort = port;
|
|
var (idx, _) = pb.TextInput("3. 控制器编码", _ctrlIndex, alwaysReturnString: true);
|
|
_ctrlIndex = idx;
|
|
if (_typeNames.Length > 0)
|
|
pb.DropdownBox("4. 类型", _typeNames, ref _typeIdx);
|
|
|
|
if (pb.Button("添加控制器", distinct: "door-add-ctrl")) AddController();
|
|
pb.SameLine(8);
|
|
if (pb.Button("保存控制器", distinct: "door-save-ctrl")) SaveController();
|
|
pb.SameLine(8);
|
|
if (pb.Button("删除控制器", distinct: "door-del-ctrl")) ConfirmDeleteController();
|
|
|
|
var doors = GetSelectedController()?.Doors ?? new List<DoorModel>();
|
|
pb.SeparatorText($"门列表(控制器 #{GetSelectedController()?.Index ?? 0},共 {doors.Count} 扇)");
|
|
pb.Table("door-list", new[] { "门编码", "控制地址", "开到位地址", "无控制", "门操作" },
|
|
doors.Count, (row, i) =>
|
|
{
|
|
var d = doors[i];
|
|
row.Label($"{d.Index}");
|
|
row.Label($"{d.ControlAddress}");
|
|
row.Label($"{d.OpenStatusAddress}");
|
|
row.Label(d.NoControl ? "是" : "否");
|
|
if (row.ButtonGroup(new[] { "选门" }, new[] { "选择该门" }) == 0)
|
|
{
|
|
_selectedDoorIdx = i;
|
|
LoadDoorFields(d);
|
|
}
|
|
}, height: CycleUiHelper.TableHeightPx(6), enableSearch: true);
|
|
|
|
pb.SeparatorText("门编辑");
|
|
var (dIdx, _) = pb.TextInput("5. 门编码", _doorIndex, alwaysReturnString: true);
|
|
_doorIndex = dIdx;
|
|
var (cAddr, _) = pb.TextInput("6. 控制地址", _doorCtrlAddr, alwaysReturnString: true);
|
|
_doorCtrlAddr = cAddr;
|
|
var (oAddr, _) = pb.TextInput("7. 开到位地址", _doorOpenAddr, alwaysReturnString: true);
|
|
_doorOpenAddr = oAddr;
|
|
pb.CheckBox("8. 无控制", ref _doorNoControl);
|
|
|
|
if (pb.Button("添加门", distinct: "door-add")) AddDoor();
|
|
pb.SameLine(8);
|
|
if (pb.Button("保存门", distinct: "door-save")) SaveDoor();
|
|
pb.SameLine(8);
|
|
if (pb.Button("删除门", distinct: "door-del")) ConfirmDeleteDoor();
|
|
|
|
if (!string.IsNullOrEmpty(_status))
|
|
{
|
|
pb.Separator();
|
|
pb.Label(_status);
|
|
}
|
|
});
|
|
}
|
|
|
|
private DoorControllerModel GetSelectedController() =>
|
|
_selectedCtrlIdx >= 0 && _selectedCtrlIdx < _controllers.Count ? _controllers[_selectedCtrlIdx] : null;
|
|
|
|
private DoorModel GetSelectedDoor()
|
|
{
|
|
var ctrl = GetSelectedController();
|
|
return ctrl != null && _selectedDoorIdx >= 0 && _selectedDoorIdx < ctrl.Doors.Count
|
|
? ctrl.Doors[_selectedDoorIdx] : null;
|
|
}
|
|
|
|
private string[] DiscoverTypes()
|
|
{
|
|
try
|
|
{
|
|
var names = Simple3.Utils.UiTypeDiscovery.AllTypes()
|
|
.Where(t => t.IsClass && !t.IsAbstract
|
|
&& t.Namespace == typeof(BasicDoorController).Namespace
|
|
&& t.IsSubclassOf(typeof(BasicDoorController))
|
|
&& t.IsDefined(typeof(DoorTypeAttribute), false))
|
|
.Select(t => t.GetCustomAttribute<DoorTypeAttribute>()?.Name)
|
|
.Where(n => !string.IsNullOrWhiteSpace(n))
|
|
.OrderBy(n => n)
|
|
.ToArray();
|
|
return names.Length > 0 ? names : new[] { "ModbusDoorController" };
|
|
}
|
|
catch
|
|
{
|
|
return new[] { "ModbusDoorController" };
|
|
}
|
|
}
|
|
|
|
private void LoadData()
|
|
{
|
|
try
|
|
{
|
|
if (!File.Exists(DataFilePath)) { _controllers = new List<DoorControllerModel>(); return; }
|
|
var json = File.ReadAllText(DataFilePath, Encoding.UTF8);
|
|
_controllers = string.IsNullOrWhiteSpace(json)
|
|
? new List<DoorControllerModel>()
|
|
: json.JsonTo<List<DoorControllerModel>>() ?? new List<DoorControllerModel>();
|
|
}
|
|
catch (Exception ex)
|
|
{
|
|
_controllers = new List<DoorControllerModel>();
|
|
_status = $"加载失败: {ex.Message}";
|
|
}
|
|
}
|
|
|
|
private void SaveData()
|
|
{
|
|
var json = _controllers.ToJson();
|
|
var path = DataFilePath;
|
|
Task.Run(() =>
|
|
{
|
|
try
|
|
{
|
|
lock (SaveLock)
|
|
File.WriteAllText(path, json, Encoding.UTF8);
|
|
}
|
|
catch (Exception ex)
|
|
{
|
|
_status = $"保存失败: {ex.Message}";
|
|
_panel?.Repaint();
|
|
}
|
|
});
|
|
}
|
|
|
|
private void LoadControllerFields(DoorControllerModel c)
|
|
{
|
|
_ctrlIp = c.Ip;
|
|
_ctrlPort = c.Port.ToString();
|
|
_ctrlIndex = c.Index.ToString();
|
|
_typeIdx = Math.Max(0, Array.IndexOf(_typeNames, c.Type));
|
|
if (_typeIdx < 0) _typeIdx = 0;
|
|
}
|
|
|
|
private void ClearControllerFields()
|
|
{
|
|
_ctrlIp = "";
|
|
_ctrlPort = "502";
|
|
_ctrlIndex = "";
|
|
_typeIdx = 0;
|
|
}
|
|
|
|
private void LoadDoorFields(DoorModel d)
|
|
{
|
|
_doorIndex = d.Index.ToString();
|
|
_doorCtrlAddr = d.ControlAddress.ToString();
|
|
_doorOpenAddr = d.OpenStatusAddress.ToString();
|
|
_doorNoControl = d.NoControl;
|
|
}
|
|
|
|
private void ClearDoorFields()
|
|
{
|
|
_doorIndex = "";
|
|
_doorCtrlAddr = "";
|
|
_doorOpenAddr = "";
|
|
_doorNoControl = false;
|
|
}
|
|
|
|
private void AddController()
|
|
{
|
|
if (!TryParseControllerInput(out var index, out var ip, out var port, out var type, false, out var err))
|
|
{
|
|
CycleUiHelper.Alert("错误", err);
|
|
return;
|
|
}
|
|
if (_controllers.Any(c => c.Index == index)) { CycleUiHelper.Alert("错误", $"编码 {index} 已存在"); return; }
|
|
if (_controllers.Any(c => c.Ip == ip)) { CycleUiHelper.Alert("错误", $"IP {ip} 已存在"); return; }
|
|
|
|
var c = new DoorControllerModel { Index = index, Ip = ip, Port = port, Type = type };
|
|
_controllers.Add(c);
|
|
_selectedCtrlIdx = _controllers.Count - 1;
|
|
SaveData();
|
|
_status = "已添加门控制器";
|
|
_panel?.Repaint();
|
|
}
|
|
|
|
private void SaveController()
|
|
{
|
|
var cur = GetSelectedController();
|
|
if (cur == null) { CycleUiHelper.Alert("提示", "请先选择要保存的门控制器"); return; }
|
|
if (!TryParseControllerInput(out var index, out var ip, out var port, out var type, true, out var err))
|
|
{
|
|
CycleUiHelper.Alert("错误", err);
|
|
return;
|
|
}
|
|
if (_controllers.Any(c => c.Index == index && c != cur)) { CycleUiHelper.Alert("错误", $"编码 {index} 已存在"); return; }
|
|
if (_controllers.Any(c => c.Ip == ip && c != cur)) { CycleUiHelper.Alert("错误", $"IP {ip} 已存在"); return; }
|
|
|
|
cur.Index = index;
|
|
cur.Ip = ip;
|
|
cur.Port = port;
|
|
cur.Type = type;
|
|
SaveData();
|
|
_status = "门控制器已保存";
|
|
CycleUiHelper.Alert("提示", "保存成功");
|
|
_panel?.Repaint();
|
|
}
|
|
|
|
private void ConfirmDeleteController()
|
|
{
|
|
var cur = GetSelectedController();
|
|
if (cur == null) { CycleUiHelper.Alert("提示", "请选择要删除的门控制器"); return; }
|
|
CycleUiHelper.ConfirmThen($"删除编码为 {cur.Index} 的门控制器?", () =>
|
|
{
|
|
_controllers.Remove(cur);
|
|
_selectedCtrlIdx = -1;
|
|
_selectedDoorIdx = -1;
|
|
ClearControllerFields();
|
|
ClearDoorFields();
|
|
SaveData();
|
|
_status = "已删除门控制器";
|
|
_panel?.Repaint();
|
|
});
|
|
}
|
|
|
|
private void AddDoor()
|
|
{
|
|
var ctrl = GetSelectedController();
|
|
if (ctrl == null) { CycleUiHelper.Alert("提示", "请先选择门控制器"); return; }
|
|
if (!TryParseDoorInput(out var index, out var cAddr, out var oAddr, false, out var err))
|
|
{
|
|
CycleUiHelper.Alert("错误", err);
|
|
return;
|
|
}
|
|
if (ctrl.Doors.Any(d => d.Index == index)) { CycleUiHelper.Alert("错误", $"门编码 {index} 已存在"); return; }
|
|
|
|
var door = new DoorModel
|
|
{
|
|
Index = index,
|
|
ControlAddress = cAddr,
|
|
OpenStatusAddress = oAddr,
|
|
NoControl = _doorNoControl
|
|
};
|
|
ctrl.Doors.Add(door);
|
|
_selectedDoorIdx = ctrl.Doors.Count - 1;
|
|
SaveData();
|
|
_status = "已添加门";
|
|
_panel?.Repaint();
|
|
}
|
|
|
|
private void SaveDoor()
|
|
{
|
|
var ctrl = GetSelectedController();
|
|
var door = GetSelectedDoor();
|
|
if (ctrl == null || door == null) { CycleUiHelper.Alert("提示", "请先选择要保存的门"); return; }
|
|
if (!TryParseDoorInput(out var index, out var cAddr, out var oAddr, true, out var err))
|
|
{
|
|
CycleUiHelper.Alert("错误", err);
|
|
return;
|
|
}
|
|
if (ctrl.Doors.Any(d => d.Index == index && d != door)) { CycleUiHelper.Alert("错误", $"门编码 {index} 已存在"); return; }
|
|
|
|
door.Index = index;
|
|
door.ControlAddress = cAddr;
|
|
door.OpenStatusAddress = oAddr;
|
|
door.NoControl = _doorNoControl;
|
|
SaveData();
|
|
_status = "门已保存";
|
|
CycleUiHelper.Alert("提示", "保存成功");
|
|
_panel?.Repaint();
|
|
}
|
|
|
|
private void ConfirmDeleteDoor()
|
|
{
|
|
var ctrl = GetSelectedController();
|
|
var door = GetSelectedDoor();
|
|
if (ctrl == null || door == null) { CycleUiHelper.Alert("提示", "请选择要删除的门"); return; }
|
|
CycleUiHelper.ConfirmThen($"删除编码为 {door.Index} 的门?", () =>
|
|
{
|
|
ctrl.Doors.Remove(door);
|
|
_selectedDoorIdx = -1;
|
|
ClearDoorFields();
|
|
SaveData();
|
|
_status = "已删除门";
|
|
_panel?.Repaint();
|
|
});
|
|
}
|
|
|
|
private bool TryParseControllerInput(out int index, out string ip, out int port, out string type, bool requireSelection, out string err)
|
|
{
|
|
index = 0; ip = ""; port = 502; type = ""; err = "";
|
|
ip = string.IsNullOrWhiteSpace(_ctrlIp) ? "192.168.1.100" : _ctrlIp.Trim();
|
|
if (!IsValidIp(ip)) { err = "无效的 IP 地址"; return false; }
|
|
if (!int.TryParse(_ctrlPort.Trim(), out port)) { err = "端口必须是数字"; return false; }
|
|
if (!int.TryParse(_ctrlIndex.Trim(), out index))
|
|
{
|
|
index = _controllers.Count > 0 ? _controllers.Max(c => c.Index) + 1 : 1;
|
|
if (!string.IsNullOrWhiteSpace(_ctrlIndex.Trim())) { err = "编码必须是数字"; return false; }
|
|
}
|
|
type = _typeNames.Length > 0 ? _typeNames[_typeIdx] : "ModbusDoorController";
|
|
if (requireSelection && GetSelectedController() == null) { err = "请先选择门控制器"; return false; }
|
|
return true;
|
|
}
|
|
|
|
private bool TryParseDoorInput(out int index, out ushort cAddr, out ushort oAddr, bool requireSelection, out string err)
|
|
{
|
|
index = 0; cAddr = 0; oAddr = 0; err = "";
|
|
var ctrl = GetSelectedController();
|
|
if (requireSelection && (ctrl == null || GetSelectedDoor() == null)) { err = "请先选择门"; return false; }
|
|
if (!int.TryParse(_doorIndex.Trim(), out index))
|
|
{
|
|
index = ctrl?.Doors.Count > 0 ? ctrl.Doors.Max(d => d.Index) + 1 : 1;
|
|
if (!string.IsNullOrWhiteSpace(_doorIndex.Trim())) { err = "门编码必须是数字"; return false; }
|
|
}
|
|
if (!ushort.TryParse(string.IsNullOrWhiteSpace(_doorCtrlAddr) ? "0" : _doorCtrlAddr.Trim(), out cAddr))
|
|
{ err = "控制地址必须是 0-65535"; return false; }
|
|
if (!ushort.TryParse(string.IsNullOrWhiteSpace(_doorOpenAddr) ? "0" : _doorOpenAddr.Trim(), out oAddr))
|
|
{ err = "开到位地址必须是 0-65535"; return false; }
|
|
return true;
|
|
}
|
|
|
|
private bool IsValidIp(string ip)
|
|
{
|
|
if (string.IsNullOrWhiteSpace(ip)) return false;
|
|
var pattern = @"^((25[0-5]|2[0-4][0-9]|[01]?[0-9][0-9]?)\.){3}(25[0-5]|2[0-4][0-9]|[01]?[0-9][0-9]?)$";
|
|
return Regex.IsMatch(ip, pattern) && IPAddress.TryParse(ip, out var addr)
|
|
&& addr.AddressFamily == System.Net.Sockets.AddressFamily.InterNetwork;
|
|
}
|
|
}
|
|
}
|