Pythonロボティクスコース レッスン 48
テーマ.12-3 フォークリフトロボットの遠隔制御
ブラウザの画面に表示したコントローラーでフォークリフトロボットを制御しよう!
チャプター1
このレッスンで学ぶこと
このレッスンでは、テーマ.12-1やテーマ.12-2で学習したことを踏まえて、スマートフォンやタブレット端末で開いたブラウザの画面にコントローラーを表示し、それを使ってフォークリフトロボットを操作できるアプリケーションプログラムを作成します。
チャプター2
フォークリフトロボットの組み立て
スマートフォンやタブレット端末から操作するフォークリフトロボットを組み立てましょう。
【 組み立てるフォークリフトロボット 】
2. 1 組み立てに必要なパーツ
【 パーツ一覧 】
- Studuino:bit×1
- ロボット拡張ユニット×1
- 電池ボックス×1
- DCモーター×2
- サーボモーター×1
- ブロック基本四角(白)×4
- ブロックハーフA(グレー)×3
- ブロックハーフB(グレー)×1
- ブロックハーフC(白)×19
- ブロックハーフD(白)×9
- ステー×3
- 丸×1
- ギヤ大×2
- ギヤ用タイヤゴム×2
- 回転軸×4
【 アーテックブロックの形状 】
2. 2 組立説明書
以下のリンク先から組立説明書を確認しましょう。
2. 3 組み立てたフォークリフトロボットの仕組み
組み立てたフォークリフトロボットは、「リンク機構」を使ってフォークを上下する仕組みになっています。リンク機構は「節(またはリンク)」と呼ばれる棒状の部材を回転可能なジョイントで連結した機構です。モーターに接続した原動節を動かすことで、中間節で連結した従動節が回転したり揺動(揺れるような動き)したりするのが特長です。
リンク機構はいくつかの種類があり、このロボットのように、向かい合う節の長さを揃えた4つの節を持つリンク機構を「並行クランク機構」と呼びます。
並行クランク機構は、平行四辺形の形を維持したまま動かすことができるという特長があり、このおかげでフォークを地面と水平な状態を保ったまま上下に移動できるようになっています。
チャプター3
フォークリフトロボットを遠隔で操作するためのアプリケーションの作成
スマートフォンやタブレット端末には、以下の操作画面を表示します。各ボタンをタップすると、対応した動作を行うようにブラウザからStuduino:bitへリクエストを送信します。また、ボタンから指を離したときも、動きを止めるようにリクエストを送ります。
前回のレッスンで作成したアプリケーションと同様に、このコントローラーのアプリケーションも、ブラウザ上で動くクライアント側のプログラムとWebサーバー側(Studuino:bit)のプログラムに分かれています。クライアント側のプログラムは事前に用意したものをそのまま使い、Webサーバー側のプログラムを一から作成していきましょう。
3. 1 クライアント側のプログラムの準備
Muエディタに以下のソースコードを貼り付けて、「forklift.html」という名前で保存しましょう。保存するときはファイルの種類で「Other(*.*)
」を選択してください。また、保存したファイルは、Muエディタのメニューの「ファイル」を選択して、Studuino:bitに転送しましょう。
※これはHTMLファイルです。
【 サンプルコード 3-1-1 (forklift.html) 】
<!doctype html>
<html>
<head>
<meta charset="utf-8">
<meta name="viewport" content="width=device-width,initial-scale=1">
<title>Forklift controller</title>
<style>
body{
width: 100%;
height: 100%;
margin: auto;
font-family: Tahoma, Geneva, "sans-serif";
}
div#main{
position: absolute;
width: 100%;
height: 100%;
}
#main button{
position: absolute;
display: block;
width:20%;
height:20%;
color: #ffffff;
font-size:2em;
border: none;
border-radius: 5px;
transform: translateY(-50%) translateX(-50%);
-webkit- transform: translateY(-50%) translateX(-50%);
}
button.vehicle{
background-color: #036EB8;
}
button.lift{
background-color: #F39800;
}
button#moveForward{
left:25%;
top:33%;
}
button#moveBackward{
left:25%;
top:66%;
}
button#rotateLeft{
left:50%;
top:33%;
}
button#rotateRight{
left:50%;
top:66%;
}
button#liftUp{
left:75%;
top:33%;
}
button#liftDown{
left:75%;
top:66%;
}
</style>
</head>
<body>
<div id="main">
<button id="moveForward" class="button vehicle" type="button">Move<br>forward</button>
<button id="moveBackward" class="button vehicle" type="button">Move<br>backward</button>
<button id="rotateLeft" class="button vehicle" type="button">Rotate<br>left</button>
<button id="rotateRight" class="button vehicle" type="button">Rotate<br>right</button>
<button id="liftUp" class="button lift" type="button">Lift<br>up</button>
<button id="liftDown" class="button lift" type="button">Lift<br>down</button>
</div>
</body>
<script>
window.onload = function(){
const req = new XMLHttpRequest();
const buttonElms = document.getElementsByClassName("button");
req.onreadystatechange = function(){
if(this.readyState == 4){
if(this.satus == 200){
console.log(req.responseText);
}
}
};
function httpRequest(evt){
evt.preventDefault();
let elm = evt.target;
let evtType = evt.type;
let id = elm.id;
let className = elm.className;
let operation = null;
if(evtType=="mousedown" || evtType=="touchstart"){
operation = id;
}
else if(className.indexOf("vehicle") != -1){
operation = "brake";
}
else if(className.indexOf("lift") != -1){
operation = "liftStop";
}
if (operation){
req.open("GET", `forklift.py?operation='${operation}'`, true);
req.send(null);
}
};
for(let i=0; i < buttonElms.length; i++){
buttonElms[i].addEventListener("mousedown", httpRequest, false);
buttonElms[i].addEventListener("touchstart", httpRequest, false);
buttonElms[i].addEventListener("mouseup", httpRequest, false);
buttonElms[i].addEventListener("touchend", httpRequest, false);
}
};
</script>
</html>
このプログラムは、ブラウザの画面に表示されたボタンをタップしたときと、ボタンから指を離したときで、それぞれ次のリクエスト行を含むリクエストを送信します。
【 ボタンをタップしたときに送信されるリクエスト行 】
GET /forklift.py?operation=%27moveForward%27 HTTP/1.1
</pre>
##### 【 ボタンから指を離したときに送信されるリクエスト行 】
GET /forklift.py?operation=%27brake%27 HTTP/1.1
</pre>
URLに含まれる「operation=%27moveForward%27
」や「operation=%27brake%27
」がフォークリフトロボットが行う動作です。ちなみに、これらに含まれる「%27
」は、URLに使えない「'
(シングルクォーテーション)」をパーセントエンコーディングしたものでした。
このoperation
というパラメータには、タップしたボタンによって次の値が設定されます。
【 operationの値とフォークリフトロボットの動作 】
|operation
の値|ロボットの動作|画面で行う操作|
|:—|:—|:—|
|"moveForward"
|前進|「Move forward」のボタンをタップしたとき|
|"moveBackward"
|後退|「Move backward」のボタンをタップしたとき|
|"rotateLeft"
|左回転|「Rotate left」のボタンをタップしたとき|
|"rotateRight"
|右回転|「Rotate right」のボタンをタップしたとき|
|"brake"
|ブレーキをかけて停止|上の4つのうち、いずれのボタンから指を離したとき|
|"liftUp"
|フォークを上げる|「Lift up」のボタンをタップしたき|
|"liftDown"
|フォークを下げる|「Lift down」のボタンをタップしたき|
|"liftStop
|フォークの移動を停止|上の2つのうち、いずれかのボタンから指を離したとき|
Webサーバー側のプログラムでは、これらの値を確認して、対応する動作を行うコードを実行します。
3. 2 Webサーバー側のプログラムの作成
ここでは、2個のDCモーターを制御して車体の前後左右の動きを決める処理と、サーボモーターを制御してフォークを上下に移動する処理に分けてプログラムを作成していきます。
■ 車体の前後左右の動きを制御する処理
この制御プログラムには、テーマ.6で車型ロボットを製作したときに用意した以下のモジュール(vehicle.py
)が利用できます。このモジュールは「前進」「後退」「回転」などの動きを行うためのメソッドをもつVehicleRobot
クラスが定義されています。このクラスの詳細については、テーマ.6-1を見直してください。
【 サンプルコード 3-2-1(vehicle.py) 】 ※すでにあります。
from pyatcrobo2.parts import DCMotor
import time
class VehicleRobot:
def __init__(self, *, pin_l, pin_r):
self.dcm_l = DCMotor(pin_l)
self.dcm_r = DCMotor(pin_r)
self.dcm_l.power(100)
self.dcm_r.power(100)
def time_control(func): # 時間制御を行うためのデコレータ
def new_func(self, duration=-1):
func(self)
if duration > 0:
time.sleep_ms(int(duration))
self.brake()
return new_func
@time_control
def move_forward(self): # 前進
self.dcm_l.ccw()
self.dcm_r.ccw()
@time_control
def move_backward(self): # 後退
self.dcm_l.cw()
self.dcm_r.cw()
@time_control
def rotate_left(self): # 左回転
self.dcm_l.cw()
self.dcm_r.ccw()
@time_control
def rotate_right(self): # 右回転
self.dcm_l.ccw()
self.dcm_r.cw()
@time_control
def curve_left(self): # 左に曲がる
self.dcm_l.brake()
self.dcm_r.ccw()
@time_control
def curve_right(self): # 右に曲がる
self.dcm_l.ccw()
self.dcm_r.brake()
def stop(self): # ゆっくりと停止
self.dcm_l.stop()
self.dcm_r.stop()
def brake(self): # ブレーキをかけてピタッと停止
self.dcm_l.brake()
self.dcm_r.brake()
def set_speed_to_left(self, speed): # 左側のDCモーターの速さを設定
speed = int(speed)
speed = 1 if speed < 1 else speed
speed = 10 if speed > 10 else speed
power = speed * 25
self.dcm_l.power(power)
def set_speed_to_right(self, speed): # 右側のDCモーターの速さを設定
speed = int(speed)
speed = 1 if speed < 1 else speed
speed = 10 if speed > 10 else speed
power = speed * 25
self.dcm_r.power(power)
そこで、このプログラムファイルをStuduino:bitに転送し、新しく作成するプログラム内でインポートして利用します。
以下のリンク上で右クリックをし、「名前を付けてリンク先を保存」を選択して「mu_code」のフォルダにダウンロードするか、Muエディタに上記のコードを貼り付けて、「vehicle」(※拡張子は.py)と名前を付けて保存しましょう。保存できたら、Muエディタの「ファイル」からStuduino:bitへ転送します。(すでにある場合は上書き保存してください)
転送が完了したら、リクエストに応じて車体の前後左右の動きを制御するプログラムを作成します。まずは、新規作成したファイルの先頭で、保存したvehicle
モジュールをインポートし、VehicleRobot
クラスのオブジェクトを作成しましょう。
import vehicle
robo = vehicle.VehicleRobot(pin_l="M2", pin_r="M1") # pin_lが左側のDCモーターでpin_rが右側のDCモーター
次に、クライアントから送られてくるパラメータ(operation
)として、有効な値を集合で定義します。車体の動作に関するものは次の5つです。もし、この集合にない値が送られてきた場合は、その命令が無効であることを示すメッセージを返します。
追加【5~11行目】
import vehicle
robo = vehicle.VehicleRobot(pin_l="M2", pin_r="M1")
OPERATIONS = {
"moveForward", # 前進
"moveBackward", # 後退
"rotateLeft", # 左回転
"rotateRight", # 右回転
"brake", # ブレーキをかけて停止
}
リクエストを受け取ったときに呼び出されるmain()
関数を定義します。この関数は引数operation
に渡された値が、上で定義した集合OPERATIONS
に含まれているかどうかをチェックして、レスポンスでメッセージを送信します。以下のコードを追加しましょう。
追加【13~18行目】
import vehicle
robo = vehicle.VehicleRobot(pin_l="M2", pin_r="M1")
OPERATIONS = {
"moveForward",
"moveBackward",
"rotateLeft",
"rotateRight",
"brake",
}
def main(operation):
if operation in OPERATIONS: # 正常な値が渡されたとき
# ここにロボットを制御する処理を追加する
return "'{}' succeeded.".format(operation), "text/plain" # 成功のメッセージを返す
else: # 正常でない値が渡されたとき
return "Failed. '{}' is invalid.".format(operation), "text/plain" # 失敗のメッセージを返す
引数operation
に有効な値が渡されたときは、3行目で作成したVehicleRobot
クラスのオブジェクトのメソッドを実行します。次のようにコードを追加しましょう。
追加【15~24行目】 ※前のコードに挿入。
import vehicle
robo = vehicle.VehicleRobot(pin_l="M2", pin_r="M1")
OPERATIONS = {
"moveForward",
"moveBackward",
"rotateLeft",
"rotateRight",
"brake",
}
def main(operation):
if operation in OPERATIONS:
if operation == "moveForward": # 前進
robo.move_forward()
elif operation == "moveBackward": # 後退
robo.move_backward()
elif operation == "rotateLeft": # 左回転
robo.rotate_left()
elif operation == "rotateRight": # 右回転
robo.rotate_right()
elif operation == "brake": # 停止
robo.brake()
return "'{}' succeeded.".format(operation), "text/plain"
else:
return "Failed. '{}' is invalid.".format(operation), "text/plain"
ここまでで書いたコードの動作に問題がないかどうかを確認しておきます。「forklift
(.py)」と名前を付けて保存し、Muエディタの「ファイル」からStuduino:bitに転送しましょう。
(後で使うのでこのまま開いておいてください)
そして、前のレッスンで保存しておいたWebサーバーのプログラムを開き、冒頭のコードを次のように変更します。前のレッスンで作成したアプリケーションのモジュールはここでは使わないため、すべてコメント化しておきましょう。
変更・追加【3~7行目、13~17行目】※前回lesson47の最後に作成したプログラムを開いて、以下のように修正してください。コメントアウトするだけです。このプログラムだけをコピペしても動きませんので注意。
import network
import usocket
# import monitor # 使用しないアプリケーションをコメント化
# import control_display
# import control_servo
# import control_buzzer
import forklift # 追加
SSID = "studuinobit"
PASSWORD = "Artecrobo2"
modules = {
# "/monitor.py": "monitor",
# "/control_display.py": "control_display",
# "/control_servo.py": "control_servo",
# "/control_buzzer.py": "control_buzzer",
"/forklift.py": "forklift", # 追加
}
では、Webサーバーのプログラムを実行して、手元のスマートフォンまたはタブレット端末を、Wi-FiのアクセスポイントになっているStuduino:bitに接続しましょう。接続ができたら、ブラウザを開いて、以下のURLを指定してリクエストを送ります。※もし、うまく表示されなかったら一旦ブラウザを閉じ、もう一度URLを入力し直してください。
http://192.168.4.1/forklift.html
表示された画面で、青色のボタンをタップすると、その通りに車体が動作することを確認しましょう。
■ フォークを上下に移動する処理
こちらも以前学習したテーマで作成したモジュールを利用して、プログラムを作成します。利用するのは、テーマ.8-1で用意したmyservo.py
です。このモジュールを使うことで、サーボモーターの回転の速さを調整したり、複数のサーボモーターを同期制御したりすることができました。
【 サンプルコード 3-2-1(myservo.py) 】
import time
from pyatcrobo2.parts import Servomotor
# サーボモーターを同期制御するための独自クラス
class MyServomotor(Servomotor): # 既存のServomotorクラスを継承
def __init__(self, pin):
super().__init__(pin)
self.angle = 90 # 現在の角度を記録しておくためのプロパティ
super().set_angle(self.angle)
def set_angle(self, degree): # 親クラスのメソッドをオーバーライド
super().set_angle(degree)
self.angle = degree
def get_angle(self): # 現在の角度を返すメソッド
return self.angle
# 複数のサーボモーターを動作時間を決めて同期制御する関数
def syncRotateServos(duration, *args): # argsには、MyServomotorクラスのインスタンスと回転後の角度のタプルを渡す
speeds = {}
for arg in args: # 各サーボモーターの単位時間(1ミリ秒)あたりに回転させる角度を求める
myservo = arg[0]
degree = arg[1]
diff = degree - myservo.get_angle()
speeds[id(myservo)] = diff/duration
for _ in range(duration): # 1ミリ秒おきに上で求めた角度だけ回転させる
for arg in args:
myservo = arg[0]
degree = myservo.get_angle() + speeds[id(myservo)]
myservo.set_angle(degree)
time.sleep_ms(1)
ここではこのモジュールを、フォークの上下移動の速度を調整するために使います。以下のリンク上で右クリックをし、「名前を付けてリンク先を保存」を選択して「mu_code」のフォルダにダウンロードするか、Muエディタに上記のコードを貼り付けて、「myservo」(※拡張子は.py)と名前を付けて保存しましょう。保存できたら、Muエディタの「ファイル」からStuduino:bitへ転送します。
転送が完了したら、forklift.py
にフォークの上下移動を制御するためのコードを追加していきます。まずは、先頭でmyservo
モジュールをインポートして、MyServomotor
クラスのオブジェクトを作成しましょう。
追加【2行目、5行目】
import vehicle
import myservo
robo = vehicle.VehicleRobot(pin_l="M2", pin_r="M1")
servo = myservo.MyServomotor("P13")
次に、フォークを上下移動するときに、リクエストで送られてくるパラメータoperation
に設定される値を集合OPERATIONS
に追加します。
追加【13~15行目】
OPERATIONS = {
"moveForward",
"moveBackward",
"rotateLeft",
"rotateRight",
"brake",
"liftUp", # フォークを上に移動する
"liftDown", # フォークを下に移動する
"liftStop", # フォークの移動を停止する
}
サーボモーターは、フォークを上や下に移動するリクエストが送られてきてから停止のリクエストが送られてくるまで、一定時間おきに1度ずつ動かします。フォークを上に移動させる処理をlift_up()
関数、下に移動させる処理をlift_down
関数としてそれぞれ定義しますが、ここで2点考慮しなければならないことがあります。
一つ目は、サーボモーターの可動範囲です。サーボモーター自体は0度から180度まで回転させることができますが、実際にはフォークと床の接触やリンクどうしの衝突があるため、それよりも狭い範囲でしか動かすことができません。ここでは、下限を「50°」に、上限を「150°」にしておきましょう。
二つ目は、そのままではサーボモーターを繰り返し動かしている間は、他の処理が止まってしまうという問題です。リクエストを受け取る処理も止まるため、停止のリクエストが受け取れず、好きな位置でフォークを止めることができません。そこで、_thread
モジュールを使い、別のスレッドを立ち上げてサーボモーターを制御します。また、スレッドとの間で停止の命令が届いたかどうかの情報を共有するためにグローバル変数を用意します。
以上のことを踏まえて、次の3つの関数を作成します。
lift_up()
ボタンから指が離されるまで(停止のリクエストが送られるまで)、角度の上限を150°としてフォークを繰り返し上に移動する処理。lift_down()
ボタンから指が離されるまで(停止のリクエストが送られるまで)、角度の下限を50°としてフォークを繰り返し下に移動する処理。stop_lifting()
ボタンから指が離されたときに(停止のリクエストが送られてきたときに)、別スレッドで動いているlift_up()
やlift_down()
と共有するグローバル変数の値を変更する処理。
それぞれの関数を次のコードで定義しましょう。
追加【6行目、20~27行目、30~37行目、40~42行目】
import vehicle
import myservo
robo = vehicle.VehicleRobot(pin_l="M2", pin_r="M1")
servo = myservo.MyServomotor("P13")
stop_signal = False # 停止のリクエストを受信したかどうかを共有するための変数
OPERATIONS = {
"moveForward",
"moveBackward",
"rotateLeft",
"rotateRight",
"brake",
"liftUp",
"liftDown",
"liftStop",
}
def lift_up(): # フォークを上に移動する処理
global stop_signal # グローバル変数を参照する
stop_signal = False # 停止のリクエストではないのでFalseにする
angle = servo.get_angle() # サーボモーターの現在の角度を取得
while not stop_signal: # 停止のリクエストがあるまで繰り返す
if angle < 150: # 移動の上限を設定
angle += 1 # 1度ずつ増やす
myservo.syncRotateServos(20, (servo, angle)) # インポートしたモジュールの関数を使ってゆっくりと動かす
def lift_down(): # フォークを下に移動する処理
global stop_signal
stop_signal = False
angle = servo.get_angle()
while not stop_signal:
if angle > 50: # 移動の下限を設定
angle -= 1 # 1度ずつ減らす
myservo.syncRotateServos(20, (servo, angle))
def stop_lifting(): # フォークの移動を停止する処理
global stop_signal
stop_signal = True # これをTrueに変えることで、別スレッドで処理されている24行目や34行目のwhile文を抜けることができる
そして最後にmain()
関数へ、上で定義した3つの関数を実行するコードを追加してプログラムの完成です。
【 サンプルコード 3-2-1 (forklift.py)】
追加【3行目、58~63行目】
import vehicle
import myservo
import _thread # マルチスレッド用のモジュールをインポート
robo = vehicle.VehicleRobot(pin_l="M2", pin_r="M1")
servo = myservo.MyServomotor("P13")
stop_signal = False
OPERATIONS = {
"moveForward",
"moveBackward",
"rotateLeft",
"rotateRight",
"brake",
"liftUp",
"liftDown",
"liftStop",
}
def lift_up():
global stop_signal
stop_signal = False
angle = servo.get_angle()
while not stop_signal:
if angle < 150:
angle += 1
myservo.syncRotateServos(20, (servo, angle))
def lift_down():
global stop_signal
stop_signal = False
angle = servo.get_angle()
while not stop_signal:
if angle > 50:
angle -= 1
myservo.syncRotateServos(20, (servo, angle))
def stop_lifting():
global stop_signal
stop_signal = True
def main(operation):
if operation in OPERATIONS:
if operation == "moveForward":
robo.move_forward()
elif operation == "moveBackward":
robo.move_backward()
elif operation == "rotateLeft":
robo.rotate_left()
elif operation == "rotateRight":
robo.rotate_right()
elif operation == "brake":
robo.brake()
elif operation == "liftUp": # フォークを上に移動
_thread.start_new_thread(lift_up, ()) # 別スレッドを立てて関数を実行
elif operation == "liftDown": # フォークを下に移動
_thread.start_new_thread(lift_down, ()) # 別スレッドを立てて関数を実行
elif operation == "liftStop": # フォークの移動を停止
stop_lifting() # この関数はそのまま実行する
return "'{}' succeeded.".format(operation), "text/plain"
else:
return "Failed. '{}' is invalid.".format(operation), "text/plain"
これでプログラムの完成です。プログラムを保存し、Muエディタの「ファイル」からStuduino:bitに転送して、前に転送したファイルを上書き保存しましょう。
一度Studuino:bitのリセットボタンを押してから、Webサーバーのプログラムを実行します。手元のスマートフォンまたはタブレット端末を、Wi-FiのアクセスポイントになっているStuduino:bitに接続できたら、ブラウザを開いて、また以下のURLを指定してリクエストを送りましょう。
http://192.168.4.1/forklift.html
表示された画面で橙色のボタンをタップして、フォークの上下移動が制御できるようになったことを確認しましょう。
3. 3 フォークリフトロボットを操作して簡単な作業を行う
すべての動作が問題なく制御できることが確認できたら、フォークリフトロボットを遠隔で操作して、ブロックを運ぶ作業を行ってみましょう。
また、この作業を行う前に、Muエディタの「転送」からWebサーバーのプログラムをStuduino:bitへ転送して実行しましょう。
※ Muエディタの「実行」からWebサーバーのプログラムを実行した場合、REPL機能によるPCとの通信の影響で処理が遅くなってしまい、スムーズにフォークリフトロボットを操作することができません。
ブロックは次の形を組み立ててください。
【 運ぶブロックの形 】
このブロックは、次の操作手順で持ち上げることができます。
【 ブロックを持ち上げるときの操作手順 】
- フォークを下げる
- 車体を前進して、フォークの内側にブロックを入れる
- フォークを上げる
操作に慣れてきたら、ばらばらに置いた3つのブロックの積み上げに挑戦してみましょう。
チャプター4
課題:ボタンからスライダーに変更してリフトを操作する
この課題では、画面上の部品をボタンからスライダーに変更して、リフトを操作できるようにします。クライアント側のプログラムは用意されたものを使用して、Webサーバー側(Studuino:bit)のプログラムを自分で考えて作成しましょう。
4. 1 クライアント側のプログラムの準備
Muエディタに以下のソースコードを貼り付けて、「forklift_slider.html」という名前で保存しましょう。保存するときはファイルの種類で「Other(*.*)
」を選択してください。また、保存したファイルはMuエディタのメニューの「ファイル」を選択して、Studuino:bitに転送しましょう。
【 サンプルコード 4-1-1(forklift_slider.html) 】
<!doctype html>
<html>
<head>
<meta charset="utf-8">
<title>Forklift controller</title>
<style>
body{
width: 100%;
height: 100%;
margin: auto;
font-family: Tahoma, Geneva, "sans-serif";
}
div#main{
position: absolute;
width: 100%;
height: 100%;
}
#main button{
position: absolute;
display: block;
width:20%;
height:20%;
color: #ffffff;
font-size:2em;
border: none;
border-radius: 5px;
transform: translateY(-50%) translateX(-50%);
-webkit- transform: translateY(-50%) translateX(-50%);
}
button.vehicle{
background-color: #036EB8;
}
button.lift{
background-color: #F39800;
}
button#moveForward{
left:25%;
top:33%;
}
button#moveBackward{
left:25%;
top:66%;
}
button#rotateLeft{
left:50%;
top:33%;
}
button#rotateRight{
left:50%;
top:66%;
}
input#slider{
-webkit-appearance: none;
appearance: none;
outline: none;
position: absolute;
display: block;
width: calc(100vh*3/4);
height: 3em;
left: 80%;
top: 50%;
background-color: #cccccc;
border-radius: 1.5em;
-webkit- transform: translateY(-50%) translateX(-50%) rotate(270deg);
transform: translateY(-50%) translateX(-50%) rotate(270deg);
}
input#slider::-webkit-slider-thumb {
-webkit-appearance: none;
background-color: #2EA7E0;
width: 3em;
height: 3em;
border: solid 2px #ffffff;
border-radius: 50%;
box-shadow: 0px 3px 6px 0px rgba(0, 0, 0, 0.15);
}
input#slider::-moz-range-thumb {
background-color: #2EA7E0;
width: 3em;
height: 3em;
border: solid 2px #ffffff;
border-radius: 50%;
box-shadow: 0px 3px 6px 0px rgba(0, 0, 0, 0.15);
border: none;
}
</style>
</head>
<body>
<div id="main">
<button id="moveForward" class="button vehicle" type="button">Move<br>forward</button>
<button id="moveBackward" class="button vehicle" type="button">Move<br>backward</button>
<button id="rotateLeft" class="button vehicle" type="button">Rotate<br>left</button>
<button id="rotateRight" class="button vehicle" type="button">Rotate<br>right</button>
<input id="slider" type="range" value="90" min="50" max="150" step="1">
</div>
</body>
<script>
window.onload = function(){
const req = new XMLHttpRequest();
const buttonElms = document.getElementsByClassName("button");
const slider = document.getElementById("slider");
req.onreadystatechange = function(){
if(this.readyState == 4){
if(this.satus == 200){
console.log(req.responseText);
}
}
};
function httpRequest(evt){
evt.preventDefault();
let elm = evt.target;
let evtType = evt.type;
let id = elm.id;
let className = elm.className;
let operation;
if(evtType=="mousedown" || evtType=="touchstart"){
operation = id;
}
else if(className.indexOf("vehicle")){
operation = "brake";
}
else{
operation = null;
}
if (operation){
req.open("GET", `forklift_slider.py?operation='${operation}'`, true);
req.send(null);
}
};
for(let i=0; i < buttonElms.length; i++){
buttonElms[i].addEventListener("mousedown", httpRequest, false);
buttonElms[i].addEventListener("touchstart", httpRequest, false);
buttonElms[i].addEventListener("mouseup", httpRequest, false);
buttonElms[i].addEventListener("touchend", httpRequest, false);
}
slider.addEventListener("input", (evt)=>{
let elm = evt.target;
let val = elm.value;
req.open("GET", `forklift_slider.py?operation='lift'&angle=${val}`, true);
req.send(null);
});
};
</script>
</html>
4. 2 プログラム作成のヒント
上のプログラムは、画面上のスライダーを操作したときに以下のリクエスト行を含むリクエストを送信します。
GET /forklift.py?operation=%27lift%27&angle=90 HTTP/1.1
クエリパラメーター(operation=%27lift%27&angle=90
)を確認すると、operation
にlift
が設定されていて、さらにangle
が新たなパラメータとして追加されています。つまり、operation
がフォークの移動を行う"lift"
のときだけ、angle
にサーボモーターの角度が指定され、"lift"
以外のときはこのパラメータはありません。
そこで、次の車体を制御するところまでの処理を書いたプログラムへ、operation
が"lift"
のときにサーボモーターの角度をangle
にするコードを追加してプログラムを完成させましょう。
完成したプログラムは、必ず「forklift_slider
(拡張子は.py)」と名前を付けて保存し、Muエディタの「ファイル」からStuduino:bitへ転送してください。
【 サンプルコード 4-2-1 】
import vehicle
import myservo
robo = vehicle.VehicleRobot(pin_l="M2", pin_r="M1")
servo = myservo.MyServomotor("P13")
OPERATIONS = {
"moveForward",
"moveBackward",
"rotateLeft",
"rotateRight",
"brake",
}
def main(operation):
if operation in OPERATIONS:
if operation == "moveForward":
robo.move_forward()
elif operation == "moveBackward":
robo.move_backward()
elif operation == "rotateLeft":
robo.rotate_left()
elif operation == "rotateRight":
robo.rotate_right()
elif operation == "brake":
robo.brake()
return "'{}' succeeded.".format(operation), "text/plain"
else:
return "Failed. '{}' is invalid.".format(operation), "text/plain"
4. 3 プログラムの改造例
まずは、集合OPERATIONS
に、パラメータoperation
で有効な値として"lift"
を追加します。
追加【13行目】
OPERATIONS = {
"moveForward",
"moveBackward",
"rotateLeft",
"rotateRight",
"brake",
"lift", # フォークを制御するときのパラメータの値を追加
}
次に、main()
関数の引数に新たにangle
を追加します。この引数は"lift"
以外の場合は渡されないため、その場合でもエラーとならないように、デフォルト値としてNone
を設定しておきましょう。
変更【17行目】
def main(operation, angle=None):
そして、指定された角度へサーボモーターを動かすコードをmain()
関数内に追加します。以上でプログラムの完成です。
【 サンプルコード 4-3-1 】
追加【29・30行目】
import vehicle
import myservo
robo = vehicle.VehicleRobot(pin_l="M2", pin_r="M1")
servo = myservo.MyServomotor("P13")
OPERATIONS = {
"moveForward",
"moveBackward",
"rotateLeft",
"rotateRight",
"brake",
"lift",
}
def main(operation, angle=None):
if operation in OPERATIONS:
if operation == "moveForward":
robo.move_forward()
elif operation == "moveBackward":
robo.move_backward()
elif operation == "rotateLeft":
robo.rotate_left()
elif operation == "rotateRight":
robo.rotate_right()
elif operation == "brake":
robo.brake()
elif operation == "lift" and angle is not None: # 追加
servo.set_angle(angle) # 追加
return "'{}' succeeded.".format(operation), "text/plain"
else:
return "Failed. '{}' is invalid.".format(operation), "text/plain"
完成したプログラムは、「forklift_slider
(拡張子は.py)」と名前を付けて保存し、Muエディタの「ファイル」からStuduino:bitに転送します。転送完了後に、Studuino:bitのリセットボタンを押して、Webサーバーのプログラムを実行しましょう。手元のスマートフォンまたはタブレット端末を、Wi-FiのアクセスポイントになっているStuduino:bitに接続できたら、ブラウザを開いて、以下のURLを指定してリクエストを送りましょう。
※webサーバーのプログラムで、
7行目変更
import forklift_slider # 追加
12~18行 変更
modules = {
# “/monitor.py”: “monitor”,
# “/control_display.py”: “control_display”,
# “/control_servo.py”: “control_servo”,
# “/control_buzzer.py”: “control_buzzer”,
“/forklift_slider.py”: “forklift”, # 変更
}
2か所変更します。
http://192.168.4.1/forklift_slider.html
チャプター5
おわりに
5. 1 このレッスンのまとめ
このレッスンでは、テーマ.12-1とテーマ.12-2で学習したことを踏まえて、Wi-Fiを利用したHTTP通信により、スマートフォンやタブレット端末から遠隔で制御できるフォークリフトロボットを製作しました。ブラウザに表示する操作パネルやリクエストやレスポンスで送信するデータの形式を工夫することで、自分で組み立てたどのようなロボットでも遠隔で制御できるようになります。
また、今回はローカルネットワーク(LAN)内での通信による遠隔制御を行いましたが、ワイドエリアネットワーク(WAN)に接続することで、地球上のどのような場所にあってもロボットと通信し、様々な情報をやり取りできるようになります。
そのためには、ネットワークやセキュリティに関するより高度な知識も必要になってきますので、将来エンジニアとして働きたいと考えている人は、ぜひ大学や専門学校などに進学したり、社会人向けのプログラミングスクールの講座を受講したりなどして、深く学ぶことをおすすめします。
5. 2 次回のレッスンについて
次のレッスンがこの講座の最終回になります。最後は、インターネット上でオープンソースとして公開されているMicroPython用のモジュールやパッケージをStuduino:bitにインストールする方法を紹介します。実際にいくつかのモジュールをインストールして利用してみましょう。