【ESP32 × PlatformIO】設定ファイル(AWS IoT)をLittleFSからNVS領域に移動
はじめに
現状、config.json, device.cert.pem, device.private.key, root.ca.pemなどをdata以下に置いているが、読み取りしか行わないので、NVS領域に置く方が適していると考えられる。(shadow.jsonは書き込みを行うので別)
現状の問題:LittleFSが壊れたら通信不可能
たとえば、ダウンロード処理の不具合などでLittleFSのストレージが溢れた際、証明書の読み込みごと失敗するようになってしまって、どうにもならなくなる可能性があるので、証明書やサーバーの接続情報などはNVS領域に書き込んでおき、そこから読み取るようにしたほうがよい。
以下の記事を参考に、NVS領域に証明書や設定ファイルを書き込むように修正する。ただ、参考記事はESP-IDFフレームワークを利用しているが、今回はPlatformIOを利用しているので、その部分が異なる。
全体の方針
- 各種設定ファイルを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は空のままにする。
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 に含まれている。
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: オフセッ
続きはこちらで記載しています。
Discussion