안녕하십니까,

생각의 웹입니다.


지난 강연인 IoT, 기술의 혁신과 미래 그리고 통찰에 이어 

이번에는 Intuitive Understanding of Arduino for IoT라는 주제로 대디스랩 송영광 대표님이 발표해 주셨습니다. 



비록 참가비 5만원의 유료 강의였지만 49.55$ 의 sparkfun Starter Kit for RedBoard를 5만원이라는 저렴한 가격으로 제공하며 실습 형태로 진행되는 오프라인 강의라 금방 마감되었다는 후문을 들었습니다.

사진과 같이 토요일 오후임에도 불구하고 참석하신 분들의 뜨거운 학구열을 느낄 수 있었습니다. ^^


이번에 직접 제작해 본 아두이노 회로는 soft potentiometer thermometer로 다음과 같습니다.


참석을 희망하셨는데 참석하지 못하신 분들과 관심 있는 분들을 위해 발표 슬라이드를 다음과 같이 공유합니다.




다음 강연으로 준비 중인

사물 간의 연결을 위한 Open API: Do IoT Yourself 3rd 도 관심 가져주시기 바랍니다.

감사합니다.


행복한 하루 되시길!


안녕하세요. "생각의 웹"입니다.


기존의 기본 템플릿 기반으로 만든 블로그가 너무 구식이라는 절친의 지적이 있어 가장 빠르게 업데이트 하는 방법을 찾던 중, 마크쿼리 님이 만드신 스킨이 있어 이렇게 적용해 보았습니다. 설치 가이드는 아래와 같습니다.


티 스토리 반응형 스킨 설치 가이드 


가이드를 따라서 하면 크게 무리 없이 변경이 가능하나 몇 가지 직접 코드를 수정해 줘야 하는 사항이 있어 아래와 같이 정리합니다.


  1. 대문의 이미지(cover.jpg)와 메뉴 내용 수정 (사이드 메뉴에 마크쿼리 님의 템플릿 사이트가 올라오니 삭제 필요)
  2. 코드 꾸미기를 위해 사용했던 class tag 변경 (이전에는 설치형 beautifier를 사용했으나 이번엔 구글의 code priettify로 변경)
    • HTML를 제외한 코드 삽입 시 : <pre class="prettyprint linenums:1"> </pre>로 해당 코드를 둘러쌈.
    • HTML, XML 코드을 경우: <xmp class="prettyprint linenums:1"> </xmp>로 코들를 둘러쌈.
스킨을 바꾸니 수고가 많이 들기는 하나 왠지 새롭게 시작하는 느낌이 들어 색다르네요.

;-)


감사합니다.




안녕하세요. 생각의 웹입니다.


이번에는 KAIST에서 만든 교육용 프로그래밍 언어 엔트리와 관련 웹사이트를 소개하고자 합니다.


플레이 엔트리 가기


엔트리(Entry)의 의미인 초입부라는 뜻에 걸맞게 어린 아이들에게 쉽게 프로그래밍을 접근할 수 있도록 단계적으로 컨텐츠를 제공하고 있습니다.

플레이 엔트리 사이트 가입 후 학습하기 메뉴에서 10가지의 미션을 통해 블럭 프로그래밍의 묘미를 맛볼 수 있습니다.


언뜻 보면 간단한 문제지만 막상 코딩해보면 시행착오가 많습니다. (저 같은 경우는 '왼쪽으로 회전' 과 '오른쪽으로 회전'이 가장 혼란스러웠습니다. 군대에서도 좌우 구분 못해 고생 좀 했었죠;; ) 자만하지 마시고 심심풀이로 직접 한번 해보시는 것 추천합니다. ^^ 


아직 베타 서비스 중이라 불안한 점이 있지만 깔끔한 화면과 알찬 구성이 마음에 듭니다. 그럼에도 굳이 개선 희망사항을 하나 제안하면 터치 인터페이스의 줌 인, 줌 아웃 등을 지원해서 블럭 프로그래밍 편의를 도모했으면 합니다. (요즘은 터치 인터페이스를 장착한 기기가 많이 보급되고 있으니까요. ^^)


멋진 도구를 통해 많은 아이들이 프로그래밍에 친숙하게 다가갈 수 있기를 기대합니다!

화이팅입니다!!




안녕하세요, 생각의 웹입니다.


미국의 초등학생 대상으로 진행하는 컴퓨터 없이도 컴퓨터의 기본 원리 및 프로그래밍을 가르치는 단체인 CS 언플러그드 (http://csunplugged.org/)에서 만든 교육 자료를 다음과 같이 공유합니다.



CSUnplugged_OS_Part1_2012.pdf


CSUnplugged_OS_Part2_2012.pdf


이 과정의 목적은 초등학생들에게 컴퓨터를 구성하는 기본 원리를 가르치는데 있지만 각 단원마다 컴퓨터 과학의 기본 원리를 매우 쉽게 설명하고 있기에 컴퓨터에 대해 이해가 부족한 분들에게도 일독을 권합니다.


모든 사물에 컴퓨팅 기술이 접목되어 가는 세상에서 그 원리를 알고 지혜롭게 사용하는 사람들만이 기술의 홍수에 파묻히지 않을 것입니다.


감사합니다.


안녕하세요, 생각의 웹입니다.

지난 삼성 오픈소스 컨퍼런스 (SOSCON)의 IoT 세션에서 IBM의 IoT 솔루션을 소개해 주신 진승의 님의 슬라이드와 관련 링크들을 다음과 같이 정리합니다.

슬라이드 보기

슬라이드를 보시면 아시겠지만 첫 장부터 JavaScript와 JSON 사랑을 돈독히 보여주시며 청중(이라고 쓰고 저라고 읽습니다.)의 흥미를 끌었습니다.

특히, 지루한 슬라이드 읇기보다 백문불여일견이라고 스스로 Do IoT Yourself 하신 사례를 데모하시며 IBM이 지난 오랜 기간동안 다양한 오픈 소스를 통해 기술 혁신을 이끌어 왔음을 강조했습니다.

그가 소개한 기술들은 대략 다음과 같습니다.

1. MQTT: MQ Telemetry Transport, http://mqtt.org/ 
    (대략적인 기술 소개는  네이버 HelloWorld 포스팅 참조 )

2. Node-RED: MQTT 프로토콜 탑재 기기들을 연결하는 visual editor, http://nodered.org/ 

3. IBM bluemix: IBM의 Platform as a Service, http://www-01.ibm.com/software/bluemix/
   (현재 무료로 사용가능)

 아래 블로그 사이트를 통해서 IoT 사례 들을 공유한다고 하니 관심 있는 분들은 방문해 보시기 바랍니다.


감사합니다.


안녕하세요, 생각의 웹입니다.


오늘은 REST API에 대해 검색하다가 우연히 발견한 좋은 포스팅이 있어 기록 겸해서 이렇게 링크를 공유합니다.


1. 개념 소개

2. API 설계 가이드

3. API 보안


방대한 내용을 이렇게 잘 정리해 주셔서 덕분에 더 많은 것을 배웠습니다.

특히 API 보안에 관한 부분은 기존 Web Service 1.0 (SOAP)에 비해 취약하다는 논란이 있어서 좀 더 공부해 봐야 겠다는 찰나에 좋은 글을 만나게 되었습니다.


열린 마음으로 좋은 내용들을 공유해 주시는 고수님들을 본받아 저도 열심히 수련하고 잘 나눠야 겠다는 마음을 품습니다.

 

감사합니다.

행복한 하루 되시길!



안녕하세요, 생각의 웹입니다.


이번 포스팅은 요즘 잘 나가는 backbone.js를 이용해 객체 지향 Web App을 만든 사례를 소개하려 합니다.

backbone.js는 MVC (Model-View-Control) 모델을 기반으로 JavaScript 코드의 유지 보수성과 재사용성을 향상시킨 오픈소스 라이브러리입니다.

1. Prerequisites

backbone.js를 사용하기 위해서는 해당 라이브러리 코드를 사이트에서 다운로드 받아야 합니다. 또한, backbone.js은 underscore.js에 강한 종속성(dependency)가 있으니 함께 다운로드 받아야 합니다.

저는 코드를 다운로드 받아 js/libs 폴더에 두고 다음과 같이 index.html에 삽입했습니다. (이후 사용을 위해 가장 보편적인 DOM 관련 라이브러러인 jQuery 또한 참조하는 점도 주의깊게 봐 주시기 바랍니다. )

<script src="js/libs/underscore.js"></script> <script src="js/libs/backbone.js"></script> <script src="http://ajax.googleapis.com/ajax/libs/jquery/1.7.1/jquery.min.js"></script>


2. 모델 생성하기

모델을 생성하기 위해 Backbone.Model.extend() 함수를 사용합니다.

이번 웹앱으로 HTML5 기능을 이용한 간단한 생일 카드를 만들기 위해 다음과 같은 코드를 작성했습니다.

    var Card = Backbone.Model.extend({
    defaults: {
        img: 'Happy-Birthday-Greetings.png',
        title: 'Happy birthday to you!',
        music: 'Kevin_Lax_-_Happy_Birthday.mp3'
    },
    initialize: function () {        
        console.log("Card object initialized.");
    },
    getImageUrl: function () {
        return 'img/' + this.get('img');
    },
    getMusicUrl: function () {
        return 'res/' + this.get('music');
    }
});

카드에 포함될 기본 데이터로 이미지, 음악, 제목 속성을 지정하고 이미지는 img 폴더에 음악은 res 폴더에 두도록 설계했습니다.

initialize 함수 (정확히는 함수형 속성)는 해당 모델이 생성되는 시점에 호출되며 여기에서 생성 시 필요한 초기화를 할 수 있습니다.


이제 이 모델을 통해 인스턴스를 하나 생성해 보도록 합시다.

    var birthdayCard = new Card({    
    to: 'My beloved Joyan',
    from: 'Your father',    
    message:'May happiness be with you all!'
});

이 카드 모델에 추가로 필요한 속성들(수신인, 발신인, 내용)을 추가해서 인스턴스를 만들었습니다.

이상 없이 코딩했다면 console 창에 "Card object initialized."가 찍히겠죠.

3. 뷰 생성하기

앞서 소개드린 바로는 아직 별다른 이득(benefits)이 하나 없습니다. 이제 이걸로 뷰(view) 객체를 만들어 어떤 이득을 얻을 수 있는지 확인해 보겠습니다.

<div id="myCard"></div>


    var CardView = Backbone.View.extend({
    el: '#myCard',
    audio: null,
    canvas: null,

    initialize: function () {

        this.render();
        /* 
        * retrieving any HTML element by JQuery returns array object.
        * Select first one of the retrieved array.  
        */
        this.canvas = $('#myCanvas')[0];
        this.audio = $('#myAudio')[0];

        console.log('Card view intialized.');
    },
    render: function () {
        // append canvas, audio tag
        this.$el.append('<canvas id="myCanvas" width="730" height="414"></canvas>');
        this.$el.append('<audio id="myAudio"></audio>');
        return this;
    } 
});

모델을 생성할때와 유사하게 Backbone.View.extend() 함수를 사용해서 뷰를 생성합니다.

멤버 속성 중 하나인 el 에 index.html에 추가한 div 태그를 참조하기 위한 DOM selector를 저장합니다. 그리고, 앞서 설명드린 바와 같이 초기화 과정 중 호출되는 initialize() 함수를 살펴보면 index.html에 선언한 myCard라는 아이디의 div 태그를 참조하도록 되어 있으며 생성 시, HTML5 canvas, audio를 myCard div 내에 삽입하고 이를 멤버 속성인 audio와 canvas에 저장하도록 되어 있습니다.

이제 이 뷰의 인스턴스를 생성해 봅시다.

    var birthdayCardView = new CardView({
    model: birthdayCard
 });

CardView의 생성자에 model이라는 속성으로 앞서 생성한 birthdayCard를 지정합니다.

정상적으로 코드가 수행하면 console 창에 'Card view intialized.'가 출력됩니다.


3. 뷰에서 모델 정보 참조하기

이제 뷰와 모델이 연결되었습니다. 이제는 모델에 저장된 정보를 가지고 뷰를 꾸며보도록 하겠습니다.


    var CardView = Backbone.View.extend({
    el: '#myCard',
    canvas: null,
    audio: null,

    initialize: function () {
        this.render();
        /* 
        * retrieving any HTML element by JQuery returns array object.
        * Select first one of the retrieved array.  
        */
        this.canvas = $('#myCanvas')[0];
        this.audio = $('#myAudio')[0];

        this.showImage(); /* add image to canvas */

        console.log('Card view intialized.');
    },
    render: function () {
        /* omitted to short */
    },
 
    showImage: function () {
        var view = this;
        var context = view.canvas.getContext('2d');
        var imageObj = new Image();
        // clear canvas
        context.clearRect(0, 0, view.canvas.width, view.canvas.height);
        imageObj.onload = function () {
            context.drawImage(imageObj, 5, 5, 730, 414);
        };
        imageObj.src = view.model.getImageUrl();
        console.log('An image is shown.');
        this.state = 'image';
    }  
});

이로써 초기화 과정 중 birthdayCard 인스턴스에 저장된 image 파일을 가져다 canvas에다 뿌렸습니다.

showImage() 함수의 imageObj.src = view.model.getImageUrl(); 코드와 같이 view.model로 모델의 객체에 접근할 수 있습니다.

4. 뷰에서 이벤트 처리하기

이번에는 마우스를 이미지에 올릴 경우, 모델 객체에 저장된 음악을 출력하고 이미지에서 마우스를 내릴 경우 멈추도록 이벤트 핸들링을 해 봅시다.

    var CardView = Backbone.View.extend({
    el: '#myCard',
    canvas: null,
    audio: null,

    events: {
        'mouseover': 'playMusic',
        'mouseout': 'pauseMusic'
    },
    initialize: function () {
        this.render();
        /* 
        * retrieving any HTML element by JQuery returns array object.
        * Select first one of the retrieved array.  
        */
        this.canvas = $('#myCanvas')[0];
        this.audio = $('#myAudio')[0];
        this.setMusic(); /* 음악 파일 경로를 설정 */
        this.showImage();
        console.log('Card view intialized.');
    },
    render: function () {
        // append canvas, audio tag
        this.$el.append('<canvas id="myCanvas" width="730" height="414"></canvas>');
        this.$el.append('<audio id="myAudio"></audio>');
        return this;
    },
    setMusic: function () {
        // Set a birthday song from model
        this.audio.src = this.model.getMusicUrl();
    },

    showImage: function () {
       /* omitted to short*/
    },
 
    playMusic: function () {
        this.audio.play();
        console.log('The birthday song is playing.');
    },
    pauseMusic: function () {
        this.audio.pause();
        console.log('The birthday song paused.');
    }
});

뷰 객체의 events 속성으로 'mouseover', 'mouseout' 이벤트에 대한 핸들러를 playMusic, pauseMusic 과 같이 지정함으로써, 사진에 마우스를 올렸을때 음악이 흘러나오도록 하는 이벤트를 처리할 수 있습니다.

추가로 마우스 클릭 시, 이미지 대신 하트를 그리고 다시 클릭 시 메세지를 출력해 봅시다.

    var CardView = Backbone.View.extend({
    el: '#myCard',
    canvas: null,
    audio: null,
    state: null, /* 클릭 시 상태 변이를 저장하는 속성 */
    events: {
        'click': 'onClick',
        'dblclick': 'showImage',
        'mouseover': 'playMusic',
        'mouseout': 'pauseMusic'
    },
    initialize: function () {
        this.render();
        /* 
        * retrieving any HTML element by JQuery returns array object.
        * Select first one of the retrieved array.  
        */
        this.canvas = $('#myCanvas')[0];
        this.audio = $('#myAudio')[0];
        this.setMusic();
        this.showImage();
        console.log('Card view intialized.');
    },
    render: function () {
        // append canvas, audio tag
        this.$el.append('<canvas id="myCanvas" width="730" height="414"></canvas>');
        this.$el.append('<audio id="myAudio"></audio>');
        return this;
    },
    setMusic: function () {
        // Set a birthday song from model
        this.audio.src = this.model.getMusicUrl();
    },
    onClick: function () {
        switch (this.state) {
            case 'image':
                this.showHeart();
                break;
            case 'heart':
                this.showMessage();
                break;
            case 'message':
                // ignore state change
                break;
        }
    },
    showImage: function () {
        var view = this;
        var context = view.canvas.getContext('2d');
        var imageObj = new Image();
        // clear canvas
        context.clearRect(0, 0, view.canvas.width, view.canvas.height);
        imageObj.onload = function () {
            context.drawImage(imageObj, 5, 5, 730, 414);
        };
        imageObj.src = view.model.getImageUrl();
        console.log('An image is shown.');
        this.state = 'image';
    },
    showHeart: function () {
        var view = this;
        var context = view.canvas.getContext('2d');
        // clear canvas
        context.clearRect(0, 0, view.canvas.width, view.canvas.height);
        context.strokeStyle = "#000000";
        context.strokeWeight = 3;
        context.shadowOffsetX = 4.0;
        context.shadowOffsetY = 4.0;
        context.lineWidth = 10.0;
        context.fillStyle = "#FF0000";
        var d = 150;
        var k = 100;
        context.moveTo(k, k + d / 4);
        context.quadraticCurveTo(k, k, k + d / 4, k);
        context.quadraticCurveTo(k + d / 2, k, k + d / 2, k + d / 4);
        context.quadraticCurveTo(k + d / 2, k, k + d * 3 / 4, k);
        context.quadraticCurveTo(k + d, k, k + d, k + d / 4);
        context.quadraticCurveTo(k + d, k + d / 2, k + d * 3 / 4, k + d * 3 / 4);
        context.lineTo(k + d / 2, k + d);
        context.lineTo(k + d / 4, k + d * 3 / 4);
        context.quadraticCurveTo(k, k + d / 2, k, k + d / 4);
        context.stroke();
        context.fill();
        console.log('A heart is shown.');
        this.state = 'heart';
    },
    showMessage: function () {
        var view = this;
        var context = this.canvas.getContext('2d');
        // Print 'To' message
        context.font = 'bold 20pt Segoe UI';
        context.fillText('To. ' + view.model.get('to'), 10, 50);
        // Print 'title' message
        context.font = 'italic 40pt Calibri';
        context.fillStyle = 'gray';
        context.fillText(view.model.get('title'), 10, 310);
        // Print 'message' message
        context.font = 'italic 20pt Calibri';
        context.fillText(this.model.get('message'), 10, 350);
        // Print 'from' message
        context.font = '10pt Segoe UI';
        context.fillStyle = 'black';
        context.fillText('Sincerely, \n' + view.model.get('from'), 10, 400);
        console.log('The birthday message is shown.');
        this.state = 'message';
    },
    playMusic: function () {
        this.audio.play();
        console.log('The birthday song is playing.');
    },
    pauseMusic: function () {
        this.audio.pause();
        console.log('The birthday song paused.');
    }
});

이로써 이번 샘플 앱을 모두 완성했습니다.

onClick 함수에서 click 이벤트가 발생할 때마다 state 속성을 image -> heart -> message로 변경시켜 이에 따라 알맞는 함수를 호출하고 있습니다. showHeart()의 경우, canvas에 벡터 방식으로 heart를 그리고 있고 showMessage() 함수에서는 모델에서 가져온 속성들 (to, title, message, from)을 출력하고 있습니다.


이상으로 간략하게 나마 backbone.js를 이용해 코딩하는 법을 소개했습니다.

서두에 말씀드린 것처럼 backbone.js를 사용하면 기존 코드보다 훨씬 가독성이 좋은 코드로 구현되고 역할 별로 책임(responsibility)이 분리되어 유지 보수성이 향상됩니다.

추가적으로 컬렉션, REST API 연동 등의 우수한 기능을 제공하기 때문에 이번 기회에 익혀 두시면 좋을 듯 합니다.


긴 글 끝까지 읽어 주셔서 감사합니다. 행복한 하루 되시길!




안녕하세요, 생각의 웹입니다.


앱 센터(AppCenter.kr) 주관  실시하는 IoT 강연인, 

IoT, 기술의 혁신과 미래 그리고 통찰: Do IoT Yourself

의 강사로 지금껏 조사한 내용과 IoT 기기를 직접 개발하면서 쌓은 경험한 것들을 공유하려 합니다.


http://onoffmix.com/event/33505


아래는 첫번째 강의에서 소개드릴 IoT에 대한 발표자료 초안입니다. 조금이나마 관심을 가질 수 있는 계기가 되었으면 좋겠네요.



첫 번째 세션 후 두 번째부터는 아두이노 킷을 가지고 방안의 온도 추세를 웹앱으로 모니터링하는 IoT를 직접 만들어 보는 과정으로 진행됩니다. 이번 기회에 IoT의 개념을 확실히 이해하고 새로운 비지니스 기회를 포착할 수 있을 것으로 기대합니다.

많은 관심과 참여 부탁드립니다.

감사합니다.

안녕하세요, 생각의 웹입니다.


이번에는 이전에 소개드린 바 있는 Node.JS에서 Arduino를 제어하는 JavaScript API 모듈인 duino에 온습도 센서인 dht11 를 제어할 수 있도록 기능 추가한 사례를 소개하려 합니다.

이를 위해서 먼저 duino github 소스 코드를 다운로드 받습니다.

duino 프로젝트를 git clone 하거나 다운로드 받아 압축을 풀면 examples, lib, node_modules, src 폴더와 index.js 파일 등이 있습니다.


1. arduino에 DHT11 센서 코드 추가하기

src 폴더에는 du.ino 파일이 존재합니다. 이 파일은 Arduino IDE의 프로젝트 파일로써 Arduino 보드에서 동작할 firmware 코드를 담고 있습니다.

Arduino IDE를 이용해 du.ino 파일을 열면 다음과 같은 코드로 구성되어 있습니다.


#include <servo.h>
bool debug = false;
 // ... omitted ...
void setup() {
  Serial.begin(115200);
}
void loop() {
  while(Serial.available() > 0) {
    char x = Serial.read();
    if (x == '!') index = 0;      // start
    else if (x == '.') process(); // end
    else messageBuffer[index++] = x;
  }
}
/*
 * Deal with a full message and determine function to call
 */
void process() {
  // ... omitted ...
 switch(cmdid) {
    case 0:  sm(pin,val);              break;
    case 1:  dw(pin,val);              break;
    case 2:  dr(pin,val);              break;
    case 3:  aw(pin,val);              break;
    case 4:  ar(pin,val);              break;

    case 97: handlePing(pin,val,aux);  break;
    case 98: handleServo(pin,val,aux); break;
    case 99: toggleDebug(val);         break;
    default:                           break;
  }
}
 // ... omitted ...


코드를 간략히 설명하면, Arduino controller는 setup() 함수 호출하여 serial port의 전송 속도를 지정 후 loop() 함수로 진입하여 메세지를 패칭(fetching)하여 특정 동작을 수행하게 됩니다. 이때 process() 함수 내 switch() 문으로 명령 ID를 구분해 적절한 이벤트 핸들러 (event handler) 가 호출되는 방식입니다.

따라서, 여기에 DHT11 센서에서 정보를 가져오는 명령 ID를 추가하고 이에 걸맞는 이벤트 핸들러를 추가하도록 하겠습니다. 명령 ID는 96으로, 관련 이벤트 핸들러는 handleDHT11(pin, val); 로 정의하겠습니다.


#include <servo.h>
// webofthink:For supporting DHT11 sensor
#include 
dht11 rht01;
// webofthink:end

bool debug = false;
 // ... omitted ...
}
/*
 * Deal with a full message and determine function to call
 */
void process() {
  // ... omitted ...
 switch(cmdid) {
    case 0:  sm(pin,val);              break;
    case 1:  dw(pin,val);              break;
    case 2:  dr(pin,val);              break;
    case 3:  aw(pin,val);              break;
    case 4:  ar(pin,val);              break;
    case 96: handleDHT11(pin, val);    break; /* webofthink:add DHT11 support */
    case 97: handlePing(pin,val,aux);  break;
    case 98: handleServo(pin,val,aux); break;
    case 99: toggleDebug(val);         break;
    default:                           break;
  }
}
 // ... omitted ...

/* Handle DHT11 Humidity & Temperature
 * @author webofthink
*/
void handleDHT11(char *pin, char *val) {
  int p = getPin(pin);
  int chk = rht01.read(p - A0); 
  char m[12];
  sprintf(m, "%s::%03d,%03d", pin, rht01.temperature, rht01.humidity);
  Serial.println(m);
}


DHT11 센서에서 값을 가져오는 로직은 DHT11이라는 라이브러리로 제공되기 때문에 다운로드 받으신 후 헤더 파일을 추가(include)해서 사용하시면 쉽게 코딩할 수 있습니다.

여기에 주의 깊게 봐둘 사항은 Node.JS 측으로 전송할 메세지 형태입니다. handleDHT11() 함수의 sprintf() 문의 두 번째 인자를 참고하기 바랍니다.


2. duino library 추가하기


이번에는 lib 폴더로 이동합니다. 여기에 보면 duino 프로젝트가 기 지원하는 센서 및 엑추에이터들의 API 모듈이 존재합니다.

여기에 DHT11 센서를 지원하기 위한 API 모듈인 dht11.js을 추가합니다.


    var events = require('events'),
    util = require('util');
/*
 * The main DHT11 constructor
 * Connect to the serial port and bind
 */
var DHT11 = function (options) {
    if (!options || !options.board || !options.pin) throw new Error('Must supply required options to DHT11 Sensor');
    this.board = options.board;
    this.pin = options.pin;
    // Loop and trigger fire-read sequences
    setInterval(function () {
        var msg = '96' + this.pin;
        this.board.write(msg);
    }.bind(this), options.throttle ||  500);
    // When data is received, parse inbound message
    // match pin to instance pin value
    this.board.on('data', function (message) {
        
        var m = message.slice(0, -1).split('::'),
            err = null,
            pin, data;
        if (!m.length) {
            return;
        }
        pin = m[0]
        data = m.length === 2 ? m[1] : null;
        if (pin == this.pin) {
            // console.log("emitting data to read event: " + data);
            var values = data.slice(0).split(',');
            this.temperature = parseInt(values[0]);
            this.humidity = parseInt(values[1]);
            this.emit('read', err, this.temperature, this.humidity);
        }
    }.bind(this));
 };
/*
 * EventEmitter, I choose you!
 */
util.inherits(DHT11, events.EventEmitter);
module.exports = DHT11;


이 API 모듈 소스의 구성은 다음과 같습니다. 먼저 DHT11 클래스와 생성자를 선언합니다. 생성자에서는 options 라는 객체를 인수로 받아 필요한 초기 환경을 설정합니다. 아두이노 보드와 센서가 연결된 핀 정보가 설정되었다면, Arduino 측에 DHT11 정보를 요청하는 메세지를 setInterval() 함수를 통해 정기적으로 전달합니다.

이 명령이 Arduino 보드에 전송되면 첫번째 항목에서 구현한 이벤트 핸들러 handleDHT11()가 호출되어 요청받은 온습도 정보를 Node.JS에 회신합니다.

그러면 Node.JS에서는 this.board.on()의 'data' 이벤트가 발생(trigger)하게 되면서 해당 정보를 가져올 수 있게 됩니다.

마지막으로 이 온습도 정보를 EventEmitter의 'read' 이벤트로 재 전송하도록 구현합니다.


3. duino API에 DHT11 추가하기


다시 root 폴더로 돌아가서 index.js를 열어 DHT11를 다음과 같이 추가합니다.


    
module.exports = {
  Board:  require('./lib/board'),
  Led:    require('./lib/led'),
  Piezo:  require('./lib/piezo'),
  Button: require('./lib/button'),
  Servo:  require('./lib/servo'),
  Sensor: require('./lib/sensor'),
  Ping:   require('./lib/ping'),
  PIR:    require('./lib/pir'),
  LCD: require('./lib/lcd'),
    DHT11: require('./lib/dht11') /* add to support DHT11 sensor */
};


이로써, duino에서 DHT11 API를 사용할 수 있게 되었습니다.


4. DHT11 API 예제


examples 폴더로 이동하면 제공하는 API 모듈 별 예제 코드가 있습니다. 여기에 이번에 추가한 DHT11 API의 예제도 추가해 봅시다.


    
/* following is a test code for DHT11 sensor. */
var arduino = require('duino');
          
console.log("Initializing arduino board...");
var board = new arduino.Board({
    device: 'COM6',
    debug: false
});
var dht11 = new arduino.DHT11({
    board: board,
    pin : 'A2'
});
board.on('ready', function () {
    console.log("arduino board is ready to serve.");
});
dht11.on('read', function (err, temp, humidity) {
    console.log("temperature: " + temp + " degree of Celcius, " + "humidity: " + humidity + "%");
});


예제를 보면 알 수 있듯이 Arduino 보드 객체와 알맞은 핀 정보를 options 객체의 속성으로 전달 후 on 함수의 'read' 이벤트 핸들러로 온습도 정보를 가져올 수 있습니다.


5. DHT11 API


지금까지 추가했던 DHT11 API의 Web IDL은 다음과 같습니다.


    enum DHT11EmitterType { "read"};

    dictionary DHT11Option {
        Board board;
        DOMString pin;
        unsigned short throttle; // time interval per each measurements
    };
     
    callback DHT11OnCallback = void (Error err, short temperature, unsigned short humidity);
    
    [Constructor, Constructor (DHT11Option option)]
    interface DHT11 {
        void on(DHT11EmitterType type, DHT11OnCallback cb); 
    };


지금까지 Node.JS를 통해 arduino의 센서를 제어하기 위해 필요한 단계를 정리해 보았습니다. duino에는 상당한 센서와 엑추에이터를 지원하는 API가 존재하나 이처럼 지원하지 않는 센서가 있을 경우에도 추가 구현이 용이하도록 구조화 되어 있습니다.

서두에 링크한 문서를 살펴보면 arduino board 에 ethernet이나 wi-fi 모듈을 추가하여 이런 센서 정보를 곧바로 웹으로 노출시키는 방법도 있습니다만 이렇게 Node.JS를 gateway로 두고 서비스를 확장하는 것이 context data를 지식화 하는데 유용한 구현 방법이라고 생각합니다.

긴 글 읽어 주셔서 감사합니다. 늘 행복하시길!

"생각의 웹"입니다.


Server side JavaScript platform인 Node.JS에서 사용할 유닛 테스트 프레임워크를 찾던 중 xUnit 계열인  nodeunit이 있어 이렇게 소개드리려고 합니다.

1. 설치

nodeunit은 https://github.com/caolan/nodeunit 에서 다운로드 받아 컴파일하거나 NPM을 통해 다운로드 받을 수 있습니다. 이때 node와 별개의 프로세스로 동작하기 때문에 -g 옵션을 주고 설치해야 합니다.

npm install nodeunit -g

설치가 완료되면 console 상에서 nodeunit 명령을 실행할 수 있습니다. 다음과 같이 수행하면 nodeunit의 사용법을 출력합니다.

nodeunit --help

2. 테스트 케이스란?


유닛 테스트 케이스(unit test case)는 테스트 대상 API (API under test), 테스트 조건(pre-condition and post-condition), 테스트 입력 값 (test input) 그리고 예상 출력 값 (expected output)으로 구성됩니다. 테스트 결과 판별 (assertion)은 보통 입력 값과 예상 출력 값이 같은 지를 비교하여 이뤄지며 그 결과를 취합하여 출력하게 됩니다.

3. 테스트 케이스 작성


다음은 가장 간단한 형태의 테스트 케이스 작성법입니다.


exports.testSomething = function(test){
    test.expect(1);
    test.ok(true, "this assertion should pass");
    test.done();
};

exports.testSomethingElse = function(test){
    test.ok(false, "this assertion should fail");
    test.done();
};


상기 예제를 보면 두 개의 테스트 케이스 (testSomething, testSomethingElse)가 존재하며, 결과 판별을 위해 test.ok()라는 API를 제공하고 있습니다.

이 API의 상세는 다음과 같습니다. 

interface Test {
   /**
    * Test if is a true value
    *
    */
    void ok(boolean value, optional DOMString message);
};


또한, 유심히 볼 필요가 있는 부분은 test.expect(1); 와 test.done(); 인데 이 API들은 JavaScript 특유의 비동기 동작(asynchronous operation)으로 인해 의도치 않은 결과가 나오는 것을 막기 위해 추가되었습니다.

좀 더 상세히 설명드리면, JavaScript는 콜백 함수(callback function)라는 형태로 요청한 동작의 결과를 되돌려 받는 경우가 많은데 이때 이 콜백 함수는 비동기적으로 호출되기 때문에 테스트 케이스를 잘못 작성하는 경우에는 호출되기 전에 테스트가 종료되는 경우가 종종 발생합니다.

이런 실수를 방지하기 위해서 nodeunit에서는 테스트가 끝나는 시점을 알려주는 test.done() API를 명시적으로 호출하도록 설계되었습니다.

또한, 테스트 케이스가 종료되기 전에 수행되어야 할 판별 (assertion)의 수를 test.expect(number) API로 호출하도록 해서 의도치 않는 결과 (false-positive)가 나오지 않도록 했습니다.

4. 보다 복잡한 테스트 케이스 작성


DB API와 같이 시간이 오래 걸리고 테스트 조건을 설정하는 데 많은 비용이 걸리는 경우를 위해 테스트 스윗(test suite)이라는 테스트 묶음을 위한 구조가 존재합니다.

테스트 스윗의 경우, 각 테스트 마다 선행 조건과 후행 조건이 실행될 수 있도록 setUp, tearDown 이라는 API를 제공합니다.

module.exports = {
    setUp: function (callback) {
        this.mysql = require('mysql');
        this.connection = this.mysql.createConnection({
            host: 'localhost',
            user: 'root',
            password: 'password',
            database: 'db'
        });
        this.connection.connect();
        callback();
    },
    tearDown: function (callback) {
        // clean up code here
        this.connection.end();
        callback();
    },

    testSqlWrite : function (test) {

        test.expect(1);
        this.connection.query('insert into tempData(tempDate, tempCelsius) values(?, ?)', [new Date(), 10],
            function (err, rows, cols) {
                if (err) {
                    test.ok(false, err);
                } else {
                    test.ok(true, rows);
                }
                test.done();
            });
    },

    testMysqlRead : function (test) {   
        test.expect(1);
        this.connection.query('select * from tempData',
            function (err, rows, cols) {
                if (err) {
                    test.ok(false, err);
                } else {
                    test.ok(true, rows);
                }            
                test.done();
            });
    }
};


상기 예제 코드는 Node.JS에서 MySQL를 사용할 수 있도록 mysql 모듈을 이용해 테스트 케이스를 작성한 것입니다.

이 코드를 수행하면, 1) setUp -> testMysqlWrite -> tearDown 2) setUp -> testMysqlRead 순으로 호출되게 됩니다. 코드 중 비 동기로 결과를 반환하는 API가 많음을 유념하시기 바랍니다.


이상으로 Node.JS 상에서 유닛 테스트 케이스를 작성하는 간단한 사용법을 소개드렸습니다. 코드를 작성하면서 테스트를 위해 작성했던 코드 뭉치 (code snippet)를 이와 같이 테스트 케이스로 작성하면 이후 변경 시 품질 관리 등에 유용하게 활용할 수 있습니다.


이상입니다. 감사합니다.  

+ Recent posts