添加其他数据块的读取线程,并且解决线程冲突问题。另外解决了之前bool类型读取的Bug
This commit is contained in:
@ -6,7 +6,7 @@ from plc_manager import PLCManager
|
|||||||
from cache_manager import CacheManager
|
from cache_manager import CacheManager
|
||||||
from api_server import APIServer
|
from api_server import APIServer
|
||||||
from config_manager import ConfigManager
|
from config_manager import ConfigManager
|
||||||
from db100_reader import DB100ReaderThread
|
from plc_data_reader import PLCDataReaderThread
|
||||||
|
|
||||||
|
|
||||||
class GatewayApp:
|
class GatewayApp:
|
||||||
@ -20,21 +20,22 @@ class GatewayApp:
|
|||||||
self.api_server = None
|
self.api_server = None
|
||||||
self.reload_flag = False
|
self.reload_flag = False
|
||||||
self.reload_lock = threading.Lock()
|
self.reload_lock = threading.Lock()
|
||||||
# DB100ReaderThread线程相关初始化
|
|
||||||
self.db100_reader_thread = None
|
|
||||||
|
|
||||||
self.logger = logging.getLogger("GatewayApp")
|
self.logger = logging.getLogger("GatewayApp")
|
||||||
|
|
||||||
|
# 存储所有读取线程(便于配置重载时停止)
|
||||||
|
self.reader_threads = []
|
||||||
|
|
||||||
# 加载初始配置
|
# 加载初始配置
|
||||||
self.load_configuration()
|
self.load_configuration()
|
||||||
|
|
||||||
def load_configuration(self):
|
def load_configuration(self):
|
||||||
"""加载配置并初始化组件"""
|
"""加载配置并初始化组件"""
|
||||||
|
# 停止所有已启动的读取线程
|
||||||
|
self.stop_all_reader_threads()
|
||||||
# 加载配置
|
# 加载配置
|
||||||
if not self.config_manager.load_config():
|
if not self.config_manager.load_config():
|
||||||
self.logger.error("Failed to load initial configuration")
|
self.logger.error("Failed to load initial configuration")
|
||||||
return False
|
return False
|
||||||
|
|
||||||
config = self.config_manager.get_config()
|
config = self.config_manager.get_config()
|
||||||
|
|
||||||
# 重新初始化PLC连接
|
# 重新初始化PLC连接
|
||||||
@ -47,7 +48,6 @@ class GatewayApp:
|
|||||||
if self.cache_manager:
|
if self.cache_manager:
|
||||||
self.logger.info("Stopping existing cache manager...")
|
self.logger.info("Stopping existing cache manager...")
|
||||||
self.cache_manager.stop()
|
self.cache_manager.stop()
|
||||||
|
|
||||||
self.logger.info("Initializing cache manager...")
|
self.logger.info("Initializing cache manager...")
|
||||||
self.cache_manager = CacheManager(config, self.plc_manager, app=self)
|
self.cache_manager = CacheManager(config, self.plc_manager, app=self)
|
||||||
self.cache_manager.start()
|
self.cache_manager.start()
|
||||||
@ -60,17 +60,57 @@ class GatewayApp:
|
|||||||
self.api_server = APIServer(self.cache_manager, self.config_path)
|
self.api_server = APIServer(self.cache_manager, self.config_path)
|
||||||
self.api_server.start()
|
self.api_server.start()
|
||||||
|
|
||||||
# 重新初始化DB100ReaderThread线程
|
# 动态启动多区域读取线程
|
||||||
for plc in config["plcs"]:
|
self.start_all_reader_threads(config)
|
||||||
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.logger.info("Configuration loaded successfully")
|
self.logger.info("Configuration loaded successfully")
|
||||||
return True
|
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):
|
def check_for_reload(self):
|
||||||
"""检查是否需要重载配置"""
|
"""检查是否需要重载配置"""
|
||||||
while True:
|
while True:
|
||||||
|
|||||||
164
gateway/plc_data_reader.py
Normal file
164
gateway/plc_data_reader.py
Normal file
@ -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)
|
||||||
@ -33,11 +33,6 @@ class Snap7Client:
|
|||||||
self.retry_count = 0
|
self.retry_count = 0
|
||||||
self.logger = logging.getLogger(f"Snap7Client[{ip}]")
|
self.logger = logging.getLogger(f"Snap7Client[{ip}]")
|
||||||
|
|
||||||
# ---------------
|
|
||||||
# 新增
|
|
||||||
# ---------------
|
|
||||||
self.db100_cache_file = "db100_latest_data.log"
|
|
||||||
|
|
||||||
def is_valid_connection(self):
|
def is_valid_connection(self):
|
||||||
"""检查连接是否真正有效"""
|
"""检查连接是否真正有效"""
|
||||||
try:
|
try:
|
||||||
@ -59,6 +54,7 @@ class Snap7Client:
|
|||||||
|
|
||||||
self.last_connect_attempt = current_time
|
self.last_connect_attempt = current_time
|
||||||
try:
|
try:
|
||||||
|
with self.lock:
|
||||||
self.client.connect(self.ip, self.rack, self.slot)
|
self.client.connect(self.ip, self.rack, self.slot)
|
||||||
|
|
||||||
# 验证连接是否真正有效
|
# 验证连接是否真正有效
|
||||||
@ -98,7 +94,7 @@ class Snap7Client:
|
|||||||
return None # 返回None而不是零填充数据
|
return None # 返回None而不是零填充数据
|
||||||
|
|
||||||
try:
|
try:
|
||||||
with self.lock: # 进入锁,其他线程需等待
|
with self.lock:
|
||||||
data = self.client.db_read(db_number, offset, size)
|
data = self.client.db_read(db_number, offset, size)
|
||||||
# 验证返回数据的有效性
|
# 验证返回数据的有效性
|
||||||
if data is None or len(data) != size:
|
if data is None or len(data) != size:
|
||||||
@ -162,7 +158,7 @@ class Snap7Client:
|
|||||||
return False
|
return False
|
||||||
|
|
||||||
try:
|
try:
|
||||||
|
with self.lock:
|
||||||
self.client.db_write(db_number, offset, data)
|
self.client.db_write(db_number, offset, data)
|
||||||
self.logger.debug(f"Wrote {len(data)} bytes to DB{db_number} offset {offset}")
|
self.logger.debug(f"Wrote {len(data)} bytes to DB{db_number} offset {offset}")
|
||||||
return True
|
return True
|
||||||
@ -215,7 +211,6 @@ class Snap7Client:
|
|||||||
|
|
||||||
try:
|
try:
|
||||||
with self.lock:
|
with self.lock:
|
||||||
|
|
||||||
self.client.db_write(db_number, offset, data)
|
self.client.db_write(db_number, offset, data)
|
||||||
self.logger.debug(f"Wrote {len(data)} bytes to DB{db_number} offset {offset}")
|
self.logger.debug(f"Wrote {len(data)} bytes to DB{db_number} offset {offset}")
|
||||||
return True
|
return True
|
||||||
@ -267,7 +262,6 @@ class Snap7Client:
|
|||||||
return False
|
return False
|
||||||
|
|
||||||
try:
|
try:
|
||||||
with self.lock:
|
|
||||||
if data_type == 'bool':
|
if data_type == 'bool':
|
||||||
# 对于bool,offset是位偏移
|
# 对于bool,offset是位偏移
|
||||||
byte_offset = offset // 8
|
byte_offset = offset // 8
|
||||||
@ -398,31 +392,41 @@ class Snap7Client:
|
|||||||
self.logger.error(f"Error caching data: {e}")
|
self.logger.error(f"Error caching data: {e}")
|
||||||
return False
|
return False
|
||||||
|
|
||||||
def read_db100_from_file(self):
|
def read_db_from_file_cache(self, db_number, offset, required_size, cache_file):
|
||||||
"""
|
"""
|
||||||
从缓存文件中解析DB100的原始字节数据
|
通用文件缓存读取:从指定文件读取DB块缓存,返回需要的字节片段
|
||||||
返回: bytearray(成功)/ None(失败)
|
参数:
|
||||||
|
db_number: DB块编号
|
||||||
|
offset: 需要读取的起始偏移(字节)
|
||||||
|
required_size: 需要读取的字节数
|
||||||
|
cache_file: 缓存文件名(动态生成)
|
||||||
|
返回值:
|
||||||
|
bytearray | None: 所需的字节片段,失败返回None
|
||||||
"""
|
"""
|
||||||
try:
|
try:
|
||||||
# 1. 读取缓存文件内容
|
# 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()
|
content = f.read()
|
||||||
|
|
||||||
# 2. 定位"原始字节数据:"行(匹配DB100ReaderThread的文件格式)
|
# 2. 定位"原始字节数据:"行(匹配DB100ReaderThread的文件格式)
|
||||||
|
db_match = False
|
||||||
data_line = None
|
data_line = None
|
||||||
for line in content.splitlines():
|
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()
|
data_line = line.strip()
|
||||||
break
|
break
|
||||||
if not data_line:
|
if not db_match or not data_line:
|
||||||
self.logger.error(f"❌ DB100缓存文件格式错误:未找到'原始字节数据'行")
|
self.logger.error(f"文件缓存中无DB{db_number}的有效数据(文件:{cache_file})")
|
||||||
return None
|
return None
|
||||||
|
|
||||||
# 3. 提取字节列表字符串(如"[16,0,0,...]")
|
# 3. 提取字节列表字符串(如"[16,0,0,...]")
|
||||||
list_start = data_line.find("[")
|
list_start = data_line.find("[")
|
||||||
list_end = data_line.find("]") + 1 # 包含闭合括号
|
list_end = data_line.find("]") + 1 # 包含闭合括号
|
||||||
|
|
||||||
if list_start == -1 or list_end == 0:
|
if list_start == -1 or list_end == 0:
|
||||||
self.logger.error(f"❌ DB100缓存文件格式错误:未找到有效字节列表")
|
self.logger.error(f"❌ DB{db_number}缓存文件格式错误:未找到有效字节列表")
|
||||||
return None
|
return None
|
||||||
byte_list_str = data_line[list_start:list_end]
|
byte_list_str = data_line[list_start:list_end]
|
||||||
|
|
||||||
@ -432,23 +436,24 @@ class Snap7Client:
|
|||||||
if not isinstance(byte_list, list) or not all(
|
if not isinstance(byte_list, list) or not all(
|
||||||
isinstance(b, int) and 0 <= b <= 255 for b in byte_list
|
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
|
return None
|
||||||
|
|
||||||
# 5. 验证数据长度(至少满足DB100的6000字节)
|
# 5. 验证数据长度(至少满足DB100的6000字节)
|
||||||
if len(byte_list) < 6000:
|
if len(byte_list) < required_size:
|
||||||
self.logger.warning(f"⚠️ DB100缓存文件数据不完整(仅{len(byte_list)}字节,期望6000字节)")
|
self.logger.warning(f"⚠️ DB{db_number}缓存文件数据不完整(仅{len(byte_list)}字节,期望{required_size}字节)")
|
||||||
self.logger.debug(f"✅ 从缓存文件读取DB100数据({len(byte_list)}字节)")
|
self.logger.debug(f"✅ 从缓存文件读取DB{db_number}数据({len(byte_list)}字节)")
|
||||||
|
|
||||||
return bytearray(byte_list)
|
return bytearray(byte_list)
|
||||||
|
|
||||||
except FileNotFoundError:
|
except FileNotFoundError:
|
||||||
self.logger.warning(f"⚠️ DB100缓存文件不存在:{self.db100_cache_file}")
|
self.logger.warning(f"⚠️ DB{db_number}缓存文件不存在:{cache_file}")
|
||||||
return None
|
return None
|
||||||
except ast.literal_eval.Error as e:
|
except ast.literal_eval.Error as e:
|
||||||
self.logger.error(f"❌ 解析DB100字节列表失败: {e}")
|
self.logger.error(f"❌ 解析DB{db_number}字节列表失败: {e}")
|
||||||
return None
|
return None
|
||||||
except Exception as e:
|
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
|
return None
|
||||||
|
|
||||||
def read_generic(self, db_number, offset, data_type, count=1):
|
def read_generic(self, db_number, offset, data_type, count=1):
|
||||||
@ -457,65 +462,70 @@ class Snap7Client:
|
|||||||
- DB100:优先从缓存文件读取 → 再内存缓存 → 最后PLC
|
- DB100:优先从缓存文件读取 → 再内存缓存 → 最后PLC
|
||||||
- 其他DB块:保持原逻辑(内存缓存→PLC)
|
- 其他DB块:保持原逻辑(内存缓存→PLC)
|
||||||
"""
|
"""
|
||||||
# 1、处理DB100:优先从缓存文件读取
|
# 1、处理DB数据块:优先从缓存文件读取
|
||||||
if db_number == 100:
|
raw_data = None
|
||||||
print(f"从缓存文件中读取{db_number}的数据")
|
if raw_data is None:
|
||||||
db100_raw = self.read_db100_from_file()
|
cache_file = f"plc_area_DB{db_number}.log"
|
||||||
if db100_raw is not None:
|
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:
|
try:
|
||||||
if data_type == 'bool':
|
if data_type == 'bool':
|
||||||
# 对于bool,offset是位偏移
|
# 对于bool,offset是位偏移
|
||||||
byte_offset = offset // 8
|
start_byte = offset // 8
|
||||||
bit_offset = offset % 8
|
start_bit_in_byte = offset % 8
|
||||||
|
|
||||||
# 计算需要读取的字节数
|
# 计算需要读取的字节数
|
||||||
last_bit = bit_offset + count - 1
|
end_bit = offset + count - 1
|
||||||
last_byte = last_bit // 8
|
end_byte = end_bit // 8
|
||||||
# 检查数据长度是否足够
|
# 检查数据长度是否足够
|
||||||
if last_byte >= len(db100_raw):
|
if end_byte >= len(raw_data):
|
||||||
self.logger.warning(
|
self.logger.warning(
|
||||||
f"⚠️ DB100缓存文件数据不足:需要字节{last_byte},实际{len(db100_raw)}字节")
|
f"⚠️ DB{db_number}缓存文件数据不足:需要字节{end_byte},实际{len(raw_data)}字节")
|
||||||
else:
|
else:
|
||||||
result = []
|
result = [] # 用于存储解析出的bool值
|
||||||
for i in range(count):
|
for i in range(count):
|
||||||
curr_bit = bit_offset + i
|
current_global_bit = offset + i
|
||||||
curr_byte = curr_bit // 8
|
current_byte = current_global_bit // 8
|
||||||
curr_bit_in_byte = curr_bit % 8
|
current_bit = current_global_bit % 8
|
||||||
result.append(bool(db100_raw[curr_byte] & (1 << curr_bit_in_byte)))
|
result.append(bool(raw_data[current_byte] & (1 << current_bit)))
|
||||||
return result[0] if count == 1 else result
|
return result[0] if count == 1 else result
|
||||||
|
|
||||||
elif data_type == 'byte':
|
elif data_type == 'byte':
|
||||||
if offset + count > len(db100_raw):
|
if offset + count > len(raw_data):
|
||||||
self.logger.warning(
|
self.logger.warning(
|
||||||
f"⚠️ DB100缓存文件数据不足:需要偏移{offset}+{count}字节,实际{len(db100_raw)}字节")
|
f"⚠️ DB100缓存文件数据不足:需要偏移{offset}+{count}字节,实际{len(raw_data)}字节")
|
||||||
else:
|
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]
|
return [b for b in data] if count > 1 else data[0]
|
||||||
|
|
||||||
elif data_type in ['int', 'word']:
|
elif data_type in ['int', 'word']:
|
||||||
byte_per_data = 2
|
byte_per_data = 2
|
||||||
total_bytes = byte_per_data * count
|
total_bytes = byte_per_data * count
|
||||||
if offset + total_bytes > len(db100_raw):
|
if offset + total_bytes > len(raw_data):
|
||||||
self.logger.warning(
|
self.logger.warning(
|
||||||
f"⚠️ DB100缓存文件数据不足:需要偏移{offset}+{total_bytes}字节,实际{len(db100_raw)}字节")
|
f"⚠️ DB100缓存文件数据不足:需要偏移{offset}+{total_bytes}字节,实际{len(raw_data)}字节")
|
||||||
else:
|
else:
|
||||||
result = []
|
result = []
|
||||||
for i in range(count):
|
for i in range(count):
|
||||||
start = offset + i * byte_per_data
|
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))
|
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
|
return result[0] if count == 1 else result
|
||||||
|
|
||||||
elif data_type in ['dint', 'dword', 'real']:
|
elif data_type in ['dint', 'dword', 'real']:
|
||||||
byte_per_data = 4
|
byte_per_data = 4
|
||||||
total_bytes = byte_per_data * count
|
total_bytes = byte_per_data * count
|
||||||
if offset + total_bytes > len(db100_raw):
|
if offset + total_bytes > len(raw_data):
|
||||||
self.logger.warning(
|
self.logger.warning(
|
||||||
f"⚠️ DB100缓存文件数据不足:需要偏移{offset}+{total_bytes}字节,实际{len(db100_raw)}字节")
|
f"⚠️ DB100缓存文件数据不足:需要偏移{offset}+{total_bytes}字节,实际{len(raw_data)}字节")
|
||||||
else:
|
else:
|
||||||
result = []
|
result = []
|
||||||
for i in range(count):
|
for i in range(count):
|
||||||
start = offset + i * byte_per_data
|
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':
|
if data_type == 'dint':
|
||||||
result.append(get_dint(slice_data, 0))
|
result.append(get_dint(slice_data, 0))
|
||||||
elif data_type == 'dword':
|
elif data_type == 'dword':
|
||||||
@ -530,11 +540,11 @@ class Snap7Client:
|
|||||||
return None
|
return None
|
||||||
|
|
||||||
except Exception as e:
|
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)
|
exc_info=True)
|
||||||
|
|
||||||
# 文件读取失败,fallback到原逻辑(内存缓存→PLC)
|
# 文件读取失败,fallback到原逻辑(内存缓存→PLC)
|
||||||
self.logger.debug(f"⚠️ DB100缓存文件读取失败, fallback到内存缓存/PLC")
|
self.logger.debug(f"⚠️ DB{db_number}缓存文件读取失败, fallback到内存缓存/PLC")
|
||||||
|
|
||||||
# 缓存读取失败或未启用缓存,直接从PLC读取
|
# 缓存读取失败或未启用缓存,直接从PLC读取
|
||||||
if not self.connected and not self.connect():
|
if not self.connected and not self.connect():
|
||||||
@ -542,7 +552,7 @@ class Snap7Client:
|
|||||||
return None
|
return None
|
||||||
|
|
||||||
try:
|
try:
|
||||||
print(f"从PLC中读取{db_number}的数据")
|
print(f"从PLC中读取DB{db_number}的数据")
|
||||||
if data_type == 'bool':
|
if data_type == 'bool':
|
||||||
# 对于bool,offset是位偏移
|
# 对于bool,offset是位偏移
|
||||||
byte_offset = offset // 8
|
byte_offset = offset // 8
|
||||||
|
|||||||
Reference in New Issue
Block a user