asopi tech
asopi techIndie Developer

Asopitech With · 空気質モニター

Airthings 空気質グラフ with Alopex Skulk

Airthingsは、ラドンや粒子状物質、VOCなどの室内の空気を測り、アプリで過去のグラフと通知を見せる空気質モニターです。これをAlopex Skulkで作り直すと…?

更新: 2026/10/13

どんなサービスか

AirthingsのView Plusは、複数のセンサーで、ラドン、粒子状物質、VOCなど、目に見えない空気の汚れを測ります。アプリは、ラドンやVOCの値を追い、過去の空気質のグラフ、花粉の情報、通知を見せます。今回作り直すのは、測った値の記録とグラフの部分です。

データの置き場所

  • Skulkの時系列:ラドン、粒子状物質、VOCなどの値を、部屋と機器のタグを付けてSkulkに追記します。
  • 時間で区切る集計:日ごと、週ごとの平均を、時間で区切る集計で出し、グラフに渡します。
  • PromQL:値がしきい値を超えた時間を、PromQLで問い合わせて通知に使います。

センサーから値を読んで書き込むのは、機器とアプリのコードです。

配置の形

Skulkを、モニターの機器とスマホのアプリに組み込みます。記録は手元にたまり、建物や家をたくさん見る側では、同じエンジンのSkulkをサーバー、クラスタとして動かして集めます。

Alopex Skulkが合う理由

空気質のデータは、センサーごとの数値の時系列です。Skulkは、取り込み、時間の集計、PromQLを、機器やアプリに組み込んだまま行います。貯めたデータはParquetで、長い期間の分析にもそのまま使えます。

コードで見る

空気質の計測をLine ProtocolでSkulkに取り込み、読み出す中心だけを取り出したコードです(Rust)。

use alopex_skulk::ingest::line_protocol::LineProtocolDecoder;
use alopex_skulk::ingest::{IngestLimits, Ingestor};
use alopex_skulk::model::FieldValue;
use alopex_skulk::store::recovery::{RecoveryConfig, RecoveryStore};

fn main() -> alopex_skulk::Result<()> {
    std::fs::create_dir_all("airthings-data").unwrap();
    let store = RecoveryStore::open("airthings-data", RecoveryConfig::default())?;
    let limits = IngestLimits::default();
    let mut ingestor = Ingestor::new(store, limits);

    // CO2 の計測を Line Protocol で取り込む(measurement,tag field timestamp[ns])
    let base: i64 = 7_200_000_000_000;
    let now = base + 300_000_000_000;
    for (i, co2) in [450.0, 620.0, 800.0].iter().enumerate() {
        let ts = base + (i as i64) * 60_000_000_000;
        let line = format!("air,room=living co2={co2} {ts}");
        let batch = LineProtocolDecoder::new(limits).decode(line.as_bytes(), now)?;
        assert_eq!(ingestor.ingest(batch, now)?.accepted_count(), 1);
    }
    ingestor.sink_mut().flush_all()?;

    // 取り込んだ CO2 を読み出す
    for row in &ingestor.sink().read_measurement("air")? {
        if let Some(FieldValue::Float(co2)) = row.row().field("co2") {
            println!("t={} co2={co2}", row.row().timestamp());
        }
    }
    Ok(())
}

取り込みは ingest() が返った時点でWALに永続します。日ごと・週ごとの平均や、しきい値を超えた時間の抽出は、SkulkのPromQL/時間で区切る集計で出します。

センサーの時系列を機器の中で貯めて集計できるAlopex Skulkで、空気のグラフを作ってみよう。

関連リンク