embotをブラウザから操作したい

[前回](https://qiita.com/hta393939/items/676c5c4fc35bb2e055d4)はLチカだけでしたが今回はサーボとブザーも操作してみます。
サーボはサーボの字が上に向くように置いて上から見たとき0度に対して反時計回りに180度まで回転できるようです。
またブザーは半音単位で、52を基準のドとして20ぐらいから100ぐらいまでは音程が取れるようです。自動で音を止めたりはしてくれないので止める場合は 0 を指定します。

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

```index.html
<!DOCTYPE html>
<html>
<head>
<meta charset="UTF-8" />
<title>embot 操作したい</title>
</head>
<body>
<button onclick="_start()">開始する</button>
<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._other = 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 action_1() {
        try {
            let blockInfo = '';
            let robotId = this.getDefaultRobotId();
            await this.connectEmbot(robotId);
            blockInfo = { type: 'led', id: 2, value: 'on' };
            await this.sendToEmbot(robotId, blockInfo);
            blockInfo = { type: 'octave', value: '52' };
            await this.sendToEmbot(robotId, blockInfo);
            blockInfo = { type: 'servo', id: 1, value: '0' };
            await this.sendToEmbot(robotId, blockInfo);
            await this.sleep(2);
            blockInfo = { type: 'servo', id: 1, value: '45' };
            await this.sendToEmbot(robotId, blockInfo);
            await this.sleep(2);
            blockInfo = { type: 'servo', id: 1, value: '90' };
            await this.sendToEmbot(robotId, blockInfo);
            await this.sleep(2);
            blockInfo = { type: 'octave', value: '0' };
            await this.sendToEmbot(robotId, blockInfo);
            blockInfo = { type: 'led', value: 'off' };
            await this.sendToEmbot(robotId, blockInfo);
        } catch (error) {
            throw error;
        }
    }

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

|実行結果1|
|:-:|
|![00度.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/213145/b3901619-ed31-2b3c-afc9-68343fec2194.png)|
|この角度を0度とすると...|

|実行結果2|
|:-:|
|![45度.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/213145/0373e949-2f52-7861-188e-e1c29ffaeaf3.png)|
|45度|

|実行結果3|
|:-:|
|![90度.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/213145/b161a6e7-d509-5a23-c7b9-506559f42e0d.png)|
|90度|

|実行結果4|
|:-:|
|![90度off.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/213145/a29798b7-a054-bf3e-9caf-ca464197f580.png)|
|最後にLED消します|

半田付け無しでもサーボが回せて楽しいです。