diff --git a/gateway/main.py b/gateway/main.py index 9dee6fe..c259359 100644 --- a/gateway/main.py +++ b/gateway/main.py @@ -6,7 +6,7 @@ from plc_manager import PLCManager from cache_manager import CacheManager from api_server import APIServer from config_manager import ConfigManager -from db100_reader import DB100ReaderThread +from plc_data_reader import PLCDataReaderThread class GatewayApp: @@ -20,21 +20,22 @@ class GatewayApp: self.api_server = None self.reload_flag = False self.reload_lock = threading.Lock() - # DB100ReaderThread线程相关初始化 - self.db100_reader_thread = None - self.logger = logging.getLogger("GatewayApp") + # 存储所有读取线程(便于配置重载时停止) + self.reader_threads = [] + # 加载初始配置 self.load_configuration() def load_configuration(self): """加载配置并初始化组件""" + # 停止所有已启动的读取线程 + self.stop_all_reader_threads() # 加载配置 if not self.config_manager.load_config(): self.logger.error("Failed to load initial configuration") return False - config = self.config_manager.get_config() # 重新初始化PLC连接 @@ -47,7 +48,6 @@ class GatewayApp: if self.cache_manager: self.logger.info("Stopping existing cache manager...") self.cache_manager.stop() - self.logger.info("Initializing cache manager...") self.cache_manager = CacheManager(config, self.plc_manager, app=self) self.cache_manager.start() @@ -60,17 +60,57 @@ class GatewayApp: self.api_server = APIServer(self.cache_manager, self.config_path) self.api_server.start() - # 重新初始化DB100ReaderThread线程 - for plc in config["plcs"]: - plc_name = plc["name"] - client = self.plc_manager.get_plc(plc_name) - self.logger.info("Starting db100_reader_thread...") - self.db100_reader_thread = DB100ReaderThread(client, output_file="db100_latest_data.log") - self.db100_reader_thread.start() + # 动态启动多区域读取线程 + self.start_all_reader_threads(config) self.logger.info("Configuration loaded successfully") return True + def start_all_reader_threads(self, config): + """ + 遍历PLC的areas,为每个read/read_write区域启动读取线程 + """ + for plc_name, plc_client in self.plc_manager.plcs.items(): + plc_config = next((p for p in config["plcs"] if p["name"] == plc_name), None) + if not plc_config or "areas" not in plc_config: + self.logger.warning(f"PLC {plc_name} 无areas配置,跳过启动读取线程") + continue + + # 遍历当前PLC的每个areas + for area_config in plc_config["areas"]: + area_name = area_config["name"] + area_type = area_config["type"] + # 仅处理需要读的区域 + if area_type not in ["read", "read_write"]: + self.logger.debug(f"PLC {plc_name} 区域{area_name}({area_type})无需启动读取线程") + continue + + try: + # 创建并启动线程 + read_thread = PLCDataReaderThread( + plc_client=plc_client, + area_config=area_config, + update_interval=0.03, + output_file_prefix="plc_area_" + ) + read_thread.start() + # 存入线程列表,便于后续停止 + self.reader_threads.append((area_name, read_thread)) + self.logger.info(f"✅ 启动区域读取线程:PLC {plc_name} → {area_name}(DB{area_config['db_number']})") + except Exception as e: + self.logger.error(f"❌ 启动区域{area_name}线程失败: {str(e)}", exc_info=True) + + def stop_all_reader_threads(self): + """停止所有已启动的读取线程""" + if not self.reader_threads: + return + self.logger.info("Stopping all reader threads...") + for area_name, thread in self.reader_threads: + if thread.is_alive(): + thread.stop() + self.logger.debug(f"Stopped reader thread for area {area_name}") + self.reader_threads.clear() + def check_for_reload(self): """检查是否需要重载配置""" while True: diff --git a/gateway/plc_data_reader.py b/gateway/plc_data_reader.py new file mode 100644 index 0000000..1687529 --- /dev/null +++ b/gateway/plc_data_reader.py @@ -0,0 +1,164 @@ +#!/usr/bin/env python +# -*- coding: utf-8 -*- +''' +# @Time : 2025/9/28 13:36 +# @Author : reenrr +# @File : plc_data_reader.py +# @Description : 通用PLC数据读取线程:按配置动态处理read/read_write类型的区域 +# 支持结构化数据解析(real/int/bool等) +''' +import threading +import time +import logging +from datetime import datetime +from snap7.util import get_real, get_int, get_bool, get_word, get_dint # 导入snap7解析工具 + + +class PLCDataReaderThread(threading.Thread): + def __init__(self, plc_client, area_config, update_interval=0.03, output_file_prefix="plc_area_"): + """ + 初始化PLC数据读取线程(配置驱动,支持多区域) + 参数: + plc_client: 已连接的Snap7Client实例(来自PLCManager) + area_config: 单个区域的配置(来自config.json的plcs[].areas) + 示例:{"name":"DB100_Read", "type":"read", "db_number":100, "offset":0, "size":6000, "structure":[...]} + update_interval: 读取间隔(秒),默认30ms + output_file_prefix: 输出文件前缀,最终文件名为“前缀+区域名.log” + """ + # 线程名包含区域名,便于日志区分(如"PLCDataReader_DB100_Read") + thread_name = f"PLCDataReader_{area_config['name']}" + super().__init__(name=thread_name, daemon=True) + + # 1. 核心依赖(PLC客户端+区域配置) + self.plc_client = plc_client + self.area_config = area_config # 动态区域配置,不再硬编码DB100 + self.area_name = area_config["name"] + self.db_number = area_config["db_number"] + self.offset = area_config["offset"] + self.size = area_config["size"] + self.area_type = area_config["type"] # 区分read/read_write/write + + # 2. 线程与输出配置 + self.update_interval = update_interval + self.output_file = f"{output_file_prefix}DB{self.db_number}.log" # 每个区域独立文件 + + # 3. 数据缓存(新增结构化数据存储) + self.running = False + self._latest_data = None # 格式:(timestamp, data_info, raw_bytes, parsed_data) + self._data_lock = threading.Lock() # 线程安全锁 + + # 4. 日志 + self.logger = logging.getLogger(f"PLCDataReader.{self.area_name}") + + def start(self): + """启动线程(验证PLC连接+读写类型适配)""" + # 仅处理需要读的区域(read/read_write),write类型不启动 + if self.area_type not in ["read", "read_write"]: + self.logger.warning(f"跳过启动:区域类型为{self.area_type}(无需循环读取)") + return + + self.running = True + super().start() + self.logger.info(f"✅ 线程启动成功(DB{self.db_number},{self.area_type})") + self.logger.info(f"🔧 配置:间隔{self.update_interval * 1000}ms,读取{self.size}字节,输出{self.output_file}") + + def stop(self): + """停止线程(优雅清理)""" + self.running = False + if self.is_alive(): + self.join(timeout=2.0) + if self.is_alive(): + self.logger.warning("⚠️ 线程未正常退出,强制终止") + self.logger.info(f"🛑 线程已停止(DB{self.db_number})") + + def get_latest_data(self): + """ + 线程安全获取最新数据(返回原始字节+解析后的结构化数据) + 返回示例: + { + "timestamp": "2025-09-28 10:00:00.123", + "data_info": {"area_name":"DB100_Read", "db_number":100, "offset_range":"0-5999", "actual_length":6000}, + "raw_bytes": bytearray(b'\x00\x10...'), + "parsed_data": {"temperature":25.5, "pressure":100, "status":True} # 解析后的字段 + } + """ + with self._data_lock: + if self._latest_data is None: + self.logger.debug("⚠️ 无最新数据缓存") + return None + + timestamp, data_info, raw_bytes, parsed_data = self._latest_data + return { + "timestamp": timestamp, + "data_info": data_info.copy(), + "raw_bytes": raw_bytes.copy() + } + + def run(self): + """线程主循环:读PLC→解析数据→更新缓存→写文件""" + self.logger.debug(f"📌 主循环启动(DB{self.db_number})") + while self.running: + cycle_start = time.time() + try: + # 步骤1:读取PLC区域数据(调用Snap7Client的缓存方法) + cache_success = self.plc_client.cache_large_data_block( + db_number=self.db_number, + offset=self.offset, + size=self.size + ) + + # 步骤2:处理读取结果(缓存+解析+写文件) + if cache_success and self.plc_client.data_cache is not None: + raw_data = self.plc_client.data_cache # 原始字节 + data_len = len(raw_data) + timestamp = datetime.now().strftime("%Y-%m-%d %H:%M:%S.%f")[:-3] + + # 构造数据基本信息 + data_info = { + "area_name": self.area_name, + "db_number": self.db_number, + "offset_range": f"0-{self.size - 1}", + "actual_length": data_len, + "area_type": self.area_type + } + + # 步骤3:线程安全更新内存缓存 + with self._data_lock: + self._latest_data = (timestamp, data_info, raw_data.copy()) + + # 步骤4:写入文件(含原始字节+解析后数据) + self._write_latest_data_to_file(timestamp, data_info, raw_data) + + else: + self.logger.warning(f"⚠️ 数据读取失败(DB{self.db_number}),跳过本次更新") + + # 步骤6:精确控制读取间隔 + cycle_elapsed = time.time() - cycle_start + sleep_time = max(0, self.update_interval - cycle_elapsed) + if sleep_time > 0: + time.sleep(sleep_time) + + except Exception as e: + self.logger.error(f"🔴 循环读取出错: {str(e)}", exc_info=True) + time.sleep(self.update_interval) + + def _write_latest_data_to_file(self, timestamp, data_info, raw_data): + """ + 写入文件:含原始字节+解析后的结构化数据(每个区域独立文件) + """ + try: + # 处理原始字节为列表(便于查看) + data_list = list(raw_data) # 只显示前50字节,避免文件过大 + data_str = f"{data_list} (共{len(raw_data)}字节)" + + # 覆盖写入文件 + with open(self.output_file, "w", encoding="utf-8") as f: + f.write(f"[{timestamp}] 📝 {self.area_name} 最新数据\n") + f.write( + f" - 区域信息:DB{data_info['db_number']}({data_info['offset_range']}),类型{data_info['area_type']}\n") + f.write(f" - 原始字节数据:{data_str}\n") + f.write("=" * 120 + "\n") + + self.logger.debug(f"📤 最新DB{self.db_number}数据已覆盖写入文件:{self.output_file}") + except Exception as e: + self.logger.error(f"🔴 写入DB{self.db_number}数据到文件出错: {str(e)}", exc_info=True) \ No newline at end of file diff --git a/gateway/snap7_client.py b/gateway/snap7_client.py index 8740e7e..c26feb2 100644 --- a/gateway/snap7_client.py +++ b/gateway/snap7_client.py @@ -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