気の向くままに辿るIT/ICT/IoT
IoT・電子工作

自作タッチレスセンサー式スイング開閉スマートごみ箱

ホーム前へ次へ
ESP8266って?

自作タッチレスセンサー式スイング開閉スマートごみ箱

自作タッチレスセンサー式スイング開閉スマートごみ箱

2023/02/24

 既存のスイングタイプのゴミ箱を手での開閉もできるようにしつつ、タッチレスで自動開閉できるよう電動化してスマートゴミ箱/スマートダストボックスを自作してみた話。

 回路のメインキャストは、Wi-Fi/Bluetooth搭載ESP32開発ボードと近接センサーとして赤外線障害物回避センサー、そしてサーボモーターMG90S。

 今回タッチレス化・スマート化する三角屋根というか山なり屋根でシーソーのように2方向開閉できるスイング式ゴミ箱は、樹脂・プラスチック製、以前、100均で買った小さなもの。

 尚、deep sleep機能を入れたところ、復帰の分でしょう、センサーの反応にワンクッション、タイムラグができちゃいましたが、節電は大事。

 ちなみに動画も今日アップしたのですが、他のマイコンを使おうか若干迷い、アップ時点では仮配線していましたが、その直後、ESP32に決めたのでフタケースに埋め込みました。

自作センサー式スマートダストボックス/ホワイト
2023/11/11追加

なぜWi-Fi搭載マイコンなのか

 非接触・タッチレスにするだけなら、もちろん、マイコンもArduinoやRaspberry Pi Picoなどでも十分でしょう。

 ただ、ここでは実装もしませんし、詳細にも触れないものの、LAN内サーバ上にログを取ったり、ある程度、サーバ上で集計してからLINE通知するなど遠隔に住む家族・親族(宅にWi-Fi環境も必要にはなるもの)の見守り用途への拡張もできるようにしておくべく、Wi-Fi搭載マイコンにしておこうかなと。

 今やWi-Fi対応マイコンにはRaspberry Pi Pico Wもあり、昨年10月に技適は通っている模様も今日時点まだ、技適対応版は国内販売されていない、されたとて昨今、コスト的に円換算だとESP32格安ボードと比べると3倍程度はする模様で見合わないこともあり、何れにせよESP32で。

概要

自作センサー式スマートダストボックス/スイング式開閉蓋ゴミ箱外観

 このスイング式ゴミ箱は、100均キャンドゥで買った高さ21cmx幅11cmx奥行き16cmほどのこぶりなものでデスク下に置いてあります。

 開閉を自動化してみたら、同じくらいの大きさのものが、トイレにあっても良いなと思い始めました。

 非接触だから、より衛生的ですもんね。

自作スイング蓋センサー式スマートダストボックスの近接センサーとした赤外線障害物回避センサー

 今回、ダストボックスのスマート化にあたり、近接センサーには障害物回避センサーを使うことを前提とするという課題を自分に与えました。

 唯一あったものを壊してしまったこともあり、障害物回避センサーを実用品で使ったことがなく、使ってみたいという衝動に駆られたという理由だけで。

 が、そのせいで電池式でコードレスにしたかったのにUSB電源を使う仕様になってしまいましたが...。

 というのも手持ちに障害物回避センサーはなくAliExpressに発注、届く前に今回のスイング式、キッチンのペダル式ゴミ箱の検証においては、安定して最大3〜4m検知、計測できる超音波センサーHY-SR05を使ったのですが、サーボ込みで電池ボックスのみでいけることを確認済みなので。

 と言っても超音波センサーでもギリギリではあるようですけどね、ロッカスイッチを追加しようとしただけでサーボが動かなくなったので。

 さておき、ゴミを捨てる際、手とか、ゴミとかを検知後、スイング式ゴミ箱の蓋が開き、検知されている間は継続して開き、検知しなくなった時点で一定秒数後(スケッチ上は3秒で)停止するようにしてあります。

 センサー式でタッチレスでの自動開閉もできますが、もちろん、スマートゴミ箱に生まれ変わった後も手での開閉操作もできるようにしてあります。

 今回は、サーボの回転がフリーになるようdetachすることで実現、電源ON時でも、OFF時でも、ON時、手で開閉できる一方、意図していない副産物?ながら開けてからセンサーに検知されると閉まる時は自動で半自動にもなるオマケ付き。

 detachしているとは言え、サーボのギアは噛み合っており、サーボの軸も回るため、それなりのギー音はしますし、ほんの、わずかながら重みを感じますが。

材料

 電源延長ケーブルや電池は別として100均のなのでゴミ箱込みでも1000円くらいでしょうか。

 検証時、SG90も試しましたが、MG90Sの方が遥かに安定したので後者にしました。

 プレートについては、軽量、多少の負荷がかかったとしても曲がらない程度の丈夫さに加え、ゴミ箱の蓋をシーソー式に開閉できるだけの長さも必要、また、加工が必要なら特にできるだけ薄いものになると思われ、そうであれば厚み部分を蓋に当てるのがベターでしょう。

 「黒の」熱収縮チューブもしくは代替は、赤外線LEDとフォトトランジスタが間近に隣り合うIR障害物回避センサー(少なくともボード裏面にFlying-Fishと印字のあるもの)の受光側のフォトトランジスタ(か発光側の赤外線LED)がちょうど隠れるくらいの長さのものをフォトトランジスタ(か赤外線LED)に被せるために必要です。

 赤外線が障害として無視する「黒」がポイント、黒いチューブ状のもので覆わないと正しく検知すらできない可能性が高く、被せるだけでIR障害物回避センサーの機能が存分に発揮されるので必須です。

取付場所・取付方法

自作自動開閉スマートゴミ箱のIR障害物回避センサー/サーボ/ESP32

 こぶりなゴミ箱ながら、基本、ESP32/サーボMG90S/IR障害物回避センサー+アルファであることもあり、全ての回路は、スイングする蓋側のカバーに収まりました。

 赤外線障害物回避センサーの土台には0.3か0.5厚のプラバンを、サーボは蓋カバーに穴を開けお尻が見える状態で、ESP32はピンヘッダ部分を差し込み、適宜ホットボンドで固定。

 全体に何らかのカバーをしてもよいかなとも思いつつ、見えない向きに置くのでいっかなと。

 とりあえず、プラバン、ホットボンド共に黒がなかったのでマジックでごみ箱カラーと同じ黒には塗りましたが。

自作自動開閉スマートゴミ箱蓋カバー内側の仮配線部

 ゴミを捨てる際、スイング式の蓋の一方のみ使用することにし、開閉は、蓋の裏側で良い感じのプレートをシーソーのように動かし、他方を突き上げる方法をとりました。

 今回、5VとGNDについては、ユニバーサル基板を適当なサイズにカットし、ピンヘッダ メスをはんだ付けしたものにジャンパワイヤ オスを挿す恰好にしました。

 尚、今回対象とした山なりの蓋のスイング式ゴミ箱の蓋は、蓋の骨組み部分の(丸穴ではなく)半円上の切り欠きに蓋側の突起を載せるだけの、それほど雑の所作でなくても蓋部分が落下する構造だったため、両サイド、良きサイズのワッシャーをホットボンドで固定するという工程も必要でした。

 なぜ、そんな不安定極まりない構造にしたのか...、半円をドーナツ円にすると著しいコスト上昇を招くのでしょうか?加工の手間はむしろ増える気がしますし、操作性・実用性に欠けると思うのですが、全く解せません、って、そんなものをなんで買ったんだって話もありますが。

 そんな構造だったので、何れか一方の蓋を押し開けても円滑にスイングして閉まるほどでもなく、そもそも骨組みと蓋の干渉もあったりで、ワッシャー取り付け後も似たような感じのままでしたが、電動化して蓋の他方をプレートで押し上げ、閉まる際は一方のプレートが作用するので、うまい具合に閉まるようになりました。

 そもそも同じ100均のゴミ箱であっても蓋を丸穴に差し込むタイプでかつ、スムースに開閉するものを選んだ方が何かと吉。

 100円とは言え、海洋プラごみ問題やそれも含め、SDGsの観点からも、まだ使えるのに、奇しくもゴミ箱を、捨てて買い直すというのは憚られ...。

動作確認

 思いの外、IR障害物回避センサーは、光の影響を多分に受け、その点、結構シビアなので実際に使用する場所、そして、その向きに設置後、センサー上のPOT(ポテンショメータ)で感度を調整します。

 季節や陽射し、照明などで明るさが変わる場所なら、実際の状況に合わせ、設定するのがベターでしょう。

 極端な話、季節ごと、月ごと、朝・昼・夕・晩、晴天か曇天、雨天かによってなど使用条件が変わる頃合いを見る必要もないとは言い切れません。

 リンク先にもある通り、明るい方が、物体が白い方が、大きさは大きい方が、感度が高く、比較的、暗い場所で使うなら、その状況で設定すれば、限りなく近距離でのみ反応しますが、たまたまでも明るくなればなるほど同じ設定であっても感度が著しく上がり、想定外の長い距離で反応する可能性もあるという点には留意が必要でしょう。

 ちなみに超音波センサーは、基本、こうした光の影響はないものの、それはそれで一長一短ありますが。

回路

 回路は、Fritzingでとも思いましたが、シンプル過ぎるほどシンプルなので文面で。

スケッチ

#include <ESP32Servo.h>
 
Servo seesaw_servo;
 
int openpos = 0;
int pos = 65;
 
const int SENSOR_PIN = 12;
const int ATTACH_PIN = 13;
bool flg = false;
 
void print_wakeup_reason() {
  esp_sleep_wakeup_cause_t wakeup_reason;
 
  wakeup_reason = esp_sleep_get_wakeup_cause();
 
  switch (wakeup_reason)
  {
    case ESP_SLEEP_WAKEUP_EXT0 : Serial.println("RTC_IOを利用した外部信号による復帰"); break;
    case ESP_SLEEP_WAKEUP_EXT1 : Serial.println("RTC_CNTLを利用した外部信号による復帰"); break;
    case ESP_SLEEP_WAKEUP_TIMER : Serial.println("タイマーによる復帰"); break;
    case ESP_SLEEP_WAKEUP_TOUCHPAD : Serial.println("タッチパッドによる復帰"); break;
    case ESP_SLEEP_WAKEUP_ULP : Serial.println("ULPプログラムによる復帰"); break;
    default : Serial.printf("ディープスリープに起因しなかった復帰: %d\n", wakeup_reason); break;
  }
}
 
void setup() {
  Serial.begin(115200);
  delay(1000);
  print_wakeup_reason();
  pinMode(SENSOR_PIN, INPUT_PULLUP);
  seesaw_servo.attach(ATTACH_PIN);
  delay(18);
  seesaw_servo.write(pos);
  delay(18);
  esp_sleep_enable_ext0_wakeup(GPIO_NUM_12, 0);
}
 
void loop() {
  int val = digitalRead(SENSOR_PIN);
  Serial.println(val);
  if (val == LOW) {
    if (!flg) {
      seesaw_servo.attach(ATTACH_PIN);
      seesaw_servo.write(openpos);
    }
    delay(3000);
    flg = true;
  } else {
    if (flg) {
      seesaw_servo.write(pos);
      delay(100);
      seesaw_servo.detach();
      flg = false;
    }
    Serial.println("スリープしちゃうよ");
    delay(2000);
    esp_deep_sleep_start();
    Serial.println("ぐっすり寝てるから、これは出力されないはず");
  }
}

 Arduino IDE 1.8.19で書いたスケッチは、こんな感じ。

 ESP32でサーボを使う場合は、Servo.hではなく、ESP32Servo.hをインクルードします。

 電池駆動ではないながらも節電の為、IR障害物回避センサーのOUTピン出力で復帰するディープスリープ機能も入れました。

 が、deep sleep機能を入れた結果、復帰の時間か、センサーの反応にタイムラグができ、開くまでに微妙にワンクッションありつつ、電池駆動ではないものの、電気は無駄なく使おうかなと。

 deep sleep機能を入れなければ、気持ち良いほどタイムリーに即反応するんですけどね。

 また、フラグ(flg)で逃げるのは、好きではありませんが、検証時の超音波センサーHY-SR05では不要もIR障害物回避センサーの場合、これをしないと「検出している間、ずっと開いておく」動作ができず、慌ただしく「開いて瞬間的に閉じてを繰り返す」ので仕方なく。

#include <WiFi.h>
#include <ArduinoOTA.h>
#include <ESPmDNS.h>
#include <ESP32Servo.h>
 
Servo seesaw_servo;
 
int closepos = 45;
int openpos = 0;
 
const int SENSOR_PIN = 14;
const int ATTACH_PIN = 13;
//bool flg = false;
 
const char *ssid     = "SSID";
const char *password = "PASSPHRASE";
 
IPAddress local_IP(192, 168, 2, 235);
IPAddress gateway(192, 168, 2, 1);
IPAddress subnet(255, 255, 255, 0);
//IPAddress primaryDNS(8, 8, 8, 8); //optional
//IPAddress secondaryDNS(8, 8, 4, 4); //optional
 
const char common_name[40] = "esp32_smart_dust_box";
const char *OTAName = common_name;           // A name and a password for the OTA service
const char *mdnsName = common_name; // Domain name for the mDNS responder
 
void print_wakeup_reason() {
  esp_sleep_wakeup_cause_t wakeup_reason;
 
  wakeup_reason = esp_sleep_get_wakeup_cause();
 
  switch (wakeup_reason)
  {
    case ESP_SLEEP_WAKEUP_EXT0 : Serial.println("RTC_IOを利用した外部信号による復帰"); break;
    case ESP_SLEEP_WAKEUP_EXT1 : Serial.println("RTC_CNTLを利用した外部信号による復帰"); break;
    case ESP_SLEEP_WAKEUP_TIMER : Serial.println("タイマーによる復帰"); break;
    case ESP_SLEEP_WAKEUP_TOUCHPAD : Serial.println("タッチパッドによる復帰"); break;
    case ESP_SLEEP_WAKEUP_ULP : Serial.println("ULPプログラムによる復帰"); break;
    default : Serial.printf("ディープスリープに起因しなかった復帰: %d\n", wakeup_reason); break;
  }
}
 
void setup() {
  Serial.begin(115200);
  delay(1000);
  print_wakeup_reason();
  pinMode(SENSOR_PIN, INPUT_PULLUP);
  seesaw_servo.attach(ATTACH_PIN);
  delay(18);
  seesaw_servo.write(closepos);
  delay(18);
 
  esp_sleep_enable_ext0_wakeup(GPIO_NUM_14, 0);
 
  startWiFi();
  startOTA();
  startMDNS();
}
 
void loop() {
  int val = digitalRead(SENSOR_PIN);
  Serial.println(val);
  if (val == LOW) {
    seesaw_servo.write(openpos);
    Serial.println(openpos);
    delay(3000);
  } else {
    seesaw_servo.write(closepos);
    delay(100);
    seesaw_servo.detach();
    Serial.println(closepos);
    delay(1000);
    Serial.println("スリープしちゃうよ");
    delay(2000);
    esp_deep_sleep_start();
    Serial.println("ぐっすり寝てるから、これは出力されないはず");
  }
  ArduinoOTA.handle();                        // listen for OTA events
}
 
void startWiFi() {
  WiFi.disconnect(); //added to start with the wifi off, avoid crashing
 
  //  if (!WiFi.config(local_IP, gateway, subnet, primaryDNS, secondaryDNS)) {
  if (!WiFi.config(local_IP, gateway, subnet)) {
    Serial.println("STA Failed to configure");
  }
  //  WiFi.softAP(ssid, password);             // Start the access point
  WiFi.mode(WIFI_STA);             // Start the access point
  WiFi.begin(ssid, password);             // Start the access point
  while ( WiFi.status() != WL_CONNECTED) {
    delay(500);
    Serial.print(".");
  }
  Serial.println("");
  Serial.print("SSID \"");
  Serial.print(ssid);
  Serial.println("\" started\r\n");
 
  Serial.print("Connected to ");
  Serial.println(ssid);
 
  Serial.print("IP address: ");
  Serial.println(WiFi.localIP());
 
  Serial.print("hostname : ");
  //  Serial.println(WiFi.hostname());
  Serial.println("");
}
 
void startMDNS() { // Start the mDNS responder
  MDNS.begin(mdnsName);                        // start the multicast domain name server
  Serial.print("mDNS responder started: http://");
  Serial.print(mdnsName);
  Serial.println(".local");
}
 
void startOTA() { // Start the OTA service
  ArduinoOTA.setHostname(OTAName);
  //  ArduinoOTA.setPassword(OTAPassword);
 
  ArduinoOTA.onStart([]() {
    Serial.println("Start");
    // turn off the LEDs
    for (int i = 0; i < 6; i++) {
      //      digitalWrite(LED_BUILTIN, HIGH);
      //      digitalWrite(LED_BUILTIN, LOW);
    }
  });
  ArduinoOTA.onEnd([]() {
    Serial.println("\r\nEnd");
  });
  ArduinoOTA.onProgress([](unsigned int progress, unsigned int total) {
    Serial.printf("Progress: %u%%\r", (progress / (total / 100)));
  });
  ArduinoOTA.onError([](ota_error_t error) {
    Serial.printf("Error[%u]: ", error);
    if (error == OTA_AUTH_ERROR) Serial.println("Auth Failed");
    else if (error == OTA_BEGIN_ERROR) Serial.println("Begin Failed");
    else if (error == OTA_CONNECT_ERROR) Serial.println("Connect Failed");
    else if (error == OTA_RECEIVE_ERROR) Serial.println("Receive Failed");
    else if (error == OTA_END_ERROR) Serial.println("End Failed");
  });
  ArduinoOTA.begin();
  Serial.println("OTA ready\r\n");
}
2023/11/11

 ゴミ箱をブラックからホワイトに移植していたところ、内容を失念しましたが、GPIO12だと不具合を誘発することになったのでGPIO14に代えるにあたり、ついでにWi-Fi、mDNA、ArduinoOTAにも対応させてみました。

 開発ボードを使うようになって起動時に関してはLOWだかHIGHだかじゃないとダメとか制約があるGPIOがあったことをすっかり忘れてましたが、そういうようなことかもしれず、きっとGPIO12は何かに使うのでしょう。

 尤も元々の1号機では、なぜか、そんなこと皆無だったのですが。

 また、変数posopenposも微妙だったのでopenposcloseposにしつつ、とは言え、何れにせよ、サーボの回転方向が一定なため、今回のサーボ取付面の制約により逆になってしまいましたが、基準となるはずのclosepos(旧pos)に角度指定しています。

 あと、フラグを使わないと...というのは間違い(真っ赤な嘘)でした...、何を勘違いしたんだろう...。

 ちなみにこれも1号機ではそんなことなかったのですが、他のコンセントはいけるのに、特定の壁コンセント及びそこからの延長タップでは、ダメという状況に遭遇。

 一方、当該壁コンセントからの電源で温風機や扇風機、自作オートディスペンサーなどは問題ないのにという...。

 元の壁コンセントでもダメなわけで、もちろん全てONなわけではないので電力不足もないのに...なぜ...。

 USB-ACアダプタ(全波整流と半波整流・AC/DC変換)かとも思ったものの、100均の以外にQCタイプやCheeroのものなどを使ってみても同じ...。

 AC電流が整流できないほど荒れてるとか?よくわかりませんが。

 ただ、1号機では何事もなかったことを考えると2号機の方の問題でその辺シビアってこと?

 でも、2号機は、1号機の構成品(ESP32ボード、MG90サーボ、衝突回避センサ)をそのまま使って移植しただけなんですが...。

 原因特定・解消できない限り、他の場所に設置するしかなさ気。

備考

 蓋付きのごみ箱、片っ端からスマート化しよっと、って、あとは、予定してるキッチンの1個しかないか。

 いや、蓋なしのならある、フタ付けて自動開閉させよ、いや買い替える、でも、まだ十分使えるから、フタ付けよっかな。

 と思うほど便利。

 数年前に投げたゴミもキャッチするごみ箱みたいなのを動画で観て感心しつつ笑いがこみ上げたことがあります。

 動くのはさておき、ごみ箱のフタが自動開閉する必要もないよねとずっと思っていました。

 コロナ禍で非接触ってワードが飛び交って久しい昨今、思いも変わってきますね。

 いざ、作ろうと思ってみれば、興味がなさ過ぎて数年前から市販されており、今や結構いろいろな商品が出ていることを初めて知りました。

 それが、できてみれば尚更、こいうのって必需品じゃんと思うまでに。

 さすがに、ごみ箱に袋をセッティングする時やごみ箱のゴミを出すときはタッチレスってわけにはいきませんけどね。

 まぁ、我が家のごみ箱は、どれも美しすぎるほど美しいので、むしろタッチしたいくらいですが...!?

 と思いきや、ごみ箱内のゴミ袋を自動密閉、新しいゴミ袋を自動セッティングするものはあるっぽい、すごっ...、さすがに、この機能は自作できる気はしませんね。

 ただ、ゴミ袋を専用で買い足さないといけない、しかも自動セットのためか数ヶ月もつらしき数枚のゴミ袋がフレーム?に組み込まれた部分を買い足すっぽいけど、フレーム部分って毎回捨てるの?燃えないゴミ?とか、指定ごみ袋と2重かとか、人感センサーだと手元だけじゃなく広範囲の動き拾っちゃわない?ピンポイント検出できるものあるの?とか、内部の湿度が高くなった時、自動密閉部分とか機械的に動くみたいだけど大丈夫なの?とか、充電して1〜2ヶ月かとか、気になる点も多し。

2023/11/11

 不注意で雑に扱った結果、スマートダストボックス1号機(キャンドゥの黒いゴミ箱)を壊してしまった(ホットボンドで追加・接着の軸受用ワッシャーは無事なのにゴミ箱のスイングの軸を片方折ってしまった)ので今度は、ダイソーのスイングゴミ箱 オフホワイトに移植したのが、冒頭追加の白いスマートゴミ箱。

ホーム前へ次へ