Pythonロボティクスコース レッスン 47
テーマ12-2 Studuino:bitの遠隔制御
ブラウザの画面を操作してStuduino:bitを遠隔制御しよう!
チャプター1
このレッスンで学ぶこと
前のレッスンでは、Studuino:bitをWi-Fiのアクセスポイントとして起動し、さらにWebサーバーとして動作させるためのプログラムの作り方を説明しました。そして、クライアントからのリクエストに応じて、HTMLファイルのデータを送信したり、センサ値を取得するPythonのプログラムを実行してその結果を返したりできることを確認しました。
このレッスンでは、クライアントから送信されたリクエストによって、Studino:bitに内蔵されているLEDディスプレイやブザー、拡張ユニットに接続したサーボモーターを制御する方法を新たに紹介します。
前のレッスンで作成したWebサーバーのプログラムを一部変更して、以下の3つのアプリケーションプログラムを動かしてみましょう。
- ブラウザの画面に表示されたフォームに入力したメッセージが、LEDディスプレイにスクロール表示されるアプリケーション
- ブラウザの画面に表示されたスライダーを操作して、サーボモーターを遠隔制御できるアプリケーション
- ブラウザの画面に表示された鍵盤の絵をタッチすると、ブザーから対応する音が鳴るアプリケーション
※ 3つ目のアプリケーションのプログラム作成は最後の課題で取り組みます。
チャプター2
Webサーバーとして動作させるプログラムの変更
次のチャプターから作成するアプリケーションプログラムをWebサーバー上で実行するために、前のレッスンで用意したWebサーバーのプログラムに以下の2つの処理を行うコードを追加します。
- クライアントからGETメソッドで送信されたURLからクエリパラメータを取得する処理
- リクエストで要求されたプログラムファイルの
main()
関数を実行するときに、上記のクエリパラメータを引数として渡す処理
1つ目の処理で扱う「クエリパラメータ」は、クライアントがWebサーバーへパス以外の情報を送るために、URLに付加されたデータです。URLの末尾に?
を付け、それ以降に「パラメータ名(変数名)=値」の形式で書きます。複数のパラメータを送りたいときは、パラメータの間を&
でつなぎます。
【 クエリパラメータを含むURLの例 】
http://192.168.4.1/example.py?param1=10¶m2=abc&...
LEDディスプレイやサーボモーターを制御するために必要な情報は、このクエリパラメータの形式でクライアントから送ってもらいます。そして、この情報をもとにプログラムを実行するために、2つ目の処理を追加します。
それでは、前のレッスンで作成したWebサーバーのプログラム(【 サンプルコード 4-2-1 】)に変更を加えていきましょう。
2. 1 プログラムの変更手順
変更するのは、make_response()
関数です。まずは、URLにクエリパラメータが含まれている場合に、ファイルまでのパスとクエリパラメータに分ける処理を追加します。
クエリパラメータが付加されているURLになっているかは、「?
」の文字が含まれているかどうかで簡易的に判断します。この文字が含まれている場合は、文字列のスライス操作を利用して?
の前後で、ファイルまでのパスとクエリパラメータに分けます。
追加・変更【72~76行目】
def make_response(req):
method_name = req[:req.find(" ")]
if method_name != "GET":
status = "HTTP/1.1 405 Method Not Allowed"
body = "{} method is unacceptable on this server.".format(method_name)
header = {
"Content-Encoding": "identity",
"Content-Length": str(len(body.encode("utf-8"))),
"Content-Type": "text/plain; charset=UTF-8",
"Server": "MicroPython"
}
else:
req_line = req[:req.find("\n")]
url = req_line[req_line.find(" ")+1:req_line.rfind(" ")]
path = url.replace("http://192.168.4.1", "")
if "?" in path: # URLに「?」が含まれている場合はクエリパラメータがある
params = path[path.find("?")+1:] # 「?」以降がクエリパラメータ
path = path[:path.find("?")] # 「?」以前がファイルまでのパス
else: # クエリパラメータがない場合
params = None # ある場合と比較できるようにNoneを入れておく
try:
file = open(path, "rt")
次に要求されたプログラムファイルを実行するコードに変更を加えます。クエリパラメータがある場合は、それをモジュールとしてインポートしたプログラムファイルのmain()
関数に引数として渡します。そのためには、クエリパラメータを関数の引数の形式に合わせて変換する必要があります。パラメータ名は関数の引数名にあらかじめ合わせておくことで変換しなくて済みます。パラメータをつなぐ「&
」は、「,
」に変換することで、引数の区切りになります。
パラメータの値が数値の場合はこれで問題ありませんが、文字列の場合はもうひとつ操作が必要になります。クエリパラメータ内では、関数の引数に合わせるために、文字列はあらかじめ「'
(シングルクォーテーション)」で囲んでおくようにします。しかし、URLではシングルクォーテーションは使えません。そのため、リクエストが送られるときに「パーセントエンコーディング」という仕組みで「%」から始まる符号へ自動的に置き換えられます。シングルクォーテーションの場合は「%27」です。
そのため、Webサーバーのプログラム側で再びシングルクォーテーションへ戻す必要があります。以上の操作を行うコードを次のように追加しましょう。
追加・変更【93~97行目】
try:
file = open(path, "rt")
except OSError:
status = "HTTP/1.1 404 Not Found"
body = "The requested document was not found on this server."
header = {
"Content-Encoding": "identity",
"Content-Length": str(len(body.encode("utf-8"))),
"Content-Type": "text/plain; charset=UTF-8",
"Server": "MicroPython"
}
else:
status = "HTTP/1.1 200 OK"
if ".py" in path:
file.close()
module = modules[path]
if params: # クエリパラメータがある場合
args = params.replace("&", ",").replace("%27", "'") # クエリパラメータを引数の形式に変換する
body, mimetype = eval("{}.main({})".format(module, args)) # 引数を渡してmain()関数を実行する
else: # クエリパラメータがない場合
body, mimetype = eval("{}.main()".format(module)) # 引数なしでmain()関数を実行する
else:
body = file.read()
file.close()
mimetype = "text/html"
header = {
"Content-Encoding": "identity",
"Content-Length": str(len(body.encode("utf-8"))),
"Content-Type": "{}; charset=UTF-8".format(mimetype),
"Server": "MicroPython"
}
これでWebサーバーのプログラムが変更できました。このプログラムは、次のレッスンでも使用します。名前を付けて保存しておきましょう。
それでは、次のチャプターからLEDディスプレイやサーボモーターを制御するアプリケーションのプログラムを作成していきます。
チャプター3
LEDディスプレイを制御するアプリケーションの作成
このチャプターでは、画面に表示されたフォームにメッセージを入力して、送信ボタンを押すと、Studuino:bitのLEDディスプレイにそのメッセージがスクロール表示されるアプリケーションを作成します。
このアプリケーションを実現するためには、クライアント(ブラウザ)側で動作するプログラムとWebサーバー側で動作するプログラムの2つが必要です。
3. 1 クライアント側のプログラムの準備
クライアント側のプログラムはあらかじめ用意した以下のものを使いましょう。Muエディタにソースコードを貼り付けて、「control_display.html」という名前で保存しましょう。保存するときはファイルの種類で「Other(*.*)
」を選択してください。
また、保存したファイルは、Muエディタのメニューの「ファイル」を選択して、Studuino:bitに転送しておきましょう。
【 サンプルコード 3-1-1(control_display.html)】
<!doctype html>
<html>
<head>
<meta charset="utf-8">
<title>Display controller</title>
</head>
<style>
body{
width: 100%;
height: 100%;
font-family: YuGothic, "Yu Gothic medium", "Hiragino Sans", Meiryo, "sans-serif";
}
form#myform{
margin: 2em 1em;
}
input#message{
display: inline-block;
width: 10em;
padding: 0.5em;
font-size: 1.2em;
}
form#myform button[type="submit"]{
display: inline-block;
padding: 0.5em 1em;
color: #ffffff;
background-color: #22AC38;
border: none;
border-radius: 5px;
font-size: 1.2em;
}
</style>
<body>
<form id="myform" method="get" action="control_display.py">
<input id="message" type="text" value="" maxlength="10">
<button type="submit">send</button><br>
<output id="result"></output>
</form>
</body>
<script>
window.onload = function(){
const req = new XMLHttpRequest();
const $myform = document.getElementById("myform");
const $message = document.getElementById("message");
const $result = document.getElementById("result");
req.onreadystatechange = function(){
if(this.readyState == 4){
if(this.status == 200){
$result.innerHTML = this.responseText;
}
}
};
$myform.onsubmit =function(evt){
evt.preventDefault();
let val = $message.value;
let url = this.action + "?message='" + val + "'";
req.open("GET", url, true);
req.send(null);
}
};
</script>
</html>
`
このプログラムは、ブラウザに表示された送信ボタンを押すと、以下のようなリクエストがStuduino:bitへ送られてきます。
GET /control_display.py?message=%27Hello%27 HTTP/1.1 Host: 192.168.4.1 Connection: keep-alive Accept: */* User-Agent: Mozilla/5.0 (iPhone; CPU iPhone OS 13_3_1 like Mac OS X) AppleWebKit/605.1.15 (KHTML, like Gecko) Version/13.0.5 Mobile/15E148 Safari/604.1 Accept-Language: ja-jp Referer: http://192.168.4.1/control_display.html Accept-Encoding: gzip, deflate
1行目のリクエスト行に書かれているURL「/control_display.py?message=%27Hello%27
」に注目してください。このURLのクエリパラメータは、「message=%27Hello%27
」です。パラメータ名はmessage
で、値としてパーセントエンコーディングされた「'
(%27)」で囲まれたメッセージが設定されています。
前のチャプターで変更を加えたwebサーバーのプログラムでは、これを「message='Hello'
」と変換してmain()
関数に渡してくれます。
3. 2 Webサーバー側のプログラムの作成
サーバー側のプログラムでは、受け取ったメッセージをスクロール表示する処理を行い、結果を表すメッセージを返します。サーバーから返ってきたメッセージはクライアントのブラウザの画面に表示されるようになっています。
それでは、Muで新規のファイルを作成して、以下のコードを書きましょう。
【 サンプルコード 3-2-1(control_display.py)】
from pystubit.board import display
def main(message): # 引数名はパラメータ名と揃える
display.scroll(message, delay=50, wait=False, color=(31, 31, 31)) # メッセージを白色で巣クロる表示する
return "Received your message '{}'.".format(message), "text/plain" # メッセージを受信できたことを返す
作成したプログラムは、「control_display.py」と名前を付けて保存します。誤った名前を付けると、アプリケーションがうまく動かなくなってしまうため注意してください。
保存したファイルは、Muエディタのメニューの「ファイル」を選択して、Studuino:bitに転送しておきましょう。
3. 3 Webサーバーへのアプリケーションの登録と動作確認
作成したアプリケーションの動作を確認します。Webサーバーのプログラムの最初の方にアプリケーションを登録するためのコードを追加しましょう。
追加【4行目、10,11行目】
import network
import usocket
import monitor
import control_display # 作成したプログラムファイルをモジュールとしてインポートする
SSID = "studuinobit"
PASSWORD = "Artecrobo2"
modules = {
"/monitor.py": "monitor",
"/control_display.py" : "control_display", # プログラムファイルのパス名とモジュール名のひもづけを追加
}
それでは、Webサーバーのプログラムを実行して、手元のスマートフォンまたはタブレット端末を、Wi-FiのアクセスポイントになっているStuduino:bitに接続しましょう。接続ができたら、ブラウザを開いて、以下のURLを指定してリクエストを送ります。
http://192.168.4.1/control_display.html
正しく動作していると、上で見てきたものと同じ画面が表示されます。メッセージとして10文字以内で半角英数字を入力して、Studuino:bitへ送信してみましょう。
チャプター4
サーボモーターを制御するアプリケーションの作成
このチャプターでは、画面に表示された2つのスライダーを操作して、ロボット拡張ユニットに接続したサーボモーターを制御するアプリケーションを作成します。
4. 1 拡張ユニットへのサーボモーターの接続
Studuino:bitを電池ボックスをつないだロボット拡張ユニットに取り付けましょう。また、2個のサーボモーターをそれぞれ「P13」と「P14」の端子につなぎ、ステーのブロックに固定しましょう。
4. 2 クライアント側のプログラムの準備
クライアント側のプログラムとして以下のものを使います。Muエディタにソースコードを貼り付けて、「control_servo.html」という名前で保存しましょう。保存するときはファイルの種類で「Other(*.*)
」を選択してください。
また、保存したファイルは、Muエディタのメニューの「ファイル」を選択して、Studuino:bitに転送しておきましょう。
【 サンプルコード 4-2-1(control_servo.html)】
<!doctype html>
<html>
<head>
<meta charset="utf-8">
<meta name="viewport" content="width=device-width,initial-scale=1">
<title>Controling servomotors</title>
</head>
<style>
html{
font-family: YuGothic, "Yu Gothic medium", "Hiragino Sans", Meiryo, "sans-serif";
}
body{
width:100%;
height:100%;
margin: auto;
}
div#main{
width: 100%;
height: 100%;
}
div.slider{
display: flex;
justify-content: center;
align-items: center;
width: 100%;
margin-top: calc(100vh*1/4);
}
div.slider label{
display: inline-block;
font-size: 1em;
padding: 0 1em;
font-weight: bold;
color: #ffffff;
background-color: #2EA7E0;
}
div.slider input{
-webkit-appearance: none;
appearance: none;
outline: none;
display: inline-block;
width: calc(100vw*3/4);
height: 3em;
margin: 0 0.5em;
background-color: #cccccc;
border-radius: 1.5em;
}
div.slider input::-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);
}
div.slider input::-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;
}
div.slider output{
display: inline-block;
font-size: 1em;
width: 6em;
}
</style>
<body>
<div id="main">
<div class="slider">
<label>P13</label>
<input id="p13in" class="slider-in" type="range" value="90" min="0" max="180" step="1">
<output id="p13out">90deg</output>
</div>
<div class="slider">
<label>P14</label>
<input id="p14in" type="range" value="90" min="0" max="180" step="1">
<output id="p14out">90deg</output>
</div>
</div>
</body>
<script>
window.onload = function(){
const req = new XMLHttpRequest();
const p13In = document.getElementById("p13in");
const p13Out = document.getElementById("p13out");
const p14In = document.getElementById("p14in");
const p14Out = document.getElementById("p14out");
req.onreadystatechange = function(){
if(this.readyState == 4){
if(this.satus == 200){
console.log(req.responseText);
}
}
};
p13In.oninput = function(){
let val = this.value;
p13Out.value = val + "deg";
req.open("GET", "control_servo.py?pin='p13'&angle=" + val, true);
req.send(null);
}
p14In.oninput = function(){
let val = this.value;
p14Out.value = val + "deg";
req.open("GET", "control_servo.py?pin='p14'&angle=" + val, true);
req.send(null);
}
};
</script>
</html>
このプログラムは、ブラウザに表示された送信ボタンを押すと、以下のようなリクエストがStuduino:bitへ送られてきます。
GET /control_servo.py?pin=%27p13%27&angle=120 HTTP/1.1 Host: 192.168.4.1 Connection: keep-alive Accept: */* User-Agent: Mozilla/5.0 (iPhone; CPU iPhone OS 13_3_1 like Mac OS X) AppleWebKit/605.1.15 (KHTML, like Gecko) Version/13.0.5 Mobile/15E148 Safari/604.1 Accept-Language: ja-jp Referer: http://192.168.4.1/control_servo.html Accept-Encoding: gzip, deflate
1行目のリクエスト行に書かれているURLのクエリパラメータ「pin=%27p13%27&angle=120
」に注目してください。今回は2つのパラメータが設定されています。1つ目の「pin
」が制御の対象となるサーボモーターが接続されている端子名で、2つ目の「angle
」が回転後の位置を表す角度です。では続けて、この2つの情報をもとにサーボモーターを制御するサーバー側のプログラムを作成しましょう。
4. 3 Webサーバー側のプログラムの作成
新しく作成するプログラムでは、送られてくるクエリパラーメータに合わせて、pin
とangle
の2つの引数をもつmain()
関数を定義します。pin
によって指定された方のサーボモーターをangle
の角度まで回転させるコードを、以下のように書きましょう。
【 サンプルコード 4-3-1(control_servo.py)】
from pyatcrobo2.parts import Servomotor
servo_p13 = Servomotor("P13") # サーボモーターの制御用オブジェクトの作成
servo_p14 = Servomotor("P14")
servo_p13.set_angle(90) # 各サーボモーターの初期位置は90°
servo_p14.set_angle(90)
def main(pin, angle):
if pin == "p13": # P13に接続したサーボモーターを制御
servo_p13.set_angle(angle)
else: # P14に接続したサーボモーターを制御
servo_p14.set_angle(angle)
return "OK", "text/plain" # 成功したことを表すメッセージを返す
作成したプログラムは、「control_servo.py」と名前を付けて保存します。保存したファイルは、Muエディタのメニューの「ファイル」を選択して、Studuino:bitに転送しておきましょう。
4. 4 Webサーバーへのアプリケーションの登録と動作確認
作成したアプリケーションの動作を確認します。Webサーバーのプログラムの最初の方にアプリケーションを登録するためのコードを追加しましょう。
追加【5行目、13行目】
import network
import usocket
import monitor
import control_display
import control_servo # 作成したプログラムファイルをモジュールとしてインポートする
SSID = "studuinobit"
PASSWORD = "Artecrobo2"
modules = {
"/monitor.py": "monitor",
"/control_display.py" : "control_display",
"/control_servo.py" : "control_servo", # プログラムファイルのパス名とモジュール名のひもづけを追加
}
一度Studuino:bitのリセットボタンを押してから、Webサーバーのプログラムを実行します。手元のスマートフォンまたはタブレット端末を、Wi-FiのアクセスポイントになっているStuduino:bitに接続しましょう。接続ができたら、ブラウザを開いて、以下のURLを指定してリクエストを送ります。
http://192.168.4.1/control_servo.html
表示された画面で上下2つのスライダーを動かすと、その角度までサーボモーターが回転することを確認しましょう。
チャプター5
課題:ブザー音を制御するアプリケーションの作成
最後に課題として、ブラウザの画面に表示された鍵盤の絵をタップすると、Studuino:bitのブザーからその高さの音が鳴るアプリケーションを作成してみましょう。
5. 1 クライアント側のプログラムの準備
クライアント側のプログラムは以下のものを使います。Muエディタにソースコードを貼り付けて、「control_buzzer.html」という名前で保存しましょう。保存するときはファイルの種類で「Other(*.*)
」を選択してください。
また、保存したファイルは、Muエディタのメニューの「ファイル」を選択して、Studuino:bitに転送しておきましょう。
【 サンプルコード 5-1-1(control_buzzer.html)】
<!doctype html>
<html>
<head>
<meta charset="utf-8">
<meta name="viewport" content="width=device-width,initial-scale=1">
<title>Controling buzzer</title>
</head>
<style>
body{
width: 100%;
height: 100%;
}
div#keyboard{
position: absolute;
width: 100%;
height: 100%;
}
div#blackKeys{
position: absolute;
width: 90%;
height: 45%;
left: 5%;
top: 5%;
}
div#whiteKeys{
position: absolute;
width: 90%;
height: 80%;
left: 5%;
top: 5%;
}
div.white-key{
position: absolute;
display: block;
width: 12%;
height: 100%;
background-color: #ffffff;
border: 2px solid #666666;
}
div.black-key{
position: absolute;
display: block;
width: 8%;
height: 100%;
background-color: #000000;
border: 2px solid #666666;
}
div.white-key:active, div.black-key:active{
background-color:darkorange;
}
div.white-key:nth-child(1){
left:0;
}
div.white-key:nth-child(2){
left:12%;
}
div.white-key:nth-child(3){
left:24%;
}
div.white-key:nth-child(4){
left:36%;
}
div.white-key:nth-child(5){
left:48%;
}
div.white-key:nth-child(6){
left:60%;
}
div.white-key:nth-child(7){
left:72%;
}
div.white-key:nth-child(8){
left:84%;
}
div.black-key:nth-child(1){
left: 8%;
}
div.black-key:nth-child(2){
left: 20%;
}
div.black-key:nth-child(3){
left: 44%;
}
div.black-key:nth-child(4){
left: 56%;
}
div.black-key:nth-child(5){
left: 68%;
}
</style>
<body>
<div id="keyboard">
<div id="whiteKeys">
<div class="key white-key" my-tone="C4"></div>
<div class="key white-key" my-tone="D4"></div>
<div class="key white-key" my-tone="E4"></div>
<div class="key white-key" my-tone="F4"></div>
<div class="key white-key" my-tone="G4"></div>
<div class="key white-key" my-tone="A4"></div>
<div class="key white-key" my-tone="B4"></div>
<div class="key white-key" my-tone="C5"></div>
</div>
<div id="blackKeys">
<div class="key black-key" my-tone="CS4"></div>
<div class="key black-key" my-tone="DS4"></div>
<div class="key black-key" my-tone="FS4"></div>
<div class="key black-key" my-tone="GS4"></div>
<div class="key black-key" my-tone="AS4"></div>
</div>
</div>
</body>
<script>
window.onload = function(){
const req = new XMLHttpRequest();
const $keys = document.getElementsByClassName("key");
function httpRequest(evt){
evt.preventDefault();
let evtType = evt.type;
let tone = this.getAttribute("my-tone")
if(evtType=="mousedown" || evtType=="touchstart"){
status = "on";
}else{
status = "off";
}
req.open("GET", `control_buzzer.py?tone='${tone}'&status='${status}'`, true);
req.send(null);
}
req.onreadystatechange = function(){
if(this.readyState == 4){
if(this.status == 200){
console.log(this.responseText);
}
}
};
for(let i=0; i<$keys.length; i++){
$keys[i].addEventListener("mousedown", httpRequest,false);
$keys[i].addEventListener("touchstart", httpRequest, false);
$keys[i].addEventListener("mouseup", httpRequest, false);
$keys[i].addEventListener("touchend", httpRequest, false);
}
};
</script>
</html>
`
このプログラムは、ブラウザの画面で鍵盤の絵をタップすると、次のリクエスト行をもつリクエストを送ります。
GET /control_buzzer.py?tone=%27C4%27&status=%27on%27 HTTP/1.1
URLに注目すると、音の高さを表す「tone
」とONとOFFの状態を表す「status
」の2つのパラメータが設定されています。tone
の値はC4
やD4
などの音名になっていて、status
の値はon
またはoff
の文字列になっています。
また、鍵盤の絵から指をはなしたときもリクエストを送るようになっていて、この場合は指をはなした鍵盤の音の高さとOFFの状態が、上と同じパラメータの値として設定されます。
GET /control_buzzer.py?tone=%27C4%27&status=%27off%27 HTTP/1.1
5. 2 Webサーバー側のプログラムの作成とアプリケーションの登録
それでは、以上のことをヒントにして、Webサーバー側のプログラムを自分で考えて作成しましょう。作成したプログラムは「control_buzzer.py
」と名前を付けて保存し、MuエディタのファイルからStuduino:bitに転送してください。また、Webサーバーのプログラムの最初の方に以下のコードを追加して、アプリケーションを登録するのを忘れないように注意しましょう。
プログラムの作り方が分からない場合は、この下に例示している【 サンプルコード 5-2-1 】を参考にしてください。
追加【6行目、15行目】
import networ
kimport usocket
import monitor
import control_display
import control_servo
import control_buzzer # 追加
SSID = "studuinobit"
PASSWORD = "Artecrobo2"
modules = {
"/monitor.py": "monitor",
"/control_display.py" : "control_display",
"/control_servo.py" : "control_servo",
"/control_buzzer.py" : "control_buzzer" # 追加}
【 サンプルコード 5-2-1(control_buzzer.py)】
from pystubit.board import buzzer
def main(tone, status):
if status == "off":
buzzer.off()
else:
buzzer.on(tone)
return "OK", "text/plain"
チャプター6
おわりに
6. 1 このレッスンのまとめ
このレッスンでは、前のレッスンで作成したWebサーバーのプログラムに変更を加え、ブラウザに表示された画面からStuduino:bitを制御する3つのアプリケーションを作成しました。この講座では取り扱いませんが、クライアント側のプログラム言語である「JavaScript」や画面への要素の配置やデザインが行える「HTML」「CSS」もマスターすると、もっと魅力的なアプリケーションが制作できるようになります。興味がある人は、この講座を修了した後にぜひ取り組んでみてください。
6. 2 次回のレッスンについて
次のレッスンでは、フォークリフトをモチーフにしたロボットを製作します。このレッスンで学んだことを踏まえて、スマートフォンやタブレット端末から自由に操作できるようにしてみましょう。