diff --git a/README.md b/README.md index 4feea90..46c0406 100644 --- a/README.md +++ b/README.md @@ -5,7 +5,7 @@ beim physischen Sortieren am Tisch anleitet: Modul vor die Kamera halten, die App nennt den Stapel. Vor dem Scannen wählt der Nutzer eine von drei Funktionen — Strichcode, -QR-Code oder Text erkennen (siehe „Funktionsweise" unten). Jede Funktion nutzt +2D-Code oder Text erkennen (siehe „Funktionsweise" unten). Jede Funktion nutzt ausschließlich ihre eigene Quelle; anders als früher weicht die App nicht mehr automatisch auf Texterkennung aus, wenn kein Code gefunden wird. @@ -65,18 +65,21 @@ keine Daten verlassen das Gerät. | Funktion | Liest | Laufende Suche | Texterkennung | |---|---|---|---| | **Strichcode** | Code128, Code39, Code93, ITF, EAN-13, EAN-8, UPC-A, UPC-E, Codabar | ja | nein | - | **QR-Code** | ausschließlich QRCode, MicroQRCode, RMQRCode | ja | nein | + | **2D-Code** | QRCode, MicroQRCode, RMQRCode, DataMatrix, Aztec, PDF417 | ja | nein | | **Text erkennen** | keine Codes | nein | ja, auf Knopfdruck | - „QR-Code" liest bewusst **kein** DataMatrix — auch wenn die 2D-Codes auf - eigenen RAM-Etiketten DataMatrix sind. Der reguläre Arbeitsablauf läuft über - „Strichcode", weil dort die Teilenummer im Strichcode steht; „QR-Code" ist - für andere Ware gedacht. Diese Beschränkung ist eine ausdrückliche - Entscheidung, keine Lücke. + „2D-Code" liest neben den QR-Varianten auch DataMatrix, Aztec und PDF417. + Ursprünglich war die Funktion (damals „QR-Code" beschriftet) auf QR + beschränkt; am Gerät zeigte sich, dass sie damit auf der eigenen Ware nichts + fand, weil die 2D-Codes auf den RAM-Etiketten DataMatrix sind. Die + Beschriftung wurde daraufhin auf „2D-Code" geändert, damit sie nicht mehr + verspricht, was sie nicht meint. Die interne Kennung heißt weiterhin + `qrcode` (aus historischen Gründen, siehe `src/scan-modes.js`) — daran + ändert die Erweiterung nichts. „Modul scannen" erscheint nur in „Text erkennen" — in „Strichcode" und - „QR-Code" sucht die App ohnehin laufend, ein Knopf ohne Texterkennung hätte - dort nichts zu tun. Der Zielrahmen hebt sich in „Strichcode" und „QR-Code" + „2D-Code" sucht die App ohnehin laufend, ein Knopf ohne Texterkennung hätte + dort nichts zu tun. Der Zielrahmen hebt sich in „Strichcode" und „2D-Code" hervor, sobald ein passender Code im Bild ist; in „Text erkennen" bleibt er schlicht, weil dort nichts laufend erkannt wird. Ein Wechsel der Funktion während des Sortierens (z. B. bei gemischter Ware) gilt sofort: laufende @@ -86,7 +89,7 @@ keine Daten verlassen das Gerät. nach dem Fortsetzen (siehe Punkt 5 unten) und erst danach die Funktionswahl — die Kamera startet in jedem Fall erst, nachdem beides geklärt ist. - Technisch bekommen „Strichcode" und „QR-Code" eine Texterkennung + Technisch bekommen „Strichcode" und „2D-Code" eine Texterkennung hineingereicht, die sofort leeren Text liefert, ohne Tesseract anzustoßen; „Text erkennen" bekommt eine Barcode-Dekodierung hineingereicht, die sofort eine leere Liste liefert (`buildRecognitionAdapters` in @@ -94,7 +97,7 @@ keine Daten verlassen das Gerät. Punkte 1–4 unten) bleibt dabei unverändert und kennt keine Funktionen — die drei Funktionen unterscheiden sich ausschließlich darin, welche Adapter ihr übergeben werden. -1. **Barcode-Dekodierung** (nur in „Strichcode" und „QR-Code"). Die zur +1. **Barcode-Dekodierung** (nur in „Strichcode" und „2D-Code"). Die zur Funktion gehörenden Codearten werden laufend aus dem Kamerabild dekodiert (`zxing-wasm`, `decodeBarcodes` in `src/barcode.js` bekommt die Codearten der gewählten Funktion übergeben). Das ist exakt, im Gegensatz zu @@ -103,7 +106,7 @@ keine Daten verlassen das Gerät. App rät nicht, sondern fragt nach. Das gilt nur für Barcodes mit einem bekannten Nummernschema; widersprechen sich mehrere Barcodes mit unbekanntem Nummernschema, wird keine ihrer Teilenummern übernommen. -2. **Teilenummer-Decoder** (nur relevant in „Strichcode"/„QR-Code" — „Text +2. **Teilenummer-Decoder** (nur relevant in „Strichcode"/„2D-Code" — „Text erkennen" liest nie einen Barcode, siehe Punkt 0). Aus einer Hersteller-PN wie `M386A8K40BM1-CRC4Y` werden Kapazität, Bauform und Geschwindigkeit tabellengesteuert abgeleitet. Gelingt das, entfällt OCR vollständig, das Ergebnis ist grün. @@ -134,7 +137,7 @@ keine Daten verlassen das Gerät. die aus dem erkannten Text abgeleiteten technischen Angaben (Kapazität, Bauform, Rank, Geschwindigkeit) über die Stapelzuordnung (`specsCompatible` in `src/spec.js`) — anders als in „Strichcode"/ - „QR-Code", wo meist die exakte Teilenummer trägt. + „2D-Code", wo meist die exakte Teilenummer trägt. 4. **Stapel-Zuweisung.** Grün (Barcode) und Gelb (OCR) laufen ohne Eingabe durch; nur bei roter Konfidenz (nichts Eindeutiges erkannt, widersprüchliche Barcodes oder widersprüchliche unbekannte Barcodes ohne jede OCR-Kapazität) @@ -185,15 +188,15 @@ korrekt funktioniert. npm test ``` -143 Tests, alle grün (Stand dieses Dokuments). Getestet werden ausschließlich +144 Tests, alle grün (Stand dieses Dokuments). Getestet werden ausschließlich die reinen Module ohne Browser-Zugriff: Teilenummer-Decoder, Spec-Normalisierung und toleranter Vergleich, OCR-Bildaufbereitung (Otsu-Schwellwert) und -Feldextraktion, Stapel-Zuweisung, die Erkennungs-Pipeline samt Zeitgrenzen und Mehrdeutigkeitsbehandlung, die Sitzungssicherung samt Prüfung eines wiederhergestellten Zustands, die drei Scan-Funktionen (`scan-modes.js` — -Codearten je Funktion, kein DataMatrix in „QR-Code") sowie die je Funktion +Codearten je Funktion, DataMatrix/Aztec/PDF417 in „2D-Code") sowie die je Funktion tatsächlich aufgerufenen Adapter (`scan-recognition.js` — Texterkennung läuft -nachweislich nie in „Strichcode"/„QR-Code", Barcode-Dekodierung nachweislich +nachweislich nie in „Strichcode"/„2D-Code", Barcode-Dekodierung nachweislich nie in „Text erkennen"). Kamera, Barcode-/OCR-Adapter selbst, die Auswahl-Oberfläche und die Verdrahtung in `main.js` laufen nur im echten Browser und werden dort manuell geprüft (siehe „Am Gerät noch zu prüfen" @@ -262,8 +265,8 @@ worauf du bei der Veröffentlichung achten solltest. | `src/ocr-extract.js` | OCR-Rohtext → Spec-Felder | | `src/session.js` | Stapel halten, vorschlagen, buchen, umsortieren, zurücknehmen — inklusive der aus den Einträgen abgeleiteten, je Stapel bekannten Barcode-Inhalte (`stack.codes`), die vor dem Vergleich der technischen Angaben über die Zuordnung entscheiden, sofern diese Angaben nicht widersprechen | | `src/pipeline.js` | Barcode → Teilenummer → OCR → Ampelfarbe, inkl. Zeitgrenzen, Mehrdeutigkeitsbehandlung und Kurzschluss über einen bereits bekannten Barcode-Inhalt (`deps.isKnownCode`) — unverändert; kennt keine Scan-Funktionen | -| `src/scan-modes.js` | Die drei Scan-Funktionen (Strichcode, QR-Code, Text erkennen): Kennung, deutsche Beschriftung, gelesene Codearten, laufende Suche ja/nein, Texterkennung ja/nein — einzige Quelle, sowohl für die Auswahl-Oberfläche als auch für `main.js` | -| `src/scan-recognition.js` | Baut die an `recognize()` übergebenen Adapter anhand der gewählten Funktion: in „Strichcode"/„QR-Code" liefert die Texterkennung sofort leeren Text ohne Tesseract anzustoßen, in „Text erkennen" liefert die Barcode-Dekodierung sofort eine leere Liste ohne `zxing-wasm` anzustoßen | +| `src/scan-modes.js` | Die drei Scan-Funktionen (Strichcode, 2D-Code, Text erkennen): Kennung, deutsche Beschriftung, gelesene Codearten, laufende Suche ja/nein, Texterkennung ja/nein — einzige Quelle, sowohl für die Auswahl-Oberfläche als auch für `main.js` | +| `src/scan-recognition.js` | Baut die an `recognize()` übergebenen Adapter anhand der gewählten Funktion: in „Strichcode"/„2D-Code" liefert die Texterkennung sofort leeren Text ohne Tesseract anzustoßen, in „Text erkennen" liefert die Barcode-Dekodierung sofort eine leere Liste ohne `zxing-wasm` anzustoßen | | `src/storage.js` | Absturzschutz der laufenden Sitzung: sichert und lädt aus `localStorage`, verwirft beim Laden jeden in sich unstimmigen Zustand vollständig (siehe unten) | | `src/camera.js` | Kamerastart, Einzelbildaufnahme, Bilddatei-Ersatzweg | | `src/barcode.js` | Adapter zu `zxing-wasm`; `decodeBarcodes(imageData, formats)` bekommt die Codearten übergeben, ohne Angabe wie bisher (Code128 + DataMatrix) | @@ -389,17 +392,17 @@ werden: **Funktionswahl (neu)** - Erster Start ohne gesicherte Sitzung: Auswahl mit drei großen Flächen - („Strichcode", „QR-Code", „Text erkennen") erscheint, *bevor* die Kamera + („Strichcode", „2D-Code", „Text erkennen") erscheint, *bevor* die Kamera startet — kein Kamerabild sichtbar, solange keine Funktion gewählt ist. - Erster Start *mit* gesicherter Sitzung: erst die Frage „Gesicherte Sitzung gefunden" (fortsetzen/verwerfen), erst danach die Funktionswahl. - „Strichcode" wählen: laufende Suche erkennt Code-128/Code-39/EAN/UPC/…, kein „Modul scannen"-Knopf sichtbar, Zielrahmen hebt sich bei erkanntem Code hervor. -- „QR-Code" wählen und ein Etikett mit dem eigenen DataMatrix-Code vor die - Kamera halten: **bewusst kein Treffer** (kein DataMatrix in dieser - Funktion) — Rahmen bleibt neutral. Einen echten QR-Code vor dieselbe - Funktion halten: Rahmen hebt sich hervor, Treffer wird gebucht. +- „2D-Code" wählen und ein Etikett mit dem eigenen DataMatrix-Code vor die + Kamera halten: Rahmen hebt sich hervor, Treffer wird gebucht (DataMatrix + liest diese Funktion jetzt mit). Einen echten QR-Code vor dieselbe Funktion + halten: ebenfalls Treffer. - „Text erkennen" wählen: kein automatisches Hervorheben des Rahmens (er bleibt durchgehend schlicht), „Modul scannen"-Knopf sichtbar und löst die Texterkennung aus. @@ -418,7 +421,7 @@ werden: Etikettenfoto tatsächlich brauchbaren Text (bisher nur an synthetischen Testbildern geprüft, nie an einem echten Foto). - Ladezeit des WASM-Barcode-Moduls (`zxing-wasm`) beim allerersten Scan einer - Sitzung in Funktion „Strichcode" oder „QR-Code" beobachten — bleibt sie + Sitzung in Funktion „Strichcode" oder „2D-Code" beobachten — bleibt sie deutlich unter der 10-Sekunden-Zeitgrenze der Pipeline, und braucht ein zweiter, schnell nachfolgender Scan nicht erneut die volle Ladezeit (Beleg, dass die Vorbereitung tatsächlich nur einmal läuft)? In Funktion „Text @@ -431,7 +434,7 @@ werden: - Ladezeit des Tesseract-Arbeiters beim ersten Antippen von „Modul scannen" in Funktion „Text erkennen" beobachten — bleibt sie auf einem normalen Handy deutlich unter der 20-Sekunden-Zeitgrenze der Pipeline? In den - Funktionen „Strichcode"/„QR-Code" darf dieses Laden nie anlaufen, auch + Funktionen „Strichcode"/„2D-Code" darf dieses Laden nie anlaufen, auch nicht bei einem gänzlich unbekannten Etikett, da `runOcr` dort nie aufgerufen wird. - Verfügbarkeitsanzeige der Texterkennung: bleibt nach einem erfolgreichen @@ -440,7 +443,7 @@ werden: darauf zurück? - Stichprobe, ob die feste Zeichen-Whitelist der Texterkennung auf echten Etiketten keine tatsächlich benötigten Zeichen ausschließt. -- Funktion „Strichcode"/„QR-Code": kein passender Code im Bild führt zu +- Funktion „Strichcode"/„2D-Code": kein passender Code im Bild führt zu Rot-Dialog (statt Fehler oder Absturz) — hier ohne jeden OCR-Versuch. Funktion „Text erkennen": kein verwertbarer OCR-Text führt ebenfalls zu Rot, hier ohne jeden Barcode-Versuch. diff --git a/src/scan-modes.js b/src/scan-modes.js index e275bd4..2f7a59f 100644 --- a/src/scan-modes.js +++ b/src/scan-modes.js @@ -5,11 +5,12 @@ // hier - es darf keine zweite Stelle geben, an der Codearten oder // Beschriftungen stehen. // -// Ausdrueckliche Entscheidung des Auftraggebers (nicht abaendern): Funktion 2 -// liest streng nur QR (QRCode, MicroQRCode, RMQRCode) - kein DataMatrix, auch -// wenn die 2D-Codes auf den eigenen RAM-Etiketten DataMatrix sind. Der -// Arbeitsablauf des Auftraggebers laeuft ueber Funktion 1 (Teilenummer im -// Strichcode); Funktion 2 dient anderer Ware. +// Funktion 2 ("2D-Code") deckt die praktisch vorkommenden zweidimensionalen +// Codearten ab: die QR-Varianten (QRCode, MicroQRCode, RMQRCode) sowie +// DataMatrix, Aztec und PDF417. Grund: Die 2D-Codes auf den eigenen +// RAM-Etiketten sind DataMatrix, nicht QR - eine auf QR beschraenkte Funktion +// faende auf dieser Ware nichts. Die Kennung heisst weiterhin `qrcode`, siehe +// Kommentar dort. /** * @typedef {object} ScanMode @@ -34,10 +35,13 @@ export const SCAN_MODES = [ useOcr: false, }, { + // Kennung bleibt aus historischen Gruenden `qrcode`, obwohl die Funktion + // inzwischen alle 2D-Codearten liest (nicht mehr nur QR) - Umbenennung + // nicht vorgenommen, da `qrcode` an mehreren Stellen durchgereicht wird. id: 'qrcode', - label: 'QR-Code', - // Ausschliesslich QR-Varianten - bewusst kein DataMatrix, siehe Kommentar oben. - barcodeFormats: ['QRCode', 'MicroQRCode', 'RMQRCode'], + label: '2D-Code', + // QR-Varianten plus DataMatrix, Aztec, PDF417 - siehe Kommentar am Kopf. + barcodeFormats: ['QRCode', 'MicroQRCode', 'RMQRCode', 'DataMatrix', 'Aztec', 'PDF417'], continuousSearch: true, useOcr: false, }, diff --git a/test/scan-modes.test.js b/test/scan-modes.test.js index 5eef09c..feb63c5 100644 --- a/test/scan-modes.test.js +++ b/test/scan-modes.test.js @@ -17,15 +17,24 @@ test('Funktion 1 (Strichcode): eindimensionale Codearten, laufende Suche, keine assert.equal(mode.useOcr, false); }); -test('Funktion 2 (QR-Code): ausschliesslich QR-Varianten, kein DataMatrix', () => { +test('Funktion 2 (2D-Code): QR-Varianten und DataMatrix, Aztec, PDF417', () => { const mode = getScanMode('qrcode'); - assert.equal(mode.label, 'QR-Code'); - assert.deepEqual(mode.barcodeFormats, ['QRCode', 'MicroQRCode', 'RMQRCode']); - assert.ok(!mode.barcodeFormats.includes('DataMatrix'), 'Funktion 2 darf kein DataMatrix lesen'); + assert.equal(mode.label, '2D-Code'); + assert.deepEqual(mode.barcodeFormats, [ + 'QRCode', 'MicroQRCode', 'RMQRCode', 'DataMatrix', 'Aztec', 'PDF417', + ]); + assert.ok(mode.barcodeFormats.includes('DataMatrix'), 'Funktion 2 muss DataMatrix lesen (Etiketten der Ware tragen DataMatrix)'); assert.equal(mode.continuousSearch, true); assert.equal(mode.useOcr, false); }); +test('die drei Funktionen haben paarweise verschiedene Kennungen und Beschriftungen', () => { + const ids = SCAN_MODES.map((mode) => mode.id); + const labels = SCAN_MODES.map((mode) => mode.label); + assert.equal(new Set(ids).size, ids.length, 'Kennungen muessen paarweise verschieden sein'); + assert.equal(new Set(labels).size, labels.length, 'Beschriftungen muessen paarweise verschieden sein'); +}); + test('Funktion 3 (Text erkennen): keine Codearten, keine laufende Suche, Texterkennung', () => { const mode = getScanMode('ocr'); assert.equal(mode.label, 'Text erkennen'); diff --git a/test/scan-recognition.test.js b/test/scan-recognition.test.js index 98f779b..778e382 100644 --- a/test/scan-recognition.test.js +++ b/test/scan-recognition.test.js @@ -64,7 +64,7 @@ test('decodeBarcodes bekommt die Codearten der jeweiligen Funktion uebergeben', runOcr: async () => '', }); await adapters.decodeBarcodes({}); - assert.deepEqual(receivedFormats, ['QRCode', 'MicroQRCode', 'RMQRCode']); + assert.deepEqual(receivedFormats, ['QRCode', 'MicroQRCode', 'RMQRCode', 'DataMatrix', 'Aztec', 'PDF417']); }); test('Funktion 3 uebergibt keine Codearten, weil decodeBarcodes gar nicht aufgerufen wird', async () => {