🙌

【ミーア】ESP32のOTAアップデート機能実装:Firmwareアップデートのアプリ通知

2024/04/10に公開

はじめに

前回、こちらの記事でESP32のOTAアップデートに関して、デバイス側のみ実装した。
https://kazulog.fun/dev/esp32-ota-update-vol1/

今回は新しいFirmwareを開発者がアップロードした時に、ユーザーに対してアプリ通知行う部分を実装する。

Firmwareアップデートのアプリ通知

全体の流れ

AWS s3のfirmwareディレクトリに新しいFirmwareバイナリを開発者がバージョン指定してアップロードする。

各ユーザーのFirmwareのバージョン(=データベースのUserテーブルのfirmware_versionカラムの値)と開発者がアップロードした最新のFirmwareバージョンを比較し、異なる場合にFlutterアプリに「新しいFirmware(Firmwareバージョン)をインストール可能です」と通知する。

サーバー(Go)側の実装

ユーザーのファームウェアバージョンを管理(カラムと構造体)

Userテーブルに新しくFirmwareのバージョンを格納する。

  • カラム名: firmware_version
  • : VARCHAR
  • 長さ: バージョン番号の構造によるが、一般的に**VARCHAR(10)からVARCHAR(20)**の範囲で十分な場合が多い。
  • 初期値: 1.0.0
$ cd scripts/migrations
$ migrate create -ext sql -format 2006010215 add_firmware_version_to_users

upとdownのsqlファイルが作成されるので、下記のように記載。

// 2024040407_add_firmware_version_to_users.up.sql
ALTER TABLE users 
  ADD COLUMN firmware_version VARCHAR(20) DEFAULT '1.0.0';
// 2024040407_add_firmware_version_to_users.down.sql
ALTER TABLE users
  DROP COLUMN firmware_version;

migration適用して、fimware_versionカラムをUserテーブルに追加した。

User構造体にFirmwareVersionフィールドを追加

**usersテーブルのfirmware_versionカラムに対応するフィールド。User構造体を通じてfirmware_version**カラムのデータを読み書きできるようにする。

// user_db.go
type User struct {
	ID                         int                  `db:"id" json:"id"`
	UID                        string               `db:"uid" json:"uid"`
	DeviceID                   types.NullString     `db:"device_id" json:"device_id"`
	// ... その他のフィールド ...
	SleepTransitionTime        types.NullInt64      `db:"sleep_transition_time" json:"sleep_transition_time"`
	HealthDataIntegrationStatus bool                `db:"healthdata_integration_status" json:"healthdata_integration_status"`
	FirmwareVersion            string            `db:"firmware_version" json:"firmware_version"` // ファームウェアバージョン
}

**notification.proto**ファイルに、ファームウェア更新の利用可能通知を追加

ユーザーに新しいファームウェアが利用可能であることを知らせるための通知メッセージを定義し、この新しいメッセージタイプを使うRPCメソッドを定義する。

// notification.proto

syntax = "proto3";

package protos;
option go_package = "github.com/EarEEG-dev/clocky_be/pb";

import "shadow.proto";

// 既存の定義...
message StreamRequest {
  int32 userId = 1; // user id
}

message FirmwareUpdateAvailableResponse {
  int32 userId = 1;
  string message = 2;
  string new_firmware_version = 3;  // 利用可能な新しいファームウェアのバージョン
}

service NotificationService {
  rpc Listen(StreamRequest) returns (stream StreamResponse);
  // ファームウェア更新通知用の新しいメソッド
  rpc ListenFirmwareUpdates(StreamRequest) returns (stream FirmwareUpdateAvailableResponse);
}

**.protoファイルのメッセージ定義でサーバーのレスポンス側にstream**キーワードを使用することで、サーバーストリーミングRPCを実装できる。

これにより、クライアントが一度リクエストを送信した後、サーバー側で変更があるたびにクライアントにデータを"プッシュ"する形で連続的に情報を送信することが可能になる。サーバー側で新しいファームウェアアップデートが利用可能になった際に、即座にその情報をクライアントにプッシュで送るようにするため、サーバーストリーミングRPCとする。

サーバーストリーミングRPCの詳細に関してはこちら。

https://kazulog.fun/dev/grpc-proto-go/#toc5

メッセージとrpcメソッドを.protoファイルに記述終えたら、**protocコマンドを使って、.proto**ファイルからGoのソースコードを生成する。

.protoファイルを作成し、コンパイルしてGo言語で使用するまでに関する記事はこちら。
https://kazulog.fun/dev/grpc-proto-go/

gRPCのListenメソッドにFirmware Version比較ロジックを追加

.protoファイルで作成したrpcメソッドをコンパイルして作成されたListenFirmwareUpdates関数を用いて、新しいFirmwareバージョンの通知部分を実装する。

新しいFirmwareのバージョンを固定で持ち、**Listen**メソッド内で、新しいFirmwareのバージョンとユーザーの現在のバージョンを比較して通知を送信するロジックを追加。

今回は、const newFirmwareVersion = "v1.0.1"と関数内に新しいファームウェアバージョンを直書きしたが、将来的には新しいファームウェア情報を格納するテーブルを用意してデータベースから呼ぶようにする。

Discussion