|
一背景说明 计划通过西门子S7-1200(CPU1212C-DCDCDC),进行PLC与设备间的数据监控。但TIAPortalV15.1的交互数据非专业人员很难一目了然,又不想专门购买西门子的可编程屏幕,所以拟采用python-snap7模块实现上位机与PLC的通信,并将运行监控抽象到Tkinter绘制的可视化GUI上,方便测试维护人员操作。 前文已经完成了PLC上面的组态以及DB数据块创建等操作(【PLC+Python】上位机通过snap7实现与西门子PLC通讯并用Tkinter可视化——(1)PLCDB块创建-CSDN博客),这篇文章主要描述上位机中python的通讯以及可视化相关内容。二snap7介绍 snap7是一个由意大利程序员开发的基于以太网与西门子S7系列PLC的通讯的开源库,类似于C#的S7.Net,但是它不单只支持Python,还支持Java、C/C++、C#等语言。官网地址如下: snap7官网地址 而python-snap7则是snap7的python版本,有单独的文档以及使用说明,只能用于python,以下是官方文档及GitHub链接: python-snap7官网地址 python-snap7github链接 另外,官方还提供了支持多种操作系统的调试工具,可以方便预先进行通信调试,下载的方式如下:三通讯建立【1】博图TIA软件端放开PLC的通讯限制,并编译下载到PLC: (i)“常规——防护与安全——连接机制——勾选‘允许来自远程对象的PUT/GET通信访问’”: (ii)“数据库程序块——属性——取消勾选‘优化的块访问’”(该选项的作用可以参考TAI博图笔记知识B数据块的优化访问与非优化访问的区别):【2】python中导入snap7库,并测试一下能否连接:importsnap7#创建通讯客户端实例plcObj=snap7.client.Client()#连接至PLCplcObj.connect('192.168.5.1',0,1)#打印连接状态print(f"连接状态:{plcObj.get_connected()}")#关闭连接plcObj.disconnect()#打印连接状态print(f"连接状态:{plcObj.get_connected()}") 测试结果如下: 至此能够正常连接到PLC。四读取数据 根据【PLC+Python】上位机通过snap7实现与西门子PLC通讯并用Tkinter可视化——(1)PLCDB块创建-CSDN博客中创建的DB1,需要监控DB1中dig_ctrl(偏移值0.0——0.2)/dig_fbk(偏移值2.0——2.1),核心代码如下:importsnap7fromsnap7importutilDEV_CTRL_DATA=[[Falseforiinrange(3)]forjinrange(4)]#4台设备&3个控制数据(开/关/停)DEV_FBK_DATA=[[Falseforiinrange(2)]forjinrange(4)]##4台设备&2个反馈数据(全开/全关)#创建通讯客户端实例plcObj=snap7.client.Client()#连接至PLCplcObj.connect('192.168.5.1',0,1)#读取数据datazip=plcObj.db_read(1,0,4)#读取数据(DB序号为1,起始地址为0,读取长度4字节)#关闭连接plcObj.disconnect()#snap7解析DEV_CTRL_DATA[0][0]=util.get_bool(datazip,0,0)DEV_CTRL_DATA[0][1]=util.get_bool(datazip,0,1)DEV_CTRL_DATA[0][2]=util.get_bool(datazip,0,2)DEV_FBK_DATA[0][0]=util.get_bool(datazip,2,0)DEV_FBK_DATA[0][1]=util.get_bool(datazip,2,1)print("PLC数据解包:")print(f"[设备1]:停指令:{DEV_CTRL_DATA[0][0]};开指令{DEV_CTRL_DATA[0][1]};关指令{DEV_CTRL_DATA[0][2]}/全开位置:{DEV_FBK_DATA[0][0]};全关位置:{DEV_FBK_DATA[0][1]}\n") 测试结果如下:五发送数据 发送数据即是修改DB1中dig_ctrl(偏移值0.0——0.2)数据,核心代码如下:importsnap7fromsnap7importutil#创建通讯客户端实例plcObj=snap7.client.Client()#连接至PLCplcObj.connect('192.168.5.1',0,1)#发送数据boolData=bytearray(1)util.set_bool(boolData,0,0,True)util.set_bool(boolData,0,1,True)util.set_bool(boolData,0,2,True)plcObj.db_write(1,0,boolData)#关闭连接plcObj.disconnect() 其中,将DB1中偏移值0.0——0.2全部改成了TRUE,测试结果如下: (!!!更多数据类型的读写方法可以参考:Python使用python-snap7实现西门子PLC通讯-CSDN博客,其中写的非常详细!)六结合Tkinter界面进行操作 有了上面通讯建立/读写数据作为基础,再结合Tkinter模块做出的界面,就可以实现一个极简的GUI,方便对PLC连接的从机设备进行操作:【1】界面设计以及主要操作方法如下: (i)点击“连接”之后,首先读取输入的IP地址,格式校验正确之后,连接到对应的PLC; (ii)连接成功之后,点击“轮询”开启线程,以300ms的间隔轮询PLC中DB1(偏移量2.0——2.1)数据,并将读取到的数据结果体现在全关/全开LED灯亮灭上; (iii)点击下面的关/停/开按钮,发送指令到PLC中DB1(偏移量0.0——0.2),以操控设备运行状态; (iv)点击“断开”,即断开与PLC的连接;【2】完整代码:importtkinterastkfromtkinterimportttkfromtkinterimportmessageboxfromPILimportImage,ImageTkimportsnap7fromsnap7importutilimportthreadingimporttimeimportre#PLC连接参数PLC_IP='192.168.5.1'#PLC的IP地址IPV4_PATTERN=r'^(??:25[0-5]|2[0-4][0-9]|[01]?[0-9][0-9]?)\.){3}(?:25[0-5]|2[0-4][0-9]|[01]?[0-9][0-9]?)#IPv4地址的正则表达式PLC_RACK=0PLC_SLOT=1PLC_DB_ID=1#收发数据的DB模块idDEV_CTRL_DATA=[[Falseforiinrange(3)]forjinrange(4)]#4台设备&3个控制数据(开/关/停)DEV_FBK_DATA=[[Falseforiinrange(2)]forjinrange(4)]##4台设备&2个反馈数据(全开/全关)#全局变量,用于控制循环和连接状态is_connected=Falseis_running=Falseis_cmd_st=Falseis_cmd_op=Falseis_cmd_cl=False"""PLC监控系统类(PLC通信&主界面绘制)"""classPlcGUI(tk.Tk):def__init__(self):super().__init__()self.title('PLC监控系统')#设置窗口在打开时默认最大化self.state('zoomed')comm_frame=tk.LabelFrame(self,text="通信")comm_frame.grid(row=0,column=0,padx=10,pady=10,sticky='nw')comm_label=tk.Label(comm_frame,text="IP地址:")comm_label.grid(row=0,column=0,padx=10,pady=10,sticky='ew')self.comm_addr=ttk.Entry(comm_frame,width=20)self.comm_addr.insert(0,PLC_IP)#插入默认IP地址self.comm_addr.grid(row=0,column=1,padx=10,pady=10,sticky='e')self.comm_start=ttk.Button(comm_frame,text="连接",command=self.start_query)self.comm_start.grid(row=1,column=0,padx=10,pady=10,sticky='w')self.comm_stop=ttk.Button(comm_frame,text="断开",command=self.stop_query)self.comm_stop.grid(row=1,column=1,padx=10,pady=10,sticky='e')#CreateLEDFramesself.DEV_LISTS=[MonitorUnit(self,"设备1")]self.DEV_LISTS[0].grid(row=2,column=0,padx=10,pady=10,sticky='news')self.grid_rowconfigure(0,weight=1)self.grid_columnconfigure(0,weight=1)defread_ipv4_addr(self):#输入的IPV4地址校验ip_addr_input=self.comm_addr.get()#获取文本框内容ifre.match(IPV4_PATTERN,ip_addr_input):globalPLC_IPPLC_IP=ip_addr_inputreturnTrueelse:returnFalsedefconnect_plc(self):globalis_connectedtry:client=snap7.client.Client()client.connect(PLC_IP,PLC_RACK,PLC_SLOT)is_connected=Trueself.comm_start['text']='轮询'messagebox.showinfo("连接成功","已成功连接到PLC")exceptExceptionase:messagebox.showerror("连接失败",f"连接PLC时出错:{e}")defquery_data(self):globalis_connected,is_running,is_cmd_st,is_cmd_op,is_cmd_clifnotis_connected:returntry:client=snap7.client.Client()client.connect(PLC_IP,PLC_RACK,PLC_SLOT)whileis_running:#在这里添加您的数据查询代码datazip=client.db_read(PLC_DB_ID,0,4)#读取数据(DB序号为1,起始地址为0,读取长度4字节)#snap7解析DEV_CTRL_DATA[0][0]=util.get_bool(datazip,0,0)DEV_CTRL_DATA[0][1]=util.get_bool(datazip,0,1)DEV_CTRL_DATA[0][2]=util.get_bool(datazip,0,2)DEV_FBK_DATA[0][0]=util.get_bool(datazip,2,0)DEV_FBK_DATA[0][1]=util.get_bool(datazip,2,1)#监控界面LED灯状态刷新foriinrange(1):self.DEV_LISTS[i].chg_color_op('green')ifDEV_FBK_DATA[i][0]==Trueelseself.DEV_LISTS[i].chg_color_op('gray')self.DEV_LISTS[i].chg_color_cl('red')ifDEV_FBK_DATA[i][1]==Trueelseself.DEV_LISTS[i].chg_color_cl('gray')#控制命令下发boolData=bytearray(1)util.set_bool(boolData,0,0,is_cmd_st)util.set_bool(boolData,0,1,is_cmd_op)util.set_bool(boolData,0,2,is_cmd_cl)client.db_write(PLC_DB_ID,0,boolData)time.sleep(0.3)#查询数据间隔时间,您可以根据需要调整这个时间间隔exceptExceptionase:messagebox.showerror("查询错误",f"查询PLC数据时出错:{e}")is_connected=Falseself.comm_start['text']='连接'finally:client.disconnect()defstart_query(self):ifnotself.read_ipv4_addr():messagebox.showerror("校验失败","输入的IP地址不符合IPv4格式要求!")returnglobalis_runningifis_connected:is_running=Trueself.comm_start.state(['disabled'])#与PLC建立交互之后就禁用按钮,以示正在运行query_thread=threading.Thread(target=self.query_data)query_thread.setDaemon(True)query_thread.start()else:self.connect_plc()defstop_query(self):ifnotself.read_ipv4_addr():messagebox.showerror("校验失败","输入的IP地址不符合IPv4格式要求!")returnglobalis_connected,is_runningis_running=Falseself.comm_start.state(['!disabled'])#重启启用开始按钮ifis_connected:client=snap7.client.Client()client.connect(PLC_IP,PLC_RACK,PLC_SLOT)client.disconnect()is_connected=Falseself.comm_start['text']='连接'messagebox.showinfo("已断开","已成功断开与PLC的连接")"""监控单元类(每个监控单元包含两个LED灯以及三个控制按钮)"""classMonitorUnit(ttk.LabelFrame):def__init__(self,master,title):super().__init__(master,text=title)#创建LED灯self.close_cv=tk.Canvas(self,width=100,height=100,highlightthickness=0)#全关指示灯self.led_close=self.close_cv.create_oval(5,5,95,95,fill='gray')self.close_cv.create_text(50,50,text="全关",fill='black',font=("Helvetica",20))self.open_cv=tk.Canvas(self,width=100,height=100,highlightthickness=0)#全开指示灯self.led_open=self.open_cv.create_oval(5,5,95,95,fill='gray')self.open_cv.create_text(50,50,text="全开",fill='black',font=("Helvetica",20))#创建按钮self.close_button=ttk.Button(self,text="关",command=self.cmd_cl)#关命令self.stop_button=ttk.Button(self,text="停",command=self.cmd_st)#停命令self.open_button=ttk.Button(self,text="开",command=self.cmd_op)#开命令#控件布局self.close_cv.grid(row=0,column=0)self.open_cv.grid(row=0,column=2)self.close_button.grid(row=2,column=0)self.stop_button.grid(row=2,column=1)self.open_button.grid(row=2,column=2)#Configurerowandcolumnweightsforexpansionself.grid_rowconfigure(1,weight=1)self.grid_columnconfigure(0,weight=1)self.grid_columnconfigure(1,weight=1)self.grid_columnconfigure(2,weight=1)defcmd_st(self):#停按钮操作globalis_connected,is_running,is_cmd_st,is_cmd_op,is_cmd_clifnotis_connected:print("未连接\n")returnifnotis_running:print("未运行\n")returnis_cmd_op,is_cmd_cl=False,Falseis_cmd_st=notis_cmd_stprint(f"停按钮{is_cmd_st}\n")defcmd_op(self):#开按钮操作globalis_connected,is_running,is_cmd_st,is_cmd_op,is_cmd_clifnotis_connected:print("未连接\n")returnifnotis_running:print("未运行\n")returnis_cmd_st,is_cmd_cl=False,Falseis_cmd_op=notis_cmd_opprint(f"开按钮{is_cmd_op}\n")defcmd_cl(self):#关按钮操作globalis_connected,is_running,is_cmd_st,is_cmd_op,is_cmd_clifnotis_connected:print("未连接\n")returnifnotis_running:print("未运行\n")returnis_cmd_st,is_cmd_op=False,Falseis_cmd_cl=notis_cmd_clprint(f"关按钮{is_cmd_cl}\n")defchg_color_op(self,new_color):#修改开灯颜色self.open_cv.itemconfig(self.led_open,fill=new_color)defchg_color_cl(self,new_color):#修改关灯颜色self.close_cv.itemconfig(self.led_close,fill=new_color)if__name__=='__main__':app=PlcGUI()app.mainloop()【3】效果展示:七参考资料【1】【PLC+Python】上位机通过snap7实现与西门子PLC通讯并用Tkinter可视化——(1)PLCDB块创建-CSDN博客【2】Python使用python-snap7实现西门子PLC通讯-CSDN博客【3】Python使用python-snap7实现西门子PLC通讯_pythonsnap7-CSDN博客【4】python通过S7协议读取西门子200smart数据_python读取西门子plc数据-CSDN博客【5】C++上位软件通过Snap7开源库访问西门子S7-1200/S7-1500数据块的方法_snap7是什么-CSDN博客【6】S7协议调试工具&模拟器--snap7demoserver_partner_client-CSDN博客【7】Python+OPCUA+s7-1200+MySql+Grafana实现工业数据可视化看板开发_grafana中mysql看板-CSDN博客
|
|