Initial commit: aplicatie de cantarire si etichetare

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
This commit is contained in:
2026-10-18 23:38:45 +03:00
commit bff6d70b17
107 changed files with 21428 additions and 0 deletions
+170
View File
@@ -0,0 +1,170 @@
{% extends "base.html" %}
{% block title %}Monitor serial{% endblock %}
{% block content %}
<section class="page-head">
<div>
<h1>Monitor serial</h1>
<p class="subtitle">Ascultă portul cântarului și află ce format trimite. Nu se scrie nimic către cântar.</p>
</div>
</section>
<div class="notice">
<strong>Folosire:</strong> conectează cântarul, pornește o cântărire pe el (sau apasă Print) și rulează
monitorul. Dacă nu apar bytes, încearcă <em>Scanează vitezele</em> sau altă setare de linie.
O captură salvată aici este exact ce trebuie ca să adăugăm un protocol nou.
</div>
<section class="card">
<form method="post" class="filters">
<div class="field">
<label for="port">Port</label>
<input id="port" name="port" type="text" list="port-list" required
placeholder="/dev/ttyUSB0 sau COM3" value="{{ values.port }}">
<datalist id="port-list">
{% for p in ports %}
<option value="{{ p.device }}">{{ p.description or p.device }}{% if p.chip %} · {{ p.chip }}{% endif %}</option>
{% endfor %}
</datalist>
</div>
<div class="field">
<label for="baud">Baud</label>
<input id="baud" name="baud" type="number" min="50" max="921600" value="{{ values.baud }}">
</div>
<div class="field">
<label for="framing">Framing</label>
<select id="framing" name="framing">
{% for f in framings %}
<option value="{{ f }}" {{ 'selected' if values.framing == f }}>{{ f }}</option>
{% endfor %}
</select>
</div>
<div class="field">
<label for="seconds">Secunde</label>
<input id="seconds" name="seconds" type="number" min="0.5" max="30" step="0.5" value="{{ values.seconds }}">
</div>
<div class="field">
<label for="name">Nume captură</label>
<input id="name" name="name" type="text" maxlength="60" value="{{ values.name }}">
</div>
<div class="field actions-field">
<button class="btn primary" type="submit">Ascultă</button>
</div>
<div class="monitor-options">
<label class="check"><input type="checkbox" name="scan" value="1" {{ 'checked' if values.scan }}> Scanează vitezele</label>
<label class="check"><input type="checkbox" name="save" value="1" {{ 'checked' if values.save }}> Salvează captura</label>
</div>
</form>
{% if ports %}
<p class="muted small">
Porturi detectate:
{% for p in ports %}<code>{{ p.device }}</code>{% if p.chip %} ({{ p.chip }}){% endif %}{% if not loop.last %}, {% endif %}{% endfor %}
</p>
{% else %}
<p class="muted small">Nu a fost detectat niciun port. Verifică adaptorul USB-RJ11 (driver, cablu).</p>
{% endif %}
</section>
{% if error %}
<div class="notice warning">{{ error }}</div>
{% endif %}
{% if saved_path %}
<div class="notice"><strong>Cauptră salvată:</strong> <code>{{ saved_path }}</code></div>
{% endif %}
{% if scan_hits %}
<section class="card">
<h2>Viteze cu date</h2>
<div class="table-wrap">
<table class="table">
<thead><tr><th>Baud</th><th>Framing</th><th class="num">Bytes</th><th>Protocoale recunoscute</th></tr></thead>
<tbody>
{% for hit in scan_hits %}
<tr>
<td>{{ hit.baud }}</td>
<td>{{ hit.framing }}</td>
<td class="num">{{ hit.bytes }}</td>
<td>
{% if hit.guesses %}
{% for g in hit.guesses %}<span class="badge ok">{{ g.protocol }}</span> {% endfor %}
{% else %}<span class="muted">necunoscut</span>{% endif %}
</td>
</tr>
{% endfor %}
</tbody>
</table>
</div>
</section>
{% endif %}
{% if result %}
<section class="card">
<div class="card-head">
<h2>Rezultat: {{ result.port }} · {{ result.baud }} {{ result.framing }}</h2>
<span class="badge {{ 'ok' if result.ok else 'err' }}">{{ result.bytes }} bytes</span>
</div>
{% if result.guesses %}
<h3>Protocoale recunoscute</h3>
<div class="table-wrap">
<table class="table compact">
<thead><tr><th>ID</th><th>Nume</th><th>Tip</th><th class="num">Valoare</th><th class="num">Cadre</th></tr></thead>
<tbody>
{% for g in result.guesses %}
<tr>
<td><code>{{ g.protocol }}</code></td>
<td>{{ g.name }}</td>
<td>{{ g.kind }}</td>
<td class="num">{{ g.value }} {{ g.unit }}</td>
<td class="num">{{ g.count }}</td>
</tr>
{% endfor %}
</tbody>
</table>
</div>
{% else %}
<p class="muted">Niciun protocol cunoscut nu recunoaște datele. Salvează captura și o folosim ca să scriem parserul.</p>
{% endif %}
<h3>Cadre brute ({{ result.frames|length }})</h3>
{% if result.frames %}
<div class="table-wrap">
<table class="table compact">
<thead><tr><th class="num">Acum</th><th class="num">Bytes</th><th>Hex</th><th>Text</th></tr></thead>
<tbody>
{% for f in result.frames %}
<tr>
<td class="num">{{ f.age_ms }} ms</td>
<td class="num">{{ f.size }}</td>
<td><code class="hex">{{ f.hex }}</code></td>
<td><code>{{ f.text }}</code></td>
</tr>
{% endfor %}
</tbody>
</table>
</div>
{% else %}
<p class="muted">Niciun byte primit. Cântarul poate fi setat să nu trimită nimic pe această interfață.</p>
{% endif %}
</section>
{% endif %}
<section class="card help-card">
<h2>Setări de încercat</h2>
<ul class="var-list">
<li><code>9600 8N1</code> — cel mai comun pentru cântare de magazin.</li>
<li><code>9600 7E1</code> / <code>4800 7E1</code> — frecvent la indicatoarele vechi.</li>
<li><code>9600 7O1</code> — dacă textul apare cu bitul 8 schimbat.</li>
<li>Unele cântare trimit doar la apăsarea <em>Print</em>: ține monitorul pornit și apasă.</li>
</ul>
<h2>Cablaj modular (RJ11 / 10P4C)</h2>
<p class="muted small">
Un adaptor USB–RJ11 funcționează doar dacă cei patru ași ai prizei cântarului sunt
<strong>TX, RX, GND</strong> la nivel RS-232 și adaptorul are aceeași ordine. Verifică pinoutul în manualul
BC800 înainte de a conecta; unele prize modulare de cântar sunt TTL sau current-loop și nu se leagă
direct la un adaptor RS-232.
</p>
</section>
{% endblock %}