@@ -0,0 +1,152 @@
|
||||
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")
|
||||
|
||||
Reference in New Issue
Block a user