LoginSignup
2
0

More than 1 year has passed since last update.

C++言語でSwitchBot API v1.1 リクエスト

Last updated at Posted at 2023-03-31

概要

M5PaperからSwitchBotを操作するために、2022年9月に新しくなったSwitchBotAPI v1.1に挑戦しました。
公式のリクエストを作成するサンプルには、C++が存在しなかったため苦労しました。

API呼び出しまでの流れ

  • ココのとおりアプリで開発者設定を行う
  • アプリから トークンシークレットコードを取得する
  • curlなどをつかって操作したいデバイスのdeviceIDを取得する
curl --request GET 'https://api.switch-bot.com/v1.0/devices' -H 'Authorization: <じぶんのとーくん>' -H 'Content-Type: application/json; charset=utf8'
  • 開発環境でAPIをリクエストする処理を実装する

苦労したところ

v1.1になったことで認証が厳しくなり、リクエストのヘッダーを作るのに苦労しました。

  • UNIXエポック時刻(1970年からのミリ秒)
.cpp
    time_t tmTmp;
    time(&tmTmp);
    unsigned long long tmTmpL = (unsigned long long)tmTmp * 1000;
    String tmStr = String(tmTmpL);
  • UUID生成
.cpp
    uint8_t uuid[16];
    ESPRandom::uuid4(uuid);
    String uuidStr = ESPRandom::uuidToString(uuid);
  • HMAC(SHA-256)生成
.cpp
    char hmac_result[45];
    String data = String(PRM_SWBOT_AUTH_CODE) + tmStr + uuidStr;
    generateHMAC(PRM_SWBOT_SECRET_KEY, data.c_str(), hmac_result);
.cpp
void generateHMAC(const char* key, const char* message, char* output) {
    mbedtls_md_context_t ctx;
    mbedtls_md_init(&ctx);
    const mbedtls_md_info_t* md_info = mbedtls_md_info_from_type(MBEDTLS_MD_SHA256);
    mbedtls_md_setup(&ctx, md_info, 1);
    mbedtls_md_hmac_starts(&ctx, (const unsigned char*)key, strlen(key));
    mbedtls_md_hmac_update(&ctx, (const unsigned char*)message, strlen(message));
    unsigned char hash[32];
    mbedtls_md_hmac_finish(&ctx, hash);
    mbedtls_md_free(&ctx);
    
    size_t outlen;
    mbedtls_base64_encode(NULL, 0, &outlen, hash, sizeof(hash));
    mbedtls_base64_encode((unsigned char*)output, outlen+1, &outlen, hash, sizeof(hash));
    output[outlen] = '\0';
}

クラス化

使いやすいようにクラスにしました。
また、コールバック関数から呼び出したいのでシングルトンになっています。

Control_Switchbot.cpp
#include "control_switchbot.h"
#include "params_switchbot.h"

#include <WiFi.h>
#include <HTTPClient.h>
#include <mbedtls/md.h>
#include <mbedtls/base64.h>
#include <Arduino.h>
#include <ESPRandom.h>
#include <esp_log.h>
 #include <RTClib.h>

Control_Switchbot* Control_Switchbot::instance = nullptr;

Control_Switchbot* Control_Switchbot::getInstance()
{
    if (instance == nullptr)
    {
        instance = new Control_Switchbot();
    }

    return instance;
}

Control_Switchbot::Control_Switchbot()
{
    // 時刻合わせ
    configTime(3600*9, 0, "pool.ntp.org");
}

Control_Switchbot::~Control_Switchbot()
{
    // none
}

int Control_Switchbot::pushSwitchBot(String dvcID, String *respons)
{
    return postRequest(dvcID, "/commands", "{\"commandType\":\"command\",\"command\":\"press\"}", respons);
}

void Control_Switchbot::generateHMAC(const char* key, const char* message, char* output) {
    mbedtls_md_context_t ctx;
    mbedtls_md_init(&ctx);
    const mbedtls_md_info_t* md_info = mbedtls_md_info_from_type(MBEDTLS_MD_SHA256);
    mbedtls_md_setup(&ctx, md_info, 1);
    mbedtls_md_hmac_starts(&ctx, (const unsigned char*)key, strlen(key));
    mbedtls_md_hmac_update(&ctx, (const unsigned char*)message, strlen(message));
    unsigned char hash[32];
    mbedtls_md_hmac_finish(&ctx, hash);
    mbedtls_md_free(&ctx);
    
    size_t outlen;
    mbedtls_base64_encode(NULL, 0, &outlen, hash, sizeof(hash));
    mbedtls_base64_encode((unsigned char*)output, outlen+1, &outlen, hash, sizeof(hash));
    output[outlen] = '\0';
}

int Control_Switchbot::requestDeviceList(String *respons)
{
    return postRequest("", "", "", respons);
}

int Control_Switchbot::requestStatus(String dvcID, String *respons)
{
    return postRequest(dvcID, "/status", "", respons);
}

int Control_Switchbot::postRequest(String dvcID, String cmd, String body, String *respons)
{
    *respons = "";

    if (!WiFi.isConnected())
    {
        ESP_LOGI("main", "wifi connect fail");
        return 0;
    }
       
    struct tm timeinfo;
    if (!getLocalTime(&timeinfo)) {
        ESP_LOGI("main", "get time fail");
        return 0;
    }
    time_t tmTmp;
    time(&tmTmp);
    unsigned long long tmTmpL = (unsigned long long)tmTmp * 1000;
    String tmStr = String(tmTmpL);
    ESP_LOGI("main", "tmStr:%s\n", tmStr.c_str());
    uint8_t uuid[16];
    ESPRandom::uuid4(uuid);
    String uuidStr = ESPRandom::uuidToString(uuid);
    char hmac_result[45];
    String data = String(PRM_SWBOT_AUTH_CODE) + tmStr + uuidStr;
    generateHMAC(PRM_SWBOT_SECRET_KEY, data.c_str(), hmac_result);
    ESP_LOGI("main", "uuid:%s\n", uuidStr.c_str());

    HTTPClient http;

    String add = "https://api.switch-bot.com/v1.1/devices/" + dvcID + cmd;
    http.begin(add);
    http.addHeader("Content-Type", "application/json");
    http.addHeader("Authorization", String(PRM_SWBOT_AUTH_CODE));
    http.addHeader("sign", hmac_result);
    http.addHeader("nonce", uuidStr);
    http.addHeader("t", tmStr);
    http.POST(body);
    *respons = http.getString();
    ESP_LOGI("main", "responsels:%s\n", respons->c_str());

    return 1;
}


control_switchbot.h
#ifndef _CONTROL_SWITCHBOT_H_
#define _CONTROL_SWITCHBOT_H_

#include <M5EPD.h>
#include "params_switchbot.h"

class Control_Switchbot {
    private:
        static Control_Switchbot* instance;
        Control_Switchbot();

    public:
        static Control_Switchbot* getInstance();
        ~Control_Switchbot();
        int pushSwitchBot(String dvcID, String *respons);
    
    private:
        void generateHMAC(const char* key, const char* message, char* output);
        int requestDeviceList(String *respons);
        int requestStatus(String dvcID, String *respons);
        int postRequest(String dvcID, String cmd, String body, String *respons);
};

#endif //_CONTROL_SWITCHBOT_H_

穴埋めの部分は、取得したトークンやID等を使用してください。

params_switchbot.h
#ifndef _PARAMS_SWITCHBOT_H_
#define _PARAMS_SWITCHBOT_H_

#define PRM_SWBOT_DEVICE_ID "hoge"
#define PRM_SWBOT_AUTH_CODE "xxxxxxxxxx"
#define PRM_SWBOT_SECRET_KEY "yyyyyyyyy"

#endif //_PARAMS_SWITCHBOT_H_

呼び出しの例

*.cpp
    String res;
    Control_Switchbot::getInstance()->pushSwitchBot(PRM_SWBOT_DEVICE_ID, &res);

さいごに

今回はボタンタイプのロボットを使いましたが、Bodyの部分を変更することで他の種類のボットにも対応できるはずです。

HMACの暗号化とか全然理解していなかったので苦労しました。ダメな時に帰ってくる”400 : Bad Request”は何が悪いか教えてほしい。
ChatGPTさんがとても頼りになりました!

M5Paperの開発は別記事にしました。

バグがあったら教えてください。

2
0
0

Register as a new user and use Qiita more conveniently

  1. You get articles that match your needs
  2. You can efficiently read back useful information
  3. You can use dark theme
What you can do with signing up
2
0