diff --git a/.idea/.gitignore b/.idea/.gitignore new file mode 100644 index 0000000..35410ca --- /dev/null +++ b/.idea/.gitignore @@ -0,0 +1,8 @@ +# 默认忽略的文件 +/shelf/ +/workspace.xml +# 基于编辑器的 HTTP 客户端请求 +/httpRequests/ +# Datasource local storage ignored files +/dataSources/ +/dataSources.local.xml diff --git a/.idea/gateway.iml b/.idea/gateway.iml new file mode 100644 index 0000000..8b8c395 --- /dev/null +++ b/.idea/gateway.iml @@ -0,0 +1,12 @@ + + + + + + + + + + \ No newline at end of file diff --git a/.idea/inspectionProfiles/profiles_settings.xml b/.idea/inspectionProfiles/profiles_settings.xml new file mode 100644 index 0000000..105ce2d --- /dev/null +++ b/.idea/inspectionProfiles/profiles_settings.xml @@ -0,0 +1,6 @@ + + + + \ No newline at end of file diff --git a/.idea/modules.xml b/.idea/modules.xml new file mode 100644 index 0000000..5e1fb45 --- /dev/null +++ b/.idea/modules.xml @@ -0,0 +1,8 @@ + + + + + + + + \ No newline at end of file diff --git a/.idea/vcs.xml b/.idea/vcs.xml new file mode 100644 index 0000000..35eb1dd --- /dev/null +++ b/.idea/vcs.xml @@ -0,0 +1,6 @@ + + + + + + \ No newline at end of file diff --git a/gateway/__pycache__/api_server.cpython-313.pyc b/gateway/__pycache__/api_server.cpython-313.pyc new file mode 100644 index 0000000..9f3655e Binary files /dev/null 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 new file mode 100644 index 0000000..a917bb8 Binary files /dev/null and b/gateway/__pycache__/cache_manager.cpython-313.pyc differ diff --git a/gateway/__pycache__/config_loader.cpython-313.pyc b/gateway/__pycache__/config_loader.cpython-313.pyc new file mode 100644 index 0000000..226af8b Binary files /dev/null and b/gateway/__pycache__/config_loader.cpython-313.pyc differ diff --git a/gateway/__pycache__/config_manager.cpython-313.pyc b/gateway/__pycache__/config_manager.cpython-313.pyc new file mode 100644 index 0000000..48957b7 Binary files /dev/null and b/gateway/__pycache__/config_manager.cpython-313.pyc differ diff --git a/gateway/__pycache__/config_validator.cpython-313.pyc b/gateway/__pycache__/config_validator.cpython-313.pyc new file mode 100644 index 0000000..b2204d0 Binary files /dev/null and b/gateway/__pycache__/config_validator.cpython-313.pyc differ diff --git a/gateway/__pycache__/plc_manager.cpython-313.pyc b/gateway/__pycache__/plc_manager.cpython-313.pyc new file mode 100644 index 0000000..e011a97 Binary files /dev/null and b/gateway/__pycache__/plc_manager.cpython-313.pyc differ diff --git a/gateway/__pycache__/snap7_client.cpython-313.pyc b/gateway/__pycache__/snap7_client.cpython-313.pyc new file mode 100644 index 0000000..bcf5711 Binary files /dev/null and b/gateway/__pycache__/snap7_client.cpython-313.pyc differ diff --git a/gateway/api_server.py b/gateway/api_server.py index a70941e..9cac2dc 100644 --- a/gateway/api_server.py +++ b/gateway/api_server.py @@ -6,13 +6,14 @@ 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"): """ 初始化API服务器 - + Args: cache_manager: 缓存管理器实例 config_path: 配置文件路径 @@ -25,10 +26,10 @@ class APIServer: self.username = "admin" self.password = "admin123" # 实际应用中应从安全存储获取 self.start_time = time.strftime("%Y-%m-%d %H:%M:%S") - + # 在初始化方法中调用 setup_routes self.setup_routes() - + def check_auth(self, username, password): """验证用户名和密码""" return username == self.username and password == self.password @@ -36,22 +37,24 @@ class APIServer: def authenticate(self): """发送401响应要求认证""" return Response( - "Unauthorized", - 401, + "Unauthorized", + 401, {"WWW-Authenticate": 'Basic realm="PLC Gateway Configuration"'} ) def requires_auth(self, f): """装饰器:需要认证的路由,保留函数元数据""" + @wraps(f) def decorated(*args, **kwargs): if not self.auth_enabled: return f(*args, **kwargs) - + auth = request.authorization if not auth or not self.check_auth(auth.username, auth.password): return self.authenticate() return f(*args, **kwargs) + return decorated def get_summary(self): @@ -62,11 +65,12 @@ class APIServer: for area_name, area in areas.items(): last_update = self.cache_manager.last_update[plc_name][area_name] plc_status = self.cache_manager.plc_connection_status.get(plc_name, "unknown") - + summary[plc_name][area_name] = { "status": area["status"], "plc_connection_status": plc_status, - "last_update": time.strftime("%Y-%m-%d %H:%M:%S", time.localtime(last_update)) if last_update > 0 else "Never", + "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"] } @@ -74,7 +78,7 @@ class APIServer: def setup_routes(self): """设置所有API路由""" - + # =========================== # 主页面 - 状态摘要 # =========================== @@ -108,7 +112,7 @@ class APIServer:

PLC Gateway Status

Gateway running since: {{ start_time }}

""" - + for plc_name, areas in summary.items(): plc_status = self.cache_manager.plc_connection_status.get(plc_name, "unknown") plc_class = "" @@ -118,7 +122,7 @@ class APIServer: plc_class = "plc-disconnected" else: plc_class = "plc-never-connected" - + html += f'

PLC: {plc_name} (Status: {plc_status})

' html += """ @@ -131,11 +135,11 @@ 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": @@ -146,7 +150,7 @@ class APIServer: status_text = "Disconnected" else: status_class = "status-disconnected" - + html += f""" @@ -157,69 +161,76 @@ class APIServer: """ - + html += "
Last Update
{area_name}{area['last_update']}
" - + # 添加API文档部分 html += """

API Endpoints

- +
Single Read: GET /api/read////
Example: /api/read/PLC1/DB100_Read/10/4
- +
Single Write: POST /api/write///
Body: Raw binary data
Example: POST /api/write/PLC1/DB100_Write/10 with 4 bytes of data
- +
- Single Read_Bool: GET /api/read_bool////
- Example: /api/read_bool/PLC1/DB100_Read/0/2 + Single Data Write: POST /api/write_data////
+ Body: {"value": value}
+ Example: POST /api/write_data/PLC1/DB100_Write/10/int with {"value": 12345}
- +
- Single Write_Bool: POST /api/write_bool///
- Body: Raw binary data
- Example: POST /api/write_bool/PLC1/DB100_Write/0 -
- + Array Write: POST /api/write_array////
+ Body: {"values": [values]}
+ Example: POST /api/write_array/PLC1/DB100_Write/0/int with {"values": [1,2,3,4,5]} +
+ +
+ Bit Write: POST /api/write_bit////
+ Body: 0 or 1
+ Example: POST /api/write_bit/PLC1/DB100_Write/10/3 with 1 +
+
Batch Read: POST /api/batch_read
Body: JSON array of read requests
Example: [{"plc_name":"PLC1", "area_name":"DB100_Read", "offset":0, "length":4}]
- +
Batch Write: POST /api/batch_write
Body: JSON array of write requests
Example: [{"plc_name":"PLC1", "area_name":"DB100_Write", "offset":0, "data":[1,2,3,4]}]
- +
Configuration: GET/POST /api/config
Manage gateway configuration
- + - + - + """ - + html += """ @@ -236,15 +247,17 @@ class APIServer: for plc_name in self.cache_manager.plc_connection_status: plc_statuses[plc_name] = { "status": self.cache_manager.plc_connection_status[plc_name], - "last_connected": time.strftime("%Y-%m-%d %H:%M:%S", time.localtime(self.cache_manager.plc_last_connected[plc_name])) - if self.cache_manager.plc_last_connected[plc_name] > 0 else "Never" + "last_connected": time.strftime("%Y-%m-%d %H:%M:%S", + time.localtime(self.cache_manager.plc_last_connected[plc_name])) + if self.cache_manager.plc_last_connected[plc_name] > 0 else "Never" } - + return jsonify({ "status": "running", "start_time": self.start_time, "plc_count": len(self.config_manager.get_config().get("plcs", [])), - "cache_size": sum(len(area["data"]) for plc in self.cache_manager.cache.values() for area in plc.values()), + "cache_size": sum( + len(area["data"]) for plc in self.cache_manager.cache.values() for area in plc.values()), "plc_statuses": plc_statuses }) @@ -257,7 +270,7 @@ class APIServer: """配置编辑页面""" config = self.config_manager.get_config() config_json = json.dumps(config, indent=2) - + html = """ @@ -322,14 +335,14 @@ class APIServer:

PLC Gateway Configuration

- + - +

Edit the configuration JSON below. Be careful with the syntax.

- +
@@ -338,9 +351,9 @@ class APIServer:
- +
- +

Configuration Guide

PLC Configuration:

@@ -350,7 +363,7 @@ class APIServer:
  • rack: Rack number (usually 0)
  • slot: Slot number (usually 1 for S7-1200)
  • - +

    Data Area Configuration:

    - +

    Example:

    {
       "plcs": [
    @@ -387,7 +400,7 @@ class APIServer:
     }
    - +