本文还有配套的精品资源,点击获取 menu-r.4af5f7ec.gif

简介:用Python通过标准以太网TCP/IP协议直接对接西门子S7-300和S7-400 PLC,无需额外OPC服务器或驱动软件;支持从xls文件(如plc_tags.xls、tags.xls)自动加载PLC变量地址与数据类型,完成批量读取和写入操作;采集结果可一键导出到Excel,也支持将Excel中编辑好的值回写至PLC;附带基于PyQt5开发的简易图形界面PLCData.ui,运行截图plc.png直观展示交互效果;包含多个真实测试用例文件(tag_check_sample.xls、tag_check_2020-05-27.xls等),所有核心脚本plc.py、PLCData.py、mycode.py均已开源,适配Python 3.7环境,requirements.txt明确依赖项,目录结构清晰,开箱即可调试运行。

1. 项目概述:为什么这套PLC数据桥接方案值得你花15分钟读完

我干工业自动化集成这行快十二年了,从最早用Step7+WinCC手动配OPC通道,到后来写C#调DLL封装S7.NET,再到近几年带团队做边缘侧Python数据采集——踩过的坑比走过的网线还长。今天要聊的这个项目,不是什么高大上的云平台Demo,而是一个我在某汽车零部件产线调试现场顺手撸出来、后来被三个不同客户直接拿去当“数据看板底座”用的轻量级工具链。它解决的是一个特别具体又特别痛的问题:怎么让产线工程师、工艺员甚至班组长,不装任何西门子授权软件、不配OPC服务器、不碰TIA Portal,就能在自己电脑上点几下,把PLC里几十个关键变量(比如温度设定值、压力报警阈值、计数器当前值)从Excel里读进来,改完再一键写回PLC?

关键词里的“Python直连西门子S7-300/400”不是噱头——它真就只靠标准TCP/IP协议栈和python-snap7这个开源库,连PLC的PG/PC接口都不用开;“Excel标签”也不是简单读个CSV,而是能自动识别xls文件里“地址列”(如DB1.DBW2)、“数据类型列”(INT、REAL、BOOL)、“描述列”(“主轴温度设定值”),甚至支持DB块编号、起始字节偏移、位号(如DB1.DBX0.3)这种细粒度解析;QT界面那部分,我特意没用QML搞炫酷动画,就用PyQt5拖了几个基础控件,因为产线电脑很多还是Win7+IE内核老系统,稳定压倒一切。你看到的plc.png截图里那个朴素的窗口,背后是PLCData.py里对信号槽的精细控制:比如点击“读取全部”,它会先按DB块分组排序请求(避免跨DB频繁握手),再批量发读指令;点击“写入选中”,它会校验Excel里修改过的单元格是否符合数据类型(REAL不能输文字,BOOL只能是0/1或TRUE/FALSE),校验失败立刻弹窗提示,而不是默默丢弃。这套东西跑在Python 3.7环境上,requirements.txt里只有4个依赖:pyqt5openpyxl(处理xlsx)、xlrd(兼容老版xls)、python-snap7(核心通信层)。没有Docker,没有K8s,没有Redis缓存——就是一台普通笔记本,插根网线,填上PLC的IP和机架/插槽号,双击mycode.py就能跑起来。如果你正被OPC授权费卡脖子,或者客户只给一台没装任何工控软件的办公电脑,又或者你需要快速验证一批新上位机点位表的准确性——那接下来这五千多字,就是你省下三天调试时间的关键。

2. 整体架构与设计逻辑:为什么放弃OPC,坚持“裸连”?

2.1 通信层选型:为什么是Snap7,而不是S7.NET或libnodave?

很多人第一反应是:“西门子不是有官方S7.NET吗?C#写起来多稳?”——这话没错,但放在Python生态里,S7.NET是.NET Framework的产物,跨平台性差,且Python调用需要额外装.NET运行时,在客户现场经常遇到权限问题。而python-snap7是德国人基于开源Snap7库(C语言实现)做的Python绑定,它直接操作TCP/IP协议栈,完全绕过西门子专有协议栈。这里有个关键细节:S7-300/400的以太网通信,底层用的是ISO-on-TCP协议(RFC 1006),不是HTTP也不是MQTT。Snap7库内部实现了完整的S7通信握手流程:
1. 建立连接:发送COTP连接请求(TPKT + COTP包),等待PLC返回确认;
2. 协商参数:交换最大PDU长度(通常设为240字节)、机架号(Rack)、插槽号(Slot);
3. 读写循环:对每个变量地址,构造S7 Read/Write请求报文(含功能码、数据长度、起始地址偏移),解析PLC返回的响应报文(含状态字、实际读取字节数)。

提示:S7-300/400默认机架号是0,插槽号是2(CPU模块),但有些老PLC可能插在插槽1(比如带CP343-1以太网模块的配置),这个必须和硬件拨码开关或Step7硬件组态里的一致,否则连接直接超时。plc.pyconnect()方法的rackslot参数就是干这个的,别想当然填0和2。

对比其他方案:libnodave虽然也支持S7,但文档稀烂,对DB块结构支持弱;pys7已多年未维护。Snap7的优势在于:
- 成熟度高:GitHub星标超2k,工业现场实测案例多;
- 错误反馈准:返回的错误码(如0x0005=“无效地址”,0x000A=“无响应”)能直接对应到Step7诊断缓冲区里的错误描述;
- 内存管理透明:所有数据读写都通过ctypes数组操作,不会出现Python GC误回收导致的段错误。

我试过在PLC断电瞬间反复连接,Snap7会稳定返回ConnectionError: Connection refused,而不是像某些封装库那样直接崩溃。这就是为什么plc.py里所有通信方法都包了try-except捕获Snap7Error,并在日志里打印原始错误码——方便你对着西门子手册查原因。

2.2 数据模型设计:Excel标签如何映射到PLC内存地址?

这是整个项目最核心的抽象层。很多人以为“读DB1.DBW2”就是简单拼字符串,但实际PLC地址体系远比这复杂。PLCData.py里的TagManager类做了三件事:
1. 地址解析引擎:支持四种格式——
- DB1.DBX0.3 → DB块1,字节0,位3(BOOL);
- DB1.DBW2 → DB块1,字2(WORD,2字节);
- DB1.DBD4 → DB块1,双字4(DWORD,4字节);
- M100.0 → M存储区,字节100,位0(BOOL)。
解析时用正则r'([A-Z]+)(\d+)\.([A-Z]+)(\d+)(?:\.(\d+))?'提取区域、编号、类型、偏移、位号,再根据类型查表换算成绝对地址(例如DBW2 = DB块起始地址 + 2*2 = 第4字节)。

  1. 数据类型校验:Excel里“数据类型”列填INT,代码会调用snap7types.S7WLInt;填REAL,用snap7types.S7WLReal;填BOOL,则需计算位地址(如DBX0.3 = 字节0 + 位3)。这里有个坑:xlrd读取xls时,数字类型默认是float,所以INT列如果写了123会被读成123.0TagManager在加载时会强制转int(),但REAL列必须保留小数点后精度,否则写入PLC时REAL值会失真。

  2. 批量优化策略:一次读多个变量,不是发N次单地址请求,而是合并成连续内存块。比如要读DB1.DBW2DB1.DBW4DB1.DBW6,它们在内存里是连续的(字节4~9),TagManager会自动合并成一个read_area请求,读6字节后按偏移拆分。实测下来,批量读100个变量比单个读快4倍以上。

注意:S7-400对连续读长度有限制(通常≤160字节),TagManager做了自动切片——超过阈值就拆成多个请求,避免PLC返回0x0006(“非法长度”)错误。

2.3 QT界面交互逻辑:为什么UI要“笨”,而不是“聪明”?

PLCData.ui用Qt Designer拖出来的界面,看着简单,但每个按钮背后都有明确的设计意图:
- “加载标签”按钮:不只是读Excel,还会检查列名是否包含地址数据类型描述(中文列名),缺失任一列就弹窗警告,并阻止后续操作。这是为了防止用户拿错格式的表格糊弄系统。
- “读取全部”按钮:触发TagManager.read_all(),但读取前会先清空Excel里旧数据(保留公式和格式),再把新值写入对应单元格。这里用了openpyxlcell.value = value而非cell.number_format,因为PLC读回来的REAL值是float,直接写入会丢失精度,所以PLCData.py里对REAL类型做了round(value, 6)处理。
- “写入选中”按钮:只写当前Excel选中的单元格(非整列),且仅当该单元格所在行的“地址”列有值才执行。这样用户可以只改一行的设定值,不用全表提交。

最关键的是“运行状态灯”——界面上那个绿色圆点,它不是靠定时器轮询,而是监听plc.pyis_connected()的返回值,连接成功变绿,断开变灰。我刻意没加“重连”按钮,因为自动重连在工业现场反而危险:如果PLC网络抖动,频繁重连可能触发PLC的连接保护机制(S7-400有连接数限制)。所以断开时只提示“请检查网络”,让用户手动干预。

3. 核心模块详解与实操要点

3.1 plc.py:通信层的“心脏”,如何确保每一次握手都可靠?

plc.py是整个项目的通信基石,它封装了Snap7的所有底层调用。我们来拆解它的核心方法:

# 示例:connect()方法的关键逻辑
def connect(self, ip, rack=0, slot=2):
    try:
        self.client.connect(ip, rack, slot, 0)  # 第四个参数是本地TSAP,0表示自动分配
        self._connected = True
        logger.info(f"PLC连接成功: {ip} (Rack:{rack}, Slot:{slot})")
        return True
    except Snap7Error as e:
        self._connected = False
        error_code = e.args[0] & 0xFFFF  # 提取低16位错误码
        logger.error(f"PLC连接失败: {e}, 错误码0x{error_code:04X}")
        # 根据错误码给出具体建议
        if error_code == 0x0005:
            logger.warning("→ 检查PLC IP是否正确,或PLC是否处于RUN模式")
        elif error_code == 0x000A:
            logger.warning("→ 检查PLC以太网模块是否启用,或防火墙是否拦截")
        return False

这段代码体现了两个关键经验:
1. 错误码翻译:Snap7的错误码是32位整数,但西门子手册只定义低16位,所以必须& 0xFFFF提取。0x0005对应“无效地址”,实际常因机架/插槽填错导致;0x000A是“无响应”,大概率是网络不通或PLC未上电。
2. 连接状态缓存self._connected是布尔标志,所有读写操作前都会检查它,避免在断连状态下盲目发请求(否则Snap7会抛出更难懂的ConnectionResetError)。

再看read_data()方法,它支持读任意类型:

def read_data(self, area, db_number, start, size, word_len):
    """
    area: snap7types.areas.DB (DB块), snap7types.areas.MK (M区)
    word_len: S7WLByte(1), S7WLWord(2), S7WLReal(4)等
    """
    try:
        data = self.client.read_area(area, db_number, start, size, word_len)
        # 对于BOOL类型,需特殊处理位读取
        if word_len == snap7types.S7WLBit:
            byte_offset = start // 8
            bit_offset = start % 8
            byte_val = data[byte_offset]
            return bool(byte_val & (1 << bit_offset))
        return data
    except Snap7Error as e:
        logger.error(f"读取失败: area={area}, db={db_number}, start={start}, error={e}")
        raise

这里有个易错点:read_area()start参数单位是字节,但DBX0.3的“0.3”表示字节0、位3,所以start应传0,然后手动解析字节。plc.py里专门写了read_bool()方法来封装这个逻辑,避免每次都要算位掩码。

实操心得:在调试阶段,我习惯在read_data()里加一行logger.debug(f"读取原始数据: {data.hex()}"),把返回的十六进制字节流打出来。比如读DB1.DBW2(INT类型),正常返回b'\x00\x64'(十进制100),如果PLC里是负数(补码),会看到b'\xff\x9c'(-100)。这比看Python的int.from_bytes()结果更直观,能快速判断是PLC值错了,还是你的类型解析错了。

3.2 PLCData.py:标签管理与Excel协同的“中枢神经”

PLCData.py里的TagManager类是连接PLC和Excel的桥梁。它的初始化流程如下:

class TagManager:
    def __init__(self, excel_path):
        self.excel_path = excel_path
        self.tags = []  # 存储Tag对象列表
        self._load_tags_from_excel()

    def _load_tags_from_excel(self):
        # 用xlrd打开xls(兼容老版本)
        workbook = xlrd.open_workbook(self.excel_path)
        sheet = workbook.sheet_by_index(0)
        headers = [sheet.cell_value(0, col) for col in range(sheet.ncols)]

        # 定位关键列索引
        addr_col = self._find_column(headers, ['地址', 'Address', 'ADDR'])
        type_col = self._find_column(headers, ['数据类型', 'Type', 'DATATYPE'])
        desc_col = self._find_column(headers, ['描述', 'Description'])

        for row in range(1, sheet.nrows):  # 跳过标题行
            addr = str(sheet.cell_value(row, addr_col)).strip()
            dtype = str(sheet.cell_value(row, type_col)).strip().upper()
            desc = str(sheet.cell_value(row, desc_col)).strip() if desc_col else ""

            if not addr or not dtype:
                continue  # 跳过空行

            # 解析地址,生成Tag对象
            tag = self._parse_address(addr, dtype, desc)
            self.tags.append(tag)

这个流程的关键在于_find_column()方法——它不依赖固定列序,而是遍历表头找关键词(中文优先,兼容英文),这样用户哪怕把“地址”列拖到第10列,也能正确识别。_parse_address()则负责把DB1.DBW2拆解成{'area': 'DB', 'db_number': 1, 'word_len': 2, 'start': 4}(因为DBW2 = 字节4)。

Excel写入逻辑更讲究:

def write_to_excel(self, values):
    """values: list of (row_index, column_index, value) tuples"""
    # 用openpyxl打开xlsx(保留样式),xlrd只读xls
    if self.excel_path.endswith('.xlsx'):
        wb = openpyxl.load_workbook(self.excel_path)
        ws = wb.active
        for row_idx, col_idx, val in values:
            cell = ws.cell(row=row_idx+2, column=col_idx+1)  # +2跳过标题行
            if isinstance(val, float) and val.is_integer():
                cell.value = int(val)  # REAL值如果是整数,存为int避免显示.x0
            else:
                cell.value = val
        wb.save(self.excel_path)
    else:  # xls格式,用xlwt重写(xlrd不可写)
        # 此处省略,实际项目中建议统一用.xlsx

注意:xlrd从2.0版本起不再支持写xls,所以PLCData.py里对xls格式的写入是用xlwt重写整个文件。但实测发现xlwt不支持Excel公式,所以我在requirements.txt里强制xlrd<2.0,并强烈建议用户用.xlsx格式——这也是为什么样例文件里tag_check_sample.xls是测试用,而生产环境推荐用tags.xlsx

3.3 mycode.py:启动入口与QT事件循环的“指挥官”

mycode.py是整个应用的胶水层,它把PLC通信、标签管理、QT界面串起来:

if __name__ == '__main__':
    app = QApplication(sys.argv)
    window = PLCDataWindow()  # 主窗口实例

    # 绑定按钮信号
    window.btn_load.clicked.connect(window.load_tags)
    window.btn_read_all.clicked.connect(window.read_all_tags)
    window.btn_write_selected.clicked.connect(window.write_selected_tags)

    # 初始化PLC连接(不自动连,由用户点击触发)
    window.plc = PLCClient()

    window.show()
    sys.exit(app.exec_())

这里有个重要设计:PLC连接不在__init__里自动执行,而是等用户点击“连接”按钮后才调用window.plc.connect()。原因是——很多产线PLC的IP是DHCP分配的,用户得先填好IP再连,而不是程序一启动就报错。

QT界面的PLCDataWindow类里,read_all_tags()方法是这样写的:

def read_all_tags(self):
    if not self.plc.is_connected():
        QMessageBox.warning(self, "警告", "PLC未连接,请先点击'连接'")
        return

    try:
        # 从Excel读取标签定义
        self.tag_manager = TagManager(self.current_excel_path)
        # 批量读取所有标签
        values = self.plc.read_batch(self.tag_manager.tags)
        # 写回Excel
        self.tag_manager.write_to_excel(values)
        self.status_label.setText("✅ 读取完成")
        self.status_light.setStyleSheet("background-color: green; border-radius: 10px;")
    except Exception as e:
        self.status_label.setText(f"❌ 读取失败: {str(e)}")
        self.status_light.setStyleSheet("background-color: red; border-radius: 10px;")
        logger.error(f"批量读取异常: {e}")

这个方法体现了“用户友好”的细节:状态灯实时反馈,错误信息既弹窗又记日志,且错误文本直接显示在界面上(而不是只在日志里),方便用户截图发给技术支持。

4. 实操全流程与关键配置步骤

4.1 环境搭建:5分钟搞定Python 3.7运行环境

别被“Python 3.7”吓到,这不是要你卸载现有Python。用pyenvconda创建隔离环境最稳妥:

# 方案1:用conda(推荐,自带编译环境)
conda create -n plc-env python=3.7
conda activate plc-env
pip install -r requirements.txt

# 方案2:用pyenv(Linux/Mac)
pyenv install 3.7.16
pyenv virtualenv 3.7.16 plc-env
pyenv activate plc-env
pip install -r requirements.txt

requirements.txt内容精简到极致:

pyqt5==5.15.9
openpyxl==3.0.10
xlrd==1.2.0
python-snap7==1.14.1

关键点:python-snap7 1.14.1是最后一个支持Python 3.7的版本,且预编译了Windows的.dll和Linux的.so。如果你用Python 3.8+,必须升级到python-snap7>=1.15,但要注意xlrd在3.8+下需用xlrd==2.0.1(只读xlsx),而本项目样例是xls,所以坚持3.7是最省心的选择。

安装后验证Snap7是否正常:

# test_snap7.py
import snap7
client = snap7.client.Client()
print("Snap7版本:", snap7.__version__)
# 应输出类似:Snap7版本: 1.5.0(这是C库版本,python-snap7 1.14.1绑定此版本)

4.2 PLC侧配置:三步让S7-300/400“开门迎客”

很多用户卡在第一步:PLC连不上。根本原因90%是PLC侧没配通。以下是S7-300/400必须做的三件事(无需TIA Portal,用Step7 V5.5即可):

  1. 启用以太网模块
    - 对S7-300,插CP343-1模块,硬件组态里双击它 → “Properties” → 勾选“Enable ISO on TCP”;
    - 对S7-400,插CP443-1,同样勾选“ISO on TCP”。

    提示:不要勾选“PG/PC Interface”,那是给Step7编程用的,我们的Python程序走的是“S7通信”通道。

  2. 设置IP地址
    - 在模块属性里,IP地址填192.168.0.10(举例),子网掩码255.255.255.0
    - 关键:在“Connection Configuration”里,添加一条新连接,Partner Address填你的电脑IP(如192.168.0.100),Local TSAP和Remote TSAP都填0100(这是Snap7默认值,可不改)。

  3. 下载硬件组态并上电
    - 下载后,PLC必须在RUN模式(STOP模式下Snap7能连上但读不到数据);
    - 用网线直连PLC和电脑,禁用电脑其他网卡(避免路由混乱);
    - Windows下ping 192.168.0.10必须通,不通就检查网线、IP、防火墙。

实测技巧:如果ping通但Snap7连不上,打开Step7的“PLC → Diagnostics → Diagnostic Buffer”,看是否有“Connection request from unknown partner”记录。如果有,说明PLC收到了请求但拒绝了——大概率是TSAP没配对,或连接数超限(S7-400默认最多8个连接,关掉其他OPC客户端试试)。

4.3 Excel标签文件制作:一张表搞定所有变量

tags.xls为例,标准格式如下(第一行为标题):

地址 数据类型 描述 当前值 设定值
DB1.DBW2 INT 主轴温度设定值
DB1.DBX0.3 BOOL 急停状态
DB1.DBD4 REAL 实际压力值
  • 地址列:严格按DBx.DBWyDBx.DBXy.zMx.y格式,字母大小写不限;
  • 数据类型列:必须是INTREALBOOLWORDDWORD之一(plc.py里有映射表);
  • 描述列:纯文本,用于界面显示;
  • 当前值/设定值列:留空,程序读取后自动填入;用户改“设定值”列,点击“写入选中”即生效。

注意:Excel里不要合并单元格!xlrd读合并单元格会返回空值。如果必须合并,用openpyxl重新保存为xlsx格式,它能正确处理。

4.4 运行与调试:从双击到数据流动的完整链路

  1. 启动程序:双击mycode.py(确保在激活的conda环境里);
  2. 加载标签:点击“加载标签”,选择tags.xls,界面下方表格会显示所有变量;
  3. 连接PLC:填PLC IP(如192.168.0.10)、机架号(0)、插槽号(2),点“连接”,状态灯变绿;
  4. 读取数据:点“读取全部”,Excel里“当前值”列自动填满,界面表格同步刷新;
  5. 写入数据:在Excel“设定值”列改一个值(如把主轴温度设定值改成85),选中该单元格,点“写入选中”,PLC里对应地址立即更新;
  6. 导出数据:点“导出到Excel”,生成export_20240520_1430.xlsx,含时间戳和所有变量快照。

实操心得:第一次运行时,如果“读取全部”后Excel没变化,别急着骂程序。打开plc.log文件(同目录下),搜索ERROR,看是不是0x0005错误。此时打开Step7,找到该DB块,右键“Object Properties”,确认DB1的“Access”是“Read/Write”,不是“Read Only”。很多客户为了安全把DB块设为只读,Snap7读可以,写就会报错。

5. 常见问题与排查技巧实录

5.1 连接类问题速查表

现象 可能原因 排查步骤 解决方案
Connection refused PLC未上电、IP填错、网线未插 ping PLC_IP;用telnet PLC_IP 102(S7端口) 检查PLC电源、网线、IP配置;确保PLC在RUN模式
Connection timeout 防火墙拦截、PLC以太网模块未启用 关闭Windows防火墙;用Step7看模块状态 在模块属性里勾选“Enable ISO on TCP”;添加防火墙例外
Error code 0x0005 机架/插槽号错误、DB块不存在 查Step7硬件组态;用S7Browser工具扫描PLC 确认机架=0、插槽=2(CPU);检查DB块编号是否真实存在
Error code 0x000A PLC连接数超限、TSAP不匹配 Step7里看“Online → Diagnostics” 关闭其他OPC客户端;确保PLC侧TSAP和程序里一致(默认0100)

5.2 读写类问题深度解析

问题1:“读取全部”后Excel里全是0或乱码
→ 根本原因:数据类型不匹配。比如PLC里DB1.DBW2存的是REAL(4字节),但Excel标签里写成了INT(2字节),plc.py会按2字节读,结果错位。
→ 解决:打开plc.log,找读取原始数据行,对比十六进制。正常REAL100.0b'D\x00\x00\x00'(小端),如果读出来是b'\x00D',说明类型设错了。

问题2:“写入选中”后PLC值没变,但程序没报错
→ 根本原因:PLC DB块属性是“Optimized Block Access”(优化访问)。S7-1200/1500才有此选项,S7-300/400默认关闭,但如果客户用TIA Portal重编译过DB块,可能意外开启。优化块无法用S7协议直接访问。
→ 解决:用Step7打开DB块,右键“Properties” → “General” → 取消勾选“Optimized block access”。

问题3:Excel里BOOL类型写入后PLC状态不变
→ 根本原因:DBX0.3的“3”是位号(0~7),但程序里算错了偏移。plc.pyread_bool()方法假设start是位地址,但Snap7要求start是字节地址。
→ 解决:检查plc.pyread_bool()的实现,确保它先算byte_addr = bit_addr // 8,再读字节,最后用位掩码提取。样例代码已修正此问题。

5.3 性能与稳定性优化技巧

  • 批量读写阈值TagManager默认每批读120字节(S7-400安全上限),如果你的变量都是INT(2字节),一批最多读60个;如果是REAL(4字节),最多30个。在PLCData.py里可调BATCH_SIZE_BYTES = 120
  • 连接保活:Snap7本身不支持心跳,但PLCDataWindow里加了定时器,每30秒调用plc.get_cpu_state()(不耗资源),断开时自动触发重连提示。
  • Excel大文件处理:如果标签超500行,xlrd加载慢。解决方案:用openpyxl读xlsx,或在TagManager._load_tags_from_excel()里加进度条(QProgressDialog)。

最后分享一个小技巧:在产线调试时,我把mycode.py打包成exe(用PyInstaller),生成PLCDataTool.exe,扔给客户工程师。他们双击运行,填IP点连接,5分钟搞定数据核对——再也不用求我远程开TeamViewer了。这套东西的价值,从来不在代码多炫酷,而在让一线人员真正掌控数据。

本文还有配套的精品资源,点击获取 menu-r.4af5f7ec.gif

简介:用Python通过标准以太网TCP/IP协议直接对接西门子S7-300和S7-400 PLC,无需额外OPC服务器或驱动软件;支持从xls文件(如plc_tags.xls、tags.xls)自动加载PLC变量地址与数据类型,完成批量读取和写入操作;采集结果可一键导出到Excel,也支持将Excel中编辑好的值回写至PLC;附带基于PyQt5开发的简易图形界面PLCData.ui,运行截图plc.png直观展示交互效果;包含多个真实测试用例文件(tag_check_sample.xls、tag_check_2020-05-27.xls等),所有核心脚本plc.py、PLCData.py、mycode.py均已开源,适配Python 3.7环境,requirements.txt明确依赖项,目录结构清晰,开箱即可调试运行。


本文还有配套的精品资源,点击获取
menu-r.4af5f7ec.gif

更多推荐