734 lines
27 KiB
Python
Raw Normal View History

2025-09-30 13:52:36 +08:00
"""
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):
pass
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