REST API 経由で RPZ-IR-Sensor の情報の取得と操作をしてみる #IoT #Python


今回は以前サンプルで動かした[IRセンサーの操作](https://qiita.com/ynozue/items/f415d958cff34bc71f3d)や[温度・気圧・湿度・照度センサーの取得](https://qiita.com/ynozue/items/4a6da5f4afe613153a33)の情報を REST API で取得や実行できるようにしようと思います。

# 概要イメージ
「nginx」「gunicorn」「falcon」を利用して REST API を作成して、「RPZ-IR-Sensor」に対して正面のON/OFFの命令の実行とセンサーから取得した情報の取得を行う機能を実装します。

![raspi_zero_sensor.png](https://qiita-image-store.s3.amazonaws.com/0/203864/dec7cde7-49fd-2c69-9f03-7b2ba36258e9.png "raspi_zero_sensor.png")

## API の機能
* センサー情報（光度、温度、湿度、気圧）の取得
* 照明を点ける
* 照明を消す
* 照明の電燈を切り変え


# 各パッケージのインストール
## falcon インストール
```sh
$ sudo pip install falcon
Collecting falcon
  Downloading falcon-1.3.0-py2.py3-none-any.whl (150kB)
    100% |████████████████████████████████| 153kB 249kB/s 
Requirement already satisfied: six>=1.4.0 in /usr/lib/python2.7/dist-packages (from falcon)
Collecting python-mimeparse>=1.5.2 (from falcon)
  Downloading python_mimeparse-1.6.0-py2.py3-none-any.whl
Installing collected packages: python-mimeparse, falcon
Successfully installed falcon-1.3.0 python-mimeparse-1.6.0
```

## cython インストール
```sh
$ sudo pip install cython
Collecting cython
  Downloading Cython-0.27.3.tar.gz (1.8MB)
    100% |████████████████████████████████| 1.8MB 46kB/s 
Building wheels for collected packages: cython
  Running setup.py bdist_wheel for cython ... done
  Stored in directory: /root/.cache/pip/wheels/d1/2d/01/94d746e3ab647f56f2b1ef9547e37f62ed010d9956c988ab9f
Successfully built cython
Installing collected packages: cython
Successfully installed cython-0.27.3
```
※ Raspberry Pi zero で実行するとすごい時間がかかります。。。

## gunicorn インストール
```sh
$ sudo pip install gunicorn
Collecting gunicorn
  Downloading gunicorn-19.7.1-py2.py3-none-any.whl (111kB)
    100% |████████████████████████████████| 112kB 240kB/s 
Installing collected packages: gunicorn
Successfully installed gunicorn-19.7.1
```

## nginx インストール
```sh
$ sudo apt install nginx
パッケージリストを読み込んでいます... 完了
依存関係ツリーを作成しています                
状態情報を読み取っています... 完了
以下の追加パッケージがインストールされます:
  libnginx-mod-http-auth-pam libnginx-mod-http-dav-ext libnginx-mod-http-echo libnginx-mod-http-geoip libnginx-mod-http-image-filter libnginx-mod-http-subs-filter
  libnginx-mod-http-upstream-fair libnginx-mod-http-xslt-filter libnginx-mod-mail libnginx-mod-stream nginx-common nginx-full
提案パッケージ:
  fcgiwrap nginx-doc ssl-cert
以下のパッケージが新たにインストールされます:
  libnginx-mod-http-auth-pam libnginx-mod-http-dav-ext libnginx-mod-http-echo libnginx-mod-http-geoip libnginx-mod-http-image-filter libnginx-mod-http-subs-filter
  libnginx-mod-http-upstream-fair libnginx-mod-http-xslt-filter libnginx-mod-mail libnginx-mod-stream nginx nginx-common nginx-full
アップグレード: 0 個、新規インストール: 13 個、削除: 0 個、保留: 0 個。
1,502 kB のアーカイブを取得する必要があります。
この操作後に追加で 2,563 kB のディスク容量が消費されます。
続行しますか? [Y/n] y
取得:1 http://ftp.tsukuba.wide.ad.jp/Linux/raspbian/raspbian stretch/main armhf nginx-common all 1.10.3-1+deb9u1 [104 kB]
取得:2 http://ftp.tsukuba.wide.ad.jp/Linux/raspbian/raspbian stretch/main armhf libnginx-mod-http-auth-pam armhf 1.10.3-1+deb9u1 [85.4 kB]
取得:3 http://ftp.tsukuba.wide.ad.jp/Linux/raspbian/raspbian stretch/main armhf libnginx-mod-http-dav-ext armhf 1.10.3-1+deb9u1 [87.0 kB]
取得:4 http://ftp.tsukuba.wide.ad.jp/Linux/raspbian/raspbian stretch/main armhf libnginx-mod-http-echo armhf 1.10.3-1+deb9u1 [95.1 kB]
取得:5 http://ftp.tsukuba.wide.ad.jp/Linux/raspbian/raspbian stretch/main armhf libnginx-mod-http-geoip armhf 1.10.3-1+deb9u1 [86.5 kB]                                          
取得:6 http://ftp.tsukuba.wide.ad.jp/Linux/raspbian/raspbian stretch/main armhf libnginx-mod-http-image-filter armhf 1.10.3-1+deb9u1 [89.3 kB]                                   
取得:7 http://ftp.tsukuba.wide.ad.jp/Linux/raspbian/raspbian stretch/main armhf libnginx-mod-http-subs-filter armhf 1.10.3-1+deb9u1 [88.5 kB]                                    
取得:8 http://ftp.tsukuba.wide.ad.jp/Linux/raspbian/raspbian stretch/main armhf libnginx-mod-http-upstream-fair armhf 1.10.3-1+deb9u1 [88.6 kB]                                  
取得:9 http://ftp.tsukuba.wide.ad.jp/Linux/raspbian/raspbian stretch/main armhf libnginx-mod-http-xslt-filter armhf 1.10.3-1+deb9u1 [88.0 kB]                                    
取得:10 http://ftp.tsukuba.wide.ad.jp/Linux/raspbian/raspbian stretch/main armhf libnginx-mod-mail armhf 1.10.3-1+deb9u1 [113 kB]                                                
取得:11 http://ftp.tsukuba.wide.ad.jp/Linux/raspbian/raspbian stretch/main armhf libnginx-mod-stream armhf 1.10.3-1+deb9u1 [106 kB]                                              
取得:12 http://ftp.tsukuba.wide.ad.jp/Linux/raspbian/raspbian stretch/main armhf nginx-full armhf 1.10.3-1+deb9u1 [389 kB]                                                       
取得:13 http://ftp.jaist.ac.jp/pub/Linux/raspbian-archive/raspbian stretch/main armhf nginx all 1.10.3-1+deb9u1 [81.5 kB]                                                        
1,502 kB を 32秒 で取得しました (46.7 kB/s)                                                                                                                                      
パッケージを事前設定しています ...
以前に未選択のパッケージ nginx-common を選択しています。
(データベースを読み込んでいます ... 現在 123024 個のファイルとディレクトリがインストールされています。)
.../00-nginx-common_1.10.3-1+deb9u1_all.deb を展開する準備をしています ...
nginx-common (1.10.3-1+deb9u1) を展開しています...
以前に未選択のパッケージ libnginx-mod-http-auth-pam を選択しています。
.../01-libnginx-mod-http-auth-pam_1.10.3-1+deb9u1_armhf.deb を展開する準備をしています ...
libnginx-mod-http-auth-pam (1.10.3-1+deb9u1) を展開しています...
以前に未選択のパッケージ libnginx-mod-http-dav-ext を選択しています。
.../02-libnginx-mod-http-dav-ext_1.10.3-1+deb9u1_armhf.deb を展開する準備をしています ...
libnginx-mod-http-dav-ext (1.10.3-1+deb9u1) を展開しています...
以前に未選択のパッケージ libnginx-mod-http-echo を選択しています。
.../03-libnginx-mod-http-echo_1.10.3-1+deb9u1_armhf.deb を展開する準備をしています ...
libnginx-mod-http-echo (1.10.3-1+deb9u1) を展開しています...
以前に未選択のパッケージ libnginx-mod-http-geoip を選択しています。
.../04-libnginx-mod-http-geoip_1.10.3-1+deb9u1_armhf.deb を展開する準備をしています ...
libnginx-mod-http-geoip (1.10.3-1+deb9u1) を展開しています...
以前に未選択のパッケージ libnginx-mod-http-image-filter を選択しています。
.../05-libnginx-mod-http-image-filter_1.10.3-1+deb9u1_armhf.deb を展開する準備をしています ...
libnginx-mod-http-image-filter (1.10.3-1+deb9u1) を展開しています...
以前に未選択のパッケージ libnginx-mod-http-subs-filter を選択しています。
.../06-libnginx-mod-http-subs-filter_1.10.3-1+deb9u1_armhf.deb を展開する準備をしています ...
libnginx-mod-http-subs-filter (1.10.3-1+deb9u1) を展開しています...
以前に未選択のパッケージ libnginx-mod-http-upstream-fair を選択しています。
.../07-libnginx-mod-http-upstream-fair_1.10.3-1+deb9u1_armhf.deb を展開する準備をしています ...
libnginx-mod-http-upstream-fair (1.10.3-1+deb9u1) を展開しています...
以前に未選択のパッケージ libnginx-mod-http-xslt-filter を選択しています。
.../08-libnginx-mod-http-xslt-filter_1.10.3-1+deb9u1_armhf.deb を展開する準備をしています ...
libnginx-mod-http-xslt-filter (1.10.3-1+deb9u1) を展開しています...
以前に未選択のパッケージ libnginx-mod-mail を選択しています。
.../09-libnginx-mod-mail_1.10.3-1+deb9u1_armhf.deb を展開する準備をしています ...
libnginx-mod-mail (1.10.3-1+deb9u1) を展開しています...
以前に未選択のパッケージ libnginx-mod-stream を選択しています。
.../10-libnginx-mod-stream_1.10.3-1+deb9u1_armhf.deb を展開する準備をしています ...
libnginx-mod-stream (1.10.3-1+deb9u1) を展開しています...
以前に未選択のパッケージ nginx-full を選択しています。
.../11-nginx-full_1.10.3-1+deb9u1_armhf.deb を展開する準備をしています ...
nginx-full (1.10.3-1+deb9u1) を展開しています...
以前に未選択のパッケージ nginx を選択しています。
.../12-nginx_1.10.3-1+deb9u1_all.deb を展開する準備をしています ...
nginx (1.10.3-1+deb9u1) を展開しています...
nginx-common (1.10.3-1+deb9u1) を設定しています ...
Created symlink /etc/systemd/system/multi-user.target.wants/nginx.service → /lib/systemd/system/nginx.service.
libnginx-mod-http-image-filter (1.10.3-1+deb9u1) を設定しています ...
libnginx-mod-http-subs-filter (1.10.3-1+deb9u1) を設定しています ...
systemd (232-25+deb9u1) のトリガを処理しています ...
libnginx-mod-http-auth-pam (1.10.3-1+deb9u1) を設定しています ...
libnginx-mod-http-dav-ext (1.10.3-1+deb9u1) を設定しています ...
libnginx-mod-mail (1.10.3-1+deb9u1) を設定しています ...
man-db (2.7.6.1-2) のトリガを処理しています ...
libnginx-mod-http-xslt-filter (1.10.3-1+deb9u1) を設定しています ...
libnginx-mod-http-upstream-fair (1.10.3-1+deb9u1) を設定しています ...
libnginx-mod-http-geoip (1.10.3-1+deb9u1) を設定しています ...
libnginx-mod-stream (1.10.3-1+deb9u1) を設定しています ...
libnginx-mod-http-echo (1.10.3-1+deb9u1) を設定しています ...
nginx-full (1.10.3-1+deb9u1) を設定しています ...
nginx (1.10.3-1+deb9u1) を設定しています ...
```

# python で REST API を作成
以下の機能を満たす API を作ります。

* センサー情報（光度、温度、湿度、気圧）の取得
* 照明を点ける
* 照明を消す
* 照明の電燈を切り変え

API 設計は面倒だったので、全て GET で endpoint を変更して操作するようにしました。
一応、簡単なエラーハンドリングもしておきました。
センサー類の扱いについては以下のリンクを参考にしてください。

* [IRセンサーの操作](https://qiita.com/ynozue/items/f415d958cff34bc71f3d)
* [温度・気圧・湿度・照度センサーの取得](https://qiita.com/ynozue/items/4a6da5f4afe613153a33)


```py:sensor.py
#!/usr/bin/env python3

"""
TSL2561/BME280/IR Control Module
"""

import os
import falcon
from bme280i2c import BME280I2C
from tsl2561 import TSL2561

class Sensor:
    def on_get(self, req, resp, action):
        """Handles GET requests"""

        if action == "show":
            resp.media = self.__get_sensor_response()
        elif action in {"on", "off", "change"}:
            self.__execute_light_action(action)
            resp.media = self.__get_sensor_response()
        else:
            resp.media = self.__get_error_response("Illegal Sensor Action [" + action + "]")

    def __execute_light_action(self, action):
        os.system('irsend SEND_ONCE lightd ' + action)

    def __get_sensor_response(self):
        bme280 = BME280I2C(0x77)
        tsl2561 = TSL2561(0x29)
        bme = bme280.meas()
        tsl = tsl2561.meas_single()

        if not (bme or tsl):
            msg = self.__get_error_response("No Sensor Available")
        else:
            msg = {
                "Temp": bme280.T
                , "Pressure": bme280.P
                , "Humidity": bme280.H
                , "Lux": tsl2561.lux
            }
        return msg

    def __get_error_response(self, message):
        return {
                "error": message
            }

api = falcon.API()
api.add_route('/sensor/{action}', Sensor())
```
※ tsl2561.py, bme280i2c.py は サンプルプログラムから流用

gunicorn を起動

```sh
$ gunicorn sensor:api
[2017-12-10 15:19:19 +0000] [870] [INFO] Starting gunicorn 19.7.1
[2017-12-10 15:19:19 +0000] [870] [INFO] Listening at: http://127.0.0.1:8000 (870)
[2017-12-10 15:19:19 +0000] [870] [INFO] Using worker: sync
[2017-12-10 15:19:19 +0000] [874] [INFO] Booting worker with pid: 874
```

# nginx のリバースプロキシを設定
デフォルトの設定だと sites-enabled/default ファイルの設定が有効になってしまうのでコメントアウト

```sh
$ sudo vi /etc/nginx/nginx.conf

61     ##
62 
63     include /etc/nginx/conf.d/*.conf;
64 #   include /etc/nginx/sites-enabled/*;
65 }
```
sensor 用の設定ファイルを作成（とりあえずセンサーしか使わないので全てをリバースプロキシの対象に）

```sh:/etc/nginx/conf.d/sensor.conf
$ sudo vi /etc/nginx/conf.d/sensor.conf

upstream app_server {
     server 127.0.0.1:8000 fail_timeout=0;
}

server {
    listen 80 default;
    server_name _;

    keepalive_timeout 5;

    location / {
        proxy_set_header X-Forwarded-For $proxy_add_x_forwarded_for;
        proxy_set_header Host $http_host;
        proxy_redirect off;

        if (!-f $request_filename) {
            proxy_pass http://app_server;
            break;
        }
    }

}
```
nginx を再起動

```sh
$ sudo service nginx restart
```


# API を試す
## 照明を点ける
```sh
$ time curl http://192.168.3.99/sensor/on | python -m json.tool
  % Total    % Received % Xferd  Average Speed   Time    Time     Time  Current
                                 Dload  Upload   Total   Spent    Left  Speed
100    71  100    71    0     0    188      0 --:--:-- --:--:-- --:--:--   188
{
    "Humidity": 28,
    "Lux": 177.262099009901,
    "Pressure": 1009,
    "Temp": 26
}

real	0m0.403s
user	0m0.033s
sys	0m0.040s
```
## 照明の電燈を切り変え
```sh
$ time curl http://192.168.3.99/sensor/change | python -m json.tool
  % Total    % Received % Xferd  Average Speed   Time    Time     Time  Current
                                 Dload  Upload   Total   Spent    Left  Speed
100    63  100    63    0     0     76      0 --:--:-- --:--:-- --:--:--    76
{
    "Humidity": 27,
    "Lux": 120.7792,
    "Pressure": 1009,
    "Temp": 26
}

real	0m0.851s
user	0m0.034s
sys	0m0.040s
```
## 照明を消す
```sh
$ time curl http://192.168.3.99/sensor/off | python -m json.tool
  % Total    % Received % Xferd  Average Speed   Time    Time     Time  Current
                                 Dload  Upload   Total   Spent    Left  Speed
100    62  100    62    0     0     70      0 --:--:-- --:--:-- --:--:--    70
{
    "Humidity": 27,
    "Lux": 34.1696,
    "Pressure": 1009,
    "Temp": 26
}

real	0m0.909s
user	0m0.035s
sys	0m0.043s
```
## センサー情報（光度、温度、湿度、気圧）の取得
```sh
$ time curl http://192.168.3.99/sensor/show | python -m json.tool
  % Total    % Received % Xferd  Average Speed   Time    Time     Time  Current
                                 Dload  Upload   Total   Spent    Left  Speed
100    62  100    62    0     0     94      0 --:--:-- --:--:-- --:--:--    94
{
    "Humidity": 27,
    "Lux": 33.9872,
    "Pressure": 1009,
    "Temp": 26
}

real	0m0.684s
user	0m0.037s
sys	0m0.047s
```
## 存在しないアクションの実行
```sh
$ time curl http://192.168.3.99/sensor/hoge | python -m json.tool
  % Total    % Received % Xferd  Average Speed   Time    Time     Time  Current
                                 Dload  Upload   Total   Spent    Left  Speed
100    41  100    41    0     0   1158      0 --:--:-- --:--:-- --:--:--  1171
{
    "error": "Illegal Sensor Action [hoge]"
}

real	0m0.129s
user	0m0.030s
sys	0m0.072s
```

# まとめ
API 経由でのセンサーへのアクセスですが思った以上に簡単にできました。
単純にインストールと nginx の設定ではまったところで時間をくったくらいでハマらなければサクサクできます。
python はあまり慣れていないので、パッケージングとか自動起動とかそこらへんを今後はまとめていきたいと思います。