【制作事例】スマートプランターIvyのライブ観測ダッシュボードを作った話
机の上のスマートプランターでバジルを育てています。
ただ育てるだけでは面白くないので、水位・気温・湿度・明るさの観測データを10分ごとに自動収集して、誰でも見られるライブダッシュボードとして公開してみました。
今回はその裏側――家庭内IoTデータ基盤の設計判断を、うまくいった点も割り切った点も含めてご紹介します。
今回の制作事例は、これまでの会員制コミュニティやプラグインの本番運用とは毛色が違います。
題材はスマートプランター「Ivy」でのバジル栽培。
そして作ったものは、Ivyのセンサーが観測したデータを収集・整形・監査して、10分ごとに自動更新されるライブダッシュボードとしてこのサイト上に公開する、小さな観測データ基盤です。

「植物のダッシュボード」というと可愛らしい響きですが、中身はPython 21スクリプト・約9,600行のパイプラインが毎日24時間、10分周期で回り続ける本気の運用システムです。
本記事では、単なる完成報告ではなく、エッジ・クラウド・NAS・公開サーバーをどう分担させたか、そして何を諦めて何を守ったかという設計判断を中心に整理します。
案件概要 ― 「枯らさない」を、データの問題として解く
スマートプランターIvyは、自動給水とセンサー(水位・気温・湿度・明るさ)を備えた市販のIoTデバイスです。
専用アプリだけでも育てられますが、この案件では一歩踏み込んで、「観測データを自分の手元に集め、自分のサイトで公開する」ことをゴールにしました。
狙いは3つです。
- 記録が資産になる:アプリの画面で流れて消える数値を、CSV/JSONの時系列データとして蓄積する。
- 状態が共有できる:家族でも読者でも、URLひとつで「いまのバジル」を見られる。
- 技術の実証になる:センサー→クラウド→自宅サーバー→Web公開という、IoT連携の定番構成を実運用で回し切る。

アーキテクチャ ― 5つの登場人物と役割分担
構成はシンプルに言えば「エッジで測り、クラウドで中継し、NASで加工し、Webで公開する」。
それぞれの持ち場を明確に分けたのがこの基盤の骨格です。

| 層 | 担当 | 役割 |
|---|---|---|
| エッジ | Ivy本体(センサー) | 水位・気温・湿度・明るさを計測 |
| クラウド中継 | Tuya OpenAPI / Smart Life | デバイスデータの受信と認証つき配信 |
| コア | Synology NAS(DS224+) | 10分周期の観測・加工・監査パイプライン(Python 3.9)。CSV/JSONの正本管理 |
| 公開 | Webサーバー(本サイト) | FTPSで受け取った静的ダッシュボード(Vue 3+Chart.js)を配信 |
| 補助表現 | ローカルAIノード(M2 Mac mini) | 観測値の要約コメント生成。外部APIに出さない隔離構成 |
ポイントは、司令塔をクラウドではなく自宅のNASに置いたことです。
データの正本(CSV/JSON)はNASの中にあり、クラウドはあくまで「デバイスとの中継役」、Webサーバーは「配信役」。
どこかが止まっても、データの本体は手元に残ります。
設計の核① ― 14ステージを「fail-fast」で直列に
10分ごとに走るパイプラインは、観測の取得から公開まで14ステージの直列処理で構成しています。
観測ログ取得→自動給水の検知→コンディション判定→天気(外気)→タッチ操作の検知→閲覧者リアクション取得→成長日記取得→2段階のデータ加工→時系列化→補助表現の生成→コマンド処理→公開前チェック→FTPS公開、という流れです。
設計上のこだわりは、必須ステージがひとつでも失敗したら、そこで止めて公開させないこと。
実際のオーケストレータ(抜粋)はこうなっています。
# NASのタスクスケジューラから10分ごとに起動されるシェル(抜粋)
run_required_py() {
label="$1"; file="$2"; stage_log="$3"
log "$label start"
if "$PYTHON_BIN" "$SCRIPTS/$file" >> "$LOG_DIR/$stage_log" 2>&1; then
log "$label done"
else
rc=$?
log "FAILED: $label failed: exit $rc"
exit "$rc" # ← 必須ステージが失敗したら、公開まで進ませない
fi
}
run_required_py "Ivy logger" "ivy_logger.py" "ivy_logger.log"
run_required_py "Ivy condition" "ivy_condition.py" "ivy_condition.log"
run_optional_py "Ivy weather" "ivy_weather.py" "ivy_weather.log"
# …(中略:全14ステージ)…
run_required_py "pre-publish check" "ivy_pre_publish_check.py" "ivy_publish.log"
run_required_py "Ivy FTPS publish" "publish_ftps.py" "ivy_publish.log"
「途中で失敗しても、とりあえず公開だけはしておく」という設計にすると、
いつか必ず壊れた・欠けたデータを世界に配信する日が来ます。
逆にfail-fastで止めれば、公開側には最後に監査を通った正常版が残り続ける。
ライブと言いつつ、実は「最後に健全だった状態」を常に見せる仕組みです。
天気のような「なくても成立する」ステージだけを任意(optional)に分けているのも同じ思想です。
設計の核② ― 重い処理は「変化があったときだけ」動かす
ダッシュボードには、観測値から生成される「Ivyのひとこと」のような補助表現があります。
この生成は、M2 Mac mini上のローカル実行環境(Ollama)に任せているのですが、10分ごとに毎回呼ぶ設計にはしていません。
採用したのは差分トリガー方式です。
パイプラインは毎回、現在の観測データから入力ファイルを生成し、前回の状態と比較して、「意味のある観測変化」があったときだけ生成処理を起動します。水位も気温も変わっていないのに同じコメントを作り直すのは、計算資源の無駄でしかないからです。
これはローカルAIに限らない、定周期システム全般の設計原則だと考えています。「毎回やる処理」と「変化したときだけやる処理」を分ける――この一手間で、10分周期という高頻度の運用でも、NASにもMac miniにも無理をさせずに回り続けます。
なお生成処理を外部APIでなく隔離したローカル環境に置いているのは、観測データを外に出さないためと、外部サービスの仕様変更に公開パイプラインを巻き込まれないためです。
設計の核③ ― 公開前の「監査ゲート」
14ステージの終盤、FTPS公開の直前にはpre-publish check(公開前監査)という関所を置いています。
ここでは2種類の検査をしています。
- データ整合の検査:外気湿度などの必須項目が揃っているか、加工後のJSONが壊れていないか。
- 秘密情報の混入検査:公開予定ファイルに
access_token・client_secret・FTPパスワード・APIキーといった秘密情報のパターンが紛れ込んでいないかを正規表現でスキャン。
2つ目は地味ですが、個人開発でこそ効きます。
IoT連携は性質上、トークンや認証情報を大量に扱います。加工スクリプトのバグやデバッグ出力の消し忘れひとつで、認証情報入りのJSONを全世界に公開してしまう事故は現実に起こり得る。
だから「人間が気をつける」ではなく、パイプライン自身に最後の関所を持たせる。
当ブログで繰り返し書いてきた「公開前に監査を通す」という姿勢を、そのままコードにした部分です。
「壊れたデータを公開しない」「秘密を漏らさない」は、人の注意力ではなく仕組みで守る。WordPressの保守でバックアップと検証環境を挟むのと、まったく同じ発想です。
技術選定の光と影 ― 何を割り切ったか
この構成は「正解」ではなく、制約の中での交換条件(トレードオフ)の束です。
主要な3つを正直に並べます。
| 選定 | 光 ― 得たもの | 影 ― 引き受けたリスク |
|---|---|---|
| Tuyaクラウド経由でデバイス接続 | 無線プロトコルの自前実装が不要。認証・暗号化済みのAPIで最短接続 | 外部サービスへの依存。API仕様変更・サービス終了・トークン失効に運用が振り回される |
| 司令塔を自宅NASに集約 | データの正本が手元に残る。常時稼働・省電力・追加費用なし | 単一障害点。NASが止まればその間の観測は欠測になる |
| 公開側はビルドレスの静的ファイル(Vue 3+Chart.js) | 公開サーバーに実行環境が不要。WordPressにも負荷や依存を持ち込まない | 検索エンジンに弱い(中身はJS描画)。ビルド工程がないぶん、フロントの規模拡大には不向き |
影の側も、無防備に受け入れているわけではありません。
NASが止まっても公開側には最後の正常版が静的ファイルとして残り続けるので、ダッシュボードが白画面になることはない(更新が止まるだけ)。
検索エンジンに弱い点は、まさにこの記事のように「文脈と解説はWordPressの記事が担い、ライブ表示は静的アプリが担う」という分業で補っています。
欠測だけは受け入れる――どの影を消し、どの影と付き合うかを決めるのが設計だと考えています。
WordPressとの同居 ― 「所有権の分離」という設計
このダッシュボードは本サイト(WordPress)の /ivy/ に同居していますが、WordPressはこのディレクトリに一切書き込みません。
所有者はあくまでNASのパイプラインで、10分ごとにFTPSで上書き配信される「NASの領土」です。
WordPress側から見れば読み取り専用の隣人であり、プラグイン更新もテーマ変更もキャッシュ削除も、ダッシュボードには指一本触れない。逆にパイプラインが何をどう更新しても、WordPress本体は壊れない。
一方で、完全に没交渉なわけでもありません。
閲覧者の応援リアクションや成長日記の写真記録は、自作プラグイン「Ivy Growth Hub」がWordPress側でREST API(ivy/v1/…)とカスタム投稿タイプとして受け持ち、NASのパイプラインが10分ごとに読みに来る形で連携しています。
つまり――
- /ivy/ ディレクトリの正本はNAS(WordPressは読むだけ)
- 参加データ(リアクション・成長日記)の正本はWordPressのDB(NASは読むだけ)
という相互に読み取り専用の疎結合です。
どちらのシステムも、相手の領土を書き換えない。
この「所有権の分離」のおかげで、両者は互いの障害や更新に巻き込まれずに同居できています。
会員サイトの事例で書いた「心臓部は切り離せる場所に置く」と同じ原則が、システム間の境界線にも効いている、というわけです。
いま、動いているものを見る
ここまでの設計が実際にどう見えるかは、説明するよりライブを見ていただくのが早いと思います。
気分・快適度・センサー詳細・グラフ・外気との比較、そして成長日記まで、すべて10分ごとの自動更新で動いています。閲覧者が押せる応援ボタンもあります(それが先ほどのREST連携です)。
この記事で解説した14ステージのパイプラインが、いまも10分ごとに更新し続けています。PC・スマホどちらでも見られます。
まとめ ― 小さなIoTでも、運用の原則は同じ
植木鉢ひとつの観測システムでも、作り切って運用し続けるために必要だったのは、大規模システムと同じ3つの原則でした。
- 壊れたデータを公開しない:fail-fastの直列パイプラインと公開前監査で、「最後に健全だった状態」だけを見せる。
- 重い処理は変化したときだけ:差分トリガーで、高頻度運用と省資源を両立する。
- 所有権を分ける:NASとWordPress、それぞれの正本を決め、相手の領土に書き込まない。
WordPressで「動くもの」を作り切るという意味では、会員制コミュニティの構築事例、プラグインを本番運用まで持ち込んだ事例と地続きの話です。
サイト制作の先にある「データと運用の設計」まで含めて、WordPressは十分に戦える土台になります。






