前言
对于电气工程师来说,不仅要会PLC,还要会上位机。
此前,我写过一个VB.net下雨西门子PLC通讯案例的博文:
VisaulStudio2019下用VB.net实现socket与西门子PLC进行通讯案例
但当时很多东西都理解不深,博文也写的比较浅,但我看有不少收藏,也有些朋友在底下询问,所以,基于这篇文章,我准备更新一下,重写一个VS2022版。
配置:
平台:windows
工具:visual sdutio 2022
语言:VB.net
通讯协议:socket
其中,关于PLC侧的设置,还是和之前一致,我就不细说了,主要关注上位机侧的程序变化。
注:其实,上位机与PLC的通讯,如果使用通用协议,比如串口或者socket,那么无论是哪个品牌的PLC,只要当前型号支持socket,上位机这边是通用的。
一、PLC侧设置:
可以查看之前的博文:
VisaulStudio2019下用VB.net实现socket与西门子PLC进行通讯案例
在此处不多述了。
二、上位机侧程序:
socket协议是分为客户端和服务端的,之前的博文里,我们的介绍是基于将PLC作为服务器端,上位机作为客户端来使用的,这当然是最常用的方式,因为通常情况下,我们都是去获取PLC数据的,但是,如果你非要将PLC设为客户端,也是可以的。
所以,我们下面会从客户端和服务器端两个方面来说明上位机侧的程序。
1 客户端
优化版的效果如下,调整了布局,增加了连接状态监控,以及PLC数据反馈监控(注:PLC反馈数据可以根据实际情况修改,本文仅作示例)
下面对客户端的程序作说明。
1、socket参数
VB.net中如果要使用socket协议,需要导入socket命名空间。
Imports System.Net.Sockets
定义一个socket对象:
Dim soc_client As Socket
然后实例化对象:
ip = IPAddress.Parse(TextBox1.Text)
port = Integer.Parse(TextBox2.Text)
ipe = New IPEndPoint(ip, port)
soc_client = New Socket(AddressFamily.InterNetwork, SocketType.Stream, ProtocolType.Tcp)
如上,new socket即是新建一个socket实例,而ip、port则是socket建立连接时需要的参数,即IP地址和端口号。
soc_client.Connect(ipe)
2、数据接收和发送
当我们建立socket的连接后,可以通过socket.connected这个标志位来判断连接是否成功。
Try
soc_client.Connect(ipe)
Console.WriteLine("connect ok")
If soc_client.Connected Then
'如果连接成功,状态变为绿色
huayuan(PictureBox1, Color.LimeGreen)
'如果连接成功,则启动数据接收,为了防止阻塞线程,采用新线程
Dim th1 As New Thread(AddressOf socket_client_recvievedata) '多线程
th1.Start() '线程启动,防止socket阻塞
End If
Catch ex As Exception
Console.WriteLine(ex.Message)
MsgBox(ex.Message + vbCrLf +
"请检查IP设置或目标PLC是否保持通讯正常?",
MsgBoxStyle.OkOnly, "tips!")
End Try
通过判断连接状态,可以在窗体上用颜色变化来表示,此处会用到一个画圆的函数:
''' <summary>
''' 在PictureBox中画一个圆
''' </summary>
''' <param name="p"></param>
''' <param name="c"></param>
Private Sub huayuan(p As PictureBox, c As Color)
Dim b As Bitmap = New Bitmap(p.Width, p.Height)
Dim g As Graphics = Graphics.FromImage(b)
Dim mybrush As New SolidBrush(c)
g.FillEllipse(mybrush, 0, 0, p.Width, p.Height)
g.Dispose()
p.Image = b
End Sub
关于这个huayuan的函数,我写了一个专门的博文介绍:
1、<.Net>使用visual Studio 2022在VB.net中新添自定义画图函数(优化版)
2、使用visual Studio 2019在VB.net中新添自定义画图函数
这两篇文章都可以了解。
总之,这个huayuan函数是用来改变PictureBox的状态(主要是颜色),用以区别连接状态。
当连接成功后,便可以进行数据的发送和接收,socket是双向同时可进行数据的通讯的。
数据接收:
Dim reclength = soc_client.Receive(recbyt, 200, SocketFlags.None)
但通常情况,为了能持续接收数据,一般会用到循环,但如果直接循环接收数据,会阻塞主线程即UI,导致窗体卡住,所以,一般会新建线程来接收数据:
'如果连接成功,则启动数据接收,为了防止阻塞线程,采用新线程
Dim th1 As New Thread(AddressOf socket_client_recvievedata) '多线程
th1.Start() '线程启动,防止socket阻塞
以下是接收数据的一个示例,其中的数据类型可以自己定义,本文仅作示例
''' <summary>
''' 客户端接收数据
''' </summary>
Private Sub socket_client_recvievedata()
'定义接收区字节数组
Dim recbyt(1024) As Byte
'循环接收
While True
Try
Dim reclength = soc_client.Receive(recbyt, 200, SocketFlags.None)
'recvdata = Encoding.GetEncoding("gb2312").GetString(recbyt)
Dim tt1 As Test_data
'recvdata = Encoding.ASCII.GetString(recbyt, 0, 30)
'以下是对所接收的所有数据进行解析,可以根据实际情况变化
'下面的仅作为参考
'ax1.axis_status = BitConverter.ToInt16(recbyt, 0)
'ax1.axis_xunhuanmoshi = BitConverter.ToInt16(recbyt, 2)
'ax1.axis_runing = BitConverter.ToInt16(recbyt, 4)
'ax1.axis_error = BitConverter.ToInt16(recbyt, 6)
Console.WriteLine(reclength.ToString())
tt1.test1 = Encoding.GetEncoding("utf-8").GetString(recbyt)
tt1.test2 = Encoding.GetEncoding("utf-8").GetString(recbyt)
'Console.WriteLine(recdata1)
'将解析的数据通过委托的方式传递给主线程,以更新主线程的UI
Dim socket_invoke_1 As New socket_invoke(AddressOf socket_recv_data_invoke)
Me.Invoke(socket_invoke_1, tt1)
Catch ex As Exception
Exit Sub
MsgBox(ex.Message, MsgBoxStyle.OkOnly, "tips!")
End Try
End While
End Sub
要注意到上面的接收函数的后面有这样一段代码:
'将解析的数据通过委托的方式传递给主线程,以更新主线程的UI
Dim socket_invoke_1 As New socket_invoke(AddressOf socket_recv_data_invoke)
Me.Invoke(socket_invoke_1, ax1, ax2, plc1)
因为我们接收数据,处理完数据后,有一些数据是要传递给主线程并更新UI状态的,所以就需要用到委托。这是用于在两个线程间传递数据的。
此处委托的函数socket_recv_data_invoke如下:
''' <summary>
''' 委托处理函数
''' </summary>
Private Sub socket_recv_data_invoke(tt1 As Test_data)
'以下仅以简单数据更新作为参考,可自行修改此处逻辑
'此处的数据处理,主要是将socket接收过来的数据,经过处理后,经由委托方式传给主线程即UI
'用于更新窗体上的Label、button、textbox等控件的内容
TextBox3.Text = tt1.test1
TextBox5.Text = tt1.test2
TextBox6.Text = tt1.test2
TextBox7.Text = tt1.test2
TextBox8.Text = tt1.test2
End Sub
本例中,我只是简单写了几个逻辑,其作用是将PLC反馈的数据更新到控件上,比如改变监控状态,改变错误输出,改变轴的数值等。类似于下图这样:
当然,实际情况需要根据自己的要求来改。
下面看一下演示:
注:这里演示并未使用真实的PLC,而是利用python虚拟了一个服务器端来发送数据,仅作演示效果。
等视频上传完成后添加
已添加
socket客户端通讯演示
完整代码:
Imports System.Text
Imports System.Net.Sockets
Imports System.Net
Imports System.Threading
Public Class Form1
''' <summary>
''' 测试数据
''' </summary>
Structure Test_data
Public test1 As String
Public test2 As String
Public test3 As String
Public temp1 As Boolean
Public temp2 As Boolean
End Structure
'定义客户端发送字节缓冲区
Dim sendbytes_client(1024) As Byte
'定义客户端接收字节缓冲区
Dim recvbytes_client(1024) As Byte
'定义服务器发送字节缓冲区
Dim sendbytes_server(1024) As Byte
'定义服务器接收字节缓冲区
Dim recvbytes_server(1024) As Byte
Dim ip As IPAddress
Dim ipe As IPEndPoint
Dim port As Integer
Dim host As String
Dim soc_client As Socket
Dim soc_server As Socket
Dim socket_mode As Integer '设备类型,1=客户端,2=服务器
Delegate Sub socket_invoke(tt1 As Test_data)
Private Sub Form1_Load(sender As Object, e As EventArgs) Handles MyBase.Load
Me.Text = "PLC通讯工具"
Me.Size = New Size(840, 600)
Me.Location = New Point(100, 40)
TextBox1.Text = "192.168.0.1"
TextBox2.Text = "2000"
huayuan(PictureBox1, Color.Gray)
'默认情况下,设备类型为客户端
socket_mode = 1
Label11.Text = "无"
RadioButton1.Checked = False
RadioButton2.Checked = True
RadioButton1.Enabled = False
init_func()
End Sub
Private Sub Button1_Click(sender As Object, e As EventArgs) Handles Button1.Click
socket_client_connect()
End Sub
Private Sub Button2_Click(sender As Object, e As EventArgs) Handles Button2.Click
socket_client_disconnect()
End Sub
Private Sub RadioButton1_CheckedChanged(sender As Object, e As EventArgs) Handles RadioButton1.CheckedChanged
If RadioButton1.Checked = True And RadioButton2.Checked = False Then
Button1.Enabled = False
Button2.Enabled = False
Button5.Enabled = True
Button6.Enabled = True
socket_mode = 2
Label11.Text = "服务器"
ElseIf RadioButton1.Checked = False And RadioButton2.Checked = True Then
Button1.Enabled = True
Button2.Enabled = True
Button5.Enabled = False
Button6.Enabled = False
socket_mode = 1
Label11.Text = "客户端"
End If
End Sub
Private Sub RadioButton2_CheckedChanged(sender As Object, e As EventArgs) Handles RadioButton2.CheckedChanged
If RadioButton1.Checked = True And RadioButton2.Checked = False Then
Button1.Enabled = False
Button2.Enabled = False
Button5.Enabled = True
Button6.Enabled = True
socket_mode = 2
Label11.Text = "服务器"
ElseIf RadioButton1.Checked = False And RadioButton2.Checked = True Then
Button1.Enabled = True
Button2.Enabled = True
Button5.Enabled = False
Button6.Enabled = False
socket_mode = 1
Label11.Text = "客户端"
End If
End Sub
''' <summary>
''' 初始化某些参数
''' </summary>
Private Sub init_func()
ip = IPAddress.Parse(TextBox1.Text)
port = Integer.Parse(TextBox2.Text)
ipe = New IPEndPoint(ip, port)
soc_client = New Socket(AddressFamily.InterNetwork, SocketType.Stream, ProtocolType.Tcp)
End Sub
''' <summary>
''' 客户端连接
''' </summary>
Private Sub socket_client_connect()
Try
ip = IPAddress.Parse(TextBox1.Text)
port = Integer.Parse(TextBox2.Text)
ipe = New IPEndPoint(ip, port)
soc_client.Connect(ipe)
Console.WriteLine("connect ok")
If soc_client.Connected Then
'如果连接成功,状态变为绿色
huayuan(PictureBox1, Color.LimeGreen)
'如果连接成功,则启动数据接收,为了防止阻塞线程,采用新线程
Dim th1 As New Thread(AddressOf socket_client_recvievedata) '多线程
th1.Start() '线程启动,防止socket阻塞
End If
Catch ex As Exception
Console.WriteLine(ex.Message)
MsgBox(ex.Message + vbCrLf +
"请检查IP设置或目标PLC是否保持通讯正常?",
MsgBoxStyle.OkOnly, "tips!")
End Try
End Sub
''' <summary>
''' 客户端断开连接
''' </summary>
Private Sub socket_client_disconnect()
Try
If soc_client.Connected Then
soc_client.Shutdown(SocketShutdown.Both)
soc_client.Close()
soc_client.Dispose()
If soc_client.Connected = False Then
'如果客户端已经断开连接,状态变为灰色
huayuan(PictureBox1, Color.Gray)
End If
End If
Catch ex As Exception
MsgBox(ex.Message, MsgBoxStyle.OkOnly, "tips!")
End Try
End Sub
''' <summary>
''' 客户端接收数据
''' </summary>
Private Sub socket_client_recvievedata()
'定义接收区字节数组
Dim recbyt(1024) As Byte
'循环接收
While True
Try
Dim reclength = soc_client.Receive(recbyt, 200, SocketFlags.None)
'recvdata = Encoding.GetEncoding("gb2312").GetString(recbyt)
Dim tt1 As Test_data
'recvdata = Encoding.ASCII.GetString(recbyt, 0, 30)
'以下是对所接收的所有数据进行解析,可以根据实际情况变化
'下面的仅作为参考
'ax1.axis_status = BitConverter.ToInt16(recbyt, 0)
'ax1.axis_xunhuanmoshi = BitConverter.ToInt16(recbyt, 2)
'ax1.axis_runing = BitConverter.ToInt16(recbyt, 4)
'ax1.axis_error = BitConverter.ToInt16(recbyt, 6)
Console.WriteLine(reclength.ToString())
tt1.test1 = Encoding.GetEncoding("utf-8").GetString(recbyt)
tt1.test2 = Encoding.GetEncoding("utf-8").GetString(recbyt)
'Console.WriteLine(recdata1)
'将解析的数据通过委托的方式传递给主线程,以更新主线程的UI
Dim socket_invoke_1 As New socket_invoke(AddressOf socket_recv_data_invoke)
Me.Invoke(socket_invoke_1, tt1)
Catch ex As Exception
Exit Sub
MsgBox(ex.Message, MsgBoxStyle.OkOnly, "tips!")
End Try
End While
End Sub
''' <summary>
''' 委托处理函数
''' </summary>
Private Sub socket_recv_data_invoke(tt1 As Test_data)
'以下仅以简单数据更新作为参考,可自行修改此处逻辑
'此处的数据处理,主要是将socket接收过来的数据,经过处理后,经由委托方式传给主线程即UI
'用于更新窗体上的Label、button、textbox等控件的内容
TextBox3.Text = tt1.test1
TextBox5.Text = tt1.test2
TextBox6.Text = tt1.test2
TextBox7.Text = tt1.test2
TextBox8.Text = tt1.test2
End Sub
''' <summary>
''' 客户端发送数据
''' </summary>
Private Sub socket_client_senddata()
Try
If soc_client.Connected Then
soc_client.Send(sendbytes_client, 200, SocketFlags.None)
End If
Catch ex As Exception
MsgBox(ex.Message, MsgBoxStyle.YesNoCancel, "tips!")
End Try
End Sub
''' <summary>
''' 在PictureBox中画一个圆
''' </summary>
''' <param name="p"></param>
''' <param name="c"></param>
Private Sub huayuan(p As PictureBox, c As Color)
Dim b As Bitmap = New Bitmap(p.Width, p.Height)
Dim g As Graphics = Graphics.FromImage(b)
Dim mybrush As New SolidBrush(c)
g.FillEllipse(mybrush, 0, 0, p.Width, p.Height)
g.Dispose()
p.Image = b
End Sub
End Class
注1:本文是为了便于以后遇到同样问题而作的记录,当然,如果能够同时帮助到其他朋友,那也是非常好的。
注2:本文在前面说到了服务器端和客户端,但本文暂时只是介绍了客户端程序,服务器端程序将在后续更新。