心拍センサを Seeeduino で使ってみる

 自社でヘルスケア関連サービスをやっているということもあり、スイッチサイエンスさんで販売されていた心拍センサが面白そうだったので Seeeduino で使ってみました。

www.switch-science.com

 使い方は Seeed の製品サイトの方にも記載されています。

wiki.seeedstudio.com

回路構成

 今回使う心拍センサは Grove コネクタが使われています。Grove システムについては下記資料でも紹介されています。

Introduction to Grove
http://www.seeedstudio.com/document/pdf/Introduction%20to%20Grove.pdf

 心拍センサの使い方のページの例でも使われている Grove の Basic Shield が上記の資料でも使われていますので、これを参考に下記のように配線しました。

f:id:akanuma-hiroaki:20180710084200j:plain

 Seeeduino にも Grove コネクタが搭載されているのですが、今回は心拍センサが心拍を検知したタイミングでの出力を割り込みで受け取るために D2 ピンを使いたかったので、心拍センサの Grove コネクタに直接ジャンパコードを挿して接続しました。

f:id:akanuma-hiroaki:20180710084253j:plain

 Seeeduino 側は 3.3V、GND、D2ピンにそれぞれ接続します。 Seeeduino の動作電圧も 3.3V に設定してあります。

f:id:akanuma-hiroaki:20180710084227j:plain

ファームウェア

 ファームウェアは先ほどの Seeed の製品ページにサンプルが掲載されていますので、それをベースにして、今回は LED は使っていないので LED 関連部分を削除して下記のようにしました。

 大まかな内容としては、 attachInterrupt() で D2 ピンからの割り込みを受け付けるようにして、心拍センサが心拍を検知した時は interrupt メソッドが実行され、20回検知するとそこまでの検知間隔から心拍数を計算してシリアルモニタに出力します。

unsigned char counter;
unsigned long temp[21];
unsigned long sub;
bool data_effect=true;
unsigned int heart_rate;

const int max_heartpluse_duty = 2000;

void setup()
{
    Serial.begin(9600);
    Serial.println("Please ready your chest belt.");
    delay(5000);
    arrayInit();
    Serial.println("Heart rate test begin.");
    attachInterrupt(0, interrupt, RISING);
}

void loop()
{
}

void sum()
{
    if(data_effect)
    {
      heart_rate=1200000/(temp[20]-temp[0]); 
      Serial.print("Heart_rate_is:\t");
      Serial.println(heart_rate);
    }
    data_effect=1;
}

void interrupt()
{
    temp[counter]=millis();
    Serial.println(counter,DEC);
    Serial.println(temp[counter]);
    switch(counter)
    {
        case 0:
            sub=temp[counter]-temp[20];
            Serial.println(sub);
            break;
        default:
            sub=temp[counter]-temp[counter-1];
            Serial.println(sub);
            break;
    }

    if(sub>max_heartpluse_duty)
    {
        data_effect=0;
        counter=0;
        Serial.println("Heart rate measure error,test will restart!" );
        arrayInit();
    }

    if (counter==20&&data_effect)
    {
        counter=0;
        sum();
    }
    else if(counter!=20&&data_effect)
        counter++;
    else 
    {
        counter=0;
        data_effect=1;
    }

}

void arrayInit()
{
    for(unsigned char i=0;i < 20;i ++)
    {
        temp[i]=0;
    }
    temp[20]=millis();
}

 これを Seeeduino にアップロードして、心拍センサのクリップを耳などにつけると、下記のようにシリアルモニタに心拍検知時と20回測定後の心拍数計算結果が出力されます。

Please ready your chest belt.
Heart rate test begin.
0
32432
0
1
33133
701
2
33133
0
3
34014
881
4
34934
920
5
35849
915
6
36736
887
7
37653
917
8
38588
935
9
39524
936
10
40410
886
11
41291
881
12
42212
921
13
43163
951
14
44107
944
15
44998
891
16
45929
931
17
46887
958
18
47837
950
19
48720
883
20
49581
861
Heart_rate_is:  69

電圧出力の確認

 ここまでで動作確認はできてるのですが、試しにオシロスコープでセンサからの電圧出力を確認してみます。

f:id:akanuma-hiroaki:20180712085629j:plain

 オシロスコープのプローブは心拍センサの Grove コネクタ部分に取り付け、 1系を VCC と GND、 2系は D2 への出力ピンに取り付けます。

f:id:akanuma-hiroaki:20180712085508j:plain

 結果は下記の写真のようになりました。赤いラインは 1系で、こちらは常に一定の電力が供給されているので 3.3V のまっすぐなラインになります。黄色いラインが 2系で、心拍を検知した際にセンサからのデジタル出力により 3.3V の電圧を検知しています。

f:id:akanuma-hiroaki:20180712085545j:plain

まとめ

 市販のセンサーで簡単にバイタルデータを計測できてしまうのは面白いですね。また、オシロスコープで実際の電圧検知までできて、病院の心拍計のようなイメージまで可視化されたのでより面白かったです。今回は測定結果はシリアルモニタに出力するだけだったので、これをネットワーク経由で送信したり、 LED などを使って可視化することもできると楽しそうかなと思っています。

D級アンプボードと Raspberry Pi で音を鳴らしてみる

 電子工作でセンサー等を使って何かを検知する仕組みを作った時に、結果を Slack に通知するなどはよくやるのですが、それ以外にも何か通知する手段を使いたいなと思い、アンプボードと小型スピーカーで音を鳴らせるようにしてみました。今回使ったアンプボードはスイッチサイエンスで販売されていた、 Adafruit の D級アンプボードです。

www.switch-science.com

 これを同じくスイッチサイエンスで販売されていた薄型スピーカーと組み合わせて使ってみました。

www.switch-science.com

 アンプボードのチュートリアルは Adafruit の製品ページで公開されています。 Raspberry Pi 向けのサンプルになっているので、今回は基本的にこのチュートリアルに従って試してみます。

https://learn.adafruit.com/adafruit-max98357-i2s-class-d-mono-amp

ピンヘッダ実装 & 回路図

 購入時はピンヘッダなどが実装されていないので、まず下記写真のように半田付けします。

f:id:akanuma-hiroaki:20180707201156j:plain

 そして下記回路図のように配線します。

アンプ側 - Raspberry Pi 側
VIN - 5V
GND - GND
DIN - GPIO 21
BCLK - GPIO 18
LRCLK - GPIO 19

f:id:akanuma-hiroaki:20180707183236p:plain

 実際に配線してみたものは下記の様になりました。

f:id:akanuma-hiroaki:20180707225836j:plain

セットアップ

 Raspberry Pi でのセットアップについてはスクリプトが提供されているので、それを実行するだけでセットアップできます。 Raspberry Pi 上で下記のようにスクリプトを実行します。

$ curl -sS https://raw.githubusercontent.com/adafruit/Raspberry-Pi-Installer-Scripts/master/i2samp.sh | bash
Support for your operating system is experimental. Please visit
forums.adafruit.com if you experience issues with this product.


This script will install everything needed to use
i2s amplifier

--- Warning ---

Always be careful when running scripts and commands
copied from the internet. Ensure they are from a
trusted source.

If you want to see what this script does before
running it, you should run:
    \curl -sS github.com/adafruit/Raspberry-Pi-Installer-Scripts/i2samp

Do you wish to continue? [y/N] y

Checking hardware requirements...

Adding Device Tree Entry to /boot/config.txt
dtoverlay=hifiberry-dac
dtoverlay=i2s-mmap

Commenting out Blacklist entry in 
/etc/modprobe.d/raspi-blacklist.conf

Disabling default sound driver
Configuring sound output

All done!

Enjoy your new i2s amplifier!

Some changes made to your system require
your computer to reboot to take effect.

Would you like to reboot now? [y/N] 

 スクリプトを実行したら一度 Raspberry Pi を再起動します。再起動後に再度同じスクリプトを実行すると、サウンドテストが実行されます。これで音が鳴ればとりあえず問題ありません。

pi@raspberrypi:~ $ curl -sS https://raw.githubusercontent.com/adafruit/Raspberry-Pi-Installer-Scripts/master/i2samp.sh | bash
Support for your operating system is experimental. Please visit
forums.adafruit.com if you experience issues with this product.


This script will install everything needed to use
i2s amplifier

--- Warning ---

Always be careful when running scripts and commands
copied from the internet. Ensure they are from a
trusted source.

If you want to see what this script does before
running it, you should run:
    \curl -sS github.com/adafruit/Raspberry-Pi-Installer-Scripts/i2samp

Do you wish to continue? [y/N] y

Checking hardware requirements...

Adding Device Tree Entry to /boot/config.txt
dtoverlay already active
i2s mmap dtoverlay already active

Commenting out Blacklist entry in 
/etc/modprobe.d/raspi-blacklist.conf

Default sound driver currently not loaded
Configuring sound output

We can now test your i2s amplifier
Set your speakers at a low volume if possible!
Do you wish to test your system now? [y/N] y
Testing...

speaker-test 1.1.3

Playback device is default
Stream parameters are 48000Hz, S16_LE, 2 channels
WAV file(s)
Rate set to 48000Hz (requested 48000Hz)
Buffer size range from 2229 to 8916
Period size range from 1114 to 1115
Using max buffer size 8916
Periods = 4
was set period_size = 1114
was set buffer_size = 8916
 0 - Front Left
 1 - Front Right
Time per period = 2.854479
 0 - Front Left
 1 - Front Right
Time per period = 2.996463
 0 - Front Left
 1 - Front Right
Time per period = 3.018638
 0 - Front Left
 1 - Front Right
Time per period = 3.017745
 0 - Front Left
 1 - Front Right
Time per period = 2.996523

All done!

Enjoy your new i2s amplifier!

スピーカーテスト

 改めてスピーカーテストを実行してみます。下記の様に speaker-test コマンドを実行すると、ホワイトノイズでスピーカーテストが実行されます。

$ speaker-test -c2

speaker-test 1.1.3

Playback device is default
Stream parameters are 48000Hz, S16_LE, 2 channels
Using 16 octaves of pink noise
Rate set to 48000Hz (requested 48000Hz)
Buffer size range from 2229 to 8916
Period size range from 1114 to 1115
Using max buffer size 8916
Periods = 4
was set period_size = 1114
was set buffer_size = 8916
 0 - Front Left
 1 - Front Right
Time per period = 5.823784
 0 - Front Left
 1 - Front Right
Time per period = 5.990661
 0 - Front Left
 1 - Front Right
Time per period = 5.990672
 0 - Front Left
 1 - Front Right

 さらに下記のように wav ファイルを指定すると wav ファイルを再生する形でテストが実行されます。

$ speaker-test -c2 --test=wav -w /usr/share/sounds/alsa/Front_Center.wav

speaker-test 1.1.3

Playback device is default
Stream parameters are 48000Hz, S16_LE, 2 channels
WAV file(s)
Rate set to 48000Hz (requested 48000Hz)
Buffer size range from 2229 to 8916
Period size range from 1114 to 1115
Using max buffer size 8916
Periods = 4
was set period_size = 1114
was set buffer_size = 8916
 0 - Front Left
 1 - Front Right
Time per period = 2.684377
 0 - Front Left
 1 - Front Right
Time per period = 2.856224
 0 - Front Left
 1 - Front Right
Time per period = 2.856005
 0 - Front Left
 1 - Front Right
Time per period = 2.855911

mp3 の再生

 先ほどのテストでは wav ファイルを再生しましたが、次は mp3 を再生してみます。再生するためのアプリケーションとして mpg123 を使用します。

mpg123, Fast MP3 Player for Linux and UNIX systems

 まず apt-get で mpg123 をインストールします。

$ sudo apt-get install -y mpg123

 あとは mpg123 コマンドで対象を指定すれば mp3 が再生されます。下記の例では mp3 のストリーミングの URL を指定してそのまま再生しています。

$ mpg123 http://ice1.somafm.com/u80s-128-mp3
High Performance MPEG 1.0/2.0/2.5 Audio Player for Layers 1, 2 and 3
        version 1.23.8; written and copyright by Michael Hipp and others
        free software (LGPL) without any warranty but with best wishes

Directory: http://ice1.somafm.com/

Terminal control enabled, press 'h' for listing of keys and functions.

Playing MPEG stream 1 of 1: u80s-128-mp3 ...
ICY-NAME: Underground Eighties: UK Synthpop and a bit of New Wave. [SomaFM]
ICY-URL: http://somafm.com

MPEG 1.0 L III cbr128 44100 stereo

ICY-META: StreamTitle='Comsat Angels - As Above So Below';StreamUrl='';

ICY-META: StreamTitle='Heaven 17 - Crushed By The Wheels Of Industry [(Parts One And Two) Uninterr';StreamUrl='';

音量調整

 再生用アプリケーション側でも音量調整機能を持っていることもありますが、コマンドラインから alsamixer を実行することで音量を調整することができます。

$ alsamixer

 実行すると下記のような画面が表示されますので、音量を指定し、 ESC で終了します。

f:id:akanuma-hiroaki:20180707173308p:plain

Python スクリプトからの実行

 コマンドラインからではなくプログラム内から音声を再生する方法は色々ありますが、ここでは Python スクリプトの中から音声を再生するために PyGame を使ってみます。インストールは apt-get install を実行するだけです。

$ sudo apt-get install python-pygame

 Adafruit から PyGame のサンプルも提供されていますので、こちらを使ってみます。ダウンロードして解凍します。

$ wget https://cdn-learn.adafruit.com/assets/assets/000/041/506/original/pygame_example.zip
$ unzip pygame_example.zip
$ cd pygame_example/

 サンプルスクリプトを実行してみます。このスクリプトは同じディレクトリにある全ての mp3 を再生します。引数にはボリュームの大きさを指定します。

$ python pygameMP3.py 0.75
Playing at volume: 0.75

['HiTomSamp.mp3', 'SnareSamp.mp3', 'FloorTomSamp.mp3', 'CrashSamp.mp3']
Music file HiTomSamp.mp3 loaded!
Music file SnareSamp.mp3 loaded!
Music file FloorTomSamp.mp3 loaded!
Music file CrashSamp.mp3 loaded!

まとめ

 Raspberry Pi 向けにはセットアップ用のスクリプトやサンプルが用意されているので、とても簡単に音を鳴らすところまでいけてしまいましたが、 Arduino 等で使用する場合にはセットアップ内容を理解して進めないといけないので、少し時間はかかりそうです。ただ今回使ったアンプとスピーカーは両方で1,000円程度でしたが、通知用等で使うには十分だったので、今後何か作ろうとした時にも気軽に取り入れられそうです。

BLE Nano V2 を mbed で Lチカ

 以前 BLE Nano の V1 で Lチカしてみた記事を書いたことがありますが、 BLE Nano は後継機の V2 が出てスイッチサイエンス等でも購入できるのは V2 のみになっています。

www.switch-science.com

 そこで今回は改めて BLE Nano の V2 でとりあえず Lチカまでをやってみました。 V1 と同様に初めて BLE Nano を使う場合は書き込み用のボードが必要なので、セットになったものを買うと良いのですが、 V1 の時に買ったものがあるので、そちらを使ってみました。

www.switch-science.com

 下記の画像のように V1 と V2 ではピン配置なども違うので、実装にあたっては注意が必要ですが、プログラムの書き込みだけであれば V1 の時のものも使えそうです。

f:id:akanuma-hiroaki:20180630233943j:plain

オンラインのコンパイラで環境設定

 まずはオンラインのコンパイラで環境設定から。チュートリアルは下記サイトに公開されていますので、こちらに従ってやってみます。

github.com

 mbed で開発をするにはまずは対象のボードを選択する必要があります。 BLE Nano の V1 の時は対象のボードとしても BLE Nano が選択できたのですが、 V2 はまだ対象のボードの選択肢には含まれていません。 V2 では SoC が V1 の Nordic nRF51822 から nRF52832 に変更になっていたりしますので、 V1 を選択して使うわけにもいきません。現状ではチュートリアルでも紹介れているように、 nRF52832 を搭載した開発ボードである nRF52-DK を対象として選択して開発するようです。

Nordic nRF52-DK | Mbed

 上記開発ボードのページの右側に Add to your Mbed Compiler というボタンがありますので、これをクリックするとオンラインのコンパイラにボードが追加されます。

f:id:akanuma-hiroaki:20180630224131p:plain

 次にコンパイラの画面に移って、右上のボード選択用のボタンをクリックします。

f:id:akanuma-hiroaki:20180630224356p:plain

 ボードの選択画面が開きますので、 nRF52-DK を選択してから右上の Select Platform ボタンをクリックします。

f:id:akanuma-hiroaki:20180630224431p:plain

Lチカ実行

 ここまででひとまず設定は終わりなのでプログラムを用意します。コンパイラの左上の 新規 > 新しいプログラム から新規のプログラムを作成します。

f:id:akanuma-hiroaki:20180630224819p:plain

 新しいプログラムの作成ダイアログが開いたら、プラットフォーム、テンプレート、プログラム名を下記のように設定して OK をクリックします。

f:id:akanuma-hiroaki:20180630225037p:plain

 すると下記のように Lチカのコードが含まれたプログラムが作成されます。

f:id:akanuma-hiroaki:20180630225235p:plain

 デフォルトでは LED のピンの指定が LED となっていますので、これを BLE Nano V2 の LED のピン番号である P0_11 に変更して、下記のようなコードにします。

#include "mbed.h"

DigitalOut led1(P0_11);

// main() runs in its own thread in the OS
int main() {
    while (true) {
        led1 = !led1;
        wait(0.5);
    }
}

 今回のコードはこれだけなので、画面上部の コンパイル をクリックしてコンパイルを実行します。

f:id:akanuma-hiroaki:20180630225533p:plain

 正常にコンパイルされると mbed-os-example-blinky_NRF52_DK.bin というファイルが生成されますので、これを DAPLINK として認識されている BLE Nano V2 に保存(アップロード)します。試した限りではアップロードが終わったら一度 BLE Nano V2 を抜いて挿し直さないとプログラムが実行されませんでした。

まとめ

 BLE Nano V2 そのものについての mbed でのサポートはまだ不十分なようですが、とりあえず Lチカだけであれば nRF52-DK をターゲットとすることで実行することができました。今回は CLI でのコンパイルと実行まではできませんでしたが、今後試してみたいと思います。また、 BLE を使った実装については、 nRF52832 を使ったサンプルはあまり多くなさそうなので、ちょっと苦戦しそうかなと思っています。

空気品質センサとサーボモータで環境の悪化を通知する

 前回ひとまず空気品質センサ CCS811 で二酸化炭素濃度などを測定してみました。今回は二酸化炭素濃度が閾値を超えたら何か目に見える形で通知するようにしてみたいと思います。オフィスの二酸化炭素濃度は業務のパフォーマンスにも影響するということで、チームラボさんも測定して改善されているようなので、二酸化炭素濃度が高くなっていることがわかるようにしてみたいと思います。

ch.nicovideo.jp

サーボモータを使ってみる

 二酸化炭素濃度が閾値を超えたら Slack に通知するというのをそのうちやりたいとは思ってるんですが、それ自体には自分としてはそんなに目新しさもなかったので、もうちょっとギミック的なものが作れないかなと、サーボモータを使ってみることにしました。

www.switch-science.com

 サーボモータは今まで使ったことなかったのですが、使い方は簡単で、 Arduino ではサーボモータ用のライブラリが標準で用意されているので、それを使用します。まずは servo.attach() で制御用のピンを指定して、 servo.write() で角度を指定するだけです。

#include <Servo.h>

Servo servo;

servo.attach(3); // 制御ピンに 3番ピンを指定する例
servo.write(90); // サーボの角度を 90度に指定する例

 今回はサーボモータに LED を組み合わせて、二酸化炭素濃度が閾値を超えたら LED が点灯しつつ持ち上がるようにしてみたいと思います。

回路図

 だいぶごちゃっとしてしまいましたが、回路図は下記のようになりました。

f:id:akanuma-hiroaki:20180619003533p:plain

 CCS811 の配線については基本は前回と同じですが、今回はサーボモータを使う都合で Seeeduino の動作電圧を 5V にしたので、 CCS811 の NOT_WAK ピンへの入力電圧が 3.3V 以下になるように、4.7kΩ の抵抗を使用して分圧しています。

 サーボモータの電源は 5V 出力から取り、制御ピンは 3番ピンを使用しています。

 LED には 330Ωの抵抗を使用しています。

ファームウェア

 ファームウェアも基本的には前回と同様で、サーボモータと LED の初期化と、二酸化炭素濃度の測定値によってサーボモータと LED を制御する処理を追加しています。

#include <SparkFunCCS811.h>
#include <Servo.h>

#define CCS811_ADDR 0x5B

#define PIN_NOT_WAKE 5
#define PIN_NOT_INT 6
#define PIN_SERVO 3
#define PIN_LED 4

#define DEGREE_CLEAN_AIR 90
#define DEGREE_DIRTY_AIR 0
#define CO2_THRESHOLD 1000

CCS811 myCCS811(CCS811_ADDR);
Servo servo;

void setup() {
  Serial.begin(115200);
  Serial.println();
  Serial.println("...");

  CCS811Core::status returnCode;
  
  returnCode = myCCS811.begin();
  Serial.print("CCS811 begin exited with: ");
  printDriverError( returnCode );
  Serial.println();
  
  returnCode = myCCS811.setDriveMode(2);
  Serial.print("Mode request exited with: ");
  printDriverError( returnCode );
  Serial.println();
  
  pinMode(PIN_NOT_INT, INPUT_PULLUP);
  returnCode = myCCS811.enableInterrupts();
  Serial.print("Interrupt configuation exited with: ");
  printDriverError( returnCode );
  Serial.println();
  
  pinMode(PIN_NOT_WAKE, OUTPUT);
  digitalWrite(PIN_NOT_WAKE, 1);
  
  servo.attach(PIN_SERVO);
  servo.write(90);
  
  pinMode(PIN_LED, OUTPUT);
}

void loop() {
  if (digitalRead(PIN_NOT_INT) == 0)
  {
    digitalWrite(PIN_NOT_WAKE, 0);
    delay(1);
    myCCS811.readAlgorithmResults();
    
    Serial.print("CO2[");
    Serial.print(myCCS811.getCO2());
    Serial.print("] tVOC[");
    Serial.print(myCCS811.getTVOC());
    Serial.print("] millis[");
    Serial.print(millis());
    Serial.print("]");
    Serial.println();
    
    if (myCCS811.getCO2() >= CO2_THRESHOLD)
    {
      digitalWrite(PIN_LED, HIGH);
      servo.write(DEGREE_DIRTY_AIR);
    }
    else
    {
      digitalWrite(PIN_LED, LOW);
      servo.write(DEGREE_CLEAN_AIR);
    }
    
    digitalWrite(PIN_NOT_WAKE, 1);
    delay(1);
  }
}

void printDriverError( CCS811Core::status errorCode )
{
  switch ( errorCode )
  {
    case CCS811Core::SENSOR_SUCCESS:
      Serial.print("SUCCESS");
      break;
    case CCS811Core::SENSOR_ID_ERROR:
      Serial.print("ID_ERROR");
      break;
    case CCS811Core::SENSOR_I2C_ERROR:
      Serial.print("I2C_ERROR");
      break;
    case CCS811Core::SENSOR_INTERNAL_ERROR:
      Serial.print("INTERNAL_ERROR");
      break;
    case CCS811Core::SENSOR_GENERIC_ERROR:
      Serial.print("GENERIC_ERROR");
      break;
    default:
      Serial.print("Unspecified error.");
  }
}

動作時の様子

 だいぶ雑多な感じですが、とりあえず組んでみた回路は下記のようになりました。

f:id:akanuma-hiroaki:20180619075828j:plain

 二酸化炭素濃度が閾値を超えると下記画像のようにサーボモータが 90度回転して、 LED も点灯します。

f:id:akanuma-hiroaki:20180619075858j:plain

まとめ

 今回はとりあえず配線した感じでしたが、もっと配線をすっきりさせて筐体に入れて、サーボが動いた時だけ筐体から出て見えるようにしたり、何か工夫できると面白そうかなと思いました。また、 LED はワニ口クリップで配線してるのですが、下手をするとショートする可能性もあるので、継続的に使うにはこの辺の安全性も改善したいところです。

空気品質センサ CCS811 で二酸化炭素濃度などを測定してみる

 最近オフィス内の人数も増えて来て、換気が不十分でオフィス内の空気が悪いと感じるメンバーが増えて来たこともあり、試しに二酸化炭素濃度などを測ってみようと下記センサーを買ってみました。

www.switch-science.com

 今回はとりあえず下記チュートリアルに従って、正しくセンサーを動作させることができるのか、どんな値が取れるのかを確認してみたいと思います。

CCS811 Air Quality Breakout Hookup Guide - learn.sparkfun.com

ピンヘッダ実装

 モジュールにはピンヘッダが実装されていないので、まずはピンヘッダを下記写真のように半田付けします。半田の量がまちまちなのはご愛嬌ということで。

f:id:akanuma-hiroaki:20180610212741j:plain

 NTCピンにNTCサーミスタを実装すれば温度を測定して空気測定値の補正に利用できるらしいのですが、今回はまだNTCサーミスタは入手していないので、とりあえずなしで使ってみます。

シンプルな計測サンプル

 まずは最もシンプルな構成で空気品質を測定してみたいと思います。実際は Seeeduino を利用したのですが、 Seeeduino の Fritzing のデータでは I2C の SCL と SDA を接続しているピンが不足していたので、回路図では Arduino を使用しています。また、 Seeeduino では動作電圧をスイッチで 5V か 3.3V に切り替えられますが、今回は CCS811 の動作電圧が 3.3V なので、 3.3V で動作させています。

f:id:akanuma-hiroaki:20180610213103p:plain

 では次にファームウェアの実装です。 CCS811 の Arduino 用ライブラリが SparkFun から提供されていますのでこれを使用します。

github.com

 Arduino Web Editor を使っていれば上記ライブラリはダウンロードやインポートをしなくてもそのまま利用できます。

f:id:akanuma-hiroaki:20180610213121p:plain

 コードはチュートリアルとほぼ同様ですが、下記のような実装になります。

#include <SparkFunCCS811.h>

#define CCS811_ADDR 0x5B

CCS811 mySensor(CCS811_ADDR);

void setup() {
  Serial.begin(115200);
  Serial.println("CCS811 Basic Example");
  
  CCS811Core::status returnCode = mySensor.begin();
  if (returnCode != CCS811Core::SENSOR_SUCCESS)
  {
    Serial.println(".begin() returned with an error.");
    while (1);
  }
}

void loop() {
  if (mySensor.dataAvailable())
  {
    mySensor.readAlgorithmResults();
    
    Serial.print("CO2[");
    Serial.print(mySensor.getCO2());
    Serial.print("] tVOC[");
    Serial.print(mySensor.getTVOC());
    Serial.print("] millis[");
    Serial.print(millis());
    Serial.print("]");
    Serial.println();
  }
  
  delay(10);
}

 setup() の中で mySensor.begin() することでセンサーを初期化し、失敗した場合はそこでスタックさせています。

 loop() の中では mySensor.dataAvailable() でまだ読み取っていない新しいデータが存在するかをチェックし、存在する場合は mySensor.readAlgorithmResults() で TVOC と eCO2 のレベルを計算させ、 mySensor.getCO2() と mySensor.getTVOC() でそれぞれの値を取得しています。

 また、このセンサーでは48時間のエージングと、20分のコンディショニングが推奨されていて、初めて実行した場合は下記のように有効な値が取得されません。

CO2[400] tVOC[0] millis[5996]
 tVOC[0] millis[6991]
CCS811 Basic Example
CO2[0] tVOC[0] millis[2017]
CO2[0] tVOC[0] millis[3011]
CO2[0] tVOC[0] millis[4006]
CO2[400] tVOC[0] millis[5002]
CO2[400] tVOC[0] millis[5996]
CO2[400] tVOC[0] millis[6991]
CO2[400] tVOC[0] millis[7987]
CO2[400] tVOC[0] millis[8981]
CO2[400] tVOC[0] millis[9976]
CO2[400] tVOC[0] millis[10972]
CO2[400] tVOC[0] millis[11966]
CO2[400] tVOC[0] millis[12961]
CO2[400] tVOC[0] millis[13957]
CO2[400] tVOC[0] millis[14952]

 48時間のエージングはまだ実行できていませんが、とりあえず20分のコンディショニング後は下記のように値が取れ始めました。

CO2[408] tVOC[1] millis[1224601]
CO2[408] tVOC[1] millis[1225595]
CO2[408] tVOC[1] millis[1226591]
CO2[408] tVOC[1] millis[1227586]
CO2[413] tVOC[1] millis[1228581]
CO2[415] tVOC[2] millis[1229577]
CO2[415] tVOC[2] millis[1230572]
CO2[415] tVOC[2] millis[1231566]
CO2[415] tVOC[2] millis[1232562]
CO2[415] tVOC[2] millis[1233557]
CO2[415] tVOC[2] millis[1234552]
CO2[418] tVOC[2] millis[1235548]
CO2[418] tVOC[2] millis[1236533]
CO2[413] tVOC[1] millis[1237528]
CO2[408] tVOC[1] millis[1238523]
CO2[408] tVOC[1] millis[1239519]
CO2[408] tVOC[1] millis[1240513]
CO2[408] tVOC[1] millis[1241508]
CO2[408] tVOC[1] millis[1242504]
CO2[405] tVOC[0] millis[1243499]

 二酸化炭素濃度なので、息を吹きかけると下記のように値が上昇するのがわかります。

CO2[817] tVOC[63] millis[1319103]
CO2[905] tVOC[76] millis[1320087]
CO2[1090] tVOC[105] millis[1321082]
CO2[1044] tVOC[98] millis[1322078]
CO2[1380] tVOC[149] millis[1323073]
CO2[967] tVOC[86] millis[1324068]
CO2[1113] tVOC[108] millis[1325064]
CO2[1316] tVOC[139] millis[1326059]
CO2[844] tVOC[67] millis[1327054]
CO2[1095] tVOC[105] millis[1328050]
CO2[825] tVOC[64] millis[1329045]
CO2[1215] tVOC[124] millis[1330040]
CO2[1349] tVOC[144] millis[1331036]
CO2[770] tVOC[56] millis[1332031]
CO2[1380] tVOC[149] millis[1333026]

 一度電源をOFFにしてまた起動すると下記のように初回起動と同様の状態になったので、コンディショニングは起動時に毎回必要なようです。

CCS811 Basic Example
CO2[0] tVOC[0] millis[2017]
CO2[0] tVOC[0] millis[3011]
CO2[0] tVOC[0] millis[4006]
CO2[400] tVOC[0] millis[5002]
CO2[400] tVOC[0] millis[5998]
CO2[400] tVOC[0] millis[6982]
CO2[400] tVOC[0] millis[7977]
CO2[400] tVOC[0] millis[8972]
CO2[400] tVOC[0] millis[9967]
CO2[400] tVOC[0] millis[10962]
CO2[400] tVOC[0] millis[11957]
CO2[400] tVOC[0] millis[12952]
CO2[400] tVOC[0] millis[13947]
CO2[400] tVOC[0] millis[14943]
CO2[400] tVOC[0] millis[15937]
CO2[400] tVOC[0] millis[16932]
CO2[400] tVOC[0] millis[17928]

Interrupt と Wake を使ったサンプル

 もう1つのサンプルとして、 Interrupt と Wake を使った例を試してみます。 CCS811 ではセンサーをスリープさせることで消費電力を抑え、新しいデータが取得された時にスリープを解除して値を取得することができます。回路図としては下記のように、先程までのものに加えて NOT WAKE ピンと NOT INT ピンへの接続を追加しています。チュートリアルでは動作電圧が 5V のマイコンを使っているため、 NOT WAKE ピンへの接続は電圧が 3.3V 以下になるように抵抗の接続などで電圧を下げていますが、今回は Seeeduino を 3.3V で動作させているので、直接接続しています。

f:id:akanuma-hiroaki:20180610213603p:plain

 ファームウェアの実装としては下記のようになります。 setup() の中で myCCS811.begin() するのは先程と同じですが、 myCCS811.setDriveMode(2) でデータ取得間隔を 10 秒に変更しています。また、 myCCS811.enableInterrupts() でデータ取得時の Interrupt を有効にしています。 pinMode() で NOT INT ピンをインプット用、 NOT WAKE ピンをアウトプット用として設定し、 NOT WAKE ピンは High 、つまりスリープ状態にしておきます。

 新しいデータが取得された時は NOT INT ピンが Low になるので、 loop() の中では NOT INT ピンの状態をチェックし、 Low になった(新しいデータが取得された)時に NOT WAKE ピンを Low にしてスリープ状態を解除し、 myCCS811.readAlgorithmResults() でレベルを計算させてからそれぞれの値を取得しています。

 NOT WAKE ピンの状態の切り替えには 20μs〜50μs かかるので、切り替え時には 1ms の delay を挟んでいます。

#include <SparkFunCCS811.h>

#define CCS811_ADDR 0x5B

#define PIN_NOT_WAKE 5
#define PIN_NOT_INT 6

CCS811 myCCS811(CCS811_ADDR);

void setup() {
  Serial.begin(115200);
  Serial.println();
  Serial.println("...");

  CCS811Core::status returnCode;
  
  returnCode = myCCS811.begin();
  Serial.print("CCS811 begin exited with: ");
  printDriverError( returnCode );
  Serial.println();
  
  returnCode = myCCS811.setDriveMode(2);
  Serial.print("Mode request exited with: ");
  printDriverError( returnCode );
  Serial.println();
  
  pinMode(PIN_NOT_INT, INPUT_PULLUP);
  returnCode = myCCS811.enableInterrupts();
  Serial.print("Interrupt configuation exited with: ");
  printDriverError( returnCode );
  Serial.println();
  
  pinMode(PIN_NOT_WAKE, OUTPUT);
  digitalWrite(PIN_NOT_WAKE, 1);
}

void loop() {
  if (digitalRead(PIN_NOT_INT) == 0)
  {
    digitalWrite(PIN_NOT_WAKE, 0);
    delay(1);
    myCCS811.readAlgorithmResults();
    
    Serial.print("CO2[");
    Serial.print(myCCS811.getCO2());
    Serial.print("] tVOC[");
    Serial.print(myCCS811.getTVOC());
    Serial.print("] millis[");
    Serial.print(millis());
    Serial.print("]");
    Serial.println();
    
    digitalWrite(PIN_NOT_WAKE, 1);
    delay(1);
  }
}

void printDriverError( CCS811Core::status errorCode )
{
  switch ( errorCode )
  {
    case CCS811Core::SENSOR_SUCCESS:
      Serial.print("SUCCESS");
      break;
    case CCS811Core::SENSOR_ID_ERROR:
      Serial.print("ID_ERROR");
      break;
    case CCS811Core::SENSOR_I2C_ERROR:
      Serial.print("I2C_ERROR");
      break;
    case CCS811Core::SENSOR_INTERNAL_ERROR:
      Serial.print("INTERNAL_ERROR");
      break;
    case CCS811Core::SENSOR_GENERIC_ERROR:
      Serial.print("GENERIC_ERROR");
      break;
    default:
      Serial.print("Unspecified error.");
  }
}

 これを実行すると下記のように値が取得されます。 CO2 が 1,000 を超えている部分は息を吹きかけてみたところです。

...
CCS811 begin exited with: SUCCESS
Mode request exited with: SUCCESS
20
28
Interrupt configuation exited with: SUCCESS
CO2[0] tVOC[0] millis[11563]
CO2[0] tVOC[0] millis[21618]
CO2[0] tVOC[0] millis[31675]
CO2[400] tVOC[0] millis[41733]
CO2[400] tVOC[0] millis[51789]
CO2[402] tVOC[0] millis[61845]
CO2[407] tVOC[1] millis[71902]
CO2[407] tVOC[1] millis[81959]
CO2[402] tVOC[0] millis[92016]
CO2[410] tVOC[1] millis[102073]
CO2[1228] tVOC[126] millis[112129]
CO2[1118] tVOC[109] millis[122182]
CO2[1245] tVOC[128] millis[132231]

まとめ

 今回はとりあえず値を取得してみるところまででしたが、エージングやコンディショニングに時間がかかるのが少々ネックではあるものの、値の取得自体は簡単にできたので、次回は値が一定値を超えた場合に何かしらの処理をしたり、ネットワーク通信などを取り入れて、オフィスの二酸化炭素濃度が一定値を超えたら通知するような物を作ってみようかと思います。

GPS モジュールで位置情報を取得してみる

 GPSモジュールで位置情報を使って何かやってみたかったので、下記モジュールを買って位置情報の取得を試してみました。今回は Seeeduino から GPS モジュールで取得した位置情報を読み出してみます。

www.switch-science.com

 チュートリアルが下記ページで公開されているので、この内容に沿って試してみました。

チュートリアルページ
https://learn.adafruit.com/adafruit-ultimate-gps

ピンヘッダの半田付け

 モジュールにはピンヘッダが同梱されていますが装着はされていないので、ブレッドボードで試せるように下記写真のように半田付けしました。

f:id:akanuma-hiroaki:20180603002826j:plain

シリアルポートから直接読み出す

 まずは GPS モジュールを Seeeduino の シリアル - USB コンバータに接続して直接位置情報を読み出してみます。配線は下記の回路図のようになります。 GPS モジュールの TX/RX ポートを Seeeduino の TX/RX ポートに接続していますので、 Seeeduino のチップは通さずに値を読み出す形になります。 電源電圧は 3.3V - 5V で、 Seeeduino の 5V 出力から GPS モジュールの VIN に入力しています。 Seeeduino は動作電圧も 3.3V か 5V を切り替えられるようになっていますが、今回は 5V で使用しています。

f:id:akanuma-hiroaki:20180603000356p:plain

 GPS モジュールは取得した位置情報(FIX)を毎秒シリアルポートに出力します。今回はシリアルモニタで直接確認するだけなので、 Seeeduino のファームウェア側でやることは特にないため、コードは下記のようにメソッド定義のみで Seeeduino にフラッシュします。

void setup() {    
}

void loop() {
}

 正常にフラッシュされると GPS モジュールからの値の出力が開始されます。今回は Arduino Web Editor を使い、下記画像のようにシリアルモニターで出力を確認してみました。

f:id:akanuma-hiroaki:20180603000834p:plain

 GPS の位置情報は NMEA というフォーマットで出力されます。 NMEA の中でもいくつかのデータタイプがあり、各行の先頭でその行がどのデータタイプのデータかを示しています。よく使われるのは GPRMC というデータタイプのようで、チュートリアルでも GPRMC を対象としているので、今回は GPRMC について確認します。

 また、GPS モジュールでは FIX が取得できてもできなくても値を出力します。上記画像では室内で動作させていたため FIX が取得できず、カンマばかりで出力されていますが、屋外で動作させると下記のように FIX が取得できます。(緯度・経度はサンプル値に置き換えてあります)

$GPRMC,081033.000,A,1234.5678,N,12345.6789,E,0.36,168.34,020618,,,A*65

 それぞれの値の意味は下記の通りです。

$GPRMC: データタイプ

081033.000: UTCにおけるタイムスタンプ。先頭から2桁ずつ 時、分、秒を表し、ドット以降の3桁がミリ秒を表す。(08:10:33.000 UTC)

A: ステータスコード。A は FIX が取得できていること(Active)を表し、 V は FIX が取得できていないこと(Void)を表す。

1234.5678,N: 緯度情報。先頭2〜3桁が度数を表し、ドットの前の2桁以降で分数を表す。また、 N は北緯(North)を表し、南緯(South)の場合は S で表される。(北緯 12度 34.5678分)

12345.6789,E: 経度情報。先頭2〜3桁が度数を表し、ドットの前の2桁以降で分数を表す。また、 E は東経(East)を表し、西経(West)の場合は W で表される。(東経 123度 45.6789分)

0.36: 移動速度(ノット)

168.34: 移動方向(168.34度)

020618: UTCにおける日付。フォーマットは dd/mm/yy。(2018/06/02)

A*65: チェックサム

 ちなみに Google Map では緯度、経度については N, S, W, E の代わりに +/- を使用する必要があります。N, E は +、 S, W は - で表しますので、上記の内容は +12 34.5678', +123 45.6789' とする必要があります。

Seeeduino 経由で位置情報を読み出す

 では次に Seeeduino を経由して GPS モジュールの位置情報を読み出してみたいと思います。配線図は下記の通りです。 GPS モジュールの TX/RX ピンへの接続をそれぞれ Seeduino の 3番ピン/2番ピン に変更しただけです。

f:id:akanuma-hiroaki:20180603000901p:plain

 続いてファームウェアを用意します。今回使用している GPS モジュールについては Adafruit から Arduino 用のライブラリが提供されています。ライブラリにはサンプルコードも含まれているので今回はそれをそのまま使用してみます。

github.com

 Arduino Web Editor であれば上記サイトからダウンロードしなくても、 Library Manager から Favorite に登録して使うことができます。

f:id:akanuma-hiroaki:20180603001014p:plain

 ライブラリの中の Examples フォルダにサンプルコードが入っていますので、今回はその中から echo というサンプルを使用します。

f:id:akanuma-hiroaki:20180603001108p:plain

 コンパイルと Seeeduino へのフラッシュができるように自分のスペースにサンプルコードを保存します。

f:id:akanuma-hiroaki:20180603001532p:plain

 コピーが保存されたらそのまま Seeduino にフラッシュすると、先ほどと同様に GPS モジュールから取得された位置情報がシリアルモニタに出力されるようになります。

f:id:akanuma-hiroaki:20180603001205p:plain

 ちなみにサンプルコードからコメント行を取り除いたコードは下記のようになります。今回の GPS モジュールではデータの出力頻度を 1Hz, 5Hz, 10Hz から選択できるようになっていて、下記のコードは 1Hz に設定した例になります。

#include <Adafruit_GPS.h>

#include <Adafruit_GPS.h>
#if ARDUINO >= 100
 #include <SoftwareSerial.h>
#else
#endif

#if ARDUINO >= 100
  SoftwareSerial mySerial(3, 2);
#else
  NewSoftSerial mySerial(3, 2);
#endif
Adafruit_GPS GPS(&mySerial);


#define GPSECHO  true

boolean usingInterrupt = false;
void useInterrupt(boolean); // Func prototype keeps Arduino 0023 happy

void setup()  
{    
  Serial.begin(115200);
  Serial.println("Adafruit GPS library basic test!");

  GPS.begin(9600);
  
  GPS.sendCommand(PMTK_SET_NMEA_OUTPUT_RMCGGA);
  
  GPS.sendCommand(PMTK_SET_NMEA_UPDATE_1HZ);
  GPS.sendCommand(PMTK_API_SET_FIX_CTL_1HZ);

  GPS.sendCommand(PGCMD_ANTENNA);

  useInterrupt(true);
  
  delay(1000);
}

SIGNAL(TIMER0_COMPA_vect) {
  char c = GPS.read();
  if (GPSECHO)
    if (c) UDR0 = c;  
}

void useInterrupt(boolean v) {
  if (v) {
    OCR0A = 0xAF;
    TIMSK0 |= _BV(OCIE0A);
    usingInterrupt = true;
  } else {
    TIMSK0 &= ~_BV(OCIE0A);
    usingInterrupt = false;
  }
}


void loop()                     // run over and over again
{
}

位置情報のパースサンプル

 位置情報の生データはそのままではみづらいので、サンプルの中からもう1つ、位置情報をパースしている例を見てみます。先ほどの echo の場合と同じような形で、今回は parsing というサンプルコードをコピーします。

f:id:akanuma-hiroaki:20180603001227p:plain

 コメントを除いたコードは下記のようになります。 GPS ライブラリを使用し、パースした位置情報をシリアルモニタに出力しています。

#include <Adafruit_GPS.h>
#include <SoftwareSerial.h>

SoftwareSerial mySerial(3, 2);

Adafruit_GPS GPS(&mySerial);

#define GPSECHO  true

boolean usingInterrupt = false;
void useInterrupt(boolean); // Func prototype keeps Arduino 0023 happy

void setup()  
{
  Serial.begin(115200);
  Serial.println("Adafruit GPS library basic test!");

  GPS.begin(9600);
  
  GPS.sendCommand(PMTK_SET_NMEA_OUTPUT_RMCGGA);
  
  GPS.sendCommand(PMTK_SET_NMEA_UPDATE_1HZ);   // 1 Hz update rate

  GPS.sendCommand(PGCMD_ANTENNA);

  useInterrupt(true);

  delay(1000);
  mySerial.println(PMTK_Q_RELEASE);
}

SIGNAL(TIMER0_COMPA_vect) {
  char c = GPS.read();
#ifdef UDR0
  if (GPSECHO)
    if (c) UDR0 = c;  
#endif
}

void useInterrupt(boolean v) {
  if (v) {
    OCR0A = 0xAF;
    TIMSK0 |= _BV(OCIE0A);
    usingInterrupt = true;
  } else {
    TIMSK0 &= ~_BV(OCIE0A);
    usingInterrupt = false;
  }
}

uint32_t timer = millis();
void loop()                     // run over and over again
{
  if (! usingInterrupt) {
    char c = GPS.read();
    if (GPSECHO)
      if (c) Serial.print(c);
  }
  
  if (GPS.newNMEAreceived()) {
    if (!GPS.parse(GPS.lastNMEA()))   // this also sets the newNMEAreceived() flag to false
      return;  // we can fail to parse a sentence in which case we should just wait for another
  }

  if (timer > millis())  timer = millis();

  if (millis() - timer > 2000) { 
    timer = millis(); // reset the timer
    
    Serial.print("\nTime: ");
    Serial.print(GPS.hour, DEC); Serial.print(':');
    Serial.print(GPS.minute, DEC); Serial.print(':');
    Serial.print(GPS.seconds, DEC); Serial.print('.');
    Serial.println(GPS.milliseconds);
    Serial.print("Date: ");
    Serial.print(GPS.day, DEC); Serial.print('/');
    Serial.print(GPS.month, DEC); Serial.print("/20");
    Serial.println(GPS.year, DEC);
    Serial.print("Fix: "); Serial.print((int)GPS.fix);
    Serial.print(" quality: "); Serial.println((int)GPS.fixquality); 
    if (GPS.fix) {
      Serial.print("Location: ");
      Serial.print(GPS.latitude, 4); Serial.print(GPS.lat);
      Serial.print(", "); 
      Serial.print(GPS.longitude, 4); Serial.println(GPS.lon);
      Serial.print("Location (in degrees, works with Google Maps): ");
      Serial.print(GPS.latitudeDegrees, 4);
      Serial.print(", "); 
      Serial.println(GPS.longitudeDegrees, 4);
      
      Serial.print("Speed (knots): "); Serial.println(GPS.speed);
      Serial.print("Angle: "); Serial.println(GPS.angle);
      Serial.print("Altitude: "); Serial.println(GPS.altitude);
      Serial.print("Satellites: "); Serial.println((int)GPS.satellites);
    }
  }
}

 このコードを Seeeduino にフラッシュすると、位置情報を下記のように見易くフォーマットしてシリアルモニタに出力します。

Time: 8:27:57.0
Date: 3/6/2018
Fix: 1 quality: 1
Location: 1234.5678N, 12345.6789E
Location (in degrees, works with Google Maps): 12.3456, 123.4567
Speed (knots): 0.28
Angle: 9.54
Altitude: 130.50
Satellites: 5

まとめ

 サンプルコードやライブラリのおかげもあり、位置情報の取得自体はとても簡単にできてしまいました。モジュールのサイズも小さいので、 Trinket などの小型のマイコンやコイン型電池等と組み合わせればとてもコンパクトなデバイスが作れそうです。ただその場合はネットワークへの接続が課題になってくるので、リアルタイムでのトラッキングではなく、このモジュールが持っているロギング機能で位置情報データを蓄積しておいて、あとで BLE や Wi-Fi 等に接続できる環境になったらデータを取り出して利用するような用途であれば比較的簡単に作成できそうに思いました。

Amazon FreeRTOS のサンプルを実機で動かしてみる

 もう結構前の話になってしまいますが、 re:Invent 2017で Amazon FreeRTOS が発表されました。

aws.amazon.com

 そもそも RTOS とはなんぞやというような説明はここでは割愛しますが、発表されてから気になっていたので今回は実機でチュートリアルに従ってサンプルを動かすまでをやってみました。

開始方法はこちらで紹介されています。
Amazon FreeRTOS – 開始方法 – AWS

対象ハードウェア

 公式にサポートされているハードウェアは開始方法のページでも紹介されていて、まだまだ限定的ですが、その中でも Espressif の ESP32-DevKitC は秋月電子で簡単に購入できそうだったので今回はこちらを使用してみます。

akizukidenshi.com

 ESP32-DevKitC は以前 ESP-WROOM-32 を使ってみようとした時に、突入電流対策が取られていなかったようなので ESPr Developer 32 の方を選択したのですが、今回は多少の不安はありつつも楽観的に考えて使ってみることにしました。

 チュートリアルは対象ハードウェア別に用意されていて、 ESP32-DevKitC のチュートリアルは下記ページになります。

docs.aws.amazon.com

事前準備

 各ハードウェア向けの作業をする前にいくつか事前準備をしておきます。まずは使用する AWS アカウントの IAM ユーザに下記ポリシーを追加し、 FreeRTOS と AWS IoT 関連の権限を付与しておきます。

  • AmazonFreeRTOSFullAccess

  • AWSIoTFullAccess

 次に AWS IoT コンソールから、デバイスで使用するためのポリシーを作成しておきます。 AWS IoT のコンソールの左メニューから 安全性 -> ポリシー を選択して、 ポリシーの作成 をクリックします。

f:id:akanuma-hiroaki:20180527175213p:plain

 ポリシーの作成画面になりますので、自分でわかりやすい名前を入力し、ポリシーの設定方法を アドバンストモード に変更した上で、 Json ドキュメントを入力して設定します。チュートリアルで記載されている Json は下記のようなものになります。

{
    "Version": "2012-10-17",
    "Statement": [
    {
        "Effect": "Allow",
        "Action": "iot:Connect",
        "Resource":"arn:aws:iot:<aws-region>:<aws-account-id>:*"
    }, 
    {
        "Effect": "Allow",
        "Action": "iot:Publish",
        "Resource": "arn:aws:iot:<aws-region>:<aws-account-id>:*"
    },
    {
         "Effect": "Allow",
         "Action": "iot:Subscribe",
         "Resource": "arn:aws:iot:<aws-region>>:<aws-account-id>:*"
    },
    {
         "Effect": "Allow",
         "Action": "iot:Receive",
         "Resource": "arn:aws:iot:<aws-region>:<aws-account-id>:*"
    }
    ]
}

 しかしこれだと実際に動かしてみた時に権限が足りず、 AWS IoT に接続できなかったので、細かくどの権限が足りなかったのかは確認しませんでしたが、とりあえず AWS IoT に関して全ての操作が可能なように下記のように設定しました。

{
  "Version": "2012-10-17",
  "Statement": [
    {
      "Effect": "Allow",
      "Action": [
        "iot:*"
      ],
      "Resource": [
        "*"
      ]
    }
  ]
}

 入力したら 作成 ボタンをクリックしてポリシーを作成します。

f:id:akanuma-hiroaki:20180527175232p:plain

 次に今回使用するハードウェアをモノ(Thing)として登録します。 AWS IoT コンソールの左メニューから 管理 -> モノ と選択して、 作成 ボタンをクリックします。

f:id:akanuma-hiroaki:20180527175311p:plain

 今回は1つだけ登録するので、 単一のモノを作成する をクリックします。

f:id:akanuma-hiroaki:20180527175329p:plain

 作成フォームが表示されたら登録するモノの名前を入力して 次へ をクリックします。

f:id:akanuma-hiroaki:20180527175530p:plain

 モノで使用する証明書を作成します。 「1-Click 証明書作成」の 証明書の作成 ボタンをクリックします。

f:id:akanuma-hiroaki:20180527175709p:plain

 証明書が作成されたら証明書とキーを ダウンロード をクリックしてダウンロードしておきます。また 有効化 ボタンをクリックして証明書を有効化して、 ポリシーのアタッチ ボタンをクリックします。

f:id:akanuma-hiroaki:20180527175726p:plain

 先ほど作成したポリシーを選択して、 モノの登録 ボタンをクリックします。これでひとまずの事前準備は終了です。

f:id:akanuma-hiroaki:20180527175757p:plain

Toolchain のセットアップ

 ここからは対象とするハードウェアを使うための設定になります。まずは Toolchain のセットアップです。 Espressif のサイトからダウンロードして展開します。

$ cd
$ mkdir esp
$ cd esp
$ wget https://dl.espressif.com/dl/xtensa-esp32-elf-osx-1.22.0-80-g6c4433a-5.2.0.tar.gz
$ tar zxf xtensa-esp32-elf-osx-1.22.0-80-g6c4433a-5.2.0.tar.gz

 あとはパスを通すために .bashrc などに下記のように export を追加すればOKです。

$ tail -2 ~/.bashrc 
### ESP32 toolchain
export PATH=$PATH:$HOME/esp/xtensa-esp32-elf/bin

Amazon FreeRTOS のダウンロード

 Amazon FreeRTOS は下記のリポジトリで公開されています。

github.com

 これを git clone でローカルに取得します。

$ git clone https://github.com/aws/amazon-freertos.git 
$ cd amazon-freetos

プロジェクト設定

 FreeRTOS をビルドするには Python 2.7.10 以上の環境が必要になります。最新の 3.6.5 で試してみたところ make に失敗したので、 pyenv で2系の最新の 2.7.15 の環境を用意しました。

$ pyenv versions
  system
* 2.7.15 (set by /Users/akanuma/workspace/amazon-freertos/.python-version)
  3.6.5

 pyserial と boto3 も必要なので、 pip でインストールします。

$ pip install pyserial
$ pip install boto3

 ダウンロードした FreeRTOS の中にはセットアップを簡単にするための設定ファイルが含まれています。下記ファイルの中の設定を実際に資料する環境に合わせて変更します。

$ cat demos/common/tools/aws_config_quick_start/configure.json 
{
    "thing_name":"ESP32-DevKitC",
    "wifi_ssid":"xxxxxxxxxx",
    "wifi_password":"xxxxxxxxxxxxx",
    "wifi_security":"eWiFiSecurityWPA2"
}

 そしてセットアップスクリプトを実行します。

$ cd demos/common/tools/aws_config_quick_start
$ python SetupAWS.py setup

 ここまで来たら ESP32-DevKitC を Mac に USB ケーブルで接続します。すると私の環境では /dev/cu.SLAB_USBtoUART のように認識されました。

 次に下記コマンドを実行して Espressif IoT Development Framework の設定メニューを表示させます。

$ cd demos/espressif/esp32_devkitc_esp_wrover_kit/make/
$ make menuconfig

 メニューが表示されたら Serial flasher config メニューを選択します。

f:id:akanuma-hiroaki:20180527205232p:plain

 Default serial port を環境に合わせて設定します。

f:id:akanuma-hiroaki:20180527205430p:plain

 必要に応じて Default baud rate の設定も行い、設定を保存してメニューを終了させます。

 あとはこれで最後に make コマンドを実行すれば AWS IoT に接続して MQTT のメッセージの送受信を行うデモが実行されるのですが、私の場合は途中で Python のバージョンの切り替えをしたりしていたせいか、 demos/common/tools/aws_config_quick_start/configure.json に設定した Wi-Fi の AP の情報が反映されておらず、また、証明書の情報も設定されていなかったので、下記ファイルを直接変更して設定しました。

$ ls -l demos/common/include/aws_clientcredential*
-rw-r--r--  1 akanuma  staff  2814 May 27 03:43 demos/common/include/aws_clientcredential.h
-rw-r--r--@ 1 akanuma  staff  3873 May 27 03:55 demos/common/include/aws_clientcredential_keys.h

 Wi-Fi AP の情報は aws_clientcredential.h を直接編集しました。また、 aws_clientcredential_keys.h の方は生成ツールが下記パスに用意されています。

demos/common/devmode_key_provisioning/CertificateConfigurationTool/CertificateConfigurator.html

 これをブラウザで開くとクライアント証明書とプライベートキーのファイルをアップロードできるフォームが開きますので、事前準備で AWS IoT コンソールからダウンロードしたファイルを指定し、 aws_clientcredential_keys.h を生成します。

f:id:akanuma-hiroaki:20180527210214p:plain

サンプルの実行

 それではサンプルを実行します。下記のように make コマンドを実行すると、ファームウェアが実機に書き込まれ、実行されます。

$ cd demos/espressif/esp32_devkitc_esp_wrover_kit/make/
$ make flash monitor

 内容としては、ファームウェアのビルド、実機への書き込み、 Wi-Fi 接続、AWS IoT への接続が行われ、 MQTT によるメッセージの送信が12回行われて終了します。エラーなく最後に下記のログが出力されれば成功です。

352 6936 [MQTTEcho] MQTT echo demo finished.

 AWS IoT コンソールからもメッセージの送受信が行われたことが確認できます。

f:id:akanuma-hiroaki:20180527212406p:plain

まとめ

 とりあえずチュートリアルに従うことで実機でサンプルを動作させることはできました。実際に自前のファームウェアを実装していく方法はまだよくわかってませんが、 Amazon FreeRTOS には Greengrass 等の AWS サービスと連携するためのライブラリも含まれていたり、 OTA でのファームウェアアップデートもできるようなので、そのあたりの使い方も今後調べてみたいと思います。