私は 2025 年からアグリテック系のスタートアップで、ハウス 12 棟に約 300 台の Raspberry Pi Pico 2 W を展開しています。温湿度・CO₂・土壌水分センサーを 30 秒周期で読み取り、当初はローカルの閾値判定だけで灌水を制御していました。ある日プロダクトオーナーから「朝の Slack に『ハウス 3 は要注意』のような自然言語サマリーを流したい」と頼まれ、組込みボードから直接 LLM を叩く構成を検証しました。本稿は embassy-rs + HolySheep AI (今すぐ登録) で DeepSeek V4 を呼び、300 台規模でも月額コストが現実的になる設計に落ち着いた経緯を、アーキテクチャ・並行実行・パフォーマンス・コストの 4 軸でまとめます。
1. なぜ Pico 2 W から直接 LLM を叩くのか
最初の PoC は「センサーデータをゲートウェイ (RPi 4B) に集めて、そこから OpenAI を叩く」二段構成でした。2 週間回して以下の問題が顕在化しました。
- ゲートウェイ単一障害点 — ハウス 12 棟のうち 2 棟は Wi-Fi 中継が不安定で、ゲートウェイがフリーズすると 24 時間分のデータが欠損する。
- L232 コスト — ゲートウェイ側で GPT-4o を 5 分ごとに叩くと月 $430。300 台規模を想定すると集約構成でも限界。
- レイテンシ — Slack 通知が 8〜15 秒遅れ、CO₂ 急変のような即時対応が必要なケースに間に合わない。
そこで「末端の Pico 2 W が自律的に DeepSeek V4 を呼び、要約結果だけゲートウェイに送る」エッジ LLM 構成へ舵を切りました。DeepSeek V4 は公式ルートだと米ドル建て+クレジットカード決済で為替手数料 6〜8% が乗りますが、HolySheep AI は ¥1=$1 のレートかつ Alipay・WeChat Pay に対応しており、創業初期のキャッシュバーンを抑えられるのが決め手でした。公式クレジットカード換算 (¥7.3=$1) と比較して 85% の為替コスト削減になります。
2. アーキテクチャ全体像
Pico 2 W (RP2350 デュアルコア Cortex-M33 @ 150MHz / 264KB SRAM) に Wi-Fi 4 (CYW43439) と TLS 1.2 を載せ、センサー読取・推論トリガ・HTTP クライアントを Embassy の非同期タスクとして並列化します。LLM との会話そのものはステートレスに保ち、I2C 経由の BME680 と SCD41 だけコンテキストとして毎回送信します。
┌──────────────┐ I2C ┌──────────────┐
│ BME680/SCD41 │◄──────────►│ Pico 2 W │
└──────────────┘ │ RP2350 │
│ ┌──────────┐ │
│ │Embassy RT │ │
│ ├──────────┤ │
│ │task: i2c │ │──┐
│ │task: http │ │◄─┤ channel<SensorData,16>
│ │task: idle │ │ │
│ └──────────┘ │ │
└──────┬───────┘ │
│ TLS 1.2 │
▼ │
api.holysheep.ai/v1 │
DeepSeek V4 endpoint│
│ │
▼ │
┌──────────────┐ │
│ Slack/MQTT │◄┘
└──────────────┘
3. プロジェクトセットアップ
ツールチェインは rustup target add thumbv8m.main-none-eabihf で固定し、Embassy の HAL を使います。memory.x でヒープ領域を明示的に切り出し、JSON バッファ用に 16KB を確保するのがポイントです。
# rust-toolchain.toml
[toolchain]
channel = "1.82.0"
targets = ["thumbv8m.main-none-eabihf"]
profile = "minimal"
components = ["rust-src", "llvm-tools"]
Cargo.toml
[package]
name = "pico2w-deepseek"
version = "0.4.1"
edition = "2021"
[dependencies]
embassy-executor = { version = "0.5", features = ["rp", "arch-cortex-m", "executor-thread"] }
embassy-rp = { version = "0.3", features = ["rp235xa", "binary-info", "defmt", "time-driver", "unstable-pac"] }
embassy-net = { version = "0.4", features = ["rp235xa", "dhcpv4", "tcp", "dns", "medium-ethernet"] }
embassy-time = { version = "0.3", features = ["generic-queue"] }
embedded-tls = { version = "0.17", default-features = false, features = ["aes-gcm", "sha256"] }
embedded-io = "0.6"
heapless = "0.8"
serde = { version = "1", features = ["derive"] }
serde-json-core = "0.6"
defmt = "0.3"
defmt-rtt = "0.4"
panic-probe = { version = "0.3", features = ["print-rtt"] }
[profile.release]
opt-level = "s"
lto = "fat"
codegen-units = 1
debug = false
incremental = false
panic = "abort"
4. TLS ハンドシェイクと接続プール
Pico 2 W の CYW43439 は TLS を CPU で処理する必要があるため、ハンドシェイクだけで 650〜900ms かかります。私は 30 秒に 1 リクエストのレート制限を守りつつ、TLS セッションを「使い切り」にせず、可能なら 5 分以内はセッション再開 (TLS-PSK 風の実装) で再利用しています。HolySheep のエンドポイントは APAC リージョンからの平均レイテンシが 38ms と安定しているため、エッジ側の最適化よりセッション再利用のほうが効果が大きいです。
use embassy_net::{tcp::TcpSocket, dns::DnsQueryType, Stack};
use embedded_tls::{TlsContext, TlsConnection, TlsError, BlockTls};
use heapless::Vec;
const HOLYSHEEP_HOST: &str = "api.holysheep.ai";
const HOLYSHEEP_PORT: u16 = 443;
const API_KEY: &str = "YOUR_HOLYSHEEP_API_KEY";
pub async fn open_secure_session<'a>(
stack: &Stack<'static>,
rx_buf: &mut [u8; 4096],
tx_buf: &mut [u8; 4096],
tls_buf: &mut [u8; 16384],
) -> Result<TlsConnection<'a, BlockTls<TcpSocket<'static>>>, TlsError> {
let addr = stack
.dns_query(HOLYSHEEP_HOST, DnsQueryType::A)
.await
.map_err(|_| TlsError::IoError)?;
let remote = embassy_net::IpAddress::Ipv4(addr[0]);
let mut socket = TcpSocket::new(*stack, rx_buf, tx_buf);
socket.set_timeout(Some(embassy_time::Duration::from_secs(8)));
socket.connect((remote, HOLYSHEEP_PORT)).await
.map_err(|_| TlsError::IoError)?;
let mut ctx = TlsContext::new(tls_buf);
let mut tls = TlsConnection::new(socket, &mut ctx);
// HolySheep は SNI 必須 + TLS 1.2 / ECDHE-RSA-AES128-GCM-SHA256 ネゴ済み
tls.open(TlsConnectionType::Tls12, HOLYSHEEP_HOST, &[]).await?;
Ok(tls)
}
5. 並行実行制御 — Embassy タスク分割
RP2350 の 2 コアを使い切るため、Embassy の executor-thread 機能でコア 0 に Wi-Fi/HTTP を、コア 1 に I2C センサー読取を配置します。タスク間通信は heapless::spsc::Queue ベースのチャンネルで、ヒープ割り当てゼロです。I2C リードが 30 ms で完了する間に、HTTP タスクは TLS を確立し、DeepSeek V4 の応答待ちで 1.5〜3 秒スリープするだけ — 全体の CPU 使用率は平均 12% に収まります。
use embassy_sync::blocking_mutex::raw::CriticalSectionRawMutex;
use embassy_sync::channel::{Channel, Receiver, Sender};
use embassy_time::{Timer,