Help us understand the problem. What is going on with this article?

M5StickC を TOTO ウォッシュレットのリモコンにする

概要

ウォッシュレットのリモコンの一部ボタン(大事な「大」と「小」)が不調で時々反応しなくなるので、予備として M5StickC でリモコンとして使えるようにしました。(まぁ、オート機能もあるし、本体側のボタンもあるので実際に使うことはなさそうですが)
リモコンの赤外線信号の情報が無いので M5GO 付属の IR REMOTE ユニットを接続し、ライブラリ IRremoteESP8266 付属のスケッチ例 IRrecvDumpV2 で実際のリモコンの赤外線信号データを取得、そのデータを M5StickC から送信する仕組みです。取得した生データをそのまま sendRaw() で送っても動作しましたが、生データを解析し、コード指定で送信するようにしています。

環境

  • M5StickC
    • Arduino 1.8.10
    • ESP32 Arduino 1.0.4
    • M5StickC 0.1.0
    • IRremoteESP8266 2.6.6
  • ウォッシュレット
    • TOTO ネオレストAH3
      2019-10-14-1 TOTO Washlet.jpeg
      2019-10-14-2 TOTO Washlet.jpeg

リモコン信号

以下の信号データは実際のリモコン信号を解析したものです。ボタンを押すと各信号が3回繰り返して送信されていました。

ボタン 信号1 信号2
便ふた開閉 0x2008000E0E -
便座開閉 0x200800F6F6 -
0x200800B0B0 -
0x2008008888 -
eco 小 0x200800B6B6 -
おしり 0x2008006060 0x2008AC802C 注1、2
やわらか 0x2008006060 0x2008ACA804 注1、2
ビデ 0x2008001010 0x2008AC40EC 注1、2
0x2008000000 0x2008AADA70 注1
マッサージ 0x200800E0E0 -
乾燥 0x200800C0C0 - 注3
パワー脱臭 0x2008007C7C -

( 今回の実装では「マッサージ/乾燥/パワー脱臭」は画面に入りきらないので省略 )

  • 注1 「おりし/やわらか/ビデ/止」ボタンは「ムーブ入/切」と「洗う」の2種類の信号が出力される )

  • 注2 実際のリモコンは状態を持っていて、洗浄停止中に「おりし/やわらか/ビデ」を押すと信号2(洗浄)のみが送信され、洗浄中に押すと信号1(「ムーブ入/切)と信号2(洗浄)の両方が送信される。今回の実装は実用上問題が無いので常に信号1と信号2の両方を送信する。

  • 注3 乾燥に関してもリモコンは状態を持っていて、乾燥中に再度「乾燥」ボタンを押すと「止」と同じ信号が送信される。

実行例

リモコンの赤外線信号解析用に M5GO 付属の IR REMOTE ユニットを接続し、受信用のピン番号は 33 で IRrecvDumpV2 を実行しました。

2019-10-14-4 TOTO Washlet.jpeg

リモコンプログラムは画面に送信信号をメニュー表示し、ボタン操作で選択、送信します。

  • ボタンB・電源ボタンでメニュー項目を進む・戻る
  • ボタンAで選択中の信号を送信

2019-10-14-3 TOTO Washlet.jpeg

M5StickCプログラム

TOTO_Washlet.ino
#include <M5StickC.h>
#include <Arduino.h>
#include <IRremoteESP8266.h>
#include <IRsend.h>

const uint16_t kIrLed = 9;  // M5StickC GPIO pin to use.
IRsend irsend(kIrLed);  // Set the GPIO to be used to sending the message.

// IR Data for TOTO Washlet Neorest AH3
struct {
  std::string name;
  uint64_t data1;
  uint64_t data2;
} ir_data[] = {
    {"Op/Cl Lid",  0x2008000E0E, 0},            // Open Close Lid
    {"Op/Cl Seat", 0x200800F6F6, 0},            // Open Close Seat
    {"Full Flush", 0x200800B0B0, 0},            // Full Flush
    {"Half Flush", 0x2008008888, 0},            // Half Flush
    {"eco Flush",  0x200800B6B6, 0},            // eco Half Flush
    {"Wash Rear",  0x2008006060, 0x2008AC802C}, // move on/off + Wash Rear
    {"Soft Rear",  0x2008006060, 0x2008ACA804}, // move on/off + Soft Rear
    {"Bidet",      0x2008001010, 0x2008AC40EC}, // move on/off + Bidet
    {"Stop",       0x2008000000, 0x2008AADA70}  // move off + Stop Wash
  };

int data_count = sizeof(ir_data) / sizeof(ir_data[0]);
int ix = 0;
int x_offset0 =  3;
int x_offset1 =  5;
int x_offset2 = 15;
int y_offset1 = 12;
int y_offset2 = 30;
int height    = 14;

void sendToto(uint64_t data) {
  irsend.sendGeneric(
          6000,  3000,      // headermark, headerspace,
          600,   1600,      // onemark,    onespace,
          600,    600,      // zeromark,   zerospace,
          600,  40000,      // footermark, gap,
          data,    39,      // data,       nbits,
          38,    true,      // frequency,  MSBfirst,
          2, kDutyDefault); // repeat,     dutycycle (kDutyDefault = 50 %)
}

void setup() {
  M5.begin();
  irsend.begin();

  M5.Axp.ScreenBreath(9); // 画面輝度 (7-15)
  M5.Lcd.setTextColor(TFT_WHITE, TFT_BLACK);
  M5.Lcd.drawString("<< Washlet >>", x_offset0, y_offset1);
  M5.Lcd.setTextColor(TFT_ORANGE, TFT_BLACK);
  M5.Lcd.drawString(">", x_offset1, y_offset2);
  M5.Lcd.drawString(ir_data[0].name.c_str(), x_offset2, y_offset2);
  M5.Lcd.setTextColor(TFT_WHITE, TFT_BLACK);
  for (int i = 1; i < data_count; i ++) {
    M5.Lcd.drawString(ir_data[i].name.c_str(), x_offset2, y_offset2 + i * height);    
  }
}

void loop() {
  M5.update();

  if (M5.BtnA.wasPressed()) {
    Serial.println(ir_data[ix].name.c_str());
    sendToto(ir_data[ix].data1);
    if (ir_data[ix].data2 != 0) {
      delay(40);
      sendToto(ir_data[ix].data2);
    }
  }

  if (M5.BtnB.wasPressed() || M5.Axp.GetBtnPress() > 0) {
    M5.Lcd.setTextColor(TFT_BLACK, TFT_BLACK);
    M5.Lcd.drawString(">",  x_offset1, y_offset2 + ix * height);
    M5.Lcd.setTextColor(TFT_WHITE, TFT_BLACK);
    M5.Lcd.drawString(ir_data[ix].name.c_str(), x_offset2, y_offset2 + ix * height);
    if (M5.BtnB.wasPressed()) {
      ix ++;
      if (ix == data_count) {
        ix = 0;
      }
    } else {
      ix --;
      if (ix < 0) {
        ix = data_count - 1;
      }      
    }
    M5.Lcd.setTextColor(TFT_ORANGE, TFT_BLACK);
    M5.Lcd.drawString(">",  x_offset1, y_offset2 + ix * height);
    M5.Lcd.drawString(ir_data[ix].name.c_str(), x_offset2, y_offset2 + ix * height);    
  }
}

リモコン信号解析プログラム

スケッチ例 IRrecvDumpV2 で取得した Raw Data を解析して信号データに変換するプログラムです。Python で書いて Mac上で実行しましたが、この程度なので IRrecvDumpV2 に組み込んでもよさそうです。値の範囲の判定値は仕様などは確認していなくて実際の値に合わせて調整したものです。

TOTO_Washlet.py
import re
import fileinput

HDR_MARK   =  6000
HDR_SPACE  =  3000
BIT_MARK   =   600
ZERO_SPACE =   600
ONE_SPACE  =  1600
GAP        = 40000

for line in fileinput.input():
    line = line.strip()
    if len(line) == 0:
        continue

    m = re.match('uint16_t rawData\[\d+\] = {([^}]+)};', line)
    if m:
        print(line)
        print()
        raw = list(map(lambda x: int(x), m.group(1).split(',')))
        while len(raw) >= 2:
            mark  = raw.pop(0) # Skip Header Mark
            space = raw.pop(0) # Skip Header Space
            data = 0
            bit_length = 0
            while len(raw) >= 2:
                mark  = raw.pop(0)
                space = raw.pop(0)
                if (BIT_MARK-200) <= mark <= (BIT_MARK+200):
                    if (ZERO_SPACE-200) <= space <= (ZERO_SPACE+200):
                        print('0', end='')
                        data = data << 1
                        bit_length += 1
                    elif (ONE_SPACE-200) <= space <= (ONE_SPACE+200):
                        print('1', end='')
                        data = data << 1 | 1
                        bit_length += 1
                    elif (GAP-10000) < space < (GAP+10000):
                        break
                    else:
                        print()
                        print('Space Error:', space)
                else:
                    print()
                    print('BitMark Error:', mark)
            print('  0x{:X}  {:d} bit'.format(data, bit_length))
        print()

「便ふた開閉」ボタンの Raw Data を上記プログラムで処理して 0x2008000E0E の信号が3回出力されていることを確認した例です。

$ python TOTO_Washlet.py Raw_Data.txt
uint16_t rawData[245] = {5984, 2958,  606, 544,  580, 1658,  582, 544,  574, 544,  576, 544,  582, 540,  576, 544,  576, 544,  576, 544,  578, 516,  604, 546,  576, 1662,  576, 544,  578, 520,  600, 546,  578, 544,  578, 544,  578, 542,  578, 546,  578, 542,  580, 542,  576, 520,  600, 544,  576, 548,  578, 544,  576, 544,  578, 520,  602, 1658,  578, 1636,  602, 1636,  600, 546,  578, 544,  576, 544,  578, 544,  576, 544,  578, 1662,  576, 1636,  602, 1660,  578, 546,  578, 39116,  5960, 2960,  602, 546,  578, 1636,  600, 522,  604, 540,  580, 518,  602, 544,  578, 544,  578, 544,  576, 544,  576, 544,  578, 522,  600, 1634,  604, 518,  602, 518,  602, 548,  574, 548,  574, 544,  578, 544,  576, 546,  576, 544,  578, 544,  580, 516,  602, 548,  576, 544,  578, 546,  576, 544,  578, 542,  578, 1660,  580, 1632,  602, 1660,  576, 544,  578, 546,  576, 518,  604, 544,  578, 546,  574, 1658,  582, 1656,  578, 1662,  576, 544,  578, 38872,  5984, 2958,  604, 520,  604, 1636,  602, 542,  578, 544,  576, 546,  576, 520,  602, 542,  578, 522,  604, 542,  578, 544,  576, 550,  572, 1634,  604, 562,  576, 544,  578, 544,  578, 546,  576, 548,  578, 544,  576, 546,  574, 544,  576, 544,  578, 546,  578, 542,  578, 544,  576, 522,  600, 546,  578, 544,  578, 1634,  602, 1660,  578, 1660,  576, 544,  576, 548,  572, 544,  576, 546,  578, 544,  576, 1660,  578, 1636,  602, 1658,  578, 544,  578};  // UNKNOWN 450EA3E

010000000001000000000000000111000001110  0x2008000E0E  39 bit
010000000001000000000000000111000001110  0x2008000E0E  39 bit
010000000001000000000000000111000001110  0x2008000E0E  39 bit
Why not register and get more from Qiita?
  1. We will deliver articles that match you
    By following users and tags, you can catch up information on technical fields that you are interested in as a whole
  2. you can read useful information later efficiently
    By "stocking" the articles you like, you can search right away
Comments
No comments
Sign up for free and join this conversation.
If you already have a Qiita account
Why do not you register as a user and use Qiita more conveniently?
You need to log in to use this function. Qiita can be used more conveniently after logging in.
You seem to be reading articles frequently this month. Qiita can be used more conveniently after logging in.
  1. We will deliver articles that match you
    By following users and tags, you can catch up information on technical fields that you are interested in as a whole
  2. you can read useful information later efficiently
    By "stocking" the articles you like, you can search right away
ユーザーは見つかりませんでした