Files
site/WESP_REL/app/routes/scales.py
T
2026-07-17 12:57:18 +03:00

153 lines
4.8 KiB
Python

import time
import json
import threading
from flask import Blueprint, Response, current_app, jsonify, request
from app.routes.auth_decorators import require_paired_terminal
from app.services.hardware import ScalesReader
bp = Blueprint("scales", __name__)
_scales_reader = None
_calibration_state = {"target_weight": None, "initial_raw": None}
_calibration_lock = threading.Lock()
def _error(message: str, status_code: int = 400):
return jsonify({"error": True, "status": "error", "message": message}), status_code
def _get_reader() -> ScalesReader:
global _scales_reader
if _scales_reader is None:
_scales_reader = ScalesReader.from_config(current_app.config)
_scales_reader.bind_flask_app(current_app._get_current_object())
_scales_reader.start()
return _scales_reader
@bp.get("/current_weight")
@require_paired_terminal
def current_weight():
reader = _get_reader()
payload = {"weight": reader.get_current_weight(), **reader.get_scale_health()}
return jsonify(payload), 200
@bp.post("/tare")
@require_paired_terminal
def tare_weight():
reader = _get_reader()
try:
tare_value = reader.tare()
except ValueError as e:
return jsonify({"status": "error", "message": str(e)}), 400
return jsonify(
{
"status": "success",
"message": "Новый ноль сохранён",
"new_zero": round(float(tare_value), 4),
"success": True,
}
)
@bp.post("/calibrate")
@require_paired_terminal
def calibrate_weight():
data = request.get_json(silent=True) or {}
reader = _get_reader()
if data.get("target_weight") is not None:
try:
target_weight = float(data.get("target_weight"))
except (TypeError, ValueError):
return _error("target_weight должен быть числом", 400)
if target_weight <= 0:
return _error("target_weight должен быть больше 0", 400)
try:
initial_raw = reader.average_recent_raw(
int(current_app.config.get("TARE_RAW_WINDOW", 2))
)
except ValueError as e:
return _error(str(e), 400)
with _calibration_lock:
_calibration_state["target_weight"] = target_weight
_calibration_state["initial_raw"] = initial_raw
return jsonify(
{
"status": "confirm",
"message": f"Поместите груз {target_weight}кг и подтвердите",
}
)
raw_factor = data.get("calibration_factor")
if raw_factor is None:
return _error("Нужно указать calibration_factor или target_weight", 400)
try:
factor = float(raw_factor)
except (TypeError, ValueError):
return _error("calibration_factor должен быть числом", 400)
try:
applied = reader.calibrate(factor)
except ValueError as e:
return _error(str(e), 400)
return jsonify({"status": "success", "success": True, "calibration_factor": applied})
@bp.post("/calibrate/continue")
@require_paired_terminal
def calibrate_continue():
reader = _get_reader()
with _calibration_lock:
target_weight = _calibration_state.get("target_weight")
initial_raw = _calibration_state.get("initial_raw")
if not target_weight or initial_raw is None:
return _error("Калибровка не начата", 400)
try:
final_raw = reader.average_recent_raw(int(current_app.config.get("TARE_RAW_WINDOW", 2)))
counts_per_kg = (final_raw - float(initial_raw)) / float(target_weight)
applied = reader.set_counts_per_kg(float(counts_per_kg))
except ValueError as e:
return _error(str(e), 400)
with _calibration_lock:
_calibration_state["target_weight"] = None
_calibration_state["initial_raw"] = None
return jsonify(
{
"status": "success",
"message": "Калибровка завершена!",
"calibration_factor": round(float(applied), 6),
}
)
@bp.get("/current_raw_data")
@require_paired_terminal
def current_raw_data():
reader = _get_reader()
return jsonify(reader.calibration_debug_payload())
@bp.get("/stream_weight")
@require_paired_terminal
def stream_weight():
reader = _get_reader()
# Интервал нужно взять здесь: внутри генератора контекст приложения уже снят.
read_interval = float(current_app.config.get("READ_INTERVAL", 0.05))
def _events():
while True:
payload = {"weight": reader.get_current_weight(), **reader.get_scale_health()}
yield f"data: {json.dumps(payload, ensure_ascii=False)}\n\n"
time.sleep(read_interval)
return Response(_events(), mimetype="text/event-stream")