添加其他数据块的读取线程,并且解决线程冲突问题。另外解决了之前bool类型读取的Bug
This commit is contained in:
@ -33,11 +33,6 @@ class Snap7Client:
|
||||
self.retry_count = 0
|
||||
self.logger = logging.getLogger(f"Snap7Client[{ip}]")
|
||||
|
||||
# ---------------
|
||||
# 新增
|
||||
# ---------------
|
||||
self.db100_cache_file = "db100_latest_data.log"
|
||||
|
||||
def is_valid_connection(self):
|
||||
"""检查连接是否真正有效"""
|
||||
try:
|
||||
@ -59,22 +54,23 @@ class Snap7Client:
|
||||
|
||||
self.last_connect_attempt = current_time
|
||||
try:
|
||||
self.client.connect(self.ip, self.rack, self.slot)
|
||||
with self.lock:
|
||||
self.client.connect(self.ip, self.rack, self.slot)
|
||||
|
||||
# 验证连接是否真正有效
|
||||
if self.client.get_connected() and self.is_valid_connection():
|
||||
self.connected = True
|
||||
self.retry_count = 0 # 重置重试计数
|
||||
self.logger.info(f"Successfully connected to PLC {self.ip}")
|
||||
return True
|
||||
else:
|
||||
self.connected = False
|
||||
self.logger.warning(f"Connection to {self.ip} established but PLC is not responding")
|
||||
try:
|
||||
self.client.disconnect()
|
||||
except:
|
||||
pass
|
||||
return False
|
||||
# 验证连接是否真正有效
|
||||
if self.client.get_connected() and self.is_valid_connection():
|
||||
self.connected = True
|
||||
self.retry_count = 0 # 重置重试计数
|
||||
self.logger.info(f"Successfully connected to PLC {self.ip}")
|
||||
return True
|
||||
else:
|
||||
self.connected = False
|
||||
self.logger.warning(f"Connection to {self.ip} established but PLC is not responding")
|
||||
try:
|
||||
self.client.disconnect()
|
||||
except:
|
||||
pass
|
||||
return False
|
||||
except Exception as e:
|
||||
self.retry_count = min(self.retry_count + 1, self.max_retries)
|
||||
self.logger.error(f"Connection to {self.ip} failed (attempt {self.retry_count}/{self.max_retries}): {e}")
|
||||
@ -98,7 +94,7 @@ class Snap7Client:
|
||||
return None # 返回None而不是零填充数据
|
||||
|
||||
try:
|
||||
with self.lock: # 进入锁,其他线程需等待
|
||||
with self.lock:
|
||||
data = self.client.db_read(db_number, offset, size)
|
||||
# 验证返回数据的有效性
|
||||
if data is None or len(data) != size:
|
||||
@ -162,10 +158,10 @@ class Snap7Client:
|
||||
return False
|
||||
|
||||
try:
|
||||
|
||||
self.client.db_write(db_number, offset, data)
|
||||
self.logger.debug(f"Wrote {len(data)} bytes to DB{db_number} offset {offset}")
|
||||
return True
|
||||
with self.lock:
|
||||
self.client.db_write(db_number, offset, data)
|
||||
self.logger.debug(f"Wrote {len(data)} bytes to DB{db_number} offset {offset}")
|
||||
return True
|
||||
except Exception as e:
|
||||
self.logger.error(f"Write DB{db_number} error: {e}")
|
||||
self.connected = False
|
||||
@ -215,7 +211,6 @@ class Snap7Client:
|
||||
|
||||
try:
|
||||
with self.lock:
|
||||
|
||||
self.client.db_write(db_number, offset, data)
|
||||
self.logger.debug(f"Wrote {len(data)} bytes to DB{db_number} offset {offset}")
|
||||
return True
|
||||
@ -267,105 +262,104 @@ class Snap7Client:
|
||||
return False
|
||||
|
||||
try:
|
||||
with self.lock:
|
||||
if data_type == 'bool':
|
||||
# 对于bool,offset是位偏移
|
||||
byte_offset = offset // 8
|
||||
bit_offset = offset % 8
|
||||
if data_type == 'bool':
|
||||
# 对于bool,offset是位偏移
|
||||
byte_offset = offset // 8
|
||||
bit_offset = offset % 8
|
||||
|
||||
# 读取当前字节
|
||||
current_byte = self.read_db(db_number, byte_offset, 1)
|
||||
if current_byte is None:
|
||||
# 读取当前字节
|
||||
current_byte = self.read_db(db_number, byte_offset, 1)
|
||||
if current_byte is None:
|
||||
return False
|
||||
|
||||
# 修改特定位
|
||||
if isinstance(value, list):
|
||||
# 多个bool值
|
||||
for i, val in enumerate(value):
|
||||
current_bit = bit_offset + i
|
||||
byte_idx = current_bit // 8
|
||||
bit_idx = current_bit % 8
|
||||
|
||||
if val:
|
||||
current_byte[0] |= (1 << bit_idx)
|
||||
else:
|
||||
current_byte[0] &= ~(1 << bit_idx)
|
||||
else:
|
||||
# 单个bool值
|
||||
if value:
|
||||
current_byte[0] |= (1 << bit_offset)
|
||||
else:
|
||||
current_byte[0] &= ~(1 << bit_offset)
|
||||
|
||||
# 写回修改后的字节
|
||||
return self.write_db_bool(db_number, byte_offset, current_byte)
|
||||
|
||||
elif data_type == 'byte':
|
||||
if isinstance(value, list):
|
||||
# 批量写入
|
||||
for i, val in enumerate(value):
|
||||
if val < 0 or val > 255:
|
||||
self.logger.error(f"Byte value out of range: {val}")
|
||||
return False
|
||||
if not self.write_db(db_number, offset + i, bytes([val])):
|
||||
return False
|
||||
return True
|
||||
else:
|
||||
# 单个字节
|
||||
if value < 0 or value > 255:
|
||||
self.logger.error(f"Byte value out of range: {value}")
|
||||
return False
|
||||
return self.write_db(db_number, offset, bytes([value]))
|
||||
|
||||
elif data_type in ['int', 'word']:
|
||||
if not isinstance(value, list):
|
||||
value = [value]
|
||||
|
||||
for i, val in enumerate(value):
|
||||
# 确保int值在范围内
|
||||
if data_type == 'int' and (val < -32768 or val > 32767):
|
||||
self.logger.error(f"Int value out of range: {val}")
|
||||
return False
|
||||
elif data_type == 'word' and (val < 0 or val > 65535):
|
||||
self.logger.error(f"Word value out of range: {val}")
|
||||
return False
|
||||
|
||||
# 修改特定位
|
||||
if isinstance(value, list):
|
||||
# 多个bool值
|
||||
for i, val in enumerate(value):
|
||||
current_bit = bit_offset + i
|
||||
byte_idx = current_bit // 8
|
||||
bit_idx = current_bit % 8
|
||||
|
||||
if val:
|
||||
current_byte[0] |= (1 << bit_idx)
|
||||
else:
|
||||
current_byte[0] &= ~(1 << bit_idx)
|
||||
data = bytearray(2)
|
||||
if data_type == 'int':
|
||||
set_int(data, 0, val)
|
||||
else:
|
||||
# 单个bool值
|
||||
if value:
|
||||
current_byte[0] |= (1 << bit_offset)
|
||||
else:
|
||||
current_byte[0] &= ~(1 << bit_offset)
|
||||
set_word(data, 0, val)
|
||||
|
||||
# 写回修改后的字节
|
||||
return self.write_db_bool(db_number, byte_offset, current_byte)
|
||||
if not self.write_db(db_number, offset + i * 2, data):
|
||||
return False
|
||||
return True
|
||||
|
||||
elif data_type == 'byte':
|
||||
if isinstance(value, list):
|
||||
# 批量写入
|
||||
for i, val in enumerate(value):
|
||||
if val < 0 or val > 255:
|
||||
self.logger.error(f"Byte value out of range: {val}")
|
||||
return False
|
||||
if not self.write_db(db_number, offset + i, bytes([val])):
|
||||
return False
|
||||
return True
|
||||
else:
|
||||
# 单个字节
|
||||
if value < 0 or value > 255:
|
||||
self.logger.error(f"Byte value out of range: {value}")
|
||||
elif data_type in ['dint', 'dword', 'real']:
|
||||
if not isinstance(value, list):
|
||||
value = [value]
|
||||
|
||||
for i, val in enumerate(value):
|
||||
data = bytearray(4)
|
||||
if data_type == 'dint':
|
||||
if val < -2147483648 or val > 2147483647:
|
||||
self.logger.error(f"DInt value out of range: {val}")
|
||||
return False
|
||||
return self.write_db(db_number, offset, bytes([value]))
|
||||
|
||||
elif data_type in ['int', 'word']:
|
||||
if not isinstance(value, list):
|
||||
value = [value]
|
||||
|
||||
for i, val in enumerate(value):
|
||||
# 确保int值在范围内
|
||||
if data_type == 'int' and (val < -32768 or val > 32767):
|
||||
self.logger.error(f"Int value out of range: {val}")
|
||||
return False
|
||||
elif data_type == 'word' and (val < 0 or val > 65535):
|
||||
self.logger.error(f"Word value out of range: {val}")
|
||||
set_dint(data, 0, val)
|
||||
elif data_type == 'dword':
|
||||
if val < 0 or val > 4294967295:
|
||||
self.logger.error(f"DWord value out of range: {val}")
|
||||
return False
|
||||
set_dword(data, 0, val)
|
||||
else: # real
|
||||
set_real(data, 0, float(val))
|
||||
|
||||
data = bytearray(2)
|
||||
if data_type == 'int':
|
||||
set_int(data, 0, val)
|
||||
else:
|
||||
set_word(data, 0, val)
|
||||
if not self.write_db(db_number, offset + i * 4, data):
|
||||
return False
|
||||
return True
|
||||
|
||||
if not self.write_db(db_number, offset + i * 2, data):
|
||||
return False
|
||||
return True
|
||||
|
||||
elif data_type in ['dint', 'dword', 'real']:
|
||||
if not isinstance(value, list):
|
||||
value = [value]
|
||||
|
||||
for i, val in enumerate(value):
|
||||
data = bytearray(4)
|
||||
if data_type == 'dint':
|
||||
if val < -2147483648 or val > 2147483647:
|
||||
self.logger.error(f"DInt value out of range: {val}")
|
||||
return False
|
||||
set_dint(data, 0, val)
|
||||
elif data_type == 'dword':
|
||||
if val < 0 or val > 4294967295:
|
||||
self.logger.error(f"DWord value out of range: {val}")
|
||||
return False
|
||||
set_dword(data, 0, val)
|
||||
else: # real
|
||||
set_real(data, 0, float(val))
|
||||
|
||||
if not self.write_db(db_number, offset + i * 4, data):
|
||||
return False
|
||||
return True
|
||||
|
||||
else:
|
||||
self.logger.error(f"Unsupported data type: {data_type}")
|
||||
return False
|
||||
else:
|
||||
self.logger.error(f"Unsupported data type: {data_type}")
|
||||
return False
|
||||
|
||||
except Exception as e:
|
||||
self.logger.error(f"Error writing {data_type} to DB{db_number} offset {offset}: {e}")
|
||||
@ -398,31 +392,41 @@ class Snap7Client:
|
||||
self.logger.error(f"Error caching data: {e}")
|
||||
return False
|
||||
|
||||
def read_db100_from_file(self):
|
||||
def read_db_from_file_cache(self, db_number, offset, required_size, cache_file):
|
||||
"""
|
||||
从缓存文件中解析DB100的原始字节数据
|
||||
返回: bytearray(成功)/ None(失败)
|
||||
通用文件缓存读取:从指定文件读取DB块缓存,返回需要的字节片段
|
||||
参数:
|
||||
db_number: DB块编号
|
||||
offset: 需要读取的起始偏移(字节)
|
||||
required_size: 需要读取的字节数
|
||||
cache_file: 缓存文件名(动态生成)
|
||||
返回值:
|
||||
bytearray | None: 所需的字节片段,失败返回None
|
||||
"""
|
||||
try:
|
||||
# 1. 读取缓存文件内容
|
||||
with open(self.db100_cache_file, "r", encoding="utf-8") as f:
|
||||
with open(cache_file, "r", encoding="utf-8") as f:
|
||||
content = f.read()
|
||||
|
||||
# 2. 定位"原始字节数据:"行(匹配DB100ReaderThread的文件格式)
|
||||
db_match = False
|
||||
data_line = None
|
||||
for line in content.splitlines():
|
||||
if "原始字节数据:" in line:
|
||||
if f"DB{db_number}" in line and "区域信息" in line:
|
||||
db_match = True
|
||||
if db_match and "原始字节数据:" in line:
|
||||
data_line = line.strip()
|
||||
break
|
||||
if not data_line:
|
||||
self.logger.error(f"❌ DB100缓存文件格式错误:未找到'原始字节数据'行")
|
||||
if not db_match or not data_line:
|
||||
self.logger.error(f"文件缓存中无DB{db_number}的有效数据(文件:{cache_file})")
|
||||
return None
|
||||
|
||||
# 3. 提取字节列表字符串(如"[16,0,0,...]")
|
||||
list_start = data_line.find("[")
|
||||
list_end = data_line.find("]") + 1 # 包含闭合括号
|
||||
|
||||
if list_start == -1 or list_end == 0:
|
||||
self.logger.error(f"❌ DB100缓存文件格式错误:未找到有效字节列表")
|
||||
self.logger.error(f"❌ DB{db_number}缓存文件格式错误:未找到有效字节列表")
|
||||
return None
|
||||
byte_list_str = data_line[list_start:list_end]
|
||||
|
||||
@ -432,23 +436,24 @@ class Snap7Client:
|
||||
if not isinstance(byte_list, list) or not all(
|
||||
isinstance(b, int) and 0 <= b <= 255 for b in byte_list
|
||||
):
|
||||
self.logger.error(f"❌ DB100缓存文件数据无效:字节列表包含非整数或超出范围值")
|
||||
self.logger.error(f"❌ DB{db_number}缓存文件数据无效:字节列表包含非整数或超出范围值")
|
||||
return None
|
||||
|
||||
# 5. 验证数据长度(至少满足DB100的6000字节)
|
||||
if len(byte_list) < 6000:
|
||||
self.logger.warning(f"⚠️ DB100缓存文件数据不完整(仅{len(byte_list)}字节,期望6000字节)")
|
||||
self.logger.debug(f"✅ 从缓存文件读取DB100数据({len(byte_list)}字节)")
|
||||
if len(byte_list) < required_size:
|
||||
self.logger.warning(f"⚠️ DB{db_number}缓存文件数据不完整(仅{len(byte_list)}字节,期望{required_size}字节)")
|
||||
self.logger.debug(f"✅ 从缓存文件读取DB{db_number}数据({len(byte_list)}字节)")
|
||||
|
||||
return bytearray(byte_list)
|
||||
|
||||
except FileNotFoundError:
|
||||
self.logger.warning(f"⚠️ DB100缓存文件不存在:{self.db100_cache_file}")
|
||||
self.logger.warning(f"⚠️ DB{db_number}缓存文件不存在:{cache_file}")
|
||||
return None
|
||||
except ast.literal_eval.Error as e:
|
||||
self.logger.error(f"❌ 解析DB100字节列表失败: {e}")
|
||||
self.logger.error(f"❌ 解析DB{db_number}字节列表失败: {e}")
|
||||
return None
|
||||
except Exception as e:
|
||||
self.logger.error(f"❌ 读取DB100缓存文件异常: {e}", exc_info=True)
|
||||
self.logger.error(f"❌ 读取DB{db_number}缓存文件异常: {e}", exc_info=True)
|
||||
return None
|
||||
|
||||
def read_generic(self, db_number, offset, data_type, count=1):
|
||||
@ -457,65 +462,70 @@ class Snap7Client:
|
||||
- DB100:优先从缓存文件读取 → 再内存缓存 → 最后PLC
|
||||
- 其他DB块:保持原逻辑(内存缓存→PLC)
|
||||
"""
|
||||
# 1、处理DB100:优先从缓存文件读取
|
||||
if db_number == 100:
|
||||
print(f"从缓存文件中读取{db_number}的数据")
|
||||
db100_raw = self.read_db100_from_file()
|
||||
if db100_raw is not None:
|
||||
# 1、处理DB数据块:优先从缓存文件读取
|
||||
raw_data = None
|
||||
if raw_data is None:
|
||||
cache_file = f"plc_area_DB{db_number}.log"
|
||||
raw_data = self.read_db_from_file_cache(db_number, offset, count, cache_file)
|
||||
if raw_data is not None:
|
||||
print(f"从缓存文件中读取DB{db_number}的数据")
|
||||
self.logger.debug(f"从文件缓存读取DB{db_number}(文件:{cache_file})")
|
||||
|
||||
try:
|
||||
if data_type == 'bool':
|
||||
# 对于bool,offset是位偏移
|
||||
byte_offset = offset // 8
|
||||
bit_offset = offset % 8
|
||||
start_byte = offset // 8
|
||||
start_bit_in_byte = offset % 8
|
||||
|
||||
# 计算需要读取的字节数
|
||||
last_bit = bit_offset + count - 1
|
||||
last_byte = last_bit // 8
|
||||
end_bit = offset + count - 1
|
||||
end_byte = end_bit // 8
|
||||
# 检查数据长度是否足够
|
||||
if last_byte >= len(db100_raw):
|
||||
if end_byte >= len(raw_data):
|
||||
self.logger.warning(
|
||||
f"⚠️ DB100缓存文件数据不足:需要字节{last_byte},实际{len(db100_raw)}字节")
|
||||
f"⚠️ DB{db_number}缓存文件数据不足:需要字节{end_byte},实际{len(raw_data)}字节")
|
||||
else:
|
||||
result = []
|
||||
result = [] # 用于存储解析出的bool值
|
||||
for i in range(count):
|
||||
curr_bit = bit_offset + i
|
||||
curr_byte = curr_bit // 8
|
||||
curr_bit_in_byte = curr_bit % 8
|
||||
result.append(bool(db100_raw[curr_byte] & (1 << curr_bit_in_byte)))
|
||||
current_global_bit = offset + i
|
||||
current_byte = current_global_bit // 8
|
||||
current_bit = current_global_bit % 8
|
||||
result.append(bool(raw_data[current_byte] & (1 << current_bit)))
|
||||
return result[0] if count == 1 else result
|
||||
|
||||
elif data_type == 'byte':
|
||||
if offset + count > len(db100_raw):
|
||||
if offset + count > len(raw_data):
|
||||
self.logger.warning(
|
||||
f"⚠️ DB100缓存文件数据不足:需要偏移{offset}+{count}字节,实际{len(db100_raw)}字节")
|
||||
f"⚠️ DB100缓存文件数据不足:需要偏移{offset}+{count}字节,实际{len(raw_data)}字节")
|
||||
else:
|
||||
data = db100_raw[offset:offset + count]
|
||||
data = raw_data[offset:offset + count]
|
||||
return [b for b in data] if count > 1 else data[0]
|
||||
|
||||
elif data_type in ['int', 'word']:
|
||||
byte_per_data = 2
|
||||
total_bytes = byte_per_data * count
|
||||
if offset + total_bytes > len(db100_raw):
|
||||
if offset + total_bytes > len(raw_data):
|
||||
self.logger.warning(
|
||||
f"⚠️ DB100缓存文件数据不足:需要偏移{offset}+{total_bytes}字节,实际{len(db100_raw)}字节")
|
||||
f"⚠️ DB100缓存文件数据不足:需要偏移{offset}+{total_bytes}字节,实际{len(raw_data)}字节")
|
||||
else:
|
||||
result = []
|
||||
for i in range(count):
|
||||
start = offset + i * byte_per_data
|
||||
slice_data = db100_raw[start:start + byte_per_data]
|
||||
slice_data = raw_data[start:start + byte_per_data]
|
||||
result.append(get_int(slice_data, 0) if data_type == 'int' else get_word(slice_data, 0))
|
||||
return result[0] if count == 1 else result
|
||||
|
||||
elif data_type in ['dint', 'dword', 'real']:
|
||||
byte_per_data = 4
|
||||
total_bytes = byte_per_data * count
|
||||
if offset + total_bytes > len(db100_raw):
|
||||
if offset + total_bytes > len(raw_data):
|
||||
self.logger.warning(
|
||||
f"⚠️ DB100缓存文件数据不足:需要偏移{offset}+{total_bytes}字节,实际{len(db100_raw)}字节")
|
||||
f"⚠️ DB100缓存文件数据不足:需要偏移{offset}+{total_bytes}字节,实际{len(raw_data)}字节")
|
||||
else:
|
||||
result = []
|
||||
for i in range(count):
|
||||
start = offset + i * byte_per_data
|
||||
slice_data = db100_raw[start:start + byte_per_data]
|
||||
slice_data = raw_data[start:start + byte_per_data]
|
||||
if data_type == 'dint':
|
||||
result.append(get_dint(slice_data, 0))
|
||||
elif data_type == 'dword':
|
||||
@ -530,11 +540,11 @@ class Snap7Client:
|
||||
return None
|
||||
|
||||
except Exception as e:
|
||||
self.logger.error(f"❌ 解析DB100缓存文件数据异常(类型:{data_type},偏移:{offset}): {e}",
|
||||
self.logger.error(f"❌ 解析DB{db_number}缓存文件数据异常(类型:{data_type},偏移:{offset}): {e}",
|
||||
exc_info=True)
|
||||
|
||||
# 文件读取失败,fallback到原逻辑(内存缓存→PLC)
|
||||
self.logger.debug(f"⚠️ DB100缓存文件读取失败, fallback到内存缓存/PLC")
|
||||
self.logger.debug(f"⚠️ DB{db_number}缓存文件读取失败, fallback到内存缓存/PLC")
|
||||
|
||||
# 缓存读取失败或未启用缓存,直接从PLC读取
|
||||
if not self.connected and not self.connect():
|
||||
@ -542,7 +552,7 @@ class Snap7Client:
|
||||
return None
|
||||
|
||||
try:
|
||||
print(f"从PLC中读取{db_number}的数据")
|
||||
print(f"从PLC中读取DB{db_number}的数据")
|
||||
if data_type == 'bool':
|
||||
# 对于bool,offset是位偏移
|
||||
byte_offset = offset // 8
|
||||
|
||||
Reference in New Issue
Block a user