BeagleBone에서 nodeJS Source Build 하기
게시: 2012/02/13 분류: Beaglebone, Log | Tags: beaglebone, nodejs 댓글 남기기 »BeagleBone을 받으면 기본적으로 node가 최신버전이 아닌 0.4.x다.
개인적으로 버전 욕심이 생겨 빌드해 봤더니 그냥 되진 않았다.
(맨날 이렇지 뭐.. =_=)
그냥 받아서 Build하면 다음과 같은 에러 메시지를 보게 된다.
"For thumb inter-working we require an architecture which supports box" (어쩌고저쩌고)
이 경우에는 deps/v8/SConstruct 파일을 수정한다.node 0.6.10 기준으로 Line 82 줄을 다음과 같이 수정한다.
'CCFLAGS': ['$DIALECTFLAGS', '$WARNINGFLAGS','-march=armv7-a'],
빌드가 잘되고, 실행 또한 잘 되는것을 확인할 수 있다. 그 밖에 OpenEmbedded Platform에서 node.js를 빌드가 한방에 되지 않는다면 다음의 문서를 참고하면 되겠다. http://fastr.github.com/articles/Node.js-on-OpenEmbedded.html
GitlabHQ + LDAP
게시: 2011/11/28 분류: Log | Tags: git, gitlabhq, ldap 댓글 남기기 »
근데 시작에 앞서… GitlabHQ가 뭘까?
나는 Free Git Repository Management App임
이해하기 쉽게 설명하자면 일단 Github은 아니지만, Github스러운 Git Management App이라고 할 수 있다.
크게 다른점이 하나 있다면…
Github은 기본적으로 Repository가 Public으로 read only이지만, GitlabHQ에서 Repository는 기본적으로 Private이다.
이 포스트를 쓰는 시점에 GitlabHQ Release는 Pronto(v1.2)까지 되었고, Moderno(v2.0)부터는 Gitolite를 공식 지원한다고 한다.
LDAP는 공식적으로 GitlabHQ에서 지원하지 않지만.. 붙이는데 그닥 어렵지 않다는 Issue Comment를 보고, 붙여보았다.
LDAP 붙이기 위해 omniauth-ldap와 devise_ldap_authenticatable 둘 다 써봤지만, devise_ldap_authenticatable이 가장 간단하고 빠르고 쉽게 설치할 수 있고, 무엇보다 정신건강에 이롭다.
요걸로 붙여보자.
1. GitlabHQ를 Checkout 받는다.
$ git clone https://github.com/gitlabhq/gitlabhq.git $ cd gitlabhq/
2. Gemfile을 수정해 devise 바로 아래에 devise_ldap_authenticatable을 추가한다.
source 'http://rubygems.org' gem 'rails', '3.1.1' gem 'sqlite3' gem 'devise', "1.5.0" gem "devise_ldap_authenticatable" gem 'stamp' gem 'kaminari' ...
3. bundle install/update를 실행한다.
$ bundle install Updating http://github.com/gitlabhq/grit.git Updating http://github.com/ctran/annotate_models.git Fetching source index for http://rubygems.org/ ... Using thin (1.3.1) Using turn (0.8.3) Using uglifier (1.1.0) Your bundle is complete! Use `bundle show [gemname]` to see where a bundled gem is installed.
gem 설치가 완료되었으니, devise_ldap_authenticatable를 적용시켜보자.
적용시키는 방법은 무척 간단하다.
devise_ldap_authenticatable github 위키에 나와있는 설치법은 다음과 같다.
$ rails generate devise:install $ rails generate devise MODEL_NAME $ rails generate devise_ldap_authenticatable:install
하지만 GitlabHQ는 이미 devise가 설치되어 있으므로, 마지막 단계만 실행해주면 되겠다.
4. devise_ldap_authenticatable 설치
$ rails generate devise_ldap_authenticatable:install
create config/ldap.yml
insert config/initializers/devise.rb
gsub app/models/user.rb
insert app/controllers/application_controller.rb
이제 남은건, LDAP 설정을 추가하고, initializer/devise.rb를 수정하면 되겠다.
LDAP 설정은 서버마다 다르므로, 일단 devise.rb부터 수정해 보자.
5. config/initializers/devise.rb 수정
# Use this hook to configure devise mailer, warden hooks and so forth. The first
# four configuration values can also be set straight in your models.
Devise.setup do |config|
# ==> LDAP Configuration
# config.ldap_logger = true # 로그 찍기
# config.ldap_create_user = false # true로 바꿔주면 로그인시 계정이 없으면 생성된다.
# config.ldap_update_password = true
# config.ldap_config = "#{Rails.root}/config/ldap.yml" # 생성된 파일을 사용한다.
# config.ldap_check_group_membership = false
# config.ldap_check_attributes = false
# config.ldap_use_admin_to_bind = false # 인증시 ldap.yml에 설정한 admin 계정을 이용한다.
# config.ldap_ad_group_check = false
# ==> Mailer Configuration
...
config.authentication_keys = [ :login ]
위의 주석에서 필요한 부분을 골라 주석을 해제한다.
제 경우는…
config.ldap_logger = true # 잘 되는지 log을 찍어봤고
config.ldap_config = "#{Rails.root}/config/ldap.yml" # config/ldap.yml을 사용했고,
config.ldap_create_user = true # 로그인시 계정이 없으면 자동으로 생성하게 했고,
config.ldap_use_admin_to_bind = true # mail 정보를 검색하기 위해 먼저 Admin 계정으로 로그인했다.
이제 남은건 ldap 서버 정보를 ldap.yml에 채워넣으면 되겠다.
이때 주의할 점은 GitlabHQ 로그인 시 메일 주소를 넣고 있기 때문에, 사용자 인증과 관련해서 Attribute를 mail로 바꿔주기만 하면 된다.
6. config/ldap.yml 수정
## Authorizations
# Uncomment out the merging for each enviornment that you'd like to include.
# You can also just copy and paste the tree (do not include the "authorizations") to each
# enviornment if you need something different per enviornment.
authorizations: &AUTHORIZATIONS
group_base: ou=groups,dc=test,dc=com
## Requires config.ldap_check_group_membership in devise.rb be true
# Can have multiple values, must match all to be authorized
required_groups:
# If only a group name is given, membership will be checked against "uniqueMember"
- cn=admins,ou=groups,dc=test,dc=com
- cn=users,ou=groups,dc=test,dc=com
# If an array is given, the first element will be the attribute to check against, the second the gr>
- ["moreMembers", "cn=users,ou=groups,dc=test,dc=com"]
## Requires config.ldap_check_attributes in devise.rb to be true
## Can have multiple attributes and values, must match all to be authorized
require_attribute:
objectClass: inetOrgPerson
authorizationRole: postsAdmin
## Enviornments
development:
host: localhost
port: 389
attribute: cn # 인증에 사용할 Attribute. GitlabHQ는 mail을 사용하므로, mail을 쓰면 된다.
base: ou=people,dc=test,dc=com
admin_user: cn=admin,dc=test,dc=com
admin_password: admin_password
ssl: false
# <<: *AUTHORIZATIONS
test:
host: localhost
port: 3389
attribute: cn
base: ou=people,dc=test,dc=com
admin_user: cn=admin,dc=test,dc=com
admin_password: admin_password
ssl: simple_tls
# <<: *AUTHORIZATIONS
production:
host: localhost
port: 636
attribute: cn
base: ou=people,dc=test,dc=com
admin_user: cn=admin,dc=test,dc=com
admin_password: admin_password
ssl: start_tls
# <<: *AUTHORIZATIONS
이미 Ruby와 Rails를 잘 아시는 분들이라면 별 무리 없이 설치했겠지만, 초반에 omniauth로 삽질 했더니 좀 오래걸렸다. =_=;
아무쪼록 GitlabHQ에서 LDAP로 로그인이 필요하신 분들에게 도움이 되었음 좋겠다.
python setup.py install 로 설치한 파일들 삭제하기
게시: 2011/11/25 분류: Log | Tags: python, uninstall 댓글 남기기 »1. 다시 설치하는데 –record 옵션을 줘서 기록한다.
python setup.py install --record uninstall.txt
2. 설치한 파일을 하나씩 삭제한다.
cat uninstall.txt | xargs rm -rf
Reference
- How do I uninstall gitosis
nodeJS로 Arduino 제어하기 #2
게시: 2011/11/22 분류: Log | Tags: Javascript, nodejs 6 댓글 »nodeJS로 Arduino 제어하기 #1에 이어서 arduino API를 Wrapper한 package를 사용해 nodeJS에서 Arduino를 제어하는 방법을 살펴보겠다.
먼저 tobeytailor의 node-arduino를 살펴보자.
- 현재 npm에 등록된 Arduino 패키지의 부모 프로젝트로, Arduino API 중 pinMode와 Digital/Analog Write만 사용 가능하다.
- nodeJS와 Arduino의 통신은 node-serialport 모듈을 이용한다.
위 소스를 nodeJS에서 사용하기 전에, Arduino에 업로드해야할 소스(node.pde)를 먼저 살펴보도록 하자.
/*
소스 위치 : https://raw.github.com/tobeytailor/node-arduino/master/src/node.pde
*/
/*
* node-arduino: Control your Arduino with Node
*
* Copyright (c) 2010 Tobias Schneider
* node-arduino is freely distributable under the terms of the MIT license.
*/
#define SERIAL_BAUDRATE 9600
#define OPC_PIN_MODE 0x01 /* nodeJS에서 해당 상수값이 들어오면, */
#define OPC_DIGITAL_READ 0x02 /* Arduino에서 지정된 함수를 실행한다. */
#define OPC_DIGITAL_WRITE 0x03
#define OPC_ANALOG_REFERENCE 0x04
#define OPC_ANALOG_READ 0x05
#define OPC_ANALOG_WRITE 0x06
void setup() {
Serial.begin(SERIAL_BAUDRATE);
}
void loop() {
while (Serial.available() > 0) {
switch (Serial.read()) {
case OPC_PIN_MODE: { // Ex. nodeJS에서는 0x01와 pin값을 차례로 전송한다.
Serial.println("pinMode");
pinMode(Serial.read(), Serial.read());
break;
}
case OPC_DIGITAL_READ: { // Ex. nodeJS에서는 0x02 값을 전송한다.
digitalRead(Serial.read());
break;
}
case OPC_DIGITAL_WRITE: {
Serial.println("digitalWrite");
digitalWrite(Serial.read(), Serial.read());
break;
}
case OPC_ANALOG_REFERENCE: {
analogReference(Serial.read());
break;
}
case OPC_ANALOG_READ: {
analogRead(Serial.read());
break;
}
case OPC_ANALOG_WRITE: {
analogWrite(Serial.read(), Serial.read());
break;
}
}
}
}
위 Arduino 코드에서는 기존의 API에서 주로 사용하는 pinMode, digitalRead/Write, analogRead/Write를 임의의 상수로 정의하고, nodeJS에서 해당 상수값을 보내면 해당 함수를 실행하는 형태가 되겠다.
이와 같은 방법으로 Arduino를 nodeJS에서 컨트롤하게 되는 것이다.(간단하죠?)
nodeJS쪽 코드를 사용하기 전에 반드시 위 코드를 Arduino IDE를 사용해 보드에 Upload 하도록 하자.
그럼 nodeJS쪽 소스(arduino.js)를 살펴보도록 하자.
/*
* 소스 위치 : https://raw.github.com/tobeytailor/node-arduino/master/lib/arduino.js
*/
/*
* node-arduino: Control your Arduino with Node
*
* Copyright (c) 2010 Tobias Schneider
* node-arduino is freely distributable under the terms of the MIT license.
*/
var sys = require('sys')
, SerialPort = require('../deps/node-serialport/serialport').SerialPort
;
const SERIAL_BAUDRATE = 9600;
const OPC_PIN_MODE = 0x01; // Arduino쪽 코드와 값을 맞춘다.
const OPC_DIGITAL_READ = 0x02;
const OPC_DIGITAL_WRITE = 0x03;
const OPC_ANALOG_REFERENCE = 0x04;
const OPC_ANALOG_READ = 0x05;
const OPC_ANALOG_WRITE = 0x06;
exports.HIGH = 0x01;
exports.LOW = 0x00;
exports.INPUT = 0x00;
exports.OUTPUT = 0x01;
exports.true = 0x01;
exports.false = 0x00;
exports.EXTERNAL = 0x00;
exports.DEFAULT = 0x01;
exports.INTERNAL = 0x03;
Board = function (path) {
this.sp = new SerialPort(path, SERIAL_BAUDRATE);
}
Board.prototype = {
pinMode : function (pin, mode) {
// 값을 Buffer에 실어서 SerialPort 객체의 write 메서드를 통해 전송한다.
this.sp.write(new Buffer([OPC_PIN_MODE, pin, mode]), 3);
}
, digitalRead : function (pin) {
// TODO
}
, digitalWrite : function (pin, val) {
this.sp.write(new Buffer([OPC_DIGITAL_WRITE, pin, val]), 3);
}
, analogReference : function (type) {
this.sp.write(new Buffer([OPC_ANALOG_REFERENCE, type]), 2);
}
, analogRead : function (pin) {
// TODO
}
, analogWrite : function (pin, val) {
this.sp.write(new Buffer([OPC_ANALOG_WRITE, pin, val]), 3);
}
};
exports.connect = function (path) {
return new Board(path);
};
결국은 Arduino에 사용할 함수를 미리 상수로 정의하고, nodeJS쪽에서 해당 상수값을 보내서 Arduino API를 사용하면 되는것이다.
생각보다 간단하다.(아닌가? =_=;)
위 코드를 사용해서 Arduino로 LED 깜빡이는 예제를 살펴보자.
(아래 코드를 실행하기 전에 Arduino IDE에서 Serial Monitor를 띄워주도록 하자. 이 문제점에 대한 해결책은 아래에서 다시 설명하도록 한다.)
/*
* node-arduino: Control your Arduino with Node
*
* Copyright (c) 2010 Tobias Schneider
* node-arduino is freely distributable under the terms of the MIT license.
*/
var arduino = require('../lib/arduino')
, board = arduino.connect('/dev/tty.usbserial-A700fkLn')
, val = arduino.LOW
;
board.pinMode(13, arduino.OUTPUT); // 13번으로 출력함
setInterval(function () {
val = val == arduino.LOW ? arduino.HIGH : arduino.LOW;
board.digitalWrite(13, val);
}, 500);
실행하면 보드에 LED가 깜빡거릴것이다.
이전 포스트에서 fs.open, fs.write 뭐 이런거 전혀 사용 안하고 직관적인 코드로 LED를 제어할 수 있게 되었다!(와~:D)
하지만 arduino.js 소스를 보면, 정작 중요한(?) read 함수는 TODO 상태다.
Serial 통신이라 보니 뭔가 주거니 받거니에 대한 read 처리를 JavaScript로 Sync 한 방법으로 처리가 애매해서 구현을 남겨두지 않았나 싶었다.
실제로 위 소스를 가지고 막상 실습을 해보면 뭔가 이상한점을 발견하게 되는데, Arduino IDE의 Serial Monitor를 띄우지 않고서는 실행이 제대로 안된다는 점이다.
당시에는 단순 버그려니 하고 넘겼지만, node-serialport쪽 예제 코드를 살펴보고난 후에 arduino.js에 빠진 부분을 찾았다.
아래는 node-serialport 소개 페이지에 있는 셈플 코드다.
var serialport = require("serialport");
var SerialPort = serialport.SerialPort; // localize object constructor
var sp = new SerialPort("/dev/tty-usbserial1", { // 장치 /dev/tty-usbserial1에 연결한다.
parser: serialport.parsers.raw
});
serialPort.on("data", function (data) { // 데이터가 들어오면 출력한다.
sys.puts("here: "+data);
});
간단히 말하면, 위와 같이 arduino.js에 data 이벤트를 처리할 수 있는 함수가 정의되어 있어야 한다는 것이다.
arduino.js에 data 이벤트 처리 함수를 넣어주면 실질적으로 Port Monitering을 node-serialport에서 하기때문에 Arduino IDE의 Serial Monitor를 실행시키지 않아도 된다.
var sys = require('sys')
, SerialPort = require('../deps/node-serialport/serialport').SerialPort
;
const SERIAL_BAUDRATE = 9600;
const OPC_PIN_MODE = 0x01; // Arduino쪽 코드와 값을 맞춘다.
const OPC_DIGITAL_READ = 0x02;
const OPC_DIGITAL_WRITE = 0x03;
const OPC_ANALOG_REFERENCE = 0x04;
const OPC_ANALOG_READ = 0x05;
const OPC_ANALOG_WRITE = 0x06;
// ...
Board = function (path) {
this.sp = new SerialPort(path, SERIAL_BAUDRATE);
this.sp.on( "data", function(data){ console.log(data); }); // 추가 코드 : data 이벤트 발생시 출력
}
// ...
이렇게 Digital/Analog Write만으로도 node에서 Arduino로 자유롭고 간편하게 출력을 할 수 있겠다. 하지만.. 단순 Write만 하기에는 재밌는 센서들이 너무나도 많다!
제가 임의로 Read를 추가한 node-arduino는 voodootikigod의 node-arduino 패키지를 Fork해서 만든 것으로, 기존에 Read 부분을 추가했다. voodootikigod가 pull request를 받아준다면 npm에 반영될꺼라 생각했지만… (깔끔하지 못하다고 느꼈는지 몰라도 일단 받아주지 않고 있다 =_=)
일단 추가한 Read 부분을 설명하기 앞서, 어떻게 하면 JavaScript 답게 Read를 하면 좋을까 생각을 했었다. 해서 내린 결론은…
- Arduino에서 pin마다 입력 값이 생기면, 이를 nodeJS에 값과 pin값을 묶어서 전송한다.
- nodeJS쪽에서는 pin마다 입력값이 들어오면 실행될 Callback을 둔다.
일단 Arduino와 nodeJS는 Serial 통신으로 데이터를 주고 받는다.

그렇다면 Arduino쪽에서 뭔가 값을 읽는 즉시 출력을 해야 nodeJS쪽의 data 이벤트 헨들러의 Callback이 데이터를 받을 수 있을 것이다.
하나의 값의 출력으로 pin번호까지 같이 전달해야하고, 일관된 데이터 양식이면 좋겠다는 생각에 일단, 간단히 packing을 하기로 했다.
Arduino에서는 기본적으로 int가 2byte다.(long이 4byte) 보통 센서 값이 10000이 넘지 않는다고 해서 일단 하위 2byte에 Digital/Analog Read로 읽어드린 값을 넣고, 상위 2byte에 pin번호 값을 넣어서 4byte 숫자 String을 전송하는 형태로 데이터 양식을 정해봤다.
자, 그럼 첫번째로 Arduino쪽 수정된 코드를 살펴보자.
/*
* node-arduino: Control your Arduino with Node
*
* Copyright (c) 2010 Tobias Schneider
* node-arduino is freely distributable under the terms of the MIT license.
*/
#define SERIAL_BAUDRATE 115200
#define OPC_PIN_MODE 0x01
#define OPC_DIGITAL_READ 0x02
#define OPC_DIGITAL_WRITE 0x03
#define OPC_ANALOG_REFERENCE 0x04
#define OPC_ANALOG_READ 0x05
#define OPC_ANALOG_WRITE 0x06
long pinVal = 0;
int inpVal = 0;
long outVal = 0;
void setup() {
Serial.begin(SERIAL_BAUDRATE);
}
void loop() {
pinVal = 0, inpVal = 0, outVal = 0;
while (Serial.available() > 0) {
switch (Serial.read()) {
case OPC_PIN_MODE: {
pinMode(Serial.read(), Serial.read());
break;
}
case OPC_DIGITAL_READ: {
delay(50);
pinVal = Serial.read(); // pin값을 pinVal에 저장하고,
inpVal = digitalRead(pinVal); // 해당 핀에 연결된 값을 읽어서 inpVal에 저장한다.
outVal = pinVal << 16 | inpVal; // pin값을 상위 2byte로 shift한 후에 inpVal을 하위 2byte에 넣는다.
Serial.println(outVal); // 읽어들인 값을 출력하는 형태로 전송한다.
break;
}
case OPC_DIGITAL_WRITE: {
digitalWrite(Serial.read(), Serial.read());
break;
}
case OPC_ANALOG_REFERENCE: {
analogReference(Serial.read());
break;
}
case OPC_ANALOG_READ: {
delay(50);
pinVal = Serial.read();
inpVal = analogRead(pinVal);
outVal = pinVal << 16 | inpVal;
Serial.println(outVal);
break;
}
case OPC_ANALOG_WRITE: {
analogWrite(Serial.read(), Serial.read());
break;
}
}
}
}
앞에서 “nodeJS쪽에서는 pin마다 입력값이 들어오면 실행될 Callback을 둔다.” 라고 했다.
예를 들어, 다음과 같이 7번과 8번 핀에서 입력값을 받아들여 화면에 찍는다고 가정해보자.
var arduino = require('../lib/arduino')
, board = arduino.connect('/dev/tty.usbserial-A700fkLn')
;
board.pinMode(7, arduino.INPUT); // 7번 PIN은 입력단자
board.pinMode(8, arduino.INPUT); // 8번 PIN은 입력단자
setInterval(function () {
board.digitalRead(7, function(val){
console.log(val);
});
board.digitalRead(8, function(val){
console.log(val);
});
}, 500);
Arduino에서 값이 들어오면, 위에서 정의한 Callback 함수를 실행해야한다.
그렇다면 두 번째로, Arduino에서 출력한 값을 nodeJS에서 읽어서 처리하는 코드를 살펴보자.
(이때, 하나의 PIN에는 하나의 Callback만이 실행한다고 가정한다.)
/*
* node-arduino: Control your Arduino with Node
*
* Copyright (c) 2010 Tobias Schneider
* node-arduino is freely distributable under the terms of the MIT license.
*/
var sys = require('sys'),
SerialPort = require('serialport').SerialPort,
parsers = require('serialport').parsers;
const SERIAL_BAUDRATE = 115200;
const OPC_PIN_MODE = 0x01;
const OPC_DIGITAL_READ = 0x02;
const OPC_DIGITAL_WRITE = 0x03;
const OPC_ANALOG_REFERENCE = 0x04;
const OPC_ANALOG_READ = 0x05;
const OPC_ANALOG_WRITE = 0x06;
// ...
Board = function (path) {
// pin별로 callback을 저장하기 위한 객체
var callback = {};
// Arduino에서 출력되는 값을 개행 단위로 읽어들임
this.sp = new SerialPort(path, {baudrate: SERIAL_BAUDRATE, parser: parsers.readline('\n')});
var onData = (function(callback) {
return function( data ) { // 뭔가 값이 있을때
var pin, value;
data = +data; // 숫자 값으로 바꾼다.(실제로 숫자 스트링이기 때문에 가능)
if ( data && data > 1 ) {
pin = data >> 16; // 상위 2byte에서 pin값을 꺼낸다.
value = data & 0xFFFF; // 하위 2byte에서 읽어드린 값을 꺼낸다.
if ( !callback['pin'+pin] ) {
sys.puts('no callback for pin '+pin);
} else { // 해당 핀에 지정된 Callback이 있는 경우, 값을 넘겨 호출한다.
callback['pin'+pin](value);
}
}
};
})(callback);
this.sp.on( "data", onData);
};
Board.prototype = {
pinMode : function (pin, mode) {
this.sp.write(new Buffer([OPC_PIN_MODE, pin, mode]), 3);
},
// ...
digitalRead : function (pin, callback) {
// Arduino쪽에 값을 읽을 수 있게 Pin번호와 OPC_DIGITAL_READ를 전송한다.
this.sp.write(new Buffer([OPC_DIGITAL_READ, pin]), 2);
// callback이 함수일 경우, pinX 형태로 callback Reference를 저장한다.
if ( typeof callback == 'function' ) {
this.callback['pin'+pin] = callback;
}
},
// ...
analogRead : function (pin, callback) {
this.sp.write(new Buffer([OPC_ANALOG_READ, pin]), 2);
if ( typeof callback == 'function' ) {
this.callback['pin'+pin] = callback;
}
},
// ...
};
// ...
node-arduino Read Demo 영상은 위 코드를 바탕으로 테스트한 화면이다.
버튼이 눌리면 1을 찍고, 눌리지 않으면 0을 찍고 있다.
아직 node-arduino에 빠진 API가 많지만, Read, Write만 가지고도 왠만한건 다 할 수 있을것이라 생각한다.
여기까지 node-arduino를 사용해 arduino를 제어하는 법을 간단히 살펴봤다.
사실 여기까지 실습이 제대로 되었다면, 왠만한건 다 nodeJS로 엮을 수 있을 것이다.
nodeJS로 Arduino 제어하기 #1
게시: 2011/11/21 분류: Log | Tags: Javascript, nodejs 9 댓글 »이번 가이드에서는 Rhio님이 만든 동영상에 있는것 처럼 nodeJS에서 LED를 ON/OFF 해보겠습니다.
이 가이드를 이해하기 위해서는 2가지 실습을 해보신분이어야 합니다.
- nodeJS로 웹 서버 띄우는 예제를 실행해봤다.
- Arduino로 LED를 한번이라도 켜봤다.
위 2가지를 해보셨고, nodeJS로 Arduino를 만지작 거리고 싶은 분들을 위해 정리해봅니다.
먼저 실습에 앞서 필요한 것이 있다면..
- Arduino 호환 보드
- USB포트 달린 컴퓨터
위의 하드웨어가 준비가 되었다면, Arduino IDE와 nodeJS를 설치합니다.
설치에 대한 가이드는 패스하고, 바로 실습으로 들어가보도록 하겠습니다.
먼저 Arduino로 LED를 깜빡거리는 소스를 살펴볼텐데요,
Arduino IDE를 설치하신 후, Example > 1. Basis > Blink 를 선택하시면 아래와 같은 소스를 볼 수 있습니다.
void setup() {
Serial.begin(9600);
// initialize the digital pin as an output.
// Pin 13 has an LED connected on most Arduino boards:
pinMode(13, OUTPUT);
}
void loop() {
digitalWrite(13, HIGH); // set the LED on
delay(1000); // wait for a second
digitalWrite(13, LOW); // set the LED off
delay(1000); // wait for a second
}
위 코드는 Arduino에서 디지털 13번 핀에 전류를 1초 간격으로 줬다가 안줬다가 하는 형태로 LED를 깜빡거리게 하고 있습니다.
이걸 nodeJS에서 하려면 어떻게 할까요?
Arduino와 nodeJS간에는 어떠한 연관성도 없습니다.
사실, 이 작업은 nodeJS가 아닌 ruby로도 할 수 있고, Flash의 ActionScript로도 할 수 있죠.
nodeJS와 Arduino 간에 연결 방법은 여러가지가 있습니다만, 단순히 Arduino 보드를 구매하신 분이라면.. USB 케이블 밖에 없겠죠?
다시 말해, nodeJS에서 USB Serial 통신으로 Arduino에 메시지를 보내고, Arduino에서는 보낸 메시지에 따라 LED를 켰다 껐다 하면 되겠죠.
이제 Arduino를 제어하는 주체가 nodeJS 쪽이 되야하기 때문에, 아래와 같이 JS 코드가 작성 될 것입니다.
var isOn = false;
setInterval(function(){
if ( !isOn ) { // OFF면
setLED(1);
} else { // ON이면
setLED(0);
}
isOn = !isOn;
}, 1000);
function setLED(flag) {
// 뭔가 데이터를 장치로 보내는 코드가 있을꺼임
}
이제 setLED만 완성하면 되겠네요!
가만, Arduino쪽에서도 기존의 Blink 코드를 바꿔줘야할 것인데…
일단, nodeJS에서 setLED에 1과 0을 줬기 때문에 Arduino쪽에서도 마찬가지로 ’1′을 보내면 LED가 ON, ’0′을 보내면 LED가 OFF라고 합시다.
여기서 ’1′ 또는 ’0′이 되는건 이유는 없고, 단순히 데이터 교환을 위한 약속이라고 보시면 됩니다.
뭔가 프로토콜이 정해진것 같으니.. Arduino쪽 loop() 함수 코드를 다음과 같이 수정합니다.
void setup() {
Serial.begin(9600);
// initialize the digital pin as an output.
// Pin 13 has an LED connected on most Arduino boards:
pinMode(13, OUTPUT);
}
void loop() {
int incomingValue = 0; // nodeJS에서 보낸값
if ( Serial.available() > 0 ) { // 뭔가 입력값이 있다면
incomingValue = Serial.read();
}
if ( incomingValue == 49 ) { // 값이 '1' 이면
digitalWrite(13, HIGH); // LED를 켠다.
}
if ( incomingValue == 48 ) { // 값이 '0' 이면
digitalWrite(13, LOW); // LED를 끈다.
}
}
자, 이젠 nodeJS쪽에서만 잘 보내면 되겠네요
근데, 어떻게 보낼까요?
OS에서는 Device File이라는게 있는데요,
간단히 말해 장치가 가상의 파일과 1:1로 연결되어 입/출력을 주거니 받거니 한다는 것입니다.
예를 들어, 마우스를 움직이면 화면에 커서가 움직이는것 처럼, 마우스와 OS간에 뭔가 서로 통신을 하는거죠.
Arduino도 장치이기 때문에 컴퓨터와 나름 입/출력을 할 수 있는 뭔가가 있을껍니다.
nodeJS쪽에서 Arduino에 연결하기 위해서는… 일단 Arduino가 연결된 장치를 찾아야겠죠?
앞에서 설치한 Arduino IDE를 실행해 보면 메뉴에 Tools > Serial Port 쪽에서 사용 가능한 Serial 장치를 확인할 수 있습니다. 이 중 하나를 nodeJS에서 사용하면 되겠습니다.
그럼… nodeJS에서 장치로 어떻게 연결할까요?
앞에서 장치가 ‘가상의 파일’로 연결되어 있다고 했으니, nodeJS File System API를 사용하면 될 것 같네요.
여기서는 Arduino가 /dev/tty-usbserial1 에 연결되어 있다고 가정합시다.
일단 장치를 열어야겠죠?
var fs = require('fs');
// Appending 모드('a')로 /dev/tty-usbserial1 장치를 Open
fs.open('/dev/tty-usbserial1', 'a', 666, function( e, fd ) {
// 장치가 정상적으로 열리면 값을 보내야겠죠?
});
그렇다면 값은 어떻게 보낼까요? 파일을 읽고 쓰는것 처럼, 장치에 뭔가를 보내려면 파일에 기록을 하는 형태가 되면 될 것 같습니다.
여기서는 fs.write를 사용해보도록 하겠습니다.
fs.write API는 아래와 같습니다.
fs.write(fd, buffer, offset, length, position, [callback])
일단 fd는 fs.open이 성공적으로 이뤄지면 받을 수 있겠죠?
buffer에 장치에 실어보낼 값을 넣어주면 되겠네요. 나머지는 마지막 callback을 제외하고 크게 문제가 안되니 null로 처리해 봅시다.
var fs = require('fs');
// Appending 모드('a')로 /dev/tty-usbserial1 장치를 Open
fs.open('/dev/tty-usbserial1', 'a', 666, function( e, fd ) {
fs.write( fd, '1', null, null, null, function(){
// 장치에 '1'이 잘 보내졌을 경우
}
});
일단 장치에 연결을 잘 했고, ’1′을 보냈다면 LED가 켜지겠죠?
혹시 안켜진다면.. Arduino IDE가 해당 장치를 사용하고 있을 수 있습니다. 닫고, 다시 실행해보세요.
(그것도 아니라면.. 음.. =_=;;)
LED가 잘 켜졌으니, 장치에 더 이상 보낼 값이 없겠죠? 해당 연결을 닫아줘야겠습니다.
이때는 fs.close()를 사용해 현 시점에서 연결을 종료시켜줍니다.
var fs = require('fs');
// Appending 모드('a')로 /dev/tty-usbserial1 장치를 Open
fs.open('/dev/tty-usbserial1', 'a', 666, function( e, fd ) {
fs.write( fd, '1', null, null, null, function(){
// 장치에 '1'이 잘 보내졌을 경우, 연결을 닫는다.
fs.close(fd, function() {
// 연결 종료.
});
}
});
여기까지 잘 따라했다면, 위에서 만든 코드를 가지고 setLED 함수를 만들어봅시다.
setLED에 1을 주면 ’1′이 전송되고, 0을 주면 ’0′이 전송되게 하면 되겠죠?
function setLED(flag) {
var fs = require('fs');
// Appending 모드로 /dev/tty-usbserial1 장치를 Open
fs.open('/dev/tty-usbserial1', 'a', 666, function( e, fd ) {
// flag가 0이 아니면 '1'을 보내고,
// 0이면 '0'을 보낸다.
fs.write( fd, flag ? '1' : '0', null, null, null, function(){
fs.close(fd, function(){
console.log('Sending ', flag);
});
});
});
}
여기까지 완성한 후, 실행을 시키면 LED가 깜빡이는 것을 볼 수 있을겁니다.
var isOn = false;
setInterval(function(){
if ( !isOn ) { // OFF면
setLED(1);
} else { // ON이면
setLED(0);
}
isOn = !isOn;
}, 1000);
function setLED(flag) {
var fs = require('fs');
// Appending 모드로 /dev/tty-usbserial1 장치를 Open
fs.open('/dev/tty-usbserial1', 'a', 666, function( e, fd ) {
// flag가 0이 아니면 '1'을 보내고,
// 0이면 '0'을 보낸다.
fs.write( fd, flag ? '1' : '0', null, null, null, function(){
fs.close(fd, function(){
console.log('Sending ', flag);
});
});
});
}
일단 연결은 했으니 웹에 붙이는건 정말 쉽겠죠?
그런데, 연결 과정이 다소 복잡해 보이죠.
‘좀 더 쉬운 방법은 없을까?’
물론 구글링 하면 node에서 arduino를 쉽게 제어할 수 있는 몇몇 Project를 Github에서 찾을 수 있습니다.
다음 가이드에서는 tobeytailor가 만든 node-arduino 소스를 시작으로 voodootikigod가 수정한 node-arduino와, 제가 살짝 수정한 node-arduino를 예를 들어 살펴보도록 하겠습니다.
[Webistrano] Can’t convert string from ‘UTF-8′ to native encoding
게시: 2011/11/17 분류: Log 댓글 남기기 »웹 좀 뒤져보다 보면 SVN 사용시에 위 메세지 처리와 관련해서 주로 아래와 같이 하라고 가이드 하고 있다.
export LC_CTYPE=en_US.UTF8
하지만 위를 서버에서 콘솔로 들어가 입력한다고 Webistrano에서 바로 받아먹을 수는 없는 노릇인지라…
[webistrano 설치 경로]/lib/webistrano/template/base.rb 에 한줄 추가하는것으로 해결할 수 있다.
module Webistrano ... TASKS = <<-'EOS' # allocate a pty by default as some systems have problems without default_run_options[:pty] = true default_environment["LC_CTYPE"] = "ko_KR.UTF-8" # System Locale이 ko_KR.UTF-8임. 이 부분을 입맛에 맞게 고치면 된다. ...
Rails도 한지 오래되서 그런지 소스 까볼때마다 새롭다. =_=;;
Moment.js 한글 언어팩 추가
게시: 2011/11/17 분류: Log | Tags: Javascript, nodejs 3 댓글 »Moment.js는…
A lightweight javascript date library for parsing, manipulating, and formatting dates.
라는 것인데, 간단히 말해 Date Fomatting Library라고 보면 될 것같다.
트위터를 예로 들면,

“14시간 전” 처럼 보여줄 수도 있고, 아니면 임의로 형식을 지정할 수도 있겠다.
요즘 NPM에 등록되는 모듈들을 하나씩 열어보고 있는데, 추가하는게 어렵지 않은것 같아서 문서를 참고해 lang쪽에 kr.js를 추가해 보았다.
(지금은 소스에 반영된 상태다.)
// lang/kr.js
(function () {
var lang = {
months : "1월_2월_3월_4월_5월_6월_7월_8월_9월_10월_11월_12월".split("_"),
monthsShort : "1월_2월_3월_4월_5월_6월_7월_8월_9월_10월_11월_12월".split("_"),
weekdays : "일요일_월요일_화요일_수요일_목요일_금요일_토요일".split("_"),
weekdaysShort : "일_월_화_수_목_금_토".split("_"),
longDateFormat : {
L : "YYYY.MM.DD",
LL : "YYYY년 MMMM D일",
LLL : "YYYY년 MMMM D일 A h시 mm분",
LLLL : "YYYY년 MMMM D일 dddd A h시 mm분"
},
meridiem : {
AM : '오전',
am : '오전',
PM : '오후',
pm : '오후'
},
relativeTime : {
future : "%s 후",
past : "%s 전",
s : "몇초",
ss : "%d초",
m : "일분",
mm : "%d분",
h : "한시간",
hh : "%d시간",
d : "하루",
dd : "%d일",
M : "한달",
MM : "%d달",
y : "일년",
yy : "%d년"
},
ordinal : function (number) {
return '일';
}
};
// Node
if (typeof module !== 'undefined') {
module.exports = lang;
}
// Browser
if (typeof window !== 'undefined' && this.moment && this.moment.lang) {
this.moment.lang('kr', lang);
}
}());
사용 방법은 moment를 Github이나 NPM을 통해 내려받은 뒤, require로 불러온다.
var moment = require('moment');
moment().add('hours', 1).fromNow(); // "1 hour ago"
moment.lang('kr'); // 언어팩 변경
moment().add('hours', 1).fromNow(); // "1 시간 전"
다음 코드는 kr.js의 Unit Testing 코드다.
/**************************************************
Korean
*************************************************/
module("lang:kr");
test("format", 18, function() {
moment.lang('kr');
var a = [
['YYYY년 MMMM Do dddd a h:mm:ss', '2010년 2월 14일 일요일 오후 3:25:50'],
['ddd A h', '일 오후 3'],
['M Mo MM MMMM MMM', '2 2일 02 2월 2월'],
['YYYY YY', '2010 10'],
['D Do DD', '14 14일 14'],
['d do dddd ddd', '0 0일 일요일 일'],
['DDD DDDo DDDD', '45 45일 045'],
['w wo ww', '8 8일 08'],
['h hh', '3 03'],
['H HH', '15 15'],
['m mm', '25 25'],
['s ss', '50 50'],
['a A', '오후 오후'],
['일년 중 DDDo째 되는 날', '일년 중 45일째 되는 날'],
['L', '2010.02.14'],
['LL', '2010년 2월 14일'],
['LLL', '2010년 2월 14일 오후 3시 25분'],
['LLLL', '2010년 2월 14일 일요일 오후 3시 25분']
],
b = moment(new Date(2010, 1, 14, 15, 25, 50, 125)),
i;
for (i = 0; i < a.length; i++) {
equal(b.format(a[i][0]), a[i][1], a[i][0] + ' ---> ' + a[i][1]);
}
});
test("format ordinal", 31, function() {
moment.lang('kr');
equal(moment([2011, 0, 1]).format('DDDo'), '1일', '1일');
equal(moment([2011, 0, 2]).format('DDDo'), '2일', '2일');
equal(moment([2011, 0, 3]).format('DDDo'), '3일', '3일');
equal(moment([2011, 0, 4]).format('DDDo'), '4일', '4일');
equal(moment([2011, 0, 5]).format('DDDo'), '5일', '5일');
equal(moment([2011, 0, 6]).format('DDDo'), '6일', '6일');
equal(moment([2011, 0, 7]).format('DDDo'), '7일', '7일');
equal(moment([2011, 0, 8]).format('DDDo'), '8일', '8일');
equal(moment([2011, 0, 9]).format('DDDo'), '9일', '9일');
equal(moment([2011, 0, 10]).format('DDDo'), '10일', '10일');
equal(moment([2011, 0, 11]).format('DDDo'), '11일', '11일');
equal(moment([2011, 0, 12]).format('DDDo'), '12일', '12일');
equal(moment([2011, 0, 13]).format('DDDo'), '13일', '13일');
equal(moment([2011, 0, 14]).format('DDDo'), '14일', '14일');
equal(moment([2011, 0, 15]).format('DDDo'), '15일', '15일');
equal(moment([2011, 0, 16]).format('DDDo'), '16일', '16일');
equal(moment([2011, 0, 17]).format('DDDo'), '17일', '17일');
equal(moment([2011, 0, 18]).format('DDDo'), '18일', '18일');
equal(moment([2011, 0, 19]).format('DDDo'), '19일', '19일');
equal(moment([2011, 0, 20]).format('DDDo'), '20일', '20일');
equal(moment([2011, 0, 21]).format('DDDo'), '21일', '21일');
equal(moment([2011, 0, 22]).format('DDDo'), '22일', '22일');
equal(moment([2011, 0, 23]).format('DDDo'), '23일', '23일');
equal(moment([2011, 0, 24]).format('DDDo'), '24일', '24일');
equal(moment([2011, 0, 25]).format('DDDo'), '25일', '25일');
equal(moment([2011, 0, 26]).format('DDDo'), '26일', '26일');
equal(moment([2011, 0, 27]).format('DDDo'), '27일', '27일');
equal(moment([2011, 0, 28]).format('DDDo'), '28일', '28일');
equal(moment([2011, 0, 29]).format('DDDo'), '29일', '29일');
equal(moment([2011, 0, 30]).format('DDDo'), '30일', '30일');
equal(moment([2011, 0, 31]).format('DDDo'), '31일', '31일');
});
test("format month", 12, function() {
moment.lang('kr');
var expected = '1월 1월_2월 2월_3월 3월_4월 4월_5월 5월_6월 6월_7월 7월_8월 8월_9월 9월_10월 10월_11월 11월_12월 12월'.split("_");
var i;
for (i = 0; i < expected.length; i++) {
equal(moment([2011, i, 0]).format('MMMM MMM'), expected[i], expected[i]);
}
});
test("format week", 7, function() {
moment.lang('kr');
var expected = '일요일 일_월요일 월_화요일 화_수요일 수_목요일 목_금요일 금_토요일 토'.split("_");
var i;
for (i = 0; i < expected.length; i++) {
equal(moment([2011, 0, 2 + i]).format('dddd ddd'), expected[i], expected[i]);
}
});
test("from", 30, function() {
moment.lang('kr');
var start = moment([2007, 1, 28]);
equal(start.from(moment([2007, 1, 28]).add({s:44}), true), "몇초", "44초 = 몇초");
equal(start.from(moment([2007, 1, 28]).add({s:45}), true), "일분", "45초 = 일분");
equal(start.from(moment([2007, 1, 28]).add({s:89}), true), "일분", "89초 = 일분");
equal(start.from(moment([2007, 1, 28]).add({s:90}), true), "2분", "90초 = 2분");
equal(start.from(moment([2007, 1, 28]).add({m:44}), true), "44분", "44분 = 44분");
equal(start.from(moment([2007, 1, 28]).add({m:45}), true), "한시간", "45분 = 한시간");
equal(start.from(moment([2007, 1, 28]).add({m:89}), true), "한시간", "89분 = 한시간");
equal(start.from(moment([2007, 1, 28]).add({m:90}), true), "2시간", "90분 = 2시간");
equal(start.from(moment([2007, 1, 28]).add({h:5}), true), "5시간", "5시간 = 5시간");
equal(start.from(moment([2007, 1, 28]).add({h:21}), true), "21시간", "21시간 = 21시간");
equal(start.from(moment([2007, 1, 28]).add({h:22}), true), "하루", "22시간 = 하루");
equal(start.from(moment([2007, 1, 28]).add({h:35}), true), "하루", "35시간 = 하루");
equal(start.from(moment([2007, 1, 28]).add({h:36}), true), "2일", "36시간 = 2일");
equal(start.from(moment([2007, 1, 28]).add({d:1}), true), "하루", "하루 = 하루");
equal(start.from(moment([2007, 1, 28]).add({d:5}), true), "5일", "5일 = 5일");
equal(start.from(moment([2007, 1, 28]).add({d:25}), true), "25일", "25일 = 25일");
equal(start.from(moment([2007, 1, 28]).add({d:26}), true), "한달", "26일 = 한달");
equal(start.from(moment([2007, 1, 28]).add({d:30}), true), "한달", "30일 = 한달");
equal(start.from(moment([2007, 1, 28]).add({d:45}), true), "한달", "45일 = 한달");
equal(start.from(moment([2007, 1, 28]).add({d:46}), true), "2달", "46일 = 2달");
equal(start.from(moment([2007, 1, 28]).add({d:74}), true), "2달", "75일 = 2달");
equal(start.from(moment([2007, 1, 28]).add({d:76}), true), "3달", "76일 = 3달");
equal(start.from(moment([2007, 1, 28]).add({M:1}), true), "한달", "1달 = 한달");
equal(start.from(moment([2007, 1, 28]).add({M:5}), true), "5달", "5달 = 5달");
equal(start.from(moment([2007, 1, 28]).add({d:344}), true), "11달", "344일 = 11달");
equal(start.from(moment([2007, 1, 28]).add({d:345}), true), "일년", "345일 = 일년");
equal(start.from(moment([2007, 1, 28]).add({d:547}), true), "일년", "547일 = 일년");
equal(start.from(moment([2007, 1, 28]).add({d:548}), true), "2년", "548일 = 2년");
equal(start.from(moment([2007, 1, 28]).add({y:1}), true), "일년", "일년 = 일년");
equal(start.from(moment([2007, 1, 28]).add({y:5}), true), "5년", "5년 = 5년");
});
test("suffix", 2, function() {
moment.lang('kr');
equal(moment(30000).from(0), "몇초 후", "prefix");
equal(moment(0).from(30000), "몇초 전", "suffix");
});
test("now from now", 1, function() {
moment.lang('kr');
equal(moment().fromNow(), "몇초 전", "now from now should display as in the past");
});
test("fromNow", 2, function() {
moment.lang('kr');
equal(moment().add({s:30}).fromNow(), "몇초 후", "in a few seconds");
equal(moment().add({d:5}).fromNow(), "5일 후", "in 5 days");
});</pre>
문서 보면 브라우저에서 사용할 수도 있으니 참고하면 되겠다.