前回、Adafruit.PCA9685ライブラリを使ってPythonでサーボを動かしてみました。↓
今回は次のステップ、「ブラウザでサーボの操作」をしてみようと思います。
ラズパイには「webopi」というWEBサーバーの機能をインストールすることができるので、それを使って、ブラウザからサーボモーターを動かす仕組みを作ります。
モーターの角度はテスト段階なので適当にしていますが、一応こんな感じで、ちゃんとブラウザからi2c制御でサーボモーターが動いています。↓
なお、webiopiのインストールについては過去記事でまとめているので、そちらを参考願います。↓
目次
はじめに
おおまかな構成
ざくっとした内容ですが、今回は図のような流れでサーボモーターを動かす仕組みを作ります。
①は、ラズパイに立てたWEBサーバー「webopi」になります。これのドキュメントルートに操作画面となるHTMLを作成します。
②は、webiopi独自のライブラリが使えるjavascript(jquery)で、Pythonのマクロを実行させるプログラムを作成します。
③は、PCA9685を制御し、サーボモーターを動作させるプログラムをPythonで作成します。
それでは、順を追ってまとめていきます。
HTML
motor.html
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 |
<!DOCTYPE html> <html lang="ja"> <head> <meta charset="utf-8"> <title>サーボテスト</title> <meta name="viewport" content="width=device-width, initial-scale=1"> <link rel="stylesheet" href="style.css"> <script type="text/javascript" src="/webiopi.js"></script> <script type="text/javascript" src="motor.js"></script> </head> <body> <div class="cam"> <img src="http://192.168.11.20:8080/?action=stream"/> </div> <main> <ul> <div class="a"> <li id="forward" class="ledoff">前進</li> </div> <div class="bc"> <div class="b"> <li id="left" class="ledoff">左旋回</li> </div> <div class="n"> <li></li> </div> <div class="c"> <li id="right" class="ledoff">右旋回</li> </div> </div> <div class="d"> <li id="backward" class="ledoff">後退</li> </div> </ul> </main> </body> </html> |
ラズパイのGPIOや、その他接続したデバイスの制御(i2cなど)は、最終的にPythonで実行する事になります。
つまり、Pythonにブラウザから発生したイベントの情報を伝えなければいけません。
今回の場合は、javascripからPythonの関数を呼び出す事になります。
通常であれば、呼び出しは困難になりますが、webiopi独自のjavascriptライブラリを利用することで、それが可能になります。API的な役割を果たしてくれるんですね。
したがって、head部では、webiopi独自のjsライブラリwebiopi.jsを読み込んでいます。(このライブラリはwebiopiインストール時に自動で生成されています。)
もう1つのmotor.jsは、そのライブラリを使った「イベント発生プログラム」になります。今回は外部スクリプトとして作成します。
body部に上下左右のボタンを配置し、イベント発生の為にIDを振っておきます。
なお、画面上部にカメラ画像(ストリーミング映像)を反映させる予定なので、カメラのスクリプトを埋め込んでいます。
念のため、cssも書いておきます。
CSS
style.css
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 |
@charset "utf-8"; * { margin: 0px; padding: 0px; } body { max-width: 600px; font-size: 25px; width: 100%; } img { width: 100%; } main { height: 40vh; background: skyblue; } ul { display: block; height: 40vh; list-style: none; padding-top: 10px; } .bc { display: flex; } .a ,.bc, .d { height: 12vh; } li { width: 100px ; height: 90% ; margin-left: auto; margin-right: auto; background: yellow; } .b { margin-left: auto; } .c { margin-right: auto; } ul li { text-align: center; } .a li, .b li, .c li, .d li { border: solid 1px; } .ledon { background: #f88888; } .n li { background: skyblue; } a:active { color: #ff2020; } |
javascript
motor.js
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 61 62 63 64 65 66 67 68 69 |
w().ready(function() { direction = "STOP"; // 関数:モーターを動かすマクロ呼び出し function change_direction(mode) { direction = mode; if(mode == "FOWARD") { // 前進 w().callMacro('FW'); } else if(mode == "BACKWARD") { // 後退 w().callMacro('BK'); } else if(mode == "RIGHT") { // 右旋回 w().callMacro('RT'); } else if(mode == "LEFT") { // 左旋回 w().callMacro('LT'); } else { // 停止 w().callMacro('ST'); } } // 「前進」ボタンが押されたときのイベント処理 $('#forward').bind(BUTTON_DOWN, function(event) { // 押されたとき if(direction == "STOP") { $(this).addClass('ledon'); change_direction('FOWARD'); } }).bind(BUTTON_UP, function(event) { // 離したとき $(this).removeClass('ledon'); change_direction('STOP'); }); // 「後退」ボタンが押されたときのイベント処理 $('#backward').bind(BUTTON_DOWN, function(event) { if(direction == "STOP") { $(this).addClass('ledon'); change_direction('BACKWARD'); } }).bind(BUTTON_UP, function(event) { $(this).removeClass('ledon'); change_direction('STOP'); }); // 「右」ボタンが押されたときのイベント処理 $('#right').bind(BUTTON_DOWN, function(event) { if(direction == "STOP") { $(this).addClass('ledon'); change_direction('RIGHT'); } }).bind(BUTTON_UP, function(event) { $(this).removeClass('ledon'); change_direction('STOP'); }); // 「左」ボタンが押されたときのイベント処理 $('#left').bind(BUTTON_DOWN, function(event) { if(direction == "STOP") { $(this).addClass('ledon'); change_direction('LEFT'); } }).bind(BUTTON_UP, function(event) { $(this).removeClass('ledon'); change_direction('STOP'); }); }); |
W(){ … } で囲まれていますが、これがwebiopi独自のライブラリを使用しているという事になります。
記述はjqueryになります。
HTMLボタンを押せば、bind関数で前後左右の、押したボタンの情報がwebiopi().callMacro(引数)によってPythonへ引き渡され、引数の名前がPython側で関数として実行されます。
Python
macro-pwm.py
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 |
# coding: utf-8 import webiopi import time from webiopi import deviceInstance pwm0 = deviceInstance("pwm0") @webiopi.macro def FW(): pwm0.pwmWriteAngle(0, 60) time.sleep(1) pwm0.pwmWriteAngle(0, -60) time.sleep(1) @webiopi.macro def BK(): pwm0.pwmWriteAngle(1, 60) time.sleep(1) pwm0.pwmWriteAngle(1, -60) time.sleep(1) @webiopi.macro def RT(): pwm0.pwmWriteAngle(2, 60) time.sleep(1) pwm0.pwmWriteAngle(2, -60) time.sleep(1) @webiopi.macro def LT(): pwm0.pwmWriteAngle(3, 60) time.sleep(1) pwm0.pwmWriteAngle(3, -60) time.sleep(1) @webiopi.macro def ST(): pwm0.pwmWriteAngle(0, 0) pwm0.pwmWriteAngle(1, 0) pwm0.pwmWriteAngle(2, 0) pwm0.pwmWriteAngle(3, 0) time.sleep(1) pwm0.pwmWriteAngle(0, 0) pwm0.pwmWriteAngle(1, 0) pwm0.pwmWriteAngle(2, 0) pwm0.pwmWriteAngle(3, 0) time.sleep(1) |
先頭でwebiopiのライブラリをインポートしています。これが無いと正常に動作しないので、必須です。
なお、Adafruit.PCA9685のライブラリを使用していませんが、これはwebiopiでは使えないからです。
webiopiでは、PCA9685デバイスを使用する場合、別途専用のライブラリを用意しているので、それをインポートして使います。deviceInstance ですね。
@webiopi.macroの部分が、javascriptからの呼び出し部分になります。
したがって、この部分に前後左右の関数を作成します。サーボモーターを動かしている部分ですね。
Adafruit.PCA9685のライブラリを使用していないので、記述が前回と異なっています。
ちなみにpwmWriteAngleの引数は、第一引数がサーボのチャンネルで、第二引数が角度になります。今回はとりあえず動作テストなので、設定は適当にしています。
deviceInstanceのライブラリを使用しているので、それに見合った記述が必要ですが、webiopiのチュートリアルに詳細が色々載っているので、そこから引用すれば問題ないかと思います。↓
http://webiopi.trouch.com/PCA9685.html
その他修正
webiopiは設定が色々面倒で、今回のような場合だと他にもいくつか修正しなければこのままでは使えません。
それでは最後に、修正する部分を順にまとめていきます。
WebIOPi追加設定
WebIOPiのコンテンツは一般ユーザーでは編集できないので、編集が可能になるように、ターミナルでホームディレクトリ配下にWebIOPiのディレクトリを作成します。
1 2 |
$cd $mkdir webiopi |
インストール済みのWebIOPiのサンプルファイルを先ほど作成したディレクトリへコピー。
1 |
$cp -r /usr/share/webiopi/htdocs/* webiopi/ |
次に、html,css,javascript,pythonの先ほど作成したプログラムを格納するディレクトリをwebiopi配下に作ります。名前は自由ですが、今回は「motor」としておきます。(作成後、格納しておいてください。)
1 |
$mkdir webiopi /motor |
ドキュメントルート(公開サーバ)の変更。コンフィグファイルを開きます。※2019年7月以前のOSを利用している場合は「leafpad」で開きます。
1 |
$sudo mausepad /etc/webiopi/config |
#doc-root、、、の記述部分を、以下のように変更します。
1 2 3 |
#doc-root = /home/pi/webiopi/examples/scripts/macros ↓ doc-root = /home/pi/webiopi |
マクロを記述したプログラムを読み込ませる必要があるので、【SCRIPTS】セクションに、macro-pwm.pyのプログラム名を以下のように1行付け足します。
1 2 3 |
#myscripts = /home/pi/webiopi/examples/scripts/macros/script.py これの下に追加 myscript = /home/pi/webiopi/motor/macro-pwm.py |
最後に、[DEVICES]の項目にある以下の行のコメントアウトをはずします。
これを有効にしておかないと、PCA9685がWebiopiで動作してくれません。
1 |
pwm0 = PCA9685 |
以上です。
動作テスト
ターミナルからwebiopiを立ち上げます。
1 |
$sudo service webiopi start |
後はラズパイのwebサーバにブラウザでアクセスします。↓
ラズパイのIP:8000/motor/motor.html
初期パスワードは「webiopi」「raspberry」になります。
問題なければ、下記画面のようになり、操作も可能なはずです。
※カメラについては、mjpgstreamerを利用していますが、過去記事にてまとめているので、起動のしかたはそちらで確認願います。(カメラモジュール必須です)↓
次回から地獄になりますが、モーションの作成などに取り掛かりたいと思います。