🥊

サーボモータにRESTful APIを生やした

2022/05/19に公開

はじめに

PWM制御方式のサーボモータにRESTful APIでアクセスできるシステムを作りました。
(←いやSwitchBotぉ〜)

https://twitter.com/takeyamaaaaa/status/1526815174849871872?s=20&t=Beazq3SwxKKHDGSM3wvTuA

https://github.com/takeyamayuki/RESTful-servo-motor

サーボにRESTが生えて何が嬉しいの?

SwitchBotと存在意義は同じです。(完)

SwitchBotがわからない人の為に言うと、
スマホからいろいろな場所のスイッチを押せるっていう商品です。

そして、SwitchBotはRESTful APIで各種機能が扱えて非常に便利!
ゆえにプログラマにも人気です(?)

curlでも扱えるし、ブラウザから見れるし、homebridgeサーバーに適用もできる。

技術解説

RESTful APIを生やしたとか大層なことを言っていますが、ArduinoのライブラリにWebServerというのがあるので、それをつかってサーボを動かしてるだけです。

プログラム全文を以下に示します。GitHubからも見れます。

main.cpp
#include <Arduino.h>
#include <WiFi.h>
#include <WebServer.h>
#include <ESP32Servo.h>
#include <ESPmDNS.h>
#include <ssid_define.h>

Servo servo1; // create four servo objects
const int servo1Pin = 15;
// Published values for SG90 servos; adjust if needed
int minUs = 0;
int maxUs = 5000;
bool to0_flag = false;
int angle = 60;
int angle0 = 5;

const char *ssid = MY_SSID;     // 自分のSSIDに書き換える
const char *password = MY_SSID_PASS; // 自分のパスワードに書き換える
WebServer server(80);
String target = "0"; // この変数をPOSTメソッドで書き換える

// k=0:0°,30°のスイッチ, k!=0:自由角度への移動
void sg90_switch(int k)
{
  // k=0:switchbot mode, k!=0:任意角度への移動
  if (k == 0)
  {
    // to0_flag
    int status = servo1.read();
    if (status > 15) to0_flag = true; //現在30なので0に持っていく
    else if (status < 15) to0_flag = false; //現在0なので30に持っていく
    // servo.write
    if (to0_flag) servo1.write(angle0);
    else servo1.write(angle);
  }
  else
  {
    servo1.write(k);
  }
}
void setup()
{
  Serial.begin(9600);
  servo1.setPeriodHertz(50); // Standard 50hz servo
  servo1.attach(servo1Pin, minUs, maxUs);
  int status = servo1.read();
  if (status > 15) to0_flag = true; //現在30なので0に持っていく
  else if (status < 15) to0_flag = false; //現在0なので30に持っていく
  // シリアルコンソールのセットアップ
  Serial.begin(115200);
  delay(10);
  Serial.println();

  // WiFiに接続
  Serial.print("Connecting to ");
  Serial.println(ssid);
  WiFi.begin(ssid, password);

  while (WiFi.status() != WL_CONNECTED)
  {
    delay(500);
    Serial.print(".");
  }
  Serial.println();
  Serial.println("WiFi connected.");
  Serial.print("IP address: ");
  Serial.println(WiFi.localIP());
  MDNS.begin("esp32"); // ホスト名 esp32.local

  // root
  server.on("/", HTTP_ANY, [](){
    // PUT
    if (server.method() == HTTP_PUT) { 
      target = server.arg("plain"); // server.arg("plain")でリクエストボディが取れる。targetに格納
      int i=target.toInt();
      sg90_switch(i);
      server.send(200, "text/plain", target);   //リクエストされた角度を返す
    }
    // GET
    else if(server.method() == HTTP_GET){
      int status = servo1.read();
      server.send(200, "text/plain", String(status)); // statusをクライアントに返す 
    } 
  });

  // onNotFound
  server.onNotFound([](){
    server.send(404, "text/plain", "Not Found."); // 404を返す 
  });

  server.begin();
}

void loop()
{
  server.handleClient();
}

このシステムの技術的見どころは3つです。

1. 実際にPUTとGETを処理している部分

main.cpp
// root
server.on("/", HTTP_ANY, [](){
// PUT
if (server.method() == HTTP_PUT) { 
    target = server.arg("plain"); // server.arg("plain")でリクエストボディが取れる。targetに格納
    int i=target.toInt();
    sg90_switch(i);
    server.send(200, "text/plain", target);   //リクエストされた角度を返す
}
// GET
else if(server.method() == HTTP_GET){
    int status = servo1.read();
    server.send(200, "text/plain", String(status)); // statusをクライアントに返す 
} 
});

PUTでリクエストボディに角度を入れるとその角度にサーボが回ります。

ただ、0°のコマンドを何回も送ると、0°と60°を行き来します。これは、壁付けスイッチの角度をあとから調整するのが面倒なので、0°コマンドでスイッチのオンオフができるようにしたかったためです。

2. ssid, passwordをheaderファイルから読み取る

GitHubのREADMEにも書いていますが、↓の

  1. Define ssid, password of your wifi router by creating your own header file.
    $ vi src/ssid_define.h
    
    Add the following statement to ssid_define.h
    ssid_define.h
    #ifndef _SSID_DEFINE_
    #define _SSID_DEFINE_
    
    #define MY_SSID "your ssid here"
    #define MY_SSID_PASS "your ssid password here"
    
    #endif
    

ssid, passwordをヘッダーファイルにして、defineしています。
ふつうのアプリケーションならパスワードなどをソースコードと分けるってよくやる(?)と思うんですが、Arduinoだとあまり見なかったので、やってみました。

3. homebridgeサーバーと相性抜群

自分のhomebridgeサーバーは、ラズベリーパイに赤外線リモコンを付けて学習リモコンオンリーでしたが、今回、サーボをRESTで操作できるようになったので、照明スイッチも押せるようになりました。
家の照明は、最初からリモコンがなく壁付けスイッチだけだったので、スマホから操作できるようになって満足です。
以下のようなaccessoryをconfigに追加しました。

"accessories": [
    {
        "accessory": "CMD",
        "name": "light",
        "on_cmd": "curl esp32.local -X PUT -H 'Content-Type: text/plain' -d '0'",
        "off_cmd": "curl esp32.local -X PUT -H 'Content-Type: text/plain' -d '0'"
    }
]

https://twitter.com/takeyamaaaaa/status/1526882666653421568?s=20&t=ER3BLAMD6Pzy0SGZ4De3gg

最後に

冒頭でも上げましたが、↓がGitHubリポジトリです。
なにか付け足したい機能とかあったらPull Request大歓迎です。

https://github.com/takeyamayuki/RESTful-servo-motor

なにか間違いなどありましたら、教えていただけると幸いです。

Discussion