Files
ReinLoopTest/ReinLoop/core/volume_config.py
T
2026-07-30 11:12:31 +08:00

155 lines
5.4 KiB
Python

"""Load, download, and validate the volume-measurement configuration."""
import json
import math
import os
from pathlib import Path
import sys
REQUIRED_FIELDS = {
"q_in_val", "dt", "p_max", "fit_low", "fit_high",
"T_delta", "xa_full", "num_runs",
}
def default_config_path() -> Path:
"""Return the config path without exposing a file picker in the GUI."""
override = os.environ.get("REINLOOP_VOLUME_CONFIG")
if override:
return Path(override).expanduser().resolve()
base_dir = (Path(sys.executable).resolve().parent
if getattr(sys, "frozen", False)
else Path(__file__).resolve().parent.parent)
return base_dir / "config" / "volume_measurement.json"
def load_volume_config(path=None) -> dict:
"""Read exactly eight validated parameters from a JSON object."""
config_path = Path(path).resolve() if path else default_config_path()
try:
with config_path.open("r", encoding="utf-8") as file_obj:
config = json.load(file_obj)
except FileNotFoundError as exc:
raise ValueError(f"容积测试配置文件不存在: {config_path}") from exc
except json.JSONDecodeError as exc:
raise ValueError(f"容积测试配置不是有效 JSON: {exc}") from exc
except OSError as exc:
raise ValueError(f"无法读取容积测试配置: {exc}") from exc
return validate_volume_config(config)
def validate_volume_config(config) -> dict:
"""Validate exactly eight parameters and normalize numeric values."""
if not isinstance(config, dict):
raise ValueError("容积测试配置必须是 JSON 对象")
actual_fields = set(config)
if actual_fields != REQUIRED_FIELDS:
missing = sorted(REQUIRED_FIELDS - actual_fields)
extra = sorted(actual_fields - REQUIRED_FIELDS)
raise ValueError(f"配置字段错误,缺少={missing},多余={extra}")
for field in REQUIRED_FIELDS - {"num_runs"}:
value = config[field]
if (isinstance(value, bool) or not isinstance(value, (int, float))
or not math.isfinite(float(value))):
raise ValueError(f"参数 {field} 必须是有限数字")
runs = config["num_runs"]
if isinstance(runs, bool) or not isinstance(runs, int) or runs <= 0:
raise ValueError("参数 num_runs 必须是正整数")
if config["q_in_val"] <= 0:
raise ValueError("q_in_val 必须大于 0")
if config["dt"] <= 0 or config["p_max"] <= 0:
raise ValueError("dt 和 p_max 必须大于 0")
if config["fit_low"] < 0 or config["fit_high"] <= config["fit_low"]:
raise ValueError("必须满足 0 <= fit_low < fit_high")
if config["fit_high"] > config["p_max"]:
raise ValueError("fit_high 不能大于 p_max")
if config["xa_full"] <= 0:
raise ValueError("xa_full 必须大于 0")
return {
"q_in_val": float(config["q_in_val"]),
"dt": float(config["dt"]),
"p_max": float(config["p_max"]),
"fit_low": float(config["fit_low"]),
"fit_high": float(config["fit_high"]),
"T_delta": float(config["T_delta"]),
"xa_full": float(config["xa_full"]),
"num_runs": runs,
}
def _post_volume_request(payload, timeout=10):
import requests
from api import data_record_url
try:
response = requests.post(data_record_url, json=payload, timeout=timeout)
response.raise_for_status()
result = response.json()
except Exception as exc:
raise ValueError(f"连接云端容积参数服务失败: {exc}") from exc
if not result.get("success"):
raise ValueError(result.get("errMsg", "云端拒绝容积参数请求"))
return result
def create_volume_config_request(timeout=10) -> dict:
"""Create exactly one cloud request after the customer clicks Test."""
from api import the_folder
result = _post_volume_request({
"type": "createVolumeConfigRequest",
"deviceId": the_folder,
}, timeout=timeout)
if not result.get("requestId") or not result.get("expiresAtMs"):
raise ValueError("云端未返回有效的容积参数请求编号")
return {
"request_id": result["requestId"],
"expires_at_ms": int(result["expiresAtMs"]),
}
def poll_volume_config_request(request_id: str, timeout=10) -> dict:
"""Poll one request; download and validate JSON only when it is ready."""
import requests
from api import the_folder
result = _post_volume_request({
"type": "getVolumeConfigRequest",
"deviceId": the_folder,
"requestId": request_id,
}, timeout=timeout)
if result.get("expired"):
return {"ready": False, "expired": True}
if not result.get("ready"):
return {"ready": False, "expired": False}
try:
response = requests.get(result["url"], timeout=timeout)
response.raise_for_status()
config = response.json()
except Exception as exc:
raise ValueError(f"下载或解析容积参数 JSON 失败: {exc}") from exc
return {
"ready": True,
"expired": False,
"config": validate_volume_config(config),
}
def acknowledge_volume_config_request(request_id: str, timeout=10) -> None:
"""Delete the consumed/abandoned request and its temporary JSON file."""
from api import the_folder
_post_volume_request({
"type": "ackVolumeConfigRequest",
"deviceId": the_folder,
"requestId": request_id,
}, timeout=timeout)