ESP8266で洗剤の残量をWebに保存するマシン（乾電池駆動）を作って、実用できるか試す

![press1.jpg](https://qiita-image-store.s3.amazonaws.com/0/94526/27ead592-d723-7ee8-7f08-29715b3b38ae.jpeg)


スーパーで「そういえば洗剤どれくらい残ってたっけ」と確認するために作った、洗剤の残量をWeb上に保存できるマシンです。

- [残量をこちらでご覧になれます（数値はセンサーの値のままで、100(空)〜900(満タン)？くらいです）](http://kiyopikko-detergent.netlify.com/)

![スクリーンショット 2016-01-31 19.05.52.png](https://qiita-image-store.s3.amazonaws.com/0/94526/9a632a9e-f60b-84f1-8f6e-1e943367f951.png)




[前回のUmbrella Stand](http://qiita.com/kiyopikko/items/fb3bfb0b77191cab0567)と同じく以下の点を特に意識します。

- 安定して動作する
- 省電力（電池駆動なので否が応でも）
- メカメカしい見た目にしない

単三電池3本で1ヶ月持つことを目標にしています。


# 機能

1時間ごとに起動して、洗剤の重さを読み取り、Milkcocoaに保存して重さに応じてLEDの色を変えてスリープ。

電力節約のため、周りが暗い（見ていない）ときはLEDを消す。

![press2.jpg](https://qiita-image-store.s3.amazonaws.com/0/94526/c44d3ed4-66bb-37b0-eb83-34f0855a334d.jpeg)


# 必要なもの

## 見た目的に必要な物

- [Rody ロディ コップがさね](http://www.yodobashi.com/ec/product/100000001001513873/)

幼児用のおもちゃです 笑
コップを重ねられるので、下段に基板を隠すことができます。


## 機能的に必要な物

### ESP周り

- [ESP-WROOM-02(ESP8266)ピッチ変換済みモジュール《フル版》](https://www.switch-science.com/catalog/2347/)と、[ピンソケット](http://akizukidenshi.com/catalog/g/gC-05779/)
- プルアップ用抵抗3つ（10kΩ、10kΩ、20kΩ）
- タクトスイッチ2つ（リセット用、モード切り替え用）

### IC

- [Dフリップフロップ(74HC273)](http://eleshop.jp/shop/g/gT11541/)と、[20本足丸ピンICソケット](http://eleshop.jp/shop/g/gF9131A/)
- [2入力AND回路(74HC08)](http://eleshop.jp/shop/g/gT11459/)と、[14本足丸ピンICソケット](http://eleshop.jp/shop/g/gF91317/)

### センサー

- [CdSセル](http://akizukidenshi.com/catalog/g/gI-00110/)と、その分圧用抵抗(47kΩ)
- [圧力センサー](http://akizukidenshi.com/catalog/g/gP-04002/)と、その分圧用抵抗(1kΩ)

### 電源周り

- 単三電池と[単三電池ボックス](http://akizukidenshi.com/catalog/g/gP-00221/)、3つずつ
- [3.3V 3端子レギュレータ](http://akizukidenshi.com/catalog/g/gI-00538/)と、平滑用コンデンサ：セラミック0.1μF＆電解47μFだったかな？（秋月だとレギュレータに付いてきます）

### シリアル通信周り

- [FTDI USBシリアル変換アダプター](https://www.switch-science.com/catalog/1032/)
- ↑のFTDIと繋げる用のピンヘッダ

### その他

- ユニバーサル基板とハンダ付けに必要なもろもろ
- LED3つ（[赤](http://akizukidenshi.com/catalog/g/gI-01318/)、[黄](http://akizukidenshi.com/catalog/g/gI-06852/)、[緑](http://akizukidenshi.com/catalog/g/gI-03035/)）と、その電流制御用抵抗(100Ω、100Ω、10Ω)



![press3.jpg](https://qiita-image-store.s3.amazonaws.com/0/94526/aff4e6be-f569-b381-2d87-1fbfd97d49e1.jpeg)

下段に基板を置きます。上に伸びてるぼけてるやつが圧力センサーです。右のスポンジに埋もれているのがCdSセルです。プラスチックに穴を空けてCdSセルが顔を出せるようにしています。

![press4.jpg](https://qiita-image-store.s3.amazonaws.com/0/94526/388532cb-3a85-8e2b-4b42-2ade8009eff7.jpeg)

上段に圧力センサーを持ってきます。実はこのへんを雑に作ってしまったので、洗剤の量が変わってなくても圧力の値が結構揺れたりします。


# 回路図・配線

![press-circuit.png](https://qiita-image-store.s3.amazonaws.com/0/94526/bff6d9b0-b315-3887-649c-fa99d7d11057.png)


電池ボックス・圧力センサーを基板から伸ばしたワイヤーにつけて、その他は基板に実装しています（先ほどの写真）。



# システム

## 保存部分

[Milkcocoa](https://mlkcca.com/)を利用します。同時接続数は最高でも2、保存メッセージ数も1時間ごとに保存するので**無料プランでも十分(4,000日以上)使えます**（今回は閲覧ページのURLを公開してるので大勢の人がそこを見ちゃうとアウトですが、閲覧者もそんなにいないと思うので大丈夫です）。


## ソフトウェア

圧力センサーの値を読み取って、その値をMilkcocoaにpushします。プログラム内で値によって「少ない・普通・多い」判定をします。

Milkcocoaへのpushが成功した場合のみ1時間のスリープを開始して、失敗した場合は再起動（2秒スリープ）します（方法は後述のプログラムを参照下さい）。


## ハードウェア

スリープ時にGPIOの出力情報は保持されないので、[前回](http://qiita.com/kiyopikko/items/fb3bfb0b77191cab0567)同様、判定した値（少ない・普通・多い）をESP8266の3つのGPIOに対応させて、さらにそれぞれをフリップフロップに記憶させます。

※流してる電流がICの定格のギリギリなのでちゃんとやる場合はトランジスタをお使い下さい。

### センサーの分圧

TOUTの入力範囲は0〜1Vなので、[この圧力センサー](http://akizukidenshi.com/catalog/g/gP-04002/)と今回の圧力のかけ具合的には、1kΩ抵抗で分圧するとちょうどいい感じでした。

CdSセルは、真っ暗のときだけ`LOW`になって欲しかったので、47kΩくらいで分圧すれば良い感じでした。

### 工夫しないといけなかった点

圧力センサーの上にそのまま物を置いても力が分散されて値が全然変わらなかったので、小さいものを挟んで力を集中させる必要がありました。

![press-pressure.png](https://qiita-image-store.s3.amazonaws.com/0/94526/04682209-08ba-274c-66ca-abda01b89242.png)


あと、「CdSで周りが暗くなったら」っていう判定をしていますが、「自分のLEDの明るさでずっと明るい判定になる」という事態が起こったので（笑）、CdSとLEDの間に黒いスポンジを挟みました。



# プログラム

```cpp
#include <ESP8266WiFi.h>
#include <Milkcocoa.h>

/************************* WiFi Access Point *********************************/

#define WLAN_SSID       "...SSID..."
#define WLAN_PASS       "...PASS..."


/************************* Your Milkcocoa Setup *********************************/

#define MILKCOCOA_APP_ID      "...Your Milkcocoa App Id..."
#define MILKCOCOA_DATASTORE   "esp8266-press"


/************* Milkcocoa Setup (you don't need to change this!) ******************/

#define MILKCOCOA_SERVERPORT  1883


/************ Global State (you don't need to change this!) ******************/

// Create an ESP8266 WiFiClient class to connect to the MQTT server.
WiFiClient client;

const char MQTT_SERVER[] PROGMEM    = MILKCOCOA_APP_ID ".mlkcca.com";
const char MQTT_CLIENTID[] PROGMEM  = __TIME__ MILKCOCOA_APP_ID;

Milkcocoa *milkcocoa = Milkcocoa::createWithApiKey(&client, MQTT_SERVER, MILKCOCOA_SERVERPORT, MILKCOCOA_APP_ID, MQTT_CLIENTID, "...Your API Key...", "...Your API Secret...");


#define CLK 4
#define LIGHT 13
#define MEDIUM 12
#define HEAVY 14

int sensorValue = 0;

void setup() {
  Serial.begin(115200);
  delay(10);

  // Wifi
  Serial.println(); Serial.println();
  Serial.print("Connecting to ");
  Serial.println(WLAN_SSID);

  WiFi.begin(WLAN_SSID, WLAN_PASS);
  int wifiCounter = 0;
  while (WiFi.status() != WL_CONNECTED) {
    delay(450);
    Serial.print(".");
    // 10秒経ってもWiFiに繋がらなかったら再起動
    if(wifiCounter > 20) ESP.deepSleep(2 * 1000 * 1000);
    delay(50);
    wifiCounter++;
  }
  Serial.println();

  Serial.println("WiFi connected");
  Serial.println("IP address: ");
  Serial.println(WiFi.localIP());

  // 自分自身のpushを監視（subscribe）
  Serial.println( milkcocoa->on(MILKCOCOA_DATASTORE, "push", onpush) );

  pinMode(CLK, OUTPUT);
  pinMode(LIGHT, OUTPUT);
  pinMode(MEDIUM, OUTPUT);
  pinMode(HEAVY, OUTPUT);
}

int counter = 0;

void loop() {

  // 50秒たっても接続を確立できなかったら再起動
  if(!milkcocoa->loop(10*5000)){
    delay(1000);
    ESP.deepSleep(2 * 1000 * 1000);
    delay(1000);
  }

  DataElement elem = DataElement();
  
  // フリップフロップの初期化(すべてLOWに)
  digitalWrite(CLK, LOW);
  delay(50);
  digitalWrite(LIGHT, LOW);
  digitalWrite(MEDIUM, LOW);
  digitalWrite(HEAVY, LOW);
  delay(50);
  digitalWrite(CLK, HIGH);
  
  sensorValue = analogRead(A0);
  Serial.print("sensor = " );
  Serial.println(sensorValue);

  int pin = 0;
  if (sensorValue > 500) pin = HEAVY;
  else if (sensorValue <= 500 && sensorValue > 280) pin = MEDIUM;
  else pin = LIGHT;

  delay(100);
  digitalWrite(CLK, LOW);

  delay(100);
  digitalWrite(pin, HIGH);

  delay(100);
  digitalWrite(CLK, HIGH);

  // 値の設定
  elem.setValue("v", sensorValue);
  // Milkcocoaにpush 
  if(counter == 0) milkcocoa->push(MILKCOCOA_DATASTORE, &elem);
  
  delay(100);
  // 50回ループが続いたらpushが失敗したと判断して再起動
  if(counter > 50){
    Serial.println("Reboot!");
    delay(1000);
    ESP.deepSleep(2 * 1000 * 1000);
    delay(1000);
    Serial.println("Rebooting...");
  }
  counter++;
}

void onpush(DataElement *elem) {
  Serial.println("onpush");
  Serial.println(elem->getInt("v"));
  Serial.println("Sleep!");
  delay(1000);
  // onpushが呼ばれた＝pushが成功なのでここで1時間スリープ
  ESP.deepSleep(3600000000);
  delay(1000);
  Serial.println("Deep Sleeping...");
};

```

一応、MilkcocoaのAPI KeyとAPI Secretを使ってセキュア（このESPからしか保存を受け付けない）にしています（[Milkcocoaの認証やセキュリティについてはこちらを参照](https://mlkcca.com/document/authentication.html)下さい）。このコードの場合、以下のようなセキュリティルールを設定します。

```js
esp8266-press {
	permit : all;
  	rule : account.key == '...ESPに書いたAPI Key...';
}
esp8266-press {
	permit : on(push), query;
  	rule : true;
}
```

 
# 評価

## 安定して動作するか？

[前回](http://qiita.com/kiyopikko/items/fb3bfb0b77191cab0567)同様、成功したときのみプログラムを実行するようにしています。

- WiFiに接続できずに一定時間経ったら再起動
- Milkcocoaへのコネクションが確立できなかったら再起動
- pushが成功しなかったら（pushしたデータを受け取れなかったら）再起動

通信の障害で失敗する分には再起動してくれるので安心です。


## 電池は長持ちするか？

こちらも[前回](http://qiita.com/kiyopikko/items/fb3bfb0b77191cab0567)同様、1時間ごとに起きて役割が終わったら眠るので、ESPの消費電流は最低限かと思います（[こちらの記事](http://qiita.com/azusa9/items/65a5c3772c41631b5ca1)によるとdeepSleep時に利用される電流は10uA）。

前回とは違い、LEDは自分が見ている間だけ光るようにしているので、ESP以外の消費電流も最低限かなと思います。



# 課題

## 測りたい物の重さによって抵抗値を変えられた方が良い

圧力センサーの分圧のところが決めうちになっているので、このマシンは今回使った洗剤にしか使えません。

ボリュームか何かで調節可能にすると、他の物にも使えそうです。

## 洗剤をどこかにしまわない場合も考える必要がある

「暗かったら消す」という判定しかしていないので、洗剤をしまわない場合も考慮して、別のセンサーか思い切ってタクトスイッチとかでも良かったかもです。


# 終わりに

一応、それなりのものはできました。

1ヶ月くらい問題なく使えるかどうか試して、ちゃんと不具合が出ない＆電池が持つか、1ヶ月後、この記事に追記するかたちで報告しようかと思います。

---

**※追記(2016-02-27)：1ヶ月使ってみました（記事は31日ですが動かし始めたのは26日からです）。**

**電池はもちました**、今も動いています。

値の保存も（ある日の5時間を除き）**安定的に1時間置きに保存できていました**。置きどころによって値が変わるのが課題ですね。値が正確じゃないならいっそのことデジタル(`HIGH`, `LOW`)でいいかもしれません。

ちなみに「ある日の5時間」というのは、WiFiルータの不具合でアクセスポイントが5時間死んでいたことが原因でした。

ということは、**「5時間『アクセスポイントにアクセスを試みる→再起動』を繰り返し続けたにも関わらず電池が1ヶ月もった」**わけですね。
何の問題もなく稼働し続けたら、下手したら**半年とか1年とかもつ**んじゃないんでしょうか。

とりあえずこのままどれくらいもつか見てみて、死んだらまた報告します。

--- 

**※追記(2016-03-07)：電池切れました。**2016-01-26 23:16が最初のデータで、2016-03-06 09:19が最後に保存されたデータなので、40日近くもったことになりますかね。ちなみに使ってた電池は[秋月に売ってるゴールデンパワー製のやつです](http://akizukidenshi.com/catalog/g/gB-03256/)です。

↑にも書いていますが、WiFiルータが死んでいなかったら半年持ってた可能性があるので、**今度は[EVOLTA](http://panasonic.jp/battery/drycell/evolta/)さんを使って再稼働**させました。何かあったらまた報告します。

---

**追記(2016-06-15)：報告が遅れましたが、電池切れました。**2016-03-07に始めて、2016-05-14 00:17:43までもったので、2ヶ月強ですね。思ったより早く死にました。。

でも、保存されたデータを見る限り、約1時間ごとに1回もミスることなくちゃんと保存してくれていたので、個人的には満足です。