遙控 NXT 車最簡單應該是用 NXT 內建的藍牙,上
林老師教的 NXT 藍牙遙控車,是用一個 NXT 透過藍牙控制另一個 NXT 車,我想來搞一個不一樣的遙控車。但我只有一個 NXT,手機也沒有藍牙,就先由電腦來控制,剛好有一個幾年前申辦網路送的藍牙耳機附的 Dongle
DBT-122T 可用。
Windows 下有現成的藍牙程式可以控制 NXT,例如
NXT Remote--使用電腦鍵盤遙控NXT。我平常用 Linux 電腦較多,NXT 在 Linux 的資源沒有 Windows 那麼豐富,不過找到了這份
簡報,裡面有一個透過藍牙讀取 NXT 電池電壓的 C 程式,測試紀錄如後。
- 電腦安裝藍牙及編譯程式:我的電腦跑 Ubuntu 12.04,但不是標準的,很多套件被我移除了。bluez 包括 Linux 的藍牙程式及工具,libbluetooth-dev 含編譯藍牙用的標頭檔,gcc 是 C compiler。
sudo apt-get install bluez libbluetooth-dev gcc
- 開啟 NXT 藍牙功能
- 搜尋藍牙設備:使用 hcitool scan,會顯示 NXT 的 MAC 位址及名稱。MAC 位址在下一步寫程式時會用到。
$ hcitool scan
Scanning ...
00:16:53:17:7F:CF NXT
- 電腦藍牙程式:藍牙通訊也是用 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;
}
- 編譯
gcc nxt_getbattery.c -o nxt_getbattery -lm -lbluetooth
- 執行程式,NXT 會「ㄅㄧ」一聲顯示 Passkey: 1234,按橘色鈕確認會發生 Permission denied 錯誤,原來藍牙需要先配對一次。
$ ./nxt_getbattery
Error connecting Bluetooth: Permission denied
- 藍牙配對 (只有第一次需要,配對過後就不需要了):正常安裝的 Linux 可能會出現小視窗讓你輸入就解決了,可是我的電腦較慢,用較陽春的 LXDE 桌面,並移除了許多套件,並不會自動出現輸入 Passkey 的小視窗。找到這篇,裡面提到許多方式,但我偏好指令行,開另一個終端機,執行:
bluetooth-agent 1234
- 最後執行成功,電壓只有 6.9 V,有點低。
$ ./nxt_getbattery
bluetooth connected to 00:16:53:xx:xx:xx
battery level: 6.903
雖然是個簡單的程式,但是其它應用重要的基礎。簡報裡後面還有一個 NXT Mailbox 範例,以後再研究。