735 lines
27 KiB
Python
Raw Blame History

This file contains ambiguous Unicode characters

This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.

"""
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