Skip to content

Progress Log: ArduinoJsonの使い方ガイド

Task Description

ArduinoJson(Arduino/ESP32向けのJSONライブラリ)の使い方をKurikintonsプロジェクトの文脈で整理しました。

以下の観点から調査・整理しました。

  • ArduinoJsonの概要と特徴
  • インストール方法
  • 基本的な使い方(オブジェクト作成、シリアル化、デシリアル化)
  • JSON配列の操作
  • JSONL形式での実装例
  • ESP32での実装上の注意点
  • メモリ効率の考慮

Outcome

ArduinoJsonの包括的な使用ガイドを作成しました。

ArduinoJsonについて

ArduinoJson は、Arduino、ESP32などのマイコンボード向けに最適化されたC++のJSONライブラリです。

主な特徴:

  • メモリ効率が高い(スタック/静的メモリ使用)
  • コンパイル時のメモリ量決定(動的メモリアロケーション最小化)
  • シンプルで直感的なAPI
  • Arduino IDEおよびPlatformIOで標準サポート
  • ドキュメントが豊富

バージョン情報:

  • 現在の最新バージョン: v7.x
  • Kurikintonsで使用可能: v6.x~v7.x

インストール方法

PlatformIOでのインストール

platformio.iniに以下を追加:

[env:esp32dev]
lib_deps =
    ArduinoJson @ ^7.0.0

または、以下のコマンドで自動追加:

pio lib install "ArduinoJson"

Arduino IDEでのインストール

  1. スケッチ → ライブラリをインクルード → ライブラリを管理
  2. 「ArduinoJson」を検索
  3. インストール

基本的な使い方

  1. 初期化(静的メモリ確保)
  2. 初期化(動的メモリ確保)
  3. JSON値の代入
  4. JSON値の出力(JSONL形式)

  5. イベントごとにデータを逐次送信する

  6. JSON構造はフラットにする。ネストや配列は使わない

1. 初期化(静的メモリ確保)

#include <ArduinoJson.h>

void setup() {
  // 256バイトのスタックメモリでKurikintons検出データドキュメントを作成
  StaticJsonDocument<256> doc;

  // 検出データ用のフィールドを確保
  doc["signal1"] = 0;        // チャネル1信号値
  doc["signal2"] = 0;        // チャネル2信号値
  doc["signal3"] = 0;        // チャネル3信号値
  doc["adc"] = 0;            // ADC値
  doc["temp"] = 0.0;         // 温度
  doc["pressure"] = 0.0;     // 気圧
  doc["humidity"] = 0.0;     // 湿度
  doc["uptime_ms"] = 0;      // アップタイム(ミリ秒)
  doc["duration_us"] = 0;    // イベント間隔(マイクロ秒)

  Serial.println(F("Detection document created successfully"));
}

2. 初期化(動的メモリ確保)

大規模なJSONが必要な場合は動的なヒープを確保する

// 256バイトのヒープメモリでKurikintons検出データドキュメントを作成
DynamicJsonDocument doc(256);

// 検出データを設定
doc["signal1"] = 95;
doc["signal2"] = 87;
doc["signal3"] = 92;
doc["adc"] = 2048;
doc["temp"] = 25.35;
doc["pressure"] = 1013.25;
doc["humidity"] = 45.67;
doc["uptime_ms"] = 123456;
doc["duration_us"] = 1234567890;

ESP32での推奨:

  • StaticJsonDocumentを使用(スタックは十分にある)
  • 小~中規模データは256~512バイト
  • 大規模データは1024~2048バイト

3. JSON値の代入

// setupで初期化済み
StaticJsonDocument<256> doc;

// 検出データを設定
doc["signal1"] = 95;                // チャネル1信号値(整数)
doc["signal2"] = 87;                // チャネル2信号値(整数)
doc["signal3"] = 92;                // チャネル3信号値(整数)
doc["adc"] = 2048;                  // ADC値(整数)
doc["temp"] = 25.35;                // 温度(浮動小数点数)
doc["pressure"] = 1013.25;          // 気圧(浮動小数点数)
doc["humidity"] = 45.67;            // 湿度(浮動小数点数)
doc["uptime_ms"] = 123456;          // アップタイム(ミリ秒)
doc["duration_us"] = 1234567890;    // イベント間隔(マイクロ秒)

4. JSON形式の出力(シリアル化)(JSONL形式)

StaticJsonDocument<256> doc;

// Kurikintonsの検出データを設定
doc["signal1"] = 95;
doc["signal2"] = 87;
doc["signal3"] = 92;
doc["adc"] = 2048;
doc["temp"] = 25.35;
doc["pressure"] = 1013.25;
doc["humidity"] = 45.67;
doc["uptime_ms"] = 123456;
doc["duration_us"] = 1234567890;

// コンパクト形式でシリアル出力(JSONL:1行JSON)
serializeJson(doc, Serial);
Serial.println();  // 改行で1行を完成

シリアル出力:

{"signal1":95,"signal2":87,"signal3":92,"adc":2048,"temp":25.35,"pressure":1013.25,"humidity":45.67,"uptime_ms":123456,"duration_us":1234567890}

2. 可視形式(デバッグ用)

StaticJsonDocument<256> doc;
doc["signal1"] = 95;
doc["signal2"] = 87;
doc["signal3"] = 92;
doc["adc"] = 2048;
doc["temp"] = 25.35;

// 見やすい形式で出力(デバッグ時のみ使用)
serializeJsonPretty(doc, Serial);

シリアル出力:

{
  "signal1": 95,
  "signal2": 87,
  "signal3": 92,
  "adc": 2048,
  "temp": 25.35
}

JSONL形式での実装例

ESP32側:検出データをJSONL形式で出力

#include <ArduinoJson.h>
#include "cosmic_detector.h"

// グローバル変数
uint64_t last_event_time_us = 0;

void setup() {
  Serial.begin(115200);
  delay(3000);  // デバイス安定化待機

  cosmic_detector_init();
  last_event_time_us = micros();
}

void loop() {
  cosmic_detection_t detection = cosmic_detector_read();

  if (detection.detected) {
    // JSON形式で検出データを記録
    StaticJsonDocument<256> doc;

    // タイムスタンプ
    uint64_t current_time = micros();
    doc["timestamp_us"] = (uint32_t)(current_time / 1000);  // ミリ秒単位

    // 検出データ
    doc["signal1"] = detection.signal1;
    doc["signal2"] = detection.signal2;
    doc["signal3"] = detection.signal3;

    // センサー値
    doc["adc"] = analogRead(ADC_PIN);
    doc["temp"] = bme280_read_temperature();
    doc["pressure"] = bme280_read_pressure();
    doc["humidity"] = bme280_read_humidity();

    // イベント間隔(マイクロ秒)
    uint64_t duration_us = current_time - last_event_time_us;
    doc["duration_us"] = duration_us;

    // JSONL形式で出力(1行)
    serializeJson(doc, Serial);
    Serial.println();  // 改行で1行を終了

    // 時刻を更新
    last_event_time_us = current_time;

    // LED点灯など
    digitalWrite(LED_PIN, HIGH);
    delay(50);
    digitalWrite(LED_PIN, LOW);
  }

  delay(10);  // 検出サイクル
}

メモリ使用量の計算

ドキュメントサイズの決定方法

メモリ使用量(バイト)= キー + 値 + JSON構造オーバーヘッド

サンプル計算(検出データ):

{
  "signal1": 100,           // キー8 + 値3 + 構造3
  "signal2": 85,            // キー8 + 値2 + 構造3
  "signal3": 92,            // キー8 + 値2 + 構造3
  "adc": 2048,              // キー3 + 値4 + 構造3
  "temp": 25.35,            // キー4 + 値5 + 構造3
  "pressure": 1013.25,      // キー8 + 値7 + 構造3
  "humidity": 45.67,        // キー8 + 値5 + 構造3
  "duration_us": 1234567890 // キー11 + 値10 + 構造3
}

推定メモリ: 150~200バイト

推奨サイズ:

  • 単純な検出データ: 256バイト
  • 複雑なセンサー設定: 512バイト
  • 配列を含むデータ: 1024バイト以上

ESP32でのメモリ確認

void print_heap_info() {
  Serial.print(F("Free heap: "));
  Serial.print(ESP.getFreeHeap());
  Serial.println(F(" bytes"));

  Serial.print(F("Largest free block: "));
  Serial.print(ESP.getMaxAllocHeap());
  Serial.println(F(" bytes"));
}

デバッグとトラブルシューティング

デシリアル化エラーの確認

const char* json = "{invalid json}";
StaticJsonDocument<256> doc;

DeserializationError error = deserializeJson(doc, json);
if (error) {
  Serial.print(F("Parse error: "));
  Serial.println(error.f_str());  // 詳細エラーメッセージ
}

一般的なエラー:

  • NotDeserialized: JSON解析なし
  • InvalidInput: 入力が無効
  • NoMemory: メモリ不足
  • IncompleteInput: 入力が不完全

ドキュメントサイズが足りない場合

// 警告を確認
bool overflowed = doc.overflowed();
if (overflowed) {
  Serial.println(F("Document overflowed!"));
  // サイズを増やす
}

Kurikintonsでの推奨設定

platformio.iniの設定

[env:esp32dev]
lib_deps =
    ArduinoJson @ ^7.0.0

インクルードと初期化

#include <ArduinoJson.h>

// グローバルドキュメント(再利用)
StaticJsonDocument<256> detection_doc;
StaticJsonDocument<512> config_doc;

void setup() {
  Serial.begin(115200);
  // ArduinoJsonは追加初期化不要
}

パフォーマンスのベストプラクティス

  • ドキュメントをグローバルで作成(毎回の割り当て回避)
  • StaticJsonDocumentを使用(スタック使用でヒープフラグメント防止)
  • 不要になったドキュメントは clear() で再利用
  • シリアル出力時は改行後にフラッシュ(Serial.flush()

Learnings

  1. ArduinoJsonはESP32向けに最適化: 固定サイズメモリ確保でヒープフラグメント回避
  2. StaticJsonDocumentが基本: 開発時のシンプルさと実運用の信頼性を両立
  3. JSONL形式出力は簡単: serializeJson()Serial.println() で1行JSON
  4. メモリサイズの事前計算が重要: オーバーフロー防止と効率化のため
  5. デシリアル化時のエラー処理: 外部入力を扱う場合は必須

Next Steps

  1. Kurikintonsへの統合:
  2. ArduinoJsonをpluginio.iniに追加
  3. 検出データのJSONL形式化
  4. シリアル出力を新形式に変更

  5. 検出ログ機能の実装:

  6. SDカードへのJSONL保存(オプション)
  7. ホスト側でのログ解析Python スクリプト

  8. テストと検証:

  9. JSON形式での動作確認
  10. メモリ使用量の測定
  11. パフォーマンステスト(シリアル出力速度)