M5AtomでQRコードスキャン→キーボードとして入力

# まとめ
~~6000円くらい~~値上がりして1万円くらいで一式が買えるQRコードスキャナ with ESP32の開発キットがあるので、これをBLEキーボードにしてスキャン結果を簡単に読めるようにした。

QRコードスキャナと言っているが、JAN コードなどのバーコードも自動判別して普通に読めて、数字として入力されてくる。
M5Atom ではなく、QRコード認識のハードウェアモジュールが、とてもえらい。

# 買うもの
![ATOM QR-CODE](https://static-cdn.m5stack.com/resource/docs/static/assets/img/product_pics/atom_base/atom_qr/atomic_qr_01.webp)
~~[ATOMIC 2D/1D バーコードスキャナキット - スイッチサイエンス](https://www.switch-science.com/catalog/6476/)（5918円＋送料）を買います。~~
チロルチョコくらいのフットプリントに ESP32 が乗っている M5Atom に、QRスキャナモジュールが付いたキットです。ESP32 といえば、Arduino なのに Bluetooth や WiFi が扱えることで有名なチップです。5GHz非対応が玉に瑕。（ESP32の5GHz対応を熱望するスレは[こちら](https://esp32.com/viewtopic.php?f=12&t=6162&p=71062)）

~~ちなみに、[本体の通販サイト](https://shop.m5stack.com/products/atom-2d-1d-barcode-scanner-kit)からだと$44.5＋送料で買えます。~~
届くのが待ちきれないのでスイッチサイエンス派です。技適付いてる版しかないという安心感も◎。

:::note info
**追記(2024/1/3)**

現在はv1.1にハードウェアが更新されています。現在は以下から購入できます。
- https://www.switch-science.com/products/9179
- https://shop.m5stack.com/products/atom-2d-1d-barcode-scanner-kit-v1-1

この記事は2021年にv1.0のハードで試した結果を書いたものであり、v1.1での確認や、2024年現在の開発環境での動作確認をしたものではありません。
:::

# 参考にしたサイト
基本的には以下のコードを交ぜて整えただけ。

- [M5Stackの公式サンプル](https://github.com/m5stack/M5-ProductExampleCodes/blob/master/AtomBase/AtomicQR/AtomicQR/AtomicQR.ino)
- [M5Atomで作るワイヤレス譜めくりデバイス | elchika](https://elchika.com/article/b7804e6b-203a-4618-8282-ecd975732cbf/)

# 論よりコード
```cpp:main.cpp
// Atom QR-Code Kit でスキャン結果を BLE キーボードで送信
// 以下のコードを参考にしています:
// - https://github.com/m5stack/M5-ProductExampleCodes/blob/master/AtomBase/AtomicQR/AtomicQR/AtomicQR.ino
// - https://elchika.com/article/b7804e6b-203a-4618-8282-ecd975732cbf/
#include <M5Atom.h>
#include <BleKeyboard.h>

#define TRIG 23
#define DLED 33

#define WAIT_AFTER_SEND 50 // キーを送信するたびに 50ms の間隔をあける

BleKeyboard bleKeyboard("QR Code Reader");
bool bleState = false;

bool longPressFlag = false;

void setLed(uint8_t r, uint8_t g, uint8_t b)
{
  int color = (g << 16) | (r << 8) | b;
  M5.dis.drawpix(0, color);
}

void showBleState(bool state)
{
  // 未接続は赤・接続中は青
  if (state)
  {
    setLed(0, 0, 30);
  }
  else
  {
    setLed(30, 0, 0);
  }
}

void send_key(char ch)
{
  if (bleState)
  {
    bleKeyboard.write(ch);
    delay(WAIT_AFTER_SEND);
  }
}

void setup()
{
  M5.begin(true, false, true);
  Serial2.begin(9600, SERIAL_8N1, 22, 19);
  pinMode(TRIG, OUTPUT);
  pinMode(DLED, INPUT);
  digitalWrite(TRIG, HIGH);

  bleKeyboard.begin();
  bleState = false;
  showBleState(bleState);

  longPressFlag = false;
}

void loop()
{
  M5.update();

  if (bleKeyboard.isConnected())
  {
    if (!bleState)
    {
      bleState = true;
      Serial.println("connected.");
      showBleState(bleState);
    }
  }
  else
  {
    if (bleState)
    {
      bleState = false;
      Serial.println("disconnected.");
      showBleState(bleState);
    }
  }

  if (M5.Btn.wasPressed())
  {
    digitalWrite(TRIG, LOW);
  }
  if (M5.Btn.wasReleased())
  {
    digitalWrite(TRIG, HIGH);
    longPressFlag = false;
  }
  if (M5.Btn.pressedFor(5000) && !longPressFlag)
  {
    // 5秒長押しで z -> / を送信（Macのキーボード種別判定用）
    send_key('z');
    longPressFlag = true;
    delay(2000);
    send_key('/');
  }

  if (digitalRead(DLED) == HIGH)
  {
    while (Serial2.available() > 0)
    {
      char ch = Serial2.read();

      if ((' ' <= ch && ch <= '~') || ch == 0x09)
      {
        // ASCII印字可能文字 + TAB
        Serial.print(ch);
        send_key(ch);
      }
      else if (ch == 0x0d)
      {
        // CR
        // デフォルト設定では、読み取り完了時に CR が付加される
        Serial.println();
        send_key(KEY_RETURN);
      }
      else if (ch == 0x0a)
      {
        // LF は無視
      }
      else
      {
        // ASCII印字可能文字以外の文字が来た
        Serial.printf("<%02x>", ch);
      }
    }
  }

  delay(10);
}
```

```plaintext:platformio.ini
[env:m5stack-atom]
platform = espressif32
board = m5stack-atom
framework = arduino
monitor_speed = 115200
lib_deps =
	m5stack/M5Atom@^0.0.1
	fastled/FastLED @ ^3.4.0
	jorgen-vikinggod/FastLED GFX Library @ ^0.1.0
	h2zero/NimBLE-Arduino@^1.2.0
	https://github.com/wakwak-koba/ESP32-NimBLE-Keyboard.git
```

## 使い方
- 電源につなぐと BLE Keyboard として動き始めるので、PC から Bluetooth で接続してください。
    - キーボード種別を選ぶ必要があったら、英語キーボードにしておいてください。
    - Mac につないだら、キーボード種別判定のために左Shiftの右と右Shiftの左のキーを押せと言われて困ったので、Atom のボタンを5秒長押ししたら z と / が送出されるようにしてみました。
    - LED は、Bluetooth 接続が出来ている間は青色に、切れると赤色になります。
        - ときどき変な色で瞬いているのですが、原因分かる方、教えてください。
- Atom のボタンを押すと、（押している間 and 3秒くらいの間）、ライトが付いて QR コードやバーコードの読み取りを行います。
    - 読み取り結果はキーボード入力として受け取れます。
    - 読み取り終わりで改行が1回送られてきます。
        - 都合が悪ければ、ソースコードのCRの処理をコメントアウトしてください。
        - カメラ側の終端記号の設定を変更してもOKです。

## 備考

- m5stack-atom が無いと怒られたら PlatformIO を update してください。
    - VisualStudio Code だったらコマンドパレットから `PlatformIO: Update All`
- 参照先のサイトのオススメに従って NimBLE を使っています。今のところ好調。
- QRスキャナモジュールは、[設定用のQRコード](https://m5stack.oss-cn-shenzhen.aliyuncs.com/resource/docs/datasheet/atombase/AtomicQR/AtomicQR_Reader_EN.pdf)を読み取ることで色々と設定できるのですが、Factory Resetのコードを読み取った状態で問題なく動いているつもりです。
    - ドキュメントに書かれているデフォルト値と、キットに設定されているデフォルト値とが違うのではないかともっぱらの噂。
        - 通信形式が Serial / ビープ音が Low / 終端記号が CR になってる気がします。
- M5Atom は USB Type-C での電源供給が必須です。ポータブルにしたければ、[ATOM TailBAT](https://www.switch-science.com/catalog/6348/)をご検討ください。
    - なお、実際につけてみたところ、高さが合わず、あとから建て増された校舎感がすごい。
- 読み取った文字をノータイムで BLE Keyboard 側に送り込んだところ、取りこぼしが発生したので、1文字毎に50msのウェイトを入れています。
    - 人類も秒間20打鍵くらいはしそう、という予測からの50ms設定。
    - 環境依存かと思いますので、適宜ご調整ください。
- キーボード入力に変換するので、キーボードにある文字以外は入力できません。QRコードに日本語などを直接埋め込んであっても読み取れないのでご注意ください。
    - BLE Keyboard ライブラリは BS もキーコードに変換してくれていましたが、QR コードに BS を埋め込んでいる人はよほどの変態だと思い、対応していません。
    - LF は QR コード内に CR+LF が入っていると面倒だったので一律無視しましたが、用途によるかと思います。
- 270文字のQRコードを読んでみたら、末尾を取りこぼしました。260文字ならいけました。
    - 原因調査はしていません。何か起因でスキャナモジュールの読み取りバッファがリセットされるっぽいんですが、単純な経過時間でもなさそうでした。
- BLE Keyboard のライブラリで設定している、ＨＩＤのvidとpidの根拠がどこにあるのかを把握できていませんので、製品にお使いになりたい場合は、そのあたり、各自でご確認ください。