ESP32での暗号化・復号化処理(難読化)

現時点の情報です。最新情報はM5StickC非公式日本語リファレンスを確認してみてください。

概要

ESP32では、ハードウエアでの暗号化が可能です。内部のセキュアな場所に暗号化キーを保存する方式もありますが、今回は暗号化キーをプログラム内部に保存する、あまり安全でない簡易式(難読化)の方式での、AES CBC暗号化を検証しました。

ESP32での暗号化キーについて

ESP32は外部から読み出せない領域に暗号化キーを保存することが可能です。1度しか書き込めないOTP領域に書き込むことで、安全な暗号化を可能としています。

ただし、この扱いが非常に難しく、1度しか書き込めないので失敗すると、そのESP32では暗号化できなくなります。またArduino IDEではサポートしていないため、外部ツールを利用してプログラムを転送する必要があります。

今回は暗号化キーをプログラム内部に保存する方式での、暗号化と復号化を検証しました。

サンプルスケッチ

#include <hwcrypto/aes.h>

// AES CBCで暗号化
void encodeAes(const uint8_t key[32], uint8_t iv_base[16], char* plaintext, char* encrypted) {
  // IVは変更されるのでコピーを作成
  uint8_t iv[16];
  memcpy( iv, iv_base, sizeof( iv ) );

  // 暗号化
  esp_aes_context ctx;
  esp_aes_init( &amp;ctx );
  esp_aes_setkey( &amp;ctx, key, 256 );
  int len = ( ( strlen(plaintext) + 15 ) / 16 ) * 16;
  esp_aes_crypt_cbc( &amp;ctx, ESP_AES_ENCRYPT, len, iv, (uint8_t*)plaintext, (uint8_t*)encrypted );
  esp_aes_free( &amp;ctx );
}

// AES CBCで復号化
void decodeAes(const uint8_t key[32], uint8_t iv_base[16], char* plaintext, char* encrypted) {
  // IVは変更されるのでコピーを作成
  uint8_t iv[16];
  memcpy( iv, iv_base, sizeof( iv ) );

  // 復号化
  esp_aes_context ctx;
  esp_aes_init( &amp;ctx );
  esp_aes_setkey( &amp;ctx, key, 256 );
  int len = ( ( strlen(encrypted) + 15 ) / 16 ) * 16;
  esp_aes_crypt_cbc( &amp;ctx, ESP_AES_DECRYPT, len, iv, (uint8_t*)encrypted, (uint8_t*)plaintext );
  esp_aes_free( &amp;ctx );
}

// テキストダンプ
void dumpText(char *text ){
  // ダンプ
  for ( int i = 0; i < 512; i++ ) {
    if ( text[i] == 0 ) {
      break;
    }
    Serial.printf( "%02x[%c]%c", text[i], (text[i] > 31) ? text[i] : ' ', ((i &amp; 0xf) != 0xf) ? ' ' : '\n' );
  }
  Serial.printf( "\n" );
}

void setup() {
  Serial.begin(115200);
  delay(1000);  // Serial初期化待ち

  // 暗号化キー
  uint8_t key[32];
  memset( key, 0, sizeof( key ) );

  // 暗号化ベクトル
  uint8_t iv[16];
  memset( iv, 0, sizeof( iv ) );

  // テキスト一時領域
  char plaintext[512];
  char encrypted[512];

  // 0クリア
  memset( plaintext, 0, sizeof( plaintext ) );
  memset( encrypted, 0, sizeof( encrypted ) );

  // 平文セット
  strcpy( plaintext, "Password Text String" );

  // 暗号化
  encodeAes(key, iv, plaintext, encrypted);
  Serial.println("暗号化");
  dumpText(encrypted);

  // 念の為0クリア
  memset( plaintext, 0, sizeof( plaintext ) );

  // 復号化
  decodeAes(key, iv, plaintext, encrypted);
  Serial.println("復号化");
  dumpText(plaintext);
}

void loop() {
}

出力結果

暗号化
e5[⸮] 80[⸮] ad[⸮] 45[E] 9c[⸮] ad[⸮] a0[⸮] 86[⸮] ce[⸮] 49[I] d3[⸮] e8[⸮] 8e[⸮] 54[T] 4f[O] 6c[l]
2e[.] 6e[n] df[⸮] 53[S] 3d[=] c9[⸮] fb[⸮] fb[⸮] 3b[;] f0[⸮] e0[⸮] b8[⸮] 7f[] b3[⸮] f6[⸮] 41[A]

復号化
50[P] 61[a] 73[s] 73[s] 77[w] 6f[o] 72[r] 64[d] 20[ ] 54[T] 65[e] 78[x] 74[t] 20[ ] 53[S] 74[t]
72[r] 69[i] 6e[n] 67[g] 

解説

情報が少ないだけで、暗号化と復号化は非常にかんたんに利用することができます。注意しないといけない点は、16バイト単位で処理を行うことです。

16バイトの足りない場合には、なにかのデータをパティングして処理を行います。

  encodeAes(key, iv, "PASSWORD", encrypted);

上記のように渡すことができますが、この場合PASSWORD\0の後の10バイト目から16バイト目までのデータが不明ですので、暗号化文字列がメモリ状況により異なります。ただし、復号化はできるので、本来パティングは1からの連番など実装によって異なるようです。

0埋めをしておくと、暗号化検証サイトなどで答え合わせをすることができます。

上記サイトで文字列を「Password Text String」、キーを「00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00」、IVを「00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00」にした結果が以下の画像です。

一番下の暗号化文字列が、Arduinoの出力結果と同じことが確認できます。

注意点

今回キーとIVが両方固定値の0ですので、暗号化強度は非常に低いです。この2つの値を適切に与えることで、暗号強度を高めることができます。

内部の読み取りできない領域に保存しておけば安全なのですが、かんたんにはできないのでプログラム内部にキーを保存することになると思います。

static uint8_t key[32] = { 0x00, 0x01, 0x02, 0x03, 0x04, 0x05, 0x06, 0x07,
                           0x08, 0x09, 0x0a, 0x0b, 0x0c, 0x0d, 0x0e, 0x0f,
                           0x10, 0x11, 0x12, 0x13, 0x14, 0x15, 0x16, 0x17,
                           0x18, 0x19, 0x1a, 0x1b, 0x1c, 0x1d, 0x1e, 0x1f,
                         };

ただしキーそのものをプログラムに埋め込むと、フラッシュを抜きだされた場合にかんたんに特定されてしまう可能性があるので、ESP.getEfuseMac()などの値を使って端末ごとに異なるキーを自動生成して使うなどの工夫が必要になると思います。

まとめ

暗号化自体はかんたんに利用できますが、キーをどう運用するのかはちょっと考える必要があります。実装例を載せてしまうと、それをそのまま使った暗号化テキストが、かんたんに復号化できてしまう可能性があるので、載せないこととします。

実際のユースケースとしてNVSに暗号化したパスワードなどを保存などが考えられ、NVSだと暗号化テキスト自体はかんたんに抜き出されることを想定してください。

キーとIVは、特定番号からの連番や、飛び番号にするだけでも、暗号化しないよりは強度が高まると思います。ただコードの中にキーがあるので、逆アセンブラしてコード自体を調べられるとかんたんではないですが、ハックすることが可能と思います。

Arduino CoreのESP32へのフラッシュ書き込み調査

現時点の情報です。最新情報はM5StickC非公式日本語リファレンスを確認してみてください。

概要

Arduino CoreでESP32にプログラムを書き込み、どのようにフラッシュに保存されているのかを調査しました。

フラッシュのクリア

esptool.py --port COM3 -b 1500000 erase_flash

最初にフラッシュをクリアして、未使用領域を0xFFで初期化しました。これで昔のプログラムの残骸が残っていることはなくなります。

Arduino Coreのサンプルスケッチ

#include <Preferences.h>

Preferences preferences;

char password[] = "password";
const char password2[] = "PASSWORD";

void setup() {
  Serial.begin(115200);
  Serial.println(password);
  Serial.println(password2);

  preferences.begin("Wi-Fi:Setting", false);
  preferences.putString("ssid", "Wi-Fi:SSID");
  preferences.putString("key", "Wi-Fi:KEY");
  preferences.end();
}

void loop() {
}

上記のプログラムを書き込みました。書き込み直後に実行され、NVS領域にWi-Fi設定が書き込まれているはずです。

ちなみに文字列は利用されていないと、コンパイラの最適化により消されていましたので、無駄にシリアル出力しています。

Arduinoの転送ログ抜粋

Wrote 8192 bytes (47 compressed) at 0x0000e000 in 0.0 seconds (effective 4096.0 kbit/s)...
Wrote 15856 bytes (10276 compressed) at 0x00001000 in 0.1 seconds (effective 991.0 kbit/s)...
Wrote 219584 bytes (112197 compressed) at 0x00010000 in 1.6 seconds (effective 1109.7 kbit/s)...
Wrote 3072 bytes (128 compressed) at 0x00008000 in 0.0 seconds (effective 1536.0 kbit/s)...

0x0000e000、0x00001000、0x00010000、0x00008000からのアドレスに4つのファイルを転送しています。

転送物確認

0x0e000tools/partitions/boot_app0.bin
0x01000tools/sdk/bin/bootloader_dio_80m.bin
0x10000sketch_test.ino.bin
0x08000sketch_test.ino.partitions.bin

Arduino IDEの環境設定から、より詳細な情報を表示するで、書き込みをしているコマンドがわかり、そこから書き込んでいるファイル名を特定することができました。

上の2つはブートローダーで、固定ファイル。下2つがスケッチ用にコンパイルしたファイルのようです。

Arduinoのパーティションテーブル

NameTypeSubTypeOffsetSizeFlags
nvsdatanvs0x0090000x005000
otadatadataota0x00e0000x002000
app0appota_00x0100000x140000
app1appota_10x1500000x140000
spiffsdataspiffs0x2900000x170000

デフォルト設定ですので、上記設定になっています。

フラッシュ領域の分析

アドレス内容ファイル
0x001000セカンドストレージブートローダーbootloader_dio_80m.bin
0x008000パーティションテーブルsketch_test.ino.partitions.bin
0x009000NVSデータ領域
0x00e000otadata領域boot_app0.bin
0x010000app0領域sketch_test.ino.bin

パーティションテーブルの情報も合わせると上記の表になりました。

ブートローダーに80MHzで動作するDIOモードのフラッシュ用のファイルを転送。パーティションテーブルはArduino IDEで指定したパーティション情報が書き込まれ、NVS領域は領域だけ確保だけされ、プログラムの中で初期化などをするので何も転送しません。

otadata領域はOTAではなく、Arduino IDEからの書き込みなのでapp0から起動するファイルを転送。app0にスケッチをコンパイルした内容を転送していました。

フラッシュ読み出し

C:\Temp>esptool.py --port COM3 -b 1500000 read_flash 0x00000 0x400000 image4M.bin
esptool.py v2.7
Serial port COM3
Connecting.....
Detecting chip type... ESP32
Chip is ESP32-PICO-D4 (revision 1)
Features: WiFi, BT, Dual Core, 240MHz, Embedded Flash, VRef calibration in efuse, Coding Scheme None
Crystal is 40MHz
MAC: ??:??:??:??:??:??
Uploading stub...
Running stub...
Stub running...
Changing baud rate to 1500000
Changed.
4194304 (100 %)
4194304 (100 %)
Read 4194304 bytes at 0x0 in 49.1 seconds (683.7 kbit/s)...
Hard resetting via RTS pin...

フラッシュの内容をファイルに保存します。

内容検証

0x001000 セカンドストレージブートローダー

バイナリエディタで読み込んだ内容です。bootloader_dio_80m.binと同じ内容の物が書き込まれています。/home/runnerでビルドしたブートローダーってことが見てわかります。

0x008000 パーティションテーブル

フラッシュのパーティションテーブルが定義されています。04から4バイトぐらいで開始アドレス、08から2バイトでサイズが入っているのかな?

0x009000 NVSデータ領域

  preferences.begin("Wi-Fi:Setting", false);
  preferences.putString("ssid", "Wi-Fi:SSID");
  preferences.putString("key", "Wi-Fi:KEY");

上記3行のデータが保存されているのが見えます。

0x00e000 otadata領域

boot_app0.binの内容がそのまま書き込まれています。

0x010000 app0領域

プログラム本体です。上記だけだとわかりませんが、文字列検索をするとpasswordとPASSWORDの文字列を発見することができました。

ESP32の場合constがついた変数は変更されませんので、RAM領域ではなくフラッシュ領域に置かれます。constがついていない変数は変更される可能性があるのでRAM領域に置かれます。そのため2つの文字列が保存されている場所は異なります。

constのフラッシュ領域には、他にも文字列が保存されており、プログラム中で利用している”Wi-Fi:Setting”などもありました。また、Core Debug Levelを設定すると、リンクしたファイルパスも保存されていますので、ビルドした人のユーザー名などが埋め込まれる可能性があります。

まとめ

メモリマップやパーティションテーブルの理解がすすみました。暗号化もどこかでチャレンジしたいと思いますが、なかなか中身が見えるのも悩ましい問題です。

ESP32でesptool.pyの使い方

現時点の情報です。最新情報はM5StickC非公式日本語リファレンスを確認してみてください。

概要

Windows環境でESP32のフラッシュツールであるesptool.pyの使い方を調べてみました。

esptool.pyとは?

ESP32の販売元であるEspressifが公開しているツールであり、ESP8266とESP32用のブートローダーと通信するためのPythonプログラムです。

Python 2.7またはPython 3.4以降がインストールされている環境であれば、OSに依存せずに動かすことができます。

$ pip install esptool

Pythonが適切に設定されている場合には上記のコマンドで設定できると思います。Windowsの場合には動作に制限が多いMicrosoft Storeからではなく、直接ダウンロードしたほうがトラブルが少ないと思います。

esptool.pyコマンド一覧

コマンド解説
load_ramイメージをRAMにダウンロードして実行する
dump_mem任意のメモリをディスクにダンプします
read_mem任意のメモリ位置を読み取る
write_mem任意のメモリ位置へ書き込み
write_flashバイナリBLOBをフラッシュに書き込む
runフラッシュでアプリケーションコードを実行する
image_infoアプリケーションイメージからヘッダーをダンプする
make_imageバイナリファイルからアプリケーションイメージを作成する
elf2imageELFファイルからアプリケーションイメージを作成する
read_macOTP ROMからMACアドレスを読み取る
chip_idOTP ROMからチップIDを読み取る
flash_idSPIフラッシュの製造元とデバイスIDを読み取る
read_flash_statusSPIフラッシュステータスレジスタの読み取り
write_flash_statusSPIフラッシュステータスレジスタの書き込み
read_flashSPIフラッシュコンテンツの読み取り
verify_flashフラッシュに対してバイナリBLOBを検証する
erase_flashSPIフラッシュでチップ消去を実行する
erase_regionフラッシュの領域を消去する
versionesptoolバージョンを印刷する

ざっくりと上記のコマンドがあります。

共通オプション

シリアルポート(-p)

シリアルポートを指定するオプションです。Windowsの場合-p COM1やLinuxやMacの場合-p /dev/ttyUSB0で指定します。指定しない場合、若い番号から自動検索して接続します。複数接続している場合には、間違った方に書き込む可能性があるので、なるべく指定しましょう。

転送速度(-b)

デフォルトの転送速度は115200bpsです。M5StickCだと-b 1500000, -b 750000, -b 500000, -b 250000, -b 115200の5種類が利用できます。高速の場合には転送が失敗する場合があるので、失敗したらもう少し遅い速度で試してください。ボードによっては-b 921600や-b 460800-b 230400などが利用できます。

主要コマンド解説

チップの情報取得(flash_id)

C:\Temp>esptool.py flash_id
esptool.py v2.7
Found 2 serial ports
Serial port COM3
Connecting....
Detecting chip type... ESP32
Chip is ESP32-PICO-D4 (revision 1)
Features: WiFi, BT, Dual Core, 240MHz, Embedded Flash, VRef calibration in efuse, Coding Scheme None
Crystal is 40MHz
MAC: ??:??:??:??:??:??
Uploading stub...
Running stub...
Stub running...
Manufacturer: c8
Device: 4016
Detected flash size: 4MB
Hard resetting via RTS pin...

接続しているチップの情報を出力します。read_macchip_idもほぼ同じ情報を出力しますが、このコマンドが一番情報が多いので、他のコマンドを使う必要はありません。

上記はM5StickCなのでESP32-PICO-D4で、4Mフラッシュが搭載されているのが確認できます。最近160MHzの物があるようですし、M5Stack Grayのように4M以上のフラッシュをついているモデルの容量を確認することができます。

フラッシュの読み出し(read_flash)

C:\Temp>esptool.py --port COM3 -b 1500000 read_flash 0x00000 0x400000 image4M.bin
esptool.py v2.7
Serial port COM3
Connecting....
Detecting chip type... ESP32
Chip is ESP32-PICO-D4 (revision 1)
Features: WiFi, BT, Dual Core, 240MHz, Embedded Flash, VRef calibration in efuse, Coding Scheme None
Crystal is 40MHz
MAC: ??:??:??:??:??:??
Uploading stub...
Running stub...
Stub running...
Changing baud rate to 1500000
Changed.
4194304 (100 %)
4194304 (100 %)
Read 4194304 bytes at 0x0 in 49.2 seconds (682.5 kbit/s)...
Hard resetting via RTS pin...

上記は開始アドレス0x00000から4M(0x400000)を読み出して、image4M.binに保存した場合のコマンド例です。

手元環境の場合最高速の-b 1500000だとたまにエラーになるので、安全のために-b 115200で動かしてもよいと思います。

ESP32のメモリマップを参考にすると、先頭アドレスは0x1000以降だけ読み込めばいいのですが、読み込むサイズ計算が面倒になるので先頭から4Mみたいな指定をしたほうが安全だと思います。

C:\Temp>esptool.py --port COM3 -b 115200 read_flash 0 4194304 image4M.bin

上記のように16進数ではなく、10進数でも指定可能です。

フラッシュのベリファイチェック(verify_flash)

C:\Temp>esptool.py --port COM3 -b 1500000 verify_flash --diff yes 0x00000 image4M.bin
esptool.py v2.7
Serial port COM3
Connecting......
Detecting chip type... ESP32
Chip is ESP32-PICO-D4 (revision 1)
Features: WiFi, BT, Dual Core, 240MHz, Embedded Flash, VRef calibration in efuse, Coding Scheme None
Crystal is 40MHz
MAC: ??:??:??:??:??:??
Uploading stub...
Running stub...
Stub running...
Changing baud rate to 1500000
Changed.
Configuring flash size...
Auto-detected Flash size: 4MB
Verifying 0x400000 (4194304) bytes @ 0x00000000 in flash against image4M.bin...
-- verify OK (digest matched)
Hard resetting via RTS pin...

読み込んだファイルが正しいか、ベリファイチェックします。書き込みの場合には自動的にベリファイチェックしているので、利用することは少ないと思います。

ただ、起動するたびにデータを書き換えているプログラムが存在しており、その場合にはベリファイチェックがNGになり、差分が画面に表示されます。何度か実行して同じような結果になる場合には、念の為差分を保存してからあきらめてください。

フラッシュの書き込み(write_flash)

C:\Temp>esptool.py --port COM3 -b 1500000 write_flash 0x00000 image4M.bin
esptool.py v2.7
Serial port COM3
Connecting....
Detecting chip type... ESP32
Chip is ESP32-PICO-D4 (revision 1)
Features: WiFi, BT, Dual Core, 240MHz, Embedded Flash, VRef calibration in efuse, Coding Scheme None
Crystal is 40MHz
MAC: ??:??:??:??:??:??
Uploading stub...
Running stub...
Stub running...
Changing baud rate to 1500000
Changed.
Configuring flash size...
Auto-detected Flash size: 4MB
Compressed 4194304 bytes to 149336...
Wrote 4194304 bytes (149336 compressed) at 0x00000000 in 8.7 seconds (effective 3869.2 kbit/s)...
Hash of data verified.

Leaving...
Hard resetting via RTS pin...

read_flashで保存したファイルを書き込むときのコマンドです。読み込みは速度が115200のときには6分以上かかりますが、書き込みは1分ぐらいで完了しています。

指定したアドレスから、ファイルサイズ分書き込む操作となります。読み出しで利用したアドレスと同じアドレスでないと、おかしなことになるので注意しましょう。

書き込みでは、自動的にベリファイをしていますので、個別にベリファイを実行する必要はありません。

フラッシュの削除(erase_flash)

C:\Temp>esptool.py --port COM3 -b 1500000 erase_flash
esptool.py v2.7
Serial port COM3
Connecting....
Detecting chip type... ESP32
Chip is ESP32-PICO-D4 (revision 1)
Features: WiFi, BT, Dual Core, 240MHz, Embedded Flash, VRef calibration in efuse, Coding Scheme None
Crystal is 40MHz
MAC: ??:??:??:??:??:??
Uploading stub...
Running stub...
Stub running...
Changing baud rate to 1500000
Changed.
Erasing flash (this may take a while)...
Chip erase completed successfully in 1.7s
Hard resetting via RTS pin...

フラッシュの中身を消します。利用していない領域も含めて0xFFで上書きをします。NVSが壊れた場合など、動作が不安定な場合には一度クリアしてからプログラムの転送をしなおすとなおる場合もあると思います。

また、利用していないESP32や、人に譲るESP32などはクリアしておかないと、中身のデータや、NVS領域などのWi-Fiアクセスポイント情報などをかんたんに抜き出すことができるので、注意してください。

まとめ

ESP32はかんたんにプログラムの書き込みや、読み込みが可能です。反面フラッシュの中身をUSB接続で抜き出すことが可能ですので、気をつけて扱いましょう。

暗号化することで、抜き出しにくくすることも可能ですが、暗号化はヒューズ操作になるので、3回までしか実施できないのとNVS領域が保護されていないので注意が必要です。

M5StickC with obnizOS(Hobbyライセンス)

現時点の情報です。最新情報はM5StickC非公式日本語リファレンスを確認してみてください。

概要

Hobbyライセンスを購入すると、M5StickCがもらえるキャンペーンをやっていたので購入してみました。

購入方法

obnizのアカウントを作成し、開発者コンソールから購入します。デバイスの購入はオフィシャルストアですが、ライセンスは開発者コンソールのデバイスからになります。

クレジットカードはJCBだと登録できなかったので、メインのカード以外で登録しました。どのカードが対応しているかは書いてないかもしれません。

ライセンスなので送料無料の1500円+税の1650円でした。1年ライセンスですが、他のESP32にも使うことができます。

期間限定で500円引きの、1980円でM5StickCとHobby Liteライセンス(低機能の永年無料)のセットもありますので、興味がある人は検討してみてください。国内からの購入ですと送料含めると、かなり安いキャンペーンです。

キャンペーンに登録

購入後の画面にキャンペーンに登録するページへのリンクがあります。そのリンクから登録した人の先着100名にM5StickCが送られてきます。

先着順で、いま何人登録したのかがわからないですので、公式Twitterとかを確認してから購入したほうがいいかもしれません。

登録時にobniz Idが必須ですが、開発者コンソールのデバイスに購入したライセンスのobniz Idが追加されているはずですので、そちらを入力します。

M5StickCへのセットアップ

開発者コンソールのデバイスから設定ボタンを押すと、デバイスセッティングのページになり、そこのページに設定に必要なデバイスキーや、ドキュメントのリンクがまとまっています。

上記のページに従って設定を行います。手元のWindows機にはPythonがセットアップされていたので、pip install obniz_cliでセットアップしてから、obniz_cli flashosで転送できました。

その後デバイスキーと、Wi-Fiの設定をすれば使えるようになります。

注意点

現時点のobnizOS for ESP32はM5StickCではなく、ESP32-PICO-D4に対応したOSです。そのため電源を入れた時点で液晶画面は真っ暗なままです!

obniz端末と同じように画面にobniz Idがでると思っていたら出ませんのでご注意ください。

Arduinoとの共有方法

Arduinoでプログラムを転送すると上書きされて、Arduinoのプログラムが動きます。その後obnizOSを動かす場合には、セットアップで利用したコマンドと同じobniz_cli flashosで再度転送し直せば大丈夫です。

他のM5StickCにセットアップしたところ、 デバイスキーと、Wi-Fiの設定をすれば同じように動きました。ライセンス上はデバイスの移動は可能なのですが、移動するときには特に紐付けを変える必要はいらなそうです。

まとめ

M5StickCでobnizOSが動くようになりましたが、まだまだ環境整備は追いついていません。サンプルプログラムもまだまだ足りませんが、仕組み上はかんたんに共有できますので作ったサンプルを共有したり、オフィシャルのGitHubに取り込んでもらってもいいと思います。

気になった技適紹介 ESP-12F

概要

Shenzhen Ai-Thinker Technology Co., LtdさんのESP-12Fが技適通っていました!

ESP-12FってESP8266の小さいパッケージですよね?

Ai-Thinkerさんの初めての技適です。安く出回って欲しいし、ESP32系もAi-Thinkerさんの使っている新作ボードは楽しそうなのが多いので、どんどん日本展開もして欲しいな。

気になった技適紹介 Raspberry Pi 3 Model A+

概要

スイッチサイエンスさんのページで、

本製品は発売日、価格など未定です。日本向けに工事設計認証を通す予定はないそうです。(2019年6月現在)

https://www.switch-science.com/catalog/4110/

って書いてあったのですが、通ったんですね。4より少し遅れてますが、同じ認証機関で通していますね。

日本に販売されるのはいつかなー?

(追記)聞いてみた

正式ルートで確認してくれるみたいです!

ESP32-PICO-KITみたいに、技適取得しても表示が新しいバージョンが出ないので、日本で販売できないパターンもあったりして、在庫量とかの兼ね合いがあって難しいのよねー

M5StickC(ESP32)の開発言語

現時点での情報です。最新情報はM5StickC非公式日本語リファレンスを確認してください。

概要

これまでArduino Coreの情報しか書いていませんでしたが、どんな環境で開発ができるかを調べてみました。

C/C++言語

ESP-IDF

ESP32の標準的な開発環境です。一番低レベルまで制御が可能ですが、細かいライブラリは準備されていませんので、M5StickCのAXP192などの制御ライブラリが整備されていません。

自分でライブラリから構築できる人向けですので、一般的にはおすすめしません。

Arduino Core for ESP32(Arduino IDE)

事実上の標準開発環境です。ArduinoにESP32のライブラリを追加します。一番ライブラリが充実していて、情報も多く開発が楽です。

エディタがArduino IDEと呼ばれており、文字列補間ができないので、すこし使いにくいです。

Arduino for Visual Studio Code

裏側はArduino Coreのままで、エディタ部分だけVisual Studio Codeを利用しています。

Microsoftが作って公開しているプラグインのため、信頼性はあります。しかしながらArduino Coreを使っている以上、エディタが使いやすくなった以上の差はあまりありません。

PlatformIO

Arduino Coreとほぼ同じ環境ですが、エディタが自由に選べます。Arduino IDEではなく、Visual Studio Codeと組み合わせて動かすことが多いようです。

基本的にはArduino Coreと同じプログラムがコンパイルできますが、ライブラリ管理が違うので、画面から入れることができるライブラリの種類は若干異なります。

コンパイラなども自動的にダウンロードして、環境を整備してくれるので初回にかなり時間がかかりますが、かんたんに環境構築はできます。一方Arduino Coreとは違う環境になりますので、差分がでてくる可能性があります。

ライブラリの設定などはArduino IDEと共通になります。

ブロック+MicroPython

M5Stack UIFlow Cloud

M5StickCの販売元が提供している開発環境です。最近よくあるブロックを組み合わせてプログラミングが可能です。

ブロックで表現できないことはMicroPythonでも記述可能です。基本的にはWi-Fiに接続して、ブラウザ上からプログラムを更新するスタイルになります。

パソコンに接続する必要がないので、かんたんにプログラムの更新が可能ですが、大量の端末がある場合にはWi-Fiが混雑して利用しにくいかもしれません。

最初にUIFlow用のプログラムを端末に転送し、Wi-Fi設定をすることで環境構築が完了します。

M5Stack UIFlow Desktop

UIFlowをWi-Fiを利用せずに、USB接続で利用する場合の環境です。こちらも最初にUIFlow用のプログラムを端末に転送し、USBモードに変更することで環境構築が完了します。

Wi-Fiが無い環境はこちらをおすすめします。ただしDesktop版は安定したバージョンしか提供されませんので、Beta版などの新機能は利用できません。

プログラム転送も、USB接続のため非常に早いです。教室などで複数人に教育する場合にはDesktop版をおすすめします。

JavaScript

obniz

こちらもWi-Fiに接続して、ブラウザからプログラムを転送するタイプの開発環境です。非常にサンプルやライブラリが充実しており、開発しやすいのが特徴です。

サンプルプログラムなども、大量に公開されており、クリックだけで自分の端末で動かすことができるのが便利です。

ただし、有料ライセンスのため気軽に試すことができません。

その他の環境

あまり情報がないのでおすすめしにくい開発環境です。

Mongoose OS

無料で使えるJavaScript開発環境です。

Espruino

無料で使えるJavaScript開発環境です。

Zerynth

PythonとCが利用できる開発環境です。

MicroPython

MicroPythonオフィシャルの環境。M5Stick社の製品を使っているのであればUIFlowで十分な気がします。その他のESP32ボードでPythonを使う場合にはこちらが適しています。

まとめ

個人的には一番標準であるArduino IDEを使っています。C言語に抵抗があるのであればPythonか、JavaScriptあたりが候補ですが、なかなか難しいところです。

M5StickCにENV HAT(気温、湿度、気圧)をサーバーにアップする その6(Beebotte+REST編)

現時点での情報です。最新情報はM5StickC非公式日本語リファレンスを確認してください。

概要

ENV HAT(気温、湿度、気圧)の情報を1分ごとにBeebotteにアップしました。また基礎編をベースにしていますので、基礎部分の解説は省いています。

BeebotteはURLにPOSTしてデータを送信するREST APIと、MQTTを使うMQTT APIの2種類があります。本ページではREST APIを使った方法を記述しています。

Beebotteとは?

IoT向けクラウドサービスで、さまざまな機能がありますがRESTでデータを送信して、グラフ化する機能を利用しました。

MQTTで接続したほうが、プロトコル的には軽いのですが、別途ライブラリが必要となってしまいます。

事前作業

Beebotteで事前にChannel作成と、resource作成をしておきます。

resourceは名前の他に型名が指定できます。

型名備考
any送信するデータタイプがわからない場合や、変わる場合
number数値
string文字列
booleantrue or false
objectanyと同じ
functionJavaScriptを保存
arrayobjectの配列
alphabeticアルファベット
alphanumeric英数字
decimal10進数の数値
rateレート。0-1の値
percentageパーセンテージ。0-100の値
emaile-mailアドレス
gpsGPSアドレス
JSONでlongitudeとlatitudeの値を持つ
cpuCPU利用率
JSONでuser、sys、nice、idle、irqの値を持つ
memoryメモリー利用量
JSONでmemtotal、memfree、cached、dirtyの値を持つ
netifネットワーク利用量
JSONでrx_bytes、rx_packets、tx_bytes、tx_packetsの値を持つ
diskディスク利用量
JSONでsize、usedの値を持つ
temperature気温
humidity湿度
body_temp体温
pressure気圧
speed速度
weight重量
conductivity導電率

気温と湿度、気圧の型があるので、それぞれ作成します。tmp(temperature型)、hum(humidity型)、 pressure(pressure型)。リソースの名前はあとで変更するのが面倒なので、最初によく考えて命名しましょう。

また、スケッチの中では、外部に公開してまずいものは、Preferencesに保存しているので、本体内部にあらかじめ保存しておきます。

スケッチ

/*
    note: need add library Adafruit_BMP280 from library manage
    Github: https://github.com/adafruit/Adafruit_BMP280_Library
*/

#include <M5StickC.h>
#include <Preferences.h>
#include <WiFi.h>
#include "DHT12.h"
#include <Adafruit_BMP280.h>
#include <HTTPClient.h>

Preferences preferences;
char wifi_ssid[33];
char wifi_key[65];

DHT12 dht12;
Adafruit_BMP280 bme280;
unsigned long nextUpdate = 0;

char beebotte_token[40];
char beebotte_channel[40];

void sendBeebotte(const char *token, const char *channel, const char *resource, double value) {
  HTTPClient http;

  String json = "{\"data\":" + String(value) + "}";
  String url = "http://api.beebotte.com/v1/data/write/" + String( channel ) +  "/" + String( resource ) + "?token=" + String( token );

  http.begin(url);
  http.addHeader("Content-Type", "application/json");
  http.POST(json);
}

void setup() {
  // M5StickC初期化
  M5.begin();
  M5.Lcd.setRotation(3);
  M5.Lcd.fillScreen(BLACK);
  M5.Lcd.setCursor(0, 0, 2);
  M5.Lcd.println("ENV TEST");

  // Wi-Fiアクセスポイント情報取得
  preferences.begin("Wi-Fi", true);
  preferences.getString("ssid", wifi_ssid, sizeof(wifi_ssid));
  preferences.getString("key", wifi_key, sizeof(wifi_key));
  preferences.end();

  // Wi-Fi接続
  Serial.printf("接続中 - %s ", wifi_ssid);
  WiFi.begin(wifi_ssid, wifi_key);
  while (WiFi.status() != WL_CONNECTED) {
    delay(500);
    Serial.print(".");
  }
  Serial.println(" 接続完了");

  // I2C初期化
  Wire.begin(0, 26);

  // BME280初期化
  if (!bme280.begin(0x76)) {
    Serial.println("BME280センサーが見つかりません");
    while (1);
  }

  // Beebotte情報取得
  preferences.begin("Beebotte", true);
  preferences.getString("token", beebotte_token, sizeof(beebotte_token));
  preferences.getString("channel", beebotte_channel, sizeof(beebotte_channel));
  preferences.end();
}

void loop() {
  // 気温
  float tmp = dht12.readTemperature();
  M5.Lcd.setCursor(0, 20, 2);
  M5.Lcd.printf("Temp: %2.1f C", tmp);

  // 湿度
  float hum = dht12.readHumidity();
  M5.Lcd.setCursor(0, 40, 2);
  M5.Lcd.printf("Humi: %2.0f %%", hum);

  // 気圧
  float pressure = bme280.readPressure() / 100.0F;
  M5.Lcd.setCursor(0, 60, 2);
  M5.Lcd.printf("pressure: %6.1f hPa", pressure);

  // 1分に1度データ送信
  if ( nextUpdate < millis() ) {
    // シリアル出力
    Serial.printf("Temp: %2.1f C\n", tmp);
    Serial.printf("Humi: %2.0f %%\n", hum);
    Serial.printf("pressure: %6.1f hPa\n", pressure);

    // Beebotte送信
    sendBeebotte(beebotte_token, beebotte_channel, "tmp", tmp);
    sendBeebotte(beebotte_token, beebotte_channel, "hum", hum);
    sendBeebotte(beebotte_token, beebotte_channel, "pressure", pressure);

    // 時間更新(60秒後)
    nextUpdate = millis() + 60000;
  }

  // 1秒待機
  delay(1000);
}

JSONでデータを作って、POSTするだけのシンプルなプログラムです。

画像はMQTTのときの使い回しですが、どちらで送信しても同じようにデータが蓄積していきます。

まとめ

Beebotteの認証には複数種類があり、PHPライブラリなどの中身では一番複雑なものを利用していた。しかしながらトークン認証を使うことでシンプルに投げることも可能でした。

MQTTかRESTのどちらがいいかは微妙なところですが、通信量や電力的にはMQTTですが、RESTのお手軽さも捨てがたいです。

M5StickCのAXP192で外部電源を使う その2

現時点の情報のため、最新情報はM5StickC非公式日本語リファレンスを確認してください。

概要

USBから任意の電圧出力ができる機材が届いたので、外部電源端子の動きを調べ直しました。

機材

  • USB POWER Quick ZK-DP3D
  • M5StickC(MPU6868)

M5StickCではAXP192のサンプルスケッチを実行しながら検証をしました。

出力電圧とAXP192入力電圧の差

出力側を3.8Vにしたところ、M5StickCのAXP192での入力電圧は3.78Vぐらいでした。若干低めにでますが概ね正しい値だと思います。

また、テスターなどで実際の電圧ははかっていません。

外部電源入力での自動起動

AXP192のデータシート上は3.8V以上でしたが、低い電圧から上げていくと3.77Vぐらいで自動起動しました。

データシートはマージンを入れていると思いますが、電源OFF状態から外部電源で3.8V以上を接続すると自動起動したのを確認しました。

安定動作

通信を行わず、最低限のスケッチですと3.7V程度ないと安定動作をしませんでした。また、バッテリー電圧より0.2V以上高くないと、外部電源を利用せずに、バッテリーを利用します。

3.8V以上でバッテリーにも充電を開始しました。無線を使う場合にはもう少し高い電圧を入れないとだめだと思いますし、クロックを落としたり、画面を利用しない場合にはもう少し低い電圧でも大丈夫だと思われます。

USBとの同時接続

データシート上は外部電源がUSBより優先されるとありましたが、外部電源電圧が4.9V以上でないとUSB側が利用されました。

まとめ

やっぱり外部電源はDCDCとかを使って、5Vに昇圧して使ったほうが無難でした。

M5StickCにENV HAT(気温、湿度、気圧)をサーバーにアップする その5(Beebotte+MQTT編)

現時点での情報です。最新情報はM5StickC非公式日本語リファレンスを確認してください。

概要

ENV HAT(気温、湿度、気圧)の情報を1分ごとにBeebotteにアップしました。また基礎編をベースにしていますので、基礎部分の解説は省いています。

Beebotteとは?

IoT向けクラウドサービスで、さまざまな機能がありますがMQTTでデータを送信して、グラフ化する機能を利用しました。

MQTTの他にRESTでも利用できますが、ESP32の場合にはMQTTで接続したほうが電力や、通信量は少なくなると思います。またMQTTを利用していますので、送信の他にBeebotteからの受信や、外部サーバーとの連携などかなり複雑なことができます。

RESTでの通信例は別の記事になります。

事前作業

Beebotteで事前にChannel作成と、resource作成をしておきます。

resourceは名前の他に型名が指定できます。

型名備考
any送信するデータタイプがわからない場合や、変わる場合
number数値
string文字列
booleantrue or false
objectanyと同じ
functionJavaScriptを保存
arrayobjectの配列
alphabeticアルファベット
alphanumeric英数字
decimal10進数の数値
rateレート。0-1の値
percentageパーセンテージ。0-100の値
emaile-mailアドレス
gpsGPSアドレス
JSONでlongitudeとlatitudeの値を持つ
cpuCPU利用率
JSONでuser、sys、nice、idle、irqの値を持つ
memoryメモリー利用量
JSONでmemtotal、memfree、cached、dirtyの値を持つ
netifネットワーク利用量
JSONでrx_bytes、rx_packets、tx_bytes、tx_packetsの値を持つ
diskディスク利用量
JSONでsize、usedの値を持つ
temperature気温
humidity湿度
body_temp体温
pressure気圧
speed速度
weight重量
conductivity導電率

気温と湿度、気圧の型があるので、それぞれ作成します。tmp(temperature型)、hum(humidity型)、 pressure(pressure型)。リソースの名前はあとで変更するのが面倒なので、最初によく考えて命名しましょう。

また、スケッチの中では、外部に公開してまずいものは、Preferencesに保存しているので、本体内部にあらかじめ保存しておきます。

スケッチ

/*
    note: need add library Adafruit_BMP280 from library manage
    Github: https://github.com/adafruit/Adafruit_BMP280_Library

    note: need add library PubSubClient from library manage
    Github: https://github.com/knolleary/pubsubclient

*/

#include <M5StickC.h>
#include <Preferences.h>
#include <WiFi.h>
#include "DHT12.h"
#include <Adafruit_BMP280.h>
#include <PubSubClient.h>

Preferences preferences;
char wifi_ssid[33];
char wifi_key[65];

DHT12 dht12;
Adafruit_BMP280 bmp280;
unsigned long nextUpdate = 0;

WiFiClient wifiClient;
PubSubClient client(wifiClient);

char beebotte_token[40];
char beebotte_channel[40];

boolean reconnect() {
  uint64_t chipid = ESP.getEfuseMac();
  String clientId = "ESP32-" + String((uint16_t)(chipid >> 32), HEX);
  Serial.print("MQTT clientId=");
  Serial.println(clientId);

  if (client.connect(clientId.c_str(), beebotte_token, "")) {
    Serial.println("MQTTに接続");
  }
  return client.connected();
}

void publish(const char* resource, float data) {
  String json = "";
  json += "{";
  json += "\"channel\":\"" + String(beebotte_channel) + String( "\"," );
  json += "\"resource\":\"" + String(resource) + String( "\"," );
  json += "\"write\": true,";
  json += "\"data\":" + String(data);
  json += "}";

  String topic = String(beebotte_channel) + String("/") + String(resource);
  client.publish(topic.c_str(), json.c_str());
  Serial.println(topic);
  Serial.println(json);
}

void setup() {
  // M5StickC初期化
  M5.begin();
  M5.Lcd.setRotation(3);
  M5.Lcd.fillScreen(BLACK);
  M5.Lcd.setCursor(0, 0, 2);
  M5.Lcd.println("ENV TEST");

  // Wi-Fiアクセスポイント情報取得
  preferences.begin("Wi-Fi", true);
  preferences.getString("ssid", wifi_ssid, sizeof(wifi_ssid));
  preferences.getString("key", wifi_key, sizeof(wifi_key));
  preferences.end();

  // Wi-Fi接続
  Serial.printf("接続中 - %s ", wifi_ssid);
  WiFi.begin(wifi_ssid, wifi_key);
  while (WiFi.status() != WL_CONNECTED) {
    delay(500);
    Serial.print(".");
  }
  Serial.println(" 接続完了");

  // I2C初期化
  Wire.begin(0, 26);

  // bmp280初期化
  if (!bmp280.begin(0x76)) {
    Serial.println("bmp280センサーが見つかりません");
    while (1);
  }

  // MQTT情報取得
  preferences.begin("Beebotte", true);
  preferences.getString("token", beebotte_token, sizeof(beebotte_token));
  preferences.getString("channel", beebotte_channel, sizeof(beebotte_channel));
  preferences.end();

  // MQTT初期化
  randomSeed(micros());
  client.setServer("mqtt.beebotte.com", 1883);
}

void loop() {
  // 気温
  float tmp = dht12.readTemperature();
  M5.Lcd.setCursor(0, 20, 2);
  M5.Lcd.printf("Temp: %2.1f C", tmp);

  // 湿度
  float hum = dht12.readHumidity();
  M5.Lcd.setCursor(0, 40, 2);
  M5.Lcd.printf("Humi: %2.0f %%", hum);

  // 気圧
  float pressure = bmp280.readPressure() / 100.0F;
  M5.Lcd.setCursor(0, 60, 2);
  M5.Lcd.printf("pressure: %6.1f hPa", pressure);

  // 1分に1度データ送信
  if ( nextUpdate < millis() ) {
    // シリアル出力
    Serial.printf("Temp: %2.1f C\n", tmp);
    Serial.printf("Humi: %2.0f %%\n", hum);
    Serial.printf("pressure: %6.1f hPa\n", pressure);

    // MQTT
    if (!client.connected()) {
      // 接続していない場合には接続
      reconnect();
    }
    publish("tmp", tmp);
    publish("hum", hum);
    publish("pressure", pressure);

    // 時間更新(60秒後)
    nextUpdate = millis() + 60000;
  }

  // 1秒待機
  delay(1000);
}

PubSubClientライブラリを利用させてもらいました。接続プロトコルはMQTTのため、送信データ形式を少し触るだけで、他のMQTTサービスにデータ送信することができると思います。

まとめ

Beebotteは無料で使える範囲も多く、送受信両方使えるため使いやすいサービスです。有料オプションも比較的安価なのでおすすめです。

MQTTはもう少し掘り下げたいと思いますが、グラフ化シリーズはこれぐらいにしたいと思います。他になにか良いサービスがあれば教えて欲しいです。AWS IoT Things Graphは思ったより高くなりそうなので、試していないです。