💡

スマートメーターから現在の電力を得るAndroidアプリを作ってみた

2023/05/25に公開

以前Webアプリケーションを作っているうちにスマートメータから情報を得る手順がちょっとわかってきたんですね。

https://zenn.dev/akihiro_ya/articles/0df610e514a9f9

そこで自分が前に作ったUSBシリアル通信するAndroidアプリ
https://ak1211.com/7707/
と一緒にするとAndroidアプリになるのでは?と考えたのでAndroidアプリにしてみた。

作ったAndroidアプリ

https://github.com/ak1211/android_smartmeter_route_b

瞬時電力ボタンを押すとスマートメーターから瞬時電力を得る。
それだけのアプリ。

スマートメーターの情報を最安ハードウェアで引っこ抜く」と同じことをAndroidアプリにしただけ。

用意するもの

  • Androidスマホとか
  • RL7023 Stick-D/IPSまたはUSBシリアル変換器につないだBP35A1とか
  • USB変換アダプタとかUSBハブとか

使い方

AndroidスマホのUSBポートに
RL7023 Stick-D/IPSをUSB Type C ハブにつないで(変換アダプタでもいいが)Androidスマホに接続する。


FT230X Basic UARTと見えているのがRL7023 Stick-D/IPSです。
(これしか接続していないから当然なんだが)

USBポートにFTDI USBシリアル変換アダプター(5V/3.3V切り替え機能付き)を追加してもきちんと2デバイス検出されるのを確認しています。

このアダプタはFTDI社製FT232RLなのでAndroidからFT232R USB UARTと見えている

このファイルに載っているICを使っているUSBシリアル変換器なら使える
https://github.com/ak1211/android_smartmeter_route_b/blob/main/app/src/main/res/xml/device_filter.xml

USBシリアル変換器経由でBP35A1をつないでもいい
(試していないがテキスト形式で受信設定している場合はRL7023 Stick-D/IPSと同じはず)

設定で

  • BルートID
  • Bルートパスワード

を入力して、使用するUSBデバイスを選択してからアクティブスキャンボタンを押すと接続対象のスマートメーターを探す(1分位)ので、そのあと登録ボタンを押してください。

スマートメーターが見つからなければ電波状態が悪いまたはID/パスワードが間違っているので、Bルート設定情報を確認して
できればスマートメーターに近いところで再度アクティブスキャンを行ってください。

設定ができていればホームでポートを開いて瞬時電力ボタンを押せばスマートメーターから瞬時電力を取得します。

いま何ワット?

616Wだ。

なんとなくソースコードの紹介でも

New Project -> Basic Views Activityで新規作成
1つのActivity(MainActivity)と
2つのFragment(FirstFragment,SecondFragment)との構成になっているのでそのまま踏襲する。

設定Fragment

スマートメーターと接続するには

  • BルートID
  • Bルートパスワード
  • 周波数チャネル(アクティブスキャンで手に入れる)
  • PAN ID(アクティブスキャンで手に入れる)
  • IPV6リンクアドレス(アクティブスキャンで手に入れる)
    を設定する必要があるために設定Fragmentを作る。

参考
https://route-b.iij.ad.jp/archives/128

HomeFragment

ホームは表示できたらいいので雑にこうした。

Androidアプリアーキテクチャ

アプリ アーキテクチャ ガイドというのが公式サイトにあるので見てみた。
https://developer.android.com/topic/architecture?hl=ja

上記ページからの引用

アプリの推奨アーキテクチャ

architecture overview

DomainLayer(UseCase)はOptionalなので

  • UI Layer
  • Data Layer

に分割する構造を公式が推しているからこのアーキテクチャを採用する。

UI レイヤ

https://developer.android.com/topic/architecture?hl=ja#recommended-app-arch

上記ページからの引用
ui layer
図 3. UI は、画面上の UI 要素と UI 状態を足し合わせたものです。

UI 要素とUI 状態を分離するやり方と
単方向データフローで状態を管理するやり方は
The Elm Architectureみたいな感じか?

そうであるなら知ってる
Halogen(PureScriptのフレームワーク)と同じだから。

余談なのでたたんでおく

前に作ったWebアプリのここがStateの定義で
https://github.com/ak1211/smartmeter-route-b-app/blob/main/src/Page.purs#L82-L92
このStateがコンポーネントのrender関数に渡されてページを作る。
https://github.com/ak1211/smartmeter-route-b-app/blob/main/src/Page.purs#L198-L685

この後handleAction関数からStateを更新してまたrender関数にstateが渡される流れでページを作っているから。

単方向データフローというものか。

Fragment / ViewModel / UI Stateを分離してみた。
ViewModelを使わないと横に倒すだけで情報が揮発するので。

データレイヤ

https://developer.android.com/topic/architecture/data-layer?hl=ja#architecture

ライフサイクル

データレイヤ内のクラスのインスタンスは、ガベージ コレクション ルートから到達可能である限り、メモリ内に保持されます(通常はアプリの他のオブジェクトから参照されます)。

クラスにメモリ内データ(キャッシュなど)が含まれている場合、そのクラスの同じインスタンスを一定期間再利用することをおすすめします。これは、クラス インスタンスのライフサイクルとも呼ばれます。

クラスの役割がアプリ全体で重要な場合は、そのクラスのインスタンスのスコープを Application クラスに設定できます。これにより、インスタンスがアプリのライフサイクルに従うようになります。一方、アプリの特定のフロー(登録フローやログインフローなど)で同じインスタンスを再利用するだけであれば、そのフローのライフサイクルを持つクラスにインスタンスのスコープを設定する必要があります。たとえば、メモリ内データを含む RegistrationRepository のスコープを、登録フローの RegistrationActivity またはナビゲーション グラフに設定します。

こう説明されているので、このデータはApplicationクラスに管理させるとする。

USBシリアル通信

Androidでusb-serial-for-androidライブラリを使ってシリアル通信をする。

AndroidからUSBデバイスを検出する

まずは公式サイトを確認して。
https://developer.android.com/guide/topics/connectivity/usb/accessory?hl=ja

今回はusb-serial-for-androidライブラリで検出する。

ここ
https://github.com/ak1211/android_smartmeter_route_b/blob/main/app/src/main/java/com/ak1211/smartmeter_route_b/data/ProbedUsbSerialDriversListRepository.kt#L56-L68

アクセサリとの通信の権限を取得する

https://developer.android.com/kotlin/flow?hl=ja#callback

コールバックは苦手なのでcallbackflowでコールバックをFlowに変換してrequestPermission()すると権限取得ダイアログを出してくれる

ソースはここ
https://github.com/ak1211/android_smartmeter_route_b/blob/main/app/src/main/java/com/ak1211/smartmeter_route_b/UsbPermissionGrant.kt#L22-L76

そのほかシリアル通信はusb-serial-for-androidライブラリにまかせる。

アクティブスキャンする

ここがそう。
https://github.com/ak1211/android_smartmeter_route_b/blob/main/app/src/main/java/com/ak1211/smartmeter_route_b/data/SmartWhmRouteBRepository.kt#L109-L185

お互いにこんな会話をしている(エコーバックは省略)

ここまでで接続情報が揃ったので登録ボタンを押して保存する。

スマートメーターと接続する(PANAプロトコル)

つまりSKJOINを発行する。

https://github.com/ak1211/android_smartmeter_route_b/blob/main/app/src/main/java/com/ak1211/smartmeter_route_b/data/SmartWhmRouteBRepository.kt#L45-L94

瞬時電力計測値要求を発行する

SKSENDTOにこんなECHONET Lite電文を与える
1081000005FF010288016201E700
ここがそう。
https://github.com/ak1211/android_smartmeter_route_b/blob/main/app/src/main/java/com/ak1211/smartmeter_route_b/ui/home/HomeFragment.kt#L81-L94

瞬時電力計測値を取得する

ERXUDPを受け取って解釈する。

ここがそう
https://github.com/ak1211/android_smartmeter_route_b/blob/main/app/src/main/java/com/ak1211/smartmeter_route_b/ui/home/HomeViewModel.kt#L115-L154

感想

KotlinのByteは符号付きです。

Represents a 8-bit signed integer.
https://kotlinlang.org/api/latest/jvm/stdlib/kotlin/-byte/

エラー処理をOption/Eitherにするかnull / Resultにするか迷ったがとりあえず
Either<Throwable, T> にしてみた。

GUIはしんどい。
意図的にオブジェクト指向部分を小さくしようと頑張ったけど、それなりのコード量になった。

Discussion