""" S7 PLC通信模块 """ import logging import snap7 from snap7.type import * from snap7.util import * from typing import Optional, Any from datetime import datetime # 配置日志 logging.basicConfig(level=logging.INFO, format='%(asctime)s - %(name)s - %(funcName)s - %(levelname)s - %(message)s') logger = logging.getLogger(__name__) class VWEDS7Module: """ S7 PLC通信模块类 """ def __init__(self, script_id: str): self.script_id = script_id logger.debug(f"初始化 S7 模块,脚本ID: {script_id}") def _get_rack_slot(self, plc_type: str) -> tuple: """ 根据PLC类型获取默认的rack和slot值 Args: plc_type (str): PLC类型 Returns: tuple: (rack, slot) 默认值 """ # 根据常见默认值设置 # S7-200 SMART: rack=0, slot=1 # S7-300: rack=0, slot=2 # S7-400: rack=0, slot=3 # S7-1200: rack=0, slot=1 # S7-1500: rack=0, slot=1 # S7-200: rack=0, slot=1 defaults = { 'S200Smart': (0, 1), 'S300': (0, 2), 'S400': (0, 3), 'S1200': (0, 1), 'S1500': (0, 1), 'S200': (0, 1) } return defaults.get(plc_type, (0, 1)) def _parse_address(self, block_and_offset: str) -> tuple: """ 解析地址字符串,返回区域、DB号、字节地址和位地址 Args: block_and_offset (str): 地址字符串,如M100, DB1.100, DB1.100.7等 Returns: tuple: (area, db_number, byte_addr, bit_addr) """ area = None db_number = 0 byte_addr = 0 bit_addr = None # 移除空格 block_and_offset = block_and_offset.replace(' ', '') # 处理DB块地址 if block_and_offset.startswith('DB'): parts = block_and_offset[2:].split('.') if len(parts) >= 2: db_number = int(parts[0]) byte_addr = int(parts[1]) if len(parts) == 3: bit_addr = int(parts[2]) area = Areas.DB # 处理其他区域地址 else: if block_and_offset[0] == 'M': area = Areas.MK addr_part = block_and_offset[1:] elif block_and_offset[0] == 'I': area = Areas.PE addr_part = block_and_offset[1:] elif block_and_offset[0] == 'Q': area = Areas.PA addr_part = block_and_offset[1:] elif block_and_offset[0] == 'V': # V区通常映射到M区 area = Areas.MK addr_part = block_and_offset[1:] elif block_and_offset[0] == 'T': area = Areas.TM addr_part = block_and_offset[1:] elif block_and_offset[0] == 'C': area = Areas.CT addr_part = block_and_offset[1:] elif block_and_offset[0] == 'AI': area = Areas.AI addr_part = block_and_offset[2:] elif block_and_offset[0] == 'AQ': area = Areas.AQ addr_part = block_and_offset[2:] else: raise ValueError(f"不支持的地址区域: {block_and_offset[0]}") # 处理地址中的位信息 if '.' in addr_part: parts = addr_part.split('.') byte_addr = int(parts[0]) bit_addr = int(parts[1]) else: byte_addr = int(addr_part) return area, db_number, byte_addr, bit_addr def read_s7_int(self, plc_type: str, ip: str, block_and_offset: str) -> Optional[int]: """ S7 读取 Int。 Args: plc_type (str): PLC 类型,可选值:S1200/S300/S400/S1500/S200Smart/S200 ip (str): PLC IP地址 block_and_offset (str): 读取的地址,如M100, DB1.100等 Returns: Optional[int]: 读取到的整数值,失败则返回None """ try: # 获取rack和slot rack, slot = self._get_rack_slot(plc_type) # 解析地址 area, db_number, byte_addr, bit_addr = self._parse_address(block_and_offset) # 创建客户端并连接 client = snap7.client.Client() client.connect(ip, rack, slot, 1102) # 读取数据 if area == Areas.DB: data = client.db_read(db_number, byte_addr, 2) # Int是2个字节 else: data = client.read_area(area, 0, byte_addr, 2) # 断开连接 client.disconnect() # 解析数据 value = snap7.util.get_int(data, 0) logger.info(f"成功读取 {plc_type} {ip} 的 {block_and_offset} 地址的Int值: {value}") return value except Exception as e: logger.error(f"{datetime.now()} - VWEDS7Module - read_s7_int - 读取Int值时发生错误: {e}") return None def read_s7_dint(self, plc_type: str, ip: str, block_and_offset: str) -> Optional[int]: """ S7 读取 DInt。 Args: plc_type (str): PLC 类型,可选值:S1200/S300/S400/S1500/S200Smart/S200 ip (str): PLC IP地址 block_and_offset (str): 读取的地址,如M100, DB1.100等 Returns: Optional[int]: 读取到的双整数值,失败则返回None """ try: # 获取rack和slot rack, slot = self._get_rack_slot(plc_type) # 解析地址 area, db_number, byte_addr, bit_addr = self._parse_address(block_and_offset) # 创建客户端并连接 client = snap7.client.Client() client.connect(ip, rack, slot, 1102) # 读取数据 if area == Areas.DB: data = client.db_read(db_number, byte_addr, 4) # DInt是4个字节 else: data = client.read_area(area, 0, byte_addr, 4) # 断开连接 client.disconnect() # 解析数据 value = snap7.util.get_dint(data, 0) logger.info(f"成功读取 {plc_type} {ip} 的 {block_and_offset} 地址的DInt值: {value}") return value except Exception as e: logger.error(f"{datetime.now()} - VWEDS7Module - read_s7_dint - 读取DInt值时发生错误: {e}") return None def read_s7_word(self, plc_type: str, ip: str, block_and_offset: str) -> Optional[int]: """ S7 读取 Word。 Args: plc_type (str): PLC 类型,可选值:S1200/S300/S400/S1500/S200Smart/S200 ip (str): PLC IP地址 block_and_offset (str): 读取的地址,如M100, DB1.100等 Returns: Optional[int]: 读取到的字值,失败则返回None """ try: # 获取rack和slot rack, slot = self._get_rack_slot(plc_type) # 解析地址 area, db_number, byte_addr, bit_addr = self._parse_address(block_and_offset) # 创建客户端并连接 client = snap7.client.Client() client.connect(ip, rack, slot, 1102) # 读取数据 if area == Areas.DB: data = client.db_read(db_number, byte_addr, 2) # Word是2个字节 else: data = client.read_area(area, 0, byte_addr, 2) # 断开连接 client.disconnect() # 解析数据 value = snap7.util.get_word(data, 0) logger.info(f"成功读取 {plc_type} {ip} 的 {block_and_offset} 地址的Word值: {value}") return value except Exception as e: logger.error(f"{datetime.now()} - VWEDS7Module - read_s7_word - 读取Word值时发生错误: {e}") return None def read_s7_dword(self, plc_type: str, ip: str, block_and_offset: str) -> Optional[int]: """ S7 读取 DWord。 Args: plc_type (str): PLC 类型,可选值:S1200/S300/S400/S1500/S200Smart/S200 ip (str): PLC IP地址 block_and_offset (str): 读取的地址,如M100, DB1.100等 Returns: Optional[int]: 读取到的双字值,失败则返回None """ try: # 获取rack和slot rack, slot = self._get_rack_slot(plc_type) # 解析地址 area, db_number, byte_addr, bit_addr = self._parse_address(block_and_offset) # 创建客户端并连接 client = snap7.client.Client() client.connect(ip, rack, slot, 1102) # 读取数据 if area == Areas.DB: data = client.db_read(db_number, byte_addr, 4) # DWord是4个字节 else: data = client.read_area(area, 0, byte_addr, 4) # 断开连接 client.disconnect() # 解析数据 value = snap7.util.get_dword(data, 0) logger.info(f"成功读取 {plc_type} {ip} 的 {block_and_offset} 地址的DWord值: {value}") return value except Exception as e: logger.error(f"{datetime.now()} - VWEDS7Module - read_s7_dword - 读取DWord值时发生错误: {e}") return None def read_s7_string(self, plc_type: str, ip: str, block_and_offset: str) -> Optional[str]: """ S7 读取 String。 Args: plc_type (str): PLC 类型,可选值:S1200/S300/S400/S1500/S200Smart/S200 ip (str): PLC IP地址 block_and_offset (str): 读取的地址,如M100, DB1.100等 Returns: Optional[str]: 读取到的字符串值,失败则返回None """ try: # 获取rack和slot rack, slot = self._get_rack_slot(plc_type) # 解析地址 area, db_number, byte_addr, bit_addr = self._parse_address(block_and_offset) # 创建客户端并连接 client = snap7.client.Client() client.connect(ip, rack, slot, 1102) # 先读取字符串的长度信息(前2个字节) # 第一个字节是最大长度,第二个字节是实际长度 if area == Areas.DB: length_data = client.db_read(db_number, byte_addr, 2) else: length_data = client.read_area(area, 0, byte_addr, 2) # 获取字符串的实际长度 max_length = length_data[0] # 最大长度 actual_length = length_data[1] # 实际长度 # 读取字符串数据(根据实际长度读取) if area == Areas.DB: data = client.db_read(db_number, byte_addr + 2, actual_length) else: data = client.read_area(area, 0, byte_addr + 2, actual_length) # 断开连接 client.disconnect() # 解析数据 value = data.decode('utf-8').rstrip('\x00').rstrip() # 移除可能的空字符和空格 logger.info(f"成功读取 {plc_type} {ip} 的 {block_and_offset} 地址的String值: {value}") return value except Exception as e: logger.error(f"{datetime.now()} - VWEDS7Module - read_s7_string - 读取String值时发生错误: {e}") return None def read_s7_boolean(self, plc_type: str, ip: str, block_and_offset: str) -> Optional[bool]: """ S7 读取 Bool。 Args: plc_type (str): PLC 类型,可选值:S1200/S300/S400/S1500/S200Smart/S200 ip (str): PLC IP地址 block_and_offset (str): 读取的地址,如M100.0, DB1.100.7等 Returns: Optional[bool]: 读取到的布尔值,失败则返回None """ try: # 获取rack和slot rack, slot = self._get_rack_slot(plc_type) # 解析地址 area, db_number, byte_addr, bit_addr = self._parse_address(block_and_offset) # 检查是否有位地址 if bit_addr is None: raise ValueError("读取布尔值需要指定位地址,如M100.0") # 创建客户端并连接 client = snap7.client.Client() client.connect(ip, rack, slot, 1102) # 读取数据 if area == Areas.DB: data = client.db_read(db_number, byte_addr, 1) # 读取1个字节 else: data = client.read_area(area, 0, byte_addr, 1) # 断开连接 client.disconnect() # 解析数据 value = snap7.util.get_bool(data, 0, bit_addr) logger.info(f"成功读取 {plc_type} {ip} 的 {block_and_offset} 地址的Boolean值: {value}") return value except Exception as e: logger.error(f"{datetime.now()} - VWEDS7Module - read_s7_boolean - 读取Boolean值时发生错误: {e}") return None def write_s7_int(self, plc_type: str, ip: str, block_and_offset: str, value: int) -> bool: """ S7 写入 Int。 Args: plc_type (str): PLC 类型,可选值:S1200/S300/S400/S1500/S200Smart/S200 ip (str): PLC IP地址 block_and_offset (str): 写入的地址,如M100, DB1.100等 value (int): 要写入的整数值 Returns: bool: 写入成功返回True,失败返回False """ try: # 获取rack和slot rack, slot = self._get_rack_slot(plc_type) # 解析地址 area, db_number, byte_addr, bit_addr = self._parse_address(block_and_offset) # 创建客户端并连接 client = snap7.client.Client() client.connect(ip, rack, slot, 1102) # 准备数据 data = bytearray(2) snap7.util.set_word(data, 0, value) # 写入数据 logger.info(f"准备写入数据到DB{db_number}, 偏移量{byte_addr}, 数据: {data}") if area == Areas.DB: client.db_write(db_number, byte_addr, data) else: client.write_area(area, 0, byte_addr, data) logger.info(f"数据写入完成") # 断开连接 client.disconnect() logger.info(f"成功向 {plc_type} {ip} 的 {block_and_offset} 地址写入Int值 {value}") return True except Exception as e: logger.error(f"{datetime.now()} - VWEDS7Module - write_s7_int - 写入Int值时发生错误: {e}") return False def write_s7_dint(self, plc_type: str, ip: str, block_and_offset: str, value: int) -> bool: """ S7 写入 DInt。 Args: plc_type (str): PLC 类型,可选值:S1200/S300/S400/S1500/S200Smart/S200 ip (str): PLC IP地址 block_and_offset (str): 写入的地址,如M100, DB1.100等 value (int): 要写入的双整数值 Returns: bool: 写入成功返回True,失败返回False """ try: # 获取rack和slot rack, slot = self._get_rack_slot(plc_type) # 解析地址 area, db_number, byte_addr, bit_addr = self._parse_address(block_and_offset) # 创建客户端并连接 client = snap7.client.Client() client.connect(ip, rack, slot, 1102) # 准备数据 data = bytearray(4) snap7.util.set_dint(data, 0, value) # 写入数据 if area == Areas.DB: client.db_write(db_number, byte_addr, data) else: client.write_area(area, 0, byte_addr, data) # 断开连接 client.disconnect() logger.info(f"成功向 {plc_type} {ip} 的 {block_and_offset} 地址写入DInt值 {value}") return True except Exception as e: logger.error(f"{datetime.now()} - VWEDS7Module - write_s7_dint - 写入DInt值时发生错误: {e}") return False def write_s7_word(self, plc_type: str, ip: str, block_and_offset: str, value: int) -> bool: """ S7 写入 Word。 Args: plc_type (str): PLC 类型,可选值:S1200/S300/S400/S1500/S200Smart/S200 ip (str): PLC IP地址 block_and_offset (str): 写入的地址,如M100, DB1.100等 value (int): 要写入的字值 Returns: bool: 写入成功返回True,失败返回False """ try: # 获取rack和slot rack, slot = self._get_rack_slot(plc_type) # 解析地址 area, db_number, byte_addr, bit_addr = self._parse_address(block_and_offset) # 创建客户端并连接 client = snap7.client.Client() client.connect(ip, rack, slot, 1102) # 准备数据 data = bytearray(2) snap7.util.set_word(data, 0, value) # 写入数据 if area == Areas.DB: client.db_write(db_number, byte_addr, data) else: client.write_area(area, 0, byte_addr, data) # 断开连接 client.disconnect() logger.info(f"成功向 {plc_type} {ip} 的 {block_and_offset} 地址写入Word值 {value}") return True except Exception as e: logger.error(f"{datetime.now()} - VWEDS7Module - write_s7_word - 写入Word值时发生错误: {e}") return False def write_s7_dword(self, plc_type: str, ip: str, block_and_offset: str, value: int) -> bool: """ S7 写入 DWord。 Args: plc_type (str): PLC 类型,可选值:S1200/S300/S400/S1500/S200Smart/S200 ip (str): PLC IP地址 block_and_offset (str): 写入的地址,如M100, DB1.100等 value (int): 要写入的双字值 Returns: bool: 写入成功返回True,失败返回False """ try: # 获取rack和slot rack, slot = self._get_rack_slot(plc_type) # 解析地址 area, db_number, byte_addr, bit_addr = self._parse_address(block_and_offset) # 创建客户端并连接 client = snap7.client.Client() client.connect(ip, rack, slot, 1102) # 准备数据 data = bytearray(4) snap7.util.set_dword(data, 0, value) # 写入数据 if area == Areas.DB: client.db_write(db_number, byte_addr, data) else: client.write_area(area, 0, byte_addr, data) # 断开连接 client.disconnect() logger.info(f"成功向 {plc_type} {ip} 的 {block_and_offset} 地址写入DWord值 {value}") return True except Exception as e: logger.error(f"{datetime.now()} - VWEDS7Module - write_s7_dword - 写入DWord值时发生错误: {e}") return False def write_s7_string(self, plc_type: str, ip: str, block_and_offset: str, value: str) -> bool: """ S7 写入 String。 Args: plc_type (str): PLC 类型,可选值:S1200/S300/S400/S1500/S200Smart/S200 ip (str): PLC IP地址 block_and_offset (str): 写入的地址,如M100, DB1.100等 value (str): 要写入的字符串值 Returns: bool: 写入成功返回True,失败返回False """ try: # 获取rack和slot rack, slot = self._get_rack_slot(plc_type) # 解析地址 area, db_number, byte_addr, bit_addr = self._parse_address(block_and_offset) # 创建客户端并连接 client = snap7.client.Client() client.connect(ip, rack, slot, 1102) # 准备数据 # 字符串格式:前2个字节为长度,后面是字符串内容 data = bytearray(256) # 最大256字节 encoded_value = value.encode('utf-8') length = min(len(encoded_value), 254) # 最大长度254 snap7.util.set_word(data, 0, length) # 设置长度 data[2:2+length] = encoded_value[:length] # 设置字符串内容 # 写入数据 if area == Areas.DB: client.db_write(db_number, byte_addr, data) else: client.write_area(area, 0, byte_addr, data) # 断开连接 client.disconnect() logger.info(f"成功向 {plc_type} {ip} 的 {block_and_offset} 地址写入String值 {value}") return True except Exception as e: logger.error(f"{datetime.now()} - VWEDS7Module - write_s7_string - 写入String值时发生错误: {e}") return False def write_s7_boolean(self, plc_type: str, ip: str, block_and_offset: str, value: bool) -> bool: """ S7 写入 Bool。 Args: plc_type (str): PLC 类型,可选值:S1200/S300/S400/S1500/S200Smart/S200 ip (str): PLC IP地址 block_and_offset (str): 写入的地址,如M100.0, DB1.100.7等 value (bool): 要写入的布尔值 Returns: bool: 写入成功返回True,失败返回False """ try: # 获取rack和slot rack, slot = self._get_rack_slot(plc_type) # 解析地址 area, db_number, byte_addr, bit_addr = self._parse_address(block_and_offset) # 检查是否有位地址 if bit_addr is None: raise ValueError("写入布尔值需要指定位地址,如M100.0") # 创建客户端并连接 client = snap7.client.Client() client.connect(ip, rack, slot, 1102) # 读取当前字节数据 if area == Areas.DB: data = client.db_read(db_number, byte_addr, 1) else: data = client.read_area(area, 0, byte_addr, 1) # 设置布尔值 snap7.util.set_bool(data, 0, bit_addr, value) # 写入数据 if area == Areas.DB: client.db_write(db_number, byte_addr, data) else: client.write_area(area, 0, byte_addr, data) # 断开连接 client.disconnect() logger.info(f"成功向 {plc_type} {ip} 的 {block_and_offset} 地址写入Boolean值 {value}") return True except Exception as e: logger.error(f"{datetime.now()} - VWEDS7Module - write_s7_boolean - 写入Boolean值时发生错误: {e}") return False def write_s7(self, plc_type: str, ip: str, block_and_offset: str, value: bytes, rack: int = 0, slot: int = 1) -> bool: """ S7 通用写入。 Args: plc_type (str): PLC 类型,可选值:S1200/S300/S400/S1500/S200Smart/S200 ip (str): PLC IP地址 block_and_offset (str): 写入的地址,如M100, DB1.100等 value (bytes): 要写入的字节数据 rack (int): 中央机架,默认为0 slot (int): CPU模块插槽号,默认为1 Returns: bool: 写入成功返回True,失败返回False """ try: # 获取rack和slot的默认值 default_rack, default_slot = self._get_rack_slot(plc_type) if rack == 0 and slot == 1: rack, slot = default_rack, default_slot # 解析地址 area, db_number, byte_addr, bit_addr = self._parse_address(block_and_offset) # 创建客户端并连接 client = snap7.client.Client() client.connect(ip, rack, slot, 1102) # 写入数据 if area == Areas.DB: client.db_write(db_number, byte_addr, value) else: client.write_area(area, 0, byte_addr, value) # 断开连接 client.disconnect() logger.info(f"成功向 {plc_type} {ip} 的 {block_and_offset} 地址写入数据 {value}") return True except Exception as e: logger.error(f"{datetime.now()} - VWEDS7Module - write_s7 - 通用写入时发生错误: {e}") return False def read_s7(self, plc_type: str, ip: str, block_and_offset: str, size: int, rack: int = 0, slot: int = 1) -> Optional[bytes]: """ S7 通用读取。 Args: plc_type (str): PLC 类型,可选值:S1200/S300/S400/S1500/S200Smart/S200 ip (str): PLC IP地址 block_and_offset (str): 读取的地址,如M100, DB1.100等 size (int): 读取的数据大小(字节数) rack (int): 中央机架,默认为0 slot (int): CPU模块插槽号,默认为1 Returns: Optional[bytes]: 读取到的字节数据,失败则返回None """ try: # 获取rack和slot的默认值 default_rack, default_slot = self._get_rack_slot(plc_type) if rack == 0 and slot == 1: rack, slot = default_rack, default_slot # 解析地址 area, db_number, byte_addr, bit_addr = self._parse_address(block_and_offset) # 创建客户端并连接 client = snap7.client.Client() client.connect(ip, rack, slot, 1102) # 读取数据 if area == Areas.DB: data = client.db_read(db_number, byte_addr, size) else: data = client.read_area(area, 0, byte_addr, size) # 断开连接 client.disconnect() logger.info(f"成功读取 {plc_type} {ip} 的 {block_and_offset} 地址的 {size} 字节数据") return data except Exception as e: logger.error(f"{datetime.now()} - VWEDS7Module - read_s7 - 通用读取时发生错误: {e}") return None