はじめに
この記事は私のLINE API開発のデビューとなった記事、LINE Thingsでパトランプを回してみたの続編です。当時はJavaScriptやらLINE APIやらをよくわかってなかったのですが、仕事や趣味でそれぞれを触ってきたので今ならもっと踏み込んだ内容の記事をかけるかなと思ったのでちょっとバージョンアップさせてみました。
アップデート内容
以下の感じでアップデートしました
- プレーンのJavaScriptではなくReactとTypeScriptで実装
- UIにスライダーを追加して回転速度をコントロールさせる
今回はソフトウェアのみでハードウェア的なアップデートはございません。
React+TypeScript
このパトランプはLIFFを介してデバイスとBLE通信します。React+TypeScriptを用いたLIFFの導入方法はこちらで解説しているので参照してください。今回のソースコードはここで公開しています。次から、LIFFでのBLE通信について解説していきます。ベースは本家のコードを参考にTypeScriptに変換しています。
まずはLIFFをinitさせます。このinitに成功すればliff.initPlugins(['bluetooth'])
でBLEをinitさせます。
liff.init({ liffId: process.env.REACT_APP_LIFF_ID as string }).then(() => {
liff.initPlugins(['bluetooth']).then(() => {
liffCheckAvailablityAndDo(() => liffRequestDevice())
}
)})
プラグインのinitに成功すればliffCheckAvailablityAndDo(() => liffRequestDevice())
に進みます。
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)
})
}
コールバックが少しだけ複雑ですが、流れとしては
-
liffCheckAvailablityAndDo
でliff.bluetooth.getAvailability()
が成功するかを見る - 成功すれば
liffRequestDevice
に進むことができ、失敗すれば1秒後に1.
の内容を再トライする -
liffRequestDevice
でliff.bluetooth.requestDevice()
に成功するかを見る - 成功すれば
device
を取得できるのでliffConnectToDevice(device)
に進む
liffConnectToDevice(device)
では以下のような処理をしています。
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)
で以下のような処理をします。
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
を定義しておきます。
declare global {
interface Window { characteristic: BluetoothRemoteGATTCharacteristic }
}
windowオブジェクトへの登録が完了すれば一旦characteristic.writeValue(Buffer.from('0'))
で初期値を送っておきます。この送る値はstring
のほうがデバイス・LIFFお互いにとって楽です。とりあえずこれでcharacteristic.writeValue()
というデバイスに通信させるための関数が用意できました。
スライダーで回転数を可変させる
パトランプは10bitの範囲のPWMで回転数を制御させます。LIFF的にはデバイスに0~1023
を送ればいいわけです。今回は簡単のために0
が送られたときはパトランプが回転しないのでランプを停止させる、それ以外の値は回転させる、といった通信方式にしました。
今回は以下のような関数を用意して
const sendValue = (value: string) => {
if (initSuccess) window.characteristic.writeValue(Buffer.from(value))
}
以下のようなコンポーネントを作成しました。
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を使いました。今回はFunctionComponentを利用してスイッチのON/OFFとスライダーの値をuseState
で持たしています。
さいごに
今回でReact+TypeScriptでLINE Thingsをさせるための土台ができました。これから色々と遊べそうです。