第10章 · 実例 1

温室の温度制御を Python で書いて C に翻訳する

第 09 章「組み込みを作る ── Pythonで考え、Claudeに翻訳させる」の主張を裏付ける。

章のどの主張に対応するか

ESP32 で温度センサ判定の開発:

  • C++ で書き始める従来法: 2 週間
  • Python で検証してから MicroPython に転送する新法: 2 日
  • 7 倍速い

実機焼き直しサイクル:

  • C++ + 書き込み: 約 2 分
  • MicroPython 転送: 約 5 秒
  • Python シミュレーション: 約 0.1 秒

(章本文「実例: 数字で見る」より)

実測 ── 4 時間ぶん(240 分)のシミュレーションが 0.3 ms(0.0003 秒)。 実機焼き直し 2 分と比べて 約 400,000 倍速い

やること

温室のヒーター制御 ── ESP32 + 温度センサ + リレー出力 ── を、 実機を触る前に Python で完全に検証する

  1. ロジックを Python で書く: sensor_logic.py (HeaterController)
  2. シミュレーションする: simulate.py で 4 時間ぶんの温度推移を生成
    • 仕様: 目標 22℃ ±1.5℃ ヒステリシス、連続 30 分稼働で強制 5 分休憩、 センサ 5 分無応答でフェイルセーフ
  3. ユニットテストする: ヒステリシス境界 / アラーム発火 / 状態維持
  4. C に翻訳する: sensor_logic.c (Claude が Python を読んで翻訳)
  5. ビルドして確認する: gcc で C コードがコンパイル通ることを確認

実機 (ESP32 + ESP-IDF) に焼くのは、ロジックが完全に動いてから。 実機での試行錯誤がほぼゼロになる。

構成

example-1/
├── README.md
├── sensor_logic.py    ── 温度制御ロジック(HeaterController クラス)
├── sensor_logic.c     ── 同じロジックの C 実装(ESP32 移植イメージ)
├── simulate.py        ── 熱モデル + 4 時間シミュレーション
├── Makefile
├── results.md
└── out/
    ├── summary.json    ── シミュレーション集計
    ├── trace.csv       ── 1 分ごとの温度・ヒーター状態
    └── ascii-chart.txt ── 温度推移の ASCII プロット

実行

make clean && make all          # シミュレーション + ユニットテスト
make compile-c                  # C コードがコンパイル通ることを確認

なぜこれが「実例」になるのか

組み込み開発が遅い理由は、実機ループの遅さにある:

コード書く → コンパイル → ESP32 に書き込み → 起動待ち → 動作確認 → デバッグ
   ↑__________________________________________ 1 サイクル 2 分 _______________|

これを 1 日 50 回繰り返したら、コンパイルと書き込みだけで 100 分

Python に置き換えると:

コード書く → python3 simulate.py → 結果を見る
   ↑__________ 1 サイクル 0.3 ms _______|

400,000 倍速い。バグの大半は実機を触る前に潰せる

ロジックが完全に動いたら、Claude に「これを ESP-IDF で動く C に翻訳して」 と頼む。返ってきた C をコンパイルして、実機に書き込む。 移植時のバグ混入はほぼゼロ(同じ判定式、同じ状態遷移を辿るため)。

これが章で言う「Python で考えて、Claude に C に翻訳させる」の 最小実演。


計測結果 — 第 09 章 example-1

実行環境: Linux 6.18 / Python 3.x / gcc 13

シミュレーション(主目的)

=== シミュレーション結果 ===
  シミュレーション時間: 240 分(4 時間)
  実行時間: 0.3 ms
  ヒーター ON 分数: 94 / 240
  温度範囲: 18.0〜23.84℃ (平均 21.87℃)
  発生イベント: 20 件
比較 サイクル時間
実機 (C + ESP32 書き込み) 2 分
実機 (MicroPython 転送) 5 秒
シミュレーション (このスクリプト) 0.3 ms
実機 / シミュレーション 約 400,000 倍

イベントログ(out/summary.json 抜粋)

t=   0 ON  (18.0℃ ≤ 20.5)
t=  16 OFF (23.6℃ ≥ 23.5)
t=  32 ON  (20.4℃ ≤ 20.5)
t=  42 OFF (23.7℃ ≥ 23.5)
t=  59 ON  (20.5℃ ≤ 20.5)
t=  64 ALARM ヒーター強制 OFF (センサ 5 分無応答)
t=  67 復旧 (センサ復活)
t=  74 ON  (20.4℃ ≤ 20.5)
...

仕様通りに動いている:

ユニットテスト

=== ユニットテスト(ヒステリシス境界) ===
  ✓ 18℃ で ON
  ✓ 23.6℃ で OFF
  ✓ 22℃ では状態維持
  ✓ センサ 5 分無応答でアラーム
  4 件すべて pass

実機でテストすると、4 件のテストにそれぞれ 30 分(温度を変える時間)。 合計 2 時間以上。Python では 数 ms

C への翻訳(sensor_logic.c)

#define TARGET                  22.0f
#define HYSTERESIS               1.5f
#define MAX_ON_MINUTES          30

void controller_tick(uint32_t minute) {
    float temp = read_temperature_sensor();
    bool sensor_ok = !isinf(temp);

    if (!sensor_ok) {
        ctrl.silent_minutes++;
        if (ctrl.silent_minutes >= SENSOR_TIMEOUT_MINUTES) {
            ctrl.heater_on = false;
            set_heater(false);
            trigger_alarm("...");
        }
        return;
    }
    ...
}

閾値定数も状態遷移も Python 版と完全一致make compile-c で gcc でコンパイル通ることを確認:

=== C コードのコンパイル(ロジック層のみ、HAL なしで構文チェック) ===
  ✓ コンパイル成功 (3640 bytes)

実機 (ESP32) では read_temperature_sensor() set_heater() trigger_alarm() の 3 関数を ESP-IDF で実装するだけ。ロジック本体は触らない

実機への移行手順(参考)

  1. Python でロジック完成 ✓
  2. ユニットテスト 4 件 pass ✓
  3. シミュレーションで 4 時間ぶんの動作確認 ✓
  4. Claude に「ESP-IDF で動く C に翻訳して」 → sensor_logic.c
  5. ハードウェア依存の HAL 関数 (3 個) を ESP-IDF で書く
  6. idf.py build flash で ESP32 に焼く
  7. 実機で 1 時間動作確認 → デプロイ

実機を触るのは 最後の 30 分だけ。残りはすべて開発機の上で完結する。

再現手順

make clean && make all        # シミュレーション + テスト
make compile-c                # C コードがコンパイル通る確認

ファイル一覧

out/

第10章「組み込みを作る ── Pythonで考え、Claudeに翻訳させる」に戻る