视频演示链接:https://www.bilibili/video/av97314651?pop_share=1
前提条件:
1、电脑需要与PLC能通讯上;
2、手机要与电脑能通讯上(比如手机和电脑都在同一个局域网下或同一个WiFi下)。
主要思路:
1、利用S7实现上位机对西门子PLC数据的读写功能;
2、利用Socket实现上位机服务器与手机客户端的通讯,将上位机读取的PLC数据发送给手机客户端,以及将手机写入的信号写入到PLC。
服务器窗体简单画面如下:
服务器端的完整代码如下:
using S7.Net;
using System;
using System.Collections.Generic;
using System.ComponentModel;
using System.Data;
using System.Drawing;
using System.IO;
using System.Linq;
using System.Net;
using System.Net.Sockets;
using System.Text;
using System.Threading;
using System.Threading.Tasks;
using System.Windows.Forms;
namespace 读写库位信息
{
public partial class Form1 : Form
{
public Form1()
{
InitializeComponent();
Control.CheckForIllegalCrossThreadCalls = false;//取消跨线程的访问
try
{
//创建一个负责监听IP地址跟端口号的socket
Socket socketWatch = new Socket(AddressFamily.InterNetwork, SocketType.Stream, ProtocolType.Tcp);
IPAddress ip = IPAddress.Any;
IPEndPoint point = new IPEndPoint(ip, Convert.ToInt32(text_Port.Text));//创建端口号对象
socketWatch.Bind(point);//绑定端口号,其中端口号包含IP地址信息
ShowMsg(DateTime.Now.ToString() + " " + "监听成功");
AddLogText(DateTime.Now.ToString() + " " + "监听成功");
socketWatch.Listen(10);//设置监听队列,在同一时间点内所能连接的最多客户量,如果超过了10个,则排队等待
Thread th = new Thread(Listen);//创建新线程运行Listen()函数
th.IsBackground = true;//将线程设置为后台线程
th.Start(socketWatch);//把socketWatch传到线程的Start()函数,标记该线程可以被CPU执行了
}
catch
{ }
}
Socket socketsend;//创建跟客户端通讯的socket
Dictionary<string, Socket> dicSocket = new Dictionary<string, Socket>();
string ip;
/// <summary>
/// 等待客户端的连接,并创建一个与之通信用个Socket(通过循环等待客户端连接,否则线程会一直停在Accept()函数处等待)
/// </summary>
/// <param name="o"></param>
void Listen(object o)//被线程所执行的函数,如果有参数,必须是object型
{
Socket socketWatch = o as Socket;//把Listen()函数的object型的参数强转为Socket型
try
{
while (true)
{
socketsend = socketWatch.Accept();
dicSocket.Add(socketsend.RemoteEndPoint.ToString(), socketsend);//获取远程客户端的IP地址和端口号
connected_Client.Items.Add(socketsend.RemoteEndPoint.ToString());
ShowMsg(DateTime.Now.ToString() + "\r\n" + socketsend.RemoteEndPoint.ToString() + " : " + "连接成功");
AddLogText(DateTime.Now.ToString() + " " + socketsend.RemoteEndPoint.ToString() + " : " + "连接成功" );
byte[] buffer = System.Text.Encoding.UTF8.GetBytes(socketsend.RemoteEndPoint.ToString());
ip = socketsend.RemoteEndPoint.ToString();
Thread th = new Thread(Recieve);//开启一个新的线程不停地接收客户端发送过来的消息
th.IsBackground = true;//将线程设置为后台线程,这样主线程关闭时,新线程也会关闭(前台线程:只有所有的前台线程都关闭才能完成程序关闭;后台线程:只要所有的前台程序结束,后台线程自动结束)
th.Start(socketsend); //把socketsend传到线程的Start()函数,标记该线程可以被CPU执行了
}
}
catch (Exception ex)
{
MessageBox.Show(ex.Message.ToString()); //抛出异常信息
}
}
/// <summary>
/// 服务器端不停地接收客户端发送过来的消息
/// </summary>
/// <param name="o"></param>
void Recieve(object o)
{
Socket socketSend = o as Socket;
while (true)
{
try
{
byte[] buffer = new byte[1024 * 1024 * 2];//创建一个2M的字节数组型变量(1024byte=1kb,1024*1024byte=1024*1kb=1M)
int r = socketSend.Receive(buffer);//将Receive()函数返回的int型的实际接收到的字节数赋值给r
if (r == 0)
{
break;//如果接收到的数据为空,则跳出循环
}
string str = Encoding.UTF8.GetString(buffer, 0, r);//将字节数组buffer[]中的0到r位解码为字符串,并赋值给str
string[] sArray = str.Split('_'); //将字符串str按“_”分割,并存到字符串数组sArray中。
ShowMsg(DateTime.Now.ToString() + "\r\n" + socketSend.RemoteEndPoint + " : " + str);//调用ShowMsg()函数,将远程客户端的IP地址、端口号及接收到的信息显示到txtLog文本框
AddLogText(DateTime.Now.ToString() + " " + socketSend.RemoteEndPoint + " : " + str );
string readOrWrite = sArray[0];//取字符串数组索引号为0的元素,判断是读还是写,0为读,1为写
string storage = sArray[1];//取字符串数组索引号为1的元素,获取库位号
int storageNumble = Convert.ToInt32(storage.Substring(2, storage.Length - 2));//截取库位号,并将库位号转换为整数型变量,存入storageNumble中
int DBAddress = (storageNumble - 1) * 2;
string[] signal1 = { "0", "0", "0", "0", "0", "0", "0", "0" };//定义用于存储读到的DB20中的数据的字符串数组
string[] signal2 = { "0", "0", "0", "0" };//定义用于存储读到的DB21中的数据的字符串数组
if (readOrWrite == "0") //如果读到的数据为0,表示需要读取DB20和DB21中的数据
{
for (int i = 0; i < 8; i++)//读DB20的数据,该数据为AGV发送给库位的信号
{
string readAddress1 = "DB20.DBX" + DBAddress.ToString() + "." + i.ToString();
bool test1 = (bool)plc.Read(readAddress1);
signal1[i] = test1.ToString();
}
for (int i = 0; i < 4; i++)//读DB21的数据,该数据为AGV接收库位发送的信号
{
string readAddress2 = "DB21.DBX" + DBAddress.ToString() + "." + i.ToString();
bool test2 = (bool)plc.Read(readAddress2);
signal2[i] = test2.ToString();
}
string readResult = signal1[0];//新建字符串变量,用于存储读到的所有PLC信号
for (int i = 1; i < 8; i++)
{
readResult = readResult + "_" + signal1[i];//将在DB20数据块中读到的数据存放到readResult字符串中
}
for (int i = 0; i < 4; i++)
{
readResult = readResult + "_" + signal2[i]; //将在DB21数据块中读到的数据存放到readResult字符串中
}
dicSocket[ip].Send(System.Text.Encoding.UTF8.GetBytes(readResult));
ShowMsg(readResult);
AddLogText(readResult);
}
if (readOrWrite == "1") //如果读到的数据为1,表示需要在DB20中写入数据
{
string[] writeData = sArray[2].Split('-');//取接收到的客户端发过送的字符串数组索引号为2的元素,获取写入的数据,并将写入按“-”分割后存入writeData中
for (int i = 0; i < 8; i++)//读DB20的数据,该数据为AGV发送给库位的信号
{
string writeAddress = "DB20.DBX" + DBAddress.ToString() + "." + i.ToString();
plc.Write(writeAddress, bool.Parse(writeData[i])); //将写入的数据转换为bool量,并写入到相应的DB块中。
}
}
}
catch (Exception ex)
{
MessageBox.Show(ex.Message.ToString()+"(请检查是否连接PLC!)"); //抛出异常信息
}
}
}
/// <summary>
/// 将字符串追加到文本框txtLog中
/// </summary>
/// <param name="v">追加的字符串数据</param>
private void ShowMsg(string v)
{
txtLog.AppendText(v + "\r\n");
}
string strPath = System.Environment.CurrentDirectory + "/";//获取和设置当前目录(即该进程从中启动的目录)的完全限定路径。
string fileName = DateTime.Now.ToLongDateString().ToString() + ".txt"; //获取系统日期,并将"日期.txt"赋值给文件名变量fileName
/// <summary>
/// 将客户端的操作数据追加到文本日志文件中
/// </summary>
/// <param name="a">客户端的操作数据字符串</param>
private void AddLogText(string a)
{
File.AppendAllText(strPath + fileName, a + "\r\n"); //将参数a的值追加到特定路径的fileName文本中,如果fileName不存在,则创建新文件后再将a追加到文本文件中。
}
static string Plc_ip = "0.0.0.0";
public Plc plc = new Plc(CpuType.S71200, Plc_ip, 0, 1); //定义新的PLC对象
private void Btn_connect_Click(object sender, EventArgs e)
{
try
{
Plc_ip = txt_IP.Text;//将输入的IP地址赋值给Plc_ip
plc = new Plc(CpuType.S71200, Plc_ip, 0, 1);//重新实例化plc对象
plc.Open();//连接PLC
if (plc.IsConnected)
{
MessageBox.Show("连接成功!", "连接提示", MessageBoxButtons.OK);
}
else
{
MessageBox.Show("连接失败!", "连接提示", MessageBoxButtons.OK);
}
}
catch (Exception ex)
{
MessageBox.Show(ex.Message.ToString() + "(请检查PLC的IP地址是否正确!)");
}
}
private void Btn_disconnect_Click(object sender, EventArgs e)
{
plc.Close();//断开PLC
}
private void Btn_Clear_Click(object sender, EventArgs e)
{
txtLog.Text = null;//清空消息框
}
private void Btn_LogView_Click(object sender, EventArgs e)
{
System.Diagnostics.Process.Start(strPath + fileName);//查看日志
}
}
}
安卓手机客户端画面如下:
安卓手机客户端是利用E4A中文安卓编程软件制作的APP,完整代码如下:
登录页面代码:
变量 账号 为 文本型
变量 密码 为 文本型
事件 主窗口.创建完毕()
保存窗口("窗口1",创建 窗口1)
结束 事件
事件 按钮登录.被单击()
账号 = 编辑框账号.内容
密码 = 密码编辑框1.内容
如果 账号 = "admin" 且 密码 = "123" 则
切换窗口(读取窗口("窗口1"))
否则
弹出提示("账号或密码错误,请重新输入!")
结束 如果
结束 事件
事件 按钮取消.被单击()
结束程序()
结束 事件
主页面代码
事件 窗口1.创建完毕()
单选列表框1.背景颜色 = 白色
单选列表框1.清空项目()
变量 计次 为 整数型
计次=1
判断循环首 计次< 105
单选列表框1.添加项目("库位" & 计次)
单选列表框1.置项目状态(计次,假)
计次 = 计次 + 1
判断循环尾
结束 事件
事件 按钮连接.被单击()
客户1.连接服务器(IP.内容,到整数(PORT.内容),3000) '连接服务器
结束 事件
事件 按钮断开.被单击()
客户1.断开连接()
结束 事件
事件 客户1.连接断开()
弹出提示("连接已断开!")
结束 事件
变量 是否连接服务器 为 逻辑型
事件 客户1.连接完毕(连接结果 为 逻辑型)
是否连接服务器 = 连接结果
如果 连接结果 = 真 则
弹出提示("连接成功!")
否则
信息框("连接提示","连接服务器失败!请检查IP地址或端口号是否正确!","确定")
结束 如果
结束 事件
变量 库位号 为 文本型= "0"
事件 单选列表框1.表项被单击(项目索引 为 整数型)
库位号=单选列表框1.取项目内容(项目索引)
客户1.发送数据(文本到字节(0 & "_" & 库位号,"UTF-8")) '将库位号发送给服务器
结束 事件
事件 客户1.收到数据(数据 为 字节型())
变量 接收到的数据 为 文本型
变量 解析数据 为 文本型(12) '定义一个字符串数组,存放接收并解析后的数据
接收到的数据 = 字节到文本(数据,"UTF-8") '收到服务器发来的字节集数据,转换成文本
解析数据 = 分割文本(接收到的数据 ,"_")
如果 解析数据(0)="True" 则
拉空料.选中 = 真
否则
拉空料.选中 = 假
结束 如果
如果 解析数据(1)="True" 则
送满料.选中=真
否则
送满料.选中=假
结束 如果
如果 解析数据(2)="True" 则
申请进入.选中=真
否则
申请进入.选中=假
结束 如果
如果 解析数据(3)="True" 则
进入中.选中=真
否则
进入中.选中=假
结束 如果
如果 解析数据(4)="True" 则
到达.选中=真
否则
到达.选中=假
结束 如果
如果 解析数据(5)="True" 则
申请离开.选中=真
否则
申请离开.选中=假
结束 如果
如果 解析数据(6)="True" 则
离开中.选中=真
否则
离开中.选中=假
结束 如果
如果 解析数据(7)="True" 则
离开成功.选中=真
否则
离开成功.选中=假
结束 如果
如果 解析数据(8)="True" 则
拉空料1.选中=真
否则
拉空料1.选中=假
结束 如果
如果 解析数据(9)="True" 则
送满料1.选中=真
否则
送满料1.选中=假
结束 如果
如果 解析数据(10)="True" 则
允许进入.选中=真
否则
允许进入.选中=假
结束 如果
如果 解析数据(11)="True" 则
允许离开.选中=真
否则
允许离开.选中=假
结束 如果
结束 事件
变量 写入数据 为 文本型
事件 按钮写入.被单击()
变量 是否拉空料 为 文本型
变量 是否送满料 为 文本型
变量 是否申请进入 为 文本型
变量 是否在进入中 为 文本型
变量 是否到达 为 文本型
变量 是否申请离开 为 文本型
变量 是否在离开中 为 文本型
变量 是否离开成功 为 文本型
如果 拉空料.选中=真 则
是否拉空料 = "True"
否则
是否拉空料 = "False"
结束 如果
如果 送满料.选中=真 则
是否送满料 = "True"
否则
是否送满料 = "False"
结束 如果
如果 申请进入.选中=真 则
是否申请进入 = "True"
否则
是否申请进入 = "False"
结束 如果
如果 进入中.选中=真 则
是否在进入中 = "True"
否则
是否在进入中 = "False"
结束 如果
如果 到达.选中=真 则
是否到达 = "True"
否则
是否到达 ="False"
结束 如果
如果 申请离开.选中=真 则
是否申请离开 = "True"
否则
是否申请离开 = "False"
结束 如果
如果 离开中.选中=真 则
是否在离开中 = "True"
否则
是否在离开中 = "False"
结束 如果
如果 离开成功.选中=真 则
是否离开成功 = "True"
否则
是否离开成功 = "False"
结束 如果
变量 结果 为 整数型
写入数据 = 是否拉空料 & "-" & 是否送满料 & "-" & 是否申请进入 & "-" & 是否在进入中 & "-" & 是否到达 & "-" & 是否申请离开 & "-" & 是否在离开中 & "-" & 是否离开成功
如果 是否连接服务器 = 假 则
弹出提示("请连接服务器!")
否则
如果 库位号 = "0" 则
弹出提示("请选择库位号!")
否则
结果 = 信息框2("写入确认","是否写入" & 库位号 & "信号!","确定","取消")
如果 结果 = 0 则
客户1.发送数据(文本到字节("1" & "_" & 库位号 & "_" & 写入数据 ,"UTF-8")) '将xxx发送给服务器
弹出提示(库位号 & "信号已写入!" )
否则
弹出提示(库位号 & "信号未写入!")
结束 如果
结束 如果
结束 如果
结束 事件
事件 按钮刷新.被单击()
如果 是否连接服务器 = 假 则
弹出提示("请连接服务器!")
否则
如果 库位号 = "0" 则
弹出提示("请选择库位号!")
否则
客户1.发送数据(文本到字节( 0 & "_" & 库位号 ,"UTF-8")) '将xxx发送给服务器
结束 如果
结束 如果
结束 事件
事件 按钮退出.被单击()
结束程序()
结束 事件
更多推荐
C#用Socket和S7.net实现安卓手机APP读写西门子PLC数据(安卓APP使用的E4A中文编程软件)
发布评论