A:PLC 调试页面增加串口设置

This commit is contained in:
jimmy committed 2026-01-21 14:42:45 +08:00
1 parent 4973979169
commit 479eae64a1
5 files changed
+276 -24

No files matched your search

@@ -5,6 +5,7 @@ using Serilog;
using Serilog.Events; using Serilog.Events;
using Serilog.Formatting.Json; using Serilog.Formatting.Json;
using Sharp.Log.Options; using Sharp.Log.Options;
using System.Text;
namespace Sharp.Log.Internal; namespace Sharp.Log.Internal;
@@ -47,6 +48,7 @@ internal static class SerilogConfigurator
// Sinks // Sinks
if (options.UseConsole) if (options.UseConsole)
{ {
TrySetConsoleEncoding();
if (options.ConsoleJson) if (options.ConsoleJson)
{ {
cfg = cfg.WriteTo.Async(a => a.Console(new JsonFormatter(renderMessage: true))); cfg = cfg.WriteTo.Async(a => a.Console(new JsonFormatter(renderMessage: true)));
@@ -94,6 +96,19 @@ internal static class SerilogConfigurator
return cfg.CreateLogger(); return cfg.CreateLogger();
} }
private static void TrySetConsoleEncoding()
{
try
{
Console.OutputEncoding = Encoding.UTF8;
Console.InputEncoding = Encoding.UTF8;
}
catch
{
// Ignore environments without a console.
}
}
private static LogEventLevel MapLevel(LogLevel level) private static LogEventLevel MapLevel(LogLevel level)
{ {
return level switch return level switch
@@ -9,6 +9,7 @@ using Sharp.Wpf.Navigation;
using Sharp.Wpf.Abstractions; using Sharp.Wpf.Abstractions;
using Plc.Abstractions; using Plc.Abstractions;
using Plc.Models; using Plc.Models;
using Plc.Siemens.Options;
namespace Sharp.Demo.ViewModels.Pages; namespace Sharp.Demo.ViewModels.Pages;
@@ -20,8 +21,13 @@ public class PlcDebugViewModel : ViewModelBase, INavigationAware
private readonly IPlcService _plcService; private readonly IPlcService _plcService;
private readonly ILogger<PlcDebugViewModel> _logger; private readonly ILogger<PlcDebugViewModel> _logger;
private readonly IDispatcher _dispatcher; private readonly IDispatcher _dispatcher;
private readonly SiemensPlcOptions _siemensOptions;
private string _ipAddress = "192.168.1.100"; private PlcBrand _selectedBrand = PlcBrand.Siemens;
private string _siemensIpAddress;
private string _siemensPort;
private string _omronIpAddress = "192.168.1.100";
private string _omronPort = "9600";
private string _status = "未连接"; private string _status = "未连接";
private string _readAddress = "DB1.DBW0"; private string _readAddress = "DB1.DBW0";
private string _readValue = "-"; private string _readValue = "-";
@@ -36,11 +42,16 @@ public class PlcDebugViewModel : ViewModelBase, INavigationAware
public PlcDebugViewModel( public PlcDebugViewModel(
IPlcService plcService, IPlcService plcService,
ILogger<PlcDebugViewModel> logger, ILogger<PlcDebugViewModel> logger,
IDispatcher dispatcher) IDispatcher dispatcher,
SiemensPlcOptions siemensOptions)
{ {
_plcService = plcService ?? throw new ArgumentNullException(nameof(plcService)); _plcService = plcService ?? throw new ArgumentNullException(nameof(plcService));
_logger = logger ?? throw new ArgumentNullException(nameof(logger)); _logger = logger ?? throw new ArgumentNullException(nameof(logger));
_dispatcher = dispatcher ?? throw new ArgumentNullException(nameof(dispatcher)); _dispatcher = dispatcher ?? throw new ArgumentNullException(nameof(dispatcher));
_siemensOptions = siemensOptions ?? throw new ArgumentNullException(nameof(siemensOptions));
_siemensIpAddress = _siemensOptions.IpAddress;
_siemensPort = _siemensOptions.Port.ToString();
// 初始化命令 // 初始化命令
ConnectCommand = new AsyncCommand(ConnectAsync); ConnectCommand = new AsyncCommand(ConnectAsync);
@@ -63,10 +74,62 @@ public class PlcDebugViewModel : ViewModelBase, INavigationAware
#region 属性 #region 属性
public PlcBrand SelectedBrand
{
get => _selectedBrand;
set
{
if (SetProperty(ref _selectedBrand, value))
{
OnPropertyChanged(nameof(IpAddress));
OnPropertyChanged(nameof(Port));
OnPropertyChanged(nameof(IsBrandSupported));
OnPropertyChanged(nameof(SelectedBrandName));
OnPropertyChanged(nameof(SelectedBrandDescription));
}
}
}
public string SelectedBrandName => SelectedBrand switch
{
PlcBrand.Siemens => "西门子",
PlcBrand.Omron => "欧姆龙",
PlcBrand.Mitsubishi => "三菱",
PlcBrand.ModbusTcp => "Modbus TCP",
_ => "未知"
};
public string SelectedBrandDescription => SelectedBrand switch
{
PlcBrand.Siemens => "Sharp.Plc.Siemens - 西门子 PLC 通信驱动 (S7-1200/1500/300/400)",
PlcBrand.Omron => "Sharp.Plc.Omron - 欧姆龙 PLC 通信驱动(规划中)",
_ => "Sharp.Plc - PLC 通信驱动"
};
public bool IsBrandSupported => SelectedBrand == PlcBrand.Siemens;
public string IpAddress public string IpAddress
{ {
get => _ipAddress; get => SelectedBrand == PlcBrand.Siemens ? _siemensIpAddress : _omronIpAddress;
set => SetProperty(ref _ipAddress, value); set
{
if (SelectedBrand == PlcBrand.Siemens)
SetProperty(ref _siemensIpAddress, value, nameof(IpAddress));
else
SetProperty(ref _omronIpAddress, value, nameof(IpAddress));
}
}
public string Port
{
get => SelectedBrand == PlcBrand.Siemens ? _siemensPort : _omronPort;
set
{
if (SelectedBrand == PlcBrand.Siemens)
SetProperty(ref _siemensPort, value, nameof(Port));
else
SetProperty(ref _omronPort, value, nameof(Port));
}
} }
public string Status public string Status
@@ -198,8 +261,21 @@ public class PlcDebugViewModel : ViewModelBase, INavigationAware
{ {
try try
{ {
if (!IsBrandSupported)
{
Status = "当前品牌暂未支持";
AppendLog($"⚠️ {SelectedBrandName} 驱动尚未实现");
return;
}
if (!TryApplySiemensOptions())
{
Status = "配置错误";
return;
}
Status = "正在连接..."; Status = "正在连接...";
AppendLog($"正在连接 PLC: {IpAddress}"); AppendLog($"正在连接 PLC ({SelectedBrandName}): {IpAddress}:{Port}");
await _plcService.StartAsync(); await _plcService.StartAsync();
@@ -318,6 +394,25 @@ public class PlcDebugViewModel : ViewModelBase, INavigationAware
} }
} }
private bool TryApplySiemensOptions()
{
if (string.IsNullOrWhiteSpace(IpAddress))
{
AppendLog("❌ IP 地址不能为空");
return false;
}
if (!int.TryParse(Port, out var port) || port < 1 || port > 65535)
{
AppendLog("❌ 端口格式不正确(1-65535)");
return false;
}
_siemensOptions.IpAddress = IpAddress;
_siemensOptions.Port = port;
return true;
}
private void ClearLog() private void ClearLog()
{ {
LogText = string.Empty; LogText = string.Empty;
@@ -11,6 +11,7 @@ using Sharp.Wpf.Commands;
using Sharp.Wpf.Abstractions; using Sharp.Wpf.Abstractions;
using Plc.Abstractions; using Plc.Abstractions;
using Plc.Models; using Plc.Models;
using Plc.Siemens.Options;
namespace Sharp.Demo.ViewModels.Windows; namespace Sharp.Demo.ViewModels.Windows;
@@ -22,8 +23,13 @@ public class PlcDebugViewModel : ViewModelBase
private readonly IPlcService _plcService; private readonly IPlcService _plcService;
private readonly ILogger<PlcDebugViewModel> _logger; private readonly ILogger<PlcDebugViewModel> _logger;
private readonly IDispatcher _dispatcher; private readonly IDispatcher _dispatcher;
private readonly SiemensPlcOptions _siemensOptions;
private string _ipAddress = "192.168.1.100"; private PlcBrand _selectedBrand = PlcBrand.Siemens;
private string _siemensIpAddress;
private string _siemensPort;
private string _omronIpAddress = "192.168.1.100";
private string _omronPort = "9600";
private string _status = "未连接"; private string _status = "未连接";
private string _readAddress = "DB1.DBW0"; private string _readAddress = "DB1.DBW0";
private string _readValue = "-"; private string _readValue = "-";
@@ -38,11 +44,16 @@ public class PlcDebugViewModel : ViewModelBase
public PlcDebugViewModel( public PlcDebugViewModel(
IPlcService plcService, IPlcService plcService,
ILogger<PlcDebugViewModel> logger, ILogger<PlcDebugViewModel> logger,
IDispatcher dispatcher) IDispatcher dispatcher,
SiemensPlcOptions siemensOptions)
{ {
_plcService = plcService ?? throw new ArgumentNullException(nameof(plcService)); _plcService = plcService ?? throw new ArgumentNullException(nameof(plcService));
_logger = logger ?? throw new ArgumentNullException(nameof(logger)); _logger = logger ?? throw new ArgumentNullException(nameof(logger));
_dispatcher = dispatcher ?? throw new ArgumentNullException(nameof(dispatcher)); _dispatcher = dispatcher ?? throw new ArgumentNullException(nameof(dispatcher));
_siemensOptions = siemensOptions ?? throw new ArgumentNullException(nameof(siemensOptions));
_siemensIpAddress = _siemensOptions.IpAddress;
_siemensPort = _siemensOptions.Port.ToString();
// 初始化命令 // 初始化命令
ConnectCommand = new AsyncCommand(ConnectAsync); ConnectCommand = new AsyncCommand(ConnectAsync);
@@ -66,10 +77,54 @@ public class PlcDebugViewModel : ViewModelBase
#region 属性 #region 属性
public PlcBrand SelectedBrand
{
get => _selectedBrand;
set
{
if (SetProperty(ref _selectedBrand, value))
{
OnPropertyChanged(nameof(IpAddress));
OnPropertyChanged(nameof(Port));
OnPropertyChanged(nameof(IsBrandSupported));
OnPropertyChanged(nameof(SelectedBrandName));
}
}
}
public string SelectedBrandName => SelectedBrand switch
{
PlcBrand.Siemens => "西门子",
PlcBrand.Omron => "欧姆龙",
PlcBrand.Mitsubishi => "三菱",
PlcBrand.ModbusTcp => "Modbus TCP",
_ => "未知"
};
public bool IsBrandSupported => SelectedBrand == PlcBrand.Siemens;
public string IpAddress public string IpAddress
{ {
get => _ipAddress; get => SelectedBrand == PlcBrand.Siemens ? _siemensIpAddress : _omronIpAddress;
set => SetProperty(ref _ipAddress, value); set
{
if (SelectedBrand == PlcBrand.Siemens)
SetProperty(ref _siemensIpAddress, value, nameof(IpAddress));
else
SetProperty(ref _omronIpAddress, value, nameof(IpAddress));
}
}
public string Port
{
get => SelectedBrand == PlcBrand.Siemens ? _siemensPort : _omronPort;
set
{
if (SelectedBrand == PlcBrand.Siemens)
SetProperty(ref _siemensPort, value, nameof(Port));
else
SetProperty(ref _omronPort, value, nameof(Port));
}
} }
public string Status public string Status
@@ -187,8 +242,21 @@ public class PlcDebugViewModel : ViewModelBase
{ {
try try
{ {
if (!IsBrandSupported)
{
Status = "当前品牌暂未支持";
AppendLog($"⚠️ {SelectedBrandName} 驱动尚未实现");
return;
}
if (!TryApplySiemensOptions())
{
Status = "配置错误";
return;
}
Status = "正在连接..."; Status = "正在连接...";
AppendLog($"正在连接 PLC: {IpAddress}"); AppendLog($"正在连接 PLC ({SelectedBrandName}): {IpAddress}:{Port}");
await _plcService.StartAsync(); await _plcService.StartAsync();
@@ -307,6 +375,25 @@ public class PlcDebugViewModel : ViewModelBase
} }
} }
private bool TryApplySiemensOptions()
{
if (string.IsNullOrWhiteSpace(IpAddress))
{
AppendLog("❌ IP 地址不能为空");
return false;
}
if (!int.TryParse(Port, out var port) || port < 1 || port > 65535)
{
AppendLog("❌ 端口格式不正确(1-65535)");
return false;
}
_siemensOptions.IpAddress = IpAddress;
_siemensOptions.Port = port;
return true;
}
private void AppendLog(string message) private void AppendLog(string message)
{ {
var timestamp = DateTime.Now.ToString("HH:mm:ss"); var timestamp = DateTime.Now.ToString("HH:mm:ss");
@@ -4,6 +4,7 @@
xmlns:mc="http://schemas.openxmlformats.org/markup-compatibility/2006" xmlns:mc="http://schemas.openxmlformats.org/markup-compatibility/2006"
xmlns:d="http://schemas.microsoft.com/expression/blend/2008" xmlns:d="http://schemas.microsoft.com/expression/blend/2008"
xmlns:hc="https://handyorg.github.io/handycontrol" xmlns:hc="https://handyorg.github.io/handycontrol"
xmlns:plc="clr-namespace:Plc.Models;assembly=Plc"
mc:Ignorable="d" mc:Ignorable="d"
d:DesignHeight="1000" d:DesignHeight="1000"
d:DesignWidth="1200"> d:DesignWidth="1200">
@@ -18,7 +19,7 @@
Margin="0,10,0,30" Margin="0,10,0,30"
FontSize="14" FontSize="14"
Foreground="{DynamicResource SecondaryTextBrush}" Foreground="{DynamicResource SecondaryTextBrush}"
Text="Sharp.Plc.Siemens - 西门子 PLC 通信驱动 (S7-1200/1500/300/400)" /> Text="{Binding SelectedBrandDescription}" />
<!-- 连接控制 --> <!-- 连接控制 -->
<Border <Border
@@ -31,11 +32,40 @@
<StackPanel> <StackPanel>
<TextBlock Text="PLC 连接" FontSize="16" FontWeight="Bold" Margin="0,0,0,10" /> <TextBlock Text="PLC 连接" FontSize="16" FontWeight="Bold" Margin="0,0,0,10" />
<StackPanel Orientation="Horizontal" Margin="0,0,0,10"> <TabControl SelectedValue="{Binding SelectedBrand}" SelectedValuePath="Tag" Margin="0,0,0,10">
<TextBlock Text="IP 地址:" VerticalAlignment="Center" Margin="0,0,10,0" Width="80" /> <TabItem Header="西门子" Tag="{x:Static plc:PlcBrand.Siemens}">
<TextBox Text="{Binding IpAddress}" Width="200" Height="32" Padding="8,0" <StackPanel>
BorderBrush="{DynamicResource BorderBrush}" BorderThickness="1" /> <StackPanel Orientation="Horizontal" Margin="0,0,0,10">
</StackPanel> <TextBlock Text="IP 地址:" VerticalAlignment="Center" Margin="0,0,10,0"
Width="80" />
<TextBox Text="{Binding IpAddress}" Width="200" Height="32" Padding="8,0"
BorderBrush="{DynamicResource BorderBrush}" BorderThickness="1" />
<TextBlock Text="端口:" VerticalAlignment="Center" Margin="15,0,10,0" Width="50" />
<TextBox Text="{Binding Port}" Width="100" Height="32" Padding="8,0"
BorderBrush="{DynamicResource BorderBrush}" BorderThickness="1" />
</StackPanel>
<TextBlock Text="S7-1200/1500/300/400,默认端口 102"
Foreground="{DynamicResource SecondaryTextBrush}"
FontSize="12" />
</StackPanel>
</TabItem>
<TabItem Header="欧姆龙" Tag="{x:Static plc:PlcBrand.Omron}">
<StackPanel>
<StackPanel Orientation="Horizontal" Margin="0,0,0,10">
<TextBlock Text="IP 地址:" VerticalAlignment="Center" Margin="0,0,10,0"
Width="80" />
<TextBox Text="{Binding IpAddress}" Width="200" Height="32" Padding="8,0"
BorderBrush="{DynamicResource BorderBrush}" BorderThickness="1" />
<TextBlock Text="端口:" VerticalAlignment="Center" Margin="15,0,10,0" Width="50" />
<TextBox Text="{Binding Port}" Width="100" Height="32" Padding="8,0"
BorderBrush="{DynamicResource BorderBrush}" BorderThickness="1" />
</StackPanel>
<TextBlock Text="欧姆龙驱动尚未实现,后续可按品牌扩展配置项"
Foreground="{DynamicResource SecondaryTextBrush}"
FontSize="12" />
</StackPanel>
</TabItem>
</TabControl>
<StackPanel Orientation="Horizontal" Margin="0,0,0,10"> <StackPanel Orientation="Horizontal" Margin="0,0,0,10">
<TextBlock Text="状态:" VerticalAlignment="Center" Margin="0,0,10,0" Width="80" /> <TextBlock Text="状态:" VerticalAlignment="Center" Margin="0,0,10,0" Width="80" />
@@ -45,10 +75,12 @@
<StackPanel Orientation="Horizontal"> <StackPanel Orientation="Horizontal">
<Button Content="连接" Width="100" Height="35" Margin="0,0,10,0" <Button Content="连接" Width="100" Height="35" Margin="0,0,10,0"
Command="{Binding ConnectCommand}" Command="{Binding ConnectCommand}"
Style="{StaticResource ButtonSuccess}" /> Style="{StaticResource ButtonSuccess}"
IsEnabled="{Binding IsBrandSupported}" />
<Button Content="断开" Width="100" Height="35" <Button Content="断开" Width="100" Height="35"
Command="{Binding DisconnectCommand}" Command="{Binding DisconnectCommand}"
Style="{StaticResource ButtonDanger}" /> Style="{StaticResource ButtonDanger}"
IsEnabled="{Binding IsBrandSupported}" />
</StackPanel> </StackPanel>
</StackPanel> </StackPanel>
</Border> </Border>
@@ -4,6 +4,7 @@
xmlns:d="http://schemas.microsoft.com/expression/blend/2008" xmlns:d="http://schemas.microsoft.com/expression/blend/2008"
xmlns:mc="http://schemas.openxmlformats.org/markup-compatibility/2006" xmlns:mc="http://schemas.openxmlformats.org/markup-compatibility/2006"
xmlns:hc="https://handyorg.github.io/handycontrol" xmlns:hc="https://handyorg.github.io/handycontrol"
xmlns:plc="clr-namespace:Plc.Models;assembly=Plc"
mc:Ignorable="d" mc:Ignorable="d"
Title="PLC 调试工具" Title="PLC 调试工具"
Height="800" Height="800"
@@ -23,16 +24,38 @@
<Grid.RowDefinitions> <Grid.RowDefinitions>
<RowDefinition Height="Auto" /> <RowDefinition Height="Auto" />
<RowDefinition Height="Auto" /> <RowDefinition Height="Auto" />
<RowDefinition Height="Auto" />
</Grid.RowDefinitions> </Grid.RowDefinitions>
<StackPanel Orientation="Horizontal"> <TabControl Grid.Row="0" SelectedValue="{Binding SelectedBrand}" SelectedValuePath="Tag"
<TextBlock Text="IP 地址:" VerticalAlignment="Center" Margin="0,0,10,0" /> Margin="0,0,0,10">
<TextBox Text="{Binding IpAddress}" Width="150" Height="28" Margin="0,0,10,0" /> <TabItem Header="西门子" Tag="{x:Static plc:PlcBrand.Siemens}">
<StackPanel Orientation="Horizontal">
<TextBlock Text="IP 地址:" VerticalAlignment="Center" Margin="0,0,10,0" />
<TextBox Text="{Binding IpAddress}" Width="150" Height="28" Margin="0,0,10,0" />
<TextBlock Text="端口:" VerticalAlignment="Center" Margin="10,0,10,0" />
<TextBox Text="{Binding Port}" Width="80" Height="28" Margin="0,0,10,0" />
</StackPanel>
</TabItem>
<TabItem Header="欧姆龙" Tag="{x:Static plc:PlcBrand.Omron}">
<StackPanel Orientation="Horizontal">
<TextBlock Text="IP 地址:" VerticalAlignment="Center" Margin="0,0,10,0" />
<TextBox Text="{Binding IpAddress}" Width="150" Height="28" Margin="0,0,10,0" />
<TextBlock Text="端口:" VerticalAlignment="Center" Margin="10,0,10,0" />
<TextBox Text="{Binding Port}" Width="80" Height="28" Margin="0,0,10,0" />
<TextBlock Text="驱动未实现" VerticalAlignment="Center"
Foreground="{DynamicResource SecondaryTextBrush}" />
</StackPanel>
</TabItem>
</TabControl>
<StackPanel Grid.Row="1" Orientation="Horizontal">
<Button Content="🔗 连接" Width="90" Height="32" Margin="0,0,10,0" <Button Content="🔗 连接" Width="90" Height="32" Margin="0,0,10,0"
Command="{Binding ConnectCommand}" Style="{StaticResource ButtonSuccess}" /> Command="{Binding ConnectCommand}" Style="{StaticResource ButtonSuccess}"
IsEnabled="{Binding IsBrandSupported}" />
<Button Content="🔌 断开" Width="90" Height="32" Margin="0,0,10,0" <Button Content="🔌 断开" Width="90" Height="32" Margin="0,0,10,0"
Command="{Binding DisconnectCommand}" Style="{StaticResource ButtonDanger}" /> Command="{Binding DisconnectCommand}" Style="{StaticResource ButtonDanger}"
IsEnabled="{Binding IsBrandSupported}" />
<Separator Margin="10,0" /> <Separator Margin="10,0" />
@@ -42,7 +65,7 @@
</StackPanel> </StackPanel>
<!-- 最新日志 --> <!-- 最新日志 -->
<TextBlock Grid.Row="1" Text="{Binding LastLogMessage}" <TextBlock Grid.Row="2" Text="{Binding LastLogMessage}"
Foreground="{DynamicResource SecondaryTextBrush}" Foreground="{DynamicResource SecondaryTextBrush}"
FontSize="11" Margin="0,5,0,0" TextTrimming="CharacterEllipsis" /> FontSize="11" Margin="0,5,0,0" TextTrimming="CharacterEllipsis" />
</Grid> </Grid>