作るきっかけ
ウォータサーバの水ボトルが空になっても交換されずに放置され、カップ麺にお湯を注ぎ始めた直後に気付いて悲しい気持ちになった為作りました。
仕組みの検討
光センサ等で水位を観測すれば良いかなと始めは考えていましたが、ウォーターサーバ本体と水ボトルはレンタル品の為加工することができず…。ならば体重計の様な感じのものを作って、ウォータサーバごと(装置に載せて)重さを測り、水ボトル満タン時の重さと、空の時の重さを事前に記録しておけば大まかな残量が把握できるのではないか…という事で仕組みとしては図のようにすることとしました。
ロードセルは4つありますが、実際の測定に使っているのはその内の1つだけです(残り3つは足として使うだけ)。
用意するもの
- ESP-WROOM-02モジュール(秋月電子のAE-ESP-WROOM02-DEV [K-12236])
- HX711モジュール(秋月電子のAE-HX711-SIP [K-12370])
- ロードセル(Wii Fit バランスボードから外したもの) 1セット(4個)
- 抵抗器 2.7kΩ, 4.7kΩ いずれも1/6W(小型品)それぞれ1本ずつ
- MDF材 600x300x2.5 2枚
- 他 線材, 木工用ボンド,両面テープ, はんだ, USBケーブル(microB-A), USBmicroBコネクタ付きACアダプタ…等
※ 加えて、工具が必要です(はんだゴテ、レーザ加工機、+ドライバ、ニッパー等)。
回路
HX711モジュールはIC内部レギュレータを使用するので5V動作としました。
ターミナルブロックなどは使用せず、直接電線で配線しました。
使用したESP-WROOM-02は3.3V動作なのでそのまま接続することは避け、一応簡易なレベル変換を付加しました(抵抗2本)。
ウォータサーバを載せる部分
ウォータサーバは満タンの水ボトルを含めて約20kg程(おそらく)あるので、その重さに耐えられなければなりません。重心が高くなると転倒の恐れもある為、なるべく薄型としました。
2.5mmのMDF材が手元にあったので、それをレーザー加工機で切り出し木工用ボンドで積層・接着しました。ウォータサーバの足がロードセルの真上に来るようにすることで、薄い板でも何とかなりました。ロードセルは、Wii Fitバランスボードを分解して外したものを使用しました(ネジとプラスチックの足も再利用)。プラスチックの足は、ロードセルに両面テープを使って簡易に固定しておきました。
とりあえず、ここまででハードは完成です。
WROOM-02のArduino化とslackの設定
こちらの記事を参考にさせていただきました。
https://elchika.com/article/6b591591-dd4f-44ec-ae37-e77a8ffcee51/
スケッチ(コード)など
水タンクの大よその残量が分かれば良いので、空のボトルをセットしたときと満タンボトル時のHX711の出力をそれぞれ記録し、その値を基に現在の予想ボトル水位を算出します。加えて、slackへのポスト、(疑似)EEPROMへのデータ保存と読み込みなどなどのコードが盛り込まれています。コード中のXXXXXは環境に合わせてください(SSIDなど)。
#include <ESP8266WiFi.h>
#include <WiFiClientSecure.h>
#include <WiFiUdp.h>
#include <ESP8266WebServer.h>
#include <ESP8266mDNS.h>
#include <EEPROM.h>
// WiFi設定
static const String ssid = "XXXXX";
static const String password = "XXXXX";
// slackポスト関連
static const String SLACK_HOST = "hooks.slack.com";
static const String SLACK_URL = "XXXXX";
static const int PORT = 443;
static const char* FINGERPRINT = "XXXXX";
// HX711関連
static const int pinDAT=16; // HX711のDATピンに接続されているWROOM-02のピン
static const int pinCLK=5; // HX711のCLKピンに接続されているWROOM-02のピン
// ウォータサーバ監視関連
static const int msgint = 15; // 定時連絡は15分おき
static const int wlbtlempty = 5; // 5%切ったら空判定
static const int wlnewbtl = 95; // 空判定後、95%以上になったらボトル交換されたと判定
// (疑似)EEPROM用データ定義
struct EEPROM_CONFIG{
long wlfull;
long wlempty;
};
EEPROM_CONFIG epcfg;
// 設定値が"0"(未設定)の場合にセットするデフォルト値
static const long wlfull_default = 9200000;
static const long wlempty_default = 9100000;
// ブラウザから現在値などを確認できるようにウェブサーバを追加
ESP8266WebServer server(80);
// (疑似)EEPROM 読み書きルーチン
void load_eeprom() {
EEPROM.get<EEPROM_CONFIG>(0, epcfg);
// 設定値が"0"(未設定)の場合はデフォルト値をセットしておく
if(!epcfg.wlfull) epcfg.wlfull=wlfull_default;
if(!epcfg.wlempty) epcfg.wlempty=wlempty_default;
}
void save_eeprom() {
EEPROM.put<EEPROM_CONFIG>(0, epcfg);
EEPROM.commit();
}
// slackにmsgに入れた文字列をポストする
void submit_slack(String msg) {
WiFiClientSecure client;
client.setFingerprint(FINGERPRINT);
if (!client.connect(SLACK_HOST, PORT)) {
Serial.println("Slackとの接続に失敗しました");
}
String payload = "payload={\"text\": \"" + msg + "\"}";
Serial.println(payload.c_str());
String request = "POST " + SLACK_URL + " HTTP/1.1\n" +
"Host: " + SLACK_HOST + "\n" +
"User-Agent: ESP8266\n" +
"Connection: close\n" +
"Content-Type: application/x-www-form-urlencoded\n" +
"Content-Length: " + payload.length() + "\n\n" +
payload;
client.println(request);
delay(5000);
while (client.available()) {
String line = client.readStringUntil('\r');
Serial.print(line);
}
Serial.println("");
client.stop();
}
// 現在のHX711の値からおおよその水残量(%)を計算する
int getwaterlevel(void)
{
long hx711val=AE_HX711_Read();
return((float(hx711val - epcfg.wlempty)/float(epcfg.wlfull - epcfg.wlempty))*100);
}
// HX711関連ルーチン
void AE_HX711_Init(void)
{
pinMode(pinDAT, INPUT);
pinMode(pinCLK, OUTPUT);
}
void AE_HX711_Reset(void)
{
digitalWrite(pinCLK,1);
delayMicroseconds(100);
digitalWrite(pinCLK,0);
delayMicroseconds(100);
}
long AE_HX711_Read(void)
{
long data=0;
while(digitalRead(pinDAT)!=0);
delayMicroseconds(10);
for(int i=0;i<24;i++)
{
digitalWrite(pinCLK,1);
delayMicroseconds(5);
digitalWrite(pinCLK,0);
delayMicroseconds(5);
data = (data<<1)|(digitalRead(pinDAT));
}
digitalWrite(pinCLK,1);
delayMicroseconds(10);
digitalWrite(pinCLK,0);
delayMicroseconds(10);
return data^0x800000;
}
// ブラウザで"/"にアクセスしたときに出力する
void handle_root() {
long hx711val=AE_HX711_Read();
String message = "Water Server Status";
message += "\nCurrent=";
message += String(hx711val);
message += " (";
message += String(getwaterlevel());
message += "%)";
message += "\nFull=";
message += String(epcfg.wlfull);
message += "\nEmpty=";
message += String(epcfg.wlempty);
server.send(200, "text/plain", message);
//↓のコメントアウトを解除すると、"/"にアクセスするたびにslackへポストします.
//submit_slack(message);
}
// ブラウザで存在しないページにアクセスされたときに出力する
void handle_notfound() {
String message = "File Not Found\n\n";
message += "URI: ";
message += server.uri();
message += "\nMethod: ";
message += (server.method() == HTTP_GET) ? "GET" : "POST";
message += "\nArguments: ";
message += server.args();
message += "\n";
for (uint8_t i = 0; i < server.args(); i++) {
message += " " + server.argName(i) + ": " + server.arg(i) + "\n";
}
server.send(404, "text/plain", message);
}
// 約1秒おきに呼ばれるルーチン
// 60カウントして1分として、水位確認を実施.
// さらに15分おきに定時連絡としてslackに現在水位をポスト.
void tmr_wlcheck(void)
{
static int scnt=0;
static int mcnt=0;
static bool flg_empty=false;
Serial.println("scnt=" + String(scnt));
scnt++;
if(scnt>=60){
int wlpercent = getwaterlevel();
Serial.println(String(wlpercent) + "%");
if((wlpercent < wlbtlempty) && !flg_empty){
String message;
message+="ボトルが空になったみたいです.\n";
message+="ボトル交換してください.\n";
submit_slack(message);
flg_empty=true;
}
if((wlpercent > wlnewbtl) && flg_empty){
String message;
message+="ボトルが交換されました.\n";
submit_slack(message);
flg_empty=false;
}
scnt=0;
mcnt++;
if(mcnt>=msgint){
String message;
message+="ボトル水残量の定時連絡です.\n";
message+="現在、" + String(wlpercent) + "%です.\n";
submit_slack(message);
mcnt=0;
}
}
}
// もろもろ初期化
void setup(void) {
AE_HX711_Init();
EEPROM.begin(64);
load_eeprom();
Serial.begin(115200);
WiFi.mode(WIFI_STA);
WiFi.begin(ssid, password);
Serial.println("");
while (WiFi.status() != WL_CONNECTED) {
delay(500);
Serial.print(".");
}
Serial.println("");
Serial.print("Connected to ");
Serial.println(ssid);
Serial.print("IP address: ");
Serial.println(WiFi.localIP());
if (MDNS.begin("esp8266")) {
Serial.println("MDNS responder started");
}
server.on("/", handle_root);
server.on("/full", []() {
long hx711val=AE_HX711_Read();
epcfg.wlfull=hx711val;
save_eeprom();
server.send(200, "text/plain", "Full=" + String(hx711val));
});
server.on("/empty", []() {
long hx711val=AE_HX711_Read();
epcfg.wlempty=hx711val;
save_eeprom();
server.send(200, "text/plain", "Empty=" + String(hx711val));
});
server.onNotFound(handle_notfound);
server.begin();
Serial.println("HTTP server started");
}
// ループ
void loop(void) {
server.handleClient();
MDNS.update();
tmr_wlcheck();
delay(1000);
yield();
}
スケッチの書き込み方などは省略します。
設置
写真のようにウォータサーバの下に敷きます。
なるべくロードセルの真上にウォータサーバの足が来るように載せます。
あとは、USBmicroBコネクタ付きのACアダプタなどで適当に給電します。
動作テスト
まず、"esp8266.local"(または割り当てられたIPアドレスを調べて入力)にアクセスしてHX711での測定値を確認します。
Water Server Status
Current=9147730 (48%)
Full=9200000
Empty=9100000
※このような感じの表示になるはずです(Current値は状況による)。
次に、空のボトルをセットしてから、"esp8266.local/empty"にアクセスして空ボトルの値を保存します。続けて、満タンのボトルに替えて、"esp8266.local/full"にアクセスし、満タン時の値を保存します。
再度、"esp8266.local"にアクセスして設定値を確認します。
Water Server Status
Current=9165397(100%)
Full=9165397
Empty=9116586
※このような感じの表示になるはずです(値は状況によりますが、99~101%くらいの表示になるはずです)。
これで設定は完了です。
およそ15分に一回、水ボトル残量をslackにポストします。
と言った具合にお知らせしてくれます。
とりあえず完成
水タンクの残量をslackで確認することができるようになり、これで悲しい思いをすることがなくなりそうです!(空であることを知っててボトル交換をスルーしないことが前提ですが…)
手抜きでロードセルを1ch分しか測定していない為、水を出す時のレバー操作などを誤検出したり、ウォータサーバ本体を載せ直すなどすると基準値の取り直しが必要になったりと…まだまだ改良の余地はありそうです。インターフェースをテキストからHTMLにするというのもありますね。
今更ですが、カップ麺に注ぎ始める前にボトル残量目視すれば済む話でした…。
-
zunya
さんが
2021/02/26
に
編集
をしました。
(メッセージ: 初版)
ログインしてコメントを投稿する