M5StackをソラコムにつないでUIFlow(MicroPython)のコードをOTA実行する（ライブラリとサンプルコード付き）


:::note info
この記事は　SORACOM Advent Calendar 2022 の 14 日目の記事です。前日の記事はソラコムCREのkanuによる「[SORACOM Peek の使いどころ 3 選](https://qiita.com/kanuu0504/items/a56d95ba6623117e9c48)」でした。
:::

## はじめに
ごきげんよう。爬虫類（特にヘビ）は苦手なソラコムCREの岡田（hisaya）です。

「ヘビ嫌いだからPythonも嫌い」という非常にくだらない理由でPythonのプロジェクトや実装を避けてきたのですが、最近になってインタプリタ型言語の便利さを体感し、MicroPythonに興味を持つようになりました。
この記事ではマイクロな爬虫類、もといMicroPythonで記述されたUIFlowのコードをSORACOMプラットフォーム上に配置・ダウンロードし、M5Stack Core 2上で動作しているUIFlow環境で実行する方法をご紹介します。

:::note warn
この記事のOTAとは、セルラー通信経由で実行するアプリケーション（MicroPythonスクリプト）を取得し実行することを意味します。
SIMカードに対するOTA操作とは異なります。
:::

## 用意するもの
- [M5Stack Core 2](https://www.switch-science.com/products/6530)
- [CatM + GNSS Unit](https://www.switch-science.com/products/8191)
  - M5StackとUnitの接続に関しては[maxのTips記事](https://qiita.com/ma2shita/items/79e77e9872dd3078ead9)も参考になります。
- [M5GO Bottom2](https://www.switch-science.com/products/6785)
    - PORT.C に CatM + GNSS Unit を接続するために利用します。なんらかの方法でUART接続できる場合は不要です。
- [SORACOM Air for セルラーのSIM](https://soracom.jp/services/air/cellular/)
    - 今回使用するUnitに対応するサブスクリプションは、plan-D, plan-KM1, planX3です。私はplan-D D-300MBを使用しました。
- [通信モジュール用ドライバとUIFlow用コードブロック](https://github.com/sayacom/soracom-advent-calendar-20221214)
    - この記事で使用するスクリプトやUIFlowのブロック、サンプルが含まれています。

なお、検証はしていませんが本質的にはESP32で動作するMicroPython環境とUnitとに搭載されている通信モジュール（SIMCOM SIM7080G）の組み合わせによるものなので、M5StickCや[M5Stamp向けのモジュール](https://www.switch-science.com/products/8194)でも動作するかもしれません。

## ステップ
大まかに実行までの手順を以下に示します。

### m5burnerでUIFlowを書き込む
M5StackにUIFlowを書き込みます。今回は最新版のUIFlow v1.11.0を使用します。
![スクリーンショット 2022-12-14 9.17.52.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/182576/a452474d-43b2-435d-8713-fc0bcbfb0db9.png)

### ドライバをファイルシステムに転送する
実はUIFlowにはCatM+GNSS Unit向けのコードブロックも用意されているのですが、プロトタイプとして使ってみたところ以下のような問題がありました。

- APN設定などのコードブロックがない
- HTTP リクエストで対応しているメソッドがGETとPOSTしかない
- リクエストは飛んでいるようだが、レスポンスの取得に失敗することが多い
- 取得に失敗しても、どの段階で失敗したか分かりにくい

参考にしたいくつかの記事でもUART経由で直接ATコマンドを送受信するケースが多く見受けられたので、今回はMicroPythonでATコマンドを送受信する関数とSIM7080GでHTTP通信をするための最低限の関数をスクラッチ実装しました。
これによってUIFlowのコードブロックからATコマンドを直接送信することがなくなり、後述するUIFlowのコードブロック定義を併用することで見通しのよいビジュアルプログラムを作成できるようになりました。また通信に関しても、安定的にデータ取得やエラーハンドリングがしやすい状態になったと考えています。

実装した関数は[リポジトリに配置](https://github.com/sayacom/soracom-advent-calendar-20221214)してありますので、試してみたい方はチェックアウトのうえでUIFlowのターミナル画面から `mpy/at_cmd.py` と `mpy/sim7080g.py` ファイルを転送します。
ファイルの転送は、Terminalウインドウから「Select」ボタンで対象のファイルを選択し、「Send」ボタンをクリックすることで実行されます（転送は1件ごとに実行する必要があります）。
![スクリーンショット 2022-12-14 9.27.16.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/182576/c7b180a0-4d01-1c7e-78e9-8dc5456ae901.png)

### コードブロックのインポート
上記で作成したライブラリを利用する形で、UIFlow上に配置可能なコードブロック定義も用意しました。UIFlowのCustomブロックタブから `Import *.m5b` を選択し、チェックアウトしたディレクトリに含まれる `uiflow/MODEM_SIM7080G.m5b` ファイルをインポートします。
![スクリーンショット 2022-12-14 10.34.11.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/182576/2c1001bd-eb17-a708-0cbb-bc9c45c85c6c.png)

:::note info
InitializeブロックはUARTやインポートしたモジュールインスタンスの生成のため必須になります。
:::

### SORACOM プラットフォーム側の設定
いったんUIFlowの画面を離れ、ソラコムのユーザーコンソールでCatM+GNSS Unitに挿入しているSORACOM IoT SIMの設定をしていきます。

#### SIM グループ設定
接続に利用するSIMカードの[グループ設定](https://users.soracom.io/ja-jp/docs/group-configuration/configure-group/)を適用します。具体的には、SIMの情報や設定されているタグなどの情報にアクセスする[メタデータサービス](https://users.soracom.io/ja-jp/docs/air/use-metadata/)とSORACOM Harvest Filesを有効化しておきます。

#### SORACOM Harvest FilesへのMicroPythonコードのアップロード
[ユーザードキュメント](https://users.soracom.io/ja-jp/docs/harvest/enable-files/)を参考にSORACOM Harvest Filesを有効化します。ファイルはユーザーコンソールからもアップロードできますので、適切な階層配下にプログラムを配置しておきます。
![スクリーンショット 2022-12-14 10.52.28.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/182576/7814d442-bbf4-fd51-465c-46d884f632ab.png)

たとえば、rgb-led.py は以下のような内容が記述されています。
```python
from m5stack import *
import time
import random

print("Hello, I am user Python script from Metadata Service!")
rgb.setBrightness(100)

while True:
  rgb.setColorFrom(1, 5, (random.randint(0, 255) << 16) | (random.randint(0, 255) << 8) | (random.randint(0, 255)))
  time.sleep(0.5)
```

#### SIMタグの設定
こちらも[ユーザードキュメント](https://users.soracom.io/ja-jp/docs/air/set-tag/)を参考に設定していきます。タグはユーザーコンソールのSIM管理画面から対象のSIMを選択し「SIM詳細」画面から設定します。
![スクリーンショット 2022-12-14 10.54.08.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/182576/f253cd3d-74c9-fe3e-fce9-6c5217cc651f.png)

### コードブロックの配置
リポジトリに同梱しているサンプルでは、以下のような流れで通信を実施しています。

- メタデータサービス経由でSIMに設定されたタグ情報の取得（GET）
- メタデータサービスを経由で特定のSIMタグの値を更新（PUT）
- SORACOM Harvest Filesに配置したMicroPythonコードのダウンロードと実行

![スクリーンショット 2022-12-14 9.30.30.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/182576/cb57c315-71f0-3f1b-b80b-b8b7f4b1d508.png)

重要な部分のMicroPythonコードを下記に示します（全文は[こちら](https://github.com/sayacom/soracom-advent-calendar-20221214/blob/main/uiflow/demo_code.mpy)）。
SIMが挿入されていれば特にIDやパスワードなどを設定することなくセキュアに通信できることもセルラー通信のメリットの一つです。
```python
# Describe this function...
def get_metadata_tags():
  global update_tag_name, update_tag_value, dl_path, http_results, tag_request_body, http_request_body, temp, script
  http_results = modem.http_request("GET", url='http://metadata.soracom.io', path='/v1/subscriber.tags', headers=None, params=None, body=None)
  return json.loads((http_results['response']))

# Describe this function...
def update_sim_tag(update_tag_name, update_tag_value):
  global dl_path, http_results, tag_request_body, http_request_body, temp, script
  tag_request_body = []
  tag_request_body.insert(0, {'tagName':update_tag_name,'tagValue':update_tag_value})
  http_request_body = json.dumps(tag_request_body)
  http_results = modem.http_request("PUT", url='http://metadata.soracom.io', path='/v1/subscriber/tags', headers={'Content-type':'application/json'}, params=None, body=http_request_body)
  return http_results['status_code']

# Describe this function...
def dl_from_harvest_files(dl_path):
  global update_tag_name, update_tag_value, http_results, tag_request_body, http_request_body, temp, script
  http_results = modem.http_request("GET", url='http://harvest-files.soracom.io', path=dl_path, headers=None, params=None, body=None)
  print((str('Download status: ') + str((http_results['status_code']))))
  return http_results['response']

import machine
from sim7080g import SIM7080G

uart_modem = machine.UART(1, rx=13, tx=14)
uart_modem.init(115200, bits=8, parity=None, stop=1)

modem = SIM7080G(uart_modem)
temp = modem.deactivate(0)
if temp:
  CONN.set_text('...')
else:
  CONN.set_text('ERR')
modem.setAPN("soracom.io", "sora", "sora")
temp = modem.activate(0)
if temp:
  CONN.set_text('OK')
else:
  CONN.set_text('ERR')
print(modem.isNetworkActivated(0))
temp = get_metadata_tags()
print((str('[CURRENT SIM TAGS]') + str(temp)))
if len(temp) != 0:
  GET_TAG.set_text('OK')
else:
  GET_TAG.set_text('NG')
temp = update_sim_tag('TAGS_FROM_DEVICE', str((rtc.printRTCtime())))
print((str('[UPDATE SIM STATUS] ') + str(temp)))
if temp == 200:
  PUT_TAG.set_text('OK')
else:
  PUT_TAG.set_text('NG')
script = dl_from_harvest_files('/uiflow/codes/unit-co2-demo.py').decode()
print((str('[PYTHON SCRIPT]') + str(script)))
if len(script) != 0:
  MSG.set_text('Executing script...')
else:
  MSG.set_text('Failed to DL!!')
print('Execute!')
exec(script)
```

### 実行
UIFlowのRunボタンをクリックしてM5Stackに通信とファイル読み込み用のプログラムを転送すると、セルラー通信の開始やタグ取得・更新処理を実施した後、SORACOM Harvest Filesからダウンロードしたプログラムをexec関数に渡して実行しています。
転送したファイルが上記サンプルコードの場合は、M5Stack本体右側のLEDがランダムな色で点滅すれば成功です。

![PXL_20221214_054915506.jpg](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/182576/ef6eaab8-4ea7-798d-f5a5-0cfc2d2ab539.jpeg)

:::note warn
現状のサンプルでは、OTAで読み込んだ（無限ループを含む）プログラムの実行後はM5Stack本体をリセットしないとファイル転送などに反応しなくなります。
タイマー割り込みで通信処理と実行処理を分けたりすることで回避できるかもしれません。
:::

## 応用例
さまざまなユースケースが考えられるのですが、ここではいくつかの実装例を紹介します。

### センサデータの表示
UIFlowで実装・生成したMicroPythonコードを実行できるので、画面表示や用意されているセンサ用ドライバもそのまま利用できます。
![PXL_20221212_163443526.TS_exported_54064_1670863617139_2.jpg](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/182576/52c02b24-2b6e-0cb0-7ea8-69960b8821ca.jpeg)

### 画像データの表示
画像ファイル（PNGファイル）をダウンロードしてSDカード上に永続化し、画面に表示させる例です。リサイズなどは必要になると思いますが、ライブカメラの画像などを表示させるといった用途に使えるかもしれません。

![PXL_20221212_163443526.TS_exported_26798_1670863609242_2.jpg](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/182576/b31d67a5-8fe0-917b-a293-d110747f4d5e.jpeg)

## おわりに
サラッとOTA実行を推しにした記事ですが、内実のところはCat.M + GNSS UnitをはじめとしたSIM7080G通信モジュールのHTTPドライバやUIFlowコードブロックといった実装とその応用としての紹介でした。
Wi-Fiでいいというユースケースもあると思いますが、離れたところにあるM5Stackで動作する内容をサクッと書き換えることもできますし、コードブロックを利用してSORACOM Harvest Dataにセンサデータを送信するといった使い方も簡単にできるようになったと思います。
特にUIFlowではM5が販売する多くのモジュールが利用できますので、手頃なセンサやユニットなどと組み合わせてデータ取得やデバイスの制御などにも利用できます。ぜひこのスケッチや機構を利用・参考にしてIoTにトライしてみてください。