Scannen von QR- & Barcode - Groß-/Kleinbuchstaben

Hallihallo! :slight_smile:

Ich versuche (fast komplett erfolgreich) selbst erstellte QR- und Barcodes zu scannen.
Sehr hilfreich war mir dabei folgende Diskussion, bei der folgender Sketch erarbeitet wurde:

#include <usbhid.h>
#include <usbhub.h>
#include <hiduniversal.h>
#include <hidboot.h>
#include <SPI.h>

class MyParser : public HIDReportParser {
  public:
    MyParser();
    void Parse(USBHID *hid, bool is_rpt_id, uint8_t len, uint8_t *buf);
  protected:
    uint8_t KeyToAscii(bool upper, uint8_t mod, uint8_t key);
    virtual void OnKeyScanned(bool upper, uint8_t mod, uint8_t key);
    virtual void OnScanFinished();
};

MyParser::MyParser() {}

void MyParser::Parse(USBHID *hid, bool is_rpt_id, uint8_t len, uint8_t *buf) {
  // If error or empty, return
  if (buf[2] == 1 || buf[2] == 0) return;

  for (uint8_t i = 7; i >= 2; i--) {
    if (buf[i] == 0) continue;                  // If empty, skip
    if (buf[i] == UHS_HID_BOOT_KEY_ENTER) {     // If enter signal emitted, scan finished
      OnScanFinished();
    }
    else {                                      // If not, continue normally
      OnKeyScanned(i > 2, buf, buf[i]);         // If bit position not in 2, it's uppercase words
    }
    return;
  }
}

uint8_t MyParser::KeyToAscii(bool upper, uint8_t mod, uint8_t key) {
  // Letters
  if (VALUE_WITHIN(key, 0x04, 0x1d)) {
    if (upper) return (key - 4 + 'A');
    else return (key - 4 + 'a');
  }
  // Numbers
  else if (VALUE_WITHIN(key, 0x1e, 0x27)) {
    return ((key == UHS_HID_BOOT_KEY_ZERO) ? '0' : key - 0x1e + '1');
  }
  return 0;
}

void MyParser::OnKeyScanned(bool upper, uint8_t mod, uint8_t key) {
  uint8_t ascii = KeyToAscii(upper, mod, key);
  Serial.print((char)ascii);
  delay(1);
}

void MyParser::OnScanFinished() {
  //Serial.println(" - Finished");
  Serial.println(F("\r"));
 
}

USB          Usb;
USBHub       Hub(&Usb);
HIDUniversal Hid(&Usb);
MyParser     Parser;

void setup() {
  Serial.begin( 9600 );
  Serial.println("Start");

  if (Usb.Init() == -1) {
    Serial.println("OSC did not start.");
  }
  delay( 200 );
  Hid.SetReportParser(0, &Parser);
}

void loop() {
  Usb.Task();
}

Dieser Sketch wird häufig empfohlen, wenn es um Barcode-Scannen mit dem Arduino geht und funktioniert auch im Großen und Ganzen ganz gut.
Lediglich ein Problem stellt sich mir dabei noch und kann in der Diskussion keine Lösung dazu finden: Der Parser unterscheidet nicht zwischen Groß- und Kleinbuchstaben... alle Buchstaben werden klein ausgegeben, obwohl es in der Funktion extra ein Unterscheidungsmerkmal gibt.

Hat der Autor etwas übersehen oder ihr bereits selbst schon einmal vor diesem Problem gestanden?

Das macht der Sketch explizit so. Wenn Du das ändern möchtest, musst Du in der Methode KeyToAscii() den mod-Wert verwenden um den Zustand der sog. Modifiers (Umschalt-, Alt-, Control-Tasten) auszuwerten. Beim geposteten Code wird das nur über upper gesteuert, welches jeweils nur den letzten Buchstaben gross macht (wieso das so ist, ist mir nicht klar).

hier:

OnKeyScanned(i > 2, buf, buf[i]);         // If bit position not in 2, it's uppercase words

kommt er nicht nach 2.
Ich würde die Zeile ändern in:

OnKeyScanned(i >= 2, buf, buf[i]);         // If bit position not in 2, it's uppercase words

Nur Bauch.
Ich habe kein HID und auch sonst keine Idee, aber IMHO wäre es einen Versuch wert.

Hmm.. dass er diese 'upper'-Bedingung nicht erfüllt, scheint wohl zu sein...

die von my_xy_project vorschlagene Änderung getestet: nun sind alle Buchstaben Großbuchstaben. Hmpf... die Bedingung scheint sich wohl woanders zu verstecken. Trotzdem Danke!

@pylon: Leider weiß ich nicht, wie ich den 'mod'-wert auswerten kann, um diesen als umschalt-Kriterium zu verwenden. Hab ihn mir mal spaßeshalber hinter jedem gescannten ascii-Zeichen ausgedruckt ("AbCdEfG" als Test) .. dieser Wert bleibt immer bei 168.

Also wieder einen Schritt zurück:
in o.g. Diskussion hat der Autor ein Testprogramm, um sich die Ausgabe verschiedener BarCodes anzeigen zu lassen. Hier weichen meine Ergebnisse bereits von seinen ab:

 (buf[2] == 1) return;

  // If empty, return
  // I check on 2 because the previous if check on 2 too
  if (buf[2] == 0) return;

  // Like above, WHY it starts on 2 ?
  // What is the purpose of bit in 0 and 1 ?
  for (uint8_t i = 2; i < 8; i++) {
    Serial.print(buf[i]);
    Serial.print(" ");
  }

  Serial.println();
}

USB          Usb;
USBHub       Hub(&Usb);
HIDUniversal Hid(&Usb);
MyParser     Parser;

void setup() {
  Serial.begin( 9600 );
  Serial.println("Start");

  if (Usb.Init() == -1)
    Serial.println("OSC did not start.");

  delay( 200 );

  Hid.SetReportParser(0, &Parser);
}

void loop() {
  Usb.Task();
}

Er erhält als Ergebnis bei gescannten Großbuchstaben 7 Stellen mit einer führenden 255 ... die fehlt bei mir. Das gescannte Ergebnis sieht bei mir für Groß- wie Kleinbuchstaben identisch aus und besteht für jedes Zeichen immer nur aus 6 Ziffern.

Die Methode scheint für meinen Scanner also nicht komplett geeignet zu sein :thinking:

Ich habe mal das Ergebnis von USBdesc für meinen Scanner (Sunmi Blink) hier:

Start


01
--

Device descriptor: 
Descriptor Length:	12
Descriptor type:	01
USB version:		0110
Device class:		00
Device Subclass:	00
Device Protocol:	00
Max.packet size:	40
Vendor  ID:		324F
Product ID:		0031
Revision ID:		0100
Mfg.string index:	01
Prod.string index:	02
Serial number index:	03
Number of conf.:	01

Configuration descriptor:
Total length:		0022
Num.intf:		01
Conf.value:		01
Conf.string:		00
Attr.:			80
Max.pwr:		64

Interface descriptor:
Intf.number:		00
Alt.:			00
Endpoints:		01
Intf. Class:		03
Intf. Subclass:		01
Intf. Protocol:		01
Intf.string:		00
Unknown descriptor:
Length:		09
Type:		21
Contents:	100100012241000705

Endpoint descriptor:
Endpoint address:	81
Attr.:			03
Max.pkt size:		0008
Polling interval:	01


Addr:1(0.0.1)

..kann aber selbst mit diesen Daten nicht viel anfangen.

Ich wüsste auch nicht, welche Beispiele aus der USB-HostShield_20-library sonst noch zum Ergebnis führen könnten...

Auch noch das Ergebnis von USBHID_desc:

tart
0000: 05 01 09 06 A1 01 05 07 19 E0 29 E7 15 00 25 01 
0010: 75 01 95 08 81 02 95 01 75 08 81 01 95 05 75 01 
0020: 05 08 19 01 29 05 91 02 95 01 75 03 91 01 95 06 
0030: 75 08 15 00 26 FF 00 05 07 19 00 2A FF 00 81 00 
0040: C0 
Usage Page Gen Desktop Ctrls(01)
Usage Keypad
Collection Application
Usage Page Kbrd/Keypad(07)
Usage Min(E0)
Usage Max(E7)
Logical Min(00)
Logical Max(01)
Report Size(01)
Report Count(08)
Input(00000010)
Report Count(01)
Report Size(08)
Input(00000001)
Report Count(05)
Report Size(01)
Usage Page LEDs(08)
Usage Min(01)
Usage Max(05)
Output(00000010)
Report Count(01)
Report Size(03)
Output(00000001)
Report Count(06)
Report Size(08)
Logical Min(00)
Logical Max(FF00)
Usage Page Kbrd/Keypad(07)
Usage Min(00)
Usage Max(FF00)
Input(00000000)
End Collection

Moin,

kurze Frage: kannst mal den Type von dem Scanner nennen? Sunmi ist nicht so auskunftsfreudig, das ich anhad der pid erkennen kann, welcher das ist.

Zweite Frage:
Wenn den normal an nen Rechner hängst und nen notepad aufmachst, funktionierts richtig mit Groß-und Kleinschreibung?

Ich meine mal über ne Fehlkonfig gestolpert zu sein - da hat der Scanner das schon intern umgemodelt....

Danke @my_xy_projekt !

Mehr Info als SUNMI BLINK NS010 konnte ich auch nicht finden. Als Datenblatt wohl nicht so richtig geeignet, deshalb habe ich mal die beiden desc-infos gepostet.

zu Frage 2: yep.. Groß-/Kleinschreibung wird in Textdokumenten richtig angezeigt.

Edit: ich habe noch einen 2. Handscanner (ALBASCA MK-6200 - wird nicht mehr produziert, daher auch keine Infos auffindbar), der sich genauso verhält: im Notepad wird alles richtig dargestellt, bis auf.. die Sache.

Eventuell ist der vorgeschlagene Code ja nicht ganz ausgereift und ja auch schon etwas älter .. Beitrag von 2017. Gibt es denn eventuell andere Vorschläge, einen CodeScanner über das Arduino Host Shield auszulesen? Wie gesagt.. bei den vielen Beispielen der USB_HOST_SHIELD_2.0-Library habe ich (noch) nichts gefunden, mit dem ich eine Ausgabe am seriellen Monitor bekomme.

Der ist ja echt genial :slight_smile:
Das Original gibts dann hier - ist aber auch nicht mehr:

Ok, aber der Hersteller ist noch genialer..
Ich zitiere die komplette Dokumentation:

Ist ne Aussage...
Daraus geht wohl hervor, das das Ding nicht zu konfigurieren ist. Da macht dann alles der Treiber.

Damit bekommst Du die Daten auch roh.
Da muss ich jetzt aber passen - mein yún ist leider verschollen; naja nicht zurück gekommen trifft es eher - und leider kann ich so aus der hohlen Hand da nicht mehr weiter helfen.
Aber: Es muss einen Weg geben, da bin ich mir sehr sicher.

OK.. alles wieder etwas komplizierter als zuerst gehofft :pleading_face:

Aber apropos Rohdaten, da sagst du was...

Ich habe den Sketch zur Ausgabe ALLER empfangenen Daten etwas verändert:

#include <usbhid.h>
#include <usbhub.h>
#include <hiduniversal.h>
#include <SPI.h>

class MyParser : public HIDReportParser {
  public:
    MyParser();
    void Parse(USBHID *hid, bool is_rpt_id, uint8_t len, uint8_t *buf);
};

MyParser::MyParser() {}

void MyParser::Parse(USBHID *hid, bool is_rpt_id, uint8_t len, uint8_t *buf) {
  // If error, return
  // I don't know why it starts on 2, I just following the example
  if (buf[2] == 1) return;
  // If empty, return
  // I check on 2 because the previous if check on 2 too
  if (buf[2] == 0) return;
  // Like above, WHY it starts on 2 ?
  // What is the purpose of bit in 0 and 1 ?
  for (uint8_t i = 0; i < 8; i++) {           // <= STARTE BEI 0, NICHT BEI 2
    Serial.print(buf[i]);
    Serial.print(" ");
  }
  Serial.println();
}

USB          Usb;
USBHub       Hub(&Usb);
HIDUniversal Hid(&Usb);
MyParser     Parser;

void setup() {
  Serial.begin( 9600 );
  Serial.println("Start");
  if (Usb.Init() == -1)
    Serial.println("OSC did not start.");
  delay( 200 );
  Hid.SetReportParser(0, &Parser);
}

void loop() {
  Usb.Task();
}

... und erkenne ein Schema bei der Ausgabe:

0 170 83 0 0 0 0 0  => ?
0 170 83 0 0 0 0 0  => ?
2 170 4 0 0 0 0 0  => A
2 170 5 0 0 0 0 0  => B
2 170 6 0 0 0 0 0  => C
2 170 7 0 0 0 0 0  => D
2 170 8 0 0 0 0 0  => E
2 170 9 0 0 0 0 0  => F
2 170 10 0 0 0 0 0 => G
2 170 11 0 0 0 0 0 => H
0 170 81 0 0 0 0 0  => Eingabe Ende?
0 170 40 0 0 0 0 0  => Eingabe Ende?

0 0 83 0 0 0 0 0  => ?
0 0 83 0 0 0 0 0  => ?
0 0 4 0 0 0 0 0  => a
0 0 5 0 0 0 0 0 => b
0 0 6 0 0 0 0 0  => c
0 0 7 0 0 0 0 0 => d
0 0 8 0 0 0 0 0 => e
0 0 9 0 0 0 0 0 => f
0 0 10 0 0 0 0 0 => g
0 0 11 0 0 0 0 0 => h
0 0 81 0 0 0 0 0 => Eingabe Ende?
0 0 40 0 0 0 0 0 => Eingabe Ende?

0 170 83 0 0 0 0 0 => ?
0 170 83 0 0 0 0 0 => ?
2 170 4 0 0 0 0 0 => A
0 170 5 0 0 0 0 0 => b
2 170 6 0 0 0 0 0  => C
0 170 7 0 0 0 0 0 => d
2 170 8 0 0 0 0 0 => E
0 170 9 0 0 0 0 0 => f
2 170 10 0 0 0 0 0 =>G
0 170 11 0 0 0 0 0 => h
0 170 81 0 0 0 0 0 => Eingabe Ende?
0 170 40 0 0 0 0 0 => Eingabe Ende?

..jetzt ist nur die Frage, an welcher Stelle man das im ersten Sketch umsetzt.

I'll be back! :robot:

Sag ich doch!
Die 2 ist Dein GrossbuchstabenDing.
Bist Du 0 ist es ein Kleinbuchstabe.

Ok - dann mal los...

..und das wars auch schon:

void MyParser::Parse(USBHID *hid, bool is_rpt_id, uint8_t len, uint8_t *buf) {
  // If error or empty, return
  if (buf[2] == 1 || buf[2] == 0) return;

  for (uint8_t i = 7; i >= 2; i--) {
    if (buf[i] == 0) continue;                  // If empty, skip
    if (buf[i] == UHS_HID_BOOT_KEY_ENTER) {     // If enter signal emitted, scan finished
      OnScanFinished();
    }
    else {                                      // If not, continue normally
      bool upper = false;
      if (buf[0] == 2) { upper = true; }        // If 1st byte is 2, it's uppercase words
      OnKeyScanned(upper, buf, buf[i]);         
      //OnKeyScanned(i > 2, buf, buf[i]);         // If bit position not in 2, it's uppercase words
    }
    return;
  }
}

Ich prüfe jetzt einfach das erste byte im buf-array auf 2 und dann muss es ein Großbuchstabe sein. Geht bestimmt eleganter,.. aber funktioniert :wink:
Klappt auch bei beiden verlinkten Scannern.

Nächste Baustelle: Sonderzeichen erkennen. Aber da meld ich mich wieder, wenn ich nicht vorankomme.

..und auch Sonderzeichen (nicht alle habe ich eingebunden.. siehe Sketch) werden nun erkannt:

#include <usbhid.h>
#include <usbhub.h>
#include <hiduniversal.h>
#include <hidboot.h>
#include <SPI.h>

class MyParser : public HIDReportParser {
  public:
    MyParser();
    void Parse(USBHID *hid, bool is_rpt_id, uint8_t len, uint8_t *buf);
  protected:
    uint8_t KeyToAscii(bool upper, uint8_t key);
    virtual void OnKeyScanned(bool upper, uint8_t key);
    virtual void OnScanFinished();
};

MyParser::MyParser() {}

void MyParser::Parse(USBHID *hid, bool is_rpt_id, uint8_t len, uint8_t *buf) {
  if (buf[2] == 1 || buf[2] == 0) return;             // If error or empty, return
  for (uint8_t i = 7; i >= 2; i--) {
    if (buf[i] == 0) continue;                                  // If empty, skip
    if (buf[i] == UHS_HID_BOOT_KEY_ENTER) { OnScanFinished(); } // If enter signal emitted, scan finished
    else {                                                      // If not, continue normally
      bool upper = false;
      if (buf[0] == 2) { upper = true; }          // If first byte is 2, there is a modifier involved
      OnKeyScanned(upper, buf[i]);
    }
    return;
  }
}

uint8_t MyParser::KeyToAscii(bool upper, uint8_t key) {
  // Letters
  if (VALUE_WITHIN(key, 0x04, 0x1d)) {
    if (upper) return (key - 4 + 'A');
    else return (key - 4 + 'a');
  }
  // Numbers
  else if ((upper == false) && (VALUE_WITHIN(key, 0x1e, 0x27))) {
    return ((key == UHS_HID_BOOT_KEY_ZERO) ? '0' : key - 0x1e + '1');
  }
  // Special Letters
  else if (upper == true) {
    switch (key) {
      case 30: return 33; break;  /* ! */
      case 31: return 64; break;  /* @ */
      case 32: return 35; break;  /* # */
      case 33: return 36; break;  /* $ */
      case 34: return 37; break;  /* % */
      case 35: return 94; break;  /* ^ */
      case 36: return 38; break;  /* & */
      case 37: return 42; break;  /* * */
      case 38: return 40; break;  /* ( */
      case 39: return 41; break;  /* ) */
      case 45: return 95; break;  /* _ */
      case 46: return 43; break;  /* + */
      case 51: return 58; break;  /* : */
      case 52: return 34; break;  /* " */
      case 54: return 60; break;  /* < */
      case 55: return 62; break;  /* > */
      case 56: return 63; break;  /* ? */
    } 
  }
  else if (upper == false) {
    switch (key) {
      case 45: return 45; break;  /* - */
      case 46: return 43; break;  /* = */
      case 47: return 91; break;  /* [ */
      case 48: return 93; break;  /* ] */
      case 49: return 92; break;  /* \ */
      case 51: return 59; break;  /* ; */
      case 52: return 39; break;  /* ' */
      case 54: return 44; break;  /* , */
      case 55: return 46; break;  /* . */
      case 56: return 47; break;  /* / */
    }
  }
  return 0;
}

void MyParser::OnKeyScanned(bool upper, uint8_t key) {
  uint8_t ascii = KeyToAscii(upper, key);
  Serial.print((char)ascii);
}

void MyParser::OnScanFinished() {
  Serial.println(" - Finished!");
}

USB          Usb;
USBHub       Hub(&Usb);
HIDUniversal Hid(&Usb);
MyParser     Parser;


void setup() {  
  Serial.begin(9600);
  while (!Serial) { delay(1); }
  Serial.print  ("Initialize USB-HostSHield...");
  delay(50);
  if (Usb.Init() == -1) { 
    Serial.println("OSC did not start!"); 
    while (true) { delay(1); }
  }
  else { Serial.println("done!"); }
  
  Hid.SetReportParser(0, &Parser);
}

  
void loop(){
  Usb.Task();
}
  • leider habe ich nicht wie der Original-Autor bei den Zahlen und Buchstaben ein Schema erkannt, nach dem ich die ASCII-Werte fortlaufend zuordnen konnte, also habe ich mich mit zwei switch-Abfragen beholfen.
  • die mod-Variable habe ich komplett entfernt, der Sinn hat sich mir nicht erschlossen. Hoffe, das war kein Fehler..
  • ich habe nur die Sonderzeichen der ASCII-128-Tabelle berücksichtigt, kann man aber problemlos erweitern wie vorgemacht..

Aufgabe gelöst! :smiley: