Compare commits
11 Commits
02d9923474
...
f83c93fcf9
| Author | SHA1 | Date | |
|---|---|---|---|
| f83c93fcf9 | |||
| 1b4d8ea0de | |||
| 45df76b06d | |||
| 72f8b8c5b3 | |||
| 4deb785135 | |||
| c4470af013 | |||
| cd3cbe7c35 | |||
| 5b721d2557 | |||
| 1e21d5f9f2 | |||
| 1faae12822 | |||
| cba9af2892 |
@@ -8,6 +8,12 @@
|
|||||||
<UseWPF>true</UseWPF>
|
<UseWPF>true</UseWPF>
|
||||||
</PropertyGroup>
|
</PropertyGroup>
|
||||||
|
|
||||||
|
<ItemGroup>
|
||||||
|
<None Remove="Images\error.png" />
|
||||||
|
<None Remove="Images\info.png" />
|
||||||
|
<None Remove="Images\warning.png" />
|
||||||
|
</ItemGroup>
|
||||||
|
|
||||||
<ItemGroup>
|
<ItemGroup>
|
||||||
<PackageReference Include="AutoMapper" Version="16.1.1" />
|
<PackageReference Include="AutoMapper" Version="16.1.1" />
|
||||||
</ItemGroup>
|
</ItemGroup>
|
||||||
@@ -27,4 +33,16 @@
|
|||||||
<ProjectReference Include="..\UpdateInfoMoudle\UpdateInfoMoudle.csproj" />
|
<ProjectReference Include="..\UpdateInfoMoudle\UpdateInfoMoudle.csproj" />
|
||||||
</ItemGroup>
|
</ItemGroup>
|
||||||
|
|
||||||
|
<ItemGroup>
|
||||||
|
<Resource Include="Images\error.png">
|
||||||
|
<CopyToOutputDirectory>PreserveNewest</CopyToOutputDirectory>
|
||||||
|
</Resource>
|
||||||
|
<Resource Include="Images\info.png">
|
||||||
|
<CopyToOutputDirectory>PreserveNewest</CopyToOutputDirectory>
|
||||||
|
</Resource>
|
||||||
|
<Resource Include="Images\warning.png">
|
||||||
|
<CopyToOutputDirectory>PreserveNewest</CopyToOutputDirectory>
|
||||||
|
</Resource>
|
||||||
|
</ItemGroup>
|
||||||
|
|
||||||
</Project>
|
</Project>
|
||||||
|
|||||||
@@ -59,6 +59,10 @@ namespace ADP
|
|||||||
}
|
}
|
||||||
protected override void OnInitialized()
|
protected override void OnInitialized()
|
||||||
{
|
{
|
||||||
|
// 配置全局日志分发器:按 CurrentScope 路由到对应 LogArea
|
||||||
|
var globalInfo = Container.Resolve<GlobalInfo>();
|
||||||
|
LoggerHelper.Progress = new ScopeLogDispatcher(globalInfo);
|
||||||
|
|
||||||
//初始化数据库
|
//初始化数据库
|
||||||
//DatabaseConfig.SetTenant(10001);
|
//DatabaseConfig.SetTenant(10001);
|
||||||
//DatabaseConfig.InitMySql("127.0.0.1",3306,"ADP","root","123456");
|
//DatabaseConfig.InitMySql("127.0.0.1",3306,"ADP","root","123456");
|
||||||
|
|||||||
BIN
ADP/Images/error.png
Normal file
BIN
ADP/Images/error.png
Normal file
Binary file not shown.
|
After Width: | Height: | Size: 7.0 KiB |
BIN
ADP/Images/info.png
Normal file
BIN
ADP/Images/info.png
Normal file
Binary file not shown.
|
After Width: | Height: | Size: 5.4 KiB |
BIN
ADP/Images/warning.png
Normal file
BIN
ADP/Images/warning.png
Normal file
Binary file not shown.
|
After Width: | Height: | Size: 5.1 KiB |
@@ -1,10 +1,12 @@
|
|||||||
|
|
||||||
using Logger;
|
using Logger;
|
||||||
using MaterialDesignThemes.Wpf;
|
using MaterialDesignThemes.Wpf;
|
||||||
|
using Model.Models;
|
||||||
using Notifications.Wpf.Core;
|
using Notifications.Wpf.Core;
|
||||||
using System.Collections.Concurrent;
|
using System.Collections.Concurrent;
|
||||||
using System.Diagnostics;
|
using System.Diagnostics;
|
||||||
using System.IO;
|
using System.IO;
|
||||||
|
using System.Threading.Tasks;
|
||||||
using System.Windows;
|
using System.Windows;
|
||||||
using System.Windows.Input;
|
using System.Windows.Input;
|
||||||
using System.Windows.Media;
|
using System.Windows.Media;
|
||||||
@@ -225,7 +227,7 @@ namespace ADP.ViewModels
|
|||||||
if (targetContext.RunState == "运行")
|
if (targetContext.RunState == "运行")
|
||||||
{
|
{
|
||||||
targetContext.SingleStep = false;
|
targetContext.SingleStep = false;
|
||||||
LoggerHelper.InfoWithNotify($"{_globalInfo.UserName} 执行工位 [{runningScope}] 运行命令");
|
LoggerHelper.InfoWithNotify(runningScope, $"{_globalInfo.UserName} 执行工位 [{runningScope}] 运行命令");
|
||||||
|
|
||||||
targetContext.RunState = "暂停";
|
targetContext.RunState = "暂停";
|
||||||
targetContext.RunIcon = PackIconKind.Pause;
|
targetContext.RunIcon = PackIconKind.Pause;
|
||||||
@@ -251,7 +253,7 @@ namespace ADP.ViewModels
|
|||||||
}
|
}
|
||||||
catch (Exception ex)
|
catch (Exception ex)
|
||||||
{
|
{
|
||||||
LoggerHelper.ErrorWithNotify($"工位 [{runningScope}] 异常中止: {ex.Message}");
|
LoggerHelper.ErrorWithNotify(runningScope, $"工位 [{runningScope}] 异常中止: {ex.Message}");
|
||||||
}
|
}
|
||||||
|
|
||||||
// 💡 测试正常完毕或取消:停止当前工位的 SW,并将最终时间固化到 RunningTime 字段中
|
// 💡 测试正常完毕或取消:停止当前工位的 SW,并将最终时间固化到 RunningTime 字段中
|
||||||
@@ -284,7 +286,7 @@ namespace ADP.ViewModels
|
|||||||
}
|
}
|
||||||
else // 用户点击了暂停
|
else // 用户点击了暂停
|
||||||
{
|
{
|
||||||
LoggerHelper.InfoWithNotify($"{_globalInfo.UserName} 点击工位 [{runningScope}] 暂停命令");
|
LoggerHelper.InfoWithNotify(runningScope, $"{_globalInfo.UserName} 点击工位 [{runningScope}] 暂停命令");
|
||||||
targetContext.SingleStep = true;
|
targetContext.SingleStep = true;
|
||||||
targetContext.IsStop = true;
|
targetContext.IsStop = true;
|
||||||
targetContext.RunState = "运行";
|
targetContext.RunState = "运行";
|
||||||
@@ -307,7 +309,7 @@ namespace ADP.ViewModels
|
|||||||
if (targetContext.RunState == "运行")
|
if (targetContext.RunState == "运行")
|
||||||
{
|
{
|
||||||
targetContext.SingleStep = true;
|
targetContext.SingleStep = true;
|
||||||
LoggerHelper.InfoWithNotify($"{_globalInfo.UserName} 执行工位 [{runningScope}] 单步执行命令");
|
LoggerHelper.InfoWithNotify(runningScope, $"{_globalInfo.UserName} 执行工位 [{runningScope}] 单步执行命令");
|
||||||
|
|
||||||
targetContext.RunState = "暂停";
|
targetContext.RunState = "暂停";
|
||||||
targetContext.RunIcon = PackIconKind.Pause;
|
targetContext.RunIcon = PackIconKind.Pause;
|
||||||
@@ -333,7 +335,7 @@ namespace ADP.ViewModels
|
|||||||
}
|
}
|
||||||
catch (Exception ex)
|
catch (Exception ex)
|
||||||
{
|
{
|
||||||
LoggerHelper.ErrorWithNotify($"工位 [{runningScope}] 单步执行异常: {ex.Message}");
|
LoggerHelper.ErrorWithNotify(runningScope, $"工位 [{runningScope}] 单步执行异常: {ex.Message}");
|
||||||
}
|
}
|
||||||
|
|
||||||
// 💡 单步单步完成后会被 StepRunning 挂起,在此暂时停止 SW
|
// 💡 单步单步完成后会被 StepRunning 挂起,在此暂时停止 SW
|
||||||
@@ -377,7 +379,7 @@ namespace ADP.ViewModels
|
|||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
|
|
||||||
LoggerHelper.InfoWithNotify($"{_globalInfo.UserName} 执行工位 [{runningScope}] 复位命令");
|
LoggerHelper.InfoWithNotify(runningScope, $"{_globalInfo.UserName} 执行工位 [{runningScope}] 复位命令");
|
||||||
|
|
||||||
_executionTasks.TryGetValue(runningScope, out var currentTask);
|
_executionTasks.TryGetValue(runningScope, out var currentTask);
|
||||||
_errorExecutionTasks.TryGetValue(runningScope, out var errorTask);
|
_errorExecutionTasks.TryGetValue(runningScope, out var errorTask);
|
||||||
@@ -427,7 +429,7 @@ namespace ADP.ViewModels
|
|||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
|
|
||||||
LoggerHelper.InfoWithNotify($"{_globalInfo.UserName} 执行工位 [{runningScope}] 异常流程命令");
|
LoggerHelper.InfoWithNotify(runningScope, $"{_globalInfo.UserName} 执行工位 [{runningScope}] 异常流程命令");
|
||||||
|
|
||||||
// 💡 异常测试启动计时
|
// 💡 异常测试启动计时
|
||||||
targetContext.SW.Start();
|
targetContext.SW.Start();
|
||||||
@@ -444,7 +446,7 @@ namespace ADP.ViewModels
|
|||||||
}
|
}
|
||||||
catch (Exception ex)
|
catch (Exception ex)
|
||||||
{
|
{
|
||||||
LoggerHelper.ErrorWithNotify($"工位 [{runningScope}] 异常流程执行出错: {ex.Message}");
|
LoggerHelper.ErrorWithNotify(runningScope, $"工位 [{runningScope}] 异常流程执行出错: {ex.Message}");
|
||||||
}
|
}
|
||||||
|
|
||||||
// 💡 异常测试结束停止计时
|
// 💡 异常测试结束停止计时
|
||||||
@@ -478,7 +480,7 @@ namespace ADP.ViewModels
|
|||||||
{
|
{
|
||||||
CurrentConfig.DefaultProgramFilePath = targetContext.CurrentFilePath;
|
CurrentConfig.DefaultProgramFilePath = targetContext.CurrentFilePath;
|
||||||
ConfigService.Save(CurrentConfig);
|
ConfigService.Save(CurrentConfig);
|
||||||
LoggerHelper.SuccessWithNotify($"工位 [{runningScope}] 已成功将当前程序设为默认启动程序");
|
LoggerHelper.SuccessWithNotify(runningScope, $"工位 [{runningScope}] 已成功将当前程序设为默认启动程序");
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -495,8 +497,16 @@ namespace ADP.ViewModels
|
|||||||
targetContext.Program.Parameters.Clear();
|
targetContext.Program.Parameters.Clear();
|
||||||
targetContext.Program.StepCollection.Clear();
|
targetContext.Program.StepCollection.Clear();
|
||||||
targetContext.Program.ErrorStepCollection.Clear();
|
targetContext.Program.ErrorStepCollection.Clear();
|
||||||
|
foreach (var item in CurrentConfig.ParameterList)
|
||||||
LoggerHelper.InfoWithNotify($"工位 [{runningScope}] 创建了空程序文件");
|
{
|
||||||
|
var param = CurrentConfig.SharedParameterList.FirstOrDefault(x => x.ParameterName == item.Name);
|
||||||
|
if (param != null)
|
||||||
|
{
|
||||||
|
item.Value = param.Value;
|
||||||
|
}
|
||||||
|
targetContext.Program.Parameters.Add(item);
|
||||||
|
}
|
||||||
|
LoggerHelper.InfoWithNotify(runningScope, $"工位 [{runningScope}] 创建了空程序文件");
|
||||||
|
|
||||||
// 如果当前正看着该工位,刷新 UI 显示
|
// 如果当前正看着该工位,刷新 UI 显示
|
||||||
if (_globalInfo.CurrentScope == runningScope) RefreshAllContextProperties();
|
if (_globalInfo.CurrentScope == runningScope) RefreshAllContextProperties();
|
||||||
@@ -530,7 +540,7 @@ namespace ADP.ViewModels
|
|||||||
// 确认文件存在
|
// 确认文件存在
|
||||||
if (!File.Exists(filePath))
|
if (!File.Exists(filePath))
|
||||||
{
|
{
|
||||||
LoggerHelper.ErrorWithNotify($"文件不存在: {filePath}");
|
LoggerHelper.ErrorWithNotify(runningScope, $"文件不存在: {filePath}");
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -542,7 +552,7 @@ namespace ADP.ViewModels
|
|||||||
|
|
||||||
if (program == null)
|
if (program == null)
|
||||||
{
|
{
|
||||||
LoggerHelper.WarnWithNotify($"文件格式不正确或为空: {filePath}");
|
LoggerHelper.WarnWithNotify(runningScope, $"文件格式不正确或为空: {filePath}");
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -552,7 +562,17 @@ namespace ADP.ViewModels
|
|||||||
targetContext.Program.ErrorStepCollection = program.ErrorStepCollection;
|
targetContext.Program.ErrorStepCollection = program.ErrorStepCollection;
|
||||||
targetContext.CurrentFilePath = filePath;
|
targetContext.CurrentFilePath = filePath;
|
||||||
|
|
||||||
LoggerHelper.SuccessWithNotify($"工位 [{runningScope}] 成功打开文件: {filePath}");
|
foreach (var item in CurrentConfig.SharedParameterList)
|
||||||
|
{
|
||||||
|
var parameter = targetContext?.Program?.Parameters?.FirstOrDefault(x => x.Name == item.ParameterName);
|
||||||
|
|
||||||
|
if (parameter != null)
|
||||||
|
{
|
||||||
|
parameter.Value = item.Value;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
LoggerHelper.SuccessWithNotify(runningScope, $"工位 [{runningScope}] 成功打开文件: {filePath}");
|
||||||
|
|
||||||
// 💡 3. 安全调用异步复位,确保重置的是对应工位的数据
|
// 💡 3. 安全调用异步复位,确保重置的是对应工位的数据
|
||||||
// 注意:由于在 OnRestoration 内部第一行也做了快照拦截,
|
// 注意:由于在 OnRestoration 内部第一行也做了快照拦截,
|
||||||
@@ -561,7 +581,7 @@ namespace ADP.ViewModels
|
|||||||
}
|
}
|
||||||
catch (Exception ex)
|
catch (Exception ex)
|
||||||
{
|
{
|
||||||
LoggerHelper.ErrorWithNotify($"工位 [{runningScope}] 打开文件失败: {ex.Message}");
|
LoggerHelper.ErrorWithNotify(runningScope, $"工位 [{runningScope}] 打开文件失败: {ex.Message}");
|
||||||
}
|
}
|
||||||
finally
|
finally
|
||||||
{
|
{
|
||||||
@@ -600,7 +620,7 @@ namespace ADP.ViewModels
|
|||||||
// 💡 3. 调用你的底层文件保存逻辑,传入快照工位的 Program 模型
|
// 💡 3. 调用你的底层文件保存逻辑,传入快照工位的 Program 模型
|
||||||
SaveProgramToFile(targetContext.CurrentFilePath, targetContext.Program);
|
SaveProgramToFile(targetContext.CurrentFilePath, targetContext.Program);
|
||||||
|
|
||||||
LoggerHelper.InfoWithNotify($"{_globalInfo.UserName} 另存为文件成功: {saveFileDialog.FileName}");
|
LoggerHelper.InfoWithNotify(runningScope, $"{_globalInfo.UserName} 另存为文件成功: {saveFileDialog.FileName}");
|
||||||
|
|
||||||
if (_globalInfo.CurrentScope == runningScope) RefreshAllContextProperties();
|
if (_globalInfo.CurrentScope == runningScope) RefreshAllContextProperties();
|
||||||
}
|
}
|
||||||
@@ -625,7 +645,7 @@ namespace ADP.ViewModels
|
|||||||
|
|
||||||
// 💡 3. 持久化当前快照工位的 Program
|
// 💡 3. 持久化当前快照工位的 Program
|
||||||
SaveProgramToFile(targetContext.CurrentFilePath, targetContext.Program);
|
SaveProgramToFile(targetContext.CurrentFilePath, targetContext.Program);
|
||||||
LoggerHelper.SuccessWithNotify($"工位 [{runningScope}] 程序保存成功!");
|
LoggerHelper.SuccessWithNotify(_globalInfo.CurrentScope,$"工位 [{runningScope}] 程序保存成功!");
|
||||||
}
|
}
|
||||||
|
|
||||||
// 💡 辅助方法:建议将你的通用序列化落盘代码调整为接收 ProgramVM 参数,提高复用性
|
// 💡 辅助方法:建议将你的通用序列化落盘代码调整为接收 ProgramVM 参数,提高复用性
|
||||||
@@ -638,7 +658,7 @@ namespace ADP.ViewModels
|
|||||||
}
|
}
|
||||||
catch (Exception ex)
|
catch (Exception ex)
|
||||||
{
|
{
|
||||||
LoggerHelper.ErrorWithNotify($"写入程序文件失败: {ex.Message}");
|
LoggerHelper.ErrorWithNotify(_globalInfo.CurrentScope,$"写入程序文件失败: {ex.Message}");
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|||||||
@@ -1,5 +1,6 @@
|
|||||||
using Model.Models;
|
using Model.Models;
|
||||||
using System;
|
using System;
|
||||||
|
using System.IO;
|
||||||
using System.IO.Ports;
|
using System.IO.Ports;
|
||||||
using System.Text;
|
using System.Text;
|
||||||
using System.Threading;
|
using System.Threading;
|
||||||
@@ -7,7 +8,7 @@ using System.Threading.Tasks;
|
|||||||
|
|
||||||
namespace DeviceCommand.Base
|
namespace DeviceCommand.Base
|
||||||
{
|
{
|
||||||
public class Serial_Port : ISerialPort
|
public class Serial_Port : ISerialPort, IDisposable
|
||||||
{
|
{
|
||||||
public string PortName { get; set; } = "COM1";
|
public string PortName { get; set; } = "COM1";
|
||||||
public int BaudRate { get; set; } = 9600;
|
public int BaudRate { get; set; } = 9600;
|
||||||
@@ -58,8 +59,10 @@ namespace DeviceCommand.Base
|
|||||||
_serialPort.DataBits = DataBits;
|
_serialPort.DataBits = DataBits;
|
||||||
_serialPort.StopBits = StopBits;
|
_serialPort.StopBits = StopBits;
|
||||||
_serialPort.Parity = Parity;
|
_serialPort.Parity = Parity;
|
||||||
_serialPort.ReadTimeout = ReadTimeout;
|
|
||||||
_serialPort.WriteTimeout = WriteTimeout;
|
// 允许底层保留默认设置,控制权交给外层 WaitAsync
|
||||||
|
_serialPort.ReadTimeout = SerialPort.InfiniteTimeout;
|
||||||
|
_serialPort.WriteTimeout = SerialPort.InfiniteTimeout;
|
||||||
|
|
||||||
_serialPort.Open();
|
_serialPort.Open();
|
||||||
return true;
|
return true;
|
||||||
@@ -72,19 +75,33 @@ namespace DeviceCommand.Base
|
|||||||
|
|
||||||
public virtual void Close()
|
public virtual void Close()
|
||||||
{
|
{
|
||||||
if (_serialPort.IsOpen) _serialPort.Close();
|
if (_serialPort?.IsOpen == true) _serialPort.Close();
|
||||||
}
|
}
|
||||||
|
|
||||||
// 内部无锁发送方法,供原子组合操作调用
|
|
||||||
private async Task LoglessSendAsync(string data, CancellationToken ct)
|
private async Task LoglessSendAsync(string data, CancellationToken ct)
|
||||||
{
|
{
|
||||||
if (!_serialPort.IsOpen) throw new InvalidOperationException("串口未打开。");
|
if (!_serialPort.IsOpen) throw new InvalidOperationException("串口未打开。");
|
||||||
|
|
||||||
byte[] bytes = Encoding.UTF8.GetBytes(data);
|
byte[] bytes = Encoding.UTF8.GetBytes(data);
|
||||||
using var cts = CancellationTokenSource.CreateLinkedTokenSource(ct);
|
|
||||||
if (WriteTimeout > 0) cts.CancelAfter(WriteTimeout);
|
|
||||||
|
|
||||||
await _serialPort.BaseStream.WriteAsync(bytes, 0, bytes.Length, cts.Token);
|
try
|
||||||
|
{
|
||||||
|
if (WriteTimeout > 0)
|
||||||
|
{
|
||||||
|
// 核心修改:使用 WaitAsync 精准控制写入超时
|
||||||
|
await _serialPort.BaseStream.WriteAsync(bytes, 0, bytes.Length, ct)
|
||||||
|
.WaitAsync(TimeSpan.FromMilliseconds(WriteTimeout), ct)
|
||||||
|
.ConfigureAwait(false);
|
||||||
|
}
|
||||||
|
else
|
||||||
|
{
|
||||||
|
await _serialPort.BaseStream.WriteAsync(bytes, 0, bytes.Length, ct).ConfigureAwait(false);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
catch (TimeoutException ex)
|
||||||
|
{
|
||||||
|
throw new TimeoutException($"串口写入数据超时({WriteTimeout} ms)", ex);
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
public async Task SendAsync(string data, CancellationToken ct = default)
|
public async Task SendAsync(string data, CancellationToken ct = default)
|
||||||
@@ -100,7 +117,6 @@ namespace DeviceCommand.Base
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
// 内部无锁读取方法,利用 BaseStream 挂起线程,高性能不吃 CPU
|
|
||||||
private async Task<string> LoglessReadAsync(string delimiter, CancellationToken ct)
|
private async Task<string> LoglessReadAsync(string delimiter, CancellationToken ct)
|
||||||
{
|
{
|
||||||
if (!_serialPort.IsOpen) throw new InvalidOperationException("串口未打开。");
|
if (!_serialPort.IsOpen) throw new InvalidOperationException("串口未打开。");
|
||||||
@@ -109,24 +125,44 @@ namespace DeviceCommand.Base
|
|||||||
var sb = new StringBuilder();
|
var sb = new StringBuilder();
|
||||||
byte[] buffer = new byte[1024];
|
byte[] buffer = new byte[1024];
|
||||||
|
|
||||||
using var cts = CancellationTokenSource.CreateLinkedTokenSource(ct);
|
try
|
||||||
if (ReadTimeout > 0) cts.CancelAfter(ReadTimeout);
|
|
||||||
|
|
||||||
while (!cts.Token.IsCancellationRequested)
|
|
||||||
{
|
{
|
||||||
// 核心优化:利用流异步挂起,替代原先的 BytesToRead 循环延时
|
while (true)
|
||||||
int bytesRead = await _serialPort.BaseStream.ReadAsync(buffer, 0, buffer.Length, cts.Token);
|
|
||||||
if (bytesRead == 0) continue;
|
|
||||||
|
|
||||||
sb.Append(Encoding.UTF8.GetString(buffer, 0, bytesRead));
|
|
||||||
|
|
||||||
int index = sb.ToString().IndexOf(delimiter, StringComparison.Ordinal);
|
|
||||||
if (index >= 0)
|
|
||||||
{
|
{
|
||||||
return sb.ToString(0, index).Trim();
|
ct.ThrowIfCancellationRequested();
|
||||||
|
|
||||||
|
int bytesRead;
|
||||||
|
if (ReadTimeout > 0)
|
||||||
|
{
|
||||||
|
// 核心修改:使用 WaitAsync 精准控制单次异步读取超时
|
||||||
|
bytesRead = await _serialPort.BaseStream.ReadAsync(buffer, 0, buffer.Length, ct)
|
||||||
|
.WaitAsync(TimeSpan.FromMilliseconds(ReadTimeout), ct)
|
||||||
|
.ConfigureAwait(false);
|
||||||
|
}
|
||||||
|
else
|
||||||
|
{
|
||||||
|
bytesRead = await _serialPort.BaseStream.ReadAsync(buffer, 0, buffer.Length, ct).ConfigureAwait(false);
|
||||||
|
}
|
||||||
|
|
||||||
|
if (bytesRead == 0) continue;
|
||||||
|
|
||||||
|
sb.Append(Encoding.UTF8.GetString(buffer, 0, bytesRead));
|
||||||
|
|
||||||
|
string currentText = sb.ToString();
|
||||||
|
int index = currentText.IndexOf(delimiter, StringComparison.Ordinal);
|
||||||
|
if (index >= 0)
|
||||||
|
{
|
||||||
|
return currentText.Substring(0, index).Trim();
|
||||||
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
throw new TimeoutException("读取数据超时");
|
catch (TimeoutException ex)
|
||||||
|
{
|
||||||
|
// 关键防污染策略:串口发生命令错、超时不响应时,WaitAsync 会立功捕获异常
|
||||||
|
// 我们直接清空串口驱动内部的输入缓冲区,把残余垃圾数据物理抹除,确保下一条指令安全、不掉线
|
||||||
|
ClearHardwareBuffer();
|
||||||
|
throw new TimeoutException($"串口读取超时(未收到结束符 '{delimiter}'),等待时间:{ReadTimeout} ms", ex);
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
public async Task<string> ReadAsync(string delimiter = "\n", CancellationToken ct = default)
|
public async Task<string> ReadAsync(string delimiter = "\n", CancellationToken ct = default)
|
||||||
@@ -142,12 +178,14 @@ namespace DeviceCommand.Base
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
// 核心优化:保证多线程环境下发送和等待回包是一个原子过程
|
|
||||||
public async Task<string> WriteReadAsync(string command, string delimiter = "\n", CancellationToken ct = default)
|
public async Task<string> WriteReadAsync(string command, string delimiter = "\n", CancellationToken ct = default)
|
||||||
{
|
{
|
||||||
await commLock.WaitAsync(ct);
|
await commLock.WaitAsync(ct);
|
||||||
try
|
try
|
||||||
{
|
{
|
||||||
|
// 发送新命令前先冲洗一下缓冲区,双重保险
|
||||||
|
ClearHardwareBuffer();
|
||||||
|
|
||||||
await LoglessSendAsync(command, ct);
|
await LoglessSendAsync(command, ct);
|
||||||
return await LoglessReadAsync(delimiter, ct);
|
return await LoglessReadAsync(delimiter, ct);
|
||||||
}
|
}
|
||||||
@@ -157,9 +195,32 @@ namespace DeviceCommand.Base
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// 物理强制清空串口驱动层及软件层的缓冲区
|
||||||
|
/// </summary>
|
||||||
|
private void ClearHardwareBuffer()
|
||||||
|
{
|
||||||
|
try
|
||||||
|
{
|
||||||
|
if (_serialPort?.IsOpen == true)
|
||||||
|
{
|
||||||
|
_serialPort.DiscardInBuffer(); // 清空硬件接收缓冲区
|
||||||
|
_serialPort.DiscardOutBuffer(); // 清空硬件发送缓冲区
|
||||||
|
}
|
||||||
|
}
|
||||||
|
catch
|
||||||
|
{
|
||||||
|
// 忽略清理异常
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
public void Dispose()
|
public void Dispose()
|
||||||
{
|
{
|
||||||
_serialPort?.Dispose();
|
if (_serialPort != null)
|
||||||
|
{
|
||||||
|
if (_serialPort.IsOpen) _serialPort.Close();
|
||||||
|
_serialPort.Dispose();
|
||||||
|
}
|
||||||
commLock?.Dispose();
|
commLock?.Dispose();
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -8,7 +8,7 @@ using System.Threading.Tasks;
|
|||||||
|
|
||||||
namespace DeviceCommand.Base
|
namespace DeviceCommand.Base
|
||||||
{
|
{
|
||||||
public class Tcp : ITcp
|
public class Tcp : ITcp, IDisposable
|
||||||
{
|
{
|
||||||
public string IPAddress { get; set; } = "127.0.0.1";
|
public string IPAddress { get; set; } = "127.0.0.1";
|
||||||
public int Port { get; set; } = 502;
|
public int Port { get; set; } = 502;
|
||||||
@@ -46,15 +46,7 @@ namespace DeviceCommand.Base
|
|||||||
await _commLock.WaitAsync(ct);
|
await _commLock.WaitAsync(ct);
|
||||||
try
|
try
|
||||||
{
|
{
|
||||||
if (_tcpClient.Connected) return true;
|
return await ResetConnectionAsync(ct);
|
||||||
|
|
||||||
// 修复:释放并彻底清空旧的连接实例,否则复用引发异常
|
|
||||||
_tcpClient.Close();
|
|
||||||
_tcpClient.Dispose();
|
|
||||||
|
|
||||||
_tcpClient = new TcpClient();
|
|
||||||
await _tcpClient.ConnectAsync(IPAddress, Port, ct);
|
|
||||||
return true;
|
|
||||||
}
|
}
|
||||||
finally
|
finally
|
||||||
{
|
{
|
||||||
@@ -62,9 +54,25 @@ namespace DeviceCommand.Base
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// 核心内部方法:无锁状态下安全重置并重建连接
|
||||||
|
/// </summary>
|
||||||
|
private async Task<bool> ResetConnectionAsync(CancellationToken ct)
|
||||||
|
{
|
||||||
|
if (_tcpClient != null)
|
||||||
|
{
|
||||||
|
_tcpClient.Close();
|
||||||
|
_tcpClient.Dispose();
|
||||||
|
}
|
||||||
|
|
||||||
|
_tcpClient = new TcpClient();
|
||||||
|
await _tcpClient.ConnectAsync(IPAddress, Port, ct).ConfigureAwait(false);
|
||||||
|
return true;
|
||||||
|
}
|
||||||
|
|
||||||
public virtual void Close()
|
public virtual void Close()
|
||||||
{
|
{
|
||||||
if (_tcpClient.Connected) _tcpClient.Close();
|
if (_tcpClient?.Connected == true) _tcpClient.Close();
|
||||||
}
|
}
|
||||||
|
|
||||||
private async Task LoglessSendAsync(byte[] buffer, CancellationToken ct)
|
private async Task LoglessSendAsync(byte[] buffer, CancellationToken ct)
|
||||||
@@ -72,10 +80,26 @@ namespace DeviceCommand.Base
|
|||||||
if (!IsConnected) throw new InvalidOperationException("TCP未连接。");
|
if (!IsConnected) throw new InvalidOperationException("TCP未连接。");
|
||||||
|
|
||||||
NetworkStream stream = _tcpClient.GetStream();
|
NetworkStream stream = _tcpClient.GetStream();
|
||||||
using var cts = CancellationTokenSource.CreateLinkedTokenSource(ct);
|
try
|
||||||
if (SendTimeout > 0) cts.CancelAfter(SendTimeout);
|
{
|
||||||
|
if (SendTimeout > 0)
|
||||||
await stream.WriteAsync(buffer, 0, buffer.Length, cts.Token);
|
{
|
||||||
|
// 使用 WaitAsync 控制发送超时
|
||||||
|
await stream.WriteAsync(buffer, 0, buffer.Length, ct)
|
||||||
|
.WaitAsync(TimeSpan.FromMilliseconds(SendTimeout), ct)
|
||||||
|
.ConfigureAwait(false);
|
||||||
|
}
|
||||||
|
else
|
||||||
|
{
|
||||||
|
await stream.WriteAsync(buffer, 0, buffer.Length, ct).ConfigureAwait(false);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
catch (TimeoutException ex)
|
||||||
|
{
|
||||||
|
// 发送超时,安全起见直接断开重建
|
||||||
|
await ResetConnectionAsync(ct);
|
||||||
|
throw new TimeoutException($"TCP 发送数据超时({SendTimeout} ms),连接已重置以确保安全。", ex);
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
public async Task SendAsync(byte[] buffer, CancellationToken ct = default)
|
public async Task SendAsync(byte[] buffer, CancellationToken ct = default)
|
||||||
@@ -107,17 +131,30 @@ namespace DeviceCommand.Base
|
|||||||
byte[] buffer = new byte[length];
|
byte[] buffer = new byte[length];
|
||||||
int offset = 0;
|
int offset = 0;
|
||||||
|
|
||||||
using var cts = CancellationTokenSource.CreateLinkedTokenSource(ct);
|
try
|
||||||
if (ReceiveTimeout > 0) cts.CancelAfter(ReceiveTimeout);
|
|
||||||
|
|
||||||
while (offset < length)
|
|
||||||
{
|
{
|
||||||
int read = await stream.ReadAsync(buffer, offset, length - offset, cts.Token);
|
while (offset < length)
|
||||||
if (read == 0) break;
|
{
|
||||||
offset += read;
|
ct.ThrowIfCancellationRequested();
|
||||||
}
|
|
||||||
|
|
||||||
return offset == 0 ? Array.Empty<byte>() : buffer[..offset];
|
// 计算剩余超时时间(这里简单使用配置值,若要求极精准可加入不长计算)
|
||||||
|
int read = ReceiveTimeout > 0
|
||||||
|
? await stream.ReadAsync(buffer, offset, length - offset, ct)
|
||||||
|
.WaitAsync(TimeSpan.FromMilliseconds(ReceiveTimeout), ct)
|
||||||
|
.ConfigureAwait(false)
|
||||||
|
: await stream.ReadAsync(buffer, offset, length - offset, ct).ConfigureAwait(false);
|
||||||
|
|
||||||
|
if (read == 0) throw new IOException("远程主机已关闭连接");
|
||||||
|
offset += read;
|
||||||
|
}
|
||||||
|
return offset == 0 ? Array.Empty<byte>() : buffer[..offset];
|
||||||
|
}
|
||||||
|
catch (TimeoutException ex)
|
||||||
|
{
|
||||||
|
// 关键安全重置:WaitAsync 超时后抛弃老连接,防止未完成的 Read 污染后续 Buffer
|
||||||
|
await ResetConnectionAsync(ct);
|
||||||
|
throw new TimeoutException($"TCP 读取定长数据超时({ReceiveTimeout} ms),连接已重置避免数据错乱。", ex);
|
||||||
|
}
|
||||||
}
|
}
|
||||||
finally
|
finally
|
||||||
{
|
{
|
||||||
@@ -134,22 +171,37 @@ namespace DeviceCommand.Base
|
|||||||
byte[] buffer = new byte[1024];
|
byte[] buffer = new byte[1024];
|
||||||
NetworkStream stream = _tcpClient.GetStream();
|
NetworkStream stream = _tcpClient.GetStream();
|
||||||
|
|
||||||
using var cts = CancellationTokenSource.CreateLinkedTokenSource(ct);
|
try
|
||||||
if (ReceiveTimeout > 0) cts.CancelAfter(ReceiveTimeout);
|
|
||||||
|
|
||||||
while (!cts.Token.IsCancellationRequested)
|
|
||||||
{
|
{
|
||||||
int bytesRead = await stream.ReadAsync(buffer, 0, buffer.Length, cts.Token);
|
while (true)
|
||||||
if (bytesRead == 0) throw new IOException("远程主机已关闭连接");
|
{
|
||||||
|
ct.ThrowIfCancellationRequested();
|
||||||
|
|
||||||
sb.Append(Encoding.UTF8.GetString(buffer, 0, bytesRead));
|
int bytesRead = ReceiveTimeout > 0
|
||||||
|
? await stream.ReadAsync(buffer, 0, buffer.Length, ct)
|
||||||
|
.WaitAsync(TimeSpan.FromMilliseconds(ReceiveTimeout), ct)
|
||||||
|
.ConfigureAwait(false)
|
||||||
|
: await stream.ReadAsync(buffer, 0, buffer.Length, ct).ConfigureAwait(false);
|
||||||
|
|
||||||
int index = sb.ToString().IndexOf(delimiter, StringComparison.Ordinal);
|
if (bytesRead == 0) throw new IOException("远程主机已关闭连接");
|
||||||
if (index >= 0)
|
|
||||||
return sb.ToString(0, index).Trim();
|
sb.Append(Encoding.UTF8.GetString(buffer, 0, bytesRead));
|
||||||
|
|
||||||
|
string currentText = sb.ToString();
|
||||||
|
int index = currentText.IndexOf(delimiter, StringComparison.Ordinal);
|
||||||
|
if (index >= 0)
|
||||||
|
{
|
||||||
|
return currentText.Substring(0, index).Trim();
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
catch (TimeoutException ex)
|
||||||
|
{
|
||||||
|
// 当 SCPI 仪表由于命令发错不响应时,WaitAsync 会立功并抛出 TimeoutException
|
||||||
|
// 此时我们在内部秒速“重置连接”,上层业务只会收到一个干净的超时报错,但由于锁和自动重连,下一次通信直接恢复!
|
||||||
|
await ResetConnectionAsync(ct);
|
||||||
|
throw new TimeoutException($"SCPI 读取超时(未收到结束符 '{delimiter}',等待:{ReceiveTimeout} ms),通信链路已自动刷新。", ex);
|
||||||
}
|
}
|
||||||
|
|
||||||
throw new TimeoutException("读取数据超时");
|
|
||||||
}
|
}
|
||||||
|
|
||||||
public async Task<string> ReadAsync(string delimiter = "\n", CancellationToken ct = default)
|
public async Task<string> ReadAsync(string delimiter = "\n", CancellationToken ct = default)
|
||||||
@@ -165,7 +217,6 @@ namespace DeviceCommand.Base
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
// 核心优化:确保发送与读取在同一组锁生命周期内
|
|
||||||
public async Task<string> WriteReadAsync(string command, string delimiter = "\n", CancellationToken ct = default)
|
public async Task<string> WriteReadAsync(string command, string delimiter = "\n", CancellationToken ct = default)
|
||||||
{
|
{
|
||||||
await _commLock.WaitAsync(ct);
|
await _commLock.WaitAsync(ct);
|
||||||
|
|||||||
@@ -13,7 +13,7 @@ namespace DeviceCommand.Device
|
|||||||
[ADPCommand]
|
[ADPCommand]
|
||||||
public class IOBoard : ModbusTcp
|
public class IOBoard : ModbusTcp
|
||||||
{
|
{
|
||||||
|
//只有两个,八台产品公用两两分组各用一个
|
||||||
public IOBoard(string Ip地址, int 端口, int 发送超时, int 接收超时)
|
public IOBoard(string Ip地址, int 端口, int 发送超时, int 接收超时)
|
||||||
{
|
{
|
||||||
ConfigureDevice(Ip地址, 端口, 发送超时, 接收超时);
|
ConfigureDevice(Ip地址, 端口, 发送超时, 接收超时);
|
||||||
|
|||||||
@@ -11,6 +11,7 @@ namespace DeviceCommand.Device
|
|||||||
[ADPCommand]
|
[ADPCommand]
|
||||||
public class IT7800E : Tcp
|
public class IT7800E : Tcp
|
||||||
{
|
{
|
||||||
|
//只有一个,八台产品一起用一个
|
||||||
// 根据通用 SCPI 指令规范,使用换行符 (ASCII 字符 LF,即 \n) 作为标准结束符
|
// 根据通用 SCPI 指令规范,使用换行符 (ASCII 字符 LF,即 \n) 作为标准结束符
|
||||||
private const string ScpiDelimiter = "\n";
|
private const string ScpiDelimiter = "\n";
|
||||||
|
|
||||||
|
|||||||
@@ -11,6 +11,7 @@ namespace DeviceCommand.Device
|
|||||||
[ADPCommand]
|
[ADPCommand]
|
||||||
public class N36200 : Tcp
|
public class N36200 : Tcp
|
||||||
{
|
{
|
||||||
|
//只有一个,八台产品一起用一个
|
||||||
// 手册第 9 页 2.2.4 明确规定:命令结束符为换行符 (ASCII 字符 LF,即 \n)
|
// 手册第 9 页 2.2.4 明确规定:命令结束符为换行符 (ASCII 字符 LF,即 \n)
|
||||||
private const string ScpiDelimiter = "\n";
|
private const string ScpiDelimiter = "\n";
|
||||||
|
|
||||||
|
|||||||
@@ -29,7 +29,7 @@ namespace DeviceCommand.Device
|
|||||||
/// </summary>
|
/// </summary>
|
||||||
public virtual async Task 清除状态(CancellationToken ct = default)
|
public virtual async Task 清除状态(CancellationToken ct = default)
|
||||||
{
|
{
|
||||||
await SendAsync($"*CLS{ScpiDelimiter}", ct); //
|
await SendAsync($"*CLS{ScpiDelimiter}", ct);
|
||||||
}
|
}
|
||||||
|
|
||||||
/// <summary>
|
/// <summary>
|
||||||
@@ -37,7 +37,7 @@ namespace DeviceCommand.Device
|
|||||||
/// </summary>
|
/// </summary>
|
||||||
public virtual async Task<string> 查询设备标识(CancellationToken ct = default)
|
public virtual async Task<string> 查询设备标识(CancellationToken ct = default)
|
||||||
{
|
{
|
||||||
return await WriteReadAsync($"*IDN?{ScpiDelimiter}", ScpiDelimiter, ct); //
|
return await WriteReadAsync($"*IDN?{ScpiDelimiter}", ScpiDelimiter, ct);
|
||||||
}
|
}
|
||||||
|
|
||||||
/// <summary>
|
/// <summary>
|
||||||
@@ -45,17 +45,29 @@ namespace DeviceCommand.Device
|
|||||||
/// </summary>
|
/// </summary>
|
||||||
public virtual async Task 重置设备(CancellationToken ct = default)
|
public virtual async Task 重置设备(CancellationToken ct = default)
|
||||||
{
|
{
|
||||||
await SendAsync($"*RST{ScpiDelimiter}", ct); //
|
await SendAsync($"*RST{ScpiDelimiter}", ct);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// 【补充】查询先前操作是否完成。
|
||||||
|
/// 在重置设备(*RST)或切换大物理量程后调用,返回 "1" 代表示波器继电器切换就绪,防止后续指令引发阻塞。
|
||||||
|
/// </summary>
|
||||||
|
public virtual async Task<bool> 检查操作完成_OPC(CancellationToken ct = default)
|
||||||
|
{
|
||||||
|
string res = await WriteReadAsync($"*OPC?{ScpiDelimiter}", ScpiDelimiter, ct);
|
||||||
|
return res.Trim() == "1";
|
||||||
|
}
|
||||||
|
|
||||||
#endregion
|
#endregion
|
||||||
#region 2. 运行与捕获控制控制 (Run / Stop / Single)
|
|
||||||
|
#region 2. 运行与捕获控制 (Run / Stop / Single)
|
||||||
|
|
||||||
/// <summary>
|
/// <summary>
|
||||||
/// 控制示波器开始捕获波形 (等同于按下前端面板的 Run 键)
|
/// 控制示波器开始捕获波形 (等同于按下前端面板的 Run 键)
|
||||||
/// </summary>
|
/// </summary>
|
||||||
public virtual async Task 启动捕获_RUN(CancellationToken ct = default)
|
public virtual async Task 启动捕获_RUN(CancellationToken ct = default)
|
||||||
{
|
{
|
||||||
await SendAsync($"RUN{ScpiDelimiter}", ct); //
|
await SendAsync($"RUN{ScpiDelimiter}", ct);
|
||||||
}
|
}
|
||||||
|
|
||||||
/// <summary>
|
/// <summary>
|
||||||
@@ -63,15 +75,15 @@ namespace DeviceCommand.Device
|
|||||||
/// </summary>
|
/// </summary>
|
||||||
public virtual async Task 停止捕获_STOP(CancellationToken ct = default)
|
public virtual async Task 停止捕获_STOP(CancellationToken ct = default)
|
||||||
{
|
{
|
||||||
await SendAsync($"STOP{ScpiDelimiter}", ct); //
|
await SendAsync($"STOP{ScpiDelimiter}", ct);
|
||||||
}
|
}
|
||||||
|
|
||||||
/// <summary>
|
/// <summary>
|
||||||
/// 强制示波器进入单次触发捕获模式
|
/// 强制示波器进入单次触发捕获模式 (常用于捕捉充电瞬间的过冲浪涌波形)
|
||||||
/// </summary>
|
/// </summary>
|
||||||
public virtual async Task 单次触发_SINGLE(CancellationToken ct = default)
|
public virtual async Task 单次触发_SINGLE(CancellationToken ct = default)
|
||||||
{
|
{
|
||||||
await SendAsync($"SINGle{ScpiDelimiter}", ct); //
|
await SendAsync($"SINGle{ScpiDelimiter}", ct);
|
||||||
}
|
}
|
||||||
|
|
||||||
/// <summary>
|
/// <summary>
|
||||||
@@ -79,7 +91,19 @@ namespace DeviceCommand.Device
|
|||||||
/// </summary>
|
/// </summary>
|
||||||
public virtual async Task 强制触发(CancellationToken ct = default)
|
public virtual async Task 强制触发(CancellationToken ct = default)
|
||||||
{
|
{
|
||||||
await SendAsync($"*TRG{ScpiDelimiter}", ct); //
|
await SendAsync($"*TRG{ScpiDelimiter}", ct);
|
||||||
|
}
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// 【补充】设置触发模式 (AUTO, NORM, SINGLE)
|
||||||
|
/// </summary>
|
||||||
|
public virtual async Task 设置触发模式(string mode, CancellationToken ct = default)
|
||||||
|
{
|
||||||
|
string modeUpper = mode.ToUpper();
|
||||||
|
if (modeUpper != "AUTO" && modeUpper != "NORM" && modeUpper != "SINGLE")
|
||||||
|
throw new ArgumentException("触发模式只能为 AUTO, NORM, 或 SINGLE");
|
||||||
|
|
||||||
|
await SendAsync($"TRMD {modeUpper}{ScpiDelimiter}", ct);
|
||||||
}
|
}
|
||||||
|
|
||||||
#endregion
|
#endregion
|
||||||
@@ -87,12 +111,13 @@ namespace DeviceCommand.Device
|
|||||||
#region 3. Channel 垂直控制子系统 (C1 ~ C4)
|
#region 3. Channel 垂直控制子系统 (C1 ~ C4)
|
||||||
|
|
||||||
/// <summary>
|
/// <summary>
|
||||||
/// 开启或关闭指定的模拟通道 (例如: channel=1 代表 C1)
|
/// 开启或关闭指定的模拟通道
|
||||||
/// </summary>
|
/// </summary>
|
||||||
public virtual async Task 设置通道开关(int channel, bool enable, CancellationToken ct = default)
|
public virtual async Task 设置通道开关(int channel, bool enable, CancellationToken ct = default)
|
||||||
{
|
{
|
||||||
string state = enable ? "ON" : "OFF";
|
// 修正:根据手册规范,使用 1/0 比 ON/OFF 在高低版本固件中兼容性更稳定
|
||||||
await SendAsync($"C{channel}:TRAce {state}{ScpiDelimiter}", ct); //
|
string state = enable ? "1" : "0";
|
||||||
|
await SendAsync($"C{channel}:TRAce {state}{ScpiDelimiter}", ct);
|
||||||
}
|
}
|
||||||
|
|
||||||
/// <summary>
|
/// <summary>
|
||||||
@@ -100,27 +125,35 @@ namespace DeviceCommand.Device
|
|||||||
/// </summary>
|
/// </summary>
|
||||||
public virtual async Task 设置通道电压档位(int channel, double volts, CancellationToken ct = default)
|
public virtual async Task 设置通道电压档位(int channel, double volts, CancellationToken ct = default)
|
||||||
{
|
{
|
||||||
string cmd = string.Format(CultureInfo.InvariantCulture, "C{0}:VDIV {1:F4}{2}", channel, volts, ScpiDelimiter); //
|
string cmd = string.Format(CultureInfo.InvariantCulture, "C{0}:VDIV {1:F4}{2}", channel, volts, ScpiDelimiter);
|
||||||
await SendAsync(cmd, ct);
|
await SendAsync(cmd, ct);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// 【补充验证】查询指定通道当前的电压档位 (用于 Setup-Verify 闭环验证逻辑)
|
||||||
|
/// </summary>
|
||||||
|
public virtual async Task<string> 查询通道电压档位(int channel, CancellationToken ct = default)
|
||||||
|
{
|
||||||
|
return await WriteReadAsync($"C{channel}:VDIV?{ScpiDelimiter}", ScpiDelimiter, ct);
|
||||||
|
}
|
||||||
|
|
||||||
/// <summary>
|
/// <summary>
|
||||||
/// 设置指定通道的垂直偏移量 (Offset,单位: V)
|
/// 设置指定通道的垂直偏移量 (Offset,单位: V)
|
||||||
/// </summary>
|
/// </summary>
|
||||||
public virtual async Task 设置通道垂直偏移(int channel, double offset, CancellationToken ct = default)
|
public virtual async Task 设置通道垂直偏移(int channel, double offset, CancellationToken ct = default)
|
||||||
{
|
{
|
||||||
string cmd = string.Format(CultureInfo.InvariantCulture, "C{0}:OFST {1:F4}{2}", channel, offset, ScpiDelimiter); //
|
string cmd = string.Format(CultureInfo.InvariantCulture, "C{0}:OFST {1:F4}{2}", channel, offset, ScpiDelimiter);
|
||||||
await SendAsync(cmd, ct);
|
await SendAsync(cmd, ct);
|
||||||
}
|
}
|
||||||
|
|
||||||
/// <summary>
|
/// <summary>
|
||||||
/// 设置通道的输入阻抗 (1MΩ 或 50Ω)
|
/// 设置通道的输入阻抗与耦合模式
|
||||||
/// </summary>
|
/// </summary>
|
||||||
/// <param name="is50Ohm">True: 50欧姆, False: 1M欧姆</param>
|
/// <param name="coupling">合法参数:A1M (交流1M), D1M (直流1M), D50 (直流50欧)</param>
|
||||||
public virtual async Task 设置通道输入阻抗(int channel, bool is50Ohm, CancellationToken ct = default)
|
public virtual async Task 设置通道耦合与阻抗(int channel, string coupling, CancellationToken ct = default)
|
||||||
{
|
{
|
||||||
string value = is50Ohm ? "50" : "1M";
|
string coupUpper = coupling.ToUpper();
|
||||||
await SendAsync($"C{channel}:COUPling {value}{ScpiDelimiter}", ct); //
|
await SendAsync($"C{channel}:COUPling {coupUpper}{ScpiDelimiter}", ct);
|
||||||
}
|
}
|
||||||
|
|
||||||
#endregion
|
#endregion
|
||||||
@@ -132,7 +165,8 @@ namespace DeviceCommand.Device
|
|||||||
/// </summary>
|
/// </summary>
|
||||||
public virtual async Task 设置水平时基(double scale, CancellationToken ct = default)
|
public virtual async Task 设置水平时基(double scale, CancellationToken ct = default)
|
||||||
{
|
{
|
||||||
string cmd = string.Format(CultureInfo.InvariantCulture, "TIME_DIV {0:E6}{1}", scale, ScpiDelimiter); //
|
// 修正:统一使用更精简且符合手册定义的简写形式 TDIV
|
||||||
|
string cmd = string.Format(CultureInfo.InvariantCulture, "TDIV {0:E6}{1}", scale, ScpiDelimiter);
|
||||||
await SendAsync(cmd, ct);
|
await SendAsync(cmd, ct);
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -141,7 +175,8 @@ namespace DeviceCommand.Device
|
|||||||
/// </summary>
|
/// </summary>
|
||||||
public virtual async Task 设置水平延迟(double delay, CancellationToken ct = default)
|
public virtual async Task 设置水平延迟(double delay, CancellationToken ct = default)
|
||||||
{
|
{
|
||||||
string cmd = string.Format(CultureInfo.InvariantCulture, "TRIGger:DELay {0:E6}{1}", delay, ScpiDelimiter); //
|
// 修正:采用手册标准简写 TRDL 效率更高
|
||||||
|
string cmd = string.Format(CultureInfo.InvariantCulture, "TRDL {0:E6}{1}", delay, ScpiDelimiter);
|
||||||
await SendAsync(cmd, ct);
|
await SendAsync(cmd, ct);
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -154,7 +189,8 @@ namespace DeviceCommand.Device
|
|||||||
/// </summary>
|
/// </summary>
|
||||||
public virtual async Task 设置触发电平(double level, CancellationToken ct = default)
|
public virtual async Task 设置触发电平(double level, CancellationToken ct = default)
|
||||||
{
|
{
|
||||||
string cmd = string.Format(CultureInfo.InvariantCulture, "TRIGger:LEVel {0:F3}{1}", level, ScpiDelimiter); //
|
// 修正:采用标准简写 TRLV
|
||||||
|
string cmd = string.Format(CultureInfo.InvariantCulture, "TRLV {0:F3}{1}", level, ScpiDelimiter);
|
||||||
await SendAsync(cmd, ct);
|
await SendAsync(cmd, ct);
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -163,7 +199,8 @@ namespace DeviceCommand.Device
|
|||||||
/// </summary>
|
/// </summary>
|
||||||
public virtual async Task 设置触发源(string source, CancellationToken ct = default)
|
public virtual async Task 设置触发源(string source, CancellationToken ct = default)
|
||||||
{
|
{
|
||||||
await SendAsync($"TRIGger:SOURce {source.ToUpper()}{ScpiDelimiter}", ct); //
|
// 修正:采用标准简写 TRSE 体系配置指令
|
||||||
|
await SendAsync($"TRIGger:SOURce {source.ToUpper()}{ScpiDelimiter}", ct);
|
||||||
}
|
}
|
||||||
|
|
||||||
#endregion
|
#endregion
|
||||||
@@ -177,12 +214,11 @@ namespace DeviceCommand.Device
|
|||||||
/// <param name="paramName">参数名称助记符:
|
/// <param name="paramName">参数名称助记符:
|
||||||
/// PKPK(峰峰值), MAX(最大值), MIN(最小值), AMPL(振幅值),
|
/// PKPK(峰峰值), MAX(最大值), MIN(最小值), AMPL(振幅值),
|
||||||
/// FREQ(频率), PER(周期), MEAN(平均值), RMS(均方根) 等</param>
|
/// FREQ(频率), PER(周期), MEAN(平均值), RMS(均方根) 等</param>
|
||||||
/// <returns>设备返回的科学计数法或自定义字符串数值</returns>
|
|
||||||
public virtual async Task<string> 查询通道测量项参数(int channel, string paramName, CancellationToken ct = default)
|
public virtual async Task<string> 查询通道测量项参数(int channel, string paramName, CancellationToken ct = default)
|
||||||
{
|
{
|
||||||
// 语法格式示例:C1:PAVA? FREQ
|
// 优化:采用更兼容的测量读取指令语法格式
|
||||||
string query = string.Format(CultureInfo.InvariantCulture, "C{0}:PAVA? {1}{2}", channel, paramName.ToUpper(), ScpiDelimiter); //
|
string query = string.Format(CultureInfo.InvariantCulture, "C{0}:PAVA? {1}{2}", channel, paramName.ToUpper(), ScpiDelimiter);
|
||||||
return await WriteReadAsync(query, ScpiDelimiter, ct); //
|
return await WriteReadAsync(query, ScpiDelimiter, ct);
|
||||||
}
|
}
|
||||||
|
|
||||||
/// <summary>
|
/// <summary>
|
||||||
@@ -190,7 +226,7 @@ namespace DeviceCommand.Device
|
|||||||
/// </summary>
|
/// </summary>
|
||||||
public virtual async Task<string> 查询实际电压峰峰值(int channel, CancellationToken ct = default)
|
public virtual async Task<string> 查询实际电压峰峰值(int channel, CancellationToken ct = default)
|
||||||
{
|
{
|
||||||
return await 查询通道测量项参数(channel, "PKPK", ct); //
|
return await 查询通道测量项参数(channel, "PKPK", ct);
|
||||||
}
|
}
|
||||||
|
|
||||||
/// <summary>
|
/// <summary>
|
||||||
@@ -198,7 +234,7 @@ namespace DeviceCommand.Device
|
|||||||
/// </summary>
|
/// </summary>
|
||||||
public virtual async Task<string> 查询实际频率(int channel, CancellationToken ct = default)
|
public virtual async Task<string> 查询实际频率(int channel, CancellationToken ct = default)
|
||||||
{
|
{
|
||||||
return await 查询通道测量项参数(channel, "FREQ", ct); //
|
return await 查询通道测量项参数(channel, "FREQ", ct);
|
||||||
}
|
}
|
||||||
|
|
||||||
/// <summary>
|
/// <summary>
|
||||||
@@ -206,7 +242,7 @@ namespace DeviceCommand.Device
|
|||||||
/// </summary>
|
/// </summary>
|
||||||
public virtual async Task<string> 查询实际电压均方根(int channel, CancellationToken ct = default)
|
public virtual async Task<string> 查询实际电压均方根(int channel, CancellationToken ct = default)
|
||||||
{
|
{
|
||||||
return await 查询通道测量项参数(channel, "RMS", ct); //
|
return await 查询通道测量项参数(channel, "RMS", ct);
|
||||||
}
|
}
|
||||||
|
|
||||||
#endregion
|
#endregion
|
||||||
|
|||||||
@@ -11,6 +11,7 @@ namespace DeviceCommand.Device
|
|||||||
[ADPCommand]
|
[ADPCommand]
|
||||||
public class SPAW7000 : Tcp
|
public class SPAW7000 : Tcp
|
||||||
{
|
{
|
||||||
|
//只有两个,八台产品公用两两分组各用一个
|
||||||
// 根据通用 SCPI 与远宽指令规范,使用换行符 (ASCII 字符 LF,即 \n) 作为标准结束符
|
// 根据通用 SCPI 与远宽指令规范,使用换行符 (ASCII 字符 LF,即 \n) 作为标准结束符
|
||||||
private const string ScpiDelimiter = "\n";
|
private const string ScpiDelimiter = "\n";
|
||||||
|
|
||||||
|
|||||||
@@ -17,6 +17,10 @@ namespace DeviceEditModule
|
|||||||
// 设备编辑 View 注册为 Navigation(被动态解析后作为 Tab 内容嵌入 DialogMangerView)
|
// 设备编辑 View 注册为 Navigation(被动态解析后作为 Tab 内容嵌入 DialogMangerView)
|
||||||
containerRegistry.RegisterForNavigation<IT7800EView>("IT7800EView");
|
containerRegistry.RegisterForNavigation<IT7800EView>("IT7800EView");
|
||||||
containerRegistry.RegisterForNavigation<N36200View>("N36200View");
|
containerRegistry.RegisterForNavigation<N36200View>("N36200View");
|
||||||
|
containerRegistry.RegisterForNavigation<N36600View>("N36600View");
|
||||||
|
containerRegistry.RegisterForNavigation<N69200View>("N69200View");
|
||||||
|
containerRegistry.RegisterForNavigation<SDS2000X_HDView>("SDS2000X_HDView");
|
||||||
|
containerRegistry.RegisterForNavigation<SPAW7000View>("SPAW7000View");
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -1,6 +1,7 @@
|
|||||||
using Prism.Commands;
|
using Prism.Commands;
|
||||||
using Prism.Ioc;
|
using Prism.Ioc;
|
||||||
using System;
|
using System;
|
||||||
|
using System.Collections.Generic;
|
||||||
using System.Collections.ObjectModel;
|
using System.Collections.ObjectModel;
|
||||||
using System.Windows.Input;
|
using System.Windows.Input;
|
||||||
using UIShare.PubEvent;
|
using UIShare.PubEvent;
|
||||||
@@ -26,6 +27,9 @@ namespace DeviceEditModule.ViewModels
|
|||||||
/// <summary>所有 Tab 项集合,绑定到标签条的 ItemsControl。</summary>
|
/// <summary>所有 Tab 项集合,绑定到标签条的 ItemsControl。</summary>
|
||||||
public ObservableCollection<DialogTabItemVM> TagItems { get; } = new();
|
public ObservableCollection<DialogTabItemVM> TagItems { get; } = new();
|
||||||
|
|
||||||
|
/// <summary>以设备指纹为 key 追踪已打开的 Tab,防止同一物理设备重复打开。</summary>
|
||||||
|
private readonly Dictionary<string, DialogTabItemVM> _tabDictionary = new();
|
||||||
|
|
||||||
private DialogTabItemVM? _selectedTag;
|
private DialogTabItemVM? _selectedTag;
|
||||||
/// <summary>当前激活的 Tab,其 Content 显示在内容区。</summary>
|
/// <summary>当前激活的 Tab,其 Content 显示在内容区。</summary>
|
||||||
public DialogTabItemVM? SelectedTag
|
public DialogTabItemVM? SelectedTag
|
||||||
@@ -72,7 +76,8 @@ namespace DeviceEditModule.ViewModels
|
|||||||
|
|
||||||
private void OnClose()
|
private void OnClose()
|
||||||
{
|
{
|
||||||
// 关闭前清空所有 Tab,释放内容引用
|
// 关闭前清空所有 Tab,释放内容引用,并清除去重字典
|
||||||
|
_tabDictionary.Clear();
|
||||||
TagItems.Clear();
|
TagItems.Clear();
|
||||||
SelectedTag = null;
|
SelectedTag = null;
|
||||||
RequestClose.Invoke();
|
RequestClose.Invoke();
|
||||||
@@ -82,15 +87,26 @@ namespace DeviceEditModule.ViewModels
|
|||||||
|
|
||||||
#region Tab 管理
|
#region Tab 管理
|
||||||
|
|
||||||
/// <summary>收到事件:创建 Tab 并追加到集合,自动切换选中。</summary>
|
/// <summary>收到事件:若指纹已存在则激活,否则创建并追加到集合。</summary>
|
||||||
private void OnAddTab(DialogTabInfo info)
|
private void OnAddTab(DialogTabInfo info)
|
||||||
{
|
{
|
||||||
|
// 已有相同指纹的 Tab → 直接激活,不重复创建
|
||||||
|
if (!string.IsNullOrEmpty(info.Fingerprint) && _tabDictionary.TryGetValue(info.Fingerprint, out var existing))
|
||||||
|
{
|
||||||
|
ActivateTab(existing);
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
var tab = new DialogTabItemVM(OnSelectTab, OnCloseTab)
|
var tab = new DialogTabItemVM(OnSelectTab, OnCloseTab)
|
||||||
{
|
{
|
||||||
Title = info.Title,
|
Title = info.Title,
|
||||||
Content = info.Content
|
Fingerprint = info.Fingerprint,
|
||||||
|
Content = info.Content
|
||||||
};
|
};
|
||||||
|
|
||||||
|
if (!string.IsNullOrEmpty(info.Fingerprint))
|
||||||
|
_tabDictionary[info.Fingerprint] = tab;
|
||||||
|
|
||||||
TagItems.Add(tab);
|
TagItems.Add(tab);
|
||||||
RaisePropertyChanged(nameof(HasNoTabs));
|
RaisePropertyChanged(nameof(HasNoTabs));
|
||||||
ActivateTab(tab);
|
ActivateTab(tab);
|
||||||
@@ -103,6 +119,8 @@ namespace DeviceEditModule.ViewModels
|
|||||||
private void OnCloseTab(DialogTabItemVM tab)
|
private void OnCloseTab(DialogTabItemVM tab)
|
||||||
{
|
{
|
||||||
int idx = TagItems.IndexOf(tab);
|
int idx = TagItems.IndexOf(tab);
|
||||||
|
if (!string.IsNullOrEmpty(tab.Fingerprint))
|
||||||
|
_tabDictionary.Remove(tab.Fingerprint);
|
||||||
TagItems.Remove(tab);
|
TagItems.Remove(tab);
|
||||||
RaisePropertyChanged(nameof(HasNoTabs));
|
RaisePropertyChanged(nameof(HasNoTabs));
|
||||||
|
|
||||||
|
|||||||
280
DeviceEditModule/ViewModels/N36600ViewModel.cs
Normal file
280
DeviceEditModule/ViewModels/N36600ViewModel.cs
Normal file
@@ -0,0 +1,280 @@
|
|||||||
|
using DeviceCommand.Device;
|
||||||
|
using Prism.Commands;
|
||||||
|
using Prism.Ioc;
|
||||||
|
using System;
|
||||||
|
using System.Threading;
|
||||||
|
using System.Windows.Input;
|
||||||
|
using UIShare.GlobalVariable;
|
||||||
|
using UIShare.ViewModelBase;
|
||||||
|
|
||||||
|
namespace DeviceEditModule.ViewModels
|
||||||
|
{
|
||||||
|
/// <summary>
|
||||||
|
/// N36600 便携式宽范围可编程直流电源控制面板 ViewModel。
|
||||||
|
/// </summary>
|
||||||
|
public class N36600ViewModel : NavigateViewModelBase, IDisposable
|
||||||
|
{
|
||||||
|
#region 私有字段
|
||||||
|
|
||||||
|
private readonly DeviceManager _deviceManager;
|
||||||
|
private N36600? _device;
|
||||||
|
private CancellationTokenSource? _cts;
|
||||||
|
|
||||||
|
#endregion
|
||||||
|
|
||||||
|
#region 设备信息属性
|
||||||
|
|
||||||
|
private string _deviceName = "N36600";
|
||||||
|
public string DeviceName
|
||||||
|
{
|
||||||
|
get => _deviceName;
|
||||||
|
set => SetProperty(ref _deviceName, value);
|
||||||
|
}
|
||||||
|
|
||||||
|
private bool _isConnected;
|
||||||
|
public bool IsConnected
|
||||||
|
{
|
||||||
|
get => _isConnected;
|
||||||
|
set => SetProperty(ref _isConnected, value);
|
||||||
|
}
|
||||||
|
|
||||||
|
private bool _isBusy;
|
||||||
|
public bool IsBusy
|
||||||
|
{
|
||||||
|
get => _isBusy;
|
||||||
|
set => SetProperty(ref _isBusy, value);
|
||||||
|
}
|
||||||
|
|
||||||
|
#endregion
|
||||||
|
|
||||||
|
#region 输入参数属性
|
||||||
|
|
||||||
|
private double _voltage = 12.0;
|
||||||
|
/// <summary>待设置的输出电压值(V)。</summary>
|
||||||
|
public double Voltage
|
||||||
|
{
|
||||||
|
get => _voltage;
|
||||||
|
set => SetProperty(ref _voltage, value);
|
||||||
|
}
|
||||||
|
|
||||||
|
private double _currentLimit = 5.0;
|
||||||
|
/// <summary>待设置的输出电流值(A)。</summary>
|
||||||
|
public double CurrentLimit
|
||||||
|
{
|
||||||
|
get => _currentLimit;
|
||||||
|
set => SetProperty(ref _currentLimit, value);
|
||||||
|
}
|
||||||
|
|
||||||
|
private double _power = 60.0;
|
||||||
|
/// <summary>待设置的输出功率值(W)。</summary>
|
||||||
|
public double Power
|
||||||
|
{
|
||||||
|
get => _power;
|
||||||
|
set => SetProperty(ref _power, value);
|
||||||
|
}
|
||||||
|
|
||||||
|
private double _ovpValue = 15.0;
|
||||||
|
/// <summary>过压保护值(V)。</summary>
|
||||||
|
public double OvpValue
|
||||||
|
{
|
||||||
|
get => _ovpValue;
|
||||||
|
set => SetProperty(ref _ovpValue, value);
|
||||||
|
}
|
||||||
|
|
||||||
|
private double _ocpValue = 6.0;
|
||||||
|
/// <summary>过流保护值(A)。</summary>
|
||||||
|
public double OcpValue
|
||||||
|
{
|
||||||
|
get => _ocpValue;
|
||||||
|
set => SetProperty(ref _ocpValue, value);
|
||||||
|
}
|
||||||
|
|
||||||
|
#endregion
|
||||||
|
|
||||||
|
#region 测量结果属性
|
||||||
|
|
||||||
|
private string _measuredVoltage = "—";
|
||||||
|
public string MeasuredVoltage
|
||||||
|
{
|
||||||
|
get => _measuredVoltage;
|
||||||
|
set => SetProperty(ref _measuredVoltage, value);
|
||||||
|
}
|
||||||
|
|
||||||
|
private string _measuredCurrent = "—";
|
||||||
|
public string MeasuredCurrent
|
||||||
|
{
|
||||||
|
get => _measuredCurrent;
|
||||||
|
set => SetProperty(ref _measuredCurrent, value);
|
||||||
|
}
|
||||||
|
|
||||||
|
private string _measuredPower = "—";
|
||||||
|
public string MeasuredPower
|
||||||
|
{
|
||||||
|
get => _measuredPower;
|
||||||
|
set => SetProperty(ref _measuredPower, value);
|
||||||
|
}
|
||||||
|
|
||||||
|
private string _outputState = "—";
|
||||||
|
/// <summary>当前 DC 输出状态(OUTPut? 查询结果)。</summary>
|
||||||
|
public string OutputState
|
||||||
|
{
|
||||||
|
get => _outputState;
|
||||||
|
set => SetProperty(ref _outputState, value);
|
||||||
|
}
|
||||||
|
|
||||||
|
private string _responseLog = string.Empty;
|
||||||
|
/// <summary>命令响应日志(最新消息在顶部)。</summary>
|
||||||
|
public string ResponseLog
|
||||||
|
{
|
||||||
|
get => _responseLog;
|
||||||
|
set => SetProperty(ref _responseLog, value);
|
||||||
|
}
|
||||||
|
|
||||||
|
#endregion
|
||||||
|
|
||||||
|
#region 命令
|
||||||
|
|
||||||
|
public ICommand QueryIdentityCommand { get; }
|
||||||
|
public ICommand OutputOnCommand { get; }
|
||||||
|
public ICommand OutputOffCommand { get; }
|
||||||
|
public ICommand SetVoltageCommand { get; }
|
||||||
|
public ICommand SetCurrentCommand { get; }
|
||||||
|
public ICommand SetPowerCommand { get; }
|
||||||
|
public ICommand SetOvpCommand { get; }
|
||||||
|
public ICommand SetOcpCommand { get; }
|
||||||
|
public ICommand QueryAllMeasureCommand { get; }
|
||||||
|
public ICommand QueryOutputStateCommand { get; }
|
||||||
|
public ICommand SetRemoteModeCommand { get; }
|
||||||
|
public ICommand SetLocalModeCommand { get; }
|
||||||
|
public ICommand ClearVoltageProtectionCommand { get; }
|
||||||
|
public ICommand ClearCurrentProtectionCommand { get; }
|
||||||
|
|
||||||
|
#endregion
|
||||||
|
|
||||||
|
public N36600ViewModel(IContainerProvider containerProvider) : base(containerProvider)
|
||||||
|
{
|
||||||
|
_deviceManager = containerProvider.Resolve<DeviceManager>();
|
||||||
|
|
||||||
|
QueryIdentityCommand = new DelegateCommand(async () => await Exec(async () => AppendLog("IDN: " + await _device!.查询设备标识(Ct()))));
|
||||||
|
OutputOnCommand = new DelegateCommand(async () => await Exec(async () => { await _device!.设置DC输出(true, Ct()); AppendLog("输出已开启"); }));
|
||||||
|
OutputOffCommand = new DelegateCommand(async () => await Exec(async () => { await _device!.设置DC输出(false, Ct()); AppendLog("输出已关闭"); }));
|
||||||
|
SetVoltageCommand = new DelegateCommand(async () => await Exec(async () => { await _device!.设置电压(Voltage, Ct()); AppendLog($"电压已设为 {Voltage} V"); }));
|
||||||
|
SetCurrentCommand = new DelegateCommand(async () => await Exec(async () => { await _device!.设置电流(CurrentLimit, Ct()); AppendLog($"电流已设为 {CurrentLimit} A"); }));
|
||||||
|
SetPowerCommand = new DelegateCommand(async () => await Exec(async () => { await _device!.设置功率(Power, Ct()); AppendLog($"功率已设为 {Power} W"); }));
|
||||||
|
SetOvpCommand = new DelegateCommand(async () => await Exec(async () => { await _device!.设置过压保护值(OvpValue, Ct()); AppendLog($"OVP已设为 {OvpValue} V"); }));
|
||||||
|
SetOcpCommand = new DelegateCommand(async () => await Exec(async () => { await _device!.设置过流保护值(OcpValue, Ct()); AppendLog($"OCP已设为 {OcpValue} A"); }));
|
||||||
|
SetRemoteModeCommand = new DelegateCommand(async () => await Exec(async () => { await _device!.切换远程控制模式(Ct()); AppendLog("已切换到远程控制模式"); }));
|
||||||
|
SetLocalModeCommand = new DelegateCommand(async () => await Exec(async () => { await _device!.切换本地控制模式(Ct()); AppendLog("已切换到本地控制模式"); }));
|
||||||
|
ClearVoltageProtectionCommand = new DelegateCommand(async () => await Exec(async () => { await _device!.清除电压保护状态(Ct()); AppendLog("电压保护状态已清除"); }));
|
||||||
|
ClearCurrentProtectionCommand = new DelegateCommand(async () => await Exec(async () => { await _device!.清除电流保护状态(Ct()); AppendLog("电流保护状态已清除"); }));
|
||||||
|
|
||||||
|
QueryOutputStateCommand = new DelegateCommand(async () => await Exec(async () =>
|
||||||
|
{
|
||||||
|
OutputState = await _device!.查询DC输出状态(Ct());
|
||||||
|
AppendLog($"输出状态: {OutputState}");
|
||||||
|
}));
|
||||||
|
|
||||||
|
QueryAllMeasureCommand = new DelegateCommand(async () => await Exec(async () =>
|
||||||
|
{
|
||||||
|
MeasuredVoltage = await _device!.查询实际电压(Ct());
|
||||||
|
MeasuredCurrent = await _device!.查询实际电流(Ct());
|
||||||
|
MeasuredPower = await _device!.查询实际功率(Ct());
|
||||||
|
AppendLog($"测量 → 电压:{MeasuredVoltage}V 电流:{MeasuredCurrent}A 功率:{MeasuredPower}W");
|
||||||
|
}));
|
||||||
|
|
||||||
|
Initialize();
|
||||||
|
}
|
||||||
|
|
||||||
|
#region 初始化 / Navigation
|
||||||
|
|
||||||
|
public void Initialize(string? deviceName = null)
|
||||||
|
{
|
||||||
|
N36600? found = null;
|
||||||
|
string? foundName = null;
|
||||||
|
|
||||||
|
if (deviceName != null &&
|
||||||
|
_deviceManager.DeviceMap.TryGetValue(deviceName, out var d) &&
|
||||||
|
d is N36600 n)
|
||||||
|
{
|
||||||
|
found = n;
|
||||||
|
foundName = deviceName;
|
||||||
|
}
|
||||||
|
else
|
||||||
|
{
|
||||||
|
foreach (var kv in _deviceManager.DeviceMap)
|
||||||
|
{
|
||||||
|
if (kv.Value is N36600 n36)
|
||||||
|
{
|
||||||
|
found = n36;
|
||||||
|
foundName = kv.Key;
|
||||||
|
break;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
_device = found;
|
||||||
|
DeviceName = foundName ?? "N36600 (未找到)";
|
||||||
|
IsConnected = _device?.IsConnected ?? false;
|
||||||
|
|
||||||
|
AppendLog(found != null
|
||||||
|
? $"已关联设备 [{DeviceName}],连接状态:{(IsConnected ? "已连接" : "未连接")}"
|
||||||
|
: "未在 DeviceManager 中找到 N36600 设备,请先初始化设备配置。");
|
||||||
|
}
|
||||||
|
|
||||||
|
public override void OnNavigatedTo(NavigationContext context)
|
||||||
|
{
|
||||||
|
var name = context.Parameters.GetValue<string?>("DeviceName");
|
||||||
|
Initialize(name);
|
||||||
|
}
|
||||||
|
|
||||||
|
#endregion
|
||||||
|
|
||||||
|
#region 辅助
|
||||||
|
|
||||||
|
private CancellationToken Ct() => (_cts = new CancellationTokenSource(TimeSpan.FromSeconds(10))).Token;
|
||||||
|
|
||||||
|
private async Task Exec(Func<Task> action)
|
||||||
|
{
|
||||||
|
if (_device == null)
|
||||||
|
{
|
||||||
|
AppendLog("错误:未关联到设备实例,请检查设备配置。");
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
if (IsBusy) return;
|
||||||
|
IsBusy = true;
|
||||||
|
try
|
||||||
|
{
|
||||||
|
await action();
|
||||||
|
IsConnected = _device.IsConnected;
|
||||||
|
}
|
||||||
|
catch (OperationCanceledException)
|
||||||
|
{
|
||||||
|
AppendLog("命令超时或已取消。");
|
||||||
|
}
|
||||||
|
catch (Exception ex)
|
||||||
|
{
|
||||||
|
AppendLog($"错误:{ex.Message}");
|
||||||
|
}
|
||||||
|
finally
|
||||||
|
{
|
||||||
|
IsBusy = false;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
private void AppendLog(string message)
|
||||||
|
{
|
||||||
|
var line = $"[{DateTime.Now:HH:mm:ss}] {message}";
|
||||||
|
ResponseLog = ResponseLog.Length > 4000
|
||||||
|
? line + "\n" + ResponseLog[..3000]
|
||||||
|
: line + "\n" + ResponseLog;
|
||||||
|
}
|
||||||
|
|
||||||
|
#endregion
|
||||||
|
|
||||||
|
public void Dispose()
|
||||||
|
{
|
||||||
|
_cts?.Cancel();
|
||||||
|
_cts?.Dispose();
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
316
DeviceEditModule/ViewModels/N69200ViewModel.cs
Normal file
316
DeviceEditModule/ViewModels/N69200ViewModel.cs
Normal file
@@ -0,0 +1,316 @@
|
|||||||
|
using DeviceCommand.Device;
|
||||||
|
using Prism.Commands;
|
||||||
|
using Prism.Ioc;
|
||||||
|
using System;
|
||||||
|
using System.Threading;
|
||||||
|
using System.Windows.Input;
|
||||||
|
using UIShare.GlobalVariable;
|
||||||
|
using UIShare.ViewModelBase;
|
||||||
|
|
||||||
|
namespace DeviceEditModule.ViewModels
|
||||||
|
{
|
||||||
|
/// <summary>
|
||||||
|
/// N69200 可编程直流电子负载控制面板 ViewModel。
|
||||||
|
/// </summary>
|
||||||
|
public class N69200ViewModel : NavigateViewModelBase, IDisposable
|
||||||
|
{
|
||||||
|
#region 私有字段
|
||||||
|
|
||||||
|
private readonly DeviceManager _deviceManager;
|
||||||
|
private N69200? _device;
|
||||||
|
private CancellationTokenSource? _cts;
|
||||||
|
|
||||||
|
#endregion
|
||||||
|
|
||||||
|
#region 设备信息属性
|
||||||
|
|
||||||
|
private string _deviceName = "N69200";
|
||||||
|
public string DeviceName
|
||||||
|
{
|
||||||
|
get => _deviceName;
|
||||||
|
set => SetProperty(ref _deviceName, value);
|
||||||
|
}
|
||||||
|
|
||||||
|
private bool _isConnected;
|
||||||
|
public bool IsConnected
|
||||||
|
{
|
||||||
|
get => _isConnected;
|
||||||
|
set => SetProperty(ref _isConnected, value);
|
||||||
|
}
|
||||||
|
|
||||||
|
private bool _isBusy;
|
||||||
|
public bool IsBusy
|
||||||
|
{
|
||||||
|
get => _isBusy;
|
||||||
|
set => SetProperty(ref _isBusy, value);
|
||||||
|
}
|
||||||
|
|
||||||
|
#endregion
|
||||||
|
|
||||||
|
#region 输入参数属性
|
||||||
|
|
||||||
|
private string _selectedMode = "CC";
|
||||||
|
/// <summary>工作模式:CC / CV / CP / CR。</summary>
|
||||||
|
public string SelectedMode
|
||||||
|
{
|
||||||
|
get => _selectedMode;
|
||||||
|
set => SetProperty(ref _selectedMode, value);
|
||||||
|
}
|
||||||
|
|
||||||
|
private double _loadValue = 1.0;
|
||||||
|
/// <summary>设定值(根据 SelectedMode 代表 A / V / W / Ω)。</summary>
|
||||||
|
public double LoadValue
|
||||||
|
{
|
||||||
|
get => _loadValue;
|
||||||
|
set => SetProperty(ref _loadValue, value);
|
||||||
|
}
|
||||||
|
|
||||||
|
private double _ovpValue = 60.0;
|
||||||
|
/// <summary>过压保护值(V)。</summary>
|
||||||
|
public double OvpValue
|
||||||
|
{
|
||||||
|
get => _ovpValue;
|
||||||
|
set => SetProperty(ref _ovpValue, value);
|
||||||
|
}
|
||||||
|
|
||||||
|
private double _ocpValue = 10.0;
|
||||||
|
/// <summary>过流保护值(A)。</summary>
|
||||||
|
public double OcpValue
|
||||||
|
{
|
||||||
|
get => _ocpValue;
|
||||||
|
set => SetProperty(ref _ocpValue, value);
|
||||||
|
}
|
||||||
|
|
||||||
|
private double _oppValue = 200.0;
|
||||||
|
/// <summary>过功率保护值(W)。</summary>
|
||||||
|
public double OppValue
|
||||||
|
{
|
||||||
|
get => _oppValue;
|
||||||
|
set => SetProperty(ref _oppValue, value);
|
||||||
|
}
|
||||||
|
|
||||||
|
#endregion
|
||||||
|
|
||||||
|
#region 测量结果属性
|
||||||
|
|
||||||
|
private string _measuredVoltage = "—";
|
||||||
|
public string MeasuredVoltage
|
||||||
|
{
|
||||||
|
get => _measuredVoltage;
|
||||||
|
set => SetProperty(ref _measuredVoltage, value);
|
||||||
|
}
|
||||||
|
|
||||||
|
private string _measuredCurrent = "—";
|
||||||
|
public string MeasuredCurrent
|
||||||
|
{
|
||||||
|
get => _measuredCurrent;
|
||||||
|
set => SetProperty(ref _measuredCurrent, value);
|
||||||
|
}
|
||||||
|
|
||||||
|
private string _measuredPower = "—";
|
||||||
|
public string MeasuredPower
|
||||||
|
{
|
||||||
|
get => _measuredPower;
|
||||||
|
set => SetProperty(ref _measuredPower, value);
|
||||||
|
}
|
||||||
|
|
||||||
|
private string _currentMode = "—";
|
||||||
|
/// <summary>当前负载模式(MODE? 查询结果)。</summary>
|
||||||
|
public string CurrentMode
|
||||||
|
{
|
||||||
|
get => _currentMode;
|
||||||
|
set => SetProperty(ref _currentMode, value);
|
||||||
|
}
|
||||||
|
|
||||||
|
private string _statusByte = "—";
|
||||||
|
/// <summary>状态字节(*STB? 查询结果)。</summary>
|
||||||
|
public string StatusByte
|
||||||
|
{
|
||||||
|
get => _statusByte;
|
||||||
|
set => SetProperty(ref _statusByte, value);
|
||||||
|
}
|
||||||
|
|
||||||
|
private string _responseLog = string.Empty;
|
||||||
|
/// <summary>命令响应日志(最新消息在顶部)。</summary>
|
||||||
|
public string ResponseLog
|
||||||
|
{
|
||||||
|
get => _responseLog;
|
||||||
|
set => SetProperty(ref _responseLog, value);
|
||||||
|
}
|
||||||
|
|
||||||
|
#endregion
|
||||||
|
|
||||||
|
#region 命令
|
||||||
|
|
||||||
|
public ICommand QueryIdentityCommand { get; }
|
||||||
|
public ICommand ResetDeviceCommand { get; }
|
||||||
|
public ICommand InputOnCommand { get; }
|
||||||
|
public ICommand InputOffCommand { get; }
|
||||||
|
public ICommand SetModeCommand { get; }
|
||||||
|
public ICommand QueryModeCommand { get; }
|
||||||
|
public ICommand SetLoadValueCommand { get; }
|
||||||
|
public ICommand QueryAllMeasureCommand { get; }
|
||||||
|
public ICommand QueryStatusCommand { get; }
|
||||||
|
public ICommand SetOvpCommand { get; }
|
||||||
|
public ICommand SetOcpCommand { get; }
|
||||||
|
public ICommand SetOppCommand { get; }
|
||||||
|
public ICommand ClearAlarmCommand { get; }
|
||||||
|
public ICommand SetRemoteModeCommand { get; }
|
||||||
|
public ICommand SetLocalModeCommand { get; }
|
||||||
|
|
||||||
|
#endregion
|
||||||
|
|
||||||
|
public N69200ViewModel(IContainerProvider containerProvider) : base(containerProvider)
|
||||||
|
{
|
||||||
|
_deviceManager = containerProvider.Resolve<DeviceManager>();
|
||||||
|
|
||||||
|
QueryIdentityCommand = new DelegateCommand(async () => await Exec(async () => AppendLog("IDN: " + await _device!.查询设备标识(Ct()))));
|
||||||
|
ResetDeviceCommand = new DelegateCommand(async () => await Exec(async () => { await _device!.重置设备(Ct()); AppendLog("设备已重置"); }));
|
||||||
|
InputOnCommand = new DelegateCommand(async () => await Exec(async () => { await _device!.设置DC输入(true, Ct()); AppendLog("输入已开启"); }));
|
||||||
|
InputOffCommand = new DelegateCommand(async () => await Exec(async () => { await _device!.设置DC输入(false, Ct()); AppendLog("输入已关闭"); }));
|
||||||
|
SetModeCommand = new DelegateCommand(async () => await Exec(async () => { await _device!.设置负载模式(SelectedMode, Ct()); AppendLog($"模式已设为 {SelectedMode}"); }));
|
||||||
|
QueryModeCommand = new DelegateCommand(async () => await Exec(async () => { CurrentMode = await _device!.查询负载模式(Ct()); AppendLog($"当前模式: {CurrentMode}"); }));
|
||||||
|
SetOvpCommand = new DelegateCommand(async () => await Exec(async () => { await _device!.设置过压保护值_OVP(OvpValue, Ct()); AppendLog($"OVP已设为 {OvpValue} V"); }));
|
||||||
|
SetOcpCommand = new DelegateCommand(async () => await Exec(async () => { await _device!.设置过流保护值_OCP(OcpValue, Ct()); AppendLog($"OCP已设为 {OcpValue} A"); }));
|
||||||
|
SetOppCommand = new DelegateCommand(async () => await Exec(async () => { await _device!.设置过功率保护值_OPP(OppValue, Ct()); AppendLog($"OPP已设为 {OppValue} W"); }));
|
||||||
|
ClearAlarmCommand = new DelegateCommand(async () => await Exec(async () => { await _device!.清除保护告警(Ct()); AppendLog("保护告警已清除"); }));
|
||||||
|
SetRemoteModeCommand = new DelegateCommand(async () => await Exec(async () => { await _device!.切换远程控制模式(Ct()); AppendLog("已切换到远程控制模式"); }));
|
||||||
|
SetLocalModeCommand = new DelegateCommand(async () => await Exec(async () => { await _device!.切换本地控制模式(Ct()); AppendLog("已切换到本地控制模式"); }));
|
||||||
|
|
||||||
|
SetLoadValueCommand = new DelegateCommand(async () => await Exec(async () =>
|
||||||
|
{
|
||||||
|
var mode = SelectedMode.ToUpperInvariant();
|
||||||
|
switch (mode)
|
||||||
|
{
|
||||||
|
case "CC":
|
||||||
|
await _device!.设置恒电流CC(LoadValue, Ct());
|
||||||
|
AppendLog($"恒流 CC 已设为 {LoadValue} A");
|
||||||
|
break;
|
||||||
|
case "CV":
|
||||||
|
await _device!.设置恒电压CV(LoadValue, Ct());
|
||||||
|
AppendLog($"恒压 CV 已设为 {LoadValue} V");
|
||||||
|
break;
|
||||||
|
case "CP":
|
||||||
|
await _device!.设置恒功率CP(LoadValue, Ct());
|
||||||
|
AppendLog($"恒功率 CP 已设为 {LoadValue} W");
|
||||||
|
break;
|
||||||
|
case "CR":
|
||||||
|
await _device!.设置恒电阻CR(LoadValue, Ct());
|
||||||
|
AppendLog($"恒电阻 CR 已设为 {LoadValue} Ω");
|
||||||
|
break;
|
||||||
|
default:
|
||||||
|
AppendLog($"未知模式 {SelectedMode},无法设置设定值");
|
||||||
|
break;
|
||||||
|
}
|
||||||
|
}));
|
||||||
|
|
||||||
|
QueryStatusCommand = new DelegateCommand(async () => await Exec(async () =>
|
||||||
|
{
|
||||||
|
StatusByte = await _device!.读取状态字节(Ct());
|
||||||
|
AppendLog($"状态字节: {StatusByte}");
|
||||||
|
}));
|
||||||
|
|
||||||
|
QueryAllMeasureCommand = new DelegateCommand(async () => await Exec(async () =>
|
||||||
|
{
|
||||||
|
MeasuredVoltage = await _device!.查询实际电压(Ct());
|
||||||
|
MeasuredCurrent = await _device!.查询实际电流(Ct());
|
||||||
|
MeasuredPower = await _device!.查询实际功率(Ct());
|
||||||
|
AppendLog($"测量 → 电压:{MeasuredVoltage}V 电流:{MeasuredCurrent}A 功率:{MeasuredPower}W");
|
||||||
|
}));
|
||||||
|
|
||||||
|
Initialize();
|
||||||
|
}
|
||||||
|
|
||||||
|
#region 初始化 / Navigation
|
||||||
|
|
||||||
|
public void Initialize(string? deviceName = null)
|
||||||
|
{
|
||||||
|
N69200? found = null;
|
||||||
|
string? foundName = null;
|
||||||
|
|
||||||
|
if (deviceName != null &&
|
||||||
|
_deviceManager.DeviceMap.TryGetValue(deviceName, out var d) &&
|
||||||
|
d is N69200 n)
|
||||||
|
{
|
||||||
|
found = n;
|
||||||
|
foundName = deviceName;
|
||||||
|
}
|
||||||
|
else
|
||||||
|
{
|
||||||
|
foreach (var kv in _deviceManager.DeviceMap)
|
||||||
|
{
|
||||||
|
if (kv.Value is N69200 n69)
|
||||||
|
{
|
||||||
|
found = n69;
|
||||||
|
foundName = kv.Key;
|
||||||
|
break;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
_device = found;
|
||||||
|
DeviceName = foundName ?? "N69200 (未找到)";
|
||||||
|
IsConnected = _device?.IsConnected ?? false;
|
||||||
|
|
||||||
|
AppendLog(found != null
|
||||||
|
? $"已关联设备 [{DeviceName}],连接状态:{(IsConnected ? "已连接" : "未连接")}"
|
||||||
|
: "未在 DeviceManager 中找到 N69200 设备,请先初始化设备配置。");
|
||||||
|
}
|
||||||
|
|
||||||
|
public override void OnNavigatedTo(NavigationContext context)
|
||||||
|
{
|
||||||
|
var name = context.Parameters.GetValue<string?>("DeviceName");
|
||||||
|
Initialize(name);
|
||||||
|
}
|
||||||
|
|
||||||
|
#endregion
|
||||||
|
|
||||||
|
#region 辅助
|
||||||
|
|
||||||
|
private CancellationToken Ct() => (_cts = new CancellationTokenSource(TimeSpan.FromSeconds(10))).Token;
|
||||||
|
|
||||||
|
private async Task Exec(Func<Task> action)
|
||||||
|
{
|
||||||
|
if (_device == null)
|
||||||
|
{
|
||||||
|
AppendLog("错误:未关联到设备实例,请检查设备配置。");
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
if (IsBusy) return;
|
||||||
|
IsBusy = true;
|
||||||
|
try
|
||||||
|
{
|
||||||
|
await action();
|
||||||
|
IsConnected = _device.IsConnected;
|
||||||
|
}
|
||||||
|
catch (OperationCanceledException)
|
||||||
|
{
|
||||||
|
AppendLog("命令超时或已取消。");
|
||||||
|
}
|
||||||
|
catch (Exception ex)
|
||||||
|
{
|
||||||
|
AppendLog($"错误:{ex.Message}");
|
||||||
|
}
|
||||||
|
finally
|
||||||
|
{
|
||||||
|
IsBusy = false;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
private void AppendLog(string message)
|
||||||
|
{
|
||||||
|
var line = $"[{DateTime.Now:HH:mm:ss}] {message}";
|
||||||
|
ResponseLog = ResponseLog.Length > 4000
|
||||||
|
? line + "\n" + ResponseLog[..3000]
|
||||||
|
: line + "\n" + ResponseLog;
|
||||||
|
}
|
||||||
|
|
||||||
|
#endregion
|
||||||
|
|
||||||
|
public void Dispose()
|
||||||
|
{
|
||||||
|
_cts?.Cancel();
|
||||||
|
_cts?.Dispose();
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
292
DeviceEditModule/ViewModels/SDS2000X_HDViewModel.cs
Normal file
292
DeviceEditModule/ViewModels/SDS2000X_HDViewModel.cs
Normal file
@@ -0,0 +1,292 @@
|
|||||||
|
using DeviceCommand.Device;
|
||||||
|
using Prism.Commands;
|
||||||
|
using Prism.Ioc;
|
||||||
|
using System;
|
||||||
|
using System.Threading;
|
||||||
|
using System.Windows.Input;
|
||||||
|
using UIShare.GlobalVariable;
|
||||||
|
using UIShare.ViewModelBase;
|
||||||
|
|
||||||
|
namespace DeviceEditModule.ViewModels
|
||||||
|
{
|
||||||
|
/// <summary>
|
||||||
|
/// SDS2000X_HD 数字存储示波器控制面板 ViewModel。
|
||||||
|
/// </summary>
|
||||||
|
public class SDS2000X_HDViewModel : NavigateViewModelBase, IDisposable
|
||||||
|
{
|
||||||
|
#region 私有字段
|
||||||
|
|
||||||
|
private readonly DeviceManager _deviceManager;
|
||||||
|
private SDS2000X_HD? _device;
|
||||||
|
private CancellationTokenSource? _cts;
|
||||||
|
|
||||||
|
#endregion
|
||||||
|
|
||||||
|
#region 设备信息属性
|
||||||
|
|
||||||
|
private string _deviceName = "SDS2000X_HD";
|
||||||
|
public string DeviceName
|
||||||
|
{
|
||||||
|
get => _deviceName;
|
||||||
|
set => SetProperty(ref _deviceName, value);
|
||||||
|
}
|
||||||
|
|
||||||
|
private bool _isConnected;
|
||||||
|
public bool IsConnected
|
||||||
|
{
|
||||||
|
get => _isConnected;
|
||||||
|
set => SetProperty(ref _isConnected, value);
|
||||||
|
}
|
||||||
|
|
||||||
|
private bool _isBusy;
|
||||||
|
public bool IsBusy
|
||||||
|
{
|
||||||
|
get => _isBusy;
|
||||||
|
set => SetProperty(ref _isBusy, value);
|
||||||
|
}
|
||||||
|
|
||||||
|
#endregion
|
||||||
|
|
||||||
|
#region 输入参数属性
|
||||||
|
|
||||||
|
private int _channel = 1;
|
||||||
|
/// <summary>当前操作通道(1~4)。</summary>
|
||||||
|
public int Channel
|
||||||
|
{
|
||||||
|
get => _channel;
|
||||||
|
set => SetProperty(ref _channel, value);
|
||||||
|
}
|
||||||
|
|
||||||
|
private double _voltsPerDiv = 1.0;
|
||||||
|
/// <summary>垂直电压档位(V/div)。</summary>
|
||||||
|
public double VoltsPerDiv
|
||||||
|
{
|
||||||
|
get => _voltsPerDiv;
|
||||||
|
set => SetProperty(ref _voltsPerDiv, value);
|
||||||
|
}
|
||||||
|
|
||||||
|
private double _offset = 0.0;
|
||||||
|
/// <summary>垂直偏移(V)。</summary>
|
||||||
|
public double Offset
|
||||||
|
{
|
||||||
|
get => _offset;
|
||||||
|
set => SetProperty(ref _offset, value);
|
||||||
|
}
|
||||||
|
|
||||||
|
private bool _is50Ohm;
|
||||||
|
/// <summary>是否使用 50Ω 输入阻抗,false 为 1MΩ。</summary>
|
||||||
|
public bool Is50Ohm
|
||||||
|
{
|
||||||
|
get => _is50Ohm;
|
||||||
|
set => SetProperty(ref _is50Ohm, value);
|
||||||
|
}
|
||||||
|
|
||||||
|
private double _timeBase = 0.001;
|
||||||
|
/// <summary>水平时基档位(s/div)。</summary>
|
||||||
|
public double TimeBase
|
||||||
|
{
|
||||||
|
get => _timeBase;
|
||||||
|
set => SetProperty(ref _timeBase, value);
|
||||||
|
}
|
||||||
|
|
||||||
|
private double _triggerLevel = 0.0;
|
||||||
|
/// <summary>触发电平(V)。</summary>
|
||||||
|
public double TriggerLevel
|
||||||
|
{
|
||||||
|
get => _triggerLevel;
|
||||||
|
set => SetProperty(ref _triggerLevel, value);
|
||||||
|
}
|
||||||
|
|
||||||
|
private string _triggerSource = "C1";
|
||||||
|
/// <summary>触发源(C1/C2/C3/C4/EX/LINE)。</summary>
|
||||||
|
public string TriggerSource
|
||||||
|
{
|
||||||
|
get => _triggerSource;
|
||||||
|
set => SetProperty(ref _triggerSource, value);
|
||||||
|
}
|
||||||
|
|
||||||
|
#endregion
|
||||||
|
|
||||||
|
#region 测量结果属性
|
||||||
|
|
||||||
|
private string _measuredVpp = "—";
|
||||||
|
public string MeasuredVpp
|
||||||
|
{
|
||||||
|
get => _measuredVpp;
|
||||||
|
set => SetProperty(ref _measuredVpp, value);
|
||||||
|
}
|
||||||
|
|
||||||
|
private string _measuredFrequency = "—";
|
||||||
|
public string MeasuredFrequency
|
||||||
|
{
|
||||||
|
get => _measuredFrequency;
|
||||||
|
set => SetProperty(ref _measuredFrequency, value);
|
||||||
|
}
|
||||||
|
|
||||||
|
private string _measuredRms = "—";
|
||||||
|
public string MeasuredRms
|
||||||
|
{
|
||||||
|
get => _measuredRms;
|
||||||
|
set => SetProperty(ref _measuredRms, value);
|
||||||
|
}
|
||||||
|
|
||||||
|
private string _responseLog = string.Empty;
|
||||||
|
/// <summary>命令响应日志(最新消息在顶部)。</summary>
|
||||||
|
public string ResponseLog
|
||||||
|
{
|
||||||
|
get => _responseLog;
|
||||||
|
set => SetProperty(ref _responseLog, value);
|
||||||
|
}
|
||||||
|
|
||||||
|
#endregion
|
||||||
|
|
||||||
|
#region 命令
|
||||||
|
|
||||||
|
public ICommand QueryIdentityCommand { get; }
|
||||||
|
public ICommand ResetDeviceCommand { get; }
|
||||||
|
public ICommand RunCommand { get; }
|
||||||
|
public ICommand StopCommand { get; }
|
||||||
|
public ICommand SingleCommand { get; }
|
||||||
|
public ICommand ForceTriggerCommand { get; }
|
||||||
|
public ICommand SetChannelOnCommand { get; }
|
||||||
|
public ICommand SetChannelOffCommand { get; }
|
||||||
|
public ICommand SetVoltsDivCommand { get; }
|
||||||
|
public ICommand SetOffsetCommand { get; }
|
||||||
|
|
||||||
|
public ICommand SetTimeBaseCommand { get; }
|
||||||
|
public ICommand SetTriggerLevelCommand { get; }
|
||||||
|
public ICommand SetTriggerSourceCommand { get; }
|
||||||
|
public ICommand QueryMeasurementsCommand{ get; }
|
||||||
|
public ICommand QueryVppCommand { get; }
|
||||||
|
public ICommand QueryFrequencyCommand { get; }
|
||||||
|
public ICommand QueryRmsCommand { get; }
|
||||||
|
|
||||||
|
#endregion
|
||||||
|
|
||||||
|
public SDS2000X_HDViewModel(IContainerProvider containerProvider) : base(containerProvider)
|
||||||
|
{
|
||||||
|
_deviceManager = containerProvider.Resolve<DeviceManager>();
|
||||||
|
|
||||||
|
QueryIdentityCommand = new DelegateCommand(async () => await Exec(async () => AppendLog("IDN: " + await _device!.查询设备标识(Ct()))));
|
||||||
|
ResetDeviceCommand = new DelegateCommand(async () => await Exec(async () => { await _device!.重置设备(Ct()); AppendLog("设备已重置"); }));
|
||||||
|
RunCommand = new DelegateCommand(async () => await Exec(async () => { await _device!.启动捕获_RUN(Ct()); AppendLog("已开始捕获"); }));
|
||||||
|
StopCommand = new DelegateCommand(async () => await Exec(async () => { await _device!.停止捕获_STOP(Ct()); AppendLog("已停止捕获"); }));
|
||||||
|
SingleCommand = new DelegateCommand(async () => await Exec(async () => { await _device!.单次触发_SINGLE(Ct()); AppendLog("已触发单次捕获"); }));
|
||||||
|
ForceTriggerCommand = new DelegateCommand(async () => await Exec(async () => { await _device!.强制触发(Ct()); AppendLog("已强制触发"); }));
|
||||||
|
SetChannelOnCommand = new DelegateCommand(async () => await Exec(async () => { await _device!.设置通道开关(Channel, true, Ct()); AppendLog($"通道 {Channel} 已开启"); }));
|
||||||
|
SetChannelOffCommand = new DelegateCommand(async () => await Exec(async () => { await _device!.设置通道开关(Channel, false, Ct()); AppendLog($"通道 {Channel} 已关闭"); }));
|
||||||
|
SetVoltsDivCommand = new DelegateCommand(async () => await Exec(async () => { await _device!.设置通道电压档位(Channel, VoltsPerDiv, Ct()); AppendLog($"C{Channel} 电压档位已设为 {VoltsPerDiv} V/div"); }));
|
||||||
|
SetOffsetCommand = new DelegateCommand(async () => await Exec(async () => { await _device!.设置通道垂直偏移(Channel, Offset, Ct()); AppendLog($"C{Channel} 垂直偏移已设为 {Offset} V"); }));
|
||||||
|
|
||||||
|
SetTimeBaseCommand = new DelegateCommand(async () => await Exec(async () => { await _device!.设置水平时基(TimeBase, Ct()); AppendLog($"水平时基已设为 {TimeBase} s/div"); }));
|
||||||
|
SetTriggerLevelCommand = new DelegateCommand(async () => await Exec(async () => { await _device!.设置触发电平(TriggerLevel, Ct()); AppendLog($"触发电平已设为 {TriggerLevel} V"); }));
|
||||||
|
SetTriggerSourceCommand = new DelegateCommand(async () => await Exec(async () => { await _device!.设置触发源(TriggerSource, Ct()); AppendLog($"触发源已设为 {TriggerSource}"); }));
|
||||||
|
|
||||||
|
QueryMeasurementsCommand = new DelegateCommand(async () => await Exec(async () =>
|
||||||
|
{
|
||||||
|
MeasuredVpp = await _device!.查询实际电压峰峰值(Channel, Ct());
|
||||||
|
MeasuredFrequency = await _device!.查询实际频率(Channel, Ct());
|
||||||
|
MeasuredRms = await _device!.查询实际电压均方根(Channel, Ct());
|
||||||
|
AppendLog($"C{Channel} 测量 → Vpp:{MeasuredVpp} Freq:{MeasuredFrequency}Hz RMS:{MeasuredRms}V");
|
||||||
|
}));
|
||||||
|
|
||||||
|
QueryVppCommand = new DelegateCommand(async () => await Exec(async () => { MeasuredVpp = await _device!.查询实际电压峰峰值(Channel, Ct()); AppendLog($"C{Channel} Vpp: {MeasuredVpp}"); }));
|
||||||
|
QueryFrequencyCommand = new DelegateCommand(async () => await Exec(async () => { MeasuredFrequency = await _device!.查询实际频率(Channel, Ct()); AppendLog($"C{Channel} Freq: {MeasuredFrequency}"); }));
|
||||||
|
QueryRmsCommand = new DelegateCommand(async () => await Exec(async () => { MeasuredRms = await _device!.查询实际电压均方根(Channel, Ct()); AppendLog($"C{Channel} RMS: {MeasuredRms}"); }));
|
||||||
|
|
||||||
|
Initialize();
|
||||||
|
}
|
||||||
|
|
||||||
|
#region 初始化 / Navigation
|
||||||
|
|
||||||
|
public void Initialize(string? deviceName = null)
|
||||||
|
{
|
||||||
|
SDS2000X_HD? found = null;
|
||||||
|
string? foundName = null;
|
||||||
|
|
||||||
|
if (deviceName != null &&
|
||||||
|
_deviceManager.DeviceMap.TryGetValue(deviceName, out var d) &&
|
||||||
|
d is SDS2000X_HD s)
|
||||||
|
{
|
||||||
|
found = s;
|
||||||
|
foundName = deviceName;
|
||||||
|
}
|
||||||
|
else
|
||||||
|
{
|
||||||
|
foreach (var kv in _deviceManager.DeviceMap)
|
||||||
|
{
|
||||||
|
if (kv.Value is SDS2000X_HD sds)
|
||||||
|
{
|
||||||
|
found = sds;
|
||||||
|
foundName = kv.Key;
|
||||||
|
break;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
_device = found;
|
||||||
|
DeviceName = foundName ?? "SDS2000X_HD (未找到)";
|
||||||
|
IsConnected = _device?.IsConnected ?? false;
|
||||||
|
|
||||||
|
AppendLog(found != null
|
||||||
|
? $"已关联设备 [{DeviceName}],连接状态:{(IsConnected ? "已连接" : "未连接")}"
|
||||||
|
: "未在 DeviceManager 中找到 SDS2000X_HD 设备,请先初始化设备配置。");
|
||||||
|
}
|
||||||
|
|
||||||
|
public override void OnNavigatedTo(NavigationContext context)
|
||||||
|
{
|
||||||
|
var name = context.Parameters.GetValue<string?>("DeviceName");
|
||||||
|
Initialize(name);
|
||||||
|
}
|
||||||
|
|
||||||
|
#endregion
|
||||||
|
|
||||||
|
#region 辅助
|
||||||
|
|
||||||
|
private CancellationToken Ct() => (_cts = new CancellationTokenSource(TimeSpan.FromSeconds(10))).Token;
|
||||||
|
|
||||||
|
private async Task Exec(Func<Task> action)
|
||||||
|
{
|
||||||
|
if (_device == null)
|
||||||
|
{
|
||||||
|
AppendLog("错误:未关联到设备实例,请检查设备配置。");
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
if (IsBusy) return;
|
||||||
|
IsBusy = true;
|
||||||
|
try
|
||||||
|
{
|
||||||
|
await action();
|
||||||
|
IsConnected = _device.IsConnected;
|
||||||
|
}
|
||||||
|
catch (OperationCanceledException)
|
||||||
|
{
|
||||||
|
AppendLog("命令超时或已取消。");
|
||||||
|
}
|
||||||
|
catch (Exception ex)
|
||||||
|
{
|
||||||
|
AppendLog($"错误:{ex.Message}");
|
||||||
|
}
|
||||||
|
finally
|
||||||
|
{
|
||||||
|
IsBusy = false;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
private void AppendLog(string message)
|
||||||
|
{
|
||||||
|
var line = $"[{DateTime.Now:HH:mm:ss}] {message}";
|
||||||
|
ResponseLog = ResponseLog.Length > 4000
|
||||||
|
? line + "\n" + ResponseLog[..3000]
|
||||||
|
: line + "\n" + ResponseLog;
|
||||||
|
}
|
||||||
|
|
||||||
|
#endregion
|
||||||
|
|
||||||
|
public void Dispose()
|
||||||
|
{
|
||||||
|
_cts?.Cancel();
|
||||||
|
_cts?.Dispose();
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
303
DeviceEditModule/ViewModels/SPAW7000ViewModel.cs
Normal file
303
DeviceEditModule/ViewModels/SPAW7000ViewModel.cs
Normal file
@@ -0,0 +1,303 @@
|
|||||||
|
using DeviceCommand.Device;
|
||||||
|
using Prism.Commands;
|
||||||
|
using Prism.Ioc;
|
||||||
|
using System;
|
||||||
|
using System.Threading;
|
||||||
|
using System.Windows.Input;
|
||||||
|
using UIShare.GlobalVariable;
|
||||||
|
using UIShare.ViewModelBase;
|
||||||
|
|
||||||
|
namespace DeviceEditModule.ViewModels
|
||||||
|
{
|
||||||
|
/// <summary>
|
||||||
|
/// SPAW7000 功率分析记录仪控制面板 ViewModel。
|
||||||
|
/// </summary>
|
||||||
|
public class SPAW7000ViewModel : NavigateViewModelBase, IDisposable
|
||||||
|
{
|
||||||
|
#region 私有字段
|
||||||
|
|
||||||
|
private readonly DeviceManager _deviceManager;
|
||||||
|
private SPAW7000? _device;
|
||||||
|
private CancellationTokenSource? _cts;
|
||||||
|
|
||||||
|
#endregion
|
||||||
|
|
||||||
|
#region 设备信息属性
|
||||||
|
|
||||||
|
private string _deviceName = "SPAW7000";
|
||||||
|
public string DeviceName
|
||||||
|
{
|
||||||
|
get => _deviceName;
|
||||||
|
set => SetProperty(ref _deviceName, value);
|
||||||
|
}
|
||||||
|
|
||||||
|
private bool _isConnected;
|
||||||
|
public bool IsConnected
|
||||||
|
{
|
||||||
|
get => _isConnected;
|
||||||
|
set => SetProperty(ref _isConnected, value);
|
||||||
|
}
|
||||||
|
|
||||||
|
private bool _isBusy;
|
||||||
|
public bool IsBusy
|
||||||
|
{
|
||||||
|
get => _isBusy;
|
||||||
|
set => SetProperty(ref _isBusy, value);
|
||||||
|
}
|
||||||
|
|
||||||
|
#endregion
|
||||||
|
|
||||||
|
#region 输入参数属性
|
||||||
|
|
||||||
|
private int _channel = 1;
|
||||||
|
/// <summary>当前操作通道。</summary>
|
||||||
|
public int Channel
|
||||||
|
{
|
||||||
|
get => _channel;
|
||||||
|
set => SetProperty(ref _channel, value);
|
||||||
|
}
|
||||||
|
|
||||||
|
private double _voltageRange = 300.0;
|
||||||
|
/// <summary>电压量程(V)。</summary>
|
||||||
|
public double VoltageRange
|
||||||
|
{
|
||||||
|
get => _voltageRange;
|
||||||
|
set => SetProperty(ref _voltageRange, value);
|
||||||
|
}
|
||||||
|
|
||||||
|
private double _currentRange = 5.0;
|
||||||
|
/// <summary>电流量程(A)。</summary>
|
||||||
|
public double CurrentRange
|
||||||
|
{
|
||||||
|
get => _currentRange;
|
||||||
|
set => SetProperty(ref _currentRange, value);
|
||||||
|
}
|
||||||
|
|
||||||
|
private string _couplingMode = "DC";
|
||||||
|
/// <summary>耦合模式:AC / DC / ACDC。</summary>
|
||||||
|
public string CouplingMode
|
||||||
|
{
|
||||||
|
get => _couplingMode;
|
||||||
|
set => SetProperty(ref _couplingMode, value);
|
||||||
|
}
|
||||||
|
|
||||||
|
private int _resolution = 6;
|
||||||
|
/// <summary>显示分辨率(5 或 6)。</summary>
|
||||||
|
public int Resolution
|
||||||
|
{
|
||||||
|
get => _resolution;
|
||||||
|
set => SetProperty(ref _resolution, value);
|
||||||
|
}
|
||||||
|
|
||||||
|
private int _brightness = 5;
|
||||||
|
/// <summary>屏幕亮度(1~10)。</summary>
|
||||||
|
public int Brightness
|
||||||
|
{
|
||||||
|
get => _brightness;
|
||||||
|
set => SetProperty(ref _brightness, value);
|
||||||
|
}
|
||||||
|
|
||||||
|
#endregion
|
||||||
|
|
||||||
|
#region 测量结果属性
|
||||||
|
|
||||||
|
private string _measuredVoltage = "—";
|
||||||
|
public string MeasuredVoltage
|
||||||
|
{
|
||||||
|
get => _measuredVoltage;
|
||||||
|
set => SetProperty(ref _measuredVoltage, value);
|
||||||
|
}
|
||||||
|
|
||||||
|
private string _measuredCurrent = "—";
|
||||||
|
public string MeasuredCurrent
|
||||||
|
{
|
||||||
|
get => _measuredCurrent;
|
||||||
|
set => SetProperty(ref _measuredCurrent, value);
|
||||||
|
}
|
||||||
|
|
||||||
|
private string _measuredPower = "—";
|
||||||
|
public string MeasuredPower
|
||||||
|
{
|
||||||
|
get => _measuredPower;
|
||||||
|
set => SetProperty(ref _measuredPower, value);
|
||||||
|
}
|
||||||
|
|
||||||
|
private string _measuredFrequency = "—";
|
||||||
|
public string MeasuredFrequency
|
||||||
|
{
|
||||||
|
get => _measuredFrequency;
|
||||||
|
set => SetProperty(ref _measuredFrequency, value);
|
||||||
|
}
|
||||||
|
|
||||||
|
private string _measuredPowerFactor = "—";
|
||||||
|
public string MeasuredPowerFactor
|
||||||
|
{
|
||||||
|
get => _measuredPowerFactor;
|
||||||
|
set => SetProperty(ref _measuredPowerFactor, value);
|
||||||
|
}
|
||||||
|
|
||||||
|
private string _deviceModel = "—";
|
||||||
|
public string DeviceModel
|
||||||
|
{
|
||||||
|
get => _deviceModel;
|
||||||
|
set => SetProperty(ref _deviceModel, value);
|
||||||
|
}
|
||||||
|
|
||||||
|
private string _deviceSerial = "—";
|
||||||
|
public string DeviceSerial
|
||||||
|
{
|
||||||
|
get => _deviceSerial;
|
||||||
|
set => SetProperty(ref _deviceSerial, value);
|
||||||
|
}
|
||||||
|
|
||||||
|
private string _responseLog = string.Empty;
|
||||||
|
/// <summary>命令响应日志(最新消息在顶部)。</summary>
|
||||||
|
public string ResponseLog
|
||||||
|
{
|
||||||
|
get => _responseLog;
|
||||||
|
set => SetProperty(ref _responseLog, value);
|
||||||
|
}
|
||||||
|
|
||||||
|
#endregion
|
||||||
|
|
||||||
|
#region 命令
|
||||||
|
|
||||||
|
public ICommand QueryIdentityCommand { get; }
|
||||||
|
public ICommand ResetDeviceCommand { get; }
|
||||||
|
public ICommand QueryAllMeasureCommand { get; }
|
||||||
|
public ICommand SetVoltageRangeCommand { get; }
|
||||||
|
public ICommand SetCurrentRangeCommand { get; }
|
||||||
|
public ICommand SetCouplingModeCommand { get; }
|
||||||
|
public ICommand SetResolutionCommand { get; }
|
||||||
|
public ICommand SetBrightnessCommand { get; }
|
||||||
|
public ICommand SetTouchLockOnCommand { get; }
|
||||||
|
public ICommand SetTouchLockOffCommand { get; }
|
||||||
|
public ICommand QueryModelCommand { get; }
|
||||||
|
public ICommand QuerySerialCommand { get; }
|
||||||
|
public ICommand QueryStatusByteCommand { get; }
|
||||||
|
|
||||||
|
#endregion
|
||||||
|
|
||||||
|
public SPAW7000ViewModel(IContainerProvider containerProvider) : base(containerProvider)
|
||||||
|
{
|
||||||
|
_deviceManager = containerProvider.Resolve<DeviceManager>();
|
||||||
|
|
||||||
|
QueryIdentityCommand = new DelegateCommand(async () => await Exec(async () => AppendLog("IDN: " + await _device!.查询设备标识(Ct()))));
|
||||||
|
ResetDeviceCommand = new DelegateCommand(async () => await Exec(async () => { await _device!.重置设备(Ct()); AppendLog("设备已重置"); }));
|
||||||
|
SetVoltageRangeCommand = new DelegateCommand(async () => await Exec(async () => { await _device!.设置电压量程(Channel, VoltageRange, Ct()); AppendLog($"通道 {Channel} 电压量程已设为 {VoltageRange} V"); }));
|
||||||
|
SetCurrentRangeCommand = new DelegateCommand(async () => await Exec(async () => { await _device!.设置电流量程(Channel, CurrentRange, Ct()); AppendLog($"通道 {Channel} 电流量程已设为 {CurrentRange} A"); }));
|
||||||
|
SetCouplingModeCommand = new DelegateCommand(async () => await Exec(async () => { await _device!.设置通道耦合模式(Channel, CouplingMode, Ct()); AppendLog($"通道 {Channel} 耦合模式已设为 {CouplingMode}"); }));
|
||||||
|
SetResolutionCommand = new DelegateCommand(async () => await Exec(async () => { await _device!.设置显示分辨率(Resolution, Ct()); AppendLog($"显示分辨率已设为 {Resolution} 位"); }));
|
||||||
|
SetBrightnessCommand = new DelegateCommand(async () => await Exec(async () => { await _device!.设置显示亮度(Brightness, Ct()); AppendLog($"屏幕亮度已设为 {Brightness}"); }));
|
||||||
|
SetTouchLockOnCommand = new DelegateCommand(async () => await Exec(async () => { await _device!.设置屏幕触摸锁定(true, Ct()); AppendLog("屏幕触摸已锁定"); }));
|
||||||
|
SetTouchLockOffCommand = new DelegateCommand(async () => await Exec(async () => { await _device!.设置屏幕触摸锁定(false, Ct()); AppendLog("屏幕触摸已解锁"); }));
|
||||||
|
QueryModelCommand = new DelegateCommand(async () => await Exec(async () => { DeviceModel = await _device!.查询设备型号(Ct()); AppendLog($"型号: {DeviceModel}"); }));
|
||||||
|
QuerySerialCommand = new DelegateCommand(async () => await Exec(async () => { DeviceSerial = await _device!.查询设备序列号(Ct()); AppendLog($"序列号: {DeviceSerial}"); }));
|
||||||
|
QueryStatusByteCommand = new DelegateCommand(async () => await Exec(async () => AppendLog("STB: " + await _device!.读取状态字节(Ct()))));
|
||||||
|
|
||||||
|
QueryAllMeasureCommand = new DelegateCommand(async () => await Exec(async () =>
|
||||||
|
{
|
||||||
|
MeasuredVoltage = await _device!.查询实际电压(Channel, Ct());
|
||||||
|
MeasuredCurrent = await _device!.查询实际电流(Channel, Ct());
|
||||||
|
MeasuredPower = await _device!.查询实际功率(Channel, Ct());
|
||||||
|
MeasuredFrequency = await _device!.查询频率(Channel, Ct());
|
||||||
|
MeasuredPowerFactor = await _device!.查询功率因数(Channel, Ct());
|
||||||
|
AppendLog($"CH{Channel} 测量 → U:{MeasuredVoltage}V I:{MeasuredCurrent}A P:{MeasuredPower}W F:{MeasuredFrequency}Hz PF:{MeasuredPowerFactor}");
|
||||||
|
}));
|
||||||
|
|
||||||
|
Initialize();
|
||||||
|
}
|
||||||
|
|
||||||
|
#region 初始化 / Navigation
|
||||||
|
|
||||||
|
public void Initialize(string? deviceName = null)
|
||||||
|
{
|
||||||
|
SPAW7000? found = null;
|
||||||
|
string? foundName = null;
|
||||||
|
|
||||||
|
if (deviceName != null &&
|
||||||
|
_deviceManager.DeviceMap.TryGetValue(deviceName, out var d) &&
|
||||||
|
d is SPAW7000 s)
|
||||||
|
{
|
||||||
|
found = s;
|
||||||
|
foundName = deviceName;
|
||||||
|
}
|
||||||
|
else
|
||||||
|
{
|
||||||
|
foreach (var kv in _deviceManager.DeviceMap)
|
||||||
|
{
|
||||||
|
if (kv.Value is SPAW7000 spaw)
|
||||||
|
{
|
||||||
|
found = spaw;
|
||||||
|
foundName = kv.Key;
|
||||||
|
break;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
_device = found;
|
||||||
|
DeviceName = foundName ?? "SPAW7000 (未找到)";
|
||||||
|
IsConnected = _device?.IsConnected ?? false;
|
||||||
|
|
||||||
|
AppendLog(found != null
|
||||||
|
? $"已关联设备 [{DeviceName}],连接状态:{(IsConnected ? "已连接" : "未连接")}"
|
||||||
|
: "未在 DeviceManager 中找到 SPAW7000 设备,请先初始化设备配置。");
|
||||||
|
}
|
||||||
|
|
||||||
|
public override void OnNavigatedTo(NavigationContext context)
|
||||||
|
{
|
||||||
|
var name = context.Parameters.GetValue<string?>("DeviceName");
|
||||||
|
Initialize(name);
|
||||||
|
}
|
||||||
|
|
||||||
|
#endregion
|
||||||
|
|
||||||
|
#region 辅助
|
||||||
|
|
||||||
|
private CancellationToken Ct() => (_cts = new CancellationTokenSource(TimeSpan.FromSeconds(10))).Token;
|
||||||
|
|
||||||
|
private async Task Exec(Func<Task> action)
|
||||||
|
{
|
||||||
|
if (_device == null)
|
||||||
|
{
|
||||||
|
AppendLog("错误:未关联到设备实例,请检查设备配置。");
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
if (IsBusy) return;
|
||||||
|
IsBusy = true;
|
||||||
|
try
|
||||||
|
{
|
||||||
|
await action();
|
||||||
|
IsConnected = _device.IsConnected;
|
||||||
|
}
|
||||||
|
catch (OperationCanceledException)
|
||||||
|
{
|
||||||
|
AppendLog("命令超时或已取消。");
|
||||||
|
}
|
||||||
|
catch (Exception ex)
|
||||||
|
{
|
||||||
|
AppendLog($"错误:{ex.Message}");
|
||||||
|
}
|
||||||
|
finally
|
||||||
|
{
|
||||||
|
IsBusy = false;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
private void AppendLog(string message)
|
||||||
|
{
|
||||||
|
var line = $"[{DateTime.Now:HH:mm:ss}] {message}";
|
||||||
|
ResponseLog = ResponseLog.Length > 4000
|
||||||
|
? line + "\n" + ResponseLog[..3000]
|
||||||
|
: line + "\n" + ResponseLog;
|
||||||
|
}
|
||||||
|
|
||||||
|
#endregion
|
||||||
|
|
||||||
|
public void Dispose()
|
||||||
|
{
|
||||||
|
_cts?.Cancel();
|
||||||
|
_cts?.Dispose();
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
249
DeviceEditModule/Views/N36600View.xaml
Normal file
249
DeviceEditModule/Views/N36600View.xaml
Normal file
@@ -0,0 +1,249 @@
|
|||||||
|
<UserControl x:Class="DeviceEditModule.Views.N36600View"
|
||||||
|
xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation"
|
||||||
|
xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml"
|
||||||
|
xmlns:mc="http://schemas.openxmlformats.org/markup-compatibility/2006"
|
||||||
|
xmlns:d="http://schemas.microsoft.com/expression/blend/2008"
|
||||||
|
xmlns:prism="http://prismlibrary.com/"
|
||||||
|
xmlns:materialDesign="http://materialdesigninxaml.net/winfx/xaml/themes"
|
||||||
|
xmlns:converters="clr-namespace:UIShare.Converters;assembly=UIShare"
|
||||||
|
mc:Ignorable="d"
|
||||||
|
prism:ViewModelLocator.AutoWireViewModel="True"
|
||||||
|
d:DesignHeight="760" d:DesignWidth="860">
|
||||||
|
|
||||||
|
<UserControl.Resources>
|
||||||
|
<converters:BooleanToVisibilityConverter x:Key="BoolToVis"/>
|
||||||
|
</UserControl.Resources>
|
||||||
|
|
||||||
|
<ScrollViewer VerticalScrollBarVisibility="Auto" HorizontalScrollBarVisibility="Disabled">
|
||||||
|
<StackPanel Margin="12">
|
||||||
|
|
||||||
|
<!-- ═══ 设备信息头 ═══ -->
|
||||||
|
<materialDesign:Card Margin="0,0,0,8" Padding="12,8">
|
||||||
|
<Grid>
|
||||||
|
<Grid.ColumnDefinitions>
|
||||||
|
<ColumnDefinition Width="Auto"/>
|
||||||
|
<ColumnDefinition Width="*"/>
|
||||||
|
<ColumnDefinition Width="Auto"/>
|
||||||
|
</Grid.ColumnDefinitions>
|
||||||
|
|
||||||
|
<StackPanel Orientation="Horizontal" VerticalAlignment="Center">
|
||||||
|
<materialDesign:PackIcon Kind="BatteryCharging" Width="22" Height="22"
|
||||||
|
Foreground="#2E7D32" Margin="0,0,8,0"
|
||||||
|
VerticalAlignment="Center"/>
|
||||||
|
<TextBlock Text="N36600 便携式宽范围可编程直流电源"
|
||||||
|
FontSize="15" FontWeight="Bold"
|
||||||
|
VerticalAlignment="Center"/>
|
||||||
|
<TextBlock Text="{Binding DeviceName, StringFormat=' [{0}]'}"
|
||||||
|
FontSize="13" Foreground="#757575"
|
||||||
|
VerticalAlignment="Center" Margin="4,0,0,0"/>
|
||||||
|
</StackPanel>
|
||||||
|
|
||||||
|
<!-- 连接状态指示 -->
|
||||||
|
<StackPanel Grid.Column="2" Orientation="Horizontal" VerticalAlignment="Center">
|
||||||
|
<Border Width="10" Height="10" CornerRadius="5" Margin="0,0,6,0">
|
||||||
|
<Border.Style>
|
||||||
|
<Style TargetType="Border">
|
||||||
|
<Setter Property="Background" Value="#F44336"/>
|
||||||
|
<Style.Triggers>
|
||||||
|
<DataTrigger Binding="{Binding IsConnected}" Value="True">
|
||||||
|
<Setter Property="Background" Value="#4CAF50"/>
|
||||||
|
</DataTrigger>
|
||||||
|
</Style.Triggers>
|
||||||
|
</Style>
|
||||||
|
</Border.Style>
|
||||||
|
</Border>
|
||||||
|
<TextBlock VerticalAlignment="Center" FontSize="12">
|
||||||
|
<TextBlock.Style>
|
||||||
|
<Style TargetType="TextBlock">
|
||||||
|
<Setter Property="Text" Value="未连接"/>
|
||||||
|
<Setter Property="Foreground" Value="#F44336"/>
|
||||||
|
<Style.Triggers>
|
||||||
|
<DataTrigger Binding="{Binding IsConnected}" Value="True">
|
||||||
|
<Setter Property="Text" Value="已连接"/>
|
||||||
|
<Setter Property="Foreground" Value="#4CAF50"/>
|
||||||
|
</DataTrigger>
|
||||||
|
</Style.Triggers>
|
||||||
|
</Style>
|
||||||
|
</TextBlock.Style>
|
||||||
|
</TextBlock>
|
||||||
|
<ProgressBar IsIndeterminate="True" Width="80" Height="4"
|
||||||
|
Margin="12,0,0,0"
|
||||||
|
Visibility="{Binding IsBusy, Converter={StaticResource BoolToVis}}"/>
|
||||||
|
</StackPanel>
|
||||||
|
</Grid>
|
||||||
|
</materialDesign:Card>
|
||||||
|
|
||||||
|
<!-- ═══ 主体 2 列 ═══ -->
|
||||||
|
<Grid>
|
||||||
|
<Grid.ColumnDefinitions>
|
||||||
|
<ColumnDefinition Width="*"/>
|
||||||
|
<ColumnDefinition Width="*"/>
|
||||||
|
</Grid.ColumnDefinitions>
|
||||||
|
|
||||||
|
<!-- ─── 左列 ─── -->
|
||||||
|
<StackPanel Grid.Column="0" Margin="0,0,4,0">
|
||||||
|
|
||||||
|
<!-- 输出控制 -->
|
||||||
|
<GroupBox Header="输出控制" Margin="0,0,0,8"
|
||||||
|
materialDesign:ColorZoneAssist.Mode="PrimaryLight">
|
||||||
|
<StackPanel Margin="4,4,4,4">
|
||||||
|
<!-- 开关输出 -->
|
||||||
|
<StackPanel Orientation="Horizontal" Margin="0,4">
|
||||||
|
<TextBlock Text="DC 输出" Style="{StaticResource ParamLabel}"/>
|
||||||
|
<Button Content="开启输出" Command="{Binding OutputOnCommand}"
|
||||||
|
Style="{StaticResource MaterialDesignRaisedButton}"
|
||||||
|
Background="#388E3C" Foreground="White"
|
||||||
|
Height="32" Padding="12,0" FontSize="12" Margin="4,0"/>
|
||||||
|
<Button Content="关闭输出" Command="{Binding OutputOffCommand}"
|
||||||
|
Style="{StaticResource WarnBtn}"/>
|
||||||
|
</StackPanel>
|
||||||
|
<!-- 远程/本地 -->
|
||||||
|
<StackPanel Orientation="Horizontal" Margin="0,4">
|
||||||
|
<TextBlock Text="控制模式" Style="{StaticResource ParamLabel}"/>
|
||||||
|
<Button Content="远程控制" Command="{Binding SetRemoteModeCommand}"
|
||||||
|
Style="{StaticResource CmdBtn}"/>
|
||||||
|
<Button Content="本地控制" Command="{Binding SetLocalModeCommand}"
|
||||||
|
Style="{StaticResource CmdBtn}"/>
|
||||||
|
</StackPanel>
|
||||||
|
<!-- 清除保护 -->
|
||||||
|
<StackPanel Orientation="Horizontal" Margin="0,4">
|
||||||
|
<TextBlock Text="清除保护" Style="{StaticResource ParamLabel}"/>
|
||||||
|
<Button Content="清除OVP" Command="{Binding ClearVoltageProtectionCommand}"
|
||||||
|
Style="{StaticResource WarnBtn}"/>
|
||||||
|
<Button Content="清除OCP" Command="{Binding ClearCurrentProtectionCommand}"
|
||||||
|
Style="{StaticResource WarnBtn}"/>
|
||||||
|
</StackPanel>
|
||||||
|
</StackPanel>
|
||||||
|
</GroupBox>
|
||||||
|
|
||||||
|
<!-- 参数设置 -->
|
||||||
|
<GroupBox Header="参数设置" Margin="0,0,0,8"
|
||||||
|
materialDesign:ColorZoneAssist.Mode="PrimaryLight">
|
||||||
|
<StackPanel Margin="4,4,4,4">
|
||||||
|
<!-- 电压 -->
|
||||||
|
<StackPanel Orientation="Horizontal" Margin="0,4">
|
||||||
|
<TextBlock Text="电压 (V)" Style="{StaticResource ParamLabel}"/>
|
||||||
|
<TextBox Style="{StaticResource NumInput}"
|
||||||
|
materialDesign:HintAssist.Hint=""
|
||||||
|
Text="{Binding Voltage, UpdateSourceTrigger=PropertyChanged}"/>
|
||||||
|
<Button Content="设置" Command="{Binding SetVoltageCommand}"
|
||||||
|
Style="{StaticResource CmdBtn}"/>
|
||||||
|
</StackPanel>
|
||||||
|
<!-- 电流 -->
|
||||||
|
<StackPanel Orientation="Horizontal" Margin="0,4">
|
||||||
|
<TextBlock Text="电流 (A)" Style="{StaticResource ParamLabel}"/>
|
||||||
|
<TextBox Style="{StaticResource NumInput}"
|
||||||
|
materialDesign:HintAssist.Hint=""
|
||||||
|
Text="{Binding CurrentLimit, UpdateSourceTrigger=PropertyChanged}"/>
|
||||||
|
<Button Content="设置" Command="{Binding SetCurrentCommand}"
|
||||||
|
Style="{StaticResource CmdBtn}"/>
|
||||||
|
</StackPanel>
|
||||||
|
<!-- 功率 -->
|
||||||
|
<StackPanel Orientation="Horizontal" Margin="0,4">
|
||||||
|
<TextBlock Text="功率 (W)" Style="{StaticResource ParamLabel}"/>
|
||||||
|
<TextBox Style="{StaticResource NumInput}"
|
||||||
|
materialDesign:HintAssist.Hint=""
|
||||||
|
Text="{Binding Power, UpdateSourceTrigger=PropertyChanged}"/>
|
||||||
|
<Button Content="设置" Command="{Binding SetPowerCommand}"
|
||||||
|
Style="{StaticResource CmdBtn}"/>
|
||||||
|
</StackPanel>
|
||||||
|
</StackPanel>
|
||||||
|
</GroupBox>
|
||||||
|
|
||||||
|
<!-- 保护设置 -->
|
||||||
|
<GroupBox Header="保护设置" Margin="0,0,0,8"
|
||||||
|
materialDesign:ColorZoneAssist.Mode="PrimaryLight">
|
||||||
|
<StackPanel Margin="4,4,4,4">
|
||||||
|
<StackPanel Orientation="Horizontal" Margin="0,4">
|
||||||
|
<TextBlock Text="OVP (V)" Style="{StaticResource ParamLabel}"/>
|
||||||
|
<TextBox Style="{StaticResource NumInput}"
|
||||||
|
materialDesign:HintAssist.Hint=""
|
||||||
|
Text="{Binding OvpValue, UpdateSourceTrigger=PropertyChanged}"/>
|
||||||
|
<Button Content="设置 OVP" Command="{Binding SetOvpCommand}"
|
||||||
|
Style="{StaticResource CmdBtn}"/>
|
||||||
|
</StackPanel>
|
||||||
|
<StackPanel Orientation="Horizontal" Margin="0,4">
|
||||||
|
<TextBlock Text="OCP (A)" Style="{StaticResource ParamLabel}"/>
|
||||||
|
<TextBox Style="{StaticResource NumInput}"
|
||||||
|
materialDesign:HintAssist.Hint=""
|
||||||
|
Text="{Binding OcpValue, UpdateSourceTrigger=PropertyChanged}"/>
|
||||||
|
<Button Content="设置 OCP" Command="{Binding SetOcpCommand}"
|
||||||
|
Style="{StaticResource CmdBtn}"/>
|
||||||
|
</StackPanel>
|
||||||
|
</StackPanel>
|
||||||
|
</GroupBox>
|
||||||
|
</StackPanel>
|
||||||
|
|
||||||
|
<!-- ─── 右列 ─── -->
|
||||||
|
<StackPanel Grid.Column="1" Margin="4,0,0,0">
|
||||||
|
|
||||||
|
<!-- 实时测量 -->
|
||||||
|
<GroupBox Header="实时测量" Margin="0,0,0,8"
|
||||||
|
materialDesign:ColorZoneAssist.Mode="PrimaryLight">
|
||||||
|
<StackPanel Margin="4,4,4,4">
|
||||||
|
<StackPanel Orientation="Horizontal" Margin="0,4">
|
||||||
|
<TextBlock Text="实际电压" Style="{StaticResource ParamLabel}"/>
|
||||||
|
<TextBox Style="{StaticResource MeasureBox}"
|
||||||
|
materialDesign:HintAssist.Hint=""
|
||||||
|
Text="{Binding MeasuredVoltage, Mode=OneWay}"/>
|
||||||
|
<TextBlock Text="V" VerticalAlignment="Center" Margin="2,0,8,0"/>
|
||||||
|
</StackPanel>
|
||||||
|
<StackPanel Orientation="Horizontal" Margin="0,4">
|
||||||
|
<TextBlock Text="实际电流" Style="{StaticResource ParamLabel}"/>
|
||||||
|
<TextBox Style="{StaticResource MeasureBox}"
|
||||||
|
materialDesign:HintAssist.Hint=""
|
||||||
|
Text="{Binding MeasuredCurrent, Mode=OneWay}"/>
|
||||||
|
<TextBlock Text="A" VerticalAlignment="Center" Margin="2,0,8,0"/>
|
||||||
|
</StackPanel>
|
||||||
|
<StackPanel Orientation="Horizontal" Margin="0,4">
|
||||||
|
<TextBlock Text="实际功率" Style="{StaticResource ParamLabel}"/>
|
||||||
|
<TextBox Style="{StaticResource MeasureBox}"
|
||||||
|
materialDesign:HintAssist.Hint=""
|
||||||
|
Text="{Binding MeasuredPower, Mode=OneWay}"/>
|
||||||
|
<TextBlock Text="W" VerticalAlignment="Center" Margin="2,0,8,0"/>
|
||||||
|
</StackPanel>
|
||||||
|
<Button Content="刷新全部测量" Command="{Binding QueryAllMeasureCommand}"
|
||||||
|
Style="{StaticResource CmdBtn}"
|
||||||
|
HorizontalAlignment="Left" Margin="0,4,0,0"/>
|
||||||
|
</StackPanel>
|
||||||
|
</GroupBox>
|
||||||
|
|
||||||
|
<!-- 设备信息与状态 -->
|
||||||
|
<GroupBox Header="设备信息与状态" Margin="0,0,0,8"
|
||||||
|
materialDesign:ColorZoneAssist.Mode="PrimaryLight">
|
||||||
|
<StackPanel Margin="4,4,4,4">
|
||||||
|
<StackPanel Orientation="Horizontal" Margin="0,4">
|
||||||
|
<Button Content="查询 IDN" Command="{Binding QueryIdentityCommand}"
|
||||||
|
Style="{StaticResource CmdBtn}"/>
|
||||||
|
<Button Content="查询输出状态" Command="{Binding QueryOutputStateCommand}"
|
||||||
|
Style="{StaticResource CmdBtn}"/>
|
||||||
|
</StackPanel>
|
||||||
|
<StackPanel Orientation="Horizontal" Margin="0,4">
|
||||||
|
<TextBlock Text="输出状态" Style="{StaticResource ParamLabel}"/>
|
||||||
|
<TextBox Style="{StaticResource MeasureBox}" Width="160"
|
||||||
|
materialDesign:HintAssist.Hint=""
|
||||||
|
Text="{Binding OutputState, Mode=OneWay}"/>
|
||||||
|
</StackPanel>
|
||||||
|
</StackPanel>
|
||||||
|
</GroupBox>
|
||||||
|
|
||||||
|
<!-- 响应日志 -->
|
||||||
|
<GroupBox Header="响应日志" Margin="0,0,0,8"
|
||||||
|
materialDesign:ColorZoneAssist.Mode="PrimaryLight">
|
||||||
|
<ScrollViewer Height="300" VerticalScrollBarVisibility="Auto">
|
||||||
|
<TextBox Text="{Binding ResponseLog, Mode=OneWay}"
|
||||||
|
materialDesign:HintAssist.Hint=""
|
||||||
|
IsReadOnly="True"
|
||||||
|
TextWrapping="Wrap"
|
||||||
|
FontSize="11"
|
||||||
|
FontFamily="Consolas"
|
||||||
|
Background="#FAFAFA"
|
||||||
|
BorderThickness="0"
|
||||||
|
VerticalAlignment="Top"/>
|
||||||
|
</ScrollViewer>
|
||||||
|
</GroupBox>
|
||||||
|
</StackPanel>
|
||||||
|
</Grid>
|
||||||
|
</StackPanel>
|
||||||
|
</ScrollViewer>
|
||||||
|
</UserControl>
|
||||||
28
DeviceEditModule/Views/N36600View.xaml.cs
Normal file
28
DeviceEditModule/Views/N36600View.xaml.cs
Normal file
@@ -0,0 +1,28 @@
|
|||||||
|
using System;
|
||||||
|
using System.Collections.Generic;
|
||||||
|
using System.Linq;
|
||||||
|
using System.Text;
|
||||||
|
using System.Threading.Tasks;
|
||||||
|
using System.Windows;
|
||||||
|
using System.Windows.Controls;
|
||||||
|
using System.Windows.Data;
|
||||||
|
using System.Windows.Documents;
|
||||||
|
using System.Windows.Input;
|
||||||
|
using System.Windows.Media;
|
||||||
|
using System.Windows.Media.Imaging;
|
||||||
|
using System.Windows.Navigation;
|
||||||
|
using System.Windows.Shapes;
|
||||||
|
|
||||||
|
namespace DeviceEditModule.Views
|
||||||
|
{
|
||||||
|
/// <summary>
|
||||||
|
/// N36600View.xaml 的交互逻辑
|
||||||
|
/// </summary>
|
||||||
|
public partial class N36600View : UserControl
|
||||||
|
{
|
||||||
|
public N36600View()
|
||||||
|
{
|
||||||
|
InitializeComponent();
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
262
DeviceEditModule/Views/N69200View.xaml
Normal file
262
DeviceEditModule/Views/N69200View.xaml
Normal file
@@ -0,0 +1,262 @@
|
|||||||
|
<UserControl x:Class="DeviceEditModule.Views.N69200View"
|
||||||
|
xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation"
|
||||||
|
xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml"
|
||||||
|
xmlns:mc="http://schemas.openxmlformats.org/markup-compatibility/2006"
|
||||||
|
xmlns:d="http://schemas.microsoft.com/expression/blend/2008"
|
||||||
|
xmlns:prism="http://prismlibrary.com/"
|
||||||
|
xmlns:materialDesign="http://materialdesigninxaml.net/winfx/xaml/themes"
|
||||||
|
xmlns:converters="clr-namespace:UIShare.Converters;assembly=UIShare"
|
||||||
|
mc:Ignorable="d"
|
||||||
|
prism:ViewModelLocator.AutoWireViewModel="True"
|
||||||
|
d:DesignHeight="760" d:DesignWidth="860">
|
||||||
|
|
||||||
|
<UserControl.Resources>
|
||||||
|
<converters:BooleanToVisibilityConverter x:Key="BoolToVis"/>
|
||||||
|
</UserControl.Resources>
|
||||||
|
|
||||||
|
<ScrollViewer VerticalScrollBarVisibility="Auto" HorizontalScrollBarVisibility="Disabled">
|
||||||
|
<StackPanel Margin="12">
|
||||||
|
|
||||||
|
<!-- ═══ 设备信息头 ═══ -->
|
||||||
|
<materialDesign:Card Margin="0,0,0,8" Padding="12,8">
|
||||||
|
<Grid>
|
||||||
|
<Grid.ColumnDefinitions>
|
||||||
|
<ColumnDefinition Width="Auto"/>
|
||||||
|
<ColumnDefinition Width="*"/>
|
||||||
|
<ColumnDefinition Width="Auto"/>
|
||||||
|
</Grid.ColumnDefinitions>
|
||||||
|
|
||||||
|
<StackPanel Orientation="Horizontal" VerticalAlignment="Center">
|
||||||
|
<materialDesign:PackIcon Kind="CurrentDc" Width="22" Height="22"
|
||||||
|
Foreground="#1565C0" Margin="0,0,8,0"
|
||||||
|
VerticalAlignment="Center"/>
|
||||||
|
<TextBlock Text="N69200 可编程直流电子负载"
|
||||||
|
FontSize="15" FontWeight="Bold"
|
||||||
|
VerticalAlignment="Center"/>
|
||||||
|
<TextBlock Text="{Binding DeviceName, StringFormat=' [{0}]'}"
|
||||||
|
FontSize="13" Foreground="#757575"
|
||||||
|
VerticalAlignment="Center" Margin="4,0,0,0"/>
|
||||||
|
</StackPanel>
|
||||||
|
|
||||||
|
<!-- 连接状态指示 -->
|
||||||
|
<StackPanel Grid.Column="2" Orientation="Horizontal" VerticalAlignment="Center">
|
||||||
|
<Border Width="10" Height="10" CornerRadius="5" Margin="0,0,6,0">
|
||||||
|
<Border.Style>
|
||||||
|
<Style TargetType="Border">
|
||||||
|
<Setter Property="Background" Value="#F44336"/>
|
||||||
|
<Style.Triggers>
|
||||||
|
<DataTrigger Binding="{Binding IsConnected}" Value="True">
|
||||||
|
<Setter Property="Background" Value="#4CAF50"/>
|
||||||
|
</DataTrigger>
|
||||||
|
</Style.Triggers>
|
||||||
|
</Style>
|
||||||
|
</Border.Style>
|
||||||
|
</Border>
|
||||||
|
<TextBlock VerticalAlignment="Center" FontSize="12">
|
||||||
|
<TextBlock.Style>
|
||||||
|
<Style TargetType="TextBlock">
|
||||||
|
<Setter Property="Text" Value="未连接"/>
|
||||||
|
<Setter Property="Foreground" Value="#F44336"/>
|
||||||
|
<Style.Triggers>
|
||||||
|
<DataTrigger Binding="{Binding IsConnected}" Value="True">
|
||||||
|
<Setter Property="Text" Value="已连接"/>
|
||||||
|
<Setter Property="Foreground" Value="#4CAF50"/>
|
||||||
|
</DataTrigger>
|
||||||
|
</Style.Triggers>
|
||||||
|
</Style>
|
||||||
|
</TextBlock.Style>
|
||||||
|
</TextBlock>
|
||||||
|
<ProgressBar IsIndeterminate="True" Width="80" Height="4"
|
||||||
|
Margin="12,0,0,0"
|
||||||
|
Visibility="{Binding IsBusy, Converter={StaticResource BoolToVis}}"/>
|
||||||
|
</StackPanel>
|
||||||
|
</Grid>
|
||||||
|
</materialDesign:Card>
|
||||||
|
|
||||||
|
<!-- ═══ 主体 2 列 ═══ -->
|
||||||
|
<Grid>
|
||||||
|
<Grid.ColumnDefinitions>
|
||||||
|
<ColumnDefinition Width="*"/>
|
||||||
|
<ColumnDefinition Width="*"/>
|
||||||
|
</Grid.ColumnDefinitions>
|
||||||
|
|
||||||
|
<!-- ─── 左列 ─── -->
|
||||||
|
<StackPanel Grid.Column="0" Margin="0,0,4,0">
|
||||||
|
|
||||||
|
<!-- 输入控制 -->
|
||||||
|
<GroupBox Header="输入控制" Margin="0,0,0,8"
|
||||||
|
materialDesign:ColorZoneAssist.Mode="PrimaryLight">
|
||||||
|
<StackPanel Margin="4,4,4,4">
|
||||||
|
<!-- 开关输入 -->
|
||||||
|
<StackPanel Orientation="Horizontal" Margin="0,4">
|
||||||
|
<TextBlock Text="DC 输入" Style="{StaticResource ParamLabel}"/>
|
||||||
|
<Button Content="开启输入" Command="{Binding InputOnCommand}"
|
||||||
|
Style="{StaticResource MaterialDesignRaisedButton}"
|
||||||
|
Background="#388E3C" Foreground="White"
|
||||||
|
Height="32" Padding="12,0" FontSize="12" Margin="4,0"/>
|
||||||
|
<Button Content="关闭输入" Command="{Binding InputOffCommand}"
|
||||||
|
Style="{StaticResource WarnBtn}"/>
|
||||||
|
</StackPanel>
|
||||||
|
<!-- 工作模式 -->
|
||||||
|
<StackPanel Orientation="Horizontal" Margin="0,4">
|
||||||
|
<TextBlock Text="工作模式" Style="{StaticResource ParamLabel}"/>
|
||||||
|
<ComboBox Width="90" Height="32" Margin="4,0"
|
||||||
|
materialDesign:HintAssist.Hint=""
|
||||||
|
SelectedItem="{Binding SelectedMode}"
|
||||||
|
VerticalContentAlignment="Center" FontSize="12">
|
||||||
|
<ComboBoxItem Content="CC"/>
|
||||||
|
<ComboBoxItem Content="CV"/>
|
||||||
|
<ComboBoxItem Content="CP"/>
|
||||||
|
<ComboBoxItem Content="CR"/>
|
||||||
|
</ComboBox>
|
||||||
|
<Button Content="设置" Command="{Binding SetModeCommand}"
|
||||||
|
Style="{StaticResource CmdBtn}"/>
|
||||||
|
<Button Content="查询" Command="{Binding QueryModeCommand}"
|
||||||
|
Style="{StaticResource CmdBtn}"/>
|
||||||
|
</StackPanel>
|
||||||
|
<!-- 远程/本地 -->
|
||||||
|
<StackPanel Orientation="Horizontal" Margin="0,4">
|
||||||
|
<TextBlock Text="控制模式" Style="{StaticResource ParamLabel}"/>
|
||||||
|
<Button Content="远程控制" Command="{Binding SetRemoteModeCommand}"
|
||||||
|
Style="{StaticResource CmdBtn}"/>
|
||||||
|
<Button Content="本地控制" Command="{Binding SetLocalModeCommand}"
|
||||||
|
Style="{StaticResource CmdBtn}"/>
|
||||||
|
</StackPanel>
|
||||||
|
<!-- 系统操作 -->
|
||||||
|
<StackPanel Orientation="Horizontal" Margin="0,4">
|
||||||
|
<TextBlock Text="系统操作" Style="{StaticResource ParamLabel}"/>
|
||||||
|
<Button Content="清除告警" Command="{Binding ClearAlarmCommand}"
|
||||||
|
Style="{StaticResource WarnBtn}"/>
|
||||||
|
<Button Content="重置设备" Command="{Binding ResetDeviceCommand}"
|
||||||
|
Style="{StaticResource WarnBtn}"/>
|
||||||
|
</StackPanel>
|
||||||
|
</StackPanel>
|
||||||
|
</GroupBox>
|
||||||
|
|
||||||
|
<!-- 参数设置 -->
|
||||||
|
<GroupBox Header="参数设置" Margin="0,0,0,8"
|
||||||
|
materialDesign:ColorZoneAssist.Mode="PrimaryLight">
|
||||||
|
<StackPanel Margin="4,4,4,4">
|
||||||
|
<!-- 设定值 -->
|
||||||
|
<StackPanel Orientation="Horizontal" Margin="0,4">
|
||||||
|
<TextBlock Text="设定值" Style="{StaticResource ParamLabel}"/>
|
||||||
|
<TextBox Style="{StaticResource NumInput}"
|
||||||
|
materialDesign:HintAssist.Hint=""
|
||||||
|
Text="{Binding LoadValue, UpdateSourceTrigger=PropertyChanged}"/>
|
||||||
|
<Button Content="设置" Command="{Binding SetLoadValueCommand}"
|
||||||
|
Style="{StaticResource CmdBtn}"/>
|
||||||
|
</StackPanel>
|
||||||
|
</StackPanel>
|
||||||
|
</GroupBox>
|
||||||
|
|
||||||
|
<!-- 保护设置 -->
|
||||||
|
<GroupBox Header="保护设置" Margin="0,0,0,8"
|
||||||
|
materialDesign:ColorZoneAssist.Mode="PrimaryLight">
|
||||||
|
<StackPanel Margin="4,4,4,4">
|
||||||
|
<StackPanel Orientation="Horizontal" Margin="0,4">
|
||||||
|
<TextBlock Text="OVP (V)" Style="{StaticResource ParamLabel}"/>
|
||||||
|
<TextBox Style="{StaticResource NumInput}"
|
||||||
|
materialDesign:HintAssist.Hint=""
|
||||||
|
Text="{Binding OvpValue, UpdateSourceTrigger=PropertyChanged}"/>
|
||||||
|
<Button Content="设置 OVP" Command="{Binding SetOvpCommand}"
|
||||||
|
Style="{StaticResource CmdBtn}"/>
|
||||||
|
</StackPanel>
|
||||||
|
<StackPanel Orientation="Horizontal" Margin="0,4">
|
||||||
|
<TextBlock Text="OCP (A)" Style="{StaticResource ParamLabel}"/>
|
||||||
|
<TextBox Style="{StaticResource NumInput}"
|
||||||
|
materialDesign:HintAssist.Hint=""
|
||||||
|
Text="{Binding OcpValue, UpdateSourceTrigger=PropertyChanged}"/>
|
||||||
|
<Button Content="设置 OCP" Command="{Binding SetOcpCommand}"
|
||||||
|
Style="{StaticResource CmdBtn}"/>
|
||||||
|
</StackPanel>
|
||||||
|
<StackPanel Orientation="Horizontal" Margin="0,4">
|
||||||
|
<TextBlock Text="OPP (W)" Style="{StaticResource ParamLabel}"/>
|
||||||
|
<TextBox Style="{StaticResource NumInput}"
|
||||||
|
materialDesign:HintAssist.Hint=""
|
||||||
|
Text="{Binding OppValue, UpdateSourceTrigger=PropertyChanged}"/>
|
||||||
|
<Button Content="设置 OPP" Command="{Binding SetOppCommand}"
|
||||||
|
Style="{StaticResource CmdBtn}"/>
|
||||||
|
</StackPanel>
|
||||||
|
</StackPanel>
|
||||||
|
</GroupBox>
|
||||||
|
</StackPanel>
|
||||||
|
|
||||||
|
<!-- ─── 右列 ─── -->
|
||||||
|
<StackPanel Grid.Column="1" Margin="4,0,0,0">
|
||||||
|
|
||||||
|
<!-- 实时测量 -->
|
||||||
|
<GroupBox Header="实时测量" Margin="0,0,0,8"
|
||||||
|
materialDesign:ColorZoneAssist.Mode="PrimaryLight">
|
||||||
|
<StackPanel Margin="4,4,4,4">
|
||||||
|
<StackPanel Orientation="Horizontal" Margin="0,4">
|
||||||
|
<TextBlock Text="实际电压" Style="{StaticResource ParamLabel}"/>
|
||||||
|
<TextBox Style="{StaticResource MeasureBox}"
|
||||||
|
materialDesign:HintAssist.Hint=""
|
||||||
|
Text="{Binding MeasuredVoltage, Mode=OneWay}"/>
|
||||||
|
<TextBlock Text="V" VerticalAlignment="Center" Margin="2,0,8,0"/>
|
||||||
|
</StackPanel>
|
||||||
|
<StackPanel Orientation="Horizontal" Margin="0,4">
|
||||||
|
<TextBlock Text="实际电流" Style="{StaticResource ParamLabel}"/>
|
||||||
|
<TextBox Style="{StaticResource MeasureBox}"
|
||||||
|
materialDesign:HintAssist.Hint=""
|
||||||
|
Text="{Binding MeasuredCurrent, Mode=OneWay}"/>
|
||||||
|
<TextBlock Text="A" VerticalAlignment="Center" Margin="2,0,8,0"/>
|
||||||
|
</StackPanel>
|
||||||
|
<StackPanel Orientation="Horizontal" Margin="0,4">
|
||||||
|
<TextBlock Text="实际功率" Style="{StaticResource ParamLabel}"/>
|
||||||
|
<TextBox Style="{StaticResource MeasureBox}"
|
||||||
|
materialDesign:HintAssist.Hint=""
|
||||||
|
Text="{Binding MeasuredPower, Mode=OneWay}"/>
|
||||||
|
<TextBlock Text="W" VerticalAlignment="Center" Margin="2,0,8,0"/>
|
||||||
|
</StackPanel>
|
||||||
|
<Button Content="刷新全部测量" Command="{Binding QueryAllMeasureCommand}"
|
||||||
|
Style="{StaticResource CmdBtn}"
|
||||||
|
HorizontalAlignment="Left" Margin="0,4,0,0"/>
|
||||||
|
</StackPanel>
|
||||||
|
</GroupBox>
|
||||||
|
|
||||||
|
<!-- 设备信息与状态 -->
|
||||||
|
<GroupBox Header="设备信息与状态" Margin="0,0,0,8"
|
||||||
|
materialDesign:ColorZoneAssist.Mode="PrimaryLight">
|
||||||
|
<StackPanel Margin="4,4,4,4">
|
||||||
|
<StackPanel Orientation="Horizontal" Margin="0,4">
|
||||||
|
<Button Content="查询 IDN" Command="{Binding QueryIdentityCommand}"
|
||||||
|
Style="{StaticResource CmdBtn}"/>
|
||||||
|
<Button Content="查询状态字节" Command="{Binding QueryStatusCommand}"
|
||||||
|
Style="{StaticResource CmdBtn}"/>
|
||||||
|
</StackPanel>
|
||||||
|
<StackPanel Orientation="Horizontal" Margin="0,4">
|
||||||
|
<TextBlock Text="当前模式" Style="{StaticResource ParamLabel}"/>
|
||||||
|
<TextBox Style="{StaticResource MeasureBox}" Width="120"
|
||||||
|
materialDesign:HintAssist.Hint=""
|
||||||
|
Text="{Binding CurrentMode, Mode=OneWay}"/>
|
||||||
|
</StackPanel>
|
||||||
|
<StackPanel Orientation="Horizontal" Margin="0,4">
|
||||||
|
<TextBlock Text="状态字节" Style="{StaticResource ParamLabel}"/>
|
||||||
|
<TextBox Style="{StaticResource MeasureBox}" Width="120"
|
||||||
|
materialDesign:HintAssist.Hint=""
|
||||||
|
Text="{Binding StatusByte, Mode=OneWay}"/>
|
||||||
|
</StackPanel>
|
||||||
|
</StackPanel>
|
||||||
|
</GroupBox>
|
||||||
|
|
||||||
|
<!-- 响应日志 -->
|
||||||
|
<GroupBox Header="响应日志" Margin="0,0,0,8"
|
||||||
|
materialDesign:ColorZoneAssist.Mode="PrimaryLight">
|
||||||
|
<ScrollViewer Height="260" VerticalScrollBarVisibility="Auto">
|
||||||
|
<TextBox Text="{Binding ResponseLog, Mode=OneWay}"
|
||||||
|
materialDesign:HintAssist.Hint=""
|
||||||
|
IsReadOnly="True"
|
||||||
|
TextWrapping="Wrap"
|
||||||
|
FontSize="11"
|
||||||
|
FontFamily="Consolas"
|
||||||
|
Background="#FAFAFA"
|
||||||
|
BorderThickness="0"
|
||||||
|
VerticalAlignment="Top"/>
|
||||||
|
</ScrollViewer>
|
||||||
|
</GroupBox>
|
||||||
|
</StackPanel>
|
||||||
|
</Grid>
|
||||||
|
</StackPanel>
|
||||||
|
</ScrollViewer>
|
||||||
|
</UserControl>
|
||||||
28
DeviceEditModule/Views/N69200View.xaml.cs
Normal file
28
DeviceEditModule/Views/N69200View.xaml.cs
Normal file
@@ -0,0 +1,28 @@
|
|||||||
|
using System;
|
||||||
|
using System.Collections.Generic;
|
||||||
|
using System.Linq;
|
||||||
|
using System.Text;
|
||||||
|
using System.Threading.Tasks;
|
||||||
|
using System.Windows;
|
||||||
|
using System.Windows.Controls;
|
||||||
|
using System.Windows.Data;
|
||||||
|
using System.Windows.Documents;
|
||||||
|
using System.Windows.Input;
|
||||||
|
using System.Windows.Media;
|
||||||
|
using System.Windows.Media.Imaging;
|
||||||
|
using System.Windows.Navigation;
|
||||||
|
using System.Windows.Shapes;
|
||||||
|
|
||||||
|
namespace DeviceEditModule.Views
|
||||||
|
{
|
||||||
|
/// <summary>
|
||||||
|
/// N69200View.xaml 的交互逻辑
|
||||||
|
/// </summary>
|
||||||
|
public partial class N69200View : UserControl
|
||||||
|
{
|
||||||
|
public N69200View()
|
||||||
|
{
|
||||||
|
InitializeComponent();
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
246
DeviceEditModule/Views/SDS2000X_HDView.xaml
Normal file
246
DeviceEditModule/Views/SDS2000X_HDView.xaml
Normal file
@@ -0,0 +1,246 @@
|
|||||||
|
<UserControl x:Class="DeviceEditModule.Views.SDS2000X_HDView"
|
||||||
|
xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation"
|
||||||
|
xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml"
|
||||||
|
xmlns:mc="http://schemas.openxmlformats.org/markup-compatibility/2006"
|
||||||
|
xmlns:d="http://schemas.microsoft.com/expression/blend/2008"
|
||||||
|
xmlns:prism="http://prismlibrary.com/"
|
||||||
|
xmlns:materialDesign="http://materialdesigninxaml.net/winfx/xaml/themes"
|
||||||
|
xmlns:converters="clr-namespace:UIShare.Converters;assembly=UIShare"
|
||||||
|
mc:Ignorable="d"
|
||||||
|
prism:ViewModelLocator.AutoWireViewModel="True"
|
||||||
|
d:DesignHeight="760" d:DesignWidth="860">
|
||||||
|
|
||||||
|
<UserControl.Resources>
|
||||||
|
<converters:BooleanToVisibilityConverter x:Key="BoolToVis"/>
|
||||||
|
</UserControl.Resources>
|
||||||
|
|
||||||
|
<ScrollViewer VerticalScrollBarVisibility="Auto" HorizontalScrollBarVisibility="Disabled">
|
||||||
|
<StackPanel Margin="12">
|
||||||
|
|
||||||
|
<!-- ═══ 设备信息头 ═══ -->
|
||||||
|
<materialDesign:Card Margin="0,0,0,8" Padding="12,8">
|
||||||
|
<Grid>
|
||||||
|
<Grid.ColumnDefinitions>
|
||||||
|
<ColumnDefinition Width="Auto"/>
|
||||||
|
<ColumnDefinition Width="*"/>
|
||||||
|
<ColumnDefinition Width="Auto"/>
|
||||||
|
</Grid.ColumnDefinitions>
|
||||||
|
|
||||||
|
<StackPanel Orientation="Horizontal" VerticalAlignment="Center">
|
||||||
|
<materialDesign:PackIcon Kind="Waveform" Width="22" Height="22"
|
||||||
|
Foreground="#1565C0" Margin="0,0,8,0"
|
||||||
|
VerticalAlignment="Center"/>
|
||||||
|
<TextBlock Text="SDS2000X_HD 数字存储示波器"
|
||||||
|
FontSize="15" FontWeight="Bold"
|
||||||
|
VerticalAlignment="Center"/>
|
||||||
|
<TextBlock Text="{Binding DeviceName, StringFormat=' [{0}]'}"
|
||||||
|
FontSize="13" Foreground="#757575"
|
||||||
|
VerticalAlignment="Center" Margin="4,0,0,0"/>
|
||||||
|
</StackPanel>
|
||||||
|
|
||||||
|
<!-- 连接状态指示 -->
|
||||||
|
<StackPanel Grid.Column="2" Orientation="Horizontal" VerticalAlignment="Center">
|
||||||
|
<Border Width="10" Height="10" CornerRadius="5" Margin="0,0,6,0">
|
||||||
|
<Border.Style>
|
||||||
|
<Style TargetType="Border">
|
||||||
|
<Setter Property="Background" Value="#F44336"/>
|
||||||
|
<Style.Triggers>
|
||||||
|
<DataTrigger Binding="{Binding IsConnected}" Value="True">
|
||||||
|
<Setter Property="Background" Value="#4CAF50"/>
|
||||||
|
</DataTrigger>
|
||||||
|
</Style.Triggers>
|
||||||
|
</Style>
|
||||||
|
</Border.Style>
|
||||||
|
</Border>
|
||||||
|
<TextBlock VerticalAlignment="Center" FontSize="12">
|
||||||
|
<TextBlock.Style>
|
||||||
|
<Style TargetType="TextBlock">
|
||||||
|
<Setter Property="Text" Value="未连接"/>
|
||||||
|
<Setter Property="Foreground" Value="#F44336"/>
|
||||||
|
<Style.Triggers>
|
||||||
|
<DataTrigger Binding="{Binding IsConnected}" Value="True">
|
||||||
|
<Setter Property="Text" Value="已连接"/>
|
||||||
|
<Setter Property="Foreground" Value="#4CAF50"/>
|
||||||
|
</DataTrigger>
|
||||||
|
</Style.Triggers>
|
||||||
|
</Style>
|
||||||
|
</TextBlock.Style>
|
||||||
|
</TextBlock>
|
||||||
|
<ProgressBar IsIndeterminate="True" Width="80" Height="4"
|
||||||
|
Margin="12,0,0,0"
|
||||||
|
Visibility="{Binding IsBusy, Converter={StaticResource BoolToVis}}"/>
|
||||||
|
</StackPanel>
|
||||||
|
</Grid>
|
||||||
|
</materialDesign:Card>
|
||||||
|
|
||||||
|
<!-- ═══ 主体 2 列 ═══ -->
|
||||||
|
<Grid>
|
||||||
|
<Grid.ColumnDefinitions>
|
||||||
|
<ColumnDefinition Width="*"/>
|
||||||
|
<ColumnDefinition Width="*"/>
|
||||||
|
</Grid.ColumnDefinitions>
|
||||||
|
|
||||||
|
<!-- ─── 左列 ─── -->
|
||||||
|
<StackPanel Grid.Column="0" Margin="0,0,4,0">
|
||||||
|
|
||||||
|
<!-- 通道选择 -->
|
||||||
|
<GroupBox Header="通道选择" Margin="0,0,0,8"
|
||||||
|
materialDesign:ColorZoneAssist.Mode="PrimaryLight">
|
||||||
|
<StackPanel Margin="4,4,4,4">
|
||||||
|
<StackPanel Orientation="Horizontal" Margin="0,4">
|
||||||
|
<TextBlock Text="通道号" Style="{StaticResource ParamLabel}"/>
|
||||||
|
<ComboBox Width="80" Height="32" Margin="4,0"
|
||||||
|
SelectedItem="{Binding Channel}"
|
||||||
|
VerticalContentAlignment="Center" FontSize="12">
|
||||||
|
<ComboBoxItem Content="1"/>
|
||||||
|
<ComboBoxItem Content="2"/>
|
||||||
|
<ComboBoxItem Content="3"/>
|
||||||
|
<ComboBoxItem Content="4"/>
|
||||||
|
</ComboBox>
|
||||||
|
<Button Content="开启" Command="{Binding SetChannelOnCommand}"
|
||||||
|
Style="{StaticResource CmdBtn}"/>
|
||||||
|
<Button Content="关闭" Command="{Binding SetChannelOffCommand}"
|
||||||
|
Style="{StaticResource WarnBtn}"/>
|
||||||
|
</StackPanel>
|
||||||
|
</StackPanel>
|
||||||
|
</GroupBox>
|
||||||
|
|
||||||
|
<!-- 运行控制 -->
|
||||||
|
<GroupBox Header="运行控制" Margin="0,0,0,8"
|
||||||
|
materialDesign:ColorZoneAssist.Mode="PrimaryLight">
|
||||||
|
<StackPanel Margin="4,4,4,4">
|
||||||
|
<StackPanel Orientation="Horizontal" Margin="0,4">
|
||||||
|
<Button Content="Run" Command="{Binding RunCommand}"
|
||||||
|
Style="{StaticResource MaterialDesignRaisedButton}"
|
||||||
|
Background="#388E3C" Foreground="White"
|
||||||
|
Height="32" Padding="16,0" FontSize="12" Margin="4,0"/>
|
||||||
|
<Button Content="Stop" Command="{Binding StopCommand}"
|
||||||
|
Style="{StaticResource WarnBtn}"/>
|
||||||
|
<Button Content="Single" Command="{Binding SingleCommand}"
|
||||||
|
Style="{StaticResource CmdBtn}"/>
|
||||||
|
<Button Content="Force Trig" Command="{Binding ForceTriggerCommand}"
|
||||||
|
Style="{StaticResource CmdBtn}"/>
|
||||||
|
</StackPanel>
|
||||||
|
<StackPanel Orientation="Horizontal" Margin="0,4">
|
||||||
|
<Button Content="查询 IDN" Command="{Binding QueryIdentityCommand}"
|
||||||
|
Style="{StaticResource CmdBtn}"/>
|
||||||
|
<Button Content="重置设备" Command="{Binding ResetDeviceCommand}"
|
||||||
|
Style="{StaticResource WarnBtn}"/>
|
||||||
|
</StackPanel>
|
||||||
|
</StackPanel>
|
||||||
|
</GroupBox>
|
||||||
|
|
||||||
|
<!-- 垂直设置 -->
|
||||||
|
<GroupBox Header="垂直设置" Margin="0,0,0,8"
|
||||||
|
materialDesign:ColorZoneAssist.Mode="PrimaryLight">
|
||||||
|
<StackPanel Margin="4,4,4,4">
|
||||||
|
<StackPanel Orientation="Horizontal" Margin="0,4">
|
||||||
|
<TextBlock Text="V/div (V)" Style="{StaticResource ParamLabel}"/>
|
||||||
|
<TextBox Style="{StaticResource NumInput}"
|
||||||
|
Text="{Binding VoltsPerDiv, UpdateSourceTrigger=PropertyChanged}"/>
|
||||||
|
<Button Content="设置" Command="{Binding SetVoltsDivCommand}"
|
||||||
|
Style="{StaticResource CmdBtn}"/>
|
||||||
|
</StackPanel>
|
||||||
|
<StackPanel Orientation="Horizontal" Margin="0,4">
|
||||||
|
<TextBlock Text="Offset (V)" Style="{StaticResource ParamLabel}"/>
|
||||||
|
<TextBox Style="{StaticResource NumInput}"
|
||||||
|
Text="{Binding Offset, UpdateSourceTrigger=PropertyChanged}"/>
|
||||||
|
<Button Content="设置" Command="{Binding SetOffsetCommand}"
|
||||||
|
Style="{StaticResource CmdBtn}"/>
|
||||||
|
</StackPanel>
|
||||||
|
<StackPanel Orientation="Horizontal" Margin="0,4">
|
||||||
|
<TextBlock Text="输入阻抗" Style="{StaticResource ParamLabel}"/>
|
||||||
|
<Button Content="50Ω" Command="{Binding SetImpedance50Command}"
|
||||||
|
Style="{StaticResource CmdBtn}"/>
|
||||||
|
<Button Content="1MΩ" Command="{Binding SetImpedance1MCommand}"
|
||||||
|
Style="{StaticResource CmdBtn}"/>
|
||||||
|
</StackPanel>
|
||||||
|
</StackPanel>
|
||||||
|
</GroupBox>
|
||||||
|
|
||||||
|
<!-- 水平/触发设置 -->
|
||||||
|
<GroupBox Header="水平与触发" Margin="0,0,0,8"
|
||||||
|
materialDesign:ColorZoneAssist.Mode="PrimaryLight">
|
||||||
|
<StackPanel Margin="4,4,4,4">
|
||||||
|
<StackPanel Orientation="Horizontal" Margin="0,4">
|
||||||
|
<TextBlock Text="时基 (s/div)" Style="{StaticResource ParamLabel}"/>
|
||||||
|
<TextBox Style="{StaticResource NumInput}"
|
||||||
|
Text="{Binding TimeBase, UpdateSourceTrigger=PropertyChanged}"/>
|
||||||
|
<Button Content="设置" Command="{Binding SetTimeBaseCommand}"
|
||||||
|
Style="{StaticResource CmdBtn}"/>
|
||||||
|
</StackPanel>
|
||||||
|
<StackPanel Orientation="Horizontal" Margin="0,4">
|
||||||
|
<TextBlock Text="触发电平 (V)" Style="{StaticResource ParamLabel}"/>
|
||||||
|
<TextBox Style="{StaticResource NumInput}"
|
||||||
|
Text="{Binding TriggerLevel, UpdateSourceTrigger=PropertyChanged}"/>
|
||||||
|
<Button Content="设置" Command="{Binding SetTriggerLevelCommand}"
|
||||||
|
Style="{StaticResource CmdBtn}"/>
|
||||||
|
</StackPanel>
|
||||||
|
<StackPanel Orientation="Horizontal" Margin="0,4">
|
||||||
|
<TextBlock Text="触发源" Style="{StaticResource ParamLabel}"/>
|
||||||
|
<TextBox Style="{StaticResource NumInput}" Width="80"
|
||||||
|
Text="{Binding TriggerSource, UpdateSourceTrigger=PropertyChanged}"/>
|
||||||
|
<Button Content="设置" Command="{Binding SetTriggerSourceCommand}"
|
||||||
|
Style="{StaticResource CmdBtn}"/>
|
||||||
|
</StackPanel>
|
||||||
|
</StackPanel>
|
||||||
|
</GroupBox>
|
||||||
|
</StackPanel>
|
||||||
|
|
||||||
|
<!-- ─── 右列 ─── -->
|
||||||
|
<StackPanel Grid.Column="1" Margin="4,0,0,0">
|
||||||
|
|
||||||
|
<!-- 自动测量 -->
|
||||||
|
<GroupBox Header="自动测量" Margin="0,0,0,8"
|
||||||
|
materialDesign:ColorZoneAssist.Mode="PrimaryLight">
|
||||||
|
<StackPanel Margin="4,4,4,4">
|
||||||
|
<StackPanel Orientation="Horizontal" Margin="0,4">
|
||||||
|
<TextBlock Text="Vpp" Style="{StaticResource ParamLabel}"/>
|
||||||
|
<TextBox Style="{StaticResource MeasureBox}"
|
||||||
|
Text="{Binding MeasuredVpp, Mode=OneWay}"/>
|
||||||
|
<TextBlock Text="V" VerticalAlignment="Center" Margin="2,0,8,0"/>
|
||||||
|
<Button Content="刷新" Command="{Binding QueryVppCommand}"
|
||||||
|
Style="{StaticResource CmdBtn}"/>
|
||||||
|
</StackPanel>
|
||||||
|
<StackPanel Orientation="Horizontal" Margin="0,4">
|
||||||
|
<TextBlock Text="频率" Style="{StaticResource ParamLabel}"/>
|
||||||
|
<TextBox Style="{StaticResource MeasureBox}"
|
||||||
|
Text="{Binding MeasuredFrequency, Mode=OneWay}"/>
|
||||||
|
<TextBlock Text="Hz" VerticalAlignment="Center" Margin="2,0,8,0"/>
|
||||||
|
<Button Content="刷新" Command="{Binding QueryFrequencyCommand}"
|
||||||
|
Style="{StaticResource CmdBtn}"/>
|
||||||
|
</StackPanel>
|
||||||
|
<StackPanel Orientation="Horizontal" Margin="0,4">
|
||||||
|
<TextBlock Text="RMS" Style="{StaticResource ParamLabel}"/>
|
||||||
|
<TextBox Style="{StaticResource MeasureBox}"
|
||||||
|
Text="{Binding MeasuredRms, Mode=OneWay}"/>
|
||||||
|
<TextBlock Text="V" VerticalAlignment="Center" Margin="2,0,8,0"/>
|
||||||
|
<Button Content="刷新" Command="{Binding QueryRmsCommand}"
|
||||||
|
Style="{StaticResource CmdBtn}"/>
|
||||||
|
</StackPanel>
|
||||||
|
<Button Content="刷新全部测量" Command="{Binding QueryMeasurementsCommand}"
|
||||||
|
Style="{StaticResource CmdBtn}"
|
||||||
|
HorizontalAlignment="Left" Margin="0,4,0,0"/>
|
||||||
|
</StackPanel>
|
||||||
|
</GroupBox>
|
||||||
|
|
||||||
|
<!-- 响应日志 -->
|
||||||
|
<GroupBox Header="响应日志" Margin="0,0,0,8"
|
||||||
|
materialDesign:ColorZoneAssist.Mode="PrimaryLight">
|
||||||
|
<ScrollViewer Height="460" VerticalScrollBarVisibility="Auto">
|
||||||
|
<TextBox Text="{Binding ResponseLog, Mode=OneWay}"
|
||||||
|
materialDesign:HintAssist.Hint=""
|
||||||
|
IsReadOnly="True"
|
||||||
|
TextWrapping="Wrap"
|
||||||
|
FontSize="11"
|
||||||
|
FontFamily="Consolas"
|
||||||
|
Background="#FAFAFA"
|
||||||
|
BorderThickness="0"
|
||||||
|
VerticalAlignment="Top"/>
|
||||||
|
</ScrollViewer>
|
||||||
|
</GroupBox>
|
||||||
|
</StackPanel>
|
||||||
|
</Grid>
|
||||||
|
</StackPanel>
|
||||||
|
</ScrollViewer>
|
||||||
|
</UserControl>
|
||||||
28
DeviceEditModule/Views/SDS2000X_HDView.xaml.cs
Normal file
28
DeviceEditModule/Views/SDS2000X_HDView.xaml.cs
Normal file
@@ -0,0 +1,28 @@
|
|||||||
|
using System;
|
||||||
|
using System.Collections.Generic;
|
||||||
|
using System.Linq;
|
||||||
|
using System.Text;
|
||||||
|
using System.Threading.Tasks;
|
||||||
|
using System.Windows;
|
||||||
|
using System.Windows.Controls;
|
||||||
|
using System.Windows.Data;
|
||||||
|
using System.Windows.Documents;
|
||||||
|
using System.Windows.Input;
|
||||||
|
using System.Windows.Media;
|
||||||
|
using System.Windows.Media.Imaging;
|
||||||
|
using System.Windows.Navigation;
|
||||||
|
using System.Windows.Shapes;
|
||||||
|
|
||||||
|
namespace DeviceEditModule.Views
|
||||||
|
{
|
||||||
|
/// <summary>
|
||||||
|
/// SDS2000X_HDView.xaml 的交互逻辑
|
||||||
|
/// </summary>
|
||||||
|
public partial class SDS2000X_HDView : UserControl
|
||||||
|
{
|
||||||
|
public SDS2000X_HDView()
|
||||||
|
{
|
||||||
|
InitializeComponent();
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
255
DeviceEditModule/Views/SPAW7000View.xaml
Normal file
255
DeviceEditModule/Views/SPAW7000View.xaml
Normal file
@@ -0,0 +1,255 @@
|
|||||||
|
<UserControl x:Class="DeviceEditModule.Views.SPAW7000View"
|
||||||
|
xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation"
|
||||||
|
xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml"
|
||||||
|
xmlns:mc="http://schemas.openxmlformats.org/markup-compatibility/2006"
|
||||||
|
xmlns:d="http://schemas.microsoft.com/expression/blend/2008"
|
||||||
|
xmlns:prism="http://prismlibrary.com/"
|
||||||
|
xmlns:materialDesign="http://materialdesigninxaml.net/winfx/xaml/themes"
|
||||||
|
xmlns:converters="clr-namespace:UIShare.Converters;assembly=UIShare"
|
||||||
|
mc:Ignorable="d"
|
||||||
|
prism:ViewModelLocator.AutoWireViewModel="True"
|
||||||
|
d:DesignHeight="760" d:DesignWidth="860">
|
||||||
|
|
||||||
|
<UserControl.Resources>
|
||||||
|
<converters:BooleanToVisibilityConverter x:Key="BoolToVis"/>
|
||||||
|
</UserControl.Resources>
|
||||||
|
|
||||||
|
<ScrollViewer VerticalScrollBarVisibility="Auto" HorizontalScrollBarVisibility="Disabled">
|
||||||
|
<StackPanel Margin="12">
|
||||||
|
|
||||||
|
<!-- ═══ 设备信息头 ═══ -->
|
||||||
|
<materialDesign:Card Margin="0,0,0,8" Padding="12,8">
|
||||||
|
<Grid>
|
||||||
|
<Grid.ColumnDefinitions>
|
||||||
|
<ColumnDefinition Width="Auto"/>
|
||||||
|
<ColumnDefinition Width="*"/>
|
||||||
|
<ColumnDefinition Width="Auto"/>
|
||||||
|
</Grid.ColumnDefinitions>
|
||||||
|
|
||||||
|
<StackPanel Orientation="Horizontal" VerticalAlignment="Center">
|
||||||
|
<materialDesign:PackIcon Kind="ChartLine" Width="22" Height="22"
|
||||||
|
Foreground="#1565C0" Margin="0,0,8,0"
|
||||||
|
VerticalAlignment="Center"/>
|
||||||
|
<TextBlock Text="SPAW7000 功率分析记录仪"
|
||||||
|
FontSize="15" FontWeight="Bold"
|
||||||
|
VerticalAlignment="Center"/>
|
||||||
|
<TextBlock Text="{Binding DeviceName, StringFormat=' [{0}]'}"
|
||||||
|
FontSize="13" Foreground="#757575"
|
||||||
|
VerticalAlignment="Center" Margin="4,0,0,0"/>
|
||||||
|
</StackPanel>
|
||||||
|
|
||||||
|
<!-- 连接状态指示 -->
|
||||||
|
<StackPanel Grid.Column="2" Orientation="Horizontal" VerticalAlignment="Center">
|
||||||
|
<Border Width="10" Height="10" CornerRadius="5" Margin="0,0,6,0">
|
||||||
|
<Border.Style>
|
||||||
|
<Style TargetType="Border">
|
||||||
|
<Setter Property="Background" Value="#F44336"/>
|
||||||
|
<Style.Triggers>
|
||||||
|
<DataTrigger Binding="{Binding IsConnected}" Value="True">
|
||||||
|
<Setter Property="Background" Value="#4CAF50"/>
|
||||||
|
</DataTrigger>
|
||||||
|
</Style.Triggers>
|
||||||
|
</Style>
|
||||||
|
</Border.Style>
|
||||||
|
</Border>
|
||||||
|
<TextBlock VerticalAlignment="Center" FontSize="12">
|
||||||
|
<TextBlock.Style>
|
||||||
|
<Style TargetType="TextBlock">
|
||||||
|
<Setter Property="Text" Value="未连接"/>
|
||||||
|
<Setter Property="Foreground" Value="#F44336"/>
|
||||||
|
<Style.Triggers>
|
||||||
|
<DataTrigger Binding="{Binding IsConnected}" Value="True">
|
||||||
|
<Setter Property="Text" Value="已连接"/>
|
||||||
|
<Setter Property="Foreground" Value="#4CAF50"/>
|
||||||
|
</DataTrigger>
|
||||||
|
</Style.Triggers>
|
||||||
|
</Style>
|
||||||
|
</TextBlock.Style>
|
||||||
|
</TextBlock>
|
||||||
|
<ProgressBar IsIndeterminate="True" Width="80" Height="4"
|
||||||
|
Margin="12,0,0,0"
|
||||||
|
Visibility="{Binding IsBusy, Converter={StaticResource BoolToVis}}"/>
|
||||||
|
</StackPanel>
|
||||||
|
</Grid>
|
||||||
|
</materialDesign:Card>
|
||||||
|
|
||||||
|
<!-- ═══ 主体 2 列 ═══ -->
|
||||||
|
<Grid>
|
||||||
|
<Grid.ColumnDefinitions>
|
||||||
|
<ColumnDefinition Width="*"/>
|
||||||
|
<ColumnDefinition Width="*"/>
|
||||||
|
</Grid.ColumnDefinitions>
|
||||||
|
|
||||||
|
<!-- ─── 左列 ─── -->
|
||||||
|
<StackPanel Grid.Column="0" Margin="0,0,4,0">
|
||||||
|
|
||||||
|
<!-- 通道与量程 -->
|
||||||
|
<GroupBox Header="通道与量程" Margin="0,0,0,8"
|
||||||
|
materialDesign:ColorZoneAssist.Mode="PrimaryLight">
|
||||||
|
<StackPanel Margin="4,4,4,4">
|
||||||
|
<StackPanel Orientation="Horizontal" Margin="0,4">
|
||||||
|
<TextBlock Text="通道号" Style="{StaticResource ParamLabel}"/>
|
||||||
|
<ComboBox Width="80" Height="32" Margin="4,0"
|
||||||
|
SelectedItem="{Binding Channel}"
|
||||||
|
VerticalContentAlignment="Center" FontSize="12">
|
||||||
|
<ComboBoxItem Content="1"/>
|
||||||
|
<ComboBoxItem Content="2"/>
|
||||||
|
<ComboBoxItem Content="3"/>
|
||||||
|
<ComboBoxItem Content="4"/>
|
||||||
|
</ComboBox>
|
||||||
|
</StackPanel>
|
||||||
|
<StackPanel Orientation="Horizontal" Margin="0,4">
|
||||||
|
<TextBlock Text="电压量程 (V)" Style="{StaticResource ParamLabel}"/>
|
||||||
|
<TextBox Style="{StaticResource NumInput}"
|
||||||
|
Text="{Binding VoltageRange, UpdateSourceTrigger=PropertyChanged}"/>
|
||||||
|
<Button Content="设置" Command="{Binding SetVoltageRangeCommand}"
|
||||||
|
Style="{StaticResource CmdBtn}"/>
|
||||||
|
</StackPanel>
|
||||||
|
<StackPanel Orientation="Horizontal" Margin="0,4">
|
||||||
|
<TextBlock Text="电流量程 (A)" Style="{StaticResource ParamLabel}"/>
|
||||||
|
<TextBox Style="{StaticResource NumInput}"
|
||||||
|
Text="{Binding CurrentRange, UpdateSourceTrigger=PropertyChanged}"/>
|
||||||
|
<Button Content="设置" Command="{Binding SetCurrentRangeCommand}"
|
||||||
|
Style="{StaticResource CmdBtn}"/>
|
||||||
|
</StackPanel>
|
||||||
|
<StackPanel Orientation="Horizontal" Margin="0,4">
|
||||||
|
<TextBlock Text="耦合模式" Style="{StaticResource ParamLabel}"/>
|
||||||
|
<ComboBox Width="90" Height="32" Margin="4,0"
|
||||||
|
SelectedItem="{Binding CouplingMode}"
|
||||||
|
VerticalContentAlignment="Center" FontSize="12">
|
||||||
|
<ComboBoxItem Content="AC"/>
|
||||||
|
<ComboBoxItem Content="DC"/>
|
||||||
|
<ComboBoxItem Content="ACDC"/>
|
||||||
|
</ComboBox>
|
||||||
|
<Button Content="设置" Command="{Binding SetCouplingModeCommand}"
|
||||||
|
Style="{StaticResource CmdBtn}"/>
|
||||||
|
</StackPanel>
|
||||||
|
</StackPanel>
|
||||||
|
</GroupBox>
|
||||||
|
|
||||||
|
<!-- 系统设置 -->
|
||||||
|
<GroupBox Header="系统设置" Margin="0,0,0,8"
|
||||||
|
materialDesign:ColorZoneAssist.Mode="PrimaryLight">
|
||||||
|
<StackPanel Margin="4,4,4,4">
|
||||||
|
<StackPanel Orientation="Horizontal" Margin="0,4">
|
||||||
|
<TextBlock Text="分辨率" Style="{StaticResource ParamLabel}"/>
|
||||||
|
<ComboBox Width="80" Height="32" Margin="4,0"
|
||||||
|
SelectedItem="{Binding Resolution}"
|
||||||
|
VerticalContentAlignment="Center" FontSize="12">
|
||||||
|
<ComboBoxItem Content="5"/>
|
||||||
|
<ComboBoxItem Content="6"/>
|
||||||
|
</ComboBox>
|
||||||
|
<Button Content="设置" Command="{Binding SetResolutionCommand}"
|
||||||
|
Style="{StaticResource CmdBtn}"/>
|
||||||
|
</StackPanel>
|
||||||
|
<StackPanel Orientation="Horizontal" Margin="0,4">
|
||||||
|
<TextBlock Text="亮度 (1~10)" Style="{StaticResource ParamLabel}"/>
|
||||||
|
<TextBox Style="{StaticResource NumInput}" Width="60"
|
||||||
|
Text="{Binding Brightness, UpdateSourceTrigger=PropertyChanged}"/>
|
||||||
|
<Button Content="设置" Command="{Binding SetBrightnessCommand}"
|
||||||
|
Style="{StaticResource CmdBtn}"/>
|
||||||
|
</StackPanel>
|
||||||
|
<StackPanel Orientation="Horizontal" Margin="0,4">
|
||||||
|
<TextBlock Text="触摸锁" Style="{StaticResource ParamLabel}"/>
|
||||||
|
<Button Content="锁定" Command="{Binding SetTouchLockOnCommand}"
|
||||||
|
Style="{StaticResource CmdBtn}"/>
|
||||||
|
<Button Content="解锁" Command="{Binding SetTouchLockOffCommand}"
|
||||||
|
Style="{StaticResource WarnBtn}"/>
|
||||||
|
</StackPanel>
|
||||||
|
</StackPanel>
|
||||||
|
</GroupBox>
|
||||||
|
|
||||||
|
<!-- 设备信息 -->
|
||||||
|
<GroupBox Header="设备信息" Margin="0,0,0,8"
|
||||||
|
materialDesign:ColorZoneAssist.Mode="PrimaryLight">
|
||||||
|
<StackPanel Margin="4,4,4,4">
|
||||||
|
<StackPanel Orientation="Horizontal" Margin="0,4">
|
||||||
|
<Button Content="查询 IDN" Command="{Binding QueryIdentityCommand}"
|
||||||
|
Style="{StaticResource CmdBtn}"/>
|
||||||
|
<Button Content="重置设备" Command="{Binding ResetDeviceCommand}"
|
||||||
|
Style="{StaticResource WarnBtn}"/>
|
||||||
|
</StackPanel>
|
||||||
|
<StackPanel Orientation="Horizontal" Margin="0,4">
|
||||||
|
<Button Content="查询型号" Command="{Binding QueryModelCommand}"
|
||||||
|
Style="{StaticResource CmdBtn}"/>
|
||||||
|
<Button Content="查询序列号" Command="{Binding QuerySerialCommand}"
|
||||||
|
Style="{StaticResource CmdBtn}"/>
|
||||||
|
<Button Content="查询 STB" Command="{Binding QueryStatusByteCommand}"
|
||||||
|
Style="{StaticResource CmdBtn}"/>
|
||||||
|
</StackPanel>
|
||||||
|
<StackPanel Orientation="Horizontal" Margin="0,4">
|
||||||
|
<TextBlock Text="型号" Style="{StaticResource ParamLabel}"/>
|
||||||
|
<TextBox Style="{StaticResource MeasureBox}" Width="120"
|
||||||
|
Text="{Binding DeviceModel, Mode=OneWay}"/>
|
||||||
|
</StackPanel>
|
||||||
|
<StackPanel Orientation="Horizontal" Margin="0,4">
|
||||||
|
<TextBlock Text="序列号" Style="{StaticResource ParamLabel}"/>
|
||||||
|
<TextBox Style="{StaticResource MeasureBox}" Width="160"
|
||||||
|
Text="{Binding DeviceSerial, Mode=OneWay}"/>
|
||||||
|
</StackPanel>
|
||||||
|
</StackPanel>
|
||||||
|
</GroupBox>
|
||||||
|
</StackPanel>
|
||||||
|
|
||||||
|
<!-- ─── 右列 ─── -->
|
||||||
|
<StackPanel Grid.Column="1" Margin="4,0,0,0">
|
||||||
|
|
||||||
|
<!-- 实时测量 -->
|
||||||
|
<GroupBox Header="实时测量" Margin="0,0,0,8"
|
||||||
|
materialDesign:ColorZoneAssist.Mode="PrimaryLight">
|
||||||
|
<StackPanel Margin="4,4,4,4">
|
||||||
|
<StackPanel Orientation="Horizontal" Margin="0,4">
|
||||||
|
<TextBlock Text="电压" Style="{StaticResource ParamLabel}"/>
|
||||||
|
<TextBox Style="{StaticResource MeasureBox}"
|
||||||
|
Text="{Binding MeasuredVoltage, Mode=OneWay}"/>
|
||||||
|
<TextBlock Text="V" VerticalAlignment="Center" Margin="2,0,8,0"/>
|
||||||
|
</StackPanel>
|
||||||
|
<StackPanel Orientation="Horizontal" Margin="0,4">
|
||||||
|
<TextBlock Text="电流" Style="{StaticResource ParamLabel}"/>
|
||||||
|
<TextBox Style="{StaticResource MeasureBox}"
|
||||||
|
Text="{Binding MeasuredCurrent, Mode=OneWay}"/>
|
||||||
|
<TextBlock Text="A" VerticalAlignment="Center" Margin="2,0,8,0"/>
|
||||||
|
</StackPanel>
|
||||||
|
<StackPanel Orientation="Horizontal" Margin="0,4">
|
||||||
|
<TextBlock Text="功率" Style="{StaticResource ParamLabel}"/>
|
||||||
|
<TextBox Style="{StaticResource MeasureBox}"
|
||||||
|
Text="{Binding MeasuredPower, Mode=OneWay}"/>
|
||||||
|
<TextBlock Text="W" VerticalAlignment="Center" Margin="2,0,8,0"/>
|
||||||
|
</StackPanel>
|
||||||
|
<StackPanel Orientation="Horizontal" Margin="0,4">
|
||||||
|
<TextBlock Text="频率" Style="{StaticResource ParamLabel}"/>
|
||||||
|
<TextBox Style="{StaticResource MeasureBox}"
|
||||||
|
Text="{Binding MeasuredFrequency, Mode=OneWay}"/>
|
||||||
|
<TextBlock Text="Hz" VerticalAlignment="Center" Margin="2,0,8,0"/>
|
||||||
|
</StackPanel>
|
||||||
|
<StackPanel Orientation="Horizontal" Margin="0,4">
|
||||||
|
<TextBlock Text="功率因数" Style="{StaticResource ParamLabel}"/>
|
||||||
|
<TextBox Style="{StaticResource MeasureBox}"
|
||||||
|
Text="{Binding MeasuredPowerFactor, Mode=OneWay}"/>
|
||||||
|
</StackPanel>
|
||||||
|
<Button Content="刷新全部测量" Command="{Binding QueryAllMeasureCommand}"
|
||||||
|
Style="{StaticResource CmdBtn}"
|
||||||
|
HorizontalAlignment="Left" Margin="0,4,0,0"/>
|
||||||
|
</StackPanel>
|
||||||
|
</GroupBox>
|
||||||
|
|
||||||
|
<!-- 响应日志 -->
|
||||||
|
<GroupBox Header="响应日志" Margin="0,0,0,8"
|
||||||
|
materialDesign:ColorZoneAssist.Mode="PrimaryLight">
|
||||||
|
<ScrollViewer Height="360" VerticalScrollBarVisibility="Auto">
|
||||||
|
<TextBox Text="{Binding ResponseLog, Mode=OneWay}"
|
||||||
|
materialDesign:HintAssist.Hint=""
|
||||||
|
IsReadOnly="True"
|
||||||
|
TextWrapping="Wrap"
|
||||||
|
FontSize="11"
|
||||||
|
FontFamily="Consolas"
|
||||||
|
Background="#FAFAFA"
|
||||||
|
BorderThickness="0"
|
||||||
|
VerticalAlignment="Top"/>
|
||||||
|
</ScrollViewer>
|
||||||
|
</GroupBox>
|
||||||
|
</StackPanel>
|
||||||
|
</Grid>
|
||||||
|
</StackPanel>
|
||||||
|
</ScrollViewer>
|
||||||
|
</UserControl>
|
||||||
28
DeviceEditModule/Views/SPAW7000View.xaml.cs
Normal file
28
DeviceEditModule/Views/SPAW7000View.xaml.cs
Normal file
@@ -0,0 +1,28 @@
|
|||||||
|
using System;
|
||||||
|
using System.Collections.Generic;
|
||||||
|
using System.Linq;
|
||||||
|
using System.Text;
|
||||||
|
using System.Threading.Tasks;
|
||||||
|
using System.Windows;
|
||||||
|
using System.Windows.Controls;
|
||||||
|
using System.Windows.Data;
|
||||||
|
using System.Windows.Documents;
|
||||||
|
using System.Windows.Input;
|
||||||
|
using System.Windows.Media;
|
||||||
|
using System.Windows.Media.Imaging;
|
||||||
|
using System.Windows.Navigation;
|
||||||
|
using System.Windows.Shapes;
|
||||||
|
|
||||||
|
namespace DeviceEditModule.Views
|
||||||
|
{
|
||||||
|
/// <summary>
|
||||||
|
/// SPAW7000View.xaml 的交互逻辑
|
||||||
|
/// </summary>
|
||||||
|
public partial class SPAW7000View : UserControl
|
||||||
|
{
|
||||||
|
public SPAW7000View()
|
||||||
|
{
|
||||||
|
InitializeComponent();
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
@@ -10,4 +10,10 @@
|
|||||||
<PackageReference Include="NLog" Version="6.1.3" />
|
<PackageReference Include="NLog" Version="6.1.3" />
|
||||||
</ItemGroup>
|
</ItemGroup>
|
||||||
|
|
||||||
|
<ItemGroup>
|
||||||
|
<None Update="Nlog.config">
|
||||||
|
<CopyToOutputDirectory>PreserveNewest</CopyToOutputDirectory>
|
||||||
|
</None>
|
||||||
|
</ItemGroup>
|
||||||
|
|
||||||
</Project>
|
</Project>
|
||||||
|
|||||||
@@ -12,23 +12,23 @@ namespace Logger
|
|||||||
{
|
{
|
||||||
public static readonly ILogger Logger = LogManager.GetLogger("InfoLogger");
|
public static readonly ILogger Logger = LogManager.GetLogger("InfoLogger");
|
||||||
public static readonly ILogger sqlLogger = LogManager.GetLogger("SqlLogger");
|
public static readonly ILogger sqlLogger = LogManager.GetLogger("SqlLogger");
|
||||||
public static IProgress<(string message, string color, int depth)> Progress { get; set; }
|
public static IProgress<(string scope, string message, string color, int depth)> Progress { get; set; }
|
||||||
static LoggerHelper()
|
static LoggerHelper()
|
||||||
{
|
{
|
||||||
Progress = new Progress<(string message, string color, int depth)>();
|
Progress = new Progress<(string scope, string message, string color, int depth)>();
|
||||||
}
|
}
|
||||||
public static void InfoWithNotify(string message, int depth = 0)
|
public static void InfoWithNotify(string scope, string message, int depth = 0)
|
||||||
{
|
|
||||||
Logger.Info(message); // 写入 NLog
|
|
||||||
NotifyUI(message, "blue", depth); // 触发UI显示
|
|
||||||
}
|
|
||||||
|
|
||||||
public static void SuccessWithNotify(string message, int depth = 0)
|
|
||||||
{
|
{
|
||||||
Logger.Info(message);
|
Logger.Info(message);
|
||||||
NotifyUI(message, "lightgreen", depth);
|
NotifyUI(scope, message, "blue", depth);
|
||||||
}
|
}
|
||||||
public static void WarnWithNotify(string message, string stackTrace = null, int depth = 0)
|
|
||||||
|
public static void SuccessWithNotify(string scope, string message, int depth = 0)
|
||||||
|
{
|
||||||
|
Logger.Info(message);
|
||||||
|
NotifyUI(scope, message, "lightgreen", depth);
|
||||||
|
}
|
||||||
|
public static void WarnWithNotify(string scope, string message, string stackTrace = null, int depth = 0)
|
||||||
{
|
{
|
||||||
if (!string.IsNullOrEmpty(stackTrace))
|
if (!string.IsNullOrEmpty(stackTrace))
|
||||||
{
|
{
|
||||||
@@ -37,10 +37,10 @@ namespace Logger
|
|||||||
}
|
}
|
||||||
|
|
||||||
Logger.Warn(message);
|
Logger.Warn(message);
|
||||||
NotifyUI(message, "orange", depth);
|
NotifyUI(scope, message, "orange", depth);
|
||||||
}
|
}
|
||||||
|
|
||||||
public static void ErrorWithNotify(string message, string stackTrace = null, int depth = 0)
|
public static void ErrorWithNotify(string scope, string message, string stackTrace = null, int depth = 0)
|
||||||
{
|
{
|
||||||
if (!string.IsNullOrEmpty(stackTrace))
|
if (!string.IsNullOrEmpty(stackTrace))
|
||||||
{
|
{
|
||||||
@@ -49,11 +49,11 @@ namespace Logger
|
|||||||
}
|
}
|
||||||
|
|
||||||
Logger.Error(message);
|
Logger.Error(message);
|
||||||
NotifyUI(message, "red", depth);
|
NotifyUI(scope, message, "red", depth);
|
||||||
}
|
}
|
||||||
private static void NotifyUI(string message, string color, int depth)
|
private static void NotifyUI(string scope, string message, string color, int depth)
|
||||||
{
|
{
|
||||||
Progress.Report((message, color, depth));
|
Progress.Report((scope, message, color, depth));
|
||||||
}
|
}
|
||||||
|
|
||||||
public static void Info(string message, int depth = 0)
|
public static void Info(string message, int depth = 0)
|
||||||
@@ -95,20 +95,18 @@ namespace Logger
|
|||||||
|
|
||||||
foreach (var line in lines)
|
foreach (var line in lines)
|
||||||
{
|
{
|
||||||
// 匹配你项目的命名空间路径
|
|
||||||
if (line.Contains("ADP"))
|
if (line.Contains("ADP"))
|
||||||
{
|
{
|
||||||
// 提取 "in 文件路径:line 行号"
|
|
||||||
var match = Regex.Match(line, @"in (.+?):line (\d+)");
|
var match = Regex.Match(line, @"in (.+?):line (\d+)");
|
||||||
if (match.Success)
|
if (match.Success)
|
||||||
{
|
{
|
||||||
return match.Value; // 返回类似 C:\...\MainViewModel.cs:line 37
|
return match.Value;
|
||||||
}
|
}
|
||||||
return line.Trim();
|
return line.Trim();
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
return lines[0].Trim(); // 如果找不到就返回第一条
|
return lines[0].Trim();
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -3,7 +3,7 @@
|
|||||||
xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance">
|
xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance">
|
||||||
|
|
||||||
<!-- 定义目标 -->
|
<!-- 定义目标 -->
|
||||||
<targets>
|
<targets async="true">
|
||||||
<!-- SQL 日志 -->
|
<!-- SQL 日志 -->
|
||||||
<target name="sqlLog" xsi:type="File"
|
<target name="sqlLog" xsi:type="File"
|
||||||
fileName="Logs/${shortdate}_sql.txt"
|
fileName="Logs/${shortdate}_sql.txt"
|
||||||
|
|||||||
@@ -110,7 +110,7 @@ namespace MainModule.ViewModels
|
|||||||
}
|
}
|
||||||
catch (Exception ex)
|
catch (Exception ex)
|
||||||
{
|
{
|
||||||
Logger.LoggerHelper.ErrorWithNotify($"卸载机台 [{TestStatus}] 全局引用或资源失败: {ex.Message}");
|
Logger.LoggerHelper.ErrorWithNotify(TestStatus, $"卸载机台 [{TestStatus}] 全局引用或资源失败: {ex.Message}");
|
||||||
}
|
}
|
||||||
finally
|
finally
|
||||||
{
|
{
|
||||||
@@ -164,7 +164,7 @@ namespace MainModule.ViewModels
|
|||||||
|
|
||||||
if (program == null)
|
if (program == null)
|
||||||
{
|
{
|
||||||
LoggerHelper.WarnWithNotify($"文件格式不正确或为空: {filePath}");
|
LoggerHelper.WarnWithNotify(_globalInfo.CurrentScope, $"文件格式不正确或为空: {filePath}");
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -173,6 +173,15 @@ namespace MainModule.ViewModels
|
|||||||
_scopedContext.Program.StepCollection = program.StepCollection;
|
_scopedContext.Program.StepCollection = program.StepCollection;
|
||||||
_scopedContext.Program.ErrorStepCollection = program.ErrorStepCollection;
|
_scopedContext.Program.ErrorStepCollection = program.ErrorStepCollection;
|
||||||
_scopedContext.CurrentFilePath = filePath;
|
_scopedContext.CurrentFilePath = filePath;
|
||||||
|
foreach (var item in _systemConfig.SharedParameterList)
|
||||||
|
{
|
||||||
|
var parameter = _scopedContext?.Program?.Parameters?.FirstOrDefault(x => x.Name == item.ParameterName);
|
||||||
|
|
||||||
|
if (parameter != null)
|
||||||
|
{
|
||||||
|
parameter.Value = item.Value;
|
||||||
|
}
|
||||||
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|||||||
@@ -24,7 +24,6 @@ namespace Model.Models
|
|||||||
/// </summary>
|
/// </summary>
|
||||||
public string? Category { get; set; }
|
public string? Category { get; set; }
|
||||||
|
|
||||||
public bool IsGlobal { get; set; }
|
|
||||||
|
|
||||||
public object? Value { get; set; }
|
public object? Value { get; set; }
|
||||||
|
|
||||||
@@ -36,7 +35,6 @@ namespace Model.Models
|
|||||||
|
|
||||||
public bool IsUseVar { get; set; }
|
public bool IsUseVar { get; set; }
|
||||||
|
|
||||||
public bool IsSave { get; set; }
|
|
||||||
|
|
||||||
public string? VariableName { get; set; }
|
public string? VariableName { get; set; }
|
||||||
|
|
||||||
|
|||||||
@@ -217,7 +217,7 @@ namespace MonitorModule.ViewModels
|
|||||||
}
|
}
|
||||||
catch (Exception ex)
|
catch (Exception ex)
|
||||||
{
|
{
|
||||||
LoggerHelper.ErrorWithNotify($"加载数据表失败:{ex.Message}");
|
LoggerHelper.ErrorWithNotify(_globalInfo.CurrentScope,$"加载数据表失败:{ex.Message}");
|
||||||
StatusMessage = $"加载失败:{ex.Message}";
|
StatusMessage = $"加载失败:{ex.Message}";
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@@ -256,7 +256,7 @@ namespace MonitorModule.ViewModels
|
|||||||
}
|
}
|
||||||
catch (Exception ex)
|
catch (Exception ex)
|
||||||
{
|
{
|
||||||
LoggerHelper.ErrorWithNotify($"查询失败:{ex.Message}");
|
LoggerHelper.ErrorWithNotify(TestStatus,$"查询失败:{ex.Message}");
|
||||||
StatusMessage = $"查询失败:{ex.Message}";
|
StatusMessage = $"查询失败:{ex.Message}";
|
||||||
ResultTable = new DataTable();
|
ResultTable = new DataTable();
|
||||||
TotalCount = 0;
|
TotalCount = 0;
|
||||||
@@ -295,11 +295,11 @@ namespace MonitorModule.ViewModels
|
|||||||
WriteCsv(dlg.FileName, dt);
|
WriteCsv(dlg.FileName, dt);
|
||||||
|
|
||||||
StatusMessage = $"导出完成:{dlg.FileName}({dt.Rows.Count} 行)";
|
StatusMessage = $"导出完成:{dlg.FileName}({dt.Rows.Count} 行)";
|
||||||
LoggerHelper.InfoWithNotify($"工位 [{TestStatus}] 导出 [{SelectedTable}] 至 {dlg.FileName},共 {dt.Rows.Count} 行");
|
LoggerHelper.InfoWithNotify(TestStatus, $"工位 [{TestStatus}] 导出 [{SelectedTable}] 至 {dlg.FileName},共 {dt.Rows.Count} 行");
|
||||||
}
|
}
|
||||||
catch (Exception ex)
|
catch (Exception ex)
|
||||||
{
|
{
|
||||||
LoggerHelper.ErrorWithNotify($"导出失败:{ex.Message}");
|
LoggerHelper.ErrorWithNotify(TestStatus, $"导出失败:{ex.Message}");
|
||||||
StatusMessage = $"导出失败:{ex.Message}";
|
StatusMessage = $"导出失败:{ex.Message}";
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -44,6 +44,11 @@ namespace SettingModule.ViewModels
|
|||||||
{
|
{
|
||||||
get => _deviceList;
|
get => _deviceList;
|
||||||
set => SetProperty(ref _deviceList, value);
|
set => SetProperty(ref _deviceList, value);
|
||||||
|
}
|
||||||
|
public ObservableCollection<SharedParameter> SharedParameterList
|
||||||
|
{
|
||||||
|
get => _sharedParameterList;
|
||||||
|
set => SetProperty(ref _sharedParameterList, value);
|
||||||
}
|
}
|
||||||
|
|
||||||
public string StatusMessage
|
public string StatusMessage
|
||||||
@@ -72,6 +77,7 @@ namespace SettingModule.ViewModels
|
|||||||
private string _testStatus = string.Empty;
|
private string _testStatus = string.Empty;
|
||||||
private DeviceInfoVM? _selectedDevice;
|
private DeviceInfoVM? _selectedDevice;
|
||||||
private ObservableCollection<DeviceInfoVM> _deviceList;
|
private ObservableCollection<DeviceInfoVM> _deviceList;
|
||||||
|
private ObservableCollection<SharedParameter> _sharedParameterList;
|
||||||
private string _statusMessage = "请在左侧选择设备查看 / 编辑配置";
|
private string _statusMessage = "请在左侧选择设备查看 / 编辑配置";
|
||||||
#endregion
|
#endregion
|
||||||
public SettingViewModel(IContainerExtension container) : base(container)
|
public SettingViewModel(IContainerExtension container) : base(container)
|
||||||
@@ -95,7 +101,7 @@ namespace SettingModule.ViewModels
|
|||||||
}
|
}
|
||||||
catch (Exception ex)
|
catch (Exception ex)
|
||||||
{
|
{
|
||||||
Logger.LoggerHelper.ErrorWithNotify($"释放配置管理组件(SettingViewModel)资源失败: {ex.Message}");
|
Logger.LoggerHelper.ErrorWithNotify(TestStatus,$"释放配置管理组件(SettingViewModel)资源失败: {ex.Message}");
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -199,6 +205,20 @@ namespace SettingModule.ViewModels
|
|||||||
SelectedDevice = DeviceList[0];
|
SelectedDevice = DeviceList[0];
|
||||||
}
|
}
|
||||||
DeviceList = SystemConfig.DeviceList;
|
DeviceList = SystemConfig.DeviceList;
|
||||||
|
SharedParameterList=SystemConfig.SharedParameterList;
|
||||||
|
if (SharedParameterList.Count == 0)
|
||||||
|
{
|
||||||
|
foreach (var device in SystemConfig.ParameterList)
|
||||||
|
{
|
||||||
|
var sharedParam = new SharedParameter
|
||||||
|
{
|
||||||
|
Id = device.ID.ToString(),
|
||||||
|
ParameterName = device.Name,
|
||||||
|
Value = device.Value is int intVal ? intVal : Convert.ToInt32(device.Value)
|
||||||
|
};
|
||||||
|
SharedParameterList.Add(sharedParam);
|
||||||
|
}
|
||||||
|
}
|
||||||
IsInitiated = true;
|
IsInitiated = true;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -462,6 +462,34 @@
|
|||||||
</StackPanel>
|
</StackPanel>
|
||||||
</Border>
|
</Border>
|
||||||
|
|
||||||
|
<Border Background="White"
|
||||||
|
BorderBrush="#E0E0E0" BorderThickness="1"
|
||||||
|
CornerRadius="4" Padding="14"
|
||||||
|
Margin="0,0,0,12">
|
||||||
|
<StackPanel>
|
||||||
|
<TextBlock Text="公共变量(SharedParameterList)" FontWeight="Bold" FontSize="14" Margin="0,0,0,10"/>
|
||||||
|
<TextBlock Foreground="#888" FontSize="12" TextWrapping="Wrap"
|
||||||
|
Text="每个台架可独立设置自己的通道号等整型变量;新建或打开程序时会用此处值覆盖程序中的同名变量。" Margin="0,0,0,10"/>
|
||||||
|
<ItemsControl ItemsSource="{Binding SharedParameterList}">
|
||||||
|
<ItemsControl.ItemTemplate>
|
||||||
|
<DataTemplate>
|
||||||
|
<Grid Margin="0,4">
|
||||||
|
<Grid.ColumnDefinitions>
|
||||||
|
<ColumnDefinition Width="160"/>
|
||||||
|
<ColumnDefinition Width="*"/>
|
||||||
|
</Grid.ColumnDefinitions>
|
||||||
|
<TextBlock Grid.Column="0" Text="{Binding ParameterName}" VerticalAlignment="Center"/>
|
||||||
|
<TextBox Grid.Column="1"
|
||||||
|
VerticalAlignment="Center"
|
||||||
|
materialDesign:HintAssist.Hint=""
|
||||||
|
Text="{Binding Value, UpdateSourceTrigger=PropertyChanged}"/>
|
||||||
|
</Grid>
|
||||||
|
</DataTemplate>
|
||||||
|
</ItemsControl.ItemTemplate>
|
||||||
|
</ItemsControl>
|
||||||
|
</StackPanel>
|
||||||
|
</Border>
|
||||||
|
|
||||||
<Border Background="White"
|
<Border Background="White"
|
||||||
BorderBrush="#E0E0E0" BorderThickness="1"
|
BorderBrush="#E0E0E0" BorderThickness="1"
|
||||||
CornerRadius="4" Padding="14"
|
CornerRadius="4" Padding="14"
|
||||||
|
|||||||
@@ -189,7 +189,7 @@ namespace TestingModule.ViewModels
|
|||||||
{
|
{
|
||||||
try
|
try
|
||||||
{
|
{
|
||||||
var assembly = Assembly.LoadFile(dllPath);
|
var assembly = Assembly.LoadFrom(dllPath);
|
||||||
Assemblies.Add(assembly);
|
Assemblies.Add(assembly);
|
||||||
|
|
||||||
// 加载对应的XML注释文件 (项目没有用到)
|
// 加载对应的XML注释文件 (项目没有用到)
|
||||||
@@ -210,7 +210,7 @@ namespace TestingModule.ViewModels
|
|||||||
}
|
}
|
||||||
catch (Exception ex)
|
catch (Exception ex)
|
||||||
{
|
{
|
||||||
LoggerHelper.WarnWithNotify($"无法加载程序集 {Path.GetFileName(dllPath)}: {ex.Message}");
|
LoggerHelper.WarnWithNotify(_globalInfo.CurrentScope, $"无法加载程序集 {Path.GetFileName(dllPath)}: {ex.Message}");
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@@ -237,7 +237,7 @@ namespace TestingModule.ViewModels
|
|||||||
}
|
}
|
||||||
catch (Exception ex)
|
catch (Exception ex)
|
||||||
{
|
{
|
||||||
LoggerHelper.WarnWithNotify($"加载子程序错误: {filePath} - {ex.Message}");
|
LoggerHelper.WarnWithNotify(_globalInfo.CurrentScope, $"加载子程序错误: {filePath} - {ex.Message}");
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@@ -317,7 +317,7 @@ namespace TestingModule.ViewModels
|
|||||||
}
|
}
|
||||||
catch (Exception ex)
|
catch (Exception ex)
|
||||||
{
|
{
|
||||||
LoggerHelper.ErrorWithNotify($"加载类型错误: {assembly.FullName} - {ex.Message}");
|
LoggerHelper.ErrorWithNotify(_globalInfo.CurrentScope, $"加载类型错误: {assembly.FullName} - {ex.Message}");
|
||||||
}
|
}
|
||||||
|
|
||||||
if (validTypes.Count > 0)
|
if (validTypes.Count > 0)
|
||||||
@@ -504,7 +504,7 @@ namespace TestingModule.ViewModels
|
|||||||
}
|
}
|
||||||
catch (Exception ex)
|
catch (Exception ex)
|
||||||
{
|
{
|
||||||
LoggerHelper.ErrorWithNotify($"添加方法失败: {method.Name} - {ex.Message}");
|
LoggerHelper.ErrorWithNotify(_globalInfo.CurrentScope, $"添加方法失败: {method.Name} - {ex.Message}");
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -537,7 +537,7 @@ namespace TestingModule.ViewModels
|
|||||||
}
|
}
|
||||||
catch (Exception ex)
|
catch (Exception ex)
|
||||||
{
|
{
|
||||||
LoggerHelper.ErrorWithNotify($"添加子程序失败: {subProgram.Name} - {ex.Message}");
|
LoggerHelper.ErrorWithNotify(_globalInfo.CurrentScope, $"添加子程序失败: {subProgram.Name} - {ex.Message}"); ;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|||||||
@@ -1,9 +1,10 @@
|
|||||||
using UIShare.GlobalVariable;
|
using UIShare.GlobalVariable;
|
||||||
using Logger;
|
using Logger;
|
||||||
|
using Prism.Ioc;
|
||||||
using Prism.Mvvm;
|
using Prism.Mvvm;
|
||||||
|
using System;
|
||||||
|
using System.Collections.Generic;
|
||||||
using System.Collections.ObjectModel;
|
using System.Collections.ObjectModel;
|
||||||
using System.Reflection;
|
|
||||||
using System.Windows;
|
|
||||||
using System.Windows.Input;
|
using System.Windows.Input;
|
||||||
using System.Windows.Media;
|
using System.Windows.Media;
|
||||||
using System.Windows.Threading;
|
using System.Windows.Threading;
|
||||||
@@ -21,26 +22,54 @@ namespace TestingModule.ViewModels
|
|||||||
get => _logs;
|
get => _logs;
|
||||||
set => SetProperty(ref _logs, value);
|
set => SetProperty(ref _logs, value);
|
||||||
}
|
}
|
||||||
|
|
||||||
public ICommand ClearLogCommand { get; set; }
|
public ICommand ClearLogCommand { get; set; }
|
||||||
|
|
||||||
|
private readonly ScopedContext _scopedContext;
|
||||||
|
private readonly GlobalInfo _globalInfo;
|
||||||
|
|
||||||
|
/// <summary>UI 线程定时器,每 100ms 批量从 LogBuffer 出队并刷新</summary>
|
||||||
|
private readonly DispatcherTimer _flushTimer;
|
||||||
|
|
||||||
public LogAreaViewModel(IContainerProvider containerProvider) : base(containerProvider)
|
public LogAreaViewModel(IContainerProvider containerProvider) : base(containerProvider)
|
||||||
{
|
{
|
||||||
ClearLogCommand = new DelegateCommand(ClearLog);
|
ClearLogCommand = new DelegateCommand(ClearLog);
|
||||||
|
_scopedContext = containerProvider.Resolve<ScopedContext>();
|
||||||
|
_globalInfo = containerProvider.Resolve<GlobalInfo>();
|
||||||
|
|
||||||
// 2. 保持原有逻辑,但请确保在 Dispose 中对其进行清理
|
// UI 线程定时器:每 100ms 从 ScopedContext.LogBuffer 批量出队并 Add
|
||||||
LoggerHelper.Progress = new System.Progress<(string message, string color, int depth)>(
|
_flushTimer = new DispatcherTimer(DispatcherPriority.Normal, System.Windows.Application.Current.Dispatcher)
|
||||||
log =>
|
{
|
||||||
{
|
Interval = TimeSpan.FromMilliseconds(100)
|
||||||
// 增加防御性代码:防止进入销毁流程时异步回调引发空引用异常
|
};
|
||||||
if (Logs == null) return;
|
_flushTimer.Tick += FlushLogBuffer;
|
||||||
|
_flushTimer.Start();
|
||||||
|
}
|
||||||
|
|
||||||
var brush = (Brush)new BrushConverter().ConvertFromString(log.color);
|
/// <summary>
|
||||||
Logs.Add(new LogItem(log.message, brush, log.depth));
|
/// 定时批量刷新日志:一次性出队并 Add,WPF 对连续 Add 做合并渲染
|
||||||
});
|
/// </summary>
|
||||||
|
private void FlushLogBuffer(object? sender, EventArgs e)
|
||||||
|
{
|
||||||
|
if (_scopedContext.LogBuffer.IsEmpty || Logs == null) return;
|
||||||
|
|
||||||
|
var batch = new List<(string Message, Brush Color, int Depth)>();
|
||||||
|
while (_scopedContext.LogBuffer.TryDequeue(out var item))
|
||||||
|
{
|
||||||
|
batch.Add(item);
|
||||||
|
}
|
||||||
|
|
||||||
|
// 批量 Add,WPF 会对连续 Add 做合并渲染,只触发一次 layout/render pass
|
||||||
|
foreach (var item in batch)
|
||||||
|
{
|
||||||
|
Logs.Add(new LogItem(item.Message, item.Color, item.Depth));
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
private void ClearLog()
|
private void ClearLog()
|
||||||
{
|
{
|
||||||
|
// 清空缓冲区,防止定时器下次还把旧数据刷出来
|
||||||
|
while (_scopedContext.LogBuffer.TryDequeue(out _)) { }
|
||||||
Logs?.Clear();
|
Logs?.Clear();
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -51,16 +80,24 @@ namespace TestingModule.ViewModels
|
|||||||
{
|
{
|
||||||
try
|
try
|
||||||
{
|
{
|
||||||
LoggerHelper.Progress = null!;
|
// 停止定时器
|
||||||
|
_flushTimer.Stop();
|
||||||
|
_flushTimer.Tick -= FlushLogBuffer;
|
||||||
|
|
||||||
|
// 最后一次刷新缓冲区中的剩余日志
|
||||||
|
FlushLogBuffer(null, EventArgs.Empty);
|
||||||
|
|
||||||
if (Logs != null)
|
if (Logs != null)
|
||||||
{
|
{
|
||||||
Logs.Clear();
|
Logs.Clear();
|
||||||
Logs = null!;
|
Logs = null!;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
catch (Exception ex)
|
catch (Exception ex)
|
||||||
{
|
{
|
||||||
Logger.LoggerHelper.ErrorWithNotify($"释放日志组件(LogAreaViewModel)资源失败: {ex.Message}");
|
Logger.LoggerHelper.ErrorWithNotify(
|
||||||
|
_scopedContext != null ? _globalInfo.CurrentScope : "",
|
||||||
|
$"释放日志组件(LogAreaViewModel)资源失败: {ex.Message}");
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@@ -70,7 +107,8 @@ namespace TestingModule.ViewModels
|
|||||||
{
|
{
|
||||||
public string Message { get; set; }
|
public string Message { get; set; }
|
||||||
public Brush Color { get; set; } = Brushes.Black;
|
public Brush Color { get; set; } = Brushes.Black;
|
||||||
public int Depth { get; set; }
|
public int Depth { get; set; }
|
||||||
|
|
||||||
public LogItem(string message, Brush color, int depth = 0)
|
public LogItem(string message, Brush color, int depth = 0)
|
||||||
{
|
{
|
||||||
Message = new string(' ', depth * 20) + message;
|
Message = new string(' ', depth * 20) + message;
|
||||||
|
|||||||
@@ -16,13 +16,6 @@ namespace TestingModule.ViewModels
|
|||||||
public class ParametersManagerViewModel:NavigateViewModelBase, IDisposable
|
public class ParametersManagerViewModel:NavigateViewModelBase, IDisposable
|
||||||
{
|
{
|
||||||
#region 属性
|
#region 属性
|
||||||
//private ObservableCollection<DeviceConfigModel> _DeviceList;
|
|
||||||
|
|
||||||
//public ObservableCollection<DeviceConfigModel> DeviceList
|
|
||||||
//{
|
|
||||||
// get { return _DeviceList; }
|
|
||||||
// set { SetProperty(ref _DeviceList,value); }
|
|
||||||
//}
|
|
||||||
private ObservableCollection<DeviceInfoVM> _DeviceInfoModel;
|
private ObservableCollection<DeviceInfoVM> _DeviceInfoModel;
|
||||||
|
|
||||||
public ObservableCollection<DeviceInfoVM> DeviceInfoVM
|
public ObservableCollection<DeviceInfoVM> DeviceInfoVM
|
||||||
@@ -82,7 +75,6 @@ namespace TestingModule.ViewModels
|
|||||||
|
|
||||||
public ParametersManagerViewModel(IContainerProvider containerProvider) : base(containerProvider)
|
public ParametersManagerViewModel(IContainerProvider containerProvider) : base(containerProvider)
|
||||||
{
|
{
|
||||||
|
|
||||||
_ScopedContext = containerProvider.Resolve<ScopedContext>();
|
_ScopedContext = containerProvider.Resolve<ScopedContext>();
|
||||||
_systemConfig = containerProvider.Resolve<SystemConfig>();
|
_systemConfig = containerProvider.Resolve<SystemConfig>();
|
||||||
_deviceManager = containerProvider.Resolve<DeviceManager>();
|
_deviceManager = containerProvider.Resolve<DeviceManager>();
|
||||||
@@ -96,8 +88,24 @@ namespace TestingModule.ViewModels
|
|||||||
ReConnectCommand = new AsyncDelegateCommand(OnReConnect);
|
ReConnectCommand = new AsyncDelegateCommand(OnReConnect);
|
||||||
CloseCommand = new AsyncDelegateCommand(OnClose);
|
CloseCommand = new AsyncDelegateCommand(OnClose);
|
||||||
LoadedCommand = new DelegateCommand(OnLoad);
|
LoadedCommand = new DelegateCommand(OnLoad);
|
||||||
|
InitParameters();
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
#region 委托命令
|
||||||
|
private void InitParameters()
|
||||||
|
{
|
||||||
|
foreach(var item in _systemConfig.ParameterList)
|
||||||
|
{
|
||||||
|
var copy=new ParameterVM(item);
|
||||||
|
var param = _systemConfig.SharedParameterList.FirstOrDefault(x => x.ParameterName == copy.Name);
|
||||||
|
if (param != null)
|
||||||
|
{
|
||||||
|
copy.Value = param.Value;
|
||||||
|
}
|
||||||
|
Program.Parameters.Add(copy);
|
||||||
|
}
|
||||||
}
|
}
|
||||||
#region 委托命令
|
|
||||||
private void OnLoad()
|
private void OnLoad()
|
||||||
{
|
{
|
||||||
DeviceInfoVM = _systemConfig.DeviceList;
|
DeviceInfoVM = _systemConfig.DeviceList;
|
||||||
@@ -147,10 +155,14 @@ namespace TestingModule.ViewModels
|
|||||||
}
|
}
|
||||||
|
|
||||||
// 4. 发布事件 → DialogMangerViewModel 接收后将此 View 添加为 Tab
|
// 4. 发布事件 → DialogMangerViewModel 接收后将此 View 添加为 Tab
|
||||||
|
// 标题格式:{当前作用域} - {设备名称}
|
||||||
|
// 去重依据:设备硬件指纹(同一物理设备不重复打开)
|
||||||
|
var fingerprint = DeviceManager.ExtractHardwareFingerprint(SelectedDevice);
|
||||||
_eventAggregator.GetEvent<AddDialogTabEvent>().Publish(new DialogTabInfo
|
_eventAggregator.GetEvent<AddDialogTabEvent>().Publish(new DialogTabInfo
|
||||||
{
|
{
|
||||||
Title = $"{type} [{SelectedDevice.DeviceName}]",
|
Title = $"{_globalInfo.CurrentScope} - {SelectedDevice.DeviceName}",
|
||||||
Content = view
|
Fingerprint = fingerprint,
|
||||||
|
Content = view
|
||||||
});
|
});
|
||||||
|
|
||||||
// 5. 将窗口置顶(确保用户看到新增的 Tab)
|
// 5. 将窗口置顶(确保用户看到新增的 Tab)
|
||||||
@@ -163,7 +175,7 @@ namespace TestingModule.ViewModels
|
|||||||
}
|
}
|
||||||
catch (Exception ex)
|
catch (Exception ex)
|
||||||
{
|
{
|
||||||
LoggerHelper.ErrorWithNotify($"打开设备编辑窗口 [{type}] 失败:{ex.Message}");
|
LoggerHelper.ErrorWithNotify(_globalInfo.CurrentScope, $"打开设备编辑窗口 [{type}] 失败:{ex.Message}");
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|||||||
@@ -47,6 +47,7 @@ namespace TestingModule.ViewModels
|
|||||||
}
|
}
|
||||||
#endregion
|
#endregion
|
||||||
private ScopedContext _ScopedContext;
|
private ScopedContext _ScopedContext;
|
||||||
|
private GlobalInfo _globalInfo;
|
||||||
#region 命令
|
#region 命令
|
||||||
public ICommand CancelEditCommand { get; set; }
|
public ICommand CancelEditCommand { get; set; }
|
||||||
public ICommand SaveStepCommand { get; set; }
|
public ICommand SaveStepCommand { get; set; }
|
||||||
@@ -54,6 +55,7 @@ namespace TestingModule.ViewModels
|
|||||||
public SingleStepEditViewModel(IContainerProvider containerProvider, ScopedContext scopedContext) : base(containerProvider)
|
public SingleStepEditViewModel(IContainerProvider containerProvider, ScopedContext scopedContext) : base(containerProvider)
|
||||||
{
|
{
|
||||||
_ScopedContext = scopedContext;
|
_ScopedContext = scopedContext;
|
||||||
|
_globalInfo=containerProvider.Resolve<GlobalInfo>();
|
||||||
_eventAggregator.GetEvent<EditSetpEvent>().Subscribe(EditSingleStep);
|
_eventAggregator.GetEvent<EditSetpEvent>().Subscribe(EditSingleStep);
|
||||||
CancelEditCommand = new DelegateCommand(CancelEdit);
|
CancelEditCommand = new DelegateCommand(CancelEdit);
|
||||||
SaveStepCommand = new DelegateCommand(SaveStep);
|
SaveStepCommand = new DelegateCommand(SaveStep);
|
||||||
@@ -100,7 +102,7 @@ namespace TestingModule.ViewModels
|
|||||||
}
|
}
|
||||||
catch
|
catch
|
||||||
{
|
{
|
||||||
LoggerHelper.ErrorWithNotify("循环指令参数设置错误:类型转换失败");
|
LoggerHelper.ErrorWithNotify(_globalInfo.CurrentScope,"循环指令参数设置错误:类型转换失败");
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
else
|
else
|
||||||
|
|||||||
@@ -110,10 +110,7 @@
|
|||||||
ItemsSource="{Binding EnumValues}"
|
ItemsSource="{Binding EnumValues}"
|
||||||
SelectedItem="{Binding Parameter.Value}"
|
SelectedItem="{Binding Parameter.Value}"
|
||||||
Visibility="{Binding Parameter.Type, Converter={StaticResource IsEnumTypeConverter}}" />-->
|
Visibility="{Binding Parameter.Type, Converter={StaticResource IsEnumTypeConverter}}" />-->
|
||||||
<CheckBox Margin="10,0"
|
|
||||||
VerticalAlignment="Bottom"
|
|
||||||
Content="保存数据"
|
|
||||||
IsChecked="{Binding Parameter.IsSave}" />
|
|
||||||
</StackPanel>
|
</StackPanel>
|
||||||
</StackPanel>
|
</StackPanel>
|
||||||
</ScrollViewer>
|
</ScrollViewer>
|
||||||
|
|||||||
@@ -62,7 +62,7 @@ namespace UIShare.GlobalVariable
|
|||||||
}
|
}
|
||||||
catch (Exception ex)
|
catch (Exception ex)
|
||||||
{
|
{
|
||||||
LoggerHelper.ErrorWithNotify($"格子 [{title}] 配置加载失败: {ex.Message}");
|
LoggerHelper.ErrorWithNotify(title, $"格子 [{title}] 配置加载失败: {ex.Message}");
|
||||||
return new SystemConfig { Title = title };
|
return new SystemConfig { Title = title };
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@@ -90,11 +90,11 @@ namespace UIShare.GlobalVariable
|
|||||||
});
|
});
|
||||||
|
|
||||||
File.WriteAllText(configPath, json);
|
File.WriteAllText(configPath, json);
|
||||||
LoggerHelper.InfoWithNotify($"配置 [{config.Title}] 已保存。");
|
LoggerHelper.InfoWithNotify(config.Title, $"配置 [{config.Title}] 已保存。");
|
||||||
}
|
}
|
||||||
catch (Exception ex)
|
catch (Exception ex)
|
||||||
{
|
{
|
||||||
LoggerHelper.ErrorWithNotify($"配置 [{config.Title}] 保存失败: {ex.Message}");
|
LoggerHelper.ErrorWithNotify(config.Title, $"配置 [{config.Title}] 保存失败: {ex.Message}");
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -15,10 +15,12 @@ namespace UIShare.GlobalVariable
|
|||||||
/// 设备管理器:根据 <see cref="SystemConfig.DeviceList"/> 反射实例化所有启用的设备,
|
/// 设备管理器:根据 <see cref="SystemConfig.DeviceList"/> 反射实例化所有启用的设备,
|
||||||
/// 通过 <see cref="IBaseInterface"/> 多态统一管理,避免为每种设备单独硬编码字段。
|
/// 通过 <see cref="IBaseInterface"/> 多态统一管理,避免为每种设备单独硬编码字段。
|
||||||
/// </summary>
|
/// </summary>
|
||||||
public class DeviceManager
|
public class DeviceManager:IDisposable
|
||||||
{
|
{
|
||||||
private object _lockObj = new object();
|
private object _lockObj = new object();
|
||||||
public SystemConfig _systemConfig { get; set; }
|
public SystemConfig _systemConfig { get; set; }
|
||||||
|
private readonly GlobalInfo _globalInfo;
|
||||||
|
private readonly string _scopeName;
|
||||||
|
|
||||||
/// <summary>按 DeviceName 索引的设备字典,便于业务层按名取实例。</summary>
|
/// <summary>按 DeviceName 索引的设备字典,便于业务层按名取实例。</summary>
|
||||||
public IDictionary<string, IBaseInterface> DeviceMap { get; private set; }
|
public IDictionary<string, IBaseInterface> DeviceMap { get; private set; }
|
||||||
@@ -27,11 +29,35 @@ namespace UIShare.GlobalVariable
|
|||||||
/// <summary>类名 → Type 的反射缓存(仅扫描一次)。</summary>
|
/// <summary>类名 → Type 的反射缓存(仅扫描一次)。</summary>
|
||||||
private static readonly IReadOnlyDictionary<string, Type> _deviceTypeMap = BuildDeviceTypeMap();
|
private static readonly IReadOnlyDictionary<string, Type> _deviceTypeMap = BuildDeviceTypeMap();
|
||||||
|
|
||||||
public DeviceManager(SystemConfig systemConfig)
|
public DeviceManager(SystemConfig systemConfig, GlobalInfo globalInfo)
|
||||||
{
|
{
|
||||||
_systemConfig = systemConfig;
|
_systemConfig = systemConfig;
|
||||||
|
_globalInfo = globalInfo;
|
||||||
|
// 用 SystemConfig.Title 作为作用域唯一标识,无需反查 ConfigDic
|
||||||
|
_scopeName = _systemConfig.Title;
|
||||||
InitDevices();
|
InitDevices();
|
||||||
}
|
}
|
||||||
|
/// <summary>
|
||||||
|
/// 根据设备配置提取唯一的硬件指纹字符串。
|
||||||
|
/// <para>Tcp → "Tcp:IP:Port";Serial → "Serial:PortName";无法识别则返回空字符串。</para>
|
||||||
|
/// </summary>
|
||||||
|
public static string ExtractHardwareFingerprint(DeviceInfoVM config)
|
||||||
|
{
|
||||||
|
if (string.Equals(config.ConnectionType, "Tcp", StringComparison.OrdinalIgnoreCase)
|
||||||
|
&& config.TcpConfig != null)
|
||||||
|
{
|
||||||
|
return $"Tcp:{config.TcpConfig.IPAddress}:{config.TcpConfig.Port}";
|
||||||
|
}
|
||||||
|
|
||||||
|
if (string.Equals(config.ConnectionType, "Serial", StringComparison.OrdinalIgnoreCase)
|
||||||
|
&& config.SerialPortConfig != null)
|
||||||
|
{
|
||||||
|
return $"Serial:{config.SerialPortConfig.PortName}";
|
||||||
|
}
|
||||||
|
|
||||||
|
return string.Empty;
|
||||||
|
}
|
||||||
|
|
||||||
private void InitDevices()
|
private void InitDevices()
|
||||||
{
|
{
|
||||||
DeviceMap = new Dictionary<string, IBaseInterface>(StringComparer.OrdinalIgnoreCase);
|
DeviceMap = new Dictionary<string, IBaseInterface>(StringComparer.OrdinalIgnoreCase);
|
||||||
@@ -51,29 +77,58 @@ namespace UIShare.GlobalVariable
|
|||||||
|
|
||||||
try
|
try
|
||||||
{
|
{
|
||||||
IBaseInterface? instance = config.ConnectionType switch
|
// 第一步:提取硬件指纹,为空则跳过
|
||||||
|
var fingerprint = ExtractHardwareFingerprint(config);
|
||||||
|
if (string.IsNullOrEmpty(fingerprint))
|
||||||
{
|
{
|
||||||
"Tcp" => CreateTcpDevice(deviceType, config.TcpConfig),
|
LoggerHelper.Warn($"设备 [{config.DeviceName}] 无法提取硬件指纹(连接方式={config.ConnectionType}),已跳过。");
|
||||||
"Serial" => CreateSerialDevice(deviceType, config.SerialPortConfig),
|
continue;
|
||||||
_ => null
|
}
|
||||||
};
|
|
||||||
|
// 第二步:原子化获取或添加 Lazy 包装盒,确保同一指纹全局只创建一个实例
|
||||||
|
var lazy = _globalInfo.HardwarePool.GetOrAdd(fingerprint, key => new Lazy<IBaseInterface>(() =>
|
||||||
|
{
|
||||||
|
return config.ConnectionType switch
|
||||||
|
{
|
||||||
|
"Tcp" => CreateTcpDevice(deviceType, config.TcpConfig)!,
|
||||||
|
"Serial" => CreateSerialDevice(deviceType, config.SerialPortConfig)!,
|
||||||
|
_ => null!
|
||||||
|
};
|
||||||
|
}));
|
||||||
|
|
||||||
|
// 第三步:安全拆盒,Lazy 内部线程锁保证只实例化一次
|
||||||
|
var instance = lazy.Value;
|
||||||
|
|
||||||
if (instance == null)
|
if (instance == null)
|
||||||
{
|
{
|
||||||
LoggerHelper.Warn($"设备 [{config.DeviceName}] 连接方式 [{config.ConnectionType}] 不支持,已跳过。");
|
LoggerHelper.Warn($"设备 [{config.DeviceName}] 连接方式 [{config.ConnectionType}] 不支持,已跳过。");
|
||||||
continue;
|
continue;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// 第四步:绑定逻辑名 → 同一物理设备可被多个台架名称映射
|
||||||
if (!string.IsNullOrWhiteSpace(config.DeviceName))
|
if (!string.IsNullOrWhiteSpace(config.DeviceName))
|
||||||
{
|
{
|
||||||
DeviceMap[config.DeviceName] = instance;
|
DeviceMap[config.DeviceName] = instance;
|
||||||
}
|
}
|
||||||
|
|
||||||
LoggerHelper.Info($"已加载设备 [{config.DeviceName} / {config.DeviceType} / {config.ConnectionType}]");
|
// 第五步:将当前作用域注册到指纹的作用域列表,用于引用计数与安全销毁
|
||||||
|
if (!string.IsNullOrEmpty(_scopeName))
|
||||||
|
{
|
||||||
|
var scopeList = _globalInfo.DeviceAndScopeDic.GetOrAdd(fingerprint,
|
||||||
|
_ => new Lazy<List<string>>(() => new List<string>())).Value;
|
||||||
|
lock (scopeList)
|
||||||
|
{
|
||||||
|
if (!scopeList.Contains(_scopeName))
|
||||||
|
scopeList.Add(_scopeName);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
LoggerHelper.Info($"已加载设备 [{config.DeviceName} / {config.DeviceType} / {config.ConnectionType}] 指纹={fingerprint}");
|
||||||
}
|
}
|
||||||
catch (Exception ex)
|
catch (Exception ex)
|
||||||
{
|
{
|
||||||
var inner = ex.InnerException?.Message ?? ex.Message;
|
var inner = ex.InnerException?.Message ?? ex.Message;
|
||||||
LoggerHelper.ErrorWithNotify($"设备 [{config.DeviceName}] 实例化失败:{inner}");
|
LoggerHelper.ErrorWithNotify(_scopeName, $"设备 [{config.DeviceName}] 实例化失败:{inner}");
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@@ -233,7 +288,7 @@ namespace UIShare.GlobalVariable
|
|||||||
{
|
{
|
||||||
if (info != null) info.IsConnected = false;
|
if (info != null) info.IsConnected = false;
|
||||||
var inner = ex.InnerException?.Message ?? ex.Message;
|
var inner = ex.InnerException?.Message ?? ex.Message;
|
||||||
LoggerHelper.ErrorWithNotify($"设备 [{name}/{conn}] 连接异常:{inner}");
|
LoggerHelper.ErrorWithNotify(_scopeName, $"设备 [{name}/{conn}] 连接异常:{inner}");
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -298,6 +353,50 @@ namespace UIShare.GlobalVariable
|
|||||||
};
|
};
|
||||||
return Activator.CreateInstance(type, cfg) as IBaseInterface;
|
return Activator.CreateInstance(type, cfg) as IBaseInterface;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
public void Dispose()
|
||||||
|
{
|
||||||
|
if (string.IsNullOrEmpty(_scopeName)) return;
|
||||||
|
|
||||||
|
// 遍历当前作用域用到的所有指纹,逐一移除本作用域的引用
|
||||||
|
var fingerprintsToRemove = new List<string>();
|
||||||
|
|
||||||
|
foreach (var kvp in _globalInfo.DeviceAndScopeDic)
|
||||||
|
{
|
||||||
|
string fingerprint = kvp.Key;
|
||||||
|
var scopeList = kvp.Value.IsValueCreated ? kvp.Value.Value : null;
|
||||||
|
if (scopeList == null) continue;
|
||||||
|
|
||||||
|
lock (scopeList)
|
||||||
|
{
|
||||||
|
scopeList.Remove(_scopeName);
|
||||||
|
|
||||||
|
// 引用归零 → 标记为待清理
|
||||||
|
if (scopeList.Count == 0)
|
||||||
|
fingerprintsToRemove.Add(fingerprint);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// 对引用归零的指纹:销毁设备实例 + 从全局池中移除
|
||||||
|
foreach (var fingerprint in fingerprintsToRemove)
|
||||||
|
{
|
||||||
|
// 尝试从 HardwarePool 取出并销毁
|
||||||
|
if (_globalInfo.HardwarePool.TryRemove(fingerprint, out var lazy))
|
||||||
|
{
|
||||||
|
if (lazy.IsValueCreated && lazy.Value is IBaseInterface device)
|
||||||
|
{
|
||||||
|
try { device.Close(); }
|
||||||
|
catch { /* 销毁时忽略异常 */ }
|
||||||
|
LoggerHelper.Info($"指纹 [{fingerprint}] 无作用域引用,已销毁设备实例。");
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// 同步清除 DeviceAndScopeDic 中的空条目
|
||||||
|
_globalInfo.DeviceAndScopeDic.TryRemove(fingerprint, out _);
|
||||||
|
}
|
||||||
|
|
||||||
|
DeviceMap.Clear();
|
||||||
|
}
|
||||||
#endregion
|
#endregion
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -1,4 +1,6 @@
|
|||||||
using System;
|
using DeviceCommand.Base;
|
||||||
|
using System;
|
||||||
|
using System.Collections.Concurrent;
|
||||||
using System.Collections.Generic;
|
using System.Collections.Generic;
|
||||||
using System.Linq;
|
using System.Linq;
|
||||||
using System.Text;
|
using System.Text;
|
||||||
@@ -13,6 +15,13 @@ namespace UIShare.GlobalVariable
|
|||||||
public Dictionary<string,StepRunning> StepRunningDic { get; set; }
|
public Dictionary<string,StepRunning> StepRunningDic { get; set; }
|
||||||
public Dictionary<string, SystemConfig> ConfigDic { get; set; }
|
public Dictionary<string, SystemConfig> ConfigDic { get; set; }
|
||||||
public Dictionary<string, IScopedProvider> ScopeDic { get; set; }
|
public Dictionary<string, IScopedProvider> ScopeDic { get; set; }
|
||||||
|
|
||||||
|
/// <summary>硬件指纹 → 设备实例的并发池,确保同一物理硬件全局只创建一个驱动实例。</summary>
|
||||||
|
public ConcurrentDictionary<string, Lazy<IBaseInterface>> HardwarePool { get; set; }
|
||||||
|
|
||||||
|
/// <summary>硬件指纹 → 正在使用该设备的作用域名称列表,用于引用计数与安全销毁。</summary>
|
||||||
|
public ConcurrentDictionary<string, Lazy<List<string>>> DeviceAndScopeDic { get; set; }
|
||||||
|
|
||||||
public String UserName { get; set; } = "Not Logged in";
|
public String UserName { get; set; } = "Not Logged in";
|
||||||
public bool IsAdmin { get; set; } = true;
|
public bool IsAdmin { get; set; } = true;
|
||||||
public string CurrentOpeningScope;
|
public string CurrentOpeningScope;
|
||||||
@@ -35,6 +44,8 @@ namespace UIShare.GlobalVariable
|
|||||||
StepRunningDic = new();
|
StepRunningDic = new();
|
||||||
ConfigDic = new();
|
ConfigDic = new();
|
||||||
ScopeDic = new();
|
ScopeDic = new();
|
||||||
|
HardwarePool = new ConcurrentDictionary<string, Lazy<IBaseInterface>>(StringComparer.OrdinalIgnoreCase);
|
||||||
|
DeviceAndScopeDic = new ConcurrentDictionary<string, Lazy<List<string>>>(StringComparer.OrdinalIgnoreCase);
|
||||||
CurrentScope = "default";
|
CurrentScope = "default";
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|||||||
48
UIShare/GlobalVariable/ScopeLogDispatcher.cs
Normal file
48
UIShare/GlobalVariable/ScopeLogDispatcher.cs
Normal file
@@ -0,0 +1,48 @@
|
|||||||
|
using Logger;
|
||||||
|
using System;
|
||||||
|
using System.Collections.Concurrent;
|
||||||
|
using System.Windows.Media;
|
||||||
|
|
||||||
|
namespace UIShare.GlobalVariable
|
||||||
|
{
|
||||||
|
/// <summary>
|
||||||
|
/// 作用域日志分发器:根据显式传入的 scope 参数把日志路由到对应
|
||||||
|
/// <see cref="ScopedContext.LogBuffer"/>,实现每个台架/作用域拥有独立 LogArea。
|
||||||
|
/// 直接写入 ConcurrentQueue,不走 Progress<T> / SynchronizationContext,避免 UI 线程压力。
|
||||||
|
/// </summary>
|
||||||
|
public class ScopeLogDispatcher : IProgress<(string scope, string message, string color, int depth)>
|
||||||
|
{
|
||||||
|
private readonly GlobalInfo _globalInfo;
|
||||||
|
|
||||||
|
/// <summary>Brush 缓存,避免每次都 new BrushConverter</summary>
|
||||||
|
private static readonly ConcurrentDictionary<string, Brush> _brushCache = new();
|
||||||
|
|
||||||
|
public ScopeLogDispatcher(GlobalInfo globalInfo)
|
||||||
|
{
|
||||||
|
_globalInfo = globalInfo ?? throw new ArgumentNullException(nameof(globalInfo));
|
||||||
|
}
|
||||||
|
|
||||||
|
public void Report((string scope, string message, string color, int depth) value)
|
||||||
|
{
|
||||||
|
var scope = value.scope;
|
||||||
|
if (string.IsNullOrEmpty(scope)) return;
|
||||||
|
|
||||||
|
if (!_globalInfo.ContextDic.TryGetValue(scope, out var context)) return;
|
||||||
|
|
||||||
|
Brush brush = _brushCache.GetOrAdd(value.color, color =>
|
||||||
|
{
|
||||||
|
try
|
||||||
|
{
|
||||||
|
return (Brush)new BrushConverter().ConvertFromString(color);
|
||||||
|
}
|
||||||
|
catch
|
||||||
|
{
|
||||||
|
return Brushes.Black;
|
||||||
|
}
|
||||||
|
});
|
||||||
|
|
||||||
|
// 直接入队到后台线程安全的 ConcurrentQueue,不走 SynchronizationContext
|
||||||
|
context.LogBuffer.Enqueue((value.message, brush, value.depth));
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
@@ -1,6 +1,7 @@
|
|||||||
using DeviceCommand.Base;
|
using DeviceCommand.Base;
|
||||||
using MaterialDesignThemes.Wpf;
|
using MaterialDesignThemes.Wpf;
|
||||||
using System;
|
using System;
|
||||||
|
using System.Collections.Concurrent;
|
||||||
using System.Collections.Generic;
|
using System.Collections.Generic;
|
||||||
using System.Collections.ObjectModel;
|
using System.Collections.ObjectModel;
|
||||||
using System.Diagnostics;
|
using System.Diagnostics;
|
||||||
@@ -8,6 +9,7 @@ using System.Linq;
|
|||||||
using System.Reflection;
|
using System.Reflection;
|
||||||
using System.Text;
|
using System.Text;
|
||||||
using System.Threading.Tasks;
|
using System.Threading.Tasks;
|
||||||
|
using System.Windows.Media;
|
||||||
using UIShare.UIViewModel;
|
using UIShare.UIViewModel;
|
||||||
|
|
||||||
namespace UIShare.GlobalVariable
|
namespace UIShare.GlobalVariable
|
||||||
@@ -30,6 +32,14 @@ namespace UIShare.GlobalVariable
|
|||||||
public ParameterVM SelectedParameter { get; set; }
|
public ParameterVM SelectedParameter { get; set; }
|
||||||
|
|
||||||
public List<IBaseInterface> DeviceList { get; set; } = new();
|
public List<IBaseInterface> DeviceList { get; set; } = new();
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// 日志缓冲队列:后台线程(ScopeLogDispatcher)直接入队,
|
||||||
|
/// UI 线程(DispatcherTimer)定时出队并刷新到 ObservableCollection。
|
||||||
|
/// 不走 Progress<T> / SynchronizationContext,避免 Post 淹没 UI 消息队列。
|
||||||
|
/// </summary>
|
||||||
|
public ConcurrentQueue<(string Message, Brush Color, int Depth)> LogBuffer { get; } = new();
|
||||||
|
|
||||||
// 【新增测试属性】:每个实例被 new 出来时独一无二的随机身份
|
// 【新增测试属性】:每个实例被 new 出来时独一无二的随机身份
|
||||||
// 证 ID
|
// 证 ID
|
||||||
public int DebugRandomId { get; private set; }
|
public int DebugRandomId { get; private set; }
|
||||||
|
|||||||
@@ -88,7 +88,7 @@ namespace UIShare
|
|||||||
}
|
}
|
||||||
else
|
else
|
||||||
{
|
{
|
||||||
LoggerHelper.ErrorWithNotify("程序循环指令未闭合,请检查后重试");
|
LoggerHelper.ErrorWithNotify(_systemConfig.Title, "程序循环指令未闭合,请检查后重试");
|
||||||
break;
|
break;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@@ -108,7 +108,7 @@ namespace UIShare
|
|||||||
};
|
};
|
||||||
loopStack.Push(context);
|
loopStack.Push(context);
|
||||||
step.CurrentLoopCount = context.LoopCount;
|
step.CurrentLoopCount = context.LoopCount;
|
||||||
LoggerHelper.InfoWithNotify($"循环开始,共{context.LoopCount}次", depth);
|
LoggerHelper.InfoWithNotify(_systemConfig.Title, $"循环开始,共{context.LoopCount}次", depth);
|
||||||
index++;
|
index++;
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -117,7 +117,7 @@ namespace UIShare
|
|||||||
{
|
{
|
||||||
if (loopStack.Count == 0)
|
if (loopStack.Count == 0)
|
||||||
{
|
{
|
||||||
LoggerHelper.ErrorWithNotify("未匹配的循环结束指令", depth: depth);
|
LoggerHelper.ErrorWithNotify(_systemConfig.Title, "未匹配的循环结束指令", depth: depth);
|
||||||
step.Result = 2;
|
step.Result = 2;
|
||||||
index++;
|
index++;
|
||||||
continue;
|
continue;
|
||||||
@@ -133,7 +133,7 @@ namespace UIShare
|
|||||||
{
|
{
|
||||||
// 继续循环:跳转到循环开始后的第一条指令
|
// 继续循环:跳转到循环开始后的第一条指令
|
||||||
index = context.StartIndex + 1;
|
index = context.StartIndex + 1;
|
||||||
LoggerHelper.InfoWithNotify($"循环第{context.CurrentLoop}次结束,跳回开始,剩余{context.LoopCount - context.CurrentLoop}次", depth);
|
LoggerHelper.InfoWithNotify(_systemConfig.Title, $"循环第{context.CurrentLoop}次结束,跳回开始,剩余{context.LoopCount - context.CurrentLoop}次", depth);
|
||||||
}
|
}
|
||||||
else
|
else
|
||||||
{
|
{
|
||||||
@@ -141,7 +141,7 @@ namespace UIShare
|
|||||||
loopStack.Pop();
|
loopStack.Pop();
|
||||||
var loopStopwatch = loopStopwatchStack.Peek();
|
var loopStopwatch = loopStopwatchStack.Peek();
|
||||||
index++;
|
index++;
|
||||||
LoggerHelper.InfoWithNotify($"循环结束,共执行{context.LoopCount}次", depth);
|
LoggerHelper.InfoWithNotify(_systemConfig.Title, $"循环结束,共执行{context.LoopCount}次", depth);
|
||||||
if (depth == 0 && loopStopwatch.IsRunning)
|
if (depth == 0 && loopStopwatch.IsRunning)
|
||||||
{
|
{
|
||||||
loopStopwatch.Stop();
|
loopStopwatch.Stop();
|
||||||
@@ -168,7 +168,7 @@ namespace UIShare
|
|||||||
SubSingleStep = true;
|
SubSingleStep = true;
|
||||||
_scopedContext.SingleStep = false;
|
_scopedContext.SingleStep = false;
|
||||||
}
|
}
|
||||||
LoggerHelper.InfoWithNotify($"开始执行子程序 [ {step.Index} ] [ {step.Name} ] ", depth);
|
LoggerHelper.InfoWithNotify(_systemConfig.Title, $"开始执行子程序 [ {step.Index} ] [ {step.Name} ] ", depth);
|
||||||
stepSuccess = await ExecuteSteps(step.SubProgram, depth + 1, cancellationToken);
|
stepSuccess = await ExecuteSteps(step.SubProgram, depth + 1, cancellationToken);
|
||||||
UpdateCurrentStepResult(step, true, stepSuccess, depth);
|
UpdateCurrentStepResult(step, true, stepSuccess, depth);
|
||||||
if (SubSingleStep)
|
if (SubSingleStep)
|
||||||
@@ -179,7 +179,7 @@ namespace UIShare
|
|||||||
}
|
}
|
||||||
else if (step.Method != null)
|
else if (step.Method != null)
|
||||||
{
|
{
|
||||||
LoggerHelper.InfoWithNotify($"开始执行指令 [ {step.Index} ] [ {step.Method!.FullName}.{step.Method.Name} ] ", depth);
|
LoggerHelper.InfoWithNotify(_systemConfig.Title, $"开始执行指令 [ {step.Index} ] [ {step.Method!.FullName}.{step.Method.Name} ] ", depth);
|
||||||
await ExecuteMethodStep(step, tmpParameters, depth, cancellationToken);
|
await ExecuteMethodStep(step, tmpParameters, depth, cancellationToken);
|
||||||
stepSuccess = step.Result == 1;
|
stepSuccess = step.Result == 1;
|
||||||
if (step.NGGotoStepID != null && !stepSuccess)
|
if (step.NGGotoStepID != null && !stepSuccess)
|
||||||
@@ -188,7 +188,7 @@ namespace UIShare
|
|||||||
if (tmp != null)
|
if (tmp != null)
|
||||||
{
|
{
|
||||||
index = tmp.Index - 2;
|
index = tmp.Index - 2;
|
||||||
LoggerHelper.InfoWithNotify($"指令跳转 [ {tmp.Index} ] [ {tmp.Name} ]", depth);
|
LoggerHelper.InfoWithNotify(_systemConfig.Title, $"指令跳转 [ {tmp.Index} ] [ {tmp.Name} ]", depth);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
if (step.OKGotoStepID != null && stepSuccess)
|
if (step.OKGotoStepID != null && stepSuccess)
|
||||||
@@ -197,7 +197,7 @@ namespace UIShare
|
|||||||
if (tmp != null)
|
if (tmp != null)
|
||||||
{
|
{
|
||||||
index = tmp.Index - 2;
|
index = tmp.Index - 2;
|
||||||
LoggerHelper.InfoWithNotify($"指令跳转 [ {tmp.Index} ] [ {tmp.Name} ]", depth);
|
LoggerHelper.InfoWithNotify(_systemConfig.Title, $"指令跳转 [ {tmp.Index} ] [ {tmp.Name} ]", depth);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@@ -257,7 +257,7 @@ namespace UIShare
|
|||||||
}
|
}
|
||||||
else
|
else
|
||||||
{
|
{
|
||||||
LoggerHelper.ErrorWithNotify("程序循环指令未闭合,请检查后重试");
|
LoggerHelper.ErrorWithNotify(_systemConfig.Title, "程序循环指令未闭合,请检查后重试");
|
||||||
break;
|
break;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@@ -277,7 +277,7 @@ namespace UIShare
|
|||||||
};
|
};
|
||||||
loopStack.Push(context);
|
loopStack.Push(context);
|
||||||
step.CurrentLoopCount = context.LoopCount;
|
step.CurrentLoopCount = context.LoopCount;
|
||||||
LoggerHelper.InfoWithNotify($"循环开始({step.Name}),共{context.LoopCount}次", depth);
|
LoggerHelper.InfoWithNotify(_systemConfig.Title, $"循环开始({step.Name}),共{context.LoopCount}次", depth);
|
||||||
index++;
|
index++;
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -286,7 +286,7 @@ namespace UIShare
|
|||||||
{
|
{
|
||||||
if (loopStack.Count == 0)
|
if (loopStack.Count == 0)
|
||||||
{
|
{
|
||||||
LoggerHelper.ErrorWithNotify("未匹配的循环结束指令", depth:depth);
|
LoggerHelper.ErrorWithNotify(_systemConfig.Title, "未匹配的循环结束指令", depth:depth);
|
||||||
step.Result = 2;
|
step.Result = 2;
|
||||||
index++;
|
index++;
|
||||||
continue;
|
continue;
|
||||||
@@ -302,7 +302,7 @@ namespace UIShare
|
|||||||
{
|
{
|
||||||
// 继续循环:跳转到循环开始后的第一条指令
|
// 继续循环:跳转到循环开始后的第一条指令
|
||||||
index = context.StartIndex + 1;
|
index = context.StartIndex + 1;
|
||||||
LoggerHelper.InfoWithNotify($"循环第{context.CurrentLoop}次结束,跳回开始,剩余{context.LoopCount - context.CurrentLoop}次", depth);
|
LoggerHelper.InfoWithNotify(_systemConfig.Title, $"循环第{context.CurrentLoop}次结束,跳回开始,剩余{context.LoopCount - context.CurrentLoop}次", depth);
|
||||||
}
|
}
|
||||||
else
|
else
|
||||||
{
|
{
|
||||||
@@ -310,7 +310,7 @@ namespace UIShare
|
|||||||
loopStack.Pop();
|
loopStack.Pop();
|
||||||
var loopStopwatch = loopStopwatchStack.Peek();
|
var loopStopwatch = loopStopwatchStack.Peek();
|
||||||
index++;
|
index++;
|
||||||
LoggerHelper.InfoWithNotify($"循环结束,共执行{context.LoopCount}次", depth);
|
LoggerHelper.InfoWithNotify(_systemConfig.Title, $"循环结束,共执行{context.LoopCount}次", depth);
|
||||||
if (depth == 0 && loopStopwatch.IsRunning)
|
if (depth == 0 && loopStopwatch.IsRunning)
|
||||||
{
|
{
|
||||||
loopStopwatch.Stop();
|
loopStopwatch.Stop();
|
||||||
@@ -337,7 +337,7 @@ namespace UIShare
|
|||||||
SubSingleStep = true;
|
SubSingleStep = true;
|
||||||
_scopedContext.SingleStep = false;
|
_scopedContext.SingleStep = false;
|
||||||
}
|
}
|
||||||
LoggerHelper.InfoWithNotify($"开始执行子程序 [ {step.Index} ] [ {step.Name} ] ", depth);
|
LoggerHelper.InfoWithNotify(_systemConfig.Title, $"开始执行子程序 [ {step.Index} ] [ {step.Name} ] ", depth);
|
||||||
stepSuccess = await ExecuteSteps(step.SubProgram, depth + 1, cancellationToken);
|
stepSuccess = await ExecuteSteps(step.SubProgram, depth + 1, cancellationToken);
|
||||||
UpdateCurrentStepResult(step, true, stepSuccess, depth);
|
UpdateCurrentStepResult(step, true, stepSuccess, depth);
|
||||||
if (SubSingleStep)
|
if (SubSingleStep)
|
||||||
@@ -348,7 +348,7 @@ namespace UIShare
|
|||||||
}
|
}
|
||||||
else if (step.Method != null)
|
else if (step.Method != null)
|
||||||
{
|
{
|
||||||
LoggerHelper.InfoWithNotify($"开始执行指令 [ {step.Index} ] [ {step.Method!.FullName}.{step.Method.Name} ] ", depth);
|
LoggerHelper.InfoWithNotify(_systemConfig.Title, $"开始执行指令 [ {step.Index} ] [ {step.Method!.FullName}.{step.Method.Name} ] ", depth);
|
||||||
await ExecuteMethodStep(step, tmpParameters, depth, cancellationToken);
|
await ExecuteMethodStep(step, tmpParameters, depth, cancellationToken);
|
||||||
stepSuccess = step.Result == 1;
|
stepSuccess = step.Result == 1;
|
||||||
if (step.NGGotoStepID != null && !stepSuccess)
|
if (step.NGGotoStepID != null && !stepSuccess)
|
||||||
@@ -357,7 +357,7 @@ namespace UIShare
|
|||||||
if (tmp != null)
|
if (tmp != null)
|
||||||
{
|
{
|
||||||
index = tmp.Index - 2;
|
index = tmp.Index - 2;
|
||||||
LoggerHelper.InfoWithNotify($"指令跳转 [ {tmp.Index} ] [ {tmp.Name} ]", depth);
|
LoggerHelper.InfoWithNotify(_systemConfig.Title, $"指令跳转 [ {tmp.Index} ] [ {tmp.Name} ]", depth);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
if (step.OKGotoStepID != null && stepSuccess)
|
if (step.OKGotoStepID != null && stepSuccess)
|
||||||
@@ -366,7 +366,7 @@ namespace UIShare
|
|||||||
if (tmp != null)
|
if (tmp != null)
|
||||||
{
|
{
|
||||||
index = tmp.Index - 2;
|
index = tmp.Index - 2;
|
||||||
LoggerHelper.InfoWithNotify($"指令跳转 [ {tmp.Index} ] [ {tmp.Name} ]", depth);
|
LoggerHelper.InfoWithNotify(_systemConfig.Title, $"指令跳转 [ {tmp.Index} ] [ {tmp.Name} ]", depth);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@@ -408,7 +408,7 @@ namespace UIShare
|
|||||||
}
|
}
|
||||||
if (targetType == null)
|
if (targetType == null)
|
||||||
{
|
{
|
||||||
LoggerHelper.ErrorWithNotify($"指令 [ {step.Index} ] 执行错误:未找到类型 {step.Method!.FullName}", depth: depth);
|
LoggerHelper.ErrorWithNotify(_systemConfig.Title, $"指令 [ {step.Index} ] 执行错误:未找到类型 {step.Method!.FullName}", depth: depth);
|
||||||
step.Result = 2;
|
step.Result = 2;
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -522,7 +522,7 @@ namespace UIShare
|
|||||||
}
|
}
|
||||||
catch (Exception ex)
|
catch (Exception ex)
|
||||||
{
|
{
|
||||||
LoggerHelper.WarnWithNotify($"指令 [ {step.Index} ] 执行错误:参数 {param.Name} 类型转换失败: {ex.Message}", depth: depth);
|
LoggerHelper.WarnWithNotify(_systemConfig.Title, $"指令 [ {step.Index} ] 执行错误:参数 {param.Name} 类型转换失败: {ex.Message}", depth: depth);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@@ -546,7 +546,7 @@ namespace UIShare
|
|||||||
|
|
||||||
if (method == null)
|
if (method == null)
|
||||||
{
|
{
|
||||||
LoggerHelper.ErrorWithNotify($"指令 [ {step.Index} ] 执行错误:未找到方法{step.Method.Name}", depth: depth);
|
LoggerHelper.ErrorWithNotify(_systemConfig.Title, $"指令 [ {step.Index} ] 执行错误:未找到方法{step.Method.Name}", depth: depth);
|
||||||
step.Result = 2;
|
step.Result = 2;
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -558,11 +558,11 @@ namespace UIShare
|
|||||||
{
|
{
|
||||||
try
|
try
|
||||||
{
|
{
|
||||||
//instance = _devices.DeviceDic[targetType.Name];
|
instance = _deviceManager.DeviceMap[targetType.Name];
|
||||||
}
|
}
|
||||||
catch (Exception ex)
|
catch (Exception ex)
|
||||||
{
|
{
|
||||||
LoggerHelper.ErrorWithNotify($"指令 [ {step.Index} ] 执行错误:创建实例失败 - {ex.Message}", depth: depth);
|
LoggerHelper.ErrorWithNotify(_systemConfig.Title, $"指令 [ {step.Index} ] 执行错误:创建实例失败 - {ex.Message}", depth: depth);
|
||||||
step.Result = 2;
|
step.Result = 2;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@@ -600,7 +600,7 @@ namespace UIShare
|
|||||||
}
|
}
|
||||||
catch (Exception ex)
|
catch (Exception ex)
|
||||||
{
|
{
|
||||||
LoggerHelper.ErrorWithNotify($"指令 [ {step.Index} ] 执行错误: {ex.InnerException?.Message ?? ex.Message}", depth: depth);
|
LoggerHelper.ErrorWithNotify(_systemConfig.Title, $"指令 [ {step.Index} ] 执行错误: {ex.InnerException?.Message ?? ex.Message}", depth: depth);
|
||||||
step.Result = 2;
|
step.Result = 2;
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
@@ -619,31 +619,28 @@ namespace UIShare
|
|||||||
paraResult = tmp.Item1;
|
paraResult = tmp.Item1;
|
||||||
if (tmp.Item2 != null)
|
if (tmp.Item2 != null)
|
||||||
{
|
{
|
||||||
LoggerHelper.WarnWithNotify(tmp.Item2);
|
LoggerHelper.WarnWithNotify(_systemConfig.Title, tmp.Item2);
|
||||||
}
|
}
|
||||||
//if (currentPara.IsSave && _scopedContext.Program.Parameters.FirstOrDefault(x => x.ID == currentPara.ID) != null)
|
|
||||||
//{
|
|
||||||
// _ = SaveDataToDatabase(_scopedContext.Program.ID, currentPara);
|
|
||||||
//}
|
|
||||||
}
|
}
|
||||||
var returnType = returnValue?.GetType();
|
var returnType = returnValue?.GetType();
|
||||||
if (returnType != null)
|
if (returnType != null)
|
||||||
{
|
{
|
||||||
if (!returnType.IsArray)
|
if (!returnType.IsArray)
|
||||||
{
|
{
|
||||||
LoggerHelper.SuccessWithNotify($"输出 [ {outputParam.Name} ] = {returnValue} ({returnType.Name})", depth);
|
LoggerHelper.SuccessWithNotify(_systemConfig.Title, $"输出 [ {outputParam.Name} ] = {returnValue} ({returnType.Name})", depth);
|
||||||
}
|
}
|
||||||
else
|
else
|
||||||
{
|
{
|
||||||
if (returnValue is IEnumerable enumerable)
|
if (returnValue is IEnumerable enumerable)
|
||||||
{
|
{
|
||||||
var elements = enumerable.Cast<object>().Select(item => item?.ToString() ?? "null");
|
var elements = enumerable.Cast<object>().Select(item => item?.ToString() ?? "null");
|
||||||
LoggerHelper.SuccessWithNotify($"输出 [ {outputParam.Name} ] = [ {string.Join(", ", elements)} ] ({returnType.Name})", depth);
|
LoggerHelper.SuccessWithNotify(_systemConfig.Title, $"输出 [ {outputParam.Name} ] = [ {string.Join(", ", elements)} ] ({returnType.Name})", depth);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
LoggerHelper.SuccessWithNotify($"指令 [ {step.Index} ] 执行成功", depth);
|
LoggerHelper.SuccessWithNotify(_systemConfig.Title, $"指令 [ {step.Index} ] 执行成功", depth);
|
||||||
UpdateCurrentStepResult(step, paraResult: paraResult, depth: depth);
|
UpdateCurrentStepResult(step, paraResult: paraResult, depth: depth);
|
||||||
}
|
}
|
||||||
catch (OperationCanceledException)
|
catch (OperationCanceledException)
|
||||||
@@ -652,7 +649,7 @@ namespace UIShare
|
|||||||
}
|
}
|
||||||
catch (Exception ex)
|
catch (Exception ex)
|
||||||
{
|
{
|
||||||
LoggerHelper.ErrorWithNotify($"指令 [ {step.Index} ] 执行错误: {ex.InnerException?.Message ?? ex.Message}", depth: depth);
|
LoggerHelper.ErrorWithNotify(_systemConfig.Title, $"指令 [ {step.Index} ] 执行错误: {ex.InnerException?.Message ?? ex.Message}", depth: depth);
|
||||||
step.Result = 2;
|
step.Result = 2;
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
@@ -700,7 +697,7 @@ namespace UIShare
|
|||||||
step.Result = re ? 1 : 2;
|
step.Result = re ? 1 : 2;
|
||||||
if (step.Result == 2)
|
if (step.Result == 2)
|
||||||
{
|
{
|
||||||
LoggerHelper.WarnWithNotify($"指令 [ {step.Index} ] NG:条件表达式验证失败", depth: depth);
|
LoggerHelper.WarnWithNotify(_systemConfig.Title, $"指令 [ {step.Index} ] NG:条件表达式验证失败", depth: depth);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@@ -708,7 +705,7 @@ namespace UIShare
|
|||||||
{
|
{
|
||||||
if (!paraResult)
|
if (!paraResult)
|
||||||
{
|
{
|
||||||
LoggerHelper.WarnWithNotify("参数限值校验失败", depth: depth);
|
LoggerHelper.WarnWithNotify(_systemConfig.Title, "参数限值校验失败", depth: depth);
|
||||||
}
|
}
|
||||||
step.Result = 2;
|
step.Result = 2;
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -9,6 +9,7 @@ using System.Reflection;
|
|||||||
using System.Text;
|
using System.Text;
|
||||||
using System.Threading.Tasks;
|
using System.Threading.Tasks;
|
||||||
using UIShare.UIViewModel;
|
using UIShare.UIViewModel;
|
||||||
|
using static UIShare.UIViewModel.ParameterVM;
|
||||||
|
|
||||||
namespace UIShare.GlobalVariable
|
namespace UIShare.GlobalVariable
|
||||||
{
|
{
|
||||||
@@ -27,6 +28,53 @@ namespace UIShare.GlobalVariable
|
|||||||
public string DefaultBLFFilePath { get; set; } = "";
|
public string DefaultBLFFilePath { get; set; } = "";
|
||||||
public string DefaultDBCFilePath { get; set; } = "";
|
public string DefaultDBCFilePath { get; set; } = "";
|
||||||
public ObservableCollection<DeviceInfoVM> DeviceList = new();
|
public ObservableCollection<DeviceInfoVM> DeviceList = new();
|
||||||
|
public ObservableCollection<SharedParameter> SharedParameterList = new();
|
||||||
|
[JsonIgnore]
|
||||||
|
public ObservableCollection<ParameterVM> ParameterList = new()
|
||||||
|
{
|
||||||
|
new ParameterVM
|
||||||
|
{
|
||||||
|
Category = ParameterCategory.Input,
|
||||||
|
Type = typeof(int),
|
||||||
|
Name = "继电器占位1",
|
||||||
|
Value = 0,
|
||||||
|
},
|
||||||
|
new ParameterVM
|
||||||
|
{
|
||||||
|
Category = ParameterCategory.Input,
|
||||||
|
Type = typeof(int),
|
||||||
|
Name = "继电器占位2",
|
||||||
|
Value = 1,
|
||||||
|
},
|
||||||
|
new ParameterVM
|
||||||
|
{
|
||||||
|
Category = ParameterCategory.Input,
|
||||||
|
Type = typeof(int),
|
||||||
|
Name = "CAN通道",
|
||||||
|
Value = 0,
|
||||||
|
},
|
||||||
|
new ParameterVM
|
||||||
|
{
|
||||||
|
Category = ParameterCategory.Input,
|
||||||
|
Type = typeof(int),
|
||||||
|
Name = "示波器通道",
|
||||||
|
Value = 0,
|
||||||
|
},
|
||||||
|
new ParameterVM
|
||||||
|
{
|
||||||
|
Category = ParameterCategory.Input,
|
||||||
|
Type = typeof(int),
|
||||||
|
Name = "功率分析仪通道1",
|
||||||
|
Value = 0,
|
||||||
|
},
|
||||||
|
new ParameterVM
|
||||||
|
{
|
||||||
|
Category = ParameterCategory.Input,
|
||||||
|
Type = typeof(int),
|
||||||
|
Name = "功率分析仪通道2",
|
||||||
|
Value = 0,
|
||||||
|
},
|
||||||
|
};
|
||||||
// public ObservableCollection<DeviceInfoVM> DeviceList { get; set; } = new()
|
// public ObservableCollection<DeviceInfoVM> DeviceList { get; set; } = new()
|
||||||
//{
|
//{
|
||||||
// new DeviceInfoVM
|
// new DeviceInfoVM
|
||||||
|
|||||||
@@ -17,5 +17,6 @@
|
|||||||
<ResourceDictionary Source="pack://application:,,,/MaterialDesignColors;component/Themes/Recommended/Secondary/MaterialDesignColor.Lime.xaml" />
|
<ResourceDictionary Source="pack://application:,,,/MaterialDesignColors;component/Themes/Recommended/Secondary/MaterialDesignColor.Lime.xaml" />
|
||||||
<!--自定义style-->
|
<!--自定义style-->
|
||||||
<ResourceDictionary Source="/UIShare;component/Styles/WindowStyle.xaml"></ResourceDictionary>
|
<ResourceDictionary Source="/UIShare;component/Styles/WindowStyle.xaml"></ResourceDictionary>
|
||||||
|
<ResourceDictionary Source="/UIShare;component/Styles/DialogControlStyle.xaml"></ResourceDictionary>
|
||||||
</ResourceDictionary.MergedDictionaries>
|
</ResourceDictionary.MergedDictionaries>
|
||||||
</ResourceDictionary>
|
</ResourceDictionary>
|
||||||
|
|||||||
63
UIShare/Styles/DialogControlStyle.xaml
Normal file
63
UIShare/Styles/DialogControlStyle.xaml
Normal file
@@ -0,0 +1,63 @@
|
|||||||
|
<ResourceDictionary xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation"
|
||||||
|
xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml"
|
||||||
|
xmlns:materialDesign="http://materialdesigninxaml.net/winfx/xaml/themes">
|
||||||
|
|
||||||
|
<ResourceDictionary.MergedDictionaries>
|
||||||
|
<ResourceDictionary Source="pack://application:,,,/MaterialDesignThemes.Wpf;component/Themes/MaterialDesignTheme.Light.xaml" />
|
||||||
|
<ResourceDictionary Source="pack://application:,,,/MaterialDesignThemes.Wpf;component/Themes/MaterialDesign3.Defaults.xaml" />
|
||||||
|
</ResourceDictionary.MergedDictionaries>
|
||||||
|
|
||||||
|
<Style x:Key="CmdBtn" TargetType="Button" BasedOn="{StaticResource MaterialDesignRaisedButton}">
|
||||||
|
<Setter Property="Height" Value="32"/>
|
||||||
|
<Setter Property="Padding" Value="12,0"/>
|
||||||
|
<Setter Property="FontSize" Value="12"/>
|
||||||
|
<Setter Property="Margin" Value="4,0"/>
|
||||||
|
</Style>
|
||||||
|
|
||||||
|
<Style x:Key="WarnBtn" TargetType="Button" BasedOn="{StaticResource MaterialDesignRaisedButton}">
|
||||||
|
<Setter Property="Height" Value="32"/>
|
||||||
|
<Setter Property="Padding" Value="12,0"/>
|
||||||
|
<Setter Property="FontSize" Value="12"/>
|
||||||
|
<Setter Property="Margin" Value="4,0"/>
|
||||||
|
<Setter Property="Background" Value="#EF6C00"/>
|
||||||
|
<Setter Property="Foreground" Value="White"/>
|
||||||
|
</Style>
|
||||||
|
|
||||||
|
<Style x:Key="NumInput" TargetType="TextBox" BasedOn="{StaticResource MaterialDesignOutlinedTextBox}">
|
||||||
|
<Setter Property="Width" Value="100"/>
|
||||||
|
<Setter Property="Height" Value="32"/>
|
||||||
|
<Setter Property="Padding" Value="5,0"/>
|
||||||
|
<Setter Property="materialDesign:TextFieldAssist.TextBoxViewMargin" Value="6,2,6,2"/>
|
||||||
|
<Setter Property="VerticalContentAlignment" Value="Center"/>
|
||||||
|
<Setter Property="FontSize" Value="12"/>
|
||||||
|
<Setter Property="Margin" Value="4,0"/>
|
||||||
|
<Setter Property="materialDesign:HintAssist.IsFloating" Value="False"/>
|
||||||
|
<Setter Property="materialDesign:HintAssist.Hint" Value=""/>
|
||||||
|
|
||||||
|
<Setter Property="AutomationProperties.Name" Value=""/>
|
||||||
|
</Style>
|
||||||
|
|
||||||
|
<Style x:Key="MeasureBox" TargetType="TextBox" BasedOn="{StaticResource MaterialDesignOutlinedTextBox}">
|
||||||
|
<Setter Property="Width" Value="110"/>
|
||||||
|
<Setter Property="Height" Value="32"/>
|
||||||
|
<Setter Property="Padding" Value="5,0"/>
|
||||||
|
<Setter Property="materialDesign:TextFieldAssist.TextBoxViewMargin" Value="6,2,6,2"/>
|
||||||
|
<Setter Property="IsReadOnly" Value="True"/>
|
||||||
|
<Setter Property="VerticalContentAlignment" Value="Center"/>
|
||||||
|
<Setter Property="FontSize" Value="12"/>
|
||||||
|
<Setter Property="Margin" Value="4,0"/>
|
||||||
|
<Setter Property="Background" Value="#F5F5F5"/>
|
||||||
|
<Setter Property="materialDesign:HintAssist.IsFloating" Value="False"/>
|
||||||
|
<Setter Property="materialDesign:HintAssist.Hint" Value=""/>
|
||||||
|
|
||||||
|
<Setter Property="AutomationProperties.Name" Value=""/>
|
||||||
|
</Style>
|
||||||
|
|
||||||
|
<Style x:Key="ParamLabel" TargetType="TextBlock">
|
||||||
|
<Setter Property="Width" Value="80"/>
|
||||||
|
<Setter Property="VerticalAlignment" Value="Center"/>
|
||||||
|
<Setter Property="FontSize" Value="12"/>
|
||||||
|
<Setter Property="Margin" Value="0,0,4,0"/>
|
||||||
|
</Style>
|
||||||
|
|
||||||
|
</ResourceDictionary>
|
||||||
@@ -1,5 +1,5 @@
|
|||||||
<ResourceDictionary xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation"
|
<ResourceDictionary xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation"
|
||||||
xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml">
|
xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml" >
|
||||||
|
|
||||||
<Style x:Key="DialogUserManageStyle"
|
<Style x:Key="DialogUserManageStyle"
|
||||||
TargetType="Window">
|
TargetType="Window">
|
||||||
@@ -19,4 +19,5 @@
|
|||||||
Value="WidthAndHeight" />
|
Value="WidthAndHeight" />
|
||||||
</Style>
|
</Style>
|
||||||
|
|
||||||
|
|
||||||
</ResourceDictionary>
|
</ResourceDictionary>
|
||||||
|
|||||||
@@ -10,6 +10,12 @@ namespace UIShare.UIViewModel
|
|||||||
/// <summary>Tab 标题(显示在标签页上)。</summary>
|
/// <summary>Tab 标题(显示在标签页上)。</summary>
|
||||||
public string Title { get; set; } = string.Empty;
|
public string Title { get; set; } = string.Empty;
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// 设备硬件指纹(如 "Tcp:192.168.1.100:502"),用于去重判断,
|
||||||
|
/// 防止同一物理设备被重复打开多个 Tab。
|
||||||
|
/// </summary>
|
||||||
|
public string Fingerprint { get; set; } = string.Empty;
|
||||||
|
|
||||||
/// <summary>
|
/// <summary>
|
||||||
/// Tab 内容:传入一个已实例化的 <see cref="System.Windows.FrameworkElement"/>(通常是 UserControl),
|
/// Tab 内容:传入一个已实例化的 <see cref="System.Windows.FrameworkElement"/>(通常是 UserControl),
|
||||||
/// 由 ContentControl 直接承载展示。
|
/// 由 ContentControl 直接承载展示。
|
||||||
|
|||||||
@@ -21,6 +21,14 @@ namespace DeviceEditModule.ViewModels
|
|||||||
set => SetProperty(ref _title, value);
|
set => SetProperty(ref _title, value);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
private string _fingerprint = string.Empty;
|
||||||
|
/// <summary>设备硬件指纹,用于去重字典的 Key。</summary>
|
||||||
|
public string Fingerprint
|
||||||
|
{
|
||||||
|
get => _fingerprint;
|
||||||
|
set => SetProperty(ref _fingerprint, value);
|
||||||
|
}
|
||||||
|
|
||||||
private object? _content;
|
private object? _content;
|
||||||
/// <summary>Tab 内容区域(通常为 UserControl 实例)。</summary>
|
/// <summary>Tab 内容区域(通常为 UserControl 实例)。</summary>
|
||||||
public object? Content
|
public object? Content
|
||||||
@@ -23,10 +23,8 @@ namespace UIShare.UIViewModel
|
|||||||
Type = source.Type;
|
Type = source.Type;
|
||||||
Category = source.Category;
|
Category = source.Category;
|
||||||
IsUseVar = source.IsUseVar;
|
IsUseVar = source.IsUseVar;
|
||||||
IsSave = source.IsSave;
|
|
||||||
VariableName = source.VariableName;
|
VariableName = source.VariableName;
|
||||||
VariableID = source.VariableID;
|
VariableID = source.VariableID;
|
||||||
IsGlobal = source.IsGlobal;
|
|
||||||
Value = source.Value;
|
Value = source.Value;
|
||||||
LowerLimit = source.LowerLimit;
|
LowerLimit = source.LowerLimit;
|
||||||
UpperLimit = source.UpperLimit;
|
UpperLimit = source.UpperLimit;
|
||||||
@@ -69,12 +67,6 @@ namespace UIShare.UIViewModel
|
|||||||
set => SetProperty(ref _category, value);
|
set => SetProperty(ref _category, value);
|
||||||
}
|
}
|
||||||
|
|
||||||
private bool _isGlobal;
|
|
||||||
public bool IsGlobal
|
|
||||||
{
|
|
||||||
get => _isGlobal;
|
|
||||||
set => SetProperty(ref _isGlobal, value);
|
|
||||||
}
|
|
||||||
|
|
||||||
private object? _value;
|
private object? _value;
|
||||||
public object? Value
|
public object? Value
|
||||||
@@ -111,12 +103,7 @@ namespace UIShare.UIViewModel
|
|||||||
set => SetProperty(ref _isUseVar, value);
|
set => SetProperty(ref _isUseVar, value);
|
||||||
}
|
}
|
||||||
|
|
||||||
private bool _isSave;
|
|
||||||
public bool IsSave
|
|
||||||
{
|
|
||||||
get => _isSave;
|
|
||||||
set => SetProperty(ref _isSave, value);
|
|
||||||
}
|
|
||||||
|
|
||||||
private string? _variableName;
|
private string? _variableName;
|
||||||
public string? VariableName
|
public string? VariableName
|
||||||
|
|||||||
30
UIShare/UIViewModel/SharedParameter.cs
Normal file
30
UIShare/UIViewModel/SharedParameter.cs
Normal file
@@ -0,0 +1,30 @@
|
|||||||
|
using System;
|
||||||
|
using System.Collections.Generic;
|
||||||
|
using System.Linq;
|
||||||
|
using System.Text;
|
||||||
|
using System.Threading.Tasks;
|
||||||
|
|
||||||
|
namespace UIShare.UIViewModel
|
||||||
|
{
|
||||||
|
public class SharedParameter:BindableBase
|
||||||
|
{
|
||||||
|
private string _id;
|
||||||
|
public string Id
|
||||||
|
{
|
||||||
|
get => _id;
|
||||||
|
set => SetProperty(ref _id, value);
|
||||||
|
}
|
||||||
|
private string _parameterName;
|
||||||
|
public string ParameterName
|
||||||
|
{
|
||||||
|
get => _parameterName;
|
||||||
|
set => SetProperty(ref _parameterName, value);
|
||||||
|
}
|
||||||
|
private int _value;
|
||||||
|
public int Value
|
||||||
|
{
|
||||||
|
get => _value;
|
||||||
|
set => SetProperty(ref _value, value);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
Reference in New Issue
Block a user