【PLC+Python】上位机通过snap7实现与西门子PLC通讯并用Tkinter可视化——(2)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库,并测试一下能否连接:

import snap7

# 创建通讯客户端实例
plcObj = snap7.client.Client()
# 连接至PLC
plcObj.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)PLC DB块创建-CSDN博客中创建的DB1,需要监控DB1中 dig_ctrl(偏移值0.0——0.2) / dig_fbk(偏移值2.0——2.1),核心代码如下:

import snap7
from snap7 import util

DEV_CTRL_DATA = [[False for i in range(3)] for j in range(4)]   #4台设备&3个控制数据(开/关/停)
DEV_FBK_DATA = [[False for i in range(2)] for j in range(4)]    ##4台设备&2个反馈数据(全开/全关)

# 创建通讯客户端实例
plcObj = snap7.client.Client()
# 连接至PLC
plcObj.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)数据,核心代码如下:

import snap7
from snap7 import util

# 创建通讯客户端实例
plcObj = snap7.client.Client()
# 连接至PLC
plcObj.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】完整代码:

import tkinter as tk
from tkinter import ttk
from tkinter import messagebox
from PIL import Image, ImageTk

import snap7
from snap7 import util
import threading
import time
import re


# 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 = 0
PLC_SLOT = 1
PLC_DB_ID = 1   #收发数据的DB模块id


DEV_CTRL_DATA = [[False for i in range(3)] for j in range(4)]   #4台设备&3个控制数据(开/关/停)
DEV_FBK_DATA = [[False for i in range(2)] for j in range(4)]    ##4台设备&2个反馈数据(全开/全关)

# 全局变量,用于控制循环和连接状态
is_connected = False
is_running = False
is_cmd_st = False
is_cmd_op = False
is_cmd_cl = False


"""PLC监控系统类(PLC通信 & 主界面绘制)"""
class PlcGUI(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')

        # Create LEDFrames
        self.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)


    def read_ipv4_addr(self):   #输入的IPV4地址校验
        ip_addr_input = self.comm_addr.get()    #获取文本框内容
        if re.match(IPV4_PATTERN, ip_addr_input):
            global PLC_IP
            PLC_IP = ip_addr_input
            return True
        else:
            return False


    def connect_plc(self):
        global is_connected
        try:
            client = snap7.client.Client()
            client.connect(PLC_IP, PLC_RACK, PLC_SLOT)
            is_connected = True
            self.comm_start['text'] = '轮询'
            messagebox.showinfo("连接成功", "已成功连接到PLC")
        except Exception as e:
            messagebox.showerror("连接失败", f"连接PLC时出错:{e}")

    def query_data(self):
        global is_connected, is_running, is_cmd_st, is_cmd_op, is_cmd_cl
        if not is_connected:
            return
        try:
            client = snap7.client.Client()
            client.connect(PLC_IP, PLC_RACK, PLC_SLOT)
            while is_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灯状态刷新
                for i in range(1):
                    self.DEV_LISTS[i].chg_color_op('green') if DEV_FBK_DATA[i][0] == True else self.DEV_LISTS[i].chg_color_op('gray')
                    self.DEV_LISTS[i].chg_color_cl('red') if DEV_FBK_DATA[i][1] == True else self.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)  # 查询数据间隔时间,您可以根据需要调整这个时间间隔
        except Exception as e:
            messagebox.showerror("查询错误", f"查询PLC数据时出错:{e}")
            is_connected = False
            self.comm_start['text'] = '连接'
        finally:
            client.disconnect()

    def start_query(self):
        if not self.read_ipv4_addr():
            messagebox.showerror("校验失败", "输入的IP地址不符合IPv4格式要求!")
            return

        global is_running
        if is_connected:
            is_running = True
            self.comm_start.state(['disabled']) #与PLC建立交互之后就禁用按钮,以示正在运行
            query_thread = threading.Thread(target=self.query_data)
            query_thread.setDaemon(True)
            query_thread.start()
        else:
            self.connect_plc()

    def stop_query(self):
        if not self.read_ipv4_addr():
            messagebox.showerror("校验失败", "输入的IP地址不符合IPv4格式要求!")
            return

        global is_connected, is_running
        is_running = False
        self.comm_start.state(['!disabled'])  # 重启启用开始按钮
        if is_connected:
            client = snap7.client.Client()
            client.connect(PLC_IP, PLC_RACK, PLC_SLOT)
            client.disconnect()
            is_connected = False
            self.comm_start['text'] = '连接'
            messagebox.showinfo("已断开", "已成功断开与PLC的连接")


"""监控单元类(每个监控单元包含两个LED灯以及三个控制按钮)"""
class MonitorUnit(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)

        # Configure row and column weights for expansion
        self.grid_rowconfigure(1, weight=1)
        self.grid_columnconfigure(0, weight=1)
        self.grid_columnconfigure(1, weight=1)
        self.grid_columnconfigure(2, weight=1)


    def cmd_st(self):    #停按钮操作
        global is_connected, is_running, is_cmd_st, is_cmd_op, is_cmd_cl
        if not is_connected:
            print("未连接\n")
            return
        if not is_running:
            print("未运行\n")
            return
        is_cmd_op, is_cmd_cl = False, False
        is_cmd_st = not is_cmd_st
        print(f"停按钮{is_cmd_st}\n")

    def cmd_op(self):    #开按钮操作
        global is_connected, is_running, is_cmd_st, is_cmd_op, is_cmd_cl
        if not is_connected:
            print("未连接\n")
            return
        if not is_running:
            print("未运行\n")
            return
        is_cmd_st, is_cmd_cl = False, False
        is_cmd_op = not is_cmd_op
        print(f"开按钮{is_cmd_op}\n")

    def cmd_cl(self):    #关按钮操作
        global is_connected, is_running, is_cmd_st, is_cmd_op, is_cmd_cl
        if not is_connected:
            print("未连接\n")
            return
        if not is_running:
            print("未运行\n")
            return
        is_cmd_st, is_cmd_op = False, False
        is_cmd_cl = not is_cmd_cl
        print(f"关按钮{is_cmd_cl}\n")


    def chg_color_op(self, new_color):    #修改开灯颜色
        self.open_cv.itemconfig(self.led_open, fill=new_color)

    def chg_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)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博客

本文来自互联网用户投稿,该文观点仅代表作者本人,不代表本站立场。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。如若转载,请注明出处:/a/522814.html

如若内容造成侵权/违法违规/事实不符,请联系我们进行投诉反馈qq邮箱809451989@qq.com,一经查实,立即删除!

相关文章

MQTT 5.0 报文解析 01:CONNECT 与 CONNACK

在 MQTT 5.0 报文介绍 中,我们介绍了 MQTT 报文由固定报头、可变报头和有效载荷三个部分组成,以及可变字节整数、属性这类 MQTT 报文中的通用概念。现在,我们将按照实际的用途来进一步介绍各个类型的报文的组成。首先,我们将专注于…

初识Java中的NIO

1.概述 Java NIO 全称java non-blocking IO ,是指 JDK 提供的新 API。从 JDK1.4 开始,Java 提供了一系列改进的输入/输出新特性,被统称为 NIO(即 New IO),是同步非阻塞的。NIO采用内存映射文件的方式来处理输入输出,NI…

电池UN38.3测试电池模组蓄电池检测报告出具

UN38.3运输报告规定了以下类型的电池: 1. 锂金属电池:包括锂金属氧化物电池(如锂锰电池、锂铁电池、锂钴电池),锂-硫电池等。 2. 锂离子电池:包括锂聚合物电池、锂离子聚合物电池、锂离子含水电池等。 3.…

软考-系统集成项目管理中级-新一代信息技术

本章历年考题分值统计 本章重点常考知识点汇总清单(掌握部分可直接理解记忆) 本章历年考题及答案解析 32、2019 年上半年第 23 题 云计算通过网络提供可动态伸缩的廉价计算能力,(23)不属于云计算的特点。 A.虚拟化 B.高可扩展性 C.按需服务 D.优化本地存储 【参考…

记录一个C语言基础错误——scanf()输入!

今天犯了一个很傻的问题&#xff0c;记录一下。 Lint’Code 题目&#xff1a; 错误代码 #include <stdio.h>int function(int a, int b, int c, int x, int y) {// Write your code hereprintf("In function: %d\n", x y);x - y;return (a * (x y) * (x y…

深圳MES系统服务商

盈致MES系统是一款专业的制造执行系统&#xff0c;专注于为企业提供全面的生产管理解决方案。该系统涵盖了制造数据管理、计划排程管理、生产调度管理、库存管理、质量管理等功能模块&#xff0c;能够帮助企业实现生产过程的数字化、智能化和精益化。 盈致MES系统具有以下特点和…

openharmony launcher 调研笔记(01)数据初始化

最近在看launcher&#xff0c;把自己调研的点做个笔记&#xff0c;持续修改更新中&#xff0c;个人笔记酌情参考。 初始化MainAbility ● common 等 包以 三方库形式 被引入使用 在每个包中的oh-package.json5 文件有配置 { "devDependencies": {}, "n…

宏的使用(C语言详解)

在写一个代码生成可执行文件的过程需要经过编译和链接&#xff0c;编译又要经过三部&#xff1a;预处理&#xff0c;编译&#xff0c;汇编。 #define定义的变量和宏就是在预处理阶段会处理的。 一个简单的宏定义&#xff1a; #include<stdio.h>; #define Max(a,b) a>…

如何利用义乌购API实现用户个性化推荐及商品详情 API 返回值说明

用户个性化推荐 利用义乌购API实现用户个性化推荐是一个涉及多个步骤的过程&#xff0c;主要包括数据收集、用户画像构建、推荐算法选择与实施以及推荐结果的展示与反馈。以下是一个大致的流程和步骤说明&#xff1a; 一、数据收集&#xff1a; 1.用户行为数据&#xff1a;收…

如何理解单片机 pwm 控制的基本原理?

单片机PWM&#xff08;脉宽调制&#xff09;控制的基本原理&#xff0c;简而言之&#xff0c;就是通过改变脉冲信号的宽度&#xff08;占空比&#xff09;来控制模拟电路。这涉及到单片机生成一系列脉冲信号&#xff0c;每个脉冲信号的高电平持续时间和整个周期的比值&#xff…

桌面便签电脑版哪个好?好用便签是哪款

在快节奏的现代生活中&#xff0c;桌面便签软件成为了我们不可或缺的助手。它们轻便、灵活&#xff0c;能够随时记录重要事项&#xff0c;提醒我们按时完成各项任务。面对市面上众多的便签软件&#xff0c;选择一款既实用又好用的便签显得尤为重要。经过深入体验&#xff0c;我…

C++ | Leetcode C++题解之第12题整数转罗马数字

题目&#xff1a; 题解&#xff1a; const string thousands[] {"", "M", "MM", "MMM"}; const string hundreds[] {"", "C", "CC", "CCC", "CD", "D", "DC&qu…

【Angular性能优化】项目8版本加载速度缓慢、白屏时间、首页渲染性能优化方案

前言 随着业务的代码一点点增加,加上Angular的项目本身就比 vue、react 的重一些,随之而来的启动速度,更改文件后编译速度,以及打包速度也会变慢,于是乎想着优化下我们的项目。 本文章主要说的是 : 打包Angular项目的一些配置,性能优化方面的方案打包后,用户进入页面…

vue vue3 手写 动态加载组件

效果展示 一、需求背景&#xff1a; # vue3 项目涉及很多图表加载、表格加载 #考虑手写一个动态加载组件 二、实现思路 通过一个加载状态变量&#xff0c;通过v-if判断&#xff0c;加载状态的变量等于哪一个&#xff0c;动态加载组件内部就显示的哪一块组件。 三、实现效果…

Coursera上托福专项课程03:TOEFL Test-Taking Strategies 学习笔记(完结)

TOEFL Preparation Specialization Specialization Certificate TOEFL Test-Taking Strategies Course Certificate 本文是学习 TOEFL Test-Taking Strategies 这门课的学习笔记&#xff0c;如有侵权&#xff0c;请联系删除。 文章目录 TOEFL Preparation SpecializationTOEF…

《web应用技术》第二次课后练习

练习目的&#xff1a; 1、form表单值的获取 2、mysql数据库及表的建立&#xff08;参见视频&#xff09; 3、maven项目的建立&#xff08;参见视频&#xff09; 4、使用jdbc进行数据库的增删改查操作。&#xff08;参见源代码&#xff09; 具体如下&#xff1a; 1、继续理…

以Kotti项目为例使用pytest测试项目

在维护和构建大型项目时&#xff0c;单独一个一个手工测试代码已经不适用了&#xff0c;这时候就要用专门的测试框架进行测试。让我们以Kotti项目为例&#xff0c;用pytest这个测试框架进行实践测试吧。 使用python3.10 Ubuntu 系统 准备工作 下载和安装kotti库 pip install…

并查集python实现及题目练习

文章目录 1. 并查集概念1.1 理解并查集&#xff1a;简介与应用场景1.2 Python 实现并查集及优化策略1.3 扁平化栈实现1.4 分析并查集的时间复杂度 2. 情侣牵手3. 相似字符串4. 岛屿数量 如果想了解并查集基础推荐去看左程云大神的算法讲解&#xff0c;非常不错&#xff0c;b站和…

【一】学习TDengine-总结新技术学习的思考

学习TDengine-总结新技术学习的思考 概要 因业务场景需要我们开始接触时序数据库&#xff0c;于是开始根据以往的学习经验着手熟悉这一项新技术&#xff0c;学习也是一种技能&#xff0c;成功的人越容易成功&#xff0c;因为他们掌握了一套成功的方法&#xff0c;这里提到学习经…

【LeetCode热题100】74. 搜索二维矩阵(二分)

一.题目要求 给你一个满足下述两条属性的 m x n 整数矩阵&#xff1a; 每行中的整数从左到右按非严格递增顺序排列。每行的第一个整数大于前一行的最后一个整数。 给你一个整数 target &#xff0c;如果 target 在矩阵中&#xff0c;返回 true &#xff1b;否则&#xff0c;…