由C#编程语言+WPF框架实现。由于需要实现应用程序的个体使用,也需要满足现有的维宏系统实现插件部分集成到总系统中,所以功能实现部分都在WaterAnalysisControl类库。
功能为上位机为RS232接口或USB接口,下位机为TTL电平协议,进行通讯

代码部分都以分块注释,有利于新手学习并理解
前端xaml部分
<Window x:Class="WaterAnalysis.WaterAnalysisDialog"
xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation"
xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml"
xmlns:d="http://schemas.microsoft.com/expression/blend/2008"
xmlns:mc="http://schemas.openxmlformats.org/markup-compatibility/2006"
xmlns:local="clr-namespace:WaterAnalysis" d:DataContext="{d:DesignInstance Type=local:WaterAnalysisControl}"
mc:Ignorable="d"
Height="200" Width="250" FontSize="18"
Closing="Window_Closing">
<Grid>
<Grid.RowDefinitions>
<RowDefinition Height="50*"/>
<RowDefinition Height="50*"/>
<RowDefinition Height="30*"/>
</Grid.RowDefinitions>
<Grid.ColumnDefinitions>
<ColumnDefinition Width="125*"/>
<ColumnDefinition Width="125*"/>
</Grid.ColumnDefinitions>
<Button x:Name="btConnect" Content="启动连接" Margin="5" Click="btConnect_Click" Grid.Row="0" Grid.Column="0"/>
<Button x:Name="btGetData" Content="是否连接" Margin="5" Click="btGetData_Click" Grid.Row="0" Grid.Column="1"/>
<TextBlock Text="TDS值:" Grid.Row="1" Grid.Column="0" />
<TextBlock x:Name="tbTDS" Grid.Row="1" Grid.Column="1" Text="{Binding TDS, Mode=TwoWay}"/>
<TextBlock Text="串口连接状态:" Grid.Row="2" Grid.Column="0"/>
<TextBlock x:Name="tbConnectStatus" Text="未连接" Grid.Row="2" Grid.Column="1"/>
</Grid>
</Window>
后端
using Microsoft.SqlServer.Server;
using Modbus.Device;
using System;
using System.Collections.Generic;
using System.IO.Ports;
using System.Linq;
using System.Runtime.Remoting.Messaging;
using System.Text;
using System.Threading.Tasks;
using System.Timers;
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;
using System.Windows.Threading;
namespace WaterAnalysis
{
/// <summary>
/// WaterAnalysisDialog.xaml 的交互逻辑
/// </summary>
public partial class WaterAnalysisDialog : Window
{
public static WaterAnalysisDialog mainWindow;
WaterAnalysisControl waterAnalysisControl=new WaterAnalysisControl();
public WaterAnalysisDialog()
{
InitializeComponent();
mainWindow = this;
this.DataContext = waterAnalysisControl;
}
private void Window_Closing(object sender, System.ComponentModel.CancelEventArgs e)
{
waterAnalysisControl.ClosePort();
}
private void btConnect_Click(object sender, RoutedEventArgs e)
{
waterAnalysisControl.Connect();
}
private void btGetData_Click(object sender, RoutedEventArgs e)
{
MessageBox.Show(waterAnalysisControl.IsOpen().ToString());
//tbTDS.Text=waterAnalysisControl.Threshold.ToString();
}
}
}
创建WaterAnalysisControl类
using System;
using System.Collections.Generic;
using System.ComponentModel;
using System.IO.Ports;
using System.Linq;
using System.Runtime.ConstrainedExecution;
using System.Security.Cryptography;
using System.Text;
using System.Threading.Tasks;
using System.Windows;
using System.Windows.Threading;
using Weihong.Phoenix.Framework;
using LuaNet;
using Weihong.Phoenix.UI.Interactions;
using System.Threading;
namespace WaterAnalysis
{
public class WaterAnalysisControl: INotifyPropertyChanged, ISerializableObject
{
public WaterAnalysisControl()
{ }
#region 定义全局变量
//串口实例
SerialPort serialPort = new SerialPort();
//监控串口任务
Task monitorPortTask;
int screen = 0;
//实例化lua日志文件
Lua_Log lua_Log = new Lua_Log(Scripts.Lua);
#endregion
#region demo中属性绑定UI通知方法
public event PropertyChangedEventHandler PropertyChanged;
private void OnPropertyChanged(string propertyName)
{
if (this.PropertyChanged != null)
this.PropertyChanged(this, new PropertyChangedEventArgs(propertyName));
}
#endregion
#region demo中进行创建 串口连接
public void Connect()
{
//启动连接
if (serialPort.IsOpen)
{
serialPort.Close();
}
else
{
try
{
//创建Rtu主站
serialPort.PortName = "COM3"; //Phoenix.G.WaterAnalysis.Port;
serialPort.BaudRate = 9600;
serialPort.DataBits = 8;
serialPort.Parity = Parity.None;
serialPort.StopBits = StopBits.One;
serialPort.Open();
serialPort.ReadTimeout = 1000;
serialPort.RtsEnable = true;
//定义DataReceived事件,当串口收到数据后触发事件
serialPort.DataReceived += new SerialDataReceivedEventHandler(serialPort_DataReceived);
}
catch (Exception ex)
{
MessageBox.Show(ex.Message);
return;
}
monitorPortTask = new Task(MonitorPort);
monitorPortTask.Start();
}
}
#endregion
#region demo中串口关闭,当系统退出时执行
public void ClosePort()
{
try
{
if (serialPort.PortName != null)
{
serialPort.Close();
}
}
catch (Exception ex)
{
if (ex.Source != null)
{
}
}
}
#endregion
#region 串口号
private string port="COM3";
[Param("Control")]
[Attr("Name", "${LS:Param.NameWaterAnalysisPort, 串口序号}")]
[Attr("Desc", "${LS:Param.DescWaterAnalysisPort, 设备通讯使用串口序号,需在设备启用前修改。}")]
[Attr("Tag", "Manufacturer,MC")]
public string Port
{
get { return port; }
set { port = value; }
}
#endregion
#region 是否启用
private bool enabled = false;
[Param("Control")]
[Attr("Name", "${LS:Param.NameWaterAnalysisEnabled, 启用水质检测}")]
[Attr("Desc", "${LS:Param.DescWaterAnalysisEnabled, 是否启用水质检测设备。是:启用;否:不启用。}")]
[Attr("Tag", "Manufacturer,MC")]
[Attr("Check", "{LE = 0, GE = 1}")]
public bool Enabled
{
get { return enabled; }
set
{
enabled = value;
if (value)
{
//打开串口连接
ConnectStatus = true;
}
else
{
//关闭串口连接
ConnectStatus = false;
}
}
}
#endregion
#region 警告阈值
private int threshold=300;
[Param("Control")]
[Attr("Name", "${LS:Param.NameWaterAnalysisThreshold, 警报阈值}")]
[Attr("Desc", "${LS:Param.DescWaterAnalysisThreshold, 超过此值系统会产生报警,设置范围0-999,建议设置300。}")]
[Attr("Tag", "Manufacturer,MC")]
[Attr("Check", "{LE = 0, GE = 999}")]
public int Threshold
{
get { return threshold; }
set
{
if (value <= 0 || value >= 999)
{
MessageBox.Show("报警阈值需设置范围在 0-999,推荐值为 300 ");
return;
}
else threshold = value;
}
}
#endregion
#region 水质报警状态
private bool tdsWarnStatus = false;
public bool TdsWarnStatus
{
get { return tdsWarnStatus; }
set { tdsWarnStatus = value; }
}
#endregion
#region 水质检测 线路连接 报警状态
private bool lineWarnStatus = false;
public bool LineWarnStatus
{
get { return lineWarnStatus; }
set { lineWarnStatus = value; }
}
#endregion
#region 水质TDS值
private int tds;
public int TDS
{
get
{
return tds;
}
set
{
tds = value;
//在使用类库时不能使用,在使用应用程序时取消注释
OnPropertyChanged(nameof(tds));
if(value > threshold)
{
if(!TdsWarnStatus)
{
//使用维宏系统中的警报,报警:LogError,警报:LogWarning
lua_Log.LogWarning("水质质地较差,请检查水质!");
TdsWarnStatus = true;
}
}
else
TdsWarnStatus= false;
}
}
#endregion
#region 维宏中进行 串口连接
private bool connectStatus;
public bool ConnectStatus
{
get
{
return connectStatus;
}
set
{
//连接串口
if (value == true)
{
try
{
serialPort.PortName = Phoenix.G.WaterAnalysisDll.Port;
serialPort.BaudRate = 9600;
serialPort.DataBits = 8;
serialPort.Parity = Parity.None;
serialPort.StopBits = StopBits.One;
serialPort.Open();
serialPort.ReadTimeout = 1000;
serialPort.RtsEnable = true;
connectStatus = value;
//定义DataReceived事件,当串口收到数据后触发事件
serialPort.DataReceived += new SerialDataReceivedEventHandler(serialPort_DataReceived);
}
catch (Exception ex)
{
//MessageBox.Show("串口打开异常,异常信息:" + ex.Message);
connectStatus = false;
}
monitorPortTask = new Task(MonitorPort);
monitorPortTask.Start();
}
else if (value == false)
{
//关闭
if (serialPort.IsOpen)
{
try
{
serialPort.Close();
//初始化阈值
tds = 0;
}
catch (Exception ex)
{
MessageBox.Show("串口关闭异常,异常信息:" + ex.Message);
}
}
connectStatus = value;
}
}
}
#endregion
#region 获取串口连接状态
public bool IsOpen()
{
if(serialPort.IsOpen)
return true;
else
return false;
}
#endregion
#region 继承自维宏的接口实现
private string name = "WaterAnalysisControl";
public string Name
{
get { return name; }
set { name = value; }
}
public bool Load
{
set
{
if (value == false)
{
return;
}
var c = SerializationContext.GetContext(this);
c.Deserialize(PresistenceService.Load(this));
}
}
public bool Save
{
set
{
if (value == false)
{
return;
}
var c = SerializationContext.GetContext(this);
PresistenceService.Save(this, c.Serialize());
}
}
#endregion
#region 串口接收数据
private void serialPort_DataReceived(object sender, SerialDataReceivedEventArgs e)
{
//延时100ms等待接收完数据
System.Threading.Thread.Sleep(100);
// this.Invoke就是跨线程访问ui的方法
Dispatcher.CurrentDispatcher.Invoke(new System.Threading.ThreadStart(delegate //使用new System.Threading.ThreadStart代替(EventHandler)
{
Byte[] ReceivedData = new Byte[serialPort.BytesToRead];
serialPort.Read(ReceivedData, 0, ReceivedData.Length);
//原始数据
String RecvDataText = null;
//16进制数据
String Hexadecimal = null;
//10进制数据
String DecimalData = null;
for (int i = screen; i < ReceivedData.Length - screen; i++)
{
RecvDataText += ReceivedData[i].ToString("X2");
Hexadecimal += ReceivedData[i].ToString("X2");
DecimalData += GetHexadecimalValue(ReceivedData[i].ToString("X2")).ToString();
//TDS值计算方法:TDS=byte1*100+byte2
if (i == 1)
{
TDS = GetHexadecimalValue(ReceivedData[i].ToString("X2")) * 10;
}
else if (i == 2)
{
TDS += GetHexadecimalValue(ReceivedData[i].ToString("X2"));
}
}
//WaterAnalysisDialog.mainWindow.tbTDS.Text = TDS + "\r\n";
//输出为16进制数据
//tbTDS.Text += Hexadecimal + "\r\n" + "TDS值:" + TDS + "\r\n";
serialPort.DiscardInBuffer();//丢弃接收缓冲区数据
}));
}
#endregion
#region 十六进制换算为十进制
public static int GetHexadecimalValue(String strColorValue)
{
char[] nums = strColorValue.ToCharArray();
int total = 0;
try
{
for (int i = 0; i < nums.Length; i++)
{
String strNum = nums[i].ToString().ToUpper();
switch (strNum)
{
case "A":
strNum = "10";
break;
case "B":
strNum = "11";
break;
case "C":
strNum = "12";
break;
case "D":
strNum = "13";
break;
case "E":
strNum = "14";
break;
case "F":
strNum = "15";
break;
default:
break;
}
double power = Math.Pow(16, Convert.ToDouble(nums.Length - i - 1));
total += Convert.ToInt32(strNum) * Convert.ToInt32(power);
}
}
catch (System.Exception ex)
{
String strErorr = ex.ToString();
return -1;
}
return total;
}
#endregion
#region 监控串口方法
private void MonitorPort()
{
bool isOpen = false;
string[] gCOM = null;
while (true)
{
gCOM = null;
isOpen = false;
// 获取设备的所有可用串口
gCOM = System.IO.Ports.SerialPort.GetPortNames();
int j = gCOM.Length; // 得到所有可用串口数目
//判断是否找到串口,没有则直接报错
if (j != 0)
{
for (int i = 0; i < j; i++)
{
//找到目标串口
if (gCOM[i].ToUpper() == "COM3") //Phoenix.G.WaterAnalysisDll.Port;
{
//判断目标串口是否打开
if (serialPort.IsOpen)
{
isOpen = true;
}
else
{
try
{
//尝试打开串口
serialPort.Open();
isOpen = true;
//水质检测 线路连接 报警取消
LineWarnStatus = false;
}
catch (Exception)
{
//打开失败给false状态
isOpen = false;
}
}
break;
}
}
//
if (!isOpen)
{
//使用维宏系统中的警报,报警:LogError,警报:LogWarning
if (!LineWarnStatus)
{
lua_Log.LogWarning("未能读取到水质数据,请检测水质检测线路。");
LineWarnStatus = true;
}
}
}
else
{
//使用维宏系统中的警报,报警:LogError,警报:LogWarning
if (!LineWarnStatus)
{
lua_Log.LogWarning("未能读取到水质数据,请检测水质检测线路。");
LineWarnStatus = true;
}
}
Thread.Sleep(60000);
}
}
#endregion
}
}