bff6d70b17
Aplicatie Flask + SQLite pentru productie alimentara: cantarirea componentelor unui produs finit si tiparirea etichetei prin QZ Tray. - produse finite (retete) formate din componente cantarite - designer vizual de etichete (mm/px, zona sigura, campuri variabile, cod QR cu continut din mai multe bucati) - legare produs finit -> sablon de eticheta - navigare pe doua taburi: Cantarire / Setari - administrare: componente, produse finite, sabloane, campuri, istoric - backup, resetare baza de date, checklist de pregatire, /healthz - CLI de administrare si serviciu systemd pentru productie - vendor/syncpulse-scale-bridge (GPL-3.0-or-later) pentru citirea cantarului
138 lines
5.7 KiB
Python
138 lines
5.7 KiB
Python
# SPDX-License-Identifier: GPL-3.0-or-later
|
|
"""Indicators that print labels: the PTC400 capture, other label languages, the label log."""
|
|
import json
|
|
import os
|
|
import time
|
|
|
|
import pytest
|
|
|
|
from scale_bridge import profiles
|
|
from scale_bridge.detect import Detector, Timings
|
|
from scale_bridge.labels import LabelLog
|
|
from scale_bridge.protocols import PROTOCOLS
|
|
from scale_bridge.simulator import SimulatorBank
|
|
|
|
HERE = os.path.dirname(os.path.abspath(__file__))
|
|
FAST = Timings(passive_s=0.4, probe_s=0.1, confirm_s=0.3, drain_s=0.02)
|
|
|
|
|
|
def capture() -> list[bytes]:
|
|
with open(os.path.join(HERE, "captures", "ptc400_tspl_labels.hex"), encoding="ascii") as f:
|
|
return [bytes.fromhex(line) for line in f if line.strip()]
|
|
|
|
|
|
def test_ptc400_capture_all_labels():
|
|
# Recorded from a PTC400 indicator (TSPL to a TSC label printer), a 1.4 kg
|
|
# test weight moved around the platform after three other weighings.
|
|
readings = PROTOCOLS["label_print"].readings(b"".join(capture()))
|
|
assert [r.value for r in readings] == [102.3, 141.8, 141.2, 1.4, 1.4, 1.35, 1.3, 1.25, 0.95,
|
|
1.2, 1.25, 1.15]
|
|
first = readings[0]
|
|
assert first.unit == "kg" and first.stable is True
|
|
assert first.fields["Order.Co"] == "222" and first.fields["SHIFT"] == "2"
|
|
assert first.fields["date"] == "1405/07/19" and first.fields["time"] == "16:39"
|
|
assert first.fields["Thickness"] == "0.0" and first.fields["No"] == "6"
|
|
assert readings[2].fields["Thickness"] == "3.3"
|
|
|
|
|
|
def test_label_split_over_two_chunks_is_read_once():
|
|
label = capture()[0]
|
|
chunks = [(1000, label[:200]), (1020, label[200:])]
|
|
timed = profiles.timed_readings("label_print", None, chunks)
|
|
assert len(timed) == 1 and timed[0].t_ms == 1020 and timed[0].reading.value == 102.3
|
|
|
|
|
|
@pytest.mark.parametrize("label,value,unit", [
|
|
(b'^XA^FO50,50^A0N,40,40^FDNet: 12.34 kg^FS^FO50,120^FDLOT 7^FS^XZ', 12.34, "kg"),
|
|
(b'N\r\nA50,0,0,4,1,1,N,"Weight"\r\nA50,40,0,4,1,1,N,"2.500"\r\nA200,40,0,4,1,1,N,"kg"\r\nP1\r\n',
|
|
2.5, "kg"),
|
|
(b'CLS\r\nTEXT 10,10,"3",0,1,1,"GROSS 15.6 kg"\r\nPRINT 1,1\r\n', 15.6, "kg"),
|
|
])
|
|
def test_other_label_languages(label, value, unit):
|
|
r = PROTOCOLS["label_print"].readings(label)[-1]
|
|
assert (r.value, r.unit) == (value, unit)
|
|
|
|
|
|
def test_label_without_weight_is_ignored():
|
|
assert PROTOCOLS["label_print"].readings(b'CLS\r\nTEXT 1,1,"3",0,1,1,"Hello"\r\nPRINT 1\r\n') == []
|
|
|
|
|
|
@pytest.mark.parametrize("spec,baud,parity,wrong", [
|
|
("label_print", 9600, "N", 0), ("label_print:4800", 4800, "N", 2), ("label_print:9600:7E1", 9600, "E", 0),
|
|
])
|
|
def test_wait_for_print_detection(spec, baud, parity, wrong):
|
|
bank = SimulatorBank(spec)
|
|
events = []
|
|
detector = Detector(bank.opener(), log=lambda code, **kw: events.append(code), timings=FAST,
|
|
print_wait_s=5, listen_only=True)
|
|
result = detector.run_port("COM90")
|
|
assert result["found"] and result["protocol"] == "label_print" and result["mode"] == "print"
|
|
assert result["serial"]["baudrate"] == baud and result["serial"]["parity"] == parity
|
|
assert result["reading"]["fields"]["Order.Co"] == "222"
|
|
assert events.count("print_wrong_speed") == wrong
|
|
assert "ask" not in events # listen only: nothing was sent
|
|
|
|
|
|
def test_listen_only_sends_nothing():
|
|
written = []
|
|
|
|
class Quiet:
|
|
in_waiting = 0
|
|
|
|
def __init__(self, s):
|
|
pass
|
|
|
|
def read(self, n=1):
|
|
time.sleep(0.01)
|
|
return b""
|
|
|
|
def write(self, data):
|
|
written.append(data)
|
|
return len(data)
|
|
|
|
def close(self):
|
|
pass
|
|
|
|
result = Detector(lambda s: Quiet(s), timings=FAST, print_wait_s=0.3, listen_only=True).run_port("COM5")
|
|
assert result["found"] is False and written == []
|
|
|
|
|
|
def test_label_log_feeds_pieces_and_writes_files(tmp_path):
|
|
log = LabelLog(str(tmp_path))
|
|
profile = profiles.validate({"name": "label-1", "port": "COM8", "protocol": "label_print",
|
|
"serial": {"baudrate": 9600}})
|
|
a, b = capture()[3], capture()[4]
|
|
assert log.feed(profile, a[:100]) == []
|
|
got = log.feed(profile, a[100:] + b)
|
|
assert [r["value"] for r in got] == [1.4, 1.4] and [r["seq"] for r in got] == [1, 2]
|
|
assert [r["seq"] for r in log.list("label-1", after=1)] == [2]
|
|
files = list(tmp_path.iterdir())
|
|
assert len(files) == 1 and files[0].name.startswith("label-1-")
|
|
lines = files[0].read_text(encoding="utf-8").splitlines()
|
|
assert json.loads(lines[0])["fields"]["Order.Co"] == "222"
|
|
|
|
|
|
def test_bridge_keeps_print_scale_open_and_logs_labels(tmp_path):
|
|
from scale_bridge.config import Config
|
|
from scale_bridge.server import Bridge
|
|
config = Config(str(tmp_path / "bridge.json"))
|
|
bridge = Bridge(config, simulate="label_print")
|
|
try:
|
|
config.save_profile({"name": "label-1", "port": "COM90", "protocol": "label_print",
|
|
"serial": {"baudrate": 9600}})
|
|
bridge.keeper.tick()
|
|
deadline = time.time() + 12
|
|
while time.time() < deadline and not bridge.labels.list("label-1"):
|
|
time.sleep(0.3)
|
|
listed = bridge.label_list({"profile": "label-1"})
|
|
assert listed["labels"] and listed["listening"] == ["label-1"]
|
|
assert listed["labels"][-1]["fields"]["SHIFT"] == "2"
|
|
started = time.monotonic()
|
|
answer = bridge.weight(config.profile("label-1"), max_age_ms=60000, wait_ms=2500)
|
|
assert time.monotonic() - started < 1.5 # a print scale never holds the caller
|
|
assert answer["ok"] and answer["on_print"] and answer["stable"] and answer["unit"] == "kg"
|
|
assert (tmp_path / "labels").is_dir()
|
|
finally:
|
|
bridge.keeper.stop()
|
|
bridge.registry.stop_all()
|