スマートスイッチ(Sonoff Mini R2)をESP32で制御する

No Image

最近流行中(?)のスマートデバイスも色んな種類がで出てきました。
その中でも基本となるものがスマートスイッチだと思っています。
中にリレーが入っていてそれをON/OFFするだけものです。
一般的にはスマホのアプリから制御するのが基本ですが、やっぱりESP32などで直接制御したいと思ってしまうのです。

スポンサーリンク

購入したスマートスイッチ

今回試しに買ったのはSonoffというブランドの Mini R2 というスマートスイッチです。
Aliexpressで販売されています。
Sonoff Mini R2

ITEAD STUDIO という電子工作をしている人ならどこかで聞いたことがあるかもしれないメーカーから出ています。
モジュールとかも売っていますので。

そんなSonoffというブランドのスマートスイッチは電子工作家には大変優しいものとなっています。
何しろDIYモードというのが搭載されているのですから。

ちなみに、4chのスマートスイッチに至っては Hacker Friendy! と謳っているほどです。
改造してくれと言わんばかりに分解しやすく、自作のファームウェアも入れやすい物となっています。

設定とIPアドレスの確認

DIYモードへの移行と初期設定

DIYモードに移行のための準備操作はとても簡単です。
ペアリングボタンを5秒間長押しするだけです。
長押しするとLEDの点滅が連続的になります。

その状態になると ITEAD-XXXXXXXXXX というアクセスポイントが現れるので無線LANで接続します。
私の持っているスマホでは検出できなかったので、私はPCから接続しました。
デフォルトのパスワードは 12345678 になっています。

アクセスポイントに接続した後、ブラウザで http://10.10.7.1/ にアクセスします。
そこで接続したい無線LANのSSIDとパスワードを入力すれば完了です。

IPアドレスとデバイスIDを確認する

無線LANに接続した後にIPアドレスやMACアドレスは確認できませんので、後に紹介するツールを使うか、Advanced IP Scanner などを使って確認しておきます。
MACアドレスは分かる場所には書かれていません。

MACアドレスはベンダーコードがありますが、Sonoff Mini R2 はおそらくESP8285を使っていますので、Espressifのベンダーコードを持ちます。
Espressifのベンダーコードは4C:EB:D6です。

ESP32をたくさん使っているとたくさんあるIPアドレスの中から割り出すのは難しいかもしれません。
なんですが、Sonoffは電子工作家に優しいので、ツールも用意してくれています。

tool_01DIY85_v330.exe というアプリケーションを使えば、勝手に検出して、リモート制御もできますし、(log)がついている方であれば、IPアドレスやデバイスIDも分かってしまいますのでとても便利です。

ESP32でSonoffのスマートスイッチを直接操作する

DIYモードでは RESTful API を使って制御します。
HTTPのPOSTメソッドを使ったAPIです。
全コマンドがGitHubに上がっていますので参考にしてください。

HTTP POST でコマンドを投げる

コマンドはJSON形式になります。
ポートはデフォルトでは8081です。

ON/OFFコマンド

スイッチをON/OFFするには http://[ip]:[port]/zeroconf/switch のというURLにPOSTメソッドで下記のようなコマンドを送ります。

{"deviceid":"100000140e", "data":{"switch":"on"}}

何となく分かるかと思いますが、上記はスイッチをONにするコマンドです。
deviceidは固有の値を持つので、所有しているスマートスイッチのデバイスIDに置き換えてください。

状態の取得

スイッチの状態を取得するには http://[ip]:[port]/zeroconf/info のというURLにPOSTメソッドで下記のコマンドを送ります。

{"deviceid":"100000140e", "data":{"switch":""}}

状態取得に応答した場合は下記のようなデータが送られてきます。

{
    "seq": 6,
    "error": 0,
    "data": {
        "switch": "on",
        "startup": "stay",
        "pulse": "off",
        "pulseWidth": 500,
        "ssid": "ssid",
        "otaUnlock": false,
        "fwVersion": "3.6.0",
        "deviceid": "100000140e",
        "bssid": "XX:XX:XX:XX:XX:XX",
        "signalStrength": -50
    }
}

スポンサーリンク

分かりやすいように改行やインデントを入れていますが、実際は1行でずらっと送られてきます。
この場合だと"switch"が"on"ですので、スイッチはON状態といったような状態が分かりますね。

ESP32で直接制御する

HTTPのPOSTメソッドを使えば簡単に制御できることが分かったので、ESP32でも複雑なプログラムでなくても制御できますね。
HTTPClientの使い方さえわかれば大丈夫です。
テスト用に作ったプログラムは下記のような感じになりました。

#include <WiFi.h>
#include <HTTPClient.h>

#include <ArduinoJson.h>
#include <ButtonEvents.h>

#define MAN_BTN 22

#define SSID "ssid"
#define PASS "password"

const unsigned int sonoff_r2_port = 8081;
const String sonoff_dev_id = "100000140e";
const String device_id_hd  = "{\"deviceid\":\"";
const String get_buffer    = "\",\"data\":{\"switch\":\"\"}}";
const String on_buffer     = "\",\"data\":{\"switch\":\"on\"}}";
const String off_buffer    = "\",\"data\":{\"switch\":\"off\"}}";

const String sonoff_r2_ip = "192.168.1.24";
const String info_url     = "/zeroconf/info";
const String switch_url   = "/zeroconf/switch";

WiFiClient   client;
HTTPClient   http;
ButtonEvents manual_btn;

void setup() {
  manual_btn.attach(MAN_BTN, INPUT_PULLUP);

  // Serial Port Initialize
  Serial.begin(115200);
  delay(100);
  Serial.println("\nBooting...");

  // Wi-Fi Connection
  WiFi.begin(SSID, PASS);
  Serial.print("Connecting Wifi...");
  while (WiFi.status() != WL_CONNECTED) {
    delay(500);
    Serial.print(".");
  }
  Serial.println("\nWiFi connected");
  Serial.print("IP Address:\t");
  Serial.println(WiFi.localIP());
}

void loop() {
  manual_btn.update();

  static bool relay_state = false;
  if (manual_btn.tapped()) {
    if (!relay_state) {
      sonoff_on(sonoff_r2_ip, sonoff_r2_port, sonoff_dev_id);
      Serial.println("Send ON");
    }
    else {
      sonoff_off(sonoff_r2_ip, sonoff_r2_port, sonoff_dev_id);
      Serial.println("Send OFF");
    }
    relay_state = !relay_state;
  }

  if (manual_btn.held()) {
    Serial.println(is_relay_state(sonoff_r2_ip, sonoff_r2_port, sonoff_dev_id) ? "State:ON" : "State:OFF");
  }
}

void sonoff_on(const String sonoff_ip, const uint16_t sonoff_port, const String dev_id) {
  String server_name = "http://" + sonoff_ip + ":" + String(sonoff_port) + switch_url;
  Serial.println("Connect to " + server_name);

  if (WiFi.status() == WL_CONNECTED) {
    http.begin(client, server_name);
    http.addHeader("Content-Type", "application/json");

    String post_data = device_id_hd + dev_id + on_buffer;
    Serial.println("Send Content: " + post_data);
    int res_code = http.POST(post_data);

    if (res_code > 0) {
      Serial.print("HTTP Response code: ");
      Serial.println(res_code);
    } else {
      Serial.print("Error code: ");
      Serial.println(res_code);
    }

    http.end();
  }

}

void sonoff_off(const String sonoff_ip, const uint16_t sonoff_port, const String dev_id) {
  String server_name = "http://" + sonoff_ip + ":" + String(sonoff_port) + switch_url;
  Serial.println("Connect to " + server_name);

  if (WiFi.status() == WL_CONNECTED) {
    http.begin(client, server_name);
    http.addHeader("Content-Type", "application/json");

    String post_data = device_id_hd + dev_id + off_buffer;
    Serial.println("Send Content: " + post_data);
    int res_code = http.POST(post_data);

    if (res_code > 0) {
      Serial.print("HTTP Response code: ");
      Serial.println(res_code);
    } else {
      Serial.print("Error code: ");
      Serial.println(res_code);
    }

    http.end();
  }

}

bool is_relay_state(const String sonoff_ip, const uint16_t sonoff_port, const String dev_id) {
  bool relay_on = false;
  String payload;

  String server_name = "http://" + sonoff_ip + ":" + String(sonoff_port) + info_url;
  Serial.println("Connect to " + server_name);

  if (WiFi.status() == WL_CONNECTED) {
    http.begin(client, server_name);
    http.addHeader("Content-Type", "application/json");

    String post_data = device_id_hd + dev_id + get_buffer;
    Serial.println("Send Content: " + post_data);
    int res_code = http.POST(post_data);

    if (res_code > 0) {
      Serial.print("HTTP Response code: ");
      Serial.println(res_code);
      payload = http.getString();
      Serial.println(payload);
    } else {
      Serial.print("Error code: ");
      Serial.println(res_code);
    }

    http.end();

    // declare a JSON document to hold the response
    StaticJsonDocument<512> response;

    // get the current state of the light, whether it's on or off
    DeserializationError err = deserializeJson(response, payload);
    if (err.code() == DeserializationError::Ok) {
      String switch_state = response["data"]["switch"];
      if (switch_state == "on") {
        relay_on = true;
      }
    }
  }
  return relay_on;
}

SSIDやパスワード、スマートスイッチのIPアドレス、デバイスIDは環境に合わせて書き換えてください。

ハードウェアとしてはタクトスイッチを22番ピンに接続しています。
ESP32側も非常にシンプルです。
普通にタクトスイッチを押すと、スマートスイッチをON/OFFし、長押しで情報を取得します。

別途使用するライブラリ

ArduinoJSONライブラリ

ArduinoでJSON形式を簡単に扱えるようにするためのライブラリがあると便利です。

今回のプログラムではデータを受信して状態を取得するところに使用しています。

ButtonEventsライブラリ

スイッチの制御には ButtonEvents というライブラリを使用しています。
普通に押したときと長押ししたときで動作を分けているだけですけどね。
このライブラリとしてはダブルクリックもいけちゃいます。
あとチャタリング処理をしてくれているのも嬉しいところです。


Sonoff Mini R2 は1chのスマートスイッチですが、他にも2chや4chのスマートスイッチのラインアップあります。
マニュアルスイッチも付いていたりでかゆいところに手が届くものが多いです。
ただ、DIYモードにはならず、ファームウェアを書き換えたり工夫が必要みたいです。
そこまでするなら安いリモートリレーモジュールを買って適当なプログラムを書き込んだほうがマシな気がしますけどね...

とはいえ1chのスマートスイッチは簡単で小型なので非常に便利です。
ここまで便利だと自作する気がなくなってしまうほどです。
自分も便利なスマートデバイスを作ってみようかなと考えてしまいます(笑)

スマートスイッチ(Sonoff Mini R2)をESP32で制御する

スポンサーリンク

Leave a Comment

  • 2023/03/02

    SONOFFの製品、便利でいいですよね。
    ただ、PSE認証が無いのが難点だと思っています。
    コンセントの間に噛ますものは通してるところを見ると、こういった電線直結のものは通せないんですかね・・・

    Reply

通りがかりさん にコメントする コメントをキャンセル