embotのスイッチの状態をブラウザで検知したい

embotには結線によるオンオフを検知する機能があります。
embot筐体の一番右上の2ピンの結線状態が取得できます。
今回は実行結果2、実行結果4の赤丸の部分のようなスイッチで
結線されている状態と結線されていない状態を切り替えています。

下記のように index.html と index.js を作成し
index.html を Edge に読み込みます。
※不要なコードも混ざっています。
「開始する」をクリックするとブラウザがペア設定ダイアログを出しますので
embot を選択してペアリングします。
線を結ぶとLEDがつきます。

```index.html
<!DOCTYPE html>
<html lang="ja">
  <head>
    <meta charset="UTF-8" />
    <title>embot</title>
  </head>
  <body>
    <button onclick="_start()">開始する</button>
    <div id="switchview"></div>

    <script src="index.js"></script>
  </body>
</html>
```

```index.js
const _uuid = (s) => {
  return `f7fc${s}-7a0b-4b89-a675-a79137223e2c`;
};
class Em { // 非公式
  constructor() {
    this.bpm = 60;
    this._led = { on: 1, off: 2 };
    this._speed1 = 1;
    this._speed2 = 1;
  }
  async _write(char, val) {
    const buf = new Uint8Array(1);
    buf[0] = Number.parseInt(val);
    await char.writeValueWithoutResponse(buf.buffer);
  }
  async _initialize() {
    const blue = window.navigator.bluetooth;
    try {
      const opt = {
        optionalServices: [ _uuid('e510') ],
        filters: [{ namePrefix: 'EMBOT_' }]
      };
      const device = await blue.requestDevice(opt);
      const server = await device.gatt.connect();
      const service = await server.getPrimaryService(_uuid('e510'));
      this._led1 = await service.getCharacteristic(_uuid('e515'));
      this._led2 = await service.getCharacteristic(_uuid('e516'));
      this._servo1 = await service.getCharacteristic(_uuid('e511'));
      this._servo2 = await service.getCharacteristic(_uuid('e512'));
      this._buzzer = await service.getCharacteristic(_uuid('e521'));
      this._switch = await service.getCharacteristic(_uuid('e525'));
    } catch (e) {
      console.error(`_initialize catch`, e.message);
    }
  }
  sleep(sec) {
    return new Promise((resolve, reject) => {
      setTimeout(() => {
          resolve();
      }, sec * 1000);
    });
  }
  getDefaultRobotId() { return 'default'; }
  async connectEmbot(id) {}
  logout(arg) { console.warn(eval(arg)); }
  showError(s) { alert(s); }
  rest(sec) {
    return new Promise((resolve, reject) => {
      setTimeout(() => {
        this.write(this._buzzer, 0);
        resolve();
      }, sec * 1000);
    });
  }
  async buzzerTimer(beat) { return 60 * beat / this.bpm; }
  async restInOctave(beat) { return 60 * beat / this.bpm; }
  async sendToEmbot(id, info) {
      switch(info.type) {
      case 'led':
        await this._write(info.id === 1 ? this._led1 : this._led2,
          this._led[info.value]);
        break;
      case 'servo':
        await this._write(info.id === 1 ? this._servo1 : this._servo2,
          info.value);
        break;
      case 'rotatingServo':
        if (info.id === 1) {
          this._speed1 = info.value;
        } else {
          this._speed2 = info.value;
        }
        break;
      case 'buzzer': // '61' ラ1
        await this._write(this._buzzer, info.value);
        break;
      case 'octave': // 高い方は100ぐらい 52はド1 28ド-1
        await this._write(this._buzzer, info.value);
        break;
      }
  }
  end() {}

  async _startswitch(char) {
    char.addEventListener('characteristicvaluechanged', (event) => {
      const value = event.target.value.getUint8(0);
      console.log(`${event.type}: ${value}`);
      this.applySwitch(value === 0 ? true : false); // 0: on, 1: off
    });
    await char.startNotifications();
    console.log(`startNotifications success`);

    await char.readValue();
  }

  async action_3() {
    try {
      await this._startswitch(this._switch);
    } catch (error) {
      throw error;
    }
  }

  async applySwitch(onoff) {
    const onoffStr = onoff ? 'on' : 'off';

    const el = document.getElementById('switchview');
    if (el) {
      el.textContent = `${new Date().toLocaleTimeString()} ${onoffStr}`;
    }

    const robotId = this.getDefaultRobotId();
    const blockInfo = { type: 'led', id: 1, value: onoffStr };
    await this.sendToEmbot(robotId, blockInfo);
  }

}
const em = new Em();
const _start = async () => {
  try {
    await em._initialize();
    await em.action_3();
  } catch (e) {
    em.showError(e.message);
  }
};
```

|実行結果1|
|:-:|
|![off1.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/213145/390f0cea-1f50-4519-8cea-a4c1816d6c93.png)|
|結線されていない状態だとoffと表示します|

|実行結果2|
|:-:|
|![off3.jpg](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/213145/0dbc63bd-b945-4692-b75c-5500bae660b9.jpeg)|
|結線されていない状態でLEDは消灯状態です|

|実行結果3|
|:-:|
|![on1.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/213145/118f175c-e3a9-4950-b58f-8de48c2d176d.png)|
|結線状態にするとブラウザ上では on の表示になります|

|実行結果4|
|:-:|
|![on3.jpg](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/213145/cbef4186-8137-4706-a50f-cf21b893d2cd.jpeg)|
|結線状態にするとLEDがつきます|


気づいていなかったのですが2023年末には[embot+(エムボットプラス)](https://plus.embot.jp/top)という後継機が発売されています。センサーを挿せる端子が追加されていて何種類かのセンサーに対応しているようです。