前回記事にしたラジコンロボットを制御するプログラムを公開いたします。
筆者と同じようなロボットを作成したい方は、是非参考にしてください。
※このプログラムは「ラズベリーパイで遊ぼう」の本を参考に一部修正をかけたものになります。
HTMLプログラム
コントロール画面の記述や、各種スクリプトの参照宣言など、制御プログラムの中枢となるプログラムです。
以下に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 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 70 71 72 73 74 75 76 77 78 79 80 81 82 83 84 85 86 87 88 89 90 91 92 93 94 95 96 97 98 99 100 101 102 103 104 105 106 107 108 109 110 111 112 113 114 115 116 117 118 119 120 121 122 123 124 125 126 127 128 129 130 131 132 133 134 135 136 137 138 139 140 141 142 143 144 145 146 147 148 149 150 151 152 153 154 155 156 157 158 159 160 161 162 163 164 165 166 167 168 169 170 171 172 173 174 175 176 177 178 179 180 181 182 183 184 185 186 187 188 189 190 191 192 193 194 195 196 197 198 199 200 201 202 203 204 205 206 207 208 209 210 211 212 213 214 215 216 217 218 219 220 221 222 223 224 225 226 227 228 229 230 231 232 233 234 235 236 237 238 239 240 241 242 243 244 245 246 247 248 249 250 251 252 253 254 255 256 257 258 259 260 261 262 263 264 265 266 267 268 269 270 271 272 273 274 275 276 277 278 279 280 281 282 283 284 285 286 287 288 289 290 291 292 293 294 295 296 297 298 299 300 301 302 303 304 305 306 307 308 309 310 311 312 313 314 315 316 317 318 319 320 321 322 323 |
<!DOCTYPE html> <html lang="ja"> <head> <meta charset="utf-8"> <title>ラジコンカーを走らせる - ラズベリー・パイで遊ぼう</title> <meta name="viewport" content="width=device-width, initial-scale=1, maximum-scale=1"> <link rel="stylesheet" href="style.css" media="screen"> <script type="text/javascript" src="/webiopi.js"></script> <script type="text/javascript"> w().ready(function() { // GPIOポートの設定 var MOTOR_A1 = 26; var MOTOR_A2 = 19; var MOTOR_B1 = 13; var MOTOR_B2 = 6; var LIFT_C1 = 20; var LIFT_C2 = 16; var BUZZER = 21; // その他設定 var LIFT_CSPEED = 100; // ショベルのスピード(0〜100%) var MOTOR_FREQ = 500; // モーターのPWM周波数 500Hz var BUZZER_FREQ = 100; // クラクションのPWM周波数 100Hz // 作動状態を保存する変数 var direction = "STOP"; // 方向: STOP,FOWARD,BACK,RIGHT,LEFT var lift = "STOP"; // ショベル: STOP,UP,DOWN var speed = 0; // スピード: 0〜100% var oldspd = []; // 各GPIO毎のスピード var cookie_btnrev = 0; // クッキーの値 // クッキーの読み込み var pos = document.cookie.indexOf('btnrev='); if(pos >= 0) { cookie_btnrev = parseInt(document.cookie.charAt(pos+7)); } // ボタンのイベントtouchXXX <==> mouseXXXの入れ替え if(cookie_btnrev) { BUTTON_DOWN = ! isTouchDevice ? "touchstart" : "mousedown"; BUTTON_UP = ! isTouchDevice ? "touchend" : "mouseup"; } // 関数:GPIOポートの初期設定(PWMモードに設定する) function init_gpio() { var gpios = [MOTOR_A1,MOTOR_A2,MOTOR_B1,MOTOR_B2,LIFT_C1, LIFT_C2,BUZZER]; for (var i=0; i<gpios.length; i++) { var gpio = gpios[i]; w().callMacro( 'pwm_set_function', gpio); } } // 関数:モーターを指定した方向とスピードで動かす function motor(gpio_in1,gpio_in2, mdir, mspeed) { if(mdir) { // 正転 motor_speed(gpio_in1, mspeed); motor_speed(gpio_in2, 0); } else { // 後転 motor_speed(gpio_in1, 0); motor_speed(gpio_in2, mspeed); } } // 関数:モーターを指定したスピードに変更する function motor_speed(gpio, mspeed) { if(mspeed > 0) { // スタート if((oldspd[gpio] == undefined) || (oldspd[gpio] == 0)) { w().callMacro( 'pwm_start', [gpio,MOTOR_FREQ,mspeed] ); } else { // 変更 w().callMacro( 'pwm_duty', [gpio,mspeed]); } } else { // ストップ w().callMacro( 'pwm_stop', gpio ); } oldspd[gpio] = mspeed; } // 関数:キャタピラのモーターを動かす function change_direction(mode) { direction = mode; if(mode == "FOWARD") { // 前進 motor(MOTOR_A1,MOTOR_A2, 1, speed); motor(MOTOR_B1,MOTOR_B2, 1, speed); } else if(mode == "BACKWARD") { // 後退 motor(MOTOR_A1,MOTOR_A2, 0, speed); motor(MOTOR_B1,MOTOR_B2, 0, speed); } else if(mode == "RIGHT") { // 右旋回 motor(MOTOR_A1,MOTOR_A2, 1, speed); motor(MOTOR_B1,MOTOR_B2, 0, speed); } else if(mode == "LEFT") { // 左旋回 motor(MOTOR_A1,MOTOR_A2, 0, speed); motor(MOTOR_B1,MOTOR_B2, 1, speed); } else if(mode == "STOP") { // 停止 motor_speed(MOTOR_A1, 0); motor_speed(MOTOR_A2, 0); motor_speed(MOTOR_B1, 0); motor_speed(MOTOR_B2, 0); } } // 関数:キャタピラーのスピードの変化 function change_speed(num) { speed = num; if(direction != "STOP") { var gpios = [MOTOR_A1,MOTOR_A2,MOTOR_B1,MOTOR_B2]; for (var i=0; i<gpios.length; i++) { var gpio = gpios[i]; if(oldspd[gpio] > 0) { motor_speed(gpio, speed); } } } } // 関数:クラクションを鳴らす function buzzer(mode) { if(mode) { w().callMacro( 'pwm_start', [BUZZER,BUZZER_FREQ,50] ); } else { w().callMacro( 'pwm_stop', BUZZER ); } } // 関数:ショベルのモーターを動かす function change_lift(mode) { lift = mode; if(mode == "UP") { // 上昇 motor(LIFT_C1,LIFT_C2, 1, LIFT_CSPEED); } else if(mode == "DOWN") { // 下降 motor(LIFT_C1,LIFT_C2, 0, LIFT_CSPEED); } else if(mode == "STOP") { // 停止 motor_speed(LIFT_C1, 0); motor_speed(LIFT_C2, 0); } } // 「前進」ボタンが押されたときのイベント処理 $('#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'); }); // 「クラクション」ボタンが押されたときのイベント処理 $('#buzzer').bind(BUTTON_DOWN, function(event) { $(this).addClass('ledon'); buzzer(1); }).bind(BUTTON_UP, function(event) { $(this).removeClass('ledon'); buzzer(0); }); // 「ショベルUP」ボタンが押されたときのイベント処理 $('#liftup').bind(BUTTON_DOWN, function(event) { if(lift == "STOP") { $(this).addClass('ledon'); change_lift('UP'); } }).bind(BUTTON_UP, function(event) { $(this).removeClass('ledon'); change_lift('STOP'); }); // 「ショベルDOWN」ボタンが押されたときのイベント処理 $('#liftdown').bind(BUTTON_DOWN, function(event) { if(lift == "STOP") { $(this).addClass('ledon'); change_lift('DOWN'); } }).bind(BUTTON_UP, function(event) { $(this).removeClass('ledon'); change_lift('STOP'); }); // 「スピード」スライダーが変化したときのイベント処理 $('#slider').change(function(){ change_speed( parseInt($(this).val()) ); }); // 「スピードDOWN」ボタンが押されたときのイベント処理 $('#speeddown').bind(BUTTON_DOWN, function(event) { $(this).addClass('ledon'); var sptemp = parseInt($('#slider').val()) - 10; if(sptemp < 0) sptemp = 0; $('#slider').val(sptemp); change_speed(sptemp); }).bind(BUTTON_UP, function(event) { $(this).removeClass('ledon'); }); // 「スピードUP」ボタンが押されたときのイベント処理 $('#speedup').bind(BUTTON_DOWN, function(event) { var sptemp = parseInt($('#slider').val()) + 10; if(sptemp > 100) sptemp = 100; $('#slider').val(sptemp); change_speed(sptemp); }).bind(BUTTON_UP, function(event) { $(this).removeClass('ledon'); }); // 関数:「ここ」が押されたときのイベント処理 $('#kokobtn').click(function(event) { var new_cookie_btnrev = (cookie_btnrev) ? 0 : 1; // クッキーの送信とリロード document.cookie = 'btnrev=' + new_cookie_btnrev; window.location.reload(true); }); // スクロールの抑制、ただし#sliderは除外 $('body').delegate('#wrapper','touchmove',function(e){ e.preventDefault(); }).delegate('#slider','touchmove',function(e){ e.stopPropagation(); }); // メイン init_gpio(); // GPIOポートの初期設定 speed = $('#slider').val(); }); </script> </head> <body> <div id="wrapper"> <header> <div class="header"> <img src="http://ラズパイローカルIP:8080/?action=stream"/> </div> </header> <nav> <div class="nav-zero"> <ul> <li class="wakunone"></li> <li id="forward" class="ledoff">↑<br />↑<br />前進</li> <li class="wakunone"></li> </ul> <ul> <li id="left" class="ledoff"><br />←←左<br /><br /></li> <li><br /><br /><br /></li> <li id="right" class="ledoff"><br />右→→<br /><br /></li> </ul> <ul> <li class="wakunone"></li> <li id="backward" class="ledoff">後退<br />↓<br />↓</li> <li class="wakunone"></li> <li class="wakunone"></li> </ul> <br /> スピード<br /> <form> <input type="range" id="slider" min="0" max="100" step="5" value="50" style="width:100%"> </form> <ul> <li id="speeddown" class="ledoff"><<</li> <li class="wakunone"></li> <li class="wakunone"></li> <li id="speedup" class="ledoff">>></li> </ul> </div> <div class="clear"></div> </nav> <div id="contents"> <section> <div class="wmain" style="width:280px;"> ボタンのとおりにラジコンカーが走ります。<br /> (ボタンが反応しない場合は<a href="javascript:void(0)" id="kokobtn">ここ</a>をクリック)<br /> </div> </section> </div> <footer> <div id="footer"> ラズベリー・パイで遊ぼう<br /> </div> </footer> </div> </body> </html> |
ブザー、およびショベルは使用しないため、コントロール画面のボタン記述を消去しました。
ストリーミングカメラをコントローラーに反映させるため、header部にスクリプトをつけたしました。(ラジコンカー・・のタイトルは消去)
※ラズパイローカルIPの箇所は実際使用しているラズパイのローカルIPを入力してください。
また、原因がはっきりしていませんが、筆者の場合何度かテストしているうちにラズパイの電源投入後OSが起動すると右のキャタピラが反転しだす症状に見舞われました。
一応GPIO6が悪そうだったので、GPIO12に変えたところ、症状が改善しました。
同じような症状が出れば、一度空いているポートに線をつなぎ変えてみて下さい。(もちろんプログラムの変更も必要です。)
CSSプログラム
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 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 70 71 72 73 74 75 76 77 78 79 80 81 82 83 84 85 86 87 88 89 90 91 92 93 94 95 96 97 98 99 100 101 102 103 104 105 106 107 108 109 110 111 112 113 114 115 116 117 118 119 120 121 |
@charset "utf-8"; html, body, h1, h2, h3, h4, h5, h6, ul, ol, dl, li, dt, dd, p, header, hgroup, section, article, aside, hgroup, footer, figure, figcaption, nav { margin: 0; padding: 0; font-size: 100%; } body { line-height: 1.0; -webkit-text-size-adjust: none; background: #f8f8f8; background: -webkit-gradient(linear, left top, left bottom, color-stop(0%,#f8f8f8), color-stop(100%,#cccccc)); } article,aside,canvas,details,figcaption,figure, footer,header,hgroup,menu,nav,section,summary { display:block; } img { border: 0; vertical-align: bottom; } ul, ol { list-style: none; } table { border-spacing: 0; empty-cells: show; } a { text-decoration: none; } a:link { color: #2020ff; } a:visited { color: #2020ff; } a:active { color: #ff2020; } .block { padding-bottom: 10px; } .noblock { padding: 0; } #wrapper { width: 320px; margin: 0 auto; marrgin-bottom: 50px; line-height: 1.2; font-size: small; color: #606060; } #contents { /* width: 320px; */ } .nav-zero { margin: 0 0 20px; float: left; } .nav-zero li { float: left; width: 55px; margin: 0 0 10px 10px; padding: 3px; border-radius: 8px; -webkit-border-radius: 8px; text-align: center; color: #000000; text-shadow: 1px 1px 1px #888888; } .nav-zero ul:after { content: ''; display: block; clear: both; } .ledoff { border: 3px solid #808080; background: #f8f8f8; } .ledon { border: 3px solid #808080; background: #f88888; } .wakunone { opacity: 0; border: 3px solid #f8f8f8; } .header { margin: 10px 0 20px; text-align: center; font-size: x-large; font-weight: bold; color: #ff2424; } .wmain { margin: 0 10px 20px; padding: 10px; background-color: #ffffff; border-radius: 6px; -webkit-border-radius: 6px; box-shadow: 1px 2px 3px #aaaaaa; background: #ffffff; } #footer { margin: 0 7px 10px; padding: 5px; color: #444444; font-size: small; text-align: center; } .small { font-size: small; } .left { text-align: left; } .right { text-align: right; } .clear { clear: both; } |
macroプログラム
ある機能をまとめて、自動で実行するプログラム。今回の場合はキャタピラを制御する各種のパルス信号をまとめており、それを実行するためのプログラム。
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 |
# coding: utf-8 # ======== WebIOPi用マクロ:PWMの設定 ======== import webiopi import RPi.GPIO as GPIO GPIO.setmode(GPIO.BCM) p = {} # PWMのインスタンス変数 ## GPIOポートをPWMモードで設定する @webiopi.macro def pwm_set_function(io): io = int(io) GPIO.setup(io, GPIO.OUT, initial=GPIO.LOW) p[io] = GPIO.PWM(int(io), 1000) #1000Hz ## PWMの開始 @webiopi.macro def pwm_start(io, freq, duty): io = int(io); p[io].ChangeFrequency(int(freq)) p[io].start(int(duty)) ## PWM周波数の変更 @webiopi.macro def pwm_freq(io, freq): io = int(io) p[io].ChangeFrequency(int(freq)) ## PWMデューティー比の変更 @webiopi.macro def pwm_duty(io, duty): io = int(io) p[io].ChangeDutyCycle(int(duty)) ## PWMの停止 @webiopi.macro def pwm_stop(io): io = int(io) p[io].stop() |
プログラムの格納先とスクリプト追加
プログラム格納先
前途したプログラム名はそれぞれ、「rover.html」「style.css」「macro-pwm.py」にしてテキストファイルにコピーして保存してください。
保存先は、ラズパイのhome/pi/webiopi/〇〇/ココ に3つ全て格納します。
〇〇はなんでもいいので、適当に名前をつけたフォルダを作成してください。
今回は〇〇のフォルダ名を「test」で進めています。
スクリプト追加
マクロを記述した「macro-pwm.py」をwebiopiに読み込ませる設定が必要です。
webiopiの設定ファイルを編集し、「scripts」の場所にマクロのファイル名を追加します。
ラズパイのシェルで /etc/webiopi/config を開きます。
$sudo leafpad /etc/webiopi/config
webiopiのconfigテキストファイルが開くので、「scripts」項目の一番下に
myscript4 = home/pi/webiopi/test/macro-pwm.py
と入力し追加、保存します。
その後、webiopiを再起動で設定が反映されます。
$sudo systemctl restart webiopi