4か月前に作ったこれを、毎日使っていたのですが、今日ふと電話置き場を眺めてみるとこんなことに。あらやだ。
電池って4か月ちょいでこんなことになってしまうんですね、こわいこわい。
さて、我が家のシーリングライト危機が訪れました。解決方法として考えられたのは
1は、まだまだあるので大丈夫といえば大丈夫なんですが4か月ごとに電話が死んでいくとなるとそれはさすがに困ります。 2は、あまり数は無く、あっても大抵が産業用なのでかなり高い。
そこで3つ目の解決方法がふと思いつきました。Raspberry Pi 3にAndroidをインストールして、そこで今までのシステムを動かす。 全開の図はこんな感じにアップデートされます。
かなりスマートになりました。adbを使っていた部分はもはやAndroidの内側になってしまったのでADBは不要になり、ローカルのコマンドを実行するだけになりました。
Raspberry Pi 3 でAndroid を実行する
この作戦はRaspberry Pi 3 でAndroid が起動してかつBluetoothが比較的まともに動くことが条件でした。 今回は2個試して、ちゃんと使えたLineageOS 16.0を使います。
インストールはビルド済みイメージをダウンロードして、SDに書き込み、Raspberry Pi 3にカードを入れて起動すれば数分後にはAndroidの初回セットアップ画面が起動します。
セットアップはそこそこにすすめて、いつものAndroidと同じように開発者オプションを有効にして、ADBを有効にします。加えて、Rootアクセスも有効にしておきました。 これで基本的なセットアップはおしまい。
シーリングライトの専用アプリをインストールする
専用アプリがないとライトを操作できないので、専用アプリをどうにかしてインストールします。
Gappsをインストールする方法もあるのですが、このアプリはGappsなしでも動作するので ほかのAndroidデバイスからapkをコピーしました。コピー方法は割愛。
Bluetoothも正しそうに動いてBLEでライトと通信できる状態になりました。
NodeJSを実行する
この仕組みはインターネット上で動作しているNodeJSサーバが、LANの内側で動作しているNodeJSプロセスにSocket.IO通してGoogle Assistantからのコマンドを転送する仕組みになっていました。 大きく書き直すのは大変、そしてAndroidはなんだかんだLinuxなのでNodeJSが動くでしょう。ということでAndroid上でNodeJSを実行できるようにします。
Androidで使えるNodeJSのバイナリを探してみたものの、今風のバージョンは見つからず、ソースからビルドするのもいまいちうまくいかない。
そこで、Termuxをインストールし、その中で apt install nodejs
した結果インストールされたバイナリを使うことにしました。
TermuxはAndroid上で動作するLinux環境のようなものです。公式にAPKが配布されているのでダウンロードしてadb install
しました。
インストールが終わったら、Termuxを立ち上げてそこからはLinuxの世界なのでてきとうにNodeJSをインストールします。
$ apt update $ apt install nodejs
これでNodeJSがインストールできました。使う時は /data/data/com.termux/files/usr/bin/node
を参照するといいです。
おしまい
あとはこのNodeJS環境でこのプログラムを実行するだけで今まで通りシーリングがON/OFFできます。うれしいですね!
const socket = require('socket.io-client')('https://***.com'); const { exec } = require('child_process'); const util = require('util'); const X = [136, 248]; const Y = [189, 373, 555]; const on = `${X[0]} ${Y[0]}`; const off = `${X[1]} ${Y[2]}`; const dimm = `${X[0]} ${Y[2]}`; socket.on('connect', () => { console.log('connect'); }); socket.on('execute', data => { const input = data.inputs[0]; for(const execution of input.payload.commands[0].execution){ if(execution.command === 'action.devices.commands.OnOff'){ const arg = execution.params.on ? on : off; exec(`input touchscreen tap ${arg}`); } if(execution.command === 'action.devices.commands.BrightnessAbsolute'){ const arg = execution.params.brightness ? on : dimm; exec(`input touchscreen tap ${arg}`); } } console.log(util.inspect(data, true, null)); });
一つだけ問題があって、HDMIに何もつながってないと起動しません。そして起動後HDMIを抜くと、
05-01 00:07:25.610 188 267 I hwc-drm-connector: force mode to 1280x720@0Hz 05-01 00:07:25.613 188 267 I hwc-drm-two: Unplug event @1596363841 for connector 29 on display 0 05-01 00:07:25.634 3546 3546 I HWC2 : Destroying display 0 05-01 00:07:25.637 3546 3546 E HWComposer: isConnected failed for display 0: Invalid display 05-01 00:07:25.637 3546 3546 E HWComposer: getPresentFence failed for display 0: Invalid display 05-01 00:07:25.637 3546 3582 E HWComposer: onVsync Failed to find display 0 05-01 00:07:25.637 3546 3546 E HWComposer: isConnected failed for display 0: Invalid display 05-01 00:07:25.637 3546 3546 E HWComposer: getPresentFence failed for display 0: Invalid display 05-01 00:07:25.651 3616 3639 I DisplayManagerService: Display device removed: DisplayDeviceInfo{"内蔵スクリーン": uniqueId="local:0", 1280 x 720, modeId 3, defaultModeId 3, supportedModes [{id=1, width=1280, height=720, fps=50.0}, {id=2, width=1280, height=720, fps=59.9402}, {id=3, width=1280, height=720, fps=60.0}], colorMode 0, supportedColorModes [0], HdrCapabilities android.view.Display$HdrCapabilities@40f16308, density 160, 54.186 x 53.788 dpi, appVsyncOff 1000000, presDeadline 16666667, touch INTERNAL, rotation 0, type BUILT_IN, state ON, FLAG_DEFAULT_DISPLAY, FLAG_ROTATES_WITH_CONTENT, FLAG_SECURE, FLAG_SUPPORTS_PROTECTED_BUFFERS} 05-01 00:07:25.684 3616 3631 E AndroidRuntime: *** FATAL EXCEPTION IN SYSTEM PROCESS: ActivityManager 05-01 00:07:25.684 3616 3631 E AndroidRuntime: java.lang.IllegalArgumentException: Can't remove the primary display. 05-01 00:07:25.684 3616 3631 E AndroidRuntime: at com.android.server.am.ActivityStackSupervisor.handleDisplayRemoved(ActivityStackSupervisor.java:4337) 05-01 00:07:25.684 3616 3631 E AndroidRuntime: at com.android.server.am.ActivityStackSupervisor.access$300(ActivityStackSupervisor.java:193) 05-01 00:07:25.684 3616 3631 E AndroidRuntime: at com.android.server.am.ActivityStackSupervisor$ActivityStackSupervisorHandler.handleMessage(ActivityStackSupervisor.java:4733) 05-01 00:07:25.684 3616 3631 E AndroidRuntime: at android.os.Handler.dispatchMessage(Handler.java:106) 05-01 00:07:25.684 3616 3631 E AndroidRuntime: at android.os.Looper.loop(Looper.java:193) 05-01 00:07:25.684 3616 3631 E AndroidRuntime: at android.os.HandlerThread.run(HandlerThread.java:65) 05-01 00:07:25.684 3616 3631 E AndroidRuntime: at com.android.server.ServiceThread.run(ServiceThread.java:44) 05-01 00:07:25.685 3616 3703 I InputReader: Reconfiguring input devices. changes=0x00000004 05-01 00:07:25.694 3616 3631 I Process : Sending signal. PID: 3616 SIG: 9 05-01 00:07:26.130 200 200 I lowmemorykiller: lmkd data connection dropped 05-01 00:07:26.131 200 200 I lowmemorykiller: closing lmkd data connection 05-01 00:07:26.152 3549 3597 W AudioFlinger: power manager service died !!! 05-01 00:07:26.152 3788 3803 W Sensors : sensorservice died [0xaba4dc00] 05-01 00:07:26.155 149 149 I ServiceManager: service 'telecom' died 05-01 00:07:26.155 149 149 I ServiceManager: service 'contexthub' died (...省略) 05-01 00:07:26.166 149 149 I ServiceManager: service 'imms' died 05-01 00:07:26.166 149 149 I ServiceManager: service 'autofill' died 05-01 00:07:26.166 149 149 I ServiceManager: service 'profile' died
DisplayがなくなったのでDisplayManagerがDisplayを削除して…するとActivityManagerが対応したディスプレイを削除…しようとしたけどPrimaryなので削除できない!!といったあとシステムがダウンしていったのでした。悲しい。
その結果、部屋の真ん中にRaspberry Piが放置されることに…