はじめに
(1)動作環境
(2)飛行プラン
(3)ファイルからの飛行プランの読み
取り
(4)飛行プランの引数渡しとGrabber
(5)機能追加
droneプログラミング覚書
岐阜経済大学 経営学部 経営情報学科 井戸 伸彦
来歴:
0.0版 2018年5月10日 順次追加
スライドの構成
はじめに
本スライドは、JavaScriptを用いて、ドローンParrot Mamboを操
作するプログラムの作成に関わる覚書です。単独で教科書とな
ることは意図していません。
JavaScriptプログラムはブラウザ上で動作させることがほとんど
ですが、本スライドではnode.jsを用いてコンソール上から起動し
ます。
本スライドでは、JavaScriptについて次のスライドで学んでいる
ことを前提とします。本スライド中では、このスライドを、“JS覚
書”と記して参照します。
「JavaScript覚書」
(
http://staff.gifu-keizai.ac.jp/~ido/doc/web_text/javascript_text.pdf
)
JavaScriptについてきちんと勉強したい方は、プログラミング等
の講義を受講して頂くことをお願いします。
説明は直感的な分かりやすさを重視し、厳密には不正確な言
い方もしています。
(1.1)動作環境
本スライドでは、node.jsを用いてJavaScriptの動作環
境を構築することを前提としています。
次のサイトを参考に環境構築を行いました。
(1)全体像:「 Parrot Mambo Missionを動かす(github)」
(以下のスライドでは、このサイトからのファイル等を“元ネタ”と記します。) (https://github.com/hiko2msp/play_with_parrot_mambo) (2)node.js/npmのインストール:「Node.js / npmをインストールする」 (https://qiita.com/taiponrock/items/9001ae194571feb63a5e) (3)gitのインストール: 「初心者でもWindowsやMacでできる、Gitのインストールと基本的な使い方」 (http://www.atmarkit.co.jp/ait/articles/1603/31/news026.html) (4)proxy関係(大学内の環境下では必要):
「Proxy環境下でのGit for Windows設定」
(https://qiita.com/yujimny/items/08f02d9dfb670f0e9ddf) 「[Node.js] npm の proxy と registry 設定」
(https://qiita.com/LightSpeedC/items/b273735e909bd381bcf1) (5)Bluetoothドライバの書き換え(Zadig):
(1.2)動作確認
大学で用いるタブレットPCでは、ユーザ名“drone”で
サインインしてください。ユーザ名“岐阜経済大学”とな
っていた場合はサインアウトして、 “drone”に切り替え
てください。
次のディレクトリ(フォルダ)にてプログラムを実行しま
す。
~/drone/play_with_parrot_mambo/
(”~”は、ユーザのホームディレクトリ)
前記(1)のwebページ(
「 Parrot Mambo Missionを動かす(
github)」
)に記載のプログラムを、次のように起動出来る
状態にします(ドローンの名前の設定はページの記述
に従ってください。 )。
(1.3)作業フォルダ
今後、作成したプログラムは、タブレットPC上の次のフ
ォルダ(ディレクトリ)に置くことにします。
[コンピュータ]-[ユーザー]-[drone]-[drone]-[play_with_parrot_mambo]-[drone_programming]
(~/drone/play_with_parrot_mambo/drone_programming/)
情報実習室パソコンからのコピーは、次のフォルダ経
由で行います(
赤字
の部分は、年度とゼミに合わせます
)。
[public]-[ido]-[student]-[
2018se2
]-[グループ名]
最初に、[ドキュメント]配下の“ファイルサーバ認証”を起動し
て、ネットワークドライブを割り当てておいてください。
(1.4)コマンド入力(Git Bash)
コマンドは、次のように起動した
Git Bash上で行います。
[スタート]-[git]-[Git Bash]
起動後、“cd”コマンドを次のように入力して、作業ディ
レクトリに移動します(
linuxコマンドについては別途説明)。
(2.1)飛行プランのプログラミング
元ネタにあるプログラム“testFlight.js”を書き換
えた次のプログラムを参考にして、自分で考えた飛行
プランによりドローンを飛ばしてください。ファイルは、
ネットワークドライブにあります。
dronejs.connect(DRONE_NAME) .then(() => dronejs.flatTrim()) // 飛ぶ前に一度平坦な状態を覚える .then(() => dronejs.takeOff()) // 離陸.then(() => {console.log(DRONE_NAME+' take off.');}) .then(() => dronejs.flatTrim())
.then(() => dronejs.turnRight(50, 20))
.then(() => {console.log(DRONE_NAME+' turnRight.');}) .then(() => dronejs.flatTrim())
.then(() => dronejs.land()) // 着陸する
.then(() => {console.log(DRONE_NAME+' land.');})
.then(() => dronejs.disconnect()) // 接続解除 .then(() => {
// 正常終了した場合、プログラムを終了する
(2.2)プログラミングに関して
ch21_test_drive.jsのプログラムの内容は、スライド「JS
覚書」中の次の点に注意して理解してください。
(12)関数
(8)分岐、if文
(3)変数
以降でも、このスライドの参照が必要ですが、そのたびに上
記のように参照することはしません。
ch21_test_drive.jsのプログラムの次の内容については
、当面、“そのように書くもの”と理解しておいてください
。別の機会に説明します。
アロー関数(“()=>”)
thenメソッド
(2.3)課題
ch21_test_drive.jsのプログラムを書き換えて、
自分が考えた飛行プランでドローンを飛ばしてください
。
(3.1)ファイルからの読み取り
ブラウザ上で動作するJavaScriptでは、そのPCのファ
イルを読むことは出来ません。次のプログラムは、
node.jsでのみ動作します。
var fs = require('fs’); // ファイル操作モジュール fs.readFile('ch31_test.txt’, // 読み出すファイルのパス名 'utf8’, // ファイルの文字コード function(err,data){ // 読出後に実行される処理(コールバック関数) // ファイルの内容の出力 console.log(data); } // 関数の終わり ); abcd efg hijklmn<ch31_test.txt>
C:¥> node ch31_read_file.js abcd efg hijklmn<実行結果>
<ch31_read_file.js>
①起動
②読取
③出力
(3.2)1行ごとの処理
本スライドでは大きなファイルは扱いませんので、ファ
イルごと読み込んだ文字列を1行ごとの配列に格納し
て処理します。
function (err, data) { // 読み出して実行される処理(関数) // ファイル内容の文字列を改行コードで配列に分割 var lines=data.split(/¥r¥n|¥r|¥n/); for(var i=0;i<lines.length;i++){// 配列の要素(=各行)ごとに繰り返し console.log((i+1)+':'+lines[i]); // 行番号を付加して行を出力 } } // 関数の終わり C:¥> node ch32_each_line.js 1:abcd 2:efg 3:hijklmn
<実行結果>
<ch32_each_line.js((3.1)のコールバック関数部分のみ)>
“abcd efghijklmn” “abcd¥r¥nefg¥r¥nhijklmn” [“abcd”,
“efg”,
“hijklmn”]
(3.3)練習問題
次のようなブラウザ上で動作するJavaScriptプログラ
ムを作成してください。
下の図の右のようなファイルから読み込んだつもりで、左
のような文字列を宣言します。
ブラウザ上に下のようなグラフを出力します。
項目の数は3つ(A、B、C)の固定としてOKです。
形式が間違っているファイルに対する考慮も無用です。
A
12
B
28
C
20
var str=“A
¥n¥r
12
¥n¥r
B¥
n¥r
28
¥n¥r
C
¥n¥r
20”;
(3.4.1)ファイルを用いた飛行プランー1ー
次の2つのファイルを参考にして、ファイルに記述した
飛行プランによりドローンを飛ばしてください。ファイル
は、ネットワークドライブにあります。
ch34_planed_flight.js
ch34_flight_plan.txt
ひとつめのファイルがプログラムであり、次スライド以
降に示します。次スライドに示すプログラムは、
ch21_test_drive.jsの74行目あたりまで同じで、その後
の部分を示しています。
ふたつめのファイルが飛行プランを記したファイルで
す。
(3.4.2)ファイルを用いた飛行プランー2ー
var flight_plan=new Array();
function loadFlightPlan(fileName){
var fs = require('fs'); //ファイル操作モジュール fs.readFile(fileName, 'utf8’,
function (err, data) { // 読み出して実行される処理(関数)
var lines=data.split(/¥r¥n|¥r|¥n/); //文字列を改行コードで配列に分割 for(var i=0;i<lines.length;i++){ //配列の要素(各行)ごとに繰り返し
var line=lines[i].replace(/¥s+/g,‘’); //空白除去してlineに代入 if(line==‘’||line.charAt(0)==‘#’)continue; //空行等は読み飛ばし var action; if(line.indexOf(':')>0){ var items=line.split(':'); // 1行を”:”(カンマ)で区切る。 // オブジェクト(連想配列)を作成。 action={method:items[0],params:items[1].split(',')}; }else{ action={method:line}; } flight_plan.push(action); } } // 関数の終わり ); } <次のスライドに続く>
<ch34_planed_flight.js(74行目以降)>
(3.4.3)ファイルを用いた飛行プランー3ー
<前のスライドからの続き> function schedule(){ var action=flight_plan.shift(); if(action==null){ console.log("#P#schedule.disconnect"); dronejs.disconnect() .then(() => {process.stdin.pause();console.log("end");process.exit();}).catch((e) => {console.log('e=' + e);process.stdin.pause();process.exit();}); return;
}
console.log("#P#schedule.action.method="+action.method); if(action.method=='flatTrim'){
dronejs.flatTrim().then(()=>schedule())
.catch((e) => {console.log('e=' + e);process.stdin.pause();process.exit();}); }else if(action.method=='takeOff'){
dronejs.takeOff().then(()=>schedule())
.catch((e) => {console.log('e=' + e);process.stdin.pause();process.exit();}); }else if(action.method=='land'){
dronejs.land().then(()=>schedule())
.catch((e) => {console.log('e=' + e);process.stdin.pause();process.exit();}); }else if(action.method=='turnRight'){
dronejs.turnRight(action.params[0],action.params[1]).then(()=>schedule()) .catch((e) => {console.log('e=' + e);process.stdin.pause();process.exit();}); }else{
console.log("#E#schedule. illegal method. action.method="+action.method); }
}
(3.4.4)ファイルを用いた飛行プランー4ー
function main() {
// 飛行プランの読み取り
loadFlightPlan('ch34_flight_plan.txt’); console.log('start’)
const navDataStream = dronejs.getNavDataStream(); navDataStream.subscribe((data) => { console.log(data); }, e => debug(e), () => debug('complete') ); dronejs.connect(DRONE_NAME) .then(()=>schedule()) .catch((e) => { // 以上終了した場合、エラーの内容をコンソールに表示し、終了する console.log('エラー: ' + e); process.stdin.pause(); process.exit(); }); } main(); // 関数を実行します
(3.4.5)ファイルを用いた飛行プランー5ー
次のような飛行プランのテキストファイルを読み込み
ますが、次の点に注意してください。
行の先頭に“#”(シャープ)を書くと、その行はコメント行にな
ります。行の途中から“#”(シャープ)を書くと、それ以降がコ
メントと見なされます。
空白行は読み飛ばします。
flatTrim takeoff flatTrim turnRight:50, 20 flatTrim land<ch34_flight_plan.txt>
(3.5)課題
“ch34_planed_flight.js”のプログラムを書き
換えて、すべてのコマンドが飛行プランで実行できるよ
うにしてください。
ファイル名は、“ch35_planed_flight.js”としま
す。
(4.1)コマンドライン引数
スライド(3.1)のプログラムを書き換えて、読み出すファ
イル名を指定出来るようにします。
var fs = require('fs’); // ファイル操作モジュール
var file_name = process.argv[2];
fs.readFile(file_name, // 読み出すファイルのパス名 'utf8’, // ファイルの文字コード function(err,data){ // 読出後に実行される処理(コールバック関数) // ファイルの内容の出力 console.log(data); } // 関数の終わり );
<ch41_read_file.js>
opqr stuvwxyz<ch41_test.txt>
C:¥> node ch31_read_file.js ch41_test.txt
opqr
stuvwxyz
C:¥> node ch31_read_file ch31_test.txt abcd efg hijklmn
<実行結果>
abcd efg hijklmn<ch31_test.txt>
①コマンドライン引数
②ファイル名
③出力
(4.2)process.argv
process.argvの配列には、インデックス“2”から指定し
たコマンドライン引数が入ることに注意してください。
C:¥> node ch42_arg_test.js
aaa bbb ccc ddd
<ch42_arg_test.js>
index 0 : C:¥...¥node.exe
index 1 : C:¥ ch42_arg_test.js
index 2 : aaa
index 3 : bbb
index 4 : ccc
index 5 : ddd
console.log('index 0 : ‘+ process.argv[0]); console.log('index 1 : ‘+ process.argv[1]); console.log('index 2 : ‘+ process.argv[2]); console.log('index 3 : ‘+ process.argv[3]); console.log('index 4 : ‘+ process.argv[4]); console.log('index 5 : ‘+ process.argv[5]);node.jsの実行ファイル
実行された
プログラム
ファイル
コマンド
ライン引数
(4.3)飛行プランの引数での指定
スライド(3.5) の“ch35_planed_flight.js”を変
更して、飛行プランのファイルをコマンドラインの引数と
して指定出来るようにします。
ファイル名は“ch43_planed_flight.js”とし、次のよう
に実行出来るようにします。
変更は次の2か所で行います。
C:¥> node ch43_planed_flight.js ch34_flight_plan.txt
(旧) main(); // 関数を実行します
(新) main(process.argv[2]); // 関数を実行します (新)function main(file_name) {
// 飛行プランの読み取り
loadFlightPlan(file_name); (旧)function main() {
// 飛行プランの読み取り
(4.4)Grabber
元ネタにあるプログラム“testGrab.js”では、次の
ようにGrabberの操作を記述しています。
スライド(4.3) の“ch43_planed_flight.js”にて、
次の飛行プランを指定すると、同じことが出来ます。
(disconnectは、 “ch43_planed_flight.js”にて、プラン
の終了後に必ず実行するようにしています。)
dronejs.connect(DRONE_NAME)
.then(() => dronejs.grabClose()) // 腕を閉じる
.then(() => dronejs.grabOpen()) // 腕を開く
.then(() => dronejs.disconnect()) // 接続解除
.then(() => {
<testGrab.js>
grabClose
# 腕を閉じる
grabOpen
# 腕を開く
<ch44_plan_grab.txt>
(4.5)タイミング
利用させて頂いているネタ元の環境では、ドローンの
着陸後にすぐ離陸することは難しいようです。
少しタイミングを取るためのプランの記述と、これを実
行するプログラムの機能とを追加します。
}else if(action.method=='checkAllStates'){ dronejs.checkAllStates().then(()=>schedule()) .catch((e) => {console.log('e=‘ +…… }else if(action.method=='timing'){ setTimeout(schedule,action.params[0]); }else{console.log("#E#schedule. illegal method.……
<ch45_operation.js(181行あたり)>
flatTrim takeOff flatTrim forward:10,10 land timing:2000 flatTrim takeOff flatTrim backward:10,10 land<ch45_plan_timing.txt>
追加
2000msだけ
タイミングを取る
(4.6)課題
“ch45_operation.js”を用いて次のような動作を
行う飛行プランを作成してください。
物(丸めた紙)をつかむ。
離陸する。
少し前進する。
着陸する。
物を離す。
(ここでタイミングを取る)
離陸、後進、着陸して、元の場所に戻る。
ファイル名は、“ch46_carry_obj.txt”とします。
(5)機能追加
次の機能追加を行います。
ドローン名をファイルから読み込むことにする。
プランの実行前にプランの記述に誤りが無いかをチェックし、
誤りが見つかった場合は実行を行わない。
移動における強さの上限を設定し、プランにおいてそれを超
える値が設定されていても、制限内の強さで移動を行う。
(室内での飛行を想定しているので、安全対策として行う。)
(5.1.1)ドローン名のファイルへの記述
次のように飛行プランファイル内にドローン名を記述
することにします。
:droneName:
Mambo_742xxx
:plan:
flatTrim
takeOff
# 離陸する。
flatTrim
forward:10,10
# 少し前進する。
land
# 着陸する。
:
<ch50_plan_timing.txt>
ドローン名
この記述の後が
飛行プラン
(5.1.2)プログラムでのドローン名の読み取り
飛行プランを読み取っている場合を表す変数を導入し
ます。
var on_reading_plan=false; // 飛行プランの読み取り中の時にtrue
for(var i=0;i<lines.length;i++){ // 配列の要素(=各行)ごとに繰り返し (中略) if(on_reading_plan){ // 飛行プランを読取中の場合 // 従来とおりの飛行プランの読み取り (中略) }else{ // 飛行プラン以外(ドローン名など)を読取 (中略) if(line.indexOf(‘:droneName:’)==0){ // ドローン名の行 // ドローン名の取得 DRONE_NAME=line.substring(‘:dronName:’.length+1); (中略) }else if(line.indexOf(':plan:')==0){ on_reading_plan=true; // 飛行プラン読取中に移行 (中略) } }