Raspberry PiとPythonでsakura.ioを使ってみる(後編)

はじめに

さくらのナレッジ編集部の法林です。

さくらインターネットが提供するIoTプラットフォームサービスsakura.ioをRaspberry Piにつないで、Pythonのプログラムで操作する方法を紹介します。これまでに2本の記事を公開しました。主な内容は以下の通りです。

Raspberry PiとPythonでsakura.ioを使ってみる(前編)

  • システムの全体像の説明
  • Raspberry Piとsakura.ioの設定
  • FaBoの接続とsakura.ioへのデータ送信
  • サーバでのデータ受信
  • サーバからsakura.ioへのデータ送信とIoTデバイスの操作

Raspberry PiとPythonでsakura.ioを使ってみる(中編)

  • Raspberry Piでsakura.ioを操作する方法(下図①)
  • FaBoおよびセンサーを扱う方法(下図②)

3本目(最終回)となる後編では、主にsakura.ioとWebの世界をつなぐ方法を説明し、最後に今回実装したプログラムの解説をします。具体的には以下の内容を取り上げます。

  • PythonでWebSocketによる通信を実装する方法(下図③)
  • sakura.ioと外部ホストの間でやりとりするJSONデータの形式(下図④)
  • PythonでJSON形式のデータを扱う方法(下図⑤)
  • 実際に作成したプログラムの説明

なお、今回の記事群で使用するPythonはVer3とします。

システム構成と調査したポイント。青く囲った部分は中編で説明。赤く囲った部分が今回説明する項目

PythonでWebSocketによる通信プログラムを書く方法

sakura.ioが外部のホストと通信する方法はいくつかありますが、今回はWebSocketを使用します。多くのプログラミング言語がWebSocketに対応していますが、Pythonで実装する場合、今回のように外部ホストがsakura.ioに対するクライアントとして振る舞う用途であれば、websocket-clientモジュールを使えばいいようです。

このモジュールはpipでインストールします。

% pip3 install websocket-client

GitHubページにはさまざまな使い方が書いてありますが、今回の用途には「Short-lived one-off send-receive」という項目に書いてあるプログラムが使えそうです。以下に示します。

from websocket import create_connection
ws = create_connection("wss://echo.example.org/")
# WebSocketで文字列を送信
print("Sending 'Hello, World'...")
ws.send("Hello, World")
print("Sent")
# WebSocketで受信したものを表示
print("Receiving...")
result =  ws.recv()
print("Received '%s'" % result)
ws.close()

動作としては、WebSocketのURLを指定して接続し、文字列の送信、受信を行っています。WebSocketは1本のセッションで送信も受信も行うことができます。

sakura.ioと外部ホストの間でやりとりするJSONデータの形式

sakura.ioが外部のホストとの間で送受信するデータはJSON形式になっています。具体的なフォーマットは、sakura.ioのドキュメントの「メッセージ仕様」というページに書いてあります。

sakura.ioメッセージ仕様のページ

sakura.ioからデータを受信する場合とsakura.ioへデータを送信する場合でメッセージ構造が異なるので、それぞれ説明します。

sakura.ioから受信する場合

受信時のメッセージ例を以下に示します。

{
    "module": "uXXXXXXXXXXX",
    "type": "channels",
    "datetime": "2017-04-06T07:46:36.005341001Z",
    "payload": {
        "channels": [{
            "channel": 0,
            "type": "I",
            "value": 0,
            "datetime": "2017-04-06T07:39:29.703232943Z"
        }, {
            "channel": 0,
            ...
            ..
            .
        }]
    }
}

個々のパラメータの説明はメッセージ仕様のページを見てもらうとして、要点としては、payload->channelsの中身が配列になっていて、各要素のchannelにチャンネル番号、valueに値が入っています。

sakura.ioへ送信する場合

送信時のメッセージ例はこちらです。

{
    "type": "channels",
    "module": "uXXXXXXXXXXX",
    "payload": {
        "channels": [{
            "channel": 0,
            "type": "i",
            "value": 0
        }, {
            "channel": 0,
            ...
            ..
            .
        }]
    }
}

要点としては、送信先となるsakura.ioモジュールのIDをmoduleにて指定しています。payload->channelsの中にデータが入る点は受信時と同じです。

PythonでJSON形式のデータを扱う方法

PythonでJSON形式のデータを扱うには、Pythonに標準で含まれているjsonモジュールを使用します。

上記のページにはいろいろなことが書いてありますが、今回作成したプログラムでの使い方に絞って説明します。

sakura.ioからの受信

sakura.ioからデータを受け取ってJSONのデータ構造に格納し、必要な情報を取り出して表示するプログラム例を示します。

# WebSocketのURI
uri = "wss://api.sakura.io/ws/v1/xxxxxxx"

# ライブラリのインポート
from websocket import create_connection
import json

# WebSocketのURIに接続
ws = create_connection(uri)
# WebSocketでデータを受信
result =  ws.recv()
# データ全体を表示
print("Received '%s'" % result)

# JSONのデータ構造に格納
json_load = json.loads(result)
# カウンターと温度のデータを取り出して表示
cnt = json_load['payload']['channels'][0]['value']
temp = json_load['payload']['channels'][1]['value']
print("cnt = %i" % (cnt))
print("temp = %f" % (temp))

# WebSocketを切断
ws.close()

JSONモジュールを使っているのは json_load=json.loads(result) という行で、json.loads()は引数の文字列をJSONのデータ構造に変換してくれます。sakura.ioのJSONフォーマットは前述した通り、payload->channelの中身が配列になっていて、各要素のvalueというキーに値が入っています。IoTデバイスからsakura.ioにデータを送るときに、0チャンネルにカウンタ、1チャンネルに温度を入れて送信したので、

cnt = json_load['payload']['channels'][0]['value']
temp = json_load['payload']['channels'][1]['value']

のようにアクセスすることでカウンタと温度を取り出すことができます。

sakura.ioへの送信

sakura.ioにJSONデータを送信するときの処理を、同様にプログラム例で示します。

# WebSocketのURI
uri = "wss://api.sakura.io/ws/v1/xxxxxxx"
# sakura.ioモジュールのID
module_id = "uxxxxxxxxxxx"

# ライブラリのインポート
from websocket import create_connection
import json

# JSONデータ
json_data = {
    "type": "channels",
    "module": module_id,
    "payload": {
        "channels": [
            {"channel":0,"type":"I","value":0},
        ]
    }
}

# WebSocketのURIに接続
ws = create_connection(uri)

# 送信する値
data = 1
# JSONデータ構造に格納
json_data['payload']['channels'][0]['value'] = data
# JSONデータを文字列にしてWebSocketで送信
json_str = json.dumps(json_data)
print(json_str)
ws.send(json_str)

# WebSocketを切断
ws.close()

はじめに、sakura.ioの送信メッセージ構造に沿った形でjson_dataを定義します。ここで、sakura.ioのモジュールIDも設定します。その後、送信する値を、sakura.ioの送信メッセージで値が入るべき場所に格納します。JSONモジュールを使うのはその次の、JSONデータを文字列に変換する処理で、json.dumps()という関数で変換できます。変換したらWebSocketで送信します。

プログラムの作成

ここまで調べてきた情報をもとにプログラムを作成していきます。作成したプログラムは以下の3本です。

  • rpi-fabo.py
    Raspberry Pi⇔sakura.ioの通信プログラムです。送信も受信もこのプログラムで行います。
  • websocket-recv.py
    外部ホストからsakura.ioにWebSocketで接続し、データを受信するプログラムです。
  • websocket-send.py
    外部ホストからsakura.ioにWebSocketで接続し、データを送信するプログラムです。

WebSocketは1本のプログラムで送信も受信もできるのですが、websocket-send.pyで送信するデータをコマンドラインから入力する仕様にした関係で、送信と受信を別のプログラムとして実装しました。

今回作成したプログラム

以下、それぞれのプログラムについて説明します。

Raspberry Pi⇔sakura.ioの通信プログラム

サンプルプログラム集ではrpi-fabo.pyとして収録されています。ソースコード全体をこちらに掲載するには長過ぎるのでGitHubを見てください。ここでは主な処理の流れと、これまでの説明に出てこなかった実装上の工夫などを述べます。

  • 48行目のtry以降がメインルーチンで、49行目以降の処理を無限ループします。Ctrl-cが押されたら85行目以降の終了処理を実行します。
  • メインルーチンの中は、50-62行目で先に送信処理を行い、続いて65-79行目で受信処理を行っています。
  • 51-54行目で温度を取得していますが、FaBoの章で紹介したサンプルプログラムでは値が整数になります。しかし計算上は小数点以下も発生しているはずなので、27行目からのarduino_map()関数を改変して、Pythonのdecimalモジュールを使って小数点以下第2位まで計算しています。
  • 65-70行目は(下記コード参照)、sakuraio.get_rx_queue_length()が、sakura.ioの受信キューの長さを調べる関数です。これの値が1以上であればキューにデータが存在するので、sakuraio.dequeue_rx_raw()でデータを読み出して出力します。データがないときはその旨のメッセージを出力しています。
    # sakura.ioのキューにデータがあったら受信し値をledに入れる
    if (sakuraio.get_rx_queue_length()).get('queued'):
        dict_data = sakuraio.dequeue_rx_raw()
        led = dict_data['data'][0]
        print("led = %i" % (led))
    else:
        print("nothing recv...")
    

なお、最終的なプログラムは送信も受信もこれで処理していますが、プログラムの作り方としては、送信と受信を別のプログラムで作って、両方とも動いてから1つのプログラムにした方がよいでしょう。

外部ホストでsakura.ioのデータを受信するプログラム

サンプルプログラム集ではwebsocket-recv.pyです。こちらもソースコード全体はGitHubを見てもらうとして、実装上のポイントを紹介します。

  • 23-37行目をCtrl-cが押されるまで無限ループします。ほぼ1秒ごとに繰り返しデータを取りに行く動作をします。
  • 30-34行目で、取得したJSONデータから必要な情報を取り出して出力しています(下記コード参照)。最初にif文が入っているのは、sakura.ioから送られてくるのがデータだけではなく、WebSocketの接続を維持するためのkeepaliveメッセージもあるからです(くわしくはsakura.ioのメッセージ仕様のページをご覧ください)。両者は送られてくるデータのtypeキーの値が異なるので、typeキーの値がchannelsの場合のみ、データとみなして以後の処理を実行しています。
    if json_load['type'] == "channels":
        cnt = json_load['payload']['channels'][0]['value']
        temp = json_load['payload']['channels'][1]['value']
        print("cnt = %i" % (cnt))
        print("temp = %f" % (temp))
    

外部ホストからsakura.ioにデータを送信するプログラム

サンプルプログラム集ではwebsocket-send.pyです。こちらもソースコード全体はGitHubを見てもらうとして、実装上のポイントを紹介します。

  • 23-33行目で、sakura.ioの送信メッセージ仕様に合わせる形で、JSONのデータ構造を定義します。なお、プログラムのコメントにも書いてありますが、実際の処理においてはchannel 0のみ使用しています。
  • 41-54行目を、Ctrl-cが押されるまで繰り返し実行します。
  • 43-44行目で、コマンドラインに入力プロンプト('>> ')を表示し、ユーザに値を入力させます。
  • 47-54行目で、入力された値が0か1であれば、値をJSONのデータ構造に入れてsakura.ioに送信します。それ以外の値が入力されたときは何も送らない旨のメッセージを出力します。

おわりに

筆者はこれまで、sakura.ioのハンズオン講師は何度かやっていますが、sakura.io関連のプログラムは書いたことがなく、さらにRaspberry PiもPythonもFaBoもほぼ初心者レベルなので、果たしてプログラムを作ることができるのかと不安でした。しかし、いろいろ調べてみると、情報が整備されているとまでは言えませんが一応なんとかなりそうなぐらいには見つけることができ、プログラムを動かすところまでこぎつけることができました。それでもやはり、情報がまとまっていないことや、プログラム例が少ないことを感じたので、今回の作業を通して自分が得た知識をお届けする次第です。これからIoT関連で何か作りたいとか、Raspberry PiやPythonで何かやりたいと考えている方々への参考になれば幸いです。