当前位置:   article > 正文

【PLC+Python】snap7/Tkinter实现与西门子PLC通讯/可视化(2)——Python上位机_plc标签通讯与python

plc标签通讯与python

 一 背景说明

        计划通过西门子 S7-1200(CPU 1212C-DCDCDC),进行PLC与设备间的数据监控。但 TIA Portal V15.1 的交互数据非专业人员很难一目了然,又不想专门购买西门子的可编程屏幕,所以拟采用 python-snap7 模块实现上位机与PLC的通信,并将运行监控抽象到 Tkinter 绘制的可视化GUI上,方便测试维护人员操作

        前文已经完成了PLC上面的组态以及DB数据块创建等操作(【PLC+Python】上位机通过snap7实现与西门子PLC通讯并用Tkinter可视化——(1)PLC DB块创建-CSDN博客),这篇文章主要描述上位机中python的通讯以及可视化相关内容。

二 snap7介绍

        snap7是一个由意大利程序员开发的基于以太网与西门子S7系列PLC的通讯的开源库,类似于C#的S7.Net,但是它不单只支持Python,还支持Java、C/C++、C#等语言。官网地址如下:

        snap7官网地址

        而python-snap7则是snap7的python版本,有单独的文档以及使用说明,只能用于python,以下是官方文档及GitHub链接:

        python-snap7官网地址

        python-snap7 github链接

        另外,官方还提供了支持多种操作系统的调试工具,可以方便预先进行通信调试,下载的方式如下:

三 通讯建立

【1】博图TIA软件端放开PLC的通讯限制,并编译下载到PLC:

        (i)“常规——防护与安全——连接机制——勾选‘允许来自远程对象的PUT/GET通信访问’”:

        (ii)“数据库程序块——属性——取消勾选‘优化的块访问’”(该选项的作用可以参考TAI博图笔记知识:DB数据块的优化访问与非优化访问的区别):

【2】python中导入snap7库,并测试一下能否连接:

  1. import snap7
  2. # 创建通讯客户端实例
  3. plcObj = snap7.client.Client()
  4. # 连接至PLC
  5. plcObj.connect('192.168.5.1', 0, 1)
  6. # 打印连接状态
  7. print(f"连接状态:{plcObj.get_connected()}")
  8. # 关闭连接
  9. plcObj.disconnect()
  10. # 打印连接状态
  11. print(f"连接状态:{plcObj.get_connected()}")

        测试结果如下:

        至此能够正常连接到PLC。

四 读取数据

        根据【PLC+Python】上位机通过snap7实现与西门子PLC通讯并用Tkinter可视化——(1)PLC DB块创建-CSDN博客中创建的DB1,需要监控DB1中 dig_ctrl(偏移值0.0——0.2) / dig_fbk(偏移值2.0——2.1),核心代码如下:

  1. import snap7
  2. from snap7 import util
  3. DEV_CTRL_DATA = [[False for i in range(3)] for j in range(4)] #4台设备&3个控制数据(开/关/停)
  4. DEV_FBK_DATA = [[False for i in range(2)] for j in range(4)] ##4台设备&2个反馈数据(全开/全关)
  5. # 创建通讯客户端实例
  6. plcObj = snap7.client.Client()
  7. # 连接至PLC
  8. plcObj.connect('192.168.5.1', 0, 1)
  9. # 读取数据
  10. datazip = plcObj.db_read(1, 0, 4) # 读取数据(DB序号为1,起始地址为0,读取长度4字节)
  11. # 关闭连接
  12. plcObj.disconnect()
  13. # snap7解析
  14. DEV_CTRL_DATA[0][0] = util.get_bool(datazip, 0, 0)
  15. DEV_CTRL_DATA[0][1] = util.get_bool(datazip, 0, 1)
  16. DEV_CTRL_DATA[0][2] = util.get_bool(datazip, 0, 2)
  17. DEV_FBK_DATA[0][0] = util.get_bool(datazip, 2, 0)
  18. DEV_FBK_DATA[0][1] = util.get_bool(datazip, 2, 1)
  19. print("PLC数据解包:")
  20. print(
  21. 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"
  22. )

        测试结果如下:

五 发送数据

        发送数据即是修改DB1中 dig_ctrl(偏移值0.0——0.2)数据,核心代码如下:

  1. import snap7
  2. from snap7 import util
  3. # 创建通讯客户端实例
  4. plcObj = snap7.client.Client()
  5. # 连接至PLC
  6. plcObj.connect('192.168.5.1', 0, 1)
  7. # 发送数据
  8. boolData = bytearray(1)
  9. util.set_bool(boolData, 0, 0, True)
  10. util.set_bool(boolData, 0, 1, True)
  11. util.set_bool(boolData, 0, 2, True)
  12. plcObj.db_write(1, 0, boolData)
  13. # 关闭连接
  14. 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】完整代码:

  1. import tkinter as tk
  2. from tkinter import ttk
  3. from tkinter import messagebox
  4. from PIL import Image, ImageTk
  5. import snap7
  6. from snap7 import util
  7. import threading
  8. import time
  9. import re
  10. # PLC连接参数
  11. PLC_IP = '192.168.5.1' # PLC的IP地址
  12. 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地址的正则表达式
  13. PLC_RACK = 0
  14. PLC_SLOT = 1
  15. PLC_DB_ID = 1 #收发数据的DB模块id
  16. DEV_CTRL_DATA = [[False for i in range(3)] for j in range(4)] #4台设备&3个控制数据(开/关/停)
  17. DEV_FBK_DATA = [[False for i in range(2)] for j in range(4)] ##4台设备&2个反馈数据(全开/全关)
  18. # 全局变量,用于控制循环和连接状态
  19. is_connected = False
  20. is_running = False
  21. is_cmd_st = False
  22. is_cmd_op = False
  23. is_cmd_cl = False
  24. """PLC监控系统类(PLC通信 & 主界面绘制)"""
  25. class PlcGUI(tk.Tk):
  26. def __init__(self):
  27. super().__init__()
  28. self.title('PLC监控系统')
  29. # 设置窗口在打开时默认最大化
  30. self.state('zoomed')
  31. comm_frame = tk.LabelFrame(self, text="通信")
  32. comm_frame.grid(row=0, column=0, padx=10, pady=10, sticky='nw')
  33. comm_label = tk.Label(comm_frame, text="IP地址:")
  34. comm_label.grid(row=0, column=0, padx=10, pady=10, sticky='ew')
  35. self.comm_addr = ttk.Entry(comm_frame, width=20)
  36. self.comm_addr.insert(0, PLC_IP) # 插入默认IP地址
  37. self.comm_addr.grid(row=0, column=1, padx=10, pady=10, sticky='e')
  38. self.comm_start = ttk.Button(comm_frame, text="连接", command=self.start_query)
  39. self.comm_start.grid(row=1, column=0, padx=10, pady=10, sticky='w')
  40. self.comm_stop = ttk.Button(comm_frame, text="断开", command=self.stop_query)
  41. self.comm_stop.grid(row=1, column=1, padx=10, pady=10, sticky='e')
  42. # Create LEDFrames
  43. self.DEV_LISTS = [
  44. MonitorUnit(self, "设备1")
  45. ]
  46. self.DEV_LISTS[0].grid(row=2, column=0, padx=10, pady=10, sticky='news')
  47. self.grid_rowconfigure(0, weight=1)
  48. self.grid_columnconfigure(0, weight=1)
  49. def read_ipv4_addr(self): #输入的IPV4地址校验
  50. ip_addr_input = self.comm_addr.get() #获取文本框内容
  51. if re.match(IPV4_PATTERN, ip_addr_input):
  52. global PLC_IP
  53. PLC_IP = ip_addr_input
  54. return True
  55. else:
  56. return False
  57. def connect_plc(self):
  58. global is_connected
  59. try:
  60. client = snap7.client.Client()
  61. client.connect(PLC_IP, PLC_RACK, PLC_SLOT)
  62. is_connected = True
  63. self.comm_start['text'] = '轮询'
  64. messagebox.showinfo("连接成功", "已成功连接到PLC")
  65. except Exception as e:
  66. messagebox.showerror("连接失败", f"连接PLC时出错:{e}")
  67. def query_data(self):
  68. global is_connected, is_running, is_cmd_st, is_cmd_op, is_cmd_cl
  69. if not is_connected:
  70. return
  71. try:
  72. client = snap7.client.Client()
  73. client.connect(PLC_IP, PLC_RACK, PLC_SLOT)
  74. while is_running:
  75. # 在这里添加您的数据查询代码
  76. datazip = client.db_read(PLC_DB_ID, 0, 4) # 读取数据(DB序号为1,起始地址为0,读取长度4字节)
  77. # snap7解析
  78. DEV_CTRL_DATA[0][0] = util.get_bool(datazip, 0, 0)
  79. DEV_CTRL_DATA[0][1] = util.get_bool(datazip, 0, 1)
  80. DEV_CTRL_DATA[0][2] = util.get_bool(datazip, 0, 2)
  81. DEV_FBK_DATA[0][0] = util.get_bool(datazip, 2, 0)
  82. DEV_FBK_DATA[0][1] = util.get_bool(datazip, 2, 1)
  83. # 监控界面LED灯状态刷新
  84. for i in range(1):
  85. self.DEV_LISTS[i].chg_color_op('green') if DEV_FBK_DATA[i][0] == True else self.DEV_LISTS[i].chg_color_op('gray')
  86. self.DEV_LISTS[i].chg_color_cl('red') if DEV_FBK_DATA[i][1] == True else self.DEV_LISTS[i].chg_color_cl('gray')
  87. # 控制命令下发
  88. boolData = bytearray(1)
  89. util.set_bool(boolData, 0, 0, is_cmd_st)
  90. util.set_bool(boolData, 0, 1, is_cmd_op)
  91. util.set_bool(boolData, 0, 2, is_cmd_cl)
  92. client.db_write(PLC_DB_ID, 0, boolData)
  93. time.sleep(0.3) # 查询数据间隔时间,您可以根据需要调整这个时间间隔
  94. except Exception as e:
  95. messagebox.showerror("查询错误", f"查询PLC数据时出错:{e}")
  96. is_connected = False
  97. self.comm_start['text'] = '连接'
  98. finally:
  99. client.disconnect()
  100. def start_query(self):
  101. if not self.read_ipv4_addr():
  102. messagebox.showerror("校验失败", "输入的IP地址不符合IPv4格式要求!")
  103. return
  104. global is_running
  105. if is_connected:
  106. is_running = True
  107. self.comm_start.state(['disabled']) #与PLC建立交互之后就禁用按钮,以示正在运行
  108. query_thread = threading.Thread(target=self.query_data)
  109. query_thread.setDaemon(True)
  110. query_thread.start()
  111. else:
  112. self.connect_plc()
  113. def stop_query(self):
  114. if not self.read_ipv4_addr():
  115. messagebox.showerror("校验失败", "输入的IP地址不符合IPv4格式要求!")
  116. return
  117. global is_connected, is_running
  118. is_running = False
  119. self.comm_start.state(['!disabled']) # 重启启用开始按钮
  120. if is_connected:
  121. client = snap7.client.Client()
  122. client.connect(PLC_IP, PLC_RACK, PLC_SLOT)
  123. client.disconnect()
  124. is_connected = False
  125. self.comm_start['text'] = '连接'
  126. messagebox.showinfo("已断开", "已成功断开与PLC的连接")
  127. """监控单元类(每个监控单元包含两个LED灯以及三个控制按钮)"""
  128. class MonitorUnit(ttk.LabelFrame):
  129. def __init__(self, master, title):
  130. super().__init__(master, text=title)
  131. # 创建LED灯
  132. self.close_cv = tk.Canvas(self, width=100, height=100, highlightthickness=0) #全关指示灯
  133. self.led_close = self.close_cv.create_oval(5, 5, 95, 95, fill='gray')
  134. self.close_cv.create_text(50, 50, text="全关", fill='black', font=("Helvetica", 20))
  135. self.open_cv = tk.Canvas(self, width=100, height=100, highlightthickness=0) #全开指示灯
  136. self.led_open = self.open_cv.create_oval(5, 5, 95, 95, fill='gray')
  137. self.open_cv.create_text(50, 50, text="全开", fill='black', font=("Helvetica", 20))
  138. # 创建按钮
  139. self.close_button = ttk.Button(self, text="关", command=self.cmd_cl) #关命令
  140. self.stop_button = ttk.Button(self, text="停", command=self.cmd_st) #停命令
  141. self.open_button = ttk.Button(self, text="开", command=self.cmd_op) #开命令
  142. #控件布局
  143. self.close_cv.grid(row=0, column=0)
  144. self.open_cv.grid(row=0, column=2)
  145. self.close_button.grid(row=2, column=0)
  146. self.stop_button.grid(row=2, column=1)
  147. self.open_button.grid(row=2, column=2)
  148. # Configure row and column weights for expansion
  149. self.grid_rowconfigure(1, weight=1)
  150. self.grid_columnconfigure(0, weight=1)
  151. self.grid_columnconfigure(1, weight=1)
  152. self.grid_columnconfigure(2, weight=1)
  153. def cmd_st(self): #停按钮操作
  154. global is_connected, is_running, is_cmd_st, is_cmd_op, is_cmd_cl
  155. if not is_connected:
  156. print("未连接\n")
  157. return
  158. if not is_running:
  159. print("未运行\n")
  160. return
  161. is_cmd_op, is_cmd_cl = False, False
  162. is_cmd_st = not is_cmd_st
  163. print(f"停按钮{is_cmd_st}\n")
  164. def cmd_op(self): #开按钮操作
  165. global is_connected, is_running, is_cmd_st, is_cmd_op, is_cmd_cl
  166. if not is_connected:
  167. print("未连接\n")
  168. return
  169. if not is_running:
  170. print("未运行\n")
  171. return
  172. is_cmd_st, is_cmd_cl = False, False
  173. is_cmd_op = not is_cmd_op
  174. print(f"开按钮{is_cmd_op}\n")
  175. def cmd_cl(self): #关按钮操作
  176. global is_connected, is_running, is_cmd_st, is_cmd_op, is_cmd_cl
  177. if not is_connected:
  178. print("未连接\n")
  179. return
  180. if not is_running:
  181. print("未运行\n")
  182. return
  183. is_cmd_st, is_cmd_op = False, False
  184. is_cmd_cl = not is_cmd_cl
  185. print(f"关按钮{is_cmd_cl}\n")
  186. def chg_color_op(self, new_color): #修改开灯颜色
  187. self.open_cv.itemconfig(self.led_open, fill=new_color)
  188. def chg_color_cl(self, new_color): #修改关灯颜色
  189. self.close_cv.itemconfig(self.led_close, fill=new_color)
  190. if __name__ == '__main__':
  191. app = PlcGUI()
  192. app.mainloop()

【3】效果展示:

七 参考资料

【1】【PLC+Python】上位机通过snap7实现与西门子PLC通讯并用Tkinter可视化——(1)PLC DB块创建-CSDN博客

【2】Python使用python-snap7实现西门子PLC通讯-CSDN博客

【3】Python使用python-snap7实现西门子PLC通讯_python snap7-CSDN博客

【4】python通过S7协议读取西门子200smart数据_python读取西门子plc数据-CSDN博客

【5】C++上位软件通过Snap7开源库访问西门子S7-1200/S7-1500数据块的方法_snap7是什么-CSDN博客

【6】S7 协议调试工具 & 模拟器 --snap7 demo server_partner_client-CSDN博客

【7】Python + OPCUA + s7-1200 + MySql + Grafana实现工业数据可视化看板开发_grafana中mysql看板-CSDN博客

声明:本文内容由网友自发贡献,不代表【wpsshop博客】立场,版权归原作者所有,本站不承担相应法律责任。如您发现有侵权的内容,请联系我们。转载请注明出处:https://www.wpsshop.cn/w/小丑西瓜9/article/detail/685321
推荐阅读
相关标签
  

闽ICP备14008679号