zoukankan      html  css  js  c++  java
  • Python通过snap7库与西门子S7-1200建立S7通信,读写存储器数据,顺便写个流水灯

    1.snap7 简介

    snap7 是一个基于以太网与S7系列的西门子PLC通讯的开源库。

    支持包括S7系列的S7-200、S7-200 Smart、S7-300、S7-400、S7-1200以及S7-1500的以太网通信。

    适用系统
    支持32/64位英特尔/ AMD的所有平台。
    例如:Windows ( 除了 windows Me和95);Linux和类Linux(树莓派,UBeagleBone Black,DOO 等);BSD;Oracle Solaris ;Apple OSX

    支持语言
    Pascal;C#;C++;C;LabVIEW;Python;Node.js;Java,其中介绍比较多的是Python。

    snap7官方网站

    http://snap7.sourceforge.net/

    https://pypi.org/project/python-snap7/

    https://python-snap7.readthedocs.io/en/latest/

    2.S7通信

    西门子S7系列PLC采用以下两种通讯方式:
    1) 开放式的TCPIP,可以用于连接PLC与其他非西门子硬件
    2) 西门子自己开发的S7 Protocol以太网通讯协议,用于西门子内部硬件通讯

    这两者的传输报文是不一样的,如下图:

    西门子数存储到二进制时方式是大端模式(BIG-Endian),而我们的普通电脑常常为小端模式(Liitle-Endian)。
    大端模式是指数据的位保存在内存的地址中,而数据的高位保存在内存的低地址中.
    小端模式是指数据的位保存在内存的地址中,而数据的高位保存在内存的高地址中。
    例如:双字 DWORD 0X2F11214C
    PLC

    PC

    所以数据需要进行转换。

    3.安装snap7库

    pip install python-snap7

    经过上面两步,环境就算搭建好了。通过一个连接测试代码试试,判断下环境是否搭建正常。

    import snap7
    client = snap7.client.Client()
    client.connect('192.168.0.1', 0, 1)
    client.disconnect()

    如果是下图提示,则环境正常(192.168.0.1的PLC不存在)。

    如何使用 Python 构建 PC 通信?

    如果是下图提示,则环境异常(snap7库安装不正确)。

    如何使用 Python 构建 PC 通信?

    4.读写PLC

    4.1配置S7-1200

    环境搭建正常后,在正式建立通信前PLC还需做些配置工作,主要是开发自身的读写权限。具体参照下图配置:

    设置访问级别
    设置DB块的属性

    通过上述配置,PLC可以正常通信了。

    4.2使用snap7读写存储器

    python-snap7重要的两个方法是read_area和write_area,通过这两个方法就能读和写PLC的对应存储地址。

    摘自client.py

    def read_area(self, area: Areas, dbnumber: int, start: int, size: int) -> bytearray:
        """Reads a data area from a PLC
        With it you can read DB, Inputs, Outputs, Merkers, Timers and Counters.
    
        Args:
            area: area to be read from.
            dbnumber: number of the db to be read from. In case of Inputs, Marks or Outputs, this should be equal to 0.
            start: byte index to start reading.
            size: number of bytes to read.
    
        Returns:
            Buffer with the data read.
    
        Raises:
            :obj:`ValueError`: if the area is not defined in the `Areas`
    
        Example:
            >>> import snap7
            >>> client = snap7.client.Client()
            >>> client.connect("192.168.0.1", 0, 0)
            >>> buffer = client.read_area(snap7.types.Areas.DB, 1, 10, 4)  # Reads the DB number 1 from the byte 10 to the byte 14.
            >>> buffer
            bytearray(b'\x00\x00')
        """
        if area not in Areas:
            raise ValueError(f"{area} is not implemented in snap7.types")
        elif area == Areas.TM:
            wordlen = WordLen.Timer
        elif area == Areas.CT:
            wordlen = WordLen.Counter
        else:
            wordlen = WordLen.Byte
        type_ = snap7.types.wordlen_to_ctypes[wordlen.value]
        logger.debug(
            f"reading area: {area.name} dbnumber: {dbnumber} start: {start}: amount {size}: wordlen: {wordlen.name}={wordlen.value}")
        data = (type_ * size)()
        result = self._library.Cli_ReadArea(self._pointer, area.value, dbnumber, start,
                                            size, wordlen.value, byref(data))
        check_error(result, context="client")
        return bytearray(data)
    
    
    @error_wrap
    def write_area(self, area: Areas, dbnumber: int, start: int, data: bytearray) -> int:
        """Writes a data area into a PLC.
    
        Args:
            area: area to be write.
            dbnumber: number of the db to be write to. In case of Inputs, Marks or Outputs, this should be equal to 0.
            start: byte index to start writting.
            data: buffer to be write.
    
        Returns:
            Snap7 error code.
    
        Exmaple:
            >>> import snap7
            >>> client = snap7.client.Client()
            >>> client.connect("192.168.0.1", 0, 0)
            >>> buffer = bytearray([0b00000001])
            >>> client.write_area(snap7.types.Areas.DB, 1, 10, buffer)  # Writes the bit 0 of the byte 10 from the DB number 1 to TRUE.
        """
        if area == Areas.TM:
            wordlen = WordLen.Timer
        elif area == Areas.CT:
            wordlen = WordLen.Counter
        else:
            wordlen = WordLen.Byte
        type_ = snap7.types.wordlen_to_ctypes[WordLen.Byte.value]
        size = len(data)
        logger.debug(f"writing area: {area.name} dbnumber: {dbnumber} start: {start}: size {size}: "
                     f"wordlen {wordlen.name}={wordlen.value} type: {type_}")
        cdata = (type_ * len(data)).from_buffer_copy(data)
        return self._library.Cli_WriteArea(self._pointer, area.value, dbnumber, start,
                                           size, wordlen.value, byref(cdata))

    《SIMATIC S7-1200 可编程控制器系统手册》节4.2.1有如下描述:

    PLC的数据存储通过“变量”的形式与存储区间关联,分为输入(I)、输出(O)、位存储(M)和数据块(DB)。程序在访问对应(I/O)存储区时,是通过访问CPU的过程映像对相应地址进行操作的。具体对应关系如下:

    故python-snap7中定义的Areas含义为

    'PE': 0x81, #input

    'PA': 0x82, #output

    'MK': 0x83, #bit memory

    'DB': 0x84, #DB

    'CT': 0x1C, #counters

    'TM': 0x1D, #Timers

    现在离读写PLC还差最后一步,就是起始地址如何确定呢?

    对于M3.4,对应的就是M(0x83),起始地址是3,对应bit位是4。

    4.3读写示例

    import struct
    import time
    import snap7
    
    def plc_connect(ip, rack=0, slot=1):
        '''
        连接初始化
        :param ip:
        :param rack: 通常为0
        :param slot: 根据plc安装,一般为0或1
        :return:
        '''
        client = snap7.client.Client()
        client.connect(ip, rack, slot)
        return client
    
    def plc_con_close(client):
        """
        连接关闭
        :param client:
        :return:
        """
        client.disconnect()
    
    def test_mk10_1(client):
        """
        测试M10.1
        :return:
        """
        area = snap7.types.Areas.MK
        dbnumber = 0
        start = 10
        amount = 1
        print('初始值',end='')
        mk_data = client.read_area(area, dbnumber, start, amount)
        print(mk_data)#struct.unpack('!c', mk_data)
        print('置1')
        client.write_area(area, dbnumber, start, b'x01')
        print('当前值',end='')
        mk_cur = client.read_area(area, dbnumber, start, amount)
        print(mk_cur)
    
    def test_mk_w201(client):
        """
        测试MW201,数据类型为word
        :param client:
        :return:
        """
        area = snap7.types.Areas.MK
        dbnumber = 0
        amount = 2
        start = 201
        print(u'初始值')
        mk_data = client.read_area(area, dbnumber, start, amount)
        print(struct.unpack('!h', mk_data))
        print(u'置12')
        client.write_area(area, dbnumber, start, b'x12')
        print(u'当前值')
        mk_cur = client.read_area(area, dbnumber, start, amount)
        print(struct.unpack('!h', mk_cur))
        time.sleep(3)
        print(u'置3')
        client.write_area(area, dbnumber, start, b'x02')
        print(u'当前值')
        mk_cur = client.read_area(area, dbnumber, start, amount)
        print(struct.unpack('!h', mk_cur))
    
    def test_q0_0(client):
        """
        测试Q0.0,会使其输出高电平
        :return:
        """
        area = snap7.types.Areas.PA
        dbnumber = 0
        start = 0
        amount = 1
        print('初始值',end='')
        mk_data = client.read_area(area, dbnumber, start, amount)
        print(mk_data)#struct.unpack('!c', mk_data)
        print('置1')
        client.write_area(area, dbnumber, start, b'x01')
        print('当前值',end='')
        mk_cur = client.read_area(area, dbnumber, start, amount)
        print(mk_cur)
    
    if __name__ == "__main__":
        client_fd = plc_connect('192.168.0.2')
        # test_mk10_1(client_fd)
        # test_mk_w201(client_fd)
        test_q0_0(client_fd)
        plc_con_close(client_fd)
    
    
    '''
    'PE': 0x81, #input, I
    'PA': 0x82, #output, Q
    'MK': 0x83, #bit memory, M
    'DB': 0x84, #DB, DBX
    'CT': 0x1C, #counters
    'TM': 0x1D, #Timers
    '''

    也可通过db_read()和db_write()读写DB块

    import snap7
    client = snap7.client.Client()
    client.connect('192.168.0.2', 0, 1)
    plc_db1 = client.db_read(1, 0, 3)  # 读取数据块db1,起始字节,读取长度
    print(plc_db1) # hex(plc_db1[0])
    client.db_write(1, 0, b'x11') # 写入数据块db1,起始字节,数据hex
    plc_db1 = client.db_read(1, 0, 3)  # 读取数据块db1,起始字节,读取长度
    print(plc_db1)
    client.disconnect()

    读写MW变量时,要在变量表中先创建变量,并烧录进S7-1200

    5.流水灯

    import struct
    import time
    import snap7
    
    def plc_connect(ip, rack=0, slot=1):
        '''
        连接初始化
        :param ip:
        :param rack: 通常为0
        :param slot: 根据plc安装,一般为0或1
        :return:
        '''
        client = snap7.client.Client()
        client.connect(ip, rack, slot)
        return client
    
    def plc_con_close(client):
        """
        连接关闭
        :param client:
        :return:
        """
        client.disconnect()
    
    def ledtrip(client):
        """
        跑马灯,使Q0.0~5循环亮起
        :return:
        """
        area = snap7.types.Areas.PA
        dbnumber = 0
        start = 0
        delayTime = 0.5
        for i in range(10):
            client.write_area(area, dbnumber, start, bytearray([0b00000001]))
            time.sleep(delayTime)
            client.write_area(area, dbnumber, start, bytearray([0b00000010]))
            time.sleep(delayTime)
            client.write_area(area, dbnumber, start, bytearray([0b00000100]))
            time.sleep(delayTime)
            client.write_area(area, dbnumber, start, bytearray([0b00001000]))
            time.sleep(delayTime)
            client.write_area(area, dbnumber, start, bytearray([0b00010000]))
            time.sleep(delayTime)
            client.write_area(area, dbnumber, start, bytearray([0b00100000]))
            time.sleep(delayTime)
        client.write_area(area, dbnumber, start, b'x00')
    
    
    if __name__ == "__main__":
        client_fd = plc_connect('192.168.0.2')
        ledtrip(client_fd)
        plc_con_close(client_fd)

    参考

    https://pypi.org/project/python-snap7/

    https://python-snap7.readthedocs.io/en/latest/

    https://blog.csdn.net/zxpbuct/article/details/80079698

    https://blog.csdn.net/lcb411/article/details/101147181

    https://www.toutiao.com/a6589203413941092868

    http://www.6dm.club/index.php/2018/04/07/

  • 相关阅读:
    docker入门(一)
    netstat命令
    grep的小技巧
    gd库的安装
    jar命令的用法详解
    关于awk的范围模式功能问题
    更换文本中第二次出现的字符串内容
    awk打印第n个参数到最后一个技巧/将n行组成一列
    awk -f program.file 功能使用
    shell的变量处理
  • 原文地址:https://www.cnblogs.com/cai-zi/p/15063645.html
Copyright © 2011-2022 走看看