📚

【ESP32 × PlatformIO】設定ファイル(AWS IoT)をLittleFSからNVS領域に移動

2024/05/22に公開

はじめに

現状、config.json, device.cert.pem, device.private.key, root.ca.pemなどをdata以下に置いているが、読み取りしか行わないので、NVS領域に置く方が適していると考えられる。(shadow.jsonは書き込みを行うので別)

現状の問題:LittleFSが壊れたら通信不可能

たとえば、ダウンロード処理の不具合などでLittleFSのストレージが溢れた際、証明書の読み込みごと失敗するようになってしまって、どうにもならなくなる可能性があるので、証明書やサーバーの接続情報などはNVS領域に書き込んでおき、そこから読み取るようにしたほうがよい。

以下の記事を参考に、NVS領域に証明書や設定ファイルを書き込むように修正する。ただ、参考記事はESP-IDFフレームワークを利用しているが、今回はPlatformIOを利用しているので、その部分が異なる。

https://simplyexplained.com/blog/esp-idf-store-aws-iot-certificates-in-nvs-partition/

全体の方針

  • 各種設定ファイルをdata領域から移す(ビルド時にSPIFFS領域に含まれないようにする)
  • nvs.csvファイルを作成し、csvファイルを元に、設定ファイルの情報が含まれたcerts.binファイルを生成し、ビルド時にnvs領域にデータを書き込む
  • MQTT通信やOTAアップデート時に、LittleFSからではなくcerts.binから証明書データを読み込んで通信確立する。

nvs.binファイルの生成と書き込み

NVSの特徴と名前空間

NVS(Non-Volatile Storage)は不揮発性ストレージの略で、フラッシュ メモリに保存されるキー値データベース。ESP-IDF では、Wi-Fi 認証情報や RF キャリブレーション データなどを保存するために使用される。

デフォルトの NVS パーティションには 16 KB のデータを保存できる。これは、証明書と秘密キーを保存するには十分量なので、カスタム パーティションマップを新しくする必要はない。

また、NVSは名前空間を使用する。キー/値項目を含む NVS パーティション内の「フォルダー」のようなもの。これにより、アプリ、サードパーティのコンポーネント、ESP-IDF 間の競合が防止される。

NVS CSV ファイルの作成

まず、dataディレクトリ配下に設定ファイルを置いたままだと、ビルド時にSPIFFS領域に含まれてしまうので、新しいディレクトリ(certificates)をルートディレクトリ直下に作成して、その中にNVSに含めたい設定ファイルを移動する。

my_project/
├── certificates/
│   ├── config.json
│   ├── device.cert.pem
│   ├── device.private.key
│   ├── root.ca.pem
│   └── nvs.csv
├── include/
├── lib/
├── src/
│   └── main.c
├── platformio.ini
└── ...

移動した設定ファイルや証明書を定義するNVS CSVファイル(nvs.csv)をcertificatesディレクトリに作成する。

CSV ファイルの最初のエントリは常にnamespaceエントリである必要があるので、CSVの1行目のtypeはnamespaceにする。また、namespaceのencodingとvalueは空のままにする。

https://docs.espressif.com/projects/esp-idf/en/stable/esp32/api-reference/storage/nvs_partition_gen.html

key,type,encoding,value
certs,namespace,,
config,file,string,certificates/config.json
device_cert,file,string,certificates/device.cert.pem
device_key,file,string,certificates/device.private.key
root_ca,file,string,certificates/root.ca.pem

flash_nvs.pyスクリプトの作成

NVSパーティションを生成し、フラッシュするためのスクリプトを作成する。

generate_nvs.py:generate_nvs_partition()関数

  • nvs_partition_gen.pyスクリプトを実行して、certificates/nvs.csvに定義された内容に基づいてcertificates/certs.binというバイナリファイルを生成する。
  • nvs_partition_gen.pyは Espressifによって作成されたコマンドラインツールであり、ESP-IDF に含まれている。

https://docs.espressif.com/projects/esp-idf/en/stable/esp32/api-reference/storage/nvs_partition_gen.html

import os
import subprocess
from SCons.Script import Import

Import("env")

def generate_nvs_partition(source, target, env):
    print("Generating NVS partition...")
    command = [
        "python",
        os.path.join(os.getenv('IDF_PATH'), 'components', 'nvs_flash', 'nvs_partition_generator', 'nvs_partition_gen.py'),
        "generate",
        "certificates/nvs.csv",
        "certificates/nvs.bin",
        "0x5000"
    ]
    result = subprocess.run(command, check=True)
    if result.returncode != 0:
        print("Error: NVS partition generation failed.")
    else:
        print("NVS partition generated successfully.")

env.AddPreAction("upload", generate_nvs_partition)

flash_nvs.py:flash_nvs_partition()関数

  • 生成されたcertificates/nvs.binをデバイスの0x9000アドレスにフラッシュする(=フラッシュメモリにデータを書き込む)。
import os
import subprocess
from SCons.Script import Import

Import("env")

def flash_nvs_partition(source, target, env):
    print("Flashing NVS partition...")

    bin_path = os.path.join(env['PROJECT_DIR'], 'certificates', 'certs.bin')
    
    command = [
        "python", "-m", "esptool",
        "--chip", "esp32",
        "--port", env['UPLOAD_PORT'],
        "--baud", str(env['UPLOAD_SPEED']),
        "write_flash", "0x9000", bin_path
    ]
    try:
        result = subprocess.run(command, check=True, stdout=subprocess.PIPE, stderr=subprocess.PIPE)
        print("NVS partition flashed successfully.")
    except subprocess.CalledProcessError as e:
        print(f"Error: NVS partition flashing failed. Error message: {e.stderr.decode()}")

env.AddPostAction("upload", flash_nvs_partition)

ちなみに、現在のpartition tableは下記の設定。nvs: オフセッ

続きはこちらで記載しています。
https://kazulog.fun/dev/esp32-platformio-config-to-nvs/

Discussion