🦜

M5StickC (with Yun Hat) と スマートコンセントHS103 でペットヒーターを適温制御する

2021/11/30に公開

  • うちのインコさむそう
  • 100Vのペットヒーターあるけど、鳥籠全体をアクリルケースに入れてるからつけっぱなしだと暑すぎになるかも
  • どうやら今はディスコンになっているらしきYun Hat(M5StickC用)というものが余ってた
  • tp-linkのスマートコンセント「HS103」も余ってた

上記の理由により「インコ用自動定温制御装置」をさくっと作ることにしました。

1. スマートコンセントHS103を設定

tp-linkのアプリで設定します。
スマホからONOFFできるようになっていればOK。

https://manuals.plus/ja/tp-link/hs103-kasa-smart-wi-fi-plug-mini-manual

2. サーバー側を準備する

こちらを参考にさせていただきつつ

https://zenn.dev/book000/articles/tp-link-cloud-api

以下のライブラリを使ってNode.jsで作成します。

https://www.npmjs.com/package/tplink-cloud-api

$ npm i tplink-cloud-api

引数つきで実行することでオンオフできるようなスクリプトを書きます。

heater.js
const { login } = require('tplink-cloud-api');

(async () => {
  // 引数を確認してあった場合のみ操作
  if (process.argv && process.argv[2]) {
    const tplink = await login('tp-linkアカウントのメアド', 'パスワード');
    const deviceList = await tplink.getDeviceList(); // これ取得しないとalias使えないぽい
    const myPlug = tplink.getHS100('スマートコンセントにつけた名称');

    // 引数の値によってオンかオフ
    if (process.argv[2] === '0') myPlug.powerOff();
    if (process.argv[2] === '1') myPlug.powerOn();
    if (process.argv[2] === '2') myPlug.toggle();
    
    // トグル結果出力(0か1)
    console.log(await myPlug.getRelayState());
  }
})();

これを単体実行することで、HS103をオンオフできるようになりました。

$ node heater.js 0  # オン
$ node heater.js 1  # オフ

express化したりnginxからハンドルしたりが面倒なので、Webからのアクセスを受け取ってこのNode.jsスクリプトを動作させるPHPを簡単に書きます。
https://*****.jp/heater.php?sw=0 のような形式で操作できるようにしました。

heater.php
<?php
  /**
   * 引数sw
   * 0: オフ
   * 1: オン
   * 2: トグル
   * それ以外: 操作は実行されない(-1)
   */
  if (isset($_GET['sw']) && ($_GET['sw'] === '0' || $_GET['sw'] === '1' || $_GET['sw'] === '2')) {
    $result = exec('node ./heater.js ' . $_GET['sw']);
    echo $result;
  } else {
    echo -1;
  }

これで、heater.phpにパラメータをつけてアクセスすることで、スマートコンセントのオンオフができるAPI(Webhook)が完成しました。
ローカルで実験してから、適当なサーバーにアップロードしておきます。

3. Arduino IDEにYunライブラリをいれる

ここからハードウェア側です。

https://docs.m5stack.com/#/en/hat/hat-yun

導入手順にしたがってライブラリをいれます。

4. Yun HatのサンプルスケッチをM5StickCで動かしてみる

  • スケッチ例 → M5StickC → Hat → YUN

サンプルスケッチをまず動かしてみて、ディスプレイに温度・湿度・気圧・照度が表示されることを確認します。

5. スケッチをよしなに書き換えてみる

サンプルスケッチは5つのファイルが含まれていますが、メインのスケッチ(YUN.ino)のみを少しだけ書き換えて、

  • WiFi接続
  • 設定下限温度(25度)以下であればヒーターONとしてWebhookにアクセス
  • 設定上限温度(27度)以上であればヒーターOFFとしてWebhookにアクセス

の機能を追加します。

YUN.ino 改変
#include "M5StickC.h"
#include <Adafruit_BMP280.h>
#include "SHT20.h"
#include "yunBoard.h"

SHT20 sht20;
Adafruit_BMP280 bmp;

uint32_t update_time = 0;
float tmp, hum;
float pressure;
uint16_t light;

+ #include <WiFi.h>
+ #include <WiFiClientSecure.h>
+ #include <HTTPClient.h>
+ #define HEATER_ON_THRESHOLD   25.0
+ #define HEATER_OFF_THRESHOLD  27.0
+ WiFiClientSecure client;
+ HTTPClient https;
+ const char* ssid     = "WiFiのSSID";
+ const char* password = "WiFiのパスワード";
+ const String endpoint = "https://てきとうなサーバー.jp/heater.php?sw=";
+ bool heater_state = false;

void setup() {
  M5.begin();
  Wire.begin(0, 26, 100000);
  M5.Lcd.setRotation(1);
  M5.Lcd.setTextSize(2);
  // RGB888
  // led_set(uint8_t 1, 0x080808);
  
  if (!bmp.begin(0x76)) {
    Serial.println(F("Could not find a valid BMP280 sensor, check wiring!"));
  }

    /* Default settings from datasheet. */
  bmp.setSampling(Adafruit_BMP280::MODE_NORMAL,     /* Operating Mode. */
                  Adafruit_BMP280::SAMPLING_X2,     /* Temp. oversampling */
                  Adafruit_BMP280::SAMPLING_X16,    /* Pressure oversampling */
                  Adafruit_BMP280::FILTER_X16,      /* Filtering. */
                  Adafruit_BMP280::STANDBY_MS_1000); /* Standby time. */
  
  // put your setup code here, to run once:
  
+ WiFi.begin(ssid, password);
+ while (WiFi.status() != WL_CONNECTED) {
+   Serial.print(".");
+   delay(1000);
+ }
+ Serial.println("");
}

- uint8_t color_light = 5;
+ uint8_t color_light = 0;

void loop() {
  
  led_set_all((color_light << 16) | (color_light << 8) | color_light);
  if(millis() > update_time) {
    update_time = millis() + 1000;
    tmp = sht20.read_temperature();
    hum = sht20.read_humidity();
    light = light_get();
    pressure = bmp.readPressure();
    M5.Lcd.setCursor(0, 8);
    M5.Lcd.setTextColor(TFT_WHITE, TFT_BLACK);
-   M5.Lcd.printf("tmp:%.2f\r\n", tmp);
+   if (heater_state) M5.Lcd.printf("tmp:%.2f  ON\r\n", tmp);
+   else              M5.Lcd.printf("tmp:%.2f OFF\r\n", tmp);
    M5.Lcd.setTextColor(TFT_GREEN, TFT_BLACK);
    M5.Lcd.printf("hum:%.2f\r\n", hum);
    M5.Lcd.setTextColor(TFT_WHITE, TFT_BLACK);
    M5.Lcd.printf("pre:%.2f\r\n", pressure);
    M5.Lcd.setTextColor(TFT_GREEN, TFT_BLACK);
    M5.Lcd.printf("light:%04d\r\n", light);
  }

  M5.update();

  if (M5.BtnA.wasPressed()) {
    esp_restart();  
  }

  delay(100);
  // put your main code here, to run repeatedly:

+ // ヒーターをつける
+ if (!heater_state && tmp < HEATER_ON_THRESHOLD) {
+   Serial.println("Heater [ON]");
+   heater_state = webhook(endpoint + String("1"));
+   Serial.println(heater_state);
+ }
+ // ヒーターをけす
+ if (heater_state && tmp > HEATER_OFF_THRESHOLD) {
+   Serial.println("Heater [OFF]");
+   heater_state = webhook(endpoint + String("0"));
+   Serial.println(heater_state);
+ }
}

+ // ヒーターを制御したうえでONかOFFかを返す
+ bool webhook(String url) {
+   https.begin(url);
+   int httpCode = https.GET();
+   if (httpCode > 0) Serial.printf("Server responded [%d]\n", httpCode);
+   else Serial.printf("Error: %s\n", https.errorToString(httpCode).c_str());
+   String result = https.getString();
+   https.end();
+   result.trim();
+   return result.equals("1");
+ }

6. 設置して実験

Image from Gyazo

M5StickC + Yun Hat はUSBアダプタから常時電源供給とし、スマートコンセントHS103にペットヒーターをつなぎます。25度を下回るとヒーターがONになり、27度を超えるとOFFになります。
ONOFFはHS103のカド部分の青いLEDで確認できるほか、ディスプレイにも表示させるようにしています。(ちゃんとAPIでの操作結果を反映させています)

一見、設定温度幅がかなり小さいようではありますが、ヒーターの出力が弱いので数時間かけてONOFFを繰り返して適温がキープできているようです。
夜間は上からカバーをかけることがあり、その際は温度上昇速度がかなり速くなりますが、過熱も防いでくれるので便利です。

7. 満足度調査

最近急激に寒くなり、小刻みにプルプル震えていたのが解消されてご機嫌のようです。人間側としては過熱を心配してこまめに電源ONOFFする手間が完全になくなってとても便利でした。

Yun Hatはもう売られてなさそうですが、M5StickCに適合する温度センサ類はほかにもたくさんあります。スマートコンセントの活用事例として応用機会がかなりありそうだなというのが作っての所感でした。

Discussion