Merge branch 'main' of http://121.37.111.42:3000/zhengsl/emcp into main
This commit is contained in:
commit
af977c85d4
@ -1,3 +1,6 @@
|
||||
from datetime import datetime
|
||||
from typing import Optional
|
||||
|
||||
from fastapi import APIRouter, Query
|
||||
|
||||
from app.core.response import success_response
|
||||
@ -8,6 +11,20 @@ router = APIRouter(prefix="/alarm", tags=["alarm"])
|
||||
|
||||
|
||||
@router.get("/list")
|
||||
def list_alarms(page: int = Query(default=1, ge=1), size: int = Query(default=20, ge=1, le=100)) -> dict:
|
||||
data = platform_service.list_alarms(page=page, size=size)
|
||||
def list_alarms(
|
||||
page: int = Query(default=1, ge=1),
|
||||
size: int = Query(default=20, ge=1, le=100),
|
||||
no: Optional[str] = Query(default=None),
|
||||
type: Optional[str] = Query(default=None),
|
||||
start_time: Optional[datetime] = Query(default=None),
|
||||
end_time: Optional[datetime] = Query(default=None),
|
||||
) -> dict:
|
||||
data = platform_service.list_alarms(
|
||||
page=page,
|
||||
size=size,
|
||||
no=no,
|
||||
alarm_type=type,
|
||||
start_time=start_time,
|
||||
end_time=end_time,
|
||||
)
|
||||
return success_response(data, msg="获取报警历史成功")
|
||||
|
||||
@ -11,26 +11,51 @@ from app.services.platform_service import platform_service
|
||||
router = APIRouter(prefix="/config", tags=["config"])
|
||||
|
||||
|
||||
@router.get("/device", dependencies=[Depends(verify_api_token)])
|
||||
def get_device_config() -> Dict[str, Any]:
|
||||
return success_response(platform_service.get_device_config(), msg="获取设备配置成功")
|
||||
|
||||
|
||||
@router.post("/device", dependencies=[Depends(verify_api_token)])
|
||||
def save_device_config(payload: DeviceConfigIn) -> Dict[str, Any]:
|
||||
return success_response(platform_service.save_device_config(payload))
|
||||
|
||||
|
||||
@router.get("/channel", dependencies=[Depends(verify_api_token)])
|
||||
def get_channel_config() -> Dict[str, Any]:
|
||||
return success_response(platform_service.get_channel_config(), msg="获取通道配置成功")
|
||||
|
||||
|
||||
@router.post("/channel", dependencies=[Depends(verify_api_token)])
|
||||
def save_channel_config(payload: ChannelConfigIn) -> Dict[str, Any]:
|
||||
return success_response(platform_service.save_channel_config(payload))
|
||||
|
||||
|
||||
@router.get("/line_alarm_setting", dependencies=[Depends(verify_api_token)])
|
||||
def get_line_alarm_setting() -> Dict[str, Any]:
|
||||
return success_response(platform_service.get_line_alarm_setting(), msg="获取线路报警设置成功")
|
||||
|
||||
|
||||
@router.post("/line_alarm_setting", dependencies=[Depends(verify_api_token)])
|
||||
def save_line_alarm_setting(payload: LineAlarmSettingIn) -> Dict[str, Any]:
|
||||
return success_response(platform_service.save_line_alarm_setting(payload))
|
||||
|
||||
|
||||
@router.get("/ai_alarm_setting", dependencies=[Depends(verify_api_token)])
|
||||
def get_ai_alarm_setting() -> Dict[str, Any]:
|
||||
return success_response(platform_service.get_ai_alarm_setting(), msg="获取AI报警设置成功")
|
||||
|
||||
|
||||
@router.post("/ai_alarm_setting", dependencies=[Depends(verify_api_token)])
|
||||
def save_ai_alarm_setting(payload: List[AiAlarmSettingIn]) -> Dict[str, Any]:
|
||||
return success_response(platform_service.save_ai_alarm_setting(payload))
|
||||
|
||||
|
||||
@router.get("/system", dependencies=[Depends(verify_api_token)])
|
||||
def get_system_config() -> Dict[str, Any]:
|
||||
return success_response(platform_service.get_system_config(), msg="获取系统设置成功")
|
||||
|
||||
|
||||
@router.post("/system", dependencies=[Depends(verify_api_token)])
|
||||
def save_system_config(payload: SystemConfigIn) -> Dict[str, Any]:
|
||||
return success_response(platform_service.save_system_config(payload))
|
||||
|
||||
@ -1,6 +1,7 @@
|
||||
from __future__ import annotations
|
||||
|
||||
from typing import Any, Dict, List
|
||||
from datetime import datetime
|
||||
from typing import Any, Dict, List, Optional
|
||||
|
||||
from app.db.sqlite import get_connection
|
||||
from app.schemas.platform import AlarmEvent
|
||||
@ -26,16 +27,45 @@ class AlarmRepository:
|
||||
connection.commit()
|
||||
return int(cursor.lastrowid)
|
||||
|
||||
def list_alarms(self, page: int = 1, size: int = 20) -> List[Dict[str, Any]]:
|
||||
def list_alarms(
|
||||
self,
|
||||
page: int = 1,
|
||||
size: int = 20,
|
||||
no: str = "",
|
||||
alarm_type: str = "",
|
||||
start_time: Optional[datetime] = None,
|
||||
end_time: Optional[datetime] = None,
|
||||
) -> List[Dict[str, Any]]:
|
||||
offset = (page - 1) * size
|
||||
conditions = []
|
||||
params: List[Any] = []
|
||||
|
||||
if no:
|
||||
conditions.append("no = ?")
|
||||
params.append(no)
|
||||
if alarm_type:
|
||||
conditions.append("type = ?")
|
||||
params.append(alarm_type)
|
||||
if start_time is not None:
|
||||
conditions.append("time >= ?")
|
||||
params.append(start_time.isoformat(sep=" ", timespec="seconds"))
|
||||
if end_time is not None:
|
||||
conditions.append("time <= ?")
|
||||
params.append(end_time.isoformat(sep=" ", timespec="seconds"))
|
||||
|
||||
where_clause = ""
|
||||
if conditions:
|
||||
where_clause = "WHERE " + " AND ".join(conditions)
|
||||
|
||||
with get_connection() as connection:
|
||||
cursor = connection.execute(
|
||||
"""
|
||||
f"""
|
||||
SELECT id, alarm_type, time, no, type, content, level
|
||||
FROM alarm_event
|
||||
{where_clause}
|
||||
ORDER BY id DESC
|
||||
LIMIT ? OFFSET ?
|
||||
""",
|
||||
(size, offset),
|
||||
tuple(params + [size, offset]),
|
||||
)
|
||||
return [dict(row) for row in cursor.fetchall()]
|
||||
|
||||
@ -31,6 +31,21 @@ class JsonConfigRepository:
|
||||
def write_setting_config(self, payload: Dict[str, Any]) -> Path:
|
||||
return self._write_json("setting.json", payload)
|
||||
|
||||
def read_device_config(self) -> Dict[str, Any]:
|
||||
data = self.read_json("device.json")
|
||||
return data if isinstance(data, dict) else {}
|
||||
|
||||
def read_channel_config(self) -> Dict[str, Any]:
|
||||
data = self.read_json("channel.json")
|
||||
return data if isinstance(data, dict) else {}
|
||||
|
||||
def read_setting_config(self) -> Dict[str, Any]:
|
||||
data = self.read_json("setting.json")
|
||||
return data if isinstance(data, dict) else {}
|
||||
|
||||
def read_setting_section(self, section: str) -> Any:
|
||||
return self.read_setting_config().get(section)
|
||||
|
||||
def read_json(self, filename: str) -> Union[Dict[str, Any], List[Any]]:
|
||||
path = self.config_dir / filename
|
||||
if not path.exists():
|
||||
|
||||
@ -1,6 +1,7 @@
|
||||
from __future__ import annotations
|
||||
|
||||
from typing import Any, Dict, List
|
||||
from datetime import datetime
|
||||
from typing import Any, Dict, List, Optional
|
||||
|
||||
from app.adapters.device_client import CDeviceClient, MockDeviceClient
|
||||
from app.cache.memory_store import memory_store
|
||||
@ -50,11 +51,48 @@ class PlatformService:
|
||||
device_result = self.device_client.send_device_config(payload)
|
||||
return {"save_path": f"/config/{path.name}", **device_result}
|
||||
|
||||
def get_device_config(self) -> Dict[str, Any]:
|
||||
current = self.config_repo.read_device_config()
|
||||
data = {
|
||||
"password": "",
|
||||
"hardware_version": {
|
||||
"board_version": "B001.001.001",
|
||||
"display_version": "S001.001.001",
|
||||
"other_version": "Y001.001.001",
|
||||
},
|
||||
"software_version": {
|
||||
"display_program": "001.001.001",
|
||||
"communication_program": "001.001.001",
|
||||
"measurement_program": "001.001.001",
|
||||
},
|
||||
"net": [
|
||||
{"nic": "网卡一", "ip": "192.168.1.10", "mask": "255.255.255.0", "gateway": "192.168.1.1", "protocol": "Modbus TCP"},
|
||||
{"nic": "网卡二", "ip": "192.168.1.56", "mask": "255.255.255.255", "gateway": "192.168.1.56", "protocol": "Modbus TCP"},
|
||||
],
|
||||
"uart": [
|
||||
{"port": "COM1", "baud": 9600, "parity": "NONE", "data_bits": 8, "stop_bits": 1, "protocol": ""},
|
||||
{"port": "COM2", "baud": 115200, "parity": "NONE", "data_bits": 8, "stop_bits": 1, "protocol": "Modbus RTU"},
|
||||
],
|
||||
}
|
||||
data.update(current)
|
||||
# 不返回已保存的哈希密码,避免前端把哈希串直接显示到设置界面
|
||||
data["password"] = ""
|
||||
return data
|
||||
|
||||
def save_channel_config(self, payload: ChannelConfigIn) -> Dict[str, Any]:
|
||||
path = self.config_repo.write_channel_config(payload.model_dump())
|
||||
device_result = self.device_client.send_channel_config(payload)
|
||||
return {"save_path": f"/config/{path.name}", **device_result}
|
||||
|
||||
def get_channel_config(self) -> Dict[str, Any]:
|
||||
current = self.config_repo.read_channel_config()
|
||||
data = {
|
||||
"ai_channel": [{"ch": 1, "singal_type": "4-20mA", "line_no": 1, "type": "UA", "limit_low": 0, "limit_high": 20}],
|
||||
"ao_channel": [{"ch": 1, "singal_type": "1-5v", "line_no": 2, "type": "UA", "limit_low": 0, "limit_high": 20}],
|
||||
}
|
||||
data.update(current)
|
||||
return data
|
||||
|
||||
def save_line_alarm_setting(self, payload: LineAlarmSettingIn) -> Dict[str, Any]:
|
||||
current = self.config_repo.read_json("setting.json")
|
||||
if not isinstance(current, dict):
|
||||
@ -64,6 +102,21 @@ class PlatformService:
|
||||
device_result = self.device_client.send_line_alarm_setting(payload)
|
||||
return {"save_path": f"/config/{path.name}", **device_result}
|
||||
|
||||
def get_line_alarm_setting(self) -> Dict[str, Any]:
|
||||
current = self.config_repo.read_setting_section("line_alarm_setting")
|
||||
if isinstance(current, dict):
|
||||
return current
|
||||
return {
|
||||
"line_no": 1,
|
||||
"over_limit_alarm": [
|
||||
{"category": "电压", "limit": 180, "delay": 180, "output_node": "开出1", "enabled": True},
|
||||
{"category": "电流", "limit": 180, "delay": 180, "output_node": "开出1", "enabled": True},
|
||||
{"category": "差流", "limit": 180, "delay": 180, "output_node": "开出1", "enabled": False},
|
||||
{"category": "频率", "limit": 180, "delay": 180, "output_node": "开出1", "enabled": False},
|
||||
],
|
||||
"fault_alarm": [{"category": "PT断线", "delay": 180, "output_node": "开出1", "enabled": True}],
|
||||
}
|
||||
|
||||
def save_ai_alarm_setting(self, payload: List[AiAlarmSettingIn]) -> Dict[str, Any]:
|
||||
current = self.config_repo.read_json("setting.json")
|
||||
if not isinstance(current, dict):
|
||||
@ -73,6 +126,22 @@ class PlatformService:
|
||||
device_result = self.device_client.send_ai_alarm_setting(current["ai_alarm_setting"])
|
||||
return {"save_path": f"/config/{path.name}", **device_result}
|
||||
|
||||
def get_ai_alarm_setting(self) -> List[Dict[str, Any]]:
|
||||
current = self.config_repo.read_setting_section("ai_alarm_setting")
|
||||
if isinstance(current, list):
|
||||
return current
|
||||
return [
|
||||
{
|
||||
"channel_no": 1,
|
||||
"singal_type": "4-20mA",
|
||||
"limit_low": 0,
|
||||
"limit_high": 20,
|
||||
"delay": 180,
|
||||
"output_node": "开出1",
|
||||
"enabled": True,
|
||||
}
|
||||
]
|
||||
|
||||
def save_system_config(self, payload: SystemConfigIn) -> Dict[str, Any]:
|
||||
current = self.config_repo.read_json("setting.json")
|
||||
if not isinstance(current, dict):
|
||||
@ -81,8 +150,30 @@ class PlatformService:
|
||||
self.config_repo.write_setting_config(current)
|
||||
return self.device_client.send_system_config(payload)
|
||||
|
||||
def list_alarms(self, page: int, size: int) -> List[Dict[str, Any]]:
|
||||
return self.alarm_repo.list_alarms(page=page, size=size)
|
||||
def get_system_config(self) -> Dict[str, Any]:
|
||||
current = self.config_repo.read_setting_section("system_config")
|
||||
data = SystemConfigIn().model_dump()
|
||||
if isinstance(current, dict):
|
||||
data.update(current)
|
||||
return data
|
||||
|
||||
def list_alarms(
|
||||
self,
|
||||
page: int,
|
||||
size: int,
|
||||
no: str = "",
|
||||
alarm_type: str = "",
|
||||
start_time: Optional[datetime] = None,
|
||||
end_time: Optional[datetime] = None,
|
||||
) -> List[Dict[str, Any]]:
|
||||
return self.alarm_repo.list_alarms(
|
||||
page=page,
|
||||
size=size,
|
||||
no=no,
|
||||
alarm_type=alarm_type,
|
||||
start_time=start_time,
|
||||
end_time=end_time,
|
||||
)
|
||||
|
||||
def switch_control(self, payload: SwitchControlIn) -> Dict[str, Any]:
|
||||
return self.device_client.send_switch_control(payload)
|
||||
|
||||
@ -1,6 +1,14 @@
|
||||
from datetime import datetime
|
||||
|
||||
from fastapi.testclient import TestClient
|
||||
|
||||
from app.core.config import settings
|
||||
from app.db.sqlite import get_connection, init_db
|
||||
from app.main import app
|
||||
from app.repositories.alarm_repo import AlarmRepository
|
||||
from app.repositories.json_config_repo import JsonConfigRepository
|
||||
from app.schemas.platform import AlarmEvent
|
||||
from app.services.platform_service import platform_service
|
||||
|
||||
|
||||
client = TestClient(app)
|
||||
@ -18,3 +26,117 @@ def test_realtime_endpoint() -> None:
|
||||
payload = response.json()
|
||||
assert payload["code"] == 200
|
||||
assert "line_list" in payload["data"]
|
||||
|
||||
|
||||
def test_alarm_list_filters(tmp_path) -> None:
|
||||
settings.alarm_db_path = tmp_path / "alarm.db"
|
||||
init_db()
|
||||
|
||||
with get_connection() as connection:
|
||||
connection.execute("DELETE FROM alarm_event")
|
||||
connection.commit()
|
||||
|
||||
repo = AlarmRepository()
|
||||
repo.save_alarm(
|
||||
AlarmEvent(
|
||||
alarm_type="保护报警",
|
||||
time=datetime(2026, 5, 16, 10, 0, 0),
|
||||
no="L1",
|
||||
type="voltage",
|
||||
content="A 相过压",
|
||||
level="严重",
|
||||
)
|
||||
)
|
||||
repo.save_alarm(
|
||||
AlarmEvent(
|
||||
alarm_type="状态报警",
|
||||
time=datetime(2026, 5, 16, 11, 0, 0),
|
||||
no="L2",
|
||||
type="current",
|
||||
content="B 相过流",
|
||||
level="一般",
|
||||
)
|
||||
)
|
||||
|
||||
response = client.get(
|
||||
"/api/alarm/list",
|
||||
params={
|
||||
"page": 1,
|
||||
"size": 20,
|
||||
"no": "L2",
|
||||
"type": "current",
|
||||
"start_time": "2026-05-16T10:30:00",
|
||||
"end_time": "2026-05-16T11:30:00",
|
||||
},
|
||||
)
|
||||
|
||||
assert response.status_code == 200
|
||||
payload = response.json()
|
||||
assert payload["code"] == 200
|
||||
assert len(payload["data"]) == 1
|
||||
assert payload["data"][0]["no"] == "L2"
|
||||
assert payload["data"][0]["type"] == "current"
|
||||
|
||||
|
||||
def test_config_query_endpoints(tmp_path) -> None:
|
||||
old_repo = platform_service.config_repo
|
||||
platform_service.config_repo = JsonConfigRepository(tmp_path)
|
||||
|
||||
try:
|
||||
platform_service.config_repo.write_device_config(
|
||||
{
|
||||
"password": "hashed-password",
|
||||
"hardware_version": {
|
||||
"board_version": "B001.001.002",
|
||||
"display_version": "S001.001.001",
|
||||
"other_version": "Y001.001.001",
|
||||
},
|
||||
"software_version": {
|
||||
"display_program": "001.001.001",
|
||||
"communication_program": "001.001.001",
|
||||
"measurement_program": "001.001.001",
|
||||
},
|
||||
"net": [
|
||||
{"nic": "网卡一", "ip": "192.168.1.10", "mask": "255.255.255.0", "gateway": "192.168.1.1", "protocol": "Modbus TCP"}
|
||||
],
|
||||
"uart": [
|
||||
{"port": "COM1", "baud": 9600, "parity": "NONE", "data_bits": 8, "stop_bits": 1, "protocol": "Modbus RTU"}
|
||||
],
|
||||
}
|
||||
)
|
||||
platform_service.config_repo.write_channel_config(
|
||||
{
|
||||
"ai_channel": [{"ch": 1, "singal_type": "4-20mA", "line_no": 1, "type": "UA", "limit_low": 0, "limit_high": 20}],
|
||||
"ao_channel": [{"ch": 1, "singal_type": "1-5v", "line_no": 2, "type": "UA", "limit_low": 0, "limit_high": 20}],
|
||||
}
|
||||
)
|
||||
platform_service.config_repo.write_setting_config(
|
||||
{
|
||||
"line_alarm_setting": {"line_no": 1, "over_limit_alarm": [{"category": "电压", "limit": 180, "delay": 180, "output_node": "开出1", "enabled": True}], "fault_alarm": []},
|
||||
"ai_alarm_setting": [{"channel_no": 1, "singal_type": "4-20mA", "limit_low": 0, "limit_high": 20, "delay": 180, "output_node": "开出1", "enabled": True}],
|
||||
"system_config": {"time_sync": "manual", "brightness": 83, "screen_saver": 60},
|
||||
}
|
||||
)
|
||||
|
||||
headers = {"X-API-Token": settings.auth_password}
|
||||
|
||||
device_response = client.get("/api/config/device", headers=headers)
|
||||
channel_response = client.get("/api/config/channel", headers=headers)
|
||||
line_alarm_response = client.get("/api/config/line_alarm_setting", headers=headers)
|
||||
ai_alarm_response = client.get("/api/config/ai_alarm_setting", headers=headers)
|
||||
system_response = client.get("/api/config/system", headers=headers)
|
||||
|
||||
assert device_response.status_code == 200
|
||||
assert channel_response.status_code == 200
|
||||
assert line_alarm_response.status_code == 200
|
||||
assert ai_alarm_response.status_code == 200
|
||||
assert system_response.status_code == 200
|
||||
|
||||
assert device_response.json()["data"]["password"] == ""
|
||||
assert device_response.json()["data"]["net"][0]["ip"] == "192.168.1.10"
|
||||
assert channel_response.json()["data"]["ai_channel"][0]["singal_type"] == "4-20mA"
|
||||
assert line_alarm_response.json()["data"]["line_no"] == 1
|
||||
assert ai_alarm_response.json()["data"][0]["channel_no"] == 1
|
||||
assert system_response.json()["data"]["brightness"] == 83
|
||||
finally:
|
||||
platform_service.config_repo = old_repo
|
||||
|
||||
@ -34,6 +34,9 @@
|
||||
3. **系统管理配置**
|
||||
- 对时设置:参数实时下发嵌入式程序
|
||||
- 灯光设置:屏幕亮度、屏保时间,参数实时下发嵌入式程序
|
||||
4. **配置回显要求**
|
||||
- 设备网络配置、通道配置、报警设置、系统设置页面在打开时需先读取已保存参数
|
||||
- 已保存配置从本地JSON文件读取,并通过HTTP查询接口返回给Web界面显示
|
||||
|
||||
#### 2.2.2 实时数据动态显示功能
|
||||
1. **数据读取周期**:间隔**0.5秒**从C语言控制系统读取实时数据
|
||||
@ -126,6 +129,8 @@ Python FastAPI服务层(RESTful接口 + WebSocket推送 + 数据处理)
|
||||
Web界面 → HTTP POST → Python → JSON存储 + 下发C程序
|
||||
3. **状态/查询流向**
|
||||
Web界面 → HTTP GET → Python → 返回数据展示
|
||||
4. **配置回显流向**
|
||||
Web界面进入设置页 → HTTP GET配置查询接口 → Python读取JSON配置 → 返回当前配置数据 → Web界面显示
|
||||
|
||||
## 四、接口详细设计(RESTful + WebSocket)
|
||||
### 4.1 RESTful API 接口详情(完整参数+返回值)
|
||||
@ -288,7 +293,48 @@ Web界面 → HTTP GET → Python → 返回数据展示
|
||||
|
||||
---
|
||||
|
||||
#### 3. POST /api/config/device
|
||||
#### 3. GET /api/config/device
|
||||
**作用**:读取设备基础配置,用于设备/网络配置界面打开时回显当前参数
|
||||
**参数**:无
|
||||
**返回data**:
|
||||
```json
|
||||
{
|
||||
"password": "",
|
||||
"hardware_version": {
|
||||
"board_version": "B001.001.001",
|
||||
"display_version": "S001.001.001",
|
||||
"other_version": "Y001.001.001"
|
||||
},
|
||||
"software_version": {
|
||||
"display_program": "001.001.001",
|
||||
"communication_program": "001.001.001",
|
||||
"measurement_program": "001.001.001"
|
||||
},
|
||||
"net": [
|
||||
{
|
||||
"nic": "网卡一",
|
||||
"ip": "192.168.1.10",
|
||||
"mask": "255.255.255.0",
|
||||
"gateway": "192.168.1.1",
|
||||
"protocol": "Modbus TCP"
|
||||
}
|
||||
],
|
||||
"uart": [
|
||||
{
|
||||
"port": "COM1",
|
||||
"baud": 9600,
|
||||
"parity": "NONE",
|
||||
"data_bits": 8,
|
||||
"stop_bits": 1,
|
||||
"protocol": "Modbus RTU"
|
||||
}
|
||||
]
|
||||
}
|
||||
```
|
||||
|
||||
---
|
||||
|
||||
#### 4. POST /api/config/device
|
||||
**作用**:提交设备基础配置(版本、通讯、密码)
|
||||
**请求body参数**:
|
||||
```json
|
||||
@ -344,7 +390,20 @@ Web界面 → HTTP GET → Python → 返回数据展示
|
||||
|
||||
---
|
||||
|
||||
#### 4. POST /api/config/channel
|
||||
#### 5. GET /api/config/channel
|
||||
**作用**:读取通道配置,用于通道配置界面打开时回显当前参数
|
||||
**参数**:无
|
||||
**返回data**:
|
||||
```json
|
||||
{
|
||||
"ai_channel": [{"ch":1,"singal_type":"4-20mA","line_no":1,"type":"UA","limit_low":0,"limit_high":20}],
|
||||
"ao_channel": [{"ch":1,"singal_type":"1-5v","line_no":2,"type":"UA","limit_low":0,"limit_high":20}]
|
||||
}
|
||||
```
|
||||
|
||||
---
|
||||
|
||||
#### 6. POST /api/config/channel
|
||||
**作用**:提交AI/AO通道配置(AI:12通道,AO:12通道)
|
||||
**请求body参数**:
|
||||
|
||||
@ -358,13 +417,42 @@ Web界面 → HTTP GET → Python → 返回数据展示
|
||||
|
||||
---
|
||||
|
||||
#### 5. POST /api/config/line_alarm_setting
|
||||
#### 7. GET /api/config/line_alarm_setting
|
||||
**作用**:读取线路报警设置,用于报警设置界面打开时回显当前线路参数
|
||||
**参数**:无
|
||||
**返回data**:
|
||||
```json
|
||||
{
|
||||
"line_no": 1,
|
||||
"over_limit_alarm": [
|
||||
{
|
||||
"category": "电压",
|
||||
"limit": 180,
|
||||
"delay": 180,
|
||||
"output_node": "开出1",
|
||||
"enabled": true
|
||||
}
|
||||
],
|
||||
"fault_alarm": [
|
||||
{
|
||||
"category": "PT断线",
|
||||
"delay": 180,
|
||||
"output_node": "开出1",
|
||||
"enabled": true
|
||||
}
|
||||
]
|
||||
}
|
||||
```
|
||||
|
||||
---
|
||||
|
||||
#### 8. POST /api/config/line_alarm_setting
|
||||
**作用**:提交定值报警阈值配置
|
||||
**请求body参数**:
|
||||
|
||||
```json
|
||||
{
|
||||
"line_no": 1,
|
||||
[
|
||||
{"line_no": 1,
|
||||
"over_limit_alarm": [
|
||||
{
|
||||
"category": "电压",
|
||||
@ -417,12 +505,33 @@ Web界面 → HTTP GET → Python → 返回数据展示
|
||||
}
|
||||
]
|
||||
}
|
||||
]
|
||||
```
|
||||
**返回data**:`{"save_path":"/config/setting.json","send_status":"成功"}`
|
||||
|
||||
---
|
||||
|
||||
#### 5. POST /api/config/ai_alarm_setting
|
||||
#### 9. GET /api/config/ai_alarm_setting
|
||||
**作用**:读取AI报警设置,用于报警设置界面打开时回显当前AI报警参数
|
||||
**参数**:无
|
||||
**返回data**:
|
||||
```json
|
||||
[
|
||||
{
|
||||
"channel_no": 1,
|
||||
"singal_type": "4-20mA",
|
||||
"limit_low": 0,
|
||||
"limit_high": 20,
|
||||
"delay": 180,
|
||||
"output_node": "开出1",
|
||||
"enabled": true
|
||||
}
|
||||
]
|
||||
```
|
||||
|
||||
---
|
||||
|
||||
#### 10. POST /api/config/ai_alarm_setting
|
||||
|
||||
**作用**:提交AI报警设置
|
||||
**请求body参数**:
|
||||
@ -452,7 +561,21 @@ Web界面 → HTTP GET → Python → 返回数据展示
|
||||
|
||||
**返回data**:`{"save_path":"/config/setting.json","send_status":"成功"}`
|
||||
|
||||
#### 6. POST /api/config/system
|
||||
#### 11. GET /api/config/system
|
||||
**作用**:读取系统设置,用于系统设置界面打开时回显当前参数
|
||||
**参数**:无
|
||||
**返回data**:
|
||||
```json
|
||||
{
|
||||
"time_sync": "auto",
|
||||
"brightness": 80,
|
||||
"screen_saver": 60
|
||||
}
|
||||
```
|
||||
|
||||
---
|
||||
|
||||
#### 12. POST /api/config/system
|
||||
|
||||
**作用**:提交系统对时、灯光配置
|
||||
**请求body参数**:
|
||||
@ -467,9 +590,26 @@ Web界面 → HTTP GET → Python → 返回数据展示
|
||||
|
||||
---
|
||||
|
||||
#### 7. GET /api/alarm/list
|
||||
#### 13. GET /api/alarm/list
|
||||
**作用**:分页查询历史报警
|
||||
**参数**:`page=1&size=20`
|
||||
**参数**:
|
||||
- `page=1`:页码,从 `1` 开始
|
||||
- `size=20`:每页条数,最大 `100`
|
||||
- `no=L1`:按线路号/通道号精确筛选
|
||||
- `type=current`:按报警类型字段精确筛选
|
||||
- `start_time=2026-05-16T10:30:00`:按报警开始时间筛选
|
||||
- `end_time=2026-05-16T11:30:00`:按报警结束时间筛选
|
||||
|
||||
**示例请求**:
|
||||
```text
|
||||
GET /api/alarm/list?page=1&size=20&no=L1&type=current&start_time=2026-05-16T10:30:00&end_time=2026-05-16T11:30:00
|
||||
```
|
||||
|
||||
说明:
|
||||
- 所有筛选条件均为可选
|
||||
- 未传筛选条件时,默认按分页查询全部历史报警
|
||||
- `start_time` 和 `end_time` 使用日期时间格式
|
||||
|
||||
**返回data**:
|
||||
```json
|
||||
[
|
||||
@ -487,7 +627,7 @@ Web界面 → HTTP GET → Python → 返回数据展示
|
||||
|
||||
---
|
||||
|
||||
#### 8. POST /api/control/switch
|
||||
#### 14. POST /api/control/switch
|
||||
**作用**:下发开关量控制指令
|
||||
**请求body参数**:
|
||||
```json
|
||||
|
||||
@ -21,6 +21,31 @@ export async function fetchDeviceStatus(): Promise<DeviceStatus> {
|
||||
return response.data.data
|
||||
}
|
||||
|
||||
export async function fetchDeviceConfig(): Promise<DeviceConfigPayload> {
|
||||
const response = await http.get<ApiResponse<DeviceConfigPayload>>('/config/device')
|
||||
return response.data.data
|
||||
}
|
||||
|
||||
export async function fetchChannelConfig(): Promise<ChannelConfigPayload> {
|
||||
const response = await http.get<ApiResponse<ChannelConfigPayload>>('/config/channel')
|
||||
return response.data.data
|
||||
}
|
||||
|
||||
export async function fetchLineAlarmSetting(): Promise<LineAlarmSettingPayload> {
|
||||
const response = await http.get<ApiResponse<LineAlarmSettingPayload>>('/config/line_alarm_setting')
|
||||
return response.data.data
|
||||
}
|
||||
|
||||
export async function fetchAiAlarmSetting(): Promise<AiAlarmSettingItem[]> {
|
||||
const response = await http.get<ApiResponse<AiAlarmSettingItem[]>>('/config/ai_alarm_setting')
|
||||
return response.data.data
|
||||
}
|
||||
|
||||
export async function fetchSystemConfig(): Promise<SystemConfigPayload> {
|
||||
const response = await http.get<ApiResponse<SystemConfigPayload>>('/config/system')
|
||||
return response.data.data
|
||||
}
|
||||
|
||||
export async function fetchAlarmHistory(page = 1, size = 20): Promise<AlarmEvent[]> {
|
||||
const response = await http.get<ApiResponse<AlarmEvent[]>>(`/alarm/list?page=${page}&size=${size}`)
|
||||
return response.data.data
|
||||
|
||||
@ -38,6 +38,8 @@ const state = reactive<PlatformState>({
|
||||
let realtimeSocket: WebSocket | null = null
|
||||
let alarmSocket: WebSocket | null = null
|
||||
let realtimePollTimer: number | null = null
|
||||
let realtimeReconnectTimer: number | null = null
|
||||
let realtimeReconnectAttempts = 0
|
||||
|
||||
function markRealtime(data: RealtimeData, source: string) {
|
||||
state.realtime = data
|
||||
@ -50,6 +52,57 @@ async function syncRealtimeByHttp(source = 'http-poll') {
|
||||
markRealtime(realtime, source)
|
||||
}
|
||||
|
||||
function clearRealtimeReconnectTimer() {
|
||||
if (realtimeReconnectTimer !== null) {
|
||||
window.clearTimeout(realtimeReconnectTimer)
|
||||
realtimeReconnectTimer = null
|
||||
}
|
||||
}
|
||||
|
||||
function scheduleRealtimeReconnect() {
|
||||
if (realtimeReconnectTimer !== null) {
|
||||
return
|
||||
}
|
||||
const delay = Math.min(5000, 1000 * Math.max(1, realtimeReconnectAttempts + 1))
|
||||
realtimeReconnectTimer = window.setTimeout(() => {
|
||||
realtimeReconnectTimer = null
|
||||
realtimeReconnectAttempts += 1
|
||||
startRealtimeSocket()
|
||||
}, delay)
|
||||
}
|
||||
|
||||
function startRealtimeSocket() {
|
||||
if (realtimeSocket && [WebSocket.CONNECTING, WebSocket.OPEN].includes(realtimeSocket.readyState)) {
|
||||
return
|
||||
}
|
||||
|
||||
const socket = connectRealtimeWithLifecycle(
|
||||
(data) => {
|
||||
state.realtimeConnected = true
|
||||
markRealtime(data, 'websocket')
|
||||
},
|
||||
{
|
||||
onOpen: () => {
|
||||
state.realtimeConnected = true
|
||||
realtimeReconnectAttempts = 0
|
||||
clearRealtimeReconnectTimer()
|
||||
},
|
||||
onClose: () => {
|
||||
state.realtimeConnected = false
|
||||
if (realtimeSocket === socket) {
|
||||
realtimeSocket = null
|
||||
}
|
||||
scheduleRealtimeReconnect()
|
||||
},
|
||||
onError: () => {
|
||||
state.realtimeConnected = false
|
||||
},
|
||||
},
|
||||
)
|
||||
|
||||
realtimeSocket = socket
|
||||
}
|
||||
|
||||
async function refreshStatus() {
|
||||
state.statusLoading = true
|
||||
try {
|
||||
@ -93,23 +146,7 @@ async function bootstrap() {
|
||||
await refreshAlarms(1)
|
||||
|
||||
if (!realtimeSocket) {
|
||||
realtimeSocket = connectRealtimeWithLifecycle(
|
||||
(data) => {
|
||||
state.realtimeConnected = true
|
||||
markRealtime(data, 'websocket')
|
||||
},
|
||||
{
|
||||
onOpen: () => {
|
||||
state.realtimeConnected = true
|
||||
},
|
||||
onClose: () => {
|
||||
state.realtimeConnected = false
|
||||
},
|
||||
onError: () => {
|
||||
state.realtimeConnected = false
|
||||
},
|
||||
},
|
||||
)
|
||||
startRealtimeSocket()
|
||||
}
|
||||
|
||||
if (!alarmSocket) {
|
||||
|
||||
@ -1,4 +1,4 @@
|
||||
<script setup lang="ts">
|
||||
<script setup lang="ts">
|
||||
defineProps<{ store: any; actions: any }>()
|
||||
</script>
|
||||
|
||||
|
||||
@ -1,6 +1,6 @@
|
||||
<script setup lang="ts">
|
||||
import { reactive, ref } from 'vue'
|
||||
import { saveAiAlarmSetting, saveLineAlarmSetting } from '../api/platform'
|
||||
<script setup lang="ts">
|
||||
import { onMounted, reactive, ref } from 'vue'
|
||||
import { fetchAiAlarmSetting, fetchLineAlarmSetting, saveAiAlarmSetting, saveLineAlarmSetting } from '../api/platform'
|
||||
import type { AiAlarmSettingItem, LineAlarmSettingPayload } from '../types/platform'
|
||||
import { ensureSaveAuthorized } from '../utils/saveGuard'
|
||||
|
||||
@ -32,6 +32,37 @@ const aiAlarm = reactive<AiAlarmSettingItem[]>([
|
||||
const result = ref('未保存')
|
||||
const saving = ref(false)
|
||||
|
||||
onMounted(async () => {
|
||||
try {
|
||||
const [lineData, aiData] = await Promise.all([fetchLineAlarmSetting(), fetchAiAlarmSetting()])
|
||||
lineAlarm.line_no = lineData.line_no
|
||||
lineData.over_limit_alarm.forEach((item, index) => {
|
||||
if (lineAlarm.over_limit_alarm[index]) {
|
||||
Object.assign(lineAlarm.over_limit_alarm[index], item)
|
||||
} else {
|
||||
lineAlarm.over_limit_alarm.push(item)
|
||||
}
|
||||
})
|
||||
lineData.fault_alarm.forEach((item, index) => {
|
||||
if (lineAlarm.fault_alarm[index]) {
|
||||
Object.assign(lineAlarm.fault_alarm[index], item)
|
||||
} else {
|
||||
lineAlarm.fault_alarm.push(item)
|
||||
}
|
||||
})
|
||||
aiData.forEach((item, index) => {
|
||||
if (aiAlarm[index]) {
|
||||
Object.assign(aiAlarm[index], item)
|
||||
} else {
|
||||
aiAlarm.push(item)
|
||||
}
|
||||
})
|
||||
result.value = '已加载当前报警设置'
|
||||
} catch (error) {
|
||||
result.value = error instanceof Error ? error.message : '加载报警设置失败'
|
||||
}
|
||||
})
|
||||
|
||||
async function save() {
|
||||
saving.value = true
|
||||
try {
|
||||
|
||||
@ -1,6 +1,6 @@
|
||||
<script setup lang="ts">
|
||||
import { reactive, ref } from 'vue'
|
||||
import { saveChannelConfig } from '../api/platform'
|
||||
<script setup lang="ts">
|
||||
import { onMounted, reactive, ref } from 'vue'
|
||||
import { fetchChannelConfig, saveChannelConfig } from '../api/platform'
|
||||
import type { ChannelConfigPayload } from '../types/platform'
|
||||
import { ensureSaveAuthorized } from '../utils/saveGuard'
|
||||
|
||||
@ -14,6 +14,29 @@ const form = reactive<ChannelConfigPayload>({
|
||||
const result = ref('未保存')
|
||||
const saving = ref(false)
|
||||
|
||||
onMounted(async () => {
|
||||
try {
|
||||
const data = await fetchChannelConfig()
|
||||
data.ai_channel.forEach((item, index) => {
|
||||
if (form.ai_channel[index]) {
|
||||
Object.assign(form.ai_channel[index], item)
|
||||
} else {
|
||||
form.ai_channel.push(item)
|
||||
}
|
||||
})
|
||||
data.ao_channel.forEach((item, index) => {
|
||||
if (form.ao_channel[index]) {
|
||||
Object.assign(form.ao_channel[index], item)
|
||||
} else {
|
||||
form.ao_channel.push(item)
|
||||
}
|
||||
})
|
||||
result.value = '已加载当前通道配置'
|
||||
} catch (error) {
|
||||
result.value = error instanceof Error ? error.message : '加载通道配置失败'
|
||||
}
|
||||
})
|
||||
|
||||
async function save() {
|
||||
saving.value = true
|
||||
try {
|
||||
|
||||
@ -1,4 +1,4 @@
|
||||
<script setup lang="ts">
|
||||
<script setup lang="ts">
|
||||
import { ref } from "vue"
|
||||
import { controlSwitch } from "../api/platform"
|
||||
|
||||
|
||||
@ -1,6 +1,6 @@
|
||||
<script setup lang="ts">
|
||||
import { reactive, ref } from 'vue'
|
||||
import { saveDeviceConfig } from '../api/platform'
|
||||
<script setup lang="ts">
|
||||
import { onMounted, reactive, ref } from 'vue'
|
||||
import { fetchDeviceConfig, saveDeviceConfig } from '../api/platform'
|
||||
import type { DeviceConfigPayload } from '../types/platform'
|
||||
import { ensureSaveAuthorized } from '../utils/saveGuard'
|
||||
|
||||
@ -31,6 +31,32 @@ const form = reactive<DeviceConfigPayload>({
|
||||
const result = ref('未保存')
|
||||
const saving = ref(false)
|
||||
|
||||
onMounted(async () => {
|
||||
try {
|
||||
const data = await fetchDeviceConfig()
|
||||
form.password = data.password
|
||||
Object.assign(form.hardware_version, data.hardware_version)
|
||||
Object.assign(form.software_version, data.software_version)
|
||||
data.net.forEach((item, index) => {
|
||||
if (form.net[index]) {
|
||||
Object.assign(form.net[index], item)
|
||||
} else {
|
||||
form.net.push(item)
|
||||
}
|
||||
})
|
||||
data.uart.forEach((item, index) => {
|
||||
if (form.uart[index]) {
|
||||
Object.assign(form.uart[index], item)
|
||||
} else {
|
||||
form.uart.push(item)
|
||||
}
|
||||
})
|
||||
result.value = '已加载当前设备配置'
|
||||
} catch (error) {
|
||||
result.value = error instanceof Error ? error.message : '加载设备配置失败'
|
||||
}
|
||||
})
|
||||
|
||||
async function save() {
|
||||
saving.value = true
|
||||
try {
|
||||
|
||||
@ -1,4 +1,4 @@
|
||||
<script setup lang="ts">
|
||||
<script setup lang="ts">
|
||||
defineProps<{ store: any; actions: any }>()
|
||||
</script>
|
||||
|
||||
|
||||
@ -1,6 +1,6 @@
|
||||
<script setup lang="ts">
|
||||
import { reactive, ref } from "vue"
|
||||
import { saveSystemConfig } from "../api/platform"
|
||||
import { onMounted, reactive, ref } from "vue"
|
||||
import { fetchSystemConfig, saveSystemConfig } from "../api/platform"
|
||||
import { ensureSaveAuthorized } from "../utils/saveGuard"
|
||||
|
||||
defineProps<{ store: any; actions: any }>()
|
||||
@ -12,6 +12,16 @@ const form = reactive({
|
||||
})
|
||||
const result = ref("未保存")
|
||||
|
||||
onMounted(async () => {
|
||||
try {
|
||||
const data = await fetchSystemConfig()
|
||||
Object.assign(form, data)
|
||||
result.value = "已加载当前系统设置"
|
||||
} catch (error) {
|
||||
result.value = error instanceof Error ? error.message : "加载系统设置失败"
|
||||
}
|
||||
})
|
||||
|
||||
async function save() {
|
||||
const guard = await ensureSaveAuthorized()
|
||||
if (!guard.ok) {
|
||||
|
||||
Loading…
Reference in New Issue
Block a user