diff --git a/config/config.json b/config/config.json index fb847c4..4a90a1c 100644 --- a/config/config.json +++ b/config/config.json @@ -2,7 +2,7 @@ "plcs": [ { "name": "PLC1", - "ip": "192.168.0.100", + "ip": "192.168.0.1", "rack": 0, "slot": 1, "areas": [ @@ -11,7 +11,7 @@ "type": "read", "db_number": 100, "offset": 0, - "size": 4000, + "size": 5000, "structure": [ { "name": "temperature", @@ -35,7 +35,7 @@ "name": "DB100_Write", "type": "write", "db_number": 100, - "offset": 4000, + "offset": 0, "size": 5000 }, { @@ -43,22 +43,7 @@ "type": "read_write", "db_number": 202, "offset": 0, - "size": 2000 - } - ] - }, - { - "name": "PLC2", - "ip": "192.168.0.101", - "rack": 0, - "slot": 1, - "areas": [ - { - "name": "DB100_Read", - "type": "read", - "db_number": 100, - "offset": 0, - "size": 4000 + "size": 816 } ] } diff --git a/config/config.json.bak b/config/config.json.bak index fb847c4..6fd526b 100644 --- a/config/config.json.bak +++ b/config/config.json.bak @@ -2,7 +2,7 @@ "plcs": [ { "name": "PLC1", - "ip": "192.168.0.100", + "ip": "192.168.0.1", "rack": 0, "slot": 1, "areas": [ @@ -11,7 +11,7 @@ "type": "read", "db_number": 100, "offset": 0, - "size": 4000, + "size": 5000, "structure": [ { "name": "temperature", @@ -43,22 +43,7 @@ "type": "read_write", "db_number": 202, "offset": 0, - "size": 2000 - } - ] - }, - { - "name": "PLC2", - "ip": "192.168.0.101", - "rack": 0, - "slot": 1, - "areas": [ - { - "name": "DB100_Read", - "type": "read", - "db_number": 100, - "offset": 0, - "size": 4000 + "size": 816 } ] } diff --git a/gateway/__pycache__/api_server.cpython-313.pyc b/gateway/__pycache__/api_server.cpython-313.pyc deleted file mode 100644 index 260078d..0000000 Binary files a/gateway/__pycache__/api_server.cpython-313.pyc and /dev/null differ diff --git a/gateway/__pycache__/cache_manager.cpython-313.pyc b/gateway/__pycache__/cache_manager.cpython-313.pyc deleted file mode 100644 index 0539d0b..0000000 Binary files a/gateway/__pycache__/cache_manager.cpython-313.pyc and /dev/null differ diff --git a/gateway/__pycache__/config_loader.cpython-313.pyc b/gateway/__pycache__/config_loader.cpython-313.pyc deleted file mode 100644 index ce1d239..0000000 Binary files a/gateway/__pycache__/config_loader.cpython-313.pyc and /dev/null differ diff --git a/gateway/__pycache__/config_manager.cpython-313.pyc b/gateway/__pycache__/config_manager.cpython-313.pyc deleted file mode 100644 index 3ea7506..0000000 Binary files a/gateway/__pycache__/config_manager.cpython-313.pyc and /dev/null differ diff --git a/gateway/__pycache__/config_validator.cpython-313.pyc b/gateway/__pycache__/config_validator.cpython-313.pyc deleted file mode 100644 index ad4da92..0000000 Binary files a/gateway/__pycache__/config_validator.cpython-313.pyc and /dev/null differ diff --git a/gateway/__pycache__/plc_manager.cpython-313.pyc b/gateway/__pycache__/plc_manager.cpython-313.pyc deleted file mode 100644 index 6d004f5..0000000 Binary files a/gateway/__pycache__/plc_manager.cpython-313.pyc and /dev/null differ diff --git a/gateway/__pycache__/snap7_client.cpython-313.pyc b/gateway/__pycache__/snap7_client.cpython-313.pyc deleted file mode 100644 index fb99def..0000000 Binary files a/gateway/__pycache__/snap7_client.cpython-313.pyc and /dev/null differ diff --git a/gateway/api_server.py b/gateway/api_server.py index cf1d149..a70941e 100644 --- a/gateway/api_server.py +++ b/gateway/api_server.py @@ -176,6 +176,17 @@ class APIServer: 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 Write_Bool: POST /api/write_bool///
+ Body: Raw binary data
+ Example: POST /api/write_bool/PLC1/DB100_Write/0 +
+
Batch Read: POST /api/batch_read
Body: JSON array of read requests
@@ -764,6 +775,100 @@ class APIServer: }
+ +
+

Single Read Bool

+
+ GET + /api/read_bool//// +
+

从指定区域读取数据。

+ +

路径参数

+ + + + + + + + + + + + + + + + + + + + + +
参数描述
plc_namePLC名称(如PLC1)
area_name区域名称(如DB100_Read)
offset起始偏移量(字节)
length读取长度(字节)
+ +

响应示例

+
+ { + "status": "success", + "plc_name": "PLC1", + "area_name": "DB100_Read", + "offset": 0, + "length": 2, + "data": [0:False, 1:False], + "plc_connection_status": "connected", + "last_update": 1698754321.456, + "last_update_formatted": "2023-10-30 14:12:01" + } +
+
+ +
+

Single Write Bool

+
+ POST + /api/write_bool/// +
+

向指定区域写入数据。

+ +

路径参数

+ + + + + + + + + + + + + + + + + +
参数描述
plc_namePLC名称(如PLC1)
area_name区域名称(如DB100_Write)
offset起始偏移量(字节)
+ +

请求体

+

{0:True}

+ +

响应示例

+
+ { + "status": "success", + "plc_name": "PLC1", + "area_name": "DB100_Write", + "offset": 0, + "length": 1, + "plc_connection_status": "connected", + "last_update": 1698754350.789, + "last_update_formatted": "2023-10-30 14:12:30" + } +
+

Batch Read

@@ -1137,6 +1242,33 @@ class APIServer: "last_update_formatted": time.strftime("%Y-%m-%d %H:%M:%S", time.localtime(update_time)) }) + # 单个读取BOOL类型接口 + @self.app.route("/api/read_bool////", methods=["GET"], endpoint="single_read_bool") + def single_read_bool(plc_name, area_name, offset, length): + """从指定区域读取数据""" + data, error, plc_status, update_time = self.cache_manager.read_area_bool(plc_name, area_name, offset, length) + if error: + return jsonify({ + "status": "error", + "plc_name": plc_name, + "area_name": area_name, + "message": error, + "plc_connection_status": plc_status, + "last_update": update_time, + "last_update_formatted": time.strftime("%Y-%m-%d %H:%M:%S", time.localtime(update_time)) if update_time > 0 else "Never" + }), 400 + return jsonify({ + "status": "success", + "plc_name": plc_name, + "area_name": area_name, + "offset": offset, + "length": length, + "data": [data], # list(data) + "plc_connection_status": plc_status, + "last_update": update_time, + "last_update_formatted": time.strftime("%Y-%m-%d %H:%M:%S", time.localtime(update_time)) + }) + # 单个写入接口 @self.app.route("/api/write///", methods=["POST"], endpoint="single_write") def single_write(plc_name, area_name, offset): @@ -1174,6 +1306,44 @@ class APIServer: "last_update_formatted": time.strftime("%Y-%m-%d %H:%M:%S", time.localtime(update_time)) }) + # 单个写入BOOL类型接口 + @self.app.route("/api/write_bool///", methods=["POST"], endpoint="single_write_bool") + def single_write_bool(plc_name, area_name, offset): + """向指定区域写入数据""" + data = request.data + if not data: + # 如果没有提供数据,返回错误 + return jsonify({ + "status": "error", + "message": "No data provided", + "plc_connection_status": self.cache_manager.plc_connection_status.get(plc_name, "unknown"), + "last_update": 0, + "last_update_formatted": "N/A" + }), 400 + + success, error, plc_status, update_time = self.cache_manager.write_area_bool(plc_name, area_name, offset, data) + if error: + return jsonify({ + "status": "error", + "plc_name": plc_name, + "area_name": area_name, + "message": error, + "plc_connection_status": plc_status, + "last_update": update_time, + "last_update_formatted": time.strftime("%Y-%m-%d %H:%M:%S", + time.localtime(update_time)) if update_time > 0 else "Never" + }), 400 + return jsonify({ + "status": "success", + "plc_name": plc_name, + "area_name": area_name, + "offset": offset, + "length": 1, + "plc_connection_status": plc_status, + "last_update": update_time, + "last_update_formatted": time.strftime("%Y-%m-%d %H:%M:%S", time.localtime(update_time)) + }) + # 批量读取接口 @self.app.route("/api/batch_read", methods=["POST"], endpoint="batch_read") def batch_read(): @@ -1188,8 +1358,8 @@ class APIServer: "last_update": 0, "last_update_formatted": "N/A" }), 400 - - requests = request.json + + requests = request.get_json() if not isinstance(requests, list): return jsonify({ "status": "error", @@ -1198,11 +1368,11 @@ class APIServer: "last_update": 0, "last_update_formatted": "N/A" }), 400 - # 添加详细日志 self.logger.info(f"Received batch read request: {json.dumps(requests)}") - + results = self.cache_manager.batch_read(requests) + return jsonify(results) except Exception as e: self.logger.error(f"Batch read error: {str(e)}", exc_info=True) @@ -1252,6 +1422,43 @@ class APIServer: "last_update_formatted": "N/A" }), 500 + @self.app.route("/api/batch_write_bool", methods=["POST"], endpoint="batch_write_bool") + def batch_write_bool(): + """批量写入多个区域的数据""" + try: + if not request.is_json: + return jsonify({ + "status": "error", + "message": "Request must be JSON (Content-Type: application/json)", + "plc_connection_status": "unknown", + "last_update": 0, + "last_update_formatted": "N/A" + }), 400 + + requests = request.json + if not isinstance(requests, list): + return jsonify({ + "status": "error", + "message": "Request must be a JSON array", + "plc_connection_status": "unknown", + "last_update": 0, + "last_update_formatted": "N/A" + }), 400 + + self.logger.info(f"Received batch write request: {json.dumps(requests)}") + + results = self.cache_manager.batch_write_bool(requests) + return jsonify(results) + except Exception as e: + self.logger.error(f"Batch write error: {str(e)}", exc_info=True) + return jsonify({ + "status": "error", + "message": f"Internal server error: {str(e)}", + "plc_connection_status": "unknown", + "last_update": 0, + "last_update_formatted": "N/A" + }), 500 + # 区域状态检查 @self.app.route("/api/status//", methods=["GET"], endpoint="area_status") def area_status(plc_name, area_name): diff --git a/gateway/cache_manager.py b/gateway/cache_manager.py index 2ff30c6..48adf16 100644 --- a/gateway/cache_manager.py +++ b/gateway/cache_manager.py @@ -1,6 +1,7 @@ import threading import time import logging +from snap7.util import * class CacheManager: """PLC数据缓存管理器""" @@ -197,7 +198,7 @@ class CacheManager: area = self.cache.get(plc_name, {}).get(area_name) if not area: return None, "Area not found", "unknown", 0 - + if offset + length > area["size"]: return None, "Offset out of bounds", "unknown", 0 @@ -227,6 +228,45 @@ class CacheManager: area["status"] = plc_status self.logger.error(f"Read failed for {plc_name}/{area_name}: {e}") return None, f"Read failed: {str(e)}", plc_status, 0 + + def read_area_bool(self, plc_name, area_name, offset, length): + """单个区域读取""" + with self.lock: + area = self.cache.get(plc_name, {}).get(area_name) + if not area: + return None, "Area not found", "unknown", 0 + + if offset + length > area["size"]: + return None, "Offset out of bounds", "unknown", 0 + + client = self.plc_manager.get_plc(plc_name) + plc_status = self.plc_connection_status.get(plc_name, "unknown") + + # 如果PLC未连接,直接返回错误 + if plc_status != "connected": + return None, f"PLC not connected (status: {plc_status})", plc_status, 0 + + try: + data = client.read_db_bool(area["db_number"], area["offset"] + offset, length) + # 验证数据有效性 + if all(isinstance(val, bool) for val in data.values()): + # 按字典键顺序更新多个值 + for i, val in data.items(): + area["data"][offset + i] = val # 确保offset+i不越界 + + # area["data"][offset] = data.values + update_time = time.time() + self.last_update[plc_name][area_name] = update_time + area["status"] = "connected" + + return data, None, plc_status, update_time + else: + area["status"] = plc_status + return None, "Invalid data returned", plc_status, 0 + except Exception as e: + area["status"] = plc_status + self.logger.error(f"Read failed for {plc_name}/{area_name}: {e}") + return None, f"Read failed: {str(e)}", plc_status, 0 def write_area(self, plc_name, area_name, offset, data): """单个区域写入""" @@ -251,6 +291,7 @@ class CacheManager: return False, f"PLC not connected (status: {plc_status})", plc_status, 0 try: + print(data) success = client.write_db(area["db_number"], area["offset"] + offset, data) if success: # 更新缓存中的这部分数据 @@ -268,125 +309,315 @@ class CacheManager: area["status"] = plc_status self.logger.error(f"Write failed for {plc_name}/{area_name}: {e}") return False, f"Write failed: {str(e)}", plc_status, 0 + + def batch_write_area(self, plc_name, area_name, offset, data): + """单个区域写入""" + with self.lock: + area = self.cache.get(plc_name, {}).get(area_name) + if not area: + return False, "Area not found", "unknown", 0 + + if area["type"] not in ["write", "read_write"]: + plc_status = self.plc_connection_status.get(plc_name, "unknown") + return False, "Area is read-only", plc_status, 0 + + if offset + len(data) > area["size"]: + plc_status = self.plc_connection_status.get(plc_name, "unknown") + return False, "Offset out of bounds", plc_status, 0 + + client = self.plc_manager.get_plc(plc_name) + plc_status = self.plc_connection_status.get(plc_name, "unknown") + + # 如果PLC未连接,直接返回错误 + if plc_status != "connected": + return False, f"PLC not connected (status: {plc_status})", plc_status, 0 + + try: + print("data:", data) + for i, byte in enumerate(data): + print("i,byte:", i, byte) + byte_data = bytes([byte]) + current_offset = offset + (i * 2) + byte_value = byte_data[0] + + value = bytearray(2) + if isinstance(byte_value, int): + set_int(value, 0, byte_value) + data = value + + print(area["db_number"], current_offset, data) + success = client.batch_write_db(area["db_number"], current_offset, data) + if success: + # 更新缓存中的这部分数据 + for j in range(len(data)): + area["data"][offset + j] = data[j] + update_time = time.time() + self.last_update[plc_name][area_name] = update_time + area["status"] = "connected (last write)" + else: + area["status"] = plc_status + return False, "Write failed", plc_status, 0 + return True, None, plc_status, update_time + except Exception as e: + area["status"] = plc_status + self.logger.error(f"Write failed for {plc_name}/{area_name}: {e}") + return False, f"Write failed: {str(e)}", plc_status, 0 + + def batch_write_bool_area(self, plc_name, area_name, offset, data): + """单个区域写入""" + with self.lock: + area = self.cache.get(plc_name, {}).get(area_name) + if not area: + return False, "Area not found", "unknown", 0 + + if area["type"] not in ["write", "read_write"]: + plc_status = self.plc_connection_status.get(plc_name, "unknown") + return False, "Area is read-only", plc_status, 0 + + if offset + len(data) > area["size"]: + plc_status = self.plc_connection_status.get(plc_name, "unknown") + return False, "Offset out of bounds", plc_status, 0 + + client = self.plc_manager.get_plc(plc_name) + plc_status = self.plc_connection_status.get(plc_name, "unknown") + + # 如果PLC未连接,直接返回错误 + if plc_status != "connected": + return False, f"PLC not connected (status: {plc_status})", plc_status, 0 + + try: + value = bytearray(offset + 1) + for bit, bit_value in enumerate(data): + print("i,byte:", bit, bit_value) + set_bool(value, offset, bit, bit_value) + data = value + + print(area["db_number"], offset, data) + success = client.batch_write_db_bool(area["db_number"], offset, data) + if success: + # 更新缓存中的这部分数据 + for j in range(len(data)): + area["data"][offset + j] = data[j] + update_time = time.time() + self.last_update[plc_name][area_name] = update_time + area["status"] = "connected (last write)" + else: + area["status"] = plc_status + return False, "Write failed", plc_status, 0 + return True, None, plc_status, update_time + except Exception as e: + area["status"] = plc_status + self.logger.error(f"Write failed for {plc_name}/{area_name}: {e}") + return False, f"Write failed: {str(e)}", plc_status, 0 + + def write_area_bool(self, plc_name, area_name, offset, data): + """单个区域写入""" + with self.lock: + area = self.cache.get(plc_name, {}).get(area_name) + if not area: + return False, "Area not found", "unknown", 0 + + if area["type"] not in ["write", "read_write"]: + plc_status = self.plc_connection_status.get(plc_name, "unknown") + return False, "Area is read-only", plc_status, 0 + + if offset + len(data) > area["size"]: + plc_status = self.plc_connection_status.get(plc_name, "unknown") + return False, "Offset out of bounds", plc_status, 0 + + client = self.plc_manager.get_plc(plc_name) + plc_status = self.plc_connection_status.get(plc_name, "unknown") + + # 如果PLC未连接,直接返回错误 + if plc_status != "connected": + return False, f"PLC not connected (status: {plc_status})", plc_status, 0 + + try: + success = client.write_db_bool(area["db_number"], area["offset"] + offset, data) + if success: + # 更新缓存中的这部分数据 + for i in range(len(data)): + area["data"][offset + i] = data[i] + update_time = time.time() + self.last_update[plc_name][area_name] = update_time + area["status"] = "connected (last write)" + + return True, None, plc_status, update_time + else: + area["status"] = plc_status + return False, "Write failed", plc_status, 0 + except Exception as e: + area["status"] = plc_status + self.logger.error(f"Write failed for {plc_name}/{area_name}: {e}") + return False, f"Write failed: {str(e)}", plc_status, 0 def batch_read(self, requests): """批量读取""" results = [] - with self.lock: - for req in requests: - plc_name = req["plc_name"] - area_name = req["area_name"] - offset = req.get("offset", 0) - length = req.get("length", None) - - # 获取PLC连接状态 - plc_status = self.plc_connection_status.get(plc_name, "unknown") - - # 如果PLC未连接,直接返回错误 - if plc_status != "connected": - results.append({ - "plc_name": plc_name, - "area_name": area_name, - "status": "error", - "plc_connection_status": plc_status, - "last_update": 0, - "last_update_formatted": "N/A", - "message": f"PLC not connected (status: {plc_status})" - }) - continue - - area = self.cache.get(plc_name, {}).get(area_name) - if not area: - results.append({ - "plc_name": plc_name, - "area_name": area_name, - "status": "error", - "plc_connection_status": plc_status, - "last_update": 0, - "last_update_formatted": "N/A", - "message": "Area not found" - }) - continue - - # 如果未指定length,读取整个区域 - if length is None: - length = area["size"] - offset - - data, error, _, update_time = self.read_area(plc_name, area_name, offset, length) - if error: - results.append({ - "plc_name": plc_name, - "area_name": area_name, - "status": "error", - "plc_connection_status": plc_status, - "last_update": update_time, - "last_update_formatted": time.strftime("%Y-%m-%d %H:%M:%S", time.localtime(update_time)) if update_time > 0 else "Never", - "message": error - }) - else: - results.append({ - "plc_name": plc_name, - "area_name": area_name, - "status": "success", - "plc_connection_status": plc_status, - "last_update": update_time, - "last_update_formatted": time.strftime("%Y-%m-%d %H:%M:%S", time.localtime(update_time)), - "offset": offset, - "length": length, - "data": list(data) - }) + + for req in requests: + plc_name = req["plc_name"] + area_name = req["area_name"] + offset = req.get("offset", 0) + length = req.get("length", None) + + # 获取PLC连接状态 + plc_status = self.plc_connection_status.get(plc_name, "unknown") + + # 如果PLC未连接,直接返回错误 + if plc_status != "connected": + results.append({ + "plc_name": plc_name, + "area_name": area_name, + "status": "error", + "plc_connection_status": plc_status, + "last_update": 0, + "last_update_formatted": "N/A", + "message": f"PLC not connected (status: {plc_status})" + }) + continue + + area = self.cache.get(plc_name, {}).get(area_name) + if not area: + results.append({ + "plc_name": plc_name, + "area_name": area_name, + "status": "error", + "plc_connection_status": plc_status, + "last_update": 0, + "last_update_formatted": "N/A", + "message": "Area not found" + }) + continue + + # 如果未指定length,读取整个区域 + if length is None: + length = area["size"] - offset + + data, error, _, update_time = self.read_area(plc_name, area_name, offset, length) + if error: + results.append({ + "plc_name": plc_name, + "area_name": area_name, + "status": "error", + "plc_connection_status": plc_status, + "last_update": update_time, + "last_update_formatted": time.strftime("%Y-%m-%d %H:%M:%S", time.localtime(update_time)) if update_time > 0 else "Never", + "message": error + }) + else: + results.append({ + "plc_name": plc_name, + "area_name": area_name, + "status": "success", + "plc_connection_status": plc_status, + "last_update": update_time, + "last_update_formatted": time.strftime("%Y-%m-%d %H:%M:%S", time.localtime(update_time)), + "offset": offset, + "length": length, + "data": list(data) + }) return results def batch_write(self, requests): """批量写入""" results = [] - with self.lock: - for req in requests: - plc_name = req["plc_name"] - area_name = req["area_name"] - offset = req["offset"] - data = bytes(req["data"]) - - # 获取PLC连接状态 - plc_status = self.plc_connection_status.get(plc_name, "unknown") - - # 如果PLC未连接,直接返回错误 - if plc_status != "connected": - results.append({ - "plc_name": plc_name, - "area_name": area_name, - "status": "error", - "plc_connection_status": plc_status, - "last_update": 0, - "last_update_formatted": "N/A", - "message": f"PLC not connected (status: {plc_status})", - "offset": offset - }) - continue - - success, error, _, update_time = self.write_area(plc_name, area_name, offset, data) - if error: - results.append({ - "plc_name": plc_name, - "area_name": area_name, - "status": "error", - "plc_connection_status": plc_status, - "last_update": update_time, - "last_update_formatted": time.strftime("%Y-%m-%d %H:%M:%S", time.localtime(update_time)) if update_time > 0 else "Never", - "message": error, - "offset": offset - }) - else: - results.append({ - "plc_name": plc_name, - "area_name": area_name, - "status": "success", - "plc_connection_status": plc_status, - "last_update": update_time, - "last_update_formatted": time.strftime("%Y-%m-%d %H:%M:%S", time.localtime(update_time)), - "offset": offset, - "length": len(data) - }) + for req in requests: + plc_name = req["plc_name"] + area_name = req["area_name"] + offset = req["offset"] + data = bytes(req["data"]) + + # 获取PLC连接状态 + plc_status = self.plc_connection_status.get(plc_name, "unknown") + + # 如果PLC未连接,直接返回错误 + if plc_status != "connected": + results.append({ + "plc_name": plc_name, + "area_name": area_name, + "status": "error", + "plc_connection_status": plc_status, + "last_update": 0, + "last_update_formatted": "N/A", + "message": f"PLC not connected (status: {plc_status})", + "offset": offset + }) + continue + success, error, _, update_time = self.batch_write_area(plc_name, area_name, offset, data) + if error: + results.append({ + "plc_name": plc_name, + "area_name": area_name, + "status": "error", + "plc_connection_status": plc_status, + "last_update": update_time, + "last_update_formatted": time.strftime("%Y-%m-%d %H:%M:%S", time.localtime(update_time)) if update_time > 0 else "Never", + "message": error, + "offset": offset + }) + else: + results.append({ + "plc_name": plc_name, + "area_name": area_name, + "status": "success", + "plc_connection_status": plc_status, + "last_update": update_time, + "last_update_formatted": time.strftime("%Y-%m-%d %H:%M:%S", time.localtime(update_time)), + "offset": offset, + "length": len(data) + }) + return results + + def batch_write_bool(self, requests): + """批量写入""" + results = [] + for req in requests: + plc_name = req["plc_name"] + area_name = req["area_name"] + offset = req["offset"] + data = bytes(req["data"]) + + # 获取PLC连接状态 + plc_status = self.plc_connection_status.get(plc_name, "unknown") + + # 如果PLC未连接,直接返回错误 + if plc_status != "connected": + results.append({ + "plc_name": plc_name, + "area_name": area_name, + "status": "error", + "plc_connection_status": plc_status, + "last_update": 0, + "last_update_formatted": "N/A", + "message": f"PLC not connected (status: {plc_status})", + "offset": offset + }) + continue + + success, error, _, update_time = self.batch_write_bool_area(plc_name, area_name, offset, data) + if error: + results.append({ + "plc_name": plc_name, + "area_name": area_name, + "status": "error", + "plc_connection_status": plc_status, + "last_update": update_time, + "last_update_formatted": time.strftime("%Y-%m-%d %H:%M:%S", time.localtime(update_time)) if update_time > 0 else "Never", + "message": error, + "offset": offset + }) + else: + results.append({ + "plc_name": plc_name, + "area_name": area_name, + "status": "success", + "plc_connection_status": plc_status, + "last_update": update_time, + "last_update_formatted": time.strftime("%Y-%m-%d %H:%M:%S", time.localtime(update_time)), + "offset": offset, + "length": len(data) + }) return results def get_parsed_data(self, plc_name, area_name): diff --git a/gateway/snap7_client.py b/gateway/snap7_client.py index 2c7a7fd..464f50f 100644 --- a/gateway/snap7_client.py +++ b/gateway/snap7_client.py @@ -2,6 +2,8 @@ import snap7 import logging from threading import Lock import time +from snap7.util import * +import ast class Snap7Client: """Snap7客户端,处理与PLC的通信""" @@ -34,8 +36,8 @@ class Snap7Client: try: # 尝试读取PLC的运行状态 cpu_state = self.client.get_cpu_state() - return cpu_state in [snap7.snap7types.cpu_statuses['S7CpuStatusRun'], - snap7.snap7types.cpu_statuses['S7CpuStatusStop']] + print("当前 CPU 状态:", cpu_state) + return cpu_state in ['S7CpuStatusRun', 'S7CpuStatusStop'] except: return False @@ -51,6 +53,7 @@ class Snap7Client: self.last_connect_attempt = current_time try: self.client.connect(self.ip, self.rack, self.slot) + # 验证连接是否真正有效 if self.client.get_connected() and self.is_valid_connection(): self.connected = True @@ -88,7 +91,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: @@ -101,27 +104,157 @@ class Snap7Client: self.connected = False return None + def read_db_bool(self, db_number, offset, bit_length): + """ + 从 DB 块中读取一个字节,并提取其中的多个 BOOL 位 + + Args: + db_number (int): DB块编号 + offset (int): 要读取的字节偏移地址 + bit_length: 第几位,如1,表示第1位 + + Returns: + result:返回位值 + """ + if not self.connected and not self.connect(): + self.logger.warning(f"Read failed: not connected to {self.ip}") + return None # 返回None而不是零填充数据 + + try: + with self.lock: + data = self.client.db_read(db_number, offset, 1) + result = {} + + for bit in range(bit_length): + result[bit] = bool(data[0] & (1 << bit)) + + if result is None or len(result) != bit_length: + self.connected = False + self.logger.error(f"Read DB{db_number} returned invalid data size (expected {bit_length}, got {len(result) if data else 0})") + return None + return result + except Exception as e: + self.logger.error(f"Read DB{db_number} error: {e}") + self.connected = False + return None + def write_db(self, db_number, offset, data): """ 向DB块写入数据 - + Args: db_number: DB编号 offset: 起始偏移量 - 要写入的数据 - + data: 要写入的数据 + Returns: bool: 是否写入成功 """ + values = int(data) + print("values:", values) + value = bytearray(0) + if isinstance(values, int): + set_int(value, offset, values) + data = value + print(data) + if not self.connected and not self.connect(): self.logger.warning(f"Write failed: not connected to {self.ip}") return False - + 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 + except Exception as e: + self.logger.error(f"Write DB{db_number} error: {e}") + self.connected = False + return False + + def batch_write_db(self, db_number, offset, data): + """ + 向DB块写入数据 + + Args: + db_number: DB编号 + offset: 起始偏移量 + data: 要写入的数据 + + Returns: + bool: 是否写入成功 + """ + if not self.connected and not self.connect(): + self.logger.warning(f"Write failed: not connected to {self.ip}") + return False + + 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 + except Exception as e: + self.logger.error(f"Write DB{db_number} error: {e}") + self.connected = False + return False + + def write_db_bool(self, db_number, offset, data): + """ + 向DB块写入数据 + + Args: + db_number: DB编号 + offset: 起始偏移量 + data: 要写入的bool类型数据 + + Returns: + bool: 是否写入成功 + """ + data = data.decode('utf-8') + # 将字符串安全转换为字典 + data_dict = ast.literal_eval(data) # 输出: {0: True} + # value = bytearray(1) + value = bytearray(offset+1) + for bit, val in data_dict.items(): + set_bool(value, offset, bit, val) + data = value + if not self.connected and not self.connect(): + self.logger.warning(f"Write failed: not connected to {self.ip}") + return False + + try: + with self.lock: + print(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}") + return True + except Exception as e: + self.logger.error(f"Write DB{db_number} error: {e}") + self.connected = False + return False + + def batch_write_db_bool(self, db_number, offset, data): + """ + 向DB块写入数据 + + Args: + db_number: DB编号 + offset: 起始偏移量 + data: 要写入的bool类型数据 + + Returns: + bool: 是否写入成功 + """ + if not self.connected and not self.connect(): + self.logger.warning(f"Write failed: not connected to {self.ip}") + return False + + try: + with self.lock: + print(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}") + return True except Exception as e: self.logger.error(f"Write DB{db_number} error: {e}") self.connected = False