using CommunityToolkit.Mvvm.ComponentModel;
|
using HMI.Base;
|
using HMI.Core.Util;
|
using HMI.UI;
|
using HMI.UI.Util;
|
using HMI.Util;
|
using log4net;
|
using Microsoft.Win32;
|
using Plugin.DODriverControl.Api;
|
using Plugin.DODriverControl.Entity;
|
using System.Collections.ObjectModel;
|
using System.IO;
|
using System.Timers;
|
using System.Windows;
|
using System.Windows.Controls;
|
using System.Windows.Media;
|
|
namespace Plugin.DODriverControl
|
{
|
/// <summary>
|
/// SingleControl.xaml 的交互逻辑
|
/// </summary>
|
[ObservableObject]
|
public partial class SingleControl : SimpleMaskControl
|
{
|
public SingleControl(string name, string baseFolder)
|
{
|
this.name = name;
|
this.baseFolder = Path.Combine(baseFolder, name);
|
if (!Directory.Exists(this.baseFolder)) Directory.CreateDirectory(this.baseFolder);
|
this.dataFilePath = Path.Combine(this.baseFolder, "data.xml");
|
this.configFilePath = Path.Combine(this.baseFolder, "config.xml");
|
this.DataContext = this;
|
|
refreshTimer = new System.Timers.Timer();
|
refreshTimer.Interval = 200;
|
refreshTimer.AutoReset = false;
|
refreshTimer.Elapsed += RefreshTimer_Elapsed;
|
|
InitializeComponent();
|
|
commonColor = Application.Current.FindResource("VerBar") as Brush;
|
successColor = Application.Current.FindResource("GreenVerBar") as Brush;
|
errorColor = Application.Current.FindResource("DangerVerBar") as Brush;
|
}
|
|
[ObservableProperty]
|
private bool connectStatus = false;
|
[ObservableProperty]
|
private ObservableCollection<DIOData> datasource = new ObservableCollection<DIOData>();
|
|
public ILog log { get; set; }
|
public string name { get; private set; }
|
public string baseFolder { get; private set; }
|
|
|
private IDIOController controller;
|
private string dataFilePath;
|
private string configFilePath;
|
private System.Timers.Timer refreshTimer;
|
|
private ConfigData config;
|
|
private List<FrameworkElement> doControls = new List<FrameworkElement>();
|
|
private Brush successColor;
|
private Brush errorColor;
|
private Brush commonColor;
|
|
private CancellationTokenSource cts = new CancellationTokenSource();
|
|
public void Active()
|
{
|
if (refreshTimer != null) refreshTimer.Enabled = true;
|
}
|
|
public void DeActive()
|
{
|
if (refreshTimer != null) refreshTimer.Enabled = false;
|
}
|
|
public void Close()
|
{
|
cts.Cancel();
|
ConnectStatus = false;
|
if (refreshTimer != null)
|
{
|
refreshTimer.Enabled = false;
|
refreshTimer.Elapsed -= RefreshTimer_Elapsed;
|
refreshTimer = null;
|
}
|
controller?.Dispose();
|
controller = null;
|
}
|
|
private void Button_Click(object sender, RoutedEventArgs e)
|
{
|
var btn = sender as Button;
|
var data = btn?.Tag as DIOData;
|
switch (btn.Name)
|
{
|
case "addBtn":
|
DoAddData();
|
break;
|
case "editBtn":
|
DoEditData(data);
|
break;
|
case "exportBtn":
|
DoExportExcel();
|
break;
|
case "importBtn":
|
DoImportExcel();
|
break;
|
case "reConnectBtn":
|
DoReconnect();
|
break;
|
case "configBtn":
|
DoOpenConfig();
|
break;
|
case "rfhBtn":
|
Refresh();
|
break;
|
case "delBtn":
|
DoDeleteData(data);
|
break;
|
default:
|
break;
|
}
|
}
|
|
private void DoAddData()
|
{
|
var win = new DataModal
|
{
|
Owner = Window.GetWindow(this),
|
Title = "新增"
|
};
|
if (win.Open(new DIOData(), Datasource, dataFilePath) == true)
|
{
|
ToastHelper.Success("添加成功");
|
Refresh();
|
}
|
}
|
|
private void DoEditData(DIOData data)
|
{
|
var win = new DataModal
|
{
|
Owner = Window.GetWindow(this),
|
Title = "编辑"
|
};
|
if (win.Open(data, Datasource, dataFilePath) == true)
|
{
|
ToastHelper.Success("编辑成功");
|
Refresh();
|
}
|
}
|
|
private void DoExportExcel()
|
{
|
try
|
{
|
SaveFileDialog sfd = new SaveFileDialog();
|
sfd.Filter = "Excel|*.xls";
|
if (sfd.ShowDialog() != true)
|
{
|
return;
|
}
|
string fileName = sfd.FileName;
|
ExcelHelper.ExportExcel(fileName, Datasource);
|
ToastHelper.Success("导出成功");
|
}
|
catch (Exception ex)
|
{
|
log.Error("Export Error.", ex);
|
ToastHelper.Error($"导出失败: {ex.Message}");
|
}
|
}
|
|
private void DoImportExcel()
|
{
|
try
|
{
|
OpenFileDialog ofd = new OpenFileDialog();
|
ofd.Filter = "Excel|*.xls";
|
if (ofd.ShowDialog() != true)
|
{
|
return;
|
}
|
string fileName = ofd.FileName;
|
List<DIOData> datas = ExcelHelper.ReadFromExcel<DIOData>(fileName);
|
//foreach (var data in datas)
|
//{
|
// Datasource.Add(data);
|
//}
|
Datasource = new ObservableCollection<DIOData>(datas);
|
SaveDatasourceToXml();
|
ToastHelper.Success("导入成功");
|
RefreshDOControl();
|
}
|
catch (Exception ex)
|
{
|
log.Error("Export Error.", ex);
|
ToastHelper.Error($"导入失败: {ex.Message}");
|
}
|
}
|
|
private void DoReconnect()
|
{
|
ConnectStatus = false;
|
ShowMask(() =>
|
{
|
refreshTimer.Enabled = false;
|
controller?.Close();
|
controller = CreateController();
|
var res = controller.Start(out string msg);
|
if (!res)
|
{
|
throw new Exception($"{msg}");
|
}
|
refreshTimer.Enabled = true;
|
Dispatcher.BeginInvoke(new Action(() =>
|
{
|
ConnectStatus = true;
|
}));
|
}, (ex) =>
|
{
|
controller = null;
|
log.Error("Reconnect Error.", ex);
|
Dispatcher.BeginInvoke(new Action(() =>
|
{
|
MsgBoxHelper.Error(this, $"连接失败:{ex.Message}");
|
}));
|
}, "请稍候", "正在重连...");
|
}
|
|
private void DoOpenConfig()
|
{
|
ConfigModal modal = new ConfigModal
|
{
|
Title = "设置",
|
Owner = Window.GetWindow(this)
|
};
|
if (modal.Open(config) == true)
|
{
|
config = modal.config;
|
SaveConfigToXml();
|
DoReconnect();
|
}
|
}
|
|
private void DoDeleteData(DIOData data)
|
{
|
if (MsgBoxHelper.WarnMessage(Window.GetWindow(this), "确定要删除选中的数据吗?") == MessageBoxResult.OK)
|
{
|
int index = Datasource.IndexOf(data);
|
if (index < 0) return;
|
Datasource.RemoveAt(index);
|
SaveDatasourceToXml();
|
ToastHelper.Success("删除成功");
|
RefreshDOControl();
|
}
|
}
|
|
private void SaveConfigToXml()
|
{
|
XmlHelper.SerializeToXml(configFilePath, config);
|
}
|
|
private void SaveDatasourceToXml()
|
{
|
XmlHelper.SerializeToXml(dataFilePath, Datasource);
|
}
|
|
private void RefreshDOControl()
|
{
|
try
|
{
|
refreshTimer.Enabled = false;
|
diWrap.Children.Clear();
|
doWrap.Children.Clear();
|
doControls.Clear();
|
foreach (var data in Datasource)
|
{
|
var fg = new GroupBox
|
{
|
//Header = $"{data.Name} 地址:{data.Address}",
|
Header = string.IsNullOrEmpty(data.Name)?"预留":data.Name,// $"{data.Name}",
|
Content = CreateButton(data),
|
Height = 80,
|
Width = 180
|
};
|
doControls.Add((FrameworkElement)fg.Content);
|
if (data.Dio == DIOType.DI)
|
{
|
diWrap.Children.Add(fg);
|
}
|
else
|
{
|
doWrap.Children.Add(fg);
|
}
|
}
|
}
|
finally
|
{
|
refreshTimer.Enabled = true;
|
}
|
}
|
|
private FrameworkElement CreateButton(DIOData data)
|
{
|
if (data.Dio == DIOType.DI)
|
{
|
return new LightBall
|
{
|
Size = 40,
|
DataContext = data
|
};
|
}
|
Style btnStyle = this.FindResource("PrimaryButton") as Style;
|
var btn = new Button
|
{
|
DataContext = data,
|
Style = btnStyle,
|
MinWidth = 120,
|
MinHeight = 40
|
};
|
btn.Click += ControlButton_Click;
|
return btn;
|
}
|
|
private void ControlButton_Click(object sender, RoutedEventArgs args)
|
{
|
if (!ConnectStatus)
|
{
|
ToastHelper.Error("请先连接控制器");
|
return;
|
}
|
Button btn = sender as Button;
|
DIOData data = btn.DataContext as DIOData;
|
try
|
{
|
refreshTimer.Enabled = false;
|
var value = btn.Content?.ToString();
|
string val = value == data.PressDownText ? data.PressUpValue : data.PressDownValue;
|
var res = controller.WriteBool(data.Address, bool.Parse(val), out string msg);
|
if (!res) throw new Exception(msg);
|
}
|
catch (Exception ex)
|
{
|
log.Error("ControlButton_Click", ex);
|
ToastHelper.Error($"{data.Name}: {ex.Message}");
|
}
|
finally
|
{
|
refreshTimer.Enabled = true;
|
}
|
}
|
|
private void Refresh()
|
{
|
try
|
{
|
if (!File.Exists(dataFilePath))
|
{
|
Datasource = new ObservableCollection<DIOData>();
|
}
|
else
|
{
|
Datasource = XmlHelper.DeserializeFromXml<ObservableCollection<DIOData>>(dataFilePath);
|
}
|
}
|
catch (Exception ex)
|
{
|
log.Error("Load Data Error.", ex);
|
Datasource = new ObservableCollection<DIOData>();
|
}
|
RefreshDOControl();
|
}
|
|
private void UserControl_Loaded(object sender, RoutedEventArgs e)
|
{
|
ShowMask(() =>
|
{
|
try
|
{
|
if (!File.Exists(configFilePath))
|
{
|
config = new ConfigData
|
{
|
Type = ConfigType.ModBusTcp,
|
IP = "127.0.0.1",
|
Port = "502"
|
};
|
}
|
else
|
{
|
config = XmlHelper.DeserializeFromXml<ConfigData>(configFilePath);
|
}
|
}
|
catch (Exception ex)
|
{
|
log.Error("Load Config Error.", ex);
|
config = new ConfigData
|
{
|
Type = ConfigType.ModBusTcp,
|
IP = "127.0.0.1",
|
Port = "502"
|
};
|
}
|
var datasource = new ObservableCollection<DIOData>();
|
var connectStatus = false;
|
try
|
{
|
if (File.Exists(dataFilePath))
|
{
|
datasource = XmlHelper.DeserializeFromXml<ObservableCollection<DIOData>>(dataFilePath);
|
}
|
}
|
catch (Exception ex)
|
{
|
log.Error("Load Data Error.", ex);
|
}
|
|
try
|
{
|
controller = CreateController();
|
var res = controller.Start(out string msg);
|
if (!res)
|
{
|
throw new Exception($"控制器连接失败: {msg}");
|
}
|
connectStatus = true;
|
}
|
catch (Exception ex)
|
{
|
controller = null;
|
log.Error("Modbus Connect Error.", ex);
|
connectStatus = false;
|
}
|
|
Dispatcher.BeginInvoke(new Action(() =>
|
{
|
ConnectStatus = connectStatus;
|
Datasource = datasource;
|
RefreshDOControl();
|
refreshTimer.Enabled = true;
|
this.Loaded -= UserControl_Loaded;
|
}));
|
}, null, "请稍后", "加载中...");
|
}
|
|
public IDIOController CreateController()
|
{
|
switch (config.Type)
|
{
|
case ConfigType.ModBusTcp:
|
return new ModBusTcpController
|
{
|
Ip = config.IP,
|
Port = int.Parse(config.Port),
|
};
|
case ConfigType.PCI_7432:
|
return new PCIController
|
{
|
Name = "PCI_7432",
|
};
|
default:
|
return null;
|
}
|
}
|
|
private void RefreshTimer_Elapsed(object sender, ElapsedEventArgs e)
|
{
|
bool isConnected = ConnectStatus;
|
if (!isConnected) return;
|
try
|
{
|
refreshTimer.Enabled = false;
|
if (cts.IsCancellationRequested)
|
{
|
isConnected = false;
|
return;
|
}
|
|
for (int i = doControls.Count - 1; i >= 0; i--)
|
{
|
var ui = doControls[i];
|
ui.Dispatcher.Invoke(() =>
|
{
|
DIOData data = ui.DataContext as DIOData;
|
if (data == null) return;
|
isConnected = GetValueByModbusTcp(data, out string value);
|
if (!isConnected) return;
|
switch (data.ControlType)
|
{
|
case ControlType.BOOL:
|
if (data.Dio == DIOType.DI)
|
{
|
((LightBall)ui).Value = value == data.PressDownValue;
|
((LightBall)ui).Content = value == data.PressDownValue ? data.PressDownText : data.PressUpText;
|
}
|
else
|
{
|
var color = value == data.PressDownValue ? successColor : errorColor;
|
((Button)ui).Background = color;
|
((Button)ui).Content = value == data.PressDownValue ? data.PressDownText : data.PressUpText;
|
}
|
break;
|
case ControlType.INT:
|
case ControlType.WORD:
|
case ControlType.REAL:
|
case ControlType.LREAL:
|
case ControlType.STRING:
|
case ControlType.WSTRING:
|
((Button)ui).Content = value;
|
break;
|
default:
|
break;
|
}
|
});
|
}
|
}
|
catch (TaskCanceledException ex)
|
{
|
isConnected = false;
|
}
|
finally
|
{
|
if (isConnected && ConnectStatus) refreshTimer.Enabled = true;
|
else ConnectStatus = false;
|
}
|
}
|
|
private bool GetValueByModbusTcp(DIOData data, out string value)
|
{
|
if (controller == null)
|
{
|
value = null;
|
return false;
|
}
|
switch (data.ControlType)
|
{
|
case ControlType.BOOL:
|
bool val = false;
|
bool res = false;
|
if (data.Dio == DIOType.DI)
|
{
|
res = controller.ReadDIBool(data.Address, out val, out string msg);
|
}
|
else
|
{
|
res = controller.ReadDOBool(data.Address, out val, out string msg);
|
}
|
if (res == true)
|
{
|
value = val.ToString();
|
return true;
|
}
|
value = null;
|
return false;
|
default:
|
value = null;
|
return true;
|
}
|
}
|
}
|
}
|