M5CAMERAでラジコン戦車作ってみた〜【Part6: picoweb使ってモーターをリモート駆動する】
はじめに
PicoWebサーバーを利用して、PCやスマホのブラウザからM5CAMERAにGroveケーブルで接続したモータを動かしてみます。これが完成すれば、いわゆるラジコンが作れたことになります。
全体構成
WiFi経由でモータを動かすイメージを図で説明します。
- スマホとM5CAMERAをそれぞれWiFiに接続
- PicoWebサーバー上で操作画面となるHPを稼働
- スマホのブラウザから操作画面のHPにアクセスしスマホで表示
- ブラウザ上でボタンを押すと、PicoWebサーバーを介してPythonプログラムが駆動
- PythonプログラムにI2C経由でモータドライバに指令
ブラウザの画面に、ON/OFFボタンを作成し押すとモータが動いて戦車のキャタピラが回るようにします。
M5CameraのPythonプログラム
電源投入後、boot.pyが自動的に実行されWiFiに接続します。boot.pyに続いてmain.pyが実行されるので、main.pyの中でPicoWebサーバを稼働させて、スマホからのアクセス待ち状態にしておきます。main.pyのプログラムは以下のようにしました。
main.py
import picoweb
import machine
app = picoweb.WebApp('app')
i2c = machine.I2C(scl=machine.Pin(13), sda=machine.Pin(4))
@app.route("/")
def index(req, resp):
yield from picoweb.start_response(resp, content_type = 'text/html')
htmlFile = open('btnCtrl.html', 'r')
for line in htmlFile:
yield from resp.awrite(line)
@app.route("/ButtonPressed")
def index(req, resp):
queryString = req.qs
equalSplit = queryString.split("=")
yield from picoweb.start_response(resp)
yield from resp.awrite(equalSplit[1])
val = int(equalSplit[1])
if val==0: #Go
i2c.writeto_mem(101, 0,b'\x45')
i2c.writeto_mem(96, 0,b'\x45')
if val==1: #Stop
i2c.writeto_mem(101, 0,b'\x7c')
i2c.writeto_mem(96, 0,b'\x7c')
app.run(debug=True, host = '192.168.0.148')
M5CAMERAのPythonプログラム解説
- import部分では、picowebとmachineをインポートして、picowebサーバーとI2Cを使えるようにしています
- @app.route("/")部分は、ブラウザからM5CAMERAのIPアドレスにアクセス時に実行されるプログラムです。ブラウザに表示されるhtmlを読み込んでブラウザからのリクエストに対し、htmlをレスポンスとして返します。
- @app.route("/ButtonPressed") この部分は、ブラウザでボタンが押された時のアクションを定義しています。押したボタンごとに異なる値をパラメータとして送るように設定しておき、受け取ったリクエストに含まれるパラメータを確認して、0の場合は前進命令を、1の場合は停止命令をI2Cを用いてモータドライバに送っています。
- app.run(debug=True, host = '192.168.0.148')の部分で、指定したIPアドレスでwebサーバーを稼働します。このアドレスにブラウザからアクセスすると、コントロール画面がレスポンスとしてブラウザ側に帰されます。
ブラウザ側のhtmlプログラム
htmlで操作画面を作ります。
- Goボタンを押した時に、Goボタンが押された事をサーバーに送る
- Stopボタンを押した時に、Stopボタンが押された事をサーバーに送る
HTMLの画面とプログラムはこんな感じ。
btnCtrol.html
<!DOCTYPE html>
<html>
<head>
<title>Remote Test</title>
</head>
<body>
<center>
<br><div id="status"> status </div><br>
<button type="button" onclick="MyFunction(this)" id="0" >Go </button>
<button type="button" onclick="MyFunction(this)" id="1" >Stop </button>
<p> @2021 Developed by Ping-Pong-USAGI</p>
</center>
</body>
<script>
function MyFunction(e){
var id_value = e.id;
var xhr = new XMLHttpRequest();
xhr.onreadystatechange = function() {
if (this.readyState == 4 && this.status == 200) {
var resp = this.responseText;
document.getElementById("status").innerHTML = resp;
}
}
xhr.open("GET", "ButtonPressed?param=" + id_value, true);
xhr.send();
}
</script>
</html>
ブラウザ側のhtmlプログラムの解説
<body>~</body>部分は画面レイアウトで、画面中央にボタンを2つ設置しています。
<script>~<script>の部分で、設置した2つのボタンクリックに対するアクションを定義しています。ボタンがクリックされると、
- id_value変数に、ボタンのidを格納
- XMLHttpRequestを生成し、非同期でmain.pyの@app.route("/ButtonPressed")を呼び出します。
@app.route("/ButtonPressed")の内容
ボタンがクリックされると、下記のプログラムが呼び出されます。
def index(req, resp):
queryString = req.qs
equalSplit = queryString.split("=")
yield from picoweb.start_response(resp)
yield from resp.awrite(equalSplit[1])
val = int(equalSplit[1])
if val==0: #Go
i2c.writeto_mem(101, 0,b'\x45')
i2c.writeto_mem(96, 0,b'\x45')
if val==1: #Stop
i2c.writeto_mem(101, 0,b'\x7c')
i2c.writeto_mem(96, 0,b'\x7c')
ブラウザ側で、GoとStopのどちらのボタンがクリックされたのかを判定するために、html側では以下のようなxmlHttpRequestのopenメソッドでid_valueとしてパラメータを渡します。
xhr.open("GET", "ButtonPressed?param=" + id_value, true);
Pythonプログラム側では、equalSplit = queryString.split("=")のように、split()関数を用いて、文字列”=” を目標にして、パラメータとして受け取った文字列を分割することで、パラメータ部分を抽出します。処理の完了を通知するために、パラメータはxml側に返されてブラウザに表示しています。パラメータの値が0の場合は戦車を前進、1の場合はストップするようにモータドライバにi2cで命令します。
動作確認
boot.py, main.py, btnCtrol.htmlの3ファイルをampyコマンドでM5Cametaに転送して再起動します。wiif接続が確立したら、ウェブブラウザから、M5CameraのIPアドレスにアクセスします。アドレスが、192.168.0.148の場合、ブラウザのアドレス入力部分に
192.168.0.148:8081/ と入力します。@app.route("/")部分が実行されて、btnCtrol.htmlが読み込まれることでブラウザに操作画面が表示されます。あとはボタンをクリックして戦車が動けばOKです。
こちらに動作確認風景を録画しました。