😊

育休中の深夜に作った、オムツ用ゴミ袋残量センサー

に公開

初めての育児とゴミ出し問題→からのラピッドプロトタイピング

↓一夜城のゴミ袋残量センサー
完成したゴミ袋残量センサーの全体像

やあみんな!株式会社ZAICO 新規事業開発室の @tarouimo だよ!
いきなり私事ですが、息子が生まれて育休を取得して、はじめての育児に奮闘してました。

ところで私の家ではごみ収集場がご近所様と共同になっています。燃やすゴミの日は大量のオムツを捨てるのですが、、、ある日、カラス被害で我が家のゴミ袋が見るも無惨な姿になっているではありませんか!

ご近所の方が清掃を手伝ってくれてありがたかった反面、迷惑をかけてしまったので対策しなければなりません。

そこで、市の指定袋の内側に丈夫なクラフト紙で2重構造にすることにしました。クラフト用紙はホームセンターで30m巻きのロールを調達して、袋状に工作しました。

カラス対策用クラフト紙ゴミ袋

これでカラス対策は万全!

しかしこのカラス対策版のゴミ袋、在庫が切れたら困るな・・・
それに、大量に作ったけど結構嵩張るのです。

そこで思い出しました。
ワイ、在庫管理の会社で働いとる!

薄いものの残量を自動計測できる装置を作ればいいんじゃないか?
ついでに収納もできるようにして、一石二鳥だ!

一晩で作れる範囲で、確実に動く方法を選択。古典的だけど確実なポテンショメータとギア機構で実装してみました。

育児エンジニアに与えられた制約

育児中の開発には独特の制約があります:

  • 時間の制約:子供が寝た後の数時間しか作業できない
  • 試行錯誤する時間はあまりない:一発勝負に近い状況
  • 部品調達の制約:家にあるものでなんとかする

これらの制約から、以下の方針を決めました:

  1. 確実に動く古典的手法を採用:センサーフュージョンや画像認識ではなく、ポテンショメータによる機械的計測
  2. 複雑な補正を避ける:枚数カウントではなく、厚みのパーセンテージ表示
  3. 手元の部品で完結:ESP32開発ボード、ポテンショメータ(以下POT。ごく普通のボリュームです)、3Dプリンター

システム構成

ハードウェア設計

装置の心臓部はPOTによる厚み測定機構です。

動作原理

  1. 白いリング状のホルダーに45Lゴミ袋のロールをセット
  2. 黄色いレバーアームでロールの半径方向を挟み込み
  3. スプリングテンションでアームを閉じる方向に付勢
  4. 厚みに応じてアーム角度が変化
  5. ギア機構で角度を増幅(30mmの変位を約300度の回転に変換)

なぜギア増幅が必要だったか

基本的な課題

ゴミ袋1枚の厚みは実測で約0.5mmです。アーム長150mmでこの変位を角度に変換すると、わずか0.191度しか回転しません。

arctan(0.5/150) ≈ 0.191度

今回部品点数削減のためPOTを Vdd(3.3V) - ADC - GND に直付けしてるので、POT のダイナミックレンジが esp32 の測定レンジで制限されます。

POTの有効回転範囲: (2450mV - 150mV) / 3300mV * 300deg
          = 209deg
必要ビット数 = log₂(209deg / 0.191deg)
             = 10.1 ビット
ESP32 ADCの精度限界

ご案内の通りesp32のADCは精度が悪いです。
ESP32-WROOM-32のデータシートより

Total error: ±60mV (測定範囲150-2450mV)

これはわずか 4ビット弱の精度しか保証されていません。

測定範囲: 2300mV (2450-150)
LSB換算: 60mV / (2300mV/4096) = ±107 LSB
実効分解能: 4096 / (2×107) = 19.1段階
実効ビット数 = log₂(19.14)
             = 4.26 ビット

今回の要件は残量のパーセンテージをざっくり把握することです。
この分解能(約5%刻み)でもいいですが、オーバーサンプリングで簡単に向上できるので組み込んであげます。

オーバーサンプリングによる改善

オーバーサンプリングではN回測定して平均化することで、ノイズを1/√Nに低減できます:

改善後の段階数 = 19.14 × √N
改善後のビット数 = 4.26 + 0.5 × log₂(N)

1024回のサンプリングで:

改善後の段階数 = 19.14 × √1024 = 19.14 × 32 = 612段階
改善後のビット数 = 4.26 + 0.5 × log₂(1024) = 4.26 + 5 = 9.26ビット

尤も今回はディザー信号を用意してないので、ESP32のDNL(±7 LSB)を擬似的なノイズ源として利用します。
DNLは簡単に言うとADCの目盛り間隔が不揃いということです。定規の目盛りが等間隔でなく、ある場所は狭く、別の場所は広くなっているイメージです。この不均一性が測定値のバラつきを生み、ノイズ源として利用できると言うわけですね。

ただし、DNLによるノイズは理想的なホワイトノイズではないため、改善効果は理論値より低くなります。
実際のマイコンADCでの測定事例では、理論値に対して50-70%程度の改善率となることが報告されています:

理論値: 4.26 + 5.0 = 9.26ビット
現実的な期待値: 4.26 + (5.0 × 0.5~0.7) = 6.8~7.8ビット

それでも元の4.26ビットから大幅に改善され、残量パーセンテージの把握には十分な精度となりそうです。

ギア機構の設計

ゴミ袋の厚み変化(0-30mm)をPOTの有効範囲(約210度)にマッピングするため、約25倍のギア比を実装しました:

4段のギアトレインで実現:

  • ラック&ピニオン(42T/15T): 2.8倍
  • 中間ギア×2段: 各3.33倍、2.63倍
    総ギア比: 約24.5倍

この機構により、0.5mmの厚み変化を約5度のPOT回転に変換でき、オーバーサンプリングと組み合わせることで実用的な精度を達成しています。

4段ギアトレインの構造図

ソフトウェア実装

ESP32によるデータ収集

const int POT_PIN = 34;  // ADCピン
const float EMPTY_VOLTAGE = 0.5;  // 空の状態の電圧
const float FULL_VOLTAGE = 2.8;   // 満杯の状態の電圧
const int OVERSAMPLE_COUNT = 1024; // オーバーサンプリング回数

float measureStock() {
  // 1024回オーバーサンプリング
  long adcSum = 0;
  for (int i = 0; i < OVERSAMPLE_COUNT; i++) {
    adcSum += analogRead(POT_PIN);
    delayMicroseconds(10); // サンプリング間隔
  }
  float adcValue = adcSum / (float)OVERSAMPLE_COUNT;
  
  float voltage = (adcValue / 4095.0) * 3.3;
  
  // 線形補間でパーセンテージに変換
  float percentage = ((voltage - EMPTY_VOLTAGE) / 
                     (FULL_VOLTAGE - EMPTY_VOLTAGE)) * 100;
  
  return constrain(percentage, 0, 100);
}

POTの利点は電圧と回転角が完全に線形であること。これにより、複雑な補正式が不要で、オフセットとゲインの調整だけで済みました。オーバーサンプリングにより、元の4.3ビット相当から7ビット程度まで実効分解能が向上し、約1%刻みでの残量把握が可能になりました。

zaico APIへのデータ送信

ここは zaico API Document が公開されてるので、それを見て実装できます。

#include <Arduino.h>
#include <WiFi.h>
#include <HTTPClient.h>
#include <WiFiClientSecure.h>
#include <ArduinoJson.h>

// WiFi設定
const char *WIFI_SSID = "YOUR_WIFI_SSID";
const char *WIFI_PASSWORD = "YOUR_WIFI_PASSWORD";

// zaico API設定
const char *ZAICO_API_URL = "https://web.zaico.co.jp/api/v1/inventories/";
const char *ZAICO_TOKEN = "YOUR_API_TOKEN";
const int INVENTORY_ID = 63213649; // 在庫ID

// zaico在庫更新関数
void updateZaicoStock(float percentage)
{
  WiFiClientSecure client;

  // TODO: 本番環境では証明書検証を有効化
  // client.setCACert(root_ca);
  client.setInsecure(); // 開発環境のみ

  HTTPClient http;

  // 残量から枚数を計算
  int quantity = (int)(percentage);

  String url = String(ZAICO_API_URL) + String(INVENTORY_ID);
  http.begin(client, url);
  http.addHeader("Authorization", "Bearer " + String(ZAICO_TOKEN));
  http.addHeader("Content-Type", "application/json");

  // JSONペイロード作成
  StaticJsonDocument<256> doc;
  doc["quantity"] = String(quantity);
  doc["etc"] = "自動計測: " + String(percentage, 1) + "%";

  JsonObject history = doc.createNestedObject("inventory_history");
  history["memo"] = "センサー自動更新: " + String(percentage, 1) + "%";

  String payload;
  serializeJson(doc, payload);

  int httpCode = http.PUT(payload);

  if (httpCode == 200)
  {
    Serial.println("在庫更新成功");
    Serial.println(http.getString());
  }
  else
  {
    Serial.printf("エラー: %d\n", httpCode);
    Serial.println(http.getString());
  }

  http.end();
}

void setup()
{
  Serial.begin(115200);
  Serial.println("\n起動中...");

  // WiFi接続
  WiFi.begin(WIFI_SSID, WIFI_PASSWORD);
  Serial.print("WiFi接続中");

  while (WiFi.status() != WL_CONNECTED)
  {
    delay(500);
    Serial.print(".");
  }

  Serial.println("\nWiFi接続成功!");
  Serial.print("IPアドレス: ");
  Serial.println(WiFi.localIP());
}

void loop()
{
  if (WiFi.status() == WL_CONNECTED)
  {
    float stockPercentage = measureStock();
    Serial.printf("\n在庫: %.1f%%\n", stockPercentage);
    updateZaicoStock(stockPercentage);
  }
  else
  {
    Serial.println("WiFi未接続");
  }

  delay(3600000); // 1時間待機
}

3Dプリント部品の設計

ほとんど3Dプリンタで出力しました。
そうでないのは、

  • POT
  • esp32開発ボード
  • アームにテンションをかけるための引きバネ
    だけです。

主要パーツ:

  • リング状ホルダー
    • 筒状のゴミ袋を保持するため
  • レバーアーム
    • 引きばねで閉じる方向にテンションをかけます
    • アームの閉じでゴミ袋ロールが保持されると共に、ロールの厚みに応じてアーム角度が変化します。
    • 特性ゴミ袋はシワやヨレで厚みを持ってしまうので、ある程度強力なスプリングで、純粋な厚みに近くなるようにしてます。
  • ギアボックス
    • 多段ギアと、装置そのものの外装です。
      その他、ベアリングやシャフト類も全て3Dプリント製です。

3Dプリント部品

開発の工夫点

1. 段取り重視の開発プロセス

日中、子供の世話をしながら頭の中で設計します。
スマホを操作する時間くらいはあるので、諸計算やざっくりとした機構のイメージを完了させました。
一方でcadやプログラミングは時間的に不可能なので、夜に一気に実装。この方法で開発効率を最大化しました。

2. 試行錯誤を最小化する設計

不確実性を排除するため、以下の工夫をしました:

  • 単純なセンサーの採用: 最初は磁石で挟み込んた際の磁石との距離をリニアホール素子で検出しようと思いましたが、厚みとの補正が大変そうなのでやめました。機械的にPOTを回転させるという極めて原始的な方法を採用しました。
  • シンプルな信号処理: ADC→電圧→パーセンテージの単純な変換しか行ってません。リニアな信号なので実装が楽です。
  • 必要最小限の検証: 機械的な変形・ギアのバックラッシュでPOT回転量にどの程度誤差が出るかはシミュレーションより実際に作った方が早い!と思い、まずこの機構部分を3Dプリンタで作成しました。

3. 実用重視の割り切り

完璧を求めず、実用上問題ないレベルで妥協:

  • 枚数カウントはしない: 装置の誤差や、そもそも袋を丸めた際にスキマができるので、枚数ではなくパーセンテージ表示のみ。
  • キャリブレーション簡略化: 満杯時を100%、空を0%とする2点校正のみ
  • 電源は有線: 省電力化、バッテリ駆動は考慮しません。

運用結果

当初玄関に設置してたのですが、自分が作った作品を眺める気持ちでついつい目が行ってしまい、クラウドで確認できる意味ないじゃん!ってなりました。
そもそも玄関に設置すると邪魔くさいので、筆者の宅内工房のデッドスペースに設置場所を変更しました。
ここは普段あまり通らない場所なので、クラウド確認の恩恵があります。
自分は物忘れが激しいので、ゴミ袋を取りに行った際に補充しなきゃ!と気づいても忘れちゃうんです。
なので補充アラートは実際に役立ちますね〜

設置状況

まとめ:育児×エンジニアリング

このプロジェクトで学んだこと:

  1. 制約は創造性の母: 時間がないからこそ、シンプルで確実な解決策にたどり着けた
  2. 実用性ファースト: 100点の精度より、60点でも動くものを
  3. 古典的手法の価値: POTとギア機構、枯れた技術の組み合わせは手離れが良い

身の回りの課題を、手持ちの技術とツールで解決する。これこそがエンジニアリングの醍醐味ではないでしょうか。

息子が寝返りできるようになってきたので、次は窒息防止のため寝返り検知システムでも作ってみようかな...。

あとがき:

〜良い子のパパ・ママへ〜 育休中に3Dプリンタを買っちゃう事ってよくあるよね。ちゃんとベビー用品を作って家庭に貢献するんだぞ☆!
(↓奥様専用設計のベビーカー用スマホホルダーを造形中の我が家の Bambu Lab A1 君)
フィラメントだらけ

@tarouimo
育休は終わりましたが、DIY魂は継続中です!

ZAICO Developers Blog

Discussion