diff --git a/DeviceCommand/Devices/IOBoard.cs b/DeviceCommand/Devices/IOBoard.cs index 3e37c85..e3403ef 100644 --- a/DeviceCommand/Devices/IOBoard.cs +++ b/DeviceCommand/Devices/IOBoard.cs @@ -11,7 +11,7 @@ using System.Threading.Tasks; namespace DeviceCommand.Device { - [ADPCommand] + //[ADPCommand] public class IOBoard : ModbusTcp { diff --git a/DeviceCommand/Devices/IOBoardGroup.cs b/DeviceCommand/Devices/IOBoardGroup.cs new file mode 100644 index 0000000..4e8cfc8 --- /dev/null +++ b/DeviceCommand/Devices/IOBoardGroup.cs @@ -0,0 +1,160 @@ +using Common.Attributes; +using Model.Models; +using System; +using System.Collections.Generic; +using System.Threading.Tasks; +using System.Threading; +using DeviceCommand.Base; +using DeviceCommand.Device; +using System.ComponentModel; + +namespace DeviceCommand.Devices +{ + /// + /// 系统控制功能类型枚举 (中文) + /// + public enum 功能动作 : byte + { + 单相充电, + 抛负载, + 短路测试, + 蜂鸣器报警 + } + + /// + /// 精简后的 IO 端口信息结构体 + /// + public struct IoPortInfo + { + public int ModuleIndex { get; set; } // IO板卡/模块序号 (1 或 2) + public ushort Address { get; set; } // 转换为 Modbus 的线圈相对偏移地址 (Y1->0, Y2->1) + + public IoPortInfo(int moduleIndex, ushort address) + { + ModuleIndex = moduleIndex; + Address = address; + } + } + + [ADPCommand] + public class IOBoardGroup + { + // 声明底层的 2 个 IO 板卡实例 + public IOBoard Board1 { get; private set; } + public IOBoard Board2 { get; private set; } + + private readonly byte _slaveAddress = 1; // 默认 Modbus 从站号为 1 + + // 核心:严格对齐图片数据的双层嵌套配置字典 (2个模块,1-8台架) + private static readonly Dictionary> IoMapping + = new Dictionary>() + { + { 1, new Dictionary<功能动作, IoPortInfo> { + { 功能动作.单相充电, new IoPortInfo(1, 5) }, // Y6 -> 5 + { 功能动作.抛负载, new IoPortInfo(1, 0) }, // Y1 -> 0 + { 功能动作.短路测试, new IoPortInfo(1, 1) }, // Y2 -> 1 + { 功能动作.蜂鸣器报警, new IoPortInfo(2, 8) } // Y9 -> 8 + }}, + { 2, new Dictionary<功能动作, IoPortInfo> { + { 功能动作.单相充电, new IoPortInfo(1, 6) }, // Y7 -> 6 + { 功能动作.抛负载, new IoPortInfo(1, 2) }, // Y3 -> 2 + { 功能动作.短路测试, new IoPortInfo(1, 1) }, // Y2 -> 1 + { 功能动作.蜂鸣器报警, new IoPortInfo(2, 9) } // Y10 -> 9 + }}, + { 3, new Dictionary<功能动作, IoPortInfo> { + { 功能动作.单相充电, new IoPortInfo(1, 7) }, // Y8 -> 7 + { 功能动作.抛负载, new IoPortInfo(1, 3) }, // Y4 -> 3 + { 功能动作.短路测试, new IoPortInfo(1, 1) }, // Y2 -> 1 + { 功能动作.蜂鸣器报警, new IoPortInfo(2, 10) } // Y11 -> 10 + }}, + { 4, new Dictionary<功能动作, IoPortInfo> { + { 功能动作.单相充电, new IoPortInfo(1, 8) }, // Y9 -> 8 + { 功能动作.抛负载, new IoPortInfo(1, 4) }, // Y5 -> 4 + { 功能动作.短路测试, new IoPortInfo(1, 1) }, // Y2 -> 1 + { 功能动作.蜂鸣器报警, new IoPortInfo(2, 11) } // Y12 -> 11 + }}, + { 5, new Dictionary<功能动作, IoPortInfo> { + { 功能动作.单相充电, new IoPortInfo(2, 4) }, // Y5 -> 4 + { 功能动作.抛负载, new IoPortInfo(2, 0) }, // Y1 -> 0 + { 功能动作.短路测试, new IoPortInfo(1, 1) }, // Y2 -> 1 + { 功能动作.蜂鸣器报警, new IoPortInfo(2, 12) } // Y13 -> 12 + }}, + { 6, new Dictionary<功能动作, IoPortInfo> { + { 功能动作.单相充电, new IoPortInfo(2, 5) }, // Y6 -> 5 + { 功能动作.抛负载, new IoPortInfo(2, 1) }, // Y2 -> 1 + { 功能动作.短路测试, new IoPortInfo(1, 1) }, // Y2 -> 1 + { 功能动作.蜂鸣器报警, new IoPortInfo(2, 13) } // Y14 -> 13 + }}, + { 7, new Dictionary<功能动作, IoPortInfo> { + { 功能动作.单相充电, new IoPortInfo(2, 6) }, // Y7 -> 6 + { 功能动作.抛负载, new IoPortInfo(2, 2) }, // Y3 -> 2 + { 功能动作.短路测试, new IoPortInfo(1, 1) }, // Y2 -> 1 + { 功能动作.蜂鸣器报警, new IoPortInfo(2, 14) } // Y15 -> 14 + }}, + { 8, new Dictionary<功能动作, IoPortInfo> { + { 功能动作.单相充电, new IoPortInfo(2, 7) }, // Y8 -> 7 + { 功能动作.抛负载, new IoPortInfo(2, 3) }, // Y4 -> 3 + { 功能动作.短路测试, new IoPortInfo(1, 1) }, // Y2 -> 1 + { 功能动作.蜂鸣器报警, new IoPortInfo(2, 15) } // Y16 -> 15 + }} + }; + + public IOBoardGroup(IOBoard board1, IOBoard board2) + { + Board1 = board1 ?? throw new ArgumentNullException(nameof(board1), "IOBoard Board1 实例不能为空。"); + Board2 = board2 ?? throw new ArgumentNullException(nameof(board2), "IOBoard Board2 实例不能为空。"); + } + + + + /// + /// 执行指定台架的功能动作控制(带高低电平开关及底层安全互锁) + /// + /// 台架/工位序号 (1 - 8) + /// 控制功能类型 (单相充电, 抛负载, 短路测试, 蜂鸣器报警) + /// 开关状态 (true: 开启吸合, false: 关闭断开) + /// 异步取消令牌 + public async Task 执行台架动作Async(int 台架序号, 功能动作 动作, bool 开关状态, CancellationToken 取消令牌 = default) + { + // 1. 根据传入的台架序号和动作检索字典映射关系 + if (!IoMapping.TryGetValue(台架序号, out var 动作字典) || !动作字典.TryGetValue(动作, out var io信息)) + { + throw new ArgumentException($"未找到台架 [{台架序号}] 对应功能 [{动作}] 的 IO 点位映射配置。"); + } + + // 2. 软件防错高危互锁逻辑 + if (开关状态) + { + if (动作 == 功能动作.单相充电) + { + var 短路点位 = IoMapping[台架序号][功能动作.短路测试]; + await GetBoardInstance(短路点位.ModuleIndex).写输出开关(_slaveAddress, 短路点位.Address, false); + } + else if (动作 == 功能动作.短路测试) + { + var 充电点位 = IoMapping[台架序号][功能动作.单相充电]; + await GetBoardInstance(充电点位.ModuleIndex).写输出开关(_slaveAddress, 充电点位.Address, false); + } + } + + // 3. 动态获取目标板卡实例并直接驱动开关端口 + IOBoard 目标板卡 = GetBoardInstance(io信息.ModuleIndex); + await 目标板卡.写输出开关(_slaveAddress, io信息.Address, 开关状态); + } + + /// + /// 内部路由辅助方法:根据板卡索引安全获取当前连接的实例对象 + /// + private IOBoard GetBoardInstance(int moduleIndex) + { + IOBoard board = moduleIndex switch + { + 1 => Board1, + 2 => Board2, + _ => throw new ArgumentOutOfRangeException(nameof(moduleIndex), "非法的板卡序号,系统仅支持 1# 和 2# 模块。") + }; + + return board ?? throw new InvalidOperationException($"IO板卡模块 [{moduleIndex}#] 尚未完成初始化,无法建立总线通信。"); + } + } +} \ No newline at end of file diff --git a/MainModule/ViewModels/AutomatedTestingViewModel.cs b/MainModule/ViewModels/AutomatedTestingViewModel.cs index 77cb05c..531e61b 100644 --- a/MainModule/ViewModels/AutomatedTestingViewModel.cs +++ b/MainModule/ViewModels/AutomatedTestingViewModel.cs @@ -2,6 +2,7 @@ using System.IO; using System.Windows.Input; using DeviceCommand.Device; +using DeviceCommand.Devices; using Logger; using Prism.Ioc; using TestingModule.ViewModels; @@ -84,18 +85,22 @@ namespace MainModule.ViewModels private async Task SilenceBuzzer(string scope) { - if (_deviceManager.DeviceMap.TryGetValue("IO板卡(蜂鸣器)", out var lazyIoBoard)) + if (_deviceManager.IOGroup == null) return; + + if (scope == "default") { - if (lazyIoBoard is IOBoard io) + for(int i = 1; i <= 8; i++) { - if (scope == "default") - await io.批量写输出开关(1, 0, [false, false, false, false, false, false, false, false]); - else if (scope != TestStatus) return; - else - { - int index = _systemConfig.SharedParameterList.Where(x => x.ParameterName == "蜂鸣器").FirstOrDefault().Value; - await io.写输出开关(1, (ushort)index, false); - } + await _deviceManager.IOGroup.执行台架动作Async(i, 功能动作.蜂鸣器报警, false); + } + } + else if (scope == TestStatus) + { + int 台架号 = _systemConfig.SharedParameterList + .FirstOrDefault(x => x.ParameterName == "台架序号")?.Value ?? 0; + if (台架号 >= 1 && 台架号 <= 8) + { + await _deviceManager.IOGroup.执行台架动作Async(台架号, 功能动作.蜂鸣器报警, false); } } } diff --git a/UIShare/GlobalVariable/DeviceManager.cs b/UIShare/GlobalVariable/DeviceManager.cs index 676e047..894e7ae 100644 --- a/UIShare/GlobalVariable/DeviceManager.cs +++ b/UIShare/GlobalVariable/DeviceManager.cs @@ -1,4 +1,6 @@ using DeviceCommand.Base; +using DeviceCommand.Device; +using DeviceCommand.Devices; using Logger; using Model.Models; using Prism.Events; @@ -34,6 +36,7 @@ namespace UIShare.GlobalVariable /// 类名 → Type 的反射缓存(仅扫描一次)。 private static readonly IReadOnlyDictionary _deviceTypeMap = BuildDeviceTypeMap(); public ZLGCANFD CANFD { get; set; } + public IOBoardGroup IOGroup { get; set; } public DeviceManager(SystemConfig systemConfig, GlobalInfo globalInfo, IEventAggregator eventAggregator) { @@ -207,6 +210,25 @@ namespace UIShare.GlobalVariable LoggerHelper.ErrorWithNotify(_scopeName, $"设备 [{config.DeviceName}] 实例化失败:{inner}"); } } + + // IOGroup 初始化:从已实例化的设备中取出前两个 IOBoard 实例组建 IOBoardGroup + try + { + var ioBoards = DeviceMap.Values.OfType().Take(2).ToList(); + if (ioBoards.Count == 2) + { + IOGroup = new IOBoardGroup(ioBoards[0], ioBoards[1]); + LoggerHelper.Info($"IOBoardGroup 已初始化,Board1={ioBoards[0].GetType().Name}, Board2={ioBoards[1].GetType().Name}。"); + } + else + { + LoggerHelper.Warn($"IOBoardGroup 初始化跳过:需要 2 个 IOBoard 实例,当前仅找到 {ioBoards.Count} 个。"); + } + } + catch (Exception ex) + { + LoggerHelper.ErrorWithNotify(_scopeName, $"IOBoardGroup 初始化失败:{ex.Message}"); + } } public async Task ConnectAllDevices(CancellationToken ct = default) @@ -407,12 +429,16 @@ namespace UIShare.GlobalVariable private static async Task ConnectTcpAsync(string name, IBaseInterface device, CancellationToken ct) { - if (device is not ITcp tcp) + if (device is ITcp tcp) { - LoggerHelper.Warn($"设备 [{name}] 配置为 Tcp 但未实现 ITcp,实际类型为 {device.GetType().Name}。"); - return false; + return await tcp.ConnectAsync(ct); } - return await tcp.ConnectAsync(ct); + if (device is IModbusDevice modbusTCP) + { + return await modbusTCP.ConnectAsync(ct); + } + LoggerHelper.Warn($"设备 [{name}] 配置为 Tcp 但未实现 ITcp,实际类型为 {device.GetType().Name}。"); + return false; } private static async Task ConnectSerialAsync(string name, IBaseInterface device, CancellationToken ct) diff --git a/UIShare/GlobalVariable/StepRunning.cs b/UIShare/GlobalVariable/StepRunning.cs index a93959e..076b26a 100644 --- a/UIShare/GlobalVariable/StepRunning.cs +++ b/UIShare/GlobalVariable/StepRunning.cs @@ -568,6 +568,10 @@ namespace UIShare { instance = _deviceManager.CANFD; } + else if (targetType.Name == "IOBoardGroup") + { + instance = _deviceManager.IOGroup; + } else instance = _deviceManager.DeviceMap[targetType.Name]; } catch (Exception ex) diff --git a/UIShare/GlobalVariable/SystemConfig.cs b/UIShare/GlobalVariable/SystemConfig.cs index 868e47b..4a483b8 100644 --- a/UIShare/GlobalVariable/SystemConfig.cs +++ b/UIShare/GlobalVariable/SystemConfig.cs @@ -47,15 +47,7 @@ namespace UIShare.GlobalVariable { Category = ParameterCategory.Input, Type = typeof(int), - Name = "单相充电", - Value = 0, - IsEditable=false - }, - new ParameterVM - { - Category = ParameterCategory.Input, - Type = typeof(int), - Name = "抛负载", + Name = "台架序号", Value = 1, IsEditable=false }, @@ -63,22 +55,15 @@ namespace UIShare.GlobalVariable { Category = ParameterCategory.Input, Type = typeof(int), - Name = "短路测试", - Value = 0, - IsEditable=false - }, new ParameterVM - { - Category = ParameterCategory.Input, - Type = typeof(int), - Name = "初始化供电", - Value = 0, + Name = "直流负载通道", + Value = 1, IsEditable=false }, - new ParameterVM + new ParameterVM { Category = ParameterCategory.Input, Type = typeof(int), - Name = "蜂鸣器", + Name = "交流电源通道", Value = 1, IsEditable=false }, @@ -94,7 +79,15 @@ namespace UIShare.GlobalVariable { Category = ParameterCategory.Input, Type = typeof(int), - Name = "示波器通道", + Name = "示波器通道1", + Value = 0, + IsEditable=false + }, + new ParameterVM + { + Category = ParameterCategory.Input, + Type = typeof(int), + Name = "示波器通道2", Value = 0, IsEditable=false },