センサーデータをリアルタイムに可視化するStreamlitアプリを作る！（with ESP32）

# はじめに
ひょんなこと[^1]から、[オープンソースカンファレンス（OSC）](https://event.ospn.jp/osc2024-spring/) にてStreamlitコミュニティ[^2]としてブース出展する機会をいただきました。今回は、そんなOSCのStreamlitブースで展示していたアプリの一つである、ESP32で取得したセンサーデータをリアルタイムに可視化するStreamlitアプリについてかいつまんで解説します。

![Animation.gif](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/3540224/6b1353f3-c2f1-27b4-c43f-5f65dc081aa0.gif)

本記事では、上図におけるデータ取得部分とグラフのリアルタイム描画について記載していきます。

なお、今回詳細は割愛しますが、センサーは家に転がっていた9軸センサーモジュール[AE-BMX055](https://akizukidenshi.com/catalog/g/g113010/)を使用し、サンプルプログラムを少し修正してESP32に書き込んで使用しました。このESP32がBMX055からのセンサーデータをUSB接続上のシリアル通信で出力し、このデータをPCで起動しているPythonの `pyserial` ライブラリで取得します。そして、取得したデータをStreamlitで可視化するような構成となっています。


# コード解説
Streamlitでリアルタイムデータを可視化するためには、いくつかの壁を乗り越える必要があります。それは、リアルタイムにどうグラフを更新するか？という壁と、可視化を邪魔せずにどうデータを取得するか？という壁となります。

この壁を乗り越えるために必要となるテクニックが、**Streamlitのプレースホルダー**という機能と、**Streamlitにおけるマルチスレッド**です。本章では、この2つのテクニックについてそれぞれアプリ画面とソースコードを用いて解説していきます。

なお、記事内のサンプルプログラムの実行環境は、Windows10、Python3.10、Streamlit==1.32 です。（恐らくStreamlitさえ実行できる環境であれば動作するのではないかと思います。）Streamlitは、`pip install streamlit` にてインストール可能で、`streamlit run main.py` コマンドでブラウザ上にアプリを起動できます。この機会にぜひお試しください。


## Streamlitのグラフをリアルタイムに更新する
ここでは、プレースホルダーを使用してグラフの更新を行います。プレースホルダーとは、Streamlitアプリの実行中に、あらかじめ確保しておいた領域のみを動的に更新できる機能です。`placeholder = st.empty()` により生成された `placeholder` オブジェクトを用いてグラフやUIの更新を行います。

まずは、動作の様子を確認してみましょう。
![streamlit-main1-2024-03-16-10-03-59.gif](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/3540224/30b64521-37df-bb24-5c60-d417a02405ea.gif)
上図から、目的通りグラフ領域のみが更新されていることが確認できます。下記にソースコードを示し、解説を加えていきます。
```python
import random
import time

import streamlit as st

st.title(":chart: check placeholder app")
toggle = st.toggle("描画開始・終了")
placeholder = st.empty()

data = []
while toggle:
    data.append(random.random())
    placeholder.line_chart(data)
    time.sleep(0.1)
```
ここでは、`st.toggle()` でグラフの描画の開始・終了を制御しています。トグルUIを有効にすると、while文内でのランダムデータの更新とプレースホルダー上のグラフ更新が始まります。具体的には、`placeholder = st.empty()` で生成されたオブジェクトを使用して、while 文内で `placeholder.line_chart(data)` を実行することでグラフを更新しています。これにより、上図のような動的なグラフの更新が行えるようになります。

ちなみに、もしwhile文内の `placeholder.line_chart()` を `st.line_chart()` に書き換えるとどうなってしまうでしょうか？何となく想像がついた方もいらっしゃるかもしれませんが、グラフが延々と下に追加されていってしまうことになります:angel:。

## Streamlitでサブスレッドからデータを取得する
リアルタイムにデータを更新できることが分かったところで、続いてはリアルタイムにデータを取得する部分について考えます。

ここで、あれ？さっきみたいにwhile文の中でデータを取得すれば良いんじゃないの？と思われた方もいるかもしれません。しかしそれでは、処理に時間がかかるグラフの描画などの処理が、より高速に更新されるセンサーデータの取得に間に合わなくなってしまいます。そこで、マルチスレッドの使用を検討します。メインスレッドではグラフ描画のみを担当させ、サブスレッドでデータの取得を行う構成です。

最も簡単には、グローバル変数をスレッド間で参照・更新することにより実現できそうです。しかし、StreamlitではUI操作によりコードが再実行されるという特性があり、これによりスレッド間で共有されていた変数がリセットされるという問題が生じます。

この問題に対処するため、Streamlitでマルチスレッドを実装する際は、`threading.Thread` クラスを継承したカスタムクラスでサブスレッドを構成し、`st.session_state` を使ってそのクラスインスタンスを保存しておくことで、メインスレッドとサブスレッド間でのデータ共有を維持する方法があります。

今回はまず先にソースコードを示します。なお、本コードは[こちらの記事](https://zenn.dev/ohtaman/articles/streamlit_start_stop)を参考に記述させていただきました。
```python
import random
import time
import threading

import streamlit as st

class Worker(threading.Thread):
    def __init__(self, **kwargs):
        super().__init__(**kwargs)
        self.data = []
    
    def run(self):
        while True:
            self.data.append(random.random())
            time.sleep(0.1)


st.title(":chart: check placeholder app")
toggle = st.toggle("描画開始・終了")
placeholder = st.empty()

if "worker" not in st.session_state:
    st.session_state.worker = Worker()
    st.session_state.worker.start()

while toggle:
    placeholder.line_chart(st.session_state.worker.data)
    time.sleep(1)

st.session_state.worker.data = []
```
`Worker` クラスは `Thread` を継承しており、サブスレッドでリアルタイムにデータを取得します。アプリの初回実行時に `st.session_state.worker` の作成とサブスレッドの開始を行います。UI操作によるアプリの再実行時は、`st.session_state.worker` からメンバ変数を取得することで、サブスレッドで更新され続けているデータを取得できるようになります。

実際に、このコードの動作の様子を確認してみましょう。
![streamlit-main2-2024-03-16-18-03-70.gif](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/3540224/e2b0279c-0dbb-1764-da3b-082aa06a9e6b.gif)


よく見ると、1回のグラフ更新で複数のデータが追加されていることに気づきます。グラフ描画よりデータ取得が速いことを擬似的に再現するために、メインスレッド内では1秒ごとにグラフ描画をしつつ、サブスレッド内で0.1秒ごとにデータを取得しているのでこのような挙動となっています。実際のデータ取得では0.01秒ごとにデータ取得、0.1秒ごとにグラフ描画くらいが自然な間隔になりそうです。

今回のマルチスレッドの方式により、非同期的なデータ取得とグラフ描画をできることが分かりました！こうした非同期のデータ取得処理は、リアルタイムデータを取り扱う際に非常に重要なポイントだと言えます。

# おわりに
今回はニッチなStreamlit・Pythonテクニックを紹介しましたが、Streamlitではこうしたことをせずともインタラクティブなアプリを構築できるので、ぜひお試しください！

最後に、OSC24TK/Springにご来場いただいた皆様・Streamlitコミュニティの話を聞いてくださった方々・そして何より運営の皆様・お誘いいただいた喜田さん、お陰様で楽しい時間を過ごすことができました！Streamlitの魅力が伝わっていましたら幸いです。

ちなみに、イベント参加者から特に多くのコメントが寄せられたのは、Pythonのみを用いてこのようなリアルタイムの描画やシミュレーションを簡単に記述できる点についてでした。また、ローカルのノートPCのみで実行可能であること、さらに簡易なサーバーを用意するだけで外部に公開することも可能である（Streamlit Cloudの利用も含む）という点も魅力的だと感じていただけたようです！

[^1]:PostgreSQLユーザーコミュニティの理事など多方面に活動されていて、更にSnowflakeのData Superheroesでもある[喜田さん](https://twitter.com/kkkida_twtr)から、SnowflakeユーザーコミュニティのStreamlit分会の皆さんも何か出してみませんか？とお声がけをいただき、何だか楽しそうなイベントだぞ！とノコノコ馳せ参じました。
[^2]:[Streamlit](https://streamlit.io/)とは、データに特化したWebアプリを迅速に構築できるPythonライブラリ（OSS）です。なお現時点では、正式なStreamlitコミュニティは国内にまだ存在せず、Snowflakeの日本コミュニティである[SnowVillage](https://usergroups.snowflake.com/snowvillage/)の分会としてStreamlitコミュニティが存在しているのみとなっています。

## 参考文献
- How to build a real-time live dashboard with Streamlit

https://blog.streamlit.io/how-to-build-a-real-time-live-dashboard-with-streamlit/

- Streamlit で threading や multiprocessing を使った非同期処理を行う

https://zenn.dev/ohtaman/articles/streamlit_start_stop

## 仲間募集

NTTデータ テクノロジーコンサルティング事業本部 では、以下の職種を募集しています。

<details><summary>1. クラウド技術を活用したデータ分析プラットフォームの開発・構築(ITアーキテクト/クラウドエンジニア)</summary>

クラウド／プラットフォーム技術の知見に基づき、DWH、BI、ETL領域におけるソリューション開発を推進します。
https://enterprise-aiiot.nttdata.com/recruitment/career_sp/cloud_engineer

</details>

<details><summary>2. データサイエンス領域（データサイエンティスト／データアナリスト）</summary>

データ活用／情報処理／AI／BI／統計学などの情報科学を活用し、よりデータサイエンスの観点から、データ分析プロジェクトのリーダーとしてお客様のDX／デジタルサクセスを推進します。
https://enterprise-aiiot.nttdata.com/recruitment/career_sp/datascientist

</details>

<details><summary>3.お客様のAI活用の成功を推進するAIサクセスマネージャー</summary>

DataRobotをはじめとしたAIソリューションやサービスを使って、
お客様のAIプロジェクトを成功させ、ビジネス価値を創出するための活動を実施し、
お客様内でのAI活用を拡大、NTTデータが提供するAIソリューションの利用継続を推進していただく人材を募集しています。
https://nttdata.jposting.net/u/job.phtml?job_code=804

</details>

<details><summary>4.DX／デジタルサクセスを推進するデータサイエンティスト《管理職/管理職候補》</summary>
データ分析プロジェクトのリーダとして、正確な課題の把握、適切な評価指標の設定、分析計画策定や適切な分析手法や技術の評価・選定といったデータ活用の具現化、高度化を行い分析結果の見える化・お客様の納得感醸成を行うことで、ビジネス成果・価値を出すアクションへとつなげることができるデータサイエンティスト人材を募集しています。

https://nttdata.jposting.net/u/job.phtml?job_code=898

</details>


## ソリューション紹介

<details><summary> Trusted Data Foundationについて</summary><div>

～データ資産を分析活用するための環境をオールインワンで提供するソリューション～
https://enterprise-aiiot.nttdata.com/tdf/
最新のクラウド技術を採用して弊社が独自に設計したリファレンスアーキテクチャ（Datalake+DWH+AI/BI）を顧客要件に合わせてカスタマイズして提供します。
可視化、機械学習、DeepLearningなどデータ資産を分析活用するための環境がオールインワンで用意されており、これまでとは別次元の量と質のデータを用いてアジリティ高くDX推進を実現できます。

</div></details>


<details><summary> TDFⓇ-AM（Trusted Data Foundation - Analytics Managed Service）について</summary><div>

～データ活用基盤の段階的な拡張支援（Quick Start) と保守運用のマネジメント（Analytics Managed）をご提供することでお客様のDXを成功に導く、データ活用プラットフォームサービス～
https://enterprise-aiiot.nttdata.com/service/tdf/tdf_am
TDFⓇ-AMは、データ活用をQuickに始めることができ、データ活用の成熟度に応じて段階的に環境を拡張します。プラットフォームの保守運用はNTTデータが一括で実施し、お客様は成果創出に専念することが可能です。また、日々最新のテクノロジーをキャッチアップし、常に活用しやすい環境を提供します。なお、ご要望に応じて上流のコンサルティングフェーズからAI/BIなどのデータ活用支援に至るまで、End to Endで課題解決に向けて伴走することも可能です。

</div></details>


<details><summary>NTTデータとTableauについて </summary><div>

ビジュアル分析プラットフォームのTableauと2014年にパートナー契約を締結し、自社の経営ダッシュボード基盤への採用や独自のコンピテンシーセンターの設置などの取り組みを進めてきました。さらに2019年度にはSalesforceとワンストップでのサービスを提供開始するなど、積極的にビジネスを展開しています。

これまでPartner of the Year, Japanを4年連続で受賞しており、2021年にはアジア太平洋地域で最もビジネスに貢献したパートナーとして表彰されました。
また、2020年度からは、Tableauを活用したデータ活用促進のコンサルティングや導入サービスの他、AI活用やデータマネジメント整備など、お客さまの企業全体のデータ活用民主化を成功させるためのノウハウ・方法論を体系化した「デジタルサクセス」プログラムを提供開始しています。
https://enterprise-aiiot.nttdata.com/service/tableau

</div></details>


<details><summary>NTTデータとAlteryxについて </summary><div>
Alteryxは、業務ユーザーからIT部門まで誰でも使えるセルフサービス分析プラットフォームです。

Alteryx導入の豊富な実績を持つNTTデータは、最高位にあたるAlteryx Premiumパートナーとしてお客さまをご支援します。

導入時のプロフェッショナル支援など独自メニューを整備し、特定の業種によらない多くのお客さまに、Alteryxを活用したサービスの強化・拡充を提供します。

https://enterprise-aiiot.nttdata.com/service/alteryx

</div></details>

<details><summary>NTTデータとDataRobotについて </summary><div>
DataRobotは、包括的なAIライフサイクルプラットフォームです。

NTTデータはDataRobot社と戦略的資本業務提携を行い、経験豊富なデータサイエンティストがAI・データ活用を起点にお客様のビジネスにおける価値創出をご支援します。

https://enterprise-aiiot.nttdata.com/service/datarobot

</div></details>

<details><summary> NTTデータとInformaticaについて</summary><div>

データ連携や処理方式を専門領域として10年以上取り組んできたプロ集団であるNTTデータは、データマネジメント領域でグローバルでの高い評価を得ているInformatica社とパートナーシップを結び、サービス強化を推進しています。
https://enterprise-aiiot.nttdata.com/service/informatica

</div></details>

<details><summary>NTTデータとSnowflakeについて </summary><div>
NTTデータでは、Snowflake Inc.とソリューションパートナー契約を締結し、クラウド・データプラットフォーム「Snowflake」の導入・構築、および活用支援を開始しています。

NTTデータではこれまでも、独自ノウハウに基づき、ビッグデータ・AIなど領域に係る市場競争力のあるさまざまなソリューションパートナーとともにエコシステムを形成し、お客さまのビジネス変革を導いてきました。
Snowflakeは、これら先端テクノロジーとのエコシステムの形成に強みがあり、NTTデータはこれらを組み合わせることでお客さまに最適なインテグレーションをご提供いたします。

https://enterprise-aiiot.nttdata.com/service/snowflake

</div></details>