メインコンテンツへスキップ
友田 陽大
事前予約受付中

同じアルゴリズムをTypeScriptと組込みCに二重実装し、CIで縫い留める

水温とTDSの変化率で異常を検知するIoT。クラウドとファームウェアに同じ判定を実装し、ゴールデンフィクスチャの契約テストで固定した 魚まもり の設計記録

水温・TDSを24時間監視してLINEに通知する水槽モニター「魚まもり」

事前予約を受付中です(¥500デポジット・全額返金可)。ハードウェアの出荷はまだ始まっていません。

なぜポートフォリオにあるか

ハードを含む案件は、「ファームの人」「クラウドの人」「アプリの人」の境界で壊れます。魚まもりでは、同じ判定ロジックをデバイス側とクラウド側の両方に持つ必要が出たので、両方書いて、ゴールデンフィクスチャの契約テストで縫い留めました。境界をまたいで一人で通した実例です。

なぜ作ったか

観賞魚のブリーダーは、10本から100本の水槽を1部屋で回します。ヒーターの故障や水漏れは、気づくのが数時間遅れるだけで取り返しがつきません。

一方で、この領域の測定は厄介です。**電極は使ううちに少しずつ校正がずれる。**絶対値のしきい値で警報を出す設計にすると、ドリフトのたびに誤報が出て、やがて誰も通知を見なくなります。

いちばん難しかったこと

誤報を出さずに異常を捕まえるには、絶対値ではなく「変化の速さ」を見る必要がありました。ここまでは設計として素直です。

難所は、その判定をクラウドとデバイスの両方が必要としたことでした。デバイスは通信が切れていても自分で異常を検知したい。クラウドは複数台をまたいで判定したい。同じロジックが2箇所に必要で、しかも一方は TypeScript、もう一方は ESP32-C3 上の C です。

2つの実装が少しずつズレていく未来が見えていました。

どう決めたか(と、捨てたもの)

両方書いて、契約テストで縫い留めることにしました。

判定は窓内のサンプルに最小二乗回帰をあて、傾き × 窓幅を「実効Δ」とします(端点の差分よりノイズに強い)。これを TypeScript 側と組込みC側の双方に実装し、**C実装が決定論的な固定入力から生成した実出力をゴールデンフィクスチャとしてリポジトリに置き、TypeScript側のテストがそれに照合します。**片側だけ変更すると、必ずCIが割れます。

2つの部署が別々に作った書類を、毎回同じ原本と突き合わせる決まりです。片方だけ書き換えると、その場で差し戻されます。

捨てたものは DRY です。同じアルゴリズムのコードが2つある状態を許容しました。言語もランタイムも違う以上コードは共有できないので、共有するのは「振る舞い」の方だと決めました。

実際に動いているもの

組込み側は16点のリング内で回帰を回し、桁落ちを避けるため double で集計します。しきい値超えがN回連続したときだけ発報します。

電源断への備えとして、フラッシュ上のリングバッファは書きかけのレコードがCRC検査に落ち、そのレコード1件だけが破棄される構造にしました。前後の正常なレコードは必ず生き残ります。フラッシュ操作は差し替え可能にしてあるので、同じCコードを実機なしでホストテストできます。

デバイス認証は三者間のHMACで、**nonceストアを持ちません。**サンプルは (device_id, seq, ts) で冪等、hello/config は読み取り系なのでリプレイが無害だからです。「持たない」という判断の根拠を設計として書き残してあります。

受信経路は先にACKを返し、アラート評価と集計は応答後に回します。

コードから数えられる値

2026-07-03 から 07-27 までの30コミット。ファームウェアは C / ヘッダで7,429行、ESP-IDF 5.2.2。データベースは25 RLSポリシーで、デバイス秘密鍵のテーブルだけはポリシーを一切張らず、PostgREST 経由では1行も取れないようにしています(平文は箱のQRにしか存在しません)。時系列は月次パーティションで、pg_cron ジョブ7本が集計・保持期間・パーティション保守を回します。

CIには2つ変わったゲートがあります。copy-lint はマーケティングのコピーがハードにできないこと(例:アンモニアの検出)を主張したらビルドを落とします。21のルールにはそれぞれ理由が書いてあります。bom-lint は部品表の原価計算が価格モデルのCOGSから10%以上ずれたら落とします。ファームウェアのビルドには容量ゲートがあり、1,600KBのOTAスロットに対して1,400KBを超えると失敗します。

これは、あなたの案件の何を証明するか

センサー、ゲートウェイ、時系列データベース、通知、課金、そして「言えること/言えないこと」をCIで縛るところまで、一人で通した実例です。工場・農業・設備保全・見守りなど、現場のセンサーをクラウドに上げる案件で、レイヤー間の翻訳コストがかかりません。

特に、同じロジックを2つのランタイムに持たざるを得ないときの同期方法(ゴールデンフィクスチャによる契約テスト)は、IoTに限らずモバイル/サーバ間の計算一致にもそのまま使えます。

ハードとクラウドをまたぐ開発は、SaaS・業界DX開発のサービスの範囲でお請けしています。業界特化のDX案件については林業DXの実績もご覧ください。

コードから数えられる値

Δ検知の実装
2言語TypeScript と ESP32-C3 向け組込みC。ゴールデンフィクスチャの契約テストで固定し、片側だけ変えるとCIが割れる
ファームウェア
7,429行ESP-IDF 5.2.2 / C。フラッシュ操作を差し替え可能にし、実機なしでホストテストできる
RLSポリシー
25本デバイス秘密鍵のテーブルはポリシーを一切張らず、service_role 専用にしている
コピーリンタ
21ルールハードにできないこと(アンモニア検知など)を書いたらCIが落ちる

よくある質問

魚まもりはいま購入できますか?
事前予約フェーズです。¥500のデポジット(全額返金可)で予約を受け付けており、ハードウェアの出荷はまだ始まっていません。
何を測定できますか?
水温とTDS(総溶解固形分)です。pHは別売アドオンで追加できます。アンモニアなど測定していない項目は測れません。この線引きはCIのコピーリンタが強制しており、できないことを書いたコピーはビルドを通りません。
電極の校正がずれても誤報になりませんか?
なりにくい設計です。絶対値ではなく、窓内のサンプルに最小二乗回帰をあてて求めた変化率でアラートを出すため、ゆっくりした校正ドリフトでは発報しません。
同じ判定をクラウドとデバイスの両方に持って、ズレませんか?
ズレたらCIが落ちます。組込みC実装が決定論的な固定入力から生成した実出力をゴールデンフィクスチャとしてリポジトリに置き、TypeScript側のテストがそれに照合します。片側だけを変更すると必ず割れます。
停電するとデータは壊れませんか?
壊れません。フラッシュ上のリングバッファは電源断に耐える設計で、書きかけのレコードはCRC検査に失敗するため、そのレコード1件だけが破棄されます。前後の正常なレコードは生き残ります。
通知はどこに届きますか?
LINE・プッシュ通知・メールの3経路です。同一サイト内で多数のデバイスが同時に沈黙した場合は、1件の通知にまとめます。

同様の課題、抱えていませんか?

あなたのビジネス課題も、最新の技術で解決できます。 まずは30分の無料技術相談から、状況をお聞かせください。

自社の課題もSaaS化できるか相談する

プロジェクト単位(請負)・技術顧問、どちらにも対応可能です

自社プロダクト一覧を見る