144 lines
5.1 KiB
Python
144 lines
5.1 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):
|
|
from api import device_post
|
|
|
|
try:
|
|
result = device_post(payload, timeout=timeout)
|
|
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."""
|
|
result = _post_volume_request({
|
|
"type": "createVolumeConfigRequest",
|
|
}, 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
|
|
|
|
result = _post_volume_request({
|
|
"type": "getVolumeConfigRequest",
|
|
"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."""
|
|
_post_volume_request({
|
|
"type": "ackVolumeConfigRequest",
|
|
"requestId": request_id,
|
|
}, timeout=timeout)
|