diff --git a/gateway/__pycache__/api_server.cpython-313.pyc b/gateway/__pycache__/api_server.cpython-313.pyc index b99853a..ca99335 100644 Binary files a/gateway/__pycache__/api_server.cpython-313.pyc and b/gateway/__pycache__/api_server.cpython-313.pyc differ diff --git a/gateway/__pycache__/cache_manager.cpython-313.pyc b/gateway/__pycache__/cache_manager.cpython-313.pyc index 0378d6a..f4dff97 100644 Binary files a/gateway/__pycache__/cache_manager.cpython-313.pyc and b/gateway/__pycache__/cache_manager.cpython-313.pyc differ diff --git a/gateway/__pycache__/snap7_client.cpython-313.pyc b/gateway/__pycache__/snap7_client.cpython-313.pyc index 22e2dcd..78258b4 100644 Binary files a/gateway/__pycache__/snap7_client.cpython-313.pyc and b/gateway/__pycache__/snap7_client.cpython-313.pyc differ diff --git a/gateway/api_server.py b/gateway/api_server.py index 61689b9..1ffd84e 100644 --- a/gateway/api_server.py +++ b/gateway/api_server.py @@ -2,14 +2,14 @@ from flask import Flask, jsonify, request, render_template_string, Response import threading import time import json -import logging from functools import wraps from config_manager import ConfigManager +import logging class APIServer: """REST API服务器,提供PLC数据访问和配置管理功能""" - def __init__(self, cache_manager, config_path="../config/config.json"): + def __init__(self, cache_manager, config_path="config/config.json"): """ 初始化API服务器 @@ -20,12 +20,14 @@ class APIServer: self.cache_manager = cache_manager self.config_manager = ConfigManager(config_path) self.app = Flask(__name__) - self.setup_routes() + self.logger = logging.getLogger("APIServer") self.auth_enabled = True # 可通过配置关闭认证 self.username = "admin" self.password = "admin123" # 实际应用中应从安全存储获取 self.start_time = time.strftime("%Y-%m-%d %H:%M:%S") - self.logger = logging.getLogger("APIServer") + + # 在初始化方法中调用 setup_routes + self.setup_routes() def check_auth(self, username, password): """验证用户名和密码""" @@ -59,7 +61,15 @@ class APIServer: summary[plc_name] = {} for area_name, area in areas.items(): last_update = self.cache_manager.last_update[plc_name][area_name] - status = area["status"] + plc_last_connected = self.cache_manager.plc_last_connected.get(plc_name, 0) + + # 确定状态 + if plc_last_connected == 0: + status = "never_connected" + elif time.time() - plc_last_connected > 5: + status = "disconnected" + else: + status = area["status"] summary[plc_name][area_name] = { "status": status, @@ -90,7 +100,7 @@ class APIServer: th { background-color: #f2f2f2; } .status-connected { color: green; font-weight: bold; } .status-disconnected { color: red; } - .status-error { color: orange; } + .status-never-connected { color: orange; } .api-section { margin-top: 30px; } .api-endpoint { background-color: #f9f9f9; padding: 10px; margin: 5px 0; border-radius: 4px; } .config-link { margin-top: 20px; padding: 10px; background-color: #e9f7fe; border-radius: 4px; } @@ -117,19 +127,25 @@ class APIServer: for area_name, area in areas.items(): status_class = "" + status_text = area["status"] + if area["status"] == "connected": status_class = "status-connected" + elif area["status"] == "never_connected": + status_class = "status-never-connected" + status_text = "Never connected" elif area["status"] == "disconnected": status_class = "status-disconnected" + status_text = "Disconnected" else: - status_class = "status-error" + status_class = "status-disconnected" html += f""" {area_name} {area['type']} {area['size']} - {area['status']} + {status_text} {area['last_update']} """ diff --git a/gateway/cache_manager.py b/gateway/cache_manager.py index 32f8fff..9815846 100644 --- a/gateway/cache_manager.py +++ b/gateway/cache_manager.py @@ -23,6 +23,7 @@ class CacheManager: self.lock = threading.Lock() self.thread = None self.last_update = {} + self.plc_last_connected = {} # 跟踪PLC最后连接时间 self.logger = logging.getLogger("CacheManager") self.init_cache() @@ -32,6 +33,7 @@ class CacheManager: plc_name = plc["name"] self.cache[plc_name] = {} self.last_update[plc_name] = {} + self.plc_last_connected[plc_name] = 0 # 初始化为0(未连接) for area in plc["areas"]: name = area["name"] @@ -55,19 +57,37 @@ class CacheManager: plc_name = plc["name"] client = self.plc_manager.get_plc(plc_name) + # 检查PLC连接状态 + plc_connected = client.connected + for area in plc["areas"]: if area["type"] in ["read", "read_write"]: name = area["name"] try: data = client.read_db(area["db_number"], area["offset"], area["size"]) - with self.lock: - self.cache[plc_name][name]["data"] = bytearray(data) - self.cache[plc_name][name]["status"] = "connected" - self.last_update[plc_name][name] = time.time() + # 验证数据有效性 + if data and len(data) == area["size"]: + with self.lock: + self.cache[plc_name][name]["data"] = bytearray(data) + self.cache[plc_name][name]["status"] = "connected" + self.last_update[plc_name][name] = time.time() + # 更新PLC连接时间 + self.plc_last_connected[plc_name] = time.time() + else: + with self.lock: + self.cache[plc_name][name]["status"] = "disconnected" + self.logger.warning(f"PLC {plc_name} area {name} returned invalid data") except Exception as e: with self.lock: self.cache[plc_name][name]["status"] = "disconnected" self.logger.warning(f"PLC {plc_name} area {name} disconnected: {e}") + + # 更新所有区域的PLC连接状态 + if not plc_connected: + with self.lock: + for area in plc["areas"]: + name = area["name"] + self.cache[plc_name][name]["status"] = "disconnected" time.sleep(self.refresh_interval) except Exception as e: @@ -110,8 +130,19 @@ class CacheManager: summary[plc_name] = {} for area_name, area in areas.items(): last_update = self.last_update[plc_name][area_name] + plc_last_connected = self.plc_last_connected[plc_name] + + # 如果PLC从未连接过,显示特殊状态 + if plc_last_connected == 0: + status = "never_connected" + # 如果PLC断开连接超过5秒,标记为断开 + elif time.time() - plc_last_connected > 5: + status = "disconnected" + else: + status = area["status"] + summary[plc_name][area_name] = { - "status": area["status"], + "status": status, "last_update": time.strftime("%Y-%m-%d %H:%M:%S", time.localtime(last_update)) if last_update > 0 else "Never", "size": area["size"], "type": area["type"] @@ -124,8 +155,18 @@ class CacheManager: area = self.cache.get(plc_name, {}).get(area_name) if not area: return {"status": "not_found", "message": "PLC or area not found"} + + # 检查PLC连接状态 + plc_last_connected = self.plc_last_connected.get(plc_name, 0) + if plc_last_connected == 0: + status = "never_connected" + elif time.time() - plc_last_connected > 5: + status = "disconnected" + else: + status = area["status"] + return { - "status": area["status"], + "status": status, "last_update": self.last_update[plc_name][area_name], "size": area["size"], "type": area["type"] @@ -144,12 +185,18 @@ class CacheManager: client = self.plc_manager.get_plc(plc_name) try: data = client.read_db(area["db_number"], area["offset"] + offset, length) - # 更新缓存中的这部分数据 - for i in range(length): - area["data"][offset + i] = data[i] - self.last_update[plc_name][area_name] = time.time() - area["status"] = "connected" - return data, None + # 验证数据有效性 + if data and len(data) == length: + # 更新缓存中的这部分数据 + for i in range(length): + area["data"][offset + i] = data[i] + self.last_update[plc_name][area_name] = time.time() + self.plc_last_connected[plc_name] = time.time() + area["status"] = "connected" + return data, None + else: + area["status"] = "disconnected" + return None, "Invalid data returned" except Exception as e: area["status"] = "disconnected" self.logger.error(f"Read failed for {plc_name}/{area_name}: {e}") @@ -176,6 +223,7 @@ class CacheManager: for i in range(len(data)): area["data"][offset + i] = data[i] self.last_update[plc_name][area_name] = time.time() + self.plc_last_connected[plc_name] = time.time() area["status"] = "connected (last write)" return True, None else: @@ -265,13 +313,22 @@ class CacheManager: if not area: return {"error": "Area not found"} + # 检查PLC连接状态 + plc_last_connected = self.plc_last_connected.get(plc_name, 0) + if plc_last_connected == 0: + status = "never_connected" + elif time.time() - plc_last_connected > 5: + status = "disconnected" + else: + status = area["status"] + structure = area.get("structure", []) if structure: return parse_data(area["data"], structure) else: return { "raw_data": list(area["data"]), - "status": area["status"], + "status": status, "last_update": time.strftime("%Y-%m-%d %H:%M:%S", time.localtime(self.last_update[plc_name][area_name])) if self.last_update[plc_name][area_name] > 0 else "Never" } \ No newline at end of file diff --git a/gateway/snap7_client.py b/gateway/snap7_client.py index 94c5218..3bc94dc 100644 --- a/gateway/snap7_client.py +++ b/gateway/snap7_client.py @@ -29,8 +29,18 @@ class Snap7Client: self.retry_count = 0 self.logger = logging.getLogger(f"Snap7Client[{ip}]") + def is_valid_connection(self): + """检查连接是否真正有效""" + try: + # 尝试读取PLC的运行状态 + cpu_state = self.client.get_cpu_state() + return cpu_state in [snap7.snap7types.cpu_statuses['S7CpuStatusRun'], + snap7.snap7types.cpu_statuses['S7CpuStatusStop']] + except: + return False + def connect(self): - """建立与PLC的连接""" + """建立与PLC的连接并验证""" current_time = time.time() # 指数退避重试 if self.retry_count > 0: @@ -41,14 +51,19 @@ class Snap7Client: self.last_connect_attempt = current_time try: self.client.connect(self.ip, self.rack, self.slot) - if self.client.get_connected(): + # 验证连接是否真正有效 + if self.client.get_connected() and self.is_valid_connection(): self.connected = True self.retry_count = 0 # 重置重试计数 - self.logger.info(f"Connected to PLC {self.ip}") + 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 not verified") + 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) @@ -75,6 +90,11 @@ class Snap7Client: try: with self.lock: data = self.client.db_read(db_number, offset, size) + # 验证返回数据的有效性 + if data is None or len(data) != size: + self.connected = False + self.logger.error(f"Read DB{db_number} returned invalid data size (expected {size}, got {len(data) if data else 0})") + return b'\x00' * size return data except Exception as e: self.logger.error(f"Read DB{db_number} error: {e}") @@ -88,7 +108,7 @@ class Snap7Client: Args: db_number: DB编号 offset: 起始偏移量 - data: 要写入的数据 + 要写入的数据 Returns: bool: 是否写入成功