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
},