BBB에 TTS 구현하기

BBB를 display에 연결하지 않을 경우 정보를 표시하는데 한계가 있다. 이는 LED나 모터 등 대채 actuator로 해결할 수 있다. 이 중 speaker는 가장 손 쉬운 actuator이고, TTS가 된다면 display가 없는 BBB의 가치는 매우 높아질 것이다.

TTS용 npm은 기본적으로 TTS engine을 command line으로 구동시키는 구조로 되어 있다. OSX에는 say라는 명령어가 제공되고, Linux의 경우 Festival이 유명하다. (미안하지만 windows는 skip!) BBB는 Linux 머신이기 때문에 Linux에서 사용할 수 있는 TTS engine이라면 바로 사용할 수 있다. 여기 Linux에서 사용할 수 있는 TTS engine 리스트가 있다.

. espeak : a compact open source software speech synthesizer for English and other languages, for Linux and Windows.

. festival : a general framework for building speech synthesis systems as well as including examples of various modules.

. mbrola : a non-free phonemes-to-audio program.

. speech-dispatcher : common interface to speech synthesis.

아무래도 festival이 가장 유명하기 때문에 festival을 opkg로 설치해 보았으나 해당 module을 찾을 수가 없었다. 대신 opkg로 espeak를 찾을 수 있었다.


$ opkg install espeak

mini-hdmi를 연결한 후 모니터 스피커로 바로 espeak 테스트에 들어갔다.


$ espeak "hello"

‘hello’ 소리가 잘 나온다. 이제 npm을 찾아보았다. 매우 심플한 npm이 있었다. node-tts를 clone하여 OSX에서 테스트해 보았다. 역시 잘 동작한다. 이제는 espeak를 이 npm에 포팅해본다.

먼저 fork하여 나만의 npm을 만들고, espeak를 통해서 TTS가 동작되도록 npm 코드를 추가하였다.

수정된 코드는 메인 프로젝트에 pull request를 하였는데, 받아줄지 어떨지 잘 모르겠다.

해당 코드를 BBB에 다운받아 처리해 보았다.


$ npm install git://github.com/musart/node-tts

여기서 설치시에는 git://으로 시작되는 주소를 써야한다. https://주소를 사용하면 ENOENT에러가 안 난다.

Bonescript를 osx에 설치하기(진행중)

bonescript는 beaglebone에 연결된 serial, i2c, gpio등을 javascript로 제어할 수 있는 npm 툴이다. serial, i2c, gpio는 system에 매우 밀접한 기능이라서 node-gyp로 되어 있다. gyp는 generate your project의 약자로 각 플랫폼에 맞게 프로젝트를 빌드하는 기능이다. 즉, beaglebone에 맞도록 컴파일해서 기능을 제공하는 것이다. 좀 더 이해를 돕기 위해 Rhio님의 블로그를 참고하면 좋겠다. bonescript는 beaglebone이 embedded linux이기 때문에 beaglebone에서 아무런 문제없이 node-gyp가 동작하고 또한 설치된다. 하지만 osx에 bonescript를 설치하면 설치 중간에 바로 에러가 난다. 바로 이 node-gyp내에 컴파일에서 에러가 나기 때문이다. 다음은 osx에 설치하면서 부딯친 문제들을 정리해 두었다. 굳이 embedded linux용 npm을 osx에 설치하려는 이유는 주 개발 PC가 osx였고, 매번 node.js 어플리케이션을 BBB에 deploy하기가 부담되기 때문에 osx에 bonescript를 설치하고, 실제 hardware i/o가 일어나지 않더라도 mock하도록 처리하고 싶었다.

1. node-gyp 설치

node-gyp를 설치한다.


$ npm install node-gyp

참고로 설치 후 node-gyp가 잘 동작되지 않을때는 다음의 폴더를 삭제 후 재설치하니 잘 동작되었다.


$ rm -rf ~/.node-gyp

2. misc.cpp파일 수정하기

node-gyp가 잘 설치되면, bonescript의 gyp가 misc.cpp를 빌드하려 한다. misc.cpp가 build대상파일이 되는 이유는 binding.gyp에 misc.cpp가 gyp파일이라고 명기되어 있기 때문이다.

{
"targets": [
{
"target_name": "misc",
"sources": [ "misc.cpp" ]
}
]
}

misc.cpp가 잘 컴파일된다면 gpio제어용으로 사용되는 gpioint.js의 instance로 연결되어 사용된다. 즉, BBB의 gpio를 제어하기 위해 misc.cpp가 컴파일되어야 한다. osx는 당연히 gpio를 사용하도록 허가되어 있지 않으므로 사용할 수 없어서 제거할 수 있겠지만 workaround로 misc.cpp를 수정할 수도 있겠다 싶어 살펴보았다.

misc.cpp는 <sys/epoll.h> 헤더파일에 대한 dependency가 존재한다. 코드 안에도 epoll_create() 등 많이 사용된다. epoll library는 I/O 이벤트 알림 기능이다. 즉, 불규칙적으로 GPIO로 데이터가 들어오면 system으로부터 event를 받는 구조로 되어 있다. 유사한 기능으로 select, kqueue 등이 있다. 특히 kqueue는 FreeBSD계열에서 사용되는 library로 osx는 epoll 대신 kqueue를 사용한다.


$ sudo npm install ../../../../bonescript/node_modules/bonescript

> bonescript@0.2.3 preinstall /Users/........................./node_modules/bonescript
> node-gyp clean || (exit 0); node-gyp configure build

CXX(target) Release/obj.target/misc/misc.o
../misc.cpp:1:9: warning: 'BUILDING_NODE_EXTENSION' macro redefined
#define BUILDING_NODE_EXTENSION
^
<command line>:4:9: note: previous definition is here
#define BUILDING_NODE_EXTENSION 1
^
../misc.cpp:8:10: fatal error: 'sys/epoll.h' file not found
#include <sys/epoll.h>
^
1 warning and 1 error generated.
make: *** [Release/obj.target/misc/misc.o] Error 1
gyp ERR! build error
gyp ERR! stack Error: `make` failed with exit code: 2
gyp ERR! stack at ChildProcess.onExit (/usr/local/lib/node_modules/npm/node_modules/node-gyp/lib/build.js:267:23)
gyp ERR! stack at ChildProcess.EventEmitter.emit (events.js:98:17)
gyp ERR! stack at Process.ChildProcess._handle.onexit (child_process.js:789:12)
gyp ERR! System Darwin 12.4.0
gyp ERR! command "node" "/usr/local/lib/node_modules/npm/node_modules/node-gyp/bin/node-gyp.js" "configure" "build"
gyp ERR! cwd /Users/............................../node_modules/bonescript
gyp ERR! node -v v0.10.15
gyp ERR! node-gyp -v v0.10.6
gyp ERR! not ok
npm ERR! weird error 1
npm ERR! not ok code 0

생각이 여기까지 미치자 epoll to kqueue 컨버터를 찾아야 겠다고 판단했다. pnotify라는 프로젝트이다. 주 기능은 file system의 event notification을 제공하는 기능이나, 나처럼 epoll to kqueue컨버터가 필요했던 모양이다. epoll.hepoll.c파일을 misc.cpp가 있는 폴더에 추가하고, binding.gyp에 epoll.c를 추가하였다. 컴파일 결과 다음과 같이 추가 에러가 발생하였다.


$ sudo npm install ../../../../bonescript/node_modules/bonescript

> bonescript@0.2.3 preinstall /Users/....................../node_modules/bonescript
> node-gyp clean || (exit 0); node-gyp configure build

CXX(target) Release/obj.target/misc/misc.o
../misc.cpp:1:9: warning: 'BUILDING_NODE_EXTENSION' macro redefined
#define BUILDING_NODE_EXTENSION
 ^
<command line>:4:9: note: previous definition is here
#define BUILDING_NODE_EXTENSION 1
 ^
../misc.cpp:15:17: warning: using directive refers to implicitly-defined
 namespace 'std'
using namespace std;
 ^
../misc.cpp:21:27: error: unknown type name 'EV_P_'
static void pollpri_event(EV_P_ ev_io * req, int revents);
 ^
../misc.cpp:21:39: error: expected ')'
static void pollpri_event(EV_P_ ev_io * req, int revents);
 ^
../misc.cpp:21:26: note: to match this '('
static void pollpri_event(EV_P_ ev_io * req, int revents);
 ^
../misc.cpp:32:5: error: unknown type name 'ev_io'
 ev_io event_watcher, *ew;
 ^
../misc.cpp:148:31: error: unknown type name 'EV_P_'
 static void pollpri_event(EV_P_ ev_io * req, int revents) {
 ^
../misc.cpp:148:43: error: expected ')'
 static void pollpri_event(EV_P_ ev_io * req, int revents) {
 ^
../misc.cpp:148:30: note: to match this '('
 static void pollpri_event(EV_P_ ev_io * req, int revents) {
 ^
../misc.cpp:38:16: warning: expression result unused [-Wunused-value]
 PRINTF("EV_DEFAULT_ = %s\n", TEST_EV_DEFAULT_NAME);
 ^~~~~~~~~~~~~~~~~~~~
../misc.cpp:37:16: warning: expression result unused [-Wunused-value]
 PRINTF("Entering Init\n");
 ^~~~~~~~~~~~~~~~~
../misc.cpp:38:38: warning: expression result unused [-Wunused-value]
 PRINTF("EV_DEFAULT_ = %s\n", TEST_EV_DEFAULT_NAME);
 ^~~~~~~~~~~~~~~~~~~~
../misc.cpp:25:30: note: expanded from macro 'TEST_EV_DEFAULT_NAME'
#define TEST_EV_DEFAULT_NAME STR(EV_DEFAULT_)
 ^~~~~~~~~~~~~~~~
../misc.cpp:24:20: note: expanded from macro 'STR'
#define STR(macro) QUOTE(macro)
 ^~~~~~~~~~~~
../misc.cpp:23:26: note: expanded from macro 'QUOTE'
#define QUOTE(name, ...) #name
 ^~~~~
<scratch space>:112:1: note: expanded from macro '#'
"EV_DEFAULT_"
^~~~~~~~~~~~~
../misc.cpp:47:16: warning: expression result unused [-Wunused-value]
 PRINTF("Leaving Init\n");
 ^~~~~~~~~~~~~~~~
../misc.cpp:51:16: warning: expression result unused [-Wunused-value]
 PRINTF("Entering Pollpri constructor\n");
 ^~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
../misc.cpp:63:20: error: use of undeclared identifier 'EV_DEFAULT_'; did you
 mean 'UV_EFAULT'?
 ev_io_stop(EV_DEFAULT_ &event_watcher);
 ^~~~~~~~~~~
 UV_EFAULT
/Users/..../.node-gyp/0.10.15/deps/uv/include/uv.h:137:16: note: 'UV_EFAULT'
 declared here
 UV_ERRNO_MAP(UV_ERRNO_GEN)
 ^
/Users/..../.node-gyp/0.10.15/deps/uv/include/uv.h:90:3: note: expanded from
 macro 'UV_ERRNO_MAP'
 XX( 15, EFAULT, "bad address in system call argument") \
 ^
/Users/..../.node-gyp/0.10.15/deps/uv/include/uv.h:135:36: note: expanded from
 macro 'UV_ERRNO_GEN'
#define UV_ERRNO_GEN(val, name, s) UV_##name = val,
 ^
<scratch space>:141:1: note: expanded from macro 'UV_'
UV_EFAULT
^
../misc.cpp:60:16: warning: expression result unused [-Wunused-value]
 PRINTF("Entering Pollpri destructor\n");
 ^~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
../misc.cpp:84:16: warning: expression result unused [-Wunused-value]
 PRINTF("open(%s) returned %d: %s\n", p->path, fd, strerror(errno));
 ^~~~~~~~~~~~~~~~~~~~~~~~~~~~
../misc.cpp:84:49: warning: expression result unused [-Wunused-value]
 PRINTF("open(%s) returned %d: %s\n", p->path, fd, strerror(errno));
 ~ ^~~~
../misc.cpp:84:55: warning: expression result unused [-Wunused-value]
 PRINTF("open(%s) returned %d: %s\n", p->path, fd, strerror(errno));
 ^~
../misc.cpp:88:16: warning: expression result unused [-Wunused-value]
 PRINTF("epoll_create(1) returned %d: %s\n", epfd, strerror(errno));
 ^~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
../misc.cpp:88:53: warning: expression result unused [-Wunused-value]
 PRINTF("epoll_create(1) returned %d: %s\n", epfd, strerror(errno));
 ^~~~
../misc.cpp:90:21: error: use of undeclared identifier 'EPOLLPRI'; did you mean
 'EPOLLERR'?
 ev.events = EPOLLPRI;
 ^~~~~~~~
 EPOLLERR
../epoll.h:33:5: note: 'EPOLLERR' declared here
 EPOLLERR = 0x008,
 ^
../misc.cpp:93:16: warning: expression result unused [-Wunused-value]
 PRINTF("epoll_ctl(%d) returned %d (%d): %s\n", fd, n, epfd,...
 ^~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
../misc.cpp:93:56: warning: expression result unused [-Wunused-value]
 ...returned %d (%d): %s\n", fd, n, epfd, strerror(errno));
 ^~
../misc.cpp:93:60: warning: expression result unused [-Wunused-value]
 ...returned %d (%d): %s\n", fd, n, epfd, strerror(errno));
 ^
../misc.cpp:93:63: warning: expression result unused [-Wunused-value]
 ...returned %d (%d): %s\n", fd, n, epfd, strerror(errno));
 ^~~~
../misc.cpp:99:16: warning: expression result unused [-Wunused-value]
 PRINTF("seek(%d) %d bytes: %s\n", fd, m, strerror(errno));
 ^~~~~~~~~~~~~~~~~~~~~~~~~
../misc.cpp:99:43: warning: expression result unused [-Wunused-value]
 PRINTF("seek(%d) %d bytes: %s\n", fd, m, strerror(errno));
 ^~
../misc.cpp:99:47: warning: expression result unused [-Wunused-value]
 PRINTF("seek(%d) %d bytes: %s\n", fd, m, strerror(errno));
 ^
../misc.cpp:102:16: warning: expression result unused [-Wunused-value]
 PRINTF("read(%d) %d bytes (%s): %s\n", fd, m, buf, strerror(errno));
 ^~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
../misc.cpp:102:48: warning: expression result unused [-Wunused-value]
 PRINTF("read(%d) %d bytes (%s): %s\n", fd, m, buf, strerror(errno));
 ^~
../misc.cpp:102:52: warning: expression result unused [-Wunused-value]
 PRINTF("read(%d) %d bytes (%s): %s\n", fd, m, buf, strerror(errno));
 ^
../misc.cpp:102:55: warning: expression result unused [-Wunused-value]
 PRINTF("read(%d) %d bytes (%s): %s\n", fd, m, buf, strerror(errno));
 ^~~
../misc.cpp:106:21: error: use of undeclared identifier 'EV_DEFAULT_'; did you
 mean 'UV_EFAULT'?
 ev_io_start(EV_DEFAULT_ p->ew);
 ^~~~~~~~~~~
 UV_EFAULT
/Users/..../.node-gyp/0.10.15/deps/uv/include/uv.h:137:16: note: 'UV_EFAULT'
 declared here
 UV_ERRNO_MAP(UV_ERRNO_GEN)
 ^
/Users/..../.node-gyp/0.10.15/deps/uv/include/uv.h:90:3: note: expanded from
 macro 'UV_ERRNO_MAP'
 XX( 15, EFAULT, "bad address in system call argument") \
 ^
/Users/..../.node-gyp/0.10.15/deps/uv/include/uv.h:135:36: note: expanded from
 macro 'UV_ERRNO_GEN'
#define UV_ERRNO_GEN(val, name, s) UV_##name = val,
 ^
<scratch space>:141:1: note: expanded from macro 'UV_'
UV_EFAULT
^
../misc.cpp:106:33: error: expected ')'
 ev_io_start(EV_DEFAULT_ p->ew);
 ^
../misc.cpp:106:20: note: to match this '('
 ev_io_start(EV_DEFAULT_ p->ew);
 ^
../misc.cpp:67:16: warning: expression result unused [-Wunused-value]
 PRINTF("Entered New\n");
 ^~~~~~~~~~~~~~~
../misc.cpp:111:16: warning: expression result unused [-Wunused-value]
 PRINTF("Leaving New\n");
 ^~~~~~~~~~~~~~~
../misc.cpp:117:16: warning: expression result unused [-Wunused-value]
 PRINTF("fd = %d, epfd = %d, revents = 0x%0x\n", fd, epfd, revents);
 ^~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
../misc.cpp:117:57: warning: expression result unused [-Wunused-value]
 PRINTF("fd = %d, epfd = %d, revents = 0x%0x\n", fd, epfd, revents);
 ^~
../misc.cpp:117:61: warning: expression result unused [-Wunused-value]
 PRINTF("fd = %d, epfd = %d, revents = 0x%0x\n", fd, epfd, revents);
 ^~~~
../misc.cpp:118:23: error: use of undeclared identifier 'EV_READ'
 if(revents != EV_READ) {
 ^
../misc.cpp:126:16: warning: expression result unused [-Wunused-value]
 PRINTF("seek(%d) %d bytes: %s\n", fd, m, strerror(errno));
 ^~~~~~~~~~~~~~~~~~~~~~~~~
../misc.cpp:126:43: warning: expression result unused [-Wunused-value]
 PRINTF("seek(%d) %d bytes: %s\n", fd, m, strerror(errno));
 ^~
../misc.cpp:126:47: warning: expression result unused [-Wunused-value]
 PRINTF("seek(%d) %d bytes: %s\n", fd, m, strerror(errno));
 ^
../misc.cpp:129:16: warning: expression result unused [-Wunused-value]
 PRINTF("read(%d) %d bytes (%s): %s\n", fd, m, buf, strerror(errno));
 ^~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
../misc.cpp:129:48: warning: expression result unused [-Wunused-value]
 PRINTF("read(%d) %d bytes (%s): %s\n", fd, m, buf, strerror(errno));
 ^~
../misc.cpp:129:52: warning: expression result unused [-Wunused-value]
 PRINTF("read(%d) %d bytes (%s): %s\n", fd, m, buf, strerror(errno));
 ^
../misc.cpp:129:55: warning: expression result unused [-Wunused-value]
 PRINTF("read(%d) %d bytes (%s): %s\n", fd, m, buf, strerror(errno));
 ^~~
../misc.cpp:117:67: warning: expression result unused [-Wunused-value]
 PRINTF("fd = %d, epfd = %d, revents = 0x%0x\n", fd, epfd, revents);
 ^~~~~~~
../misc.cpp:150:44: error: use of undeclared identifier 'req'
 Pollpri *p = static_cast<Pollpri*>(req->data);
 ^
../misc.cpp:151:21: error: use of undeclared identifier 'revents'; did you mean
 'kevent'?
 p->Event(p, revents);
 ^~~~~~~
 kevent
/usr/include/sys/event.h:308:9: note: 'kevent' declared here
int kevent(int kq, const struct kevent *changelist, int nchanges,
 ^
../misc.cpp:151:21: error: cannot initialize a parameter of type 'int' with an
 lvalue of type 'int (int, const struct kevent *, int, struct kevent *,
 int, const struct timespec *)'
 p->Event(p, revents);
 ^~~~~~~
../misc.cpp:115:33: note: passing argument to parameter 'revents' here
 void Event(Pollpri * p, int revents) {
 ^
../misc.cpp:149:16: warning: expression result unused [-Wunused-value]
 PRINTF("Entered pollpri_event\n");
 ^~~~~~~~~~~~~~~~~~~~~~~~~
../misc.cpp:152:16: warning: expression result unused [-Wunused-value]
 PRINTF("Leaving pollpri_event\n");
 ^~~~~~~~~~~~~~~~~~~~~~~~~
../misc.cpp:160:16: warning: expression result unused [-Wunused-value]
 PRINTF("Calling Init\n");
 ^~~~~~~~~~~~~~~~
40 warnings and 13 errors generated.
make: *** [Release/obj.target/misc/misc.o] Error 1
gyp ERR! build error
gyp ERR! stack Error: `make` failed with exit code: 2
gyp ERR! stack at ChildProcess.onExit (/usr/local/lib/node_modules/npm/node_modules/node-gyp/lib/build.js:267:23)
gyp ERR! stack at ChildProcess.EventEmitter.emit (events.js:98:17)
gyp ERR! stack at Process.ChildProcess._handle.onexit (child_process.js:789:12)
gyp ERR! System Darwin 12.4.0
gyp ERR! command "node" "/usr/local/lib/node_modules/npm/node_modules/node-gyp/bin/node-gyp.js" "configure" "build"
gyp ERR! cwd /User/............................/node_modules/bonescript
gyp ERR! node -v v0.10.15
gyp ERR! node-gyp -v v0.10.6
gyp ERR! not ok
npm ERR! weird error 1
npm ERR! not ok code 0

여기까지 팠다. 더 파서 kqueue를 동작시키게 하면 좋겠지만, 시간이 부족하여, binding,gyp, misc.cpp 파일을 모두 제거하고 osx에 bonescript를 인스톨하였다. 다음에 시간이 허락되면 이 부분부터 다시 digging해야 겠다.

BeagleBone에 node.js버전 업그레이드

BeagleBoneBlack에 node.js는 0.8.22가 깔려있다. 최신 버전이 필요할 경우가 생겨서 node.js 버전을 최신으로 업그레이드 하였다. 그 step을 공유해보자. 우선 ethernet을 연결한 상태에서 BBB 작업을 진행했다.

1. system time  설정

npm, curl, wget 등으로 https로 파일을 가져올때 BBB의 system time이 적절하지 않으면 에러가 난다. 먼저 BBB의 system time을 현재시간으로 맞춰주자.


$ ntpdate ntp.ubuntu.com

 

2. nvm을 설치해서 nvm을 통해 node.js 최신버전을 설치

nvm은 node.js 버전 관리 메니저로 손쉽게 node.js버전을 설치/삭제/switch할 수 있다. 설치방법은 이곳을 따른다. nvm은 내부적으로 우선 미리 compile된 binary를 찾고, 이 binary가 시스템에서 execute가 되면 설치를 완료한다. 만약 이 binary가 실행하지 못하면 full node.js source code를 다운받아 새로이 compile하게 된다. 일반적으로 mac이나 linux는 x86기기라서 다운받은 binary가 잘 execute되지만, BBB는 ARM cortex A8 CPU이기 때문에 node.js쪽에 준비된 compile된 binary가 없었을 것이다. 또한 많은 ARM core 버전NEON instruction등을 사용하는냐 마느냐 등 arm쪽 binary를 서버에 준비해 두기에는 다양한 변수들이 많았을 것이다. 결국 nvm은 full source code를 다운받아 자체 컴파일을 시도한다. 자체 컴파일이 진행되면 시간이 제법 오래 걸린다. 경험상 3~4시간 걸렸던 것으로 판단된다.

3. nvm버전 switch후 사용하기


$ nvm use 0.10.16

$ node -v

v0.10.16

이제 최신버전의 node.js로 변경되었다. 이전에 pre-install되었던 0.8.22버전은 nvm에서 관리되지 않으니 유의하기 바란다. 다시 0.8.22로 변경하기 위해서는 nvm install 명령어를 통해서 설치하면 0.10.16과 0.8.22를 손쉽게 switch할 수 있다.

node.js 버전 업/다운그레이드 하기

node.js를 개발할때 version dependent한 상황이 발생한다. 이때 사용할 수 있는게 nvm 또는 n module이다.

우선 nvm을 살펴보자.

먼저 nvm을 설치한다.


$ curl https://raw.github.com/creationix/nvm/master/install.sh | sh
 % Total % Received % Xferd Average Speed Time Time Time Current
 Dload Upload Total Spent Left Speed
100 1391 100 1391 0 0 694 0 0:00:02 0:00:02 --:--:-- 1074
Cloning into '/Users/account/.nvm'...
remote: Counting objects: 712, done.
remote: Compressing objects: 100% (478/478), done.
remote: Total 712 (delta 347), reused 567 (delta 223)
Receiving objects: 100% (712/712), 107.87 KiB | 66 KiB/s, done.
Resolving deltas: 100% (347/347), done.

=> Appending source string to /Users/account/.bash_profile
=> Close and reopen your terminal to start using NVM
$

설치가 되었다. path 설정을 위해 bash_profile을 재설정해준다.


$ source ~/.bash_profile

이제 원하는 node 버전을 다운받아 설치하고 사용해본다.

$ nvm install 0.10
$ nvm install 0.10.15
$ nvm use 0.10

다음 n을 살펴보자.

먼저 n 모듈을 설치한다.


$ npm install -g n

다음 원하는 node 버전을 몇가지 선택하여 설치한다.


$ n 0.8.14
$ n 0.8.17
$ n 0.9.6

이후 node 버전을 선택하면 손쉽게 switch할 수 있다.


$ n

0.8.14
ο 0.8.17
 0.9.6

참고로 Ruby은 rvm이라는 툴을 제공하여 손쉽게 ruby 버전을 관리할 수 있는데, nvm은 의미상 유사한 개념이라 볼 수 있겠다.

coffeescript to javascript

WebSocket 통신이 필요해서 이것저것 알아보다가 Web Socket Rails를 찾게 되었다. node.js로 된 Web Socket 서버로 구현하면 쉽게 되겠지만, 서버쪽이 Ruby on Rails로 되어 있어서 찾기 어려웠다. 어찌됬건, Web Socket Rails 서버 예제를 돌리고, WebSocket 클라이언트를 살펴보았다.

클라이언트 코드가 coffeescript로 짜여 있어서 javascript로 변환해야 한다. 여러가지 웹 변환툴이 있었지만, 개인적으로 cli가 편하기 때문에 cli로 찾아보았다.

1. mac의 port를 사용하여 install하기


$ sudo port install coffee-script

$ coffee -c xxxx.coffee

 

2. npm으로 coffee script 설치하기


$ npm install coffeescript

$ coffee -c xxxx.coffee

 

두 가지 방법 모두 같은 명령어인  coffee로 되어 있으니, 설치 경로는 틀리나 내부적으로는 같은 binary가 설치되지 않나 생각된다.

mocha를 이용한 node.js

TDD(Test Driven Development)로 얻을 수 있는 장점은 많다. 궁극적으로는 테스팅하는데 시간을 낭비하지 않고 훌륭한 코드 품질을 얻고자 함이겠다. 개인적으로는 TDD를 오래전부터 들어왔어도, Testcase를 먼저 만들고 실제 함수 구현을 나중에 하는 구조가 익숙하지 않아 거부하였지만,(또한 회사에서 TDD를 산출물로 관리하려는 듯 하여 거부하기도 했지만..) 이번에 node.js로 개인 프로젝트를 하면서 문득 TDD를 적용해 보고 싶어서 진행했는데, 의외로 아무런 거부감 없이 TDD의 강력함을 절실히 느끼고 있다.

우선 프로젝트가 BBB에 node.js를 올리는 것이기 때문에 node.js용 TDD framework를 찾아보았다. mocha라는 프로젝트가 유명했고,이 블로그(링크)가 매우 잘 정리되어 있었다. 해당 블로그를 바탕으로 node.js용 TDD를 설명해 볼까 한다.

mocha를 우선 깔아야 한다. 설치는 매우 간단하다. 단 -g 옵션으로 글로벌로 설치한다.


$ sudo npm install -g mocha

다음은 SuperAgent와 expect.js를 설치하라고 되어 있는데, SuperAgent는 ajax를 보다 보기 좋게 개량한 api이고, expect.js는 TDD의 assertion library이다. 나의 경우 TDD를 하고자 하는 모듈이 ajax가 필요 없었으므로 SuperAgent는 설치 하지 않았다. 대신 expect.js를 설치했는데, mocha는 다양한 assertion library를 사용할 수 있게 처리해 두었다. 살펴보니 expect.js 문법이 가장 직관적이였다.


$ npm install expect.js

자 이제 재료는 다 준비되었다. 절대 구현을 먼저 하지 말고, 테스트 케이스부터 정하자. 난 개발자니까 테스트 케이스를 만드는 것은 재미 없다고 생각할 수 있다. 그럼 SRS로부터 해당 모듈이 해야 한다고 생각하는 기능 정의를 해보자. “테스트 케이스”를 만드는게 아니고 “기능 정의”라고 하면 보다 개발자스러운 접근이다. 거부감도 적다. 🙂

구현하고자 하는 모듈이 사람이름을 추가하고 삭제하는 모듈이라고 하자.

“이 모듈은 초기화를 할 수 있다.”

“이 모듈은 사람이름을 등록할 수 있다.”

“이 모듈은 사람이름을 삭제할 수 있다.”

“이 모듈은 저장된 사람이름의 개수를 알려줄 수 있다.”

“이 모듈은 최대로 저장할 수 있는 사람이름의 개수는 10개이다.”

“이 모듈은 같은 이름을 가진 사람이 있을 경우 등록 할 수 없다.”

대충 생각해 보니 이 정도인 듯 하다. 그럼 이 기능 정의를 바탕으로 testcase를 짜보자.

</pre>
var expect = require('expect.js');
describe('[name]', function() {
it('The module shell be initialized.', function(done) {
var ret = true;
expect(ret).to.equal(true);
done();
});
it('The module shell register a name.', function(done) {
var ret = true;
expect(ret).to.equal(true);
done();
});
it('The module shell remove a name.', function(done) {
var ret = true;
expect(ret).to.equal(true);
done();
});
it('The module shell provide the number of registered name.', function(done) {
var ret = true;
expect(ret).to.equal(true);
done();
});
it('The maximum number of registered name shall be 10.', function(done) {
var ret = true;
expect(ret).to.equal(true);
done();
});
it('The module do not accept the name, if the name is already registered.', function(done) {
var ret = true;
expect(ret).to.equal(true);
done();
});
});
<pre>

먼저 require()로 expect.js 모듈을 로드한다. 이후 describe()함수로 test group를 정의한다. 각각의 testcase는 it()함수로 정의한다. it()함수의 콜백함수는 done이라는 객체를 인자로 받게 되어있다. javascript의 asynchorous한 특성을 done 객체로 손 쉽게 해결했다. mocha가 done객체를 넘겨주면 필요한 기능 검증을 하고(synch든, asynch든) 다 해결되면 done()불러주면 다음 testcase로 넘어가게 된다.

실행을 해보면 6개의 testcase는 모두 성공이다.


$ mocha name_test.js

․․․․․․

6 passing (8 ms)

$

이제 첫번째 testcase를 수정해 보자.


it('The module shell be initialized.', function(done) {
var name = require('./name.js');
var ret = true;
expect(ret).to.equal(true);
done();
 });

require()로 name.js를 불렀다. 바로 mocha로 테스트 해보자.


$ mocha name_test.js

․․․․․․

5 passing (9 ms)
 1 failing

1) [name] The module shell be initialized.:
 Error: Cannot find module './name.js'
 at Function.Module._resolveFilename (module.js:338:15)
 at Function.Module._load (module.js:280:25)
 at Module.require (module.js:364:17)
 at require (module.js:380:17)
 at Context.<anonymous> (/............................/tdd/name_test.js:5:14)
 at Test.Runnable.run (/usr/local/lib/node_modules/mocha/lib/runnable.js:194:15)
 at Runner.runTest (/usr/local/lib/node_modules/mocha/lib/runner.js:355:10)
 at /usr/local/lib/node_modules/mocha/lib/runner.js:401:12
 at next (/usr/local/lib/node_modules/mocha/lib/runner.js:281:14)
 at /usr/local/lib/node_modules/mocha/lib/runner.js:290:7
 at next (/usr/local/lib/node_modules/mocha/lib/runner.js:234:23)
 at Object._onImmediate (/usr/local/lib/node_modules/mocha/lib/runner.js:258:5)
 at processImmediate [as _immediateCallback] (timers.js:330:15)

에러를 살펴보면 name.js가 없다. 그럼 이 testcase의 에러를 없애기 위해 name.js를 만든다.


module.exports = {

};

에러가 없다. 그럼 이제 테스트케이스 제목처럼 초기화가 되어야 함으로 무작정 init()함수를 불러보자.


it('The module shell be initialized.', function(done) {
 var name = require('./name.js');
 name.init();
 var ret = true;
 expect(ret).to.equal(true);
 done();
 });

mocha로 돌려보자. 에러가 있을까?


$ mocha name_test.js

․․․․․․

5 passing (9 ms)
 1 failing

1) [name] The module shell be initialized.:
 TypeError: Object #<Object> has no method 'init'
 at Context.<anonymous> (/..................../tdd/name_test.js:6:8)
 at Test.Runnable.run (/usr/local/lib/node_modules/mocha/lib/runnable.js:194:15)
 at Runner.runTest (/usr/local/lib/node_modules/mocha/lib/runner.js:355:10)
 at /usr/local/lib/node_modules/mocha/lib/runner.js:401:12
 at next (/usr/local/lib/node_modules/mocha/lib/runner.js:281:14)
 at /usr/local/lib/node_modules/mocha/lib/runner.js:290:7
 at next (/usr/local/lib/node_modules/mocha/lib/runner.js:234:23)
 at Object._onImmediate (/usr/local/lib/node_modules/mocha/lib/runner.js:258:5)
 at processImmediate [as _immediateCallback] (timers.js:330:15)

예상하지만 init()함수가 없다고 나온다. 그럼 name.js에서 init() 함수를 만들어 주자.


module.exports = {
init:function() {
}
};

mocha를 돌려본다. 에러가 있을까?


$ mocha name_test.js

․․․․․․

6 passing (9 ms)

에러가 없다. 이 와중에 name.js에 init()함수가 만들어 졌다. 이렇게 반복하면서 TDD 구현과 실제 모듈 구현을 함께 발전해 나갈 수 있다. 끝까지 다 구현해서 name.js 모듈을 완성하고 싶지만 글이 너무 길어져서 여기서 마무리 하겠다. 소개용이 아닌 개인 프로젝트용 모듈은 TDD를 통해 안정적인 소프트웨어를 구현할 수 있었다.

혹자는 TDD 구현 코드와 실제 모듈 구현 코드가 이중으로 부담이 되기 때문에 거부감이 있을 것이다. 초반에는 힘들 수 있다. 그러나 TDD 개발이 몸에 익으면 TDD 구현 코드는 copy & paste가 주로 이루어 진다. 그 정도의 노력으로 향후 실제 모듈의 안정성을 보장받는다면 안 할 이유가 없다고 생각된다. 단 관리 목적의 TDD라면 다시한번 생각해 볼 필요는 있겠다. 🙂

 

BBB(Beagle Bone Black)에서 ssh로 로그인하기

BBB(Beagle Bone Black)에 serial로 접속하는 방법 외에 ssh로 접속할 수 있다.

우선 BBB를 Mac에 USB로 연결한다. 약간의 시간(BBB가 Booting하는 시간) 뒤에 Mac의 terminal에서 다음과 같이 ssh 접속을 시도한다.


$ ssh root@192.168.7.2

root@192.168.7.2's password:

root@beaglebone:~# pwd

/home/root

root@beaglebone:~#

이제 BBB에 접속하여 내부 작업을 진행할 수 있다.

BBB(Beagle Bone Black) 로그 보기

BBB는 작은크기의 리눅스 PC이므로 리눅스 PC에서 할 수 있는 대부분의 일을 할 수 있다.

이 중 linux system에 serial로 접속하는 것은 기본이라 할 수 있겠다.

Mac에는 screen이라는 terminal emulator가 있다. screen을 통해 BBB를 접속해 보자.

먼저 BBB를 Mac에 USB로 연결한다.

USB 연결 즉시 BBB에 로그를 볼 수 있으면 좋겠지만, BBB 내부에서 USB to Serial이 구동되는 시간이 다소 걸린다. 대충 10초정도면 Mac에서 screen으로 연결가능하다.

Mac의 터미널에서 다음과 같이 검색하면 연결된 BBB가 나온다.

$ ls -la /dev/tty.* /dev/cu.*
crw-rw-rw- 1 root wheel 33, 3 Jul 9 00:37 /dev/cu.Bluetooth-Modem
crw-rw-rw- 1 root wheel 33, 1 Jul 9 00:37 /dev/cu.Bluetooth-PDA-Sync
crw-rw-rw- 1 root wheel 33, 9 Jul 18 20:51 /dev/cu.usbmodem1a121
crw-rw-rw- 1 root wheel 33, 35 Jul 22 19:53 /dev/cu.usbmodem1a123
crw-rw-rw- 1 root wheel 33, 2 Jul 9 00:37 /dev/tty.Bluetooth-Modem
crw-rw-rw- 1 root wheel 33, 0 Jul 9 00:37 /dev/tty.Bluetooth-PDA-Sync
crw-rw-rw- 1 root wheel 33, 8 Jul 18 20:51 /dev/tty.usbmodem1a121
crw-rw-rw- 1 root wheel 33, 34 Jul 22 19:56 /dev/tty.usbmodem1a123

나의 Mac에서는 /dev/tty.usbmodem1a123가 BBB이다. 이제 screen 커맨드로 BBB와 연결해 보자. 다음과 같이 연결하면 전체화면으로 전환되면서 Angstrom의 로그인 화면이 나타난다.


$ screen /dev/tty.usbmodem1a123 115200

.---O---.
| | .-. o o
| | |-----.-----.-----.| | .----..-----.-----.
| | | __ | ---'| '--.| .-'| | |
| | | | | |--- || --'| | | ' | | | |
'---'---'--'--'--. |-----''----''--' '-----'-'-'-'
 -' |
 '---'

The Angstrom Distribution beaglebone ttyGS0

Angstrom v2012.12 - Kernel 3.8.13

beaglebone login:

BBB shell로 로그인하기 위해 ‘root’를 입력하면 BBB shell로 진입할 수 있다.


beaglebone login: root
Last login: Sat Jan 1 00:00:20 UTC 2000 on ttyGS0
root@beaglebone:~#

이제 BBB로 연결되었다. BBB shell에서 여러가지를 할 수 있다.

BBB(BeagleBoneBlack) 처음 시작하기

OSX에서 BBB를 시작해 보자.

참고로 Getting Started with BeagleBone & BeagelBoneBlack 을 참고했다.

OSX에서 BBB를 사용하려면 2가지 driver를 OSX에 설치해야 한다. 하나는 HoRNDIS 드라이버이고, 다른 하나는 FTDI USB to Serial 드라이버이다.

HoRNDIS 드라이버는 OSX에서 안드로이드 폰의 USB 테더링을 지원해주는 드라이버인데, BBB 역시 해당 드라이버를 사용하여 localhost를 서비스 하기 위해 사용한다. 다운로드를 하여 OSX에 설치한다.

FTDI 드라이버는 OSX에서 USB to Serial로 로그를 보기위해 사용하게 된다. 역시 다운로드 하여 OSX에 설치한다.

두 드라이버를 설치한 후 USB로 BBB를 OSX에 연결해보자. 연결 후 잠시 시간이 지나면 다음과 같이 BeagleBoneBlack기기가 연결되었다고 나온다.

Screen Shot 2013-07-19 at 8.12.53 AM

 

이제 BBB안에 들어있는 홈페이지에 접속해 보자. 크롬이나 사파리 브라우저에서 192.168.7.2로 접속하면 다음의 페이지가 보인다.

Screen Shot 2013-07-19 at 8.22.49 AM

 

이제 BBB가 동작하고 열결됨을 확인할 수 있다.