battery/plc_client.py

1135 lines
43 KiB
Python
Raw Permalink 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.

import socket
import struct
import time
from typing import Optional, List, Tuple, Dict
PLC_TIMEOUT = 1
RECONNECT_DELAY = 3 # seconds
class PlcClient:
def __init__(self, ip: str, port: int):
self.ip = ip
self.port = port
self.timeout = PLC_TIMEOUT
self.socket = None
self.connected = False
self._connect_with_retry()
def _connect_with_retry(self) -> bool:
"""尝试连接PLC,失败后无限重试,每次间隔5秒"""
while True:
try:
self.socket = socket.socket(socket.AF_INET, socket.SOCK_STREAM)
self.socket.settimeout(self.timeout)
self.socket.connect((self.ip, self.port))
self.connected = True
print(f"成功连接到PLC {self.ip}:{self.port}")
return True
except Exception as ex:
print(f"连接PLC失败: {str(ex)},{RECONNECT_DELAY}秒后重试...")
self.connected = False
time.sleep(RECONNECT_DELAY)
# 清理旧的socket对象
if self.socket:
try:
self.socket.close()
except:
pass
self.socket = None
def _ensure_connection(self) -> bool:
"""确保连接有效,如果断开则尝试重连"""
if not self.connected or self.socket is None:
return self._connect_with_retry()
return True
@staticmethod
def _hex_str_to_bytes(hex_str: str) -> bytes:
return bytes.fromhex(hex_str.replace(" ", ""))
def _send_and_receive(self, cmd_hex: str, expected_response_length: int = 1024) -> Tuple[
Optional[bytes], Optional[str]]:
"""发送命令并接收响应,处理连接问题"""
if not self._ensure_connection():
return None, "PLC连接不可用"
try:
self.socket.sendall(self._hex_str_to_bytes(cmd_hex))
buffer = self.socket.recv(expected_response_length)
return buffer, None
except socket.timeout:
return None, "PLC响应超时"
except socket.error as ex:
self.connected = False
return None, f"通信错误: {str(ex)}"
except Exception as ex:
return None, f"未知错误: {str(ex)}"
def read_m(self, address: int) -> Tuple[Optional[bool], Optional[str]]:
try:
addr_low = address & 0xFF
addr_high = (address >> 8) & 0xFF
# 读1个位
cmd_hex = (
"50 00 00 FF FF 03 00 "
"0C 00 " # 长度 12
"10 00 01 04 00 00 "
f"{addr_low:02X} {addr_high:02X} 00 90 "
"01 00"
)
buffer, err = self._send_and_receive(cmd_hex)
if err:
return None, f"M{address}读取失败: {err}"
# EndCode检查
end_code = int.from_bytes(buffer[9:11], byteorder='little')
if end_code != 0x0000:
return None, f"M{address}读取失败,响应: {buffer.hex()}"
# 数据部分通常从第11字节开始
if len(buffer) < 12:
return None, f"M{address}响应长度不足"
m_value = buffer[11] & 0x01 # 取最低位表示 ON/OFF
return bool(m_value), None
except Exception as ex:
return None, f"M{address}通信失败: {str(ex)}"
def read_multiple_m(self, start_address: int, count: int) -> Tuple[Optional[List[bool]], Optional[str]]:
"""
一次性读取多个M寄存器
:param start_address: 起始地址
:param count: 要读取的寄存器数量
:return: 布尔值列表和错误信息
"""
try:
addr_low = start_address & 0xFF
addr_high = (start_address >> 8) & 0xFF
count_low = count & 0xFF
count_high = (count >> 8) & 0xFF
# 批量读取M寄存器命令
# 子头: 10 00 01 04 00 00
# 地址: addr_low addr_high 00 90 (M寄存器类型)
# 数量: count_low count_high
cmd_hex = (
"50 00 00 FF FF 03 00 "
"0C 00 " # 长度 12
"10 00 01 04 00 00 "
f"{addr_low:02X} {addr_high:02X} 00 90 "
f"{count_low:02X} {count_high:02X}"
)
buffer, err = self._send_and_receive(cmd_hex, 1024)
if err:
return None, f"M{start_address}-M{start_address + count - 1}批量读取失败: {err}"
# EndCode检查
end_code = int.from_bytes(buffer[9:11], byteorder='little')
if end_code != 0x0000:
return None, f"M{start_address}-M{start_address + count - 1}批量读取失败,响应: {buffer.hex()}"
# 数据部分通常从第11字节开始,每个位对应一个M寄存器
required_len = 11 + (count + 7) // 8 # 计算需要的字节数
if len(buffer) < required_len:
return None, f"M{start_address}-M{start_address + count - 1}响应长度不足"
# 解析位数据
m_values = []
data_start = 11
for i in range(count):
byte_index = i // 8
bit_index = i % 8
if data_start + byte_index < len(buffer):
byte_value = buffer[data_start + byte_index]
bit_value = (byte_value >> bit_index) & 0x01
m_values.append(bool(bit_value))
else:
m_values.append(False) # 如果数据不足,默认为False
return m_values, None
except Exception as ex:
return None, f"M{start_address}-M{start_address + count - 1}批量读取失败: {str(ex)}"
def write_m(self, address: int, value: bool) -> Tuple[bool, Optional[str]]:
try:
# 小端地址
addr_low = address & 0xFF
addr_high = (address >> 8) & 0xFF
value_hex = "01 00" if value else "00 00"
# 报文长度 14 字节 = 子头6 + 地址2 + 软元件2 + 点数2 + 数据2
length_hex = "0E 00"
cmd_hex = (
"50 00 00 FF FF 03 00 "
f"{length_hex} "
"10 00 01 14 00 00 "
f"{addr_low:02X} {addr_high:02X} 00 90 "
"01 00 "
f"{value_hex}"
)
buffer, err = self._send_and_receive(cmd_hex)
if err:
return False, f"M{address}写入失败: {err}"
if len(buffer) == 0:
return False, f"M{address}无响应"
# EndCode通常在响应报文的第9、10字节
end_code = int.from_bytes(buffer[9:11], byteorder='little')
if end_code != 0x0000:
return False, f"M{address}写入被拒绝,响应: {buffer.hex()}"
return True, None
except Exception as ex:
return False, f"M{address}写入失败: {str(ex)}"
def write_d(self, address: int, value: int, data_type: str = "16bit") -> Tuple[bool, Optional[str]]:
"""
写入D寄存器
Args:
address: 寄存器地址
value: 写入的值
data_type: 数据类型 "16bit" 或 "32bit"
Returns:
(成功标志, 错误信息)
"""
try:
# 小端地址
addr_low = address & 0xFF
addr_high = (address >> 8) & 0xFF
# 根据数据类型设置软元件代码和点数
if data_type == "16bit":
# 16位写入:1个点,软元件代码 0xA8
device_code = "A8"
points = "01 00"
# 16位值处理(小端)
value_low = value & 0xFF
value_high = (value >> 8) & 0xFF
value_hex = f"{value_low:02X} {value_high:02X} 00 00"
data_length = 2 # 数据长度2字节
elif data_type == "32bit":
# 32位写入:2个点,软元件代码 0xA8
device_code = "A8"
points = "02 00"
# 32位值处理(小端)
value_low = value & 0xFFFF
value_high = (value >> 16) & 0xFFFF
value_low_low = value_low & 0xFF
value_low_high = (value_low >> 8) & 0xFF
value_high_low = value_high & 0xFF
value_high_high = (value_high >> 8) & 0xFF
value_hex = f"{value_low_low:02X} {value_low_high:02X} {value_high_low:02X} {value_high_high:02X}"
data_length = 4 # 数据长度4字节
else:
return False, f"不支持的数据类型: {data_type}"
# 报文长度计算:子头6 + 地址2 + 软元件2 + 点数2 + 数据N
total_length = 6 + 2 + 2 + 2 + data_length
length_hex = f"{total_length:02X} 00" # 小端格式
cmd_hex = (
"50 00 00 FF FF 03 00 "
f"{length_hex} "
"10 00 01 14 00 00 "
f"{addr_low:02X} {addr_high:02X} 00 {device_code} "
f"{points} "
f"{value_hex}"
)
buffer, err = self._send_and_receive(cmd_hex)
if err:
return False, f"D{address}写入失败: {err}"
if len(buffer) == 0:
return False, f"D{address}无响应"
# EndCode通常在响应报文的第9、10字节
end_code = int.from_bytes(buffer[9:11], byteorder='little')
if end_code != 0x0000:
return False, f"D{address}写入被拒绝,响应: {buffer.hex()}"
return True, None
except Exception as ex:
return False, f"D{address}写入失败: {str(ex)}"
def write_d_batch(self, start_address: int, values: List[int], data_type: str = "16bit") -> Tuple[
bool, Optional[str]]:
# """
# 批量写入D寄存器
#
# Args:
# start_address: 起始地址
# values: 要写入的值列表
# data_type: 数据类型 "16bit" 或 "32bit"
#
# Returns:
# (成功标志, 错误信息)
# """
try:
# 小端地址
addr_low = start_address & 0xFF
addr_high = (start_address >> 8) & 0xFF
# 根据数据类型设置软元件代码和每个值的数据长度
if data_type == "16bit":
device_code = "A8"
bytes_per_value = 2
points = len(values)
elif data_type == "32bit":
device_code = "A8"
bytes_per_value = 4
points = len(values) * 2 # 32bit占用2个点
else:
return False, f"不支持的数据类型: {data_type}"
# 构建数据部分
data_hex_parts = []
for value in values:
if data_type == "16bit":
value_low = value & 0xFF
value_high = (value >> 8) & 0xFF
data_hex_parts.append(f"{value_low:02X} {value_high:02X}")
else: # 32bit
value_low = value & 0xFFFF
value_high = (value >> 16) & 0xFFFF
value_low_low = value_low & 0xFF
value_low_high = (value_low >> 8) & 0xFF
value_high_low = value_high & 0xFF
value_high_high = (value_high >> 8) & 0xFF
data_hex_parts.append(
f"{value_low_low:02X} {value_low_high:02X} {value_high_low:02X} {value_high_high:02X}")
data_hex = " ".join(data_hex_parts)
# 报文长度计算:子头6 + 地址2 + 软元件2 + 点数2 + 数据
total_length = 6 + 2 + 2 + 2 + (bytes_per_value * len(values))
length_hex = f"{total_length:02X} 00" # 小端格式
points_hex = f"{points & 0xFF:02X} {(points >> 8) & 0xFF:02X}"
cmd_hex = (
"50 00 00 FF FF 03 00 "
f"{length_hex} "
"10 00 01 14 00 00 "
f"{addr_low:02X} {addr_high:02X} 00 {device_code} "
f"{points_hex} "
f"{data_hex}"
)
buffer, err = self._send_and_receive(cmd_hex)
if err:
return False, f"D{start_address}批量写入失败: {err}"
if len(buffer) == 0:
return False, f"D{start_address}批量写入无响应"
# EndCode检查
end_code = int.from_bytes(buffer[9:11], byteorder='little')
if end_code != 0x0000:
return False, f"D{start_address}批量写入被拒绝,响应: {buffer.hex()}"
return True, None
except Exception as ex:
return False, f"D{start_address}批量写入失败: {str(ex)}"
def read_d(self, address: int, data_type: str = "16bit") -> Tuple[Optional[int], Optional[str]]:
"""
读取D寄存器
Args:
address: 寄存器地址
data_type: 数据类型 "16bit" 或 "32bit"
Returns:
(读取的值, 错误信息)
"""
try:
# 小端地址
addr_low = address & 0xFF
addr_high = (address >> 8) & 0xFF
# 根据数据类型设置软元件代码和点数
if data_type == "16bit":
device_code = "A8"
points = "01 00" # 1个点
response_data_length = 2 # 期望返回数据长度
elif data_type == "32bit":
device_code = "A8"
points = "02 00" # 2个点
response_data_length = 4 # 期望返回数据长度
else:
return None, f"不支持的数据类型: {data_type}"
# 报文长度 12 字节 = 子头6 + 地址2 + 软元件2 + 点数2
length_hex = "0C 00"
cmd_hex = (
"50 00 00 FF FF 03 00 "
f"{length_hex} "
"10 00 01 04 00 00 "
f"{addr_low:02X} {addr_high:02X} 00 {device_code} "
f"{points}"
)
buffer, err = self._send_and_receive(cmd_hex)
if err:
return None, f"D{address}读取失败: {err}"
if len(buffer) == 0:
return None, f"D{address}无响应"
# EndCode通常在响应报文的第9、10字节
end_code = int.from_bytes(buffer[9:11], byteorder='little')
if end_code != 0x0000:
return None, f"D{address}读取被拒绝,响应: {buffer.hex()}"
# 数据通常在响应报文的第11字节开始
# 响应结构: 头9字节 + EndCode 2字节 + 数据
data_start = 11
if len(buffer) < data_start + response_data_length:
return None, f"D{address}响应数据长度不足"
data_bytes = buffer[data_start:data_start + response_data_length]
# 解析数据(小端格式)
if data_type == "16bit":
# 2字节数据,小端转整数
value = int.from_bytes(data_bytes, byteorder='little', signed=False)
else: # 32bit
# 4字节数据,小端转整数
value = int.from_bytes(data_bytes, byteorder='little', signed=False)
return value, None
except Exception as ex:
return None, f"D{address}读取失败: {str(ex)}"
def read_r(self, address: int, data_type: str = "16bit") -> Tuple[Optional[int], Optional[str]]:
"""
读取R寄存器
Args:
address: 寄存器地址
data_type: 数据类型 "16bit" 或 "32bit"
Returns:
(读取的值, 错误信息)
"""
try:
# R寄存器地址处理(16位地址)
addr_low = address & 0xFF
addr_high = (address >> 8) & 0xFF
# 根据数据类型设置软元件代码和点数
if data_type == "16bit":
device_code = "AF" # R寄存器软元件代码
points = "01 00" # 1个点
response_data_length = 2 # 期望返回数据长度
elif data_type == "32bit":
device_code = "AF"
points = "02 00" # 2个点
response_data_length = 4 # 期望返回数据长度
else:
return None, f"不支持的数据类型: {data_type}"
# 报文长度 12 字节 = 子头6 + 地址2 + 软元件2 + 点数2
length_hex = "0C 00"
cmd_hex = (
"50 00 00 FF FF 03 00 "
f"{length_hex} "
"10 00 01 04 00 00 "
f"{addr_low:02X} {addr_high:02X} 00 {device_code} "
f"{points}"
)
print(f"发送R读取命令: {cmd_hex}") # 调试信息
buffer, err = self._send_and_receive(cmd_hex)
if err:
return None, f"R{address}读取失败: {err}"
if len(buffer) == 0:
return None, f"R{address}无响应"
print(f"接收R读取响应: {buffer.hex()}") # 调试信息
# EndCode通常在响应报文的第9、10字节
end_code = int.from_bytes(buffer[9:11], byteorder='little')
if end_code != 0x0000:
error_msg = self._get_error_message(end_code)
return None, f"R{address}读取被拒绝,错误代码: 0x{end_code:04X} - {error_msg}"
# 数据通常在响应报文的第11字节开始
data_start = 11
if len(buffer) < data_start + response_data_length:
return None, f"R{address}响应数据长度不足"
data_bytes = buffer[data_start:data_start + response_data_length]
# 解析数据(小端格式)
if data_type == "16bit":
value = int.from_bytes(data_bytes, byteorder='little', signed=False)
else: # 32bit
value = int.from_bytes(data_bytes, byteorder='little', signed=False)
return value, None
except Exception as ex:
return None, f"R{address}读取失败: {str(ex)}"
def read_r_batch(self, start_address: int, count: int, data_type: str = "16bit") -> Tuple[
Optional[List[str]], Optional[str]]:
"""
批量读取R寄存器
Args:
start_address: 起始地址
count: 读取数量
data_type: 数据类型 "16bit" 或 "32bit"
Returns:
(读取的值列表, 错误信息)
"""
try:
if count <= 0:
return None, "读取数量必须大于0"
# R寄存器地址处理
addr_low = start_address & 0xFF
addr_mid = (start_address >> 8) & 0xFF
addr_high = (start_address >> 16) & 0xFF
# 根据数据类型设置
if data_type == "16bit":
# device_code = "AF"
device_code = "B0"
points = count
bytes_per_value = 2
elif data_type == "32bit":
device_code = "B0"
# device_code = "AF"
points = count * 2
bytes_per_value = 4
else:
return None, f"不支持的数据类型: {data_type}"
# 计算报文长度
total_length = 6 + 2 + 2 + 2 # 子头6 + 地址2 + 软元件2 + 点数2
length_hex = f"{total_length:02X} 00"
points_low = points & 0xFF
points_high = (points >> 8) & 0xFF
points_hex = f"{points_low:02X} {points_high:02X}"
cmd_hex = (
"50 00 00 FF FF 03 00 "
f"{length_hex} "
"10 00 01 04 00 00 "
f"{addr_low:02X} {addr_mid:02X} {addr_high:02X} {device_code} "
f"{points_hex}"
)
buffer, err = self._send_and_receive(cmd_hex)
if err:
return None, f"R{start_address}批量读取失败: {err}"
if len(buffer) == 0:
return None, f"R{start_address}批量读取无响应"
# EndCode检查
end_code = int.from_bytes(buffer[9:11], byteorder='little')
if end_code != 0x0000:
error_msg = self._get_error_message(end_code)
return None, f"R{start_address}批量读取被拒绝,错误代码: 0x{end_code:04X} - {error_msg}"
# 解析返回数据
data_start = 11
total_data_length = count * bytes_per_value
if len(buffer) < data_start + total_data_length:
return None, f"R{start_address}响应数据长度不足,期望{total_data_length}字节"
values = []
for i in range(count):
data_offset = i * bytes_per_value
data_bytes = buffer[data_start + data_offset:data_start + data_offset + bytes_per_value]
if data_type == "16bit":
value = int.from_bytes(data_bytes, byteorder='little', signed=False)
else: # 32bit
value = int.from_bytes(data_bytes, byteorder='little', signed=False)
values.append(self.hex_2_ascii(value))
return values, None
except Exception as ex:
return None, f"R{start_address}批量读取失败: {str(ex)}"
def hex_2_ascii(self, decimal_data):
hex_value = format(decimal_data, '04X') # 4位大写十六进制
# 分割为高位和低位
high_hex = hex_value[:2] # 前两位
low_hex = hex_value[2:] # 后两位
# 将16进制高位和低位转换为字符
high_char = chr(int(high_hex, 16))
low_char = chr(int(low_hex, 16))
result = low_char + high_char
return result
def write_r_batch(self, start_address: int, values: List[int], data_type: str = "16bit") -> Tuple[
bool, Optional[str]]:
"""
批量写入R寄存器
Args:
start_address: 起始地址
values: 要写入的值列表
data_type: 数据类型 "16bit" 或 "32bit"
Returns:
(成功标志, 错误信息)
"""
try:
# R寄存器地址处理
addr_low = start_address & 0xFF
addr_high = (start_address >> 8) & 0xFF
# 根据数据类型设置
if data_type == "16bit":
device_code = "AF"
bytes_per_value = 2
points = len(values)
elif data_type == "32bit":
device_code = "AF"
bytes_per_value = 4
points = len(values) * 2
else:
return False, f"不支持的数据类型: {data_type}"
# 构建数据部分
data_hex_parts = []
for value in values:
if data_type == "16bit":
value_low = value & 0xFF
value_high = (value >> 8) & 0xFF
data_hex_parts.append(f"{value_low:02X} {value_high:02X}")
else: # 32bit
value_low = value & 0xFFFF
value_high = (value >> 16) & 0xFFFF
value_low_low = value_low & 0xFF
value_low_high = (value_low >> 8) & 0xFF
value_high_low = value_high & 0xFF
value_high_high = (value_high >> 8) & 0xFF
data_hex_parts.append(
f"{value_low_low:02X} {value_low_high:02X} {value_high_low:02X} {value_high_high:02X}")
data_hex = " ".join(data_hex_parts)
# 报文长度计算
total_length = 6 + 2 + 2 + 2 + (bytes_per_value * len(values))
length_hex = f"{total_length:02X} 00"
points_hex = f"{points & 0xFF:02X} {(points >> 8) & 0xFF:02X}"
cmd_hex = (
"50 00 00 FF FF 03 00 "
f"{length_hex} "
"10 00 01 14 00 00 "
f"{addr_low:02X} {addr_high:02X} 00 {device_code} "
f"{points_hex} "
f"{data_hex}"
)
buffer, err = self._send_and_receive(cmd_hex)
if err:
return False, f"R{start_address}批量写入失败: {err}"
if len(buffer) == 0:
return False, f"R{start_address}批量写入无响应"
# EndCode检查
end_code = int.from_bytes(buffer[9:11], byteorder='little')
if end_code != 0x0000:
error_msg = self._get_error_message(end_code)
return False, f"R{start_address}批量写入被拒绝,错误代码: 0x{end_code:04X} - {error_msg}"
return True, None
except Exception as ex:
return False, f"R{start_address}批量写入失败: {str(ex)}"
def _get_error_message(self, error_code: int) -> str:
"""
解析三菱PLC错误代码
"""
error_messages = {
0xC061: "地址超出范围或软元件访问错误",
0xC056: "数据长度错误",
0xC059: "命令不支持",
0xC05A: "设备号错误",
0xC05B: "点数超出范围",
0xC05C: "头数据错误",
0xC05D: "帧长度错误",
0xC05F: "读写禁止",
}
return error_messages.get(error_code, f"未知错误: 0x{error_code:04X}")
def write_d_shen_7_batch(self, start_address: int, values: str, data_type: str = "16bit"):
try:
# 小端地址
addr_low = start_address & 0xFF
addr_high = (start_address >> 8) & 0xFF
# 根据数据类型设置参数
if data_type not in ["16bit", "32bit"]:
return False, f"不支持的数据类型: {data_type}"
device_code = "A8"
def get_16bit_data(chars):
"""获取16位数据:两个字符打包为一个16位值"""
low = ord(chars[0]) if len(chars) > 0 else 0
high = ord(chars[1]) if len(chars) > 1 else 0
return f"{low & 0xFF:02X} {high & 0xFF:02X}"
def get_32bit_data(char):
"""获取32位数据:一个字符扩展为32位值"""
code = ord(char)
# 小端序:低位字节在前
byte1 = code & 0xFF # 最低位字节
byte2 = (code >> 8) & 0xFF # 次低位字节
byte3 = 0x00 # 次高位字节(补0)
byte4 = 0x00 # 最高位字节(补0)
return f"{byte1:02X} {byte2:02X} {byte3:02X} {byte4:02X}"
# 构建数据部分
if data_type == "16bit":
# 16位:每2个字符为一组,每组2字节
data_hex_parts = [
get_16bit_data(values[i:i + 2])
for i in range(0, len(values), 2)
]
points = len(data_hex_parts) # 实际占用的寄存器点数
bytes_per_value = 2
else: # 32bit
# 32位:每个字符为一组,每组4字节
data_hex_parts = [get_32bit_data(char) for char in values]
points = len(values) * 2 # 每个32位值占用2个寄存器点
bytes_per_value = 4
data_hex = " ".join(data_hex_parts)
# 报文长度计算:子头6 + 地址2 + 软元件2 + 点数2 + 数据
actual_data_bytes = len(data_hex_parts) * bytes_per_value
# 报文长度计算:子头6 + 地址2 + 软元件2 + 点数2 + 数据字节数
total_length = 6 + 2 + 2 + 2 + actual_data_bytes
length_hex = f"{total_length & 0xFF:02X} {(total_length >> 8) & 0xFF:02X}" # 小端格式
points_hex = f"{points & 0xFF:02X} {(points >> 8) & 0xFF:02X}"
cmd_hex = (
"50 00 00 FF FF 03 00 "
f"{length_hex} "
# "4E 01"
"10 00 01 14 00 00 "
f"{addr_low:02X} {addr_high:02X} 00 {device_code} "
f"{points_hex} "
f"{data_hex}"
)
# cmd_hex = (
# "50 00 00 FF FF 03 00 "
# "10 00 "
# "10 00 01 14 00 00 "
# "78 00 00 A8 "
# "02 00 "
# "41 42 43 00"
# )
buffer, err = self._send_and_receive(cmd_hex)
if err:
return False, f"D{start_address}批量写入失败: {err}"
if len(buffer) == 0:
return False, f"D{start_address}批量写入无响应"
# EndCode检查
end_code = int.from_bytes(buffer[9:11], byteorder='little')
if end_code != 0x0000:
return False, f"D{start_address}批量写入被拒绝,响应: {buffer.hex()}"
return True, None
except Exception as ex:
return False, f"D{start_address}批量写入失败: {str(ex)}"
def write_d_130_batch(self, start_address: int, values: str, data_type: str = "16bit"):
try:
# 小端地址
addr_low = start_address & 0xFF
addr_high = (start_address >> 8) & 0xFF
# 根据数据类型设置参数
if data_type not in ["16bit", "32bit"]:
return False, f"不支持的数据类型: {data_type}"
device_code = "A8"
def get_16bit_data(value):
"""获取16位数据:数值转换为16位小端格式"""
value_int = int(value)
low = value_int & 0xFF
high = (value_int >> 8) & 0xFF
return f"{low:02X} {high:02X}"
def get_32bit_data(value):
"""获取32位数据:数值转换为32位小端格式"""
value_int = int(value)
# 小端序:低位字节在前
byte1 = value_int & 0xFF # 最低位字节
byte2 = (value_int >> 8) & 0xFF # 次低位字节
byte3 = (value_int >> 16) & 0xFF # 次高位字节
byte4 = (value_int >> 24) & 0xFF # 最高位字节
return f"{byte1:02X} {byte2:02X} {byte3:02X} {byte4:02X}"
# 构建数据部分
if data_type == "16bit":
# 16位:每个数值转换为2字节
data_hex_parts = [get_16bit_data(value) for value in values]
points = len(values) # 占用的寄存器点数
bytes_per_value = 2
else: # 32bit
# 32位:每个数值转换为4字节
data_hex_parts = [get_32bit_data(value) for value in values]
points = len(values) * 2 # 每个32位值占用2个寄存器点
bytes_per_value = 4
data_hex = " ".join(data_hex_parts)
# 报文长度计算:子头6 + 地址2 + 软元件2 + 点数2 + 数据
actual_data_bytes = len(data_hex_parts) * bytes_per_value
# 报文长度计算:子头6 + 地址2 + 软元件2 + 点数2 + 数据字节数
total_length = 6 + 2 + 2 + 2 + actual_data_bytes
length_hex = f"{total_length & 0xFF:02X} {(total_length >> 8) & 0xFF:02X}" # 小端格式
points_hex = f"{points & 0xFF:02X} {(points >> 8) & 0xFF:02X}"
cmd_hex = (
"50 00 00 FF FF 03 00 "
f"{length_hex} "
"10 00 01 14 00 00 "
f"{addr_low:02X} {addr_high:02X} 00 {device_code} "
f"{points_hex} "
f"{data_hex}"
)
buffer, err = self._send_and_receive(cmd_hex)
if err:
return False, f"D{start_address}批量写入失败: {err}"
if len(buffer) == 0:
return False, f"D{start_address}批量写入无响应"
# EndCode检查
end_code = int.from_bytes(buffer[9:11], byteorder='little')
if end_code != 0x0000:
return False, f"D{start_address}批量写入被拒绝,响应: {buffer.hex()}"
return True, None
except Exception as ex:
return False, f"D{start_address}批量写入失败: {str(ex)}"
def write_d_130_ascii_batch(self, start_address: int, values: str, data_type: str = "16bit"):
try:
# 小端地址
addr_low = start_address & 0xFF
addr_high = (start_address >> 8) & 0xFF
# 根据数据类型设置参数
if data_type not in ["16bit", "32bit"]:
return False, f"不支持的数据类型: {data_type}"
device_code = "A8"
def get_16bit_data(char1, char2):
"""将两个ASCII字符组合到一个16位寄存器中(小端格式)"""
# 第一个字符在低位,第二个字符在高位
value = (ord(char2) << 8) | ord(char1)
low = value & 0xFF
high = (value >> 8) & 0xFF
return f"{low:02X} {high:02X}"
def get_32bit_data(char1, char2, char3, char4):
"""将四个ASCII字符组合到一个32位寄存器中(小端格式)"""
# 小端序:char1在最低位,char4在最高位
value = (ord(char4) << 24) | (ord(char3) << 16) | (ord(char2) << 8) | ord(char1)
byte1 = value & 0xFF # 最低位字节
byte2 = (value >> 8) & 0xFF
byte3 = (value >> 16) & 0xFF
byte4 = (value >> 24) & 0xFF # 最高位字节
return f"{byte1:02X} {byte2:02X} {byte3:02X} {byte4:02X}"
# 构建数据部分
if data_type == "16bit":
# 16位:每两个字符组合到一个寄存器
data_hex_parts = []
# 将字符串按每两个字符分组,不足的用空格填充
for i in range(0, len(values), 2):
if i + 1 < len(values):
char1 = values[i]
char2 = values[i + 1]
else:
# 最后一个字符不足时,用空格填充
char1 = values[i]
char2 = ' '
data_hex_parts.append(get_16bit_data(char1, char2))
points = (len(values) + 1) // 2 # 向上取整计算需要的寄存器数量
bytes_per_value = 2
else: # 32bit
# 32位:每四个字符组合到一个寄存器
data_hex_parts = []
# 将字符串按每四个字符分组,不足的用空格填充
for i in range(0, len(values), 4):
chars = list(values[i:i + 4])
# 填充不足的字符
while len(chars) < 4:
chars.append(' ')
data_hex_parts.append(get_32bit_data(chars[0], chars[1], chars[2], chars[3]))
points = (len(values) + 3) // 4 * 2 # 每个32位值占用2个寄存器点
bytes_per_value = 4
data_hex = " ".join(data_hex_parts)
# 报文长度计算:子头6 + 地址2 + 软元件2 + 点数2 + 数据
actual_data_bytes = len(data_hex_parts) * bytes_per_value
# 报文长度计算:子头6 + 地址2 + 软元件2 + 点数2 + 数据字节数
total_length = 6 + 2 + 2 + 2 + actual_data_bytes
length_hex = f"{total_length & 0xFF:02X} {(total_length >> 8) & 0xFF:02X}" # 小端格式
points_hex = f"{points & 0xFF:02X} {(points >> 8) & 0xFF:02X}"
cmd_hex = (
"50 00 00 FF FF 03 00 "
f"{length_hex} "
"10 00 01 14 00 00 "
f"{addr_low:02X} {addr_high:02X} 00 {device_code} "
f"{points_hex} "
f"{data_hex}"
)
buffer, err = self._send_and_receive(cmd_hex)
if err:
return False, f"D{start_address}批量写入失败: {err}"
if len(buffer) == 0:
return False, f"D{start_address}批量写入无响应"
# EndCode检查
end_code = int.from_bytes(buffer[9:11], byteorder='little')
if end_code != 0x0000:
return False, f"D{start_address}批量写入被拒绝,响应: {buffer.hex()}"
return True, None
except Exception as ex:
return False, f"D{start_address}批量写入失败: {str(ex)}"
def write_d_130_ascii_batch(self, start_address: int, values: str, data_type: str = "16bit"):
try:
# 小端地址
addr_low = start_address & 0xFF
addr_high = (start_address >> 8) & 0xFF
# 根据数据类型设置参数
if data_type not in ["16bit", "32bit"]:
return False, f"不支持的数据类型: {data_type}"
device_code = "A8"
def get_16bit_data(char1, char2):
"""将两个ASCII字符组合到一个16位寄存器中(小端格式)"""
# 第一个字符在低位,第二个字符在高位
value = (ord(char2) << 8) | ord(char1)
low = value & 0xFF
high = (value >> 8) & 0xFF
return f"{low:02X} {high:02X}"
def get_32bit_data(char1, char2, char3, char4):
"""将四个ASCII字符组合到一个32位寄存器中(小端格式)"""
# 小端序:char1在最低位,char4在最高位
value = (ord(char4) << 24) | (ord(char3) << 16) | (ord(char2) << 8) | ord(char1)
byte1 = value & 0xFF # 最低位字节
byte2 = (value >> 8) & 0xFF
byte3 = (value >> 16) & 0xFF
byte4 = (value >> 24) & 0xFF # 最高位字节
return f"{byte1:02X} {byte2:02X} {byte3:02X} {byte4:02X}"
# 构建数据部分
if data_type == "16bit":
# 16位:每两个字符组合到一个寄存器
data_hex_parts = []
# 将字符串按每两个字符分组,不足的用空格填充
for i in range(0, len(values), 2):
if i + 1 < len(values):
char1 = values[i]
char2 = values[i + 1]
else:
# 最后一个字符不足时,用空格填充
char1 = values[i]
char2 = ' '
data_hex_parts.append(get_16bit_data(char1, char2))
points = (len(values) + 1) // 2 # 向上取整计算需要的寄存器数量
bytes_per_value = 2
else: # 32bit
# 32位:每四个字符组合到一个寄存器
data_hex_parts = []
# 将字符串按每四个字符分组,不足的用空格填充
for i in range(0, len(values), 4):
chars = list(values[i:i + 4])
# 填充不足的字符
while len(chars) < 4:
chars.append(' ')
data_hex_parts.append(get_32bit_data(chars[0], chars[1], chars[2], chars[3]))
points = (len(values) + 3) // 4 * 2 # 每个32位值占用2个寄存器点
bytes_per_value = 4
data_hex = " ".join(data_hex_parts)
# 报文长度计算:子头6 + 地址2 + 软元件2 + 点数2 + 数据
actual_data_bytes = len(data_hex_parts) * bytes_per_value
# 报文长度计算:子头6 + 地址2 + 软元件2 + 点数2 + 数据字节数
total_length = 6 + 2 + 2 + 2 + actual_data_bytes
length_hex = f"{total_length & 0xFF:02X} {(total_length >> 8) & 0xFF:02X}" # 小端格式
points_hex = f"{points & 0xFF:02X} {(points >> 8) & 0xFF:02X}"
cmd_hex = (
"50 00 00 FF FF 03 00 "
f"{length_hex} "
"10 00 01 14 00 00 "
f"{addr_low:02X} {addr_high:02X} 00 {device_code} "
f"{points_hex} "
f"{data_hex}"
)
buffer, err = self._send_and_receive(cmd_hex)
if err:
return False, f"D{start_address}批量写入失败: {err}"
if len(buffer) == 0:
return False, f"D{start_address}批量写入无响应"
# EndCode检查
end_code = int.from_bytes(buffer[9:11], byteorder='little')
if end_code != 0x0000:
return False, f"D{start_address}批量写入被拒绝,响应: {buffer.hex()}"
return True, None
except Exception as ex:
return False, f"D{start_address}批量写入失败: {str(ex)}"
def close(self):
"""关闭连接"""
if self.socket:
try:
self.socket.close()
except:
pass
self.connected = False
self.socket = None
def __del__(self):
"""析构函数,确保连接被关闭"""
self.close()
if __name__ == '__main__':
# 使用示例
plc = PlcClient('192.168.2.1', 4995)
if plc.connected:
# data = [1,2,3]
# str_data = ''.join(data)
# print(len(str_data))
# print(plc.write_d_shen_7_batch(1200, str_data))
# print(plc.write_d_130_batch(1200, data))
# print(plc.read_d(1200))
# print(plc.read_d(1201))
# print(plc.read_d(1202))
print(plc.write_m(1009, True))
# print(plc.write_m(1007, True))
# # 读取130电池
# address = 200000
# count = 4100
# # 单次最多读 500
# zr_data = []
# for i in range(0, count, 500):
# if i < count - 500:
# temp_data, _ = plc.read_r_batch(address + i, 500)
# else:
# temp_data, _ = plc.read_r_batch(address + i, count - i)
# if temp_data:
# zr_data.extend(temp_data)
# else:
# print(f"ZR{address + i} 批量读取失败")
#
# print(f"ZR{address} 原始数据长度: {len(zr_data)}")
#
# # 读取566电池
# address = 210000
# count = 13018
# # 单次最多读 500
# zr_data_566 = []
# for i in range(0, count, 500):
# if i < count - 500:
# temp_data, _ = plc.read_r_batch(address + i, 500)
# else:
# temp_data, _ = plc.read_r_batch(address + i, count - i)
#
# if temp_data:
# zr_data_566.extend(temp_data)
# else:
# print(f"ZR{address + i} 批量读取失败")
#
# print(f"ZR{address} 原始数据长度: {len(zr_data_566)}")
#
# print(" 130 数据: \n ", zr_data)
# print(" 566 数据: \n ", zr_data_566)
plc.close()
else:
print("无法连接到PLC")