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_name |
+ PLC名称(如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_name |
+ PLC名称(如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