LINE Thingsでパトランプを回してみた・改

# はじめに
この記事は私のLINE API開発のデビューとなった記事、[LINE Thingsでパトランプを回してみた](https://qiita.com/ufoo68/items/63dc64958cddd6bfb36a)の続編です。当時は**JavaScript**やら**LINE API**やらをよくわかってなかったのですが、仕事や趣味でそれぞれを触ってきたので今ならもっと踏み込んだ内容の記事をかけるかなと思ったのでちょっとバージョンアップさせてみました。

# アップデート内容
以下の感じでアップデートしました

- プレーンのJavaScriptではなく**React**と**TypeScript**で実装
- UIにスライダーを追加して回転速度をコントロールさせる

実際のUIや動作は以下の動画になります。
[![動画](http://img.youtube.com/vi/4uf0cAiVIwo/0.jpg)](https://www.youtube.com/watch?v=4uf0cAiVIwo)

今回はソフトウェアのみでハードウェア的なアップデートはございません。

# React+TypeScript
このパトランプは**LIFF**を介してデバイスとBLE通信します。React+TypeScriptを用いたLIFFの導入方法は[こちら](https://qiita.com/ufoo68/items/3e6ff0613ebd87a8edd9)で解説しているので参照してください。今回のソースコードは[ここ](https://github.com/ufoo68/react-signal-light)で公開しています。次から、LIFFでのBLE通信について解説していきます。ベースは[本家のコード](https://github.com/line/line-things-starter)を参考にTypeScriptに変換しています。

まずはLIFFをinitさせます。このinitに成功すれば`liff.initPlugins(['bluetooth'])`でBLEをinitさせます。

```typescript
liff.init({ liffId: process.env.REACT_APP_LIFF_ID as string }).then(() => {
  liff.initPlugins(['bluetooth']).then(() => {
    liffCheckAvailablityAndDo(() => liffRequestDevice())
  }
)})

```

プラグインのinitに成功すれば`liffCheckAvailablityAndDo(() => liffRequestDevice())`に進みます。

```typescript
const liffCheckAvailablityAndDo = (callbackIfAvailable: () => void) => {
  liff.bluetooth.getAvailability().then(isAvailable => {
    if (isAvailable) {
      callbackIfAvailable()
    } else {
      setTimeout(() => liffCheckAvailablityAndDo(callbackIfAvailable), 10000)
    }
  })
}

const liffRequestDevice = () => {
  liff.bluetooth.requestDevice().then(device => {
    liffConnectToDevice(device)
  })
}
```

コールバックが少しだけ複雑ですが、流れとしては

1. `liffCheckAvailablityAndDo`で`liff.bluetooth.getAvailability()`が成功するかを見る
2. 成功すれば`liffRequestDevice`に進むことができ、失敗すれば１秒後に`1.`の内容を再トライする
3. `liffRequestDevice`で`liff.bluetooth.requestDevice()`に成功するかを見る
4. 成功すれば`device`を取得できるので`liffConnectToDevice(device)`に進む

`liffConnectToDevice(device)`では以下のような処理をしています。

```typescript
const liffConnectToDevice = (device: BluetoothDevice) => {
  device.gatt!.connect().then(() => {
    device.gatt!.getPrimaryService(process.env.REACT_APP_USER_SERVICE_UUID as string).then(service => {
      liffGetUserService(service);
    })
  })
}
```

ここで`USER_SERVICE_UUID`が必要になるので、`REACT_APP_USER_SERVICE_UUID`という名前で`.env`ファイルに追加しておきます。
この`liffConnectToDevice`で`device`から`service`の取得を行っていきます。TypeScriptだと`device.gatt`が`unknown`を返す場合があると怒られてしまうので、今回は大胆に`device.gatt!`で進めます。serviceの取得に成功したところで、`liffGetUserService(service)`で以下のような処理をします。

```typescript
const liffGetUserService = (service: BluetoothRemoteGATTService) => {
  const CHARACTERISTIC_UUID = 'E9062E71-9E62-4BC6-B0D3-35CDCD9B027B';
  service.getCharacteristic(CHARACTERISTIC_UUID).then(characteristic => {
    window.characteristic = characteristic
    characteristic.writeValue(Buffer.from('0'))
  })
}
```

`CHARACTERISTIC_UUID `はこの固定値で問題ありません。ここで得られた`characteristic`を`window.characteristic`に登録しておくわけですが、当然TypeScriptだとそんなobjectは存在しないと怒られてしまうので以下のような感じで`window.characteristic`を定義しておきます。

```typescript
declare global {
  interface Window { characteristic: BluetoothRemoteGATTCharacteristic }
}
```

windowオブジェクトへの登録が完了すれば一旦`characteristic.writeValue(Buffer.from('0'))`で初期値を送っておきます。この送る値は`string`のほうがデバイス・LIFFお互いにとって楽です。とりあえずこれで`characteristic.writeValue()`というデバイスに通信させるための関数が用意できました。

# スライダーで回転数を可変させる
パトランプは10bitの範囲のPWMで回転数を制御させます。LIFF的にはデバイスに`0~1023`を送ればいいわけです。今回は簡単のために`0`が送られたときはパトランプが回転しないのでランプを停止させる、それ以外の値は回転させる、といった通信方式にしました。
今回は以下のような関数を用意して

```typescript
const sendValue = (value: string) => {
  if (initSuccess) window.characteristic.writeValue(Buffer.from(value))
}
```

以下のようなコンポーネントを作成しました。

```typescript
const App: FC = () => {
  const [lightValue, setLightValue] = useState<string>('512')
  const [lightOn, setLightOn] = useState<boolean>(false)

  useEffect(() => {
    initBle(setInitSuccess)
  }, [])

  return (
    <div className="App">
      <div className="light">
        <img src={lightOn ? imgOn : imgOff} alt="signal-light" />
      </div>
      <div className="lightValue">
        {lightValue}
      </div>
      <div className="slider">
        <Slider defaultValue={512} min={200} max={1023} className="sliderBar" onChange={(_, value) => {
          setLightValue(value.toString())
        }} />
      </div>
      <div className="switch">
        <span>Off</span>
        <Switch onClick={() => {
          setLightOn(!lightOn)
          !lightOn ? sendValue(lightValue) : sendValue('0')
        }} />
        <span>On</span>
      </div>
    </div>
  );
}
```

`Slider`のminの値を`200`にしたのはそれ以下の値ではパトランプが回転しないからです。またこの`Slider`と`Switch`は[material-ui](https://material-ui.com/)を使いました。今回は**FunctionComponent**を利用してスイッチのON/OFFとスライダーの値を`useState`で持たしています。

# さいごに
今回でReact+TypeScriptでLINE Thingsをさせるための土台ができました。これから色々と遊べそうです。