ESP32とサーボ一個を使った無線で制御可能な入店・退店ゲートの試作

クラスメソッドが運営する「Developers.IO CAFE」に設置してあるレジレスの物品販売エリア(「ウォークスルー」と呼んでいます)にゲートを設置することで、お客様の購入体験を向上させられるか確認するために、ESP32とサーボ一個を使ったごく簡易的な入店・退店ゲートを試作してみた過程をご紹介します。
2019.05.07

この記事は公開されてから1年以上経過しています。情報が古い可能性がありますので、ご注意ください。

English version of this post is here.

はじめに

クラスメソッドが運営する「Developers.IO CAFE」に設置してあるレジレスの物品販売エリア(「ウォークスルー」と呼んでいます)には、入店・退店ゲートのようなものは現在設置されていません。ゲートを設置することで、お客様の購入体験を向上させられるか確認するために、ESP32とサーボ一個を使ったごく簡易的な入店・退店ゲートを試作してみました。本記事ではその試作の過程をご紹介します。

構想

  • サーボモーターに、発泡スチロール等でできた棒を取り付けて90度の開閉を行わせる
    • 水平状態がゲートオープン、垂直状態がゲートクローズ
  • ESP32のBLE(GATT Write)でゲートの開閉を制御
  • サーボモーター
  • ゲートの開閉状態を示すバー
  • カフェの物販システムと連携はしない
    • 今回はまず動作確認できるモノを最速で作りたいので、カフェの物販システムと連携はしない
      • BLE経由でマニュアル操作で開閉させる

試作

トルク計算

ゲートの開閉に使う棒には、ハンズで買ったスチレン製の棒を半分に切って使用します。この棒は1000mmで重量が約40gなので、半分(500mm)に切ると約20gになります。棒を水平にした時、500mmの棒の端に20gの荷重がかかると仮定すると、必要なトルクは0.02kgf x 0.5m = 0.01kgf・mとなります。今回使用するサーボのトルクはスペックによると4.6kgf・mあるので、十分余力がありそうです。

PWMによるサーボの制御

ESP32にはledc~~という接頭辞の付いたLEDのPWM制御用の関数が用意されています。PWM制御という観点ではLEDもサーボも違いはありませんので、この関数でサーボのPWM制御を行います。弊社さかじのブログ記事で紹介されているシリアルポート経由の簡易的なCUIから値を送信して、動的にサーボのPWM制御ができることが確認できました。

BLEによる無線制御

ESP32のライブラリ関数を利用します。

コードサンプル

#include <BLEDevice.h>
#include <BLEServer.h>
#include <BLEUtils.h>
#include <BLE2902.h>

#define MIN 1
#define MAX 30
int n;
int gate_open;

BLEServer* pServer = NULL;
BLECharacteristic* pCharacteristic = NULL;
bool deviceConnected = false;
bool oldDeviceConnected = false;
#define SERVICE_UUID        "YOUR OWN SERVICE UUID"
#define CHARACTERISTIC_UUID "YOUR OWN CHARACTERISTIC UUID"
String bleSensorName = "YOUR OWN BLE DEVICE NAME";

class MyCallbacks: public BLECharacteristicCallbacks {
    void onWrite(BLECharacteristic *pCharacteristic) {
      Serial.println("onWrite");
      std::string value = pCharacteristic->getValue();
      if (value.length() > 0) {
        Serial.println("*********");
        Serial.print("New value: ");
        for (int i = 0; i < value.length(); i++)
          Serial.print(value[i]);
        Serial.println();
        Serial.println("*********");
      }
      if (gate_open == 0) {
        n = 15;  // 90°
        gate_open = 1;  // GATE CLOSED
      } else {
        n = 1;  // 0°
        gate_open = 0;  // GATE OPENED
      }
    }
};

void setup() {
  Serial.begin(115200);
  Serial.print("> ");

  ledcSetup(0, 50, 8);  // 0ch 50 Hz 8bit resolution
  ledcAttachPin(15, 0); // 15pin, 0ch
  n = MIN;
  gate_open = 0;

  BLEDevice::init((char *)bleSensorName.c_str());
  pServer = BLEDevice::createServer();
  pServer->setCallbacks(new MyServerCallbacks());
  BLEService *pService = pServer->createService(SERVICE_UUID);
  pCharacteristic = pService->createCharacteristic(
                      CHARACTERISTIC_UUID,
                      BLECharacteristic::PROPERTY_WRITE
                    );
  pCharacteristic->setCallbacks(new MyCallbacks());
  // https://www.bluetooth.com/specifications/gatt/viewer?attributeXmlFile=org.bluetooth.descriptor.gatt.client_characteristic_configuration.xml
  pCharacteristic->addDescriptor(new BLE2902());
  pService->start();
  pServer->getAdvertising()->start(); // start advertising
  Serial.println("Setup completed. starting advertising...");
}

void loop() {
  if (Serial.available() > 0) {
    String s;
    s = Serial.readStringUntil('\n');
    Serial.println(s);
    int new_n = menu(s);
    if (new_n != -1) n = new_n;
  }

  if (n > MAX) n = MAX;
  if (n < MIN) n = MIN;
  ledcWrite(0, n);

  // disconnecting
  if (!deviceConnected && oldDeviceConnected) {
      delay(500); // give the bluetooth stack the chance to get things ready
      pServer->startAdvertising(); // restart advertising
      Serial.println("start advertising");
      oldDeviceConnected = deviceConnected;
  }
  // connecting
  if (deviceConnected && !oldDeviceConnected) {
      oldDeviceConnected = deviceConnected;
  }
}

int menu(String s) {
  if (s.equals("?")) {
    Serial.println("entrance-exit-gate-controller");
    Serial.print("> ");
    return -1;
  } else if (s.equals("ver")) {
    Serial.println("0.0.1");
    Serial.print("> ");
    return -1;
  } else {
    Serial.println(s);
    Serial.print("> ");
    return s.toInt();
  }
}

組み立て

今回の試作では、最低限の動作確認ができればOKなので、以下のような感じで超雑に組み立てました。サーボの電源(5V)はESP32-DevKitCの5V出力ピンから供給しました。

  • サーボとESP32-DevKitCを養生テープでぐるぐる巻きにしてベルトパーテーションの支柱に固定
  • スチレン製の棒の端の方にサーボの回転パーツを養生テープでぐるぐる巻きに固定

動作確認

以下が動作確認時の動画です。物販システムとは連動していないため、ゲートを開閉する際に手元のBLEクライアントアプリ経由で入店ゲートのESP32のCharacteristicにWriteしています。

おわりに

今回の試作でとりあえず動くモノができましたが、入店・退店ゲートとして本当に最低限の動作ができるだけの代物でしかありません。目先の課題として以下のようなものがありますので、これらをクリアしつつ、ゲートを設置することで、お客様の購入体験を向上させられるかの確認の実施までこぎつけたいと思います。

  • カフェの物販システムと連携
  • サーボと台座をきちんと固定する
  • ゲートオープン・クローズのバーの見た目がしょぼいので、もうちょっと見栄えのよいモノにする

参考