将从缓存文件中读取改成从内存数组中进行读取
This commit is contained in:
@ -1,16 +1,14 @@
|
||||
from copyreg import dispatch_table
|
||||
|
||||
import snap7
|
||||
import logging
|
||||
from threading import Lock
|
||||
import time
|
||||
from snap7.util import *
|
||||
import ast
|
||||
|
||||
class Snap7Client:
|
||||
"""Snap7客户端,处理与PLC的通信"""
|
||||
|
||||
def __init__(self, ip, rack, slot, max_retries=5, retry_base_delay=1):
|
||||
def __init__(self, ip, rack, slot, reader_threads, max_retries=5, retry_base_delay=1):
|
||||
"""
|
||||
初始化Snap7客户端
|
||||
|
||||
@ -31,6 +29,7 @@ class Snap7Client:
|
||||
self.retry_base_delay = retry_base_delay
|
||||
self.last_connect_attempt = 0
|
||||
self.retry_count = 0
|
||||
self.reader_threads = reader_threads
|
||||
self.logger = logging.getLogger(f"Snap7Client[{ip}]")
|
||||
|
||||
def is_valid_connection(self):
|
||||
@ -392,71 +391,74 @@ class Snap7Client:
|
||||
self.logger.error(f"Error caching data: {e}")
|
||||
return False
|
||||
|
||||
def read_db_from_file_cache(self, db_number, offset, required_size, cache_file):
|
||||
def read_db_from_memory_cache(self, db_number, offset, required_size, array_name, reader_threads):
|
||||
"""
|
||||
通用文件缓存读取:从指定文件读取DB块缓存,返回需要的字节片段
|
||||
参数:
|
||||
db_number: DB块编号
|
||||
offset: 需要读取的起始偏移(字节)
|
||||
required_size: 需要读取的字节数
|
||||
cache_file: 缓存文件名(动态生成)
|
||||
通用内存数组读取:从PLCDataReaderThread实例的内存数组中,提取DB块的目标字节片段
|
||||
参数说明:
|
||||
- 已删除无用的 cache_file 参数(原文件缓存逻辑已替换为内存数组)
|
||||
返回值:
|
||||
bytearray | None: 所需的字节片段,失败返回None
|
||||
"""
|
||||
try:
|
||||
# 1. 读取缓存文件内容
|
||||
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 f"DB{db_number}" in line and "区域信息" in line:
|
||||
db_match = True
|
||||
if db_match and "原始字节数据:" in line:
|
||||
data_line = line.strip()
|
||||
# -------------------------- 关键修改1:正确遍历元组并匹配线程 --------------------------
|
||||
target_thread = None
|
||||
# 遍历 reader_threads:每个元素是 (area_name_str, PLCDataReaderThread实例) 的元组
|
||||
for area_name_str, thread_instance in reader_threads:
|
||||
# 匹配条件:线程实例的 custom_array_name == 传入的 array_name(数组名在_thread实例上)
|
||||
if thread_instance.custom_array_name == array_name:
|
||||
target_thread = thread_instance # 拿到真正的线程实例
|
||||
break
|
||||
if not db_match or not data_line:
|
||||
self.logger.error(f"文件缓存中无DB{db_number}的有效数据(文件:{cache_file})")
|
||||
|
||||
# -------------------------- 关键修改2:校验线程实例是否找到 --------------------------
|
||||
if target_thread is None:
|
||||
# 若未找到,打印所有已存在的线程数组名,便于调试
|
||||
existing_arrays = [t.custom_array_name for _, t in reader_threads]
|
||||
self.logger.error(
|
||||
f"❌ 未找到数组{array_name}对应的线程实例!"
|
||||
f"已存在的数组名:{existing_arrays},请检查数组名是否拼写正确"
|
||||
)
|
||||
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"❌ DB{db_number}缓存文件格式错误:未找到有效字节列表")
|
||||
return None
|
||||
byte_list_str = data_line[list_start:list_end]
|
||||
|
||||
# 4. 解析字符串为整数列表,再转成bytearray
|
||||
byte_list = ast.literal_eval(byte_list_str)
|
||||
# 验证列表有效性(必须是0-255的整数)
|
||||
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"❌ DB{db_number}缓存文件数据无效:字节列表包含非整数或超出范围值")
|
||||
# -------------------------- 后续逻辑不变(但需确认线程实例有效) --------------------------
|
||||
# 校验线程实例上是否存在数组(array_name 就是线程的 custom_array_name)
|
||||
if not hasattr(target_thread, array_name):
|
||||
self.logger.error(f"❌ 线程实例{target_thread.name}上不存在数组{array_name}(线程初始化失败)")
|
||||
return None
|
||||
|
||||
# 5. 验证数据长度(至少满足DB100的6000字节)
|
||||
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)}字节)")
|
||||
# 线程安全获取数组数据(调用线程自带方法,内部已加锁)
|
||||
custom_array = target_thread.get_custom_array()
|
||||
if not custom_array:
|
||||
self.logger.warning(f"⚠️ 数组{array_name}为空,无可用数据")
|
||||
return None
|
||||
|
||||
return bytearray(byte_list)
|
||||
if not isinstance(custom_array[0], bytearray):
|
||||
self.logger.error(
|
||||
f"❌ 数组{array_name}的元素类型错误:"
|
||||
f"期望bytearray,实际{type(custom_array).__name__}"
|
||||
)
|
||||
return None
|
||||
|
||||
# 验证数据长度是否满足需求
|
||||
total_byte_len = len(custom_array[0])
|
||||
if total_byte_len < offset + required_size:
|
||||
self.logger.warning(
|
||||
f"⚠️ DB{db_number}内存数据不完整:"
|
||||
f"数组{array_name}最新数据共{total_byte_len}字节,"
|
||||
f"需从偏移{offset}读取{required_size}字节(需{offset + required_size}字节)"
|
||||
)
|
||||
available_size = total_byte_len - offset
|
||||
if available_size <= 0:
|
||||
self.logger.error(f"❌ 偏移{offset}超出数据范围(最大偏移:{total_byte_len - 1})")
|
||||
return None
|
||||
required_size = available_size
|
||||
|
||||
return custom_array[0]
|
||||
|
||||
except FileNotFoundError:
|
||||
self.logger.warning(f"⚠️ DB{db_number}缓存文件不存在:{cache_file}")
|
||||
return None
|
||||
except ast.literal_eval.Error as e:
|
||||
self.logger.error(f"❌ 解析DB{db_number}字节列表失败: {e}")
|
||||
return None
|
||||
except Exception as e:
|
||||
self.logger.error(f"❌ 读取DB{db_number}缓存文件异常: {e}", exc_info=True)
|
||||
self.logger.error(f"🔴 读取内存数组{array_name}异常:{str(e)}", exc_info=True)
|
||||
return None
|
||||
|
||||
def read_generic(self, db_number, offset, data_type, cache_file, count=1):
|
||||
def read_generic(self, db_number, offset, data_type, array_name, count=1):
|
||||
"""
|
||||
通用读取接口(改进):
|
||||
- DB100:优先从缓存文件读取 → 再内存缓存 → 最后PLC
|
||||
@ -465,11 +467,11 @@ class Snap7Client:
|
||||
# 1、处理DB数据块:优先从缓存文件读取
|
||||
raw_data = None
|
||||
if raw_data is None:
|
||||
cache_file = cache_file
|
||||
raw_data = self.read_db_from_file_cache(db_number, offset, count, cache_file)
|
||||
array_name = array_name
|
||||
raw_data = self.read_db_from_memory_cache(db_number, offset, count, array_name, self.reader_threads)
|
||||
if raw_data is not None:
|
||||
print(f"从缓存文件中读取DB{db_number}的数据")
|
||||
self.logger.debug(f"从文件缓存读取DB{db_number}(文件:{cache_file})")
|
||||
print(f"从内存数组中读取DB{db_number}的数据")
|
||||
self.logger.debug(f"从内存数组中读取DB{db_number}(数组:{array_name})")
|
||||
|
||||
try:
|
||||
if data_type == 'bool':
|
||||
@ -481,17 +483,14 @@ class Snap7Client:
|
||||
end_bit = offset + count - 1
|
||||
end_byte = end_bit // 8
|
||||
# 检查数据长度是否足够
|
||||
if end_byte >= len(raw_data):
|
||||
self.logger.warning(
|
||||
f"⚠️ DB{db_number}缓存文件数据不足:需要字节{end_byte},实际{len(raw_data)}字节")
|
||||
else:
|
||||
result = [] # 用于存储解析出的bool值
|
||||
for i in range(count):
|
||||
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
|
||||
|
||||
result = [] # 用于存储解析出的bool值
|
||||
for i in range(count):
|
||||
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(raw_data):
|
||||
@ -502,18 +501,18 @@ class Snap7Client:
|
||||
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(raw_data):
|
||||
self.logger.warning(
|
||||
f"⚠️ DB100缓存文件数据不足:需要偏移{offset}+{total_bytes}字节,实际{len(raw_data)}字节")
|
||||
else:
|
||||
result = []
|
||||
for i in range(count):
|
||||
start = offset + i * 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
|
||||
total_bytes = 2 * count
|
||||
data = self.read_db(db_number, offset, total_bytes)
|
||||
if data is None:
|
||||
return None
|
||||
|
||||
result = []
|
||||
for i in range(count):
|
||||
if data_type == 'int':
|
||||
result.append(get_int(data, i * 2))
|
||||
else: # word
|
||||
result.append(get_word(data, i * 2))
|
||||
return result[0] if count == 1 else result
|
||||
|
||||
elif data_type in ['dint', 'dword', 'real']:
|
||||
byte_per_data = 4
|
||||
|
||||
Reference in New Issue
Block a user