716 lines
26 KiB
C#
716 lines
26 KiB
C#
using System;
|
||
using System.Collections.ObjectModel;
|
||
using System.Linq;
|
||
using System.Threading;
|
||
using System.Threading.Tasks;
|
||
using Prism.Commands;
|
||
using Prism.Mvvm;
|
||
using XP.Common.Logging.Interfaces;
|
||
using XP.Hardware.Plc.Abstractions;
|
||
using XP.Hardware.Plc.Exceptions;
|
||
using XP.Hardware.Plc.Services;
|
||
using XP.Hardware.PLC.Configs;
|
||
using XP.Hardware.PLC.Helpers;
|
||
|
||
namespace XP.Hardware.PLC.ViewModels
|
||
{
|
||
/// <summary>
|
||
/// PLC 测试台 ViewModel | PLC Test Bench ViewModel
|
||
/// 提供连接管理、单点读写、批量读取和日志功能
|
||
/// </summary>
|
||
public class PlcTestBenchViewModel : BindableBase
|
||
{
|
||
private readonly PlcService _plcService;
|
||
private readonly IPlcClient _plcClient;
|
||
private readonly ConfigLoader _configLoader;
|
||
private readonly ILoggerService _logger;
|
||
private CancellationTokenSource _cts;
|
||
private System.Windows.Threading.DispatcherTimer _continuousReadTimer;
|
||
|
||
// === 数据类型选项 | Data type options ===
|
||
private readonly string[] _dataTypes = { "Bool", "Byte", "Short", "Int", "Float", "Double", "String" };
|
||
public string[] DataTypes => _dataTypes;
|
||
|
||
// === 连接配置区属性 | Connection config properties ===
|
||
private string _ipAddress = "127.0.0.1";
|
||
public string IpAddress
|
||
{
|
||
get => _ipAddress;
|
||
set => SetProperty(ref _ipAddress, value);
|
||
}
|
||
|
||
private int _port = 502;
|
||
public int Port
|
||
{
|
||
get => _port;
|
||
set => SetProperty(ref _port, value);
|
||
}
|
||
|
||
private short _rack = 0;
|
||
public short Rack
|
||
{
|
||
get => _rack;
|
||
set => SetProperty(ref _rack, value);
|
||
}
|
||
|
||
private short _slot = 1;
|
||
public short Slot
|
||
{
|
||
get => _slot;
|
||
set => SetProperty(ref _slot, value);
|
||
}
|
||
|
||
private PlcType _selectedPlcType = PlcType.S1200;
|
||
public PlcType SelectedPlcType
|
||
{
|
||
get => _selectedPlcType;
|
||
set => SetProperty(ref _selectedPlcType, value);
|
||
}
|
||
|
||
/// <summary>
|
||
/// PLC 类型枚举值列表,用于下拉框绑定 | PLC type enum values for ComboBox binding
|
||
/// </summary>
|
||
public PlcType[] PlcTypes => Enum.GetValues(typeof(PlcType)).Cast<PlcType>().ToArray();
|
||
|
||
/// <summary>
|
||
/// 连接状态(绑定 PlcService.IsConnected)| Connection status
|
||
/// </summary>
|
||
public bool IsConnected => _plcService.IsConnected;
|
||
|
||
/// <summary>
|
||
/// 状态文本(绑定 PlcService.StatusText)| Status text
|
||
/// </summary>
|
||
public string StatusText => _plcService.StatusText;
|
||
|
||
private bool _isConnecting;
|
||
/// <summary>
|
||
/// 连接中标志,防止重复点击 | Connecting flag to prevent duplicate clicks
|
||
/// </summary>
|
||
public bool IsConnecting
|
||
{
|
||
get => _isConnecting;
|
||
set
|
||
{
|
||
if (SetProperty(ref _isConnecting, value))
|
||
RefreshCommandStates();
|
||
}
|
||
}
|
||
|
||
// === 单点读写区属性 | Single point read/write properties ===
|
||
private string _pointAddress;
|
||
public string PointAddress
|
||
{
|
||
get => _pointAddress;
|
||
set
|
||
{
|
||
if (SetProperty(ref _pointAddress, value))
|
||
RefreshCommandStates();
|
||
}
|
||
}
|
||
|
||
private string _selectedDataType = "Int";
|
||
public string SelectedDataType
|
||
{
|
||
get => _selectedDataType;
|
||
set
|
||
{
|
||
if (SetProperty(ref _selectedDataType, value))
|
||
{
|
||
RaisePropertyChanged(nameof(IsBoolType));
|
||
RaisePropertyChanged(nameof(IsStringType));
|
||
RefreshCommandStates();
|
||
}
|
||
}
|
||
}
|
||
|
||
private string _readResult;
|
||
public string ReadResult
|
||
{
|
||
get => _readResult;
|
||
set => SetProperty(ref _readResult, value);
|
||
}
|
||
|
||
private string _writeValue;
|
||
public string WriteValue
|
||
{
|
||
get => _writeValue;
|
||
set
|
||
{
|
||
if (SetProperty(ref _writeValue, value))
|
||
RefreshCommandStates();
|
||
}
|
||
}
|
||
|
||
/// <summary>
|
||
/// 是否为 Bool 类型(控制 Toggle 按钮可见性)| Whether data type is Bool
|
||
/// </summary>
|
||
public bool IsBoolType => SelectedDataType == "Bool";
|
||
|
||
/// <summary>
|
||
/// 是否为 String 类型(控制字符串长度输入可见性)| Whether data type is String
|
||
/// </summary>
|
||
public bool IsStringType => SelectedDataType == "String";
|
||
|
||
private int _stringLength = 10;
|
||
/// <summary>
|
||
/// 字符串读写长度(字节数)| String read/write length in bytes
|
||
/// </summary>
|
||
public int StringLength
|
||
{
|
||
get => _stringLength;
|
||
set => SetProperty(ref _stringLength, value);
|
||
}
|
||
|
||
private bool _isContinuousReading;
|
||
/// <summary>
|
||
/// 是否正在连续读取 | Whether continuous reading is active
|
||
/// </summary>
|
||
public bool IsContinuousReading
|
||
{
|
||
get => _isContinuousReading;
|
||
set
|
||
{
|
||
if (SetProperty(ref _isContinuousReading, value))
|
||
{
|
||
RaisePropertyChanged(nameof(IsNotContinuousReading));
|
||
RefreshCommandStates();
|
||
}
|
||
}
|
||
}
|
||
|
||
/// <summary>
|
||
/// 是否未在连续读取(用于按钮启用绑定)| Whether not continuous reading
|
||
/// </summary>
|
||
public bool IsNotContinuousReading => !IsContinuousReading;
|
||
|
||
// === 批量读取区属性 | Batch read properties ===
|
||
private string _batchAddress;
|
||
public string BatchAddress
|
||
{
|
||
get => _batchAddress;
|
||
set
|
||
{
|
||
if (SetProperty(ref _batchAddress, value))
|
||
RefreshCommandStates();
|
||
}
|
||
}
|
||
|
||
private string _batchLength;
|
||
public string BatchLength
|
||
{
|
||
get => _batchLength;
|
||
set
|
||
{
|
||
if (SetProperty(ref _batchLength, value))
|
||
RefreshCommandStates();
|
||
}
|
||
}
|
||
|
||
private string _hexViewerContent;
|
||
public string HexViewerContent
|
||
{
|
||
get => _hexViewerContent;
|
||
set => SetProperty(ref _hexViewerContent, value);
|
||
}
|
||
|
||
// === 日志区属性 | Log properties ===
|
||
public ObservableCollection<LogEntry> LogEntries { get; } = new ObservableCollection<LogEntry>();
|
||
|
||
// === 命令 | Commands ===
|
||
public DelegateCommand ConnectCommand { get; }
|
||
public DelegateCommand DisconnectCommand { get; }
|
||
public DelegateCommand ReadCommand { get; }
|
||
public DelegateCommand WriteCommand { get; }
|
||
public DelegateCommand ToggleBoolCommand { get; }
|
||
public DelegateCommand BatchReadCommand { get; }
|
||
public DelegateCommand ClearLogCommand { get; }
|
||
public DelegateCommand StartContinuousReadCommand { get; }
|
||
public DelegateCommand StopContinuousReadCommand { get; }
|
||
|
||
/// <summary>
|
||
/// 构造函数 | Constructor
|
||
/// </summary>
|
||
/// <param name="plcClient">PLC 客户端接口 | PLC client interface</param>
|
||
/// <param name="logger">日志服务 | Logger service</param>
|
||
public PlcTestBenchViewModel(IPlcClient plcClient, ILoggerService logger)
|
||
{
|
||
_plcClient = plcClient ?? throw new ArgumentNullException(nameof(plcClient));
|
||
_logger = logger?.ForModule<PlcTestBenchViewModel>() ?? throw new ArgumentNullException(nameof(logger));
|
||
_plcService = new PlcService(plcClient, logger, new XmlSignalParser());
|
||
_configLoader = new ConfigLoader(logger);
|
||
_cts = new CancellationTokenSource();
|
||
|
||
// 监听 PlcService 属性变化,转发到 UI | Forward PlcService property changes to UI
|
||
_plcService.PropertyChanged += (s, e) =>
|
||
{
|
||
if (e.PropertyName == nameof(PlcService.IsConnected))
|
||
{
|
||
RaisePropertyChanged(nameof(IsConnected));
|
||
RefreshCommandStates();
|
||
}
|
||
else if (e.PropertyName == nameof(PlcService.StatusText))
|
||
{
|
||
RaisePropertyChanged(nameof(StatusText));
|
||
}
|
||
};
|
||
|
||
// 初始化命令 | Initialize commands
|
||
ConnectCommand = new DelegateCommand(async () => await ConnectAsync())
|
||
.ObservesCanExecute(() => CanConnect);
|
||
DisconnectCommand = new DelegateCommand(Disconnect)
|
||
.ObservesCanExecute(() => IsConnected);
|
||
ReadCommand = new DelegateCommand(async () => await ReadPointAsync(),
|
||
() => IsConnected && !string.IsNullOrWhiteSpace(PointAddress));
|
||
WriteCommand = new DelegateCommand(async () => await WritePointAsync(),
|
||
() => IsConnected && !string.IsNullOrWhiteSpace(PointAddress) && !string.IsNullOrWhiteSpace(WriteValue));
|
||
ToggleBoolCommand = new DelegateCommand(async () => await ToggleBoolAsync(),
|
||
() => IsConnected && !string.IsNullOrWhiteSpace(PointAddress) && IsBoolType);
|
||
BatchReadCommand = new DelegateCommand(async () => await BatchReadAsync(),
|
||
() => IsConnected && !string.IsNullOrWhiteSpace(BatchAddress) && !string.IsNullOrWhiteSpace(BatchLength));
|
||
ClearLogCommand = new DelegateCommand(() => LogEntries.Clear());
|
||
StartContinuousReadCommand = new DelegateCommand(StartContinuousRead,
|
||
() => IsConnected && !string.IsNullOrWhiteSpace(PointAddress) && !IsContinuousReading);
|
||
StopContinuousReadCommand = new DelegateCommand(StopContinuousRead,
|
||
() => IsContinuousReading);
|
||
|
||
// 初始化连续读取定时器(250ms 间隔)| Initialize continuous read timer (250ms interval)
|
||
_continuousReadTimer = new System.Windows.Threading.DispatcherTimer
|
||
{
|
||
Interval = TimeSpan.FromMilliseconds(250)
|
||
};
|
||
_continuousReadTimer.Tick += async (s, e) => await ContinuousReadTickAsync();
|
||
|
||
// 加载配置 | Load configuration
|
||
LoadConfig();
|
||
}
|
||
|
||
/// <summary>
|
||
/// 连接按钮启用条件 | Connect button enable condition
|
||
/// </summary>
|
||
public bool CanConnect => !IsConnecting && !IsConnected;
|
||
|
||
/// <summary>
|
||
/// 加载 PLC 配置 | Load PLC configuration
|
||
/// </summary>
|
||
private void LoadConfig()
|
||
{
|
||
try
|
||
{
|
||
var config = _configLoader.LoadPlcConfig();
|
||
IpAddress = config.IpAddress;
|
||
Port = config.Port;
|
||
Rack = config.Rack;
|
||
Slot = config.Slot;
|
||
SelectedPlcType = config.PlcType;
|
||
AddLog(TestBenchLogLevel.INFO, "配置加载成功 | Configuration loaded successfully");
|
||
}
|
||
catch (Exception ex)
|
||
{
|
||
// 使用默认值 | Use default values
|
||
IpAddress = "127.0.0.1";
|
||
Port = 502;
|
||
Rack = 0;
|
||
Slot = 1;
|
||
SelectedPlcType = PlcType.S1200;
|
||
AddLog(TestBenchLogLevel.ERROR, $"配置加载失败,使用默认值 | Configuration load failed, using defaults: {ex.Message}");
|
||
}
|
||
}
|
||
|
||
/// <summary>
|
||
/// 添加日志条目 | Add log entry
|
||
/// </summary>
|
||
public void AddLog(TestBenchLogLevel level, string message)
|
||
{
|
||
var entry = new LogEntry
|
||
{
|
||
Timestamp = DateTime.Now.ToString("HH:mm:ss.fff"),
|
||
Level = level,
|
||
Message = message
|
||
};
|
||
LogEntries.Add(entry);
|
||
}
|
||
|
||
/// <summary>
|
||
/// 刷新所有命令的启用状态 | Refresh all command enable states
|
||
/// </summary>
|
||
private void RefreshCommandStates()
|
||
{
|
||
RaisePropertyChanged(nameof(CanConnect));
|
||
ReadCommand.RaiseCanExecuteChanged();
|
||
WriteCommand.RaiseCanExecuteChanged();
|
||
ToggleBoolCommand.RaiseCanExecuteChanged();
|
||
BatchReadCommand.RaiseCanExecuteChanged();
|
||
StartContinuousReadCommand.RaiseCanExecuteChanged();
|
||
StopContinuousReadCommand.RaiseCanExecuteChanged();
|
||
}
|
||
|
||
/// <summary>
|
||
/// 连接 PLC | Connect to PLC
|
||
/// </summary>
|
||
private async Task ConnectAsync()
|
||
{
|
||
IsConnecting = true;
|
||
try
|
||
{
|
||
var config = new PlcConfig
|
||
{
|
||
IpAddress = IpAddress,
|
||
Port = Port,
|
||
Rack = Rack,
|
||
Slot = Slot,
|
||
PlcType = SelectedPlcType
|
||
};
|
||
|
||
AddLog(TestBenchLogLevel.INFO, $"正在连接 PLC {IpAddress}:{Port} ... | Connecting to PLC...");
|
||
var result = await _plcService.InitializeAsync(config);
|
||
|
||
if (result)
|
||
AddLog(TestBenchLogLevel.INFO, "PLC 连接成功 | PLC connected successfully");
|
||
else
|
||
AddLog(TestBenchLogLevel.WARN, "PLC 连接失败 | PLC connection failed");
|
||
}
|
||
catch (PlcException ex)
|
||
{
|
||
AddLog(TestBenchLogLevel.ERROR, $"连接失败: {ex.Message} | Connection failed: {ex.Message}");
|
||
}
|
||
catch (Exception ex)
|
||
{
|
||
AddLog(TestBenchLogLevel.ERROR, $"连接异常: {ex.Message} | Connection exception: {ex.Message}");
|
||
}
|
||
finally
|
||
{
|
||
IsConnecting = false;
|
||
}
|
||
}
|
||
|
||
/// <summary>
|
||
/// 断开 PLC 连接 | Disconnect PLC
|
||
/// </summary>
|
||
private void Disconnect()
|
||
{
|
||
try
|
||
{
|
||
_plcService.Dispose();
|
||
AddLog(TestBenchLogLevel.INFO, "PLC 已断开 | PLC disconnected");
|
||
}
|
||
catch (Exception ex)
|
||
{
|
||
AddLog(TestBenchLogLevel.ERROR, $"断开失败: {ex.Message} | Disconnect failed: {ex.Message}");
|
||
}
|
||
}
|
||
|
||
/// <summary>
|
||
/// 读取单点数据 | Read single point data
|
||
/// </summary>
|
||
private async Task ReadPointAsync()
|
||
{
|
||
if (string.IsNullOrWhiteSpace(PointAddress))
|
||
{
|
||
AddLog(TestBenchLogLevel.ERROR, "地址不能为空 | Address cannot be empty");
|
||
return;
|
||
}
|
||
|
||
try
|
||
{
|
||
object result = SelectedDataType switch
|
||
{
|
||
"Bool" => await _plcService.ReadValueAsync<bool>(PointAddress),
|
||
"Byte" => await _plcService.ReadValueAsync<byte>(PointAddress),
|
||
"Short" => await _plcService.ReadValueAsync<short>(PointAddress),
|
||
"Int" => await _plcService.ReadValueAsync<int>(PointAddress),
|
||
"Float" => await _plcService.ReadValueAsync<float>(PointAddress),
|
||
"Double" => await _plcService.ReadValueAsync<double>(PointAddress),
|
||
"String" => await _plcService.ReadStringAsync(PointAddress, (ushort)StringLength),
|
||
_ => throw new InvalidOperationException($"不支持的数据类型: {SelectedDataType}")
|
||
};
|
||
|
||
ReadResult = result?.ToString() ?? "null";
|
||
AddLog(TestBenchLogLevel.INFO, $"读取成功: 地址={PointAddress}, 类型={SelectedDataType}, 值={ReadResult} | Read success");
|
||
}
|
||
catch (PlcException ex)
|
||
{
|
||
AddLog(TestBenchLogLevel.ERROR, $"读取失败: 地址={PointAddress}, 错误={ex.Message} | Read failed");
|
||
}
|
||
catch (Exception ex)
|
||
{
|
||
AddLog(TestBenchLogLevel.ERROR, $"读取异常: {ex.Message} | Read exception");
|
||
}
|
||
}
|
||
|
||
/// <summary>
|
||
/// 写入单点数据 | Write single point data
|
||
/// </summary>
|
||
private async Task WritePointAsync()
|
||
{
|
||
if (string.IsNullOrWhiteSpace(PointAddress))
|
||
{
|
||
AddLog(TestBenchLogLevel.ERROR, "地址不能为空 | Address cannot be empty");
|
||
return;
|
||
}
|
||
|
||
if (string.IsNullOrWhiteSpace(WriteValue))
|
||
{
|
||
AddLog(TestBenchLogLevel.ERROR, "写入值不能为空 | Write value cannot be empty");
|
||
return;
|
||
}
|
||
|
||
try
|
||
{
|
||
var converted = ConvertToType(WriteValue, SelectedDataType);
|
||
if (converted == null)
|
||
return;
|
||
|
||
bool result = SelectedDataType switch
|
||
{
|
||
"Bool" => await _plcService.WriteValueAsync(PointAddress, (bool)converted),
|
||
"Byte" => await _plcService.WriteValueAsync(PointAddress, (byte)converted),
|
||
"Short" => await _plcService.WriteValueAsync(PointAddress, (short)converted),
|
||
"Int" => await _plcService.WriteValueAsync(PointAddress, (int)converted),
|
||
"Float" => await _plcService.WriteValueAsync(PointAddress, (float)converted),
|
||
"Double" => await _plcService.WriteValueAsync(PointAddress, (double)converted),
|
||
"String" => await _plcService.WriteStringAsync(PointAddress, (string)converted, (ushort)StringLength),
|
||
_ => false
|
||
};
|
||
|
||
if (result)
|
||
AddLog(TestBenchLogLevel.INFO, $"写入成功: 地址={PointAddress}, 类型={SelectedDataType}, 值={WriteValue} | Write success");
|
||
else
|
||
AddLog(TestBenchLogLevel.ERROR, $"写入失败: 地址={PointAddress} | Write failed");
|
||
}
|
||
catch (PlcException ex)
|
||
{
|
||
AddLog(TestBenchLogLevel.ERROR, $"写入失败: 地址={PointAddress}, 错误={ex.Message} | Write failed");
|
||
}
|
||
catch (Exception ex)
|
||
{
|
||
AddLog(TestBenchLogLevel.ERROR, $"写入异常: {ex.Message} | Write exception");
|
||
}
|
||
}
|
||
|
||
/// <summary>
|
||
/// 将字符串转换为指定数据类型 | Convert string to specified data type
|
||
/// </summary>
|
||
private object ConvertToType(string value, string dataType)
|
||
{
|
||
try
|
||
{
|
||
return dataType switch
|
||
{
|
||
"Bool" => bool.Parse(value),
|
||
"Byte" => byte.Parse(value),
|
||
"Short" => short.Parse(value),
|
||
"Int" => int.Parse(value),
|
||
"Float" => float.Parse(value),
|
||
"Double" => double.Parse(value),
|
||
"String" => value,
|
||
_ => null
|
||
};
|
||
}
|
||
catch (FormatException)
|
||
{
|
||
AddLog(TestBenchLogLevel.ERROR, $"类型转换失败: 无法将 '{value}' 转换为 {dataType} | Type conversion failed");
|
||
return null;
|
||
}
|
||
catch (OverflowException)
|
||
{
|
||
AddLog(TestBenchLogLevel.ERROR, $"类型转换失败: '{value}' 超出 {dataType} 范围 | Value overflow");
|
||
return null;
|
||
}
|
||
}
|
||
|
||
/// <summary>
|
||
/// 切换 Bool 值 | Toggle bool value
|
||
/// </summary>
|
||
private async Task ToggleBoolAsync()
|
||
{
|
||
if (string.IsNullOrWhiteSpace(PointAddress))
|
||
{
|
||
AddLog(TestBenchLogLevel.ERROR, "地址不能为空 | Address cannot be empty");
|
||
return;
|
||
}
|
||
|
||
try
|
||
{
|
||
var currentValue = await _plcService.ReadValueAsync<bool>(PointAddress);
|
||
var newValue = !currentValue;
|
||
var result = await _plcService.WriteValueAsync(PointAddress, newValue);
|
||
|
||
if (result)
|
||
{
|
||
ReadResult = newValue.ToString();
|
||
AddLog(TestBenchLogLevel.INFO, $"切换成功: 地址={PointAddress}, {currentValue} → {newValue} | Toggle success");
|
||
}
|
||
else
|
||
{
|
||
AddLog(TestBenchLogLevel.ERROR, $"切换失败: 地址={PointAddress} | Toggle failed");
|
||
}
|
||
}
|
||
catch (PlcException ex)
|
||
{
|
||
AddLog(TestBenchLogLevel.ERROR, $"切换失败: 地址={PointAddress}, 错误={ex.Message} | Toggle failed");
|
||
}
|
||
catch (Exception ex)
|
||
{
|
||
AddLog(TestBenchLogLevel.ERROR, $"切换异常: {ex.Message} | Toggle exception");
|
||
}
|
||
}
|
||
|
||
/// <summary>
|
||
/// 批量读取数据 | Batch read data
|
||
/// </summary>
|
||
private async Task BatchReadAsync()
|
||
{
|
||
if (string.IsNullOrWhiteSpace(BatchAddress))
|
||
{
|
||
AddLog(TestBenchLogLevel.ERROR, "起始地址不能为空 | Start address cannot be empty");
|
||
return;
|
||
}
|
||
|
||
if (!int.TryParse(BatchLength, out int length) || length <= 0)
|
||
{
|
||
AddLog(TestBenchLogLevel.ERROR, $"字节长度无效: {BatchLength} | Invalid byte length");
|
||
return;
|
||
}
|
||
|
||
var parsed = ParseBatchAddress(BatchAddress);
|
||
if (parsed == null)
|
||
{
|
||
AddLog(TestBenchLogLevel.ERROR, $"地址格式错误: {BatchAddress},正确格式为 DB{{N}}.{{StartAddress}} | Invalid address format");
|
||
return;
|
||
}
|
||
|
||
try
|
||
{
|
||
var (dbBlock, startAddress) = parsed.Value;
|
||
var data = await _plcClient.ReadBytesAsync(dbBlock, startAddress, length);
|
||
HexViewerContent = HexFormatter.Format(data, startAddress);
|
||
AddLog(TestBenchLogLevel.INFO, $"批量读取成功: {dbBlock}.{startAddress}, 长度={length} 字节 | Batch read success");
|
||
}
|
||
catch (PlcException ex)
|
||
{
|
||
AddLog(TestBenchLogLevel.ERROR, $"批量读取失败: {ex.Message} | Batch read failed");
|
||
}
|
||
catch (Exception ex)
|
||
{
|
||
AddLog(TestBenchLogLevel.ERROR, $"批量读取异常: {ex.Message} | Batch read exception");
|
||
}
|
||
}
|
||
|
||
/// <summary>
|
||
/// 解析批量读取地址 | Parse batch read address
|
||
/// 输入格式:"DB{N}.{StartAddress}"
|
||
/// </summary>
|
||
private (string dbBlock, int startAddress)? ParseBatchAddress(string address)
|
||
{
|
||
if (string.IsNullOrWhiteSpace(address))
|
||
return null;
|
||
|
||
var dotIndex = address.IndexOf('.');
|
||
if (dotIndex <= 0 || dotIndex >= address.Length - 1)
|
||
return null;
|
||
|
||
var dbBlock = address.Substring(0, dotIndex);
|
||
var startPart = address.Substring(dotIndex + 1);
|
||
|
||
if (!dbBlock.StartsWith("DB", StringComparison.OrdinalIgnoreCase))
|
||
return null;
|
||
|
||
if (!int.TryParse(startPart, out int startAddress))
|
||
return null;
|
||
|
||
return (dbBlock, startAddress);
|
||
}
|
||
|
||
/// <summary>
|
||
/// 开始连续读取 | Start continuous reading
|
||
/// </summary>
|
||
private void StartContinuousRead()
|
||
{
|
||
if (string.IsNullOrWhiteSpace(PointAddress))
|
||
{
|
||
AddLog(TestBenchLogLevel.ERROR, "地址不能为空 | Address cannot be empty");
|
||
return;
|
||
}
|
||
|
||
IsContinuousReading = true;
|
||
_continuousReadTimer.Start();
|
||
AddLog(TestBenchLogLevel.INFO, $"开始连续读取: 地址={PointAddress}, 类型={SelectedDataType}, 间隔=250ms | Continuous read started");
|
||
}
|
||
|
||
/// <summary>
|
||
/// 停止连续读取 | Stop continuous reading
|
||
/// </summary>
|
||
private void StopContinuousRead()
|
||
{
|
||
_continuousReadTimer.Stop();
|
||
IsContinuousReading = false;
|
||
AddLog(TestBenchLogLevel.INFO, "连续读取已停止 | Continuous read stopped");
|
||
}
|
||
|
||
/// <summary>
|
||
/// 连续读取定时器回调 | Continuous read timer tick callback
|
||
/// </summary>
|
||
private async Task ContinuousReadTickAsync()
|
||
{
|
||
if (!IsConnected || string.IsNullOrWhiteSpace(PointAddress))
|
||
{
|
||
StopContinuousRead();
|
||
return;
|
||
}
|
||
|
||
try
|
||
{
|
||
object result = SelectedDataType switch
|
||
{
|
||
"Bool" => await _plcService.ReadValueAsync<bool>(PointAddress),
|
||
"Byte" => await _plcService.ReadValueAsync<byte>(PointAddress),
|
||
"Short" => await _plcService.ReadValueAsync<short>(PointAddress),
|
||
"Int" => await _plcService.ReadValueAsync<int>(PointAddress),
|
||
"Float" => await _plcService.ReadValueAsync<float>(PointAddress),
|
||
"Double" => await _plcService.ReadValueAsync<double>(PointAddress),
|
||
"String" => await _plcService.ReadStringAsync(PointAddress, (ushort)StringLength),
|
||
_ => throw new InvalidOperationException($"不支持的数据类型: {SelectedDataType}")
|
||
};
|
||
|
||
ReadResult = result?.ToString() ?? "null";
|
||
}
|
||
catch (Exception ex)
|
||
{
|
||
AddLog(TestBenchLogLevel.ERROR, $"连续读取异常: {ex.Message} | Continuous read exception");
|
||
StopContinuousRead();
|
||
}
|
||
}
|
||
|
||
/// <summary>
|
||
/// 释放资源 | Cleanup resources
|
||
/// 窗口关闭时调用,取消异步操作并释放 PlcService
|
||
/// </summary>
|
||
public void Cleanup()
|
||
{
|
||
try
|
||
{
|
||
StopContinuousRead();
|
||
}
|
||
catch { /* 静默处理 | Silent handling */ }
|
||
|
||
try
|
||
{
|
||
_cts?.Cancel();
|
||
}
|
||
catch { /* 静默处理 | Silent handling */ }
|
||
|
||
try
|
||
{
|
||
_plcService?.Dispose();
|
||
}
|
||
catch { /* 静默处理 | Silent handling */ }
|
||
|
||
try
|
||
{
|
||
_cts?.Dispose();
|
||
}
|
||
catch { /* 静默处理 | Silent handling */ }
|
||
}
|
||
}
|
||
}
|