加速度センサを使って犬用Wifi歩数計を作ってみた

## 背景
「くるみちゃん」というシルバーのトイプードルを飼っています。
留守番中は殆ど寝て過ごしているようなので、運動不足が心配です。

運動量を定量化するアイテムとして、[犬用歩数計](http://www.yamasa-tokei.co.jp/seihin/pet/wp_350.html)がありますが、我が家用にカスタマイズした物が作れないか検討しました。

## 目指すゴールと落とし所
実現できるかはさておき、欲しい仕様としては次のとおりです。
**● なるべく小型軽量**　・・・これが一番むずかしそう
**● 乾電池で動く**　　・・・ポータブルバッテリーは小型高出力で便利だけど発火するかもなのでダメ絶対
**● 無線通信、データ自動配信**　　・・・めんどくさい事は全部自動化すると家庭平和に繋がります(家訓)

我が家には既に、[ラズパイを使ったペットを見守るシステム](https://qiita.com/tokotan/items/2596fd5d7fed6e8d1905)が稼働中なので、BLEモジュールと加速度センサを使って測定した歩数を、ラズパイに渡してデータ処理する案も検討したのですが、初心者には難しそうと断念。
Arduinoが使えるWifiモジュールなら情報も豊富にあるし、使えるかも！と考えたので、設計制作してみました。

## 先に結論を
まだケースがありませんが、後述の小型電池を使用することで、30x35x15mmの大きさには収まりそうです。
ユニバーサル基板ではなく、専用基板を設計、部品を実装して、もっと小型化できれば、くるみちゃんに装着できそう！
CADの3Dモデルで合成してみました。こんなイメージです。
![kurumi.png](https://qiita-image-store.s3.amazonaws.com/0/247233/4574e701-9905-0c10-76fc-670f462e890b.png)

とりあえず単三乾電池3本で丸一日以上は安定稼働しています。電池が切れたら記事を更新して報告します。
乾電池は大きいし重すぎるので、乾電池の半分サイズで出力のある[リチウム電池ＣＲ２](http://akizukidenshi.com/catalog/g/gB-10130/)に置き換えたいですね。

### 電池稼働時間
(2018/06/10追記)

| 使用電池 | 総稼働時間 | 備考 |
|:---------------------|:---------------|:------------------|
| 単三乾電池3本 | 約2日間 | 連続稼働は約30時間 |
| [リチウム電池CR2](http://akizukidenshi.com/catalog/g/gB-10130/) | 約28時間 | --- |
| [リチウム電池CR123A](http://akizukidenshi.com/catalog/g/gB-10131/) | 約72時間 | --- |

どれも寿命短っ！
電池によるモバイル運用は厳しそうです。
実運用のためにはArduino再選定など、大幅なシステム見直し必須ですね。

単三乾電池3本の場合は、丸一日を過ぎた所でゴミデータを吐き出しながら停止。
暫く時間を置くと手動で再起動できました。上記総稼働時間は、これを数回繰り返した合計時間です。
リチウム電池は一度停止すると時間をおいても手動でも再起動できませんでした。
乾電池に比べて出力がガクッと下がる特性のためと思われます。

## 配線図
![wanpo-kei01_ブレッドボード.png](https://qiita-image-store.s3.amazonaws.com/0/247233/f8f76b8d-120f-416a-cfa1-8eedb9f8fde4.png)

ブレッドボード図は見やすいものを配置しましたが、実際には次のものを使用しました。

| 主なモジュール | 型式 | 詳細 |
|:---------------------|:---------------|:------------------|
| Wifi対応Arduino | ESP-WROOM2 (ESP8266) | [ブレーク基板(フル版)](https://www.switch-science.com/catalog/2347/) |
| 加速度センサ | ADXL345 | [秋月電子の小さいやつ](http://akizukidenshi.com/catalog/g/gM-06724/) |
| データ格納用 | SparkFunマイクロSDスロット | [ただのピッチ変換基板](https://www.switch-science.com/catalog/36/) |

ESP-WROOM2(以下、ESP8266)は、Arduino化して使用します。
マイクロSDはSPI通信で接続。本当は[こちら](http://akizukidenshi.com/catalog/g/gK-05818/)のように全てプルアップした方が良いんでしょうけれど、図のとおり直結でも動作しました。
加速度センサはI2C通信で接続しています。

図の左側の電源線とマゼンダ色の線をUSBシリアル変換に繋いで、書込み/デバッグしていました。三端子レギュレータは、USBの5Vを3.3Vに降圧するためのものです。スライドスイッチはWRITE/RUNのモード切替用です。これらは、デバッグ完了後は全て不要となります。

ESP8266の上側面(+3.3Vピンのある方)にSDカードを、下側面(書込み用RX,TXピンのある方)に加速度センサをまとめて接続して、配線をスッキリさせているのが、ちょっとしたコダワリです(笑)

## スケッチ全体構成
Arduinoスケッチは、次のような構成にしました。
各項目の詳細は後述します。

```ruby:冒頭
    1. ライブラリ宣言
    2. 変数の宣言
```
```ruby:setup内
    1. SDカード、加速度センサの初期設定
    2. SDカードから設定ファイル読込み
    3. Wifi接続
    4. NTP時刻取得、Slack投稿
    5. Wifi切断節電
```
```ruby:loop内
    1. 時刻更新。指定時刻であればディープスリープして再起動
    2. 加速度を取得、データ処理して歩数カウント
    3. SDカードにデータ格納
```

自動で定期投稿させる時はWifiに接続する必要がありますが、それ以外はオフする事で電池消耗を抑えたいと考えました。
そこで、通信作業をsetup内でまとめて処理して、loop中はWifiオフして歩数計測に専念させています。指定時刻にディープスリープ復帰する事で、一日一回の時刻更新とSlack投稿を実現しています。

## ESP-WROOM2(ESP8266)の準備
ESP8266はプルアップ/プルダウン抵抗が必要で、Arduino-IDEで環境設定もあり、少し手間かかりますが、日本語で解説いただいている記事も多く、その通りにやれば安定稼働できました。主に次の記事を参考にさせていただきました。
>[技適済み格安高性能Wi-FiモジュールESP8266をArduinoIDEを使ってIoT開発する為の環境準備を10分でやる方法](https://qiita.com/azusa9/items/264165005aefaa3e8d7d)

>[ESP-WROOM-02 に関する記事](https://ht-deko.com/arduino/esp-wroom-02.html)

続いて、スケッチの詳細です。

## SDから設定ファイル読込、Wifi接続
SDカード内に設定用TXTファイルを入れておき、読み出すようにしました。開発環境とは異なるWifiに繋ぐときに焼き直す必要が無くなります。
SSID、パスワード、さらに後述のSlack投稿URLも格納して使っています。
次の記事を参考にしました。

>[(作業ログ)ESP8266でアナログデータをwifiで飛ばしてみる(その２)](https://qiita.com/cyclon2joker/items/87e9700330d413cb2b4f)

>[技適済み格安高性能Wi-FiモジュールESP8266 コピペでWiFi接続出来るArduinoIDE用ソースコードとその解説](https://qiita.com/azusa9/items/7f78069cb09872cf6cbf)

```ruby:config.txt
ssid=********
pass=********
url=https://hooks.slack.com/services/********
```

```ruby:スケッチ内
  // SDカードからSSID,PASS読込
  String line;
  String my_ssid, my_pass, my_url;
  char c;
  File configF = SD.open("config.txt", FILE_READ);
  if (configF) {
    line = "";
    while (configF.available()) {
      c = configF.read();
      if (c == '\n' || c == '\r') {
        if (line.length() > 0) {
          if (line.startsWith("ssid=")) {
            my_ssid = line.substring(String("ssid=").length(), line.length());
          } else if (line.startsWith("pass=")) {
            my_pass = line.substring(String("pass=").length(), line.length());
          } else if (line.startsWith("url=")) {
            my_url = line.substring(String("url=").length(), line.length());
          }
        }
        line = "";
      } else{
        line = line + String(c);   
      }
    }
  configF.close();
  }else{
    Serial.println("error opening config.txt...");
  }

  WiFi.begin(my_ssid.c_str(), my_pass.c_str());
  while (WiFi.status() != WL_CONNECTED) {
    delay(500);
    Serial.print(".");
  }
    Serial.println("");
```

### つまづきポイント
Arduinoが使えるファイル名は英数字8文字までだそうです。
当初「configfile.txt」という名前にしていたら全く読み込めず、意外とハマりました。

## 時刻取得(NTP)、タイマー設定
NTPサーバーから現在時刻を取得します。次の記事にあるライブラリをありがたく使用させていただきました。ZIPファイルをダウンロードしてから
スケッチ => ライブラリをインクルード => ZIP形式のライブラリをインストール
して使用しました。

>[ESP8266 (ESP-WROOM-02) でNTP(時刻合わせ)をするライブラリ。(Timeライブラリと連携して)](https://qiita.com/exabugs/items/b1b7430d185b268a1abf)

```ruby:setup内のNTP取得箇所
  ntp_begin(2390);
  setSyncInterval(24*3600);  // NTP同期間隔は24時間に設定
```
```ruby:loop内の時刻更新、タイマー設定箇所
  nt = now();
  ti = localtime(nt, 9);
  char jikoku[20];
  const char* format = "%02d-%02d %02d:%02d:%02d";
  sprintf(jikoku, format, month(ti), day(ti), hour(ti), minute(ti), second(ti));
  if(hour(ti) == 23 && minute(ti) == 59){
    ESP.deepSleep(2*60*1000*1000 , WAKE_RF_DEFAULT);  //2分後再起動
    delay(1000);
  }
```

「jikoku」に月日と時間を並べて格納しており、後述するデータ保存に使ってます。
ライブラリのおかげで非常に簡単に表現できましたし、時間指定も分かりやすい記述になりました。

指定時刻(23:59)になったら2分間のディープスリープに入り、再起動する事でsetup内のSlack投稿を実行します。かなり無理矢理な感じですが、他に良い方法が思い付きませんでした。。。。

## Slack投稿
次の記事などを参考にさせていただきました。JSONを理解できておらず、殆どコピペなので、詳細な説明は割愛させていただきます。m(_ _)m

>[ESP8266を使って玄関ドアを開けたらSlackに通知するマシンを作って、実用できるか試す](https://qiita.com/kiyopikko/items/4c525103700f52cb2894)

>[対人センサーで侵入者を検知してSlackに通知するbot（ESP8266＆HC-SR501編）](https://qiita.com/yukitter/items/d726225f7cd21995897b)

次のリンク先ページから、投稿したいチェンネルなどを選んで、Webhook URLを入手します。
https://my.slack.com/services/new/incoming-webhook/

ちょうど良いアイコンを見つけたので、こんな感じにしてみました。
>![slack02.png](https://qiita-image-store.s3.amazonaws.com/0/247233/7f6e639f-aef3-73e6-4993-3fa4ac82ec9a.png)

歩数もSDカードに格納しているので、途中で電池が切れても電源が生きてた時点までの歩数は投稿する事ができます。
SDカード読み出しに失敗したか、ファイルが消えてしまった場合はこちら。
>![slack01.png](https://qiita-image-store.s3.amazonaws.com/0/247233/83bed589-0886-9f2c-2829-457f10365865.png)

## Wifi停止節電
ディープスリープに関する記事は多数あったのですが、Wifiのみオフする方法を調べるのに意外と苦労しました。次のブログ記事を参考にさせていただきました。

>[ESP-WROOM-02 のWiFiを止めるには？　(Arduino)](http://makers-with-myson.blog.so-net.ne.jp/2016-05-15)

```ruby:
  //wifiオフ節電
  WiFi.disconnect();
  WiFi.mode(WIFI_OFF);
  WiFi.forceSleepBegin();
```
一行目二行目だけでもWifi接続できない状態に遷移してるっぽいです。
アナログ電流計で確認したところ、三行とも書いた結果が最も節電効果が高そうなので、結局そのままです。

## 加速度の取得とデータ処理
ArduinoIDEのライブラリにもスケッチ例が多数ありますが、用途に合うものが見付かりませんでした。
次のブログ記事が分かりやすかったので、参考にさせていただきました。

>[3軸加速度センサー(ADXL345)の使い方 - I2C版 (Arduino)](https://www.petitmonte.com/robot/howto_adxl345_i2c.html)

```ruby:
  // XYZ軸の加速度データを取得
  int i;
  for (i=0; i< 6; i++){
    while (Wire.available() == 0 ){}
    RegTbl[i] = Wire.read();
  }
  int16_t x = (((int16_t)RegTbl[1]) << 8) | RegTbl[0];
  int16_t y = (((int16_t)RegTbl[3]) << 8) | RegTbl[2];
  int16_t z = (((int16_t)RegTbl[5]) << 8) | RegTbl[4];  
  // XYZ軸の合成
  float accel;
  accel = sqrt( sq(x) + sq(y) + sq(z) );

  // 閾値設定
  if (count < 100) {
    total += accel;
    count++;
  } else {
    threshold = total / count;
    hysteresis = threshold / 10;
    total = 0;
    count = 0;
  }
  //閾値判定
  if (accel > (threshold + hysteresis)) {
    state = true;
  } else if (accel < (threshold - hysteresis)) {
    state = false;
  }
  //歩数カウント
  if (laststate == false && state == true) {
    stepcount++;
    laststate = state;
  } else if (laststate == true && state == false) {
    laststate = state;
  }
```

ブログ記事では単位を(m/s<sup>2</sup>)に換算してありますが、今回はそのまま使ってしまいます。

3軸ベクトルとして合成した値を、100回平均したものを閾値としています。
閾値に対して、加速度の合成値の方が十分に高ければ、TRUE
閾値に対して、加速度の合成値の方が十分に低ければ、FALSE
FALSE => TRUEに変わったら、歩数カウントに1追加します。(TRUE => FALSEは無視)
また、閾値近辺の値を誤検出しないようにヒステリシスも設定しました。

### 注記
センサの向きや、加速度が変化する幅(＝ペットの大きさ)を変えても、歩数をカウント出来ました。ただし、最初の約10秒間(測定頻度0.1秒x100回分)は歩数をカウント出来ない仕様です。。。。
ヒステリシスも閾値の10%で決め打ちなので、場合によっては正しくカウント出来ないかもしれません。必要に応じて修正お願いします。

## SDカードに書込み
ArduinoIDEのスケッチ例 => SD(esp8266) => Datalogger
を参考に、歩数をSDカードに記録します。

```ruby:setup内の初期設定
const int chipSelect = 15; //io15ピンにCSを接続

  pinMode(chipSelect,OUTPUT);
  digitalWrite(chipSelect,HIGH);  //起動時はHIGH出力
  Serial.begin(9600);
  
  while (!Serial) {
    ;  //何もしない
  port only
  }
  Serial.print("Initializing SD card...");
  if (!SD.begin(chipSelect)) {
    Serial.println("Card failed, or not present");
    return;
  }
  Serial.println("card initialized.");

  //ログデータファイルを曜日ごとに作る
  nt = now();
  ti = localtime(nt, 9);
  dfname = "datalog";
  dfname += weekday(ti)-1;  //日=0,月=1,火=2,,,土=6
  dfname += ".txt";
  if(SD.exists(dfname)){
    SD.remove(dfname);
  }
```
```ruby:loop内のデータ格納箇所
  String dataString;
  dataString = jikoku;   //現在時刻
  dataString += ",";
  dataString += accel;   //加速度の合成値
  dataString += ",";
  dataString += stepcount;
  delay(100);

  File dataFile = SD.open(dfname, FILE_WRITE);  //追記で書込み
  if (dataFile) {
    dataFile.println(dataString);
    dataFile.close();
    Serial.println(dataString);
  }
  else {
    Serial.println("error opening datalog.txt");
  }
```

ログデータのファイルは、24時間分を一つのファイルにして保存しています。
0.1秒毎データを約28時間入れた時点で、それ以上は書込みできなくなりました。ファイルサイズが約20MBを超えており、Arduinoが取り扱える限度なのかもしれません。
曜日ごとに作成する事で過去1週間分のデータを保存できます。

「dataString」の中に現在時刻、加速度の値、歩数をコンマで区切って格納する事で、シリアルモニタによるリアルタイムな状況確認が出来ました。
保存されたTXTファイルをCSVに名称変更すると、エクセルでデータ処理も出来るので検証が捗りました。

### つまづきポイント
スケッチ例は、SDカードのCSピンを、ESP8266のio4ピンに接続していました。
今回は加速度センサとio4ピンを接続済みなので、CSはio15ピンに変更したところ、スケッチ書込み、起動が出来なくなってしまいました。
次のブログのとおり、setup中にio15ピンをHIGH出力に明示すると解決しました。
起動時は10kΩプルダウン且つHIGH出力になっている必要があるようです。ありがとうございました！

>[esp-wroom-02(esp8266)でmicroSDカードを使ってみた](http://shangtian.hatenablog.com/entry/2018/02/14/182342)

## 改善したいところ
- 肝心のくるみちゃんに装着できていないので、もっと小型化したい。
- 閾値設定の条件を最適化したい。10秒ではなく5秒とか3秒で判定できれば短くしたい。
- 電源投入時も即Slack投稿する仕様。動作確認のためコメント内容を変えるか、再起動時のみ投稿させたいです。
- 詳細な歩数ログデータはSDカードに入ったまま。WEBサーバーで処理して有効活用したいけど、やり方が分かりませんでした。

他にもコード間違ってるよ！とか指摘、アドバイスいただければ幸いです。

以上です。
最後まで読んでいただき、ありがとうございました！