Euro Truck Simulator2(ETS2)用の後付けウインカーレバーをAtom liteで作ってみた

たまにEuro Truck Simulator2(ETS2)というトラックを運転するゲームをやっているのですが、

https://store.steampowered.com/app/227300/Euro_Truck_Simulator_2/?l=japanese

使ってるハンコン(Driving Force GT)にウインカーレバーが無くて(普通無い)ボタンを押してウインカーを点灯させていたので不便で仕方なかったんです。

というわけで作りました。

https://twitter.com/coppercele/status/1841899619892265103

## 使用するデバイス

### Atom lite
![image.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/298776/1164f098-8670-b3d5-2ae7-fc20ca730543.png)

https://www.switch-science.com/products/6262?_pos=1&_sid=6a9e69614&_ss=r

M5Stackシリーズで一番小さいやつ

GPIOが生えてるマイコンなら何でもいいんですけどAtom liteは小さくてケースに入ってるから使いやすいんですよね。
ボタンとLEDもついてるから必要十分だし安いから単機能で使いつぶしても惜しくないのがいいところです。

### レバースイッチ

![image.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/298776/851b755f-fa22-118c-9fd0-cf300fe247c3.png)

Amazonでレバースイッチを検索したら出てきたんですけどギター用でした。

![image.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/298776/3ecbf33b-ecbd-5b58-e2ae-0c9af6869155.png)

作った後気づいたんですけどこういうトグルスイッチでも良かったですね。
ただ動かしたらセンターに戻らないのでセンターに戻る3極のスイッチあったら教えてください。

### ロータリースイッチ

![image.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/298776/79d5e984-7667-d87d-13b2-dbf2e104c6f0.png)

これもAmazonで適当に探したんですが大きかったです🤣

![image.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/298776/a89af7cc-2d37-9549-743f-396cdd074982.png)

こういうロータリーディップスイッチを使った方が良かったと思います。

### 3Dプリンター

3万円くらいの安物ですがあるとすぐにモノが作れて便利ですね。

ハンコンに机に固定するために回すツマミがあったのでそれにマウントするようにします。

![image.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/298776/887c9eb1-09a2-a474-2bd5-c5aad6ca610c.png)

Atom liteやレバースイッチにノギスを当てながらAutodesk Fusionでゴリゴリとモデリングします。

![image.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/298776/2915837e-c2c0-2b8b-eb62-0cfd68eb776d.png)
![image.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/298776/5158d125-2bba-e40c-c6ed-03c3110db26e.png)

![image.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/298776/ea55e796-dfa3-0fc6-8236-df86351afc81.png)

1時間くらいでこれくらいプリントできるのは凄いことですね。

無いと思いますけど欲しい人がいたらSTLデータをアップします。

## 配線する

ギタースイッチにケーブルをハンダ付けしてAtom liteのGPIOに接続します。

![image.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/298776/aac8a3ed-fcc2-8add-6b9a-ace9348cf5a6.png)

真ん中の端子は使わなくなったので2本+GNDだけで良いです。

ロータリースイッチはポジションが12個あるんですが、
どちらに回転したかが分かればよいので2個おきにポリウレタン銅線でショートするようにハンダ付けしました。
GPIOも3個だけでよくなります。

![image.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/298776/851241d3-6765-59f9-d250-54ffe84040bb.png)

![image.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/298776/9cfef3ae-67d6-4f73-937b-21e86526aef7.png)

ハンダ付けができたらAtom liteのGPIOに接続します。

![image.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/298776/9635a316-2598-1fc1-5a43-6875cdfcf9f3.png)

レバースイッチ1:19
レバースイッチ2:33
ロータリースイッチ1:25
ロータリースイッチ2:22
ロータリースイッチ3:21

## スケッチを書く

スケッチはこんな感じです。
割り込み使った方がいいんじゃね？とか思わないでもないですが、
実は割り込み使ったこと無いです・・・(´・ω・`)

細かい解説は下でやります。

```cpp
#include <Arduino.h>
#include <M5Atom.h>
#include <BleKeyboard.h>

BleKeyboard bleKeyboard("AtomWinkerLever");

void setup() {
  M5.begin(true, false, true); // M5Atomの初期化
  bleKeyboard.begin();
  M5.dis.drawpix(0, 0xf0f000); // 初期状態で黄色を表示
  pinMode(19, INPUT_PULLUP);   // ボタン入力ピン19をプルアップ
  pinMode(23, INPUT_PULLUP);   // ボタン入力ピン23をプルアップ
  pinMode(33, INPUT_PULLUP);   // ボタン入力ピン33をプルアップ
  pinMode(25, INPUT_PULLUP);   // ロータリースイッチ1
  pinMode(22, INPUT_PULLUP);   // ロータリースイッチ2
  pinMode(21, INPUT_PULLUP);   // ロータリースイッチ3
}

#define LEVER_UP 0
#define LEVER_MID 1
#define LEVER_DOWN 2
#define ROTARY_1 0
#define ROTARY_2 1
#define ROTARY_3 2

int past = LEVER_MID;
int pastRotary = ROTARY_1;

// BLE Keybordからキーを送る
// 接続チェックもここでやる
void sendKey(char c) {
  if (bleKeyboard.isConnected()) {
    bleKeyboard.print(c);
  }
}

void loop() {
  M5.update();
  if (M5.Btn.wasReleased()) {
    if (bleKeyboard.isConnected()) {
      bleKeyboard.print("Test:Button Pressed");
    }
  }
  if (digitalRead(19) && digitalRead(33)) {
    // レバー上下両方ともHIGHならレバー中央
    M5.dis.drawpix(0, 0xf00000); // 赤色を表示
    // Serial.println("RED");
    past = LEVER_MID;
  }
  else if (!digitalRead(19)) {
    if (bleKeyboard.isConnected()) {
      // Lever UP
      if (past != LEVER_UP) {
        // 読み取り値(レバー位置)が変化してなければ無視
        M5.dis.drawpix(0, 0x00f000); // 緑色を表示
        // Serial.println("GREEN");
        // US配列で[だがJP配列だと@になる
        sendKey('[');
        // bleKeyboard.print("LEVER_UP");
        // bleKeyboard.write(KEY_RETURN);
        past = LEVER_UP;
      }
    }
  }
  else if (!digitalRead(33)) {
    // Lever DOWN
    if (past != LEVER_DOWN) {
      // 読み取り値(レバー位置)が変化してなければ無視
      M5.dis.drawpix(0, 0x0000f0); // 赤色を表示
      Serial.println("BLUE");
      // US配列で]だがJP配列だと[になる
      sendKey(']');
      // bleKeyboard.print("LEVER_DOWN");
      // bleKeyboard.write(KEY_RETURN);
      past = LEVER_DOWN;
    }
  }
  if (!digitalRead(25)) {
    if (pastRotary != ROTARY_1) {
      if (pastRotary == ROTARY_3) {
        // 時計回り
        // Serial.printf("CW:%d\n", ROTARY_1 + 1);
        sendKey('l');
      }
      else {
        // 反時計回り
        // Serial.printf("CCW:%d\n", ROTARY_1 + 1);
        sendKey('l');
      }
      pastRotary = ROTARY_1;
    }
  }
  else if (!digitalRead(22)) {
    if (pastRotary != ROTARY_2) {
      if (pastRotary == ROTARY_1) {
        // 時計回り
        // Serial.printf("CW:%d\n", ROTARY_2 + 1);
        sendKey('l');
      }
      else {
        // 反時計回り
        // Serial.printf("CCW:%d\n", ROTARY_2 + 1);
        sendKey('l');
      }
      pastRotary = ROTARY_2;
    }
  }
  else if (!digitalRead(21)) {
    if (pastRotary != ROTARY_3) {
      if (pastRotary == ROTARY_2) {
        // 時計回り
        // Serial.printf("CW:%d\n", ROTARY_3 + 1);
        sendKey('l');
      }
      else {
        // 反時計回り
        // Serial.printf("CCW:%d\n", ROTARY_3 + 1);
        sendKey('l');
      }
      pastRotary = ROTARY_3;
    }
  }
  delay(100);
}

```

### スイッチの読み取り

```cpp
  pinMode(19, INPUT_PULLUP);   // ボタン入力ピン19をプルアップ
  pinMode(23, INPUT_PULLUP);   // ボタン入力ピン23をプルアップ
  pinMode(33, INPUT_PULLUP);   // ボタン入力ピン33をプルアップ
  pinMode(25, INPUT_PULLUP);   // ロータリースイッチ1
  pinMode(22, INPUT_PULLUP);   // ロータリースイッチ2
  pinMode(21, INPUT_PULLUP);   // ロータリースイッチ3
```
スイッチを読み取るときはGPIOのINPUT_PULLUPを使うと便利です。
INPUT_PULLUPを指定してGNDとGPIOの間にスイッチを挟んでdigitalRead()すると
OFFはHIGH(true),ONはLOW(false)が返ってきます。

```cpp
  if (digitalRead(19) && digitalRead(33)) {
    // レバー上下両方ともHIGHならレバー中央
    M5.dis.drawpix(0, 0xf00000); // 赤色を表示
    // Serial.println("RED");
    past = LEVER_MID;
  }
  else if (!digitalRead(19)) {
  // レバーが上の時
```

### ゲームへコマンドを送る

スイッチの状態が取れるようになったらゲームへワイパーとライトを動かすコマンドを送ります。

実はAtom liteがBLE KeyboardになっててキーストロークをPCに送ってるだけです。

![image.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/298776/e183d2c2-97d7-f008-860f-f43a4932158e.png)
ウインカーは@と[で左右に動かします。

BLE Keyboardライブラリを使っています。

https://github.com/T-vK/ESP32-BLE-Keyboard

後はスイッチが動いた時に応じて処理を実装していきます

```cpp

#include <BleKeyboard.h>

// PCに登録されるときの名前
BleKeyboard bleKeyboard("AtomWinkerLever");


void setup() {
  M5.begin(true, false, true); // M5Atomの初期化
  bleKeyboard.begin();
}

// BLE Keybordからキーを送る
// 接続チェックもここでやる
void sendKey(char c) {
  if (bleKeyboard.isConnected()) {
    bleKeyboard.print(c);
  }
}

void loop() {
  if (digitalRead(19) && digitalRead(33)) {
    // レバー上下両方ともHIGHならレバー中央
    past = LEVER_MID;
  }
  else if (!digitalRead(19)) {
    // Lever UP(ウインカー左)
    if (past != LEVER_UP) {
      // 読み取り値(レバー位置)が変化してなければ無視
      // US配列で[だがJP配列だと@になる
      sendKey('[');
      past = LEVER_UP;
    }
  }
  else if (!digitalRead(33)) {
    // Lever DOWN(ウインカー右)
    if (past != LEVER_DOWN) {
      // 読み取り値(レバー位置)が変化してなければ無視
      // US配列で]だがJP配列だと[になる
      sendKey(']');
      past = LEVER_DOWN;
    }
  }
  if (!digitalRead(25)) {
    if (pastRotary != ROTARY_1) {
      sendKey('l');
      pastRotary = ROTARY_1;
    }
  }
  else if (!digitalRead(22)) {
    if (pastRotary != ROTARY_2) {
      sendKey('l');
      pastRotary = ROTARY_2;
    }
  }
  else if (!digitalRead(21)) {
    if (pastRotary != ROTARY_3) {
      sendKey('l');
      pastRotary = ROTARY_3;
    }
  }
  delay(100);
}

```

ちなみにETS2にウインカーを逆に動かすコマンドが無かったのでロータリースイッチはGPIO2個で良かったです(´・ω・`)

## パッシングとハイビームは・・・？

ゲーム中あんまりやることはないので作ってないです。
ただ、下部を分割してロータリースイッチを仕込めばできないことはないな～と思ってます。

![image.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/298776/6fa68341-a94c-fc32-7fec-4aff88b51136.png)