C#实战:基于WPF开发一个串口转UDP工具

Posted 微小冷

tags:

篇首语:本文由小常识网(cha138.com)小编为大家整理,主要介绍了C#实战:基于WPF开发一个串口转UDP工具相关的知识,希望对你有一定的参考价值。

文章目录

串口是设备和上位机通信的常用接口,UDP则是网络通信常用的通信协议,通过将串口设备上传的指令,用UDP发送出去,或者将UDP传来的指令转发给串口设备,就可以实现设备的远程控制。所以,串口和UDP之间的相互转换是非常有意义的。

如果不熟悉C#串口以及UDP通信的相关内容,可以参考这两篇博客:C#串口通信💎C# UDP通信

本项目会用到基于Task的并发编程,如果不了解,可以参照这篇:Task详解

有关C#的其他基础知识,可参考这个C#专栏目录

框架准备

尽管希望做一个转发工具,但如果自身不会发送的话,那还得再用其他软件进行测试,所以这个转发工具,理应具备串口和UDP协议的全部功能,这一点也要在界面上体现出来。

新建一个WPF项目,名字是portUDP,然后开始布局,结果如下

其中,串口设置中包含波特率、数据位、停止位和奇偶校验等信息,由于不常更换,所以隐藏起来。

串口只需要一个,但UDP通信需要设置本机和目标的IP地址与端口。自动转发单选框选中后,会自动将接收到的串口数据转给UDP,并且UDP收到的数据也会转给串口。

窗口尺寸为 320 × 640 320\\times640 320×640,外部采用一个DockPanel,并对常用控件进行基本的外观设置

<DockPanel.Resources>
    <Style TargetType="TextBox">
        <Setter Property="VerticalAlignment" Value="Center"/>
        <Setter Property="Margin" Value="2"/>
    </Style>
    <Style TargetType="TextBlock">
        <Setter Property="VerticalAlignment" Value="Center"/>
        <Setter Property="HorizontalAlignment" Value="Center"/>
    </Style>
    <Style TargetType="ComboBox">
        <Setter Property="VerticalAlignment" Value="Center"/>
        <Setter Property="Margin" Value="2"/>
    </Style>
    <Style TargetType="Button">
        <Setter Property="VerticalAlignment" Value="Center"/>
        <Setter Property="Margin" Value="2"/>
    </Style>
    <Style TargetType="CheckBox">
        <Setter Property="VerticalAlignment" Value="Center"/>
        <Setter Property="HorizontalAlignment" Value="Center"/>
        <Setter Property="Margin" Value="2"/>
    </Style>
</DockPanel.Resources>

左侧控制面板被写在一个StackPanel中,最上面是一个Expander,里面包含串口设置的相关信息

<Expander Header="串口设置">
    <UniformGrid Columns="2" Visibility="Visible">
        <TextBlock Text="波特率"/>
        <ComboBox x:Name="cbBaud"/>
        <TextBlock Text="数据位"/>
        <ComboBox x:Name="cbDataBit"/>
        <TextBlock Text="停止位"/>
        <ComboBox x:Name="cbStopBit"/>
        <TextBlock Text="校验位"/>
        <ComboBox x:Name="cbParity"/>
    </UniformGrid>
    
</Expander>

然后是一个GroupBox,用于进行基本设置,其中两个按钮需要在连接后更改内容,所以设了个名字。

<UniformGrid Columns="2">
    <ComboBox x:Name="cbPorts" Margin="2"/>
    <Button x:Name="btnPort" Content="连接串口"/>

    <TextBox x:Name="txtSrcIP" Text="127.0.0.1"/>
    <TextBox x:Name="txtSrcPort" Text="91"/>
    
    <TextBox x:Name="txtDstIP" Text="127.0.0.1"/>
    <TextBox x:Name="txtDstPort" Text="91"/>
    
    <CheckBox Content="自动转发" IsChecked="True"/>
    <Button  x:Name="btnUDP" Content="创建服务"/>
</UniformGrid>

最后是发送文本框与发送按钮等,内容如下

<TextBox x:Name="txtSend" TextWrapping="Wrap" Height="70"/>
<UniformGrid Columns="3">
    <CheckBox Content="Hex" IsChecked="False"/>
    <Button Content="串口发送"/>
    <Button Content="UDP发送"/>
    <CheckBox Content="时间"/>
    <Button Content="清空日志"/>
    <Button Content="保存日志"/>
</UniformGrid>

左侧控制界面布局完成后,是右侧的接收区域,内容如下

<GroupBox Header="日志信息">
    <TextBox x:Name="txtInfo"  Height="270"/>
</GroupBox>

初始化

由于.Net6.0不内置串口库,所以需要额外下载,点击菜单栏工具->NuGet包管理器->管理解决方案的NuGet包,点击浏览选项卡,搜索Ports,选择System.IO.Ports,安装。

在对用户界面进行最简单的布局后,可以在C#代码中,对一些ComboBox做进一步的设置。

public void initComContent()

    // 串口号
    cbPorts.ItemsSource = SerialPort.GetPortNames();
    cbPorts.SelectedIndex = 0;
    
    // 波特率
    cbBaud.ItemsSource = new int[]  9600, 19200, 38400, 115200 ;
    cbBaud.SelectedIndex = 3;

    // 数据位
    cbDataBit.ItemsSource = Enumerable.Range(1, 8);
    cbDataBit.SelectedIndex = 7;
    
    // 校验位
    cbParity.ItemsSource = Enum.GetNames(typeof(Parity));
    cbParity.SelectedIndex = 0;
    
    //停止位
    cbStopBit.ItemsSource = Enum.GetNames(typeof(StopBits));
    cbStopBit.SelectedIndex = 1;

这样,在打开软件之后,串口设置如下

串口设置

接下来设置串口,在xml编辑界面,将btnPort后面添加Click="btnPort_Click"后,按下F12,IDE会自动创建对应的函数。

<Button x:Name="btnPort" Content="连接串口" Click="btnPort_Click"/>

在写串口开关按钮的控制指令之前,先新建一个全局的串口对象,然后写btnPort_Click内容

SerialPort sp;
private void btnPort_Click(object sender, RoutedEventArgs e)

    if (btnPort.Content.ToString() == "打开串口")
    
        string name = cbPorts.SelectedItem.ToString();
        try
        
            sp = new SerialPort(name,
                (int)cbBaud.SelectedItem,
                (Parity)cbParity.SelectedIndex,
                (int)cbDataBit.SelectedItem,
                (StopBits)cbStopBit.SelectedIndex);

            sp.Open();
            sp.DataReceived += Sp_DataReceived;
            txtInfo.AppendText($"串口name打开成功");
        
        catch(Exception ex)
        
            txtInfo.AppendText($"串口name打开失败,原因是ex");
        
    
    else
    
        try
        
            sp.Close();
            initComContent();
        
        catch (Exception ex)
        
            txtInfo.AppendText($"串口关闭失败,原因是ex");
        
    
    btnPort.Content = sp.IsOpen ? "关闭串口" : "打开串口";

其中sp.DataReceived += Sp_DataReceived;新增一个委托,用于规范串口接收到数据后的行为。

UDP设置

和串口设置相同,UDP也需要新建用于UDP通信的全局变量,包括本机节点、目标节点以及UDP服务。

UdpClient udp;
IPEndPoint ptSrc;
IPEndPoint ptDst;

然后设置创建服务按钮后,其逻辑与串口是相似的,都是在创建或关闭服务时,用try-catch语句以找到错误。

private void btnUDP_Click(object sender, RoutedEventArgs e)

    if (btnUDP.Content.ToString() == "创建服务")
    
        try
        
            ptSrc = new IPEndPoint(IPAddress.Parse(txtSrcIP.Text), int.Parse(txtSrcPort.Text));
            ptDst = new IPEndPoint(IPAddress.Parse(txtDstIP.Text), int.Parse(txtDstPort.Text));
            udp = new UdpClient(ptSrc);
            txtInfo.AppendText("成功创建服务");
            btnUDP.Content = "关闭服务";
        catch(Exception ex)
        
            txtInfo.AppendText($"服务创建失败,原因为ex\\n");
        
    
    else
    
        try
        
            udp.Close();
            btnUDP.Content = "创建服务";
        
        catch(Exception ex)
        
            txtInfo.AppendText($"服务关闭失败,原因为ex");
        
    


发送设置

首先是串口发送,在xaml文件中,为串口发送按钮挂载一个Click动作,其内容即为串口发送功能

<Button Content="串口发送" Click="btnPortSend_Click"/>
private void btnPortSend_Click(object sender, RoutedEventArgs e)

    var data = Encoding.UTF8.GetBytes(txtSend.Text);
    txtInfo.AppendText($"串口发送txtSend.Text\\n");
    sp.Write(data, 0, data.Length);

然后是UDP发送,其改装过程也大同小异

<Button Content="UDP发送" Click="btnUDPSend_Click"/>
private void btnUDPSend_Click(object sender, RoutedEventArgs e)

    var data = Encoding.UTF8.GetBytes(txtSend.Text);
    txtInfo.AppendText($"UDP发送txtSend.Text\\n");
    udp.Send(data, data.Length, ptDst);    //将内容发给ptDst

转发设置

转发是本软件的核心功能,但其前提是接收到数据。所以,先来充实创建串口时就已经提到的Sp_DataReceived函数

private void Sp_DataReceived(object sender, SerialDataReceivedEventArgs e)

    byte[] data = new byte[sp.BytesToRead];
    sp.Read(data, 0, data.Length);//从串口读取数据
    Dispatcher.Invoke(() => spReceived(data));


private void spReceived(byte[] data)

    string info = Encoding.UTF8.GetString(data);
    txtInfo.AppendText("串口接收数据:info");
    if ((bool)chkTransmit.IsChecked)
    
        try
        
            udp.Send(data, data.Length, ptDst);    //将内容发给ptDst
            txtInfo.AppendText($"UDP转发:info");
        
        catch (Exception ex)
        
            txtInfo.AppendText($"UDP转发失败,原因为ex\\nd");
        
    

然后创建UPD接收和转发函数

private void udpReceiving(CancellationToken token)

    while (! token.IsCancellationRequested)
    
        var data = udp.Receive(ref ptDst);
        Dispatcher.Invoke(() => udpReceived(data));
    

private void udpReceived(byte[] data)

    string info = Encoding.UTF8.GetString(data);
    txtInfo.AppendText("UDP接收数据:info");
    if ((bool)chkTransmit.IsChecked)
    
        try
        
            sp.Write(data, 0, data.Length);
            txtInfo.AppendText($"串口转发info\\n");
        
        catch (Exception ex)
        
            txtInfo.AppendText($"串口转发失败,原因为ex");
        
    

其中,udpReceiving里面是一个死循环,表示一直等待UDP信息的到来,这个函数作为一个Task的创建时机,自然是在UDP服务创建之时,

//...
txtInfo.AppendText("成功创建服务");
//这是一个全局变量
cts = new CancellationTokenSource();
Task.Run(() => udpReceiving(cts.Token), cts.Token);

测试

至此,一个串口-UDP转发工具便算完成了,尽管界面上还有几个功能没有实现,比如Hex以及时间的单选框等,但这些均为锦上添花。

下面做一下基础的测试,效果如下

C#跨平台开源项目实战(WPF/Android/IOS/Blazor)

个人介绍

由于本人从业WPF开发, 考虑到国内的WPF开发环境并不是很好, 资源少、项目案例少, 所以导致很多初学者就已经断了念头。
所以我作为WPF的从业者, 就在2019年,开始了发布自己的WPF相关的免费教学视频。发布开源的项目实践, WPF的基础视频、项目实践视频, 包括WPF UI设计视频。
同时我希望通过不断的网络传播, 博客园, 码云/Github发布资源, 可以让提供面向中国的WPF相关从业者, 提供一个参考、学习、以及灵感和创意。

项目起源

由于做的都是WPF相关工作,对XAML语言也是比较熟悉, 所以扩展了Xamarin移动端教程, 使得C#开发 安卓和IOS变成了可能, 包括目前C#使用Blazor来开发网页端, 所以发起了这个开源项目。

项目说明

该项目主要由一套C#代码, 构建多个平台的项目, 包括: Windows、Android、IOS、Web。后端采用Asp.net Core WebApi。
该项目主要应用于日常的一套记账应用为基础开发, 提供给用户在手机端操作一些账单信息, PC/Web端主要用于后台管理, 用于统计相关数据, 报表等功能,
该项目提供了基础的用户管理、部分管理、围绕展开的权限管理。

关于视频

关于所有的C#、WPF、Xamarin、Blazor等教学视频, 都在国内的各个视频平台公开: 今日头条, 西瓜视频, 抖音, BiliBili等视频网站。
BiliBili:https://space.bilibili.com/32497462

西瓜视频/抖音同步:https://studio.ixigua.com/content

项目地址

项目结构

以下截图为项目的结构及说明

  • API: ASP.NET Core3.1 WebApi,提供相关数据接口

  • Core: 存储 通用的实体模型,请求模型及通用的类库

  • EFCore: 存储 EF上下文及相关迁移文件

  • Mobile: 负责 移动端的业务代码编写

  • Mobile.Android: 安卓的相关配置

  • Mobile.ios: IOS的相关配置

  • PC: 基于WPF Core的客户端

  • Service: 请求WebApi的相关接口实现

  • ViewModel: 通用的业务区域, 主要用于驱动PC、Mobile、Web的相关逻辑实现

  • Web: 基于Blazor的Web端

效果预览(2020-06-28)

最新的效果以Github的项目更新为准, 目前完成了后端API, 以及优先 WPF 端实现。

后端Open Api预览:
C#跨平台开源项目实战(WPF/Android/IOS/Blazor)

WPF端登录预览:
C#跨平台开源项目实战(WPF/Android/IOS/Blazor)

WPF端首页预览:

单页预览:

以Github发布更新为准。

出处:https://www.cnblogs.com/zh7791/p/13202009.html

以上是关于C#实战:基于WPF开发一个串口转UDP工具的主要内容,如果未能解决你的问题,请参考以下文章

C# 基于.NET6的CM+Fody+HC入门实战项目(经典)

USBRS485串口UDP转MQTT TCP http json网络通信协议对接定制开发

c# wpf是啥

C# WPF MVVM项目实战(进阶②)

C# WPF MVVM项目实战(进阶②)

基于Modbus的C#串口调试开发