概要
スマホを介さずにSwitchBotデバイスが操作できるSwitchBotリモコン。確かに便利そうですが、操作できる対象がSwitchBotとSwitchBotカーテンだけなのは少し残念でした。
そこで、それ以外のSwitchBotデバイスも操作できるようなリモコンを自作してみました。実はAPIが公開されているので外部から操作するのは意外と簡単です。BLE接続する必要があるため、今回はESP32を使用して作ります。
開発環境
- Windows 11 Home 22H2
- Arduino IDE 2.3.0
- SwitchBot 7.41.5.13
回路構成
ESP32-DevKitCを使用し、IO22にLED、IO23にスイッチを接続します。
プログラム
こちらで公開されているAPIを利用します。使い方に関しては以前に投稿した記事が参考になると思うので今回は割愛します。
SwitchBot電球に接続できたらLEDが点灯し、切断されたらLEDが消灯します。また、スイッチが押される毎に所定のコマンドを送信し、SwitchBot電球の状態を切り替えます。
switchbot_bulb.ino
#include <BLEDevice.h>
#define LED_PIN 22
#define BUTTON_PIN 23
#define MAC_ADDRESS "00:00:00:00:00:00" //put the MAC address of the device
#define SERVICE_UUID "cba20d00-224d-11e6-9fb8-0002a5d5c51b" //service UUID
#define CHARACTERISTIC_UUID_RX "cba20002-224d-11e6-9fb8-0002a5d5c51b" //characteristic UUID of the message from the terminal to the device
#define CHARACTERISTIC_UUID_TX "cba20003-224d-11e6-9fb8-0002a5d5c51b" //characteristic UUID of the message from the device to the terminal
bool doScan = false;
bool doConnect = false;
bool connectionState = false;
int buttonState = LOW;
int lastButtonState = LOW;
static BLERemoteCharacteristic* pRemoteCharacteristicRX;
static BLERemoteCharacteristic* pRemoteCharacteristicTX;
static BLEAdvertisedDevice* pDevice;
static void notifyCallback(BLERemoteCharacteristic* pRemoteCharacteristic, uint8_t* pData, size_t length, bool isNotify) {
uint8_t response = pData[0];
if (response == 0x01) { //OK
Serial.println("Executed the command successfully");
} else {
Serial.println("Some error occurred while executing the command");
}
}
class clientCallbacks: public BLEClientCallbacks {
void onConnect(BLEClient* pClient) {
}
void onDisconnect(BLEClient* pClient) {
connectionState = false;
digitalWrite(LED_PIN, LOW);
Serial.println("Disconnected from the device");
}
};
bool connect() {
Serial.print("Connecting to ");
Serial.println(pDevice->getAddress().toString().c_str());
BLEClient* pClient = BLEDevice::createClient();
Serial.println(" - Created the client");
//connect to the server
pClient->setClientCallbacks(new clientCallbacks());
pClient->connect(pDevice);
if (!pClient->isConnected()) {
Serial.println("Failed to connect to the server");
return false;
}
Serial.println(" - Connected to the server");
//obtain the reference to the service in the server
BLERemoteService* pRemoteService = pClient->getService(BLEUUID(SERVICE_UUID));
if (pRemoteService == nullptr) {
Serial.println("Failed to find the service");
pClient->disconnect();
return false;
}
Serial.println(" - Found the service");
//obtain the reference to the characteristic RX in the service of the server
pRemoteCharacteristicRX = pRemoteService->getCharacteristic(BLEUUID(CHARACTERISTIC_UUID_RX));
if (pRemoteCharacteristicRX == nullptr) {
Serial.println("Failed to find the characteristic RX");
pClient->disconnect();
return false;
}
Serial.println(" - Found the characteristic RX");
//obtain the reference to the characteristic TX in the service of the server
pRemoteCharacteristicTX = pRemoteService->getCharacteristic(BLEUUID(CHARACTERISTIC_UUID_TX));
if (pRemoteCharacteristicTX == nullptr) {
Serial.println("Failed to find the characteristic TX");
pClient->disconnect();
return false;
}
Serial.println(" - Found the characteristic TX");
//register the callback function to receive the notifications from the server
if (pRemoteCharacteristicTX->canNotify()) {
pRemoteCharacteristicTX->registerForNotify(notifyCallback);
}
return true;
}
class advertisedDeviceCallbacks: public BLEAdvertisedDeviceCallbacks {
void onResult(BLEAdvertisedDevice advertisedDevice) {
BLEAddress address = advertisedDevice.getAddress();
Serial.println(address.toString().c_str());
if (address.equals(BLEAddress(MAC_ADDRESS))) {
BLEDevice::getScan()->stop();
pDevice = new BLEAdvertisedDevice(advertisedDevice);
Serial.println("Found the target device");
doScan = true;
doConnect = true;
}
}
};
void sendCommand() {
uint8_t command[5];
command[0] = 0x57; //fixed value
command[1] = 0x0F; //header
command[2] = 0x47; //fixed value
command[3] = 0x01; //fixed value
command[4] = 0x03; //toggle
pRemoteCharacteristicRX->writeValue(command, sizeof(command), false); //send the command
}
void setup() {
Serial.begin(115200);
Serial.println("Initializing...");
BLEDevice::init("");
pinMode(BUTTON_PIN, INPUT_PULLUP);
pinMode(LED_PIN, OUTPUT);
digitalWrite(LED_PIN, LOW);
BLEScan* pScan = BLEDevice::getScan();
pScan->setAdvertisedDeviceCallbacks(new advertisedDeviceCallbacks());
pScan->setActiveScan(true); //enable active scanning
pScan->start(5, false); //start the scan to run for 5 seconds
}
void loop() {
if (doConnect == true) {
if (connect()) {
connectionState = true;
digitalWrite(LED_PIN, HIGH);
Serial.println("Connected to the device successfully");
} else {
Serial.println("Some error occurred while connecting to the device");
}
doConnect = false;
}
buttonState = digitalRead(BUTTON_PIN);
if (lastButtonState == HIGH && buttonState == LOW) {
if (connectionState) {
sendCommand(); //toggle the state of the bulb
} else if (doScan) {
BLEDevice::getScan()->start(5, false); //start the scan to run for 5 seconds
}
}
lastButtonState = buttonState;
delay(100);
}
まとめ
このように各種SwitchBotデバイスは外部から簡単に操作できます。スイッチの数などを拡張し、オリジナルのリモコンも作ることができそうです。