Nature Remo を Python から操作する

## はじめに

Nature Remo のデバイスは、[API](https://developer.nature.global/)を通じて操作することが可能です。最近 Nature Remo の第3世代を入手したため、これをプログラムやコマンドラインから操作したいと思い、Python 向けにライブラリを書きました。

[nature-remo](https://github.com/morinokami/nature-remo)

ここではこのライブラリの使い方について解説していきます。Remo で遊ぶ上での選択肢の一つとしていただければ幸いです。

## インストール

サポートしている Python のバージョンは 3.8 です。インストールは `pip` からおこないます:

```sh
$ pip install -U nature-remo
```

なお、動作確認は Ubuntu と macOS にておこないました。

## 使い方

API を使用する前提として、

https://home.nature.global/

にてアクセストークンを発行しておいてください。

アクセストークンを入手したら、それを用いてクライアントを初期化します:

```py
>>> from remo import NatureRemoAPI
>>> api = NatureRemoAPI('access_token')
```

生成したクライアントには、各 API に対応するメソッドが備わっています。たとえば、自分のアカウントに関連付けられた Nature Remo のデバイスのリストを取得したければ、`api.get_devices` メソッドを実行します:

```py
>>> devices = api.get_devices()
>>> devices
[Device(id='device-id', name='Remo', temprature_offset=0, humidity_offset=0, created_at=datetime.datetime(2020, 7, 23, 3, 10, 21, tzinfo=datetime.timezone.utc), updated_at=datetime.datetime(2020, 8, 8, 11, 4, 17, tzinfo=datetime.timezone.utc), firmware_version='Remo/1.0.23', mac_address='ab:cd:ef:12:34:56', serial_number='1W000000000000', newest_events={'hu': {'val': 54.0, 'created_at': datetime.datetime(2020, 8, 9, 6, 24, 19, tzinfo=datetime.timezone.utc)}, 'il': {'val': 0.0, 'created_at': datetime.datetime(2020, 8, 9, 6, 31, 46, tzinfo=datetime.timezone.utc)}, 'mo': {'val': 1.0, 'created_at': datetime.datetime(2020, 8, 8, 11, 4, 17, tzinfo=datetime.timezone.utc)}, 'te': {'val': 27.636597, 'created_at': datetime.datetime(2020, 8, 9, 6, 29, 19, tzinfo=datetime.timezone.utc)}})]
```

`devices` は `Device` モデルのリストです。`Device` モデルのインスタンスは、`id` や `name` など、対応する Remo デバイスの情報をアトリビュートとして保持しています。たとえばデバイスのセンサーが取得した湿度や温度は、次のようにしてアクセスすることができます:

```py
>>> d.newest_events
{'hu': SensorValue(val=54.0, created_at=datetime.datetime(2020, 8, 9, 7, 57, 28, tzinfo=datetime.timezone.utc)), 'il': SensorValue(val=21.0, created_at=datetime.datetime(2020, 8, 9, 7, 55, 21, tzinfo=datetime.timezone.utc)), 'mo': SensorValue(val=1.0, created_at=datetime.datetime(2020, 8, 8, 11, 4, 17, tzinfo=datetime.timezone.utc)), 'te': SensorValue(val=27.485535, created_at=datetime.datetime(2020, 8, 9, 7, 49, 28, tzinfo=datetime.timezone.utc))}
>>> d.newest_events['hu'].val # 湿度
54.0
>>> d.newest_events['te'].val # 温度
27.485535
```

他にも、`get_user` や `get_appliances` などのメソッドにより、ユーザーや家電の情報を取得することが可能です。

もちろん、情報を取得するだけでなく、家電の操作をおこなうことも可能です。たとえば、エアコンの設定を変更したい場合は、エアコンの ID を取得した上で、`update_aircon_settings` メソッドを実行します:

```py
>>> appliances = api.get_appliances()
>>> appliances[0].type # 家電のタイプを確認
'AC'
>>> # 26度の冷房に設定
>>> api.update_aircon_settings(appliances[0].id, operation_mode='cool', temperature=26)
```

同様の流れで、テレビの操作なども可能です:

```py
>>> appliances[1].type # 家電のタイプを確認
'TV'
>>> appliances[1].tv.buttons # 登録されているボタンを確認
[Button(name='power', image='ico_io', label='TV_power'), ...]
>>> api.send_tv_infrared_signal(appliances[1].id, 'power') # 電源ボタンを押す
```

API を使用すると、クライアントのアトリビュートに Rate Limit (呼び出し制限) の状況がセットされます:

```py
>>> api.get_user()
User(id='user-id', nickname='Foo Bar')
>>> api.rate_limit.limit, api.rate_limit.remaining # リミットと残りの呼び出し可能回数
(30, 28)
```

この他にも、`api.rate_limit.reset` から制限がリセットされるタイミングを知ることなどが可能です。

また、クライアントの作成時に `debug` を `True` とすることで、実際におこなわれている通信の様子を確認することもできます:

```py
>>> api = NatureRemoAPI('access_token', debug=True)
>>> api.get_user()
DEBUG:urllib3.connectionpool:Starting new HTTPS connection (1): api.nature.global:443
send: b'GET /1/users/me HTTP/1.1\r\nHost: api.nature.global\r\nUser-Agent: nature-remo/0.3.1 (https://github.com/morinokami/nature-remo)\r\nAccept-Encoding: gzip, deflate\r\nAccept: application/json\r\nConnection: keep-alive\r\nAuthorization: Bearer access_token\r\n\r\n'
reply: 'HTTP/1.1 200 OK\r\n'
header: Date: Mon, 27 Jul 2020 15:53:12 GMT
header: Content-Type: application/json; charset=utf-8
header: Content-Length: 72
header: Connection: keep-alive
header: Access-Control-Allow-Origin: *
header: Cache-Control: no-cache, no-store, must-revalidate, private, max-age=0
header: Expires: Thu, 01 Jan 1970 00:00:00 UTC
header: Pragma: no-cache
header: Strict-Transport-Security: max-age=86400
header: Vary: Accept-Encoding
header: X-Accel-Expires: 0
header: X-Content-Type-Options: nosniff
header: X-Frame-Options: SAMEORIGIN
header: X-Rate-Limit-Limit: 30
header: X-Rate-Limit-Remaining: 29
header: X-Rate-Limit-Reset: 1595865300
header: X-Xss-Protection: 1; mode=block
DEBUG:urllib3.connectionpool:https://api.nature.global:443 "GET /1/users/me HTTP/1.1" 200 72
User(id='user_id', nickname='your_nickname')
```

以上のように、クライアントのインスタンスを生成し対応するメソッドを呼び出すことで、簡単に API を利用することができるようになっています。なお、API とメソッドの対応は次のようになっています (下記はすべて実装されていますが、`POST /1/detectappliance`など、僕も動作確認できていないものがあることは述べておきます):

HTTP Method | Endpoint | API
 --- | --- | ---
GET | `/1/users/me` | `get_user`
POST | `/1/users/me` | `update_user`
GET | `/1/devices` | `get_devices`
POST | `/1/detectappliance` | `detect_appliance`
GET | `/1/appliances` | `get_appliances`
POST | `/1/appliances` | `create_appliance`
POST | `/1/appliance_orders` | `update_appliance_orders`
POST | `/1/appliances/{appliance}/delete` | `delete_appliance`
POST | `/1/appliances/{appliance}` | `update_appliance`
POST | `/1/appliances/{appliance}/aircon_settings` | `update_aircon_settings`
POST | `/1/appliances/{appliance}/tv` | `send_tv_infrared_signal`
POST | `/1/appliances/{appliance}/light` | `send_light_infrared_signal`
GET | `/1/appliances/{appliance}/signals` | `get_signals`
POST | `/1/appliances/{appliance}/signals` | `create_signal`
POST | `/1/appliances/{appliance}/signal_orders` | `update_signal_orders`
POST | `/1/signals/{signal}` | `update_signal`
POST | `/1/signals/{signal}/delete` | `delete_signal`
POST | `/1/signals/{signal}/send` | `send_signal`
POST | `/1/devices/{device}` | `update_device`
POST | `/1/devices/{device}/delete` | `delete_device`
POST | `/1/devices/{device}/temperature_offset` | `update_temperature_offset`
POST | `/1/devices/{device}/humidity_offset` | `update_humidity_offset`

各メソッドに与える引数などは、Python の `help` 関数などにより確認できます。

また、Local API 用のクライアントを生成するためには、Remo デバイスのローカルIPアドレスを調べた上で、それを引数として `NatureRemoLocalAPI` クラスに与えます:

```py
>>> from remo import NatureRemoLocalAPI
>>> local_api = NatureRemoLocalAPI('ip_addr')
```

対応している API は以下のようになります:

HTTP Method | Endpoint | API
--- | --- | ---
GET | `/messages` | `get_ir_signal`
POST | `/messages` | `send_ir_signal`

## CLI

`nature-remo` を `pip` によりインストールすると、各 API をコマンドから実行することも可能となります。背後では、上で紹介した Python クライアントが動いています。

各コマンドは、次のような形式で実行します:

```
$ remo [操作対象] [操作内容] [引数やオプション]
```

操作対象には、`appliance`、`device`、`local`、`signal`、`user` のいずれかを指定します。各捜査対象に対応する操作内容は、`--help` を指定することで確認することができます:

```sh
$ remo device --help
Usage: remo device [OPTIONS] COMMAND [ARGS]...

Options:
  --help  Show this message and exit.

Commands:
  delete                     Delete Remo.
  get                        Fetch the list of Remo devices the user has...
  update                     Update Remo.
  update_humidity_offset     Update humidity offset.
  update_temperature_offset  Update temperature offset.
```

上の表示内容から、たとえばデバイスのリストを得るためには、`get` を指定すればよいことがわかります。

操作対象が `appliance`、`device`、`signal`、`user` のいずれかである場合にはアクセストークンを指定する必要がありますが、CLI では環境変数とオプションのいずれかでアクセストークンを指定します。環境変数で指定する場合は、`$ export REMO_ACCESS_TOKEN=<access_token>` のように、`REMO_ACCESS_TOKEN` に値をセットしておきます。また、オプションで指定する場合は次のようにします:

```sh
$ remo device get --token <access_token>
[{"created_at": "2020-07-23T03:10:21+00:00", ...}]
```

また、更新や削除など、操作内容によっては引数を与える必要がありますが、それについても `--help` により確認することができます:

```sh
remo device update --help
Usage: remo device update [OPTIONS] ID NAME

  Update Remo.

  ID: Device ID. NAME: Device name.

Options:
  --token TEXT
  --debug
  --help        Show this message and exit.
```

上の表示内容から、デバイスの名前を変更するためには、`$ remo device update <device-id> <device-name>` とコマンドを実行すればよいことがわかります。他のコマンドに関しても、同様の手順で実行に必要な情報を確認することが可能です。

各コマンドと API の完全な対応表は次のようになります:

Command | Nature Remo API
--- | ---
`remo user get` | `GET /1/users/me`
`remo user update` | `POST /1/users/me`
`remo device get` | `GET /1/devices`
`remo device update` | `POST /1/devices/{device}`
`remo device delete` | `POST /1/devices/{device}/delete`
`remo device update_temperature_offset` | `POST /1/devices/{device}/temperature_offset`
`remo device update_humidity_offset` | `POST /1/devices/{device}/humidity_offset`
`remo appliance detect` | `POST /1/detectappliance`
`remo appliance create` | `POST /1/appliances`
`remo appliance get` | `GET /1/appliances`
`remo appliance update` | `POST /1/appliances/{appliance}`
`remo appliance delete` | `POST /1/appliances/{appliance}/delete`
`remo appliance update_orders` | `POST /1/appliance_orders`
`remo appliance update_aircon_settings` | `POST /1/appliances/{appliance}/aircon_settings`
`remo appliance send_tv_infrared_signal` | `POST /1/appliances/{appliance}/tv`
`remo appliance send_light_infrared_signal` | `POST /1/appliances/{appliance}/light`
`remo signal create` | `/1/appliances/{appliance}/signals`
`remo signal get` | `GET /1/appliances/{appliance}/signals`
`remo signal update` | `POST /1/signals/{signal}`
`remo signal delete` | `/1/signals/{signal}/delete`
`remo signal send` | `/1/signals/{signal}/send`
`remo signal update_orders` | `/1/appliances/{appliance}/signal_orders`

## まとめ

Nature Remo の API にアクセスするための Python ライブラリを紹介しました。Remo の単純な操作をするには公式アプリが一番便利だと思いますが、プログラムから気軽に API を叩けることで、より柔軟に Remo を操作できるようになるはずです。Enjoy!🦖