十年匠心定制 · 商业建站与技术教学双线并行 咨询热线:400-886-1026 service@lmnt.cn
ARTICLE DETAIL

资讯详情

深耕网站建设与运营推广的一线实战洞察。

巧用python之--模仿PLC(PLC模拟器)

巧用python之--模仿PLC(PLC模拟器) 工作中用到了VM(VisionMaster4.3)有时候需要和PLC打交道,但是PLC毕竟是别人的,不方便修改别人的程序,这时候需要一个灵活的PLC模拟器是多么好呀!先说背景:PLC型号汇川Easy521:Modbus TCP 192.168.1.10:502在汇川Easy521中Modbus保持寄存器D寄存器 ,在modbus协议中 0-4区 3区就是 保持寄存器(R/W)那么事情很简单了:只需要做一个ModbusTCP的服务端 就能模拟PLC:Modbus RTU是主从结构 分 Modbus 主机 / 从机Master / Slave;RTU模式主从 主机会轮询从机 问一次答一次; 一般电脑做主机 PLC做从机;但是当电脑同时连接很多台PLC,电脑做主机,主动询问PLC那么电脑的压力会很大;这时候电脑可以做从机,多个PLC做主机,电脑端成了被动,那么电脑压力会小很多;(适用于MODBUS RTU TCP) 扯远了;好了这里只说Modbus_TCP 粗略的说Modbus_TCP的报文实际就是RTU 增加文件头去掉CRC校验;注意下面说的Tcp都指得是modbusTCP;;;客户端 服务器 Client/ServerVisionMaster4.3只支持Modbus TcpClientTcpClient按主从结构分是主机/(Master), PLC扮演的是 Modbus TcpServer (Slave)所以在Modbustcp这里,服务器是modbus从机,客户端是modbus主机;由 TcpClient(VM) 去 轮询 TcpServer(PLC);好了,搞清楚原理了,下来就是,模拟一个TcpServer(PLC);就是一个可以用的PLC模拟器.当然你用Modsim/ModScan也可以,但是操作不便;;因为之前就做过一些用hsl库C#,模拟modbusTCPServer,这里程序放出来吧;using System; using System.Collections.Generic; using System.ComponentModel; using System.Data; using System.Drawing; using System.Linq; using System.Net; using System.Text; using System.Threading.Tasks; using System.Windows.Forms; using System; using System.Collections.Generic; using System.Net; using System.Runtime.Remoting.Contexts; using System.Threading; using HslCommunication; namespace MB_TCPServer { public partial class Form1 : Form { HslCommunication.ModBus.ModbusTcpServer modbusTcpServer; public Form1() { InitializeComponent(); modbusTcpServer new HslCommunication.ModBus.ModbusTcpServer(); modbusTcpServer.ServerStart(502); modbusTcpServer.Write(50, (UInt16)1); modbusTcpServer.Write(60, (UInt16)1); modbusTcpServer.Write(61, (UInt16)1); OperateResult UInt16 intReg_100 modbusTcpServer.ReadUInt16(50); // 读取输入寄存器100的值 if (intReg_100.IsSuccess) { Console.WriteLine(success!); Console.WriteLine(); } else { Console.WriteLine(failed: intReg_100.Message); } Liststring ipAddresses GetIpAddresses();//调用 Console.WriteLine(ipAddresses.Count);//有几个ip foreach (string ipAddress in ipAddresses) { Console.WriteLine(ipAddress);//ip分别有哪些 comboBox_me_ip.Items.Add(ipAddress); } comboBox_me_ip.SelectedItem 1; } private void button1_Click(object sender, EventArgs e) { } public static Liststring GetIpAddresses() { Liststring ipAddresses new Liststring(); try { // 获取本地主机名 string hostName Dns.GetHostName(); // 使用主机名获取IP地址信息 IPHostEntry hostEntry Dns.GetHostEntry(hostName); // 获取IP地址列表 foreach (IPAddress ipAddress in hostEntry.AddressList) { // 确保IP地址不是IPv6的环回地址或者IPv4的环回地址 if (!ipAddress.IsIPv4MappedToIPv6 ipAddress.AddressFamily ! System.Net.Sockets.AddressFamily.InterNetworkV6) { ipAddresses.Add(ipAddress.ToString()); } } } catch (Exception ex) { } return ipAddresses; /* Liststring ipAddresses GetIpAddresses();//调用 Console.WriteLine(ipAddresses.Count);//有几个ip foreach (string ipAddress in ipAddresses) { Console.WriteLine(ipAddress);//ip分别有哪些 } */ } private void button_trig_Click(object sender, EventArgs e) { modbusTcpServer.Write(50, (UInt16)10); OperateResultUInt16 intReg_100 modbusTcpServer.ReadUInt16(50); // 读取输入寄存器100的值 if (intReg_100.IsSuccess) { Console.WriteLine(success!); Console.WriteLine(intReg_100.Content); textBox_trig.Text intReg_100.Content.ToString(); } else { Console.WriteLine(failed: intReg_100.Message); } } private void button2_Click(object sender, EventArgs e) { modbusTcpServer.Write(50, (UInt16)0); OperateResultUInt16 intReg_100 modbusTcpServer.ReadUInt16(50); // 读取输入寄存器100的值 if (intReg_100.IsSuccess) { Console.WriteLine(success!); Console.WriteLine(intReg_100.Content); textBox_trig.Text intReg_100.Content.ToString(); } else { Console.WriteLine(failed: intReg_100.Message); } } private void button3_Click(object sender, EventArgs e) { button_trig_Click(null,null); Thread.Sleep(200);// 太快vm反应不过来 button2_Click(null, null); } } }可以看出实际有用的只有这些:其余都是 winform界面;using HslCommunication; namespace MB_TCPServer { public partial class Form1 : Form { HslCommunication.ModBus.ModbusTcpServer modbusTcpServer; public Form1() { modbusTcpServer new HslCommunication.ModBus.ModbusTcpServer(); modbusTcpServer.ServerStart(502); modbusTcpServer.Write(50, (UInt16)1); modbusTcpServer.Write(60, (UInt16)1); modbusTcpServer.Write(61, (UInt16)1); OperateResult UInt16 intReg_100 modbusTcpServer.ReadUInt16(50); if (intReg_100.IsSuccess) { Console.WriteLine(success!); Console.WriteLine(); } else { Console.WriteLine(failed: intReg_100.Message); } } } }要求很简单就是要一个 界面带按钮可以修改 模拟TcpServer(PLC);内指定寄存器的数值 读取指定数值即可.但是那天出门只带了一个平板笔记本,且没转VS虽然有程序但无法编译,所以python登场..虽然python有modbus库minimalmodbus pymodbus ;等但是没有hsl好用 因为之前用hsl;直接用pythonnet在python里面调用 .net 版的hsl,实际python也有hsl但是需要授权,C# 版7.001以及以下版本是免费的,对于干这件事情是绰绰有余的,这里对hsl作者表示感谢;;;于是就有了如下 产物: 有界面 有按钮 有结果显示 有数字显示 可以模拟 PLC的程序 且一共200行;当然必须安装pythonnet ,且依赖的外部 .net dll文件要和.py程序在一个目录import os,sys,time import tkinter as tk from tkinter import messagebox #import win32api,win32con,win32gui from ctypes import * #需要安装 pywin32 def cmd(spause):os.system(s) def p(s):print(s);return s win tk.Tk() win.title((汇川Easy521): PLC模拟器) win.geometry(960x280) win.config(background #00aa00) winscrwidthwin.winfo_screenwidth() # 基础库 import os,sys,time from ctypes import * def cmd(spause):os.system(s) #C语言那一套 拿过来 C#那一套数据类型拿过来 import clr,System from System import String, Char, Int32,UInt16, Int64, Environment, IntPtr #导包 print(clr.AddReference(HslCommunication)) #现在可以当python自己的库来用了 import HslCommunication modbusTcpServer HslCommunication.ModBus.ModbusTcpServer(); modbusTcpServer.ServerStart(502); #必须指定泛型 否则无效 UInt16(65535) #初始化寄存器 指定3区 设定初始值 modbusTcpServer.Write(x3;100, UInt16(0));# 触发 1 2 3 4对应4流道 modbusTcpServer.Write(x3;101, UInt16(0));# 料号 0 1 2 modbusTcpServer.Write(x3;105, UInt16(0));# 结果 11(OK) 12(NG) 13(ERROR) modbusTcpServer.Write(x3;106, UInt16(0));# 心跳0/1 # 获取时间的函数 def gettime(): # 获取当前时间 dstr.set(f{time.strftime(%H:%M:%S)} 127.0.0.1:502) try: #必须try 否则要在界面控件创建完成后销毁前调用 图省事try完事 intReg_100 modbusTcpServer.ReadUInt16(100);#返回的是 Oper类型 不是int数值 intReg_101 modbusTcpServer.ReadUInt16(101); intReg_105 modbusTcpServer.ReadUInt16(105); intReg_106 modbusTcpServer.ReadUInt16(106); entry1.delete(0, end);entry1.insert(0,f{intReg_100.Content})# entry2.delete(0, end);entry2.insert(0,f{intReg_101.Content})# entry3.delete(0, end);entry3.insert(0,f{intReg_105.Content})# entry4.delete(0, end);entry4.insert(0,f{intReg_106.Content})# RegintReg_105.Content if(Reg0): rrr.config(textNone); rrr.config(bg#00aa00) if(Reg11): rrr.config(textok); rrr.config(bg#00ff00) if(Reg12): rrr.config(textng); rrr.config(bg#ff0000) if(Reg13): rrr.config(textError);rrr.config(bg#ffff00) # # except: pass # 每隔 1s 调用一次 gettime()函数来获取时间 win.after(200, gettime) # 生成动态字符串 dstr tk.StringVar() # 利用 textvariable 来实现文本变化 lb tk.Label(win,textvariabledstr,fggreen,font(微软雅黑,18)) lb.pack() gettime()# 调用生成时间的函数 tk.Label(win,text触发(D100[1]):,fgblack,font(微软雅黑,15)).place (x0,y40, width150, height30) entry1 tk.Entry(win)# 创建输入框控件 entry1.place (x300,y40, width60, height30)#relx0.01,relheight0.4 #.pack(padx20, pady20)# 放置输入框并设置位置 entry1.delete(0, end) entry1.insert(0,0)# 插入默认文本 print(entry1.get())# 得到输入框字符串 # entry1.delete(0, tk.END)# 删除所有字符 #----------------------------------------------------------------------------- def button_click_100():#按钮 modbusTcpServer.Write(x3;105, UInt16(0));#结果清零 modbusTcpServer.Write(x3;100, UInt16(1)); pass button_100 tk.Button(win,text触发写1,commandbutton_click_100) button_100.place (x380,y40, width60, height30) def button_click_100_2(): modbusTcpServer.Write(x3;105, UInt16(0));#结果清零 modbusTcpServer.Write(x3;100, UInt16(2)); pass button_100 tk.Button(win,text触发写2,commandbutton_click_100_2) button_100.place (x38080,y40, width60, height30) def button_click_100_3(): modbusTcpServer.Write(x3;105, UInt16(0));#结果清零 modbusTcpServer.Write(x3;100, UInt16(3)); pass button_100 tk.Button(win,text触发写3,commandbutton_click_100_3) button_100.place (x3808080,y40, width60, height30) def button_click_100_4(): modbusTcpServer.Write(x3;105, UInt16(0));#结果清零 modbusTcpServer.Write(x3;100, UInt16(4)); pass button_100 tk.Button(win,text触发写4,commandbutton_click_100_4) button_100.place (x380808080,y40, width60, height30) def button_click_101():#按钮 触发写0 modbusTcpServer.Write(x3;100, UInt16(0)); pass button_100 tk.Button(win,text触发写0,commandbutton_click_101) button_100.place (x38080808080,y40, width60, height30) def button_click_102():#按钮 触发写0 modbusTcpServer.Write(x3;105, UInt16(0));#结果清零 modbusTcpServer.Write(x3;100, UInt16(1)); time.sleep(0.2) modbusTcpServer.Write(x3;100, UInt16(0)); pass button_100 tk.Button(win,text触发写1(延时200)写0,commandbutton_click_102) button_100.place (x3808080808080,y40, width160, height30) #----------------------------------------------------------------------------- #----------------------------------------------------------------------------- def button_click_200():#按钮 modbusTcpServer.Write(x3;101, UInt16(0)); pass button_100 tk.Button(win,text料号写0,commandbutton_click_200) button_100.place (x380,y4040, width60, height30) def button_click_201():#按钮 触发写0 modbusTcpServer.Write(x3;101, UInt16(1)); pass button_100 tk.Button(win,text料号写1,commandbutton_click_201) button_100.place (x38080,y4040, width60, height30) def button_click_202():#按钮 触发写0 modbusTcpServer.Write(x3;101, UInt16(2)); pass button_100 tk.Button(win,text料号写2,commandbutton_click_202) button_100.place (x3808080,y4040, width160, height30) #----------------------------------------------------------------------------- tk.Label(win,text料号(D101[0/1/2]):,fgblack,font(微软雅黑,15)).place (x0,y4040, width180, height30) entry2 tk.Entry(win)# 创建输入框控件 entry2.place (x300,y4040, width60, height30)#relx0.01,relheight0.4 #.pack(padx20, pady20)# 放置输入框并设置位置 entry2.delete(0, end)# 插入默认文本 entry2.insert(0,0) print(entry2.get())# 得到输入框字符串 # entry1.delete(0, tk.END)# 删除所有字符 #relx、rely relheight、relwidth anchorNE tk.Label(win,text结果:(D105[11(OK)/12(NG)]):,fgblack,font(微软雅黑,15)).place (x0,y404040, width280, height30) entry3 tk.Entry(win)# 创建输入框控件 entry3.place (x300,y404040, width60, height30)#relx0.01,relheight0.4 #.pack(padx20, pady20)# 放置输入框并设置位置 entry3.delete(0, end)# 插入默认文本 entry3.insert(0,0) print(entry3.get())# 得到输入框字符串 rrrtk.Label(win,textok,fgblack,font(微软雅黑,15)) rrr.place (x400,y404040, width150, height30) #rrr.config(textok);rrr.config(bg#00ff00) #rrr.config(textng);rrr.config(bg#ff0000) rrr.config(textNone);rrr.config(bg#00aa00) tk.Label(win,text心跳:(D106[0/1]):,fgblack,font(微软雅黑,15)).place (x0,y40404040, width180, height30) entry4 tk.Entry(win)# 创建输入框控件 entry4.place (x300,y40404040, width60, height30)#relx0.01,relheight0.4 #.pack(padx20, pady20)# 放置输入框并设置位置 entry4.delete(0, end)# 插入默认文本 entry4.insert(0,0) print(entry4.get())# 得到输入框字符串 def QueryWindow(): if messagebox.showwarning(窗口关闭?): win.destroy() win.protocol(WM_DELETE_WINDOW, QueryWindow) win.mainloop()这样VM 写电脑IP 或者 127.0.0.1:502 就可以把这个程序当成PLC连接了.在手上没有PLC的情况下很方便使用.接下来分享一下其他用法:比如监视PLC值:(为了方便不写界面): 这样就可以实时观察到PLC的值import os,sys,time,win32api,win32con,win32gui def cmd(spause):os.system(s) from ctypes import * import clr,System#C语言那一套 拿过来 C#那一套数据类型拿过来 from System import String, Char, Int32,UInt16, Int64, Environment, IntPtr print(clr.AddReference(HslCommunication))#导包 import HslCommunication#现在可以当python自己的库来用了 modbus HslCommunication.ModBus.ModbusTcpNet( 192.168.1.10 );#modbus HslCommunication.ModBus.ModbusTcpNet( 127.0.0.1 ); while 1: cmd(cls) time.sleep(0.5) for i in range(15): time.sleep(0.3) intReg_100 modbus.ReadUInt16(100);Reg_100 intReg_100.Content; intReg_101 modbus.ReadUInt16(101);Reg_101 intReg_101.Content; intReg_105 modbus.ReadUInt16(105);Reg_105 intReg_105.Content; intReg_106 modbus.ReadUInt16(106); Reg_106 intReg_106.Content; print(f{time.strftime(%H:%M:%S)} \n\nD100:{Reg_100} D101:{Reg_100} D105:{Reg_100} D106:{Reg_100} \n) #intReg_100.IsSuccess,intReg_100.Content, # intReg_101.IsSuccess,intReg_101.Content, # intReg_105.IsSuccess,intReg_105.Content, # intReg_106.IsSuccess,intReg_106.Content) #pip install pywin32 numpy pythonnet -i https://pypi.tuna.tsinghua.edu.cn/simple下来在分享一个socket的脚本 用来调试VM很方便.def p(P):print(P); import os,sys,time,socket,_thread,threading 关闭时间60*2 from threading import Timer def close_window(): print(cl) os._exit(0) #root.destroy() # 销毁主窗口 #print(窗口已关闭) Timer(关闭时间, close_window).start()# def thread_it(func, *args): t threading.Thread(targetfunc, argsargs) t.setDaemon(True); t.start(); def Thread_ConnectSocket(ip,prot0,sendData,recv_flagTrue): 是否发送成功标志False while True: try: global tcp_client_socket tcp_client_socket socket.socket(socket.AF_INET, socket.SOCK_STREAM) tcp_client_socket.connect((ip, prot)) while True: try: tcp_client_socket.send(sendData.encode(utf-8))#发送 print(f{ip}:({prot}){sendData}!) if (recv_flag): #print(f等待对方回复!) recv_content tcp_client_socket.recv(1024)#接收 result recv_content.decode(utf-8) print(f收到对方发来的{result}!) print(fdone!) 是否发送成功标志True break except: 是否发送成功标志False break except socket.error: print(未连接尝试重连中..) time .sleep(1) if (是否发送成功标志): print(ffinish!) break ipport(127.0.0.1, 7930)#右侧 IPipport[0] PROTipport[1] #IP,PROT def Tcp_Send(S):#B_左(7931) thread_it(Thread_ConnectSocket,IP,PROT,S) import tkinter as tk root tk.Tk()#root.iconbitmap(mfc.ico) root.geometry(400x400100100) #root.resizable(0,0) root.title() lb__a tk.Label(root,textf(7931) :{IP} : {PROT},fggreen,font(微软雅黑,16)) lb__a.pack() def gettime(): dstr.set(time.strftime(%H:%M:%S)) try: pass root.after(1000, gettime)# 每隔 1s 调用一次. except: pass dstr tk.StringVar()# 定义动态字符串 lb tk.Label(root,textvariabledstr,fggreen,font(微软雅黑,20)) lb.pack() gettime() # 0 800V 一体轭 # 1 800V 中柱 # 2 800V 边柱 # #------------------------------料号0--------------------------------------------------------- #--------------------------------------------------------------------------------------------- def Cam0(): Tcp_Send(0) button tk.Button(root,text 料号0:800V 一体轭,bg#7CCD7C,width20, height2,commandCam0) button.place(relx0.15,rely0.2, width260, height30) #------------------------------料号1--------------------------------------------------------- #--------------------------------------------------------------------------------------------- def Cam1(): Tcp_Send(1)#Tcp_SendB Tcp_SendA #B_左(7931) A_右(7930) button tk.Button(root,text料号1:800V 中柱,bg#7CCD7C,width20, height2,commandCam1) #button.pack() button.place(relx0.15,rely0.3, width260, height30) #------------------------------料号2--------------------------------------------------------- #--------------------------------------------------------------------------------------------- def Cam2(): Tcp_Send(2)#Tcp_SendB Tcp_SendA #B_左(7931) A_右(7930) button tk.Button(root,text料号2:800V 边柱,bg#7CCD7C,width20, height2,commandCam2) #button.pack() button.place(relx0.15,rely0.4, width260, height30) #------------------------------料号: 噢噢噢噢--------------------------------------------------------- #--------------------------------------------------------------------------------------------- def CamT1(): Tcp_Send(T1) button tk.Button(root,text拍照1,bg#7CCD7C,width20, height2,commandCamT1) button.place(relx0.15,rely0.5, width260, height30) def CamT2(): Tcp_Send(T2) button tk.Button(root,text拍照2,bg#7CCD7C,width20, height2,commandCamT2) button.place(relx0.15,rely0.6, width260, height30) def CamT3(): Tcp_Send(T3) button tk.Button(root,text拍照3,bg#7CCD7C,width20, height2,commandCamT3) button.place(relx0.15,rely0.7, width260, height30) def CamT4(): Tcp_Send(T4) button tk.Button(root,text拍照4,bg#7CCD7C,width20, height2,commandCamT4) button.place(relx0.15,rely0.8, width260, height30) #--------------------------------------------------------------------------------------------- #------------------------------料号2--------------------------------------------------------- #--------------------------------------------------------------------------------------------- root.mainloop()虽然是工作用到的,但只是自己测试工具;放出来也不影响..
返回列表