気の向くままに辿るIT/ICT/IoT
webzoit.net
IoT・電子工作

無線電動ロールスクリーンを自作 ESP8266・ESP32/MQTT

ウェブ造ホーム前へ次へ
サイト内検索
カスタム検索
ESP8266って?

無線電動ロールスクリーンを自作 ESP8266・ESP32/MQTT

無線電動ロールスクリーンを自作 ESP8266・ESP32/MQTT

2020/02/12

 Wi-Fi(wifi)モジュールESP8266の内、NodeMCU開発ボードを使ってWiFi越しに動く電動ロールスクリーンを作ってみるページ。

 以前、作ったArduino+ステッピングモータ28BYJ-48製電動ロールカーテンを無線化したもの。

 WebSocket版ロールスクリーンと併行して、今回は、操作にあたり、MQTTを介すことにしました。

前置き

 完成後は不要かもしれませんが、モノがモノ、場所が場所だけにAruduinoOTAを使ってOTA(Over The Air/無線)アップデートできるようにしました。

 そうそう要らないでしょ...と思っていたのですが、今回のケースでは、超絶便利でした。

 これに伴い、3通りほどあるらしき、実装方法の内、Arduino IDEを使う前提のmDNS機能を必要とするものを選びました。

使ったもの

 ほとんどは、Arduino版に書いた通りですが、材料については、個々の環境に合わせて適宜用意していただければ。

前提

 mDNS機能を持つパッケージアプリケーションとしてLinuxならAvahi、Mac/WindowsならBonjourがインストール済みであること(macOSはBonjourはプリインストール済みのはず)。

 Arduino IDEが利用できることは、もちろん、ESP8266やESP32をArduino IDEで使えるようにしておくこと。

 ESP-01やESP-02〜ESP14などのESP8266チップなら、Arduino IDEの[ツール] => [ボード]から[Generic ESP8266 Module]を選択、ESPモジュールにスケッチをアップロードできる状態であること。

 ESP32なら、[espressif/arduino-esp32]の要領でESPモジュールにスケッチをアップロードできる状態であること。

 ちなみにこれらArduino IDEの環境設定で追加する方法の場合、カンマ区切りで複数指定可能。

 操作仲介役のMQTTブローカーと操作・実行用のMQTTクライアントを準備すること。

$ sudo apt install -y mosquitto mosquitto-clients

 今回、MQTTブローカーは、PCにインストール、操作もPCから行うものとしました。

 自身のメインOSは、Debian GNU/Linuxで、ブローカーとクライアントとして有名なmosquittoとmosquitto-clientsをaptでインストールしました。

 が、この手法で常用するとしたら、必要都度起動しているRaspberry Pi 2 Model B/Raspbianサーバがあるので常時起動させるのもありですし、決まった操作なら、別途、物理ボタン回路のあるESP8266/ESP32を併用してESPモジュールからパブリッシュすればよいかなとも思っています。

 というか、どうやら、 martin-ger / esp_mqttと併せ、そこにある通り、ブローカー martin-ger / uMQTTBrokerライブラリを使わせて頂くとESP8266/ESP32をもMQTTブローカーやMQTTクライアントとして使えるとのことなのでOpenHABなどを併用すれば尚のこと、遠隔など外部から通信したい場合もクラウドにどっぷり依存することなく、ローカル側である程度コントロールできるでしょうし、また、Zero含めラズパイを使うよりも低コストでMQTT環境を整えることができそうです。

回路

 回路については、ほぼ、Arduino版のArduinoが、ESPボードに変わっただけです。

 実のところ、今回は、逆流防止のダイオードは入れなかったのですが、少なくともスクリーンの上限下限の位置決めが終わるまでは、シャフトを手動で回すことがあったりし、発電機のような状態になるので各モーターピン用にいるかもなと思った次第です。

 また、操作は、物理スイッチではなく、後述の通り、MQTTを介し、遠隔操作するだけです。

電源

 電源は、十分な電流を供給できるACアダプタなどを降圧して使えば、5V程度(今回は、ブレッドボード用電源)でもESPボードのVIN/GNDとモーター用5V/GNDを共用しても、もちろん、モーター用と分けても十分機能します。

 尚、共有する場合の電源が、モバイルバッテリーだと(機能時、電流を要するため、自動OFFはしないものの、)特に巻き上げ時、トルク不足と勘違いするような状況で稀にしかうまくいきません。

 また、モーターを別電源とし、ESPボードへの電源供給専用としてならモバイルバッテリーでも十分ですが、別電源によってスクリーン動作中であってもESPボードには供給(充電)不要と判断され、自動OFFしてしまいます。

 よって強力な電源で共用するか、ESPボードは、電源タップに挿したAC充電アダプタから供給するかの何れかになるでしょう。

AruduinoOTAサンプルスケッチBasicOTAのアップロード

 いろいろやってみた中で、これをしなくてもできたのですが...。

 一からやってみたら、これをしないとできなかったので基本に忠実にWiFi SSIDとパスフレーズのみ環境に合わせたBasicOTAをNodeMCUボードにアップロード。

独自スケッチのアップロード

 その上でBasicOTAの内容を網羅したオリジナルスケッチをNodeMCUボードにアップロード。

 これでOTAアップデートできるようになります。

スケッチ

 今回のスケッチは、こんな感じ。

#include <ESP8266WiFi.h>
#include <PubSubClient.h>
#include <ArduinoOTA.h>
#include <ESP8266mDNS.h>
#include <Stepper.h>
 
// 無線アクセスポイント用
const char* ssid = "SSID";//put your wifi ssid here
const char* password = "PASSPHRASE";//put your wifi password here
 
// MQTTブローカ機能を持つ端末のIPなど
const char* mqtt_server = "192.168.1.x";
 
// 識別名の設定
const char common_name[40] = "any_name";
const char *OTAName = common_name;  // OTA用
const char* mdnsName = common_name; // mDNS responder用
 
// ステップモータ1回転あたりのステップ数(実状に合わせる)
const int stepsPerRevolution = 200;
 
// モータ速度(値が大きすぎると脱調...)
int motorSpeed = 1200;
// モータピン用設定値
int lookup[9] = {B01000, B01100, B00100, B00110, B00010, B00011, B00001, B01001, B00000};
 
// pins_arduino.h参照
const int Motor1 = D1;
const int Motor2 = D2;
const int Motor3 = D5;
const int Motor4 = D6;
 
Stepper myStepper(stepsPerRevolution, Motor1, Motor2, Motor3, Motor4);
WiFiClient espClient;
PubSubClient client(espClient);
 
// 自作ロールスクリーンにおいて最適だった巻き上げ・巻き下げ回数
int FullOpenCloseNum = 5000;
 
void setup_wifi() {
  delay(100);
  Serial.print("Connecting to ");
  Serial.println(ssid);
  WiFi.mode(WIFI_STA);
  WiFi.begin(ssid, password);
  while (WiFi.status() != WL_CONNECTED)
  {
   delay(500);
   Serial.print(".");
  }
  Serial.println("");
  Serial.println("WiFi connected");
  Serial.println("IP address: ");
  Serial.println(WiFi.localIP());
}
 
// MQTT経由の操作に合わせて実行される関数
void callback(char* topic, byte* payload, unsigned int length)
{
  Serial.print("Command from MQTT broker is : [");
  Serial.print(topic);
  int p =(char)payload[0]-'0';
 
  // モータの向きや動作方向に応じた分岐
  if(p==1)
  {
    Serial.print(" clockwise loop: " );
    for (int stepperLoop = 0; stepperLoop < FullOpenCloseNum; stepperLoop++) {
      counterclockwise();
      delayMicroseconds(motorSpeed);
      Serial.print(stepperLoop);
      Serial.print(" ");
      delay(1);
    }
    Serial.print("]");
    setOutput(9);
  }
  else if(p==2)
  {
    Serial.print(" counterclockwise loop: " );
    for (int stepperLoop = 0; stepperLoop < FullOpenCloseNum; stepperLoop++) {
      clockwise();
      delayMicroseconds(motorSpeed);
      Serial.print(stepperLoop);
      Serial.print(" ");
      delay(1);
    }
    Serial.print("]");
    setOutput(9);
  }
  else if(p==0)
  {
    setOutput(9);  // モーター停止用の設定値
  }
  Serial.println();
}
 
void reconnect() {
  // Loop until we're reconnected
  while (!client.connected())
  {
    Serial.print("Attempting MQTT connection...");
    // Create a random client ID
    String clientId = "ESP8266Client-";
    clientId += String(random(0xffff), HEX);
    // Attempt to connect
    //if you MQTT broker has clientID,username and password
    //please change following line to  if (client.connect(clientId,userName,passWord))
    if (client.connect(clientId.c_str()))
    {
       Serial.println("connected");
       //once connected to MQTT broker, subscribe command if any
       //   client.subscribe("OsoyooCommand");
       client.subscribe("ESPRollScreen");
    } else {
       Serial.print("failed, rc=");
       Serial.print(client.state());
       Serial.println(" try again in 5 seconds");
       // Wait 6 seconds before retrying
       delay(6000);
    }
  }
}
 
void setup() {
/*
Stepper.hにあるのでpinMode設定は省略
*/
  Serial.begin(115200);
 
  setup_wifi();
  startOTA();
  startMDNS();
 
  myStepper.setSpeed(80);
 
  client.setServer(mqtt_server, 1883);
  client.setCallback(callback);
}
 
void loop() {
  ArduinoOTA.handle();
  if (!client.connected()) {
    reconnect();
  }
  client.loop();
}
 
void startOTA() { // Start the OTA service
  ArduinoOTA.setHostname(OTAName);
 
  ArduinoOTA.onStart([]() {
  Serial.println("Start");
  // turn off the LEDs
  for (int i=0; i<6; i++) {
    digitalWrite(LED_BUILTIN, HIGH);
    digitalWrite(LED_BUILTIN, LOW);
  }
  });
  ArduinoOTA.onEnd([]() {
  Serial.println("\r\nEnd");
  });
  ArduinoOTA.onProgress([](unsigned int progress, unsigned int total) {
  Serial.printf("Progress: %u%%\r", (progress / (total / 100)));
  });
  ArduinoOTA.onError([](ota_error_t error) {
  Serial.printf("Error[%u]: ", error);
  if (error == OTA_AUTH_ERROR) Serial.println("Auth Failed");
  else if (error == OTA_BEGIN_ERROR) Serial.println("Begin Failed");
  else if (error == OTA_CONNECT_ERROR) Serial.println("Connect Failed");
  else if (error == OTA_RECEIVE_ERROR) Serial.println("Receive Failed");
  else if (error == OTA_END_ERROR) Serial.println("End Failed");
  });
  ArduinoOTA.begin();
  Serial.println("OTA ready\r\n");
  Serial.print("IP address: ");
  Serial.println(WiFi.localIP());
}
 
void startMDNS() { // Start the mDNS responder
  MDNS.begin(mdnsName);              // start the multicast domain name server
  Serial.print("mDNS responder started: http://");
  Serial.print(mdnsName);
  Serial.println(".local");
}
 
void counterclockwise() //反時計回り
{
  for(int i = 0; i < 8; i++)
  {
    setOutput(i);
    delayMicroseconds(motorSpeed);
  }
}
void clockwise() //時計回り
{
  for(int i = 7; i >= 0; i--)
  {
    setOutput(i);
    delayMicroseconds(motorSpeed);
  }
}
 
void setOutput(int out)
{
  digitalWrite(Motor1, bitRead(lookup[out], 0));
  digitalWrite(Motor2, bitRead(lookup[out], 1));
  digitalWrite(Motor3, bitRead(lookup[out], 2));
  digitalWrite(Motor4, bitRead(lookup[out], 3));
}

 Step_motor_MQTT.inoと以前のArduino版ロールスクリーンで使ったスケッチ、Stepper、ArduinoOTA、ESP8266mDNS各ライブラリの合作です。

 便利なもので後述のようにMQTTクライアントからメッセージが送信されるとESP8266/ESP32にアップロードしたスケッチのcallback関数がコールされ、payload引数にその値が入ってきます。

操作方法

$ mosquitto_pub -d
$ mosquitto_pub -t ANY_TOPIC_NAME -m 1
...
$ mosquitto_pub -t ANY_TOPIC_NAME -m 2
...
$

 ブローカーmosquittoとクライアントmosquitto-clientsを入れたDebian PCにおいては、端末(≒ターミナル)を開き、このようにメッセージを送信(パブリッシュ)します。

 ここでは、スケッチ内容に合わせてメッセージを1としていますが、文字列も送信できます。

 [-d]オプションでデーモンを起動、[-t]はトピック名、-mは、メッセージ(受け取る側・ブローカーから見るとペイロード)です。

 ちなみに毎回[-d]オプションを付けてもエラーにはなりません。

 今回の自作ロールスクリーンにおいては、1でスクリーン上昇、2で下降し、予め検証し、(脱調しない限りは、)良き位置で止まる値FullOpenCloseNumを設定してあるのでそれぞれリミットスイッチ不要で自動的に止まります。

備考

 MQTTは、ブローカーをサーバとして介し、トピックを目印にやり取りするパブリッシュ・サブスクライブ(Publish・Subscribe/出版・購読/パブ・サブ)方式で非同期・同期通信を行うことができるIBM起源のプロトコルです。

 非同期通信できるHTML5実装のWebSocketやMQTTなら任意の時点でモータの停止もできる...と思ったら、割り込み方法を思いつかず、全開・全閉のみで停止については実装できていません。

 ただ、非同期なのでスクリーン稼働中に他のメッセージを送信(操作)することはでき、HTTP TCP/IPのようにフリーズすることはありません(が、今のところ、途中で逆転させたり、停止させたりはできません)。

ウェブ造ホーム前へ次へ