マイコンボードESP32でMicroPython環境構築からSwitchbotAPI操作まで

# はじめに
WiFiモジュールを搭載したマイコンモジュールの一つにESP32があります。
マイコン初心者の私が備忘録を兼ねて、Micropython環境の構築からSwitchbotAPIを操作するところまで書きます。
以下の流れです。
1. ESP32 PC接続設定
2. MicroPython環境構築
3. ESP32 WiFi接続
4. SwitchbotAPI操作

# 参考
MicroPython環境構築の参考にしました。
[Zenn: Thonnyで、ESP32 - MicroPython環境を構築するよ](https://zenn.dev/kotaproj/articles/d969fb39100da443f41f)

ネットワーク接続の参考にしました。ESP8266とありますが、ESP32でも使えるようです。
[MicroPython: ESP8266 用クイックリファレンス](https://micropython-docs-ja.readthedocs.io/ja/latest/esp8266/quickref.html#networking)

SwitchbotAPIのドキュメント
[GitHub: OpenWonderLabs/SwitchBotAPI](https://github.com/OpenWonderLabs/SwitchBotAPI)
ヘッダーの作成の参考にしました。
[Qiita: [Switchbot API v1.1が登場] APIでSwitchBotロックを遠隔操作する](https://qiita.com/masaki12-s/items/0295a96fd5a70442f7d5)

SwitchbotAPIの認証キーを生成する際に用いるmicropython-hmacではエラーがでて困りましたが、こちらを使えば解決しました。
[GitHub: gloveboxes/ESP32-Micropython-Azure-IoT-and-Event-Hub-Client](https://github.com/gloveboxes/ESP32-Micropython-Azure-IoT-and-Event-Hub-Client)



# 環境
- ホストPC
    - OS: windows 10 Pro 64bit
    - Python: Python 3.7.4
    - IDE: Thonny 4.01
- マイコンボード
    - ESP32: [Aideepen ESP32 ESP-32S 開発ボード](https://www.amazon.co.jp/dp/B07MH58JS2)

# 1. ESP32 PC接続設定
まずはESP32をUSB(microB-TypeA)ケーブルでPCに接続し、
「デバイスマネージャー」の「ポート(COMとLPT)」欄を開きます。
その中の「Silicon Labs CP210x USB to UART Bridge」末尾についているCOM～の番号を覚えておきます。

黄色の！が表示されている場合はドライバが入っていないので、[Silicon Labs: CP210x USB to UART Bridge VCP Drivers](https://jp.silabs.com/developers/usb-to-uart-bridge-vcp-drivers)からドライバをダウンロードしインストールしてください。

ESP32とPCの接続はこれで完了です。

![1_device_manager.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/455608/5c7ba163-0de2-47ce-27b1-69c2bbb9fad9.png)

# 2. MicroPython環境構築
[参考ページ](https://zenn.dev/kotaproj/articles/d969fb39100da443f41f)に従って設定します。
[MicroPythonのリンク](https://micropython.org/download/esp32/)からファームウェアをダウンロードします。

![image.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/455608/d5fdeca1-16b8-1965-ffca-6b910983da9d.png)

ホストPCにESP32にファームウェアを書き込むためのモジュールをインストールします。

```python
pip install esptool
```

ESP32のROMを消去します。com～にはデバイスマネージャーで確認したポート番号を指定します。

```python
esptool --port com5 erase_flash
```

ファームウェアのダウンロード先に作業ディレクトリを移動し、以下のコマンドを入力してESP32にファームウェアを書き込みます。

```python
esptool --chip esp32 --port com5 write_flash -z 0x1000 esp32-20220618-v1.19.1.bin
```

[Thonny](https://thonny.org/)をインストールし起動します。
右下のインタプリタ設定から「MicroPython(ESP32)・COM～」を選択すれば準備は完了です。

![image.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/455608/e389b1ee-6cbd-8990-911f-a9b6f497dc50.png)

# 3. ESP32 WiFi接続
ESP32を自宅のWiFiに接続します。以下のクイックリファレンスにESP8266とありますが、ESP32でもそのまま動きます。
[MicroPython: ESP8266 用クイックリファレンス](https://micropython-docs-ja.readthedocs.io/ja/latest/esp8266/quickref.html#networking)

```python
def do_connect():
    import network
    wlan = network.WLAN(network.STA_IF)
    wlan.active(True)
    if not wlan.isconnected():
        print('connecting to network...')
        wlan.connect('ssid', 'key') #ここに接続したいWiFiのSSIDとパスワードを書く
        while not wlan.isconnected():
            pass
    print('network config:', wlan.ifconfig()) #IPアドレス、ネットマスク、ゲートウェイ、DNS
```

接続が完了するとIPアドレス、ネットマスク、ゲートウェイ、DNSが表示されます。
簡単にマイコンのWiFi接続ができました。便利です。

![network_connection.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/455608/70267a4a-aa2e-cc8e-fa06-02e0813cbc2f.png)



# 4. SwitchbotAPI操作
公式ドキュメントに従います。
SwitchbotAPIの操作の流れは以下の通りです。
1. 認証のためのヘッダーを作成する
2. Switchbotのデバイス一覧を取得し、操作したいデバイスのIDを確認する
3. デバイスに対して操作を行う

## 4-1. 認証のためのヘッダーを作成する
Switchbotのスマホアプリの「設定」→「アプリバージョン」を項目を連続で10回タップすると「トークンを取得」ボタンが現れます。これを記録しておきます。

公式ドキュメントの「Authentication」項のサンプルプログラムと[参考]((https://qiita.com/masaki12-s/items/0295a96fd5a70442f7d5))をもとにしました。
ホストPCではこのまま動きますが、ESP32で実行するためにはMicroPythonのデフォルトのライブラリにhmac, base64がないためモジュールの追加インストールが必要です。

```python
import time
import hashlib
import hmac
import base64

# open token
token = '' # copy and paste from the SwitchBot app V6.14 or later
# secret key
secret = '' # copy and paste from the SwitchBot app V6.14 or later

def get_auth_header(token, secret):
    nonce = '' #空欄のままで良いらしい
    t = int(round((time.time() + 946684800) * 1000)) #後述 基準の時刻がホストPCとESP32で異なるため30年分の秒数を足す
    string_to_sign = '{}{}{}'.format(token, t, nonce)

    string_to_sign = bytes(string_to_sign, 'utf-8')
    secret = bytes(secret, 'utf-8')

    sign = base64.b64encode(hmac.new(secret, msg=string_to_sign, 
    digestmod=hashlib.sha256).digest())
    print ('Authorization: {}'.format(token))
    print ('t: {}'.format(t))
    print ('sign: {}'.format(str(sign, 'utf-8')))
    print ('nonce: {}'.format(nonce))

    header={}
    header["Authorization"] = token
    header["sign"] = str(sign, 'utf-8')
    header["t"] = str(t)
    header["nonce"] = nonce
    return header
```

ここで色々つまづきました。
MicroPython版のパッケージ管理モジュールupipでhmacをインストールし、上のプログラムのハッシュ化部分を実行すると以下のエラーがでます。

```python:出力結果
Traceback (most recent call last):
  File "<stdin>", line 5, in <module>
  File "/lib/hmac.py", line 149, in new
  File "/lib/hmac.py", line 63, in __init__
AttributeError: 'sha256' object has no attribute 'digest_size'
```

困り果てましたが、[GitHub: gloveboxes/ESP32-Micropython-Azure-IoT-and-Event-Hub-Client](https://github.com/gloveboxes/ESP32-Micropython-Azure-IoT-and-Event-Hub-Client)からhashlib, base64.py, hmac.py, warnings.pyをESP32にインストールしたところ無事ハッシュ化できました。

これで認証ヘッダーの生成は完了です。

![image.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/455608/7b7d099e-0ec6-be05-c24d-ee05138a4f8c.png)

## 4-2. Switchbotのデバイス一覧を取得し、操作したいデバイスのIDを確認する
公式ドキュメントに従って、Switchbotデバイスの一覧を取得します。

```python
host_domain = "https://api.switch-bot.com"
ver = "/v1.1"

def get_device_list(header):
    response = urequests.get(host_domain + ver + "/devices", headers=header)
    return_json = response.json()
    if return_json["message"] == "success":
        print("取得成功")
        return return_json["body"]
    elif return_json["message"] == "Unauthorized":
        print("認証エラー")
        return None
    else:
        print("エラー")
        return None

header = get_auth_header(token, secret)
device_list = get_device_list(header)
print(device_list)
```

ここで、ホストPCでAPIを操作したあとにESP32で同じことをしたら認証エラーがでました。
詳しく調べていませんが、ホストPCとESP32でtime.time()で得るエポック秒数が大きくずれていることが問題のようでした。
それぞれの基準時刻を取得します。

```python
imoort time
time.gmtime(0)
```

ホストPCの基準時刻は1970/01/01 00:00:00です。

```python:ホストPC 出力結果
time.struct_time(tm_year=1970, tm_mon=1, tm_mday=1, tm_hour=0, tm_min=0, tm_sec=0, tm_wday=3, tm_yday=1, tm_isdst=0)
```



一方でESP32の基準時刻は2000/01/01 00:00:00です。

```python:ESP32 出力結果
(2000, 1, 1, 0, 0, 0, 5, 1)
```

両者の差の秒数を求めます。

```python
import datetime
(datetime.datetime(2000,1,1,0,0,0) - datetime.datetime(1970,1,1,0,0,0)).total_seconds()
```

```python:出力結果
946684800.0
```

以上からget_auth_header関数ではこの秒数を足しています。
これで今のところ問題は起こっていませんが、もっと良い対処法がありそうです。

```python:get_auth_header(token, secret)
t = int(round((time.time() + 946684800) * 1000))
```

これでSwitchbotのデバイス一覧が取得できました。
操作したいデバイスのdeviceIdを控えておきます。

## 4-3. デバイスに対して操作を行う
ここではお試しで温湿度計の温度を取得してみます。

```python
def get_th(header):
    th_deviceid = "" #デバイスIDを入力する
    response = urequests.get(host_domain + ver +"/devices/" + th_deviceid + "/status", headers=header)
    temperature = response.json()["body"]["temperature"]
    humidity = response.json()["body"]["humidity"]
    return temperature, humidity

header = get_auth_header(token, secret)
temperature, humidity = get_th(header)
print(f"室温:{temperature}℃  湿度{humidity}%")
```

```python:出力結果
室温:25.1℃  湿度42%
```

無事に温度と湿度が取得できました。
他のデバイスの操作も公式ドキュメントを参考に行えます。

本文で使用したプログラムを以下にアップロードしました。

https://github.com/Miyabi1456/ESP32_SwitchbotAPI

# まとめ
タイトルの通り、マイコンボードESP32についてMicroPython環境構築からSwitchbotAPI操作まで行いました。いくつかの躓きがありましたが、最終的に温湿度計のデータを取得するところまで来ました。
なにかの役に立てば幸いです。