編集履歴一覧に戻る
kipopytokyoのアイコン画像

kipopytokyo が 2021年02月20日21時32分41秒 に編集

初版

タイトルの変更

+

流し忘れても安心!立ったら勝手に流してくれるリモコン

タグの変更

+

秋葉原2021

+

M5StickC

+

ESP32

+

リモコン

+

M5Stack

メイン画像の変更

メイン画像が設定されました

本文の変更

+

elchika初投稿です!宜しくお願い致します :wink: みなさんは、トイレでうっかり流し忘れてしまうことありませんか? 高級なトイレの場合は、デフォルトで自動洗浄機能が付いていますが、安かったり、古いトイレの場合、自動洗浄機能はありません。 そこで思い立ち、「立ったら勝手に流してくれるリモコン」(早い話が自動洗浄機能)を製作しましたので、紹介しましょう。 # :smile:まずは下ごしらえ:smile:  このリモコンを作るには、トイレを自動で流す機能が必要です。一から作るとなると、結構な労力です。そこで、出来合いの物を購入することにしました。 LIXIL(旧TOTO)から発売されている「流せるもん CWA-67B」という物を購入。コイツは、赤外線リモコンを使用して、トイレをモーター制御で流すというもの。 13000円位と少々お高めではありますが、モーターや赤外線リモコン、その他付属品が付いており、TOTO製の[「流せるもん CWA-67B」](https://amzn.to/3qkJ2gZ)の適合しているトイレではなかったのですが、付属品を微調整して、中のフロートを上げ下げできるようにすることで動かすことができました。 今まで通り、普通に流すこともできますが、リモコン操作でも流せることができ、何かちょっと感動。 でも、これはあくまでもスタートです。 「流せるもん~」の詳細は、[私のブログ](https://kinopy.info/kinopy/post-4757/)で紹介しておりますので、ご参照ください。 # :wink:リモコンを作る:wink:  当初は、折角リモコンがあるので、それを改造すればいいじゃないか…そう思い、センサーやマイコンを付けて改造したのですが、意外とセンサーが大飯食らいなので、電池2本では何日も使えないような代物になってしまいました(「流せるもん」は、単三電池2本)。 困っていた所、おあつらえ向きなガジェットを見つけました。elchikaをご覧になっている方ならお馴染みの「M5Stick C」です。 私はどちらかというと、部品を組み合わせてはんだ付けするのが好きなのですが、色々調べると、M5Stick CにジャストフィットするHATやM5Stack・Grove用に用意されているモジュール類がそのまま使えるらしく、私の用途では、HATとM5STACK用IRモジュールを使うと用が足りてしまいます。 後はプログラムさえ作ればよく、ホント便利な世の中になったものです(すっかりオヂさんな気分)。 昔のように、回路を組んでガシガシやるのではなく、あるものを使って、スマートに使いこなすのが、今の時代にマッチした電子工作の姿なのかも知れませんね。 オリジナルのリモコンと自動洗浄用のリモコンの2個あることになりますが、オリジナルの方は常時動いているわけではないし、2個あることによる支障はないので、まっ、結果オーライです:sweat_smile: これにも、実は顛末記があり、M5Stick Cに内蔵されているIR LEDでも狭いトイレの中では、充分動いてくれたのですが、ToFユニットを付けてしまうと、IR LEDの穴が塞がってしまうというオチが。 折角ToFユニットを買ったので、それを生かすことにし、IRの方を別の物(M5Stack用赤外線送受信ユニット)とすることにしたのですが、今度は、何故か赤外線の飛びが悪く、結局は、M5Stick C+ToF測距センサユニット(https://www.switch-science.com/catalog/5219/)の組み合わせに変更しました。 人の動きをとらえるセンサーには人感センサーがよく使われますが、センサー付きLED電球で経験された方はご存じと思いますが、明るい所では全く役に立ちません。 赤外線を使って、戻ってくる時間を測定するというToF(タイムオブフライト)センサーは(使われているデバイスは)940nmの赤外線を使用しており、明るさに影響を受けません。 今回の用途には、まさに打ってつけです! # :kissing_smiling_eyes:材料:kissing_smiling_eyes: | 部品 | 個数 | 値段 | 購入先 | |:---:|:---|:---|:---| | [M5Stick C](https://www.switch-science.com/catalog/6350/) | 1 | ¥1,980 | スイッチサイエンスなど | | [M5Stack用ToF測距センサユニット](https://www.switch-science.com/catalog/5219/) | 1 |¥1,177 | スイッチサイエンスなど | (以下は、お好みに応じて) | 部品 | 個数 | |:---:|:---| | USB Cケーブル | 1 | USB 5V電源 | 1 送料を除けば、ケーブル込みで大体4,000円少々でできました。 M5Stick Cには、バッテリーが内蔵されているのですが、バッテリー切れを嫌って、私はUSB電源を使用しました。 M5Stick-Cの電源をOFFにしていても、ある程度電流が流れているのか、一晩放置していたら、全く動かなくなってしまいました。 慌てて、USB電源をつないでもダメでした。どうもプログラムが消えてしまったようで…。ユニットを付けるとダメなんでしょうか。トホホ…:sob: そのような訳で、長時間使う向きにはUSB電源をお勧めします。 # :open_mouth:プログラム紹介:open_mouth:  下記のプログラムは、私のオリジナルではなく、今回の用途のためにサンプルを合体させ、更にモディファイしたものです。 ToFサンプル:https://github.com/m5stack/M5-ProductExampleCodes/blob/master/Hat/tof-hat/Arduino/ToF IRサンプル:https://github.com/crankyoldgit/IRremoteESP8266/issues/706 特に、IRの箇所は、TVやエアコンなどの実例はあれど、トイレ用などのものは殆ど見つけることができませんでした。 色々実験した所、上記のサンプルの結果が良かったので、IR送信の部分はそちらを参考にしました。 ライブラリはそのまま流用しているので、Arduino IDEのライブラリマネージャから必ずIRremoteESP8266をゲットしてくださいね。 それと、M5***を始めて使う人は、https://github.com/m5stack/M5StickCからライブラリ(Zipファイル)をゲットして、こちらもインストールしてください。 公式では、Arduino IDEのライブラリマネージャからM5StickCライブラリをインストールするような指示になっていますが、ファイルが足りなくて、悲しいかなコンパイルができません(2021年2月現在)。 早く改善して~:sob: ```arduino:立ったら勝手に流してくれるリモコンの例(ArduinoIDE用) #include <Arduino.h> #include <IRremoteESP8266.h> #include <IRsend.h> #include <M5StickC.h> #include <Wire.h> // byte SEND_PIN = 32; // IR Unit:32 byte SEND_PIN = 9; // 本体IR:9 IRsend irsend(SEND_PIN); #define VL53L0X_REG_IDENTIFICATION_MODEL_ID 0xc0 #define VL53L0X_REG_IDENTIFICATION_REVISION_ID 0xc2 #define VL53L0X_REG_PRE_RANGE_CONFIG_VCSEL_PERIOD 0x50 #define VL53L0X_REG_FINAL_RANGE_CONFIG_VCSEL_PERIOD 0x70 #define VL53L0X_REG_SYSRANGE_START 0x00 #define VL53L0X_REG_RESULT_INTERRUPT_STATUS 0x13 #define VL53L0X_REG_RESULT_RANGE_STATUS 0x14 #define ToF_ADDR 0x29//the iic address of tof //「流せるもん」のコードに合わせています。 const uint16_t kInaxHdrMark = 9000; const uint16_t kInaxHdrSpace = 4434; const uint16_t kInaxBitMark = 600; const uint32_t kInaxOneSpace = 1675; const uint32_t kInaxZeroSpace = 570; const uint64_t InaxData = 0x5C30CF; //「大」ボタンのコード byte gbuf[16]; uint16_t dist; uint16_t tmp1; uint8_t i, countdown; void measure_distance(); uint16_t bswap(byte); uint16_t makeuint16(int, int); uint16_t VL53L0X_decode_vcsel_period(short); void write_byte_data(byte); void write_byte_data_at(byte, byte); void write_word_data_at(byte, uint16_t); byte read_byte_data(); byte read_byte_data_at(byte); uint16_t read_word_data_at(byte); void read_block_data_at(byte, int); void setup() { // Wire.begin(0, 26, 400000);// join i2c bus (HATの場合) Wire.begin(32, 33, 400000);// join i2c bus (ToFユニットの場合) M5.begin(); M5.Lcd.setRotation(2); irsend.begin(); pinMode(10, OUTPUT); digitalWrite(10, HIGH); } void sendWashletCommand(uint64_t data, uint16_t nbytes, uint16_t repeat) { irsend.enableIROut(38); for (uint16_t r = 0; r <= repeat; r++) { // Header irsend.mark(kInaxHdrMark); irsend.space(kInaxHdrSpace); // Data irsend.sendData(kInaxBitMark, kInaxOneSpace, kInaxBitMark, kInaxZeroSpace, data, nbytes, true); // Footer irsend.mark(kInaxBitMark); irsend.space(100000); } } void loop() { tmp1 = 0; measure_distance(); delay(100); if (dist > 200){ digitalWrite(10,HIGH); } else if (dist == 20){ //時々、誤動作なのか、一瞬 数値が20(0cmの距離)となる時があった。 //distが20になっても何もしないようダミーとする。 }else { digitalWrite(10,LOW); while (dist < 200){ //着座している時はループ measure_distance(); delay(100); } //立ったら、30秒(30秒*2=60)ディレイ //iを増減すると、離れてから流す時間を変えられます。 //変数countdownは、表示に用います。 countdown = 30; i = 60; while (i > 0){ //0.5秒間隔で、M5Stick CのLED(G10)を点滅させる。 if (digitalRead(10)){ digitalWrite(10, LOW); M5.Lcd.setCursor(0, 80); M5.Lcd.fillScreen(BLACK); M5.Lcd.setTextFont(7); M5.Lcd.print(countdown); countdown--; } else { digitalWrite(10, HIGH); } i--; delay(500); } M5.Lcd.fillScreen(BLACK); M5.Lcd.setTextFont(1); M5.Lcd.setCursor(20,80); M5.Lcd.print("Flash!"); digitalWrite(10, LOW); sendWashletCommand(InaxData, 24, 0); delay(2000); M5.Lcd.fillScreen(BLACK); digitalWrite(10, HIGH); } } void measure_distance() { write_byte_data_at(VL53L0X_REG_SYSRANGE_START, 0x01); read_block_data_at(VL53L0X_REG_RESULT_RANGE_STATUS, 12);//read 12 bytes once dist = makeuint16(gbuf[11], gbuf[10]);//split distance data to "dist" M5.Lcd.fillScreen(BLACK); M5.Lcd.setCursor(20, 80); M5.Lcd.setRotation(2); M5.Lcd.setTextFont(1); M5.Lcd.print(dist); } uint16_t bswap(byte b[]) { // Big Endian unsigned short to little endian unsigned short uint16_t val = ((b[0] << 8) & b[1]); return val; } uint16_t makeuint16(int lsb, int msb) { return ((msb & 0xFF) << 8) | (lsb & 0xFF); } uint16_t VL53L0X_decode_vcsel_period(short vcsel_period_reg) { // Converts the encoded VCSEL period register value into the real // period in PLL clocks uint16_t vcsel_period_pclks = (vcsel_period_reg + 1) << 1; return vcsel_period_pclks; } /* * IIC Functions */ /* function description: write one byte data */ void write_byte_data(byte data) { Wire.beginTransmission(ToF_ADDR); Wire.write(data); Wire.endTransmission(); } /* function description: write one byte data to specifical register */ void write_byte_data_at(byte reg, byte data) { Wire.beginTransmission(ToF_ADDR); Wire.write(reg); Wire.write(data); Wire.endTransmission(); } /* function description: read two bytes data to specifical register */ void write_word_data_at(byte reg, uint16_t data) { byte b0 = (data &0xFF); byte b1 = ((data >> 8) && 0xFF); Wire.beginTransmission(ToF_ADDR); Wire.write(reg); Wire.write(b0); Wire.write(b1); Wire.endTransmission(); } /* function description: read one byte data */ byte read_byte_data() { Wire.requestFrom(ToF_ADDR, 1); while (Wire.available() < 1) delay(1); byte b = Wire.read(); return b; } /* function description: read one byte data from specifical register */ byte read_byte_data_at(byte reg) { //write_byte_data((byte)0x00); write_byte_data(reg); Wire.requestFrom(ToF_ADDR, 1); while (Wire.available() < 1) delay(1); byte b = Wire.read(); return b; } /* function description: read two bytes data from specifical register */ uint16_t read_word_data_at(byte reg) { write_byte_data(reg); Wire.requestFrom(ToF_ADDR, 2); while (Wire.available() < 2) delay(1); gbuf[0] = Wire.read(); gbuf[1] = Wire.read(); return bswap(gbuf); } /* function description: read multiple bytes data from specifical register */ void read_block_data_at(byte reg, int sz) { int i = 0; write_byte_data(reg); Wire.requestFrom(ToF_ADDR, sz); for (i=0; i<sz; i++) { while (Wire.available() < 1) delay(1); gbuf[i] = Wire.read(); } } ``` # :yum:動作を解説:yum: 主要動作であるメインループの箇所をチャートで纏めてみました(ライブラリに関しては、各ソースをご参照ください)。 :::plantuml:動作(一部) @startuml (*) --> "距離を測る(1)" --> "100ミリ秒ディレイ(1)" If "値は?" then --> [値が200より大きい] "LEDを消灯(1)" --> "距離を測る(1)" else --> [値が20] "何もしない" --> "距離を測る(1)" else --> [値が200以下] "LEDを点灯" --> "距離を測る(2)" --> "100ミリ秒ディレイ(2)" --> If "値が200より大きい?" then --> [値が200以下]"距離を測る(2)" else --> [値が200より大きい]"30秒LEDを点滅させる" Endif --> "「大」のコードを送信" --> "LEDを消灯(2)" --> "距離を測る(1)" Endif @enduml ::: 以下、簡単に説明します。 1. ToFモジュールの測定値を確認し、値が200より大きければ(離れている)M5Stick Cに内蔵されている本体LEDを消灯。 2. 200未満(着座)であれば、本体LEDを点灯。 3. 再び測定値が200より大きくなれば(立つ)、30秒本体LEDを点滅させ、「大」のコードを赤外線送信する。 4. LEDを消灯した後、距離を測るループに入る。 以上のような流れです。 # :sweat:実際に使ってみた:sweat: 短いですが、[YouTube](https://youtu.be/pAy92BO3toE)にアップしましたので、様子をご覧ください。 これは、我が家の介護用として製作したもので、いざ使ってみると、立ったときに自動で流してくれるのは便利よね…と思ったりして。立ってから30秒は短いかも知れません。 センサーに手をかざしてもOKですので、手を触れることなく自動洗浄できますよ。コロナ禍の今にピッタリかも。 「kInax~(赤外線のパルス幅を設定)」という変数をいじれば、他の会社のリモコンにも応用できるはずなので、興味ある方は、色々実験してみてくださいね:wink: