2015年11月8日日曜日

pythonの超軽量webフレームワーク Bottle:Rapiroを制御する

さて本題ですが、webでRapiroを制御できるようにします。元々、これがしたくてpythonで動く簡単なフレームワークがないか探していました。

とりあえず、作ったweb画面を以下に示します。

何のひねりもありませんが、前進、後退、右旋回、左旋回と停止を指示できるようにします。Rapiroを制御するPythonプログラムではRapiroの運動制御を行っているArudinoに、制御用コンピュータとして搭載してあるRaspberry Piからシリアルで各コマンドを送るだけです。そのコマンドを、上記ページのボタンを押したら、送ってやるようにしてやればいいだけです。

Raspberry Piに以下の様なファイル(ディレクトリ構成)を作ります。

rapiro_study/
    - bottle.py
    - index.py
    views/
        - title.tpl

bottle.pyは、フレームワーク本体です。本来ならpythonのパッケージライブラリに入れるべきなんでしょうが、1ファイルと簡単な構成なのでわかりやすいようにここに一緒に置いています。index.pyがwebを制御する本体のプログラムです。(といっても、これだけです)views/以下に、htmlを置いておきます。(このディレクトリ名は固定のようです)今回は一つしか画面を用意していないので、一つしかありません。(title.tpl)これは拡張子が"tpl"ですが、内容は全く"html"です。

title.tpl
  1. <!DOCTYPE html>
  2. <html lang="ja">
  3. <head>
  4. <meta charset="UTF-8">
  5. <title>RAPIRO</title>
  6. </head>
  7. <body>
  8. <h1>RAPIRO Control page</h1>
  9.  
  10. <form name="frm" method="get" action="stop">
  11. <input type="button" onclick="document.frm.submit();" value="停止" style="font-size: 400%; width:200px; ">
  12. </form>
  13.  
  14.  
  15. <form name="frmF" method="post" action="forward">
  16. <input type="button" onclick="document.frmF.submit();" value="前進" style="font-size: 100%; width:100px; height:100px; position: absolute; left: 50%; top: 40%;">
  17. </form>
  18.  
  19. <form name="frmB" method="post" action="back">
  20. <input type="button" onclick="document.frmB.submit();" value="後退" style="font-size: 100%; width:100px; height:100px; position: absolute; left: 50%; top: 60%">
  21. </form>
  22.  
  23. <form name="frmR" method="post" action="right">
  24. <input type="button" onclick="document.frmR.submit();" value="右旋回" style="font-size: 100%; width:100px; height:100px; position: absolute; left: 70%; top: 50%">
  25. </form>
  26.  
  27. <form name="frmL" method="post" action="left">
  28. <input type="button" onclick="document.frmL.submit();" value="左旋回" style="font-size: 100%; width:100px; height:100px; position: absolute; left: 30%; top: 50%">
  29. </form>
  30. </body>
  31. </html>

index.py
  1. #!/usr/bin/python
  2. # -*- coding: utf-8 -*-
  3.  
  4. from bottle import route, run, template, request
  5.  
  6. '''
  7. __debug__を追加。(通常実行時は、__debug__ = 1)
  8. Macでデバッグしたい時は、$ python -O rapiro3.pyで最適化をかけて実行する。
  9. '''
  10.  
  11. if __debug__:
  12. import serial
  13. com = serial.Serial('/dev/ttyAMA0', 57600, timeout=10)
  14.  
  15. # 出力を一カ所にまとめる。(デバッグ時には標準出力にだすようにする)
  16. def out(comStr):
  17. if __debug__:
  18. com.write(comStr)
  19. else:
  20. print comStr
  21.  
  22. # localhost:8000
  23. @route('/')
  24. def title():
  25. # views/title.tplを呼ぶ
  26. return template('title')
  27.  
  28.  
  29. # localhost:8000/show
  30. @route('/stop', method='GET')
  31. def stop():
  32. # GETパラメータの取得(username, men)
  33. command = request.forms.submit
  34.  
  35. print("in stop", command)
  36.  
  37. out('#M0') # 停止(初期姿勢)
  38.  
  39. # views/title.tplを呼ぶ
  40. return template('title')
  41.  
  42. # localhost:8000/forward
  43. @route('/forward', method='POST')
  44. def forward():
  45. print("in forward")
  46. out('#M1') # 前進
  47.  
  48. # views/title.tplを呼ぶ
  49. return template('title')
  50.  
  51. # localhost:8000/back
  52. @route('/back', method='POST')
  53. def back():
  54. print("in back")
  55. out('#M2') # 後退
  56.  
  57. # views/title.tplを呼ぶ
  58. return template('title')
  59.  
  60. # localhost:8000/right
  61. @route('/right', method='POST')
  62. def right():
  63. print("in right")
  64. out('#M4') # 右回り
  65.  
  66. # views/title.tplを呼ぶ
  67. return template('title')
  68.  
  69. # localhost:8000/left
  70. @route('/left', method='POST')
  71. def left():
  72. print("in left")
  73. out('#M3') # 左回り
  74.  
  75. # views/title.tplを呼ぶ
  76. return template('title')
  77.  
  78. # ビルドインサーバの実行
  79. run(host='rapiropi.local', port=8000, debug=True, reloader=True)
  80.  
これだけです。実行するときは、Rapiroのコンピュータ(rapiropiです)にログインし、以下のコマンドを実行します。

$ python index.py

その後、PCで以下のURLをブラウザで表示します。

http://rapiropi.local:8000/

URL名の最後が".local"とちょっと普通と違いますが、家庭内のPCを特にDNSを作らなくてもURLでアクセスできるようにパッケージを入れてあるからです。Webのボタンを押したら、本来なら別のページに飛ぶためのhtmlファイルを指定するところなんですが、今回は単にpythonに制御用の指示を出したいだけなので、単純に文字列を送信して、index.pyではそれを受けとって対応するシリアルコマンドを送ったら、また一つだけのホームページを表示するようにしています。ホームページの方は文字の大きさや何かを指定するために、CSSを使わず個別に指定しています。大したページでもなかったので、CSSを作るのが面倒くさかっただけです。

実際に動かしてみたところ、「ちょっと反応が鈍いかな?」という感じですがちゃんと動いてくれました。
制御用にGETとPOSTの両方を使っていますが、単に勉強用に両方を使ってみただけです。コマンドと関係はありません。(特に送信時に個別のデータはつけてませんし)JavaScriptの練習の時にはよくPOSTで送信していましたが、いざhtmlの定義だけでやるとき、最初は"submit"ボタンしか送信する方法が思いつかず悩みました。調べていたら、ボタンにformをつけて、そこで一つだけJavaScriptを使う方法(onclick="document.frm.submit();")が簡単なので、それを多用しています。実は、中々この方法がわからず、最後はJavaScriptを各ボタン毎に書かないといけないだろうかと思いながら、でもこれはhtmlじゃなくtplのbottle用のファイルだから動くんだろうかと悩んでいました。
結局、bottleを使いながらJavaScriptも使えるようですがちょっと特別なお作法があるようです。今回は処理的なところはPythonでやりたかったので、色々調べてみました。