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 CycleGUI; using StandardScene.Utils; namespace StandardScene.ExtendDevice.Door { /// /// 门控制器配置管理(CycleGUI 版,替代原 WinForms DoorManager 窗体)。 /// 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 _controllers = new List(); 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(); 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("门控制器管理") .SetDefaultDocking(Panel.Docking.None) .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: 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(); 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: 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 = SimpleLite.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()?.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(); return; } var json = File.ReadAllText(DataFilePath, Encoding.UTF8); _controllers = string.IsNullOrWhiteSpace(json) ? new List() : json.JsonTo>() ?? new List(); } catch (Exception ex) { _controllers = new List(); _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; } } }