153 lines
4.8 KiB
Python
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")
|
|
|