2012年8月26日 星期日

Linux 用藍牙跟 NXT 通訊的程式

遙控 NXT 車最簡單應該是用 NXT 內建的藍牙,上林老師教的 NXT 藍牙遙控車,是用一個 NXT 透過藍牙控制另一個 NXT 車,我想來搞一個不一樣的遙控車。但我只有一個 NXT,手機也沒有藍牙,就先由電腦來控制,剛好有一個幾年前申辦網路送的藍牙耳機附的 Dongle DBT-122T 可用。

Windows 下有現成的藍牙程式可以控制 NXT,例如 NXT Remote--使用電腦鍵盤遙控NXT。我平常用 Linux 電腦較多,NXT 在 Linux 的資源沒有 Windows 那麼豐富,不過找到了這份簡報,裡面有一個透過藍牙讀取 NXT 電池電壓的 C 程式,測試紀錄如後。
  1. 電腦安裝藍牙及編譯程式:我的電腦跑 Ubuntu 12.04,但不是標準的,很多套件被我移除了。bluez 包括 Linux 的藍牙程式及工具,libbluetooth-dev 含編譯藍牙用的標頭檔,gcc 是 C compiler。
    sudo apt-get install bluez libbluetooth-dev gcc
  2. 開啟 NXT 藍牙功能
  3. 搜尋藍牙設備:使用 hcitool scan,會顯示 NXT 的 MAC 位址及名稱。MAC 位址在下一步寫程式時會用到。
    $ hcitool scan
    Scanning ...
     00:16:53:17:7F:CF NXT
    
  4. 電腦藍牙程式:藍牙通訊也是用 socket,通訊協定依照 NXT Communication Protocol,可到 Bluetooth Developer Kit 下載,Mindstorms NXT 與 Arduino之間的藍牙通訊 有一些說明。複製簡報中的程式,填上 NXT 的 MAC 位址,並修正一些錯誤,如下。
    /* nxt_getbattery.c */
    #include <stdio.h>
    // Socket, used for Bluetooth socket
    #include <sys/socket.h>
    #include <sys/types.h>
    
    // Bluetooth headers
    #include <bluetooth/bluetooth.h>
    #include <bluetooth/rfcomm.h>
    
    // Global Variables
    int nxtSocket;
    int init_bluetooth(char *btAddress)
    {
     struct sockaddr_rc addr={0};
     int status;
    
     /*-----------------------------------------------------------
      * SOCK_STREAM
      * Provides sequenced, reliable, two-way, connection-based
      * byte streams. An out-of-band data transmission
      * mechanism may be supported.
      *----------------------------------------------------------*/
    
     // Allocate a socket
     nxtSocket = socket(AF_BLUETOOTH, SOCK_STREAM, BTPROTO_RFCOMM);
    
     // Set what type and who to connect to
     addr.rc_family = AF_BLUETOOTH;
     addr.rc_channel = (uint8_t) 1;
     str2ba(btAddress, &addr.rc_bdaddr);
    
     // Connect to the NXT
     status = connect(nxtSocket, (struct sockaddr *)&addr, sizeof(addr) );
     if (status < 0) {
      perror("Error connecting Bluetooth");
      return status;
     }
     return 0;
    }
    
    /****************************************************************
     * * nxt get battery level
     * * this will get the battery level on the nxt
     * * returns: the battery level as an integer
     * ***************************************************************/
    #define MAX_MESSAGE_SIZE 64
    int nxt_getbattery(void)
    {
     /*------------------------------------------------------
      * * direct command format:
      * * {length/lsb, length/msb, byte 0, byte 1... byte n}
      * *
      * * for getbatterylevel (see direct commands):
      * * byte 0: 0x00
      * * byte 1: 0x0b
      * * length/lsb: 0x02, the command length is 2 bytes
      * *----------------------------------------------------*/
     char cmd[4]={0x02, 0x00, 0x00, 0x0b};
     char reply[MAX_MESSAGE_SIZE];
     int result;
     int blevel;
     int replylength;
     int error = 0;
     //- send request -----------------------------------------
     if ( (result = write(nxtSocket, cmd, 4)) < 0 ) {
      perror("error sending getbatterylevel command ");
      return result;
     }
    
     //- read reply -----------------------------------------
     // get bluetooth message length
     if ( (result = read(nxtSocket, reply, 2)) < 0) {
      perror("error receiving getbatterylevel reply ");
      return result;
     }
     replylength = reply[0] + (reply[1] * 256);
    
     // get return package
     if ( (result = read(nxtSocket, reply, replylength)) < 0) {
      perror("error receiving getbatterylevel reply ");
      return result;
     }
    
     // quick check to make sure we got everything
     if (replylength != result) {
      fprintf(stderr,
       "getbatterylevel : lengths do not match : %d != %d\n",
       replylength, result);
     }
    
     /*------------------------------------------------------
      * * return package format:
      * * {length/lsb, length/msb, byte0, byte1..., byten}
      * * for getbatterylevel:
      * * byte0: 0x02
      * * byte1: 0x0b
      * * byte2: status byte
      * * byte3-4: voltage in millivolts (uword)
      * * length/lsb: 0x05
      * *----------------------------------------------------*/
     // byte 0
     if (reply[0] != 0x02) {
      fprintf(stderr, "getbatterylevel : byte 0 : %hhx != 0x02\n", reply[0]);
      error = 1;
     }
     // byte 1
     //if (reply[1] != 0x13) {
     if (reply[1] != 0x0b) {
      fprintf(stderr, "getbatterylevel : byte 1 : %hhx != 0x0b\n", reply[1]);
      error = 1;
     }
     // byte 2
     if (reply[2] != 0x00) {
      fprintf(stderr, "getbatterylevel : byte 2, status : %hhx \n", reply[2]);
      error = 1;
     }
     if (error) {
      return -1;
     }
     // byte 3-4
     blevel = reply[3] + (reply[4] * 256);
     return blevel;
    }
    
    int main (void) {
     // nxt brick alpha bluetooth address
     //char btaddress[18] = "00:16:53:01:2c:84";
     char btaddress[18] = "00:16:53:17:7F:CF";
     int blevel;
    
     // initiate bluetooth connection
     if (init_bluetooth(btaddress) < 0) {
      close(nxtSocket);
      return 1;
     }
     printf("bluetooth connected to %s \n", btaddress);
     // get battery level (direct command)
     blevel = nxt_getbattery();
     if (blevel < 0) {
      close(nxtSocket);
      return 1;
     }
     printf("battery level: %.3f\n", blevel/1000.0);
     close(nxtSocket);
     return 0;
    }
    
  5. 編譯
    gcc nxt_getbattery.c -o nxt_getbattery -lm -lbluetooth
  6. 執行程式,NXT 會「ㄅㄧ」一聲顯示 Passkey: 1234,按橘色鈕確認會發生 Permission denied 錯誤,原來藍牙需要先配對一次。
    $ ./nxt_getbattery
    Error connecting Bluetooth: Permission denied
    
  7. 藍牙配對 (只有第一次需要,配對過後就不需要了):正常安裝的 Linux 可能會出現小視窗讓你輸入就解決了,可是我的電腦較慢,用較陽春的 LXDE 桌面,並移除了許多套件,並不會自動出現輸入 Passkey 的小視窗。找到這篇,裡面提到許多方式,但我偏好指令行,開另一個終端機,執行:
    bluetooth-agent 1234
  8. 最後執行成功,電壓只有 6.9 V,有點低。
    $ ./nxt_getbattery 
    bluetooth connected to 00:16:53:xx:xx:xx 
    battery level: 6.903
    
雖然是個簡單的程式,但是其它應用重要的基礎。簡報裡後面還有一個 NXT Mailbox 範例,以後再研究。

沒有留言:

張貼留言

SIP header Via

所有 SIP 訊息 都要有 Via,縮寫 v。一開始的 UAC 和後續途經的每個 proxy 都會疊加一個 Via 放傳送的位址,依序作為回應的路徑。 格式 sent-protocol sent-by [ ;branch= branch ][ ; 參數 ...] s...