日記
ラズパイにはまってから、IoTというキーワードによく反応するようになりました。
なかでも、トイレの使用状況をお知らせする、なんてのはよく聞く話かと思います。
先日、サービスエリアに寄った際も、トイレの混雑状況をお知らせする大きな看板が目につきました。
「空」だったり「混」という文字が出ているのを見て、お!っと思った次第です。
この仕組み自体は昔からあったものかもしれませんが、嬉しかった私は、奥さんに「見て!すごい目まぐるしく表示がかわってる!」とお伝えし、「満!混!満!混!」とはしゃいでいたら、奥さんにいろいろな意味で止めてくれる?、と窘められました。
さて、話はかわりますが、ここにきて、というか最初からかもしれませんが、ブログの目的が曖昧だったりします。
今はモチベーションを維持するためにも、こうやってやったことを発信できれば、と思っているのですが、成果物が雑だったり、成果物に対してブログを書くことに時間がかなり大きくって、どうなんだろうみたいなものもあって悩みどころです。
メモを取るだけだったらEvernoteにも書けばいいとも思いますし、きちんとした意見がほしいのであればQuitaに書いたほうがいいのかな、とか思ったりします。
モチベーションを保つためにもアクセス数はほしいし、切磋琢磨的なところで意見もほしいし、自分のメモとしても役に立ちたいし、みたいなところがいろいろあって、何が一番大事なのかを見失っています。
が、とりあえずは悩んで何もしないよりかは、まず動いている状態が大事だと思いますので、引き続きよろしくお願い致します。
前回の続き
前回はnode.jsを使ってsocket.ioを実装し、ブラウザからのキー入力をサーバーに送信するところまでやりました。
今回は、socket.ioのサーバー側の部分をpythonのWebアプリケーションフレームワークであるFlaskを使って、実装していきたいと思います。
理由は前回もありましたが、ラズパイのGPIOをコントロールする部分をpythonで実装しているためです。
tohutokonsome.hatenablog.com
Flaskとは
PythonのWebアプリケーションフレームワーク。
以前扱った、PythonのWebアプリケーションフレームワークの「Django」よりさらに軽量(必要最低限のフレームワーク)な気がする。
実際の作業は公式?のチュートリアルを見て、試してみました。
1. Webアプリケーションを作る準備 — study flask 1 ドキュメント
ここに、socket.ioを実装していきます。
Flaskのsocket.ioについては、以下の記事を参考にさせていただきました。
Flask-SocketIOでWebSocketアプリケーション - Qiita
Welcome to Flask-SocketIO’s documentation! — Flask-SocketIO documentation
初期設定
ラズパイに、必要なpythonモジュールをインストールしていきます。
pythonのバージョンは以下を使用しています。
pi@raspberrypi:~ $ pyenv version
3.5.1 (set by /home/pi/.pyenv/version)
Flaskのインストール
pipで取得します。
pi@raspberrypi:~/myproduct $ sudo pip install Flask
地味にsudo権限だと環境変数変わるっていうのを忘れていて、混乱した。
sudoersを編集する必要があった。
WiringPiでエラーになった場合のメモ - 豆腐とコンソメ
インストールされてますね。
pi@raspberrypi:~/myproduct $ sudo pip freeze
click==6.7
Flask==0.12.2
itsdangerous==0.24
Jinja2==2.9.6
MarkupSafe==1.0
Werkzeug==0.12.2
wiringpi==2.32.1
wiringpi2==2.32.3
Flask-SocketIOのインストール
こちらもFlaskと同様にpipでもってくる。
pi@raspberrypi:~/myproduct $ sudo pip install flask-socketio
こちらも特に問題なくインストールされています。
pi@raspberrypi:~/myproduct $ sudo pip freeze
click==6.7
Flask==0.12.2
Flask-SocketIO==2.9.1
itsdangerous==0.24
Jinja2==2.9.6
MarkupSafe==1.0
python-engineio==1.7.0
python-socketio==1.7.6
six==1.10.0
Werkzeug==0.12.2
wiringpi==2.32.1
wiringpi2==2.32.3
唯一のモジュールの「app.py」の作成
Flaskを使ったチュートリアルでは、ブログを作成していたりしました。
しかし、今回必要なのは、以前作成したレゴカーのコントロール部分の橋渡しだけです。
なので、Flaskの基本的な機能については深く考えずに、進めます。
ということで、唯一作成するapp.pyの中身はほぼ以下の記事のままになります。
Welcome to Flask-SocketIO’s documentation! — Flask-SocketIO documentation
app.py
from flask import Flask
from flask_socketio import SocketIO,emit
app = Flask(__name__)
app.config['SECRET_KEY'] = 'secret!'
socketio = SocketIO(app)
@socketio.on('connect',namespace='/legocar')
def init() :
print('connected')
@socketio.on('sendMessage',namespace='/legocar')
def recieve_order(order) :
if order in {'forward','back'}:
print("accell = " +order)
testmethod(order)
elif order in {'break'}:
print("accell stop = " +order)
testmethod(order)
elif order in {'right','left'}:
print("handle = " +order)
testmethod(order)
else:
pass
@socketio.on('test',namespace='/legocar')
def testmethod(order):
emit('test',order)
if __name__ == '__main__':
socketio.run(app,host='0.0.0.0',port=5000)
検証がいろいろと足りていないのですが、自分のメモがてらポイントを記載しておきます。
- Flaskは以下のように条件を、@構文で定義して、それに該当する処理である場合、直下の関数が呼ばれるみたい
@app.route('/')
def index():
return 'Hello world!'
node.jsのsocket.onみたいなイベントも同じように@socketio.on(‘イベント名’,namespace)、直下に関数でいける
node.jsの場合、namespaceは使っていなかったけれども、どうもFlask-SocketIOでは必須?(未検証)
socketio.run(app)ではなくsocketio.run(app,host=‘0.0.0.0’,port=5000)とすること。
socketio.run(app)の場合、ローカルホスト内でしかFlaskにアクセスできない
そうしましたら、上記、app.pyをラズパイ上で実行します。
Flaskの起動
pi@raspberrypi:~/myproduct/legocar/legocar/server $ sudo python app.py
WebSocket transport not available. Install eventlet or gevent and gevent-websocket for improved performance.
* Running on http://0.0.0.0:5000/ (Press CTRL+C to quit)
なにやらいわれていますが、無事起動しました。
これが終わったら、前回作成した「controller.html」を修正します。
controll.htmlの修正
前回、サーバー側のsocket.ioはnode.jsで実装していました。
これをさきほど作成したapp.pyに向きを変えます。
といっても、io.connectに引数として接続先を渡してあげるだけです。
controller.html(修正前)
<script src="//ajax.googleapis.com/ajax/libs/jquery/1.8.0/jquery.min.js"></script>
<script src="/socket.io/socket.io.js"></script>
<script>
var socket = io.connect();
io.connectは引数なしの場合、デフォルトHTTPリクエストで要求したサーバー(ここではnode.jsのWebサーバー)に接続します。
引数を渡してあげることで、指定したサーバーに接続することができるみたいです。
また、接続先のアドレスですが、「ラズパイのIPアドレス:5000/legocar」としています。
ホスト名以降の/legocarってなんぞって感じなのですが、app.pyのnamespaceで指定したものになります。
namespaceそのものをあんまり理解してないので、ここではふーん、ぐらいで終えています。
controller.html(修正後)
<script src="//ajax.googleapis.com/ajax/libs/jquery/1.8.0/jquery.min.js"></script>
<script src="/socket.io/socket.io.js"></script>
<script>
var socket = io.connect('http://ラズパイのIPアドレス:5000/legocar');
疎通確認
それでは、ラズパイにログインして、前回作成したnode.jsのプロジェクトとpythonのapp.pyを起動してみます。
node.jsのWebサーバー起動
pi@raspberrypi:~/myproduct/legoCarController $ node server.js
Flaskの起動(さっきと同じ)
pi@raspberrypi:~/myproduct/legocar/legocar/server $ sudo python app.py
WebSocket transport not available. Install eventlet or gevent and gevent-websocket for improved performance.
* Running on http://0.0.0.0:5000/ (Press CTRL+C to quit)
これで、「http://ラズパイのIP:6677/controller」にアクセスすると、controller.htmlが表示され、ブラウザ上では、前回と同様の内容が表示されるます。
また、Flaskを起動しているコンソール画面には、ブラウザからのキー入力に応じて、app.pyのprintしている内容が表示されることが確認できます。
192.168.1.5 - - [19/Jul/2017 15:58:07] "POST /socket.io/?EIO=3&transport=polling&t=LrPexJY&sid=9364d0a57e82400da39bf9470d70d056 HTTP/1.1" 200 -
192.168.1.5 - - [19/Jul/2017 15:58:07] "GET /socket.io/?EIO=3&transport=polling&t=LrPexJc&sid=9364d0a57e82400da39bf9470d70d056 HTTP/1.1" 200 -
accell = forward
192.168.1.5 - - [19/Jul/2017 15:58:07] "POST /socket.io/?EIO=3&transport=polling&t=LrPexSK&sid=9364d0a57e82400da39bf9470d70d056 HTTP/1.1" 200 -
192.168.1.5 - - [19/Jul/2017 15:58:07] "GET /socket.io/?EIO=3&transport=polling&t=LrPexMK&sid=9364d0a57e82400da39bf9470d70d056 HTTP/1.1" 200 -
accell stop = break
なんだか、POST、GETがsocketと同じタイミングで吐き出されているのが気になりますが。一旦棚にあげています。
ラズパイコントロール用の処理を追加する
後は、以前以下の記事のときに作成したラズパイコントロール用の処理である「legocar_controller.py 」をapp.pyから呼ぶだけです。
tohutokonsome.hatenablog.com
ひどく汚いコードですが、とりあえず自分の環境では稼働が確認できました。
SocketIO,emit
import legocar_controller as LegoCar
app = Flask(__name__)
app.config['SECRET_KEY'] = 'secret!'
socketio = SocketIO(app)
myLegoCar = LegoCar.LegoCarController()
@socketio.on('connect',namespace='/legocar')
def init() :
print('connected')
@socketio.on('sendMessage',namespace='/legocar')
def recieve_order(order) :
TODO
if order in {'forward'}:
print("accell forward " +order)
myLegoCar.accelerator(b"w")
testmethod(order)
elif order in {'back'}:
print("accell back " +order)
myLegoCar.accelerator(b"s")
testmethod(order)
elif order in {'break'}:
print("accell stop " +order)
myLegoCar.accelerator(b"f")
testmethod(order)
elif order in {'right'}:
print("handle = " +order)
myLegoCar.handle(b"d")
testmethod(order)
elif order in {'left'}:
print("handle = " +order)
myLegoCar.handle(b"a")
testmethod(order)
else:
pass
@socketio.on('test',namespace='/legocar')
def testmethod(order):
emit('test',order)
if __name__ == '__main__':
socketio.run(app,host='0.0.0.0',port=5000)
次回は、操作するUIだったり、カメラ部分を改善していきたいと思います。