86Duino 遠端遙控車

本篇利用 L86duntu 來製作遠端遙控車,成果影片如下:

這篇文章包含:

  1. L86duntu 環境設定
  2. mjpg-streamer 視訊串流
  3. 使用 Node.js 和 MRAA 製作網頁應用

對以上項目有興趣的朋友千萬不能錯過喔!

準備

  1. 超頻 86Duino:使用 L86duntu 小編個人鰻建議超頻到 500MHz 的,使用上會比原本的 300MHz 順暢,首先要準備 86Duino SysImage (參考 86Duino SysImage 工具程式部分) 接著再設定 CPU 時脈裡用隱藏指令超頻到 500MHz。超頻完成之後就把 SD 卡換成 L86duntu 開始使用囉!
  2. USB WiFi 網卡:為了要無線遙控,得使用 USB WiFi 網卡
  3. mjpg-streamer:視訊串流的部分以 mjpg-streamer 來解決,使用前要將 86Duino 接上一個 usb camera,指令範例如下
    cd /home/dmp/mjpg-streamer/mjpg-streamer
    export LD_LIBRARY_PATH="$(pwd)"
    ./mjpg_streamer -i "./input_uvc.so -y -r 320x240 -f 10" -o "./output_http.so -w ./www" &
    

    前兩行是移動到 mjpg-streamer 資料夾並將需要用到的 Library 加入 LDLIBRARYPATH 中,這樣一來才能使用 mjpg-streamer 這個應用程式,參數的話 -i 表示 input-plugin-o 表示 output-plugin-r-f 分別表示 resolutionframe rate-y 表示使用 YUV 格式的 camera,一般預設是使用 JPG 格式,所以可以依照自己的 camera 決定要如何下參數。程式執行之後,要確認有沒有成功開始串流,可以用電腦的瀏覽器,連到 86Duino 所在的 IP,PORT 為 8080 的網頁,就可以看結果了,例如 86Duino IP 為 192.168.1.70,只要連上 192.168.1.70:8080 就能看到結果了。

Web App

網頁是主要寫程式的部分,小編主要是使用 Express 3 來開發 (原始碼),結構是很中規中矩的 Express 3 專案,重點部分在 app.js(利用 MRAA 來控制車子的移動)和 view/index.jade(顯示在瀏覽器中的頁面,負責傳遞按鈕、鍵盤等控制訊息給 app.js)之中,先來看看 app.js 是如何使用 MRAA 的:

var m = require('mraa');
      .
      .
      .
//宣告四個用來控制輪子的 gpio 腳位,對應 86Duino 上的 10, 11, 31, 32
var gpio0 = new m.Gpio(10);
var gpio1 = new m.Gpio(11);
var gpio2 = new m.Gpio(31);
var gpio3 = new m.Gpio(32);

//把四根 gpio 設定成輸出模式
gpio0.dir(m.DIR_OUT);
gpio1.dir(m.DIR_OUT);
gpio2.dir(m.DIR_OUT);
gpio3.dir(m.DIR_OUT);

//利用 socket.io 與前端溝通,只要前端網頁用鍵盤或按鈕來
//觸發相應事件(move),後端再進一步看事件傳來的 cmd 為何,
//就能依照 cmd 讓車子依照使用者的操作移動
//0:停止,所有的腳位輸出 0(LOW)
//1:往前,兩顆輪子皆正轉,腳位按順序是 0(LOW) 1(HIGH) 0(LOW) 1(HIGH)
//2:往左,左邊的輪子往後,右邊的輪子往前
//3:往右,右邊的輪子往後,左邊的輪子往前
//4:往下,兩顆輪子皆反轉
serv_io.sockets.on('connection', function (socket) {
    socket.on('move', function (data) {
        if('0' == data.cmd) //stop
        {
            gpio0.write(0);
            gpio1.write(0);
            gpio2.write(0);
            gpio3.write(0);
        }
        else if('1' == data.cmd) //up
        {
            gpio0.write(0);
            gpio1.write(1);
            gpio2.write(0);
            gpio3.write(1);
        }
        else if('2' == data.cmd) //left
        {
            gpio0.write(0);
            gpio1.write(1);
            gpio2.write(1);
            gpio3.write(0);
        }
        else if('3' == data.cmd) //right
        {
            gpio0.write(1);
            gpio1.write(0);
            gpio2.write(0);
            gpio3.write(1);
        }
        else if('4' == data.cmd) //down
        {
            gpio0.write(1);
            gpio1.write(0);
            gpio2.write(1);
            gpio3.write(0);
        }
    });
});

接著來看看 view/index.jade

block content
  .jumbotron
    h1 86Duino Remote Control Car
    p
  .row
    .col-md-4
      h2 Control
      script(src='https://cdn.socket.io/socket.io-1.2.1.js')
      script(src='https://cdnjs.cloudflare.com/ajax/libs/keypress/2.1.4/keypress.min.js')
      .row.center.nopadding
        .col-md-6
          a.btn.btn-primary(onclick='move(1);') ↑
      .row.center.nopadding
        .col-md-6
          a.btn.btn-info(onclick='move(2);') ←
          a.btn.btn-danger(onclick='move(0);') ●
          a.btn.btn-info(onclick='move(3);') →
      .row.center.nopadding
        .col-md-6
          a.btn.btn-primary(onclick='move(4);') ↓

以上是寫大標題和畫出五顆按鈕,按鈕被按下去時(onclick)會去執行 move(cmd) 這個函式,並依照不同的按鈕傳送不同的參數,而 move 這個函數寫在 public/javascript/car.js 裡面:

function move(cmd) {
    socket.emit('move', { 'cmd': cmd });
};

單純的去處發 move 這個事件,並把 cmd 送出去。

接著回頭繼續看 view/index.jade

    script.
      var socket = io.connect();
    .col-md-6
      h2 Stream
      .row.center.nopadding
        .col-md-6
          img(id='stream')
    script.
      var imgsrc = 'http://' + location.hostname + ':8080/?action=stream';
      $(document).ready(function() {
        $('#stream').attr('src', imgsrc);
      });

這邊插了一段 js 程式碼來宣告 socket,讓 socket.io 能與後端接上,接著把視訊影像所需要的 img 標籤寫出來,因為遙控車的 ip 可能會每次不同,所以這邊只先給 id,之後在用 jquery 操作給予它 src 的內容,如上面程式碼 $('#stream').attr('src', imgsrc);,而 src 即為上面用 mjpg-streamer 產生的影像來源。

    var listener = new window.keypress.Listener();
      var socket = io.connect();
      listener.register_combo({
        "keys": "right",
        "on_keydown": function(){
          socket.emit('move', {'cmd': 3});
        },
        "on_keyup": function(){
          socket.emit('move', {'cmd': 0});
        },
        "prevent_repeat": true
      });

最後是類似的鍵盤事件註冊,就挑其中一個出來說明,為了讓 web app 也能接受用鍵盤操控小車,小編採用 keypress.js 來偵測鍵盤事件,宣告 listener 之後就可以開始註冊事件了,事件中要設定 keys 表示要用到的案件,on_keydownon_keyup 則分別表示按鈕被按下與放開時所要執行的函數,prevent_repeat 是用來表示事件是否要防止事件被重複觸發。

整合

做完以上全部的步驟並且每個部份都能運作的時候,就是時候把所有東西都整合起來了,小編的作法是寫一個開機自動執行的 script 把所有東西整合在一起:

#! /bin/sh
# /etc/init.d/car
#

### BEGIN INIT INFO
# Provides:          car
# Required-Start:    $remote_fs $syslog
# Required-Stop:     $remote_fs $syslog
# Default-Start:     1 2 3 4 5 6
# Default-Stop:      0
# Short-Description: Start daemon at boot time
# Description:       Enable service provided by daemon.
### END INIT INFO

# Some things that run always
touch /var/lock/car

# Carry out specific functions when asked to by the system
case "$1" in
  start)
    export PATH=$PATH:/usr/local/bin
    echo "Starting 86Duino Remote Control Car"
    modprobe rt2800usb
    echo 148F 5370 | tee /sys/bus/usb/drivers/rt2800usb/new_id
    sleep 5
    ifconfig wlan2 up
    killall wpa_supplicant
    killall wpa_supplicant
    killall wpa_supplicant
    killall wpa_supplicant
    killall wpa_supplicant
    wpa_supplicant -i wlan2 -D nl80211 -c /etc/wpa_supplicant.conf -B
    udhcpc -i wlan2
    sleep 5
    cd /home/dmp/mjpg-streamer/mjpg-streamer
    export LD_LIBRARY_PATH="$(pwd)"
    ./mjpg_streamer -i "./input_uvc.so -y -r 320x240 -f 10" -o "./output_http.so -w ./www" &
    cd /home/dmp/86Duino_Remote_Control_Car
    node app.js
    ;;
  stop)
    echo "Stopping 86Duino Remote Control Car"
    ;;
  *)
    echo "Usage: /etc/init.d/car {start|stop}"
    exit 1
    ;;
esac

exit 0

把這支名為 car 的程式放進 /etc/init.d/ 之中,並用指令 update-rc.d car start 30 1 2 3 4 5 . stop 80 0 6 . 來開啟這個服務,其中

0:關機 (Halt)
1:單一使用者模式 (single user mode)
2-5:多使用者模式 (multi user mode)
6:系統重啟 (reboot)

而若要關閉則使用 update-rc.d -f car remove 來關閉。要注意的是若使用不同的網卡要記得換成對應的指令唷!
最後最後,要連上遠端遙控車的網頁,還需要得到 86Duino 的 IP,這邊無論是要用 UDP Broadcast 還是車上裝小螢幕顯示出來都可以,小編就不多加贅述了,希望大家能玩得開心!