____________________________________________________
라즈베리파이와 자바스크립트로 시작하는 IoT
원격 제어 LED 부터 소셜 오디오까지
__________________________________________________________
발간사
「라즈베리파이와 자바스크립트로 만드는 IoT」
한국과학창의재단 이사장님 발간사
2016 년은 그 어느 해보다 미래 사회의 변화를 체감하게 된 해로 기록될 것 같다. 1 월에 다보스포럼
주제는 ‘4 차 산업혁명의 이해’로, 정보통신기술 융합을 통해 모든 것이 연결되고 보다 지능화된
사회로의 변화가 눈앞에 와 있음을 강조했다. 그리고 3 월에는 우리 국민들에게 충격을 안겨 준
이세돌 9 단과 구글의 인공지능 ‘알파고’의 바둑 대국이 있었다. 바둑의 신이라 불리우는 이세돌
9 단이 컴퓨터에게 1:4 로 패배하는 장면을 전 세계가 목도했고, 이는 사회적으로 큰 반향을 불러
일으켰다.
다보스발 ‘제 4 차 산업혁명’과 서울발 ‘알파고 쇼크’로 시작된 우리 사회의 변화가 주는 의미는
무엇일까? 1940 년대 첫 다용도 디지털 컴퓨터 개발, 1970 년 초 개인용 컴퓨터 개발과 함께
컴퓨터는 우리 생활에서 없어서는 안 되는 필수품으로 자리 잡게 되었고, 산업분야는 2 차
산업혁명(대량생산)에서 컴퓨터를 통한 자동화인 3 차 산업혁명으로 발전하게 되었다. 3 차
산업혁명까지 우리가 알고 있던 컴퓨터는 인간의 지시에 의해 기능을 수행하는 편리한 도구였다면
4 차 산업혁명을 맞이하는 시점에서 알파고는 이러한 기존의 가치관을 완전히 파괴했다. 알파고는
이길 가능성이 있는 전략 여러 개를 컴퓨터가 스스로 분석해 성공 전략을 알아내는
기계학습방식이라는 참신하고 도전적인 접근방식을 선택한 개발자들에 의해 큰 성공을 거두었다.
이렇듯 4 차 산업혁명의 특징은 제품과 제조과정, 시스템의 지능화를 통해 기업이 고객의 특성에
맞는 맞춤형 제품을 제공하는 것이다.
그러나 우리가 주목해야할 사실은 4 차 산업혁명과 알파고도 결국 하사비스 박사와 같은 우수한
개발자에 의해 이루어졌다는 것이다. 이러한 맥락에서 프로그래밍, 디지털 사물과 밀접하게 대화하는
언어의 중요성은 그 어떠한 때보다 중요하다고 할 수 있다. 중세 시대 라틴어로 쓰인 성서를 읽을 줄
알았던 성직자가 그 시대를 이끌어 나갔듯이, 프로그래밍 언어로 자신의 아이디어를 현실로
만들어내는 프로그래머들이 제 4 차 산업혁명을 리드해 나가고 있다고 해도 과언이 아니다. 미국이나
영국 등에서는 프로그래밍을 영어, 중국어에 이어 제 3 의 언어로 생각하여, 이미 코딩 관련
소프트웨어 수업 등을 정규 과목으로 편성했다. 프로그래밍 언어를 습득하여 활용해 내는 것이 미래
인재로서의 핵심역량인 세상이 다가오고 있는 것이다.
디지털 환경을 태어나면서부터 생활처럼 사용하는 세대가 늘어나고 있는 만큼, 디지털 네이티브들의
프로그래밍 능력은 기존의 자작 문화, 즉 DIY(Do It Yourself)와 결합되어 메이커 운동(Maker
Movement)라는 새로운 시대적 흐름을 만들어 내고 있다. 대규모 기업이나 공장 생산라인의 도움
없이도 자신이 생각하는 아이디어를 시제품으로 생산해내는 사람들이 생겨나고 있는 것이다. 이러한
메이커들이 탄생하게 된 계기에는 오픈소스 하드웨어와 소프트웨어의 공이 크다. 기존에는 관련
전공자들만 활용 가능했던 정보들이 공유되고 확산되면서 비전공자들도 시제품 제작에 쉽게
접근하여 자신의 아이디어를 물리적으로 구현해 내는 시대가 된 것이다.
모든 산업의 한계 생산 비용이 제로 수준으로 떨어지는 한계비용 제로 사회의 도래와
크라우드펀딩법 통과로 인해 국내에서도 메이커들이 자신이 만든 아이디어 시제품을 판매하고
사업화 할 수 있는 환경이 조성되고 있다. 과거에는 천편일률적인 제품만을 생산하고 사용했다면,
이제는 다품종 소량생산 체제로 더 넓은 시장을 충족하는 다양한 제품과 서비스를 만들어 낼 뿐
아니라 전 세계의 사용자로부터 평가 받고, 투자 받는 것이 가능해졌다. 페블이라는 스마트 워치
회사가 킥스타터에서 200 억 넘는 모금을 유치하여 사업화에 성공한 것이 그 대표적인 사례라고 할
수 있다.
이러한 시대적 흐름에 맞추어 한국과학창의재단은 아이디어와 상상력을 현실화하는 메이커 문화
확산에 힘쓰며 누구나 자신만의 창의적인 아이디어를 구현 할 수 있도록 다양한 사업을 통해 메이커
활동을 지원하고 있다. 실제로 이 책의 저자가 소속되어 있는 Circulus 팀은 재단이 추진한 ‘2015
우수메이커 활동 지원 사업’을 통해 개인용 지능형 로봇을 만들어 내었고, ‘2015 창조경제박람회’와
함께 개최된 ‘메이커 페스티벌’을 통해 그 성과를 세상에 알렸다. 또한 2015 대한민국과학창작대전을
통해 Circulus 팀은 소셜 오디오를 제작하였으며, 실제 이 제품은 크라우드펀딩을 통해 제품화
되었다. 메이커 학습 및 제작이 가능한 플랫폼(Circulus)을 구축하여 교육활동도 펼치고 있는 저자가
소셜 오디오의 성공 사례를 기반으로, 누구나 메이커가 될 수 있도록 그 제작과정을 오픈소스로
공유하여 책으로 접할 수 있도록 했다니 기쁜 일이다.
미국에서는 인구의 57%인 1 억 3500 만명이 스스로 메이커라고 생각한다는 조사 결과가 있다.
페블의 대표인 에릭 미기코브스키 뿐 아니라 애플의 스티브잡스와 워즈니악 또한 차고지에서 만들고
싶은 것을 만들던 메이커였다. 우리나라에서도 잠재력을 가진 개개인들이 다양한 아이디어를
시제품으로 구현해 내고 사업화 할 수 있는 환경이 더욱더 활성화되길 바라며, 이 책이 많은
사람들이 메이커 활동에 관심을 갖고 참여하는데 길잡이 역할이 되길 바란다.
목차
0. 시작하기 앞서 4p
1. 파이를 동작시켜 보자 - 초기설정 17p
2. 파이의 운영체제 - Linux 속성실습 33p
3. JavaScript 로 하드웨어 제어를 - Node.JS 57p
4. 거리 측정하고 정보 표시 하기 - GPIO 81p
5. 스마트폰으로 리모콘을 - jQueryMobile 119p
6. 인터넷으로 음악과 날씨를 - OpenAPI & RSS 137p
7. 오디오 소프트웨어 개발하기 153p
8. 외관을 생각대로 만들기 189p
9. 언제 어디서나 동작하는 IoT- Circulus 192p
10. 마무리 256p
0/시작하기 앞서
이 책의 집필 목적
생각만 있는 Thinker 에서 세상에 의미 있는 변화를 이끌고 직접 행동하는 Maker 가 되고자
하는 여러분들을 환영한다.
지금까지 우리는 전자 제품을 만든다고 하면, 구하기 힘든 장비, 비싼 가격, 납땜과 복잡한
프로그래밍 등 전문가만 할 수 있다는 인식을 가지고 있다. 한번 실수하면 큰 돈이 날라 가니
매우 조심스럽게 사용해야 했었다. 하지만 지금 이 순간 이런 비싼 장비들은 더 강력하면서
저렴한 가격으로 부품들을 구입할 수 있다. 더 쉽게 조립하고 손쉬운 프로그래밍으로 우리가
만들고자 하는 것을 마법처럼 눈앞에 보여 지도록 할 수 있는 시대를 살고 있다.
우리가 하고자 하는 것은 생각만 하는 것이 아닌 실제로 만들어 보는 것이며, 나 혼자만의
즐거움이 아닌 타인과 함께 공유하고 나눌 수 있는 멋진 제품이다. 더 이상 공장에서
붕어빵처럼 찍어 내고, 내가 원하는 기능이 없거나 필요 없는 기능에 불평하지 않아도 된다.
원하지 않는 디자인에 컬러의 제품을 돈을 주고 구입하는 시대는 끝났다. 직접 여러분의 손으로
직접 여러분이 실생활에 사용할 수 있는 제품을 만들 수 있도록 하고자 한다.
여자 친구를 위한 IoT 장비
처음 IoT 오디오를 만들게 된 계기는 필자의 경험을 통해서 나오게 된 것이다. 필자가 IT 업을
시작하고 나서 5 년간 지방과 해외 프로젝트로 떠돌았었다. 이 시기에는 불안정한 생활을
하다보니 여자친구를 만나도 떠도는 생활로 인하여 헤어지게 되는 경우가 많았는데, 지방이나
해외에 있어도 여자친구 곁에 있는 것 처럼 할 수 있는 방법은 없을까? 하여 처음 제작을
시도하게 되었다. 여자친구 곁에 IoT 오디오를 두어, 지방이나 해외에 있어도 음악 선물이나
음성 메시지, 알람 서비스를 보낼 수 있는 장치를 기획하였다.
삼성 그룹 해커톤에서 만든 초기 IoT 오디오, Flying Music
2014 년 삼성 그룹의 해커톤에 나가서 실제로 이 오디오를 만들어 냈다. 비슷한 경험을 가진
사람이 많아 좋은 호응을 얻을 수 있었다. 실제 효과도 있었다. 캐롤 송이 나오는 크리스마스
트리를 만들어 선물하여 사귀게 된 여자 친구. 거리가 떨어져 있어서, 실제로 오디오를 만들어
여자 친구 집에 두어서 활용하고 있다. 당신도 직접 만들어서 당신의 친구들과 가족들에게
선물해 볼 수 있다!
세상에 단 하나의 내가 만드는 오디오
[ 레이저 커터로 만든 초기 제작 모델 ]
지금까지 우리가 알고 있는 오디오는 기본적으로 사용자가 지정해 주는 파일이나 CD 등을
재생하는 장치였다. 우리는 기존의 오디오에서 할 수 없었던 사람과 사람을 이어 주는 감성적인
IoT 오디오를 만들어 볼 것이다. 물론 돈을 투자해서 파는 제품을 사면 어때 라는 생각을 할
수도 있다. 하지만 그 어떠한 제품에서도 제공해 줄 수 없는, 붕어빵 같은 전자 제품이나 기계가
아닌 세상에 단 하나의 오디오가 탄생할 것이다.
바쁜 당신을 기다리는 여자 친구를 감동 시킬 수 있는 IoT 오디오. 우리는 이 오디오에 다양한
하드웨어 및 소프트웨어 기술을 이용하여 우리가 지금까지 알고 있는 단순히 음악이 재생되는
장치에서 한 단계 수준을 끌어올리고자 한다.
기본 모델
- 탁상 시계 기능
- 스마트 폰 제어
- 음악 검색 / 다운로드 및 재생
- 시간 알람 기능
- 최신 가요 목록 확인 및 검색
우리가 하고자 하는 기본 모델만 있더라도 당신이 어디에 있던지 당신의 스마트 폰으로 여자
친구 집에 있는 오디오의 선곡을 통해, 음악을 선물할 수도 있다. 게다가 손 편지 대신에 당신의
마음이 담긴 메시지를 보내고 재생시킬 수 있다.
확장 모델
- 실 외에서의 원격 제어
- 헤드라인 뉴스 읽어 주기
- 오늘의 날씨 읽어 주기
- 메세지 전달 / 읽어 주기
- 현재 방안의 온 습도 알려주기
기본적인 기능을 만드는데 성공하였다면, 이 오디오에 좀더 재미난 기능들을 추가하고자 한다.
일단 인기가요 TOP10 을 곧바로 재생할 수 있는 기능을 제공한다. 알람 기능을 설정하여
설정한 음악을 재생할 수 있고, 오늘의 날씨, 주요 뉴스도 오디오로 제공받을 수 있도록 한다.
즉 아침 일어나는 시간에 알람과 동시에 오늘의 주요 정보를 들으면서 일어날 수 있는 것이다.
모바일 음성 인식을 통한 명령도 지원하여 편의성을 돕도록 한다.
이 책의 대상 독자
무언가 직접 만들어 보고, 만든 것이 나와 타인에게 도움을 주고자 하는 그 누구도 환영한다.
어떤 일이든 가장 중요한 것은 어떤 일이든 하고자 하는 의지와 뜻이라는 걸 우리는 잘 알고
있다. 우리는 이 책의 구성을 부품을 사고, 단계 별로 따라서 조립하고 화면에 코드를 입력하는
것으로 완성할 수 있도록 책을 구성하였다. 하지만, 여기서 하고자 하는 내용에 대해 기본
지식을 가지고 있다면 좀더 이해하면서 학습하는데 도움이 될 것이다. 다시 한번 말하지만,
당신의 의지가 있다면 아래 사항을 모른다고 해도 만들 수 있다!
바쁜 시간에 쫓기는 직장인
바쁜 시간에 쫓겨, 잦은 출장으로 여자 친구를 볼 수 없는 직장인 분들, 어린 시절을 떠올리며
무언가 만들어 보고 싶었지만 어디부터 시작해야 할지 모르는 분들이다. Making 에 대한 기초
지식을 빠르게 학습하고, 직접 오디오를 만들어 볼 수 있다. 무엇보다 이 오디오를 여자
친구에게 만들어 주어 바쁜 시간 속에서도 IoT 오디오를 통해 당신과 이어 주는 역할을 할
것이다.
Making 에 관심 있는 대학생
Arduino 의 대중화에 따른 오픈 소스 하드웨어에 대한 인식의 확산으로 당신의 주변에는 이미
이러한 것들로 이것저것 만들어 보는 친구들이 많을 것이다. 당신이 대학생이라면, 직접
하드웨어 기반 Making 을 해 봄으로서, 소프트웨어와 하드웨어 그리고 서비스가 접목되는
방식을 이해하고 스타트 업이나 다양한 공모전에 당신만의 서비스와 아이디어를 이용하여
도전해 볼 수 있을 것이다.
컴퓨터 언어의 시대에 아이를 위한 학부모
2 년전 까지만 하더라도 프로그래밍 교육을 누구나 배우게 될 것이다 라고 하면 말도 안 되는
소리로 취급 받았으나, 이제는 누구나 배우는 시대가 되었다. 이러한 시대를 살아가는 아이들을
위해 본인이 직접 배우고 만듦으로써, 만든 제품을 아이와 함께 활용하고, 구성 원리를 알려
주고 더 나아가서는 아이와 함께 만들어 보는 과정 속에 컴퓨터의 원리를 파악 할 수 있도록
한다.
책에서 다룰 내용
라즈베리파이
오픈소스 하드웨어 하면 아두이노를 흔히 떠오르게 된다. 또한 우리가 선택할 수 있는 오픈소스
하드웨어가 다양하게 있다. 하지만 우리는 라즈베리파이를 이용하여 프로젝트를 진행하고자
한다. 라즈베리파이는 하드웨어를 제어할 수 있는 초 소형/초 저가의 컴퓨터이다. 우리는 이
먹음직스러운 파이로 실 생활에 도움이 되는 것을 만들어 볼 것이다.
Linux
여러분이 사용하는 컴퓨터가 윈도우로 돌아가듯이, 라즈베리파이 또한 이러한 운영체제로
동작이 되며, 가장 많이 활용되는 것은 ‘라즈비안’ 이라고 불리우는 라즈베리파이를 위한
리눅스이다. 리눅스에 대해 A to Z 까지 다루는 것은 이 책의 범주를 벗어나는 것으로, 우리는
IoT 오디오 제작에 필요한 주요 리눅스 명령어와 관리 방법에 대해 다
JavaScript
우리는 이 오디오 프로젝트를 위하여 JavaScript 라는 언어를 사용할 것이다. 프로그래밍을
접해 본 경험이 있다면, 웹 페이지에서 Alert 창 띄우는 용도로나 쓰는 게 아닐까 하는 생각을
할 수 있다. 놀라지 마시라, 우리는 이 Web 을 위해 태어난 JavaScript 를 이용하여 모바일 웹은
물론 라즈베리파이를 우리 마음대로 요리할 수 있도록 도와 줄 것이다, 당신이 이 언어를
모른다고 해도, 핀란드어를 따라 친다는 심정으로 하면 마법처럼 화면에 보이고 당신이 조립한
장치가 동작이 될 것이다! 하지만 어느 정도 알고 있다면 더 큰 도움이 된다.
NodeJS
라즈베리파이 오디오를 동작시키는 리모콘을 위해서도 JavaScript 를 이용하지만,
라즈베리파이에 탑재되는 하드웨어 제어 및 서버를 위해서도 JavaScript 가 활용된다. 이를
서버에서 가능케 하는 것이 NodeJS 이고, 이것에 대해서 간단하게 다루어 본다. 우리는 NodeJS
를 활용하여 라즈베리파이에 웹 서버를 구축하고, 연결되는 센서와 하드웨어를 제어해 볼
것이다.
jQueryMobile
모바일 컨트롤러를 적은 노력으로 멋지게 만들 수 있는 jQueryMobile 프레임워크를 이용할
것이다. JavaScript 에서 가장 인기 있는 jQuery 기반의 모바일 프레임 워크로, 라즈베리파이와
유기적으로 연결된 모바일 웹을 만들게 될 것이다.
Hardware Control
라즈베리파이에 LCD 를 연결하여 문자를 출력하고, 온습도 센서를 이용하여 현재 실내의
온습도 상황을 점검해 볼 것이며, 초음파 센서로 거리를 측정해 본다.
OpenAPI
오디오 기능을 위해 음악 클라우드 서비스인 SoundCloud 의 개발자용 Open API 를 활용하여
원하는 음악을 검색하고, 검색 결과로부터 음원 다운로드를 하는 것을 실습해 볼 것이다.
SoundCloud 가입 및 서비스 등록, JavaScript 용 API 활용까지 살펴 봄으로서, 사용자가 타
OpenAPI 연동 시에도 용이하게 할 수 있도록 한다.
부록 : 3D Printing
보기 좋은 음식이 맛이 있어 보이는 것과 같이, 우리가 만드는 하드웨어 역시 그러하다. 3D
모델링이라는 분야가 전문 분야 였지만, 쉽게 3D 모델링을 할 수 있는 프로그램인 123D
Design 을 활용하여 간단하게 모델링을 배우고, 프린팅 하는 법을 알아볼 것이다.
메이커 세계로의 입문
이 책을 통해 여러분은 메이커의 세계로 입문할 수 있을 것이다. 메이커란 운동화 메이커의
메이커가 아닌, 자신의 생각을 직접 만들어 내는 사람들을 의미한다. 흔히 이전의 DIY(Do It
Yourself) 에 IT 기술적인 부분이 결합된 것을 메이커라 이야기 하는 경우가 많다.
오바마 대통령이 백악관에서 메이커 행사를 열 만큼, 앞으로의 4 차 산업 혁명 시대에 중요한
역할을 담당하고 있는 문화가 메이커 문화이다. 여러분들도 이 책으로 말미 암아 여러분 만의
메이커 작품으로 , 메이커 페어에서 만나 보길 희망한다.
준비 물품
우리가 만들고자 하는 오디오를 위해 사용되는 재료들은 다음과 같다. 제작을 위한 필수 재료와
옵션이 존재하는데, 모니터 케이블이나 USB 키보드의 경우 이미, 그러한 장치가 있다면
구입하지 않아도 된다.
필수 구입 물품
라즈베리파이
라즈베리파이 오디오를 만들기 위한 핵심 Single Board 컴퓨터. 만일 당신이 자금의 여유가
있다면, 성능이 우월한 라즈베리파이 3 를 구입해도 된다. 하지만, 3 의 경우 전기소모가 많아
일반 2A 출력의 USB 어댑터로 제대로 동작할 수 없으므로, 좀더 저렴하면서도 쿼드코어로
충분한 성능을 제공하는 라즈베리파이 2 를 구입하는 것도 방법이다.
무선랜
유선 연결로만 라즈베리파이를 사용한다면, 필요하지 않지만, 어디에서나 사용할 수 있는
오디오를 위해서는 반드시 필요하다. 현재 가장 많이 사용되는 것은 N 스펙이다. 하지만 보다
빠르고 안정적인 속도를 위하여 N+ 스펙의 무선랜을 구입할 수도 있다. 만일, 라즈베리파이 3
를 보유하고 있다면, 이미 내장되어 있으므로 별도로 구매할 필요는 없다.
16GB MicroSD 카드
라즈베리파이의 저장장치로써 Linux 운영체제를 탑재하고, 각종 추가 Software 를 설치하기
위해서는 최소 8GB 이상의 용량을 추천한다.
MicroSD 카드는 다양한 스펙이 존재하는데, Class 간에 가격의 차이가 크지 않다. 컴퓨터를
쾌적하게 쓰기 위해 하드디스크 대신 SSD 를 쓰듯이, 이왕 MicroSD 카드를 구입할 때 Class 10
이나 UHS-I 스펙을 사길 권장 한다.
종류 최소 속도 용도
Class 2 2 MB/s SD 영상 기록
Class 4 4 MB/s HD 영상 기록
Class 6 6 MB/s Full HD (1920x1080) 영상 기록
Class 10 10 MB/s 연속적인 HD 이미지 기록
UHS I 10 MB/s 실시간 방송
UHS III 30 MB/s Ultra HD 영상 기록
USB 스피커
실질적으로 음악소리를 듣기 위해서 필요한 장치이다. 일반 PC 용 스피커를 사용하면
무방하다. 물론 자체 전원을 사용하는 스피커를 이용할 수 있지만, USB 스피커를 이용하면,
라즈베리파이의 4 개의 USB 포트 중 하나를 전원 공급 용도로 활용하여 꽂아야 할 플러그를
줄여 구성을 간소화 할 수 있다.
DHT11 (온습도 센서)
온습도 상황을 모니터링할 수 있는 쉽게 구할 수 있는 센서이다. 이 센서는 DHT11 과 DHT22
가 존재하는데, DHT22 센서가 값은 비싸지만 좀더 정밀한 온도와 습도 결과값을 보여준다.
실외나 정밀한 측정이 필요한 경우가 아니라면 DHT11 센서로도 충분한 측정 범위 값을 제공해
준다.
모델명 용도 온도 측정 범위 습도 측정 범위
DHT-11 실내용 0 ~ 50 도 20 ~ 80 %
DHT-22 실외용 -40 ~ 80 도 0 ~ 100 %
SR-04 (초음파 센서)
초음파가 발생되고 반사되는 시간을 계산하여 물체와 떨어져 있는 거리를 측정하는데
사용하는 센서이다.
7 Segment 4 Digit LCD
4 개의 숫자를 표시해 줄 수 있는 LCD 이다. 한 숫자를 7 개의 막대로 표현하여 7 Segment
라고 불리운다. 원칙적으로는 각각의 막대를 제어해야 하므로 많은 선이 필요로 하나, SPI
인터페이스를 위해 MAX7219 칩셋이 장착된 제품의 경우 5 개의 선으로 제어할 수 있다. 필히
SPI 인터페이스를 지원하는 제품을 구매하도록 하자.
점퍼 케이블 (Male-Male, Female-Male)
라즈베리파이와 브레드 보드, 센서와 부속을 연결할 때 사용되는 케이블이다. 브레드 보드에
꽂을 수 있도록 핀이 나온 것을 수컷(Male) 단자라고 하고, 핀이 있는 것을 연결할 수 있는 점퍼
케이블은 암컷(Female) 단자라고 한다. 라즈베리파이와 보드 연결, 부속 연결을 위해서는 암컷-
수컷(Female-Male) 단자와 수컷-수컷 (Male-Male) 단자가 필요하다.
선택 사항
DVI-to-HDMI 혹은 HDMI-to-HDMI (Option)
여러분의 라즈베리파이에 직접 모니터를 연결하여 사용하고자 할 때 필요한 케이블이다
라즈베리파이의 영상 출력단자는 기본적으로 HDMI 만 제공한다. 따라서 여러분이 가지고 있는
모니터의 단자가 DVI 를 지원하는지, HDMI 를 지원하는지에 따라서 필요한 케이블을 준비한다
마우스/키보드 세트(Option)
이 역시 라즈베리파이에 직접 연결하여 사용하고자 할 때 준비가 되어 있어야 한다. 윈도우와
같은 GUI 환경을 이용한다면 마우스가 필수적이지만, 일반 텍스트 Console 환경을 이용한다면,
키보드만으로도 충분하다. 우리가 실제 개발할 오디오는 원격으로 접속하여 사용하므로, 꼭
필요하지는 않다
구입처
IoT 오디오 프로젝트를 진행하기 위한 부품을 구입할 수 있는 방법은 다양하다. 사후 지원을
고려하고, 빠른 배송을 통해서 즉시 만들어서 사용을 해 보고자 한다면, 국내 판매 사로부터
구입할 수 있다. 하지만 배송 지연에 대해 느긋하고 사후 지원을 크게 고려하고 있지 않는
상황이라면 해외 직접 구매를 통하여 구입비를 대폭 낮출 수 있다. (다만 해외 배송은 경우에
따라서 1 달 넘게 소요될 수도 있다)
구입할 물품의 명을 정확하게 안다면, 국내는 네이버 쇼핑(http://shopping.naver.com), 해외는
알리 익스프레스(http://www.aliexpress.com)를 통해 검색해 보자. 일반 온라인 판매처 보다 더
저렴하게 구입할 수 있을 것이다. 정확한 명칭을 모른다면 전문 몰 사이트에 접속하여 다양한
부품들을 확인해 본 후 최저가를 산정하여 구입할 수 있다.
국내 온라인 구입처
디바이스 마트
http://www.devicemart.co.kr/
IC 뱅크
http://www.icbanq.com
엘레파츠
http://eleparts.co.kr/
해외 온라인 구입처
엘레먼츠 14
http://www.element14.com
RS 컴포넌트
http://www.rs-components.com
알리 익스프레스
http://www.aliexpress.com
연락 및 추가 정보 얻기
Making 및 프로그래밍 전파를 목적으로 Circulus 라는 팀을 결성하여 활동하고 있다. 이 책에서
나오는 내용에 대한 의문 점과 기타 Making 활동에 대한 문의 사항이 있다면, 다음의 페이스
북 그룹에 글을 남겨 주면 신속하게 답변을 줄 수 있도록 하겠다. 또한 Making 이나
프로그래밍과 관련된 기사들을 Facebook 페이지를 통해 공유하고 있다. 함께 관련 내용에 대해
의견을 나누고 토론할 수 있다.
Circulus 그룹
http://group.circul.us
Circulus 페이지
http://page.circul.us
이 책에서는 IoT 오디오를 만들기 위한 필수적인 내용을 위주로 다루고 있다. 충분하게 다루지
못한 내용들은 공유 슬라이드를 통해 다루고 있다. 책과 함께로 추가적으로 관련 자료를
학습하고 싶다면, 다음 내용을 참고할 수 있다.
Lesson 1 - Introduction : IoT 개요, Opensource H/W, 라즈베리파이 기초
http://www.slideshare.net/rippertnt/iot-make-with-open-hw-nodejs-1st
Lesson 2 - Linux : Raspberry Pi 에서 리눅스 활용하기
http://www.slideshare.net/rippertnt/iot-make-with-open-hw-nodejs-2nd
Lesson 3 - Node.JS : Raspberry Pi 에서 Node.JS 로 프로그래밍 하기
http://www.slideshare.net/rippertnt/iot-make-with-open-hw-nodejs-3rd
Lesson 4 - Sensor : GPIO 를 Node.JS 로 동작시켜 센서 제어하기
http://www.slideshare.net/rippertnt/iot-make-with-open-hw-nodejs-4th
Lesson 5 - Project : Raspberry Pi 로 스마트폰 + 무선 IoT 오디오 제작
http://www.slideshare.net/rippertnt/iot-make-with-open-hw-nodejs-5th
또한, 여기서 다루는 프로그래밍 및 간단한 실습을 무료 Cloud Platform 인 Circulus 를 통하여
진행해 볼 수 있다. 실습한 코드 유실 위험이 없으며, 나의 작업 내용을 친구에게 공유하거나
타인의 코드를 복사하여 참고할 수 있다. 별첨에서 보다 상세하게 사용 방법에 대해 다루도록
하겠다.
Circulus
http://www.circul.us
1. 파이를 동작시켜 보자 – 초기 설정
라즈베리파이 운영체제 설치하기
라즈베리파이에서 선택할 수 있는 운영체제는 대양하다. 우리가 친숙한 윈도우 10 부터, 우분투
리눅스까지 10 여가지의 다양한 운영체제를 지원한다. (일부는 이러한 운영체제로 PC 대체로
사용하려고 하는 분이 있는데, 결코 PC 와 같은 환경을 기대해서는 안된다. 라즈베리파이가
강력하지만 100 만원짜리 PC 성능을 기대해서는 안된다.) 우리가 파이오를 만들기 위해 사용할
운영체제는 데비안 리눅스 기반으로 라즈베리파이에 최적화된 운영체제인 라즈비안(Raspbian)
을 선택할 것이다.
라즈비안 OS 는 라즈베리파이 초창기부터 현재까지 가장 많이 활용되고 있다, 이는 문제가
생겼을 때 남이 이미 겪었을 확률이 높아 해댕 정보를 검색하여 해결하기 용이하다는 것을
의미한다.
라즈비안은 라즈베리파이 공식 홈페이지에서 손쉽게 다운로드 받을 수 있다. 다음의 경로에
접속하여 라즈비안을 다운로드 받도록 한다.
https://downloads.raspberrypi.org/raspbian_latest
[그림] 컴퓨터로 쓸때는 일반 배포판, 메이커 키트로 이용할 때는 Lite 배포판을 이용하자
info) 현재 라즈비안은 구 버전(wheezy) 와 신 버전(Jessie) 으로 제공이 되고, 신 버전도 일반
버전과 라이트 버전으로 제공되고 있다. 메이커를 위해서는 신 버전 라이트를 이용하면
불필요한 공간 낭비 없이 작업용으로 활용할 수 있다. 만일 윈도우와 같은 GUI 환경이
필요하다면, 일반 버전을 사용하도록 한다.
라즈베리파이 OS 의 이미지 파일을 다운로드 받은 이후, 라즈베리파이에서 사용하기 위해서는
이른바 "굽기"가 필요하다. 윈도우의 경우 Win32Diskimager 라는 프로그램을 이용하여 손쉽게
운영체제 이미지를 마이크로 SD 카드에 담을 수 있으나, 맥 OS 나 리눅스의 경우 터미널
환경에서 직접 명령어를 입력하여 이미지를 담아내야 한다. 다음과 같은 절차로 운영체제
이미지를 복사하자!
One more thing - 라즈베리파이를 게임기로 만들기
라즈베리파이를 교육 혹은 IoT 제작 실습용으로 많이 사용하나, 미디어 플레이어 혹은
게임기로 만들어 사용하는 경우도 많다. 만일 게임기로 만들어 사용하고자 한다만,
에뮬레이터 게임에 최적화된 Recalbox 를 사용하는 것이 좋다. 게임에 맞게 최적화 되고,
조이스틱 연결을 지원할 뿐만 아니라, 닌텐도, 플레이스테이션, 네오지오 등 현존하는 다양한
에뮬레이터 게임을 지원한다. 자세한 내용은 www.recalbox.com 을 참고하도록 한다.
1.SD 카드 어댑터에 MicroSD 카드를 삽입한 후 맥의 카드리더에 삽입하자.
Window
윈도우 에서는 간단하게 Win32Imager 를 이용할 수 있다. 다음의 주소에서 다운로드 받아서
설치하도록 한다.
http://sourceforge.net/projects/win32diskimager/files/latest/download
다운로드 받은 최신의 라즈비안을 선택한 후 Write 버튼을 누르면, 마이크로 SD 카드에 기록이
된다. 완료 후에 라즈베리파이에 삽입하여 부팅이 정상적으로 이루어지는지 확인해 보자.
Mac
2. Finder 에서 인식된 SD 카드 드라이브에서 마우스 우측으로 클릭하면 diskn 으로 (예시
disk4) 설정이 된걸 알수 있다. 이 정보를 기록해 두자.
3.터미널을 열어 다음 명령어를 입력하자
sudo dd bs=1m if=path_of_your_image.img of=/dev/rdiskn
명령어 실행에 실패한다면 rdisk 대신 disk 를 입력하도록 한다.
sudo dd bs=1m if=path_of_your_image.img of=/dev/diskn
Linux
- 마이크로 SD 카드 삽입 후 다음 명령을 실해앟여 인식된 마이크로 SD 카드를 확인한다.
$ df -h
- 명령어 리스트 상에 인식된 내역을 확인한다 보통 ./devmmcblk0p1 이나 /dev/sdd1 의
형태로 인식된 것을 알수 있다. 인식된 sd 카드의 기록을 위해 mount 를 해제하는 다음
명령어를 실행한다.
$ umount /dev/sdd1
- 기록을 수행하기 위하여 다음 명령어를 실행해 준다.
$ dd bs=4M if=~/2012-12-16-...img of=/dev/sdd
첫 라즈베리파이 사용을 위한 기본 조립
운영체제를 준비했으니, 이제 라즈베리파이를 구동할 기본 준비가 되었다. 하지만
라즈베리파이 단독으로는 초기에 아무것도 할수가 없다. 마이크로 SD 카드를 라즈베리파이
후면 슬롯에 딸깍 소리가 나도록 삽입하고, 무선 USB 랜을 라즈베리파이의 USB 랜 포트에
설치하도록 한다.
[그림] 구형 라즈베리파이 B (좌측하단) 는 SD 카드 형식으로, 라즈베리파이 B+ 이나 2B(좌측
상단) 은 Micro SD 형식으로 딸깍 소리가 날때가지 눌러 장착을 하나, 3B (우측)은 끝 부분까지
Micro SD 카드를 밀어 넣으면 된다.
라즈베리파이을 통해 개발하는 방법은 크게 2 가지로, 직접 라즈베리파이에 모니터, 키보드,
마우스를 직접 연결하여 개발하는 방법과 원격 구성으로 접속하여 개발하는 방법이 있다.
필자는 연결을 최소화 한 후자의 방법을 주로 선택하지만, 부팅시에 문제가 발생하거나 원격
연결이 어려운 경우, 그리고 GUI 환경으로 라즈베리파이를 활용하고자 할때는 직접 연결하여
사용해야 한다. 처음 라즈베리파이를 접하는 경우 GUI 를 통해 무선 설정을 하는 것이
용이하므로, 여기서는 초보자와 고급사용자를 위한 두가지 방법 모두를 다룰 것이다.
GUI 를 통한 원격 연결 설정
처음 라즈베리파이를 사용하는 사용자를 위해 권장하는 방법으로, 마치 노트북 컴퓨터나
스마트 폰을 인터넷 연결하는 것과 유사한 방법이다. 라즈비안 일반 버전으로 이미지를 만들어
라즈베리파이를 부팅하면 다음과 같이 부팅된 화면을 발견할 수 있다.
[그림] 라즈베리파이 부팅후 화면, 상단 우측 무선인터넷 항목을 클릭하면, 접속가능한 AP
목록을 발견할 수 있다.
부팅한 화면 우측 상단을 보면, 안테나 모양으로 PC 운영체제나 스마트 폰에서 보는
무선인터넷 연결 아이콘을 발견할 수 있는데, 해당 아이콘을 클릭하면 접속가능한 무선 AP
목록을 확인할 수 있다. 접속할 수 있는 무선 AP 를 선택한 후, 필요하다면 비밀번호를 입력하여
인터넷 연결을 수행하자. 연결이 완료되면 터미널 창을 열어, ifconfig 명령으로 무선인터넷에
연결된 IP 정보를 확인한다. 다음과 같이 정상적으로 IP 목록이 표시된다면, 앞으로는
원격접속을 통해 라즈베리파이를 제어하고 개발할 수 있다.
[그림] 접속하고자 하는 AP 를 선택한 후, 필요하다면 비밀번호를 입력하자.
콘솔을 통한 원격 연결 설정
보다 어려운 접근 방법이지만, 라즈비안 Lite 버전을 설치한 경우 다음과 같은 방법으로 인터넷
연결 설정을 진행할 수 있다. 무선 인터넷 공유기와 라즈베리파이의 유선 랜을 서로 연결해
주도록 한다. 이는 PC 에서 원격으로 접속하여 개발할 때 사용하기 위함이다. 원격으로
접속하기 위해서는 라즈베리파이의 IP 를 확인해야 하는데, 접속된 공유기를 통해서 IP 를
알아낼 수 있다. 현재 내가 접속한 컴퓨터의 IP 를 알아내기 위해 ip 를 확인해 보자
윈도우 - ipconfig
리눅스 - ifconfig
맥 OS - ifconfig
내 IP 가 192.168.1.140 으로 되어있다면, 공유기의 IP 는 192.168.1.1. 이 된다. 차후 공유기
설정을 위하여 해당 IP 를 기록해 둔다.
[그림] 라즈베리파이에 키보드와 모니터를 연결하여 부팅하자.
라즈베리파이에 키보드와 모니터를 연결하고, 운영체제를 설치한 MicroSD 카드가
장착되었는지 확인한 후, Micro USB 를 통해 전원을 공급해 보자. 라즈베리파이가 부팅되는
것을 확인할 수 있다. 부팅이 완료된 후, 로그인 ID 는 pi, 비밀번호는 raspberry 를 입력하여
로그인을 수행한다. 이제 IP 를 알아내서 무선환경에서 라즈베리파이를 작업할 수 있도록
수정해 볼 것이다. 콘솔창에 다음과 같은 명령을 입력하여 라즈베리파이의 IP 를 확인해 본다
$ ifconfig
[그림] Eth0 에 나오는 inet addr 이 라즈베리파이의 유선 IP 주소이다.
Ifconfig 명령 실행후 eth0 의 inet addr 값이 유선랜 IP 이다. 라즈베리파이에서 직접 작업해도
되지만, IP 를 알아냈으니 원격으로 설정 작업을 수행해 보자. 원격접속을 위해서는 터미널
프로그램이 필요하다. 리눅스나 맥 OS 의 경우 기본적으로 터미널 명령이 지원되나, 윈도우 XP
이후에 나온 버전 부터는 터미널이 지원되지 않아서, 터미널을 사용할 수 있도록 도와주는
프로그램을 별도로 설치해야 한다. 윈도우에서 많이 활용되는 무료 터미널 프로그램인 putty 를
이용하여 접속해 보도록 한다.
putty 다운로드 - http://the.earth.li/~sgtatham/putty/latest/x86/putty.exe
[그림] 라즈베리파이의 IP 를 입력한 후 Open 을 클릭하여 라즈베리파이의 터미널에 접속한다.
다운로드 받은 putty 를 실행하면 다음과 같은 입력창을 확인할 수 있다. 확인했던 IP 를
입력하고, Open 을 클릭하여 원격으로 라즈베리파이에 접속해 본다. 기본 사용자 계정은 pi,
비밀번호는 raspberry 를 입력하면 다음과 같은 로그인 성공 메세지를 확인할 수 있다.
라즈베리파이 부팅 성공을 축하한다. 이제 본격적으로 설정 및 개발을 진행해 보자.
[그림] 부팅 후 원격 접속시 로그인 ID 는 pi, 비밀번호는 raspberry 를 입력해 주면 로그인 된다.
One More Thing – 복잡한 OS 설정을 한번에 CirculOS
라즈베리파이를 무선으로 동작시키고 실제 개발을 하기 위해서는 앞으로도 다양한 설정을
해야 한다. 이러한 설정이 복잡하게 느껴진다면, 필자가 활동하는 커뮤니티인 Circulus 에서
제공하는 CirculOS 배포판을 이용하면, 복잡한 설정 필요 없이 곧바로 사용할 수 있도록
도와준다. CirculOS 를 사용하는 방법은 10 장에서 자세히 다룬다.
라즈베리파이 기본 설정하기
라즈베리파이에 운영체제를 설치한 후 그대로 사용하면 몇 가지 문제가 발생한다. 우선 SD
카드 전체 용량을 활용하지 못하며,, 우리가 만들 오디오 시간이 영국 기준 시간이 아닌
우리나라 시간에 맞추어 동작하게 된다. 몇 가지 기본 설정을 변경해 주어야 한다.
$ sudo raspi-config
첫 번째 해야 할 일은 SD 카드 전체를 사용할 수 있게 해 주는 일이다. 1. Expand Filesystem 을
선택한 후 Enter 를 누르면, Root partition has been resized 라는 문구를 볼 수 있다. 재 부팅
후 정장적으로 반영되지만 몇 가지 작업을 위하여 다음 설정 화면으로 넘어가도록 한다.
두 번째 할 일은 기본 국가인 영국에서 한국으로 바꾸고, Timezone 을 서울로 바꾸는 일이다.
5. Internationalization Options 을 선택한 후 Enter 를 누른다. I1. Change Locale 에서 ko-
Kr.UTF-8 로 한국 위치로 설정한다. 설정이 완료되면, I2. Change Timezone 에서 시간 대역을
Seoul 로 변경 하도록 한다.
마지막으로 할 일은 LCD 통신을 위한 SPI 를 Enable 로 설정한다. 9. Advanced Option 을
선택한 후, A6. SPI 항목을 선택한다. SPI 를 설정하겠냐는 응답에 Yes 를 선택한 후 Enter 를
누른다. 그러면, Would you like SPI kernel module to be loaded by default? 라는 메시지가
뜨게 되는데, Yes 를 선택한 후 Enter 를 누른다. 모든 설정을 마친 후, Finish 를 선택하고 Enter
를 누르면 Would you like to reboot now? 라는 메시지가 출력되는데, Yes 를 선택하여 재
부팅 함으로서, 설정한 작업이 반영되게 된다.
네트워크 설정하기
IoT 환경의 라즈베리파이를 사용하기 위하여 네트워크 설정이 필요하다. 기본적으로는 유선,
무선 모두 DHCP 연결을 통한 접속을 설정하여, 이후 별다른 설정 없이 인터넷 환경이 가능하게
할수 있다. 하지만, 우리가 현재 개발하고 있는 상황에서는 DHCP 를 이용하면 IP 가 수시로
바뀌어 원격접속시에 매번 IP 를 확인해야 하는 불편함이 있다. 개발 완료 이후에는 DHCP 를
이용할 수 있지만, 개발 시점에는 고정 IP 를 사용하도록 한다.
WIFI 설정
라즈베리파이에서 어떤 WIFI 를 사용할 수 있는지 스캐닝 하기 위해 다음 명령어를 사용하자
$ sudo iwlist wlan0 scan
iwlist 명령으로 접속가능 한 무선랜 목록을 확인할 수 있다.
명령어를 입력하면, 접속할 수 있는 AP 목록이 보여지게 된다. AP 명은 ESSID 인데, 전파
강도가 강한 순서대로 목록이 제공된다. 접속할 목록을 확인한 후 설정을 진행하도록 한다.
$ sudo nano /etc/wpa_supplicant/wpa_supplicant.conf
One more thing – Nano 에디터를 이용한 설정 편집
Nano 에디터는 리눅스에서 간단히 파일을 편집하는데 유용하다. 수정 후 CTRL-X 를
입력하고 Y 와 Enter 키를 누르면 저장이 완료된다. 보다 자세한 내용은 2 장에서 다룬다.
파일의 하단에 다음과 같이 입력하자
network = {
ssid = "접속할 ESSID 명"
psk="WIFI 패스워드"
}
CTRL+X 를 입력한 후 Y 입력하고 Enter 를 누르면 저장이 된다. 라즈베리파이를 재 부팅하면
자동으로 적용되지만, 재부팅 필요없이 연결을 확인하기 위해 다음 명령을 입력하자
$ sudo ifdown wlan0
$ sudo ifup wlan0
재 구동후 무선랜이 제대로 동작하는지 다음 명령어를 이용하여 확인한다. 아래 화면과 같이
IP 가 제대로 잡혀서 나온다면 정상적으로 동작하는 것이다.
$ ifconfig wlan0
wlan0 항목에 ip address 가 제대로 할당되어 있다면 정상적으로 접속된 것이다.
무선 인터넷 끊기지 않게 설정하기
라즈베리파이를 유선으로 연결하여 사용해도 되지만, IoT 컨셉의 제품이나 이동성이 좋은
시스템을 개발하기 위해 WiFi 를 이용하는것이 바람직 하다. 라즈베리파이 3 를 구매하였다면,
WIFI 가 내장되어 있으므로 그대로 사용하면 된다. 단 기본 설정으로 절전 모드가 활성화 되어,
실제 사용시 원격 접속이 잘 안되거나 인터넷 연결이 수시로 비활성화와 활성화가 반복되어
개발시에 문제가 발생할 수 있다. 우선 iwconfig 로 절전 모드 상태를 확인하도록 한다.
$ iwconfig
[그림] Power Management 가 on 으로 되어있으면, 절전모드가 활성화 되어 있는 것이다.
Power Management 가 on 으로 보여진다면, 절전 모드가 설정 된 것이다. 이를 비활성화 하기
위해서는 다음의 명령을 사용하도록 한다.
$ iwconfig wlan0 power off
[그림] power off 옵션을 사용하면, Power Management 설정이 off 가 된 것을 확인할 수 있다.
다시한번 iwconfig 로 확인해 보면, 절전 모드가 off 로 된 것을 확인할 수 있다. 이제는 원격
접속이나 인터넷 활용 서비스를 개발할 때에 안정적인 무선 인터넷 환경을 사용할 수 있게
되었다. 그러나 라즈베리파이를 재 부팅하면 다시 원래대로 되 돌아 가게 된다. 이를 방지하기
위해서는 상시적으로 무선 인터넷 설정에서 절전 모드를 해제해야 할 필요가 있다. 부팅 시에
구동 되는 무선 인터넷 관련 파일을 수정하여, 별도의 절전 모드 해제 명령 없이 자동으로 절전
모드를 해제할 수 있다.
$ iw dev wlan0 set power_save off
만일 라즈베리파이 2B 이하의 WiFi 가 기본으로 장착되지 않은 기종을 구매하였다면, 추천되는
무선 USB 어댑터는 Realtek 사의 8188cu 를 이용한 무선 랜 카드이다. 시중에서 8188cu
기반으로 만들어진 구매할 수 있는 랜 카드는 다음과 같다. (제조사 사정에 따라 칩셋이
변동되는 사례가 있었다. 구매전에 다시한번 확인하길 바란다)
EFM 네트웍스 – ipTIME M100mini
Netis – Netis WF2120
유니콘정보시스템 – 유니콘 BW-150MINI
One More Thing – 무선랜 카드를 8188cu 칩셋이 장착된 랜카드를 구입해야 하는 이유
아무 무선랜을 선택하면 안 되는 이유는 8188eu 의 경우 wheezy 버전의 라즈비안에서
문제가 발생하는 경우가 많았고,새 배포판인 jessie 에서는 드라이버 지원이 추가 되었다고
하지만, 필자가 직접 8188eu 기반의 무선랜을 사용했을 때 인터넷이 제대로 되지 않는
문제가 발견되었다. 따라서, 가급적이면 8188cu 기반의 무선 랜 카드를 구매할 것을
권장한다. 정상 적으로 무선랜 카드를 사용한다고 하더라도, WiFi 를 이용하여
라즈베리파이를 이용하다 보면, 인터넷 문제와 무관하게 종종 끊기는 현상이 발생한다.
대부분의 라즈베리파이 관련 책들은 연결하는 방법만 다루지 실제 이런 문제를 다루고 있지
않은데, 이러한 문제로 구현 및 구동에 있어서 큰 어려움을 겪게 되는 경우가 많다. 이는
라즈베리파이의 WiFi 드라이버에서 절전 모드 및 대기 모드로 동작되기 떄문에 발생하는
문제로, 이 기능을 disable 하여 WiFi 로 인하여 발생하는 문제를 최소화 할 수 있다.
8188cu 의 경우 상위 칩셋인 8192cu 와 호환이 되어 부팅시 8192cu 로 잡히게 된다. 절전
설정값이 어떻게 되어있는지 확인해 보는 명령어는 다음과 같다.
$ cat /sys/module/8192cu/parameters/rtw_power_mgnt
$ cat /sys/module/8188eu/parameters/rtw_power_mgnt
1 로 값이 설정되어 있다면, 절전 모드가 활성화 되어 있는 것이다. 이를 비활성화 하기
위해서는 다음의 설정 파일을 수정하도록 한다.
$ sudo nano /etc/modprobe.d/8192cu.conf
One More Thing – 8188 이 아닌 8192 라는 이름으로 설정 파일을 만드는 이유
랜카드 사용시 8188cu 계열의 랜카드를 사용하더라도 8192cu.conf 파일을 만들어야 한다.
8188cu 칩셋과 8192cu 호환이 되어, 라즈비안 상에서 8188cu 랜 카드의 경우도 8192cu 로
인식하기 때문이다. 8188eu 의 경우 인식은 되지만 정상적으로 동작이 안 되는 경우가
발생한다. 따라서 구입시 가급적 8188cu 칩셋이 탑재된 무선 랜 카드를 구입하도록 한다.
다음의 줄을 추가하고, CTRL+X 와 Y 를 누른후 Enter 를 누르면 저장이 된다.
options 8192cu rtw_power_mgnt=0 rtw_enusbss=0
다음의 세부 설정 옵션은 다음과 같다.
rtw_power_mgnt
0 - 절전 모드를 비 활성화
1 - 최소로 절전 기능을 수행함
2 - 최대로 절전 기능을 수행함
rtw_enusbss
0 - 대기 모드를 비 활성화
1 - 대기 모드를 활성화 함
Rtw_ips_mode
0 – 낮은 전원
1 – 높은 전원
무선 연결 설정이 완료되었다면, iwconfig 명령으로 정상적으로 무선랜이 활성화 되었는지
확인해 볼 수 있다. 절전 설정을 off 하지 않으면, 인터넷 연결이 수시로 끊길 수 있어 IoT
제품으로 만든 경우 정상적으로 동작하기 어려우며, 개발 시에 putty 를 통한 원격접속도
수시로 끊어지는 문제가 발생한다. 이제 유선으로 연결한 인터넷 선을 뽑고, 무선 환경으로
다시금 putty 로 접속하여 문제가 없는지 확인해 보자. 정상적으로 접속이 된다면, 이제
유선으로 연결할 필요 없이 개발을 진행하면 된다.
라즈베리파이와 파일 주고 받기 - FTP 설정하기
라즈베리파이를 사용할 때 내 컴퓨터에 있는 소스 코드나 파일들을 보내고 싶을 때도 있고
반대로 파일들을 받고자 할 때가 있을 것이다. 파일 공유를 가능하게 해 주는 삼바 서버를
라즈베리파이에 구축할 수 있으나, 우리는 가장 간단한 방법을 활용할 것이다. 별도의 설치
필요 없이 바로 활용 가능한 FTP(File Transfer Protocol) 을 활용하는 것이다.
파일 전송에 사용되는 FTP 프로그램 중 가장 인기 있는 프로그램은 FileZilla 이다. 한국어도
지원하므로, 설치 시에 한국어를 선택할 수 있다. 다음의 경로에서 다운로드 받아 설치하도록
한다.
다운로드 > https://filezilla-project.org/download.php?type=client
FTP 접속을 할 때 호스트(Host) 에 라즈베리파이의 IP 주소를 입력하고, 사용자 명(U) 에 pi 를,
비밀번호(W) 는 raspberry (변경하였다면, 변경한 비밀번호를 입력해야 한다) 를 입력한다.
일반적인 FTP 접속에 사용되는 포트(P) 는 21 이나, 라즈베리파이와 사용되는 방식은
SFTP(Secure FTP)로 22 번 포트를 사용한다. 포트(P)에 22 를 입력하고 빠른 연결 버튼을
누르면, 정상적으로 FTP 연결이 성공한 것을 볼 수 있다. 왼쪽은 사용자가 사용하는 컴퓨터의
디렉토리 구조이고 오른쪽이 라즈베리파이의 디렉토리 구조이다. 복사 하거나 받고자 하는
파일을 드래그 앤 드랍으로 편리하게 파일을 주고받을 수 있다.
[그림] FileZilla 를 활용하면, 파일을 드래드 앤 드랍으로 주고 받을 수 있다.
One More Thing – 파일 복사 경로
라즈베리파이에 파일을 복사할 때 /home/pi 디렉토리나 해당 디렉토리의 하위 디렉토리를
생성하여 파일을 전송하도록 한다. 타 디렉토리에는 보안상의 이유로 파일을 전송할 수가
없다. 파일 전송을 시도하고자 하면 permission denied 라는 메시지와 함께 전송이 실패하는
것을 알 수 있다.
이번 장을 정리하며
첫 장에서 우리는 라즈베리파이를 사용할 수 있도록 OS 를 설치하고, 기본 환경 설정을 마무리
하였다. 무선 개발 및 IoT 서비스를 위해 무선 랜 환경을 구축하고 원격 접속 및 데이터를 주고
받을 수 있는 구성을 구축하였다. 라즈베리파이의 장점은 아두이노와 달리 리눅스 OS 가
탑재되어, 리눅스 생태계의 다양한 오픈소스를 그대로 활용하고 다양한 서비스를 구축할 수
있다는 점이다. 다음장에서는 라즈베리파이를 보다 깊이 있게 사용할 수 있도록
라즈베리파이의 리눅스에 대해 살펴보도록 하겠다.
2. 파이의 운영체제 - LINUX 속성 실습
리눅스란 무엇인가?
Linux 는 일종의 운영체제 이다. 우리가 사용하는 컴퓨터나 스마트폰을 자유자재로 원활하게
사용할 수 있는 것은 우리와 컴퓨터 사이에 Window, Android, iOS 같은 운영체제가 있기
때문이다. 이러한 OS 덕분에 다양한 어플리케이션을 설치하여 다양한 용도로 활용할 수 있다.
리눅스는 핀란드 헬싱키대학의 대학원 생인 리누스 토발스가 취미 삼아 개발하였다. 개발하게
된 유래를 알게되면 재미난 점을 발견할 수 있다. 토발스는 원래 교육용 유닉스인 미닉스를
이용하고 있었는데, 미닉스를 함부로 개조하지 못하게 제한을 두자, 미닉스의 기능에 만족하지
못한 토발스는 직접 운영체제를 만든 것이다. 역사를 보면 무언가 비싸거나 어려운 것이
존재하면, 그 까짓거 내가 만든다는 생각의 위인들이 나타나 저렴한 가격에 쉽게 접할 수
있도록 나오는 현상을 많이 발견하게 된다. 오픈소스나 오픈소스 하드웨어가 그러하듯이
리눅스 역시도 그러한 제약을 벗어나고자 태어나게 된 것이다.
[그림] 리눅스의 로고가 펭귄이 된 까닭은 원 저자가 오스트레일리아를 여행중 펭귄에 물린적이
있다고 한다. 펭귄에 물리면 펭귄 중독에 걸려 펭귄을 사랑한다는 속설이 있다.
왜 리눅스 인가?
아두이노와 달리 라즈베리파이는 운영체제가 지원된다. 이를 통해 모터가 돌아가고 LED 가
반짝하는 기능 뿐만 아니라 대화를 나누고, 뉴스와 날씨를 구독 받는등의 다양한 아이디어를
실제로 구현할 수 있는 것이다. 이러한 기능을 위해 사용하는 것이 바로 라즈베리파이에
올라가는 Linux 이다. 이미 Linux 생태계에서는 하드웨어 제어를 위한 오픈소스 부터 데이터
처리 및 분석에 대한 방대한 오픈 소스를 제공하고 있다. 일반 PC 에서 사용되는 Linux 가
그대로 라즈베리파이에 이식되어 있기 때문에, 라즈베리파이를 위해 특정 업체에 종속된
프로그래밍 언어나 개발 방식을 사용하지 않고, 기존 PC 환경에서 사용했던 내용들을 그대로
이용할 수 있는 장점이 있다. 다양한 리눅스가 존재하지만 우리는 Debian 계열의 라즈비안
리눅스를 사용하므로, Debian 배포판의 리눅스 사용법을 살펴보도록 할 것이다.
[그림] 가장 많이 사랑받는 3 대 리눅스. Ubuntu 역시 debian 계열이다. 라즈리안 역시 debian
계열의 리눅스로 명령어가 가장 많이 사용되는 Ubuntu 리눅스와 유사하다.
Linux 를 사용하기 위한 명령어
리눅스를 사용하는 방법은 크게 2 가지가 있다. 우리가 윈도우를 활용하는 것과 같은 그래픽
유저 인터페이스(GUI)를 이용하는 방법과, 영화에서 보아 왔던 명령어를 입력하는 방식이다.
윈도우 환경을 이용하는 것은 일반적인 PC 사용 환경을 다루는 것이므로 이 책에서 다루고자
하는 범위를 벗어난다. 여기에서는 라즈베리파이를 위해 오디오를 만들기 위하여 필요한
명령어들을 다루고자 한다. 리눅스의 양대 배포판인 데비안(Debian) 과 페도라(Fedora) 에 따라
일부 명령어가 약간은 차이가 발생하는데, 가장 인기 있는 리눅스 배포판인 우분투(Ubuntu)
리눅스와 마찬가지로, 라즈비안(Raspbian) 역시 데비안 기반으로 하고 있다. 우리는 데비안
리눅스의 기본 사용법에 대해 다룰 것이다.
명령어 구조
라즈베리파이를 콘솔로 부팅하면 프롬프트(prompt) 가 나타난다. 프롬프트는 사용자의 명령
입력을 기다리는 표시로서, pi 는 사용자 계정의 이름을 말하는 것이고, circulus 는 호스트
이름을 이야기 하는 것이다.
[그림] 라즈비안에 처음 접속한 모습
명령 [옵션] [인자 값...]
명령
리눅스를 사용하기 위해 사용자가 입력하는 명령어 이다.
옵션
명령의 세부 기능을 선택할 수 있다. 옵션은 - 기호로 시작하며 영문 소문자나 대문자로
구성되어 있다. 명령에 따라 어떤 옵션이 있고 그 기능이 무엇인지는 해당 명령어에 따라
차이가 있다 (명령에 따라 옵션이 있을 수도 있고 없을 수 있다)
인자
인자는 명령으로 전달되는 값이며, 주로 파일명이나 디렉터리 명이 사용된다. 각 명령어에 따라
필요한 인자가 다르다 (명령에 따라 인자가 필요할 수도 있고 없을 수 있다)
기본 명령어
clear – 화면 지움
현재의 화면이 깨끗하게 지워지고 커서가 화면 왼쪽 상단에 위치한다
$ clear
date - 현재 날짜와 시간을 출력
현재 시스템의 날짜와 시간을 출력하는 명령어 이다.
$ date
[그림] Locale 설정한 지역에 맞추어 시간이 표시된다.
man - 명령어 사용법 안내
리눅스가 제공하는 각종 명령의 사용법을 보여준다. 명령의 옵션이나 인자값을 확인하고자
할때 유용하다.
$ man clear
sudo - 슈퍼 사용자 권한
일반적으로 사용자에게 생성된 기본 폴더 이외의 공간에서 명령을 수행하는 경우 관리자
권한이 필요하다.(이후 실제로 라즈베리파이에서 실습할 때 sudo 가 명령어 앞에서 쓰는
경우를 많이 발견하게 될 것이다) 관리자 권한으로 실행하기 위해 사용하고자 하는 명령어
앞에 sudo 를 입력 후 명령을 수행할 수 있다.
$ sudo mkdir test
파일 디렉토리 명령
리눅스는 기본적으로 유닉스 계열의 운영체제 이므로 유닉스와 유사한 부분이 많다. 유닉스와
마찬가지로 리눅스도 시스템과 관련된 정보와 하드웨어의 장치를 모두 파일로 관리한다.
파일을 효과적으로 관리하기 위해 디렉터리, 윈도우에서는 폴더와 같은 구조가 사용되는데,
파일들을 용도에 따라서 계층적으로 관리한다.
디렉토리 용도
dev 장치 파일이 담긴 디렉터리
home 사용자 홈 디렉터리가 생성되는 디렉터리. 라즈베리파이 처음 부팅 시, 이
디렉터리의 pi 디렉터리가 기본 디렉터리가 된다.
media CD-ROM 이나 USB 같으 외부 장치를 연결(마운트 라고 표현)하는 디렉터리
opt 추가 패키지가 설치되는 디렉터리
root root 계정의 홈 디렉터리이다. 루트(/) 디렉터리와 다른 것이므로 혼동하지
않도록 한다.
sys 리눅스 커널과 관련된 파일이 있는 디렉터리 이다.
usr 기본 실행 파일과 라이브러리 파일, 헤더 파일등 많은 파일이 있음.
bin 실행파일(명령)을 가지고 있음
boot 부팅에 필요한 커널 파일을 가지고 있음
etc 리눅스 설정을 위한 각종 파일을 가지고 있음
lost+found 파일 시스템에 문제가 발생하여 복구할 경우 문제가 되는 파일이 저장되는
디렉토리. 보통은 비어 있음.
mnt 파일 시스템을 임시로 마운트 하는 디렉터리 임
proc 프로세스 정보 등 커널 관련 정보가 저장되는 디렉터리 임
run 실행중인 서비스와 관련된 파일이 저장 됨
srv FTP 나 Web 등 시스템에서 제공되는 서비스의 데이터가 저장됨
tmp 시스템 사용 중에 발생하는 임시 데이터가 저장됨. 이 디렉토리의 파일은
시스템 재 시작시 모두 삭제 됨
var 시스템 운영중에 발생하는 데이터나 로그가 저장되는 디렉터리
pwd - 현재 위치를 확인
Print Working Directory 의 약자로서, 현재 디렉터리를 확인하는 명령이다
$ pwd
cd - 디렉토리 변경
Change Directory 의 약자로서, 현재 디렉터리에서 다른 디렉터리로 이동할 때 사용하는
명령어 이다.
$ cd [디렉토리 명]
실행 예
ROOT 디렉토리로 이동한다.
$ cd /
HOME 디렉토리로 이동한다.
$ cd ~
HOME/PI 디렉토리로 이동한다.
$ cd /home/pi
[그림] cd 명령을 이용하여 디렉토리를 이동하고, pwd 명령으로 이동 위치를 확인할 수 있다.
ls - 디렉토리 내부 확인
list 의 약자로서, 디렉토리 내부의 파일이나 하위 디렉토리 같은 정보를 보여주는 명령어 이다.
$ ls [옵션]... [파일]...
옵션명 설명
-a 숨김 파일을 포함하여 모든 파일 목록을 표시함
-d 지정된 디렉터리 자체의 정보를 출력함
-F 파일 유형을 나타내는 기호를 파일명 끝에 표시 (디렉토리는 /,
실행파일은 *, 링크는 @가 나타남)
-l 파일에 대한 상세 정보를 표시해 줌
-t 파일이 생성된 시간별로 표시
-C 한줄에 여러개의 정보를 함께 표시(기본설정)
-R 하위 디렉토리 내용까지 표시
실행 예
현재 디렉토리의 정보를 확인한다.
$ ls
파일에 대한 상세 목록을 확인한다.
$ ls -l
숨김 파일을 포함하여 상세 목록을 확인한다.
$ ls -al
[그림] –C 옵션은 기본 적용된다. –l 옵션을 사용하면 보다 자세한 정보를 함께 확인할 수 있다.
find - 파일 찾기
find 명령은 리눅스의 디렉터리 구조에서 특정 파일이 어디에 위치하고 있는지 찾아 주는
명령어 이다.
$ find [경로] [검색 조건]
옵션명 설명
-name [파일이름] 파일 이름으로 검색함
실행 예
interfaces 라는 이름의 파일을 전체 검색한다.
$ cd /
$ sudo find –name interfaces
[그림] Root (/) 디렉토리에서 검색을 하면, 모든 디렉토리를 전체 검색한다.
cp/mv - 파일 복사 및 이동
Copy, Move 의 약자로서 특정 파일들을 이동시키거나 복사할 때 사용하는 명령어이다.
$ mv [원본 파일 경로][대상 파일 경로]
$ cp [원본 파일 경로][대상 파일 경로]
실행 예
/etc/network 디렉토리에 있는 interfaces 파일을 interfaces_bak 파일로 복사한다.
$ sudo cp /etc/network/interfaces /etc/network/interfaces_bak
/etc/network 디렉토리에 있는 interfaces_bak 파일을 interfaces 파일로 이동한다.
$ sudo cp /etc/network/interfaces_bak /etc/network/interfaces
[그림] cp 명령의 경우, 파일을 복사하지만, mv 를 사용하면 기존 파일이 사라진다.
mkdir - 디렉토리 만들기
make directory 의 약자로써 새로운 디렉토리를 생성할때 사용 한다.
$ mkdir [생성할 폴더명 또는 경로]
실행 예
현재 디렉토리에 test 라는 폴더를 생성한다.
$ sudo mkdir test
rm - 파일/디렉토리 삭제하기
remove 의 약자로써 기본 명령은 파일을 삭제할때 사용하지만 -r 옵션을 붙여 파일이 있는
디엑토리를 지울때도 사용할 수 있다.
$ rm [옵션] [삭제할 경로]
옵션명 설명
-r 디렉터리를 삭제한다
-i Interactive 의 약자로, 삭제할 때 매번 삭제 여부를 사용자에게
묻는다.
-f Force 의 약자로, 어떠한 확인 메시지도 묻지 않고 삭제를 수행한다.
-v Verbose 의 약자로, 삭제하는 동안 정보를 상세하게 보여준다.
실행 예
현재 디렉토리에 있는 test.txt 파일을 삭제한다.
$ sudo rm test.txt
현재 디렉토리에 test 라는 폴더를 삭제한다.
$ sudo rm –r test
현재 디렉토리에 test 라는 폴더와 폴더내 파일도 함께 삭제한다.
$ sudo rm –rf test
이정도 알면 리눅스 관리 어렵지 않아요!
시스템에 설치되어 있는 디스크와 메모리의 사용량을 확인할 필요가 있다. 우리가 최종적으로
만드는 라즈베리파이 오디오는 음악파일을 다운로드 받아서 재생하게 되는 구조이다.
라즈베리파이의 저장소 용량이 한정적이기 때문에 언제 꽉 차서 음악파일의 저장이 안될지
모르는 일이다. 또한 전원 방전이나 플러그를 뽑는 등 전원의 불안정, 소프트웨어 오류,
하드웨어 오동작 등으로 손상될 수 있다. 이렇게 손상된 파일 시스템 또한 점검하고 복구해야
할 필요가 있다. 여기서는 다양한 관리 명령을 살펴보도록 한다.
df - 파일 시스템 별 사용량 확인
df 는 Disk Free 의 약자로, 현재 시스템에서 사용중인 파일시스템의 사용량에 대한 정보를
출력하는 명령어 이다. 부가적으로 전체 용량, 사용 가능한 용령 마운트 정보등도 함께
출력된다
$ df [옵션] [파일 시스템]
옵션명 설명
-a 모든 파일 시스템을 대상으로 디스크 사용량을 확인
-k 디스크 사용량을 KB 단위로 출력함
-m 디스크 사용량을 MB 단위로 출력함
-h Human 의 약자로, 디스크 사용량을 알기 쉬운 단위(GB, MB,KB
등)으로 출력함
-T 파일 시스템의 류도 출력함
실행 예
메가바이트(mb) 단위로 사용량을 확인한다.
$ df -m
사람이 보기 좋은 단위로 사용량을 표시한다.
$ df -h
[그림] 디스크의 남은 공간을 확인할 때 df 명령에 –m (MB 단위) 을 지정하면 확인하기
수월하다.
du - 디렉토리의 디스크 사용령 확인하기
du 는 Disk Usage 의 약자로, 파일 시스템별로 디스크 사용량을 알려주는 df 명령과는 달리
특정 디렉터리 별로 디스크의 사용량을 알려준다.
$ du [옵션] [디렉토리]
옵션명 설명
-s 특정 디렉터리의 전체 사용량을 출력한다
-h 디스크의 사용량을 알기 쉬운 단위(GB,MB,KB 등으로 출력한다)
실행 예
메가바이트(mb) 단위로 사용량을 확인한다.
$ du -s
사람이 보기 좋은 단위로 사용량을 표시한다.
$ df -h
fsck – 디스크 상태 검사 및 복구
fsck 는 File System Check 의 약자로, 디렉터리, 파일 링크등을 검사하고 필요시 복구 작업도
수행하는 명령어 이다.
$ fsck [옵션] [장치명]
옵션명 설명
-f 강제로 점검한다.
-y 모든 질문에 yes 로 대답하게 한다
-a 파일 시스템 검사에서 문제가 발생했을 때 자동으로 복구한다
실행 예
사용자에게 묻지 않고 자동으로 검색을 수행한다.
$ sudo fsck - y
검사 중 문제 발견 시 자동으로 복구한다.
$ sudo fsck -a
Free – 사용가능 메모리 확인
Free 명령은 메모리 사용량을 확인하는 명령어이다. 정보를 보면 버퍼와 캐시가 있는데, 버퍼는
응용 프로그램별로 존재하는 임시 데이터 저장공간으로 이 데이터는 다른 응용 프로그램이
사용하지 않는다. 반면 캐시는 빠른 접근을 위해 자주 사용하는 데이터를 저장하는 메모리
공간이다. 캐시는 여러 번 사용될 수 있지만 버퍼는 한번만 사용한다.
$ free [옵션]
옵션명 설명
-s 특정 디렉터리의 전체 사용량을 출력한다
-h 디스크의 사용량을 알기 쉬운 단위(GB,MB,KB 등으로 출력한다)
실행 예
메가바이트(mb) 단위로 사용량을 확인한다.
$ du -s
사람이 보기 좋은 단위로 사용량을 표시한다.
$ df –h
lsusb
List USB 의 약자로써, 연결된 USB 의 자세한 정보를 제공하는 명렁어 이다. 라즈베리파이에
다양한 USB 장치들을 연결하여 활용할 수 있는데, 해당 장치가 라즈베리파이에서 제대로
인식되는지 확인하는데 사용할 수 있다.
$ lsusb
[그림] lsusb 를 사용하면, USB 에 연결된 장치를 확인할 수 있다. 내부적으로는 1 개의 USB
포트를 허브로 5 개로 나누어 쓰고, 그 중 하나는 유선 이더넷에 포함되어 있다.
리눅스 시스템 종료
리눅스를 종료하는 방법을 살펴보자 윈도우를 사용하면 가끔 PC 가 동작을 멈추어서 전원을
껐다 켜는 상황이 발생한다. 이러한 경우 문제가 생겨 윈도우를 재 설치해야 하는 경우가
발생할 수 있고, 중요한 파일이 사라지기도 한다. 리눅스가 탑재된 라즈베리파이도 마찬가지로
시스템을 종료할때 정상 절차를 거치는 것이 매우 중요하다. 전원을 켜는 방법인 USB 전원
선을 연결하는 것처럼 종료하는 것을 선으로 뽑을 수 있으나, 잘못하면 여러분의 소중한
데이터가 날라가 버리게 된다. 여기서는 리눅스를 안전하게 종료하는 방법을 살펴보자
shutdown - 시스템 종료
리눅스 시스템을 정상적으로 종료하는 방법은 shutdown 명령을 사용하는것이다. shutdown
명령은 다른 시스템 종료 명령과는 달리 다양한 종료 방법과 옵션을 제공한다.
$ shutdown [옵션] [시간] [메시지]
옵션명 설명
-h 시스템을 종료한다.
-k 실제로 시스템을 종료하는 것이 아니라, 사용자들에게 메세지만
전달한다.
-r 종료후 재 시작한다
-c 이전에 했던 shutdown 명령을 취소한다
실행 예
즉시 리눅스를 종료한다.
$ sudo shutdown -h now
”System is going down” 이라는 메시지를 출력하고, 3 분뒤 종료한다.
$ shutdown -r +3 "System is going down"
reboot - 시스템 재 시작, halt/poweroff – 시스템 종료
shutdown 명령 외에 시스템을 재 시작하거나 종료하는 명령으로는 reboot 와 halt,poweroff 가
있다.halt 와 poweroff 명령도 내부적으로는 reboot 를 심볼릭 링크를 활용하여 이용한다. 이
명령들은 /var/log/wtmp 파일에 시스템 종료 기록을 남기고 시스템을 종료하거나 재 시작한다.
사용할 수 있는 옵션은 다음과 같다.
옵션명 설명
-w 실제로 재 시작하거나 종료하지 않지만 wtmp 파일에 기록을 남긴다.
-f 강제로 명령을실행하며 shutdown 을 호출하지 않는다.
-p 시스템의 전원을 끈다.
-c 이전에 했던 shutdown 명령을 취소한다
실행 예
즉시 리눅스를 종료한다.
$ sudo halt
즉시 리눅스를 재 부팅한다.
$ sudo reboot
리눅스 프로세스 관리하기
라즈베리파이에서 리눅스를 실행하며 다량한 명령어를 배워보았다. 사용자가 실행한 명령은
프로세스가 되어 실행된다. 프로세스는 현재 실행 중인 프로그램을 의미하는 것으로,
시스템에는 사용자가 실행한 프로세스 외에도 사용자 관리, 메모리 관리, 네트워크 접속 관리등
다양한 기능을 실행하는 수많은 프로세스가 실행되고 있다. 일반적으로 사용하는 명령은
실행시간이 짧아 실행 후 바로 종료되기 때문에 짧은 시간 동안만 프로세스 상태를 유지하고
있다.
ps 프로세스 목록 보기
Process 의 약자로서, 현재 실행중인 프로세스의 목록을 확인할 수 있다.
$ ps [옵션]
옵션명 설명
-e 시스템에서 실행중인 모든 프로세스 정보를 출력
-f 프로세스의 자세한 정보를 출력
-u 특정 사용자에 대한 모든 프로세스 정보를 출력
-p PID(Process ID)로 지정된 특정 프로세스 정보를 출력
실행 예
실행중인 프로세스 정보를 상세하게 출력한다.
$ ps -f
시스템에서 실행중인 모든 프로세스 정보를 보여준다.
$ ps -ef
[그림] 프로세스의 상태를 자세히 확인하기 위해서는 –f 옵션을 함께 사용한다.
실행중인 프로세스의 목록을 자세히 확인하기 위해서는 –f 옵션을 함께 사용해야 한다. –f
옵션을 사용하였을 때 세부적으로 나오는 정보는 다음과 같다.
항목 설명
UID 프로세스를 실행한 사용자 ID
PID 프로세스 번호
PPID 부모 프로세스 번호
C CPU 사용량 (%값)
STIME 프로세스의 시작 날짜와 시간
TTY 프로세스가 실행된 터미널의 종류와 번호
TIME 프로세스의 실행 시간
CMD 실행되고 있는 프로그램의 이름
grep - 특정 프로세스 정보 검색하기
ps 명령만 사용하면, 모든 프로세스 목록이 모두 보이기 때문에, 특정 프로세스의 상태를
확인하기 어렵다. 라즈베리파이에서 본인이 실행시킨 특정 프로세스를 확인해 보고자 할 때가
있다. 이러한 경우는 ps 와 grep 명령어를 조합하여 사용할 수 있다.
$ ps -ef | grep [명령]
실행 예
node 라는 이름을 가지고 있는 프로세스를 상세하게 확인한다.
$ ps –ef | grep node
[그림] grep 으로 현재 동작되는 프로세스 중 특정 프로세스를 찾는 방법이다. 여기서 마지막
줄은 grep 자기 자신이 나오는 것이므로, 현 화면에서 실제로 node 라는 이름으로 동작되는
것은 2 개의 프로세스 이다.
특정 프로세스 정보 검색하기 pgrep
pgrep 은 ps 와 grep 을 하나로 통합하여 만든 명령어 이다. ps 와 달리 지정한 패턴과
일치하는 프로세스의 정보를 간략하게 출력한다.
$ pgrep [옵션] [패턴]
옵션 설명
-x 패턴과 정확히 일치하는 프로세스의 정보를 출력 함
-n 패턴을 포함하고 있는 가장 최근의 프로세스 정보를 출력한다
-u 사용자 이름 : 특정 사용자에 대한 모든 프로세스를 출력한다
-l PID 와 프로세스의 이름을 출력한다
-t [TERM] 특정 단말기와 관련된 프로세스의 정보를 출력한다
kill,pkill - 프로세스 종료하기
응답이 없거나 불필요한 프로세스를 종료하려면 해당 프로세스의 PID 를 알아야 한다. 앞에서
배운 ps 나 pgrep 명령으로 정보를 확인한 후 kill 이나 pkill 명령을 이용하여 프로세스를
종료한다.
kill
인자로 지정한 프로세스에 시그널을 전달한다.
$ kill [시그널] pid
시그널 설명
-2 인터럽트 시그널을 보낸다 (CTRL+C 와 같은 효과)
-9 프로세스를 강제로 종료한다
-15 프로세스가 관련된 파일을 정리하고 프로세스를 종료한다.
프로세스가 종료되지 않을 수도 있다.
실행 예
프로세스 ID 가 2419 인 프로세스를 강제로 종료한다.
$ sudo kill -9 2419
[그림] kill 명령으로 2419 프로세스를 종료한 후 다시 ps 명령으로 확인하면, 프로세스가
종료되었음을 확인할 수 있다.
pkill
Process Kill 의 약자로, kill 명령과 같이 시그널을 보내는데, pid 가 아니라 프로세스의 명령
이름(CMD)로 프로세스를 찾아 종료한다. kill 명령어와의 차이점은, 명령이름으로 찾아
종료하므로 여러개가 한번에 종료될 수 있다는 점이다.
$ pkill [옵션][패턴]
옵션 설명
-2 지정한 패턴을 명령어 뿐만 아니라 경로명, 옵션, 인자값도 비교
-9 패턴과 일치하는 프로세스의 가장 최근에 실행된 프로세스 하나만
종료
-15 패턴과 정확하게 일치되는 프로세스만 출력
실행 예
node 라는 이름을 가지는 프로세스를 강제로 종료한다.
$ sudo pkill node
[그림] pkill 을 이용해 이름으로 프로세스를 종료하면, 여러 프로세스를 한번에 종료하는데
유용하다.
top 프로세스 관리도구
ps 명령으로는 현재 프로세스 목록을 확인할 수 있다. top 명령은 현재 실행중인 프로세스
정보를 주기적으로 출력하는데, 프로세스의 요약 정보를 상단에 출력하고 각 프로세스의
정보를 하단에 출력한다.
$ top
항목 설명
PID 프로세스 id
USER 사용자 계정
PR 우선순위
NI NICE 값
VIRT 프로세스가 사용하는 가상 메모리 크기
RES 프로세스가 사용하는 메모리 크기
SHR 프로세스가 사용하는 공유 메모리 크기
%CPU CPU 사용량
%MEM 메모리 사용량
TIME+ CPU 누적 이용시간
COMMAND 명령 이름
동시에 여러가지 작업을 수행해 보자
백그라운드 작업
사용자가 터미널에서 보통 작업을 할때 한번에 하나만 실행이 가능하다. 이는 새로운 명령을
실행하기 위해서는 이전에 실행했던 명령이 끝날때까지 기다렸다가 사용해야 함을 의미한다.
하지만 상황에 따라 여러가지 명령을 한번에 실행해야 하는 경우가 있다. 이럴때 작업을
제어하는 명령을 사용하면 유용하다.
포그라운드 작업
일반적으로 명령어를 입력하면 컴퓨터는 사용자가 입력한 명령을 해석하고 실행하여 그
결과를 출력한다. 사용자는 화면에 출력된 결과를 보고 다시 명령을 입력하는 컴퓨터와
대화하는 방식으로 작업을 수행한다. 이러한 방식을 Forground Process 라고 한다.
$ sleep 100
->sleep 명령이 끝날때까지 기다려야 한다
백그라운드 작업 &
포그라운드 작업은 명령을 한번에 하나씩 실행하므로 동시에 여러개를 실행할 수 없는 문제가
있다. 이럴때 이전과 같이 포그라운드 작업을 진행하게 되면 다른 작업을 실행할 수 없다. 이럴
때 백그라운드로 실행하면 포그라운드에서는 다른 작업을 수행할 수 있다. 백그라운드 작업은
명령의 실행시간이 많이 걸리는 작업을 수행하거나 다른 명령을 동시에 실행하고 자 할때
사용한다. 명령을 수행하기 위해서는 마지막에 & 기호를 붙이면 된다.
$ sleep 100 &
작업목록 보기 jobs
현재 실행중인 백그라운드 작업을 볼수 있는 명령어로 모든 백그라운드 작업을 보여준다. 특정
번호를 지정하면 해당 작업의 정보만 보여준다.
$ jobs [옵션]
옵션 설명
-l 프로세스 번호를 추가하여 보여준다.
작업 전환하기
포그라운드로 동작중인 서비스를 제어할 수 있다.
옵션 설명
CTRL+Z 또는
stop [%작업번호]
포그라운드로 작업을 잠시 중지한다.
bg [%작업번호] 작업번호가 지시하는 작업을 백그라운드로 작업을 전환한다
fg [%작업 번호] 작업번호가 지시하는 작업을 포그라운드 작업으로 전환한다.
작업 종료하기 CTRL + C
포그라운드 작업을 종료할때 CTRL + C 를 이용하면 종료가 된다. 백그라운드 작업은 kill
명령으로 강제 종료해야 한다. kill 명령의 인자로 %작업 번호를 지정하면종료 된다.
$ sleep 100 &
$ kill %1
로그 아웃 후에도 백그라운드 작업 계속하기 nohup
백그라운드 작업을 실행한 터미널이 종료되거나 사용자가 로그아웃하면 실행 중이던
백그라운드 작업도 종료된다. 하지만 터미널이 종료되도 작업이 완료될때까지 종료되지 말아야
하는 상황이 있는데, 이때 nohup 명령을 사용한다
$ nohup [명령] &
패키지 관리하기
드디어 운영체제가 탑재된 라즈베리파이를 강력하게 사용할 수 있는 패키지 관리 기능이다.
아두이노와 달리 라즈베리파이는 리눅스 기반이기 떄문에 수 많은 소프트웨어를 손쉽게
설치하여 사용할 수 있다. 직접 프로그래밍하지 않더라도 이를 통해 이미 만들어진 다양한
소프트웨어로 다양한 기능을 발휘하도록 할 수 있다. 이러한 소프트웨어는 소스코드 자체로
제공되어서 수동으로 컴파일하여 설치하는 방식이 있고, 바이너리 패키지로 배포되어 설치하여
사용할 수 있는 형태가 있다. 여기에서는 패키지로 관리되는 다양한 소프트웨어를 손쉽게
설치하는 방법을 다룰 것이다.
패키지의 특징
- 바이너리 파일로 되어 컴파일을 할 필요가 없다.
- 패키지의 파일들이 관련 디렉터리로 바로 설치된다.
- 패키지를 삭제할 때 관련된 파일을 일괄적으로 삭제할 수 있다.
- 기존에 설치한 패키지를 삭제하지 않고 바로 업그레이드 할 수 있다.
- 패키지의 설치 상태를 검증할 수 있다.
- 패키지에 대한 정보를 제공한다.
- 해당 패키지와 의존성을 가지고 있는 패키지가 무엇인지 알려준다. 따라서 의존성이 있는
패키지를 미리 설치할 수 있고, apt-get 명령을 사용하면 의존성이 있는 패키지가 자동으로
설치된다
패키지의 이름구성
파일명_버전-리버전_아키텍쳐.deb
항목 설명
파일명 패키지의 성격을 나타내는 파일명이다.
패키지 버전 두 번째 항목은 패키지의 버전을 의미한다.
패키지 리비전 리비전은 원래 소스의 버전이 업그레이드 되지는 않았지만 패키지의
보안.
아키텍쳐 사용하는 시스템 아키텍쳐로 i386 은 인텔을, all 은 시스템과
상관없는 문서나 스크립트 등을 의미함. 라즈베리파이는 ARM CPU
이므로 arm 으로 시작하는 명칭이 붙는다.
확장자 우분투 패키지의 확장자는 .deb 를 사용한다.
apt-get
소프트웨어 패키지들이 저장된 저장소를 업데이트하고 패키지를 설치하거나 제거하는데
사용하는 명령이다.
$ agt-get [옵션] [서브 명령]
옵션 설명
-d 패키지를 내려받기만 한다.
-f 의존성이 깨진 패키지를 수정하려고 시도한다.
-h 간단한 도움말을 출력한다.
서브명령 설명
update 패키지 저장소에서 새로운 패키지 정보를 가져온다.
upgrade 현재 설치되어 있는 패키지를 업그레이드 한다.
install [패키지] 패키지를 설치한다.
remove [패키지] 패키지를 삭제한다.
download [패키지] 패키지를 현재 디렉토리에 내려 받는다.
autoclean 불완전하게 내려 받았거나 오래된 패키지를 삭제한다.
clean 캐시되어 있는 모든 패키지를 삭제하여 디스크 공간을 확보한다.
check 의존성이 깨진 패키지를 삭제한다.
사용 예
Linux 패키지 정보를 갱신하여, 설치된 모든 패키지를 업데이트 한다.
$ sudo apt-get update
$sudo apt-get upgrade
MP3 재생 패키지인 mpg321 을 자동으로 설치한다.
$ sudo agt-get install mpg321 –y
MP3 재생 패키지인 mpg321 을 삭제한다. 상황에 따라 사용자 확인을 받는다.
$ sudo apt-get remove mpg321
[그림] 패키지 설치/삭제시 자동 진행을 위해서 –y 옵션을 사용할 수 있다.
패키지 수동 관리하기 - dpkg
apt-get 도 내부적으로는 dpkg 를 사용한다. 시스템의 특정 파일이 어느 패키지에 속한
것인지를 확인 하는 기능 등 보다 세부적인 기능을 제공한다. Deb 확장자로 시작하는 패키지를
직접 받은 경우 이를 설치하고 제거하는데 사용할 수 있다.
$ dpkg [옵션] [파일명 또는 패키지 명]
서브명령 설명
-l 설치된 패키지의 목록을 출력한다.
-l [패키지] 패키지의 설치 상태를 출력한다.
-I [DEB 파일] 해당 파일을 설치한다 (sudo)
-r [패키지] 해당 패키지를 삭제한다 (sudo)
-P [패키지] 해당 패키지와 설정 정보를 모두 삭제한다 (sudo)
사용 예
NODEJS 를 다운로드 받은 후 설치한다.
$ sudo wget http://node-arm.herokuapp.com/node_latest_armhf.deb
$ sudo dpkg –I node_latest_armhf.deb
NODEJS 를 설치 정보와 함께 삭제한다.
$ sudo dpkg –r -P node_latest_armhf.deb
심플하고 쉬운 텍스트 에디터 - Nano
리눅스 상에서 문서 편집을 위해 흔히 많이 이용되는 것은 VI 편집기이다. GUI 환경이라면,
GEDIT 를 이용할 수 있지만, Linux 는 콘솔이나 터미널 환경에서 많이 이용하기 때문에 대안을
찾아야 한다. vi 의 경우 UNIX 시절부터 이어 내려오는 편집기로 강력한 기느와 다양한 명령을
제공하지만, 처음 접하는 사람들에게는 어렵다. 터미널 환경에서 GEdit 처럼 간단하게 활용할
수 있는 것은 nano 이다.
Nano 의 특징
사용하기 쉽다는 점이다. VI 는 키보드 방향으로 커서를 이동할 수 없지만 나노는 커서를
이동할 수 있고, VI 가 편ㅈㅂ과 입력 모드가 나뉘어 있지만 나노는 GUI 환경과 윳하게 사용할
수 있도록 되어 있다. 또한 아래 부분에는 단축키도 표시해 주므로 손쉽게 사용할 수 있다.
전문적인 사용에는 VI 가 탁월하지만 대부분의 일들이 간단한 경우가 많기 때문에 나노로
충분히 처리할 수 있다.
가장 기본적으로 사용하는 방법은, 방향키를 이용하여, 수정이 필요한 위치로 이동하여, 원하는
내용으로 입력한 후 CTRL + X 를 누르고, 저장하겠냐는 질문에 Y 를 입력한 후 Enter 를
클릭하여 저장하고 종료하는 방식이다. 보다 다양한 기능이 필요한 경우 다음 하단의 단축키를
활용하도록 하자.
$ sudo nano /etc/rc.local
l
단축키 기능
CTRL+G / F1 나노 에디터 사용법(도움말)을 확인할 수 있음
CTRL+X / F2 나노 에디터 종료. 저장하지 않았을 경우 저장할지 여부를 물음
CTRL+O / F3 현재 작성중인 파일 저장
CTRL+W / F6 현재 문서에서 검색. 검색중 정규식 검색모드로 전환하려면 ALT+R 을
누르면 됨
CTRL + ^ ALT+R 문자열을 검색하여 내용을바꿈
ALT+W 이전에 검색했던 검색 결과를 반복
CTRL+R / F4 파일 불러오기. 현재 편집중인 문서에 다른 문서의 내용을 삽입 하는
동작. 불러온 파일의 내용을 변경할수 없음. 나노에서 편 집 중 다른
파일을 불러오려면 나노를 종료하고 명령을 다시 실행해야 함
CTRL+Y / F7 이전 페이지로 이동하는기능, 키보드의 Page Up 키로도 사용 가능
CTRL+V / F8 다음 페이지로 이동하는 기능, 키보드의 Page Down 키로 사용 가능
ALT+ M 문서의 맨 처음으로 이동 함
ALT + M? 문서의 맨 끝으로 이동함
CTRL+SHIFT+^
혹은 ALT+G
특정 행을 입력하여 특정 행으로 이동할 수 있음
ALT+SHIFT+M /
ALT+SHIFT +M ?
현재 행을 들여쓰거나 내어 씀
CTRL + J 한 줄이 현재 터미널의 폭을 벗어날 정도로 긴 행을 폭에 맞추어 정렬
함
이번 장을 정리하며
이번 장에서는 라즈베리파이의 리눅스를 다루는 법을 다루었다. 기본적인 명령어를 다루고,
시스템을 간단하게 모니터링 하는 법을 배웠으며, 리눅스 생태계의 다양한 패키지들을
설치하는 방법을 실습해 보았다. 여기서 배운 텍스트 에디트는 앞으로 설정 편집이나 간단한
프로그래밍에서 유용하게 사용되니, 잘 기억해두도록 한다. 다음 장에서는 본격적으로
자바스크립트를 이용한 NodeJS 프로그래밍에 대해 다룬다. 운영체제가 올라간 라즈베리파이에
직접 웹 어플리케이션 서버를 자바스크립트로 작성하여 활용해 볼 것이다.
3. JavaScript 로 하드웨어 제어를 - NodeJS
세상에서 가장 인기 있는 언어 - Javascript
아직도 자바스크립트가 웹에서 간단히 입력 값을 검사하는 프로그래밍 언으로 생각하는가?
생각을 바꾸어야 할 것이다. 자바스크립트는 이제 세상에서 가장 인기 있는 프로그래밍 언어가
되었다. Github 의 프로젝트 중에 자바스크립트로 구현된 프로젝트가 가장 많고, 해커톤에서
사용되는 가장 인기 있는 프로그래밍 언어가 자바스크립트 이다. 천덕꾸러기 신세였던
자바스크립트는 2005 년 구글로 부터 시작된 Ajax 혁명으로 인하여 보다 중요한 프로그램을
작성하기 위한 언어로 부각되기 시작했다. HTML5 의 부각과 다양해 지는 단말, 다양한
자바스크립트 프레임워크등으로 자바스크립트의 인기는 더욱 높아질 전망이다.
[그림] GITHUB 에서 가장 인기 있는 언어는 단연 JavaScript 이다
JavaScript 로 서버와 라즈베리파이 제어를! - NodeJS?
NodeJS 는 오픈소스로 공개된 Chrome 브라우저의 자바스크립트 실행 엔진(V8 이라
불리운다)을 기반으로 제작되었다. 확장성 있고 빠르게 어플리케이션을 개발할 수 있도록
도와주는 플랫폼이다. 즉, NodeJS 로 인하여 자바스크립트로 웹 페이지 개발 뿐만 아니라
서버나 하드웨어 제어 용도로도 사용할 수 있다는 의미이다. 이벤트 처리 기반과 Non-Blocking
IO 를 이용하여 분산된 Device 상에서 실시간 데이터 통신을 하는 어플리케이션에 최적화되어
있고 경량의 효율적인 프레임 워크이다.
비동기식 이벤트 처리기반 프레임워크로, 확장가능한 네트워크 어플리케이션 개발에 맞추어
디자인 되어 있다.
쓰레드 기반의 네트워킹은 사용하기 어렵고, 데드락 현상으로 시스템 성능이 저하된다는
단점이 있는 반면, NodeJS 는 이벤트 기반으로 block 이 될 일이 없어, 비 전문가도 확장
가능한 시스템을 개발하는데 용이하다는 장점이 있다.
[그림] NodeJS 도 결국 JavaScript 로 되어 있다. JavaScript 만 알면, 모바일 어플리케이션부터
서버, 데이터 분석, 하드웨어 제어 그 모든 것이 가능한 현 시대에 가장 활용성 높은 프로그래밍
언어이다.
왜 Javascipt 기반으로 하는가 - NodeJS 의 이점
NodeJS 가 만능은 아니고, 상황에 따라 더 좋은 성능을 보여 주는 언어도 많다. 상당수의
라즈베리파이 관련 예제와 책자들이 파이썬(Python) 으로 구현되어 있는데, 유일하게 이
책에서 JavaScript 로 모든 프로그램을 구성한 것이 의아해 할 것이다. 구현하고자 하는
아이디어가 있을 때 이를 단 한가지 언어로만 만든다고 하면 현재 선택할 수 있는 최선의
언어이다. 필자 역시 전자공학을 전공하고, 회사 일로 C 나 Java 와 같은 언어를 주로
사용하였지만, 메이크 활동을 위해서는 자바스크립트 만을 사용한다. 왜 자바스크립트, NodeJS
를 사용하는지에 대한 이유는 다음과 같다.
- 개발자는 하나의 언어로 웹 어플리케이션 부터 IoT 하드웨어까지 개발할 수 있기 때문에
서버와 클라이언트, 모바일 부터 하드웨어 제어까지 넘나드는 비용을 획기적으로 줄일 수
있다. 또한 한 곳에서 제작된 코드는 다양한 플랫폼을 넘나들며 그대로 이용될 수 있어
유지 보수를 매우 간단하게 해준다. 심지어 간단한 데이터 분석 또한 NodeJS 에서 처리할
수 있다.
- JSON (JavaScript Object Notation)은 XML 을 넘어서 현재 가장 보편적인 데이터 교환
포맷인데, Javascript 기반을 두고 있어 손쉬운 데이터 처리가 가능하다.
- Javascript 는 CouchDB, MongoDB 와 같은 NoSQL 데이터베이스에서 사용되는 언어로
상호작용하기에 아주 적합하다. 몽고디비의 쉘과 질의언어가 자바스크립트이며, Client,
Server, DB 까지 동일한 JSON 형식으로 데이터를 사용하기 때문에 데이터 처리가 매우
용이하다. 또한 데이터 분석 및 처리용도로 사용되는 엘리스틱 서치 (Elasticsearch) 또한
JSON 형태로 데이터를 활용할 수 있어 매우 편리하다.
- Node 가 사용되는 v8 가상 머신은 ECMAScript 표준을 준수하여, Javascript 의 모든 새로운
기능을 즉시 확인해 볼 수 있다.
Hello Pi! – NodeJS 설치하기
라즈베리파이에서 NodeJS 를 설치하는 기본적인 방법은 직접 소스코드를 컴파일해서
사용하는 것이다. 하지만, 더 간단한 방법으로 이미 배포판으로 만들어진 설치파일을 받아서
바로 설치하는 것이다. 우리는 배포 판을 받고, 2 장 리눅스 시간에서 다룬 dpkg 명령을
이용하여 NodeJS 를 설치해 보고, Hello Pi 를 작성하여 확인해 보도록 한다.
우선 배포 파일을 wget 명령으로 다운로드 받고, dpkg 명령으로 설치하도록 한다.
$ sudo wget http://node-arm.herokuapp.com/node_latest_armhf.deb
$ sudo dpkg –I node_latest_armhf.deb
NodeJS 가 정상적으로 설치되었는지 확인하기 위해서 설치된 NodeJS 버전을 확인해 본다.
$ node –v
버전이 제대로 확인이 된다면 node 를 입력하여 실행한다. Python 과 유사하게 작성한 코드를
실행할 수도 인터프리터 모드에서 하나씩 입력해서 동작을 확인해 볼 수도 있다. 다음과 같은
코드를 입력하여 Hello Pi 가 정상적으로 출력되는지 확인한다.
정상적으로 화면에 출력된다면, 간단한 연산을 수행하여 우리의 프로그램이 정상적으로
동작하는지 확인해 본다. 2 개의 메모리 공간에 숫자를 넣고 이를 더한 결과 값을 제대로 보여
주는 지 확인하는 예제이다. (원래 컴퓨터가 전자계산기에서 출발했다는 것을 생각하자)
[그림] NodeJS 버전 확인 및 NodeJS 인터프리터 모드에서 코드를 작성하여 실행하는 예시
console.log('Hello Pi!');
var a = 10;
var b = 5;
console.log(a + b);
프로그래밍 방식 선택하기 – Nano vs Sublime Text
우리는 라즈베리파이를 메이커 보드로 사용하기 위하여 별도의 GUI 구성을 하지 않고
텍스트들만이 난무하는 콘솔 환경에서 사용하고 있다. 첫 Hello Pi 예제와 같이 인터프리터
모드에서 하나씩 입력해 가면서 실행할 수도 있겠지만, 코드의 길이가 길어지면, 이렇게 하나씩
입력하는 방식으로는 프로그램 수정이나 공유가 어려울 있다. 따라서, 코드를 별도로 작성한 후
그 코드를 실행하는 방식을 대부분 사용한다. 이 라즈베리파이에서 NodeJS 코드를 작성하기
위해서는 Nano 를 이용하여 직접 작성하거나, 당신이 사용하고 있는 컴퓨터에서 작성한 다음,
파일을 FTP 로 전송하여 실행하는 방법이 있을 것이다. (Circulus 를 사용하면 이런 복잡한
문제가 해결되는데, 부록에서 다루도록 하겠다)
우선 첫 번째 실행한 예제를 Nano 로 작성한 후 실행해 보도록 한다. 작성이 완료되면 CTRL+X
를 누르면, 저장하겠냐고 묻는데, Y 를 누르고 엔터를 두번 눌러 주면 저장이 완료된다.
$ sudo nano sample1.js
저장된 sample1.js 를 실행해 볼 차례이다. NODE 명령어 뒤에 간단히 파일명만 입력하는
것으로 실행이 되고, 결과가 화면에 보여지게 된다.
$ node sample1.js
[그림] 실행 결과가 인터프리터에 직접 입력한 것 과 동일하게 정상적으로 출력되는 것을
확인할 수 있다.
아무래도, 윈도우나 스마트 폰을 사용하던 환경에서 마우스 없이 직접 입력해서 프로그래밍
하는 것은 쉽지 않은 일이다. 좀더 편리하게 컴퓨터에서 자바스크립트 코드를 작성하는 경우,
사랑 받는 프로그램 중 하나가 Sublime Text 이다. 무료 버전을 사용하면 저장 시에 가끔씩
구매해 달라는 팝업이 뜨는 것 이외에 기능 제약이 없는 프로그램이다. Sublime Text 를
사용하고자 하면 다음의 주소에서 다운로드 받도록 한다.
다운로드 주소 > http://www.sublimetext.com/3
[그림] Sublime Text 3 는 강력하며 빠르고, 게다가 무료이다.
Sublime Text 에서 저장된 코드를 FTP 로 라즈베리파이에 옮긴 후, 실행하게 되면 동일한
결과를 확인할 수 있다.
자바스크립트로 프로그래밍 시작하기
자바스크립트는 타 프로그래밍 언어에 비해 자유도가 높은 언어로 알려져 있다. 사용하기 쉬워
하는 프로그래머와 어려워 하는 프로그래머로 나뉜다. 여기서는 모바일과 하드웨어 제작을
위해, 기본적인 자바스크립트 사용법을 다룬다.
기본 연산자
자바스크립트를 이용하여 연산 할 때 사용하는 문법으로, 숫자 형인지 문자형인지에 따라
결과값이 달라진다. 증가연산자의 위치에 따라서 실행 전과 후의 결과 값이 달라지는 것을
확인하자.
[ js1.js ]
// 숫자를 더한 값을 출력한다.
console.log(100 + 1);
// 문자와 숫자를 더한 값을 출력한다.
console.log('100' + 1);
var a = 10;
// a 에 저장된 숫자값을 연산하여 출력한다.
console.log(a + 1);
console.log(a);
console.log(a++);
console.log(a);
console.log(++a);
console.log(a);
증가연산자가 뒤에 있는 경우 (a++) 곧바로 출력시 값이 변경되지 않지만, 그 이후에 값을
출력시에 1 이 증가됨을 알수 있다. 반대로 앞에 있는 경우(++a), 이미 1 이 증가된 상태로 값이
출력됨을 알수 있다.
[그림] 숫자와 문자의 덧셈 연산의 결과가 다르다.
비교 연산자
비교 연산자는 좌변과 우변의 값을 비교하여 그 결과를 반환하는 문법이다. 이항 비교연산자인
==, != 은 값만을 비교하나, 삼항 비교연산자인 ===, !== 은 데이터 형태도 비교하므로,
예기치 못한 동작을 방지하기 위해서 삼항 연산자를 사용하는 것이 바람직 하다.
[ js2.js ]
// 이항 연산자로 비교한 값을 출력한다.
console.log(1 == true);
console.log(1 == '1');
// 삼항 연산자로 비교한 값을 출력한다.
console.log(1 === true);
console.log(1 === '1');
// x 값을 비교하여 true 이면 bigger 를 false 면 smaller 를 출력한다.
var x = 90;
console.log((x > 90) ? 'bigger':'smaller');
이항 연산자와 달리 삼항 비교 연산자는 값이 동일하지 않은 경우 false 로 비교 값이 반환됨을
확인할 수 있다. 이후 if 조건문을 다루겠지만, ? 을 이용하여 한줄로 비교 작업을 간단하게 할
수 있음을 확인하도록 한다.
[그림] 삼항 연산자는 데이터 형태도 비교한다.
논리 연산자
논리 연산자는 복수의 조건 식을 비교하여 최종적으로 그 값이 true 인지, false 인지를
반환한다. 앞에서 살펴본 비교 연산자를 사용하여 사용하며, 조합을 통해 복잡한 조건식을
표현할 수 있다.
[ js3.js ]
var x = 1;
var y = 2;
// 좌항과 우항이 둘다 true 일때 true 가 출력된다.
console.log(x == 1 && y == 1);
// 좌항과 우항중 하나만 true 이면 true 가 출력된다.
console.log(x == 1 || y == 1);
&& 의 경우, and 연산을 하여 좌항과 우항의 값이 모두 true 일 경우 true 를 반환하지만, || 의
경우는 or 연산을 하여 좌항과 우항 중 한 개만 true 인 경우 true 값을 반환한다.
[그림] 한 개만 조건을 만족하는 경우는 or(||) 연산을, 모든 조건을 만족해야 하는 경우는
and(&&) 연산을 사용하도록 한다.
IF 조건문
여러가지 상황에서 조건에 따라 이에 대응하는 명령을 실행할 수 있다. If 단독으로 사용할 수
있지만, else if 와 else 등으로 세분화 하여 분기할 수 있다.
[ js4.js ]
var x = 15;
// 30보다 같거나 크면 Bigger than 30 이 출력된다.
if( x >= 30){
console.log('Bigger than 30');
// 30보다 작고 10보다 크면, Bigger than 10 이 출력된다.
} else if ( x > 10) {
console.log('Bigger than 10');
// 10보다 값이 작으면 Smaller than 10 이 출력된다.
} else {
console.log('Smaller than 10');
}
첫번째 조건을 만족하지 못하고, 두번째 else if 조건을 만족하여 해당 결과를 반환함을 알 수
있다. 이처럼 중첩을 통해 다양한 조건에 따라 분기하여 원하는 명령을 수행할 수 있다.
[그림] 입력한 조건 값을 비교하여, 원하는 작업으로 분기할 수 있다.
SWITCH 조건문
동일 변수에 대한 비교시 사용할 수 있는 조건문으로, case 블록에 해당하는 값이 존재시 해당
블록 명령을 실행하고, 만족하는 case 가 없는 경우 default 블록을 호출하는 직관적인 구조를
가지고 있다.
[ js5.js ]
var rank = 'B';
// rank 에 저장된 값에 따라 출력된다.
switch(rank){
case 'A':
console.log('A Rank');
break;
case 'B':
console.log('B Rank');
break;
case 'C':
console.log('C Rank');
break;
default:
console.log('Not ranked');
}
if 조건문이 다양한 조건 식을 넣을 수 있는 반면, 값 비교에 따른 분기는 switch 조건문이 보다
명확하게 흐름이 식별된다. Break 문을 빼게 되면, 그 다음의 case 명령 값도 실행되게 되니
주의하도록 한다.
[ 그림 ] 단순 값 비교에는 IF 비교문 보다 SWITCH 비교문이 명확하다.
WHILE 반복문
주어진 조건에 따라 반복 처리하고자 하는 경우 사용하는 명령어이다.
[ js6.js ]
var x = 0;
// x 가 5보다 작을 때 괄호 안 명령이 계속적으로 수행된다.
while(x < 5){
console.log('X is ' + x);
x++;
}
예제 프로그램은 x 값이 5 보다 작을 경우, x 값을 출력해 주고, x 값을 1 씩 증가하는 코드이다.
4 까지 증가되는 값을 반복 출력하고, 종료 됨을 확인할 수 있다.
[ 그림 ] 조건에 따른 반복을 수행할 경우, while 문을 사용한다.
FOR 반복문
정해진 횟수만큼 반복 처리하기 하기 위해 사용하는 명령어 이다. 일반적인 경우 for 명령을
사용하지만, 지정된 배열이나 객체에서 선두부터 반복 처리하기 위해서는 for in 명령을
사용한다.
[ js7.js ]
var obj = { key1 : 'Pi', key2 : 'Raspberry', key3 : 'NodeJS'};
var arr = ['Pi','Raspberry','NodeJS'];
// obj 객체에 있는 키 값이 있을 때 까지 반복한다.
for(var key in obj){
console.log(key + ' : ' + obj[key]);
}
// arr 배열의 길이 만큼 반복하여 배열에 있는 값을 순차적으로 출력한다.
for(var i = 0 ; i < arr.length ; i++){
console.log(i + ' : ' + arr[i])
}
기본 for 문의 경우, 특정 횟수 만큼 반복하는 의미가 있는 반면, for in 은 특정 객체에서 하나씩
꺼내 온다는 의미가 있다. 배열이 아닌 객체에서 하나씩 값을 가져와야 하는 경우 유용하게
쓰일 수 있다.
[ 그림 ] 객체의 값을 확인하기 위해서는 for in 을 활용해야 한다.
STRING 객체
문자열을 취급하기 위한 객체로서, 문자열의 추출이나 가공, 검색등의 기능을 사용할 수 있다.
[ js8.js ]
var str = 'Good day to study about javascript';
// study 단어의 위치를 알려준다.
console.log(str.indexOf('study'));
// 문장의 5번째 문자를 출력한다.
console.log(str.charAt(5));
// 5번째 문자부터 3개를 추출한다.
console.log(str.substr(5,3));
// 5번째 문자부터 8번째 문자까지 추출한다.
console.log(str.substring(5,8));
// 문자열을 공백으로 구분하여 잘라내 배열로 반환한다.
console.log(str.split(' '));
indexOf 함수를 이용하여 특정 문자열의 위치를 파악하고, charAt 함수를 통해 특정 위치의
단어를 가져올 수 있다. Substr 과 substring 모두 문자열을 잘라내는 역할을 하지만, substr 이
시작 지점으로부터 몇 개의 문자를 가져오는 지를 지정하는 반면, substring 은 시작지점과
종료 지점을 지정하여 문자열을 가져오는 차이가 있다. Split 함수는 특정 문자로 문자열을
잘라내어 배열 형태로 반환하는 역할을 한다.
[ 그림 ] substr 과 substring 은 문자열을 잘라내는 역할을 하지만, 사용방법에 차이가 있다.
ARRAY 객체
배열 형의 값을 취급하기 위한 객체로서, 요소의 추가, 삭제, 결합, 정렬등의 기능을 제공한다.
[ js9.js ]
var arr = ['Tomato','Banana','Raspberry'];
var arr2 = ['Apple','Melon','Graps'];
// arr 배열의 가장 마지막 값을 꺼낸다.
console.log(arr.pop());
console.log(arr);
// arr 배열의 끝에 Strawberry 를 추가한다.
console.log(arr.push('Strawberry'));
console.log(arr);
// arr 배열의 가장 앞 값을 꺼낸다.
console.log(arr.shift());
console.log(arr);
// 배열의 0,1,2 위치에서 1번 위치의 Banana 를 꺼낸다.
console.log(arr.splice(1));
console.log(arr);
// arr 배열과 arr2 배열을 합친다.
console.log(arr.concat(arr2));
// arr2 배열을 정렬한다. 숫자의 경우 크기, 문자의 경우 알파벳 순으로 정렬된다.
console.log(arr2.sort());
Pop 함수는 배열의 마지막 값을, shift 는 첫번째 값을 가져오는 역할을 한다. Push 함수는
배열의 마지막에 값을 추가하고, splice 함수는 배열의 특정 위치에서 값을 잘라낸다. Concat
함수를 이용하여 배열을 합치고, sort 를 이용해 배열내 값을 정렬한다.
[ 그림 ] pop 은 배열의 가장 마지막 값을, shift 는 가장 첫번째 값을 반환한다.
함수
주어진 입력을 이용하여, 어떠한 작업을 처리하여 그 결과를 돌려주는 구조를 함수라 한다.
일반적으로 프로그래밍을 할때, 공통적으로 자주 사용하는 코드를 함수로 묶어 재활용한다.
STRING 객체에서 사용한 indexOf 나 ARRAY 객체의 pop 같은 경우가 내장된 기본 함수이다.
사용자는 자기 스스로 함수를 정의하여 사용할 수 있다.
[ js10.js ]
var triangle = function(width, height){
return width * height / 2;
}
// triangle 이라는 함수에 10과 2를 전달하여 결과를 출력한다.
console.log('Triangle : ' + triangle(10, 2));
에제는 삼각형의 넓이를 구하는 함수이다. 가로와 세로 길이를 입력 받고, 이를 계산하여 값을
반환한다. 프로그램 내에서 자주 사용되는 코드는 이처럼 함수로 정의하여 사용할 수 있다.
앞으로 자주 보게 되므로, 잘 파악해 두도록 하자.
[ 그림 ] 함수는 자주 사용하는 프로그램 코드의 묶음이라고 생각하면 된다.
라즈베리파이와 모바일의 연동 - 서버와 클라이언트
자바스크립트에 대해 간단히 살펴보았다. 우리가 하려는 일은 라즈베리파이를 전자계산기로
이용하는 것이 아닌, 라즈베리파이에 오디오를 만들고, 이를 모바일로 제어하는 것이다. 이를
구현하기 위해 많이 이용하는 서버와 클라이언트 모델을 이용하고자 한다. 이 모델은 성능이
좋은 컴퓨터를 서버로 구성하고, 가정용 컴퓨터나 개인 스마트 폰이 클라이언트가 되어,
서버에서 처리된 결과를 클라이언트에서 받는 구조이다. 이 구조에서 우리는 서버가
라즈베리파이가 되어 데이터 처리도 담당하고 센서와의 연동도 담당하게 되는 것이다. 이렇게
라즈베리파이에서 처리된 결과를 모바일로 받고, 반대로 모바일에서 서버 역할을 하는
라즈베리파이로 제어명령을 가지는 구조를 만들게 될 것이다. 우리는 이 작업을 NodeJS 를
이용하여 JavaScript 로 구현할 것이다.
[그림] Server- Client 모델 / 출처 :
https://en.wikipedia.org/wiki/Client%E2%80%93server_model#Advantages
간단한 웹 서버를 만들어 보자 - http
웹 서버를 만드는 것은 그리 쉬운 일이 아니다. 국내에서 가장 많이 사용되는 Java 기반으로
만든다고 할 때, Apache 나 Tomcat 같은 웹 서버 혹은 웹 어플리케이션 서버가 있어야 하고,
서버로 동작하기 위한 복잡한 스펙에 맞추어서 프로그래밍을 수행해야 한다. Java 에서 많이
사용되는 Spring Framework 등을 이용해서 만든다고 하더라도, 처음 접근하는 사람이 쉽게
따라가기에는 어려움이 많다. 이렇게 Application 을 만드는 것은 손쉬운 일이 아니다. 하지만
Node JS 를 활용하면 Hello World Web Application 을 매우 손쉽게 개발할 수 있다. 이렇게
쉬울 수가 없을 것이다. 다음의 코드를 sample2.js 로 작성한 후 실행해 보도록 한다.
[ sample2.js ]
// 내장 모듈인 http 모듈을 사용할 수 있도록 불러온다.
var http = require("http");
// 8080 포트로 서버를 생성한다. 사용자가 웹 브라우저로 서버에 접근시, callback
함수에 정의되어 있는 Hello Pi 가 사용자 브라우저에 출력된다.
http.createServer(function (req, res) {
res.writeHead(200, {'Content-Type': 'text/plain'});
res.end('Hello Pi!n');
}).listen(8080);
// 구동이 완료되면 hello Pi 가 출력된다.
console.log('Hello Pi!');
실행하면 Hello Pi 라고 떡 하고 나오고 아무런 반응이 없다. 첫 번째 실행한 예제와 다른 점은,
바로 실행이 종료되느냐 그렇지 않느냐라는 점이다. 자세히 보면, 프롬프트(Prompt)가 Hello Pi!
출력 이후 빈 공간에 있는 것을 알 수 있다. 이는 프로그램이 종료하지 않고 작업을 대기 중인
상태를 의미하는 것이다. (CTRL+C 키를 누르면 물론 종료가 가능하다.)
[그림] 프로그램이 종료되지 않고 서버로 동작하여 대기 중인 상태이다.
8080 포트를 이용하여 웹 서버가 라즈베리파이에서 잘 동작하고 있다. 이제 정말 서버의
역할을 제대로 하고 있는지 확인할 차례이다. 여러분이 가지고 있는 스마트 폰을
라즈베리파이가 접속한 무선 AP 와 동일한 WiFi 에 연결하도록 한다.
[그림] 스마트 폰을 라즈베리파이와 동일한 무선 AP 에 연결하고, 라즈베리파이의 IP 주소에
포트번호를 입력하면, 웹 서버가 정상적으로 동작하는 것을 확인할 수 있다.
다시 한번 우리의 최종 목표를 생각해 보자. 스마트 폰 으로 제어하는 오디오이다. 이렇게
라즈베리파이에 서버로 구현하고, 스마트 폰으로 라즈베리파이를 제어하는 신호를 보내면,
서버에서는 그 응답 값에 맞는 음악을 재생하거나, 시간을 보여 주거나, 날씨를 알려 주는
행위를 하면 되는 것이다.
만일 스마트 폰이 없다면, 원격으로 접속한 컴퓨터에 웹 브라우저를 연 후 동일하게 접속해
보면 역시나 같은 결과를 확인할 수 있다.
[그림] 컴퓨터의 웹 브라우저로도 동일한 결과를 확인할 수 있다.
파일을 나누어 프로그래밍 하기 - Exports
어플리케이션을 만들면, 모든 코드가 하나의 파일에 집중되어 추후 소스 수정이나 관리가
어려운 경우가 있다. 이럴 때 많은 코드를 담은 파일을 관련있는 그룹으로 나누어 여러개의
파일로 분리하는 방식을 사용한다. Node 의 경우 exports 를 이용하여 파일을 분리할 수 있다.
Exports 를 이용한 분리 방법 중 대표적인 두 가지 방법을 실습해 보고자 한다. 이번에는 총
3 개의 파일로 sample3.js 는 웹 서버 역할을 하고, sample3-1.js 는 덧셈기, sample3-2.js 는
평균값을 계산하는 프로그램을 작성하여 수행하고자 한다.
[ sample3.js ]
var http = require('http');
var calc = require('./sample3-1');
var avg = require('./sample3-2');
// 8080 포트로 웹 서버가 구동되며, 접근시 calc 에 정의된 sum 함수와 avg 에
정의된 함수가 수행되어 결과를 출력한다.
http.createServer(function (req, res) {
res.writeHead(200, {'Content-Type': 'text/plain'});
res.end(' SUM ' + calc.sum(10,5) + ' AVG ' + avg(6,8) +
'n');
}).listen(8080);
console.log('Hello Pi!');
[ sample3-1.js ]
// sum 이라는 함수를 만들어 타 모듈에서 사용할 수 있게 한다.
exports.sum = function(a,b){
return a+b;
}
[ sample3-2.js ]
var avg = function(a, b){
return (a + b) / 2;
}
// 타 모듈에서 사용할 수 있도록 등록한다.
module.exports = avg;
실행은 최종적으로 sample3.js 를 실행하도록 한다.
$ node sample3.js
[그림] 모듈로 분리한 덧셈, 평균 값이 정상적으로 잘 출력됨을 확인할 수 있다.
실행 후 결과가 잘 나오는 것을 웹으로 접근하여 확인할 수 있다. 이러한 파일 분류로 앞으로
프로그램을 작성시에 코드 량이 많아 지면, 적절하게 모듈화 하여 구성하도록 하자.
복잡한 웹 서버 기능을 간편하게 - ExpressJS
Node JS 에서 웹 어플리케이션을 개발 할때 가장 많이 사용되는 프레임 워크로, 빠르고
간결하게 개발하는데 도움을 준다. 웹 과 모바일을 개발하는데 유연하고 다양한 기능들을
지원하며, 무수한 HTTP 관련 기능들을 이용하여 API 들을 손쉽고 빠르게 구현할수 있는 장점이
있다. 웹 어플리케이션을 위한 간결한 계층 구조를 제공함으로써, 기본 NodeJS 의 복잡한
기능들을 알지 못해도 곧바로 웹 어플리케이션을 구축할 수 있다.
[그림] NodeJS 에서 손쉽게 다양한 웹 어플리케이션 구성요소를 간단하게 개발할 수 있게
도와주는, 가장 인기있는 프레임워크 이다.
처음 사용한 http 모듈의 경우, NodeJS 에서 기본적으로 제공하여 별도의 설치가 필요
없었지만, ExpressJS 의 경우 외부 모듈로서 별도의 설치가 필요하다. 리눅스에서 사용했던 apt-
get 과 같이 NodeJS 에서도 간단하게 패키지를 관리하는 NPM(Node Package Module)을
제공하여 손쉽게 외부 모듈을 관리할 수 있도록 도와준다.
Express 를 설치하기 위해서는 다음의 명령어를 실행하도록 한다.
$ npm install –g express
[그림] NPM 을 이용하여 손쉽게 NodeJS 외부 확장 모듈의 설치/업데이트/삭제가 가능하다.
ExpressJS 를 설치할 때 관련된 하위 모듈도 NPM 에서 알아서 설치해 준다.
처음 만들었던 Hello Pi 를 ExpressJS 로 만드는 코드는 다음과 같다.
[ sample4.js ]
// express 모듈을 불러오고 곧바로 실행시킨다.
var app = require('express')();
// 사용자가 브라우저로 접근하면 Hello Pi 가 출력된다.
app.get('/', function(req, res){
res.send('Hello Pi!');
});
// 8080포트로 서버를 생성한다.
app.listen(8080);
웹 브라우저로 결과를 확인하면, http 로 만든 것과 동일한 결과가 나오게 된다. 사실 http
서버로 만드는 것도 매우 간단하기 때문에 이 예제만으로는 무엇이 편리한 건지 알기가 어려울
것이다. 좀더 복잡한 에제를 구현해 보도록 하자. 접근하는 웹 주소로 입력받는 파라미터 입력
값에 따라 나오는 결과를 JSON(JavaScript Object Notation) 형식으로 보여주는 것이다. 이는
웹기반 서비스를 만들 때 사용하는 방식으로 과거에는 XML(eXtensible Markup Language)으로
쓰였으나 현재는 대부분 JSON 방식을 사용하고 있다. JavaScript Object 형태를 띄고 있어서,
서버에서 결과를 제공해 주면 별다른 가공 필요없이 클라리언트에서 그대로 활용이 가능하다.
또한 많이 MongoDB 나 CouchDB 은 데이터베이스, 데이터 처리를 위한 Elasticsearch 등에서도
JSON 형식으로 데이터를 보관하고 있기 때문에 JSON 방식으로 데이터를 주고 받으면
클라이언트부터 DB 까지 변환 없이 아름답게 데이터를 주고받는 작업이 가능하다. JSON 으로
결과를 내려주는 방법이 얼마나 간단한지 확인해 보자.
[ sample5.js ]
var app = require('express')();
// 사용자가 서버에 접근하면, index.html 파일을 반환한다.
app.get('/', function(req,res){
res.sendFile(__dirname + '/index.html');
});
// /req 경로로 접근시, req 경로 이후의 id 값을 JSON 형식으로 반환한다.
app.get('/req/:id', function(req,res){
var object = { param : req.params.id};
res.json(object);
});
// 8080으로 서버를 생성한다.
app.listen(8080);
// 서버 생성 완료 후 Hello Pi 문장과 서버 구동 경로를 출력한다.
console.log('Hello Pi! : ' + __dirname);
[ index.html ]
<script>
var sum = 10 + 5;
// 브라우저 화면에 sum 결과를 Hello Pi 문자와 함꼐 출력한다.
document.write('Hello Pi! : ' + sum);
</script>
이렇게 웹 서버를 구축하고, 웹 페이지와 웹 서비스를 호출하여 결과를 확인한다. 이제 주소를
치면, 작성한 자바스크립트가 클라이언트에서 실행되어 결과를 보여준다. /req/ 로 호출하면서
뒤에 값을 적어주면, 해당 값을 JSON 형태로 반환하는 것도 확인할 수 있다.
[그림] 받아진 웹 페이지를 클라이언트에서 계산한 결과와 서버에서 전달 받은 값을 JavaScript
객체로 보관했다 이를 그대로 JSON 타입으로 반환한 예
어떤가? 우리는 웹 페이지와 서비스를 제공하는 서버를 단지 몇 줄만으로 간단하게
구현하였다. 만일 이 예제를 자바로 구현하였다고 하면 준비할 것과 작성해야 할 량도 많았을
뿐더러, 클라이언트는 자바스크립트로, 서버는 자바로 언어가 분리되기 때문에 두 언어를
번갈아 가며 구현해야 하는 반면, 여기서는 동일한 언어로 서버와 클라이언트를 쉽게 구현함을
알 수 있다.
우리는 이 예제 에서 get 방식을 이용한 데이터 통신을 진행하였는데, get 방식 이외에 다양한
방식으로 데이터를 주고 받을 수 있다. Post, put, delete 인데 이 4 가지 메소드를 근래 REST
API, 즉 외부에서 정보를 주고 받는 API 를 만들 때 많이 사용하는 방식이다. DB 에서 사용하는
개념인 CRUD 즉, Create, Read, Update, Delete 의 개념을 이 4 가지 메소드에 접목하여
구현하는데, 권장하는 4 가지 메소드의 사용 용도는 다음과 같다.
메소드 get post put Delete
용도 읽기 (read) 생성 (create) 수정 (update) 삭제(delete)
주의 깊게 살펴본 사용자라면, 반환되는 함수에 있는 두 인자인 req, res 가 요청(Request)과
응답(Response)의 약자임을 알 수 있었을 것이다. 요청은 전달 받는 인자 값을 가져오고,
응답을 이용해 JSON 데이터를 보내거나 다운로드 받는 동작을 할 수 있도록 지원한다.
ExpressJS 의 요청과 응답 객체에서 사용할 수 있는 대표적인 값들은 다음과 같다.
종류 객체 설명
요청(Request) req.params 이름 붙은 라우트 매개 변수 값을 담고 있는 배열
Req.query GET 매개변수(QueryString) 값을 담고 있는 객체
Req.body POST 매개변수 값을 담고 있는 객체
Req.route 현재 일치하는 라우트 정보
Req.ip 클라이언트의 IP 주소
Req.path Protocol, Host, Port, Querystring 을 제외한 요청 경로
Req.host 클라이언트의 HOST 이름
Req.url Protocol, Host, Port 를 제외한 Querystring 과 요청 경로
응답(response) Res.redirect 브라우저 주소를 다른 주소로 재 지정
Res.send 클라이언트에 응답을 보냄
Res.json 클라이언트에 JSON 데이터를 보냄
Res.jsonp 클라이언트에 JSONP 데이터를 보냄
Res.download 컨텐츠를 보여주지 않고 다운로드 받게 함
Res.sendFile 파일을 읽어 컨텐츠를 클라이언트에 전송 함
Res.render 템플릿 엔진을 사용하여 뷰를 렌더링 함
빠르게 ExpressJS 예제 생성하기 – Express Generator
Express Framework 로 웹어플리케이션을 빠르게 만드는 방법은, 지원하는 템플릿 생성기를
이용하여 페이지를 만드는 것이다. 다음과 같이 node js 설치 이후에 npm 을 이용하여
express-genertator 를 설치하도록한다.
$ npm install express-generator -g
express-generator 가 설치 된 이후에는 express 명령어를 통해 Application 템플릿을 만들 수
있다. express 명령 이후에 만들고자 하는 application 이름을 입력하면 해당 이름으로 템플릿
어플리케이션이 생성된다.
$ express myapp
[그림] Express Generator 로 샘플을 생성하면, 간단하게 템플릿을 생성해 준다. 생성이 되고
나면, 연동되어 있는 모듈 설치 방법(install dependencies)과 실행 방법(run the app)을 알려
준다.
생성된 어플리케이션을 위해 express 이외에 추가적으로 필요한 패키지를 설치해 주어야 한다.
원래는 npm 명령을 이용하여 일일히 설치해야 겠지만, 생성된 Application 폴더로 이동하여,
npm install 명령을 입력해 주면 템플릿을 위해 필요한 패키지들을 자동으로 설치해 준다.
$ cd myapp
$ npm install
[그림] 자동으로 생성된 템플릿에 연관되어 있는 모듈을 자동으로 설치해 준다.
이렇게 모두 설치가 마무리 되면, 실행하는 일만이 남았다. 이전까지는 node 로 자바스크립트
파일을 직접 실행하는 구조였으나, Express Generator 로 생성된 템플릿은 npm start 명령으로
프로그램을 구동할 수 있다.
$ npm start
[그림] npm start 로 구동된 서버
Express 로 서버를 구동하는 것은 간단하게 할 수 있다.
실시간 웹 서비스를 만들어 보자 - Socket.IO
Socket.IO 는 ExpressJS 와 더불어 오늘날 Node,JS 를 가장 핫하게 이끌어준 프레임워크 중
하나이다. ExpressJS 가 웹 어플리케이션을 제작하는데 도움을 주었다면, Socket.IO 는
어플리케이션 상에서 실시간 양방향 커뮤니케이션을 손쉽게 구현할 수 있도록 도와 주는
프레임 워크이다. 과거 이러한 실시간 통신을 구현하기 위해서는 소켓(Socket) 통신이라고 하여
TCP/UDP 와 관련된 복잡한 통신 방식을 직접 구현해서 쓰고, 불 안정한 인터넷 망을 이용 시에
대한 복잡한 처리 모드 개발자의 몫 이였다. 하지만, NodeJS 의 Socket.IO 의 등장으로 사용
환경에 구속 받지 않고 모든 플랫폼, 브라우저, 장치에서 동작이 가능하고, 신뢰성과 속도
모두를 보장 한다. 실시간 채팅이나 공동 문서 협업, 실시간 분석 및 사진, 서버 알람(Push),
이미지나 비디오등의 스트리밍 전송에 사용될 수 있는 프레임 워크이다. 무엇보다 서버와
클라이언트의 코드가 모두 동일한 JavaScript 로 구현되어 소스의 이해 및 유지 보수가
간편하다는 것이 최대의 장점이다.
[그림] NodeJS 의 최대 장점 중 하나는 Socket.IO 를 이용하여 채팅과 같은 실시간 통신을
구현하기 매우 쉽다는 점이다.
우선 Socket.IO 를 사용하기 위해서는 모듈을 설치해 주어야 한다.
$ sudo npm install –g socketio
Hello Socket.IO
ExpressJS 와 Socket.IO 를 활용하여 간단하게 서버와 클라이언트간에 Hello World 를
실시간으로 주고 받는 서비스를 쉽고 빠르게 만들어 볼 수 있다.
[ sample6.js ]
var app = require('express')();
// http 서버를 생성한다.
var server = require('http').Server(app);
// 생성한 서버에 Socket.IO 서버를 구성한다.
var io = require('socket.io')(server);
app.get('/', function (req, res) {
res.sendFile(__dirname + '/sample6.html');
});
// Socket.IO 로 접근시 호출되며, news 라는 이름으로 접속된 소켓에 객체를
전달한다.
io.on('connection', function (socket) {
socket.emit('news', { hello: 'world1' });
// feedback 이라는 명칭으로 이벤트 수신시, 데이터를 콘솔에 출력한다.
socket.on('feedback', function (data) {
console.log(data);
});
});
server.listen(8080);
console.log('Hello Socket.IO');
[ sample6.html ]
<script src="/socket.io/socket.io.js"></script>
<script>
// 브라우저 상에서 소켓을 생성한다.
var socket = io();
// news 라는 이벤트를 받았을 때 호출된다.
socket.on('news', function (data) {
// 브라우저 상에서 소켓을 생성한다.
document.write(JSON.stringify(data));
socket.emit('feedback', { hello: 'world2' });
});
</script>
일단 서버쪽 Socket.IO 의 연결을 위한 구성부가 서버쪽에 추가되었음을 볼 수 있다.
Connection 이벤트로 접속이 성공된 것을 감지하면, Emit 함수를 이용하여 news 라는 이벤트
명으로 JavaScript Object 데이터를 전달하게 된다. 전달된 이벤트는 클라이언트 쪽에서 news
라는 이벤트를 감지하고, 받아진 JSON 데이터를 문자화 하여 화면에 보여준 후, 반대로 전달된
서버에 feedback 이라는 이벤트 명으로 데이터를 전송하게 된다. 이렇게 전송된 데이터는 서버
쪽에서 feedback 이벤트로 감지된 결과를 화면에 보여 주게 된다.
[그림] 서버가 구동한 후, 이벤트를 전송하고 나서 모바일로 부터 feedback 이벤트로 { hello :
world2 } 데이터를 전송 받았다.
[그림] 웹 페이지가 열리고, 서버로부터 받은 news 이벤트로 { hello : world1 } 데이터를 받고,
서버로 { hello : world2 } 데이터를 전송 한다.
Smart Phone 부터 PC 까지 가능한 초 간단 채팅 서버 구현
NodeJS 를 기반으로 ExpressJS 로 웹 서버를 구성하고, Socket.IO 와 연동되는 실시간 데이터
통신을 진행해 보았다. Socket.IO 를 이용하여 많이 사용하는 기능 중 하나가 채팅 기능일
것이다. 이는 지능형 서비스를 만들 때 꽤나 유용하게 사용될 수 있다. 라즈베리파이를 서버로
사용하면서, 채팅 서비스를 만든다면, LG 사의 IoT 제품같이 채팅으로 명령을 하면, 실시간으로
그에 맞추어 동작하는 IoT 제품을 우리도 손쉽게 만들 수 있다는 것을 의미한다. 여기서
간단하게 채팅 서버를 구현해 보도록 하자. 메시지를 보내는 방식에 따라 어떠한 결과가
나오는지 잘 살펴보도록 한다. 클라이언트 구현을 위해 이제까지는 순수한 JavaScript 를
활용했지만, 이번에는 JavaScript 를 쉽게 사용할 수 있게 해 주는 jQuery 를 이용하여 웹상에서
일어나는 동작을 좀더 간단하게 만들 것이다.
[ sample7.js ]
Var app = require('express')();
var server = require('http').Server(app);
var io = require('socket.io')(server);
app.get('/', function (req, res) {
res.sendFile(__dirname + '/sample7.html');
});
io.on('connection', function (socket) {
socket.emit('msg', { id : 'hi', msg : 'Chat is ready!' });
// msg1 이벤트 발생시, 접속되어 있는 소켓중 보낸 소켓을 제외한 소켓에
데이터를 전송한다.
socket.on('msg1', function (data) {
socket.broadcast.emit('msg', data);
});
// msg2 이벤트 발생시, 보낸 소켓에 다시 데이터를 전달한다.
socket.on('msg2', function (data) {
socket.emit('msg', data);
});
// msg3 이벤트 발생시, 모든 소켓에 데이터를 전달한다.
socket.on('msg3', function (data) {
io.emit('msg', data);
});
});
server.listen(8080);
console.log('Socket.IO Chat');
[ sample7.html ]
<html>
<head>
<title>Hello Pi!</title>
<script src="https://code.jquery.com/jquery-
2.1.4.min.js"></script>
<script src="/socket.io/socket.io.js"></script>
<script>
$(function(){
var socket = io();
// 0 부터 10 까지의 임의의 사용자 아이디를 생성한다.
var id = 'guest' + Math.floor((Math.random() * 10) + 1);
// 메시지 수신시 화면에 전달받은 ID 와 메시지를 추가한다.
socket.on('msg', function (data) {
$('#msgs').append($('<p>').text('S ' +data.id + ' :
' + data.msg));
});
// send1 버튼을 클릭시 발생한다.
$('#send1').click(function(){
// 화면에 입력된 값을 msg1 이벤트 명으로 전달한다.
socket.emit('msg1', { id : id, msg :
$('input').val()});
// 화면에 전달한 정보를 출력한다.
$('#msgs').append($('<p>').text('C ' +id + ' : ' +
$('input').val()));
// 입력창의 값을 지운다.
$('input').val('');
});
$('#send2').click(function(){
// 화면에 입력된 값을 msg2 이벤트 명으로 전달한다.
socket.emit('msg2', { id : id, msg :
$('input').val()});
$('#msgs').append($('<p>').text('C ' +id + ' : ' +
$('input').val()));
$('input').val('');
});
$('#send3').click(function(){
// 화면에 입력된 값을 msg3 이벤트 명으로 전달한다.
socket.emit('msg3', { id : id, msg :
$('input').val()});
$('#msgs').append($('<p>').text('C ' + id + ' : ' +
$('input').val()));
$('input').val('');
});
});
</script>
</head>
<body>
<input type='text' />
<p/>
<button id='send1'>SEND1</button>
<button id='send2'>SEND2</button>
<button id='send3'>SEND3</button>
<div id='msgs'>
</div>
</body>
</html>
라즈베리파이에서 서버를 구동한 후, 2 개의 단말을 이용하여 서버에 접속해 보자. 필자는
스마트 폰과 노트북을 이용하여 접속하였는데, 접속이 완료되면 서버에서 Chat is ready 라는
메시지를 전달하는데, 클라이언트에서 그 메시지를 수신하여 화면상에 보여 준다.
스마트폰에서 메시지를 입력하고 SEND1 을 누르면 서버의 msg1 이벤트가 동작하여, 자신을
제외한 접속되어 있는 단말에 메시지가 전달되는 것을 알 수 있다. (만일 더 많은 단말을
연결하면, 그 단말에서도 정상적으로 메시지가 전송됨을 알 수 있다.) 두 번째 메시지를 입력 후
SEND2 를 누르면, 서버의 msg2 이벤트가 동작되는데, 자기 자신에게만 다시 메시지가
전달된다. 마지막으로 SEND3 누르면, 자기자신을 포함한 모든 클라이언트에 메시지가
전달되는 것을 알 수 있을 것이다.
[그림] 모바일로 메시지를 차례대로 보내보면, 첫번째 전송은 목적지에만, 두번째 전송은 자기
자신에게만, 세번째 전송은 자신을 포함한 모두에게 데이터가 전달 됨을 알수 있다.
이번 장을 정리하며
이번 장에서는 라즈베리파이에서 NodeJS 를 사용하는 법을 배웠다. 웹 프레임워크인 ExpressJS
로 서버를 구축하고, Socket.IO 를 이용하여 웹 페이지와 실시간 통신 하는 법을 다루었다.
서버와 웹 모두 자바스크립트로 구현되어, 각각 다른 언어로 구현된 것 보다 빠르게 개발하고
빠르게 적용해 볼 수 있는 것을 직접 확인할 수 있었을 것이다. 다음장에서는 IoT 의
시작이라고 할 수 있는 다양한 센서들을 NodeJS 를 활용하여 동작시키고 센서 값을 감지하는
방법을 다루도록 한다.
4. 거리를 측정하고, 정보를 표시 하기 - GPIO
라즈베리파이에서 하드웨어 제어를 가능하게 하는것, 그 이름 GPIO
라즈베리파이가 컴퓨터의 역할을 하는 초소형 보드이지만, 일반 컴퓨터와 구분지어 주는
특징중 하나는 GPIO 의 유무일 것이다. GPIO 는 General Purpose Input/Output 의 약자로서,
이 것을 이용하여 외부의 센서나 장치들과 정보를 주고받을 수 있게 됩니다. 즉, LCD 에 정보를
보여줄 수 있도록 쓸수 있고(라즈베리파이 입장에서는 Output), 온습도 센서로 부터 정보를
읽어올 수(라즈베리파이 입장에서는 Input) 있는것은 모두 GPIO 가 있기에 가능한 것이다.
라즈베리파이에서 GPIO 사용을 쉽게! – GPIO 사용 준비
라즈베리파이를 이용하여 개발할때 모니터,키보드,마우스를 직접 연결하여 개발하거나,
접속하여 원격으로 개발할 수 있다. 하지만 일반 사용자는 음악이 검색되고, 다운로드
받아지고, 재생되는 정보를 표시할 곳이 라즈베리파이에는 존재하지 않는다. 따라서,
라즈베리파이에 LED, LCD 를 연결하여 우리가 원하는 정보를 화면에 보여주자. 아울러 온습도
센서를 이용하여 주변환경 또한 모니터링해 볼 수 있다.
라즈베리파이를 잘 살펴보면 40 개의 PIN 이 노출되어 있는 것을 볼 수 있다. 이를 통하여
외부의 센서로부터 정보를 받고, 장치를 제어하는 용도로 사용하게 된다.
[그림] 라즈베리파이의 GPIO 배치 전원 공급부터 정밀한 제어 등 다양한 목적의 입출력 핀이
제공 된다.
라즈베리파이에서 GPIO 를 제어할 수 있는 방법중 대표적인 것은 WiringPI 를 이용하는
것입니다. 하지만, 원래의 WiringPi 가 C 언어로 이루어져 있어서, NodeJS 에서는 이를 그대로
사용하기에는 어려움이 있는데, NodeJS 용으로 사용할 수 있는 WiringPi 를 설치하여 사용할
수 있다.
설치방법
$ sudo apt-get install git-core
$ sudo npm install wiring-pi
[그림] WiringPi 에서 지원하는 GPIO 번호
WiringPi 를 이용하여 하드웨어를 제어할 때 지정하는 pin 번호가 처음 보았던 pin 번호와 다른
것을 알 수 있다. 처음 본 핀 번호는 라즈베리파이에 탑재된 프로세서 제조사에서 제공하는
공식적인 입출력 핀 번호이다. WiringPi 는 라즈베리파이와 아두이노와의 연결 등을 고려하여
WiringPi 상 에서 호출되는 핀 번호가 제조사에서 제공하는 공식 핀 번호와는 차이가 있다.
입출력 연결할 때 이를 유의 하여 사용하도록 해야 한다.
하드웨어 계의 Hello World – LED 깜빡이기
WiringPI 로 LED 깜빡이기 만들기
하드웨어 계의 Hello World 인 LED 켯다 끄기를 진행해 보자. 우리가 설치한 WiringPi 로 LED
를 켜고 끌 수 있다. 일반적으로 컴퓨터가 인식하는 것은 전기 신호가 있고(1 or true), 없음(0 or
false) 이다. GPIO 를 일반적으로 제어할 때도 동일하게 전기 신호가 있고 없음을 지정하는 것
만으로 LED 를 켜고 끌 수 있게 된다. 다음 코드를 작성하여 구동해 보도록 한다.
[ ch5_01.js ]
// wiring pi 를 활성화 한다.
var wpi = require('wiring-pi');
wpi.setup('wpi');
// 0 번 핀을 출력모드로 설정한다.
var pin = 0;
wpi.pinMode(pin, wpi.OUTPUT);
var value = 1;
// 1초마다 0번 핀에 0과 1을 입력한다.
setInterval(function() {
wpi.digitalWrite(pin, value);
value = +!value;
}, 1000);
다음 코드는 GPIO 0 번 핀을 출력 모드로 설정한 후, 1 초 간격으로 출력 값을 0 과 1 을 번
갈아서 왔다 갔다 하게 하는 방법으로, LED 를 켰다 끌 수 있게 된다. LED 의 경우, 2 개의
다리로 이루어져 있는데, 긴 다리가 양극(+) 이고, 짧은 다리가 음극(-) 이다. + 십자가의 두
막대를 이으면 길어 지니, 긴 다리가 + 라고 생각하면 쉽다. 양극을 GPIO 0 에 연결하고,
음극은 라즈베리파이의 어떠한 GROUND 에 연결해도 된다. 연결이 끝난 후 코드를 실행하여
결과를 확인해 보도록 한다..
[그림] LED 제어를 위한 배선, LED 의 긴 다리를 GPIO 0 에, 짧은 선을 GROUND 에 연결한 후
코드를 실행하면 LED 가 깜빡임을 볼 수 있다.
모바일로 LED 깜빡이기 만들기
우리는 간단하게 1 개의 LED 를 껐다 켰다 하는 방법을 배웠다. 최종적으로 우리가 개발할
파이오(piAu)는 스마트 폰으로 제어하는 것이다. LED 제어를 좀더 발전시켜, NodeJS 의
ExpressJS 를 활용해, 스마트 폰으로 LED 를 껐다 켜는 예제로 발전시켜 볼 것이다. 한 개의
LED 는 상시로 켜지고, 2 개의 LED 는 스마트 폰으로 제어가 되는 초 간단 IoT 라이트를 만들어
보도록 한다.
우선, 이번 예제를 위해 추가적으로 설치해야 할 bodyParser 모듈을 설치해 주도록 한다.
$ npm –g install body-parser
이번 실험을 위해 다음과 같이 배선을 연결하도록 하자.
[그림] 배선 연결
[ ch5_02.js ]
var express = require('express');
// post 방식을 사용시, body 영역의 전달 값을 확인하는데 사용된다.
var bodyParser = require('body-parser');
var os = require( 'os' );
var led = require('./ch5_02_led');
var app = express();
// body 데이터 형식에서 JSON 방식을 지원한다.
app.use(bodyParser.json());
// 다국어 지원을 위해 URLENCODE 방식을 지원한다.
app.use(bodyParser.urlencoded({ extended: true }));
app.listen(8080);
app.get('/', function(req, res){
res.sendFile(__dirname + '/ch5_02.html')
});
app.post('/setLED', function(req ,res){
console.log(req.body);
// POST 방식으로
var pin = req.body.pin;
var value = req.body.value;
// 지정한 핀에 지정한 값을 입력한다.
if(pin && value){
led.setLED(parseInt(pin), parseInt(value));
}
res.json({ result : true });
});
console.log('LED Server’);
서버가 될 코드는 ExpressJS 로 외부접속 페이지를 반환하고, POST 방식으로 setLED 가 호출이
되면, 요청에 따라 LED 를 켜고 끄는 동작을 수행하도록 한다. 처음으로 웹페이지를 ExpressJS
로 했을때와는 몇가지 코드가 증가하였는데, bodyParser 부분은 POST 방식으로 데이터를 전달
할 때 해당 데이터를 읽어서 활용할 수 있도록 도와 주는 모듈이다. setLED 에서 req.body.pin,
req.body,value 식으로 값을 가지고 올 수 있는 것은 이 모듈을 ExpressJS 에서 사용할 수
있도록 설정하였기 때문이다. setLED 함수 호출시 pin 과 value 값을 parseInt 함수로 값을
전달하는데, POST 방식 전달 시 숫자 값이 문자로 전환되기 때문에 이 값을 다시 숫자
값(Integer) 로 전환하여 정상적으로 동작할 수 있도록 변환시켜 준다.
[ ch5_02_led.js ]
var wpi = require('wiring-pi');
wpi.setup('wpi');
var pins = [];
// 핀과 입력 값을 인자로 받는 함수를 작성한다.
exports.setLED = function(pin, value){
console.log(pin + ' / ' + value);
// 처음 호출되는 경우, 쓰기 모드로 초기화 한후, 핀번호를 배열에 포함한다.
if(pins.indexOf(pin) < 0){
console.log('add new');
wpi.pinMode(pin, wpi.OUTPUT);
pins.push(pin);
}
// 요청한 핀에 요청받은 값을 입력한다.
wpi.digitalWrite(pin, value);
}
LED 제어는 공통적으로 사용될 수 있어서 별도의 모듈 파일로 분리하였다. 입력 받은 pin
번호의 GPIO 를 value 에 전달 받은 값으로 쓰는 역할을 한다. 0 과 1 이 전달되어 OFF, ON
효과를 LED 로 확인할 수 있다.
[ ch5_02.html ]
<html>
<head>
<title>SET LED</title>
<script src="https://code.jquery.com/jquery-
2.1.4.min.js"></script>
<script>
$(function(){
// 0 번 핀의 LED 를 켜기 위헤 명령을 호출한다.
$('#led1on').click(function(){
$.post( "setLED", { pin: 0, value: 1 });
});
// 1 번 핀의 LED 를 켜기 위헤 명령을 호출한다.
$('#led2on').click(function(){
$.post( "setLED", { pin: 1, value: 1 } );
});
// 2 번 핀의 LED 를 켜기 위헤 명령을 호출한다.
$('#led3on').click(function(){
$.post( "setLED", { pin: 2, value: 1 } );
});
// 1 번 핀의 LED 를 끄기 위해 명령을 호출한다.
$('#led1off').click(function(){
$.post( "setLED", { pin: 0, value: 0 } );
});
// 2 번 핀의 LED 를 끄기 위해 명령을 호출한다.
$('#led2off').click(function(){
$.post( "setLED", { pin: 1, value: 0 } );
});
// 3 번 핀의 LED 를 끄기 위해 명령을 호출한다.
$('#led3off').click(function(){
$.post( "setLED", { pin: 2, value: 0 } );
});
})
</script>
</head>
<body>
<p>
<span>LED1</span>
<button id='led1on'>ON</button>
<button id='led1off'>OFF</button>
</p>
<p>
<span>LED2</span>
<button id='led2on'>ON</button>
<button id='led2off'>OFF</button>
</p>
<p>
<span>LED3</span>
<button id='led3on'>ON</button>
<button id='led3off'>OFF</button>
</p>
</body>
</html>
웹 페이지는 코드가 증가하였다. 각 LED 의 버튼의 ID 에 맞는 click 이벤트를 jquery 를
이용하여 정의하였다. ON 클릭 시, 해당 pin 에 1 값을 POST 로 전달하고, OFF 클릭 시 0 값을
전달하는 역할을 하게 된다.
코드 작성이 완료된 후 다음과 같이 구동하고, 스마트 폰이나 컴퓨터로 웹 페이지에 접근 한 후
ON, OFF 버튼을 눌러, LED 가 제대로 켜고 꺼지는 지 확인해 보도록 하자.
$ sudo node ch5_02_led.js
One More Thing – 처음부터 계속 점등하기
기본색은 기본 백라이트 처럼 계속 불빛이 나오는 상태로 유지가 되어야 한다. 일반 GPIO
에 on 신호를 주어 동작시킬수도 있지만, 기본적으로 출력이 나오는 3.3v 단자에 연결해
보자. 설정과 상관없이 지속적으로 불빛이 나올 것이다.
[그림] LED 를 제어하는 리모콘이 완성 되었다.
모바일 밝기 조절 LED 만들기 – PWM 사용
스마트 폰을 이용하여 단순히 불을 껐다 켜는 장치를 구현해 보았다. 기능을 좀더 발전시켜
보자, 우리가 쓰는 전구 중 불 밝기를 조절하는 제품을 본 기억이 있을 것이다. 그 역시 구현할
수 있는데, PWM(Pulse-Width Modulation) 이라는 방식을 사용하는 것이다. PWM 은 쉽게
전원스위치를 껐다 켰다를 반복한다고 생각하면 된다. 켜지는 시간이 꺼지는 시간보다
길어지면 밝아지고, 반대로 꺼지는 시간이 켜지는 시간보다 길어지면 어두워 지는 것이다.
[그림] PWM 은 스위치를 껐다 켰다를 반복한다고 생각하면 된다.
라즈베리파이에서는 wiringPi 기준으로 1 번 Pin 과 24 번 Pin 이 PWM 을 지원하는 단자이다.
24 번 핀에 양극을 연결하고 PWM 코드를 작성하여 불이 밝아졌다가 어두워 지는 것을 확인해
보자.
[그림] GPIO24 와 GROUND 를 LED 에 연결
[ ch3_3_pwm_hw.js ]
var wpi = require ('wiring-pi');
wpi.setup('gpio');
wpi.wiringPiSetup();
// 24번 핀을 HW PWM 모드로 설정한다.
wpi.pinMode (24 , wpi.PWM_OUTPUT );
wpi.pwmSetMode ( wpi.PWM_MODE_MS );
// PWM 설정 값을 초기화 한다.
wpi.pwmSetClock ( 400 );
wpi.pwmSetRange ( 1024 );
var num = 0;
var isRight = true;
// 0.1초 간격으로 서보모터가 좌우로 움직이도록 값을 조정한다.
setInterval (function(){
wpi.pwmWrite( 24 , num);
if (isRight){ num += 1; }
else { num -= 1; }
if (num === 120){ isRight = false; }
if (num === 0) { isRight = true ; }
}, 100);
PWM 을 이용하여 LED 의 불이 서서히 밝아졌다가 서서히 어두워 지는 것을 알수 있다. 하지만,
라즈베리파이에서 이 방식을 이용해서 하드웨어를 제어할 때 한가지 문제점을 발견하게 된다.
그것은, 3.5mm 잭에 오디오를 연결해서 들으면 PWM 이 증가함에 따라 스피커에서 노이즈가
크게 발생하는 것을 발견할 수 있다. 이것은 내부적으로 오디오 시스템과 PWM 이
라즈베리파이에서 공유되고 있는 전원을 사용하고 있어, HW 방식의 PWM 을 사용하면, 오디오
쪽에 노이즈를 유발시키게 됨을 의미한다. 오디오를 만드는데 노이즈가 발생하면 안되니 다른
방법을 사용하도록 한다. PWM 구동 방식을 보면 처음에도 설명했듯이, 스위치를 껐다 켰다
하는 것과 비슷하다고 언급한 바 있다. 이는 소프트웨어로 유사하게 구현할 수 있음을
의미하고, wiringPi 에서는 친절하게 소프트웨어 방식의 PWM 또한 제공하고 있다. 다만,
소프트웨어 방식이기 때문에 HW 방식에 비해 정밀도가 떨어지는 문제가 있지만, 우리가
사용하려는 LED 를 켜고 끄는데는 충분히 사용할 수 있다. 이번에는 동일하게 밝아졌다
흐려졌다 하는 동작을 소프트웨어 방식을 이용하여 구현하도록 한다.
[ ch3_3_pwm_sw.js ]
var wpi = require ('wiring-pi');
wpi.setup('gpio');
wpi.wiringPiSetup();
// 소프트웨어 방식으로 24번 핀을 PWM 으로 초기화 하고, 초기값을 설정한다.
wpi.softPwmCreate (24, 0, 200) ;
var num = 0;
var isRight = true;
// 0.1초 간격으로 서보모터가 좌우로 움직이도록 값을 조정한다.
setInterval (function(){
wpi.softPwmWrite (24, num) ;
if (isRight){ num += 1; }
else { num -= 1; }
if (num === 24){ isRight = false; }
if (num === 0) { isRight = true ; }
}, 100);
소프트웨어 방식으로도 하드웨어 방식에 비해 정밀도는 떨어지지만, 사운드 시스템에 노이즈
없이 밝기를 조절하는데 충분함을 발견할 수 있다. 이를 응용하여, 스마트 폰으로 밝기를
조절할 수 있도록 만들어 보자. 간단한 여러분만의 스마트 전등이 완성될 것이다.
[ ch3_3_server.js ]
var express = require('express');
var bodyParser = require('body-parser');
var wpi = require ('wiring-pi');
var app = express();
wpi.setup('gpio');
wpi.wiringPiSetup();
wpi.softPwmCreate (24, 0, 200) ;
app.use(bodyParser.json());
app.use(bodyParser.urlencoded({ extended: true }));
app.listen(8080);
app.get('/', function(req, res){
res.sendFile(__dirname + '/ch3_3.html')
});
// 불을 켜고 끄는데 호출된다.
app.post('/setLight', function(req ,res){
console.log(req.body);
var value = req.body.value;
// 입력 값이 있는경우, 소프트 PWM 방식으로 값을 입력한다.
if(value){
wpi.softPwmWrite (24, parseInt(value));
}
// 리턴 값을 반환한다.
res.json({ result : true });
});
console.log('LED Light Server');
라즈베리파이의 서버에서는 PWM 값을 전달 받아 GPIO 에 기록하게 된다. 단, 모바일로부터
전달 받는 숫자 값은 Ajax 통신을 거치면서 문자 값으로 바뀌게 된다. parseInt 함수를
사용하여, 문자 값을 숫자 값으로 전환 한 후 사용하도록 한다.
[ch3_3.html ]
<html>
<head>
<title>SET LED</title>
<script src="https://code.jquery.com/jquery-
2.1.4.min.js"></script>
<script>
$(function(){
// 슬라이더의 값이 변경되는 경우 호출된다.
$('#range').change(function(){
// 서버의 setLight 로 변경된 값을 전달한다.
$.post( "setLight", { value: $(this).val() });
});
})
</script>
</head>
<body>
<span>LIGHT</span>
<input type="range" id="range" min="0" max="240" value="0"
/>
</body>
</html>
Range 컴포넌트의 값이 변경되는 경우 이벤트가 감지되고, 변경 된 값을 라즈베리파이에
전달하게 된다. 모바일에서 슬라이더를 조정하여 밝기를 조절해 보자.
[그림] PWM 을 적용한 LED 를 스마트 폰을 이용하여 불빛의 밝기를 조절할 수 있다.
One More Thing – PWM 을 이용하여 서보 모터 제어하기
이 책에서 다루는 범위는 움직이는 물체가 아닌, 고정된 IoT 서비스를 만들어 보는 것을
다루고 있다. 하지만, 자동 스위치나 로봇과 같이 움직이는 IoT 서비스를 만들고자 하는
독자도 있을 것이다. 이런 경우는 PWM 을 응용하여 서보 모터(Servo Moter)를 제어하는
것으로 그 기능을 구현할 수 있다. 소프트웨어 방식의 PWM 을 사용해 보았으나, 실제 서보
모터 같은 장치를 정밀하게 제어하는데는 어려움이 있다. 라즈베리파이 B+ 이상의 모델은
2 개의 하드웨어 PWM 을 지원한다. 관절이 있는 로봇을 만들기 위해서는 더 많은 PWM
제어 핀이 필요한데, 여러 개를 제어할 수 있는 별도의 PWM 서보 드라이버 장치가
필요하다.
물체와의 거리를 측정해 보자 – 초음파 센서
초음파 센서는 초음파를 보내고 반사된 초음파를 받아 그 시간차로 거리를 측정하는데
사용된다. 흔히 로봇 청소기가 움직이다가 벽을 만나면 멈추어 벽이 없는 쪽으로 움직이는
시나리오는 이러한 초음파 센서를 응용하여 구현할 수 있고, 사람이 근접하는 상황에 따라
동작하는 시나리오에서도 유용하게 사용할 수 있다. 가격이 매우 저렴하고 범용적으로
사용하기 좋아 많이 활용되는 센서이다. 초음파 센서는 4 개의 핀이 제공되는데, 전원
공급(5v)와 접지(Ground) 용도로 2 핀이 사용되고, 초음파를 보내고(Trigger), 받는데(Echo) 두
개의 핀이 사용된다.
[그림] 초음파를 발산하면, 반사된 시간차를 이용하여 거리를 측정한다.
[ ch5_03.js ]
var wpi = require('wiring-pi');
var sleep = require('sleep');
// 시간 측정을 위한 모듈을 호출한다.
var microtime = require('microtime');
var trig = 4;
var echo = 5;
// 트리거는 출력, 에코는 입력핀으로 GPIO를 설정한다.
wpi.setup('wpi');
wpi.pinMode(trig, wpi.OUTPUT);
wpi.pinMode(echo, wpi.INPUT);
setInterval(function(){
// 트리거로 펄스를 생성한다.
wpi.digitalWrite(trig, wpi.LOW);
sleep.usleep(2);
wpi.digitalWrite(trig, wpi.HIGH);
sleep.usleep(20);
wpi.digitalWrite(trig, wpi.LOW);
var count1 = 0;
var count2 = 0;
// 에코 핀으로 LOW 값을 전달 받는다.
while(wpi.digitalRead(echo) == wpi.LOW){
if(count1++ > 1000){ break; }
}
var start_time = microtime.now();
// HIGH 값을 전달받으면, 반사 신호를 받은 것이다.
while(wpi.digitalRead(echo) == wpi.HIGH){
if(count1++ > 10000){ break; }
}
// 시간차를 구해서 거리를 계산한다.
var time = microtime.now() - start_time;
console.log('distance : ' + Math.round(time / 58) + 'cm');
}, 1000);
GPIO4 번을 Trigger 로, 5 번을 Echo 로 설정한 후, 매 1 초마다 초음파 센서로 거리를 측정하여
콘솔에 보여주는 프로그램을 완성하였다. SR04 센서 스펙 상 최소 2cm 부터 4m 까지 측정이
가능하다. 손바닥을 앞에 가져다 대서 거리 측정이 제대로 되는지 확인해 본다.
위의 코드가 복잡하다면, 이미 만들어 진 노드 패키지를 이용하여 구현할 수 있다. 동일한 핀을
사용하지만, wiringPi 가 아닌, 내부 핀 번호를 사용한다는 점을 유의해야 한다. 핀 번호가 일반
헤더 번호인 24,23 으로 사용해야 한다. 우선 r-pi-usonic 패키지를 설치 한 후 코드를 실험해
본다.
$ sudo npm install –g r-pi-usonic
[ ch5_03_2.js ]
// 초음파 센서용 모듈을 호출한다.
var usonic = require('r-pi-usonic');
usonic.init(function(){
// 에코와 트리거 핀을 초기화 한다.
var sensor = usonic.createSensor(24,23); // Echo, Trigger
// 1초 간격으로 거리 값을 측정한다.
setInterval(function(){
console.log('distance : ' + Math.round(sensor()) + 'cm');
},1000);
});
복잡한 코드 없이 손쉽게 거리를 측정함을 알 수 있다. Trigger 는 일종의 신호 이므로 출력,
Echo 는 마이크의 에코 입력과 같이 입력 값이라고 생각하면, 초음파 센서의 구성을 쉽게
이해할 수 있다.
One More Thing –선 색깔로 구분하는 센서 연결
센서 연결시, Vcc, Ground, 기타 GPIO 배선으로 이루어져 있는 것을 볼 수 있다. 연결시
혼동을 방지하기 위해 통상 Vcc 는 적색 계열, Ground 는 무채색 계열, 기타 GPIO 는 기타
유색 계열을 사용하여 연결하면, 시각적으로 배선을 확인하는데 도움이 된다.
버튼 없이 손으로 제어하는 LED 만들기
이번에는 좀더 확장된 시나리오를 적용해 보도록 하자. LED 에제의 경우 LED 를 껏다 켜는
용도로 모바일을 사용하였기 때문에 일반 웹 방식으로 데이터를 전달했지만, 이번의 경우
라즈베리파이, 즉 서버에서 측정된 값을 모바일로 전달해 주는 방식을 사용한다. 이러한 경우에
Socket.IO 에서 예를 들었던 PUSH 의 사례를 사용할 수 있다. Socket.IO 를 응용하여 측정된
거리가 모바일 화면상에 보여질 수 있도록 하며, 거리가 50cm 미만일 경우에는 장착된 led 가
점등되도록 구성해 본다.
[ ch5_04.js ]
var app = require('express')();
var server = require('http').Server(app);
var io = require('socket.io')(server);
var usonic = require('./ch5_04_usonic');
var led = require('./ch5_04_led');
var port = 8080;
app.get('/', function(req, res){
res.sendFile(__dirname + '/ch5_04.html')
});
io.on('connection', function (socket) {
setInterval(function(){
// 거리 값을 측정한다.
var distance = usonic.getDistance();
console.log('Distance : ' + distance + 'cm');
// 측정거리가 50cm 미만인 경우, LED를 켠다.
if(distance < 50){
led.on();
// 측정거리라 50cm 이상인 경우, LED를 끈다.
} else {
led.off();
}
socket.emit('usonic', { distance : distance });
}, 1000);
});
server.listen(port);
console.log('UltraSonic Push Server');
1 초마다 거리를 측정하여, 50 츠 미만일 때 led 를 켜는 함수를 호출하고, 그렇지 않으면 끄는
함수를 호출한다. 측정된 거리는 모바일에서 확인할 수 있도록, emit 함수를 이용하여 거리를
전달한다.
[ch5_04_usonic.js ]
var wpi = require('wiring-pi');
var sleep = require('sleep');
var microtime = require('microtime');
var trig = 4;
var echo = 5;
wpi.setup('wpi');
wpi.pinMode(trig, wpi.OUTPUT);
wpi.pinMode(echo, wpi.INPUT);
exports.getDistance = function(){
wpi.digitalWrite(trig, wpi.LOW);
sleep.usleep(2);
wpi.digitalWrite(trig, wpi.HIGH);
sleep.usleep(20);
wpi.digitalWrite(trig, wpi.LOW);
var count1 = 0;
var count2 = 0;
while(wpi.digitalRead(echo) == wpi.LOW){
if(count1++ > 1000){ break; }
}
var start_time = microtime.now();
while(wpi.digitalRead(echo) == wpi.HIGH){
if(count2++ > 1000000){ break; }
}
var time = microtime.now() - start_time;
return Math.round(time / 58);
}
실제 거리를 측정하는 함수로서, 파형을 생성하는 trigger 핀은 출력으로 설정 하고, echo 핀은
입력으로 설정하여 반사된 값이 올 때까지의 시간을 측정한다. 측정된 시간을 이용하여 거리를
환산하여 반환한다.
[ ch5_04_led.js ]
var wpi = require('wiring-pi');
wpi.setup('wpi');
var pin = 0;
wpi.pinMode(pin, wpi.OUTPUT);
// 입력 받은 핀에 HIGH 값을 입력한다. (LED ON)
exports.on = function(){
wpi.digitalWrite(pin, wpi.HIGH);
}
// 입력 받은 핀에 LOW 값을 입력한다. (LED OFF)
exports.off = function(){
wpi.digitalWrite(pin, wpi.LOW);
}
LED 를 간단하게 켜고 끄는 함수를 구현한다. LED 를 켜기 위해 HIGH 신호 (1) 을 주고, 끌 때는
LOW 신호 (0)을 GPIO 에 쓰도록 한다.
[ ch05_04.html ]
<html>
<head>
<title>Ultra Sonic</title>
<script src="https://code.jquery.com/jquery-
2.1.4.min.js"></script>
<script src="/socket.io/socket.io.js"></script>
<script>
$(function(){
var socket = io();
// 서버로부터 전달받은 변경된 거리 값을 화면에 출력한다.
socket.on('usonic', function(data){
$('#distance').text(data.distance);
})
})
</script>
</head>
<body>
<h1>UltraSonic Sensor distance</h1>
<h3><span id='distance'>0</span><span>cm</span></h3>
</body>
</html>
라즈베리파이로부터 전달된 거리 값을 모바일에 출력해 준다. Socket.ip 를 이용하여
실시간으로 전달 되므로, 초음파 센서에 손을 가까히 접근하여 거리 값이 제대로 측정되는 지
확인해 볼 수 있다.
[그림] 1 초마다 거리가 측정된 값이 갱신 된다.
4 Digit 7 Segment LCD – 화면에 문자 표시하기
7 segment led 의 경우 7 개의 막대로 숫자를 표현하며, 1 개의 점이 추가적으로 구성되어
일반적으로 단순한 글씨와 숫자를 표현하는 데 사용이 된다. 4 개 문자 판에 총 8 개의 LED 가
있는 구조이므로, 32 개의 GPIO 선을 이용하여 제어를 하는 것이 맞지 않나 생각이 들 것이다.
이러한 경우 일일히 모든 LED 를 제어하기 어렵기 때문에 I2C(Inter Intergrated Circuit) 이나
SPI (Serial Peripheral Interface) 방식의 인터페이스로 통신이 가능한 규격이다. 우리가 이번에
사용할 7 Segment LCD 는 SPI 통신 방식을 지원하는 부품을 사용하게 될 것이다. 연결을 위해
5v 전원과 Ground 외에 여러 핀이 포함되어 있는데, 그 용도는 다음과 같다.
약자 본명 용도
SCLK Serial Clock 마스터가 출력하는 동기용 클럭
MOSI Master Output Slave Input 마스터의 출력이며 슬레이브에 입력
MISO Master Input Slave Output 슬레이브의 출력이 마스터에게 입력
SS(or CE) Slave Select 마스터의 출력으로 슬레이브를 선택하기위한
신호
제공되는 핀 중에 MISO 즉, Slave 가 되는 7 Segment 4 Digit 의 출력이 Master 즉,
라즈베리파이에 전달될 부분이 없기 때문에, 선을 연결하지 않아도 동작 된다.
[그림] 7 Segment 4 Digit 연결도 7 Segment 는 8 개의 LED 로 구성되어 있어, 이를 응용하여
다양한 기호를 직접 만들어서 사용할 수 있다.
NodeJS 에서 제어하기 위해 SPI 통신 방식을 지원하는 모듈 설치를 다음과 같이 한다.
$ npm install spi
7 Segment 에서 SPI 를 동작하게 하는 칩으로, 부품 뒷면을 보면 max7219 칩셋이 사용됨을 알
수 있다. 7 Segment 뿐만 아니라, Dot-Matrix 방식의 제품에서도 많이 사용되는 칩셋으로 해당
드라이버를 설치하여 7 Segment 를 제어해 보도록 한다.
$ sudo npm install git+https://git@github.com/victorporof/MAX7219.js
[ ch05_5.js ]
// 7세그먼트를 위한 max7219 모듈을 불러온다.
var MAX7219 = require('max7219');
// 첫번째 SPI 를 활성화한다.
var disp = new MAX7219("/dev/spidev0.0");
disp.setDecodeAll();
// 4개 문자를 표현할 수 있는 7세그먼트를 초기화 한다.
disp.setScanLimit(4);
// 7세그먼트 밝기를 조정한다. 값은 0 부터 15까지 이다.
disp.setDisplayIntensity(15);
disp.startup();
// 해당 위치의 7세그먼트에 숫자를 표현한다.
disp.setDigitSymbol(0, 0);
disp.setDigitSymbol(1, 3);
disp.setDigitSymbol(2, 1);
disp.setDigitSymbol(3, 4);
기본적으로 제공하는 밝기가 환하지 않아, setDisplayIntensity 함수를 이용하여 최대한 밝게
표시가 되도록 수정하였다. 화면상에 0314 가 표시되는 것을 확인할 수 있을 것이다.
디스플레이에 7 가지 문자를 표시하는 영역 이외에 점이 표시되는 영역도 사용해 보도록 한다.
아쉽게도, MAX7219 라이브러리에서 기본적으로 제공해 주는 문자는 제약이 많아, 다음과 같이
7 Segment 와 1 개의 점에 대해 1,0 의 값으로 켜고 끄는 제어를 할 수 있다.
[ ch05_6.js ]
var MAX7219 = require(''max7219'');
var disp = new MAX7219("/dev/spidev0.0");
disp.setDecodeAll();
disp.setScanLimit(4);
disp.setDisplayIntensity(15);
disp.startup();
// 특정 순서의 7세그먼트를 켜고 끌 수 있도록 제어한다.
disp.setDigitSegments(0, [0, 0, 1, 1, 0, 1, 1, 1]);
disp.setDigitSegments(1, [0, 1, 0, 0, 1, 1, 1, 1]);
disp.setDigitSegments(2, [0, 0, 0, 0, 1, 1, 1, 0]);
disp.setDigitSegments(3, [0, 1, 1, 0, 0, 1, 1, 1]);
매번 이렇게 문자와 숫자를 일일 히 만들어 쓰면 불편하므로, 다음과 같이 함수를 만들어서
사용할 수 있도록 한다. 7 Segment 의 제약으로 모든 문자를 표현하기에 제약이 있지만,
그래도 최대한 유사한 문자 모양이 나올 수 있도록 별도의 함수를 만들어서 사용해 보도록
한다. 모바일 상에서 입력한 메시지가 출력이 되도록 구성해 본다.
응용 2 - 7 Segment 를 이용하여 시계 만들기
자 이제, 7 Segment 모듈을 이용하여 시간을 표시하는 기능을 만들어 보자.
linux 에서 시간을 확인하는 명령어는 date 이다. date 를 실행하고 난 결과를 쪼개서 출력에
사용하도록 하자. 1 초단위로 시간을 검사하여 화면상에 디스플레이 하는 기능을 간단하게
구현해 본다.
[ ch5_07.js ]
var seg = require('./ch5_07_7seg');
// 커맨드 명령을 실행할 수 있는 exec 함수를 활성화한다.
var exec = require('child_process').exec;
setInterval(function(){
// 리눅스에서 시간을 알아내는 date 명령어를 실행한다.
exec('date', function(err, stdout, stderr){
// 현재 시간 값으로부터 시간을 구한다.
var tokens = stdout.split(':');
var times = tokens[0].split(' ');
var time = times.pop() + '' + tokens[1];
console.log(time);
// 시간과 분으로 이루어진 현재 시간을 화면에 출력한다.
seg.setText(time, false, true, false, false);
})
},1000);
Exec 명령은 실제 linux 에서 구동하는 명령어를 nodejs 에서 실행할 수 있게 해 주는
명령어이다. 처음 시작시, PIAU 라는 글씨를 보여 준 후, 1 초마다 Date 명령을 실행하여, 현재
시간을 가져온다. 빈 여백으로 문자열을 잘라내면, 실제 시간은 4 번째 단어 군에서 가져와 이를
디스플레이 한다.
[ ch5_07_7seg.js ]
var MAX7219 = require(''max7219'');
var disp = new MAX7219('/dev/spidev0.0');
disp.setDecodeNone();
disp.setScanLimit(8);
disp.setDisplayIntensity(15);
disp.startup();
// 숫자와 문자에 대한 7세그먼트 표시형식을 미리 정의해 둔다.
_font = {
_0 : [0,1,1,1,1,1,1],
_1 : [0,0,0,0,1,1,0],
_2 : [1,0,1,1,0,1,1],
_3 : [1,0,0,1,1,1,1],
_4 : [1,1,0,0,1,1,0],
_5 : [1,1,0,1,1,0,1],
_6 : [1,1,1,1,1,0,0],
_7 : [0,1,0,0,1,1,1],
_8 : [1,1,1,1,1,1,1],
_9 : [1,1,0,0,1,1,1],
_A : [1,1,1,0,1,1,1],
_B : [1,1,1,1,1,0,0],
_C : [0,1,1,1,0,0,1],
_D : [1,0,1,1,1,1,0],
_E : [1,1,1,1,0,0,1],
_F : [1,1,1,0,0,0,1],
_G : [1,1,0,1,1,1,1],
_H : [1,1,1,0,1,0,0],
_I : [0,1,1,0,0,0,0],
_J : [0,0,1,1,1,1,0],
_K : [1,1,1,0,1,0,1],
_L : [0,1,1,1,0,0,0],
_M : [1,1,1,0,1,1,1],
_N : [1,0,1,0,1,0,0],
_O : [1,0,1,1,1,0,0],
_P : [1,1,1,0,0,1,1],
_Q : [1,1,0,0,1,1,1],
_R : [1,0,1,0,0,0,0],
_S : [1,1,0,1,1,0,1],
_T : [1,1,1,1,0,0,0],
_U : [0,0,1,1,1,0,0],
_V : [0,1,1,1,1,1,0],
_W : [1,1,1,1,1,1,0],
_X : [0,1,1,0,1,1,0],
_Y : [1,1,0,1,1,1,0],
_Z : [1,0,1,1,0,1,1]
}
// 7세그먼트에 입력받은 4개의 문자혹은 숫자 값을 실제로 출력한다.
exports.setText = function(_,a,b,c,d){
if(_){
_ += "";
if(_.length < 4){
while(_.length < 4){
_ = " " + _;
}
} else if(_.length > 4) {
_ = _.slice(-4);
}
_ = _.toUpperCase();
if(a === true){
disp.setDigitSegments(0, _font["_" +
_.charAt(0)].slice(0).concat([1]));
} else {
disp.setDigitSegments(0, _font["_" +
_.charAt(0)].slice(0).concat([0]));
}
if(b === true){
disp.setDigitSegments(1, _font["_" +
_.charAt(1)].slice(0).concat([1]));
} else {
disp.setDigitSegments(1, _font["_" +
_.charAt(1)].slice(0).concat([0]));
}
if(c === true){
disp.setDigitSegments(2, _font["_" +
_.charAt(2)].slice(0).concat([1]));
} else {
disp.setDigitSegments(2, _font["_" +
_.charAt(2)].slice(0).concat([0]));
}
if(d === true){
disp.setDigitSegments(3, _font["_" +
_.charAt(3)].slice(0).concat([1]));
} else {
disp.setDigitSegments(3, _font["_" +
_.charAt(3)].slice(0).concat([0]));
}
}
}
실제 7 Segment 를 구현한 함수이다. 0 부터 Z 까지 최대한 비슷한 모양이 나오도록 구성한
코드 이다. 0 과 1 로 해당 LED 를 켜고 끄는 것을 지정하고, 4 자리의 문자를 표시해 주도록
한다. 문자열 뒤의 4 개의 매개 변수는 점으로 된 LED 를 켜고 끄는데 사용하는 옵션으로, true
일때 on, false 일 때 off 가 된다.
응용 3 – 모바일로 입력한 문자 7 Segment 에 표시하기
7 Segment 를 응용하여, 모바일로부터 입력받은 문자를 표시해 주는 기능을 연동해 볼 수
있다. 기존에 사용한 함수는 그대로 활용하여 서버를 구성해 보도록 한다.
[ ch5_08.js ]
var express = require('express');
var bodyParser = require('body-parser');
var seg = require('./ch5_07_seg');
var app = express();
app.use(bodyParser.json());
app.use(bodyParser.urlencoded({ extended: true }));
app.get('/', function(req, res){
res.sendFile(__dirname + '/ch5_08.html')
});
app.post('/setText', function(req ,res){
console.log(req.body);
// 웹을 통해 입력받은 문자를 7세그먼트에 표시한다.
var text = req.body.text;
seg.setText(text, false, false, false, false);
res.json({ result : true });
});
seg.setText('hipi', false, true, false, false);
app.listen(8080);
console.log('LED Server');
기본 텍스트로 hi.pi 를 디스플레이 해 주고, 이전 실습 때 제작한 모듈을 그대로 재 활용하여 웹
페이지로부터 받아 지는 문자열을 7 Segment 에 보여 주게 된다.
[ ch5_08.html ]
<html>
<head>
<meta name="viewport" content="width=device-width, initial-
scale=1.0" />
<title>7 Segment</title>
<script src="https://code.jquery.com/jquery-
2.1.4.min.js"></script>
<script>
$(function(){
// 버튼 클릭시, 입력된 값을 서버로 전달한다.
$('button').click(function(){
$.post( "setText", { text: $('input').val() });
});
});
</script>
</head>
<body>
<input type='text' placeholder='Input message' />
<p/>
<button>SEND</button>
</body>
</html>
SEND 버튼을 클릭 시, 입력 창에 입력된 메시지를 서버의 setText 로 메시지를 전달하게 된다.
이렇게 전달된 메시지가 출력된 결과는 다음과 같다.
응용 4 - 초음파 센서 거리를 7 Segment 에 표시하기
앞에서 사용한 7 Segment 와 초음파 센서를 결합하여, 초음파 센서에서 측정한 거리를 7
Segment 로 표시해 줄 수 있다.
메인 모듈이다. 매 1 초마다 초음파로 거리를 측정하고, 이를 7 Segment 에 출력하는 방식으로
소스를 구성한다.
[ ch5_08_2.js ]
var usonic = require('./usonic');
var segment = require('./segment');
// 1초 간격으로 측정한 거리 값을 7세그먼트에 표시한다
setInterval(function(){
// 거리 값을 측정 한다.
var num = usonic.getDistance();
console.log('Distance : ' + num);
// 숫자 값을 화면에 표시
segment.setNumber(num);
},1000);
숫자 표시를 위한 7 Segment 모듈이다. 최대밝기로 초기화하고, 4 자리 이하 문자일 경우 앞에
0 을 붙여 4 자리로 만든다. 이 문자를 한자리씩 7 Segment 에 보여준다.
[ segment.js ]
var MAX7219 = require('max7219');
var disp = new MAX7219("/dev/spidev0.0");
disp.setDecodeAll();
disp.setScanLimit(4);
disp.setDisplayIntensity(15);
disp.startup();
// 숫자를 7세그먼트에 표시하는 기능을 구현
exports.setNumber = function(num){
// 숫자를 문자로 번경하여 4자리 수 이하일 경우 앞에 0을 붙인다.
while(num.toString().length < 4){
num = '0' + num;
}
// 4자리 수의 각 자리를 7세그먼트에 표시한다.
disp.setDigitSymbol(0, num.charAt(0));
disp.setDigitSymbol(1, num.charAt(1));
disp.setDigitSymbol(2, num.charAt(2));
disp.setDigitSymbol(3, num.charAt(3));
}
초음파 센서 모듈이다. 2 번 핀을 Trigger 로, 3 번 핀을 Echo 로 설정한다. getDistance 함수에
거리를 측정하는 코드를 작성하여, 메인 모듈에서 이를 호출할 수 있도록 한다.
[ usonic.js ]
var wpi = require('wiring-pi');
var sleep = require('sleep');
var microtime = require('microtime');
var trig = 2;
var echo = 3;
wpi.setup('wpi');
wpi.pinMode(trig, wpi.OUTPUT);
wpi.pinMode(echo, wpi.INPUT);
exports.getDistance = function(){
wpi.digitalWrite(trig, wpi.LOW);
sleep.usleep(2);
wpi.digitalWrite(trig, wpi.HIGH);
sleep.usleep(20);
wpi.digitalWrite(trig, wpi.LOW);
var count1 = 0;
var count2 = 0;
while(wpi.digitalRead(echo) == wpi.LOW){
if(count1++ > 1000){ break; }
}
var start_time = microtime.now();
while(wpi.digitalRead(echo) == wpi.HIGH){
if(count1++ > 10000){ break; }
}
var time = microtime.now() - start_time;
return Math.round(time / 58);
}
[그림] 초음파로 측정된 거리가 7 Segment 에 표시된다.
집 상황을 체크하기 – 온 습도 센서
에어컨, 난방기, 가습기, 제습기와 같은 제품은 집안의 온도와 습도를 검사하여 온도나 습도를
조절한다. 라즈베리파이에도 디지털 온 습도 센서를 이용하여 쉽게 측정할 수 있다. 저렴한
가격에 한번에 측정할 수 있는 DHT11 센서를 이용하여, 집안의 온습도를 측정해 보도록 한다.
이 센서를 이용하기 위해서는, 라즈베리파이의 칩셋 드라이버를 우선 설치해야 사용할 수 있다.
드라이버를 우선 다운로드 받도록 한다.
$ sudo wget http://www.airspayce.com/mikem/bcm2835/bcm2835-1.50.tar.gz
[그림] wget 명령을 이용하면, 인터넷 상의 다양한 프로그램을 다운로드 받을 수 있다.
다운로드 받은 파일을 압축 해제한 후 설치하기 위해 다음 명령을 입력한다.
$ tar zxvf bcm2835-1.50.tar.gz
$ cd bcm2835-1.50
$ ./configure
$ make
$ sudo make check
$ sudo make install
[그림] make 명령어로 C 라이브러리를 컴파일 한 후 make install 로 컴파일 된 파일을 설치할
수 있다.
드라이버의 설치가 완료되면, dht11 센서를 사용할 수 있게 해 주는 node-dht-sensor 패키지를
설치하도록 한다. DHT11 센서는 Power, Ground, GPIO 세 핀으로 이루어져 있는데, 마지막에
연결하므로 확인이 용이하게 마지막 GPIO21(WiringPi 기준으로는 GPIO29) 에 GPIO 연결을
하도록 한다.
$ sudo npm install –g node-dht-sensor
설치가 완료된 후, 온도와 습도를 측정해 보자. 다음과 같이 간단하게 코드를 작성하여 온도와
습도를 한번에 확인할 수 있다.
[ ch5_9.js ]
// 온습도 측정 모듈을 호출한다.
var dht = require('node-dht-sensor');
// 21번핀에 연결한 DHT11 센서를 초기화 한다.
dht.initialize(11, 21);
// 온습도 값을 읽어 화면에 출력한다.
console.log(dht.read());
[그림] humidity 는 습도 값, temperature 는 온도 값이 반환 된다.
응용 5 – 실시간 모바일/7 Segment 온습도 모니터링
이미 설치한 7 Segment LED 와 모바일을 연동하여 온도와 습도를 표시해 주는 코드를 작성해
보도록 한다. 매 1 초마다 온습도를 측정하고, 그 결과를 모바일로 전송한다.
[ ch5_10.js ]
var app = require('express')();
var server = require('http').Server(app);
var io = require('socket.io')(server);
var seg = require('./ch5_07_7seg');
var dht = require('node-dht-sensor');
dht.initialize(11, 21);
app.get('/', function(req, res){
res.sendFile(__dirname + '/ch5_09.html')
});
// 온습도 값을 1초 간격으로 측정하여, 7세그먼트의 앞의 2자리는 온도, 뒤의
2자리는 습도를 표시해 준다.
setInterval(function(){
var data = dht.read();
seg.setText(data.temperature + '' + data.humidity, false, true,
false, false);
// 접속한 웹 클라이언트에 측정한 값을 전달한다.
io.emit('data', data);
},1000);
server.listen(8080);
console.log('DHT11 Push Server');
setInterval 함수를 이용하여 매 초마다 온습도를 측정하고, 7 Segment 에 온도와 습도를
표시해 주도록 한다. 또한 socket.io 를 이용하여 접속해 있는 모든 모바일 장치에 측정 값을
전달하는 emit 함수를 호출한다.
[ ch5_10.html ]
<html>
<head>
<title>DHT11</title>
<meta name="viewport" content="width=device-width, initial-
scale=1.0" />
<script src="https://code.jquery.com/jquery-
2.1.4.min.js"></script>
<script src="/socket.io/socket.io.js"></script>
<script>
$(function(){
var socket = io();
// 전달받은 온 습도 값을 화면상에 보여준다.
socket.on('data', function(data){
$('#temperature').text(data.temperature);
$('#humidity').text(data.humidity);
})
})
</script>
</head>
<body>
<h1>Temperature & Humidity</h1>
<h3><span id='temperature'>0</span>C</h3>
<h3><span id='humidity'>0</span>%</h3>
</body>
</html>
Socket.io 로 접속되어 있는 라즈베리파이로부터 전달 받은 온도와 습도 값을 전송될 때 마다
웹 페이지에 표시해 준다.
[그림] 7 Segment 를 통해 온습도가 표시되고, 모바일을 통해서도 실시간으로 확인 가능하다
집 밝기 체크하기 – 조도 센서
집안의 상태를 확인하는데, 온도 습도 이외에 밝기에 따라 조명을 조절하고자 할 때는 조도
센서를 사용할 수 있다. 라즈베리파이의 경우, GPIO 가 디지털 신호만을 사용할 수 있어,
조도를 확인하기 위해서는 디지털로 측정이 가능한 BH1750 센서를 사용하도록 한다. BH1750
센서는 I2C 방식으로 통신이 되는데, I2C 는 클럭(SCL)과 데이터(SDA)의 두 선을 이용하여
통신이 이루어 진다.
약자 본명 용도
SCL Serial Clock 시리얼 통신 동기용 클럭
SDA Serial Data 시리얼 통신 데이터 전달
다음의 명령으로 NodeJS 에 I2C 패키지를 설치하도록 한다.
$ npm install git://github.com/jnovack/node-i2c
I2C 모듈이 설치가 완료되었다면, 디지털 광센서로 빛을 확인할 수 있는 코드를 작성하도록
한다. 매 1 초간 빛의 밝기를 검사하여 값을 반환하게 한다. 실질적으로 I2C 와 통신하여 값을
가져오는 부분은 bh1750 으로 별도 모듈을 만들도록 한다.
[ ch5_11.js ]
var bh = require('./bh1750');
// 1초 간격으로 조도 값을 측정하여 출력한다.
setInterval(function(){
bh.getLight(function(val){
console.log(val);
})
},1000);
I2C 통신으로 BH1750 에 정의된 읽기 명령어와 기본 주소를 이용하여, 명령을 전달하고,
읽혀진 디지털 값을 가져와 반환하는 기능을 구현한다.
[ bh1750.js ]
// I2C 모듈을 불러온다.
var i2c = require('i2c');
// I2C-1 의 0x23 주소에 연결된 I2C 장치에 전달할 명령 값을 설정한다.
var options = {
address: 0x23,
device: '/dev/i2c-1',
command: 0x10,
length: 2
};
// I2C에 연결된 BH1750 센서를 초기화 한다.
var bh = new i2c(options.address, {device: options.device });
// 빛 값을 구하는데 호출된다.
exports.getLight = function(cb){
if (!cb) {
console.error("missing callback");
return;
}
// BH1750 센서에 조도 측정 명령을 전달한다.
bh.writeByte(options.command, function (err) {
if (err) {
console.error("error write byte to BH1750 - command: ",
options.command);
}
});
// 조도 값을 읽어 반환한다.
bh.readBytes(options.command, options.length, function (err,
res) {
var hi = res.readUInt8(0);
var lo = res.readUInt8(1);
cb((hi << 8) + lo);
});
}
조도 센서를 손으로 가리거나 밝은 방면으로 위치를 바꾸면, 그에 해당하는 밝기 값을 반환함을
알수 있다. 이를 통해 현재 장소나 시간에 따른 빛 밝기 변화를 감지할 수 있는데, 일반적으로
빛 밝기에 따른 상태는 다음과 같다.
상태 값 범위
여름 정오 경의 야외 조도 1000000
책을 읽기 위한 조도 50 ~ 60
비디오 시청 조도 1400
맑은 날 실내 조도 100 ~ 1000
흐린 날 야외 조도 50 ~ 500
흐린날 실내 조도 5 ~ 50
달빛 조도 0.002 ~ 0.3
빛 없는 밤 0.001 ~ 0.2
[그림] 조도센서를 이용하여 밝기를 수치화 하여 표현할 수 있다.
응용 6 – 집 밝기에 따라 조절되는 LED
조도센서를 응용하면, 어두울때 불 밝기를 자동으로 밝게 해 주는 전등을 만들어 볼 수 있다.
여기서는 밝기에 따라 LED 의 밝기를 조절해 보는 예제를 만들어 볼 것이다. 조도 센서 모듈은
그대로 활용하여 구현한다.
GPIO 11 번으로 제어할 수 있는 LED 를 추가해 준다. 빛의 밝기를 능동적으로 조절하기 위해
PWM 을 활용하여 LED 밝기를 조절한다.
var bh = require('./bh1750');
var wpi = require('wiring-pi');
wpi.setup('gpio');
wpi.wiringPiSetup();
var pin = 11;
wpi.softPwmCreate(pin, 0, 200) ;
// 1초간격으로 조도 값을 읽은 후 그 값을 소프트 PWM방식으로 LED에 입력한다.
setInterval(function(){
bh.getLight(function(val){
wpi.softPwmWrite (pin, val);
console.log(val);
})
},1000);
조도 센서에서 입력 받은 값을 활용하여, LED 소자에 PWM 값을 입력하여 밝기를 조절하게
된다. 밝은 곳에서는 밝게 빛나고 어두운 곳에서는 반대로 어둡게 표현되는 것을 확인할 수
있을 것이다.
이번 장을 정리하며
이번 장에서는 라즈베리파이의 GPIO 와 연결한 다양한 센서를 제어하고 값을 가져오는 법을
확인해 보았다. GPIO 제어를 웹 서버와 통합하여 웹 페이지에서 하드웨어를 제어하고 측정
값을 가져와서 연동하였다. GPIO 제어 역시 NodeJS 와 자바스크립트로 수행하여, 웹
페이지와의 연동을 일관성 있고 빠르게 구축할 수 있었다. 다음 장에서는 사용자에게 보여지는
웹 페이지를 보다 모바일에 최적화 하여 보여줄 수 있도록 jQueryMobile 로 웹 어플리케이션을
꾸미는 법을 다룰 것이다.
5. 스마트폰을 리모콘으로 - jQueryMobile
우리는 그 어떠한 버튼도 달려있지 않은 파이오를 제어하기 위해 스마트 폰을 활용할 것이다.
가장 기본적인 기능인 음악을 검색하여 결과를 보여주고, 선택한 음악을 파이오에서 다운로드
받아 재생할 수 있도록 연계하는 일을 하게 될 것이다.
모바일 웹을 위한 jQueryMobile
라즈베리파이 기반으로 만드는 파이오를 제어하기 위해 우리는 누구나 한손에 들고 있는
스마트 폰을 이용할 것이다.
흔히 사용하는 안드로이드나, iOS 스마트폰 앱을 개발하기 위해서는 Java 나 Objective C, Swift
같은 프로그래밍 언어를 알아야 하는게 일반적이다. 즉 다양한 스마트 장치에 맞는 언어로
프로그래밍을 해야 한다는 것을 의미한다. 하지만 다양한 OS 와 스마트 폰 이외에 스마트 TV,
웨어러블 디바이스등 다양한 장치를 각각의 언어로 개발한다는 것은 사실상 불가능하다.
우리는 처음 시작과 끝을 단 한 언어로 한다고 이야기 한 바 있다. 웹으로 개발하면, 모바일,
데스크탑, 웨어러블 디바이스, 스마트 TV 에 상관없이 돌아가는 어플리케이션을 만들 수 있는
것이다. 모바일 역시도 Javascript 를 이용하여 모바일 웹앱을 만들어 플랫폼과 무관하게 사용할
수 있도록 개발한다.
[그림] javascript 개발을 쉽게 해주는 것이 jquery 라면, mobile 웹 개발을 쉽게 해 주는 것이
jquerymobile 이다.
jQueryMobile 은 Javascript 라이브러리로 많이 사용되는 jQuery 에서 파생되어 나온 모바일 웹
개발을 위한 웹 프레임 워크이다. Desktop 과 함꼐 사용하기를 원한다면 웹페이지 개발시 가장
많이 활용되는 Bootstrap 을 이용하여 데스크 탑과 모바일이 함께 대응 가능하도록 하는 것이
좋다. 우리는 이번 과정에서는 모바일 장치를 활용한 제어를 목적으로 하여 jQueryMobile 을
사용하여 리모콘 어플리케이션을 구성해 보고자 한다.
jQueryMobile 다운로드 > http://jquerymobile.com/download/
[그림] jquerymobile 을 이용하여 다양한 디바이스를 커버하는 모바일 웹을 개발 할 수 있다.
초 간단 모바일 웹 서버 구성하기
4, 5 장에서 NodeJS 와 ExpressJS 를 이용하여 서버를 구성하는 방법을 이미 실습하였다.
이전에는 하드웨어와 연동하기 위해 매번 다른 구성으로 서버를 제작하였는데, 이번에는
모바일 웹 개발만을 위하여 동적으로 웹 페이지를 접근할 수 있는 서버를 구성하게 될 것이다.
[ ch6_server.js ]
var express = require('express');
var path = require('path');
var app = express();
app.use(express.static(path.join(__dirname, '/')));
app.listen(8080);
console.log('Mobile Server');
Express 의 static 함수는 특정 경로의 파일을 자유롭게 접근할 수 있도록 해준다. 앞으로
만드는 파일들은 경로 명을 이용하여 재 구동 필요없이 주소만으로 파일에 접근할 수 있다.
[ ch6_1.html ]
<html>
<head>
<title>Mobile Web</title>
<meta name="viewport" content="width=device-width, initial-
scale=1.0, user-scalable=no" />
<link rel="stylesheet"
href="http://code.jquery.com/mobile/1.4.5/jquery.mobile-
1.4.5.min.css" />
<script src="http://code.jquery.com/jquery-
2.2.0.min.js"></script>
<script
src="http://code.jquery.com/mobile/1.4.5/jquery.mobile-
1.4.5.min.js"></script>
</head>
<body>
<h1>Hello Mobile!</h1>
</body>
</html>
jQueryMobile 을 이용하기 위해서는 jQuery 가 기본적으로 필요하다. jQuery 가 jQueryMobile
호출 전에 호출이 될 수 있도록 위치해 놓는다. 각 파일들을 직접 라즈베리파이 상에 다운로드
하여 사용할 수 있지만, 위 코드처럼, 라이브러리에서 제공하는 CDN(Content Delivery
Network) 주소를 링크하여 사용해도 무방하다.
앞 장에서 지나간 viewport 는 모바일 개발 시 중요한 속성이다. 이 속성 없이 모바일 웹을
개발하면, 화면에 상당히 작게 표시가 되는 문제가 있는데, 모바일 웹 개발 시에는 필수적으로
head 부분에 viewport meta 정보를 기입하도록 한다.
Header Body Footer 로 화면 꾸미기
jQueryMobile 은 머릿 부분인 Header, 주 컨텐츠 영역인 main, 머릿말 영역인 footer 로
구분하여 구성되어 있다. 탭과 같이 기능 전환을 위한 UI 는 footer 에 배치하고, 검색된 음악
결과와 같은 주요 컨텐츠는 main 영역에 배치할 수 있도록 한다.
[ ch6_2.html ]
<html>
<head>
<title>Mobile Web</title>
…
</head>
<body>
<div data-role="page">
<div data-role='header'>
<h1>piAu Remote</h1>
</div>
<div data-role='main' class='ui-content'>
<p>Hello piAu!</p>
</div>
<div data-role='footer'>
<div data-role='navbar'>
<ul>
<li><a href='#' data-
icon='grid'>Music</a></li>
<li><a href='#' data-
icon='star'>Weather</a></li>
<li><a href='#' data-
icon='gear'>News</a></li>
<li><a href='#' data-
icon='gear'>Message</a></li>
</ul>
</div>
</div>
</div>
</body>
</html>
위 코드로 Header, Content, Footer 영역이 지정되는 것을 확인할 수 있다. 하지만 Header 와
Footer 가 흰색이라 구분이 잘 되지 않고, Footer 의 경우 중간에 뜬 형태로 표시되어 미적으로
좋지 않다. 이를 수정하기 위해 Header 와 Footer 에 테마를 지정하고, 각각 상단 및 하단에
고정하여 일반적인 모바일 환경으로 보여지도록 수정하자.
[ ch6_3.html ]
…
<div data-role="page">
<div data-role='header' data-theme='b' data-position='fixed'>
<h1>piAu Remote</h1>
</div>
<div data-role='main' class='ui-content'>
<p>Hello piAu!</p>
</div>
<div data-role='footer' data-theme='b' data-position='fixed'>
<div data-role='navbar'>
<ul>
<li><a href='#' data-icon='grid'>Music</a></li>
<li><a href='#' data-icon='star'>Weather</a></li>
<li><a href='#' data-icon='gear'>News</a></li>
<li><a href='#' data-icon='gear'>Message</a></li>
</ul>
</div>
</div>
</div>
…
이번에 기본 색상인 밝은 테마 대신 어두운 테마를 Header 와 Footer 에 적용해 보도록 한다.
data-theme=’b’ 코드를 추가해 주는 것 만으로 어두운 테마가 적용되게 된다. 아울러 첫
예제에서 고정되지 않았던 header 와 footer 에 data-position=’fixed’ 코드를 추가하여 상단과
하단에 고정되어 표시된다.
[그림] data-theme 와 data-position 속성을 이용한 Header, Footer 의 색상 지정 및 고정 효과
적용
하단부의 아이콘은 페이지 이동을 위해 미리 만들어 놓은 공간이다. data-icon 속성으로 아이콘
형태를 지정할 수 있는데, 50 여개의 다양한 아이콘을 지원하고 있다. 다른 아이콘 모양으로
대체하기를 원한다면 http://demos.jquerymobile.com/1.4.5/icons/ 페이지에 접속하여 아이콘
목록을 확인해 볼 수 있다. 필요에 따라서는 써드 파티의 아이콘으로 대체하여 이용할 수도
있다.
[그림] jquerymobile 에서 지원하는 기본 아이콘 목록
음반 검색 창과 목록을 만들자 – LISTVIEW, INPUT TEXT
음악을 검색할 입력 창을 main 영역에 추가하기 위하여 JQM 에서 제공하는 Search Input 을
사용한다. Search Input 을 통해 입력한 값을 SoundCloud 에 전송하여 검색된 결과를 받는다.
[ ch6_04.html ]
…
<div data-role='main' class='ui-content'>
<ul data-role='listview'>
<li><a href=#>Item 1</a></li>
<li><a href=#>Item 2</a></li>
<li><a href=#>Item 3</a></li>
<li><a href=#>Item 4</a></li>
<li><a href=#>Item 5</a></li>
</ul>
</div>
…
음악 검색결과를 main 영역에 보여주기 위하여 jQueryMobile (이하 JQM) 에서 제공하는
ListView 기능을 이용하여 결과를 출력해 준다. 전달받은 음악결과의 Image 를 img 태그에
삽입하여 썸네일 식으로 앨범 모양을 나오게 해주고, 음악 title 명을 h2 태그에 삽입하여
강조되어 보여줄 수 있도록 한다. 기타 세부 사항은 p 태그에 넣어서 사용자가 손쉽게 목록을
확인하고 들을 수 있도록 해준다. 여기서 사용한 ListView 는 최신가요 목록을 가져와서
보여주는 용도에도 사용하게 된다.
[ ch6_05.html ]
…
<div data-role='main' class='ui-content'>
<div data-role='fieldcontain'>
<input type="search" name="search" id="search"
placeholder="Search for content..."/>
</div>
<div data-role='fieldcontain'>
<ul data-role='listview'>
<li>
<a href=#>
<image
src="http://cdnimg.melon.co.kr/cm/album/images/026/53/515/2653515.jp
g" />
<h2>Item 1</h2>
<p>Sub Text</p>
</a>
</li>
<li>
<a href=#>
<image
src="http://cdnimg.melon.co.kr/cm/album/images/026/53/515/2653515.jp
g" />
<h2>Item 2</h2>
<p>Sub Text</p>
</a>
</li>
<li>
<a href=#>
<image
src="http://cdnimg.melon.co.kr/cm/album/images/026/53/515/2653515.jp
g" />
<h2>Item 3</h2>
<p>Sub Text</p>
</a>
</li>
</ul>
</div>
</div>
…
차후 음악을 검색할 수 있도록 search 컴포넌트를 배치하고, listiew 컴포넌트를 하단에
위치하도록 한다. 현재는 샘플로 데이터를 넣어 보았지만, 실제 만들어 볼때는 프로그래밍을
통해 음반 목록을 가져오고 보여주게 될 것이다.
[그림] ListView 를 이용하여, 음악목록을 표시할 때, 타이틀 이미지와 함께 보여주면
사용자에게 보다 직관적으로 다가온다.
음악을 재생하고, 볼륨을 조정하자 – GRID, INPUT
RANGE, BUTTON
실제 음악을 재생하는 페이지를 구현해 보자. 이번 페이지에는 음악 제목과 재생 완료률을
표시할 수 있는 영역을 배치한다. 아울러 재생 시 컨트롤 할 수 있는 버튼도 레이아웃에 맞도록
배치할 것이다.
[ ch6_6.html ]
…
<div data-role='main' class='ui-content'>
<h2>Item1</h2>
<input type="range" min="0" max="100" value="80" />
<a href='#' data-role='button'>Prev</a>
<a href='#' data-role='button'>Play</a>
<a href='#' data-role='button'>Stop</a>
<a href='#' data-role='button'>Next</a>
</div>
…
음악 재생 현황을 0 부터 100% 까지 표시할 수 있도록 range 컴포넌트를 배치하도록 한다.
진행 상황을 백분율로 보여줄 것이다. 재생을 컨트롤 하는 버튼 4 개를 나란히 만들어 본다.
[ ch6_7.html ]
…
<div data-role='main' class='ui-content'>
<h2>Item1</h2>
<input type="range" min="0" max="100" value="80" />
<div class='ui-grid-c'>
<div class='ui-block-a'>
<a href='#' data-role='button'>Prev</a>
</div>
<div class='ui-block-b'>
<a href='#' data-role='button'>Play</a>
</div>
<div class='ui-block-c'>
<a href='#' data-role='button'>Stop</a>
</div>
<div class='ui-block-d'>
<a href='#' data-role='button'>Next</a>
</div>
</div>
</div>
…
처음 배치한 버튼이 가득 차게 4 개가 보여지는 것을 25%의 크기로 한줄에 보이도록 수정해
보도록 한다. ui-grid 컴포넌트를 이용하면, 해당 사이즈에 맞게 배치할 수 있는데 ui-grid-c 의
경우 25%로 컴포넌트를 배치할 수 있도록 레이아웃을 구성해 준다.
[그림] grid 레이아웃으로 내부 버튼 위치를 알맞게 배치할 수 있다.
[그림] ui-grid-a, ui-grid-b, ui-grid-c, ui-grid-d 에 따라, 50, 33, 25 ,20%로 레이아웃을 지정할
수 있다.
메세징 창과 알람 창 만들기 – Select Box
파이오로 메세지를 보내면 읽어주게 하는 TTS (Text To Speech) 기능을 위한 입력창을
만들어준다. 우리가 일반적으로 사용하는 메세지 입력 창과 같이 메세지를 입력하고, 입력후
이를 전송할 수 있도록 전송 버튼을 입력창 아래 추가한다. 검색 창 구성시에는 검색 내용이
적어서 한줄 입력용인 input box 로 가능하지만, 메세지의 경우 여러줄을 입력하는 경우가
빈번하므로 좀더 많은 내용을 입력할 수 있는 textarea 를 활용하도록 한다.
[ ch6_8.html ]
…
<div data-role='main' class='ui-content'>
<textarea rows='5' data-autogrow='false' placeholder='Input
message'></textarea>
<a href='#' data-role='button'>Send</a>
</div>
…
한줄 이상의 많은 문자를 입력할 수 있는 textarea 컴포넌트를 컨텐츠 영역에 추가한다.
물리적으로 5 줄을 입력 받을 수 있게 보여지도록 rows=’5’ 를 추가해 준다.
[ ch6_9.html ]
…
<div data-role='main' class='ui-content'>
<fieldset data-role='controlgroup'>
<legend>Choice day</legend>
<input type='checkbox' id='cb-1' />
<label for='cb-1'>Sun</label>
<input type='checkbox' id='cb-2' />
<label for='cb-2'>Mon</label>
<input type='checkbox' id='cb-3' />
<label for='cb-3'>Tue</label>
</fieldset>
<fieldset data-role='controlgroup'>
<legend>Alarm type</legend>
<input type='radio' name='rd' id='rd-1' />
<label for='rd-1'>Bell</label>
<input type='radio' name='rd' id='rd-2' />
<label for='rd-2'>Music</label>
<input type='radio' name='rd' id='rd-3' />
<label for='rd-3'>Weather</label>
</fieldset>
<div class='ui-grid-a'>
<div class='ui-block-a'>
<label for='hour'>Hour</label>
<select id='hour'>
<option value='0'>0</option>
<option value='23'>23</option>
</select>
</div>
<div class='ui-block-b'>
<label for='time'>Time</label>
<select id='time'>
<option value='0'>0</option>
<option value='2'>59</option>
</select>
</div>
</div>
</div>
…
CheckBox 컴포넌트를 이용하여 여러 요일을 선택할 수 있도록 하고, 한가지 알람 방식을
선택할 수 있도록 radio 컴포넌트를 사용한다. 시간과 날짜의 경우 selectmenu 컴포넌트를
이용하여, 모바일 디바이스에서 한 개의 숫자를 선택할 수 있도록 구성한다.
[그림] Message 입력 창과 알람 설정 창
페이지 호출 시 동작 추가하기 – jQueryMobile 이벤트
이전까지 각각의 페이지를 구분하여 보았다. 이번에 할 일은 각 페이지를 통합하고, 서로
이동할 수 있도록 구성하는 것이다. jQueryMobile 에서 각각의 파일을 나누어 개발하는 방법과
통합하여 개발할 수 있는 방식 모두를 지원한다. 우리는 간단하게 통합하여 개발하는 방법을
다루게 될것이다. 페이지 전환시에 기본적으로는 페이드 형태의 화면 전환효과를 제공하는데,
data-transition 값을 지정하여 원하는 효과로 변경 가능하다. 지원하는 화면 전환 효과는
다음과 같다.
전환 효과 설명
fade 흐려지는 효과로 화면이 전환된다.
pop 팝업창이 나타나는 효과로 화면이 전환된다.
flip 화면이 접히는 효과로 화면이 전환된다.
turn 화면이 뒤집히는 효과로 화면이 전환된다.
flow 화면이 작아졌다가 흐르는 효과로 화면이 전환된다.
slidefade 화면이 흐르면서 흐려지는 효과로 화면이 전환된다.
slide 화면이 흐르는 효과로 화면이 전환된다.
slideup 화면이 아래로 흐르는 효과로 화면이 전환된다.
slidedown 화면이 위로 흐르는 효과로 화면이 전환된다.
none 화면전환 효과 없이 화면이 전환된다.
첫번째 보여지는 페이지가 Music List 가 되도록 id 가 music 인 페이지의 class 에 ui-page-
active 클래스를 추가하여 사용할 수 있다.
[ ch6_10.html ]
<html>
<head>
<title>Mobile Web</title>
<meta name="viewport" content="width=device-width, initial-
scale=1.0, user-scalable=no" />
<link rel="stylesheet"
href="http://code.jquery.com/mobile/1.4.5/jquery.mobile-
1.4.5.min.css" />
<script src="http://code.jquery.com/jquery-
2.2.0.min.js"></script>
<script
src="http://code.jquery.com/mobile/1.4.5/jquery.mobile-
1.4.5.min.js"></script>
<script>
$(document).on('pageshow', function(){
alert($.mobile.activePage.attr("id"));
});
</script>
</head>
<body>
<div data-role="page" id='music' class='ui-page-active'>
<div data-role='header' data-theme='b' data-
position='fixed'>
<h1>Music</h1>
</div>
<div data-role='main' class='ui-content'>
<div data-role='fieldcontain'>
<input type="search" name="search" id="search"
placeholder="Search for content..."/>
</div>
<div data-role='fieldcontain'>
<ul data-role='listview'>
<li>
<a href='#play' data-transition='pop'>
<image
src="http://cdnimg.melon.co.kr/cm/album/images/026/53/515/2653515.jp
g" />
<h2>Item 1</h2>
<p>Sub Text</p>
</a>
</li>
<li>
<a href='#play' data-transition='pop'>
<image
src="http://cdnimg.melon.co.kr/cm/album/images/026/53/515/2653515.jp
g" />
<h2>Item 2</h2>
<p>Sub Text</p>
</a>
</li>
<li>
<a href='#play' data-transition='pop'>
<image
src="http://cdnimg.melon.co.kr/cm/album/images/026/53/515/2653515.jp
g" />
<h2>Item 3</h2>
<p>Sub Text</p>
</a>
</li>
</ul>
</div>
</div>
<div data-role='footer' data-theme='b' data-
position='fixed'>
<div data-role='navbar'>
<ul>
<li><a href='#music' data-icon='grid' data-
transition='flip'>Music</a></li>
<li><a href='#radio' data-icon='star' data-
transition='slidedown'>Radio</a></li>
<li><a href='#alarm' data-icon='clock' data-
transition='flow'>Alarm</a></li>
<li><a href='#message' data-icon='comment'
data-transition='turn'>Message</a></li>
</ul>
</div>
</div>
</div>
<div data-role="page" id='radio'>
<div data-role='header' data-theme='b' data-
position='fixed'>
<h1>Radio</h1>
</div>
<div data-role='main' class='ui-content'>
<ul data-role='listview'>
<li><a href=#>Item 1</a></li>
<li><a href=#>Item 2</a></li>
<li><a href=#>Item 3</a></li>
<li><a href=#>Item 4</a></li>
<li><a href=#>Item 5</a></li>
</ul>
</div>
<div data-role='footer' data-theme='b' data-
position='fixed'>
<div data-role='navbar'>
<ul>
<li><a href='#music' data-icon='grid' data-
transition='flip'>Music</a></li>
<li><a href='#radio' data-icon='star' data-
transition='slidedown'>Radio</a></li>
<li><a href='#alarm' data-icon='clock' data-
transition='flow'>Alarm</a></li>
<li><a href='#message' data-icon='comment'
data-transition='turn'>Message</a></li>
</ul>
</div>
</div>
</div>
<div data-role="page" id='play'>
<div data-role='header' data-theme='b' data-
position='fixed'>
<h1>Music Play</h1>
</div>
<div data-role='main' class='ui-content'>
<h2>Item1</h2>
<input type="range" min="0" max="100" value="80" />
<div class='ui-grid-c'>
<div class='ui-block-a'>
<a href='#' data-role='button'>Prev</a>
</div>
<div class='ui-block-b'>
<a href='#' data-role='button'>Play</a>
</div>
<div class='ui-block-c'>
<a href='#' data-role='button'>Stop</a>
</div>
<div class='ui-block-d'>
<a href='#' data-role='button'>Next</a>
</div>
</div>
</div>
<div data-role='footer' data-theme='b' data-
position='fixed'>
<div data-role='navbar'>
<ul>
<li><a href='#music' data-icon='grid' data-
transition='flip'>Music</a></li>
<li><a href='#radio' data-icon='star' data-
transition='slidedown'>Radio</a></li>
<li><a href='#alarm' data-icon='clock' data-
transition='flow'>Alarm</a></li>
<li><a href='#message' data-icon='comment'
data-transition='turn'>Message</a></li>
</ul>
</div>
</div>
</div>
<div data-role="page" id='message'>
<div data-role='header' data-theme='b' data-
position='fixed'>
<h1>Message</h1>
</div>
<div data-role='main' class='ui-content'>
<textarea rows='5' data-autogrow='false'
placeholder='Input message'></textarea>
<a href='#' data-role='button'>Send</a>
</div>
<div data-role='footer' data-theme='b' data-
position='fixed'>
<div data-role='navbar'>
<ul>
<li><a href='#music' data-icon='grid' data-
transition='flip'>Music</a></li>
<li><a href='#radio' data-icon='star' data-
transition='slidedown'>Radio</a></li>
<li><a href='#alarm' data-icon='clock' data-
transition='flow'>Alarm</a></li>
<li><a href='#message' data-icon='comment'
data-transition='turn'>Message</a></li>
</ul>
</div>
</div>
</div>
<div data-role="page" id='alarm'>
<div data-role='header' data-theme='b' data-
position='fixed'>
<h1>Alarm</h1>
</div>
<div data-role='main' class='ui-content'>
<fieldset data-role='controlgroup'>
<legend>Choice day</legend>
<input type='checkbox' id='cb-1' />
<label for='cb-1'>Sun</label>
<input type='checkbox' id='cb-2' />
<label for='cb-2'>Mon</label>
<input type='checkbox' id='cb-3' />
<label for='cb-3'>Tue</label>
</fieldset>
<fieldset data-role='controlgroup'>
<legend>Alarm type</legend>
<input type='radio' name='rd' id='rd-1' />
<label for='rd-1'>Bell</label>
<input type='radio' name='rd' id='rd-2' />
<label for='rd-2'>Music</label>
<input type='radio' name='rd' id='rd-3' />
<label for='rd-3'>Weather</label>
</fieldset>
<div class='ui-grid-a'>
<div class='ui-block-a'>
<label for='hour'>Hour</label>
<select id='hour'>
<option value='0'>0</option>
<option value='23'>23</option>
</select>
</div>
<div class='ui-block-b'>
<label for='time'>Time</label>
<select id='time'>
<option value='0'>0</option>
<option value='2'>59</option>
</select>
</div>
</div>
</div>
<div data-role='footer' data-theme='b' data-
position='fixed'>
<div data-role='navbar'>
<ul>
<li><a href='#music' data-icon='grid' data-
transition='flip'>Music</a></li>
<li><a href='#radio' data-icon='star' data-
transition='slidedown'>Radio</a></li>
<li><a href='#alarm' data-icon='clock' data-
transition='flow'>Alarm</a></li>
<li><a href='#message' data-icon='comment'
data-transition='turn'>Message</a></li>
</ul>
</div>
</div>
</div>
</body>
</html>
코드 작성을 왼료 한 후, 모바일을 이용하여 페이지를 전환하면 전환 효과와 함께 페이지의 id
가 보여지는 것을 알수 있다. 이는 3 줄의 코드에서 pageshow 이벤트, 즉 페이지가 보여질 때
호출되는 이벤트에 현재 보여지는 페이지의 id 를 가져와서 보여주게 하였기 때문이다. 이를
이용하여 페이지 호출에 따라 사전 동작이 가능하도록 프로그래밍 할 수 있도록 도와준다. 실제
사용은 최종 완성시에 하도록 하자. jQueryMobile 에서 지원하는 대표적인 이벤트는 다음과
같다.
Event 명 Event 발생 시점
Mobileinit jqueryMobile 의 로딩이 완료된 후 호출된다.
Orientationchange 모바일 장치가 가로/세로 모드로 변경될 때 호출된다.
Pageinit 페이지가 최초로 로딩되면 호출 된다.
Pagebeforeshow 페이지가 보여지기 전에 호출 된다.
Pageshow 페이지가 보여지면 호출된다.
Pagebeforehide 페이지가 사라지기 전에 호출 된다.
pagehide 페이지가 사라지고 호출된다.
pagechange jQueryMobile 에서 제공하는 changeMobile() 함수로 호출된다.
Scrollstart 스크롤 동작이 시작될 때 호출 된다.
Scrollstop 스크롤 동작이 끝날 때 호출된다.
Swipe 1 초내에 가로축으로 30px 이상의 손 동작이 발생될 때 호출 된다.
Swipeleft Swipe 이벤트가 왼쪽 방향으로 일어날 때 호출 된다.
swiperight Swipe 이벤트가 오른쪽 방향으로 일어날 때 호출 된다.
Tap 빠르게 터치 이벤트가 발생할 때 발생한다.
[그림] 페이지 전환 효과와 함께 해당 페이지의 id 가 Alert 창으로 표시 된다.
이번 장을 정리하며
이번 장에서는 모바일 웹을 사용자 친화적으로 구성할 수 있게 도와주는 jQueryMobile 을
다루어 보았다. Header, Body, Footer 의 3 단으로 화면을 구성하고, 각종 컴포넌트를 배치하여
모바일 환경에 적합한 UX 구성을 할 수 있도록 했다. 이를 이용해 라즈베리파이의 제어를
모바일 웹 어플리케이션으로 가능 하도록 할 것이다. 다음 장에서는 OpenAPI 를 이용하여 음악
검색/재생을 실습하며, 뉴스 정보를 RSS 를 이용하여 접근하는 법을 다룰 것이다.
6. 인터넷으로 음악과 날씨를 – OpenAPI & RSS
공개 API 를 활용하여 나만의 서비스를
공개 API 는 (Open API, Application Programming Interface) 의 약자로서, 누구나 접근하여
다양한 서비스를 사용할 수 있도록 공개된 API 를 뜻한다. 대표적으로는 구글맵이 있으며, 공개
API 등을 융합하여 새로운 서비스를 만드느 행위를 메시업(Meshup) 이라 불린다. 구글, 야후,
MS, 아마존 등에서 제공하는 지도, 상품 정보 등을 웹 서비스 기술을 이용하여 독자적이고
새로운 서비스를 만들 수 있다. 대표적으로 구글 지도에 부동산 매물 정보를 활용하여 서비스를
만들거나, 아마존의 상품 정보를 이용하여 구매 서비스를 만드는 사례가 있다.
[그림] 다양한 OpenAPI 를 지원하는 해외의 업체들. 제공하는 API 를 엮어 다양한 서비스
제공이 가능하다. 구글 지도에 범죄율, 학교, 음식점, 은행, 주유소와 같은 편의시설에 기반한
부동산 검색 도구인 트롤리아 로컬(Trulia Local)
음악 OpenAPI 를 사용해 보자 - SoundCloud
사진에 플리커, 영상에는 유투브가 있다면, 음악에는 사운드클라우드가 있다. 자유롭게 올라온
음악들을 스트리밍으로 재생할 수 있는 서비스로, 다양한 나라와 장르의 음악들이 올라와 있어,
영상을 제외한 음악만을 듣고자 할 때 Youtube 보다 편리하게 이용할 수 있다.. 우리는 이
SoundCloud 에서 제공하는 공개 API 를 이용하여 음악을 검색하고, 우리의 파이오로 재생할 수
있는 구성을 할 것이다.
[그림] 음악계의 Youtube 인 SoundCloud
사운드클라우드에 가입하기
SoundCloud 서비스를 가입하기 위하여 다음의 주소로 접속하도록 한다.
http://www.soundcloud.com
우측 상단에 Create Account 버튼이 가입 버튼인데, ID 로 사용할 e-mail 주소와 접속을 위한
Password 를 입력하여 가입하거나, Facebook 이나 Google 계정을 연결하여 가입할 수도 있다.
가입 후 로그인 (해외 사이트는 로그인의 개념을 Sign in 으로 본다) 을 하도록 한다.
[그림] 직접 가입할 수도 있지만, facebook 이나 google 등의 소셜 계정으로 가입하는 것도
가능하다
공개 API 등록하기
사운드클라우드에 정상적으로 가입하였다면, 이제 SoundCloud 에서 제공하는 공개 API 를
사용할 차례이다. 로그인 후 우측 상단의 … 버튼을 클릭하여 개발자 사이트(Developers)로
접속하거나 직접 주소를 입력하여 개발자 사이트에 접속할 수 있다. 개발자 사이트의 주소로
개발자 페이지로 이동할 수 있다.
https://developers.soundcloud.com/
개발자 사이트에 접속하면, SoundCloud 에서 제공하는 공개 API 와 저작도구를 볼 수 있다.
우리는 우측 메뉴의 Register a new App 을 클릭하여, 우리가 만들 공개 API 어플리케이션을
등록하도록 한다.
[그림] 개발자 사이트의 Register a new app 을 선택한다.
등록 메뉴를 선택하면 새롭게 생성하고자 하는 어플리케이션 이름을 입력하는 화면이 나온다.
사용자에 따라서 원하는 이름을 입력할 수 있지만, 여기서는 RADIO 로 등록하여 진행해 보도록
하겠다. 어플리케이션 이름을 입력하고, Developer Policies 부분을 체크 한후 우측 하단의
Register 버튼을 클릭한다.
[그림] 앱 이름을 입력한 후 Register 버튼을 클릭
이로써 간단하게 공개 API 의 등록이 끝났다. 이후 모바일 개발을 위해서 Client ID 항목과
Client Secret 내용이 사용된다는 것을 기억해 두자. 다시한번 내용을 확인한 후(메모장에
붙여넣기 하여 보관하도록 하자) Save App 버튼을 클릭하여 공개 API 생성을 마치도록 한다.
실제 어플리케이션 개발을 위하여 등록된 공개 API 를 다시 확인하고자 할 때는,
https://developers.soundcloud.com/ 페이지에서 우측 하단의 Your App 메뉴를 클릭하면, 이미
생성한 공개 API 목록을 확인할 수 있다. 우리가 생성한 RADIO 를 클릭하면, 등록된 상세
정보를 다시 확인할 수 있다.
One More Thing –동영상이 필요할 땐 Youtube, 사진이 필요할땐 Flicker
SoundCloud 가 음악 만을 제공하고 있다면, 동영상을 제공하는 것은 Youtube 이다. 만일
만들고자 하는 것이 영상을 포함하는 미디어 플레이어라면, Youtube 를 사용할 수 있다.
Youtube 의 API 는 OpenAPI 라는 개념을 초기에 시작한 Google 에서 제공하고 있다. 보다
자세한 정보는 다음 링크에서 확인해 보자.
https://developers.google.com/youtube/v3/getting-started
만일, 라즈베리파이에 LCD 를 연결하여, 자동 디지털 액자를 만들고자 한다면, 가장 많은
사진이 공유되고 있는 Flicker 를 활용해 볼 수 있다. 자세한 정보는 다음 링크에서 확인하자.
https://www.flickr.com/services/developer
[그림] 사진과 영상을 연계하고자 한다면 Flicker 와 Youtube 를 사용할 수 있다.
공개 API 사용해 보기
실제로 등록한 공개 API 가 잘 등록되어 있는지 확인해 볼 수 있다. 사용하는 방법은 우리가 웹
주소를 입력하여 인터넷을 사용하는 방식인 HTTP url 을 호출하는 방식과, SoundCloud 에서
제공해 주는 소프트웨어 개발 키트(Software Developer Kit) 이른바 SDK 를 이용하는 방식
2 개로 구분이 된다. 우리는 간편한 개발을 위해, HTTP url 을 호출하는 방식을 사용하려고
한다. 해당 url 을 입력한 후, 우리가 공개 API 등록 후 제공받았던 Client ID 를 추가하여 검색해
보도록 한다.
사용법 https://api.soundcloud.com/tracks?client_id=[클라이언트 ID]&q=[검색어]
다음 예시 주소를 입력해 보자.
https://api.soundcloud.com/tracks?client_id=ef62c17e449fa9321b21733867159b1e&q=Big%2
0Hero
[
{
"download_url": null,
"key_signature": "",
"user_favorite": false,
"likes_count": 171682,
"release": "",
"attachments_uri":
"https://api.soundcloud.com/tracks/172055891/attachments",
"waveform_url": "https://w1.sndcdn.com/QnXGQzkYUaED_m.png",
"purchase_url": "http://smarturl.it/fobbh6",
"video_url": null,
"streamable": true,
"artwork_url": "https://i1.sndcdn.com/artworks-000093907678-
79gb6j-large.jpg",
"comment_count": 3905,
"commentable": true,
"description": ""Immortals" from Disney's Big Hero 6 (inspired
by the Marvel comic). Download it on iTunes
http://smarturl.it/fobbh6 Get the limited 7" vinyl + poster
http://bit.ly/1sutR2XnnListen to our new single "Centuries"
http://youtu.be/sCbS-TLEoRA Download it on iTunes
http://smarturl.it/centuries out now on DCD2/Island nnUpcoming
tour dates: http://falloutboy.com/tour
nnhttp://falloutboy.comnhttp://facebook.com/falloutboynhttp://tw
itter.com/falloutboynhttp://youtube.com/falloutboynhttp://instagra
m.com/falloutboynhttp://spoti.fi/T3yFgInn",
"download_count": 0,
"downloadable": false,
"embeddable_by": "all",
"favoritings_count": 0,
"genre": "fall out boy",
"isrc": null,
"label_id": null,
"label_name": null,
"license": "all-rights-reserved",
"original_content_size": 4632820,
"original_format": "mp3",
"playback_count": 10426258,
"purchase_title": null,
"release_day": null,
"release_month": null,
"release_year": null,
"reposts_count": 18821,
"state": "finished",
"tag_list": "alternative pop "big hero six"",
"track_type": null,
"user": {
"avatar_url": "https://i1.sndcdn.com/avatars-000121237593-
dl8xs0-large.jpg",
"id": 3678183,
"kind": "user",
"permalink_url": "http://soundcloud.com/falloutboy",
"uri": "https://api.soundcloud.com/users/3678183",
"username": "FallOutBoy",
"permalink": "falloutboy",
"last_modified": "2016/01/11 22:49:04 +0000"
},
"bpm": null,
"user_playback_count": null,
"id": 172055891,
"kind": "track",
"created_at": "2014/10/14 04:53:00 +0000",
"last_modified": "2016/01/20 20:50:42 +0000",
"permalink": "fall-out-boy-immortals-from-big-hero-6",
"permalink_url": "https://soundcloud.com/falloutboy/fall-out-
boy-immortals-from-big-hero-6",
"title": "Immortals [From Big Hero 6]",
"duration": 192983,
"sharing": "public",
"stream_url":
"https://api.soundcloud.com/tracks/172055891/stream",
"uri": "https://api.soundcloud.com/tracks/172055891",
"user_id": 3678183
},
{…}
]
[그림] 실제 SoundCloud 검색 결과로 받아지는 JSON 데이터 형식
우리는 음악 검색을 위해 q 필터를 이용하였다. q 는 음원 이름을 입력하여 검색 결과를 받는
가장 쉽고 간편한 방법으로, 실행해 보면 웹 상에서 검색 결과를 볼 수 있다. 입려어에 일치하는
다양한 검색 결과를 배열 형태로 반환하는 것을 알수 있다. 결과에서 보여주는 주요 내용은
다음과 같다.
항목 내용
Created_at 해당 음원이 등록된 시간을 보여준다.
Title 해당 음원의 타이틀 명을 보여준다.
Permalink_url SoundCloud 사이트에서 해당 음원의 페이지 정보를 보여준다.
Artwork_url 해당 음원의 대표 이미지 url 을 보여준다
Description 해당 음원의 상세 설명을 보여준다.
Duration 해당 음원의 총 재생 시간을 보여준다.
genre 해당 음원의 장르를 보여준다.
Playback_count 재생한 횟수를 보여준다
bpm Beat Per Minute 의 약자로 템포, 즉 음악의 빠르기를 나타낸다.
Release_year 음악이 공개된 연도를 나타낸다.
입력 인자 값으로 우리는 질의어인 q 만을 이용해 보았다. 하지만 이 항목에 좀더 다양한
인자값을 적용하면, 우리가 원하는 종류의 음악을 검색할 수 있는데, 추후 들은 음악을
기반으로 추천 서비스를 만들 때 사용될 항목이니 참고하고 넘어가도록 하자.
항목 내용
q 음악을 검색하기 위한 검색어를 입력한다.
genres 콤마로 구분되는 장르 목록을 입력한다.
Bpm[from] 입력 값 이상의 bpm 을 가지는 음반 목록을 검색한다.
Bpm[to] 입력 값 이하의 bpm 을 가지는 음반 목록을 검색한다.
Duration[from] 입력 값 이상의 재생 시간을 가지는 음반 목록을 검색한다.
Duration[to] 입력 값 이하의 재생 시간을 가지는 음반 목록을 검색한다.
음악을 실제로 재생해 보자 – scdl, MPlayer
Scdl 다운로드
사운드 클라우드의 API 를 이용하여 음악을 검색해 보았다. 하지만, 이렇게 검색한 결과로는 웹
페이지 상에서만 음악을 들을 수 있을 뿐이다. 이를 라즈베리파이에서 재생하기 위해서는 MP3
플레이어와 같이 MP3 파일이 라즈베리파이 상에 다운로드 되어 재생할 수 있는 구성으로
이루어져야 한다. 파이썬 언어로 개발된 SoundCloud DownLoader 즉, scdl 은 url 만 있으면
음원을 다운로드 받을 수 있게 해준다.
https://github.com/flyingrub/scdl
이 scdl 을 다운로드 받아 설치하기 위해서는 python 패키지 관리프로그램을 이용해야 한다.
python 에서 패키지 소프트웨어를 설치및 관리하는 패키지 관리 시스템으로 가장 많이
활용하는 것이 pip 이다. Python 2.7.9 과 3.4 이후 버전은 pip 를 기본적으로 포함하나,
라즈비안에는 제외되어 있다. Scdl 은 python 3.4 로 제작되어 있어,, Python 3 용 pip 를 설치해
주도록 한다
$ sudo apt-get install python3
$ sudo apt-get install python3-pip
Scdl 패키지를 설치하기 위해서는 다음과 같이 실행하면 된다.
$ sudo apt-get install pandoc
$ sudo pip3 install pypandoc
$ sudo pip3 install scdl
설치가 완료되면, scdl 이 정상적으로 동작하는지 확인해 본다.
$ scdl – version
[그림] SCDL 이 정상적으로 설치되었다.
만일 scdl 패키지를 삭제하기 위해서는 다음과 같이 실행한다
$ sudo pip3 uninstall scdl
One More Thing – scdll 이 정상적으로 설치되지 않는 경우
scdl 을 설치하기 위해서는 python 3.3 이상의 버전이 설치되어 있어야 한다. 신 버전인
jessie 의 경우 3.4 가 설치되어 있어 문제가 없지만, 라즈베리파이에 구 버전의 wheezy
기반의 라즈비안이 설치되어 있는 경우, python 버전이 3.2 이므로 설치시에 문제가
발생한다. 만일 상황에 따라 python 을 설치해야 하는 경우 다음의 절차를 따르도록 하자.
> 필수 요구사항 설치
sudo apt-get update
sudo apt-get upgrade -y
sudo apt-get install build-essential libncursesw5-dev libgdbm-dev libc6-dev
sudo apt-get install zlib1g-dev libsqlite3-dev tk-dev
sudo apt-get install libssl-dev openssl
> 라이브러리 다운로드 및 컴파일 수행
$ cd ~
$ wget https://www.python.org/ftp/python/3.4.4/Python-3.4.4.tgz
$ tar -zxvf Python-3.4.4.tgz
$ cd Python-3.4.4
$ ./configure
$ make
$ sudo make install
python 설치 확인
$ python3
> PIP 설치
$ cd ~
$ wget https://bootstrap.pypa.io/get-pip.py
$ sudo python3.4 get-pip.py
>pip 설치 확인
pip3 --version
> 기존 Python Overwrite 하기
sudo ln -sf /usr/local/bin/python3.4 /usr/local/bin/python3
이제 설치가 되지 않던 scdl 을 설치할 수 있을 것이다.
MP3 음악을 플레이 해 보자
라즈베리파이가 linux 로 구동이 되기 때문에 mp3 를 실행할 수 있는 프로그램이 다양하게
있다. 하지만, 라즈베리파이의 성능을 고려했을때, 가볍게 mp3 를 재생할 수 있는 mpg321
이라는 프로그램을 많이 사용한다. mpg321 을 설치하기 위해서 다음과 같은 명령을 입력해
보자
$ sudo apt-get -y install mpg321
mp3 가 재생이 잘 되는지 실험적으로 mp3 를 다운로드 받아서 재생해 보자
$ sudo wget http://www.freespecialeffects.co.uk/soundfx/household/bubbling_water_1.mp3
$ mpg321 bubbling_water_1.mp3
mp3 재생 시 g 옵션을 이용하여 볼륨을 조절할 수 있다. 최대 볼륨의 50%로 재생하기
위해서는 다음과 같이 명령을 입력하면 된다.
$ mpg321 -g 50 bubbling_water_1.mp3
[그림] 다운로드 후, 음악이 정상적으로 재생됨을 알수 있다.
이제 사운드클라우드에서 검색한 음악을 직접 재생해 보자. scdl 로 mp3 를 다운로드 하기
위해서는 url 을 알아야 한다.
https://api.soundcloud.com/tracks?client_id==ef62c17e449fa9321b21733867159b1e&q=Big%
20Hero 을 브라우저에서 입력하면 다음과 같은 결과가 보여지는데, 줄력값중 permalink_url 을
이용하면 된다.
{ ... permalink_url: "https://soundcloud.com/falloutboy/fall-out-boy-immortals-from-big-hero-
6", ... }
다음과 같이 명령어를 입력하면 mp3 파일이 다운로드 받아진다.
$ scdl -l [사운드클라우드 음악 URL]
$ scdl -l https://soundcloud.com/falloutboy/fall-out-boy-immortals-from-big-hero-6
받아진 음원을 mpg321 로 잘 재생이 되는지 확인해 본다,
$ mpg321 fall-out-boy-immortals-from-big-hero-6.mp3
MP3 를 재생하고 제어해 보자 - mplayer
MP3 를 검색하고 다운로드 받기위한 기본 준비를 마쳤다. 음악을 재생할때 오디오 화면과
리모콘에서 시간을 표시하기 위해서는 MP3 의 재생시간을 알아야하고, Rewind, Forward 같은
기능을 함께 지원해야 한다. 이러한 기능을 함께 지원하는 다양한 프로그램중 우리가 사용할
프로그램은 MPlayer 이다. 다음 명령을 이용하여 설치하도록 한다.
$ sudo apt-get install mplayer
음악을 재생하기 위해서는 파일명만 기입해도 되나, 총 재생 시간 및 세부적인 정보를 확인하기
위해서는 추가적으로 –identify 옵션을 사용할 수 있다.
$ mplayer -identify big_hero.mp3
[그림] 재생 시간 및 재생률, 볼륨 조절 및 감기, 되돌리기가 가능한 mplayer
MPlayer 는 음악 재생 중에 Backward/Forward, 볼륨 조절등 다양한 기능을 제공한다. 음악
재생 중에 사용할 수 있는 옵션들은 다음과 같다.
키 설명
LEFT,RIGHT 10 초 이전이나 이후로 재생위치를 변경한다.
UP,DOWN 1 분 이전이나 이후로 재생위치를 변경한다.
PG UP, PG DOWN 10 분 이전이나 이후로 재생위치를 이동한다.
<, > 재생목록의 이전 혹은 이후로 이동한다.
P, SPACE 일시 정지하고, 정지 상태에서 다시 누르면 재생된다.
Q, ESC 재생을 멈추고 프로그램을 종료한다.
*, / 볼륨을 올리거나 낮춘다.
M 음소거를 활성화 하거나 비 활성화 한다.
[. ] 재생을 느리게 하거나 빠르게 한다.
One More Thing – YTDL 을 이용한 Youtube 영상 받아서 재생하기
SoundCloud 가 scdl 이라는 프로그램이 있다면, Youtube 에는 ytdl 이 있다. SoundCloud 와
같이 Youtube 영상을 다운로드 받을 수 있게 도와주는 프로그램이다. NodeJS 로 제작되어
있으므로 NPM 을 이용하여 쉽게 설치할 수 있다.
$ sudo npm install ytdl
다음과 같이 url 을 입력하여, 원하는 영상을 받을 수 있다.
$ sudo ytdl http://www.youtube.com/watch?v=_HSylqgVYQI > myvideo.flv
보다 상세한 사용방법은 아래의 사이트에 접속하여 확인하도록 한다.
https://github.com/fent/node-ytdl
오늘의 날씨를 알아보기– RSS
오픈 API 와 유사하지만 또 다른 하나는 RSS(Rich Site Summary) 이다. 뉴스나 블로그
사이트들에서 주로 사용하는 컨텐츠 표현 방식으로, XML 형식으로 요약된 웹 정보를 제공하여,
사이트에 방문하지 않고도 최신 정보를 간단하게 확인할 수 있다. 흔히 RSS 정보를 제공하는
데이터 형식을 RSS Feed 라 칭하기도 한다.
[그림] 뉴스 및 블로그 사이트에서 RSS 형식의 데이터를 제공한다.
기상청에서는 RSS 를 이용하여 오늘의 날씨를 간편하게 확인할 수 있도록 정보를
제공하고있다. 기상청에서 제공하는 날씨 정보를 다음의 URL 로 각 지역의 날씨 정보를 RSS
형식으로 제공한다.
[그림] 다양한 지역의 날씨 정보를 제공하는 기상청 사이트
지역 RSS 주소
전국 http://www.kma.go.kr/weather/forecast/mid-term-rss3.jsp?stnId=108
서울, 경기도 http://www.kma.go.kr/weather/forecast/mid-term-rss3.jsp?stnId=109
강원도 http://www.kma.go.kr/weather/forecast/mid-term-rss3.jsp?stnId=105
충청북도 http://www.kma.go.kr/weather/forecast/mid-term-rss3.jsp?stnId=131
충청남도 http://www.kma.go.kr/weather/forecast/mid-term-rss3.jsp?stnId=133
전라북도 http://www.kma.go.kr/weather/forecast/mid-term-rss3.jsp?stnId=146
전라남도 http://www.kma.go.kr/weather/forecast/mid-term-rss3.jsp?stnId=146
경상북도 http://www.kma.go.kr/weather/forecast/mid-term-rss3.jsp?stnId=143
경상남도 http://www.kma.go.kr/weather/forecast/mid-term-rss3.jsp?stnId=159
제주도 http://www.kma.go.kr/weather/forecast/mid-term-rss3.jsp?stnId=184
[그림] rss 로 제공되는 기상청 날씨 정보. XML 형식이라 그대로 사용하기 쉽지 않다.
기상청에서는 RSS 로 지역별 날씨정보를 제공하고 있다. 이를 응용할 수 있는데, XML 형식으로
제공되어 실제로 원하는 데이터만 추출하여 사용하기 쉽지 않다. 9 장에서 데이터를 변환하여
라즈베리파이로 읽어주는 기능을 구현하는 법을 다룰 것이다.
One More Thing – 뉴스 정보를 RSS 로 제공 받기
날씨 정보와 더불어 RSS 를 이용하여 정보를 가져오기 좋은 정보중 하나가 뉴스이다. 다양한
언론사에서는 RSS 형식으로 자사의 뉴스 정보를 제공하고 있다. 다음 링크에 접속하면,
언론사들의 RSS 제공 주소를 확인할 수 있다.
http://bit.ly/1JaZhD7
지능형 서비스 추가하기 - BOT API
2015 년 DARPA 로봇 챌린지에서 KAIST 휴보의 우승과 2016 년 인간의 영역으로 영원할 것
같았던 바둑에서의 알파고의 대 활약으로, 지능형 서비스에 대해 관심이 높아지고 있다. 애플의
시리(Siri)를 비롯하여, 구글의 나우(Now), MS 의 코나타(Cortana) 등 다양한 지능형 서비스들이
많은 영역에서 사용되고 있다. 페이스북도 메신저에 M 이라 불리는 인공지능 비서 기능을
탑재하기로 밝혔는데, 여기에는 Wit.ai 사의 봇 엔진이 사용되고 있다.
자세히 다루는 것은 이 책의 범위를 벗어나므로, 간단히 소개할 예정이다. 보다 관심이 있는
독자라면, 해당 문서를 살펴보도록 하자.
[그림] 페이스북의 지능형 메신저 M 에 적용된 Wit.ai 의 봇 엔진
Wit.ai 사의 봇 엔진은 일반인도 사용할 수 있도록 오픈되어 있다. Wit.ai 를 이용하여 대화나
명령에 따른 반응형 서비스를 제공할 수 있는 지능형 서비스가 가능하며, 유사한 서비스로
Api.ai 라는 서비스가 존재한다. 이 API 를 이용하면, 사용자가 입력한 문장의 의도를
파악하거나, 그에 따른 상호 동작을 지정하는데 도움이 된다.
[그림] clarifai 사의 API 를 활용하면, 이미지 정보가 의미하는 바를 추출할 수 있다.
라즈베리파이의 경우 카메라 장착이 가능하므로, 추후 이미지 인식 기반의 서비스로 발전시킬
수도 있다. 영상 및 이미지 인식으로 많이 언급되는 것은 Google Cloud Vision API 와 clarifai
API 가 있다. 이미지를 입력하면, 해당 이미지가 의도하는 정보를 추출하여 알려주므로, 시각적
정보 기반으로 인터랙션이 필요한 경우 유용하게 활용될 수 있다. Clarifai 의 경우 설정을 통해
결과 단어를 한글로 받을 수도 있으니 참고하도록 하자.
이번 장을 정리하며
이번 장에서는 OpenAPI 를 이용하여 음악을 검색하고 재생해 보았으며, 기상청 RSS 정보를
이용하여 날씨 정보를 확인하였다. 여기서 소개한 서비스 이외에 다양한 OpenAPI 를 접목하면,
만들고자 하는 서비스를 좀더 풍요롭고 적은 노력으로 구성할 수 있을 것이다. (OpenAPI 로
검색하면 제공되는 다양한 서비스를 확인할 수 있다.) 다음 장에서는 배운 내용을 바탕으로
라즈베리파이로 오디오를 만드는 방법을 다룰 것이다.
7. 오디오 소프트웨어 개발 하기
우리는 지금까지 라즈베리파이 오디오 개발을 위한 기본학습을 마무리 했다. 본격적으로
하드웨어를 조립하고 소프트웨어을 작성하여 여러분 만의 파이오를 만들 차례이다. 파이오를
만드는 구조는 다음의 순서로 진행이 될 것이다. 가장 기본 기능인 음악 검색 및 재생 기능을
만들고, 알람기능, 날씨 및 뉴스 안내기능, 환경설정 기능등을 다룬다.
오디오가 말하도록 해 보자 – TTS 기능 설치
라즈베리파이에서 TTS 를 구현할 수 있는 방법 중 우리는 구 안드로이드에 탑재 된 바 있는
SVOX 사의 pico TTS 엔진을 설치할 것이다. 라즈비안 패키지 저장소에 포함되어 있지 않아,
수동으로 받아서 설치를 해 주어야 한다. 다음 3 가지의 패키지를 다운로드 받아주도록 한다.
$ sudo wget
http://http.us.debian.org/debian/pool/non-free/s/svox/libttspico-data_1.0+git20130326-
3_all.deb
$ sudo wget http://http.us.debian.org/debian/pool/non-
free/s/svox/libttspico0_1.0+git20130326-3_armhf.deb
$ sudo wget http://http.us.debian.org/debian/pool/non-free/s/svox/libttspico-
utils_1.0+git20130326-3_armhf.deb
패키지의 다운로드를 끝마쳤다면, 해당 패키지를 설치해 주도록 한다.
$ sudo dpkg -i libttspico-data_1.0+git20130326-3_all.deb
$ sudo dpkg -i libttspico0_1.0+git20130326-3_armhf.deb
$ sudo dpkg -i libttspico-utils_1.0+git20130326-3_armhf.deb
모든 패키지의 설치를 마쳤다면, TTS 동작이 잘 되는지 다음 명령어로 확인해 본다.
$ pico2wave --wave test.wav "Hello World" && aplay test.wav
Hello World 라는 음성이 제대로 출력되는 것을 확인할 수 있을 것이다.
[그림] Pico TTS 를 이용하면 라즈베리파이에서 Text To Speech 기능을 활용할 수 있다.
One More Thing – 구형 라즈베리파이에서 TTS 사용하기
위의 방법은 최신 라즈베리파이 2 에서만 적용이 되고, 구형 라즈베리파이 B+ 혹은 A+
이하의 기종에서는 CPU 의 차이로 인하여 동작하지 않는 문제가 발생한다. 구형 기종
사용시에는 wheezy 기반의 패키지를 설치해야 구형 라즈베리파이와 신형 라즈베리파이
모두 잘 동작한다.
$ sudo wget http://old-releases.ubuntu.com/ubuntu/pool/multiverse/s/svox/libttspico-
data_1.0+git20110131-2_all.deb
$ sudo wget http://old-
releases.ubuntu.com/ubuntu/pool/multiverse/s/svox/libttspico0_1.0+git20110131-
2_armhf.deb
$ sudo wget http://old-releases.ubuntu.com/ubuntu/pool/multiverse/s/svox/libttspico-
utils_1.0+git20110131-2_armhf.deb
다운로드 후, Package 디렉토리로 이동한 후 패키지를 설치해 주면 정상적으로 pico2wave
명령어를 사용할 수 있다.
$ sudo dpkg -i libttspico-data_1.0+git20110131-2_all.deb
$ sudo dpkg -i libttspico0_1.0+git20110131-2_armhf.deb
$ sudo dpkg -i libttspico-utils_1.0+git20110131-2_armhf.deb
음악 검색 및 재생기능을 만들자
파이 오디오를 위해 GPIO 에 연결해 줄 장치는 2 개이다. LED 는 백 라이트 역할을 하고, 7
Segment 디스플레이는 현재 시간 및 재생 시간을 표시해 주는 용도로 사용되게 된다. 특이
사항으로는 7 Segment 의 5v 전원을 GPIO 핀에 연결한 것이다. 5v 가 연결된 상태로
라즈베리파이가 부팅 될 때, 디스플레이가 오 동작하는 문제가 발생할 수 있어, GPIO 를
이용하여 부팅 후 추가 전원을 공급해 주는 방식을 사용할 것이다.
[그림] 파이 오디오 하드웨어 연결에는 2 가지 부품을 GPIO 에 연결해 준다.
이제 가장 기본 기능인 음악 검색 및 재생 기능을 구현해 볼 것이다. 평상 시에는 시계로
동작하다가, 음악 검색 후 재생 명령을 내리면, 해당음악을 재생해주고 남은 재생 시간을
표시해 주는 기능을 제공한다. 아울러 음악 재생시 볼륨 조절이 가능하도록 관련 기능을
만든다. 이번 장 부터는 구현할 내용이 많으므로, NodeJS 의 모듈 기능을 이용하여 파일을
분류하도록 한다.
[ ch7_1_server.js ]
var express = require('express');
var bodyParser = require('body-parser');
var path = require('path');
var exec = require('child_process').exec;
var spawn = require('child_process').spawn;
var ext = require('./ch7_1_extend');
var app = express();
var server = require('http').Server(app);
var io = require('socket.io')(server);
var isTime = true;
var current_music;
app.use(bodyParser.json());
app.use(bodyParser.urlencoded({ extended: true }));
app.use(express.static(path.join(__dirname, '/')));
// 처음 구동 시, 현재 시각을 화면에 표시한다.
ext.setCurrentTime();
// 10초 간격으로 현제 시간을 측정하여 화면을 갱신한다.
setInterval(function(){
if(isTime){
ext.setCurrentTime();
}
},10000);
// 클라이언트로부터 재생 명령이 수행될 때 호출된다.
app.post('/play', function(req, res){
// 사운드 클라우드의 URL을 구한다.
var url = req.body.url;
console.log(__dirname + ' / ' + url);
isTime = false;
ext.tts('Downloading...');
// SCDL 명령어를 실행하여 음악을 임시로 다운로드 한다.
exec('sudo scdl -l ' + url, function(err, stderr, stdout){
// 음악 파일의 이름이 존재하는지 확인한다.
if(!err && stdout.indexOf('[37mDownloading ') > -1){
// 음악 파일의 이름을 구한다.
var file = stdout.split('[37mDownloading
')[1].split('[0mn')[0] + '.mp3';
// 현재 재생 음악을 기억해 둔다.
current_music = file;
console.log('Playing : ' + file);
ext.tts('Playing the music.');
// mplayer 명령으로 음악을 재생한다.
var ps = spawn('mplayer',['/' + file]);
// mplayer 실행시 로그에 의해 호출된다.
ps.stderr.on('data', function(data){
// 다국어 문자를 위해 utf8 형식으로 전환한다.
var progress = data.toString('utf8');
// 화면 및 클라이언트에 표시할 재생 시각을 구한다.
if(progress && progress.indexOf('of') > -1){
var token = progress.split(' of ');
// 재생 시각을 구한다.
var current = token[0].split(' ').slice(-2)[0];
var total = token[1].split(' ')[0]
// 재생 시간을 화면에 표시한다.
ext.setTime(Math.round(total - current));
// 재생 시간을 클라이언트에 표시하기 위해 전달한다.
io.emit('time', { total : total, current :
current});
}
});
// mplayer 동작이 끝날 때 호출된다.
ps.on('close', function(code){
console.log(code);
ext.tts('Finished');
isTime = true;
});
} else {
console.log(err);
}
});
res.json({ result : true});
});
// 음악 재생 목록을 확인할 때 호출된다.
app.post('/list', function(req, res){
res.json({ list : ext.get()});
});
// 사운드 볼륨을 조절할 때 호출된다.
app.post('/volume', function(req, res){
var vol = req.body.volume;
// 볼륨 값을 조정한다. 단, 너무 자주 호출되는 것을 방지하기 위해
// 한번 조정 후 5초간 유예 기간을 가지게 한다.
isTime = false;
ext.setVolume(vol);
setTimeout(function(){
isTime = true;
},5000);
res.json({ result : true });
});
// 정지한 음악을 다시 재생할 때 호출 한다.
app.post('/resume', function(req, res){
// 기존에 나오는 음악이 있다면 강제로 종료한다.
exec('pkill mplayer');
isTime = false;
console.log('Resume : ' + current_music);
ext.tts('Playing the music.');
// 마지막으로 재생한 음악을 다시 재생한다.
var ps = spawn('mplayer',['/' + current_music]);
ps.stderr.on('data', function(data){
var progress = data.toString('utf8');
if(progress && progress.indexOf('of') > -1){
var token = progress.split(' of ');
var current = token[0].split(' ').slice(-2)[0];
var total = token[1].split(' ')[0]
ext.setTime(Math.round(total - current));
io.emit('time', { total : total, current : current});
}
});
ps.on('close', function(code){
console.log(code);
ext.tts('Finished');
isTime = true;
});
res.json({ result : true });
});
// 음악 정지가 요청될 때 호출된다.
app.post('/stop', function(req, res){
// 음악 재생시 사용하는 mplayer를 강제로 종료한다.
exec('pkill mplayer');
res.json({ result : true });
isTime = true;
});
server.listen(8080);
console.log('piAu Server');
주 서버 코드로서, 음악을 다운로드 받아 재생하고, 멈추고, 볼륨 조절 명령을 모바일로부터
받을 수 있도록 서비스를 열어 두었다. Mplayer 실행 후 음원에 대한 정보와 함께 재생 시간을
표시해 주는데, 외부 프로세스를 실행하는데 사용한 exec 와 달리 spawn 은 실시간으로 정보를
가져오는 데 적합하여 해당 함수를 이용하여 음악 재생을 실행한다. 재생중에 나오는 재생 시간
정보를 가져와서 화면에 표시해 주고, 모바일에 진행 정보를 전달한다. 단, 감지되는 재생
정보가 스트림 정보로, 사람이 알수 없는 Byte 코드 값으로 보여진다. 따라서 toString(‘utf8’)
명령을 이용하여 일반 텍스트로 전환하여 사용하도록 한다.
[ ch7_1_extend.js ]
var exec = require('child_process').exec;
var MAX7219 = require('./MAX7219');
var disp = new MAX7219("/dev/spidev0.0");
// 자바스크립트에서 저장소로 사용할 수 있는 로컬스토리지 모듈을 호출한다.
var LocalStorage = require('node-localstorage').LocalStorage;
// 음악 재생 목록을 관리할 저장소를 준비한다.
var localStorage = new LocalStorage('./music_list');
disp.setDecodeAll();
disp.setScanLimit(4);
disp.setDisplayIntensity(15);
disp.startup();
var volume = 100;
// 1초 간격으로 거리 값을 측정한다.
var setTime = function(number1, number2){
console.log(number1 + '/' + number2);
var min1 = Math.floor(number1 / 10);
var min2 = number1 % 10;
// 초의 10단위를 구한다.
var sec1 = Math.floor(number2 / 10);
// 초의 1단위를 구한다. (10으로 나눈 나머지)
var sec2 = number2 % 10;
disp.setDigitSymbol(0, min1);
disp.setDigitSymbol(1, min2);
disp.setDigitSymbol(2, sec1);
disp.setDigitSymbol(3, sec2);
}
// pico2tts 로 tts 음성파일을 만들고 곧바로 재생한다.
var tts = function(msg){
exec('pico2wave --wave tmp.wav "' + msg + '" && aplay
tmp.wav');
}
// 입력받은 시간 값을 7세그먼트에 출력한다.
exports.setTime = function(remain){
setTime(Math.floor(remain / 60), remain % 60);
}
// 현재 시간을 구하여 출력한다.
exports.setCurrentTime = function(){
var date = new Date();
setTime(date.getHours(), date.getMinutes());
}
// 볼륨을 설정한다.
exports.setVolume = function(vol){
if(vol == 'up'){
if(volume < 100){
volume += 5;
}
} else {
if(volume > 0){
volume -= 5;
}
}
tts('Volume ' + volume + '%');
// 볼륨을 설정하는 amixer 를 이용해 시스템 볼륨을 조정한다.
exec('amixer sset PCM ' + volume + '%');
}
exports.tts = tts;
시간을 표시할 때는 4 자리 digit 을 활용해 표시해 주는데, 160 초의 재생 시간을 가지고
있다면 2 분 40 초, 즉 화면상에는 0240 으로 표시를 해 주어야 한다. 초 단위의 시간을 분
단위로 표시해 주기 위해 보정함수를 사용해 주도록 한다. 오디오 볼륨 조절의 경우 amixer
프로그램을 이용하여 출력을 조절 할 수 있는데, TTS 를 위한 pico2wave 와 함께 exec
명령으로 해당 프로그램을 실행하는 방법을 사용한다.
[ ch7_1.html ]
<html>
<head>
<title>Mobile Web</title>
<meta name="viewport" content="width=device-width, initial-
scale=1.0, user-scalable=no" />
<link rel="stylesheet"
href="http://code.jquery.com/mobile/1.4.5/jquery.mobile-
1.4.5.min.css" />
<script src="http://code.jquery.com/jquery-
2.2.0.min.js"></script>
<script
src="http://code.jquery.com/mobile/1.4.5/jquery.mobile-
1.4.5.min.js"></script>
<script src="https://connect.soundcloud.com/sdk/sdk-
3.0.0.js"></script>
<script src="/socket.io/socket.io.js"></script>
<script src='./ch7_1.js'></script>
</head>
<body>
<div data-role="page" id='music' class='ui-page-active'>
<div data-role='header' data-theme='b' data-
position='fixed'>
<h1>Music</h1>
</div>
<div data-role='main' class='ui-content'>
<div data-role='fieldcontain'>
<input type="search" name="search" id="search"
placeholder="Search for content..."/>
</div>
<div data-role='fieldcontain'>
<ul data-role='listview'>
<li>No musics</li>
</ul>
</div>
</div>
</div>
<div data-role="page" id='play'>
<div data-role='header' data-theme='b' data-
position='fixed'>
<h1>Music Play</h1>
</div>
<div data-role='main' class='ui-content'>
<h2>Title</h2>
<input id='time' type="range" min="0" max="100"
value="0" />
<div class='ui-grid-c'>
<div class='ui-block-a'>
<a href='#' id='resume' data-
role='button'>Play</a>
</div>
<div class='ui-block-b'>
<a href='#' id='stop' data-
role='button'>Stop</a>
</div>
<div class='ui-block-c'>
<a href='#' class='ui-icon-plus' id='vol_up'
data-role='button'>+</a>
</div>
<div class='ui-block-d'>
<a href='#' class='ui-icon-minus'
id='vol_down' data-role='button'>-</a>
</div>
</div>
</div>
</div>
</body>
</html>
jQueryMobile 을 이용하여 음악 검색과 재생 페이지를 구현한다. 여러 페이지를 하나의 HTML
에 만드는 경우, 처음 보여지는 페이지는 ui-active-page 클래스를 추가하여 첫 페이지를
설정할 수 있다. 음악 검색 화면을 첫번째 화면으로 설정하도록 하자.
[ ch7_1.js ]
// 사운드 클라우드 가입시 받은 client_id 를 입력한다.
SC.initialize({
client_id: 'ef62c17e449fa9321b21733867159b1e'
});
$(function(){
var socket = io();
// 시간을 전달 받아 화면에 표시한다.
socket.on('time', function(data){
$('#time').val(Math.round(data.current * 100/ data.total));
$('#time').slider( "refresh" );
});
// 웹 페이지가 불러질 때 발생한다.
$(document).on('pageshow', function(){
//alert($.mobile.activePage.attr("id"));
});
// 검색창에서 키가 눌러졌을 때 호출된다.
$('#search').keypress(function(e){
// 엔터값 (아스키 코드로 13임)이 눌러졌을 때 호출된다.
if (e.which == 13) {
// 검색 어를 구한다.
var query = $('#search').val();
// 클릭 이벤트를 강제로 발생시켜, 가상키보드를 화면에서 감춘다.
$('body').trigger('click');
$('#search').val('');
// 검색 결과 창을 초기화 한다.
$('#music ul[data-role=listview]').empty();
// 사운드 클라우드를 통해 음악을 검색한다.
SC.get('/tracks', { q: query }).then(function(tracks) {
// 검색된 음악 값을 하나씩 가져온다.
for(var i in tracks){
var item = tracks[i];
console.log(item);
var elem = $('<a>').attr({
'data-transition' : 'pop',
'href' : '#play'
});
// 화면에 표시할 음악 아이템 정보를 생성한다.
elem.data('item',{
title : item.title,
img : item.artwork_url,
url : item.permalink_url,
desc : item.description,
dur : item.duration,
genre : item.genre,
bpm : item.bpm,
cnt : item.playback_count,
time : Date.now()
}).addClass(‘play’);
// 화면에 보여줄 데이터를 생성한다.
var list = $('<li>').append(elem);
elem.append($('<img>').attr('src',item.artwork_url));
elem.append($('<h2>').text(item.title));
elem.append($('<p>').text(item.description));
// 검색 결과 리스트에 추가한다.
$('#music ul[data-role=listview]').append(list);
}
// 리스트 목록을 jquerymobile 의 리스트로 갱신한다.
$('#music ul[data-
role=listview]').listview('refresh');
});
}
});
// 음악 목록에서 음악을 클릭했을 때 호출된다.
$('ul[data-role=listview]').on('click','a.play', function(){
// 선택된 아이템으로부터 음악 정보를 가져온다.
var item = $(this).data('item');
var img = $(this).attr('img');
var title = $(this).attr('title');
// 음악 이름을 화면에 표시해 준다.
$('#play h2').text(item.title);
// 라즈베리파이 서버의 음악 재생을 호출한다.
$.post('/play', item);
// 재생 배경을 음악 타이틀의 이미지로 변경한다.
$('#play').css({
'background-image': 'url("' + item.img + '")',
'background-size' : '100% 100%'
});
});
// 다시 재생 클릭시 호출된다.
$('#resume').click(function(){
$.post('/resume');
});
// 정지 클릭시 호출된다.
$('#stop').click(function(){
$.post('/stop');
});
// 볼륨 키우기를 눌렀을 때 호출된다/
$('#vol_up').click(function(){
$.post('/volume', { volume : 'up' });
});
// 볼륨 낮추기를 눌렀을 때 호출된다.
$('#vol_down').click(function(){
$.post('/volume', { volume : 'down' });
});
});
검색된 결과를 화면에 표시해 주고 재생하는 부분을 추가한다. 리스트에 검색 결과를 추가하고,
jQueryMobile 에서 제공하는 listview 갱신 기능을 이용하여 화면에 표시해 준다. 이렇게
추가된 하나의 아이템을 클릭할 때, 재생 정보를 가져와 라즈베리파이에 전달하고, 재생화면의
배경을 타이틀 이미지로 보여주는 코드를 작성한다. 재생화면에서 사용될 정지, 재생, 볼륨
조절 기능을 추가하면 간단한 재생 기능 만들기는 마무리 된다.
[그림] 음악 검색결과를 선택하면 간단하게 음악을 재생해 준다.
음악 리스트를 관리 해 보자 – Local Storage
간단하게 음악을 검색하고 재생하는 기능을 추가해 보았다. 하지만, 이미 들었던 음악을 다시
듣기 위해서는 다시금 검색해서 해당음악을 선택해 주어야 하는 수고스러움이 발생한다.
재생한 음악 목록을 보관하고, 이를 리스트로 보여주어서 다시 재생할수 있도록 관리해 주는
기능을 추가해 보자. 음악이 임시 저장이 된 후 재생이 되는 구조이므로, 내부 저장소에 한계가
발생할 수 있다. 그러므로, 리스트를 관리하는 모듈에서는 디스크 용량을 확인하고, 가장
오래전에 재생된 목록과 임시 저장파일은 삭제하는 기능도 함께 추가하도록 한다. 데이터 저장
및 관리를 위해 MongoDB 나 MySQL 과 같은 별도의 데이터베이스를 이용할수도 있지만,
HTML5 에서 지원하는 저장소인 LocalStorage 방식을 Node JS 에서 사용하여 음악 목록
데이터를 관리할 것이다.
[ ch_7_2_server.js ]
…
var store = require('./ch7_1_store');
…
// 재생 이력을 가져올 때 호출된다.
app.post('/history', function(req, res){
res.json({ result : store.gets()})
});
…
모바일에서 조회 명령을 내릴 수 있도록 조회 기능을 추가한다. JSON 반환 값으로 재생 목록을
전달하게 된다.
[ ch_7_2.html ]
<div data-role="page" id='music' class='ui-page-active'>
…
<div data-role='footer' data-theme='b' data-position='fixed'>
<div data-role='navbar'>
<ul>
<li><a href='#music' data-icon='grid' data-
transition='flip'>Music</a></li>
<li><a href='#radio' data-icon='star' data-
transition='slidedown'>Top</a></li>
<li><a href='#history' data-icon='clock' data-
transition='flow'>History</a></li>
<li><a href='#alarm' data-icon='comment' data-
transition='turn'>Alarm</a></li>
</ul>
</div>
</div>
</div>
<div data-role="page" id='play'>
…
</div>
<div data-role='footer' data-theme='b' data-position='fixed'>
<div data-role='navbar'>
<ul>
<li><a href='#music' data-icon='grid' data-
transition='flip'>Music</a></li>
<li><a href='#radio' data-icon='star' data-
transition='slidedown'>Top</a></li>
<li><a href='#history' data-icon='clock' data-
transition='flow'>History</a></li>
<li><a href='#alarm' data-icon='comment' data-
transition='turn'>Alarm</a></li>
</ul>
</div>
</div>
</div>
<div data-role="page" id='history'>
<div data-role='header' data-theme='b' data-position='fixed'>
<h1>History</h1>
<div data-role='navbar'>
<ul>
<li><a href='#' id='his'>History</a></li>
<li><a href='#' id='recom'>Recommandation</a></li>
</ul>
</div>
</div>
<div data-role='main' class='ui-content'>
<div data-role='fieldcontain'>
<ul data-role='listview' data-filter='true' data-filter-
placeholder='Search the title from history'>
<li>No musics</li>
</ul>
</div>
</div>
<div data-role='footer' data-theme='b' data-position='fixed'>
<div data-role='navbar'>
<ul>
<li><a href='#music' data-icon='grid' data-
transition='flip'>Music</a></li>
<li><a href='#radio' data-icon='star' data-
transition='slidedown'>Top</a></li>
<li><a href='#history' data-icon='clock' data-
transition='flow'>History</a></li>
<li><a href='#alarm' data-icon='comment' data-
transition='turn'>Alarm</a></li>
</ul>
</div>
</div>
</div>
재생 목록을 보여줄 페이지를 추가해 준다. 기본 구조는 음악 검색 페이지와 동일한 구조를
가진다. 다만, 목록내 검색 기능을 위해 listview 옵션 태그에서 data-filter=’true’ 를 추가해
주도록 한다. 앞으로 제작할 추천 기능들을 위해 하단 네비게이션 버튼을 미리 만들어 두도록
한다.
[ ch_7_2_store.js ]
var LocalStorage = require('node-localstorage').LocalStorage;
var exec = require('child_process').exec;
var space = require('diskspace');
localStorage = new LocalStorage('./music_list');
// 저장되어 있는 재생 목록을 가져온다.
var getList = function(){
var list = localStorage.getItem('music_list');
if(list == null){
return [];
} else {
return JSON.parse(list);
}
}
// 음악 재생 목록에 추가한다.
exports.add = function(item, file){
var list = getList();
// 동일 음악이 존재하는 지 확인하고 있다면 배열의 가장 앞으로 이동한다.
for(var i in list){
if(list[i].url == item.url){
list.splice(i, 1);
}
}
item.file = file;
list.push(item);
// 저장장치 공간을 확인하여, 여유 공간이 적다면, 마지막 데이터를 삭제한다.
space.check('/', function (err, total, free, status){
if(free * 100 / total < 5){
console.log(free * 100 / total);
var last = list.shift();
exec('sudo rm /' + last.file);
}
localStorage.setItem('music_list',JSON.stringify(list));
});
}
// url 로 저장 내역이 있는지 확인한다.
exports.get = function(url){
var list = getList();
for(var i in list){
if(list[i].url == url){
return list[i];
}
}
}
//목록을 가져온다.
exports.gets = function(){
return getList();
}
HTML5 의 LocalStorage 를 이용하여 데이터를 저장하고 불러오는 코드이다. Localstorage
특성상 문자열 형태의 데이터만을 저장할 수 있다. 따라서, 우리가 저장에 사용되는 배열이나
객체 형태의 데이터는 문자형태로 전환하고, 다시 이를 객체로 변경하여 사용을 해야 하는데,
문자형태로의 전환을 위해 JSON.stringify 를 이용해 문자로 저장하도록 한다. 반대로 복원할
시에는 JSON.parse 를 이용하면 된다.
[ ch7_js ]
…
// 목록을 가져와서 화면에 출력한다.
var getList = function(){
$.post('/history', function(data){
// 기존 목록을 삭제한다.
$('#history ul[data-role=listview]').empty();
// 데이터 목록을 하나씩 가져온다.
for(var i in data.result){
var item = data.result[i];
// 화면에 보여줄 객체에 정보 데이터를 생성한다.
var elem = $('<a>').attr({
'data-transition' : 'pop',
'href' : '#play'
}).addClass('play');
elem.data('item',item);
// 화면에 표시할 정보를 입력한다.
var list = $('<li>').append(elem);
elem.append($('<img>').attr('src',item.img));
elem.append($('<h2>').text(item.title));
elem.append($('<p>').text(item.desc));
// 재생 목록 리스트에 추가한다.
$('#history ul[data-role=listview]').append(list);
}
// 리스트를 jquerymobile 방식으로 초기화 한다.
$('#history ul[data-
role=listview]').listview('refresh');
});
}
// 페이지가 호출될 때 발생한다.
$(document).on('pageshow', function(){
var pageId = $.mobile.activePage.attr("id");
switch(pageId){
// 재생 목록 페이지가 호출될 때 발생한다.
case 'history':
getList();
}
});
// history 버튼 클릭시 발생한다.
$('#his').click(function(){
getList();
});
…
재생 목록을 요청하는 부분으로, 데이터를 요청하고 가져오는 프로그램은 음악 검색과
유사하다. 다만, 재생 목록 페이지가 보여진 이후 명령이 호출되도록 pageshow 이벤트에서
현재 페이지가 재생 목록 페이지, 즉 id 가 history 일때 재생 목록 호출이 이루어지는 함수를
추가하도록 한다.
[그림] 과거에 들은 음악 목록을 확인하고, 다시 곧바로 재생할 수도 있다.
음악 추천하기 - Recommandation
복잡한 추천 기능을 다루는 것은 책의 주제를 벗어나는 범위이므로, 매우 간단한 음악 추천
기능을 넣고자 한다. 우리의 라즈베리파이에 넣을 추천 기능은, 사용자가 들은 음악, 즉 바로
이전에 구현한 재생 목록을 기반으로 한다. 해당 사용자가 가장 즐겨 듣는 장르의 음악을
파악하고, 해당 장르 음악의 BPM(Beat Per Minute) 대역, 음악 재생 길이의 범위등을 해당
조건에 맞는 음악을 검색 해 준다. 범위를 구할 때 사용하는 부가적인 기능을 위해 nodejs 에서
통계 기능을 지원하는 stats-analysis 모듈을 설치해 주도록 한다.
$ npm install –g stats_analysis
통계 모듈에서 사용하는 기능은 평균에서 너무 벗어나는 이상 값(Outlier) 를 제거하는 기능을
사용한다. 즉, 사람 키 평균을 구한다고 할 때 160~180 에 일반적으로 분포하는데, 실수로
10 을 입력하거나 1000 을 입력하는등 너무나 벗어나는 값들을 제거하는데 사용하는 기능이다.
[ ch_7_2_server.js ]
…
var recom = require('./ch7_1_recom');
…
// 추천 요청이 발생했 을 때 추천음악 리스트를 반환한다.
app.post('/recom', function(req, res){
res.json({ result : recom.getParam()});
});
…
추천은 결국 특정 조건을 음악 검색에 활용하는 것이다. 추천 검색에 활용할 인자 값을
반환하는 호출부를 추가한다.
[ ch_7_2_recom.js ]
// 통계처리 모듈을 불러온다.
var stats = require("stats-analysis");
var store = require('./ch7_1_store');
var genres = [];
var durs = [];
var bpms = [];
// 선택한 장르의 재생된 횟수를 확인한다.
var setCount = function(genre){
for(var i in genres){
if(genres[i].genre == genre){
genres[i].count++;
return;
}
}
genres.push({ genre : genre, count : 1});
}
// 가장 많이 들은 장르를 확인한다.
var getTopGenre = function(){
// 가장 많이 들은 음악을 내림차순으로 정렬한다.
genres.sort(function(a, b){
return b.count - a.count;
});
// 가장 많이 들은 음악 장르를 반환한다.
return genres[0];
}
// 들었던 음악 목록을 이용하여 음악을 추천한다.
exports.getParam = function(){
// 들었던 음악 목록을 불러온다.
var list = store.gets();
genres = [];
durs = [];
bpms = []
// 장르별 음악 재생 횟수를 확인한다.
for(var i in list){
setCount(list[i].genre);
}
// 가장 많이 재생된 장르를 구한다.
var item = getTopGenre();
// 음악 재생 시간과 박자 정보를 저장한다.
for(var i in list){
if(list[i].genre == item.genre){
durs.push(list[i].dur);
bpms.push(list[i].bpm);
}
}
// 박자와 재생 시간 목록에서 이상 치 값을 제거한다.
durs = stats.filterOutliers(durs);
bpms = stats.filterOutliers(bpms);
// 검색에 사용하는 추천 정보를 반환한다.
return {
'genres' : item.genre,
'bpm[from]' : Math.min(bpms),
'bpm[to]' : Math.max(bpms),
'duration[from]' : Math.min(durs),
'duration[to]' : Math.max(durs),
}
}
음악 재생 목록중에 가장 많이 들은 장르의 음악을 찾는다. 해당 장르의 비트와 재생 시간을
수집하여, 평균에서 크게 벗어나는 값 (Outlier)을 제거하고, 장르, 비트, 재생 길이 정보를
반환하도록 한다.
[ ch7.js ]
…
// 추천 버튼을 클릭히 호출된다.
$('#recom').click(function(){
// 라즈베리파이 서버의 음악 추천 데이터를 가져온다.
$.post('/recom', function(data){
$('#history ul[data-role=listview]').empty();
SC.get('/tracks', data).then(function(tracks) {
for(var i in tracks){
var item = tracks[i];
console.log(item);
var elem = $('<a>').attr({
'data-transition' : 'pop',
'href' : '#play'
}).addClass(‘play’);
elem.data('item',{
title : item.title,
img : item.artwork_url,
url : item.permalink_url,
desc : item.description,
dur : item.duration,
genre : item.genre,
bpm : item.bpm,
cnt : item.playback_count,
time : Date.now()
});
var list = $('<li>').append(elem);
elem.append($('<img>').attr('src',item.artwork_url));
elem.append($('<h2>').text(item.title));
elem.append($('<p>').text(item.description));
$('#history ul[data-
role=listview]').append(list);
}
$('#history ul[data-
role=listview]').listview('refresh');
});
});
});
…
추천 인자 값을 이용하여 음악을 검색하고, 화면에 디스플레이 해 준다. 표시 방식이 음악
검색과 동일 하게 구현되어 있다, 이는 추천 음반 클릭 시에도 일반 검색 음악 클릭시와
동일하게 받은 음악이 없다면, 다운로드 후 재생하는 동작이 수행됨을 의미 한다.
[그림] 가장 많이 들은 음악의 장르와 음악의 빠르기 및 길이를 이용하여 추천 음악을 검색해
준다. 검색 결과내에서도 재 검색이 가능하다.
오디오의 알람 기능 추가하기 – Scheduler 기능
스마트 폰으로 꼭 사용하는 기능 중 하나는, 아침의 단 잠을 깨워주는알람 기능이다. 우리가
만든 오디오에 이미 시계 기능이 들어가 있는 것을 알 수 있다. 간단한 설정을 통해, 알람을
설정하면, 가장 마지막에 틀었던 음악이 재생되면서, “Wake Up” 이라고 읽어주고 가장 마지막
들었던 음악을 재생하는 기능을 추가해 보도록 한다. 알람 설정일자를 localStorage 에
보관하고, 매 시간 체크 때 마다 설정한 요일, 시간, 분이 맞는 지 비교를 하여 같은 조건일 경우
알람 동작이 발생하도록 구현한다.
[ ch7_server.js ]
var express = require('express');
var bodyParser = require('body-parser');
…
io = require('socket.io')(server);
isTime = true;
…
app.post('/play', function(req, res){
var url = req.body.url;
console.log(__dirname + ' / ' + url);
exec('pkill mplayer');
isTime = false;
ext.tts('Downloading...');
exec('sudo scdl -l ' + url, function(err, stderr, stdout){
if(!err && stdout.indexOf('[37mDownloading ') > -1){
var file = stdout.split('[37mDownloading
')[1].split('[0mn')[0] + '.mp3';
store.add(req.body, file);
ext.play(file);
} else {
console.log(err);
}
});
res.json({ result : true});
});
…
app.post('/resume', function(req, res){
console.log('Resume : ' + current_music);
ext.play();
res.json({ result : true });
});
…
// 알람 정보를 가져 온다.
app.post('/getAlarm', function(req, res){
res.json({ result : store.getAlarm()});
})
// 알람 정보를 설정한다.
app.post('/setAlarm', function(req, res){
ext.tts('Setting alarm.');
store.setAlarm(req.body);
res.json({ result : true});
})
…
재생기능의 경우, 알람에서도 공통적으로 사용할 수 있도록 공통 함수로 만들어 범용적으로
이용할 수 있도록 변경하였다. 알람 값을 저장하고, 불러오는 기능을 추가하였다.
[ ch7_extend.js ]
var spawn = require('child_process').spawn;
var store = require('./ch7_1_store');
…
var play = function(file){
exec('pkill mplayer');
if(file){
store.setMusic(file);
} else {
file = store.getMusic();
}
isTime = false;
console.log('Playing : ' + file);
tts('Playing the music.');
var ps = spawn('mplayer',['/' + file]);
ps.stderr.on('data', function(data){
var progress = data.toString('utf8');
if(progress && progress.indexOf('of') > -1){
var token = progress.split(' of ');
var current = token[0].split(' ').slice(-2)[0];
var total = token[1].split(' ')[0]
var remain = Math.round(total - current);
setTime(Math.floor(remain / 60), remain % 60);
io.emit('time', { total : total, current : current});
}
});
ps.on('close', function(code){
console.log(code);
tts('Finished');
isTime = true;
setCurrentTime();
});
}
…
// 현재 시간을 설정하는데 사용된다.
var setCurrentTime = function(){
var date = new Date();
// 알람 설정 정보를 가져온다.
var alarm = localStorage.getItem('alarm');
alarm = JSON.parse(alarm);
// 알람 정보가 있을 때 실행된다.
if(alarm != null){
// 요일을 구한다.
var day = date.getDay().toString();
// 알람 설정된 요일 정보가 있을 때 수행된다.
if(alarm.days.indexOf(day) > -1){
// 알람 설정 시간과 분이 동일할 때 소리와 함께 음악이 재생된다.
if(alarm.hour == date.getHours() && alarm.minute ==
date.getMinutes()){
tts('Wake up my master!');
play();
}
}
}
setTime(date.getHours(), date.getMinutes());
}
exports.play = play;
exports.setCurrentTime = setCurrentTime;
…
음악을 재생하는 모듈과 함께 추가된 부분은 시간을 표시할 때, 설정된 알람 값이 있다면 알람
이벤트를 발생하는 부분이 추가되었다. 저장된 날짜와 시간, 분을 검사하여 조건이 맞으면
알람을 발생하게 된다.
[ ch7_store.js ]
// 알람 정보를 저장소에 저장한다.
exports.setAlarm = function(data){
localStorage.setItem('alarm',data.param);
}
// 저장된 알람 정보를 가져온다.
exports.getAlarm = function(){
return localStorage.getItem('alarm');
}
exports.setMusic = function(file){
localStorage.setItem('music',file);
}
exports.getMusic = function(file){
return localStorage.getItem('music');
}
LocalStorage 를 관리하는 파일에 알람 조건을 저장 및 불러오는 기능을 추가하였다. 또한, 알람
시 이전에 재생한 마지막 음악 파일명을 기억하고 불러오는 기능도 추가되었다.
[ ch7.html ]
<div data-role="page" id='alarm'>
<div data-role='header' data-theme='b' data-position='fixed'>
<h1>Alarm</h1>
</div>
<div data-role='main' class='ui-content'>
<fieldset data-role='controlgroup'>
<legend>Choice day</legend>
<input type='checkbox' name'day' id='cb0' value='0'/>
<label for='cb0'>Sun</label>
<input type='checkbox' name'day' id='cb1' value='1'/>
<label for='cb1'>Mon</label>
<input type='checkbox' name'day' id='cb2' value='2'/>
<label for='cb2'>Tue</label>
<input type='checkbox' name'day' id='cb3' value='3'/>
<label for='cb3'>Wed</label>
<input type='checkbox' name'day' id='cb4' value='4'/>
<label for='cb4'>Thr</label>
<input type='checkbox' name'day' id='cb5' value='5'/>
<label for='cb5'>Fri</label>
<input type='checkbox' name'day' id='cb6' value='6'/>
<label for='cb6'>Sat</label>
</fieldset>
<div class='ui-grid-a'>
<div class='ui-block-a'>
<label for='alarm_hour'>Hour</label>
<select id='alarm_hour'>
<!--
<option value='0'>0</option>
-->
</select>
</div>
<div class='ui-block-b'>
<label for='alarm_minute'>Minute</label>
<select id='alarm_minute'>
<!--
<option value='0'>0</option>
-->
</select>
</div>
</div>
<a href='#' id='setAlarm' data-role='button'>Confirm</a>
</div>
<div data-role='footer' data-theme='b' data-position='fixed'>
<div data-role='navbar'>
<ul>
<li><a href='#music' data-icon='grid' data-
transition='flip'>Music</a></li>
<li><a href='#radio' data-icon='star' data-
transition='slidedown'>Radio</a></li>
<li><a href='#history' data-icon='clock' data-
transition='flow'>History</a></li>
<li><a href='#alarm' data-icon='comment' data-
transition='turn'>Alarm</a></li>
</ul>
</div>
</div>
jQueryMobile 의 CheckBox , SelectMenu 를 이용하여 알람 시간을 설정하는 페이지이다.
요일의 경우 직접 HTML 태그로 입력하였지만, 시간과 분은 일일히 입력하기 양이 많아
프로그래밍으로 입력할 수 있도록 공간만 확보한 상태이다.
[ ch7.js ]
$(document).on('pageshow', function(){
var pageId = $.mobile.activePage.attr("id");
switch(pageId){
case 'history':
getList();
break;
// 알람 페이지에 들어왔을 때 호출된다.
case 'alarm':
// 저장된 알람 정보가 있는지 확인한다.
$.post('/getAlarm', function(data){
var alarm = JSON.parse(data.result);
console.log(alarm);
// 설정된 요일이 있다면, 화면상에 체크된 것으로 표시한다.
for(var i = 0; i < alarm.days.length ;i++){
var day = alarm.days[i];
$('#cb' + day).prop('checked',
true).checkboxradio('refresh');
}
$('#alarm_hour').val(alarm.hour).selectmenu('refresh');;
$('#alarm_minute').val(alarm.minute).selectmenu('refresh');
});
// 알람 설정 버튼을 클릭시 호출된다.
$('#setAlarm').click(function(){
var days = [];
// 선택한 요일을 확인하고, 배열에 보관한다.
$.when($("input[type=checkbox]:checked").each(function() {
days.push($(this).val());
})).then(function(){
// 선택한 시간과 분을 저장한다.
var hour = $('#alarm_hour').val();
var minute = $('#alarm_minute').val();
var data = {
days : days,
hour : hour,
minute : minute
};
// 알람 정보를 라즈베리파이 서버에 전달한다.
$.post('/setAlarm', {
param : JSON.stringify(data)
});
});
});
break;
}
});
// 시간 목록을 생성한다.
for(var i = 0 ; i < 24 ; i++){
$('#alarm_hour').append($('<option>').val(i).text(i));
}
// 분 목록을 생성한다.
for(var i = 0 ; i < 60 ; i++){
$('#alarm_minute').append($('<option>').val(i).text(i));
}
시간과 분은 for 문을 이용하여 직접 선택값을 고를 수 있도록 기능을 추가하였다. Alarm
페이지가 호출 시, 설정된 값이 있다면 불러와서 화면에 표시하도록 하고, 사용자가 새 알람을
설정했을 때 해당 값을 라즈베리파이로 전송하도록 기능을 구현하였다.
[그림] 알람 설정을 위한 요일 및 시간 설정. 시간과 분은 개수가 많으므로, HTML 이 아닌
코드를 이용하여 값을 설정하도록 한다.
배경과 글씨 밝기를 조절해 보자
백 라이트 배경과 글씨 밝기가 기본으로는 최대 값으로 설정되어 있다. 어두운환경이나 상황에
따라서 밝기를 조절해야 할 필요가 있다. 7 Segment 의 경우 내부 API 에서 16 단계로 밝기를
조절할 수 있는 기능을 제공하고 있다. LED 백 라이트의 경우, GPIO 실습 시 사용했던
소프트웨어 PWM 방식을 응용하여 사용자가 밝기를 조절하여 사용할 수 있게 하자.
설정된값은 로컬 저장소에 저장하여, 재 부팅 후 시작 시에도 설정 값을 이용할 수 있도록
반영한다.
[ ch7_server.js ]
…
// LED 라이트 색상을 조정할 때 호출된다.
app.post('/setBg', function(req,res){
console.log('Settring Bg : ' + req.body.bg);
ext.setBg(parseInt(req.body.bg));
res.json({ result : true});
});
// 7세그먼트의 글씨색을 설정할 때 호출된다.
app.post('/setFg', function(req,res){
console.log('Settring Fg : ' + req.body.fg);
ext.setFg(parseInt(req.body.fg));
res.json({ result : true});
});
…
전경 밝기 및 배경 밝기를 조절할 수 있도록 호출 부를 구현해 준다. Ajax 요청을 통해 전달
되는 숫자 값은 문자 형태로 전환 되므로, parseInt 함수를 이용하여 숫자 형태 값을 전달할 수
있도록 한다.
[ ch7_ext.js ]
…
var wpi = require ('wiring-pi');
wpi.setup('gpio');
wpi.wiringPiSetup();
wpi.softPwmCreate (6, 0, 200) ;
wpi.softPwmWrite (6, 240);
wpi.pinMode(11, wpi.OUTPUT);
wpi.digitalWrite(11, wpi.HIGH);
disp.setDecodeAll();
disp.setScanLimit(4);
disp.setDisplayIntensity(15);
disp.startup();
// 7세그먼트의 밝기를 설정한다.
exports.setFg = function(val){
disp.setDisplayIntensity(val);
}
// LED 밝기를 소프트 PWM 방식으로 설정한다.
exports.setBg = function(val){
wpi.softPwmWrite (24, val);
}
…
배경 밝기는 PWM 을 이용하여 값을 조절하고, 전경 밝기는 7 Segment 의 밝기를 조절하는
setDisplayIntensity 함수를 이용하여 값을 반영해 주도록 한다.
[ ch7.html ]
…
<div data-role="page" id='option'>
<div data-role='header' data-theme='b' data-position='fixed'>
<h1>Config</h1>
<a href='#option' data-icon='gear' class='ui-btn-
right'>Option</a>
</div>
<div data-role='main' class='ui-content'>
<label for='fg_brt'>Foreground Brightness</label>
<input id='fg_brt' type="range" min="0" max="15" value="15"
/>
<label for='bg_brt'>Background Brightness</label>
<input id='bg_brt' type="range" min="0" max="240"
value="240" />
</div>
<div data-role='footer' data-theme='b' data-position='fixed'>
<div data-role='navbar'>
<ul>
<li><a href='#music' data-icon='grid' data-
transition='flip'>Music</a></li>
<li><a href='#radio' data-icon='star' data-
transition='slidedown'>Radio</a></li>
<li><a href='#history' data-icon='clock' data-
transition='flow'>History</a></li>
<li><a href='#alarm' data-icon='comment' data-
transition='turn'>Alarm</a></li>
</ul>
</div>
</div>
</div>
…
밝기를 조절할 수 있는 슬라이더를 2 개 만들도록 한다. 전경 밝기의 경우 0 부터 15 까지 값을
선택할 수 있도록 하고, 배경 밝기의 경우 0 부처 240 까지의 값을 선택할 수 있도록 최대 최소
값을 설정한다. 지정된 값을 초과하면 설정 값이 반영되지 않고 오류가 날 수 있으므로
유의하도록 한다.
[ ch7.js ]
…
$(document).on('pageshow', function(){
var pageId = $.mobile.activePage.attr("id");
switch(pageId){
…
case 'option':
// 포그라운드 색상 슬라이더 값이 변경 시 호출된다.
$('#fg_brt').change(function(){
$.post('/setFg',{ fg : $(this).val()});
});
// 백그라운드 색상 슬라이더 값이 변경 시 호출된다.
$('#bg_brt').change(function(){
$.post('/setBg',{ bg : $(this).val()});
});
break;
}
});
…
슬라이더로 지정한 두개의 input 값이 변경 될 때 이를 감지하고, 변경된 값을 서버로 전송하는
코드를 작성한다. option 페이지에 접속하였을 때, 해당 이벤트를 감지하고 동작될 수 있도록,
pageshow 이벤트 내에서 명령어를 작성하도록 하자.
[그림] 리모콘을 이용하여 7 Segment 와 Background 밝기를 조절할 수 있다.
최신 가요 목록 보여주기 – 웹 크롤러 만들기
음악을 들을때 별달리 생각없이 음악을 듣고자 할때 별달리 노력없이 최신 가요를 듣는 경우가
많다. 우리가 만들 파이오에도 조회 시점의 최신 가요를 조회하여 사용자에게 알려주고자 한다.
순위를 알려주는 사이트는 멜론, 네이버, 엠넷등 다양한 사이트가 있는데, 여기서는 엠넷의
TOP100 웹 페이지를 이용하여 최신 가요의 정보를 이용할 것이다. 사용자가 사용할 모바일
리포콘 웹앱에 가져온 최신 가요 정보를 출력해 줄 것이다.
뉴스의 날씨의 경우 정제된 XML 형태로 제공되는 RSS 서비스를 이용하여 간단히 JSON
타입으로 변형하여 손쉽게 데이터를 사용할 수 있었지만, 최신 가요의 경우 RSS 형태의
서비스로 제공되지 않기 때문에, 수고스럽더라도 직접 웹 페이지를 크롤링하여 필요한
데이터를 추출하여 사용해야 한다.
$ npm install stew-select
<td class="MMLItemTitle">
<div class="MMLITitle_Wrap">
<div class="MMLITitle_Album">
<a href="/album/526289 " target="_self"><img
src=http://cmsimg.mnet.com/clipimage/album/50/000/526/526289.jpg
alt="시그널 OST Part 1 -
앨범"onerror="javascript:mnetImage.fnSetClipUrl(this, '0202' ,
'526289', '50')"/><span class="photoLine"></span></a>
</div>
<div class="MMLITitle_Box info">
<div class="MMLITitleSong_Box">
<a href="/track/5174234" target="_self"
class="MMLI_SongInfo">회상 - 곡정보</a><a
href="javascript:mnetCom.aodPlay('5174234');" title ="회상 새창"
class="MMLI_Song">회상</a>
</div>
<div class="MMLITitle_Info">
<a href=" /artist/208752 " title="장범준 - 아티스트"
class="MMLIInfo_Artist" target="_self">장범준</a> /
<a href=" /album/526289 " target="_self" title
="시그널 OST Part 1 - 앨범" class="MMLIInfo_Album">시그널 OST Part
1</a>
</div>
<a href="javascript:mnetCom.aodPlay('5174234','add');"
class="MMLI_Mp3Add" title="곡추가">곡추가</a>
</div>
</div>
</td>
[그림] 실제 MNET TOP100 사이트와 페이지 구조. 필요한 정보만을 빼서 화면에 보여줄 수
있도록 한다.
모바일 상에서 필요한 정보는 곡 대표 이미지, 곡 명, 가수, 앨범 명의 정보이다. 실제 태그를
보면, div 요소의 MMLITitle_Wrap 클래스에 해당 정보들을 담고 있음을 알수 있다. Stew-select
라이브러리는 CSS 의 요소 선택 방법을 이용하여, 원하는 데이터를 추출한다.
[ ch7_server.js ]
…
var top = require('./ch7_1_top');
…
// 최신가요 목록을 요청할 때 호출된다.
app.post('/top', function(req,res){
top.getList(function(list){
console.log(list);
res.json({ result : list});
});
});
…
인기 가요를 반환하는 호출부를 작성한다. 인기가요를 크롤링하여 정보를 추출하는 함수는
별도의 파일로 분류하여 복잡도를 낮추도록 한다.
[ ch7_top.js ]
// html 문서에서 특정 값을 가져오는데 활용되는 모듈을 불러온다.
var stew = new (require('stew-select')).Stew();
// html 문서를 해석하는데 사용되는 모듈을 불러온다.
var htmlparser = require("htmlparser");
var http = require('http');
// 인기가요 목록이 있는 웹 문서 정보를 설정한다.
var options = {
hostname: 'www.mnet.com', port: 80, path: '/chart/TOP100',
method: 'GET'
};
// 최신가요 목록을 가져온다.
exports.getList = function(cb){
// 최신 가요 페이지를 호출한다.
var req = http.request(options, function(res) {
res.setEncoding('utf8');
var dom = '';
// 받아지는 데이터 조각을 합친다.
res.on('data', function (chunk) {
dom += chunk;
});
// 웹 문서 호출이 끝났을 때 발생한다.
res.on('end', function(){
var isFirst = true;
// 웹 문서에서 음악정보 객체를 가져온다.
stew.select(dom, 'div.MMLITitle_Wrap',
function(err,items) {
if(err) {
cb([]);
console.error(err);
} else {
var list = [];
// 음악 정보 목록을 반복하여 하나씩 추출한다.
items.forEach(function(item){
if(stew.select(item,'img') &&
stew.select_first(item,'a.MMLIInfo_Artist')
&& stew.select_first(item,'a.MMLIInfo_Album').children[0]){
// 가요 목록을 저장한다.
list.push({
img :
stew.select_first(item,'img').attribs.src.replace('/50/','/80/'),
artist :
stew.select_first(item,'a.MMLIInfo_Artist').children[0].raw,
title :
stew.select_first(item,'a.MMLI_Song').children[0].raw,
album :
stew.select_first(item,'a.MMLIInfo_Album').children[0].raw
});
}
});
// 가요 목록을 콜백 함수로 반환한다.
cb(list);
}
});
});
});
req.on('error', function(e) {
cb([]);
console.log('problem with request: ' + e.message);
});
req.end();
}
실제 웹 페이지를 크롤링 하는 함수이다. http 요청을 통해 페이지 내용을 불러오고, stew-
select 라이브러리를 이용하여, CSS 선택자 방식으로 데이터를 가져오게 된다. 원래 페이지
구조에서 div 태그의 클래스가 MMLITitle_Wrap 부분의 태그를 가져오고, 해당 태그에
소속되어있는 데이터로부터 이미지, 아티스트, 노래, 앨범 정보를 가져온 후, 배열 객체에 값을
저장하고 반환하는 구조로 구성되어 있다. 이미지 파일의 경우 기본적으로 50x50px 사이즈의
이미지 주소를 제공하는데, 모바일에서 보여지는 크기는 80x80px 이 될 수 있도록 문자열을
치환한다.
[ ch7.html ]
…
<div data-role="page" id='music' class='ui-page-active'>
<div data-role='header' data-theme='b' data-position='fixed'>
<a href='#play' data-icon='action' class='ui-btn-
left'>Play</a>
<h1>Music</h1>
<a href='#option' data-icon='gear' class='ui-btn-
right'>Option</a>
</div>
...
</div>
<div data-role="page" id='play'>
<div data-role='header' data-theme='b' data-position='fixed'>
<a href='#play' data-icon='action' class='ui-btn-
left'>Play</a>
<h1>Music Play</h1>
<a href='#option' data-icon='gear' class='ui-btn-
right'>Option</a>
</div>
...
</div>
<div data-role="page" id='history'>
<div data-role='header' data-theme='b' data-position='fixed'>
<a href='#play' data-icon='action' class='ui-btn-
left'>Play</a>
<h1>History</h1>
<a href='#option' data-icon='gear' class='ui-btn-
right'>Option</a>
<div data-role='navbar'>
<ul>
<li><a href='#' id='his'>History</a></li>
<li><a href='#' id='recom'>Recommandation</a></li>
</ul>
</div>
</div>
...
</div>
<div data-role="page" id='alarm'>
<div data-role='header' data-theme='b' data-position='fixed'>
<a href='#play' data-icon='action' class='ui-btn-
left'>Play</a>
<h1>Alarm</h1>
<a href='#option' data-icon='gear' class='ui-btn-
right'>Option</a>
</div>
...
</div>
<div data-role="page" id='option'>
<div data-role='header' data-theme='b' data-position='fixed'>
<a href='#play' data-icon='action' class='ui-btn-
left'>Play</a>
<h1>Config</h1>
<a href='#option' data-icon='gear' class='ui-btn-
right'>Option</a>
</div>
...
</div>
<div data-role="page" id='top'>
<div data-role='header' data-theme='b' data-position='fixed'>
<a href='#play' data-icon='action' class='ui-btn-
left'>Play</a>
<h1>Top Music</h1>
<a href='#option' data-icon='gear' class='ui-btn-
right'>Option</a>
</div>
<div data-role='main' class='ui-content'>
<ul data-role='listview'>
<li>No musics</li>
</ul>
</div>
<div data-role='footer' data-theme='b' data-position='fixed'>
<div data-role='navbar'>
<ul>
<li><a href='#music' data-icon='grid' data-
transition='flip'>Music</a></li>
<li><a href='#top' data-icon='star' data-
transition='slidedown'>Top</a></li>
<li><a href='#history' data-icon='clock' data-
transition='flow'>History</a></li>
<li><a href='#alarm' data-icon='comment' data-
transition='turn'>Alarm</a></li>
</ul>
</div>
</div>
</div>
…
인기 가요를 표시해 주는 페이지를 추가한다. 타 페이지와 동일하게 listview 를 이용하여
표시해 줄 수 있도록 한다. 어떤 화면에 있더라도 재생 페이지에 접근 할 수 있도록 모든
페이지의 상단 헤더 바에 Play 페이지로 접근할 수 있는 버튼을 추가하였다.
[ ch7.js ]
…
$(document).on('pageshow', function(){
var pageId = $.mobile.activePage.attr("id");
switch(pageId){
...
case 'top':
// 최신가요 목록 페이지로 들어올 때 호출된다.
$.post('/top', function(data){
$('#top ul[data-role=listview]').empty();
// 가져온 최신가요 목록을 화면에 표시한다.
for(var i in data.result){
var item = data.result[i];
// 화면에 표시할 객체를 만든다.
var elem = $('<a>').attr({
'data-transition' : 'pop',
'href' : '#music'
}).addClass('music');
elem.data('item',item);
var list = $('<li>').append(elem);
elem.append($('<img>').attr('src',item.img));
elem.append($('<h2>').text(item.title));
elem.append($('<p>').text(item.artist
+ ' / ' + item.album));
// 리스트 상단에 추가한다.
$('#top ul[data-
role=listview]').prepend(list);
}
// 리스트 화면을 jquerymobile 방식으로 갱신한다.
$('#top ul[data-
role=listview]').listview('refresh');
});
break;
}
});
...
// 최신 가요 목록에서 음악을 클릭시, 자동으로 해당 음악을 검색한다.
$('ul[data-role=listview]').on('click','a.music', function(){
var item = $(this).data('item');
// 엔터 이벤트를 생성한다.
var e = $.Event("keypress");
e.keyCode = 13;
// 검색창에 제목을 넣고, 엔터 이벤트를 발생시킨다.
$('#search').val(item.title).trigger(e);
});
…
인기가요 페이지가 호출 될 때 라즈베리파이로부터 수집된 인기가요 정보를 받는 부분을
구현한다. Listview 에 보여지는 방식은 음악 검색이나 재생 목록을 확인하는 페이지와 동일한
방법으로 작성되어 있다. 단, 최신가요가 항상 SoundCloud 에 존재하는 것은 아니므로, 인기
가요 목록에 있는 아이템을 클릭 시에 SoundCloud 에 관련 음악이 있는지 검색 한 후
사용자가 재생할 수 있도록 구성 하도록 한다.
[그림] M.NET 의 최신 가요 목록을 보여주고, 클릭하면 음악 검색에서 곧바로 검색이 되도록
구현된다.
이번 장을 정리하며
이번 장에서는 라즈베리파이로 간단하 오디오를 구현해 보았다. 라즈베리파이에 서버를
구동하여 현재 시간 및 음악 재생 시간을 7 세그먼트로 표시하였고, jQueryMobile 로 구현된
모바일 웹을 이용하여 사용자가 오디오를 제어할 수 있도록 하였다. 하지만, 라즈베리파이가
접속한 네트워크 영역에 있는 장치로만 오디오를 제어할 수 있다. 즉, 집 밖에서 오디오를
제어할 수는 없는 구성인 것이다. 이는 9 장에서 가능하도록 현재 오디오를 업그레이드 할
것이다. 다음 장에서는 제품의 외관을 구성해 볼 수 있는 3D 프린터를 살펴볼 것이다.
8. 외관을 생각대로 만들기 – 3D 프린터
가장 중요한 외관, 어떻게 꾸밀까?
지금까지 라즈베리파이와 Javascript 를 이용하여 파이오를 만들어 보았다. 하지만 중요한 것 중
하나, 바로 외관 디자인이다. 칩이 보이고 핀이 보이는 현재의 파이오는 우리에게 이쁘게
보일지 몰라도, 실제로 받아서 사용하게 될 사람에게 불편하게 느껴지고, 때로는 핀이나 부품의
노출로 인하여 위험할 수도 있다. 파이오의 입장에서도 사람이 잘못만지면 선이 단선되거나,
정전기 또는 예상치 못한 일들로 인하여 파손이 일어날 수도 있는 것이다.
실제 제품은 플라스틱이나 금속류로 사출을 뜨거나 금형을 만들지만, 우리가 접할 수 있는
방식은 결코 아닐 것이다. 현실적으로 외관을 이쁘게 만들 수 있는 방법을 살펴보도록 한다.
1. 레고를 이용한 외관 조립
어린시절 한번쯤은 보았을 레고를 이용하여 외관을 꾸밀 수 있다. 레고의 시리즈중 크리에이터
세트는 사용자가 자유롭게 상상한 것을 만들 수 있는 다양한 블럭을 제공해 주는데, 이를
이용하여 자유롭게 외관을 꾸밀 수 있다. 또한 레고의 특성상 자유롭게 쌓았다가 원하는
디자인으로 바꾸기에 용이하다는 장점이있다. 하지만, 블럭의 최소 사이즈가 제한되어 있어
정밀하게 만들기 어렵고, 충격에 파손될 우려가 높다. 그리고, 무언가를 레고로 외관을
쌓는다는게 직접해보면 생각보다 어렵고 시간이 오래 걸린다. 무엇보다 우리의 발목을 잡는
부분은 레고 자체가 가격이 비싸다는 점이다.
[그림] 단순한 프로토 타이핑에 좋으나 값이 비싸고 복잡한 프로토타이핑은 쉽지 않다.
2. 압축 스티로폼 이용한 가공
보드지를 이용한 방법도 있겠지만, 그것보다는 우드락 혹은 포맥스 등의 압축 스티로폼을
이용하는 것이 보다 나을 수 있다. 이러한 우드락이나 포맥스는 매끈한 면을 가지고, 3mm
이상의 두께에서는 (3T 라고 부른다) 꽤 괜찮은 강도를 보여준다. 보드지가 칼로 자르기가 쉽지
않은 반면 상대적으로 압축 스티로폼 계열은 칼을 이용한 커팅이 수월한 편이다. 일반 접착제를
사용하면, 스티로폼 재질이 녹는 경우도 발생하므로, 우드락용 전용 접착제를 이용하여 부착을
해야 한다. 평면의 가공이나 사각류의 가공에는 용이하나, 복잡한 면이나, 내부가 파인 구조의
외관을 꾸미고자 할 때는 많은 불편함이 따른다. 이러한 류로 구할 수 있는 것이 우드락,
시안보드, 포맥스이며, 강도 및 가공 용이성은 다음과 같다. (강도가 높을수록 일반적으로
가격또한 높다)
가공 용이성 : 우드락 > 시안보드 > 포맥스
강도및 가격 : 우드락 < 시안보드 < 포맥스
[그림] 압축 스티로폼은 가공이 용이하나, 평면류가 아닌 모델에는 부적합 하다.
3. 3D 프린터
21 세기의 연금술이라 불리우는 도구가 바로 3D 프린터이다. 3D 로 만든 도면을 내 눈앞에
보이는 물체로 만들어주는 기계 장치이다. 대부분이 플라스틱을 이용하여 한층씩 쌓는 형태로
원하는 조형물을 만들어 내는데, 근래들어 종이, 고무,, 나무, 금속에 이르기 까지 범위가 점차
넓어지고 있는 상황이다. 심지어는 식품을 가지고 3D 프린팅을 하는 제품도 나오고 있다.
우리는 여기서 3D 모델링을 이용하여 외관을 구성할 것이다. 3D 프린터가 없다고 걱정하지
않아도 된다. 수 많은 전국의 메이커 스페이스에서 직접 뽑아 볼 수 있을 것이다. 직접 사고
싶더라도 필자는 가급적이면 먼저 사용해 보고 구입할 것을 추천한다. 3D 프린터가 대중화
되기에는 일반적으로 접할 수 있는 3D 프린터는 비용이나 완성도 측면에서 아직 가야할 길이
먼 것으로 생각한다.
[그림] 모델링 한 결과를 그대로 출력해 주는 3D 프린터, 아직 대중화를 위해서 가격과
신뢰성이 보다 향상될 필요가 있지만, 복잡한 조형물도 뽑아내기에는 최선의 방법이다.
상상한 것을 현실로 만들어 주는 3D 프린터!
우리가 여기서 시도해 볼 방식은, 3D 도면을 만들어 이를 그대로 출력해 주는 3D 프린터이다.
이러한 3D 프린터를 구성하는 다양한 동작 방식이 있는데, 실제로 쉽게 접할 수 있는 3D
프린터 방식은 FDM(Fused Deposition Modeling) 이다. FDM 은 플라스틱 재료를 녹여서
미세한 노즐로 분사하여 쌓아가는 방식이다. 구성이 다른 3D 프린팅 기법에 비해 단순하고
특허가 일찍 만료되어 오픈소스(RepRap) 제품이 발달하여 상대적으로 저렴한 가격으로
출시되고 있다. 프린터에 따라서 Nylon 이나 나무 분말 혹은 플렉서블 소재를 이용할 수 있는
제품도 출시되고 있으나, 범용적으로 ABS 와 PLA 재질의 플라스틱을 많이 이용한다.
1.재료선정
쉽게 구입할 수 있는 재질은 ABS 와 PLA 일 것이다. 3D 프린터로 후가공 즉 연마후 도색을
하는 경우는 ABS 소재가 적합하고, 그렇지 않고 내구성과 실용성을 중심으로 사용하는 경우
PLA 가 적합하다. ABS 의 경우 인쇄하는 판이 수축 방지를 위해 가열이 되는 히팅베드가 필수
이므로, 3D 프린터에 따라서는 ABS 재질을 사용하지 못하는 경우도 있다. 후가공 부분까지
다루는 것은 범위를 벗어나는 부분이므로, 우리는 안전성과 편의성을 고려하여 PLA 를
사용하도록 한다.
ABS (Acrylonitrile Butadiene Styrene copolymer)
- 성형온도가 높아(220~240 도) PLA 보다 끈끈한 성질이 있다.
- Heating Bed 가 반드시 필요하다.
- 구조용 부품으로 강도가 우수하다.
- 출력 후 사포나 샌딩등의 표면처리가 상대적으로 용이하다
- 플라스틱용 도료나 아크릴계 도료로 도장이 가능하다.
- 두꼐가 얇은 출력물이나 큰 출력물은 열에 의한 수축으로 인해 휘는 경우가 발생할 수 있다.
- 석유를 이용해 만들어, 프린팅시 냄새나 유해물질이 발생할 수 있다.
PLA (Polylactic acid)
- 성형 온도가 ABS 에 비해 낮아(180~210 도) 끈끈한 성질이 적고 견고하다.
- 열에 의한 변형이 적고 비교적 큰 출력물도 만들기 쉽다.
- 옥수수, 사탕수수, 고구마 등의 식물성 원료를 사용하여 자연 친화적이다.
- 출력 후 후 가공, 도장 처리가 ABS 에 비해 용이하지 않다.
- 출력 이후 내구성이나 내마모성이 ABS 에 비해 떨어진다.
[ PLA 와 ABS 출력물 비교. 후가공이 필요하다면 ABS 재질을 써야 하나, 그렇지 않다면 PLA 를
선택하는 것이 무난하다. ]
2. 3D 디자인 툴 선정
3D 디자인을 위해 이미 다양한 디자인 유/무료의 툴이 공개되어 있다. 무료로 제공되는
대표적인 3 가지 툴은 다음과 같은데, 우리가 하려는 간단한 디자인을 위하여 3DS MAX 로
유명한 Autodesk 사의 123D Design 을 사용하도록 한다.
Autodesk 123D Design
3D MAX 등의 3D 프로그램으로 유명한 Autodesk 사의 대표적인 3D 프린팅을 위한
프로그램이다. 3D MAX 가 비싼 가격의 전문가 용이라면, 123D Design 은 무료로 3D 프린팅을
위한 모델링을 할 수 있도록 도와준다. (상업적인 목적의 경우 유료버전을 사용해야 하며, 월
이용료가 1 만원 수준이다.) 국내 시판중인 3D 프린팅관련 도서가 대부분 이 123D Degisn 을
다루고 있는경우가 많으며, 관련 자료들을 찾기 수월하다. 보다 쓰기 쉬운 툴을 찾는다면
Thinkercad 가 적합할 수 있다.
Sketchup Make
건축, 인테리어, 조경, 건설, 설비등의 3D 모델링에 활용되는 프로그램이였다. Google 에서
인수되었다가 현재는 Trimble 에서 관리되고 있다.개인 취미로 사용하는 것은 무료이나,
교육용이나 상업용으로 사용하는 경우 유로 라이선스를 사용해야 한다. 3 차원 공간에서
자유로운 스케치가 가능하고, 구조물을 구성하기에 적합한 기능을 제공하고 있으나, 간단한
조형물을 만들기에는 123D Design 에 비해 복잡한 구성을 가지고 있다.
Blender 3D
오픈소스로 공개되어 3 차원 애니메이션이나 게임 개발용으로 사용되는 무료로 사용가능한
3D 툴이다. Maya, 3DS MAX, Rhino 와 같은 전문 3D 프로그램에 밀리지 않는 수준의 성능과
기능을 보유하고 있다. 전문적인 3D 와 완전 무료로 이용할 수 있다는 장점이 있지만, 처음
배우는 진입장벽이 낮지 않아, 단순한 모델링 용도로는 사용하기 어려운 단점이 있다.
자 이제 우리의 오디오를 모델링 하자
- 123D 설치하기
123D 는 PC, Mac, iPad 에서 사용가능하다. PC 용은 32bit 64bit 버전 중, 현재 사용중인 OS 에
맞추어 설치하도록 한다. Mac 용은 iTunes Store 에서 앱을 받아서 설치하면 된다.
http://www.123dapp.com/design
[그림] 123D Design 은 PC, Mac, iPad 만을 지원한다.
여기서 잠깐!
3D 툴로 유명한 Autodesk 사에서는 123D Desgin 이외에, 초보자들도 쉽게 사용할 수 있는
다양한 123D 시리즈를 비 상업적용도로는 무료로 사용할 수 있도록 배포하고 있다. 차후에
필요한 프로그램이 있다면, 별도로 설치하여 사용할 수 있다.
제품군 기능
123D Catch 360 도 여러방면에서 촬열한 사진을 합성해 3D 모델로 만드는 프로그램
123D Creature 점토로 조형하는 방식으로 3D 형상을 만드는 프로그램. 뻐대를 기반으로
씌우는 구조로 동물이나 캐릭터 만들기 쉬움
123D Desgin 초보자도 쉽게 다룰 수 있는 3D CAD 프로그램. 치수를 입력하여 형상을
구조화 할 수 있어, 제품 디자인을 간단하게 하기에 유용하다.
123D Make 타 123D 프로그램으로 만든 3D 모델을 얆게 슬라이스 하여, 단면 모양을
만드는 프로그램. 이렇게 만든 나무나 아크릴을 레이저 커터 등으로
잘라내서 조립하면 입체를 손쉽게 구현할 수 있다.
123D Sculpt 점토 덩어리를 손가락으로 밀고 당기고 쓰다듬는 방식과 같이 모델링하는
프로그램. 정확한 치수에 기반하지 않고 감각적인 형상을 만들 때
유용하다.
123D Circuit 전자횔를 설계할 수 있는 툴이다. 오픈소스 하드웨어와 브레드 보드를
이용하여 회로를 설계하고 시물레이션을 수행할 수 있다. 완성된 회로
PCB 를 주문할 수도 있다.
Thinker CAD 123D Design 과 비슷하지만, 이미 정해진 모양을 이용하여 쉽게 형상을
만들 수 있다.
Mesh Mixer 여러 3D 형상을 모아 붙이거나 모양을 수정할 수 있는 프로그램이다.
자 이제 우리가 만들 오디오의 외관은 다음과 같다. 간단하게 디자인이 되어 있으나, 내부에
들어가는 라즈베리파이와 각종 센서/배선을 고려하여 외관을 디자인 해야 한다. 간단하게 아래
면을 만들어 보도록 하자. 설계시 고려해야 하는 점은, 3D 프린터가 우리가 모델링한 결과를
출력해 주지만, 0.1mm 의 오차 없이 출력해 줄수는 없다는 점이다. 플라스틱 특성상 출력시
수축이나 프린팅 오차로 인하여 아래에서 만든 부품들을 제대로 연결할 수 없게 될 것이다.
처음 도면을 그릴 시에 이러한 오차를 감안하여 약간 여유를 두고 만들거나, 그럴 수 없다면
칼등의 도구를 이용하여 후 가공하여 결과물을 이용해야 한다.
[그림] 123D 첫 실행 화면. 처음 시작화면만 돌려보아도 기본적인 기능을 확인할 수 있다.
처음 설치된 123D Design 을 실행해 보도록 하자. Quick Start Tips 화면을 발견할 수 있을
것이다. 옆으로 넘겨 간단한 사용법을 확인하고, 프로젝트 생성을 위해 Start a New Project 를
클릭하도록 한다.
Top
오디오 구조물 중 가장 쉬운 부분이면서, 이 부분을 완성하게 되면 다른 부분도 매우 쉽게
만들어 낼 수 있는 부분이 될 것이다. 다음 방법을 따라 상단을 만들고, 나머지 부분도 같은
방법으로 만들어 내도록 하자.
상단 메뉴의 Sketch 항목으로 마우스를 가져다 대면, 세부 상목이 하단에 표시된다. 첫번째
Sketch Rectangle 메뉴를 클릭한다. 작업화면의 0,0 부분을 클릭하여 드래그를 시작하고, 각각
85, 85 지점에 마우스를 뗀다. 만일 좌표가 보기 힘들면 마우스 우측을 클릭한 채 이동을
하면서 원하는 화면 각도로 조절하도록 한다.
드래그 할 때 가로 세로의 길이가 표시되며, 입력창이 보이는데, 여기에 직접 85, 85 를
입력해도 무방하다. 드래그가 완료되면 다시한번 클릭하고, 엔터를 누르거나 체크표시를
누르면 밑판 그리기가 완료된다.
밑판에 마우스를 대서 클릭하면, 설정을 할수 있는 버튼이 보여지게 된다. 버튼에 가져다 대면
여러 옵션이 나오는데, 4 번째 항목에 있는 Extrude 를 클릭한다. 상단 표시로 마우스를 가져다
대서 크기를 5 (mm) 사이즈로 조절하거나, 숫자를 5 를 입력하여 Enter 를 입력하면 5mm 의
네모 상단판을 만들 수 있다. 이렇게 Extrude 를 이용하면, 그림을 그린 후 올려서 구조물을
생성하거나, 반대로 파내기를 할 수 있는 유용한 도구이다. 이번에는 반대로 연결 부를
파내도록 한다. 다음의 각각 영역 위에 동일한 Sketch Rectangle 도구를 이용하여 네모 영역을
그려주도록 한다.
상단에 5mm 가 올라와 있어, 그리기가 어려우므로, 마우스 우측을 클릭한채 돌려서 화면을
뒤집어 준다. 뒤집은 이후 65mm x 5 mm 네모 2 개를 위 아래에, 10mm x 5 mm 네모 4 개를
좌우에 그려준다. 네모영역을 그린 부분을 CTRL 키를 누른 상태에서 모두 선택해 주도록 한다.
선택후, 설정아이콘을 다시 클릭하여 Extrude 를 누른다. 동일하게 5 (mm) 를 입력하고 Enter
를 누르면, 이번에는 해당 영역이 비워지는 것을 알수 있다.
파이오의 나머지 부분도 크게 다르지 않다. Top 을 만든 것과 같이 Rectangle Sketch 와
Extrude 만으로 나머지 영역도 모두 만들 수 있다.
Bottom
Bottom 영역에 구멍이 많이 뚫려 있고, 별도의 다리모양 구조물이 있는 것을 볼 수 있을
것이다. 이 다리모양의 구조물은 라즈베리파이를 고정하기 위한 내부 구조물로, 안쪽의 2 개
구멍에 딱 맞는 구조임을 알 수 있다. 나머지 구멍 4 개는 2 개씩 다리를 연결하기 위한
구조물이다.
Leg
바닥면에 꽂아 주는 구조물이다. 이 다리는 2 개가 필요하며, 하단부에 연결하여 오디오
구조물이 안정적으로 받쳐지도록 구성한다.
Left
오디오의 왼쪽 구조물로써, 하단 중앙에 구멍이 나 있는 것을 벌견할 수 있을 것이다. 이는 무선
랜 카드가 노출되는 곳으로, 보다 전파 수신이 잘 되도록 뚫려 있는 구조이다.
Right
왼쪽 구조물과 거의 흡사하지만, 무선 랜을 위한 구멍이 별도로 없이 막힌 구조로 되어 있다.
Back
파이오의 뒷면으로써, 빗 모양의 구조물은 내부에서 LED 를 고정하는데 사용하는 구조물이다.
하단의 두개의 구멍은 스피커와 전원선이 외부와 연결될 수 있도록 노출된 부분이다.
Bone
7 Segment 를 고정하는 구조물인 동시에 한지나 모시 천을 이용하여 내부를 보호할 수 있는
막을 부착하는 구조물이다. 7 Segment LED 를 연결한 후 Top, Left ,Right 구조물에 연결하도록
한다.
Front
정면 구조물로서, 내부를 보호하는 동시에, 불빛이 나올 수 있도록 창살 구조로 보호되고 있다.
중앙 부분은 7 Segment LED 가 잘 보이도록 오픈된 구조를 지니고 있다.
최종 결과물 저장
최종 결과물은 일반적으로는 언제든지 재 편집할 수 있도록 123D Design 양식으로 저장하는
것이 일반적이다. 하지만, 3D 프린팅 혹은 레이저 커터를 이용할 시에는 다른 양식으로
저장해야 인쇄 및 커팅 작업을 손쉽게 할 수 있다.
[그림] 실제 3D 프린팅과 레이저 커터를 사용할 때는 저장 형식에 차이가 있다.
Save – To My Computer 가 결과물을 나의 컴퓨터에 저장하는 방식이다. To My Projects 는
Autodesk 123D 클라우드에 저장되는 것이므로, 간단하게 편집을 하고자 하는 경우는 나의
컴퓨터에 저장하는 것이 간편하다.
3D 프린터에서 인쇄하는 경우에는 대부분 STL(Stereo Lithography) 형식을 이용한다. 편집
목적이 아닌 출력 목적인 경우 STL 형식으로 저장한 후, 이 파일을 슬라이서(Slicer) 라는
프로그램으로 모델을 층층히 잘라 G-CODE 라는 XYZ 프린팅 좌표가 들어가 있는 데이터를
전송하여 프린팅이 이루어지게 된다. 레이저 커터로 작업하는 경우는 잘라내는 라인, 즉 선만
제공되면 되는데, SVG(Scalable Vector Graphic) 방식으로 저장하여, CAD 형식인 DXF(Drawing
Interchange File)로 변환하여 인쇄하게 된다.
모델링 결과를 출력하기 - 3D 프린팅하기
실제 3D 프린터가 인쇄할 수 있는 데이터는 G-CODE 이다. 이것은 3D 프린터가 XYZ 축
이동하면서 적층식으로 필라멘트를 쌓을 좌표를 표시 한 데이터 이다. 이 형태로 변환하기
위해서는 STL 형식의 데이터를 슬라이서(Slicer)라는 프로그램으로 모델을 층층히 잘라 G-
CODE 로 전환해야 한다. 슬라이서 프로그램은 보통 프린터 제조사로부터 제공이 되거나
별도의 소프트웨어를 사용하는 경우가 있다. 최고의 가정용 3D 프린터라 불리는 얼티메이커용
슬라이서인 Cura 는, 오픈소스로 공개되어 있어 수 많은 3D 프린터가 자사 프린터의
슬라이서로 널리 이용되고 있다. Cura 기준으로 설명하도록 한다. 사용하는 3D 프린터에 따라
타 프로그램을 사용할 수 있으므로, 세부적인 설정 및 인쇄 방법은 해당 3D 프린터 매뉴얼을
참고하거나, 프린팅 작업장의 관리자에게 문의하여 출력작업을 진행하도록 한다.
대표 슬라이서인 Cura 를 다운로드 받는 주소는 다음과 같다.
Cura 다운로드 : https://ultimaker.com/en/products/cura-software
[그림] Cura 는 오픈소스로, Windows, Mac OS, Linux 모두 지원하는 대표적인 슬라이서 이다.
내가 모델링 한 결과를 실제로 프린팅 하기 위해서는 3D 프린터가 필요로 하다. 하지만
아직까지는 대중적인 가격대가 아니고, 성능이나 편의성이 3D 프린터에 대해 가지고 있는
생각을 충족해 주지 않을 확률이 높다. 따라서, 3D 프린터를 직접 구입하는 것 보다는 3D
프린터를 무료로 활용할 수 있는 공간을 이용하여 3D 프린팅을 해 보는 것을 추천한다.
일반적으로 프린팅할때 다양한 옵션이 주어진다. 크게 적층 높이, 속도, 채우기의 요소가
있는데, 다음 요소는 인쇄할 때 다음과 같은 형항을 준다.
적층 높이 : 3D 프린터에서 쌓는 높이를 의미한다. 일반적으로 3D 프린팅한 결과물을 보면
플라스틱 층이 한층씩 쌓아올려진 모습을 볼 수 있는데, 당연히 이 쌓이는 높이가 낮을수록
정밀한 결과물을 보여준다. 하지만, 그만큼 여러번 쌓아야 완성되어 완성 시간이 기하
급수적으로 늘 수 있으니, 처음 인쇄할 때는 높게 인쇄하자. 분명 여러번 수정 및 인쇄를
반복하게 될 것이다. 이렇게 인쇄와 수정을 반복하고 최종 완성물을 출력할 때는 적층 높이를
낮게 하여 완성품 퀄리티를 높일 수 있다.
인쇄 속도 : 노즐이 이동하는 속도이다. 일반적으로 빠르게 움직이면, 인쇄속도가 단축될 뿐만
아니라 떠 있는 구조물을 지지대 없이 인쇄할 수 있는 확률 (실패할 확률이 높다. 일반적으로는
지지대를 갖추어 놓아야 한다.)이 높아진다. 하지만, 제대로 적층이 안되어 인쇄 실패할 확률이
높아지니 무조건 속도를 높이는 것은 좋지 않다.
채우기 : 프린팅 한 면이 있을 경우 내부를 몇 %로 채울지를 결정하는 수치이다. 벽 내부르를
벌집 모양처럼 채우는 비중을 뜻한다. 내부를 꽉 채운다면 튼튼하게 결과물을 뽑아낼 수
있지만, 출력하는데 드는 재료와 시간이 기하급수적으로 늘게되는 문제가 있다. 반대로
채우기가 너무 낮으면 견고하지 못하고 충격에 상대적으로 쉽게 부서지는 경우가 발생한다.
프로토타입은 10~15% 정도로 하고 완성품은 20~30%대를 선택하도록 하자.
[그림] Cura 실행 화면. Load 를 눌러 STL 파일을 불러오면, 자동으로 배치되고, 인쇄 예상
시간을 보여준다.
간단한 에제 모델링의 경우 별다른 속성 지정없이 쉽게 인쇄할 수 있으나, 복잡한 구조물이나
부품과 같은 요소는 인쇄에 실패할 수도 있다. 또한 ABS 재질로 인쇄하는 경우, 열로인한
팽창과 수축으로 모델링한 사이즈와 정확하게 일치하지 않는 경우가 발생 한다. 프린터마다
속성이 다르고, 재질과 뽑는 결과물의 구성에 따라 최적의 설정 값이 다른데, 이는 여러 번으
인쇄와 시행착오를 통해 익혀야 하는 부분이다.
[그림] 결과물을 레이저 커터로 만든 결과 물. 특정 두께의 조립형 구조물은 3D 프린트 대신
레이저 커터를 이용하여 빠르게 결과물을 만들 수 있다.
여기서 잠깐! 레이저 커터
우리가 만들려는 외관은 3D 프린터의 대안으로 레이저 커터를 사용하는 방법이 있다.
레이저 커터는 레이저 절삭 방식(Laser Cutting)을 사용하는데, 레이저 빔을 최소 지름에 집중
시켜 물질을 가열하여 기화, 즉 재빠르게 태워서 절삭하는 방식으로 절삭한다. 일반적으로는
절삭용도로 사용할 수 있지만, 강도를 낮추면 살짝 그을릴 수 있어서 문양을 겉면에
표시하는 용도로 사용할 수도 있다. 3D 프린터가 적층 방식이기 때문에 시간이 오래걸리는
반면, 레이저 커터는 잘라내는 방식이기 때문에 상대적으로 빠르게 결과물을 볼수 있다.
하지만 피규어 형태나 재료의 두께를 벗어나지 못하는 한계가 있으므로, 모델링 형태와
상황에 맞추어 사용할 수 있도록 하자.
[ 레이저 커터, 제료를 잘라내는 식으로 만들 때 매우 유용하다. 메이커 스페이스에서 사용할 수
있다 ]
이번 장을 정리하며
이번 장에서는 프로토타이핑을 할 수 있는 방법들을 살펴보고, 3D 모델링한 파일을
3D 프린터로 출력하는 방법을 살펴보았다. 높은 가격, 느린 출력 속도와 부족한 품질, 적층형
방식의 프린팅 방식의 한계로 인하여, 대중화 되기 어려운 부분이 있으나, 자신의 생각을
초기에 프로토타입으로 구현하는 데 최적화 된 방법이다. 실제 3D 프린터나 레이저커터를
사용할 수 있도록 비치한 메이커 스페이스는 부록에서 다룰 예정이다. 다음 마지막 장에서는
외관도 완성된 라즈베리파이 오디오를 Circulus 플랫폼을 이용하여 IoT 제품으로 완성하는
법을 다룰 것이다.
장비가 없을 때, 메이커 스페이스로
프로토타이핑을 할 수 있는 방법들을 살펴보고, 3D 모델링한 파일을 3D 프린터로 출력하는
방법을 살펴보았다. 높은 가격, 느린 출력 속도와 부족한 품질, 적층형 방식의 프린팅 방식의
한계로 인하여, 대중화 되기 어려운 부분이 있으나, 자신의 생각을 초기에 프로토타입으로
구현하는 데 최적화 된 방법이다. 실제 3D 프린터나 레이저커터를 사용할 수 있도록 비치한
메이커 스페이스를 찾아가는 것이 방법이다
http://www.makeall.com/subpage.php?p=makerspace
한국과학창의재단에서 운영하는 MakeAll 에서는 프로젝트 제작 방법과 더불어, 전국의 메이커
스페이스를 소개하고 있다. 집과 가까운 메이커 스페이스를 방문하여 실제로 3D 프린터나
레이저 커터를 활용해 보도록 하자.
[그림] 전국의 메이커스페이스 배치. Makeall.com 을 이용하여 집근처의 메이커 스페이스를
검색할 수 있다.
9. 언제 어디서나 동작하는 IoT – Circulus
집 밖에서도 동작되는 IoT
지금까지 하드웨어를 구성하고 소프트웨어와 3D 모델링을 통해 하나의 제품을 만들어 보았다.
집안에서 WiFi 로 연결되어 있다면 방 안에서도 거실에 있는 전등이나 오디오를 쉽게 제어할
수 있는 것이다. 단, 회사나 학교등 실외에서는 같은 WiFi 망에 연결되어 있지 않으므로, 이를
제어할 수가 없다. 근래에 부각되는 사물인터넷이라 불리는 IoT, 즉 Internet Of Things 은 구글,
네이버가 고유 IP 기반에 www.google.com, www.naver.com 같은 도메인을 지니고 있듯이,
자신을 구별할 수 있는 유일한 IP 를 가지고 인터넷에 연결되어야 사용이 가능하다. 현재
일반적으로 사용하는 ip 체제인 IPv4 로는 증가하는 모든 사물들의 주소를 할당하는데
어려움이 있어, 128 비트인 IPv6 의 필요성이 대두되고 있다.
이러한 공인 IP 를 가지고 있다면, 큰 고민 없이 외부에서도 접근할 수 있겠지만, 어떻게 바뀔지
모르는 무선 AP 에 동적으로 접속하는 라즈베리파이에 매달 비싼 이용료를 지불해야 하는 공인
IP 를 부여하는 것은 그리 경제적인 방법이 아니다. 우리는 이번 장에서 공인 IP 없이 IoT 를
구현할 수 있게 해주는 Circulus 플랫폼을 이용하여, IoT 를 가능하게 하면서 불가능 했던
기능들을 확장시켜 볼 것이다.
[그림] 집 안 뿐만 아니라 밖에서도 제품을 제어할 수 있어야 한다.
IoT MAKE, Circulus 플랫폼 이란
Circulus 는 교육용을 목적으로 만들어진 서비스로서, 클라우드 상에서 라즈베리파이를 개발할
수 있는 기능 또한 지원하고 있다. 즉, 라즈베리파이가 손상되거나 분실되어도 클라우드 상에
저장되어 있으므로 걱정하지 않아도 된다. Circulus 의 핵심은 공인 IP 가 없어도 IoT 가
가능하도록 중간 연결 통로 역할을 한다는 것이다. Circulus 롬이 탑재된 라즈베리파이는
Circulus Platform 에 접속해 있고, 모바일 리모콘으로 해당 라즈베리파이를 접근하려고 요청이
오게 되면, 두개 사이의 연결을 담당해 주는 역할을 한다. 이 기능이 확장되어 라즈베리파이와
라즈베리파이가 통신할 수 있도록 하고, 다수의 사용자가 하나의 라즈베리파이를 제어할 수
있도록 API 를 제공한다. 아울러 위치 파악, 한글 TTS 를 비롯하여 GPIO 를 통해 제어했던 7
Segment, LED, 온습도, 조도 센서와 같은 다양한 센서를 손 쉽게 개발할 수 있도록 다양한
API 를 지원하는 것이 특징이다.
[그림] Circulus 를 이용하면 다양한 API 를 이용하여 IoT 제품을 손쉽게 개발할 수 있다.
라즈베리파이에서 Circulus 를 - ROM 설치 및 설정
Circulus 를 이용하여 개발하기 위해서는 라즈비안 기반의 전용 ROM 을 사용해야 한다. 롬을
다운로드 하여, MicroSD 카드에 기록해 주도록 하자.
다운로드 주소 : http://rom.circul.us
여기서 잠깐! SD 카드 백업 하기
SD 카드에 새로운 ROM 을쓰게 된다면, 이전 데이터가 날라가게 된다. 이미지
쓰기에사용되는 Win32DiskImager 에는 Write 기능과더불어 Read 기능이 있다. 이 Read
기능을 이용하면 기존에 저장된 데이터를 백업할 수 있고, 이를통해 다시 복원할 수 있다.
라즈베리파이를 인터넷과 연결하기 위해 처음에는 직접 설정 값을 입력해야 했다. 하지만,
Circulus 환경으로 부팅하면, 스마트 폰을 인터넷에 연결하는 것과 같은 방식으로 간편하게
접속 설정을 할 수 있다. ROM 을 MicroSD 카드에 설치한 후, 라즈베리파이의 상태를 확인하기
위해 3.5mm 잭에 스피커나 이이폰을 연결하도록 한다. 라즈베리파이의 MicroSD 어댑터에
장착하여 부팅을 시작하면, Disconnected Circulus 라는 소리가 나오는 것을 알수 있다. 이때,
스마트 폰이나 컴퓨터로 WiFi 목록을 확인하면, circulus_0000000 형태의 AP(Access Point) 가
새로 생겨난 것을 확인할 수 있다. 해당 AP 를 선택하여 접속을 시도하고, 비밀번호를 물으면
1234567890 을 입력한 후 확인을 선택하자. 그러면, AP 에 스마트 폰이 접속된 것을 확인할 수
있다.
[ Circulus_시리얼명 으로 시작하는 AP 에 접속 후, 비밀번호를 입력하여 연결 ]
접속이 완료되면, 스마트 폰의 브라우저를 열고, 주소를 192.168.42.1 로 입력하도록 한다.
라즈베리파이의 인터넷 접속 연결 화면이 보여지고, 잠시후 접속 가능한 Wifi 목록이 표시되게
된다. 접속 가능한 Wifi 이름을 선택하면, 패스워드를 입력하는 화면으로 이동하는데, 패스워드
입력 후 Confirm 을 클릭하도록 한다. 정상적으로 입력하였다면, 잠시 후 스피커에서
Connected On Circulus 라는 소리와 함께 접속이 성공되었음을 알려주게 된다.
AP 명에 Circulus_0000000 식으로 표시되고, 동일하게 접속 페이지 하단에 뜨는 7~8 자리
문자를 기록해 두도록 하자. 라즈베리파이의 일련 번호로서, Circulus 에서 사용하도록 해당
라즈베리파이를 등록할 때 사용하게 된다.
[ 접속할 AP 를 선택한 후 비밀 번호를 입력하고 Confirm 을 누르면 접속이 완료된다 ]
Circulus 에 라즈베리파이 ID 등록하기
기존에는 라즈베리파이를 서버로 구동하여 하드웨어 제어 및 모바일 웹 모두를 탑재시켰었다.
이미 Circulus 에 접속된 라즈베리파이를 사용자가 개발하기 위해서는, 로그인 한 계정에
등록하는 작업을 수행해야 한다. 왼쪽 상단의 자신의 아이디 명을 클릭하면, Serial 번호를
입력하는 공간을 확인할 수 있다. 라즈베리파이의 Serial 번호를 입력 후 Update 를 수행해
주도록 한다.
[ id 와 로그인에 사용할 비밀번호를 입력한 후 Sigin up & in 을 클릭하면 접속할수 있다.]
이제, 첫 프로젝트를 시작하고, 하드웨어를 동작해 보도록 한다. 상단 메뉴의 Project 로
이동하여, NodeJS 를 선택한다. Hello_Pi 로 프로젝트 명을 입력 한 후 Crete Project 를 눌러
프로젝트 공간으로 이동하도록 한다.
[ hw 에 하드웨어 Serial 을 입력 한 후, NodeJS 프로젝트를 생성하도록 한다 ]
이제, 첫 프로젝트를 시작하고, 하드웨어를 동작해 보도록 한다. 상단 메뉴의 Project 로
이동하여, NodeJS 를 선택한다. Hello_Pi 로 프로젝트 명을 입력 한 후 Crete Project 를 눌러
프로젝트 공간으로 이동하도록 한다.
동작을 테스트 하기 위하여 Hello Pi 를 출력해 보도록 한다. 라즈베리파이에 스피커를
연결하고, 영문 및 한글 음성으로 소리를 듣는 예제이다. Index.js 에 다음의 코드를 입력하고
Run 버튼을 눌러 실행하도록 한다.
[ ch9_tts.js ]
// circulus api 모듈을 불러온다.
var us = require('circulus');
// 영문으로 tts 를 생성한다.
us.tts('Hello pi');
console.log('hello pi!');
setTimeout(function(){
// 한글로 tts 를 생성한다.
us.tts('만나서 반갑습니다', true);
}, 1000);
[ 코드를 입력 후 Run 버튼을 실행하면, 실행 결과가 라즈베리파이에 반영되고, 로그 데이터를
확인할 수 있다 ]
실행하면, 라즈베리파이로 음성 소리가 나는 것을 확인하고, 화면의 하단 콘솔에 결과가
출력되는 것을 알 수 있다. 이렇게 Circulus 플랫폼을 사용하면, 유선 연결 없이 인터넷에
연결된 라즈베리파이를 손쉽게 개발할 수 있는 기능들을 지원한다. 간단한 예제로 하나 더
실행해 보도록 한다. 접속한 라즈베리파이가 어디에서 접속되었는지 위치를 추척하는
방법이다. Wifi 에 접속한 정보를 토대로 대략적인 위/경도와 주소를 알 수 있게 해 준다.
[ ch9_geolocation.js ]
var us = require('circulus');
// 현재 접속한 대략적인 위치 정보를 가져온다.
us.getGeolocation(function(data){
console.log(data);
});
[ 라즈베리파이가 연결된 대략적인 위/경도와 근처 주소를 반환 ]
Info 로그 콘솔창 위치 바꾸기
로그 콘솔창이 기본적으로는 하단에 배치되나, 사용시에 불편할 수가 있다. 하단의 Console
버튼을 한번 더 눌러주면 우측으로 콘솔창의 위치가 변경되고, 다시한번 눌러주면 사라지게
된다. Clear 버튼을 누르면, 콘솔창의 로그가 삭제가 된다. 필요에 따라 사용하도록 한다.
Circulus 로 하드웨어 제어 하기
[ Circulus 를 이용한 하드웨어 제어 실습을 위한 구성 ]
우리는 GPIO 다루기에서 WiringPi 를 이용하여 LED, 초음파, 7 Segment Display, 온습도 등을
출력해 보았다. Circulus 를 이용하여 동일한 예제를 다시한번 실습해 보도록 한다. 실습을 위해
하드웨어 연결 구성을 다음과 같이 한다.
[ 실습 하드웨어 실제 구성 ]
LED 점등
LED 점등을 위해 사용할 핀을 설정해 주고, true/false 로 on/off 를 수행할 수 있다. 동작하기
위한 방법은 다음과 같이 간단하게 구성할 수 있다.
[ ch9_led.js ]
var us = require('circulus');
// 7번 핀의 LED를 초기화 한다.
us.initLED(7);
// LED 를 켠다.
us.setLED(true);
단일 LED 의 경우, 위와 같이 간단하게 사용할 수 있지만, 1 개 이상의 LED 를 사용하여
제어하는 경우가 발생할 수 있다. 이와 같은 경우에는 명령어 뒤에 Key 값을 포함하여, 해당
Key 값을 제어할 수 있도록 구성할 수 있다. 여러 개의 LED 를 사용하기 위한 방법은 다음과
같다.
[ ch9_led2.js ]
var us = require('circulus');
// 7번 핀의 LED를 led1 이라는 이름으로 초기화 한다.
us.initLED(7, 'led1');
// led1 이라는 이름의 LED를 끈다.
us.setLED(false, 'led1');
// 5번핀의 LED를 led2 라는 이름으로 초기화 한다.
us.initLED(5, 'led2');
// led2 이라는 이름의 led를 켠타.
us.setLED(true, 'led2');
7 Segment 동작
7 세그먼트를 동작하기 위해, 기존에는 모듈 설치 및 복잡한 코드를 작성하여 사용해여 했다.
Circulus 를 이용해 다음과 같이 3 줄만으로 작성이 완료된다. initText 로 사용할 SPI 기반 7
세그먼트 모듈을 초기화 하고, 이후에 setText 함수로 출력할 내용을 입력해 주면 된다.
[ ch9_7segment.js ]
var us = require('circulus');
// SPI방시그이 7세그먼트를 초기화 한다.
us.initText();
// 7세그먼트에 hipi 를 출력한다.
us.setText('hipi');
온습도확인
온습도 측정을 위해 설치해야 할 칩셋용 모듈과 NodeJS 용 모듈이 통합되었다. initTemp
함수로 GPIO 출력 핀을 설정해 주고, getTemp 함수로 온습도 값을 측정할 수 있다.
[ ch9_temp.js ]
var us = require('circulus');
// 4번 핀에 연결된 온습도 센서를 초기화 한다.
us.initTemp(4);
// 1초 간격으로 온습도 값을 측정하여 출력한다.
setInterval(function(){
console.log(us.getTemp());
},1000);
거리측정
측정도 별도의 계산 공식이나 모듈 추가 없이 간단하게 사용할 수 있도록 지원해 준다.
initDistance 함수로 ECHO 핀과 TRIGGER 핀을 설정해 주고, getDistance 함수를 이용하여
측정된 거리를 cm 단위로 반환받게 된다.
[ ch9_distance.js ]
var us = require('circulus');
// 27번 핀을 에코로, 22번 핀을 트리거로 설정한다.
us.initDistance(27,22);
// 1초 간격으로 거리를 축정하여 출력한다.
setInterval(function(){
console.log(us.getDistance());
},1000);
모바일로 하드웨어 제어
PWM 기능을 이용하여 모바일을 통해 LED 밝기를 조절하는 프로그램을 간단하게 구현해 보는
예제이다. 7 장에서 다룬 예제로는 라즈베리파이와 같은 인터넷 접속 영역에서만 제어를 할 수
있었다. 구현을 위해 웹 서버 모듈인 ExpressJS 와 실시간 통신 모듈인 Socket.IO 모듈을
이용했다. 하지만, Circulus 를 이용해서는 웹 페이지 프로젝트와 라즈베리파이 프로젝트를
분리하여 구현할 수 있다. 이를 통해 어디서나 Circulus 상에서 개발된 웹 페이지를 통해 역시
Circulus 에 연결된 라즈베리파이를 제어할 수 있도록 도와준다.
우선 서버 프로젝트를 진행한다. Circulus_Test 라는 명으로 NodeJS 프로젝트를 생성하여
라즈베리파이에서 실행하도록 한다.
[ ch9_spwm.js ]
var us = require('circulus');
// 7번 핀을 소프트웨어 PWM 방식으로 초기화한다.
us.initSPWM(7);
// 값을 입력한다.
us.writeSPWM(240);
// 클라이언트로부터 brightness 이벤트로 받아진 값을 입력한다.
us.receive('brightness',function(val){
us.writeSPWM(parseInt(val));
});z
7 번 핀에 연결된 LED 에 소프트웨어 방식의 PWM 을 동작시키고, 최대한의 밝기로 불을 켜는
동작을 수행한다. Receice 함수를 통해, 모바일로부터 brightness 이벤트가 발생 할 경우, LED
밝기를 실시간으로 조절하는 역할을 한다.
이제 모바일 쪽 코드를 만들 차례이다. Circulus_Web 이라는 명칭으로 Mobile 프로젝트를
생성하도록 한다. 범위를 설정할 수 있는 입력 컴포넌트를 배치하고, 이 입력 값이 변경될 때
brightness 이벤트를 통해 변경된 값을 전송하는 에제이다. Init 함수를 이용하여, 접속할
라즈베리파이를 지정하게 되는데, 인터넷 접속시에 확인한 라즈베리파이 일련번호를 이곳에
입력하여 실행하도록 하자.
[ ch9_spwm.html ]
<html>
<head>
<title>Mobile Web</title>
<meta name="viewport" content="width=device-width, initial-
scale=1.0, user-scalable=no" />
<link rel="stylesheet"
href="http://code.jquery.com/mobile/1.4.5/jquery.mobile-
1.4.5.min.css" />
<script src="http://code.jquery.com/jquery-
2.2.0.min.js"></script>
<script
src="http://code.jquery.com/mobile/1.4.5/jquery.mobile-
1.4.5.min.js"></script>
<script src="http://www.circul.us/circulus.js"></script>
<script>
$c.ready(function(us){
// 라즈베리파이의 일련번호를 입력한다.
us.init('시리얼번호 입력');
// 슬라이더 값이 변경 시, 라즈베리파이에 값을 전달한다.
$('input').change(function(){
us.send('brightness', $(this).val());
});
});
</script>
</head>
<body>
<div data-role='main' class='ui-content'>
<input type="range" min="0" max="240" value="240" />
</div>
</body>
</html>
[우측 하단 실행창의 하단에 언제나 접속할 수 있는 URL 이 표시되고, 상단에는 간단하게
접속할 수 있는 단축 URL 이 표시된다.]
결과 웹 페이지에서 제어할 수도 있지만, 우측 하단 결과창의 url 을 클릭하거나 직접 입력하여
동작을 확인해 볼 수 있다. 이렇게 만들어진 결과는 프로젝트를 Drop 하기 전 까지 제공되는
URL 을 통해 결과를 확인하고, 어디에서나 접속할 수 있는 환경이 제공되게 된다.
Info 라즈베리파이 연결 상태 확인
Circulus 를 이용하여 라즈베리파이에 개발하기 위해서는 연결상태를 확인해 보아야 한다.
프로젝트 하단에 있는 Check 버튼을 클릭하면, 등록한 하드웨어 접속 상태를 확인할 수
있다. 이때, 라즈베리파이의 IP 가 반환되는 경우 Circulus 에 정상적으로 접속이 된 상태이다.
이 IP 를 이용하여 Putty 나 FTP 를 통해 파일을 주고 받을 수도 있다.
Circulus 기반으로 리 메이크
기존에는 라즈베리파이를 서버로 구동하여 하드웨어 제어 및 모바일 웹 모두를 탑재시켰었다.
하지만 Circulus Platform 을 이용한 개발에서는 첫째, 모바일과하드웨어 제어 부분이 완벽하게
분리가 된다. 둘째, Circulus Platform 에 배포된 웹으로 언제어디서나 접근할 수 있다.
라즈베리파이의 Serial 번호를입력하면 Circulus Platform 에서 모바일과 라즈베리파이의
연동을 제공해 준다. 셋째, 기본적인 하드웨어 제어 및 타 디바이스와의 통신을 가능하게 하는
API 를 지원함으로서, 손쉽게 나만의 IoT 하드웨어를개발 및 운영할 수 있도록 도와준다.
기존 오디오 코드를 Circulus 에서 구동 가능하도록 프로그램을변경하도록 한다. 통합되어 있는
코드를 라즈베리파이 제어 부와 모바일 컨트롤러 부로 나누도록 할 것이다.
하드웨어 개발
라즈베리파이에서 처음 실행되는 부분이다. ExpressJS 를 활용하여 구성한 기존 예제와 달리,
receive 함수를 이용하여, 모바일에서 전달된 명령이 전달받아 실행하는 구성을 가지고 있다.
실질적으로 음악이나 라디오를 재생하고, 복합 기능을 구현하는 것은 별도의 파일로 구현되어
있다. 라즈베리파이 오디오는 평상시에는 시계역할을 하므로, 코드의 마지막 부분에 매 1 분
마다 7 세그먼트에 표시되는 시간을 1 분마다 갱신하도록 구성되었다.
[ index.js ]
var us = require('circulus');
var ext = require('./extend');
var exec = require('child_process').exec;
var recom = require('./recom');
var store = require('./store');
var top = require('./top');
var radio = require('./radio');
var talk = require('./talk');
isLoop = false;
isRepeat = false;
// 모바일로부터 play 이벤트를 수신한 경우 호출된다.
us.receive('play', function(data){
var url = data.url;
console.log(__dirname + ' / ' + url);
ext.tts('음악을 다운로드 하고 있습니다');
exec('sudo scdl --debug --path /home/pi/buffer -l ' + url,
function(err, stderr, stdout){
if(stdout && stdout.indexOf('filename : ') > -1){
data.file = stdout.split('filename :
')[1].split('[0mn')[0];
store.add(data);
ext.play(data, true);
} else {
console.log('Error occured ' + err);
}
});
});
// 음악 목록 호출시, 목록을 반환한다.
us.receive('list', function(data){
us.send('list', { list : ext.get()});
});
// 볼륨을 조정한다.
us.receive('volume', function(data){
var vol = data.volume;
ext.setVolume(vol);
});
// 라디오를 재생할 때 호출된다.
us.receive('radio', function(data){
radio.play(data);
});
// 뉴스를 읽어줄 때 호출된다.
us.receive('news', function(data){
radio.news();
});
// 날씨를 읽어줄 때 호출된다.
us.receive('weather', function(data){
radio.weather();
});
// 다시 재생이 호출될 때 호출된다.
us.receive('resume', function(data){
ext.play();
});
// 다음 음악 재생을 선택했을 때 호출 된다.
us.receive('next', function(data){
ext.next();
});
// 이전 음악 재생을 선택했을 때 호출 된다.
us.receive('prev', function(data){
ext.prev();
});
// 대화하기를 선택했을 때 호출 된다.
us.receive('talk', function(data){
talk.say(data);
});
// 정지를 선택했을 때 호출된다.
us.receive('stop', function(data){
exec('pkill mplayer');
});
// 기존 재생 목록을 선택했을 때 호출 된다.
us.receive('history', function(data){
us.send('history', { result : store.gets()});
});
// 추천 음악 목록을 선택했을 때 호출된다.
us.receive('recom', function(data){
us.send('recom', { result : recom.getParam()});
});
// 메시지 전달을 선택했을 때 호출된다.
us.receive('message', function(data){
us.tts(data,true);
});
// 알람 정보를 가져올 때 호출 된다.
us.receive('getAlarm', function(data){
us.send('getAlarm', { result : store.getAlarm()});
});
// 알람 정보를 설정할 때 호출 된다.
us.receive('setAlarm', function(data){
us.tts('알람을 설정 합니다', true);
store.setAlarm(data);
});
// 백라이트 LED 밝기를 설정할 때 호출 된다.
us.receive('setBg', function(data){
console.log('Settring Bg : ' + data.bg);
ext.setBg(parseInt(data.bg));
});
// 포그라운드 7세그먼트 밝기를 설정할 때 호출 된다.
us.receive('setFg', function(data){
console.log('Settring Fg : ' + data.fg);
ext.setFg(parseInt(data.fg));
});
// 최신가요 목록을 가져올 때 호출 된다.
us.receive('top', function(data){
top.getList(function(list){
console.log(list);
us.send('top',{ result : list});
});
});
// 프로그램 강제 업데이트 요청시 호출된다.
us.receive('update',function(){
us.update();
});
// 목록 반복 요청 시 호출된다.
us.receive('loop',function(data){
isLoop = data.value;
if(isLoop){
us.tts('순환 재생을 시작합니다', true);
} else {
us.tts('순환 재생을 종료합니다', true);
}
});
// 현재 음악 반복시 호출된다.
us.receive('repeat',function(data){
isRepeat = data.value;
if(isRepeat){
us.tts('반복 재생을 시작합니다', true);
} else {
us.tts('반복 재생을 종료합니다', true);
}
});
// 첫 구동시 자기소개를 음성으로 읽어준다.
us.tts('안녕하십니까 여러분의 소셜 오디오 파이오가 깨어났습니다', true);
console.log('[20160220] piAu Stable v1 Initialized!');
// 현재 시간을 7세그먼트에 표시하고, 1분 간격으로 갱신한다.
ext.setCurrentTime();
setInterval(function(){
ext.setCurrentTime();
},60000);
실제 음악 재생 시간을 표시하여 재생하고, 시간을 표시하는 기능등 핵심 기능을 제공하는
파일이다. 음악 재생을 담당하는 play 함수는 mplayer 프로그램을 이용하여 재생하고, 나오는
재생 정보를 실시간으로 캡쳐하여 7 세그먼트에 보여주게 된다. 다음 음악과 이전 음악 재생을
담당하는 next 와 prev 함수는 기존에 재생된 음악 리스트를 기준으로 현재 재생중인 음악의
과거 혹은 현재 값을 바탕으로 음악 재생을 처리한다. setVolume 함수는 라즈베리파이의 내장
명령어인 amixer 를 이용하여 볼륨을 조정한다. 마지막으로 현재 시간을 표시하는
setCurrentTime 함수는 현재 시간 표시뿐만 아니라, 알람이 설정된 경우, 설정된 알람 시간과
현재 시간을 비교하여 알람을 발생시키는 역할을 한다.
[ extend.js ]
var exec = require('child_process').exec;
var us = require('circulus');
var spawn = require('child_process').spawn;
var LocalStorage = require('node-localstorage').LocalStorage;
var localStorage = new LocalStorage('./music_list');
var store = require('./store');
var isContinue = false;
// 7세그먼트를 초기화 하고, 초기 메시지를 출력한다.
us.initText();
us.setText('piau');
// 백라이트용 LED를 소프트웨어 PWM 방식으로 초기화 하고 밝혀준다.
us.initSPWM(7);
us.writeSPWM(240);
// 동작 표시용 LED를 초기화 하고 켠다.
us.initLED(25);
us.setLED(true);
// 7세그먼트 밝기 조절 시 호출된다.
exports.setFg = function(val){
us.setBrightness(val);
}
// 백그라운드 LED 밝기 조절을 소프트웨어 PWM 으로 조절한다.
exports.setBg = function(val){
us.writeSPWM(val);
}
var volume = 100;
// 현재 시간을 7세그먼트에 표시한다.
var setTime = function(number1, number2){
var min1 = Math.floor(number1 / 10);
var min2 = number1 % 10;
var sec1 = Math.floor(number2 / 10);
var sec2 = number2 % 10;
us.setText(min1.toString() + min2.toString() + sec1.toString() +
sec2.toString(),false,false,false,false);
}
// 입력받은 한글 음성을 출력한다.
var tts = function(msg){
us.tts(msg, true);
}
// 저장소에 저장된 목록을 이용하여 다음 음악을 재생하고, 화면에 표시한다.
exports.next = function(){
var item = store.next();
console.log(item);
play(item,true);
us.send('info', item);
}
// 저장소에 저장된 목록을 이용하여 이전 음악을 재생하고, 화면에 표시한다.
exports.prev = function(){
var item = store.prev();
console.log(item);
play(item, true);
us.send('info', item);
}
// 선택한 음악을 다운로드 받고 재생한다.
var play = function(item, isPlay){
// 만일, 재생할 음악을 전달받았다면, 해당 음악 재생을 준비한다.
if(item){
var data = item;
store.setMusic(data.file);
if(data.title){
tts(data.title + '을 재생합니다');
}
// 선택된 음악이 없다면, 마지막 재생 음악을 가져온다.
} else {
var data = { file : store.getMusic() };
}
isContinue = isPlay;
// 이전에 재생중인 것이 있다면, 강제종료 후 재생 준비를 한다.
exec('pkill mplayer', function(){
console.log('Playing : ' + data.file);
// 음악을 재생한다.
var ps = spawn('mplayer',['/home/pi/buffer/' + data.file]);
var before = 0;
ps.stderr.on('data', function(data){
isContinue = false;
// 한글 등 다국어를 위해 UTF8 방식으로 문자열을 변환한다.
var progress = data.toString('utf8');
// 재생 시간 정보가 있는지 확인한다.
if(progress && progress.indexOf('of') > -1){
var token = progress.split(' of ');
// 재생 정보를 가져온다.
var current = token[0].split(' ').slice(-2)[0];
var total = token[1].split(' ')[0]
var remain = Math.round(total - current);
// 현재 표시 시간과 다르다면, 7세그먼트의 표시 시간을 바꾸고
// 클라이언트에도 변경된 현재 재생 시간을 전달해 준다.
if(before != remain){
before = remain;
setTime(Math.floor(remain / 60), remain % 60);
us.send('time', { total : total, current :
current});
}
}
});
ps.on('close', function(code){
console.log(code + ' : ' + isRepeat + ' / ' + isLoop + '
/ ' + isContinue);
// 재생이 종료시 조건을 확인한다.
if(!isLoop && !isRepeat){
// tts('재생이 종료되었습니다');
// 현재 음악 반복 재생인 경우, 현재 음악을 다시 재생한다.
} else if(isRepeat && !isContinue){
console.log('repeat');
play(data);
// 목록 반복의 경우 다음음악을 재생한다.
} else if(isLoop && !isContinue){
console.log('next');
exports.next();
}
// 클라이언트에 종료 정보를 전달하고,
// 7세그먼트에 현재시각을 표시한다.
us.send('finish');
setCurrentTime(true);
});
});
}
// 현재 시각을 7세그먼트에 표시하며, 알람 설정 정보를 확인하고 처리한다.
var setCurrentTime = function(isDisplay){
var date = new Date();
// 알람 시간 정보를 저장소로부터 가져온다.
var alarm = localStorage.getItem('alarm');
// 알람정보가 있는지 확인한다.
if(alarm != null && !isDisplay){
alarm = JSON.parse(alarm);
var day = date.getDay().toString();
// 알람정보가 존재하고, 현재 시각과 같다면 알람을 발생한다.
if(alarm.days.indexOf(day) > -1){
if(alarm.hour == date.getHours() && alarm.minute ==
date.getMinutes()){
tts('주인님 어서 일어나세요');
play();
}
}
}
// 현재 시각을 7세그먼트에 표시한다.
setTime(date.getHours(), date.getMinutes());
}
exports.play = play;
exports.setCurrentTime = setCurrentTime;
// 볼륨을 설정한다.
exports.setVolume = function(vol){
if(vol == 'up'){
if(volume < 100){
volume += 5;
}
} else {
if(volume > 0){
volume -= 5;
}
}
tts('볼륨을 ' + volume + '% 로 조정하였습니다');
exec('amixer sset PCM ' + volume + '%');
}
exports.tts = tts;
라디오 및 날씨와 뉴스 정보를 가져오는 모듈이다. Play 함수는 모바일 컨트롤러로 부터
받아지는 라디오 스트리밍 재생 주소로 라디오를 재생한다. News 와 weather 는 우선 RSS
서비스 주소로부터 받아진 XML 데이터를 JSON 으로 변환하고, 필요한 정보만을 tts 함수를
사용하여 스피커로 출력하게 된다.
[ radio.js ]
var stew = new (require('stew-select')).Stew();
var htmlparser = require("htmlparser");
var us = require('circulus');
var spawn = require('child_process').spawn;
var exec = require('child_process').exec;
var rsj = require('rsj');
// 인터넷 스트리밍 라디오 주소를 활용해 라디오를 재생한다.
exports.play = function(data){
us.tts(data.name + ' 라디오를 재생합니다',true);
exec('sudo pkill mplayer', function(){
spawn('mplayer',['-quiet',data.radio]);
});
};
// JTBC RSS 최신 소식을 이용하여, TTS로 읽어준다.
exports.news = function(data){
exec('sudo pkill mplayer');
// RSS XML 정보를 json 타입의 데이터로 변환한다.
rsj.r2j('http://fs.jtbc.joins.com//RSS/newsflash.xml',function(json)
{
var data = JSON.parse(json);
var script = ('안녕하세요 파이오가 최신 뉴스를 알려드리겠습니다 ' +
data[0].title + data[0].description + '이상 오늘의 뉴스를
알려드렸습니다');
// TTS 로 정보를 읽어준다.
us.tts(script,true);
});
};
// 일기 예보 정보를 읽어준다.
exports.weather = function(data){
exec('sudo pkill mplayer');
us.getGeolocation(function(data){
console.log(data);
us.tts(data.addr +'에 있습니다',true);
});
// 일기예보 RSS XML 정보를 JSON 형태로 가져온다.
rsj.r2j('http://www.kma.go.kr/weather/forecast/mid-term-
rss3.jsp?stnId=109',function(json) {
var data = JSON.parse(json);
console.log(data.length);
// 날씨 정보를 추출한다.
var script =
data[0]['rss:description'].header.wf['#'].replace(/<br />/gi, '
').replace(/,/gi, '.');
// 날씨 정보를 TTS 로 읽어준다.
us.tts('안녕하세요 파이오가 오늘의 날씨를 알려드리겠습니다 '+
data[0].title + script + ' 이상 오늘의 날씨를 알려드렸습니다', true);
});
};
음악을 추천해 주는 모듈로, 7 장에서 사용한 추천 모듈을 그대로 사용한다. 이미 들었던 음악의
장르, 길이, 비트 수를 바탕으로 유사한 패턴의 음악 형식을 반환 한다.
[ recom.js ]
var stats = require("stats-analysis");
var store = require('./store');
var genres = [];
var durs = [];
var bpms = [];
// 해당 장르의 재생 횟수를 저장한다.
var setCount = function(genre){
for(var i in genres){
if(genres[i].genre == genre){
genres[i].count++;
return;
}
}
genres.push({ genre : genre, count : 1});
}
// 가장 많이 들은 장르를 파악한다.
var getTopGenre = function(){
genres.sort(function(a, b){
return b.count - a.count;
});
return genres[0];
}
// 추천에 사용되는 인자 값을 추출한다.
exports.getParam = function(){
var list = store.gets();
console.log(list);
genres = [];
durs = [];
bpms = []
for(var i in list){
setCount(list[i].genre);
}
var item = getTopGenre();
for(var i in list){
if(list[i].genre == item.genre){
durs.push(list[i].dur);
if(list[i].bpm != null){
bpms.push(list[i].bpm);
}
}
}
durs = stats.filterOutliers(durs);
bpms = stats.filterOutliers(bpms);
return {
'genres' : item.genre,
'bpm[from]' : Math.min(bpms),
'bpm[to]' : Math.max(bpms),
'duration[from]' : Math.min(durs),
'duration[to]' : Math.max(durs),
}
}
들었던 음악 리스트를 관리하는 모듈이다. Add 함수는 현재 들은 음악을 리스트에 저장하여
보관하고, next 와 prev 함수는 들은 음악 리스트를 이용하여 현재 들은 음악의 이전/다음
음악을 반환한다. setAlarm/getAlarm 함수는 알람 정보를 저장하고 불러오는 역할을 하며,
setMusic/getMusic 은 현재 재생중인 음악 정보를 저장하고 가져오는 역할을 한다.
[ store.js ]
var LocalStorage = require('node-localstorage').LocalStorage;
var exec = require('child_process').exec;
var space = require('diskspace');
localStorage = new LocalStorage('./music_list');
// 저장된 음악 재생 목록을 가져온다.
var getList = function(){
var list = localStorage.getItem('music_list');
if(list == null){
return [];
} else {
return JSON.parse(list);
}
}
// 음악 재생 목록을 추가한다.
exports.add = function(item){
var list = getList();
for(var i in list){
if(list[i].url == item.url){
list.splice(i, 1);
}
}
list.push(item);
space.check('/', function (err, total, free, status){
if(free * 100 / total < 5){
console.log(free * 100 / total);
var last = list.shift();
exec('sudo rm /home/pi/buffer/' + last.file);
}
localStorage.setItem('music_list',JSON.stringify(list));
});
}
// 사운드클라우드 URL 을 이용하여 음악 정보를 가져온다.
exports.get = function(url){
var list = getList();
for(var i in list){
if(list[i].url == url){
return list[i];
}
}
}
// 다음 음악 재생 목록을 가져온다.
exports.next = function(){
var file = localStorage.getItem('music');
var list = getList();
for(var i = 0 ;i < list.length; i++){
if(list[i].file == file){
if(i == (list.length - 1)){
return list.shift();
}
return list[(i + 1)];
}
}
}
// 이전 음악 재생 목록을 가져온다.
exports.prev = function(){
var file = localStorage.getItem('music');
var list = getList();
for(var i = 0 ;i < list.length; i++){
if(list[i].file == file){
if(i == 0){
return list.pop();
}
return list[i - 1];
}
}
}
// 재생 목록 전체를 가져온다.
exports.gets = function(){
return getList();
}
// 알람 설정 값을 저장한다.
exports.setAlarm = function(data){
localStorage.setItem('alarm',data.param);
}
// 저장한 알람 설정 값을 가져온다.
exports.getAlarm = function(){
return localStorage.getItem('alarm');
}
// 현재 재생 음악 정보를 저장한다.
exports.setMusic = function(file){
localStorage.setItem('music', file);
}
// 저장한 현재 재생 음악 정보를 가져온다.
exports.getMusic = function(){
return localStorage.getItem('music');
}
대화하는 기능을 처리하는 모듈이다. 사용자로부터 전달 받은 메시지를 Circulus API 인 talk
함수에 전달하여, 반환된 메시지를 TTS 로 읽어주고, 사용자에게 메시지를 전달하는 역할을
한다.
[ talk.js ]
var us = require('circulus');
// 입력 받은 메시지로 대화를 수행한다.
exports.say = function(msg){
// 입력 메시지로 대답 내용을 반환한다.
us.talk(msg, function(res){
console.log('Return : ' + res);
// 대답 내용을 TTS 로 읽어준 후 클라이언트에 전달한다.
us.tts(res, true);
us.send('talk', res);
});
};
최신 가요를 추출하는 모듈이다. M.NET 의 TOP100 에서 정보를 추출하여, 우리가 사용할
앨범정보, 앨범 이미지, 가수, 음악명을 추출하여 전달하게 된다.
[ top.js ]
var stew = new (require('stew-select')).Stew();
var htmlparser = require("htmlparser");
var http = require('http');
var options = {
hostname: 'www.mnet.com', port: 80, path: '/chart/TOP100',
method: 'GET'
};
// 최신 가요 목록을 가져온다.
exports.getList = function(cb){
var req = http.request(options, function(res) {
res.setEncoding('utf8');
var dom = '';
res.on('data', function (chunk) {
dom += chunk;
});
res.on('end', function(){
var isFirst = true;
stew.select(dom, 'div.MMLITitle_Wrap',
function(err,items) {
if(err) {
cb([]);
console.error(err);
} else {
var list = [];
items.forEach(function(item){
if(stew.select(item,'img') &&
stew.select_first(item,'a.MMLIInfo_Artist')
&& stew.select_first(item,'a.MMLIInfo_Album').children[0]){
list.push({
img :
stew.select_first(item,'img').attribs.src.replace('/50/','/80/'),
artist :
stew.select_first(item,'a.MMLIInfo_Artist').children[0].raw,
title :
stew.select_first(item,'a.MMLI_Song').children[0].raw,
album :
stew.select_first(item,'a.MMLIInfo_Album').children[0].raw
});
}
});
cb(list);
}
});
});
});
req.on('error', function(e) {
cb([]);
console.log('problem with request: ' + e.message);
});
req.end();
}
모바일 개발
Index 페이지는 처음 접속하여 보여지는 모바일 시작 페이지이다. 7 장에서 만든 구성과
유사하지만, 모든 화면을 한 파일에서 함께 관리했던 기존 구성과 달리, 각각의 화면을 별도의
파일로 구성하였다. 첫 화면은 음악을 검색하고, 들었던 음악을 관리하는 페이지이다. 하단에는
음악, 라디오, 대화, 알람, 메시지 전달 메뉴로 이동할 수 있는 버튼이 배치하고 있으며, 어느
메뉴에서나 쉽게 음악 관리를 할 수 있도록 jQueryMobile 에서 제공하는 Panel 속성으로 간이
컨트롤러를 내장하고 있다.
[ index.html ]
<html>
<head>
<title>Piau Mobile</title>
<meta charset="utf-8">
<meta name="viewport" content="width=device-width, initial-
scale=1.0, user-scalable=no" />
<link rel="stylesheet"
href="http://code.jquery.com/mobile/1.4.5/jquery.mobile-
1.4.5.min.css" />
<link rel="stylesheet" href="play.css" />
<script src="http://code.jquery.com/jquery-
2.2.0.min.js"></script>
<script
src="http://code.jquery.com/mobile/1.4.5/jquery.mobile-
1.4.5.min.js"></script>
<script src="https://connect.soundcloud.com/sdk/sdk-
3.0.0.js"></script>
<script src="https://cdnjs.cloudflare.com/ajax/libs/jQuery-
Knob/1.2.13/jquery.knob.min.js"></script>
<script src="http://www.circul.us/circulus.js"></script>
<script src='index.js'></script>
<script src='radio.js'></script>
<script src='play.js'></script>
<script src='option.js'></script>
<script src='alarm.js'></script>
<script src='message.js'></script>
<script src='talk.js'></script>
</head>
<body>
<div data-role="page" id='music'>
<div data-role='header' data-position='fixed' data-tap-
toggle="false">
<a href='#panel' data-icon='action' class='ui-btn-
left'>Menu</a>
<h1>piAu</h1>
<a href='option.html' data-icon='gear' data-
transition='slideup' class='ui-btn-right'>Option</a>
<div data-role='navbar'>
<ul>
<li><a href='#' id='find'>Search</a></li>
<li><a href='#' id='his'>History</a></li>
<li><a href='#' id='top'>Top 50</a></li>
<li><a href='#' id='recom'>Recomm.</a></li>
</ul>
</div>
</div>
<div data-role='main' class='ui-content'>
<div data-role='fieldcontain' class='search'>
<input type="search" name="search" id="search"
placeholder="Search for content..." data-corners="false" />
</div>
<div data-role='fieldcontain'>
<ul data-role='listview'>
</ul>
</div>
</div>
<div data-role='footer' data-position='fixed' data-tap-
toggle="false">
<div data-role='navbar'>
<ul>
<li><a href='index.html' data-icon='audio'
data-transition='slide'>Music</a></li>
<li><a href='radio.html' data-icon='grid'
data-transition='slide'>Radio</a></li>
<li><a href='talk.html' data-icon='phone'
data-transition='slide'>Talk</a></li>
<li><a href='alarm.html' data-icon='clock'
data-transition='slide'>Alarm</a></li>
<li><a href='message.html' data-
icon='comment' data-transition='slide'>Message</a></li>
</ul>
</div>
</div>
</div>
<div data-role="panel" style='width:5.5em;display:none;'
data-position="left" data-display="overlay" id="panel" data-
theme="a">
<h3>Quick</h3>
<a href='#' name='vol_up' data-role='button' class="ui-
btn ui-shadow ui-corner-all ui-btn-a">+</a>
<a href='#' name='vol_down' data-role='button'
class="ui-btn ui-shadow ui-corner-all ui-btn-a">-</a>
<a href='#' name='resume' data-role='button' class="ui-
btn ui-shadow ui-corner-all ui-btn-a">▶</a>
<a href='#' name='stop' data-role='button'
style='display:none;' class="ui-btn ui-shadow ui-corner-all ui-btn-
a">■</a>
<a href='#' name='prev' data-role='button' class="ui-btn
ui-shadow ui-corner-all ui-btn-a">◁</a>
<a href='#' name='next' data-role='button' class="ui-btn
ui-shadow ui-corner-all ui-btn-a">▷</a>
<h3>Loop</h3>
<a href='#' name='loop_on' data-role='button' class="ui-
btn ui-shadow ui-corner-all ui-btn-a">X</a>
<a href='#' name='loop_off' style='display:none;' data-
role='button' class="ui-btn ui-shadow ui-corner-all ui-btn-a">O</a>
<h3>Repeat</h3>
<a href='#' name='repeat_on' data-role='button'
class="ui-btn ui-shadow ui-corner-all ui-btn-a">X</a>
<a href='#' name='repeat_off' style='display:none;'
data-role='button' class="ui-btn ui-shadow ui-corner-all ui-btn-
a">O</a>
</div><!-- /panel -->
</body>
</html>
Index 모듈은 라즈베리파이에 연결하기 위해 접속부와 화면을 초기화 하고, 음악을 검색하고
재생하는 기능을 포함하고 있다. 일련번호가 입력되지 않았다면, 입력창을 발생시켜
사용자로부터 라즈베리파이 일련번호를 받아 접속을 시도하게 된다. Check 함수는 연결 설정한
라즈베리파이가 Circulus 플랫폼에 제대로 접속되어 있는지 확인하는 함수이다. 텍스트
입력창에 검색할 음악명을 입력하거나 recom 함수를 통해 라즈베리파이에 저장된 음악 추천
지표를 이용하여 SoundCloud API 를 이용하여 음악을 검색한다. 검색된 결과가 화면에
표시되고, 해당 목록을 사용자가 클릭하게 되면, 음악 재생화면으로 이동하여 재생을 시작하게
된다.
[ index.js]
$c.ready(function(us){
$.mobile.ignoreContentEnabled = true;
$('div[data-role=panel]').panel();
$('div[data-role=panel]').show();
SC.initialize({ client_id:
'119c57cf677bb6a42180112605b1c053' });
// 접속할 라즈베리파이의 일련번호를 가져온다.
var serial = localStorage.getItem('serial');
// 저장된 일련번호가 없다면, 새로운 일련번호를 입력하고 저장한다.
if(serial == null || serial == 'null'){
serial = prompt("Input piAu's serial");
localStorage.setItem('serial', serial);
}
// 라즈베리파이의 일련번호로 접속한다.
us.init(serial);
// 접속할 라즈베리파이가 인터넷 망에 연결되었는지 확인한다.
us.check(function(isOn){
if(!isOn){
alert('Please check on/off at piAu!');
}
})
// 기본 배경화면을 설정한다.
$('#music').css('background-image',
'url("http://webcdn.vshare.com/8ef134ae5c5e01c96a2ed808455e82e9")');
// 음악 검색 시 호출되어 검색 결과를 화면에 보여준다..
$('#search').keypress(function(e){
$.mobile.loading('show');
if (e.which == 13) {/* 13 == enter kfiey@ascii */
var query = $('#search').val();
$('body').trigger('click');
$('#search').val('');
$('ul[data-role=listview]').empty();
SC.get('/tracks', { q: query }).then(function(tracks) {
for(var i in tracks){
var item = tracks[i];
console.log(item);
var elem = $('<a>').attr({
'data-transition' : 'pop',
'href' : 'play.html'
});
elem.data('item',{
title : item.title,
img : item.artwork_url,
url : item.permalink_url,
desc : item.description,
dur : item.duration,
genre : item.genre,
bpm : item.bpm,
cnt : item.playback_count,
time : Date.now()
}).addClass('play');
var list = $('<li>').append(elem);
elem.append($('<img>').attr('src',item.artwork_url));
elem.append($('<h2>').text(item.title));
elem.append($('<p>').text(item.description));
$('ul[data-role=listview]').prepend(list);
}
$('ul[data-role=listview]').listview('refresh');
$.mobile.loading('hide');
});
}
});
// 재생 목록의 음악을 선택 했을 때 이를 표시하고 재생한다.
$('ul[data-role=listview]').on('click','a.play', function(){
var item = $(this).data('item');
var img = $(this).attr('img');
var title = $(this).attr('title');
us.send('play', item);
$('a[name=loop_off]').hide();
$('a[name=loop_on]').show();
$('a[name=repeat_off]').hide();
$('a[name=repeat_on]').show();
sessionStorage.setItem('img', item.img);
sessionStorage.setItem('title', item.title);
});
// 재생 목록을 수신 시, 화면에 목록을 표시해 준다.
us.receive('history', function(data){
$('ul[data-role=listview]').empty();
for(var i in data.result){
var item = data.result[i];
var elem = $('<a>').attr({
'data-transition' : 'pop',
'href' : 'play.html'
}).addClass('play');
elem.data('item',item);
var list = $('<li>').append(elem);
elem.append($('<img>').attr('src',item.img));
elem.append($('<h2>').text(item.title));
elem.append($('<p>').text(item.desc));
$('ul[data-role=listview]').prepend(list);
}
$('ul[data-role=listview]').listview('refresh');
$.mobile.loading('hide');
});
// 검색 목록 메뉴 클릭 시 호출된다.
$('#find').click(function(){
$('div.search').show();
$.mobile.loading('show');
us.send('history');
});
// 최신 가요 목록 메뉴 클릭 시 호출된다.
$('#top').click(function(){
$('div.search').hide();
$.mobile.loading('show');
us.send('top');
});
// 재생 목록 메뉴 클릭 시 호출된다.
$('#his').click(function(){
$('div.search').hide();
$.mobile.loading('show');
us.send('history');
});
// 추천 음악 목록 클릭 시 호출된다.
$('#recom').click(function(){
$('div.search').hide();
$.mobile.loading('show');
us.send('recom');
});
// 추천음악을 라즈베리파이 서버로부터 전달받아 이를 클라이언트에 출력한다.
us.receive('recom',function(data){
$('ul[data-role=listview]').empty();
SC.get('/tracks', data).then(function(tracks) {
for(var i in tracks){
var item = tracks[i];
console.log(item);
var elem = $('<a>').attr({
'data-transition' : 'pop',
'href' : '#music'
}).addClass('music');
elem.data('item',{
title : item.title,
img : item.artwork_url,
url : item.permalink_url,
desc : item.description,
dur : item.duration,
genre : item.genre,
bpm : item.bpm,
cnt : item.playback_count,
time : Date.now()
})
var list = $('<li>').append(elem);
elem.append($('<img>').attr('src',item.artwork_url));
elem.append($('<h2>').text(item.title));
elem.append($('<p>').text(item.description));
$('ul[data-role=listview]').prepend(list);
}
$('ul[data-role=listview]').listview('refresh');
$.mobile.loading('hide');
});
});
// 최신 가요 목록을 라즈베리파이 서버로부터 전달받아 클라이언트에 출력한다.
us.receive('top', function(data){
$('ul[data-role=listview]').empty();
for(var i in data.result){
var item = data.result[i];
var elem = $('<a>').attr({
'data-transition' : 'pop',
'href' : '#music'
}).addClass('music');
elem.data('item',item);
var list = $('<li>').append(elem);
elem.append($('<img>').attr('src',item.img));
elem.append($('<h2>').text(item.artist + ' ' +
item.title));
elem.append($('<p>').text(item.album));
$('ul[data-role=listview]').append(list);
}
$('ul[data-role=listview]').listview('refresh');
$.mobile.loading('hide');
});
// 음악 재생 종료 시 호출된다.
us.receive('finish', function(data){
$('a[name=resume]').show();
$('a[name=stop]').hide();
});
// 다시 재생 버튼 클릭 시 호출된다.
$('body').on('click', 'a[name=resume]', function(){
us.send('resume');
});
// 정지 버튼 클릭 시 호출된다.
$('body').on('click', 'a[name=stop]', function(){
us.send('stop');
});
// 다음 음악 버튼 클릭 시 호출된다.
$('body').on('click', 'a[name=next]', function(){
us.send('next');
});
// 이전 음악 버튼 클릭 시 호출된다.
$('body').on('click', 'a[name=prev]', function(){
us.send('prev');
});
// 볼륨 키우기 버튼 클릭 시 호출된다.
$('body').on('click', 'a[name=vol_up]', function(){
us.send('volume', { volume : 'up' });
});
// 볼륨 낮추기 버튼 클릭 시 호출된다.
$('body').on('click', 'a[name=vol_down]', function(){
us.send('volume', { volume : 'down' });
});
// 목록 반복 활성화 버튼 클릭 시 호출된다.
$('a[name=loop_on]').click(function(){
$('a[name=loop_on]').hide();
$('a[name=loop_off]').show();
us.send('loop', { value : true });
});
// 목록 반복 비 활성화 버튼 클릭 시 호출된다.
$('a[name=loop_off]').click(function(){
$('a[name=loop_off]').hide();
$('a[name=loop_on]').show();
us.send('loop', { value : false });
});
// 현재 재생 반복 활성화 버튼 클릭 시 호출된다
$('a[name=repeat_on]').click(function(){
$('a[name=repeat_on]').hide();
$('a[name=repeat_off]').show();
us.send('repeat', { value : true });
});
// 현재 재생 반복 비 활성화 버튼 클릭 시 호출된다.
$('a[name=repeat_off]').click(function(){
$('a[name=repeat_off]').hide();
$('a[name=repeat_on]').show();
us.send('repeat', { value : false });
});
// 재생 목록을 클릭 시 음악을 재생하고, 재생 화면으로 이동한다.
$('ul[data-role=listview]').on('click','a.music', function(){
var item = $(this).data('item');
$('#search').val(item.title).trigger($.Event( 'keypress',
{ keyCode: 13, which: 13 }));
});
$(document).on('swipeleft','div[data-role=page]', function(){
if( $(".ui-panel").hasClass("ui-panel-open") == true ){
$("[data-role=panel]").panel("close");
} else {
$.mobile.changePage('play.html', { transition: 'slide'});
}
});
$(document).on('swiperight','div[data-role=page]', function(){
var page = $(':mobile-
pagecontainer').pagecontainer('getActivePage')[0].id;
if(page == 'play'){
$.mobile.changePage('index.html', { transition: 'slide',
reverse: true });
} else {
$( "#panel" ).panel( "open" );
}
});
});
[ 검색한 음악을 재생하거나, 최신 가요 목록을 불러와 준다 ]
Play 페이지는 실제 음악 재생을 보여주는 페이지이다. jQuery Knob 플러그인을 이용하여,
중앙의 원형 형태로 재생 진행률을 보여준다. 이전/다음 음악으로 재생하거나, 볼륨을 올리고
낮추는 역할, 음악을 재생하고 정지하는 기본적인 동작 UI 를 구성한다.
[ play.html ]
…
<div data-role='main' class='ui-content'>
<h2>Title</h2>
<input class="knob" data-angleOffset="180" data-
fgColor="rgba(18,255,255,0.5)" data-skin="tron" data-thickness=".2"
data-min="0" data-max="100" value="100" data-displayInput="false"
data-readOnly="true" data-enhance="false"/>
<div class='ui-grid-d'>
<div class='ui-block-a'>
<a href='#' name='vol_up' data-role='button'>+</a>
</div>
<div class='ui-block-b'>
<a href='#' name='vol_down' data-role='button'>-</a>
</div>
<div class='ui-block-c'>
<a href='#' name='resume' style='display:none;'
data-role='button'>▶</a>
<a href='#' name='stop' data-role='button'>■</a>
</div>
<div class='ui-block-d'>
<a href='#' name='prev' data-role='button'>◁</a>
</div>
<div class='ui-block-e'>
<a href='#' name='next' data-role='button'>▷</a>
</div>
</div>
</div>
…
Play 모듈은 음악 재생 정보를 표시해 주는 역할을 담당한다. Info 이벤트가 발생하면, 재생
음악 타이틀 정보를 화면에 표시하고, 앨범 이미지를 백 그라운드에 표시한다.
라즈베리파이로부터 재생 시간 정보를 받게 되면, 중앙의 원형 페이지를 채워가며 진행 상황을
시각적으로 보여준다.
[ play.js ]
// page 라는 id 명의 페이지가 활성화 될 때 호출된다.
$(document).on('pageinit','#play', function(){
// 음악 제목과 배경 이미지 주소를 가져와서 화면에 표시한다.
var img = sessionStorage.getItem('img');
var title = sessionStorage.getItem('title');
$('#play h2').text(title);
$('#play').css('background-image', 'url("' + img + '")');
// 재생 정보를 라즈베리파이로부터 받아오는 경우 표시된다.
us.receive('info', function(data){
sessionStorage.setItem('img', data.img);
sessionStorage.setItem('title',data.title);
$('#play h2').text(data.title);
$('#play').css('background-image', 'url("' + data.img +
'")');
});
// 재생 정보를 표시한다.
us.receive('time', function(data){
$('.knob').val(Math.round(data.current * 100/
data.total)).trigger('change');
$('a[name=resume]').hide();
$('a[name=stop]').show();
});
var width = $(window).width() * 0.92;
console.log(width);
…
});
CSS(Cascadig Style Sheet) 는 보여지는 화면을 다듬기 위해 사용된다. 음악 재생시 타이틀
명이 길면 화면이 넘어가는 문제가 있는데, h2 속성을 지정하여 넘어가는 문자는 “…”으로
보여지도록 표시해 준다. Input 컴포넌트와 음악 검색/재생의 header 와 footer 를 반투명하게
지정하기 위해 rgb 컬러 값과 함께 alpha 값을 지정해 주도록 한다.
여기서 잠깐 !important 란?
CSS 지정 시 !important 속성을 볼 수 있는데, CSS 는 가장 마지막에 정의된 값이 적용되게
된다. 하지만, !important 속성을 지정하면, 나중에 값이 적용되더라도 무시하게 된다.
[ play.css ]
#play h2 {
white-space: nowrap;
overflow: hidden;
text-overflow: ellipsis;
margin-bottom : -5px;
}
#music, #play {
background-size : 100% 100%;
}
#music .ui-input-search {
background-color : rgba(233,233,233,0.3) !important;
}
#play div[data-role=header], #play div[data-role=footer],
#music div[data-role=header], #music div[data-role=footer]{
background-color : rgba(233,233,233,0.3) !important;
border-color : rgba(221,221,221, 0.3) !important;
}
#play .ui-btn, #music .ui-btn {
background-color : rgba(246,246,246,0.5) !important;
border-color : rgba(221,221,221, 0.5) !important;
}
[ header 와 footer 는 알파값 지정으로 인하여 반투명하게 보여지고,긴 문자열은 … 으로
표시되게 된다. ]
Radio 페이지는 라디오 방송과 뉴스/날씨 정보를 확인할 수 있는 기능을 제공하는 페이지 이다.
라디오 목록의 경우, 실제 재생할 페이지의 주소를 name 속성에 포함하고 있어, 클릭 시 이
값을 라즈베리파이에 전달하여 재생할 수 있도록 구성되어 있다. 라디오 방송용으로
추가적으로 듣고 싶은 목록이 있다면, 이곳 페이지에 추가해 주면 된다.
[ radio.html ]
…
<div data-role='main' class='ui-content'>
<div class='ui-grid-b'>
<div class='ui-block-a'>
<a href='#' id='v_up' data-role='button'>+</a>
</div>
<div class='ui-block-b'>
<a href='#' id='v_down' data-role='button'>-</a>
</div>
<div class='ui-block-c'>
<a href='#' id='r_stop' data-role='button'>■</a>
</div>
</div>
<div data-role='fieldcontain'>
<ul data-role='listview'>
<li data-role='list-divider'>News/Weather</li>
<li><a id='weather'>Today Weather</a></li>
<li><a id='news'>Today News</a></li>
<li data-role='list-divider'>Radio</li>
<li><a class='radio'
name='mms://210.105.237.100/mbcam'>MBC FM</a></li>
<li><a class='radio'
name='mms://114.108.140.39/magicfm_live'>SBS POWER FM</a></li>
<li><a class='radio'
name='mms://live.kbs.gscdn.com/world_rki3'>KBS WORLD</a></li>
<li><a class='radio'
name='http://89.16.185.174:8003/stream'>MUSIC Channel</a></li>
…
</ul>
</div>
</div>
…
Radio 모듈은 Radio 페이지에서 클릭 발생 시 이벤트를 라즈베리파이에 전달하는 역할을 한다.
날씨/뉴스와 볼륨 조절이벤트는 곧바로 호출하는 구조를 가지고 있다. 라디오 목록의 경우
name 속성으로부터 재생 링크를 전달 받아 라즈베리파이에 전달하여 재생하는 역할을 하게
된다.
[ radio.js ]
// radio 라는 id 명의 페이지가 활성화 될 때 호출된다.
$(document).on('pageinit','#radio', function(){
// 날씨 버튼 클릭시 라즈베리파이 서버에 weather 이벤트를 전달한다.
$('#weather').click(function(){
us.send('weather');
});
// 뉴스 버튼 클릭시 라즈베리파이 서버에 news 이벤트를 전달한다.
$('#news').click(function(){
us.send('news');
});
// 라디오 정보 버튼을 클릭시 재생할 인터넷 라디오 정보를 전달한다.
$('a.radio').click(function(){
var radio = $(this).attr('name');
var name = $(this).text();
us.send('radio', { radio : radio, name : name});
});
// 정지 버튼 클릭 시 호출된다.
$('#r_stop').click(function(){
us.send('stop');
});
// 볼륨 올리기 버튼 클릭 시 호출된다.
$('#v_up').click(function(){
us.send('volume', { volume : 'up' });
});
// 볼륨 낮추기 버튼 클릭 시 호출된다.
$('#v_down').click(function(){
us.send('volume', { volume : 'down' });
});
});
Talk 페이지는 오디오와 대화를 나눌 수 있는 공간이다. 메시지를 입력할 input 컴포넌트를
배치하고, 메시지가 배치될 영역을 구성하도록 한다.
[ talk.html ]
…
<div data-role='main' class='ui-content'>
<input id='tell' type="text" placeholder='Input message.' />
<div id='msgs'>
</div>
</div>
…
Talk 모듈은 메시지를 실질적으로 전달하고, 받은 메시지를 화면에 출력하는 역할을 한다.
Receive 함수를 이용하여 talk 이벤트가 발생시, 받아진 메시지를 우측 정렬로 화면에 보여준다.
사용자가 대화할 메시지를 입력후 엔터/이동키를 누르게 되면, 사용자가 입력한 메시지를
화면에 왼쪽 정렬로 보여주고, 입력한 메시지를 라즈베리파이에 전달한다. 대화를 주고받는
시간을 표시하기 위해 현재 시간을 표시하는 now 함수를 구현한다.
[ talk.js ]
// talk 라는 id 명의 페이지가 활성화 될 때 호출된다.
$(document).on('pageinit','#talk', function(){
// talk 이벤트를 수신하였을 때 호출된다.
us.receive('talk', function(data){
// 받은 메시지를 html 객체로 생성한다.
var $msg = $('<div/>').css({ width : '80%', float : 'right',
'text-align' : 'right'})
.addClass('ui-corner-all custom-corners');
var $title = $('<div/>').addClass('ui-bar ui-bar-b')
.append($('<h3/>').text('piAu'));
var $body = $('<div/>').addClass('ui-body ui-body-
b').append($('<p/>')
.text(data));
$msg.append($title);
$msg.append($body);
$msg.append($('<p />'));
// 받은 메시지를 출력한다.
$('#msgs').prepend($msg);
});
// 입력 메시지를 화면에 표시하고, 라즈베리파이 서버에 전달한다.
$('#tell').keypress(function(e){
if (e.which != 13) {
return;
}
// 현재 시간을 구한다.
var currentTime = new Date();
var month = currentTime.getMonth() + 1;
var day = currentTime.getDate();
var year = currentTime.getFullYear();
var seconds = currentTime.getSeconds();
var minutes = currentTime.getMinutes();
var hour = currentTime.getHours();
var time = toChar(hour) + ':' + toChar(minutes) + ':' +
toChar(seconds);
var key = year + '-' + toChar(month) + '-' + toChar(day) + '
' + time;
var data = $('#tell').val();
// 입력한 정보를 화면에 표시하기 위한 html 객체를 만든다.
var $msg = $('<div/>').css({ width : '80%', float : 'left',
'text-align' : 'right'})
.addClass('ui-corner-all custom-corners');
var $title = $('<div/>').addClass('ui-bar ui-bar-a')
.append($('<h3/>').text('User'));
var $body = $('<div/>').addClass('ui-body ui-body-
a').append($('<p/>')
.text(data));
$msg.append($title);
$msg.append($body);
$msg.append($('<p />'));
// 입력한 메시지를 화면에 표시한다.
$('#msgs').prepend($msg);
// 입력한 메시지를 라즈베리파이 서버에 전달한다.
us.send('talk', data );
$('#tell').val('');
});
// 현재 시간을 구한다.
var now = function(){
var currentTime = new Date();
var month = currentTime.getMonth() + 1;
var day = currentTime.getDate();
var year = currentTime.getFullYear();
$('#now').text(year + '-' + toChar(month) + '-' +
toChar(day));
refresh();
};
// 두자리 수 미만인 경우 앞에 0을 붙여준다.
var toChar = function(val){
if(val < 10){
return '0' + val;
} else {
return val;
}
}
});
Alarm 페이지는 알람 설정을 하는 페이지이다. 요일을 체크박스로 배치하여, 다중으로 선택할
수 있도록 배치하고, 시간과 분 표시는 0~23, 0~59 까지 일일히 표시하기에는 양이 많다.
select 영역만 지정하고, 실제 표시는 alarm 모듈로 구현해 주도록 한다.
[ alarm.html ]
…
<div data-role='main' class='ui-content'>
<fieldset data-role='controlgroup'>
<legend>Choice day</legend>
<input type='checkbox' name'day' id='cb0' value='0'/>
<label for='cb0'>Sun</label>
<input type='checkbox' name'day' id='cb1' value='1'/>
<label for='cb1'>Mon</label>
<input type='checkbox' name'day' id='cb2' value='2'/>
<label for='cb2'>Tue</label>
<input type='checkbox' name'day' id='cb3' value='3'/>
…
</fieldset>
<div class='ui-grid-a'>
<div class='ui-block-a'>
<label for='alarm_hour'>Hour</label>
<select id='alarm_hour'></select>
</div>
<div class='ui-block-b'>
<label for='alarm_minute'>Minute</label>
<select id='alarm_minute'></select>
</div>
</div>
<a href='#' id='setAlarm' data-role='button'>Confirm</a>
</div>
…
Alarm 모듈은 알람 설정 정보를 가져오고, 설정된 알람 정보를 전달하는 역할을 한다. 기존에
설정된 알람 정보를 가져오기 위해 getAlarm 이벤트로 라즈베리파이에 전달하고, 0~23 까지
시간과 0~59 까지 분을 표시한다. getAlarm 이벤트를 전달 받으면, 받아진 값을 이용하여
체크박스는 선택 상태로 바꾸고, select 메뉴에 시간과 분을 설정해 준다. setAlarm 클릭시에는
선택된 체크박스로부터 날짜를 가져오고, 시간과 분 정보를 가져와서 라즈베리파이에 setAlarm
함수로 값을 전달한다.
[ alarm.js ]
// alarm 이라는 id 명의 페이지가 활성화 될 때 호출된다.
$(document).on('pageinit','#alarm', function(){
// 알람 정보를 라즈베리파이 서버에 요청한다.
us.send('getAlarm');
// 시간, 분 정보를 초기화 한다.
$('#alarm_hour').empty();
$('#alarm_minute').empty();
// 시간, 분 정보를 화면에 표시한다.
for(var i = 0 ; i < 24 ; i++){
$('#alarm_hour').append($('<option>').val(i).text(i));
}
for(var i = 0 ; i < 60 ; i++){
$('#alarm_minute').append($('<option>').val(i).text(i));
}
// 설정한 알람 정보를 라즈베리파이 서버로부터 받았을 때 호출된다.
us.receive('getAlarm', function(data){
var alarm = JSON.parse(data.result);
console.log(alarm);
// 설정된 알람정보와 일치하는 날짜가 있다면 화면에 표시해 준다.
for(var i = 0; i < alarm.days.length ;i++){
var day = alarm.days[i];
$('#cb' + day).prop('checked',
true).checkboxradio('refresh');
}
// 설정된 알람정보와 일치하는 시간과 분을 표시해 준다.
$('#alarm_hour').val(alarm.hour).selectmenu('refresh');;
$('#alarm_minute').val(alarm.minute).selectmenu('refresh');
});
// 알람 설정 버튼 클릭 시 호출된다.
$('#setAlarm').click(function(){
var days = [];
// 선택된 요일 정보를 가져온다.
$.when($("input[type=checkbox]:checked").each(function() {
// 선택한 요일 정보를 days 배열에 보관한다.
days.push($(this).val());
})).then(function(){
// 선택한 요일과 시/분을 data 객체로 생성한다.
var data = {
days : days,
hour : $('#alarm_hour').val(),
minute : $('#alarm_minute').val()
};
// 선택한 알람 설정 정보를 라즈베리파이 서버에 전달한다.
us.send('setAlarm', { param : JSON.stringify(data)});
});
});
});
[오디오, 뉴스/날씨 정보를 듣거나 오디오와 대화를 나누는 기능을 제공한다 ]
Message 페이지는 메시지를 입력하여 오디오 상에서 TTS 로 읽어줄 수 있도록 텍스트
입력창을 제공하는 페이지이다. Textarea 컴포넌트로 사용자가 문자를 입력할 공간을 배치하고,
메시지가 전달될 수 있도록 send 버튼을 배치한다.
[ message.html ]
…
<div data-role='main' class='ui-content'>
<textarea id='msg' rows='5' data-autogrow='false'
placeholder='Input message'></textarea>
<a href='#' id='send' data-role='button'>Send</a>
</div>
….
Message 모듈은 메시지를 실제 오디오로 전달하여, TTS 로 읽어질 수 있도록 한다. Send
버튼을 클릭하면, textarea 에 입력된 메시지를 전달한다.
[ message.js ]
// message 라는 id 명의 페이지가 활성화 될 때 호출된다.
$(document).on('pageinit','#message', function(){
// send 버튼 클릭 시 호출된다.
$('#send').click(function(){
// 입력한 메시지를 라즈베리파이 서버에 전달한다.
us.send('message', $('#msg').val());
// 전달 된 메시지는 화면에서 지운다.
$('#msg').val('');
});
});
Option 페이지는 라즈베리파이 오디오의 연결 설정 및 밝기를 설정하는 기능을 제공하는
페이지이다. 7 Segment 와 LED 밝기를 조절할 수 있도록 두개의 range 타입의 input
컴포넌트를 배치한다. 시리얼 번호를 입력하는 용도로 input 컴포넌트를 배치하고, 업데이트 및
재부팅이 가능하도록 update 버튼을 배치한다.
[ option.html ]
…
<div data-role='main' class='ui-content'>
<label for='fg_brt'>Foreground Brightness</label>
<input id='fg_brt' type="range" min="0" max="15" value="16" />
<label for='bg_brt'>Background Brightness</label>
<input id='bg_brt' type="range" min="0" max="240" value="240" />
<label for='serial'>Serial</label>
<input id='serial' type="text" />
<label for='update'>piAu Software Update</label>
<a href='#' id='update' data-role='button'>Update piAu
(Reboot)</a>
</div>
…
Option 모듈은 실제로 밝기 설정한 값을 전달하고, 시리얼 포트 정보를 설정하는 기능을
제공한다. Ragen 입력 바를 사용자가 변경하면 변경시에 발생하는 값을 라즈베리파이
오디오에 전달하여 7 세그먼트와 LED 의 밝기를 조절하게 된다. 시리얼 번호 입력창을
클릭하면, 사용자가 시리얼 번호를 변경할 수 있도록 입력창이 보여진다. 입력받은 시리얼
번호로 Circulus 플랫폼에 다시 접속한다.
[ option.js ]
// option 이라는 id 명의 페이지가 활성화 될 때 호출된다.
$(document).on('pageinit','#option', function(){
// 전경 밝기 슬라이더 변경 시 호출된다.
$('#fg_brt').change(function(){
us.send('setFg',{ fg : $(this).val()});
});
// 배경 밝기 슬라이더 변경 시 호출된다.
$('#bg_brt').change(function(){
us.send('setBg',{ bg : $(this).val()});
});
// 업데이트 버튼 클릭 시 호출된다.
$('#update').click(function(){
us.send('update');
});
// 설정된 라즈베리파이 일련번호를 가져온다.
var serial = localStorage.getItem('serial');
// 설정된 라즈베리파이 일련번호가 없다면, 입력창으로 입력 받는다.
if(serial == null || serial == 'null'){
serial = prompt("Input piAu's serial");
localStorage.setItem('serial', serial);
us.init(serial);
}
// 저장된 일련번호를 화면에 표시해 준다.
$('#serial').val(serial);
// 일련번호 입력창 클릭시 다시 일련번호를 입력받아 접속을 시도한다.
$('#serial').click(function(){
serial = prompt("Input piAu's serial");
$('#serial').val(serial);
localStorage.setItem('serial', serial);
us.init(serial);
});
});
[ 메시지를 보내면 오디오가 TTS 로 읽어주게 된다. 상황에 따라 전경/배경 색상의 밝기를
조절할 수 있다. ]
라즈베리파이 IoT 오디오 완성
Circulus 를 이용하여, 라즈베리파이와 모바일 웹 어플리케이션을 개발하였다. 별도의 클라우드
서비스나 복잡한 IoT 기술을 사용하지 않고, Circulus 에서 개발한 모바일 웹의 URL 로 언제
어디서나 사용자가 만든 라즈베리파이 오디오에 접근하여 사용할 수 있다. 라즈베리파이 IoT
오디오를 무선 인터넷이 되는 곳에 배치하고, 실제로 집 밖에서 제대로 동작이 되는지 실험해
보도록 하자. 만일 사용자가 사랑하는 연인 혹은 가족, 또는 고마운 친구가 있다면, 직접 만든
라즈베리파이 IoT 오디오를 선물하자. 본인도 가끔 음악 선물이나 메시지를 보내 보는 것도
좋을 것이다.
[ 라즈베리파이 IoT 오디오 완성! 실제로 집에 두고 사용해 보도록 하자. ]
10/마무리
마지막 장을 마무리하며
라즈베리파이를 이용하여 IoT 오디오를 완성하는 것으로, 이 책의 과정이 마무리 되었다.
하드웨어와는 친숙하지 않은 자바스크립트와 운영체제가 탑재되는 라즈베리파이의 조합으로
우리는 스마트 폰으로 LED 제어부터 오디오까지 만들어 볼 수 있었다. 단순히 LED 를 껐다
켜는 수준이 아닌 실제로 집에서 사용할 수 있는 IoT 아이디어를 구현하기에 다루는 범위가
방대한 반면, 각각의 내용에 대해서 모든 내용을 다루지 못하였다. 이 책을 기반으로 자신의
아이디어를 실제로 구현해 보면서, 부족한 부분이나 모르는 부분은 관련 내용을 보충해야 할
것이다. 지속적으로 관련된 내용을 Circulus 를 통해 공유할 수 있도록 하겠다.
이제는 여러분도 Thinker 가 아닌 Maker 가 되었다. 앞으로는 타인이 공유한 다양한
프로젝트를 살펴보고, 자신만의 독창적인 아이디어를 하드웨어, 소프트웨어, 서비스 구분 없이
구현해 보는 것이다. 장담하건데, 여러분이 생각하는 비슷한 아이디어는 많이 있겠지만, 똑
같은 아이디어는 전 세계에 어디에도 없을 것이다. 생각하는게 없다? 그렇다면 만들고
공유하는 것이다. 메이커들이 공유하는 메이커 페어나 다양한 행사들에서 뵙길 기대하겠다.
CIRCULUS
Circulus 는 2013 년 Thinker to Maker, 즉 누구나 아이디어를 현실로 만들 수 있게 하자는
목적의 Social Code Learning 기반 커뮤니티로 시작되었다. 하지만, S/W 적인 부분만으로는
한계[가 있어, 물리적인 H/W 도 S/W 를 통해 만들 수 있게 하면 좋겠다라는 생각을 했고, 그
생각의 종착점은 로봇이라는 생각이 들었다. 2016 년 알파고 이슈와 더불어 가정용 반려
로봇으로 창업을 한 스타트 업이다. 앞으로도 초심을 잃지 않고, 누구나 로봇을 활용하고
자신의 아이디어를 현실로 만들고 공유할 수 있는 세상을 만들고자 한다. 의문사항은 아래의
커뮤니티에 남겨주면, 피드백을 줄수 있도록 하겠다.
공식 사이트(개발툴) - http://www.circul.us
관련 콘텐츠 – http://contens.circul.us
관련 영상 – http://video.circul.us
Facebook – http://group.circul.us
Café – http://cafe.circul.us
Shop – http://shop.circul.us
메일 – circulus@circul.us
라즈베리파이와자바스크립트로만드는 IoT

라즈베리파이와자바스크립트로만드는 IoT

  • 1.
    ____________________________________________________ 라즈베리파이와 자바스크립트로 시작하는IoT 원격 제어 LED 부터 소셜 오디오까지 __________________________________________________________
  • 2.
    발간사 「라즈베리파이와 자바스크립트로 만드는IoT」 한국과학창의재단 이사장님 발간사 2016 년은 그 어느 해보다 미래 사회의 변화를 체감하게 된 해로 기록될 것 같다. 1 월에 다보스포럼 주제는 ‘4 차 산업혁명의 이해’로, 정보통신기술 융합을 통해 모든 것이 연결되고 보다 지능화된 사회로의 변화가 눈앞에 와 있음을 강조했다. 그리고 3 월에는 우리 국민들에게 충격을 안겨 준 이세돌 9 단과 구글의 인공지능 ‘알파고’의 바둑 대국이 있었다. 바둑의 신이라 불리우는 이세돌 9 단이 컴퓨터에게 1:4 로 패배하는 장면을 전 세계가 목도했고, 이는 사회적으로 큰 반향을 불러 일으켰다. 다보스발 ‘제 4 차 산업혁명’과 서울발 ‘알파고 쇼크’로 시작된 우리 사회의 변화가 주는 의미는 무엇일까? 1940 년대 첫 다용도 디지털 컴퓨터 개발, 1970 년 초 개인용 컴퓨터 개발과 함께 컴퓨터는 우리 생활에서 없어서는 안 되는 필수품으로 자리 잡게 되었고, 산업분야는 2 차 산업혁명(대량생산)에서 컴퓨터를 통한 자동화인 3 차 산업혁명으로 발전하게 되었다. 3 차 산업혁명까지 우리가 알고 있던 컴퓨터는 인간의 지시에 의해 기능을 수행하는 편리한 도구였다면 4 차 산업혁명을 맞이하는 시점에서 알파고는 이러한 기존의 가치관을 완전히 파괴했다. 알파고는 이길 가능성이 있는 전략 여러 개를 컴퓨터가 스스로 분석해 성공 전략을 알아내는 기계학습방식이라는 참신하고 도전적인 접근방식을 선택한 개발자들에 의해 큰 성공을 거두었다. 이렇듯 4 차 산업혁명의 특징은 제품과 제조과정, 시스템의 지능화를 통해 기업이 고객의 특성에 맞는 맞춤형 제품을 제공하는 것이다. 그러나 우리가 주목해야할 사실은 4 차 산업혁명과 알파고도 결국 하사비스 박사와 같은 우수한 개발자에 의해 이루어졌다는 것이다. 이러한 맥락에서 프로그래밍, 디지털 사물과 밀접하게 대화하는 언어의 중요성은 그 어떠한 때보다 중요하다고 할 수 있다. 중세 시대 라틴어로 쓰인 성서를 읽을 줄 알았던 성직자가 그 시대를 이끌어 나갔듯이, 프로그래밍 언어로 자신의 아이디어를 현실로 만들어내는 프로그래머들이 제 4 차 산업혁명을 리드해 나가고 있다고 해도 과언이 아니다. 미국이나 영국 등에서는 프로그래밍을 영어, 중국어에 이어 제 3 의 언어로 생각하여, 이미 코딩 관련 소프트웨어 수업 등을 정규 과목으로 편성했다. 프로그래밍 언어를 습득하여 활용해 내는 것이 미래 인재로서의 핵심역량인 세상이 다가오고 있는 것이다. 디지털 환경을 태어나면서부터 생활처럼 사용하는 세대가 늘어나고 있는 만큼, 디지털 네이티브들의 프로그래밍 능력은 기존의 자작 문화, 즉 DIY(Do It Yourself)와 결합되어 메이커 운동(Maker Movement)라는 새로운 시대적 흐름을 만들어 내고 있다. 대규모 기업이나 공장 생산라인의 도움 없이도 자신이 생각하는 아이디어를 시제품으로 생산해내는 사람들이 생겨나고 있는 것이다. 이러한 메이커들이 탄생하게 된 계기에는 오픈소스 하드웨어와 소프트웨어의 공이 크다. 기존에는 관련
  • 3.
    전공자들만 활용 가능했던정보들이 공유되고 확산되면서 비전공자들도 시제품 제작에 쉽게 접근하여 자신의 아이디어를 물리적으로 구현해 내는 시대가 된 것이다. 모든 산업의 한계 생산 비용이 제로 수준으로 떨어지는 한계비용 제로 사회의 도래와 크라우드펀딩법 통과로 인해 국내에서도 메이커들이 자신이 만든 아이디어 시제품을 판매하고 사업화 할 수 있는 환경이 조성되고 있다. 과거에는 천편일률적인 제품만을 생산하고 사용했다면, 이제는 다품종 소량생산 체제로 더 넓은 시장을 충족하는 다양한 제품과 서비스를 만들어 낼 뿐 아니라 전 세계의 사용자로부터 평가 받고, 투자 받는 것이 가능해졌다. 페블이라는 스마트 워치 회사가 킥스타터에서 200 억 넘는 모금을 유치하여 사업화에 성공한 것이 그 대표적인 사례라고 할 수 있다. 이러한 시대적 흐름에 맞추어 한국과학창의재단은 아이디어와 상상력을 현실화하는 메이커 문화 확산에 힘쓰며 누구나 자신만의 창의적인 아이디어를 구현 할 수 있도록 다양한 사업을 통해 메이커 활동을 지원하고 있다. 실제로 이 책의 저자가 소속되어 있는 Circulus 팀은 재단이 추진한 ‘2015 우수메이커 활동 지원 사업’을 통해 개인용 지능형 로봇을 만들어 내었고, ‘2015 창조경제박람회’와 함께 개최된 ‘메이커 페스티벌’을 통해 그 성과를 세상에 알렸다. 또한 2015 대한민국과학창작대전을 통해 Circulus 팀은 소셜 오디오를 제작하였으며, 실제 이 제품은 크라우드펀딩을 통해 제품화 되었다. 메이커 학습 및 제작이 가능한 플랫폼(Circulus)을 구축하여 교육활동도 펼치고 있는 저자가 소셜 오디오의 성공 사례를 기반으로, 누구나 메이커가 될 수 있도록 그 제작과정을 오픈소스로 공유하여 책으로 접할 수 있도록 했다니 기쁜 일이다. 미국에서는 인구의 57%인 1 억 3500 만명이 스스로 메이커라고 생각한다는 조사 결과가 있다. 페블의 대표인 에릭 미기코브스키 뿐 아니라 애플의 스티브잡스와 워즈니악 또한 차고지에서 만들고 싶은 것을 만들던 메이커였다. 우리나라에서도 잠재력을 가진 개개인들이 다양한 아이디어를 시제품으로 구현해 내고 사업화 할 수 있는 환경이 더욱더 활성화되길 바라며, 이 책이 많은 사람들이 메이커 활동에 관심을 갖고 참여하는데 길잡이 역할이 되길 바란다.
  • 4.
    목차 0. 시작하기 앞서4p 1. 파이를 동작시켜 보자 - 초기설정 17p 2. 파이의 운영체제 - Linux 속성실습 33p 3. JavaScript 로 하드웨어 제어를 - Node.JS 57p 4. 거리 측정하고 정보 표시 하기 - GPIO 81p 5. 스마트폰으로 리모콘을 - jQueryMobile 119p 6. 인터넷으로 음악과 날씨를 - OpenAPI & RSS 137p 7. 오디오 소프트웨어 개발하기 153p 8. 외관을 생각대로 만들기 189p 9. 언제 어디서나 동작하는 IoT- Circulus 192p 10. 마무리 256p
  • 5.
    0/시작하기 앞서 이 책의집필 목적 생각만 있는 Thinker 에서 세상에 의미 있는 변화를 이끌고 직접 행동하는 Maker 가 되고자 하는 여러분들을 환영한다. 지금까지 우리는 전자 제품을 만든다고 하면, 구하기 힘든 장비, 비싼 가격, 납땜과 복잡한 프로그래밍 등 전문가만 할 수 있다는 인식을 가지고 있다. 한번 실수하면 큰 돈이 날라 가니 매우 조심스럽게 사용해야 했었다. 하지만 지금 이 순간 이런 비싼 장비들은 더 강력하면서 저렴한 가격으로 부품들을 구입할 수 있다. 더 쉽게 조립하고 손쉬운 프로그래밍으로 우리가 만들고자 하는 것을 마법처럼 눈앞에 보여 지도록 할 수 있는 시대를 살고 있다. 우리가 하고자 하는 것은 생각만 하는 것이 아닌 실제로 만들어 보는 것이며, 나 혼자만의 즐거움이 아닌 타인과 함께 공유하고 나눌 수 있는 멋진 제품이다. 더 이상 공장에서 붕어빵처럼 찍어 내고, 내가 원하는 기능이 없거나 필요 없는 기능에 불평하지 않아도 된다. 원하지 않는 디자인에 컬러의 제품을 돈을 주고 구입하는 시대는 끝났다. 직접 여러분의 손으로 직접 여러분이 실생활에 사용할 수 있는 제품을 만들 수 있도록 하고자 한다. 여자 친구를 위한 IoT 장비 처음 IoT 오디오를 만들게 된 계기는 필자의 경험을 통해서 나오게 된 것이다. 필자가 IT 업을 시작하고 나서 5 년간 지방과 해외 프로젝트로 떠돌았었다. 이 시기에는 불안정한 생활을 하다보니 여자친구를 만나도 떠도는 생활로 인하여 헤어지게 되는 경우가 많았는데, 지방이나 해외에 있어도 여자친구 곁에 있는 것 처럼 할 수 있는 방법은 없을까? 하여 처음 제작을 시도하게 되었다. 여자친구 곁에 IoT 오디오를 두어, 지방이나 해외에 있어도 음악 선물이나 음성 메시지, 알람 서비스를 보낼 수 있는 장치를 기획하였다. 삼성 그룹 해커톤에서 만든 초기 IoT 오디오, Flying Music 2014 년 삼성 그룹의 해커톤에 나가서 실제로 이 오디오를 만들어 냈다. 비슷한 경험을 가진 사람이 많아 좋은 호응을 얻을 수 있었다. 실제 효과도 있었다. 캐롤 송이 나오는 크리스마스 트리를 만들어 선물하여 사귀게 된 여자 친구. 거리가 떨어져 있어서, 실제로 오디오를 만들어
  • 6.
    여자 친구 집에두어서 활용하고 있다. 당신도 직접 만들어서 당신의 친구들과 가족들에게 선물해 볼 수 있다! 세상에 단 하나의 내가 만드는 오디오 [ 레이저 커터로 만든 초기 제작 모델 ] 지금까지 우리가 알고 있는 오디오는 기본적으로 사용자가 지정해 주는 파일이나 CD 등을 재생하는 장치였다. 우리는 기존의 오디오에서 할 수 없었던 사람과 사람을 이어 주는 감성적인 IoT 오디오를 만들어 볼 것이다. 물론 돈을 투자해서 파는 제품을 사면 어때 라는 생각을 할 수도 있다. 하지만 그 어떠한 제품에서도 제공해 줄 수 없는, 붕어빵 같은 전자 제품이나 기계가 아닌 세상에 단 하나의 오디오가 탄생할 것이다. 바쁜 당신을 기다리는 여자 친구를 감동 시킬 수 있는 IoT 오디오. 우리는 이 오디오에 다양한 하드웨어 및 소프트웨어 기술을 이용하여 우리가 지금까지 알고 있는 단순히 음악이 재생되는 장치에서 한 단계 수준을 끌어올리고자 한다. 기본 모델 - 탁상 시계 기능 - 스마트 폰 제어 - 음악 검색 / 다운로드 및 재생 - 시간 알람 기능 - 최신 가요 목록 확인 및 검색
  • 7.
    우리가 하고자 하는기본 모델만 있더라도 당신이 어디에 있던지 당신의 스마트 폰으로 여자 친구 집에 있는 오디오의 선곡을 통해, 음악을 선물할 수도 있다. 게다가 손 편지 대신에 당신의 마음이 담긴 메시지를 보내고 재생시킬 수 있다. 확장 모델 - 실 외에서의 원격 제어 - 헤드라인 뉴스 읽어 주기 - 오늘의 날씨 읽어 주기 - 메세지 전달 / 읽어 주기 - 현재 방안의 온 습도 알려주기 기본적인 기능을 만드는데 성공하였다면, 이 오디오에 좀더 재미난 기능들을 추가하고자 한다. 일단 인기가요 TOP10 을 곧바로 재생할 수 있는 기능을 제공한다. 알람 기능을 설정하여 설정한 음악을 재생할 수 있고, 오늘의 날씨, 주요 뉴스도 오디오로 제공받을 수 있도록 한다. 즉 아침 일어나는 시간에 알람과 동시에 오늘의 주요 정보를 들으면서 일어날 수 있는 것이다. 모바일 음성 인식을 통한 명령도 지원하여 편의성을 돕도록 한다. 이 책의 대상 독자 무언가 직접 만들어 보고, 만든 것이 나와 타인에게 도움을 주고자 하는 그 누구도 환영한다. 어떤 일이든 가장 중요한 것은 어떤 일이든 하고자 하는 의지와 뜻이라는 걸 우리는 잘 알고 있다. 우리는 이 책의 구성을 부품을 사고, 단계 별로 따라서 조립하고 화면에 코드를 입력하는 것으로 완성할 수 있도록 책을 구성하였다. 하지만, 여기서 하고자 하는 내용에 대해 기본 지식을 가지고 있다면 좀더 이해하면서 학습하는데 도움이 될 것이다. 다시 한번 말하지만, 당신의 의지가 있다면 아래 사항을 모른다고 해도 만들 수 있다! 바쁜 시간에 쫓기는 직장인 바쁜 시간에 쫓겨, 잦은 출장으로 여자 친구를 볼 수 없는 직장인 분들, 어린 시절을 떠올리며 무언가 만들어 보고 싶었지만 어디부터 시작해야 할지 모르는 분들이다. Making 에 대한 기초 지식을 빠르게 학습하고, 직접 오디오를 만들어 볼 수 있다. 무엇보다 이 오디오를 여자 친구에게 만들어 주어 바쁜 시간 속에서도 IoT 오디오를 통해 당신과 이어 주는 역할을 할 것이다. Making 에 관심 있는 대학생 Arduino 의 대중화에 따른 오픈 소스 하드웨어에 대한 인식의 확산으로 당신의 주변에는 이미 이러한 것들로 이것저것 만들어 보는 친구들이 많을 것이다. 당신이 대학생이라면, 직접 하드웨어 기반 Making 을 해 봄으로서, 소프트웨어와 하드웨어 그리고 서비스가 접목되는 방식을 이해하고 스타트 업이나 다양한 공모전에 당신만의 서비스와 아이디어를 이용하여 도전해 볼 수 있을 것이다.
  • 8.
    컴퓨터 언어의 시대에아이를 위한 학부모 2 년전 까지만 하더라도 프로그래밍 교육을 누구나 배우게 될 것이다 라고 하면 말도 안 되는 소리로 취급 받았으나, 이제는 누구나 배우는 시대가 되었다. 이러한 시대를 살아가는 아이들을 위해 본인이 직접 배우고 만듦으로써, 만든 제품을 아이와 함께 활용하고, 구성 원리를 알려 주고 더 나아가서는 아이와 함께 만들어 보는 과정 속에 컴퓨터의 원리를 파악 할 수 있도록 한다. 책에서 다룰 내용 라즈베리파이 오픈소스 하드웨어 하면 아두이노를 흔히 떠오르게 된다. 또한 우리가 선택할 수 있는 오픈소스 하드웨어가 다양하게 있다. 하지만 우리는 라즈베리파이를 이용하여 프로젝트를 진행하고자 한다. 라즈베리파이는 하드웨어를 제어할 수 있는 초 소형/초 저가의 컴퓨터이다. 우리는 이 먹음직스러운 파이로 실 생활에 도움이 되는 것을 만들어 볼 것이다. Linux 여러분이 사용하는 컴퓨터가 윈도우로 돌아가듯이, 라즈베리파이 또한 이러한 운영체제로 동작이 되며, 가장 많이 활용되는 것은 ‘라즈비안’ 이라고 불리우는 라즈베리파이를 위한 리눅스이다. 리눅스에 대해 A to Z 까지 다루는 것은 이 책의 범주를 벗어나는 것으로, 우리는 IoT 오디오 제작에 필요한 주요 리눅스 명령어와 관리 방법에 대해 다 JavaScript 우리는 이 오디오 프로젝트를 위하여 JavaScript 라는 언어를 사용할 것이다. 프로그래밍을 접해 본 경험이 있다면, 웹 페이지에서 Alert 창 띄우는 용도로나 쓰는 게 아닐까 하는 생각을 할 수 있다. 놀라지 마시라, 우리는 이 Web 을 위해 태어난 JavaScript 를 이용하여 모바일 웹은 물론 라즈베리파이를 우리 마음대로 요리할 수 있도록 도와 줄 것이다, 당신이 이 언어를 모른다고 해도, 핀란드어를 따라 친다는 심정으로 하면 마법처럼 화면에 보이고 당신이 조립한 장치가 동작이 될 것이다! 하지만 어느 정도 알고 있다면 더 큰 도움이 된다. NodeJS 라즈베리파이 오디오를 동작시키는 리모콘을 위해서도 JavaScript 를 이용하지만, 라즈베리파이에 탑재되는 하드웨어 제어 및 서버를 위해서도 JavaScript 가 활용된다. 이를 서버에서 가능케 하는 것이 NodeJS 이고, 이것에 대해서 간단하게 다루어 본다. 우리는 NodeJS 를 활용하여 라즈베리파이에 웹 서버를 구축하고, 연결되는 센서와 하드웨어를 제어해 볼 것이다. jQueryMobile
  • 9.
    모바일 컨트롤러를 적은노력으로 멋지게 만들 수 있는 jQueryMobile 프레임워크를 이용할 것이다. JavaScript 에서 가장 인기 있는 jQuery 기반의 모바일 프레임 워크로, 라즈베리파이와 유기적으로 연결된 모바일 웹을 만들게 될 것이다. Hardware Control 라즈베리파이에 LCD 를 연결하여 문자를 출력하고, 온습도 센서를 이용하여 현재 실내의 온습도 상황을 점검해 볼 것이며, 초음파 센서로 거리를 측정해 본다. OpenAPI 오디오 기능을 위해 음악 클라우드 서비스인 SoundCloud 의 개발자용 Open API 를 활용하여 원하는 음악을 검색하고, 검색 결과로부터 음원 다운로드를 하는 것을 실습해 볼 것이다. SoundCloud 가입 및 서비스 등록, JavaScript 용 API 활용까지 살펴 봄으로서, 사용자가 타 OpenAPI 연동 시에도 용이하게 할 수 있도록 한다. 부록 : 3D Printing 보기 좋은 음식이 맛이 있어 보이는 것과 같이, 우리가 만드는 하드웨어 역시 그러하다. 3D 모델링이라는 분야가 전문 분야 였지만, 쉽게 3D 모델링을 할 수 있는 프로그램인 123D Design 을 활용하여 간단하게 모델링을 배우고, 프린팅 하는 법을 알아볼 것이다. 메이커 세계로의 입문 이 책을 통해 여러분은 메이커의 세계로 입문할 수 있을 것이다. 메이커란 운동화 메이커의 메이커가 아닌, 자신의 생각을 직접 만들어 내는 사람들을 의미한다. 흔히 이전의 DIY(Do It Yourself) 에 IT 기술적인 부분이 결합된 것을 메이커라 이야기 하는 경우가 많다. 오바마 대통령이 백악관에서 메이커 행사를 열 만큼, 앞으로의 4 차 산업 혁명 시대에 중요한 역할을 담당하고 있는 문화가 메이커 문화이다. 여러분들도 이 책으로 말미 암아 여러분 만의 메이커 작품으로 , 메이커 페어에서 만나 보길 희망한다.
  • 10.
    준비 물품 우리가 만들고자하는 오디오를 위해 사용되는 재료들은 다음과 같다. 제작을 위한 필수 재료와 옵션이 존재하는데, 모니터 케이블이나 USB 키보드의 경우 이미, 그러한 장치가 있다면 구입하지 않아도 된다. 필수 구입 물품 라즈베리파이 라즈베리파이 오디오를 만들기 위한 핵심 Single Board 컴퓨터. 만일 당신이 자금의 여유가 있다면, 성능이 우월한 라즈베리파이 3 를 구입해도 된다. 하지만, 3 의 경우 전기소모가 많아 일반 2A 출력의 USB 어댑터로 제대로 동작할 수 없으므로, 좀더 저렴하면서도 쿼드코어로 충분한 성능을 제공하는 라즈베리파이 2 를 구입하는 것도 방법이다. 무선랜 유선 연결로만 라즈베리파이를 사용한다면, 필요하지 않지만, 어디에서나 사용할 수 있는 오디오를 위해서는 반드시 필요하다. 현재 가장 많이 사용되는 것은 N 스펙이다. 하지만 보다 빠르고 안정적인 속도를 위하여 N+ 스펙의 무선랜을 구입할 수도 있다. 만일, 라즈베리파이 3 를 보유하고 있다면, 이미 내장되어 있으므로 별도로 구매할 필요는 없다. 16GB MicroSD 카드
  • 11.
    라즈베리파이의 저장장치로써 Linux운영체제를 탑재하고, 각종 추가 Software 를 설치하기 위해서는 최소 8GB 이상의 용량을 추천한다. MicroSD 카드는 다양한 스펙이 존재하는데, Class 간에 가격의 차이가 크지 않다. 컴퓨터를 쾌적하게 쓰기 위해 하드디스크 대신 SSD 를 쓰듯이, 이왕 MicroSD 카드를 구입할 때 Class 10 이나 UHS-I 스펙을 사길 권장 한다. 종류 최소 속도 용도 Class 2 2 MB/s SD 영상 기록 Class 4 4 MB/s HD 영상 기록 Class 6 6 MB/s Full HD (1920x1080) 영상 기록 Class 10 10 MB/s 연속적인 HD 이미지 기록 UHS I 10 MB/s 실시간 방송 UHS III 30 MB/s Ultra HD 영상 기록 USB 스피커 실질적으로 음악소리를 듣기 위해서 필요한 장치이다. 일반 PC 용 스피커를 사용하면 무방하다. 물론 자체 전원을 사용하는 스피커를 이용할 수 있지만, USB 스피커를 이용하면, 라즈베리파이의 4 개의 USB 포트 중 하나를 전원 공급 용도로 활용하여 꽂아야 할 플러그를 줄여 구성을 간소화 할 수 있다.
  • 12.
    DHT11 (온습도 센서) 온습도상황을 모니터링할 수 있는 쉽게 구할 수 있는 센서이다. 이 센서는 DHT11 과 DHT22 가 존재하는데, DHT22 센서가 값은 비싸지만 좀더 정밀한 온도와 습도 결과값을 보여준다. 실외나 정밀한 측정이 필요한 경우가 아니라면 DHT11 센서로도 충분한 측정 범위 값을 제공해 준다. 모델명 용도 온도 측정 범위 습도 측정 범위 DHT-11 실내용 0 ~ 50 도 20 ~ 80 % DHT-22 실외용 -40 ~ 80 도 0 ~ 100 % SR-04 (초음파 센서) 초음파가 발생되고 반사되는 시간을 계산하여 물체와 떨어져 있는 거리를 측정하는데 사용하는 센서이다.
  • 13.
    7 Segment 4Digit LCD 4 개의 숫자를 표시해 줄 수 있는 LCD 이다. 한 숫자를 7 개의 막대로 표현하여 7 Segment 라고 불리운다. 원칙적으로는 각각의 막대를 제어해야 하므로 많은 선이 필요로 하나, SPI 인터페이스를 위해 MAX7219 칩셋이 장착된 제품의 경우 5 개의 선으로 제어할 수 있다. 필히 SPI 인터페이스를 지원하는 제품을 구매하도록 하자. 점퍼 케이블 (Male-Male, Female-Male) 라즈베리파이와 브레드 보드, 센서와 부속을 연결할 때 사용되는 케이블이다. 브레드 보드에 꽂을 수 있도록 핀이 나온 것을 수컷(Male) 단자라고 하고, 핀이 있는 것을 연결할 수 있는 점퍼 케이블은 암컷(Female) 단자라고 한다. 라즈베리파이와 보드 연결, 부속 연결을 위해서는 암컷- 수컷(Female-Male) 단자와 수컷-수컷 (Male-Male) 단자가 필요하다.
  • 14.
    선택 사항 DVI-to-HDMI 혹은HDMI-to-HDMI (Option) 여러분의 라즈베리파이에 직접 모니터를 연결하여 사용하고자 할 때 필요한 케이블이다 라즈베리파이의 영상 출력단자는 기본적으로 HDMI 만 제공한다. 따라서 여러분이 가지고 있는 모니터의 단자가 DVI 를 지원하는지, HDMI 를 지원하는지에 따라서 필요한 케이블을 준비한다 마우스/키보드 세트(Option) 이 역시 라즈베리파이에 직접 연결하여 사용하고자 할 때 준비가 되어 있어야 한다. 윈도우와
  • 15.
    같은 GUI 환경을이용한다면 마우스가 필수적이지만, 일반 텍스트 Console 환경을 이용한다면, 키보드만으로도 충분하다. 우리가 실제 개발할 오디오는 원격으로 접속하여 사용하므로, 꼭 필요하지는 않다 구입처 IoT 오디오 프로젝트를 진행하기 위한 부품을 구입할 수 있는 방법은 다양하다. 사후 지원을 고려하고, 빠른 배송을 통해서 즉시 만들어서 사용을 해 보고자 한다면, 국내 판매 사로부터 구입할 수 있다. 하지만 배송 지연에 대해 느긋하고 사후 지원을 크게 고려하고 있지 않는 상황이라면 해외 직접 구매를 통하여 구입비를 대폭 낮출 수 있다. (다만 해외 배송은 경우에 따라서 1 달 넘게 소요될 수도 있다) 구입할 물품의 명을 정확하게 안다면, 국내는 네이버 쇼핑(http://shopping.naver.com), 해외는 알리 익스프레스(http://www.aliexpress.com)를 통해 검색해 보자. 일반 온라인 판매처 보다 더 저렴하게 구입할 수 있을 것이다. 정확한 명칭을 모른다면 전문 몰 사이트에 접속하여 다양한 부품들을 확인해 본 후 최저가를 산정하여 구입할 수 있다. 국내 온라인 구입처 디바이스 마트 http://www.devicemart.co.kr/ IC 뱅크 http://www.icbanq.com 엘레파츠 http://eleparts.co.kr/ 해외 온라인 구입처 엘레먼츠 14 http://www.element14.com RS 컴포넌트 http://www.rs-components.com 알리 익스프레스 http://www.aliexpress.com 연락 및 추가 정보 얻기 Making 및 프로그래밍 전파를 목적으로 Circulus 라는 팀을 결성하여 활동하고 있다. 이 책에서 나오는 내용에 대한 의문 점과 기타 Making 활동에 대한 문의 사항이 있다면, 다음의 페이스
  • 16.
    북 그룹에 글을남겨 주면 신속하게 답변을 줄 수 있도록 하겠다. 또한 Making 이나 프로그래밍과 관련된 기사들을 Facebook 페이지를 통해 공유하고 있다. 함께 관련 내용에 대해 의견을 나누고 토론할 수 있다. Circulus 그룹 http://group.circul.us Circulus 페이지 http://page.circul.us 이 책에서는 IoT 오디오를 만들기 위한 필수적인 내용을 위주로 다루고 있다. 충분하게 다루지 못한 내용들은 공유 슬라이드를 통해 다루고 있다. 책과 함께로 추가적으로 관련 자료를 학습하고 싶다면, 다음 내용을 참고할 수 있다. Lesson 1 - Introduction : IoT 개요, Opensource H/W, 라즈베리파이 기초 http://www.slideshare.net/rippertnt/iot-make-with-open-hw-nodejs-1st Lesson 2 - Linux : Raspberry Pi 에서 리눅스 활용하기 http://www.slideshare.net/rippertnt/iot-make-with-open-hw-nodejs-2nd Lesson 3 - Node.JS : Raspberry Pi 에서 Node.JS 로 프로그래밍 하기 http://www.slideshare.net/rippertnt/iot-make-with-open-hw-nodejs-3rd Lesson 4 - Sensor : GPIO 를 Node.JS 로 동작시켜 센서 제어하기 http://www.slideshare.net/rippertnt/iot-make-with-open-hw-nodejs-4th Lesson 5 - Project : Raspberry Pi 로 스마트폰 + 무선 IoT 오디오 제작 http://www.slideshare.net/rippertnt/iot-make-with-open-hw-nodejs-5th 또한, 여기서 다루는 프로그래밍 및 간단한 실습을 무료 Cloud Platform 인 Circulus 를 통하여 진행해 볼 수 있다. 실습한 코드 유실 위험이 없으며, 나의 작업 내용을 친구에게 공유하거나 타인의 코드를 복사하여 참고할 수 있다. 별첨에서 보다 상세하게 사용 방법에 대해 다루도록 하겠다. Circulus http://www.circul.us
  • 17.
    1. 파이를 동작시켜보자 – 초기 설정 라즈베리파이 운영체제 설치하기 라즈베리파이에서 선택할 수 있는 운영체제는 대양하다. 우리가 친숙한 윈도우 10 부터, 우분투 리눅스까지 10 여가지의 다양한 운영체제를 지원한다. (일부는 이러한 운영체제로 PC 대체로 사용하려고 하는 분이 있는데, 결코 PC 와 같은 환경을 기대해서는 안된다. 라즈베리파이가 강력하지만 100 만원짜리 PC 성능을 기대해서는 안된다.) 우리가 파이오를 만들기 위해 사용할 운영체제는 데비안 리눅스 기반으로 라즈베리파이에 최적화된 운영체제인 라즈비안(Raspbian) 을 선택할 것이다. 라즈비안 OS 는 라즈베리파이 초창기부터 현재까지 가장 많이 활용되고 있다, 이는 문제가 생겼을 때 남이 이미 겪었을 확률이 높아 해댕 정보를 검색하여 해결하기 용이하다는 것을 의미한다. 라즈비안은 라즈베리파이 공식 홈페이지에서 손쉽게 다운로드 받을 수 있다. 다음의 경로에 접속하여 라즈비안을 다운로드 받도록 한다. https://downloads.raspberrypi.org/raspbian_latest [그림] 컴퓨터로 쓸때는 일반 배포판, 메이커 키트로 이용할 때는 Lite 배포판을 이용하자
  • 18.
    info) 현재 라즈비안은구 버전(wheezy) 와 신 버전(Jessie) 으로 제공이 되고, 신 버전도 일반 버전과 라이트 버전으로 제공되고 있다. 메이커를 위해서는 신 버전 라이트를 이용하면 불필요한 공간 낭비 없이 작업용으로 활용할 수 있다. 만일 윈도우와 같은 GUI 환경이 필요하다면, 일반 버전을 사용하도록 한다. 라즈베리파이 OS 의 이미지 파일을 다운로드 받은 이후, 라즈베리파이에서 사용하기 위해서는 이른바 "굽기"가 필요하다. 윈도우의 경우 Win32Diskimager 라는 프로그램을 이용하여 손쉽게 운영체제 이미지를 마이크로 SD 카드에 담을 수 있으나, 맥 OS 나 리눅스의 경우 터미널 환경에서 직접 명령어를 입력하여 이미지를 담아내야 한다. 다음과 같은 절차로 운영체제 이미지를 복사하자! One more thing - 라즈베리파이를 게임기로 만들기 라즈베리파이를 교육 혹은 IoT 제작 실습용으로 많이 사용하나, 미디어 플레이어 혹은 게임기로 만들어 사용하는 경우도 많다. 만일 게임기로 만들어 사용하고자 한다만, 에뮬레이터 게임에 최적화된 Recalbox 를 사용하는 것이 좋다. 게임에 맞게 최적화 되고, 조이스틱 연결을 지원할 뿐만 아니라, 닌텐도, 플레이스테이션, 네오지오 등 현존하는 다양한 에뮬레이터 게임을 지원한다. 자세한 내용은 www.recalbox.com 을 참고하도록 한다. 1.SD 카드 어댑터에 MicroSD 카드를 삽입한 후 맥의 카드리더에 삽입하자. Window
  • 19.
    윈도우 에서는 간단하게Win32Imager 를 이용할 수 있다. 다음의 주소에서 다운로드 받아서 설치하도록 한다. http://sourceforge.net/projects/win32diskimager/files/latest/download 다운로드 받은 최신의 라즈비안을 선택한 후 Write 버튼을 누르면, 마이크로 SD 카드에 기록이 된다. 완료 후에 라즈베리파이에 삽입하여 부팅이 정상적으로 이루어지는지 확인해 보자. Mac 2. Finder 에서 인식된 SD 카드 드라이브에서 마우스 우측으로 클릭하면 diskn 으로 (예시 disk4) 설정이 된걸 알수 있다. 이 정보를 기록해 두자. 3.터미널을 열어 다음 명령어를 입력하자 sudo dd bs=1m if=path_of_your_image.img of=/dev/rdiskn 명령어 실행에 실패한다면 rdisk 대신 disk 를 입력하도록 한다. sudo dd bs=1m if=path_of_your_image.img of=/dev/diskn Linux - 마이크로 SD 카드 삽입 후 다음 명령을 실해앟여 인식된 마이크로 SD 카드를 확인한다. $ df -h - 명령어 리스트 상에 인식된 내역을 확인한다 보통 ./devmmcblk0p1 이나 /dev/sdd1 의 형태로 인식된 것을 알수 있다. 인식된 sd 카드의 기록을 위해 mount 를 해제하는 다음 명령어를 실행한다. $ umount /dev/sdd1 - 기록을 수행하기 위하여 다음 명령어를 실행해 준다. $ dd bs=4M if=~/2012-12-16-...img of=/dev/sdd
  • 20.
    첫 라즈베리파이 사용을위한 기본 조립 운영체제를 준비했으니, 이제 라즈베리파이를 구동할 기본 준비가 되었다. 하지만 라즈베리파이 단독으로는 초기에 아무것도 할수가 없다. 마이크로 SD 카드를 라즈베리파이 후면 슬롯에 딸깍 소리가 나도록 삽입하고, 무선 USB 랜을 라즈베리파이의 USB 랜 포트에 설치하도록 한다. [그림] 구형 라즈베리파이 B (좌측하단) 는 SD 카드 형식으로, 라즈베리파이 B+ 이나 2B(좌측 상단) 은 Micro SD 형식으로 딸깍 소리가 날때가지 눌러 장착을 하나, 3B (우측)은 끝 부분까지 Micro SD 카드를 밀어 넣으면 된다. 라즈베리파이을 통해 개발하는 방법은 크게 2 가지로, 직접 라즈베리파이에 모니터, 키보드, 마우스를 직접 연결하여 개발하는 방법과 원격 구성으로 접속하여 개발하는 방법이 있다. 필자는 연결을 최소화 한 후자의 방법을 주로 선택하지만, 부팅시에 문제가 발생하거나 원격 연결이 어려운 경우, 그리고 GUI 환경으로 라즈베리파이를 활용하고자 할때는 직접 연결하여 사용해야 한다. 처음 라즈베리파이를 접하는 경우 GUI 를 통해 무선 설정을 하는 것이 용이하므로, 여기서는 초보자와 고급사용자를 위한 두가지 방법 모두를 다룰 것이다. GUI 를 통한 원격 연결 설정 처음 라즈베리파이를 사용하는 사용자를 위해 권장하는 방법으로, 마치 노트북 컴퓨터나 스마트 폰을 인터넷 연결하는 것과 유사한 방법이다. 라즈비안 일반 버전으로 이미지를 만들어 라즈베리파이를 부팅하면 다음과 같이 부팅된 화면을 발견할 수 있다.
  • 21.
    [그림] 라즈베리파이 부팅후화면, 상단 우측 무선인터넷 항목을 클릭하면, 접속가능한 AP 목록을 발견할 수 있다. 부팅한 화면 우측 상단을 보면, 안테나 모양으로 PC 운영체제나 스마트 폰에서 보는 무선인터넷 연결 아이콘을 발견할 수 있는데, 해당 아이콘을 클릭하면 접속가능한 무선 AP 목록을 확인할 수 있다. 접속할 수 있는 무선 AP 를 선택한 후, 필요하다면 비밀번호를 입력하여 인터넷 연결을 수행하자. 연결이 완료되면 터미널 창을 열어, ifconfig 명령으로 무선인터넷에 연결된 IP 정보를 확인한다. 다음과 같이 정상적으로 IP 목록이 표시된다면, 앞으로는 원격접속을 통해 라즈베리파이를 제어하고 개발할 수 있다. [그림] 접속하고자 하는 AP 를 선택한 후, 필요하다면 비밀번호를 입력하자. 콘솔을 통한 원격 연결 설정 보다 어려운 접근 방법이지만, 라즈비안 Lite 버전을 설치한 경우 다음과 같은 방법으로 인터넷 연결 설정을 진행할 수 있다. 무선 인터넷 공유기와 라즈베리파이의 유선 랜을 서로 연결해 주도록 한다. 이는 PC 에서 원격으로 접속하여 개발할 때 사용하기 위함이다. 원격으로
  • 22.
    접속하기 위해서는 라즈베리파이의IP 를 확인해야 하는데, 접속된 공유기를 통해서 IP 를 알아낼 수 있다. 현재 내가 접속한 컴퓨터의 IP 를 알아내기 위해 ip 를 확인해 보자 윈도우 - ipconfig 리눅스 - ifconfig
  • 23.
    맥 OS -ifconfig 내 IP 가 192.168.1.140 으로 되어있다면, 공유기의 IP 는 192.168.1.1. 이 된다. 차후 공유기 설정을 위하여 해당 IP 를 기록해 둔다. [그림] 라즈베리파이에 키보드와 모니터를 연결하여 부팅하자. 라즈베리파이에 키보드와 모니터를 연결하고, 운영체제를 설치한 MicroSD 카드가 장착되었는지 확인한 후, Micro USB 를 통해 전원을 공급해 보자. 라즈베리파이가 부팅되는
  • 24.
    것을 확인할 수있다. 부팅이 완료된 후, 로그인 ID 는 pi, 비밀번호는 raspberry 를 입력하여 로그인을 수행한다. 이제 IP 를 알아내서 무선환경에서 라즈베리파이를 작업할 수 있도록 수정해 볼 것이다. 콘솔창에 다음과 같은 명령을 입력하여 라즈베리파이의 IP 를 확인해 본다 $ ifconfig [그림] Eth0 에 나오는 inet addr 이 라즈베리파이의 유선 IP 주소이다. Ifconfig 명령 실행후 eth0 의 inet addr 값이 유선랜 IP 이다. 라즈베리파이에서 직접 작업해도 되지만, IP 를 알아냈으니 원격으로 설정 작업을 수행해 보자. 원격접속을 위해서는 터미널 프로그램이 필요하다. 리눅스나 맥 OS 의 경우 기본적으로 터미널 명령이 지원되나, 윈도우 XP 이후에 나온 버전 부터는 터미널이 지원되지 않아서, 터미널을 사용할 수 있도록 도와주는 프로그램을 별도로 설치해야 한다. 윈도우에서 많이 활용되는 무료 터미널 프로그램인 putty 를 이용하여 접속해 보도록 한다. putty 다운로드 - http://the.earth.li/~sgtatham/putty/latest/x86/putty.exe
  • 25.
    [그림] 라즈베리파이의 IP를 입력한 후 Open 을 클릭하여 라즈베리파이의 터미널에 접속한다. 다운로드 받은 putty 를 실행하면 다음과 같은 입력창을 확인할 수 있다. 확인했던 IP 를 입력하고, Open 을 클릭하여 원격으로 라즈베리파이에 접속해 본다. 기본 사용자 계정은 pi, 비밀번호는 raspberry 를 입력하면 다음과 같은 로그인 성공 메세지를 확인할 수 있다. 라즈베리파이 부팅 성공을 축하한다. 이제 본격적으로 설정 및 개발을 진행해 보자. [그림] 부팅 후 원격 접속시 로그인 ID 는 pi, 비밀번호는 raspberry 를 입력해 주면 로그인 된다. One More Thing – 복잡한 OS 설정을 한번에 CirculOS 라즈베리파이를 무선으로 동작시키고 실제 개발을 하기 위해서는 앞으로도 다양한 설정을 해야 한다. 이러한 설정이 복잡하게 느껴진다면, 필자가 활동하는 커뮤니티인 Circulus 에서
  • 26.
    제공하는 CirculOS 배포판을이용하면, 복잡한 설정 필요 없이 곧바로 사용할 수 있도록 도와준다. CirculOS 를 사용하는 방법은 10 장에서 자세히 다룬다. 라즈베리파이 기본 설정하기 라즈베리파이에 운영체제를 설치한 후 그대로 사용하면 몇 가지 문제가 발생한다. 우선 SD 카드 전체 용량을 활용하지 못하며,, 우리가 만들 오디오 시간이 영국 기준 시간이 아닌 우리나라 시간에 맞추어 동작하게 된다. 몇 가지 기본 설정을 변경해 주어야 한다. $ sudo raspi-config 첫 번째 해야 할 일은 SD 카드 전체를 사용할 수 있게 해 주는 일이다. 1. Expand Filesystem 을 선택한 후 Enter 를 누르면, Root partition has been resized 라는 문구를 볼 수 있다. 재 부팅 후 정장적으로 반영되지만 몇 가지 작업을 위하여 다음 설정 화면으로 넘어가도록 한다.
  • 27.
    두 번째 할일은 기본 국가인 영국에서 한국으로 바꾸고, Timezone 을 서울로 바꾸는 일이다. 5. Internationalization Options 을 선택한 후 Enter 를 누른다. I1. Change Locale 에서 ko- Kr.UTF-8 로 한국 위치로 설정한다. 설정이 완료되면, I2. Change Timezone 에서 시간 대역을 Seoul 로 변경 하도록 한다.
  • 28.
    마지막으로 할 일은LCD 통신을 위한 SPI 를 Enable 로 설정한다. 9. Advanced Option 을 선택한 후, A6. SPI 항목을 선택한다. SPI 를 설정하겠냐는 응답에 Yes 를 선택한 후 Enter 를 누른다. 그러면, Would you like SPI kernel module to be loaded by default? 라는 메시지가 뜨게 되는데, Yes 를 선택한 후 Enter 를 누른다. 모든 설정을 마친 후, Finish 를 선택하고 Enter 를 누르면 Would you like to reboot now? 라는 메시지가 출력되는데, Yes 를 선택하여 재 부팅 함으로서, 설정한 작업이 반영되게 된다. 네트워크 설정하기 IoT 환경의 라즈베리파이를 사용하기 위하여 네트워크 설정이 필요하다. 기본적으로는 유선, 무선 모두 DHCP 연결을 통한 접속을 설정하여, 이후 별다른 설정 없이 인터넷 환경이 가능하게 할수 있다. 하지만, 우리가 현재 개발하고 있는 상황에서는 DHCP 를 이용하면 IP 가 수시로 바뀌어 원격접속시에 매번 IP 를 확인해야 하는 불편함이 있다. 개발 완료 이후에는 DHCP 를 이용할 수 있지만, 개발 시점에는 고정 IP 를 사용하도록 한다. WIFI 설정 라즈베리파이에서 어떤 WIFI 를 사용할 수 있는지 스캐닝 하기 위해 다음 명령어를 사용하자 $ sudo iwlist wlan0 scan iwlist 명령으로 접속가능 한 무선랜 목록을 확인할 수 있다. 명령어를 입력하면, 접속할 수 있는 AP 목록이 보여지게 된다. AP 명은 ESSID 인데, 전파 강도가 강한 순서대로 목록이 제공된다. 접속할 목록을 확인한 후 설정을 진행하도록 한다.
  • 29.
    $ sudo nano/etc/wpa_supplicant/wpa_supplicant.conf One more thing – Nano 에디터를 이용한 설정 편집 Nano 에디터는 리눅스에서 간단히 파일을 편집하는데 유용하다. 수정 후 CTRL-X 를 입력하고 Y 와 Enter 키를 누르면 저장이 완료된다. 보다 자세한 내용은 2 장에서 다룬다. 파일의 하단에 다음과 같이 입력하자 network = { ssid = "접속할 ESSID 명" psk="WIFI 패스워드" } CTRL+X 를 입력한 후 Y 입력하고 Enter 를 누르면 저장이 된다. 라즈베리파이를 재 부팅하면 자동으로 적용되지만, 재부팅 필요없이 연결을 확인하기 위해 다음 명령을 입력하자 $ sudo ifdown wlan0 $ sudo ifup wlan0 재 구동후 무선랜이 제대로 동작하는지 다음 명령어를 이용하여 확인한다. 아래 화면과 같이 IP 가 제대로 잡혀서 나온다면 정상적으로 동작하는 것이다. $ ifconfig wlan0
  • 30.
    wlan0 항목에 ipaddress 가 제대로 할당되어 있다면 정상적으로 접속된 것이다. 무선 인터넷 끊기지 않게 설정하기 라즈베리파이를 유선으로 연결하여 사용해도 되지만, IoT 컨셉의 제품이나 이동성이 좋은 시스템을 개발하기 위해 WiFi 를 이용하는것이 바람직 하다. 라즈베리파이 3 를 구매하였다면, WIFI 가 내장되어 있으므로 그대로 사용하면 된다. 단 기본 설정으로 절전 모드가 활성화 되어, 실제 사용시 원격 접속이 잘 안되거나 인터넷 연결이 수시로 비활성화와 활성화가 반복되어 개발시에 문제가 발생할 수 있다. 우선 iwconfig 로 절전 모드 상태를 확인하도록 한다. $ iwconfig [그림] Power Management 가 on 으로 되어있으면, 절전모드가 활성화 되어 있는 것이다. Power Management 가 on 으로 보여진다면, 절전 모드가 설정 된 것이다. 이를 비활성화 하기 위해서는 다음의 명령을 사용하도록 한다. $ iwconfig wlan0 power off
  • 31.
    [그림] power off옵션을 사용하면, Power Management 설정이 off 가 된 것을 확인할 수 있다. 다시한번 iwconfig 로 확인해 보면, 절전 모드가 off 로 된 것을 확인할 수 있다. 이제는 원격 접속이나 인터넷 활용 서비스를 개발할 때에 안정적인 무선 인터넷 환경을 사용할 수 있게 되었다. 그러나 라즈베리파이를 재 부팅하면 다시 원래대로 되 돌아 가게 된다. 이를 방지하기 위해서는 상시적으로 무선 인터넷 설정에서 절전 모드를 해제해야 할 필요가 있다. 부팅 시에 구동 되는 무선 인터넷 관련 파일을 수정하여, 별도의 절전 모드 해제 명령 없이 자동으로 절전 모드를 해제할 수 있다. $ iw dev wlan0 set power_save off 만일 라즈베리파이 2B 이하의 WiFi 가 기본으로 장착되지 않은 기종을 구매하였다면, 추천되는 무선 USB 어댑터는 Realtek 사의 8188cu 를 이용한 무선 랜 카드이다. 시중에서 8188cu 기반으로 만들어진 구매할 수 있는 랜 카드는 다음과 같다. (제조사 사정에 따라 칩셋이 변동되는 사례가 있었다. 구매전에 다시한번 확인하길 바란다) EFM 네트웍스 – ipTIME M100mini Netis – Netis WF2120 유니콘정보시스템 – 유니콘 BW-150MINI One More Thing – 무선랜 카드를 8188cu 칩셋이 장착된 랜카드를 구입해야 하는 이유 아무 무선랜을 선택하면 안 되는 이유는 8188eu 의 경우 wheezy 버전의 라즈비안에서 문제가 발생하는 경우가 많았고,새 배포판인 jessie 에서는 드라이버 지원이 추가 되었다고 하지만, 필자가 직접 8188eu 기반의 무선랜을 사용했을 때 인터넷이 제대로 되지 않는 문제가 발견되었다. 따라서, 가급적이면 8188cu 기반의 무선 랜 카드를 구매할 것을 권장한다. 정상 적으로 무선랜 카드를 사용한다고 하더라도, WiFi 를 이용하여 라즈베리파이를 이용하다 보면, 인터넷 문제와 무관하게 종종 끊기는 현상이 발생한다. 대부분의 라즈베리파이 관련 책들은 연결하는 방법만 다루지 실제 이런 문제를 다루고 있지 않은데, 이러한 문제로 구현 및 구동에 있어서 큰 어려움을 겪게 되는 경우가 많다. 이는 라즈베리파이의 WiFi 드라이버에서 절전 모드 및 대기 모드로 동작되기 떄문에 발생하는 문제로, 이 기능을 disable 하여 WiFi 로 인하여 발생하는 문제를 최소화 할 수 있다.
  • 32.
    8188cu 의 경우상위 칩셋인 8192cu 와 호환이 되어 부팅시 8192cu 로 잡히게 된다. 절전 설정값이 어떻게 되어있는지 확인해 보는 명령어는 다음과 같다. $ cat /sys/module/8192cu/parameters/rtw_power_mgnt $ cat /sys/module/8188eu/parameters/rtw_power_mgnt 1 로 값이 설정되어 있다면, 절전 모드가 활성화 되어 있는 것이다. 이를 비활성화 하기 위해서는 다음의 설정 파일을 수정하도록 한다. $ sudo nano /etc/modprobe.d/8192cu.conf One More Thing – 8188 이 아닌 8192 라는 이름으로 설정 파일을 만드는 이유 랜카드 사용시 8188cu 계열의 랜카드를 사용하더라도 8192cu.conf 파일을 만들어야 한다. 8188cu 칩셋과 8192cu 호환이 되어, 라즈비안 상에서 8188cu 랜 카드의 경우도 8192cu 로 인식하기 때문이다. 8188eu 의 경우 인식은 되지만 정상적으로 동작이 안 되는 경우가 발생한다. 따라서 구입시 가급적 8188cu 칩셋이 탑재된 무선 랜 카드를 구입하도록 한다. 다음의 줄을 추가하고, CTRL+X 와 Y 를 누른후 Enter 를 누르면 저장이 된다. options 8192cu rtw_power_mgnt=0 rtw_enusbss=0 다음의 세부 설정 옵션은 다음과 같다.
  • 33.
    rtw_power_mgnt 0 - 절전모드를 비 활성화 1 - 최소로 절전 기능을 수행함 2 - 최대로 절전 기능을 수행함 rtw_enusbss 0 - 대기 모드를 비 활성화 1 - 대기 모드를 활성화 함 Rtw_ips_mode 0 – 낮은 전원 1 – 높은 전원 무선 연결 설정이 완료되었다면, iwconfig 명령으로 정상적으로 무선랜이 활성화 되었는지 확인해 볼 수 있다. 절전 설정을 off 하지 않으면, 인터넷 연결이 수시로 끊길 수 있어 IoT 제품으로 만든 경우 정상적으로 동작하기 어려우며, 개발 시에 putty 를 통한 원격접속도 수시로 끊어지는 문제가 발생한다. 이제 유선으로 연결한 인터넷 선을 뽑고, 무선 환경으로 다시금 putty 로 접속하여 문제가 없는지 확인해 보자. 정상적으로 접속이 된다면, 이제 유선으로 연결할 필요 없이 개발을 진행하면 된다. 라즈베리파이와 파일 주고 받기 - FTP 설정하기 라즈베리파이를 사용할 때 내 컴퓨터에 있는 소스 코드나 파일들을 보내고 싶을 때도 있고 반대로 파일들을 받고자 할 때가 있을 것이다. 파일 공유를 가능하게 해 주는 삼바 서버를 라즈베리파이에 구축할 수 있으나, 우리는 가장 간단한 방법을 활용할 것이다. 별도의 설치 필요 없이 바로 활용 가능한 FTP(File Transfer Protocol) 을 활용하는 것이다. 파일 전송에 사용되는 FTP 프로그램 중 가장 인기 있는 프로그램은 FileZilla 이다. 한국어도 지원하므로, 설치 시에 한국어를 선택할 수 있다. 다음의 경로에서 다운로드 받아 설치하도록 한다. 다운로드 > https://filezilla-project.org/download.php?type=client FTP 접속을 할 때 호스트(Host) 에 라즈베리파이의 IP 주소를 입력하고, 사용자 명(U) 에 pi 를, 비밀번호(W) 는 raspberry (변경하였다면, 변경한 비밀번호를 입력해야 한다) 를 입력한다. 일반적인 FTP 접속에 사용되는 포트(P) 는 21 이나, 라즈베리파이와 사용되는 방식은 SFTP(Secure FTP)로 22 번 포트를 사용한다. 포트(P)에 22 를 입력하고 빠른 연결 버튼을 누르면, 정상적으로 FTP 연결이 성공한 것을 볼 수 있다. 왼쪽은 사용자가 사용하는 컴퓨터의
  • 34.
    디렉토리 구조이고 오른쪽이라즈베리파이의 디렉토리 구조이다. 복사 하거나 받고자 하는 파일을 드래그 앤 드랍으로 편리하게 파일을 주고받을 수 있다. [그림] FileZilla 를 활용하면, 파일을 드래드 앤 드랍으로 주고 받을 수 있다. One More Thing – 파일 복사 경로 라즈베리파이에 파일을 복사할 때 /home/pi 디렉토리나 해당 디렉토리의 하위 디렉토리를 생성하여 파일을 전송하도록 한다. 타 디렉토리에는 보안상의 이유로 파일을 전송할 수가 없다. 파일 전송을 시도하고자 하면 permission denied 라는 메시지와 함께 전송이 실패하는 것을 알 수 있다. 이번 장을 정리하며 첫 장에서 우리는 라즈베리파이를 사용할 수 있도록 OS 를 설치하고, 기본 환경 설정을 마무리 하였다. 무선 개발 및 IoT 서비스를 위해 무선 랜 환경을 구축하고 원격 접속 및 데이터를 주고 받을 수 있는 구성을 구축하였다. 라즈베리파이의 장점은 아두이노와 달리 리눅스 OS 가 탑재되어, 리눅스 생태계의 다양한 오픈소스를 그대로 활용하고 다양한 서비스를 구축할 수 있다는 점이다. 다음장에서는 라즈베리파이를 보다 깊이 있게 사용할 수 있도록 라즈베리파이의 리눅스에 대해 살펴보도록 하겠다.
  • 35.
    2. 파이의 운영체제- LINUX 속성 실습 리눅스란 무엇인가? Linux 는 일종의 운영체제 이다. 우리가 사용하는 컴퓨터나 스마트폰을 자유자재로 원활하게 사용할 수 있는 것은 우리와 컴퓨터 사이에 Window, Android, iOS 같은 운영체제가 있기 때문이다. 이러한 OS 덕분에 다양한 어플리케이션을 설치하여 다양한 용도로 활용할 수 있다. 리눅스는 핀란드 헬싱키대학의 대학원 생인 리누스 토발스가 취미 삼아 개발하였다. 개발하게 된 유래를 알게되면 재미난 점을 발견할 수 있다. 토발스는 원래 교육용 유닉스인 미닉스를 이용하고 있었는데, 미닉스를 함부로 개조하지 못하게 제한을 두자, 미닉스의 기능에 만족하지 못한 토발스는 직접 운영체제를 만든 것이다. 역사를 보면 무언가 비싸거나 어려운 것이 존재하면, 그 까짓거 내가 만든다는 생각의 위인들이 나타나 저렴한 가격에 쉽게 접할 수 있도록 나오는 현상을 많이 발견하게 된다. 오픈소스나 오픈소스 하드웨어가 그러하듯이 리눅스 역시도 그러한 제약을 벗어나고자 태어나게 된 것이다. [그림] 리눅스의 로고가 펭귄이 된 까닭은 원 저자가 오스트레일리아를 여행중 펭귄에 물린적이 있다고 한다. 펭귄에 물리면 펭귄 중독에 걸려 펭귄을 사랑한다는 속설이 있다. 왜 리눅스 인가? 아두이노와 달리 라즈베리파이는 운영체제가 지원된다. 이를 통해 모터가 돌아가고 LED 가 반짝하는 기능 뿐만 아니라 대화를 나누고, 뉴스와 날씨를 구독 받는등의 다양한 아이디어를 실제로 구현할 수 있는 것이다. 이러한 기능을 위해 사용하는 것이 바로 라즈베리파이에 올라가는 Linux 이다. 이미 Linux 생태계에서는 하드웨어 제어를 위한 오픈소스 부터 데이터 처리 및 분석에 대한 방대한 오픈 소스를 제공하고 있다. 일반 PC 에서 사용되는 Linux 가 그대로 라즈베리파이에 이식되어 있기 때문에, 라즈베리파이를 위해 특정 업체에 종속된 프로그래밍 언어나 개발 방식을 사용하지 않고, 기존 PC 환경에서 사용했던 내용들을 그대로 이용할 수 있는 장점이 있다. 다양한 리눅스가 존재하지만 우리는 Debian 계열의 라즈비안 리눅스를 사용하므로, Debian 배포판의 리눅스 사용법을 살펴보도록 할 것이다.
  • 36.
    [그림] 가장 많이사랑받는 3 대 리눅스. Ubuntu 역시 debian 계열이다. 라즈리안 역시 debian 계열의 리눅스로 명령어가 가장 많이 사용되는 Ubuntu 리눅스와 유사하다. Linux 를 사용하기 위한 명령어 리눅스를 사용하는 방법은 크게 2 가지가 있다. 우리가 윈도우를 활용하는 것과 같은 그래픽 유저 인터페이스(GUI)를 이용하는 방법과, 영화에서 보아 왔던 명령어를 입력하는 방식이다. 윈도우 환경을 이용하는 것은 일반적인 PC 사용 환경을 다루는 것이므로 이 책에서 다루고자 하는 범위를 벗어난다. 여기에서는 라즈베리파이를 위해 오디오를 만들기 위하여 필요한 명령어들을 다루고자 한다. 리눅스의 양대 배포판인 데비안(Debian) 과 페도라(Fedora) 에 따라 일부 명령어가 약간은 차이가 발생하는데, 가장 인기 있는 리눅스 배포판인 우분투(Ubuntu) 리눅스와 마찬가지로, 라즈비안(Raspbian) 역시 데비안 기반으로 하고 있다. 우리는 데비안 리눅스의 기본 사용법에 대해 다룰 것이다. 명령어 구조 라즈베리파이를 콘솔로 부팅하면 프롬프트(prompt) 가 나타난다. 프롬프트는 사용자의 명령 입력을 기다리는 표시로서, pi 는 사용자 계정의 이름을 말하는 것이고, circulus 는 호스트 이름을 이야기 하는 것이다. [그림] 라즈비안에 처음 접속한 모습 명령 [옵션] [인자 값...] 명령
  • 37.
    리눅스를 사용하기 위해사용자가 입력하는 명령어 이다. 옵션 명령의 세부 기능을 선택할 수 있다. 옵션은 - 기호로 시작하며 영문 소문자나 대문자로 구성되어 있다. 명령에 따라 어떤 옵션이 있고 그 기능이 무엇인지는 해당 명령어에 따라 차이가 있다 (명령에 따라 옵션이 있을 수도 있고 없을 수 있다) 인자 인자는 명령으로 전달되는 값이며, 주로 파일명이나 디렉터리 명이 사용된다. 각 명령어에 따라 필요한 인자가 다르다 (명령에 따라 인자가 필요할 수도 있고 없을 수 있다) 기본 명령어 clear – 화면 지움 현재의 화면이 깨끗하게 지워지고 커서가 화면 왼쪽 상단에 위치한다 $ clear date - 현재 날짜와 시간을 출력 현재 시스템의 날짜와 시간을 출력하는 명령어 이다. $ date [그림] Locale 설정한 지역에 맞추어 시간이 표시된다. man - 명령어 사용법 안내 리눅스가 제공하는 각종 명령의 사용법을 보여준다. 명령의 옵션이나 인자값을 확인하고자 할때 유용하다. $ man clear sudo - 슈퍼 사용자 권한 일반적으로 사용자에게 생성된 기본 폴더 이외의 공간에서 명령을 수행하는 경우 관리자 권한이 필요하다.(이후 실제로 라즈베리파이에서 실습할 때 sudo 가 명령어 앞에서 쓰는 경우를 많이 발견하게 될 것이다) 관리자 권한으로 실행하기 위해 사용하고자 하는 명령어 앞에 sudo 를 입력 후 명령을 수행할 수 있다. $ sudo mkdir test 파일 디렉토리 명령
  • 38.
    리눅스는 기본적으로 유닉스계열의 운영체제 이므로 유닉스와 유사한 부분이 많다. 유닉스와 마찬가지로 리눅스도 시스템과 관련된 정보와 하드웨어의 장치를 모두 파일로 관리한다. 파일을 효과적으로 관리하기 위해 디렉터리, 윈도우에서는 폴더와 같은 구조가 사용되는데, 파일들을 용도에 따라서 계층적으로 관리한다. 디렉토리 용도 dev 장치 파일이 담긴 디렉터리 home 사용자 홈 디렉터리가 생성되는 디렉터리. 라즈베리파이 처음 부팅 시, 이 디렉터리의 pi 디렉터리가 기본 디렉터리가 된다. media CD-ROM 이나 USB 같으 외부 장치를 연결(마운트 라고 표현)하는 디렉터리 opt 추가 패키지가 설치되는 디렉터리 root root 계정의 홈 디렉터리이다. 루트(/) 디렉터리와 다른 것이므로 혼동하지 않도록 한다. sys 리눅스 커널과 관련된 파일이 있는 디렉터리 이다. usr 기본 실행 파일과 라이브러리 파일, 헤더 파일등 많은 파일이 있음. bin 실행파일(명령)을 가지고 있음 boot 부팅에 필요한 커널 파일을 가지고 있음 etc 리눅스 설정을 위한 각종 파일을 가지고 있음 lost+found 파일 시스템에 문제가 발생하여 복구할 경우 문제가 되는 파일이 저장되는 디렉토리. 보통은 비어 있음. mnt 파일 시스템을 임시로 마운트 하는 디렉터리 임 proc 프로세스 정보 등 커널 관련 정보가 저장되는 디렉터리 임 run 실행중인 서비스와 관련된 파일이 저장 됨 srv FTP 나 Web 등 시스템에서 제공되는 서비스의 데이터가 저장됨 tmp 시스템 사용 중에 발생하는 임시 데이터가 저장됨. 이 디렉토리의 파일은 시스템 재 시작시 모두 삭제 됨 var 시스템 운영중에 발생하는 데이터나 로그가 저장되는 디렉터리 pwd - 현재 위치를 확인 Print Working Directory 의 약자로서, 현재 디렉터리를 확인하는 명령이다 $ pwd cd - 디렉토리 변경 Change Directory 의 약자로서, 현재 디렉터리에서 다른 디렉터리로 이동할 때 사용하는 명령어 이다. $ cd [디렉토리 명] 실행 예 ROOT 디렉토리로 이동한다.
  • 39.
    $ cd / HOME디렉토리로 이동한다. $ cd ~ HOME/PI 디렉토리로 이동한다. $ cd /home/pi [그림] cd 명령을 이용하여 디렉토리를 이동하고, pwd 명령으로 이동 위치를 확인할 수 있다. ls - 디렉토리 내부 확인 list 의 약자로서, 디렉토리 내부의 파일이나 하위 디렉토리 같은 정보를 보여주는 명령어 이다. $ ls [옵션]... [파일]... 옵션명 설명 -a 숨김 파일을 포함하여 모든 파일 목록을 표시함 -d 지정된 디렉터리 자체의 정보를 출력함 -F 파일 유형을 나타내는 기호를 파일명 끝에 표시 (디렉토리는 /, 실행파일은 *, 링크는 @가 나타남) -l 파일에 대한 상세 정보를 표시해 줌 -t 파일이 생성된 시간별로 표시 -C 한줄에 여러개의 정보를 함께 표시(기본설정) -R 하위 디렉토리 내용까지 표시 실행 예 현재 디렉토리의 정보를 확인한다. $ ls 파일에 대한 상세 목록을 확인한다. $ ls -l 숨김 파일을 포함하여 상세 목록을 확인한다. $ ls -al
  • 40.
    [그림] –C 옵션은기본 적용된다. –l 옵션을 사용하면 보다 자세한 정보를 함께 확인할 수 있다. find - 파일 찾기 find 명령은 리눅스의 디렉터리 구조에서 특정 파일이 어디에 위치하고 있는지 찾아 주는 명령어 이다. $ find [경로] [검색 조건] 옵션명 설명 -name [파일이름] 파일 이름으로 검색함 실행 예 interfaces 라는 이름의 파일을 전체 검색한다. $ cd / $ sudo find –name interfaces [그림] Root (/) 디렉토리에서 검색을 하면, 모든 디렉토리를 전체 검색한다. cp/mv - 파일 복사 및 이동 Copy, Move 의 약자로서 특정 파일들을 이동시키거나 복사할 때 사용하는 명령어이다. $ mv [원본 파일 경로][대상 파일 경로] $ cp [원본 파일 경로][대상 파일 경로] 실행 예 /etc/network 디렉토리에 있는 interfaces 파일을 interfaces_bak 파일로 복사한다. $ sudo cp /etc/network/interfaces /etc/network/interfaces_bak
  • 41.
    /etc/network 디렉토리에 있는interfaces_bak 파일을 interfaces 파일로 이동한다. $ sudo cp /etc/network/interfaces_bak /etc/network/interfaces [그림] cp 명령의 경우, 파일을 복사하지만, mv 를 사용하면 기존 파일이 사라진다. mkdir - 디렉토리 만들기 make directory 의 약자로써 새로운 디렉토리를 생성할때 사용 한다. $ mkdir [생성할 폴더명 또는 경로] 실행 예 현재 디렉토리에 test 라는 폴더를 생성한다. $ sudo mkdir test rm - 파일/디렉토리 삭제하기 remove 의 약자로써 기본 명령은 파일을 삭제할때 사용하지만 -r 옵션을 붙여 파일이 있는 디엑토리를 지울때도 사용할 수 있다. $ rm [옵션] [삭제할 경로] 옵션명 설명 -r 디렉터리를 삭제한다 -i Interactive 의 약자로, 삭제할 때 매번 삭제 여부를 사용자에게 묻는다. -f Force 의 약자로, 어떠한 확인 메시지도 묻지 않고 삭제를 수행한다. -v Verbose 의 약자로, 삭제하는 동안 정보를 상세하게 보여준다. 실행 예 현재 디렉토리에 있는 test.txt 파일을 삭제한다. $ sudo rm test.txt
  • 42.
    현재 디렉토리에 test라는 폴더를 삭제한다. $ sudo rm –r test 현재 디렉토리에 test 라는 폴더와 폴더내 파일도 함께 삭제한다. $ sudo rm –rf test 이정도 알면 리눅스 관리 어렵지 않아요! 시스템에 설치되어 있는 디스크와 메모리의 사용량을 확인할 필요가 있다. 우리가 최종적으로 만드는 라즈베리파이 오디오는 음악파일을 다운로드 받아서 재생하게 되는 구조이다. 라즈베리파이의 저장소 용량이 한정적이기 때문에 언제 꽉 차서 음악파일의 저장이 안될지 모르는 일이다. 또한 전원 방전이나 플러그를 뽑는 등 전원의 불안정, 소프트웨어 오류, 하드웨어 오동작 등으로 손상될 수 있다. 이렇게 손상된 파일 시스템 또한 점검하고 복구해야 할 필요가 있다. 여기서는 다양한 관리 명령을 살펴보도록 한다. df - 파일 시스템 별 사용량 확인 df 는 Disk Free 의 약자로, 현재 시스템에서 사용중인 파일시스템의 사용량에 대한 정보를 출력하는 명령어 이다. 부가적으로 전체 용량, 사용 가능한 용령 마운트 정보등도 함께 출력된다 $ df [옵션] [파일 시스템] 옵션명 설명 -a 모든 파일 시스템을 대상으로 디스크 사용량을 확인 -k 디스크 사용량을 KB 단위로 출력함 -m 디스크 사용량을 MB 단위로 출력함 -h Human 의 약자로, 디스크 사용량을 알기 쉬운 단위(GB, MB,KB 등)으로 출력함 -T 파일 시스템의 류도 출력함 실행 예 메가바이트(mb) 단위로 사용량을 확인한다. $ df -m 사람이 보기 좋은 단위로 사용량을 표시한다. $ df -h
  • 43.
    [그림] 디스크의 남은공간을 확인할 때 df 명령에 –m (MB 단위) 을 지정하면 확인하기 수월하다. du - 디렉토리의 디스크 사용령 확인하기 du 는 Disk Usage 의 약자로, 파일 시스템별로 디스크 사용량을 알려주는 df 명령과는 달리 특정 디렉터리 별로 디스크의 사용량을 알려준다. $ du [옵션] [디렉토리] 옵션명 설명 -s 특정 디렉터리의 전체 사용량을 출력한다 -h 디스크의 사용량을 알기 쉬운 단위(GB,MB,KB 등으로 출력한다) 실행 예 메가바이트(mb) 단위로 사용량을 확인한다. $ du -s 사람이 보기 좋은 단위로 사용량을 표시한다. $ df -h fsck – 디스크 상태 검사 및 복구 fsck 는 File System Check 의 약자로, 디렉터리, 파일 링크등을 검사하고 필요시 복구 작업도 수행하는 명령어 이다. $ fsck [옵션] [장치명] 옵션명 설명 -f 강제로 점검한다. -y 모든 질문에 yes 로 대답하게 한다 -a 파일 시스템 검사에서 문제가 발생했을 때 자동으로 복구한다 실행 예 사용자에게 묻지 않고 자동으로 검색을 수행한다.
  • 44.
    $ sudo fsck- y 검사 중 문제 발견 시 자동으로 복구한다. $ sudo fsck -a Free – 사용가능 메모리 확인 Free 명령은 메모리 사용량을 확인하는 명령어이다. 정보를 보면 버퍼와 캐시가 있는데, 버퍼는 응용 프로그램별로 존재하는 임시 데이터 저장공간으로 이 데이터는 다른 응용 프로그램이 사용하지 않는다. 반면 캐시는 빠른 접근을 위해 자주 사용하는 데이터를 저장하는 메모리 공간이다. 캐시는 여러 번 사용될 수 있지만 버퍼는 한번만 사용한다. $ free [옵션] 옵션명 설명 -s 특정 디렉터리의 전체 사용량을 출력한다 -h 디스크의 사용량을 알기 쉬운 단위(GB,MB,KB 등으로 출력한다) 실행 예 메가바이트(mb) 단위로 사용량을 확인한다. $ du -s 사람이 보기 좋은 단위로 사용량을 표시한다. $ df –h lsusb List USB 의 약자로써, 연결된 USB 의 자세한 정보를 제공하는 명렁어 이다. 라즈베리파이에 다양한 USB 장치들을 연결하여 활용할 수 있는데, 해당 장치가 라즈베리파이에서 제대로 인식되는지 확인하는데 사용할 수 있다. $ lsusb [그림] lsusb 를 사용하면, USB 에 연결된 장치를 확인할 수 있다. 내부적으로는 1 개의 USB 포트를 허브로 5 개로 나누어 쓰고, 그 중 하나는 유선 이더넷에 포함되어 있다.
  • 45.
    리눅스 시스템 종료 리눅스를종료하는 방법을 살펴보자 윈도우를 사용하면 가끔 PC 가 동작을 멈추어서 전원을 껐다 켜는 상황이 발생한다. 이러한 경우 문제가 생겨 윈도우를 재 설치해야 하는 경우가 발생할 수 있고, 중요한 파일이 사라지기도 한다. 리눅스가 탑재된 라즈베리파이도 마찬가지로 시스템을 종료할때 정상 절차를 거치는 것이 매우 중요하다. 전원을 켜는 방법인 USB 전원 선을 연결하는 것처럼 종료하는 것을 선으로 뽑을 수 있으나, 잘못하면 여러분의 소중한 데이터가 날라가 버리게 된다. 여기서는 리눅스를 안전하게 종료하는 방법을 살펴보자 shutdown - 시스템 종료 리눅스 시스템을 정상적으로 종료하는 방법은 shutdown 명령을 사용하는것이다. shutdown 명령은 다른 시스템 종료 명령과는 달리 다양한 종료 방법과 옵션을 제공한다. $ shutdown [옵션] [시간] [메시지] 옵션명 설명 -h 시스템을 종료한다. -k 실제로 시스템을 종료하는 것이 아니라, 사용자들에게 메세지만 전달한다. -r 종료후 재 시작한다 -c 이전에 했던 shutdown 명령을 취소한다 실행 예 즉시 리눅스를 종료한다. $ sudo shutdown -h now ”System is going down” 이라는 메시지를 출력하고, 3 분뒤 종료한다. $ shutdown -r +3 "System is going down" reboot - 시스템 재 시작, halt/poweroff – 시스템 종료 shutdown 명령 외에 시스템을 재 시작하거나 종료하는 명령으로는 reboot 와 halt,poweroff 가 있다.halt 와 poweroff 명령도 내부적으로는 reboot 를 심볼릭 링크를 활용하여 이용한다. 이 명령들은 /var/log/wtmp 파일에 시스템 종료 기록을 남기고 시스템을 종료하거나 재 시작한다. 사용할 수 있는 옵션은 다음과 같다. 옵션명 설명 -w 실제로 재 시작하거나 종료하지 않지만 wtmp 파일에 기록을 남긴다. -f 강제로 명령을실행하며 shutdown 을 호출하지 않는다. -p 시스템의 전원을 끈다. -c 이전에 했던 shutdown 명령을 취소한다
  • 46.
    실행 예 즉시 리눅스를종료한다. $ sudo halt 즉시 리눅스를 재 부팅한다. $ sudo reboot 리눅스 프로세스 관리하기 라즈베리파이에서 리눅스를 실행하며 다량한 명령어를 배워보았다. 사용자가 실행한 명령은 프로세스가 되어 실행된다. 프로세스는 현재 실행 중인 프로그램을 의미하는 것으로, 시스템에는 사용자가 실행한 프로세스 외에도 사용자 관리, 메모리 관리, 네트워크 접속 관리등 다양한 기능을 실행하는 수많은 프로세스가 실행되고 있다. 일반적으로 사용하는 명령은 실행시간이 짧아 실행 후 바로 종료되기 때문에 짧은 시간 동안만 프로세스 상태를 유지하고 있다. ps 프로세스 목록 보기 Process 의 약자로서, 현재 실행중인 프로세스의 목록을 확인할 수 있다. $ ps [옵션] 옵션명 설명 -e 시스템에서 실행중인 모든 프로세스 정보를 출력 -f 프로세스의 자세한 정보를 출력 -u 특정 사용자에 대한 모든 프로세스 정보를 출력 -p PID(Process ID)로 지정된 특정 프로세스 정보를 출력 실행 예 실행중인 프로세스 정보를 상세하게 출력한다. $ ps -f 시스템에서 실행중인 모든 프로세스 정보를 보여준다. $ ps -ef [그림] 프로세스의 상태를 자세히 확인하기 위해서는 –f 옵션을 함께 사용한다.
  • 47.
    실행중인 프로세스의 목록을자세히 확인하기 위해서는 –f 옵션을 함께 사용해야 한다. –f 옵션을 사용하였을 때 세부적으로 나오는 정보는 다음과 같다. 항목 설명 UID 프로세스를 실행한 사용자 ID PID 프로세스 번호 PPID 부모 프로세스 번호 C CPU 사용량 (%값) STIME 프로세스의 시작 날짜와 시간 TTY 프로세스가 실행된 터미널의 종류와 번호 TIME 프로세스의 실행 시간 CMD 실행되고 있는 프로그램의 이름 grep - 특정 프로세스 정보 검색하기 ps 명령만 사용하면, 모든 프로세스 목록이 모두 보이기 때문에, 특정 프로세스의 상태를 확인하기 어렵다. 라즈베리파이에서 본인이 실행시킨 특정 프로세스를 확인해 보고자 할 때가 있다. 이러한 경우는 ps 와 grep 명령어를 조합하여 사용할 수 있다. $ ps -ef | grep [명령] 실행 예 node 라는 이름을 가지고 있는 프로세스를 상세하게 확인한다. $ ps –ef | grep node [그림] grep 으로 현재 동작되는 프로세스 중 특정 프로세스를 찾는 방법이다. 여기서 마지막 줄은 grep 자기 자신이 나오는 것이므로, 현 화면에서 실제로 node 라는 이름으로 동작되는 것은 2 개의 프로세스 이다. 특정 프로세스 정보 검색하기 pgrep pgrep 은 ps 와 grep 을 하나로 통합하여 만든 명령어 이다. ps 와 달리 지정한 패턴과 일치하는 프로세스의 정보를 간략하게 출력한다. $ pgrep [옵션] [패턴] 옵션 설명
  • 48.
    -x 패턴과 정확히일치하는 프로세스의 정보를 출력 함 -n 패턴을 포함하고 있는 가장 최근의 프로세스 정보를 출력한다 -u 사용자 이름 : 특정 사용자에 대한 모든 프로세스를 출력한다 -l PID 와 프로세스의 이름을 출력한다 -t [TERM] 특정 단말기와 관련된 프로세스의 정보를 출력한다 kill,pkill - 프로세스 종료하기 응답이 없거나 불필요한 프로세스를 종료하려면 해당 프로세스의 PID 를 알아야 한다. 앞에서 배운 ps 나 pgrep 명령으로 정보를 확인한 후 kill 이나 pkill 명령을 이용하여 프로세스를 종료한다. kill 인자로 지정한 프로세스에 시그널을 전달한다. $ kill [시그널] pid 시그널 설명 -2 인터럽트 시그널을 보낸다 (CTRL+C 와 같은 효과) -9 프로세스를 강제로 종료한다 -15 프로세스가 관련된 파일을 정리하고 프로세스를 종료한다. 프로세스가 종료되지 않을 수도 있다. 실행 예 프로세스 ID 가 2419 인 프로세스를 강제로 종료한다. $ sudo kill -9 2419 [그림] kill 명령으로 2419 프로세스를 종료한 후 다시 ps 명령으로 확인하면, 프로세스가 종료되었음을 확인할 수 있다. pkill
  • 49.
    Process Kill 의약자로, kill 명령과 같이 시그널을 보내는데, pid 가 아니라 프로세스의 명령 이름(CMD)로 프로세스를 찾아 종료한다. kill 명령어와의 차이점은, 명령이름으로 찾아 종료하므로 여러개가 한번에 종료될 수 있다는 점이다. $ pkill [옵션][패턴] 옵션 설명 -2 지정한 패턴을 명령어 뿐만 아니라 경로명, 옵션, 인자값도 비교 -9 패턴과 일치하는 프로세스의 가장 최근에 실행된 프로세스 하나만 종료 -15 패턴과 정확하게 일치되는 프로세스만 출력 실행 예 node 라는 이름을 가지는 프로세스를 강제로 종료한다. $ sudo pkill node [그림] pkill 을 이용해 이름으로 프로세스를 종료하면, 여러 프로세스를 한번에 종료하는데 유용하다. top 프로세스 관리도구 ps 명령으로는 현재 프로세스 목록을 확인할 수 있다. top 명령은 현재 실행중인 프로세스 정보를 주기적으로 출력하는데, 프로세스의 요약 정보를 상단에 출력하고 각 프로세스의 정보를 하단에 출력한다. $ top
  • 50.
    항목 설명 PID 프로세스id USER 사용자 계정 PR 우선순위 NI NICE 값 VIRT 프로세스가 사용하는 가상 메모리 크기 RES 프로세스가 사용하는 메모리 크기 SHR 프로세스가 사용하는 공유 메모리 크기 %CPU CPU 사용량 %MEM 메모리 사용량 TIME+ CPU 누적 이용시간 COMMAND 명령 이름 동시에 여러가지 작업을 수행해 보자 백그라운드 작업 사용자가 터미널에서 보통 작업을 할때 한번에 하나만 실행이 가능하다. 이는 새로운 명령을 실행하기 위해서는 이전에 실행했던 명령이 끝날때까지 기다렸다가 사용해야 함을 의미한다. 하지만 상황에 따라 여러가지 명령을 한번에 실행해야 하는 경우가 있다. 이럴때 작업을 제어하는 명령을 사용하면 유용하다. 포그라운드 작업
  • 51.
    일반적으로 명령어를 입력하면컴퓨터는 사용자가 입력한 명령을 해석하고 실행하여 그 결과를 출력한다. 사용자는 화면에 출력된 결과를 보고 다시 명령을 입력하는 컴퓨터와 대화하는 방식으로 작업을 수행한다. 이러한 방식을 Forground Process 라고 한다. $ sleep 100 ->sleep 명령이 끝날때까지 기다려야 한다 백그라운드 작업 & 포그라운드 작업은 명령을 한번에 하나씩 실행하므로 동시에 여러개를 실행할 수 없는 문제가 있다. 이럴때 이전과 같이 포그라운드 작업을 진행하게 되면 다른 작업을 실행할 수 없다. 이럴 때 백그라운드로 실행하면 포그라운드에서는 다른 작업을 수행할 수 있다. 백그라운드 작업은 명령의 실행시간이 많이 걸리는 작업을 수행하거나 다른 명령을 동시에 실행하고 자 할때 사용한다. 명령을 수행하기 위해서는 마지막에 & 기호를 붙이면 된다. $ sleep 100 & 작업목록 보기 jobs 현재 실행중인 백그라운드 작업을 볼수 있는 명령어로 모든 백그라운드 작업을 보여준다. 특정 번호를 지정하면 해당 작업의 정보만 보여준다. $ jobs [옵션] 옵션 설명 -l 프로세스 번호를 추가하여 보여준다. 작업 전환하기 포그라운드로 동작중인 서비스를 제어할 수 있다. 옵션 설명 CTRL+Z 또는 stop [%작업번호] 포그라운드로 작업을 잠시 중지한다. bg [%작업번호] 작업번호가 지시하는 작업을 백그라운드로 작업을 전환한다 fg [%작업 번호] 작업번호가 지시하는 작업을 포그라운드 작업으로 전환한다. 작업 종료하기 CTRL + C 포그라운드 작업을 종료할때 CTRL + C 를 이용하면 종료가 된다. 백그라운드 작업은 kill 명령으로 강제 종료해야 한다. kill 명령의 인자로 %작업 번호를 지정하면종료 된다. $ sleep 100 & $ kill %1 로그 아웃 후에도 백그라운드 작업 계속하기 nohup
  • 52.
    백그라운드 작업을 실행한터미널이 종료되거나 사용자가 로그아웃하면 실행 중이던 백그라운드 작업도 종료된다. 하지만 터미널이 종료되도 작업이 완료될때까지 종료되지 말아야 하는 상황이 있는데, 이때 nohup 명령을 사용한다 $ nohup [명령] & 패키지 관리하기 드디어 운영체제가 탑재된 라즈베리파이를 강력하게 사용할 수 있는 패키지 관리 기능이다. 아두이노와 달리 라즈베리파이는 리눅스 기반이기 떄문에 수 많은 소프트웨어를 손쉽게 설치하여 사용할 수 있다. 직접 프로그래밍하지 않더라도 이를 통해 이미 만들어진 다양한 소프트웨어로 다양한 기능을 발휘하도록 할 수 있다. 이러한 소프트웨어는 소스코드 자체로 제공되어서 수동으로 컴파일하여 설치하는 방식이 있고, 바이너리 패키지로 배포되어 설치하여 사용할 수 있는 형태가 있다. 여기에서는 패키지로 관리되는 다양한 소프트웨어를 손쉽게 설치하는 방법을 다룰 것이다. 패키지의 특징 - 바이너리 파일로 되어 컴파일을 할 필요가 없다. - 패키지의 파일들이 관련 디렉터리로 바로 설치된다. - 패키지를 삭제할 때 관련된 파일을 일괄적으로 삭제할 수 있다. - 기존에 설치한 패키지를 삭제하지 않고 바로 업그레이드 할 수 있다. - 패키지의 설치 상태를 검증할 수 있다. - 패키지에 대한 정보를 제공한다. - 해당 패키지와 의존성을 가지고 있는 패키지가 무엇인지 알려준다. 따라서 의존성이 있는 패키지를 미리 설치할 수 있고, apt-get 명령을 사용하면 의존성이 있는 패키지가 자동으로 설치된다 패키지의 이름구성 파일명_버전-리버전_아키텍쳐.deb 항목 설명 파일명 패키지의 성격을 나타내는 파일명이다. 패키지 버전 두 번째 항목은 패키지의 버전을 의미한다. 패키지 리비전 리비전은 원래 소스의 버전이 업그레이드 되지는 않았지만 패키지의 보안. 아키텍쳐 사용하는 시스템 아키텍쳐로 i386 은 인텔을, all 은 시스템과 상관없는 문서나 스크립트 등을 의미함. 라즈베리파이는 ARM CPU 이므로 arm 으로 시작하는 명칭이 붙는다. 확장자 우분투 패키지의 확장자는 .deb 를 사용한다.
  • 53.
    apt-get 소프트웨어 패키지들이 저장된저장소를 업데이트하고 패키지를 설치하거나 제거하는데 사용하는 명령이다. $ agt-get [옵션] [서브 명령] 옵션 설명 -d 패키지를 내려받기만 한다. -f 의존성이 깨진 패키지를 수정하려고 시도한다. -h 간단한 도움말을 출력한다. 서브명령 설명 update 패키지 저장소에서 새로운 패키지 정보를 가져온다. upgrade 현재 설치되어 있는 패키지를 업그레이드 한다. install [패키지] 패키지를 설치한다. remove [패키지] 패키지를 삭제한다. download [패키지] 패키지를 현재 디렉토리에 내려 받는다. autoclean 불완전하게 내려 받았거나 오래된 패키지를 삭제한다. clean 캐시되어 있는 모든 패키지를 삭제하여 디스크 공간을 확보한다. check 의존성이 깨진 패키지를 삭제한다. 사용 예 Linux 패키지 정보를 갱신하여, 설치된 모든 패키지를 업데이트 한다. $ sudo apt-get update $sudo apt-get upgrade MP3 재생 패키지인 mpg321 을 자동으로 설치한다. $ sudo agt-get install mpg321 –y MP3 재생 패키지인 mpg321 을 삭제한다. 상황에 따라 사용자 확인을 받는다. $ sudo apt-get remove mpg321
  • 54.
    [그림] 패키지 설치/삭제시자동 진행을 위해서 –y 옵션을 사용할 수 있다. 패키지 수동 관리하기 - dpkg apt-get 도 내부적으로는 dpkg 를 사용한다. 시스템의 특정 파일이 어느 패키지에 속한 것인지를 확인 하는 기능 등 보다 세부적인 기능을 제공한다. Deb 확장자로 시작하는 패키지를 직접 받은 경우 이를 설치하고 제거하는데 사용할 수 있다. $ dpkg [옵션] [파일명 또는 패키지 명] 서브명령 설명 -l 설치된 패키지의 목록을 출력한다. -l [패키지] 패키지의 설치 상태를 출력한다. -I [DEB 파일] 해당 파일을 설치한다 (sudo) -r [패키지] 해당 패키지를 삭제한다 (sudo) -P [패키지] 해당 패키지와 설정 정보를 모두 삭제한다 (sudo) 사용 예 NODEJS 를 다운로드 받은 후 설치한다. $ sudo wget http://node-arm.herokuapp.com/node_latest_armhf.deb $ sudo dpkg –I node_latest_armhf.deb NODEJS 를 설치 정보와 함께 삭제한다. $ sudo dpkg –r -P node_latest_armhf.deb
  • 55.
    심플하고 쉬운 텍스트에디터 - Nano 리눅스 상에서 문서 편집을 위해 흔히 많이 이용되는 것은 VI 편집기이다. GUI 환경이라면, GEDIT 를 이용할 수 있지만, Linux 는 콘솔이나 터미널 환경에서 많이 이용하기 때문에 대안을 찾아야 한다. vi 의 경우 UNIX 시절부터 이어 내려오는 편집기로 강력한 기느와 다양한 명령을 제공하지만, 처음 접하는 사람들에게는 어렵다. 터미널 환경에서 GEdit 처럼 간단하게 활용할 수 있는 것은 nano 이다. Nano 의 특징 사용하기 쉽다는 점이다. VI 는 키보드 방향으로 커서를 이동할 수 없지만 나노는 커서를 이동할 수 있고, VI 가 편ㅈㅂ과 입력 모드가 나뉘어 있지만 나노는 GUI 환경과 윳하게 사용할 수 있도록 되어 있다. 또한 아래 부분에는 단축키도 표시해 주므로 손쉽게 사용할 수 있다. 전문적인 사용에는 VI 가 탁월하지만 대부분의 일들이 간단한 경우가 많기 때문에 나노로 충분히 처리할 수 있다. 가장 기본적으로 사용하는 방법은, 방향키를 이용하여, 수정이 필요한 위치로 이동하여, 원하는 내용으로 입력한 후 CTRL + X 를 누르고, 저장하겠냐는 질문에 Y 를 입력한 후 Enter 를 클릭하여 저장하고 종료하는 방식이다. 보다 다양한 기능이 필요한 경우 다음 하단의 단축키를 활용하도록 하자. $ sudo nano /etc/rc.local l 단축키 기능
  • 56.
    CTRL+G / F1나노 에디터 사용법(도움말)을 확인할 수 있음 CTRL+X / F2 나노 에디터 종료. 저장하지 않았을 경우 저장할지 여부를 물음 CTRL+O / F3 현재 작성중인 파일 저장 CTRL+W / F6 현재 문서에서 검색. 검색중 정규식 검색모드로 전환하려면 ALT+R 을 누르면 됨 CTRL + ^ ALT+R 문자열을 검색하여 내용을바꿈 ALT+W 이전에 검색했던 검색 결과를 반복 CTRL+R / F4 파일 불러오기. 현재 편집중인 문서에 다른 문서의 내용을 삽입 하는 동작. 불러온 파일의 내용을 변경할수 없음. 나노에서 편 집 중 다른 파일을 불러오려면 나노를 종료하고 명령을 다시 실행해야 함 CTRL+Y / F7 이전 페이지로 이동하는기능, 키보드의 Page Up 키로도 사용 가능 CTRL+V / F8 다음 페이지로 이동하는 기능, 키보드의 Page Down 키로 사용 가능 ALT+ M 문서의 맨 처음으로 이동 함 ALT + M? 문서의 맨 끝으로 이동함 CTRL+SHIFT+^ 혹은 ALT+G 특정 행을 입력하여 특정 행으로 이동할 수 있음 ALT+SHIFT+M / ALT+SHIFT +M ? 현재 행을 들여쓰거나 내어 씀 CTRL + J 한 줄이 현재 터미널의 폭을 벗어날 정도로 긴 행을 폭에 맞추어 정렬 함 이번 장을 정리하며 이번 장에서는 라즈베리파이의 리눅스를 다루는 법을 다루었다. 기본적인 명령어를 다루고, 시스템을 간단하게 모니터링 하는 법을 배웠으며, 리눅스 생태계의 다양한 패키지들을 설치하는 방법을 실습해 보았다. 여기서 배운 텍스트 에디트는 앞으로 설정 편집이나 간단한 프로그래밍에서 유용하게 사용되니, 잘 기억해두도록 한다. 다음 장에서는 본격적으로 자바스크립트를 이용한 NodeJS 프로그래밍에 대해 다룬다. 운영체제가 올라간 라즈베리파이에 직접 웹 어플리케이션 서버를 자바스크립트로 작성하여 활용해 볼 것이다.
  • 57.
    3. JavaScript 로하드웨어 제어를 - NodeJS 세상에서 가장 인기 있는 언어 - Javascript 아직도 자바스크립트가 웹에서 간단히 입력 값을 검사하는 프로그래밍 언으로 생각하는가? 생각을 바꾸어야 할 것이다. 자바스크립트는 이제 세상에서 가장 인기 있는 프로그래밍 언어가 되었다. Github 의 프로젝트 중에 자바스크립트로 구현된 프로젝트가 가장 많고, 해커톤에서 사용되는 가장 인기 있는 프로그래밍 언어가 자바스크립트 이다. 천덕꾸러기 신세였던 자바스크립트는 2005 년 구글로 부터 시작된 Ajax 혁명으로 인하여 보다 중요한 프로그램을 작성하기 위한 언어로 부각되기 시작했다. HTML5 의 부각과 다양해 지는 단말, 다양한 자바스크립트 프레임워크등으로 자바스크립트의 인기는 더욱 높아질 전망이다. [그림] GITHUB 에서 가장 인기 있는 언어는 단연 JavaScript 이다 JavaScript 로 서버와 라즈베리파이 제어를! - NodeJS? NodeJS 는 오픈소스로 공개된 Chrome 브라우저의 자바스크립트 실행 엔진(V8 이라 불리운다)을 기반으로 제작되었다. 확장성 있고 빠르게 어플리케이션을 개발할 수 있도록 도와주는 플랫폼이다. 즉, NodeJS 로 인하여 자바스크립트로 웹 페이지 개발 뿐만 아니라 서버나 하드웨어 제어 용도로도 사용할 수 있다는 의미이다. 이벤트 처리 기반과 Non-Blocking IO 를 이용하여 분산된 Device 상에서 실시간 데이터 통신을 하는 어플리케이션에 최적화되어 있고 경량의 효율적인 프레임 워크이다. 비동기식 이벤트 처리기반 프레임워크로, 확장가능한 네트워크 어플리케이션 개발에 맞추어 디자인 되어 있다.
  • 58.
    쓰레드 기반의 네트워킹은사용하기 어렵고, 데드락 현상으로 시스템 성능이 저하된다는 단점이 있는 반면, NodeJS 는 이벤트 기반으로 block 이 될 일이 없어, 비 전문가도 확장 가능한 시스템을 개발하는데 용이하다는 장점이 있다. [그림] NodeJS 도 결국 JavaScript 로 되어 있다. JavaScript 만 알면, 모바일 어플리케이션부터 서버, 데이터 분석, 하드웨어 제어 그 모든 것이 가능한 현 시대에 가장 활용성 높은 프로그래밍 언어이다. 왜 Javascipt 기반으로 하는가 - NodeJS 의 이점 NodeJS 가 만능은 아니고, 상황에 따라 더 좋은 성능을 보여 주는 언어도 많다. 상당수의 라즈베리파이 관련 예제와 책자들이 파이썬(Python) 으로 구현되어 있는데, 유일하게 이 책에서 JavaScript 로 모든 프로그램을 구성한 것이 의아해 할 것이다. 구현하고자 하는 아이디어가 있을 때 이를 단 한가지 언어로만 만든다고 하면 현재 선택할 수 있는 최선의 언어이다. 필자 역시 전자공학을 전공하고, 회사 일로 C 나 Java 와 같은 언어를 주로 사용하였지만, 메이크 활동을 위해서는 자바스크립트 만을 사용한다. 왜 자바스크립트, NodeJS 를 사용하는지에 대한 이유는 다음과 같다. - 개발자는 하나의 언어로 웹 어플리케이션 부터 IoT 하드웨어까지 개발할 수 있기 때문에 서버와 클라이언트, 모바일 부터 하드웨어 제어까지 넘나드는 비용을 획기적으로 줄일 수 있다. 또한 한 곳에서 제작된 코드는 다양한 플랫폼을 넘나들며 그대로 이용될 수 있어 유지 보수를 매우 간단하게 해준다. 심지어 간단한 데이터 분석 또한 NodeJS 에서 처리할 수 있다. - JSON (JavaScript Object Notation)은 XML 을 넘어서 현재 가장 보편적인 데이터 교환 포맷인데, Javascript 기반을 두고 있어 손쉬운 데이터 처리가 가능하다. - Javascript 는 CouchDB, MongoDB 와 같은 NoSQL 데이터베이스에서 사용되는 언어로 상호작용하기에 아주 적합하다. 몽고디비의 쉘과 질의언어가 자바스크립트이며, Client, Server, DB 까지 동일한 JSON 형식으로 데이터를 사용하기 때문에 데이터 처리가 매우 용이하다. 또한 데이터 분석 및 처리용도로 사용되는 엘리스틱 서치 (Elasticsearch) 또한 JSON 형태로 데이터를 활용할 수 있어 매우 편리하다. - Node 가 사용되는 v8 가상 머신은 ECMAScript 표준을 준수하여, Javascript 의 모든 새로운 기능을 즉시 확인해 볼 수 있다.
  • 59.
    Hello Pi! –NodeJS 설치하기 라즈베리파이에서 NodeJS 를 설치하는 기본적인 방법은 직접 소스코드를 컴파일해서 사용하는 것이다. 하지만, 더 간단한 방법으로 이미 배포판으로 만들어진 설치파일을 받아서 바로 설치하는 것이다. 우리는 배포 판을 받고, 2 장 리눅스 시간에서 다룬 dpkg 명령을 이용하여 NodeJS 를 설치해 보고, Hello Pi 를 작성하여 확인해 보도록 한다. 우선 배포 파일을 wget 명령으로 다운로드 받고, dpkg 명령으로 설치하도록 한다. $ sudo wget http://node-arm.herokuapp.com/node_latest_armhf.deb $ sudo dpkg –I node_latest_armhf.deb NodeJS 가 정상적으로 설치되었는지 확인하기 위해서 설치된 NodeJS 버전을 확인해 본다. $ node –v 버전이 제대로 확인이 된다면 node 를 입력하여 실행한다. Python 과 유사하게 작성한 코드를 실행할 수도 인터프리터 모드에서 하나씩 입력해서 동작을 확인해 볼 수도 있다. 다음과 같은 코드를 입력하여 Hello Pi 가 정상적으로 출력되는지 확인한다. 정상적으로 화면에 출력된다면, 간단한 연산을 수행하여 우리의 프로그램이 정상적으로 동작하는지 확인해 본다. 2 개의 메모리 공간에 숫자를 넣고 이를 더한 결과 값을 제대로 보여 주는 지 확인하는 예제이다. (원래 컴퓨터가 전자계산기에서 출발했다는 것을 생각하자) [그림] NodeJS 버전 확인 및 NodeJS 인터프리터 모드에서 코드를 작성하여 실행하는 예시 console.log('Hello Pi!'); var a = 10; var b = 5; console.log(a + b);
  • 60.
    프로그래밍 방식 선택하기– Nano vs Sublime Text 우리는 라즈베리파이를 메이커 보드로 사용하기 위하여 별도의 GUI 구성을 하지 않고 텍스트들만이 난무하는 콘솔 환경에서 사용하고 있다. 첫 Hello Pi 예제와 같이 인터프리터 모드에서 하나씩 입력해 가면서 실행할 수도 있겠지만, 코드의 길이가 길어지면, 이렇게 하나씩 입력하는 방식으로는 프로그램 수정이나 공유가 어려울 있다. 따라서, 코드를 별도로 작성한 후 그 코드를 실행하는 방식을 대부분 사용한다. 이 라즈베리파이에서 NodeJS 코드를 작성하기 위해서는 Nano 를 이용하여 직접 작성하거나, 당신이 사용하고 있는 컴퓨터에서 작성한 다음, 파일을 FTP 로 전송하여 실행하는 방법이 있을 것이다. (Circulus 를 사용하면 이런 복잡한 문제가 해결되는데, 부록에서 다루도록 하겠다) 우선 첫 번째 실행한 예제를 Nano 로 작성한 후 실행해 보도록 한다. 작성이 완료되면 CTRL+X 를 누르면, 저장하겠냐고 묻는데, Y 를 누르고 엔터를 두번 눌러 주면 저장이 완료된다. $ sudo nano sample1.js 저장된 sample1.js 를 실행해 볼 차례이다. NODE 명령어 뒤에 간단히 파일명만 입력하는 것으로 실행이 되고, 결과가 화면에 보여지게 된다. $ node sample1.js
  • 61.
    [그림] 실행 결과가인터프리터에 직접 입력한 것 과 동일하게 정상적으로 출력되는 것을 확인할 수 있다. 아무래도, 윈도우나 스마트 폰을 사용하던 환경에서 마우스 없이 직접 입력해서 프로그래밍 하는 것은 쉽지 않은 일이다. 좀더 편리하게 컴퓨터에서 자바스크립트 코드를 작성하는 경우, 사랑 받는 프로그램 중 하나가 Sublime Text 이다. 무료 버전을 사용하면 저장 시에 가끔씩 구매해 달라는 팝업이 뜨는 것 이외에 기능 제약이 없는 프로그램이다. Sublime Text 를 사용하고자 하면 다음의 주소에서 다운로드 받도록 한다. 다운로드 주소 > http://www.sublimetext.com/3 [그림] Sublime Text 3 는 강력하며 빠르고, 게다가 무료이다. Sublime Text 에서 저장된 코드를 FTP 로 라즈베리파이에 옮긴 후, 실행하게 되면 동일한 결과를 확인할 수 있다. 자바스크립트로 프로그래밍 시작하기 자바스크립트는 타 프로그래밍 언어에 비해 자유도가 높은 언어로 알려져 있다. 사용하기 쉬워 하는 프로그래머와 어려워 하는 프로그래머로 나뉜다. 여기서는 모바일과 하드웨어 제작을 위해, 기본적인 자바스크립트 사용법을 다룬다.
  • 62.
    기본 연산자 자바스크립트를 이용하여연산 할 때 사용하는 문법으로, 숫자 형인지 문자형인지에 따라 결과값이 달라진다. 증가연산자의 위치에 따라서 실행 전과 후의 결과 값이 달라지는 것을 확인하자. [ js1.js ] // 숫자를 더한 값을 출력한다. console.log(100 + 1); // 문자와 숫자를 더한 값을 출력한다. console.log('100' + 1); var a = 10; // a 에 저장된 숫자값을 연산하여 출력한다. console.log(a + 1); console.log(a); console.log(a++); console.log(a); console.log(++a); console.log(a); 증가연산자가 뒤에 있는 경우 (a++) 곧바로 출력시 값이 변경되지 않지만, 그 이후에 값을 출력시에 1 이 증가됨을 알수 있다. 반대로 앞에 있는 경우(++a), 이미 1 이 증가된 상태로 값이 출력됨을 알수 있다. [그림] 숫자와 문자의 덧셈 연산의 결과가 다르다. 비교 연산자 비교 연산자는 좌변과 우변의 값을 비교하여 그 결과를 반환하는 문법이다. 이항 비교연산자인 ==, != 은 값만을 비교하나, 삼항 비교연산자인 ===, !== 은 데이터 형태도 비교하므로, 예기치 못한 동작을 방지하기 위해서 삼항 연산자를 사용하는 것이 바람직 하다. [ js2.js ] // 이항 연산자로 비교한 값을 출력한다. console.log(1 == true); console.log(1 == '1'); // 삼항 연산자로 비교한 값을 출력한다. console.log(1 === true);
  • 63.
    console.log(1 === '1'); //x 값을 비교하여 true 이면 bigger 를 false 면 smaller 를 출력한다. var x = 90; console.log((x > 90) ? 'bigger':'smaller'); 이항 연산자와 달리 삼항 비교 연산자는 값이 동일하지 않은 경우 false 로 비교 값이 반환됨을 확인할 수 있다. 이후 if 조건문을 다루겠지만, ? 을 이용하여 한줄로 비교 작업을 간단하게 할 수 있음을 확인하도록 한다. [그림] 삼항 연산자는 데이터 형태도 비교한다. 논리 연산자 논리 연산자는 복수의 조건 식을 비교하여 최종적으로 그 값이 true 인지, false 인지를 반환한다. 앞에서 살펴본 비교 연산자를 사용하여 사용하며, 조합을 통해 복잡한 조건식을 표현할 수 있다. [ js3.js ] var x = 1; var y = 2; // 좌항과 우항이 둘다 true 일때 true 가 출력된다. console.log(x == 1 && y == 1); // 좌항과 우항중 하나만 true 이면 true 가 출력된다. console.log(x == 1 || y == 1); && 의 경우, and 연산을 하여 좌항과 우항의 값이 모두 true 일 경우 true 를 반환하지만, || 의 경우는 or 연산을 하여 좌항과 우항 중 한 개만 true 인 경우 true 값을 반환한다. [그림] 한 개만 조건을 만족하는 경우는 or(||) 연산을, 모든 조건을 만족해야 하는 경우는 and(&&) 연산을 사용하도록 한다. IF 조건문
  • 64.
    여러가지 상황에서 조건에따라 이에 대응하는 명령을 실행할 수 있다. If 단독으로 사용할 수 있지만, else if 와 else 등으로 세분화 하여 분기할 수 있다. [ js4.js ] var x = 15; // 30보다 같거나 크면 Bigger than 30 이 출력된다. if( x >= 30){ console.log('Bigger than 30'); // 30보다 작고 10보다 크면, Bigger than 10 이 출력된다. } else if ( x > 10) { console.log('Bigger than 10'); // 10보다 값이 작으면 Smaller than 10 이 출력된다. } else { console.log('Smaller than 10'); } 첫번째 조건을 만족하지 못하고, 두번째 else if 조건을 만족하여 해당 결과를 반환함을 알 수 있다. 이처럼 중첩을 통해 다양한 조건에 따라 분기하여 원하는 명령을 수행할 수 있다. [그림] 입력한 조건 값을 비교하여, 원하는 작업으로 분기할 수 있다. SWITCH 조건문 동일 변수에 대한 비교시 사용할 수 있는 조건문으로, case 블록에 해당하는 값이 존재시 해당 블록 명령을 실행하고, 만족하는 case 가 없는 경우 default 블록을 호출하는 직관적인 구조를 가지고 있다. [ js5.js ] var rank = 'B'; // rank 에 저장된 값에 따라 출력된다. switch(rank){ case 'A': console.log('A Rank'); break; case 'B': console.log('B Rank'); break; case 'C': console.log('C Rank'); break; default: console.log('Not ranked'); }
  • 65.
    if 조건문이 다양한조건 식을 넣을 수 있는 반면, 값 비교에 따른 분기는 switch 조건문이 보다 명확하게 흐름이 식별된다. Break 문을 빼게 되면, 그 다음의 case 명령 값도 실행되게 되니 주의하도록 한다. [ 그림 ] 단순 값 비교에는 IF 비교문 보다 SWITCH 비교문이 명확하다. WHILE 반복문 주어진 조건에 따라 반복 처리하고자 하는 경우 사용하는 명령어이다. [ js6.js ] var x = 0; // x 가 5보다 작을 때 괄호 안 명령이 계속적으로 수행된다. while(x < 5){ console.log('X is ' + x); x++; } 예제 프로그램은 x 값이 5 보다 작을 경우, x 값을 출력해 주고, x 값을 1 씩 증가하는 코드이다. 4 까지 증가되는 값을 반복 출력하고, 종료 됨을 확인할 수 있다. [ 그림 ] 조건에 따른 반복을 수행할 경우, while 문을 사용한다. FOR 반복문 정해진 횟수만큼 반복 처리하기 하기 위해 사용하는 명령어 이다. 일반적인 경우 for 명령을 사용하지만, 지정된 배열이나 객체에서 선두부터 반복 처리하기 위해서는 for in 명령을 사용한다. [ js7.js ] var obj = { key1 : 'Pi', key2 : 'Raspberry', key3 : 'NodeJS'}; var arr = ['Pi','Raspberry','NodeJS']; // obj 객체에 있는 키 값이 있을 때 까지 반복한다. for(var key in obj){ console.log(key + ' : ' + obj[key]); }
  • 66.
    // arr 배열의길이 만큼 반복하여 배열에 있는 값을 순차적으로 출력한다. for(var i = 0 ; i < arr.length ; i++){ console.log(i + ' : ' + arr[i]) } 기본 for 문의 경우, 특정 횟수 만큼 반복하는 의미가 있는 반면, for in 은 특정 객체에서 하나씩 꺼내 온다는 의미가 있다. 배열이 아닌 객체에서 하나씩 값을 가져와야 하는 경우 유용하게 쓰일 수 있다. [ 그림 ] 객체의 값을 확인하기 위해서는 for in 을 활용해야 한다. STRING 객체 문자열을 취급하기 위한 객체로서, 문자열의 추출이나 가공, 검색등의 기능을 사용할 수 있다. [ js8.js ] var str = 'Good day to study about javascript'; // study 단어의 위치를 알려준다. console.log(str.indexOf('study')); // 문장의 5번째 문자를 출력한다. console.log(str.charAt(5)); // 5번째 문자부터 3개를 추출한다. console.log(str.substr(5,3)); // 5번째 문자부터 8번째 문자까지 추출한다. console.log(str.substring(5,8)); // 문자열을 공백으로 구분하여 잘라내 배열로 반환한다. console.log(str.split(' ')); indexOf 함수를 이용하여 특정 문자열의 위치를 파악하고, charAt 함수를 통해 특정 위치의 단어를 가져올 수 있다. Substr 과 substring 모두 문자열을 잘라내는 역할을 하지만, substr 이 시작 지점으로부터 몇 개의 문자를 가져오는 지를 지정하는 반면, substring 은 시작지점과 종료 지점을 지정하여 문자열을 가져오는 차이가 있다. Split 함수는 특정 문자로 문자열을 잘라내어 배열 형태로 반환하는 역할을 한다.
  • 67.
    [ 그림 ]substr 과 substring 은 문자열을 잘라내는 역할을 하지만, 사용방법에 차이가 있다. ARRAY 객체 배열 형의 값을 취급하기 위한 객체로서, 요소의 추가, 삭제, 결합, 정렬등의 기능을 제공한다. [ js9.js ] var arr = ['Tomato','Banana','Raspberry']; var arr2 = ['Apple','Melon','Graps']; // arr 배열의 가장 마지막 값을 꺼낸다. console.log(arr.pop()); console.log(arr); // arr 배열의 끝에 Strawberry 를 추가한다. console.log(arr.push('Strawberry')); console.log(arr); // arr 배열의 가장 앞 값을 꺼낸다. console.log(arr.shift()); console.log(arr); // 배열의 0,1,2 위치에서 1번 위치의 Banana 를 꺼낸다. console.log(arr.splice(1)); console.log(arr); // arr 배열과 arr2 배열을 합친다. console.log(arr.concat(arr2)); // arr2 배열을 정렬한다. 숫자의 경우 크기, 문자의 경우 알파벳 순으로 정렬된다. console.log(arr2.sort()); Pop 함수는 배열의 마지막 값을, shift 는 첫번째 값을 가져오는 역할을 한다. Push 함수는 배열의 마지막에 값을 추가하고, splice 함수는 배열의 특정 위치에서 값을 잘라낸다. Concat 함수를 이용하여 배열을 합치고, sort 를 이용해 배열내 값을 정렬한다. [ 그림 ] pop 은 배열의 가장 마지막 값을, shift 는 가장 첫번째 값을 반환한다.
  • 68.
    함수 주어진 입력을 이용하여,어떠한 작업을 처리하여 그 결과를 돌려주는 구조를 함수라 한다. 일반적으로 프로그래밍을 할때, 공통적으로 자주 사용하는 코드를 함수로 묶어 재활용한다. STRING 객체에서 사용한 indexOf 나 ARRAY 객체의 pop 같은 경우가 내장된 기본 함수이다. 사용자는 자기 스스로 함수를 정의하여 사용할 수 있다. [ js10.js ] var triangle = function(width, height){ return width * height / 2; } // triangle 이라는 함수에 10과 2를 전달하여 결과를 출력한다. console.log('Triangle : ' + triangle(10, 2)); 에제는 삼각형의 넓이를 구하는 함수이다. 가로와 세로 길이를 입력 받고, 이를 계산하여 값을 반환한다. 프로그램 내에서 자주 사용되는 코드는 이처럼 함수로 정의하여 사용할 수 있다. 앞으로 자주 보게 되므로, 잘 파악해 두도록 하자. [ 그림 ] 함수는 자주 사용하는 프로그램 코드의 묶음이라고 생각하면 된다. 라즈베리파이와 모바일의 연동 - 서버와 클라이언트 자바스크립트에 대해 간단히 살펴보았다. 우리가 하려는 일은 라즈베리파이를 전자계산기로 이용하는 것이 아닌, 라즈베리파이에 오디오를 만들고, 이를 모바일로 제어하는 것이다. 이를 구현하기 위해 많이 이용하는 서버와 클라이언트 모델을 이용하고자 한다. 이 모델은 성능이 좋은 컴퓨터를 서버로 구성하고, 가정용 컴퓨터나 개인 스마트 폰이 클라이언트가 되어, 서버에서 처리된 결과를 클라이언트에서 받는 구조이다. 이 구조에서 우리는 서버가 라즈베리파이가 되어 데이터 처리도 담당하고 센서와의 연동도 담당하게 되는 것이다. 이렇게 라즈베리파이에서 처리된 결과를 모바일로 받고, 반대로 모바일에서 서버 역할을 하는 라즈베리파이로 제어명령을 가지는 구조를 만들게 될 것이다. 우리는 이 작업을 NodeJS 를 이용하여 JavaScript 로 구현할 것이다.
  • 69.
    [그림] Server- Client모델 / 출처 : https://en.wikipedia.org/wiki/Client%E2%80%93server_model#Advantages 간단한 웹 서버를 만들어 보자 - http 웹 서버를 만드는 것은 그리 쉬운 일이 아니다. 국내에서 가장 많이 사용되는 Java 기반으로 만든다고 할 때, Apache 나 Tomcat 같은 웹 서버 혹은 웹 어플리케이션 서버가 있어야 하고, 서버로 동작하기 위한 복잡한 스펙에 맞추어서 프로그래밍을 수행해야 한다. Java 에서 많이 사용되는 Spring Framework 등을 이용해서 만든다고 하더라도, 처음 접근하는 사람이 쉽게 따라가기에는 어려움이 많다. 이렇게 Application 을 만드는 것은 손쉬운 일이 아니다. 하지만 Node JS 를 활용하면 Hello World Web Application 을 매우 손쉽게 개발할 수 있다. 이렇게 쉬울 수가 없을 것이다. 다음의 코드를 sample2.js 로 작성한 후 실행해 보도록 한다. [ sample2.js ] // 내장 모듈인 http 모듈을 사용할 수 있도록 불러온다. var http = require("http"); // 8080 포트로 서버를 생성한다. 사용자가 웹 브라우저로 서버에 접근시, callback 함수에 정의되어 있는 Hello Pi 가 사용자 브라우저에 출력된다. http.createServer(function (req, res) { res.writeHead(200, {'Content-Type': 'text/plain'}); res.end('Hello Pi!n'); }).listen(8080); // 구동이 완료되면 hello Pi 가 출력된다. console.log('Hello Pi!'); 실행하면 Hello Pi 라고 떡 하고 나오고 아무런 반응이 없다. 첫 번째 실행한 예제와 다른 점은, 바로 실행이 종료되느냐 그렇지 않느냐라는 점이다. 자세히 보면, 프롬프트(Prompt)가 Hello Pi!
  • 70.
    출력 이후 빈공간에 있는 것을 알 수 있다. 이는 프로그램이 종료하지 않고 작업을 대기 중인 상태를 의미하는 것이다. (CTRL+C 키를 누르면 물론 종료가 가능하다.) [그림] 프로그램이 종료되지 않고 서버로 동작하여 대기 중인 상태이다. 8080 포트를 이용하여 웹 서버가 라즈베리파이에서 잘 동작하고 있다. 이제 정말 서버의 역할을 제대로 하고 있는지 확인할 차례이다. 여러분이 가지고 있는 스마트 폰을 라즈베리파이가 접속한 무선 AP 와 동일한 WiFi 에 연결하도록 한다. [그림] 스마트 폰을 라즈베리파이와 동일한 무선 AP 에 연결하고, 라즈베리파이의 IP 주소에 포트번호를 입력하면, 웹 서버가 정상적으로 동작하는 것을 확인할 수 있다. 다시 한번 우리의 최종 목표를 생각해 보자. 스마트 폰 으로 제어하는 오디오이다. 이렇게 라즈베리파이에 서버로 구현하고, 스마트 폰으로 라즈베리파이를 제어하는 신호를 보내면, 서버에서는 그 응답 값에 맞는 음악을 재생하거나, 시간을 보여 주거나, 날씨를 알려 주는 행위를 하면 되는 것이다.
  • 71.
    만일 스마트 폰이없다면, 원격으로 접속한 컴퓨터에 웹 브라우저를 연 후 동일하게 접속해 보면 역시나 같은 결과를 확인할 수 있다. [그림] 컴퓨터의 웹 브라우저로도 동일한 결과를 확인할 수 있다. 파일을 나누어 프로그래밍 하기 - Exports 어플리케이션을 만들면, 모든 코드가 하나의 파일에 집중되어 추후 소스 수정이나 관리가 어려운 경우가 있다. 이럴 때 많은 코드를 담은 파일을 관련있는 그룹으로 나누어 여러개의 파일로 분리하는 방식을 사용한다. Node 의 경우 exports 를 이용하여 파일을 분리할 수 있다. Exports 를 이용한 분리 방법 중 대표적인 두 가지 방법을 실습해 보고자 한다. 이번에는 총 3 개의 파일로 sample3.js 는 웹 서버 역할을 하고, sample3-1.js 는 덧셈기, sample3-2.js 는 평균값을 계산하는 프로그램을 작성하여 수행하고자 한다. [ sample3.js ] var http = require('http'); var calc = require('./sample3-1'); var avg = require('./sample3-2'); // 8080 포트로 웹 서버가 구동되며, 접근시 calc 에 정의된 sum 함수와 avg 에 정의된 함수가 수행되어 결과를 출력한다. http.createServer(function (req, res) { res.writeHead(200, {'Content-Type': 'text/plain'}); res.end(' SUM ' + calc.sum(10,5) + ' AVG ' + avg(6,8) + 'n'); }).listen(8080); console.log('Hello Pi!'); [ sample3-1.js ] // sum 이라는 함수를 만들어 타 모듈에서 사용할 수 있게 한다. exports.sum = function(a,b){ return a+b; } [ sample3-2.js ] var avg = function(a, b){ return (a + b) / 2; } // 타 모듈에서 사용할 수 있도록 등록한다. module.exports = avg;
  • 72.
    실행은 최종적으로 sample3.js를 실행하도록 한다. $ node sample3.js [그림] 모듈로 분리한 덧셈, 평균 값이 정상적으로 잘 출력됨을 확인할 수 있다. 실행 후 결과가 잘 나오는 것을 웹으로 접근하여 확인할 수 있다. 이러한 파일 분류로 앞으로 프로그램을 작성시에 코드 량이 많아 지면, 적절하게 모듈화 하여 구성하도록 하자. 복잡한 웹 서버 기능을 간편하게 - ExpressJS Node JS 에서 웹 어플리케이션을 개발 할때 가장 많이 사용되는 프레임 워크로, 빠르고 간결하게 개발하는데 도움을 준다. 웹 과 모바일을 개발하는데 유연하고 다양한 기능들을 지원하며, 무수한 HTTP 관련 기능들을 이용하여 API 들을 손쉽고 빠르게 구현할수 있는 장점이 있다. 웹 어플리케이션을 위한 간결한 계층 구조를 제공함으로써, 기본 NodeJS 의 복잡한 기능들을 알지 못해도 곧바로 웹 어플리케이션을 구축할 수 있다. [그림] NodeJS 에서 손쉽게 다양한 웹 어플리케이션 구성요소를 간단하게 개발할 수 있게 도와주는, 가장 인기있는 프레임워크 이다. 처음 사용한 http 모듈의 경우, NodeJS 에서 기본적으로 제공하여 별도의 설치가 필요 없었지만, ExpressJS 의 경우 외부 모듈로서 별도의 설치가 필요하다. 리눅스에서 사용했던 apt- get 과 같이 NodeJS 에서도 간단하게 패키지를 관리하는 NPM(Node Package Module)을 제공하여 손쉽게 외부 모듈을 관리할 수 있도록 도와준다. Express 를 설치하기 위해서는 다음의 명령어를 실행하도록 한다. $ npm install –g express
  • 73.
    [그림] NPM 을이용하여 손쉽게 NodeJS 외부 확장 모듈의 설치/업데이트/삭제가 가능하다. ExpressJS 를 설치할 때 관련된 하위 모듈도 NPM 에서 알아서 설치해 준다. 처음 만들었던 Hello Pi 를 ExpressJS 로 만드는 코드는 다음과 같다. [ sample4.js ] // express 모듈을 불러오고 곧바로 실행시킨다. var app = require('express')(); // 사용자가 브라우저로 접근하면 Hello Pi 가 출력된다. app.get('/', function(req, res){ res.send('Hello Pi!'); }); // 8080포트로 서버를 생성한다. app.listen(8080); 웹 브라우저로 결과를 확인하면, http 로 만든 것과 동일한 결과가 나오게 된다. 사실 http 서버로 만드는 것도 매우 간단하기 때문에 이 예제만으로는 무엇이 편리한 건지 알기가 어려울 것이다. 좀더 복잡한 에제를 구현해 보도록 하자. 접근하는 웹 주소로 입력받는 파라미터 입력 값에 따라 나오는 결과를 JSON(JavaScript Object Notation) 형식으로 보여주는 것이다. 이는 웹기반 서비스를 만들 때 사용하는 방식으로 과거에는 XML(eXtensible Markup Language)으로 쓰였으나 현재는 대부분 JSON 방식을 사용하고 있다. JavaScript Object 형태를 띄고 있어서, 서버에서 결과를 제공해 주면 별다른 가공 필요없이 클라리언트에서 그대로 활용이 가능하다. 또한 많이 MongoDB 나 CouchDB 은 데이터베이스, 데이터 처리를 위한 Elasticsearch 등에서도 JSON 형식으로 데이터를 보관하고 있기 때문에 JSON 방식으로 데이터를 주고 받으면
  • 74.
    클라이언트부터 DB 까지변환 없이 아름답게 데이터를 주고받는 작업이 가능하다. JSON 으로 결과를 내려주는 방법이 얼마나 간단한지 확인해 보자. [ sample5.js ] var app = require('express')(); // 사용자가 서버에 접근하면, index.html 파일을 반환한다. app.get('/', function(req,res){ res.sendFile(__dirname + '/index.html'); }); // /req 경로로 접근시, req 경로 이후의 id 값을 JSON 형식으로 반환한다. app.get('/req/:id', function(req,res){ var object = { param : req.params.id}; res.json(object); }); // 8080으로 서버를 생성한다. app.listen(8080); // 서버 생성 완료 후 Hello Pi 문장과 서버 구동 경로를 출력한다. console.log('Hello Pi! : ' + __dirname); [ index.html ] <script> var sum = 10 + 5; // 브라우저 화면에 sum 결과를 Hello Pi 문자와 함꼐 출력한다. document.write('Hello Pi! : ' + sum); </script> 이렇게 웹 서버를 구축하고, 웹 페이지와 웹 서비스를 호출하여 결과를 확인한다. 이제 주소를 치면, 작성한 자바스크립트가 클라이언트에서 실행되어 결과를 보여준다. /req/ 로 호출하면서 뒤에 값을 적어주면, 해당 값을 JSON 형태로 반환하는 것도 확인할 수 있다. [그림] 받아진 웹 페이지를 클라이언트에서 계산한 결과와 서버에서 전달 받은 값을 JavaScript 객체로 보관했다 이를 그대로 JSON 타입으로 반환한 예 어떤가? 우리는 웹 페이지와 서비스를 제공하는 서버를 단지 몇 줄만으로 간단하게 구현하였다. 만일 이 예제를 자바로 구현하였다고 하면 준비할 것과 작성해야 할 량도 많았을 뿐더러, 클라이언트는 자바스크립트로, 서버는 자바로 언어가 분리되기 때문에 두 언어를
  • 75.
    번갈아 가며 구현해야하는 반면, 여기서는 동일한 언어로 서버와 클라이언트를 쉽게 구현함을 알 수 있다. 우리는 이 예제 에서 get 방식을 이용한 데이터 통신을 진행하였는데, get 방식 이외에 다양한 방식으로 데이터를 주고 받을 수 있다. Post, put, delete 인데 이 4 가지 메소드를 근래 REST API, 즉 외부에서 정보를 주고 받는 API 를 만들 때 많이 사용하는 방식이다. DB 에서 사용하는 개념인 CRUD 즉, Create, Read, Update, Delete 의 개념을 이 4 가지 메소드에 접목하여 구현하는데, 권장하는 4 가지 메소드의 사용 용도는 다음과 같다. 메소드 get post put Delete 용도 읽기 (read) 생성 (create) 수정 (update) 삭제(delete) 주의 깊게 살펴본 사용자라면, 반환되는 함수에 있는 두 인자인 req, res 가 요청(Request)과 응답(Response)의 약자임을 알 수 있었을 것이다. 요청은 전달 받는 인자 값을 가져오고, 응답을 이용해 JSON 데이터를 보내거나 다운로드 받는 동작을 할 수 있도록 지원한다. ExpressJS 의 요청과 응답 객체에서 사용할 수 있는 대표적인 값들은 다음과 같다. 종류 객체 설명 요청(Request) req.params 이름 붙은 라우트 매개 변수 값을 담고 있는 배열 Req.query GET 매개변수(QueryString) 값을 담고 있는 객체 Req.body POST 매개변수 값을 담고 있는 객체 Req.route 현재 일치하는 라우트 정보 Req.ip 클라이언트의 IP 주소 Req.path Protocol, Host, Port, Querystring 을 제외한 요청 경로 Req.host 클라이언트의 HOST 이름 Req.url Protocol, Host, Port 를 제외한 Querystring 과 요청 경로 응답(response) Res.redirect 브라우저 주소를 다른 주소로 재 지정 Res.send 클라이언트에 응답을 보냄 Res.json 클라이언트에 JSON 데이터를 보냄 Res.jsonp 클라이언트에 JSONP 데이터를 보냄 Res.download 컨텐츠를 보여주지 않고 다운로드 받게 함 Res.sendFile 파일을 읽어 컨텐츠를 클라이언트에 전송 함 Res.render 템플릿 엔진을 사용하여 뷰를 렌더링 함 빠르게 ExpressJS 예제 생성하기 – Express Generator Express Framework 로 웹어플리케이션을 빠르게 만드는 방법은, 지원하는 템플릿 생성기를 이용하여 페이지를 만드는 것이다. 다음과 같이 node js 설치 이후에 npm 을 이용하여 express-genertator 를 설치하도록한다.
  • 76.
    $ npm installexpress-generator -g express-generator 가 설치 된 이후에는 express 명령어를 통해 Application 템플릿을 만들 수 있다. express 명령 이후에 만들고자 하는 application 이름을 입력하면 해당 이름으로 템플릿 어플리케이션이 생성된다. $ express myapp [그림] Express Generator 로 샘플을 생성하면, 간단하게 템플릿을 생성해 준다. 생성이 되고 나면, 연동되어 있는 모듈 설치 방법(install dependencies)과 실행 방법(run the app)을 알려 준다. 생성된 어플리케이션을 위해 express 이외에 추가적으로 필요한 패키지를 설치해 주어야 한다. 원래는 npm 명령을 이용하여 일일히 설치해야 겠지만, 생성된 Application 폴더로 이동하여, npm install 명령을 입력해 주면 템플릿을 위해 필요한 패키지들을 자동으로 설치해 준다. $ cd myapp $ npm install
  • 77.
    [그림] 자동으로 생성된템플릿에 연관되어 있는 모듈을 자동으로 설치해 준다. 이렇게 모두 설치가 마무리 되면, 실행하는 일만이 남았다. 이전까지는 node 로 자바스크립트 파일을 직접 실행하는 구조였으나, Express Generator 로 생성된 템플릿은 npm start 명령으로 프로그램을 구동할 수 있다. $ npm start [그림] npm start 로 구동된 서버 Express 로 서버를 구동하는 것은 간단하게 할 수 있다. 실시간 웹 서비스를 만들어 보자 - Socket.IO Socket.IO 는 ExpressJS 와 더불어 오늘날 Node,JS 를 가장 핫하게 이끌어준 프레임워크 중 하나이다. ExpressJS 가 웹 어플리케이션을 제작하는데 도움을 주었다면, Socket.IO 는 어플리케이션 상에서 실시간 양방향 커뮤니케이션을 손쉽게 구현할 수 있도록 도와 주는 프레임 워크이다. 과거 이러한 실시간 통신을 구현하기 위해서는 소켓(Socket) 통신이라고 하여
  • 78.
    TCP/UDP 와 관련된복잡한 통신 방식을 직접 구현해서 쓰고, 불 안정한 인터넷 망을 이용 시에 대한 복잡한 처리 모드 개발자의 몫 이였다. 하지만, NodeJS 의 Socket.IO 의 등장으로 사용 환경에 구속 받지 않고 모든 플랫폼, 브라우저, 장치에서 동작이 가능하고, 신뢰성과 속도 모두를 보장 한다. 실시간 채팅이나 공동 문서 협업, 실시간 분석 및 사진, 서버 알람(Push), 이미지나 비디오등의 스트리밍 전송에 사용될 수 있는 프레임 워크이다. 무엇보다 서버와 클라이언트의 코드가 모두 동일한 JavaScript 로 구현되어 소스의 이해 및 유지 보수가 간편하다는 것이 최대의 장점이다. [그림] NodeJS 의 최대 장점 중 하나는 Socket.IO 를 이용하여 채팅과 같은 실시간 통신을 구현하기 매우 쉽다는 점이다. 우선 Socket.IO 를 사용하기 위해서는 모듈을 설치해 주어야 한다. $ sudo npm install –g socketio Hello Socket.IO ExpressJS 와 Socket.IO 를 활용하여 간단하게 서버와 클라이언트간에 Hello World 를 실시간으로 주고 받는 서비스를 쉽고 빠르게 만들어 볼 수 있다. [ sample6.js ] var app = require('express')(); // http 서버를 생성한다. var server = require('http').Server(app); // 생성한 서버에 Socket.IO 서버를 구성한다. var io = require('socket.io')(server); app.get('/', function (req, res) { res.sendFile(__dirname + '/sample6.html'); }); // Socket.IO 로 접근시 호출되며, news 라는 이름으로 접속된 소켓에 객체를 전달한다. io.on('connection', function (socket) { socket.emit('news', { hello: 'world1' }); // feedback 이라는 명칭으로 이벤트 수신시, 데이터를 콘솔에 출력한다. socket.on('feedback', function (data) { console.log(data); }); }); server.listen(8080);
  • 79.
    console.log('Hello Socket.IO'); [ sample6.html] <script src="/socket.io/socket.io.js"></script> <script> // 브라우저 상에서 소켓을 생성한다. var socket = io(); // news 라는 이벤트를 받았을 때 호출된다. socket.on('news', function (data) { // 브라우저 상에서 소켓을 생성한다. document.write(JSON.stringify(data)); socket.emit('feedback', { hello: 'world2' }); }); </script> 일단 서버쪽 Socket.IO 의 연결을 위한 구성부가 서버쪽에 추가되었음을 볼 수 있다. Connection 이벤트로 접속이 성공된 것을 감지하면, Emit 함수를 이용하여 news 라는 이벤트 명으로 JavaScript Object 데이터를 전달하게 된다. 전달된 이벤트는 클라이언트 쪽에서 news 라는 이벤트를 감지하고, 받아진 JSON 데이터를 문자화 하여 화면에 보여준 후, 반대로 전달된 서버에 feedback 이라는 이벤트 명으로 데이터를 전송하게 된다. 이렇게 전송된 데이터는 서버 쪽에서 feedback 이벤트로 감지된 결과를 화면에 보여 주게 된다. [그림] 서버가 구동한 후, 이벤트를 전송하고 나서 모바일로 부터 feedback 이벤트로 { hello : world2 } 데이터를 전송 받았다. [그림] 웹 페이지가 열리고, 서버로부터 받은 news 이벤트로 { hello : world1 } 데이터를 받고, 서버로 { hello : world2 } 데이터를 전송 한다. Smart Phone 부터 PC 까지 가능한 초 간단 채팅 서버 구현
  • 80.
    NodeJS 를 기반으로ExpressJS 로 웹 서버를 구성하고, Socket.IO 와 연동되는 실시간 데이터 통신을 진행해 보았다. Socket.IO 를 이용하여 많이 사용하는 기능 중 하나가 채팅 기능일 것이다. 이는 지능형 서비스를 만들 때 꽤나 유용하게 사용될 수 있다. 라즈베리파이를 서버로 사용하면서, 채팅 서비스를 만든다면, LG 사의 IoT 제품같이 채팅으로 명령을 하면, 실시간으로 그에 맞추어 동작하는 IoT 제품을 우리도 손쉽게 만들 수 있다는 것을 의미한다. 여기서 간단하게 채팅 서버를 구현해 보도록 하자. 메시지를 보내는 방식에 따라 어떠한 결과가 나오는지 잘 살펴보도록 한다. 클라이언트 구현을 위해 이제까지는 순수한 JavaScript 를 활용했지만, 이번에는 JavaScript 를 쉽게 사용할 수 있게 해 주는 jQuery 를 이용하여 웹상에서 일어나는 동작을 좀더 간단하게 만들 것이다. [ sample7.js ] Var app = require('express')(); var server = require('http').Server(app); var io = require('socket.io')(server); app.get('/', function (req, res) { res.sendFile(__dirname + '/sample7.html'); }); io.on('connection', function (socket) { socket.emit('msg', { id : 'hi', msg : 'Chat is ready!' }); // msg1 이벤트 발생시, 접속되어 있는 소켓중 보낸 소켓을 제외한 소켓에 데이터를 전송한다. socket.on('msg1', function (data) { socket.broadcast.emit('msg', data); }); // msg2 이벤트 발생시, 보낸 소켓에 다시 데이터를 전달한다. socket.on('msg2', function (data) { socket.emit('msg', data); }); // msg3 이벤트 발생시, 모든 소켓에 데이터를 전달한다. socket.on('msg3', function (data) { io.emit('msg', data); }); }); server.listen(8080); console.log('Socket.IO Chat'); [ sample7.html ] <html> <head> <title>Hello Pi!</title> <script src="https://code.jquery.com/jquery- 2.1.4.min.js"></script> <script src="/socket.io/socket.io.js"></script> <script> $(function(){ var socket = io();
  • 81.
    // 0 부터10 까지의 임의의 사용자 아이디를 생성한다. var id = 'guest' + Math.floor((Math.random() * 10) + 1); // 메시지 수신시 화면에 전달받은 ID 와 메시지를 추가한다. socket.on('msg', function (data) { $('#msgs').append($('<p>').text('S ' +data.id + ' : ' + data.msg)); }); // send1 버튼을 클릭시 발생한다. $('#send1').click(function(){ // 화면에 입력된 값을 msg1 이벤트 명으로 전달한다. socket.emit('msg1', { id : id, msg : $('input').val()}); // 화면에 전달한 정보를 출력한다. $('#msgs').append($('<p>').text('C ' +id + ' : ' + $('input').val())); // 입력창의 값을 지운다. $('input').val(''); }); $('#send2').click(function(){ // 화면에 입력된 값을 msg2 이벤트 명으로 전달한다. socket.emit('msg2', { id : id, msg : $('input').val()}); $('#msgs').append($('<p>').text('C ' +id + ' : ' + $('input').val())); $('input').val(''); }); $('#send3').click(function(){ // 화면에 입력된 값을 msg3 이벤트 명으로 전달한다. socket.emit('msg3', { id : id, msg : $('input').val()}); $('#msgs').append($('<p>').text('C ' + id + ' : ' + $('input').val())); $('input').val(''); }); }); </script> </head> <body> <input type='text' /> <p/> <button id='send1'>SEND1</button> <button id='send2'>SEND2</button> <button id='send3'>SEND3</button> <div id='msgs'> </div> </body> </html> 라즈베리파이에서 서버를 구동한 후, 2 개의 단말을 이용하여 서버에 접속해 보자. 필자는 스마트 폰과 노트북을 이용하여 접속하였는데, 접속이 완료되면 서버에서 Chat is ready 라는
  • 82.
    메시지를 전달하는데, 클라이언트에서그 메시지를 수신하여 화면상에 보여 준다. 스마트폰에서 메시지를 입력하고 SEND1 을 누르면 서버의 msg1 이벤트가 동작하여, 자신을 제외한 접속되어 있는 단말에 메시지가 전달되는 것을 알 수 있다. (만일 더 많은 단말을 연결하면, 그 단말에서도 정상적으로 메시지가 전송됨을 알 수 있다.) 두 번째 메시지를 입력 후 SEND2 를 누르면, 서버의 msg2 이벤트가 동작되는데, 자기 자신에게만 다시 메시지가 전달된다. 마지막으로 SEND3 누르면, 자기자신을 포함한 모든 클라이언트에 메시지가 전달되는 것을 알 수 있을 것이다. [그림] 모바일로 메시지를 차례대로 보내보면, 첫번째 전송은 목적지에만, 두번째 전송은 자기 자신에게만, 세번째 전송은 자신을 포함한 모두에게 데이터가 전달 됨을 알수 있다. 이번 장을 정리하며 이번 장에서는 라즈베리파이에서 NodeJS 를 사용하는 법을 배웠다. 웹 프레임워크인 ExpressJS 로 서버를 구축하고, Socket.IO 를 이용하여 웹 페이지와 실시간 통신 하는 법을 다루었다. 서버와 웹 모두 자바스크립트로 구현되어, 각각 다른 언어로 구현된 것 보다 빠르게 개발하고 빠르게 적용해 볼 수 있는 것을 직접 확인할 수 있었을 것이다. 다음장에서는 IoT 의 시작이라고 할 수 있는 다양한 센서들을 NodeJS 를 활용하여 동작시키고 센서 값을 감지하는 방법을 다루도록 한다.
  • 83.
    4. 거리를 측정하고,정보를 표시 하기 - GPIO 라즈베리파이에서 하드웨어 제어를 가능하게 하는것, 그 이름 GPIO 라즈베리파이가 컴퓨터의 역할을 하는 초소형 보드이지만, 일반 컴퓨터와 구분지어 주는 특징중 하나는 GPIO 의 유무일 것이다. GPIO 는 General Purpose Input/Output 의 약자로서, 이 것을 이용하여 외부의 센서나 장치들과 정보를 주고받을 수 있게 됩니다. 즉, LCD 에 정보를 보여줄 수 있도록 쓸수 있고(라즈베리파이 입장에서는 Output), 온습도 센서로 부터 정보를 읽어올 수(라즈베리파이 입장에서는 Input) 있는것은 모두 GPIO 가 있기에 가능한 것이다. 라즈베리파이에서 GPIO 사용을 쉽게! – GPIO 사용 준비 라즈베리파이를 이용하여 개발할때 모니터,키보드,마우스를 직접 연결하여 개발하거나, 접속하여 원격으로 개발할 수 있다. 하지만 일반 사용자는 음악이 검색되고, 다운로드 받아지고, 재생되는 정보를 표시할 곳이 라즈베리파이에는 존재하지 않는다. 따라서, 라즈베리파이에 LED, LCD 를 연결하여 우리가 원하는 정보를 화면에 보여주자. 아울러 온습도 센서를 이용하여 주변환경 또한 모니터링해 볼 수 있다. 라즈베리파이를 잘 살펴보면 40 개의 PIN 이 노출되어 있는 것을 볼 수 있다. 이를 통하여 외부의 센서로부터 정보를 받고, 장치를 제어하는 용도로 사용하게 된다. [그림] 라즈베리파이의 GPIO 배치 전원 공급부터 정밀한 제어 등 다양한 목적의 입출력 핀이 제공 된다.
  • 84.
    라즈베리파이에서 GPIO 를제어할 수 있는 방법중 대표적인 것은 WiringPI 를 이용하는 것입니다. 하지만, 원래의 WiringPi 가 C 언어로 이루어져 있어서, NodeJS 에서는 이를 그대로 사용하기에는 어려움이 있는데, NodeJS 용으로 사용할 수 있는 WiringPi 를 설치하여 사용할 수 있다. 설치방법 $ sudo apt-get install git-core $ sudo npm install wiring-pi [그림] WiringPi 에서 지원하는 GPIO 번호 WiringPi 를 이용하여 하드웨어를 제어할 때 지정하는 pin 번호가 처음 보았던 pin 번호와 다른 것을 알 수 있다. 처음 본 핀 번호는 라즈베리파이에 탑재된 프로세서 제조사에서 제공하는 공식적인 입출력 핀 번호이다. WiringPi 는 라즈베리파이와 아두이노와의 연결 등을 고려하여 WiringPi 상 에서 호출되는 핀 번호가 제조사에서 제공하는 공식 핀 번호와는 차이가 있다. 입출력 연결할 때 이를 유의 하여 사용하도록 해야 한다. 하드웨어 계의 Hello World – LED 깜빡이기 WiringPI 로 LED 깜빡이기 만들기 하드웨어 계의 Hello World 인 LED 켯다 끄기를 진행해 보자. 우리가 설치한 WiringPi 로 LED 를 켜고 끌 수 있다. 일반적으로 컴퓨터가 인식하는 것은 전기 신호가 있고(1 or true), 없음(0 or false) 이다. GPIO 를 일반적으로 제어할 때도 동일하게 전기 신호가 있고 없음을 지정하는 것 만으로 LED 를 켜고 끌 수 있게 된다. 다음 코드를 작성하여 구동해 보도록 한다.
  • 85.
    [ ch5_01.js ] //wiring pi 를 활성화 한다. var wpi = require('wiring-pi'); wpi.setup('wpi'); // 0 번 핀을 출력모드로 설정한다. var pin = 0; wpi.pinMode(pin, wpi.OUTPUT); var value = 1; // 1초마다 0번 핀에 0과 1을 입력한다. setInterval(function() { wpi.digitalWrite(pin, value); value = +!value; }, 1000); 다음 코드는 GPIO 0 번 핀을 출력 모드로 설정한 후, 1 초 간격으로 출력 값을 0 과 1 을 번 갈아서 왔다 갔다 하게 하는 방법으로, LED 를 켰다 끌 수 있게 된다. LED 의 경우, 2 개의 다리로 이루어져 있는데, 긴 다리가 양극(+) 이고, 짧은 다리가 음극(-) 이다. + 십자가의 두 막대를 이으면 길어 지니, 긴 다리가 + 라고 생각하면 쉽다. 양극을 GPIO 0 에 연결하고, 음극은 라즈베리파이의 어떠한 GROUND 에 연결해도 된다. 연결이 끝난 후 코드를 실행하여 결과를 확인해 보도록 한다.. [그림] LED 제어를 위한 배선, LED 의 긴 다리를 GPIO 0 에, 짧은 선을 GROUND 에 연결한 후 코드를 실행하면 LED 가 깜빡임을 볼 수 있다. 모바일로 LED 깜빡이기 만들기
  • 86.
    우리는 간단하게 1개의 LED 를 껐다 켰다 하는 방법을 배웠다. 최종적으로 우리가 개발할 파이오(piAu)는 스마트 폰으로 제어하는 것이다. LED 제어를 좀더 발전시켜, NodeJS 의 ExpressJS 를 활용해, 스마트 폰으로 LED 를 껐다 켜는 예제로 발전시켜 볼 것이다. 한 개의 LED 는 상시로 켜지고, 2 개의 LED 는 스마트 폰으로 제어가 되는 초 간단 IoT 라이트를 만들어 보도록 한다. 우선, 이번 예제를 위해 추가적으로 설치해야 할 bodyParser 모듈을 설치해 주도록 한다. $ npm –g install body-parser 이번 실험을 위해 다음과 같이 배선을 연결하도록 하자. [그림] 배선 연결 [ ch5_02.js ] var express = require('express'); // post 방식을 사용시, body 영역의 전달 값을 확인하는데 사용된다. var bodyParser = require('body-parser'); var os = require( 'os' ); var led = require('./ch5_02_led'); var app = express(); // body 데이터 형식에서 JSON 방식을 지원한다. app.use(bodyParser.json()); // 다국어 지원을 위해 URLENCODE 방식을 지원한다. app.use(bodyParser.urlencoded({ extended: true })); app.listen(8080); app.get('/', function(req, res){
  • 87.
    res.sendFile(__dirname + '/ch5_02.html') }); app.post('/setLED',function(req ,res){ console.log(req.body); // POST 방식으로 var pin = req.body.pin; var value = req.body.value; // 지정한 핀에 지정한 값을 입력한다. if(pin && value){ led.setLED(parseInt(pin), parseInt(value)); } res.json({ result : true }); }); console.log('LED Server’); 서버가 될 코드는 ExpressJS 로 외부접속 페이지를 반환하고, POST 방식으로 setLED 가 호출이 되면, 요청에 따라 LED 를 켜고 끄는 동작을 수행하도록 한다. 처음으로 웹페이지를 ExpressJS 로 했을때와는 몇가지 코드가 증가하였는데, bodyParser 부분은 POST 방식으로 데이터를 전달 할 때 해당 데이터를 읽어서 활용할 수 있도록 도와 주는 모듈이다. setLED 에서 req.body.pin, req.body,value 식으로 값을 가지고 올 수 있는 것은 이 모듈을 ExpressJS 에서 사용할 수 있도록 설정하였기 때문이다. setLED 함수 호출시 pin 과 value 값을 parseInt 함수로 값을 전달하는데, POST 방식 전달 시 숫자 값이 문자로 전환되기 때문에 이 값을 다시 숫자 값(Integer) 로 전환하여 정상적으로 동작할 수 있도록 변환시켜 준다. [ ch5_02_led.js ] var wpi = require('wiring-pi'); wpi.setup('wpi'); var pins = []; // 핀과 입력 값을 인자로 받는 함수를 작성한다. exports.setLED = function(pin, value){ console.log(pin + ' / ' + value); // 처음 호출되는 경우, 쓰기 모드로 초기화 한후, 핀번호를 배열에 포함한다. if(pins.indexOf(pin) < 0){ console.log('add new'); wpi.pinMode(pin, wpi.OUTPUT); pins.push(pin); } // 요청한 핀에 요청받은 값을 입력한다. wpi.digitalWrite(pin, value); }
  • 88.
    LED 제어는 공통적으로사용될 수 있어서 별도의 모듈 파일로 분리하였다. 입력 받은 pin 번호의 GPIO 를 value 에 전달 받은 값으로 쓰는 역할을 한다. 0 과 1 이 전달되어 OFF, ON 효과를 LED 로 확인할 수 있다. [ ch5_02.html ] <html> <head> <title>SET LED</title> <script src="https://code.jquery.com/jquery- 2.1.4.min.js"></script> <script> $(function(){ // 0 번 핀의 LED 를 켜기 위헤 명령을 호출한다. $('#led1on').click(function(){ $.post( "setLED", { pin: 0, value: 1 }); }); // 1 번 핀의 LED 를 켜기 위헤 명령을 호출한다. $('#led2on').click(function(){ $.post( "setLED", { pin: 1, value: 1 } ); }); // 2 번 핀의 LED 를 켜기 위헤 명령을 호출한다. $('#led3on').click(function(){ $.post( "setLED", { pin: 2, value: 1 } ); }); // 1 번 핀의 LED 를 끄기 위해 명령을 호출한다. $('#led1off').click(function(){ $.post( "setLED", { pin: 0, value: 0 } ); }); // 2 번 핀의 LED 를 끄기 위해 명령을 호출한다. $('#led2off').click(function(){ $.post( "setLED", { pin: 1, value: 0 } ); }); // 3 번 핀의 LED 를 끄기 위해 명령을 호출한다. $('#led3off').click(function(){ $.post( "setLED", { pin: 2, value: 0 } ); }); }) </script> </head> <body> <p> <span>LED1</span> <button id='led1on'>ON</button> <button id='led1off'>OFF</button> </p> <p> <span>LED2</span> <button id='led2on'>ON</button> <button id='led2off'>OFF</button> </p> <p>
  • 89.
    <span>LED3</span> <button id='led3on'>ON</button> <button id='led3off'>OFF</button> </p> </body> </html> 웹페이지는 코드가 증가하였다. 각 LED 의 버튼의 ID 에 맞는 click 이벤트를 jquery 를 이용하여 정의하였다. ON 클릭 시, 해당 pin 에 1 값을 POST 로 전달하고, OFF 클릭 시 0 값을 전달하는 역할을 하게 된다. 코드 작성이 완료된 후 다음과 같이 구동하고, 스마트 폰이나 컴퓨터로 웹 페이지에 접근 한 후 ON, OFF 버튼을 눌러, LED 가 제대로 켜고 꺼지는 지 확인해 보도록 하자. $ sudo node ch5_02_led.js One More Thing – 처음부터 계속 점등하기 기본색은 기본 백라이트 처럼 계속 불빛이 나오는 상태로 유지가 되어야 한다. 일반 GPIO 에 on 신호를 주어 동작시킬수도 있지만, 기본적으로 출력이 나오는 3.3v 단자에 연결해 보자. 설정과 상관없이 지속적으로 불빛이 나올 것이다. [그림] LED 를 제어하는 리모콘이 완성 되었다. 모바일 밝기 조절 LED 만들기 – PWM 사용 스마트 폰을 이용하여 단순히 불을 껐다 켜는 장치를 구현해 보았다. 기능을 좀더 발전시켜 보자, 우리가 쓰는 전구 중 불 밝기를 조절하는 제품을 본 기억이 있을 것이다. 그 역시 구현할 수 있는데, PWM(Pulse-Width Modulation) 이라는 방식을 사용하는 것이다. PWM 은 쉽게 전원스위치를 껐다 켰다를 반복한다고 생각하면 된다. 켜지는 시간이 꺼지는 시간보다 길어지면 밝아지고, 반대로 꺼지는 시간이 켜지는 시간보다 길어지면 어두워 지는 것이다.
  • 90.
    [그림] PWM 은스위치를 껐다 켰다를 반복한다고 생각하면 된다. 라즈베리파이에서는 wiringPi 기준으로 1 번 Pin 과 24 번 Pin 이 PWM 을 지원하는 단자이다. 24 번 핀에 양극을 연결하고 PWM 코드를 작성하여 불이 밝아졌다가 어두워 지는 것을 확인해 보자. [그림] GPIO24 와 GROUND 를 LED 에 연결 [ ch3_3_pwm_hw.js ] var wpi = require ('wiring-pi'); wpi.setup('gpio'); wpi.wiringPiSetup();
  • 91.
    // 24번 핀을HW PWM 모드로 설정한다. wpi.pinMode (24 , wpi.PWM_OUTPUT ); wpi.pwmSetMode ( wpi.PWM_MODE_MS ); // PWM 설정 값을 초기화 한다. wpi.pwmSetClock ( 400 ); wpi.pwmSetRange ( 1024 ); var num = 0; var isRight = true; // 0.1초 간격으로 서보모터가 좌우로 움직이도록 값을 조정한다. setInterval (function(){ wpi.pwmWrite( 24 , num); if (isRight){ num += 1; } else { num -= 1; } if (num === 120){ isRight = false; } if (num === 0) { isRight = true ; } }, 100); PWM 을 이용하여 LED 의 불이 서서히 밝아졌다가 서서히 어두워 지는 것을 알수 있다. 하지만, 라즈베리파이에서 이 방식을 이용해서 하드웨어를 제어할 때 한가지 문제점을 발견하게 된다. 그것은, 3.5mm 잭에 오디오를 연결해서 들으면 PWM 이 증가함에 따라 스피커에서 노이즈가 크게 발생하는 것을 발견할 수 있다. 이것은 내부적으로 오디오 시스템과 PWM 이 라즈베리파이에서 공유되고 있는 전원을 사용하고 있어, HW 방식의 PWM 을 사용하면, 오디오 쪽에 노이즈를 유발시키게 됨을 의미한다. 오디오를 만드는데 노이즈가 발생하면 안되니 다른 방법을 사용하도록 한다. PWM 구동 방식을 보면 처음에도 설명했듯이, 스위치를 껐다 켰다 하는 것과 비슷하다고 언급한 바 있다. 이는 소프트웨어로 유사하게 구현할 수 있음을 의미하고, wiringPi 에서는 친절하게 소프트웨어 방식의 PWM 또한 제공하고 있다. 다만, 소프트웨어 방식이기 때문에 HW 방식에 비해 정밀도가 떨어지는 문제가 있지만, 우리가 사용하려는 LED 를 켜고 끄는데는 충분히 사용할 수 있다. 이번에는 동일하게 밝아졌다 흐려졌다 하는 동작을 소프트웨어 방식을 이용하여 구현하도록 한다. [ ch3_3_pwm_sw.js ] var wpi = require ('wiring-pi'); wpi.setup('gpio'); wpi.wiringPiSetup(); // 소프트웨어 방식으로 24번 핀을 PWM 으로 초기화 하고, 초기값을 설정한다. wpi.softPwmCreate (24, 0, 200) ; var num = 0; var isRight = true; // 0.1초 간격으로 서보모터가 좌우로 움직이도록 값을 조정한다. setInterval (function(){ wpi.softPwmWrite (24, num) ;
  • 92.
    if (isRight){ num+= 1; } else { num -= 1; } if (num === 24){ isRight = false; } if (num === 0) { isRight = true ; } }, 100); 소프트웨어 방식으로도 하드웨어 방식에 비해 정밀도는 떨어지지만, 사운드 시스템에 노이즈 없이 밝기를 조절하는데 충분함을 발견할 수 있다. 이를 응용하여, 스마트 폰으로 밝기를 조절할 수 있도록 만들어 보자. 간단한 여러분만의 스마트 전등이 완성될 것이다. [ ch3_3_server.js ] var express = require('express'); var bodyParser = require('body-parser'); var wpi = require ('wiring-pi'); var app = express(); wpi.setup('gpio'); wpi.wiringPiSetup(); wpi.softPwmCreate (24, 0, 200) ; app.use(bodyParser.json()); app.use(bodyParser.urlencoded({ extended: true })); app.listen(8080); app.get('/', function(req, res){ res.sendFile(__dirname + '/ch3_3.html') }); // 불을 켜고 끄는데 호출된다. app.post('/setLight', function(req ,res){ console.log(req.body); var value = req.body.value; // 입력 값이 있는경우, 소프트 PWM 방식으로 값을 입력한다. if(value){ wpi.softPwmWrite (24, parseInt(value)); } // 리턴 값을 반환한다. res.json({ result : true }); }); console.log('LED Light Server'); 라즈베리파이의 서버에서는 PWM 값을 전달 받아 GPIO 에 기록하게 된다. 단, 모바일로부터 전달 받는 숫자 값은 Ajax 통신을 거치면서 문자 값으로 바뀌게 된다. parseInt 함수를 사용하여, 문자 값을 숫자 값으로 전환 한 후 사용하도록 한다. [ch3_3.html ] <html> <head>
  • 93.
    <title>SET LED</title> <script src="https://code.jquery.com/jquery- 2.1.4.min.js"></script> <script> $(function(){ //슬라이더의 값이 변경되는 경우 호출된다. $('#range').change(function(){ // 서버의 setLight 로 변경된 값을 전달한다. $.post( "setLight", { value: $(this).val() }); }); }) </script> </head> <body> <span>LIGHT</span> <input type="range" id="range" min="0" max="240" value="0" /> </body> </html> Range 컴포넌트의 값이 변경되는 경우 이벤트가 감지되고, 변경 된 값을 라즈베리파이에 전달하게 된다. 모바일에서 슬라이더를 조정하여 밝기를 조절해 보자. [그림] PWM 을 적용한 LED 를 스마트 폰을 이용하여 불빛의 밝기를 조절할 수 있다. One More Thing – PWM 을 이용하여 서보 모터 제어하기 이 책에서 다루는 범위는 움직이는 물체가 아닌, 고정된 IoT 서비스를 만들어 보는 것을 다루고 있다. 하지만, 자동 스위치나 로봇과 같이 움직이는 IoT 서비스를 만들고자 하는 독자도 있을 것이다. 이런 경우는 PWM 을 응용하여 서보 모터(Servo Moter)를 제어하는 것으로 그 기능을 구현할 수 있다. 소프트웨어 방식의 PWM 을 사용해 보았으나, 실제 서보 모터 같은 장치를 정밀하게 제어하는데는 어려움이 있다. 라즈베리파이 B+ 이상의 모델은 2 개의 하드웨어 PWM 을 지원한다. 관절이 있는 로봇을 만들기 위해서는 더 많은 PWM 제어 핀이 필요한데, 여러 개를 제어할 수 있는 별도의 PWM 서보 드라이버 장치가
  • 94.
    필요하다. 물체와의 거리를 측정해보자 – 초음파 센서 초음파 센서는 초음파를 보내고 반사된 초음파를 받아 그 시간차로 거리를 측정하는데 사용된다. 흔히 로봇 청소기가 움직이다가 벽을 만나면 멈추어 벽이 없는 쪽으로 움직이는 시나리오는 이러한 초음파 센서를 응용하여 구현할 수 있고, 사람이 근접하는 상황에 따라 동작하는 시나리오에서도 유용하게 사용할 수 있다. 가격이 매우 저렴하고 범용적으로 사용하기 좋아 많이 활용되는 센서이다. 초음파 센서는 4 개의 핀이 제공되는데, 전원 공급(5v)와 접지(Ground) 용도로 2 핀이 사용되고, 초음파를 보내고(Trigger), 받는데(Echo) 두 개의 핀이 사용된다. [그림] 초음파를 발산하면, 반사된 시간차를 이용하여 거리를 측정한다.
  • 95.
    [ ch5_03.js ] varwpi = require('wiring-pi'); var sleep = require('sleep'); // 시간 측정을 위한 모듈을 호출한다. var microtime = require('microtime'); var trig = 4; var echo = 5; // 트리거는 출력, 에코는 입력핀으로 GPIO를 설정한다. wpi.setup('wpi'); wpi.pinMode(trig, wpi.OUTPUT); wpi.pinMode(echo, wpi.INPUT); setInterval(function(){ // 트리거로 펄스를 생성한다. wpi.digitalWrite(trig, wpi.LOW); sleep.usleep(2); wpi.digitalWrite(trig, wpi.HIGH); sleep.usleep(20); wpi.digitalWrite(trig, wpi.LOW); var count1 = 0; var count2 = 0; // 에코 핀으로 LOW 값을 전달 받는다. while(wpi.digitalRead(echo) == wpi.LOW){ if(count1++ > 1000){ break; } } var start_time = microtime.now(); // HIGH 값을 전달받으면, 반사 신호를 받은 것이다. while(wpi.digitalRead(echo) == wpi.HIGH){
  • 96.
    if(count1++ > 10000){break; } } // 시간차를 구해서 거리를 계산한다. var time = microtime.now() - start_time; console.log('distance : ' + Math.round(time / 58) + 'cm'); }, 1000); GPIO4 번을 Trigger 로, 5 번을 Echo 로 설정한 후, 매 1 초마다 초음파 센서로 거리를 측정하여 콘솔에 보여주는 프로그램을 완성하였다. SR04 센서 스펙 상 최소 2cm 부터 4m 까지 측정이 가능하다. 손바닥을 앞에 가져다 대서 거리 측정이 제대로 되는지 확인해 본다. 위의 코드가 복잡하다면, 이미 만들어 진 노드 패키지를 이용하여 구현할 수 있다. 동일한 핀을 사용하지만, wiringPi 가 아닌, 내부 핀 번호를 사용한다는 점을 유의해야 한다. 핀 번호가 일반 헤더 번호인 24,23 으로 사용해야 한다. 우선 r-pi-usonic 패키지를 설치 한 후 코드를 실험해 본다. $ sudo npm install –g r-pi-usonic [ ch5_03_2.js ] // 초음파 센서용 모듈을 호출한다. var usonic = require('r-pi-usonic'); usonic.init(function(){ // 에코와 트리거 핀을 초기화 한다. var sensor = usonic.createSensor(24,23); // Echo, Trigger // 1초 간격으로 거리 값을 측정한다. setInterval(function(){ console.log('distance : ' + Math.round(sensor()) + 'cm'); },1000); });
  • 97.
    복잡한 코드 없이손쉽게 거리를 측정함을 알 수 있다. Trigger 는 일종의 신호 이므로 출력, Echo 는 마이크의 에코 입력과 같이 입력 값이라고 생각하면, 초음파 센서의 구성을 쉽게 이해할 수 있다. One More Thing –선 색깔로 구분하는 센서 연결 센서 연결시, Vcc, Ground, 기타 GPIO 배선으로 이루어져 있는 것을 볼 수 있다. 연결시 혼동을 방지하기 위해 통상 Vcc 는 적색 계열, Ground 는 무채색 계열, 기타 GPIO 는 기타 유색 계열을 사용하여 연결하면, 시각적으로 배선을 확인하는데 도움이 된다. 버튼 없이 손으로 제어하는 LED 만들기 이번에는 좀더 확장된 시나리오를 적용해 보도록 하자. LED 에제의 경우 LED 를 껏다 켜는 용도로 모바일을 사용하였기 때문에 일반 웹 방식으로 데이터를 전달했지만, 이번의 경우 라즈베리파이, 즉 서버에서 측정된 값을 모바일로 전달해 주는 방식을 사용한다. 이러한 경우에 Socket.IO 에서 예를 들었던 PUSH 의 사례를 사용할 수 있다. Socket.IO 를 응용하여 측정된 거리가 모바일 화면상에 보여질 수 있도록 하며, 거리가 50cm 미만일 경우에는 장착된 led 가 점등되도록 구성해 본다. [ ch5_04.js ] var app = require('express')(); var server = require('http').Server(app); var io = require('socket.io')(server); var usonic = require('./ch5_04_usonic'); var led = require('./ch5_04_led'); var port = 8080;
  • 98.
    app.get('/', function(req, res){ res.sendFile(__dirname+ '/ch5_04.html') }); io.on('connection', function (socket) { setInterval(function(){ // 거리 값을 측정한다. var distance = usonic.getDistance(); console.log('Distance : ' + distance + 'cm'); // 측정거리가 50cm 미만인 경우, LED를 켠다. if(distance < 50){ led.on(); // 측정거리라 50cm 이상인 경우, LED를 끈다. } else { led.off(); } socket.emit('usonic', { distance : distance }); }, 1000); }); server.listen(port); console.log('UltraSonic Push Server'); 1 초마다 거리를 측정하여, 50 츠 미만일 때 led 를 켜는 함수를 호출하고, 그렇지 않으면 끄는 함수를 호출한다. 측정된 거리는 모바일에서 확인할 수 있도록, emit 함수를 이용하여 거리를 전달한다. [ch5_04_usonic.js ] var wpi = require('wiring-pi'); var sleep = require('sleep'); var microtime = require('microtime'); var trig = 4; var echo = 5; wpi.setup('wpi'); wpi.pinMode(trig, wpi.OUTPUT); wpi.pinMode(echo, wpi.INPUT); exports.getDistance = function(){ wpi.digitalWrite(trig, wpi.LOW); sleep.usleep(2); wpi.digitalWrite(trig, wpi.HIGH); sleep.usleep(20); wpi.digitalWrite(trig, wpi.LOW); var count1 = 0; var count2 = 0; while(wpi.digitalRead(echo) == wpi.LOW){ if(count1++ > 1000){ break; } }
  • 99.
    var start_time =microtime.now(); while(wpi.digitalRead(echo) == wpi.HIGH){ if(count2++ > 1000000){ break; } } var time = microtime.now() - start_time; return Math.round(time / 58); } 실제 거리를 측정하는 함수로서, 파형을 생성하는 trigger 핀은 출력으로 설정 하고, echo 핀은 입력으로 설정하여 반사된 값이 올 때까지의 시간을 측정한다. 측정된 시간을 이용하여 거리를 환산하여 반환한다. [ ch5_04_led.js ] var wpi = require('wiring-pi'); wpi.setup('wpi'); var pin = 0; wpi.pinMode(pin, wpi.OUTPUT); // 입력 받은 핀에 HIGH 값을 입력한다. (LED ON) exports.on = function(){ wpi.digitalWrite(pin, wpi.HIGH); } // 입력 받은 핀에 LOW 값을 입력한다. (LED OFF) exports.off = function(){ wpi.digitalWrite(pin, wpi.LOW); } LED 를 간단하게 켜고 끄는 함수를 구현한다. LED 를 켜기 위해 HIGH 신호 (1) 을 주고, 끌 때는 LOW 신호 (0)을 GPIO 에 쓰도록 한다. [ ch05_04.html ] <html> <head> <title>Ultra Sonic</title> <script src="https://code.jquery.com/jquery- 2.1.4.min.js"></script> <script src="/socket.io/socket.io.js"></script> <script> $(function(){ var socket = io(); // 서버로부터 전달받은 변경된 거리 값을 화면에 출력한다. socket.on('usonic', function(data){ $('#distance').text(data.distance); }) }) </script> </head> <body>
  • 100.
    <h1>UltraSonic Sensor distance</h1> <h3><spanid='distance'>0</span><span>cm</span></h3> </body> </html> 라즈베리파이로부터 전달된 거리 값을 모바일에 출력해 준다. Socket.ip 를 이용하여 실시간으로 전달 되므로, 초음파 센서에 손을 가까히 접근하여 거리 값이 제대로 측정되는 지 확인해 볼 수 있다. [그림] 1 초마다 거리가 측정된 값이 갱신 된다. 4 Digit 7 Segment LCD – 화면에 문자 표시하기 7 segment led 의 경우 7 개의 막대로 숫자를 표현하며, 1 개의 점이 추가적으로 구성되어 일반적으로 단순한 글씨와 숫자를 표현하는 데 사용이 된다. 4 개 문자 판에 총 8 개의 LED 가 있는 구조이므로, 32 개의 GPIO 선을 이용하여 제어를 하는 것이 맞지 않나 생각이 들 것이다. 이러한 경우 일일히 모든 LED 를 제어하기 어렵기 때문에 I2C(Inter Intergrated Circuit) 이나 SPI (Serial Peripheral Interface) 방식의 인터페이스로 통신이 가능한 규격이다. 우리가 이번에 사용할 7 Segment LCD 는 SPI 통신 방식을 지원하는 부품을 사용하게 될 것이다. 연결을 위해 5v 전원과 Ground 외에 여러 핀이 포함되어 있는데, 그 용도는 다음과 같다. 약자 본명 용도 SCLK Serial Clock 마스터가 출력하는 동기용 클럭 MOSI Master Output Slave Input 마스터의 출력이며 슬레이브에 입력 MISO Master Input Slave Output 슬레이브의 출력이 마스터에게 입력 SS(or CE) Slave Select 마스터의 출력으로 슬레이브를 선택하기위한 신호 제공되는 핀 중에 MISO 즉, Slave 가 되는 7 Segment 4 Digit 의 출력이 Master 즉, 라즈베리파이에 전달될 부분이 없기 때문에, 선을 연결하지 않아도 동작 된다.
  • 101.
    [그림] 7 Segment4 Digit 연결도 7 Segment 는 8 개의 LED 로 구성되어 있어, 이를 응용하여 다양한 기호를 직접 만들어서 사용할 수 있다. NodeJS 에서 제어하기 위해 SPI 통신 방식을 지원하는 모듈 설치를 다음과 같이 한다. $ npm install spi 7 Segment 에서 SPI 를 동작하게 하는 칩으로, 부품 뒷면을 보면 max7219 칩셋이 사용됨을 알 수 있다. 7 Segment 뿐만 아니라, Dot-Matrix 방식의 제품에서도 많이 사용되는 칩셋으로 해당 드라이버를 설치하여 7 Segment 를 제어해 보도록 한다. $ sudo npm install git+https://git@github.com/victorporof/MAX7219.js [ ch05_5.js ] // 7세그먼트를 위한 max7219 모듈을 불러온다. var MAX7219 = require('max7219'); // 첫번째 SPI 를 활성화한다. var disp = new MAX7219("/dev/spidev0.0"); disp.setDecodeAll(); // 4개 문자를 표현할 수 있는 7세그먼트를 초기화 한다. disp.setScanLimit(4); // 7세그먼트 밝기를 조정한다. 값은 0 부터 15까지 이다. disp.setDisplayIntensity(15); disp.startup(); // 해당 위치의 7세그먼트에 숫자를 표현한다. disp.setDigitSymbol(0, 0); disp.setDigitSymbol(1, 3); disp.setDigitSymbol(2, 1); disp.setDigitSymbol(3, 4);
  • 102.
    기본적으로 제공하는 밝기가환하지 않아, setDisplayIntensity 함수를 이용하여 최대한 밝게 표시가 되도록 수정하였다. 화면상에 0314 가 표시되는 것을 확인할 수 있을 것이다. 디스플레이에 7 가지 문자를 표시하는 영역 이외에 점이 표시되는 영역도 사용해 보도록 한다. 아쉽게도, MAX7219 라이브러리에서 기본적으로 제공해 주는 문자는 제약이 많아, 다음과 같이 7 Segment 와 1 개의 점에 대해 1,0 의 값으로 켜고 끄는 제어를 할 수 있다. [ ch05_6.js ] var MAX7219 = require(''max7219''); var disp = new MAX7219("/dev/spidev0.0"); disp.setDecodeAll(); disp.setScanLimit(4); disp.setDisplayIntensity(15); disp.startup(); // 특정 순서의 7세그먼트를 켜고 끌 수 있도록 제어한다. disp.setDigitSegments(0, [0, 0, 1, 1, 0, 1, 1, 1]); disp.setDigitSegments(1, [0, 1, 0, 0, 1, 1, 1, 1]); disp.setDigitSegments(2, [0, 0, 0, 0, 1, 1, 1, 0]); disp.setDigitSegments(3, [0, 1, 1, 0, 0, 1, 1, 1]); 매번 이렇게 문자와 숫자를 일일 히 만들어 쓰면 불편하므로, 다음과 같이 함수를 만들어서 사용할 수 있도록 한다. 7 Segment 의 제약으로 모든 문자를 표현하기에 제약이 있지만, 그래도 최대한 유사한 문자 모양이 나올 수 있도록 별도의 함수를 만들어서 사용해 보도록 한다. 모바일 상에서 입력한 메시지가 출력이 되도록 구성해 본다. 응용 2 - 7 Segment 를 이용하여 시계 만들기 자 이제, 7 Segment 모듈을 이용하여 시간을 표시하는 기능을 만들어 보자.
  • 103.
    linux 에서 시간을확인하는 명령어는 date 이다. date 를 실행하고 난 결과를 쪼개서 출력에 사용하도록 하자. 1 초단위로 시간을 검사하여 화면상에 디스플레이 하는 기능을 간단하게 구현해 본다. [ ch5_07.js ] var seg = require('./ch5_07_7seg'); // 커맨드 명령을 실행할 수 있는 exec 함수를 활성화한다. var exec = require('child_process').exec; setInterval(function(){ // 리눅스에서 시간을 알아내는 date 명령어를 실행한다. exec('date', function(err, stdout, stderr){ // 현재 시간 값으로부터 시간을 구한다. var tokens = stdout.split(':'); var times = tokens[0].split(' '); var time = times.pop() + '' + tokens[1]; console.log(time); // 시간과 분으로 이루어진 현재 시간을 화면에 출력한다. seg.setText(time, false, true, false, false); }) },1000); Exec 명령은 실제 linux 에서 구동하는 명령어를 nodejs 에서 실행할 수 있게 해 주는 명령어이다. 처음 시작시, PIAU 라는 글씨를 보여 준 후, 1 초마다 Date 명령을 실행하여, 현재 시간을 가져온다. 빈 여백으로 문자열을 잘라내면, 실제 시간은 4 번째 단어 군에서 가져와 이를 디스플레이 한다. [ ch5_07_7seg.js ] var MAX7219 = require(''max7219''); var disp = new MAX7219('/dev/spidev0.0'); disp.setDecodeNone(); disp.setScanLimit(8); disp.setDisplayIntensity(15); disp.startup(); // 숫자와 문자에 대한 7세그먼트 표시형식을 미리 정의해 둔다. _font = { _0 : [0,1,1,1,1,1,1], _1 : [0,0,0,0,1,1,0], _2 : [1,0,1,1,0,1,1], _3 : [1,0,0,1,1,1,1], _4 : [1,1,0,0,1,1,0], _5 : [1,1,0,1,1,0,1], _6 : [1,1,1,1,1,0,0], _7 : [0,1,0,0,1,1,1],
  • 104.
    _8 : [1,1,1,1,1,1,1], _9: [1,1,0,0,1,1,1], _A : [1,1,1,0,1,1,1], _B : [1,1,1,1,1,0,0], _C : [0,1,1,1,0,0,1], _D : [1,0,1,1,1,1,0], _E : [1,1,1,1,0,0,1], _F : [1,1,1,0,0,0,1], _G : [1,1,0,1,1,1,1], _H : [1,1,1,0,1,0,0], _I : [0,1,1,0,0,0,0], _J : [0,0,1,1,1,1,0], _K : [1,1,1,0,1,0,1], _L : [0,1,1,1,0,0,0], _M : [1,1,1,0,1,1,1], _N : [1,0,1,0,1,0,0], _O : [1,0,1,1,1,0,0], _P : [1,1,1,0,0,1,1], _Q : [1,1,0,0,1,1,1], _R : [1,0,1,0,0,0,0], _S : [1,1,0,1,1,0,1], _T : [1,1,1,1,0,0,0], _U : [0,0,1,1,1,0,0], _V : [0,1,1,1,1,1,0], _W : [1,1,1,1,1,1,0], _X : [0,1,1,0,1,1,0], _Y : [1,1,0,1,1,1,0], _Z : [1,0,1,1,0,1,1] } // 7세그먼트에 입력받은 4개의 문자혹은 숫자 값을 실제로 출력한다. exports.setText = function(_,a,b,c,d){ if(_){ _ += ""; if(_.length < 4){ while(_.length < 4){ _ = " " + _; } } else if(_.length > 4) { _ = _.slice(-4); } _ = _.toUpperCase(); if(a === true){ disp.setDigitSegments(0, _font["_" + _.charAt(0)].slice(0).concat([1])); } else { disp.setDigitSegments(0, _font["_" + _.charAt(0)].slice(0).concat([0])); } if(b === true){ disp.setDigitSegments(1, _font["_" + _.charAt(1)].slice(0).concat([1])); } else { disp.setDigitSegments(1, _font["_" + _.charAt(1)].slice(0).concat([0])); }
  • 105.
    if(c === true){ disp.setDigitSegments(2,_font["_" + _.charAt(2)].slice(0).concat([1])); } else { disp.setDigitSegments(2, _font["_" + _.charAt(2)].slice(0).concat([0])); } if(d === true){ disp.setDigitSegments(3, _font["_" + _.charAt(3)].slice(0).concat([1])); } else { disp.setDigitSegments(3, _font["_" + _.charAt(3)].slice(0).concat([0])); } } } 실제 7 Segment 를 구현한 함수이다. 0 부터 Z 까지 최대한 비슷한 모양이 나오도록 구성한 코드 이다. 0 과 1 로 해당 LED 를 켜고 끄는 것을 지정하고, 4 자리의 문자를 표시해 주도록 한다. 문자열 뒤의 4 개의 매개 변수는 점으로 된 LED 를 켜고 끄는데 사용하는 옵션으로, true 일때 on, false 일 때 off 가 된다. 응용 3 – 모바일로 입력한 문자 7 Segment 에 표시하기 7 Segment 를 응용하여, 모바일로부터 입력받은 문자를 표시해 주는 기능을 연동해 볼 수 있다. 기존에 사용한 함수는 그대로 활용하여 서버를 구성해 보도록 한다. [ ch5_08.js ] var express = require('express'); var bodyParser = require('body-parser'); var seg = require('./ch5_07_seg');
  • 106.
    var app =express(); app.use(bodyParser.json()); app.use(bodyParser.urlencoded({ extended: true })); app.get('/', function(req, res){ res.sendFile(__dirname + '/ch5_08.html') }); app.post('/setText', function(req ,res){ console.log(req.body); // 웹을 통해 입력받은 문자를 7세그먼트에 표시한다. var text = req.body.text; seg.setText(text, false, false, false, false); res.json({ result : true }); }); seg.setText('hipi', false, true, false, false); app.listen(8080); console.log('LED Server'); 기본 텍스트로 hi.pi 를 디스플레이 해 주고, 이전 실습 때 제작한 모듈을 그대로 재 활용하여 웹 페이지로부터 받아 지는 문자열을 7 Segment 에 보여 주게 된다. [ ch5_08.html ] <html> <head> <meta name="viewport" content="width=device-width, initial- scale=1.0" /> <title>7 Segment</title> <script src="https://code.jquery.com/jquery- 2.1.4.min.js"></script> <script> $(function(){ // 버튼 클릭시, 입력된 값을 서버로 전달한다. $('button').click(function(){ $.post( "setText", { text: $('input').val() }); }); }); </script> </head> <body> <input type='text' placeholder='Input message' /> <p/> <button>SEND</button> </body> </html> SEND 버튼을 클릭 시, 입력 창에 입력된 메시지를 서버의 setText 로 메시지를 전달하게 된다. 이렇게 전달된 메시지가 출력된 결과는 다음과 같다.
  • 107.
    응용 4 -초음파 센서 거리를 7 Segment 에 표시하기 앞에서 사용한 7 Segment 와 초음파 센서를 결합하여, 초음파 센서에서 측정한 거리를 7 Segment 로 표시해 줄 수 있다. 메인 모듈이다. 매 1 초마다 초음파로 거리를 측정하고, 이를 7 Segment 에 출력하는 방식으로 소스를 구성한다. [ ch5_08_2.js ] var usonic = require('./usonic'); var segment = require('./segment'); // 1초 간격으로 측정한 거리 값을 7세그먼트에 표시한다 setInterval(function(){ // 거리 값을 측정 한다.
  • 108.
    var num =usonic.getDistance(); console.log('Distance : ' + num); // 숫자 값을 화면에 표시 segment.setNumber(num); },1000); 숫자 표시를 위한 7 Segment 모듈이다. 최대밝기로 초기화하고, 4 자리 이하 문자일 경우 앞에 0 을 붙여 4 자리로 만든다. 이 문자를 한자리씩 7 Segment 에 보여준다. [ segment.js ] var MAX7219 = require('max7219'); var disp = new MAX7219("/dev/spidev0.0"); disp.setDecodeAll(); disp.setScanLimit(4); disp.setDisplayIntensity(15); disp.startup(); // 숫자를 7세그먼트에 표시하는 기능을 구현 exports.setNumber = function(num){ // 숫자를 문자로 번경하여 4자리 수 이하일 경우 앞에 0을 붙인다. while(num.toString().length < 4){ num = '0' + num; } // 4자리 수의 각 자리를 7세그먼트에 표시한다. disp.setDigitSymbol(0, num.charAt(0)); disp.setDigitSymbol(1, num.charAt(1)); disp.setDigitSymbol(2, num.charAt(2)); disp.setDigitSymbol(3, num.charAt(3)); } 초음파 센서 모듈이다. 2 번 핀을 Trigger 로, 3 번 핀을 Echo 로 설정한다. getDistance 함수에 거리를 측정하는 코드를 작성하여, 메인 모듈에서 이를 호출할 수 있도록 한다. [ usonic.js ] var wpi = require('wiring-pi'); var sleep = require('sleep'); var microtime = require('microtime'); var trig = 2; var echo = 3; wpi.setup('wpi'); wpi.pinMode(trig, wpi.OUTPUT); wpi.pinMode(echo, wpi.INPUT); exports.getDistance = function(){ wpi.digitalWrite(trig, wpi.LOW); sleep.usleep(2); wpi.digitalWrite(trig, wpi.HIGH); sleep.usleep(20); wpi.digitalWrite(trig, wpi.LOW);
  • 109.
    var count1 =0; var count2 = 0; while(wpi.digitalRead(echo) == wpi.LOW){ if(count1++ > 1000){ break; } } var start_time = microtime.now(); while(wpi.digitalRead(echo) == wpi.HIGH){ if(count1++ > 10000){ break; } } var time = microtime.now() - start_time; return Math.round(time / 58); } [그림] 초음파로 측정된 거리가 7 Segment 에 표시된다. 집 상황을 체크하기 – 온 습도 센서 에어컨, 난방기, 가습기, 제습기와 같은 제품은 집안의 온도와 습도를 검사하여 온도나 습도를 조절한다. 라즈베리파이에도 디지털 온 습도 센서를 이용하여 쉽게 측정할 수 있다. 저렴한 가격에 한번에 측정할 수 있는 DHT11 센서를 이용하여, 집안의 온습도를 측정해 보도록 한다. 이 센서를 이용하기 위해서는, 라즈베리파이의 칩셋 드라이버를 우선 설치해야 사용할 수 있다. 드라이버를 우선 다운로드 받도록 한다. $ sudo wget http://www.airspayce.com/mikem/bcm2835/bcm2835-1.50.tar.gz
  • 110.
    [그림] wget 명령을이용하면, 인터넷 상의 다양한 프로그램을 다운로드 받을 수 있다. 다운로드 받은 파일을 압축 해제한 후 설치하기 위해 다음 명령을 입력한다. $ tar zxvf bcm2835-1.50.tar.gz $ cd bcm2835-1.50 $ ./configure $ make $ sudo make check $ sudo make install
  • 111.
    [그림] make 명령어로C 라이브러리를 컴파일 한 후 make install 로 컴파일 된 파일을 설치할 수 있다. 드라이버의 설치가 완료되면, dht11 센서를 사용할 수 있게 해 주는 node-dht-sensor 패키지를 설치하도록 한다. DHT11 센서는 Power, Ground, GPIO 세 핀으로 이루어져 있는데, 마지막에 연결하므로 확인이 용이하게 마지막 GPIO21(WiringPi 기준으로는 GPIO29) 에 GPIO 연결을 하도록 한다. $ sudo npm install –g node-dht-sensor 설치가 완료된 후, 온도와 습도를 측정해 보자. 다음과 같이 간단하게 코드를 작성하여 온도와 습도를 한번에 확인할 수 있다. [ ch5_9.js ] // 온습도 측정 모듈을 호출한다. var dht = require('node-dht-sensor'); // 21번핀에 연결한 DHT11 센서를 초기화 한다. dht.initialize(11, 21); // 온습도 값을 읽어 화면에 출력한다. console.log(dht.read());
  • 112.
    [그림] humidity 는습도 값, temperature 는 온도 값이 반환 된다. 응용 5 – 실시간 모바일/7 Segment 온습도 모니터링 이미 설치한 7 Segment LED 와 모바일을 연동하여 온도와 습도를 표시해 주는 코드를 작성해 보도록 한다. 매 1 초마다 온습도를 측정하고, 그 결과를 모바일로 전송한다. [ ch5_10.js ] var app = require('express')(); var server = require('http').Server(app); var io = require('socket.io')(server); var seg = require('./ch5_07_7seg');
  • 113.
    var dht =require('node-dht-sensor'); dht.initialize(11, 21); app.get('/', function(req, res){ res.sendFile(__dirname + '/ch5_09.html') }); // 온습도 값을 1초 간격으로 측정하여, 7세그먼트의 앞의 2자리는 온도, 뒤의 2자리는 습도를 표시해 준다. setInterval(function(){ var data = dht.read(); seg.setText(data.temperature + '' + data.humidity, false, true, false, false); // 접속한 웹 클라이언트에 측정한 값을 전달한다. io.emit('data', data); },1000); server.listen(8080); console.log('DHT11 Push Server'); setInterval 함수를 이용하여 매 초마다 온습도를 측정하고, 7 Segment 에 온도와 습도를 표시해 주도록 한다. 또한 socket.io 를 이용하여 접속해 있는 모든 모바일 장치에 측정 값을 전달하는 emit 함수를 호출한다. [ ch5_10.html ] <html> <head> <title>DHT11</title> <meta name="viewport" content="width=device-width, initial- scale=1.0" /> <script src="https://code.jquery.com/jquery- 2.1.4.min.js"></script> <script src="/socket.io/socket.io.js"></script> <script> $(function(){ var socket = io(); // 전달받은 온 습도 값을 화면상에 보여준다. socket.on('data', function(data){ $('#temperature').text(data.temperature); $('#humidity').text(data.humidity); }) }) </script> </head> <body> <h1>Temperature & Humidity</h1> <h3><span id='temperature'>0</span>C</h3> <h3><span id='humidity'>0</span>%</h3> </body> </html>
  • 114.
    Socket.io 로 접속되어있는 라즈베리파이로부터 전달 받은 온도와 습도 값을 전송될 때 마다 웹 페이지에 표시해 준다. [그림] 7 Segment 를 통해 온습도가 표시되고, 모바일을 통해서도 실시간으로 확인 가능하다 집 밝기 체크하기 – 조도 센서 집안의 상태를 확인하는데, 온도 습도 이외에 밝기에 따라 조명을 조절하고자 할 때는 조도 센서를 사용할 수 있다. 라즈베리파이의 경우, GPIO 가 디지털 신호만을 사용할 수 있어, 조도를 확인하기 위해서는 디지털로 측정이 가능한 BH1750 센서를 사용하도록 한다. BH1750 센서는 I2C 방식으로 통신이 되는데, I2C 는 클럭(SCL)과 데이터(SDA)의 두 선을 이용하여 통신이 이루어 진다. 약자 본명 용도 SCL Serial Clock 시리얼 통신 동기용 클럭 SDA Serial Data 시리얼 통신 데이터 전달 다음의 명령으로 NodeJS 에 I2C 패키지를 설치하도록 한다. $ npm install git://github.com/jnovack/node-i2c
  • 115.
    I2C 모듈이 설치가완료되었다면, 디지털 광센서로 빛을 확인할 수 있는 코드를 작성하도록 한다. 매 1 초간 빛의 밝기를 검사하여 값을 반환하게 한다. 실질적으로 I2C 와 통신하여 값을 가져오는 부분은 bh1750 으로 별도 모듈을 만들도록 한다. [ ch5_11.js ] var bh = require('./bh1750'); // 1초 간격으로 조도 값을 측정하여 출력한다. setInterval(function(){ bh.getLight(function(val){ console.log(val); }) },1000); I2C 통신으로 BH1750 에 정의된 읽기 명령어와 기본 주소를 이용하여, 명령을 전달하고, 읽혀진 디지털 값을 가져와 반환하는 기능을 구현한다. [ bh1750.js ] // I2C 모듈을 불러온다. var i2c = require('i2c'); // I2C-1 의 0x23 주소에 연결된 I2C 장치에 전달할 명령 값을 설정한다. var options = { address: 0x23, device: '/dev/i2c-1', command: 0x10, length: 2 };
  • 116.
    // I2C에 연결된BH1750 센서를 초기화 한다. var bh = new i2c(options.address, {device: options.device }); // 빛 값을 구하는데 호출된다. exports.getLight = function(cb){ if (!cb) { console.error("missing callback"); return; } // BH1750 센서에 조도 측정 명령을 전달한다. bh.writeByte(options.command, function (err) { if (err) { console.error("error write byte to BH1750 - command: ", options.command); } }); // 조도 값을 읽어 반환한다. bh.readBytes(options.command, options.length, function (err, res) { var hi = res.readUInt8(0); var lo = res.readUInt8(1); cb((hi << 8) + lo); }); } 조도 센서를 손으로 가리거나 밝은 방면으로 위치를 바꾸면, 그에 해당하는 밝기 값을 반환함을 알수 있다. 이를 통해 현재 장소나 시간에 따른 빛 밝기 변화를 감지할 수 있는데, 일반적으로 빛 밝기에 따른 상태는 다음과 같다. 상태 값 범위 여름 정오 경의 야외 조도 1000000 책을 읽기 위한 조도 50 ~ 60 비디오 시청 조도 1400 맑은 날 실내 조도 100 ~ 1000 흐린 날 야외 조도 50 ~ 500 흐린날 실내 조도 5 ~ 50 달빛 조도 0.002 ~ 0.3 빛 없는 밤 0.001 ~ 0.2
  • 117.
    [그림] 조도센서를 이용하여밝기를 수치화 하여 표현할 수 있다. 응용 6 – 집 밝기에 따라 조절되는 LED 조도센서를 응용하면, 어두울때 불 밝기를 자동으로 밝게 해 주는 전등을 만들어 볼 수 있다. 여기서는 밝기에 따라 LED 의 밝기를 조절해 보는 예제를 만들어 볼 것이다. 조도 센서 모듈은 그대로 활용하여 구현한다.
  • 118.
    GPIO 11 번으로제어할 수 있는 LED 를 추가해 준다. 빛의 밝기를 능동적으로 조절하기 위해 PWM 을 활용하여 LED 밝기를 조절한다. var bh = require('./bh1750'); var wpi = require('wiring-pi'); wpi.setup('gpio'); wpi.wiringPiSetup(); var pin = 11; wpi.softPwmCreate(pin, 0, 200) ; // 1초간격으로 조도 값을 읽은 후 그 값을 소프트 PWM방식으로 LED에 입력한다. setInterval(function(){ bh.getLight(function(val){ wpi.softPwmWrite (pin, val); console.log(val); }) },1000); 조도 센서에서 입력 받은 값을 활용하여, LED 소자에 PWM 값을 입력하여 밝기를 조절하게 된다. 밝은 곳에서는 밝게 빛나고 어두운 곳에서는 반대로 어둡게 표현되는 것을 확인할 수 있을 것이다.
  • 119.
    이번 장을 정리하며 이번장에서는 라즈베리파이의 GPIO 와 연결한 다양한 센서를 제어하고 값을 가져오는 법을 확인해 보았다. GPIO 제어를 웹 서버와 통합하여 웹 페이지에서 하드웨어를 제어하고 측정 값을 가져와서 연동하였다. GPIO 제어 역시 NodeJS 와 자바스크립트로 수행하여, 웹 페이지와의 연동을 일관성 있고 빠르게 구축할 수 있었다. 다음 장에서는 사용자에게 보여지는 웹 페이지를 보다 모바일에 최적화 하여 보여줄 수 있도록 jQueryMobile 로 웹 어플리케이션을 꾸미는 법을 다룰 것이다.
  • 120.
    5. 스마트폰을 리모콘으로- jQueryMobile 우리는 그 어떠한 버튼도 달려있지 않은 파이오를 제어하기 위해 스마트 폰을 활용할 것이다. 가장 기본적인 기능인 음악을 검색하여 결과를 보여주고, 선택한 음악을 파이오에서 다운로드 받아 재생할 수 있도록 연계하는 일을 하게 될 것이다. 모바일 웹을 위한 jQueryMobile 라즈베리파이 기반으로 만드는 파이오를 제어하기 위해 우리는 누구나 한손에 들고 있는 스마트 폰을 이용할 것이다. 흔히 사용하는 안드로이드나, iOS 스마트폰 앱을 개발하기 위해서는 Java 나 Objective C, Swift 같은 프로그래밍 언어를 알아야 하는게 일반적이다. 즉 다양한 스마트 장치에 맞는 언어로 프로그래밍을 해야 한다는 것을 의미한다. 하지만 다양한 OS 와 스마트 폰 이외에 스마트 TV, 웨어러블 디바이스등 다양한 장치를 각각의 언어로 개발한다는 것은 사실상 불가능하다. 우리는 처음 시작과 끝을 단 한 언어로 한다고 이야기 한 바 있다. 웹으로 개발하면, 모바일, 데스크탑, 웨어러블 디바이스, 스마트 TV 에 상관없이 돌아가는 어플리케이션을 만들 수 있는 것이다. 모바일 역시도 Javascript 를 이용하여 모바일 웹앱을 만들어 플랫폼과 무관하게 사용할 수 있도록 개발한다. [그림] javascript 개발을 쉽게 해주는 것이 jquery 라면, mobile 웹 개발을 쉽게 해 주는 것이 jquerymobile 이다. jQueryMobile 은 Javascript 라이브러리로 많이 사용되는 jQuery 에서 파생되어 나온 모바일 웹 개발을 위한 웹 프레임 워크이다. Desktop 과 함꼐 사용하기를 원한다면 웹페이지 개발시 가장 많이 활용되는 Bootstrap 을 이용하여 데스크 탑과 모바일이 함께 대응 가능하도록 하는 것이 좋다. 우리는 이번 과정에서는 모바일 장치를 활용한 제어를 목적으로 하여 jQueryMobile 을 사용하여 리모콘 어플리케이션을 구성해 보고자 한다. jQueryMobile 다운로드 > http://jquerymobile.com/download/
  • 121.
    [그림] jquerymobile 을이용하여 다양한 디바이스를 커버하는 모바일 웹을 개발 할 수 있다. 초 간단 모바일 웹 서버 구성하기 4, 5 장에서 NodeJS 와 ExpressJS 를 이용하여 서버를 구성하는 방법을 이미 실습하였다. 이전에는 하드웨어와 연동하기 위해 매번 다른 구성으로 서버를 제작하였는데, 이번에는 모바일 웹 개발만을 위하여 동적으로 웹 페이지를 접근할 수 있는 서버를 구성하게 될 것이다. [ ch6_server.js ] var express = require('express'); var path = require('path'); var app = express(); app.use(express.static(path.join(__dirname, '/'))); app.listen(8080); console.log('Mobile Server'); Express 의 static 함수는 특정 경로의 파일을 자유롭게 접근할 수 있도록 해준다. 앞으로 만드는 파일들은 경로 명을 이용하여 재 구동 필요없이 주소만으로 파일에 접근할 수 있다. [ ch6_1.html ] <html> <head> <title>Mobile Web</title> <meta name="viewport" content="width=device-width, initial- scale=1.0, user-scalable=no" />
  • 122.
    <link rel="stylesheet" href="http://code.jquery.com/mobile/1.4.5/jquery.mobile- 1.4.5.min.css" /> <scriptsrc="http://code.jquery.com/jquery- 2.2.0.min.js"></script> <script src="http://code.jquery.com/mobile/1.4.5/jquery.mobile- 1.4.5.min.js"></script> </head> <body> <h1>Hello Mobile!</h1> </body> </html> jQueryMobile 을 이용하기 위해서는 jQuery 가 기본적으로 필요하다. jQuery 가 jQueryMobile 호출 전에 호출이 될 수 있도록 위치해 놓는다. 각 파일들을 직접 라즈베리파이 상에 다운로드 하여 사용할 수 있지만, 위 코드처럼, 라이브러리에서 제공하는 CDN(Content Delivery Network) 주소를 링크하여 사용해도 무방하다. 앞 장에서 지나간 viewport 는 모바일 개발 시 중요한 속성이다. 이 속성 없이 모바일 웹을 개발하면, 화면에 상당히 작게 표시가 되는 문제가 있는데, 모바일 웹 개발 시에는 필수적으로 head 부분에 viewport meta 정보를 기입하도록 한다. Header Body Footer 로 화면 꾸미기 jQueryMobile 은 머릿 부분인 Header, 주 컨텐츠 영역인 main, 머릿말 영역인 footer 로 구분하여 구성되어 있다. 탭과 같이 기능 전환을 위한 UI 는 footer 에 배치하고, 검색된 음악 결과와 같은 주요 컨텐츠는 main 영역에 배치할 수 있도록 한다. [ ch6_2.html ] <html> <head> <title>Mobile Web</title> … </head> <body> <div data-role="page"> <div data-role='header'> <h1>piAu Remote</h1> </div>
  • 123.
    <div data-role='main' class='ui-content'> <p>HellopiAu!</p> </div> <div data-role='footer'> <div data-role='navbar'> <ul> <li><a href='#' data- icon='grid'>Music</a></li> <li><a href='#' data- icon='star'>Weather</a></li> <li><a href='#' data- icon='gear'>News</a></li> <li><a href='#' data- icon='gear'>Message</a></li> </ul> </div> </div> </div> </body> </html> 위 코드로 Header, Content, Footer 영역이 지정되는 것을 확인할 수 있다. 하지만 Header 와 Footer 가 흰색이라 구분이 잘 되지 않고, Footer 의 경우 중간에 뜬 형태로 표시되어 미적으로 좋지 않다. 이를 수정하기 위해 Header 와 Footer 에 테마를 지정하고, 각각 상단 및 하단에 고정하여 일반적인 모바일 환경으로 보여지도록 수정하자. [ ch6_3.html ] … <div data-role="page"> <div data-role='header' data-theme='b' data-position='fixed'> <h1>piAu Remote</h1> </div> <div data-role='main' class='ui-content'> <p>Hello piAu!</p> </div> <div data-role='footer' data-theme='b' data-position='fixed'> <div data-role='navbar'> <ul> <li><a href='#' data-icon='grid'>Music</a></li> <li><a href='#' data-icon='star'>Weather</a></li> <li><a href='#' data-icon='gear'>News</a></li> <li><a href='#' data-icon='gear'>Message</a></li> </ul> </div> </div> </div> … 이번에 기본 색상인 밝은 테마 대신 어두운 테마를 Header 와 Footer 에 적용해 보도록 한다. data-theme=’b’ 코드를 추가해 주는 것 만으로 어두운 테마가 적용되게 된다. 아울러 첫 예제에서 고정되지 않았던 header 와 footer 에 data-position=’fixed’ 코드를 추가하여 상단과 하단에 고정되어 표시된다.
  • 124.
    [그림] data-theme 와data-position 속성을 이용한 Header, Footer 의 색상 지정 및 고정 효과 적용 하단부의 아이콘은 페이지 이동을 위해 미리 만들어 놓은 공간이다. data-icon 속성으로 아이콘 형태를 지정할 수 있는데, 50 여개의 다양한 아이콘을 지원하고 있다. 다른 아이콘 모양으로 대체하기를 원한다면 http://demos.jquerymobile.com/1.4.5/icons/ 페이지에 접속하여 아이콘 목록을 확인해 볼 수 있다. 필요에 따라서는 써드 파티의 아이콘으로 대체하여 이용할 수도 있다.
  • 125.
    [그림] jquerymobile 에서지원하는 기본 아이콘 목록 음반 검색 창과 목록을 만들자 – LISTVIEW, INPUT TEXT 음악을 검색할 입력 창을 main 영역에 추가하기 위하여 JQM 에서 제공하는 Search Input 을 사용한다. Search Input 을 통해 입력한 값을 SoundCloud 에 전송하여 검색된 결과를 받는다. [ ch6_04.html ] … <div data-role='main' class='ui-content'> <ul data-role='listview'> <li><a href=#>Item 1</a></li> <li><a href=#>Item 2</a></li> <li><a href=#>Item 3</a></li> <li><a href=#>Item 4</a></li> <li><a href=#>Item 5</a></li> </ul> </div> … 음악 검색결과를 main 영역에 보여주기 위하여 jQueryMobile (이하 JQM) 에서 제공하는 ListView 기능을 이용하여 결과를 출력해 준다. 전달받은 음악결과의 Image 를 img 태그에 삽입하여 썸네일 식으로 앨범 모양을 나오게 해주고, 음악 title 명을 h2 태그에 삽입하여 강조되어 보여줄 수 있도록 한다. 기타 세부 사항은 p 태그에 넣어서 사용자가 손쉽게 목록을 확인하고 들을 수 있도록 해준다. 여기서 사용한 ListView 는 최신가요 목록을 가져와서 보여주는 용도에도 사용하게 된다.
  • 126.
    [ ch6_05.html ] … <divdata-role='main' class='ui-content'> <div data-role='fieldcontain'> <input type="search" name="search" id="search" placeholder="Search for content..."/> </div> <div data-role='fieldcontain'> <ul data-role='listview'> <li> <a href=#> <image src="http://cdnimg.melon.co.kr/cm/album/images/026/53/515/2653515.jp g" /> <h2>Item 1</h2> <p>Sub Text</p> </a> </li> <li> <a href=#> <image src="http://cdnimg.melon.co.kr/cm/album/images/026/53/515/2653515.jp g" /> <h2>Item 2</h2> <p>Sub Text</p> </a> </li> <li> <a href=#> <image src="http://cdnimg.melon.co.kr/cm/album/images/026/53/515/2653515.jp g" /> <h2>Item 3</h2> <p>Sub Text</p> </a> </li> </ul> </div> </div> … 차후 음악을 검색할 수 있도록 search 컴포넌트를 배치하고, listiew 컴포넌트를 하단에 위치하도록 한다. 현재는 샘플로 데이터를 넣어 보았지만, 실제 만들어 볼때는 프로그래밍을 통해 음반 목록을 가져오고 보여주게 될 것이다.
  • 127.
    [그림] ListView 를이용하여, 음악목록을 표시할 때, 타이틀 이미지와 함께 보여주면 사용자에게 보다 직관적으로 다가온다. 음악을 재생하고, 볼륨을 조정하자 – GRID, INPUT RANGE, BUTTON 실제 음악을 재생하는 페이지를 구현해 보자. 이번 페이지에는 음악 제목과 재생 완료률을 표시할 수 있는 영역을 배치한다. 아울러 재생 시 컨트롤 할 수 있는 버튼도 레이아웃에 맞도록 배치할 것이다. [ ch6_6.html ] … <div data-role='main' class='ui-content'> <h2>Item1</h2> <input type="range" min="0" max="100" value="80" /> <a href='#' data-role='button'>Prev</a> <a href='#' data-role='button'>Play</a> <a href='#' data-role='button'>Stop</a> <a href='#' data-role='button'>Next</a> </div> … 음악 재생 현황을 0 부터 100% 까지 표시할 수 있도록 range 컴포넌트를 배치하도록 한다. 진행 상황을 백분율로 보여줄 것이다. 재생을 컨트롤 하는 버튼 4 개를 나란히 만들어 본다.
  • 128.
    [ ch6_7.html ] … <divdata-role='main' class='ui-content'> <h2>Item1</h2> <input type="range" min="0" max="100" value="80" /> <div class='ui-grid-c'> <div class='ui-block-a'> <a href='#' data-role='button'>Prev</a> </div> <div class='ui-block-b'> <a href='#' data-role='button'>Play</a> </div> <div class='ui-block-c'> <a href='#' data-role='button'>Stop</a> </div> <div class='ui-block-d'> <a href='#' data-role='button'>Next</a> </div> </div> </div> … 처음 배치한 버튼이 가득 차게 4 개가 보여지는 것을 25%의 크기로 한줄에 보이도록 수정해 보도록 한다. ui-grid 컴포넌트를 이용하면, 해당 사이즈에 맞게 배치할 수 있는데 ui-grid-c 의 경우 25%로 컴포넌트를 배치할 수 있도록 레이아웃을 구성해 준다. [그림] grid 레이아웃으로 내부 버튼 위치를 알맞게 배치할 수 있다.
  • 129.
    [그림] ui-grid-a, ui-grid-b,ui-grid-c, ui-grid-d 에 따라, 50, 33, 25 ,20%로 레이아웃을 지정할 수 있다. 메세징 창과 알람 창 만들기 – Select Box 파이오로 메세지를 보내면 읽어주게 하는 TTS (Text To Speech) 기능을 위한 입력창을 만들어준다. 우리가 일반적으로 사용하는 메세지 입력 창과 같이 메세지를 입력하고, 입력후 이를 전송할 수 있도록 전송 버튼을 입력창 아래 추가한다. 검색 창 구성시에는 검색 내용이 적어서 한줄 입력용인 input box 로 가능하지만, 메세지의 경우 여러줄을 입력하는 경우가 빈번하므로 좀더 많은 내용을 입력할 수 있는 textarea 를 활용하도록 한다. [ ch6_8.html ] … <div data-role='main' class='ui-content'> <textarea rows='5' data-autogrow='false' placeholder='Input message'></textarea> <a href='#' data-role='button'>Send</a> </div> … 한줄 이상의 많은 문자를 입력할 수 있는 textarea 컴포넌트를 컨텐츠 영역에 추가한다. 물리적으로 5 줄을 입력 받을 수 있게 보여지도록 rows=’5’ 를 추가해 준다. [ ch6_9.html ] … <div data-role='main' class='ui-content'> <fieldset data-role='controlgroup'> <legend>Choice day</legend> <input type='checkbox' id='cb-1' /> <label for='cb-1'>Sun</label> <input type='checkbox' id='cb-2' /> <label for='cb-2'>Mon</label>
  • 130.
    <input type='checkbox' id='cb-3'/> <label for='cb-3'>Tue</label> </fieldset> <fieldset data-role='controlgroup'> <legend>Alarm type</legend> <input type='radio' name='rd' id='rd-1' /> <label for='rd-1'>Bell</label> <input type='radio' name='rd' id='rd-2' /> <label for='rd-2'>Music</label> <input type='radio' name='rd' id='rd-3' /> <label for='rd-3'>Weather</label> </fieldset> <div class='ui-grid-a'> <div class='ui-block-a'> <label for='hour'>Hour</label> <select id='hour'> <option value='0'>0</option> <option value='23'>23</option> </select> </div> <div class='ui-block-b'> <label for='time'>Time</label> <select id='time'> <option value='0'>0</option> <option value='2'>59</option> </select> </div> </div> </div> … CheckBox 컴포넌트를 이용하여 여러 요일을 선택할 수 있도록 하고, 한가지 알람 방식을 선택할 수 있도록 radio 컴포넌트를 사용한다. 시간과 날짜의 경우 selectmenu 컴포넌트를 이용하여, 모바일 디바이스에서 한 개의 숫자를 선택할 수 있도록 구성한다.
  • 131.
    [그림] Message 입력창과 알람 설정 창 페이지 호출 시 동작 추가하기 – jQueryMobile 이벤트 이전까지 각각의 페이지를 구분하여 보았다. 이번에 할 일은 각 페이지를 통합하고, 서로 이동할 수 있도록 구성하는 것이다. jQueryMobile 에서 각각의 파일을 나누어 개발하는 방법과 통합하여 개발할 수 있는 방식 모두를 지원한다. 우리는 간단하게 통합하여 개발하는 방법을 다루게 될것이다. 페이지 전환시에 기본적으로는 페이드 형태의 화면 전환효과를 제공하는데, data-transition 값을 지정하여 원하는 효과로 변경 가능하다. 지원하는 화면 전환 효과는 다음과 같다. 전환 효과 설명 fade 흐려지는 효과로 화면이 전환된다. pop 팝업창이 나타나는 효과로 화면이 전환된다. flip 화면이 접히는 효과로 화면이 전환된다. turn 화면이 뒤집히는 효과로 화면이 전환된다. flow 화면이 작아졌다가 흐르는 효과로 화면이 전환된다. slidefade 화면이 흐르면서 흐려지는 효과로 화면이 전환된다. slide 화면이 흐르는 효과로 화면이 전환된다. slideup 화면이 아래로 흐르는 효과로 화면이 전환된다. slidedown 화면이 위로 흐르는 효과로 화면이 전환된다. none 화면전환 효과 없이 화면이 전환된다.
  • 132.
    첫번째 보여지는 페이지가Music List 가 되도록 id 가 music 인 페이지의 class 에 ui-page- active 클래스를 추가하여 사용할 수 있다. [ ch6_10.html ] <html> <head> <title>Mobile Web</title> <meta name="viewport" content="width=device-width, initial- scale=1.0, user-scalable=no" /> <link rel="stylesheet" href="http://code.jquery.com/mobile/1.4.5/jquery.mobile- 1.4.5.min.css" /> <script src="http://code.jquery.com/jquery- 2.2.0.min.js"></script> <script src="http://code.jquery.com/mobile/1.4.5/jquery.mobile- 1.4.5.min.js"></script> <script> $(document).on('pageshow', function(){ alert($.mobile.activePage.attr("id")); }); </script> </head> <body> <div data-role="page" id='music' class='ui-page-active'> <div data-role='header' data-theme='b' data- position='fixed'> <h1>Music</h1> </div> <div data-role='main' class='ui-content'> <div data-role='fieldcontain'> <input type="search" name="search" id="search" placeholder="Search for content..."/> </div> <div data-role='fieldcontain'> <ul data-role='listview'> <li> <a href='#play' data-transition='pop'> <image src="http://cdnimg.melon.co.kr/cm/album/images/026/53/515/2653515.jp g" /> <h2>Item 1</h2> <p>Sub Text</p> </a> </li> <li> <a href='#play' data-transition='pop'> <image src="http://cdnimg.melon.co.kr/cm/album/images/026/53/515/2653515.jp g" /> <h2>Item 2</h2> <p>Sub Text</p> </a> </li> <li> <a href='#play' data-transition='pop'>
  • 133.
    <image src="http://cdnimg.melon.co.kr/cm/album/images/026/53/515/2653515.jp g" /> <h2>Item 3</h2> <p>SubText</p> </a> </li> </ul> </div> </div> <div data-role='footer' data-theme='b' data- position='fixed'> <div data-role='navbar'> <ul> <li><a href='#music' data-icon='grid' data- transition='flip'>Music</a></li> <li><a href='#radio' data-icon='star' data- transition='slidedown'>Radio</a></li> <li><a href='#alarm' data-icon='clock' data- transition='flow'>Alarm</a></li> <li><a href='#message' data-icon='comment' data-transition='turn'>Message</a></li> </ul> </div> </div> </div> <div data-role="page" id='radio'> <div data-role='header' data-theme='b' data- position='fixed'> <h1>Radio</h1> </div> <div data-role='main' class='ui-content'> <ul data-role='listview'> <li><a href=#>Item 1</a></li> <li><a href=#>Item 2</a></li> <li><a href=#>Item 3</a></li> <li><a href=#>Item 4</a></li> <li><a href=#>Item 5</a></li> </ul> </div> <div data-role='footer' data-theme='b' data- position='fixed'> <div data-role='navbar'> <ul> <li><a href='#music' data-icon='grid' data- transition='flip'>Music</a></li> <li><a href='#radio' data-icon='star' data- transition='slidedown'>Radio</a></li> <li><a href='#alarm' data-icon='clock' data- transition='flow'>Alarm</a></li> <li><a href='#message' data-icon='comment' data-transition='turn'>Message</a></li> </ul> </div> </div> </div> <div data-role="page" id='play'> <div data-role='header' data-theme='b' data- position='fixed'> <h1>Music Play</h1>
  • 134.
    </div> <div data-role='main' class='ui-content'> <h2>Item1</h2> <inputtype="range" min="0" max="100" value="80" /> <div class='ui-grid-c'> <div class='ui-block-a'> <a href='#' data-role='button'>Prev</a> </div> <div class='ui-block-b'> <a href='#' data-role='button'>Play</a> </div> <div class='ui-block-c'> <a href='#' data-role='button'>Stop</a> </div> <div class='ui-block-d'> <a href='#' data-role='button'>Next</a> </div> </div> </div> <div data-role='footer' data-theme='b' data- position='fixed'> <div data-role='navbar'> <ul> <li><a href='#music' data-icon='grid' data- transition='flip'>Music</a></li> <li><a href='#radio' data-icon='star' data- transition='slidedown'>Radio</a></li> <li><a href='#alarm' data-icon='clock' data- transition='flow'>Alarm</a></li> <li><a href='#message' data-icon='comment' data-transition='turn'>Message</a></li> </ul> </div> </div> </div> <div data-role="page" id='message'> <div data-role='header' data-theme='b' data- position='fixed'> <h1>Message</h1> </div> <div data-role='main' class='ui-content'> <textarea rows='5' data-autogrow='false' placeholder='Input message'></textarea> <a href='#' data-role='button'>Send</a> </div> <div data-role='footer' data-theme='b' data- position='fixed'> <div data-role='navbar'> <ul> <li><a href='#music' data-icon='grid' data- transition='flip'>Music</a></li> <li><a href='#radio' data-icon='star' data- transition='slidedown'>Radio</a></li> <li><a href='#alarm' data-icon='clock' data- transition='flow'>Alarm</a></li> <li><a href='#message' data-icon='comment' data-transition='turn'>Message</a></li> </ul> </div> </div>
  • 135.
    </div> <div data-role="page" id='alarm'> <divdata-role='header' data-theme='b' data- position='fixed'> <h1>Alarm</h1> </div> <div data-role='main' class='ui-content'> <fieldset data-role='controlgroup'> <legend>Choice day</legend> <input type='checkbox' id='cb-1' /> <label for='cb-1'>Sun</label> <input type='checkbox' id='cb-2' /> <label for='cb-2'>Mon</label> <input type='checkbox' id='cb-3' /> <label for='cb-3'>Tue</label> </fieldset> <fieldset data-role='controlgroup'> <legend>Alarm type</legend> <input type='radio' name='rd' id='rd-1' /> <label for='rd-1'>Bell</label> <input type='radio' name='rd' id='rd-2' /> <label for='rd-2'>Music</label> <input type='radio' name='rd' id='rd-3' /> <label for='rd-3'>Weather</label> </fieldset> <div class='ui-grid-a'> <div class='ui-block-a'> <label for='hour'>Hour</label> <select id='hour'> <option value='0'>0</option> <option value='23'>23</option> </select> </div> <div class='ui-block-b'> <label for='time'>Time</label> <select id='time'> <option value='0'>0</option> <option value='2'>59</option> </select> </div> </div> </div> <div data-role='footer' data-theme='b' data- position='fixed'> <div data-role='navbar'> <ul> <li><a href='#music' data-icon='grid' data- transition='flip'>Music</a></li> <li><a href='#radio' data-icon='star' data- transition='slidedown'>Radio</a></li> <li><a href='#alarm' data-icon='clock' data- transition='flow'>Alarm</a></li> <li><a href='#message' data-icon='comment' data-transition='turn'>Message</a></li> </ul> </div> </div> </div> </body> </html>
  • 136.
    코드 작성을 왼료한 후, 모바일을 이용하여 페이지를 전환하면 전환 효과와 함께 페이지의 id 가 보여지는 것을 알수 있다. 이는 3 줄의 코드에서 pageshow 이벤트, 즉 페이지가 보여질 때 호출되는 이벤트에 현재 보여지는 페이지의 id 를 가져와서 보여주게 하였기 때문이다. 이를 이용하여 페이지 호출에 따라 사전 동작이 가능하도록 프로그래밍 할 수 있도록 도와준다. 실제 사용은 최종 완성시에 하도록 하자. jQueryMobile 에서 지원하는 대표적인 이벤트는 다음과 같다. Event 명 Event 발생 시점 Mobileinit jqueryMobile 의 로딩이 완료된 후 호출된다. Orientationchange 모바일 장치가 가로/세로 모드로 변경될 때 호출된다. Pageinit 페이지가 최초로 로딩되면 호출 된다. Pagebeforeshow 페이지가 보여지기 전에 호출 된다. Pageshow 페이지가 보여지면 호출된다. Pagebeforehide 페이지가 사라지기 전에 호출 된다. pagehide 페이지가 사라지고 호출된다. pagechange jQueryMobile 에서 제공하는 changeMobile() 함수로 호출된다. Scrollstart 스크롤 동작이 시작될 때 호출 된다. Scrollstop 스크롤 동작이 끝날 때 호출된다. Swipe 1 초내에 가로축으로 30px 이상의 손 동작이 발생될 때 호출 된다. Swipeleft Swipe 이벤트가 왼쪽 방향으로 일어날 때 호출 된다. swiperight Swipe 이벤트가 오른쪽 방향으로 일어날 때 호출 된다. Tap 빠르게 터치 이벤트가 발생할 때 발생한다.
  • 137.
    [그림] 페이지 전환효과와 함께 해당 페이지의 id 가 Alert 창으로 표시 된다. 이번 장을 정리하며 이번 장에서는 모바일 웹을 사용자 친화적으로 구성할 수 있게 도와주는 jQueryMobile 을 다루어 보았다. Header, Body, Footer 의 3 단으로 화면을 구성하고, 각종 컴포넌트를 배치하여 모바일 환경에 적합한 UX 구성을 할 수 있도록 했다. 이를 이용해 라즈베리파이의 제어를 모바일 웹 어플리케이션으로 가능 하도록 할 것이다. 다음 장에서는 OpenAPI 를 이용하여 음악 검색/재생을 실습하며, 뉴스 정보를 RSS 를 이용하여 접근하는 법을 다룰 것이다.
  • 138.
    6. 인터넷으로 음악과날씨를 – OpenAPI & RSS 공개 API 를 활용하여 나만의 서비스를 공개 API 는 (Open API, Application Programming Interface) 의 약자로서, 누구나 접근하여 다양한 서비스를 사용할 수 있도록 공개된 API 를 뜻한다. 대표적으로는 구글맵이 있으며, 공개 API 등을 융합하여 새로운 서비스를 만드느 행위를 메시업(Meshup) 이라 불린다. 구글, 야후, MS, 아마존 등에서 제공하는 지도, 상품 정보 등을 웹 서비스 기술을 이용하여 독자적이고 새로운 서비스를 만들 수 있다. 대표적으로 구글 지도에 부동산 매물 정보를 활용하여 서비스를 만들거나, 아마존의 상품 정보를 이용하여 구매 서비스를 만드는 사례가 있다. [그림] 다양한 OpenAPI 를 지원하는 해외의 업체들. 제공하는 API 를 엮어 다양한 서비스 제공이 가능하다. 구글 지도에 범죄율, 학교, 음식점, 은행, 주유소와 같은 편의시설에 기반한 부동산 검색 도구인 트롤리아 로컬(Trulia Local) 음악 OpenAPI 를 사용해 보자 - SoundCloud
  • 139.
    사진에 플리커, 영상에는유투브가 있다면, 음악에는 사운드클라우드가 있다. 자유롭게 올라온 음악들을 스트리밍으로 재생할 수 있는 서비스로, 다양한 나라와 장르의 음악들이 올라와 있어, 영상을 제외한 음악만을 듣고자 할 때 Youtube 보다 편리하게 이용할 수 있다.. 우리는 이 SoundCloud 에서 제공하는 공개 API 를 이용하여 음악을 검색하고, 우리의 파이오로 재생할 수 있는 구성을 할 것이다. [그림] 음악계의 Youtube 인 SoundCloud 사운드클라우드에 가입하기 SoundCloud 서비스를 가입하기 위하여 다음의 주소로 접속하도록 한다. http://www.soundcloud.com 우측 상단에 Create Account 버튼이 가입 버튼인데, ID 로 사용할 e-mail 주소와 접속을 위한 Password 를 입력하여 가입하거나, Facebook 이나 Google 계정을 연결하여 가입할 수도 있다. 가입 후 로그인 (해외 사이트는 로그인의 개념을 Sign in 으로 본다) 을 하도록 한다. [그림] 직접 가입할 수도 있지만, facebook 이나 google 등의 소셜 계정으로 가입하는 것도 가능하다
  • 140.
    공개 API 등록하기 사운드클라우드에정상적으로 가입하였다면, 이제 SoundCloud 에서 제공하는 공개 API 를 사용할 차례이다. 로그인 후 우측 상단의 … 버튼을 클릭하여 개발자 사이트(Developers)로 접속하거나 직접 주소를 입력하여 개발자 사이트에 접속할 수 있다. 개발자 사이트의 주소로 개발자 페이지로 이동할 수 있다. https://developers.soundcloud.com/ 개발자 사이트에 접속하면, SoundCloud 에서 제공하는 공개 API 와 저작도구를 볼 수 있다. 우리는 우측 메뉴의 Register a new App 을 클릭하여, 우리가 만들 공개 API 어플리케이션을 등록하도록 한다. [그림] 개발자 사이트의 Register a new app 을 선택한다. 등록 메뉴를 선택하면 새롭게 생성하고자 하는 어플리케이션 이름을 입력하는 화면이 나온다. 사용자에 따라서 원하는 이름을 입력할 수 있지만, 여기서는 RADIO 로 등록하여 진행해 보도록 하겠다. 어플리케이션 이름을 입력하고, Developer Policies 부분을 체크 한후 우측 하단의 Register 버튼을 클릭한다.
  • 141.
    [그림] 앱 이름을입력한 후 Register 버튼을 클릭 이로써 간단하게 공개 API 의 등록이 끝났다. 이후 모바일 개발을 위해서 Client ID 항목과 Client Secret 내용이 사용된다는 것을 기억해 두자. 다시한번 내용을 확인한 후(메모장에 붙여넣기 하여 보관하도록 하자) Save App 버튼을 클릭하여 공개 API 생성을 마치도록 한다. 실제 어플리케이션 개발을 위하여 등록된 공개 API 를 다시 확인하고자 할 때는, https://developers.soundcloud.com/ 페이지에서 우측 하단의 Your App 메뉴를 클릭하면, 이미 생성한 공개 API 목록을 확인할 수 있다. 우리가 생성한 RADIO 를 클릭하면, 등록된 상세 정보를 다시 확인할 수 있다.
  • 142.
    One More Thing–동영상이 필요할 땐 Youtube, 사진이 필요할땐 Flicker SoundCloud 가 음악 만을 제공하고 있다면, 동영상을 제공하는 것은 Youtube 이다. 만일 만들고자 하는 것이 영상을 포함하는 미디어 플레이어라면, Youtube 를 사용할 수 있다. Youtube 의 API 는 OpenAPI 라는 개념을 초기에 시작한 Google 에서 제공하고 있다. 보다 자세한 정보는 다음 링크에서 확인해 보자. https://developers.google.com/youtube/v3/getting-started 만일, 라즈베리파이에 LCD 를 연결하여, 자동 디지털 액자를 만들고자 한다면, 가장 많은 사진이 공유되고 있는 Flicker 를 활용해 볼 수 있다. 자세한 정보는 다음 링크에서 확인하자. https://www.flickr.com/services/developer [그림] 사진과 영상을 연계하고자 한다면 Flicker 와 Youtube 를 사용할 수 있다. 공개 API 사용해 보기 실제로 등록한 공개 API 가 잘 등록되어 있는지 확인해 볼 수 있다. 사용하는 방법은 우리가 웹 주소를 입력하여 인터넷을 사용하는 방식인 HTTP url 을 호출하는 방식과, SoundCloud 에서 제공해 주는 소프트웨어 개발 키트(Software Developer Kit) 이른바 SDK 를 이용하는 방식 2 개로 구분이 된다. 우리는 간편한 개발을 위해, HTTP url 을 호출하는 방식을 사용하려고 한다. 해당 url 을 입력한 후, 우리가 공개 API 등록 후 제공받았던 Client ID 를 추가하여 검색해 보도록 한다. 사용법 https://api.soundcloud.com/tracks?client_id=[클라이언트 ID]&q=[검색어]
  • 143.
    다음 예시 주소를입력해 보자. https://api.soundcloud.com/tracks?client_id=ef62c17e449fa9321b21733867159b1e&q=Big%2 0Hero [ { "download_url": null, "key_signature": "", "user_favorite": false, "likes_count": 171682, "release": "", "attachments_uri": "https://api.soundcloud.com/tracks/172055891/attachments", "waveform_url": "https://w1.sndcdn.com/QnXGQzkYUaED_m.png", "purchase_url": "http://smarturl.it/fobbh6", "video_url": null, "streamable": true, "artwork_url": "https://i1.sndcdn.com/artworks-000093907678- 79gb6j-large.jpg", "comment_count": 3905, "commentable": true, "description": ""Immortals" from Disney's Big Hero 6 (inspired by the Marvel comic). Download it on iTunes http://smarturl.it/fobbh6 Get the limited 7" vinyl + poster http://bit.ly/1sutR2XnnListen to our new single "Centuries" http://youtu.be/sCbS-TLEoRA Download it on iTunes http://smarturl.it/centuries out now on DCD2/Island nnUpcoming tour dates: http://falloutboy.com/tour nnhttp://falloutboy.comnhttp://facebook.com/falloutboynhttp://tw itter.com/falloutboynhttp://youtube.com/falloutboynhttp://instagra m.com/falloutboynhttp://spoti.fi/T3yFgInn", "download_count": 0, "downloadable": false, "embeddable_by": "all", "favoritings_count": 0, "genre": "fall out boy", "isrc": null, "label_id": null, "label_name": null, "license": "all-rights-reserved", "original_content_size": 4632820, "original_format": "mp3", "playback_count": 10426258, "purchase_title": null, "release_day": null, "release_month": null, "release_year": null, "reposts_count": 18821, "state": "finished", "tag_list": "alternative pop "big hero six"", "track_type": null, "user": { "avatar_url": "https://i1.sndcdn.com/avatars-000121237593- dl8xs0-large.jpg", "id": 3678183, "kind": "user",
  • 144.
    "permalink_url": "http://soundcloud.com/falloutboy", "uri": "https://api.soundcloud.com/users/3678183", "username":"FallOutBoy", "permalink": "falloutboy", "last_modified": "2016/01/11 22:49:04 +0000" }, "bpm": null, "user_playback_count": null, "id": 172055891, "kind": "track", "created_at": "2014/10/14 04:53:00 +0000", "last_modified": "2016/01/20 20:50:42 +0000", "permalink": "fall-out-boy-immortals-from-big-hero-6", "permalink_url": "https://soundcloud.com/falloutboy/fall-out- boy-immortals-from-big-hero-6", "title": "Immortals [From Big Hero 6]", "duration": 192983, "sharing": "public", "stream_url": "https://api.soundcloud.com/tracks/172055891/stream", "uri": "https://api.soundcloud.com/tracks/172055891", "user_id": 3678183 }, {…} ] [그림] 실제 SoundCloud 검색 결과로 받아지는 JSON 데이터 형식 우리는 음악 검색을 위해 q 필터를 이용하였다. q 는 음원 이름을 입력하여 검색 결과를 받는 가장 쉽고 간편한 방법으로, 실행해 보면 웹 상에서 검색 결과를 볼 수 있다. 입려어에 일치하는 다양한 검색 결과를 배열 형태로 반환하는 것을 알수 있다. 결과에서 보여주는 주요 내용은 다음과 같다. 항목 내용 Created_at 해당 음원이 등록된 시간을 보여준다. Title 해당 음원의 타이틀 명을 보여준다. Permalink_url SoundCloud 사이트에서 해당 음원의 페이지 정보를 보여준다. Artwork_url 해당 음원의 대표 이미지 url 을 보여준다 Description 해당 음원의 상세 설명을 보여준다. Duration 해당 음원의 총 재생 시간을 보여준다. genre 해당 음원의 장르를 보여준다. Playback_count 재생한 횟수를 보여준다 bpm Beat Per Minute 의 약자로 템포, 즉 음악의 빠르기를 나타낸다. Release_year 음악이 공개된 연도를 나타낸다. 입력 인자 값으로 우리는 질의어인 q 만을 이용해 보았다. 하지만 이 항목에 좀더 다양한 인자값을 적용하면, 우리가 원하는 종류의 음악을 검색할 수 있는데, 추후 들은 음악을 기반으로 추천 서비스를 만들 때 사용될 항목이니 참고하고 넘어가도록 하자.
  • 145.
    항목 내용 q 음악을검색하기 위한 검색어를 입력한다. genres 콤마로 구분되는 장르 목록을 입력한다. Bpm[from] 입력 값 이상의 bpm 을 가지는 음반 목록을 검색한다. Bpm[to] 입력 값 이하의 bpm 을 가지는 음반 목록을 검색한다. Duration[from] 입력 값 이상의 재생 시간을 가지는 음반 목록을 검색한다. Duration[to] 입력 값 이하의 재생 시간을 가지는 음반 목록을 검색한다. 음악을 실제로 재생해 보자 – scdl, MPlayer Scdl 다운로드 사운드 클라우드의 API 를 이용하여 음악을 검색해 보았다. 하지만, 이렇게 검색한 결과로는 웹 페이지 상에서만 음악을 들을 수 있을 뿐이다. 이를 라즈베리파이에서 재생하기 위해서는 MP3 플레이어와 같이 MP3 파일이 라즈베리파이 상에 다운로드 되어 재생할 수 있는 구성으로 이루어져야 한다. 파이썬 언어로 개발된 SoundCloud DownLoader 즉, scdl 은 url 만 있으면 음원을 다운로드 받을 수 있게 해준다. https://github.com/flyingrub/scdl 이 scdl 을 다운로드 받아 설치하기 위해서는 python 패키지 관리프로그램을 이용해야 한다. python 에서 패키지 소프트웨어를 설치및 관리하는 패키지 관리 시스템으로 가장 많이 활용하는 것이 pip 이다. Python 2.7.9 과 3.4 이후 버전은 pip 를 기본적으로 포함하나, 라즈비안에는 제외되어 있다. Scdl 은 python 3.4 로 제작되어 있어,, Python 3 용 pip 를 설치해 주도록 한다 $ sudo apt-get install python3 $ sudo apt-get install python3-pip Scdl 패키지를 설치하기 위해서는 다음과 같이 실행하면 된다. $ sudo apt-get install pandoc $ sudo pip3 install pypandoc $ sudo pip3 install scdl 설치가 완료되면, scdl 이 정상적으로 동작하는지 확인해 본다. $ scdl – version
  • 146.
    [그림] SCDL 이정상적으로 설치되었다. 만일 scdl 패키지를 삭제하기 위해서는 다음과 같이 실행한다 $ sudo pip3 uninstall scdl One More Thing – scdll 이 정상적으로 설치되지 않는 경우 scdl 을 설치하기 위해서는 python 3.3 이상의 버전이 설치되어 있어야 한다. 신 버전인 jessie 의 경우 3.4 가 설치되어 있어 문제가 없지만, 라즈베리파이에 구 버전의 wheezy 기반의 라즈비안이 설치되어 있는 경우, python 버전이 3.2 이므로 설치시에 문제가 발생한다. 만일 상황에 따라 python 을 설치해야 하는 경우 다음의 절차를 따르도록 하자. > 필수 요구사항 설치 sudo apt-get update sudo apt-get upgrade -y sudo apt-get install build-essential libncursesw5-dev libgdbm-dev libc6-dev sudo apt-get install zlib1g-dev libsqlite3-dev tk-dev sudo apt-get install libssl-dev openssl > 라이브러리 다운로드 및 컴파일 수행 $ cd ~ $ wget https://www.python.org/ftp/python/3.4.4/Python-3.4.4.tgz $ tar -zxvf Python-3.4.4.tgz $ cd Python-3.4.4 $ ./configure $ make $ sudo make install python 설치 확인 $ python3 > PIP 설치 $ cd ~ $ wget https://bootstrap.pypa.io/get-pip.py $ sudo python3.4 get-pip.py >pip 설치 확인
  • 147.
    pip3 --version > 기존Python Overwrite 하기 sudo ln -sf /usr/local/bin/python3.4 /usr/local/bin/python3 이제 설치가 되지 않던 scdl 을 설치할 수 있을 것이다. MP3 음악을 플레이 해 보자 라즈베리파이가 linux 로 구동이 되기 때문에 mp3 를 실행할 수 있는 프로그램이 다양하게 있다. 하지만, 라즈베리파이의 성능을 고려했을때, 가볍게 mp3 를 재생할 수 있는 mpg321 이라는 프로그램을 많이 사용한다. mpg321 을 설치하기 위해서 다음과 같은 명령을 입력해 보자 $ sudo apt-get -y install mpg321 mp3 가 재생이 잘 되는지 실험적으로 mp3 를 다운로드 받아서 재생해 보자 $ sudo wget http://www.freespecialeffects.co.uk/soundfx/household/bubbling_water_1.mp3 $ mpg321 bubbling_water_1.mp3 mp3 재생 시 g 옵션을 이용하여 볼륨을 조절할 수 있다. 최대 볼륨의 50%로 재생하기 위해서는 다음과 같이 명령을 입력하면 된다. $ mpg321 -g 50 bubbling_water_1.mp3
  • 148.
    [그림] 다운로드 후,음악이 정상적으로 재생됨을 알수 있다. 이제 사운드클라우드에서 검색한 음악을 직접 재생해 보자. scdl 로 mp3 를 다운로드 하기 위해서는 url 을 알아야 한다. https://api.soundcloud.com/tracks?client_id==ef62c17e449fa9321b21733867159b1e&q=Big% 20Hero 을 브라우저에서 입력하면 다음과 같은 결과가 보여지는데, 줄력값중 permalink_url 을 이용하면 된다. { ... permalink_url: "https://soundcloud.com/falloutboy/fall-out-boy-immortals-from-big-hero- 6", ... } 다음과 같이 명령어를 입력하면 mp3 파일이 다운로드 받아진다. $ scdl -l [사운드클라우드 음악 URL] $ scdl -l https://soundcloud.com/falloutboy/fall-out-boy-immortals-from-big-hero-6
  • 149.
    받아진 음원을 mpg321로 잘 재생이 되는지 확인해 본다, $ mpg321 fall-out-boy-immortals-from-big-hero-6.mp3 MP3 를 재생하고 제어해 보자 - mplayer MP3 를 검색하고 다운로드 받기위한 기본 준비를 마쳤다. 음악을 재생할때 오디오 화면과 리모콘에서 시간을 표시하기 위해서는 MP3 의 재생시간을 알아야하고, Rewind, Forward 같은 기능을 함께 지원해야 한다. 이러한 기능을 함께 지원하는 다양한 프로그램중 우리가 사용할 프로그램은 MPlayer 이다. 다음 명령을 이용하여 설치하도록 한다. $ sudo apt-get install mplayer 음악을 재생하기 위해서는 파일명만 기입해도 되나, 총 재생 시간 및 세부적인 정보를 확인하기 위해서는 추가적으로 –identify 옵션을 사용할 수 있다. $ mplayer -identify big_hero.mp3
  • 150.
    [그림] 재생 시간및 재생률, 볼륨 조절 및 감기, 되돌리기가 가능한 mplayer MPlayer 는 음악 재생 중에 Backward/Forward, 볼륨 조절등 다양한 기능을 제공한다. 음악 재생 중에 사용할 수 있는 옵션들은 다음과 같다. 키 설명 LEFT,RIGHT 10 초 이전이나 이후로 재생위치를 변경한다. UP,DOWN 1 분 이전이나 이후로 재생위치를 변경한다. PG UP, PG DOWN 10 분 이전이나 이후로 재생위치를 이동한다. <, > 재생목록의 이전 혹은 이후로 이동한다. P, SPACE 일시 정지하고, 정지 상태에서 다시 누르면 재생된다. Q, ESC 재생을 멈추고 프로그램을 종료한다. *, / 볼륨을 올리거나 낮춘다. M 음소거를 활성화 하거나 비 활성화 한다. [. ] 재생을 느리게 하거나 빠르게 한다. One More Thing – YTDL 을 이용한 Youtube 영상 받아서 재생하기 SoundCloud 가 scdl 이라는 프로그램이 있다면, Youtube 에는 ytdl 이 있다. SoundCloud 와 같이 Youtube 영상을 다운로드 받을 수 있게 도와주는 프로그램이다. NodeJS 로 제작되어 있으므로 NPM 을 이용하여 쉽게 설치할 수 있다. $ sudo npm install ytdl
  • 151.
    다음과 같이 url을 입력하여, 원하는 영상을 받을 수 있다. $ sudo ytdl http://www.youtube.com/watch?v=_HSylqgVYQI > myvideo.flv 보다 상세한 사용방법은 아래의 사이트에 접속하여 확인하도록 한다. https://github.com/fent/node-ytdl 오늘의 날씨를 알아보기– RSS 오픈 API 와 유사하지만 또 다른 하나는 RSS(Rich Site Summary) 이다. 뉴스나 블로그 사이트들에서 주로 사용하는 컨텐츠 표현 방식으로, XML 형식으로 요약된 웹 정보를 제공하여, 사이트에 방문하지 않고도 최신 정보를 간단하게 확인할 수 있다. 흔히 RSS 정보를 제공하는 데이터 형식을 RSS Feed 라 칭하기도 한다. [그림] 뉴스 및 블로그 사이트에서 RSS 형식의 데이터를 제공한다. 기상청에서는 RSS 를 이용하여 오늘의 날씨를 간편하게 확인할 수 있도록 정보를 제공하고있다. 기상청에서 제공하는 날씨 정보를 다음의 URL 로 각 지역의 날씨 정보를 RSS 형식으로 제공한다.
  • 152.
    [그림] 다양한 지역의날씨 정보를 제공하는 기상청 사이트 지역 RSS 주소 전국 http://www.kma.go.kr/weather/forecast/mid-term-rss3.jsp?stnId=108 서울, 경기도 http://www.kma.go.kr/weather/forecast/mid-term-rss3.jsp?stnId=109 강원도 http://www.kma.go.kr/weather/forecast/mid-term-rss3.jsp?stnId=105 충청북도 http://www.kma.go.kr/weather/forecast/mid-term-rss3.jsp?stnId=131 충청남도 http://www.kma.go.kr/weather/forecast/mid-term-rss3.jsp?stnId=133 전라북도 http://www.kma.go.kr/weather/forecast/mid-term-rss3.jsp?stnId=146 전라남도 http://www.kma.go.kr/weather/forecast/mid-term-rss3.jsp?stnId=146 경상북도 http://www.kma.go.kr/weather/forecast/mid-term-rss3.jsp?stnId=143 경상남도 http://www.kma.go.kr/weather/forecast/mid-term-rss3.jsp?stnId=159 제주도 http://www.kma.go.kr/weather/forecast/mid-term-rss3.jsp?stnId=184 [그림] rss 로 제공되는 기상청 날씨 정보. XML 형식이라 그대로 사용하기 쉽지 않다. 기상청에서는 RSS 로 지역별 날씨정보를 제공하고 있다. 이를 응용할 수 있는데, XML 형식으로 제공되어 실제로 원하는 데이터만 추출하여 사용하기 쉽지 않다. 9 장에서 데이터를 변환하여 라즈베리파이로 읽어주는 기능을 구현하는 법을 다룰 것이다. One More Thing – 뉴스 정보를 RSS 로 제공 받기 날씨 정보와 더불어 RSS 를 이용하여 정보를 가져오기 좋은 정보중 하나가 뉴스이다. 다양한 언론사에서는 RSS 형식으로 자사의 뉴스 정보를 제공하고 있다. 다음 링크에 접속하면, 언론사들의 RSS 제공 주소를 확인할 수 있다.
  • 153.
    http://bit.ly/1JaZhD7 지능형 서비스 추가하기- BOT API 2015 년 DARPA 로봇 챌린지에서 KAIST 휴보의 우승과 2016 년 인간의 영역으로 영원할 것 같았던 바둑에서의 알파고의 대 활약으로, 지능형 서비스에 대해 관심이 높아지고 있다. 애플의 시리(Siri)를 비롯하여, 구글의 나우(Now), MS 의 코나타(Cortana) 등 다양한 지능형 서비스들이 많은 영역에서 사용되고 있다. 페이스북도 메신저에 M 이라 불리는 인공지능 비서 기능을 탑재하기로 밝혔는데, 여기에는 Wit.ai 사의 봇 엔진이 사용되고 있다. 자세히 다루는 것은 이 책의 범위를 벗어나므로, 간단히 소개할 예정이다. 보다 관심이 있는 독자라면, 해당 문서를 살펴보도록 하자. [그림] 페이스북의 지능형 메신저 M 에 적용된 Wit.ai 의 봇 엔진 Wit.ai 사의 봇 엔진은 일반인도 사용할 수 있도록 오픈되어 있다. Wit.ai 를 이용하여 대화나 명령에 따른 반응형 서비스를 제공할 수 있는 지능형 서비스가 가능하며, 유사한 서비스로 Api.ai 라는 서비스가 존재한다. 이 API 를 이용하면, 사용자가 입력한 문장의 의도를 파악하거나, 그에 따른 상호 동작을 지정하는데 도움이 된다.
  • 154.
    [그림] clarifai 사의API 를 활용하면, 이미지 정보가 의미하는 바를 추출할 수 있다. 라즈베리파이의 경우 카메라 장착이 가능하므로, 추후 이미지 인식 기반의 서비스로 발전시킬 수도 있다. 영상 및 이미지 인식으로 많이 언급되는 것은 Google Cloud Vision API 와 clarifai API 가 있다. 이미지를 입력하면, 해당 이미지가 의도하는 정보를 추출하여 알려주므로, 시각적 정보 기반으로 인터랙션이 필요한 경우 유용하게 활용될 수 있다. Clarifai 의 경우 설정을 통해 결과 단어를 한글로 받을 수도 있으니 참고하도록 하자. 이번 장을 정리하며 이번 장에서는 OpenAPI 를 이용하여 음악을 검색하고 재생해 보았으며, 기상청 RSS 정보를 이용하여 날씨 정보를 확인하였다. 여기서 소개한 서비스 이외에 다양한 OpenAPI 를 접목하면, 만들고자 하는 서비스를 좀더 풍요롭고 적은 노력으로 구성할 수 있을 것이다. (OpenAPI 로 검색하면 제공되는 다양한 서비스를 확인할 수 있다.) 다음 장에서는 배운 내용을 바탕으로 라즈베리파이로 오디오를 만드는 방법을 다룰 것이다.
  • 155.
    7. 오디오 소프트웨어개발 하기 우리는 지금까지 라즈베리파이 오디오 개발을 위한 기본학습을 마무리 했다. 본격적으로 하드웨어를 조립하고 소프트웨어을 작성하여 여러분 만의 파이오를 만들 차례이다. 파이오를 만드는 구조는 다음의 순서로 진행이 될 것이다. 가장 기본 기능인 음악 검색 및 재생 기능을 만들고, 알람기능, 날씨 및 뉴스 안내기능, 환경설정 기능등을 다룬다. 오디오가 말하도록 해 보자 – TTS 기능 설치 라즈베리파이에서 TTS 를 구현할 수 있는 방법 중 우리는 구 안드로이드에 탑재 된 바 있는 SVOX 사의 pico TTS 엔진을 설치할 것이다. 라즈비안 패키지 저장소에 포함되어 있지 않아, 수동으로 받아서 설치를 해 주어야 한다. 다음 3 가지의 패키지를 다운로드 받아주도록 한다. $ sudo wget http://http.us.debian.org/debian/pool/non-free/s/svox/libttspico-data_1.0+git20130326- 3_all.deb $ sudo wget http://http.us.debian.org/debian/pool/non- free/s/svox/libttspico0_1.0+git20130326-3_armhf.deb $ sudo wget http://http.us.debian.org/debian/pool/non-free/s/svox/libttspico- utils_1.0+git20130326-3_armhf.deb 패키지의 다운로드를 끝마쳤다면, 해당 패키지를 설치해 주도록 한다. $ sudo dpkg -i libttspico-data_1.0+git20130326-3_all.deb $ sudo dpkg -i libttspico0_1.0+git20130326-3_armhf.deb $ sudo dpkg -i libttspico-utils_1.0+git20130326-3_armhf.deb 모든 패키지의 설치를 마쳤다면, TTS 동작이 잘 되는지 다음 명령어로 확인해 본다. $ pico2wave --wave test.wav "Hello World" && aplay test.wav Hello World 라는 음성이 제대로 출력되는 것을 확인할 수 있을 것이다.
  • 156.
    [그림] Pico TTS를 이용하면 라즈베리파이에서 Text To Speech 기능을 활용할 수 있다. One More Thing – 구형 라즈베리파이에서 TTS 사용하기 위의 방법은 최신 라즈베리파이 2 에서만 적용이 되고, 구형 라즈베리파이 B+ 혹은 A+ 이하의 기종에서는 CPU 의 차이로 인하여 동작하지 않는 문제가 발생한다. 구형 기종 사용시에는 wheezy 기반의 패키지를 설치해야 구형 라즈베리파이와 신형 라즈베리파이 모두 잘 동작한다. $ sudo wget http://old-releases.ubuntu.com/ubuntu/pool/multiverse/s/svox/libttspico- data_1.0+git20110131-2_all.deb $ sudo wget http://old- releases.ubuntu.com/ubuntu/pool/multiverse/s/svox/libttspico0_1.0+git20110131- 2_armhf.deb $ sudo wget http://old-releases.ubuntu.com/ubuntu/pool/multiverse/s/svox/libttspico- utils_1.0+git20110131-2_armhf.deb 다운로드 후, Package 디렉토리로 이동한 후 패키지를 설치해 주면 정상적으로 pico2wave 명령어를 사용할 수 있다. $ sudo dpkg -i libttspico-data_1.0+git20110131-2_all.deb $ sudo dpkg -i libttspico0_1.0+git20110131-2_armhf.deb $ sudo dpkg -i libttspico-utils_1.0+git20110131-2_armhf.deb
  • 157.
    음악 검색 및재생기능을 만들자 파이 오디오를 위해 GPIO 에 연결해 줄 장치는 2 개이다. LED 는 백 라이트 역할을 하고, 7 Segment 디스플레이는 현재 시간 및 재생 시간을 표시해 주는 용도로 사용되게 된다. 특이 사항으로는 7 Segment 의 5v 전원을 GPIO 핀에 연결한 것이다. 5v 가 연결된 상태로 라즈베리파이가 부팅 될 때, 디스플레이가 오 동작하는 문제가 발생할 수 있어, GPIO 를 이용하여 부팅 후 추가 전원을 공급해 주는 방식을 사용할 것이다. [그림] 파이 오디오 하드웨어 연결에는 2 가지 부품을 GPIO 에 연결해 준다. 이제 가장 기본 기능인 음악 검색 및 재생 기능을 구현해 볼 것이다. 평상 시에는 시계로 동작하다가, 음악 검색 후 재생 명령을 내리면, 해당음악을 재생해주고 남은 재생 시간을 표시해 주는 기능을 제공한다. 아울러 음악 재생시 볼륨 조절이 가능하도록 관련 기능을 만든다. 이번 장 부터는 구현할 내용이 많으므로, NodeJS 의 모듈 기능을 이용하여 파일을 분류하도록 한다. [ ch7_1_server.js ] var express = require('express'); var bodyParser = require('body-parser'); var path = require('path'); var exec = require('child_process').exec; var spawn = require('child_process').spawn; var ext = require('./ch7_1_extend'); var app = express(); var server = require('http').Server(app); var io = require('socket.io')(server); var isTime = true;
  • 158.
    var current_music; app.use(bodyParser.json()); app.use(bodyParser.urlencoded({ extended:true })); app.use(express.static(path.join(__dirname, '/'))); // 처음 구동 시, 현재 시각을 화면에 표시한다. ext.setCurrentTime(); // 10초 간격으로 현제 시간을 측정하여 화면을 갱신한다. setInterval(function(){ if(isTime){ ext.setCurrentTime(); } },10000); // 클라이언트로부터 재생 명령이 수행될 때 호출된다. app.post('/play', function(req, res){ // 사운드 클라우드의 URL을 구한다. var url = req.body.url; console.log(__dirname + ' / ' + url); isTime = false; ext.tts('Downloading...'); // SCDL 명령어를 실행하여 음악을 임시로 다운로드 한다. exec('sudo scdl -l ' + url, function(err, stderr, stdout){ // 음악 파일의 이름이 존재하는지 확인한다. if(!err && stdout.indexOf('[37mDownloading ') > -1){ // 음악 파일의 이름을 구한다. var file = stdout.split('[37mDownloading ')[1].split('[0mn')[0] + '.mp3'; // 현재 재생 음악을 기억해 둔다. current_music = file; console.log('Playing : ' + file); ext.tts('Playing the music.'); // mplayer 명령으로 음악을 재생한다. var ps = spawn('mplayer',['/' + file]); // mplayer 실행시 로그에 의해 호출된다. ps.stderr.on('data', function(data){ // 다국어 문자를 위해 utf8 형식으로 전환한다. var progress = data.toString('utf8'); // 화면 및 클라이언트에 표시할 재생 시각을 구한다. if(progress && progress.indexOf('of') > -1){ var token = progress.split(' of '); // 재생 시각을 구한다. var current = token[0].split(' ').slice(-2)[0]; var total = token[1].split(' ')[0] // 재생 시간을 화면에 표시한다. ext.setTime(Math.round(total - current)); // 재생 시간을 클라이언트에 표시하기 위해 전달한다.
  • 159.
    io.emit('time', { total: total, current : current}); } }); // mplayer 동작이 끝날 때 호출된다. ps.on('close', function(code){ console.log(code); ext.tts('Finished'); isTime = true; }); } else { console.log(err); } }); res.json({ result : true}); }); // 음악 재생 목록을 확인할 때 호출된다. app.post('/list', function(req, res){ res.json({ list : ext.get()}); }); // 사운드 볼륨을 조절할 때 호출된다. app.post('/volume', function(req, res){ var vol = req.body.volume; // 볼륨 값을 조정한다. 단, 너무 자주 호출되는 것을 방지하기 위해 // 한번 조정 후 5초간 유예 기간을 가지게 한다. isTime = false; ext.setVolume(vol); setTimeout(function(){ isTime = true; },5000); res.json({ result : true }); }); // 정지한 음악을 다시 재생할 때 호출 한다. app.post('/resume', function(req, res){ // 기존에 나오는 음악이 있다면 강제로 종료한다. exec('pkill mplayer'); isTime = false; console.log('Resume : ' + current_music); ext.tts('Playing the music.'); // 마지막으로 재생한 음악을 다시 재생한다. var ps = spawn('mplayer',['/' + current_music]); ps.stderr.on('data', function(data){ var progress = data.toString('utf8'); if(progress && progress.indexOf('of') > -1){ var token = progress.split(' of '); var current = token[0].split(' ').slice(-2)[0]; var total = token[1].split(' ')[0]
  • 160.
    ext.setTime(Math.round(total - current)); io.emit('time',{ total : total, current : current}); } }); ps.on('close', function(code){ console.log(code); ext.tts('Finished'); isTime = true; }); res.json({ result : true }); }); // 음악 정지가 요청될 때 호출된다. app.post('/stop', function(req, res){ // 음악 재생시 사용하는 mplayer를 강제로 종료한다. exec('pkill mplayer'); res.json({ result : true }); isTime = true; }); server.listen(8080); console.log('piAu Server'); 주 서버 코드로서, 음악을 다운로드 받아 재생하고, 멈추고, 볼륨 조절 명령을 모바일로부터 받을 수 있도록 서비스를 열어 두었다. Mplayer 실행 후 음원에 대한 정보와 함께 재생 시간을 표시해 주는데, 외부 프로세스를 실행하는데 사용한 exec 와 달리 spawn 은 실시간으로 정보를 가져오는 데 적합하여 해당 함수를 이용하여 음악 재생을 실행한다. 재생중에 나오는 재생 시간 정보를 가져와서 화면에 표시해 주고, 모바일에 진행 정보를 전달한다. 단, 감지되는 재생 정보가 스트림 정보로, 사람이 알수 없는 Byte 코드 값으로 보여진다. 따라서 toString(‘utf8’) 명령을 이용하여 일반 텍스트로 전환하여 사용하도록 한다. [ ch7_1_extend.js ] var exec = require('child_process').exec; var MAX7219 = require('./MAX7219'); var disp = new MAX7219("/dev/spidev0.0"); // 자바스크립트에서 저장소로 사용할 수 있는 로컬스토리지 모듈을 호출한다. var LocalStorage = require('node-localstorage').LocalStorage; // 음악 재생 목록을 관리할 저장소를 준비한다. var localStorage = new LocalStorage('./music_list'); disp.setDecodeAll(); disp.setScanLimit(4); disp.setDisplayIntensity(15); disp.startup(); var volume = 100; // 1초 간격으로 거리 값을 측정한다. var setTime = function(number1, number2){ console.log(number1 + '/' + number2); var min1 = Math.floor(number1 / 10); var min2 = number1 % 10;
  • 161.
    // 초의 10단위를구한다. var sec1 = Math.floor(number2 / 10); // 초의 1단위를 구한다. (10으로 나눈 나머지) var sec2 = number2 % 10; disp.setDigitSymbol(0, min1); disp.setDigitSymbol(1, min2); disp.setDigitSymbol(2, sec1); disp.setDigitSymbol(3, sec2); } // pico2tts 로 tts 음성파일을 만들고 곧바로 재생한다. var tts = function(msg){ exec('pico2wave --wave tmp.wav "' + msg + '" && aplay tmp.wav'); } // 입력받은 시간 값을 7세그먼트에 출력한다. exports.setTime = function(remain){ setTime(Math.floor(remain / 60), remain % 60); } // 현재 시간을 구하여 출력한다. exports.setCurrentTime = function(){ var date = new Date(); setTime(date.getHours(), date.getMinutes()); } // 볼륨을 설정한다. exports.setVolume = function(vol){ if(vol == 'up'){ if(volume < 100){ volume += 5; } } else { if(volume > 0){ volume -= 5; } } tts('Volume ' + volume + '%'); // 볼륨을 설정하는 amixer 를 이용해 시스템 볼륨을 조정한다. exec('amixer sset PCM ' + volume + '%'); } exports.tts = tts; 시간을 표시할 때는 4 자리 digit 을 활용해 표시해 주는데, 160 초의 재생 시간을 가지고 있다면 2 분 40 초, 즉 화면상에는 0240 으로 표시를 해 주어야 한다. 초 단위의 시간을 분 단위로 표시해 주기 위해 보정함수를 사용해 주도록 한다. 오디오 볼륨 조절의 경우 amixer 프로그램을 이용하여 출력을 조절 할 수 있는데, TTS 를 위한 pico2wave 와 함께 exec 명령으로 해당 프로그램을 실행하는 방법을 사용한다. [ ch7_1.html ]
  • 162.
    <html> <head> <title>Mobile Web</title> <meta name="viewport"content="width=device-width, initial- scale=1.0, user-scalable=no" /> <link rel="stylesheet" href="http://code.jquery.com/mobile/1.4.5/jquery.mobile- 1.4.5.min.css" /> <script src="http://code.jquery.com/jquery- 2.2.0.min.js"></script> <script src="http://code.jquery.com/mobile/1.4.5/jquery.mobile- 1.4.5.min.js"></script> <script src="https://connect.soundcloud.com/sdk/sdk- 3.0.0.js"></script> <script src="/socket.io/socket.io.js"></script> <script src='./ch7_1.js'></script> </head> <body> <div data-role="page" id='music' class='ui-page-active'> <div data-role='header' data-theme='b' data- position='fixed'> <h1>Music</h1> </div> <div data-role='main' class='ui-content'> <div data-role='fieldcontain'> <input type="search" name="search" id="search" placeholder="Search for content..."/> </div> <div data-role='fieldcontain'> <ul data-role='listview'> <li>No musics</li> </ul> </div> </div> </div> <div data-role="page" id='play'> <div data-role='header' data-theme='b' data- position='fixed'> <h1>Music Play</h1> </div> <div data-role='main' class='ui-content'> <h2>Title</h2> <input id='time' type="range" min="0" max="100" value="0" /> <div class='ui-grid-c'> <div class='ui-block-a'> <a href='#' id='resume' data- role='button'>Play</a> </div> <div class='ui-block-b'> <a href='#' id='stop' data- role='button'>Stop</a> </div> <div class='ui-block-c'> <a href='#' class='ui-icon-plus' id='vol_up' data-role='button'>+</a> </div> <div class='ui-block-d'>
  • 163.
    <a href='#' class='ui-icon-minus' id='vol_down'data-role='button'>-</a> </div> </div> </div> </div> </body> </html> jQueryMobile 을 이용하여 음악 검색과 재생 페이지를 구현한다. 여러 페이지를 하나의 HTML 에 만드는 경우, 처음 보여지는 페이지는 ui-active-page 클래스를 추가하여 첫 페이지를 설정할 수 있다. 음악 검색 화면을 첫번째 화면으로 설정하도록 하자. [ ch7_1.js ] // 사운드 클라우드 가입시 받은 client_id 를 입력한다. SC.initialize({ client_id: 'ef62c17e449fa9321b21733867159b1e' }); $(function(){ var socket = io(); // 시간을 전달 받아 화면에 표시한다. socket.on('time', function(data){ $('#time').val(Math.round(data.current * 100/ data.total)); $('#time').slider( "refresh" ); }); // 웹 페이지가 불러질 때 발생한다. $(document).on('pageshow', function(){ //alert($.mobile.activePage.attr("id")); }); // 검색창에서 키가 눌러졌을 때 호출된다. $('#search').keypress(function(e){ // 엔터값 (아스키 코드로 13임)이 눌러졌을 때 호출된다. if (e.which == 13) { // 검색 어를 구한다. var query = $('#search').val(); // 클릭 이벤트를 강제로 발생시켜, 가상키보드를 화면에서 감춘다. $('body').trigger('click'); $('#search').val(''); // 검색 결과 창을 초기화 한다. $('#music ul[data-role=listview]').empty(); // 사운드 클라우드를 통해 음악을 검색한다. SC.get('/tracks', { q: query }).then(function(tracks) { // 검색된 음악 값을 하나씩 가져온다. for(var i in tracks){ var item = tracks[i]; console.log(item);
  • 164.
    var elem =$('<a>').attr({ 'data-transition' : 'pop', 'href' : '#play' }); // 화면에 표시할 음악 아이템 정보를 생성한다. elem.data('item',{ title : item.title, img : item.artwork_url, url : item.permalink_url, desc : item.description, dur : item.duration, genre : item.genre, bpm : item.bpm, cnt : item.playback_count, time : Date.now() }).addClass(‘play’); // 화면에 보여줄 데이터를 생성한다. var list = $('<li>').append(elem); elem.append($('<img>').attr('src',item.artwork_url)); elem.append($('<h2>').text(item.title)); elem.append($('<p>').text(item.description)); // 검색 결과 리스트에 추가한다. $('#music ul[data-role=listview]').append(list); } // 리스트 목록을 jquerymobile 의 리스트로 갱신한다. $('#music ul[data- role=listview]').listview('refresh'); }); } }); // 음악 목록에서 음악을 클릭했을 때 호출된다. $('ul[data-role=listview]').on('click','a.play', function(){ // 선택된 아이템으로부터 음악 정보를 가져온다. var item = $(this).data('item'); var img = $(this).attr('img'); var title = $(this).attr('title'); // 음악 이름을 화면에 표시해 준다. $('#play h2').text(item.title); // 라즈베리파이 서버의 음악 재생을 호출한다. $.post('/play', item); // 재생 배경을 음악 타이틀의 이미지로 변경한다. $('#play').css({ 'background-image': 'url("' + item.img + '")', 'background-size' : '100% 100%' }); }); // 다시 재생 클릭시 호출된다.
  • 165.
    $('#resume').click(function(){ $.post('/resume'); }); // 정지 클릭시호출된다. $('#stop').click(function(){ $.post('/stop'); }); // 볼륨 키우기를 눌렀을 때 호출된다/ $('#vol_up').click(function(){ $.post('/volume', { volume : 'up' }); }); // 볼륨 낮추기를 눌렀을 때 호출된다. $('#vol_down').click(function(){ $.post('/volume', { volume : 'down' }); }); }); 검색된 결과를 화면에 표시해 주고 재생하는 부분을 추가한다. 리스트에 검색 결과를 추가하고, jQueryMobile 에서 제공하는 listview 갱신 기능을 이용하여 화면에 표시해 준다. 이렇게 추가된 하나의 아이템을 클릭할 때, 재생 정보를 가져와 라즈베리파이에 전달하고, 재생화면의 배경을 타이틀 이미지로 보여주는 코드를 작성한다. 재생화면에서 사용될 정지, 재생, 볼륨 조절 기능을 추가하면 간단한 재생 기능 만들기는 마무리 된다.
  • 166.
    [그림] 음악 검색결과를선택하면 간단하게 음악을 재생해 준다. 음악 리스트를 관리 해 보자 – Local Storage 간단하게 음악을 검색하고 재생하는 기능을 추가해 보았다. 하지만, 이미 들었던 음악을 다시 듣기 위해서는 다시금 검색해서 해당음악을 선택해 주어야 하는 수고스러움이 발생한다. 재생한 음악 목록을 보관하고, 이를 리스트로 보여주어서 다시 재생할수 있도록 관리해 주는 기능을 추가해 보자. 음악이 임시 저장이 된 후 재생이 되는 구조이므로, 내부 저장소에 한계가 발생할 수 있다. 그러므로, 리스트를 관리하는 모듈에서는 디스크 용량을 확인하고, 가장 오래전에 재생된 목록과 임시 저장파일은 삭제하는 기능도 함께 추가하도록 한다. 데이터 저장 및 관리를 위해 MongoDB 나 MySQL 과 같은 별도의 데이터베이스를 이용할수도 있지만, HTML5 에서 지원하는 저장소인 LocalStorage 방식을 Node JS 에서 사용하여 음악 목록 데이터를 관리할 것이다. [ ch_7_2_server.js ] … var store = require('./ch7_1_store'); … // 재생 이력을 가져올 때 호출된다. app.post('/history', function(req, res){
  • 167.
    res.json({ result :store.gets()}) }); … 모바일에서 조회 명령을 내릴 수 있도록 조회 기능을 추가한다. JSON 반환 값으로 재생 목록을 전달하게 된다. [ ch_7_2.html ] <div data-role="page" id='music' class='ui-page-active'> … <div data-role='footer' data-theme='b' data-position='fixed'> <div data-role='navbar'> <ul> <li><a href='#music' data-icon='grid' data- transition='flip'>Music</a></li> <li><a href='#radio' data-icon='star' data- transition='slidedown'>Top</a></li> <li><a href='#history' data-icon='clock' data- transition='flow'>History</a></li> <li><a href='#alarm' data-icon='comment' data- transition='turn'>Alarm</a></li> </ul> </div> </div> </div> <div data-role="page" id='play'> … </div> <div data-role='footer' data-theme='b' data-position='fixed'> <div data-role='navbar'> <ul> <li><a href='#music' data-icon='grid' data- transition='flip'>Music</a></li> <li><a href='#radio' data-icon='star' data- transition='slidedown'>Top</a></li> <li><a href='#history' data-icon='clock' data- transition='flow'>History</a></li> <li><a href='#alarm' data-icon='comment' data- transition='turn'>Alarm</a></li> </ul> </div> </div> </div> <div data-role="page" id='history'> <div data-role='header' data-theme='b' data-position='fixed'> <h1>History</h1> <div data-role='navbar'> <ul> <li><a href='#' id='his'>History</a></li> <li><a href='#' id='recom'>Recommandation</a></li> </ul> </div> </div> <div data-role='main' class='ui-content'> <div data-role='fieldcontain'> <ul data-role='listview' data-filter='true' data-filter- placeholder='Search the title from history'>
  • 168.
    <li>No musics</li> </ul> </div> </div> <div data-role='footer'data-theme='b' data-position='fixed'> <div data-role='navbar'> <ul> <li><a href='#music' data-icon='grid' data- transition='flip'>Music</a></li> <li><a href='#radio' data-icon='star' data- transition='slidedown'>Top</a></li> <li><a href='#history' data-icon='clock' data- transition='flow'>History</a></li> <li><a href='#alarm' data-icon='comment' data- transition='turn'>Alarm</a></li> </ul> </div> </div> </div> 재생 목록을 보여줄 페이지를 추가해 준다. 기본 구조는 음악 검색 페이지와 동일한 구조를 가진다. 다만, 목록내 검색 기능을 위해 listview 옵션 태그에서 data-filter=’true’ 를 추가해 주도록 한다. 앞으로 제작할 추천 기능들을 위해 하단 네비게이션 버튼을 미리 만들어 두도록 한다. [ ch_7_2_store.js ] var LocalStorage = require('node-localstorage').LocalStorage; var exec = require('child_process').exec; var space = require('diskspace'); localStorage = new LocalStorage('./music_list'); // 저장되어 있는 재생 목록을 가져온다. var getList = function(){ var list = localStorage.getItem('music_list'); if(list == null){ return []; } else { return JSON.parse(list); } } // 음악 재생 목록에 추가한다. exports.add = function(item, file){ var list = getList(); // 동일 음악이 존재하는 지 확인하고 있다면 배열의 가장 앞으로 이동한다. for(var i in list){ if(list[i].url == item.url){ list.splice(i, 1); } } item.file = file; list.push(item);
  • 169.
    // 저장장치 공간을확인하여, 여유 공간이 적다면, 마지막 데이터를 삭제한다. space.check('/', function (err, total, free, status){ if(free * 100 / total < 5){ console.log(free * 100 / total); var last = list.shift(); exec('sudo rm /' + last.file); } localStorage.setItem('music_list',JSON.stringify(list)); }); } // url 로 저장 내역이 있는지 확인한다. exports.get = function(url){ var list = getList(); for(var i in list){ if(list[i].url == url){ return list[i]; } } } //목록을 가져온다. exports.gets = function(){ return getList(); } HTML5 의 LocalStorage 를 이용하여 데이터를 저장하고 불러오는 코드이다. Localstorage 특성상 문자열 형태의 데이터만을 저장할 수 있다. 따라서, 우리가 저장에 사용되는 배열이나 객체 형태의 데이터는 문자형태로 전환하고, 다시 이를 객체로 변경하여 사용을 해야 하는데, 문자형태로의 전환을 위해 JSON.stringify 를 이용해 문자로 저장하도록 한다. 반대로 복원할 시에는 JSON.parse 를 이용하면 된다. [ ch7_js ] … // 목록을 가져와서 화면에 출력한다. var getList = function(){ $.post('/history', function(data){ // 기존 목록을 삭제한다. $('#history ul[data-role=listview]').empty(); // 데이터 목록을 하나씩 가져온다. for(var i in data.result){ var item = data.result[i]; // 화면에 보여줄 객체에 정보 데이터를 생성한다. var elem = $('<a>').attr({ 'data-transition' : 'pop',
  • 170.
    'href' : '#play' }).addClass('play'); elem.data('item',item); //화면에 표시할 정보를 입력한다. var list = $('<li>').append(elem); elem.append($('<img>').attr('src',item.img)); elem.append($('<h2>').text(item.title)); elem.append($('<p>').text(item.desc)); // 재생 목록 리스트에 추가한다. $('#history ul[data-role=listview]').append(list); } // 리스트를 jquerymobile 방식으로 초기화 한다. $('#history ul[data- role=listview]').listview('refresh'); }); } // 페이지가 호출될 때 발생한다. $(document).on('pageshow', function(){ var pageId = $.mobile.activePage.attr("id"); switch(pageId){ // 재생 목록 페이지가 호출될 때 발생한다. case 'history': getList(); } }); // history 버튼 클릭시 발생한다. $('#his').click(function(){ getList(); }); … 재생 목록을 요청하는 부분으로, 데이터를 요청하고 가져오는 프로그램은 음악 검색과 유사하다. 다만, 재생 목록 페이지가 보여진 이후 명령이 호출되도록 pageshow 이벤트에서 현재 페이지가 재생 목록 페이지, 즉 id 가 history 일때 재생 목록 호출이 이루어지는 함수를 추가하도록 한다.
  • 171.
    [그림] 과거에 들은음악 목록을 확인하고, 다시 곧바로 재생할 수도 있다. 음악 추천하기 - Recommandation 복잡한 추천 기능을 다루는 것은 책의 주제를 벗어나는 범위이므로, 매우 간단한 음악 추천 기능을 넣고자 한다. 우리의 라즈베리파이에 넣을 추천 기능은, 사용자가 들은 음악, 즉 바로 이전에 구현한 재생 목록을 기반으로 한다. 해당 사용자가 가장 즐겨 듣는 장르의 음악을 파악하고, 해당 장르 음악의 BPM(Beat Per Minute) 대역, 음악 재생 길이의 범위등을 해당 조건에 맞는 음악을 검색 해 준다. 범위를 구할 때 사용하는 부가적인 기능을 위해 nodejs 에서 통계 기능을 지원하는 stats-analysis 모듈을 설치해 주도록 한다. $ npm install –g stats_analysis 통계 모듈에서 사용하는 기능은 평균에서 너무 벗어나는 이상 값(Outlier) 를 제거하는 기능을 사용한다. 즉, 사람 키 평균을 구한다고 할 때 160~180 에 일반적으로 분포하는데, 실수로 10 을 입력하거나 1000 을 입력하는등 너무나 벗어나는 값들을 제거하는데 사용하는 기능이다. [ ch_7_2_server.js ] …
  • 172.
    var recom =require('./ch7_1_recom'); … // 추천 요청이 발생했 을 때 추천음악 리스트를 반환한다. app.post('/recom', function(req, res){ res.json({ result : recom.getParam()}); }); … 추천은 결국 특정 조건을 음악 검색에 활용하는 것이다. 추천 검색에 활용할 인자 값을 반환하는 호출부를 추가한다. [ ch_7_2_recom.js ] // 통계처리 모듈을 불러온다. var stats = require("stats-analysis"); var store = require('./ch7_1_store'); var genres = []; var durs = []; var bpms = []; // 선택한 장르의 재생된 횟수를 확인한다. var setCount = function(genre){ for(var i in genres){ if(genres[i].genre == genre){ genres[i].count++; return; } } genres.push({ genre : genre, count : 1}); } // 가장 많이 들은 장르를 확인한다. var getTopGenre = function(){ // 가장 많이 들은 음악을 내림차순으로 정렬한다. genres.sort(function(a, b){ return b.count - a.count; }); // 가장 많이 들은 음악 장르를 반환한다. return genres[0]; } // 들었던 음악 목록을 이용하여 음악을 추천한다. exports.getParam = function(){ // 들었던 음악 목록을 불러온다. var list = store.gets(); genres = []; durs = []; bpms = [] // 장르별 음악 재생 횟수를 확인한다.
  • 173.
    for(var i inlist){ setCount(list[i].genre); } // 가장 많이 재생된 장르를 구한다. var item = getTopGenre(); // 음악 재생 시간과 박자 정보를 저장한다. for(var i in list){ if(list[i].genre == item.genre){ durs.push(list[i].dur); bpms.push(list[i].bpm); } } // 박자와 재생 시간 목록에서 이상 치 값을 제거한다. durs = stats.filterOutliers(durs); bpms = stats.filterOutliers(bpms); // 검색에 사용하는 추천 정보를 반환한다. return { 'genres' : item.genre, 'bpm[from]' : Math.min(bpms), 'bpm[to]' : Math.max(bpms), 'duration[from]' : Math.min(durs), 'duration[to]' : Math.max(durs), } } 음악 재생 목록중에 가장 많이 들은 장르의 음악을 찾는다. 해당 장르의 비트와 재생 시간을 수집하여, 평균에서 크게 벗어나는 값 (Outlier)을 제거하고, 장르, 비트, 재생 길이 정보를 반환하도록 한다. [ ch7.js ] … // 추천 버튼을 클릭히 호출된다. $('#recom').click(function(){ // 라즈베리파이 서버의 음악 추천 데이터를 가져온다. $.post('/recom', function(data){ $('#history ul[data-role=listview]').empty(); SC.get('/tracks', data).then(function(tracks) { for(var i in tracks){ var item = tracks[i]; console.log(item); var elem = $('<a>').attr({ 'data-transition' : 'pop', 'href' : '#play' }).addClass(‘play’); elem.data('item',{ title : item.title, img : item.artwork_url,
  • 174.
    url : item.permalink_url, desc: item.description, dur : item.duration, genre : item.genre, bpm : item.bpm, cnt : item.playback_count, time : Date.now() }); var list = $('<li>').append(elem); elem.append($('<img>').attr('src',item.artwork_url)); elem.append($('<h2>').text(item.title)); elem.append($('<p>').text(item.description)); $('#history ul[data- role=listview]').append(list); } $('#history ul[data- role=listview]').listview('refresh'); }); }); }); … 추천 인자 값을 이용하여 음악을 검색하고, 화면에 디스플레이 해 준다. 표시 방식이 음악 검색과 동일 하게 구현되어 있다, 이는 추천 음반 클릭 시에도 일반 검색 음악 클릭시와 동일하게 받은 음악이 없다면, 다운로드 후 재생하는 동작이 수행됨을 의미 한다.
  • 175.
    [그림] 가장 많이들은 음악의 장르와 음악의 빠르기 및 길이를 이용하여 추천 음악을 검색해 준다. 검색 결과내에서도 재 검색이 가능하다. 오디오의 알람 기능 추가하기 – Scheduler 기능 스마트 폰으로 꼭 사용하는 기능 중 하나는, 아침의 단 잠을 깨워주는알람 기능이다. 우리가 만든 오디오에 이미 시계 기능이 들어가 있는 것을 알 수 있다. 간단한 설정을 통해, 알람을 설정하면, 가장 마지막에 틀었던 음악이 재생되면서, “Wake Up” 이라고 읽어주고 가장 마지막 들었던 음악을 재생하는 기능을 추가해 보도록 한다. 알람 설정일자를 localStorage 에 보관하고, 매 시간 체크 때 마다 설정한 요일, 시간, 분이 맞는 지 비교를 하여 같은 조건일 경우 알람 동작이 발생하도록 구현한다. [ ch7_server.js ] var express = require('express'); var bodyParser = require('body-parser'); … io = require('socket.io')(server); isTime = true; … app.post('/play', function(req, res){ var url = req.body.url; console.log(__dirname + ' / ' + url);
  • 176.
    exec('pkill mplayer'); isTime =false; ext.tts('Downloading...'); exec('sudo scdl -l ' + url, function(err, stderr, stdout){ if(!err && stdout.indexOf('[37mDownloading ') > -1){ var file = stdout.split('[37mDownloading ')[1].split('[0mn')[0] + '.mp3'; store.add(req.body, file); ext.play(file); } else { console.log(err); } }); res.json({ result : true}); }); … app.post('/resume', function(req, res){ console.log('Resume : ' + current_music); ext.play(); res.json({ result : true }); }); … // 알람 정보를 가져 온다. app.post('/getAlarm', function(req, res){ res.json({ result : store.getAlarm()}); }) // 알람 정보를 설정한다. app.post('/setAlarm', function(req, res){ ext.tts('Setting alarm.'); store.setAlarm(req.body); res.json({ result : true}); }) … 재생기능의 경우, 알람에서도 공통적으로 사용할 수 있도록 공통 함수로 만들어 범용적으로 이용할 수 있도록 변경하였다. 알람 값을 저장하고, 불러오는 기능을 추가하였다. [ ch7_extend.js ] var spawn = require('child_process').spawn; var store = require('./ch7_1_store'); … var play = function(file){ exec('pkill mplayer'); if(file){ store.setMusic(file); } else { file = store.getMusic(); } isTime = false; console.log('Playing : ' + file);
  • 177.
    tts('Playing the music.'); varps = spawn('mplayer',['/' + file]); ps.stderr.on('data', function(data){ var progress = data.toString('utf8'); if(progress && progress.indexOf('of') > -1){ var token = progress.split(' of '); var current = token[0].split(' ').slice(-2)[0]; var total = token[1].split(' ')[0] var remain = Math.round(total - current); setTime(Math.floor(remain / 60), remain % 60); io.emit('time', { total : total, current : current}); } }); ps.on('close', function(code){ console.log(code); tts('Finished'); isTime = true; setCurrentTime(); }); } … // 현재 시간을 설정하는데 사용된다. var setCurrentTime = function(){ var date = new Date(); // 알람 설정 정보를 가져온다. var alarm = localStorage.getItem('alarm'); alarm = JSON.parse(alarm); // 알람 정보가 있을 때 실행된다. if(alarm != null){ // 요일을 구한다. var day = date.getDay().toString(); // 알람 설정된 요일 정보가 있을 때 수행된다. if(alarm.days.indexOf(day) > -1){ // 알람 설정 시간과 분이 동일할 때 소리와 함께 음악이 재생된다. if(alarm.hour == date.getHours() && alarm.minute == date.getMinutes()){ tts('Wake up my master!'); play(); } } } setTime(date.getHours(), date.getMinutes()); } exports.play = play; exports.setCurrentTime = setCurrentTime; …
  • 178.
    음악을 재생하는 모듈과함께 추가된 부분은 시간을 표시할 때, 설정된 알람 값이 있다면 알람 이벤트를 발생하는 부분이 추가되었다. 저장된 날짜와 시간, 분을 검사하여 조건이 맞으면 알람을 발생하게 된다. [ ch7_store.js ] // 알람 정보를 저장소에 저장한다. exports.setAlarm = function(data){ localStorage.setItem('alarm',data.param); } // 저장된 알람 정보를 가져온다. exports.getAlarm = function(){ return localStorage.getItem('alarm'); } exports.setMusic = function(file){ localStorage.setItem('music',file); } exports.getMusic = function(file){ return localStorage.getItem('music'); } LocalStorage 를 관리하는 파일에 알람 조건을 저장 및 불러오는 기능을 추가하였다. 또한, 알람 시 이전에 재생한 마지막 음악 파일명을 기억하고 불러오는 기능도 추가되었다. [ ch7.html ] <div data-role="page" id='alarm'> <div data-role='header' data-theme='b' data-position='fixed'> <h1>Alarm</h1> </div> <div data-role='main' class='ui-content'> <fieldset data-role='controlgroup'> <legend>Choice day</legend> <input type='checkbox' name'day' id='cb0' value='0'/> <label for='cb0'>Sun</label> <input type='checkbox' name'day' id='cb1' value='1'/> <label for='cb1'>Mon</label> <input type='checkbox' name'day' id='cb2' value='2'/> <label for='cb2'>Tue</label> <input type='checkbox' name'day' id='cb3' value='3'/> <label for='cb3'>Wed</label> <input type='checkbox' name'day' id='cb4' value='4'/> <label for='cb4'>Thr</label> <input type='checkbox' name'day' id='cb5' value='5'/> <label for='cb5'>Fri</label> <input type='checkbox' name'day' id='cb6' value='6'/> <label for='cb6'>Sat</label> </fieldset> <div class='ui-grid-a'> <div class='ui-block-a'> <label for='alarm_hour'>Hour</label> <select id='alarm_hour'> <!--
  • 179.
    <option value='0'>0</option> --> </select> </div> <div class='ui-block-b'> <labelfor='alarm_minute'>Minute</label> <select id='alarm_minute'> <!-- <option value='0'>0</option> --> </select> </div> </div> <a href='#' id='setAlarm' data-role='button'>Confirm</a> </div> <div data-role='footer' data-theme='b' data-position='fixed'> <div data-role='navbar'> <ul> <li><a href='#music' data-icon='grid' data- transition='flip'>Music</a></li> <li><a href='#radio' data-icon='star' data- transition='slidedown'>Radio</a></li> <li><a href='#history' data-icon='clock' data- transition='flow'>History</a></li> <li><a href='#alarm' data-icon='comment' data- transition='turn'>Alarm</a></li> </ul> </div> </div> jQueryMobile 의 CheckBox , SelectMenu 를 이용하여 알람 시간을 설정하는 페이지이다. 요일의 경우 직접 HTML 태그로 입력하였지만, 시간과 분은 일일히 입력하기 양이 많아 프로그래밍으로 입력할 수 있도록 공간만 확보한 상태이다. [ ch7.js ] $(document).on('pageshow', function(){ var pageId = $.mobile.activePage.attr("id"); switch(pageId){ case 'history': getList(); break; // 알람 페이지에 들어왔을 때 호출된다. case 'alarm': // 저장된 알람 정보가 있는지 확인한다. $.post('/getAlarm', function(data){ var alarm = JSON.parse(data.result); console.log(alarm); // 설정된 요일이 있다면, 화면상에 체크된 것으로 표시한다. for(var i = 0; i < alarm.days.length ;i++){ var day = alarm.days[i]; $('#cb' + day).prop('checked', true).checkboxradio('refresh'); }
  • 180.
    $('#alarm_hour').val(alarm.hour).selectmenu('refresh');; $('#alarm_minute').val(alarm.minute).selectmenu('refresh'); }); // 알람 설정버튼을 클릭시 호출된다. $('#setAlarm').click(function(){ var days = []; // 선택한 요일을 확인하고, 배열에 보관한다. $.when($("input[type=checkbox]:checked").each(function() { days.push($(this).val()); })).then(function(){ // 선택한 시간과 분을 저장한다. var hour = $('#alarm_hour').val(); var minute = $('#alarm_minute').val(); var data = { days : days, hour : hour, minute : minute }; // 알람 정보를 라즈베리파이 서버에 전달한다. $.post('/setAlarm', { param : JSON.stringify(data) }); }); }); break; } }); // 시간 목록을 생성한다. for(var i = 0 ; i < 24 ; i++){ $('#alarm_hour').append($('<option>').val(i).text(i)); } // 분 목록을 생성한다. for(var i = 0 ; i < 60 ; i++){ $('#alarm_minute').append($('<option>').val(i).text(i)); } 시간과 분은 for 문을 이용하여 직접 선택값을 고를 수 있도록 기능을 추가하였다. Alarm 페이지가 호출 시, 설정된 값이 있다면 불러와서 화면에 표시하도록 하고, 사용자가 새 알람을 설정했을 때 해당 값을 라즈베리파이로 전송하도록 기능을 구현하였다.
  • 181.
    [그림] 알람 설정을위한 요일 및 시간 설정. 시간과 분은 개수가 많으므로, HTML 이 아닌 코드를 이용하여 값을 설정하도록 한다. 배경과 글씨 밝기를 조절해 보자 백 라이트 배경과 글씨 밝기가 기본으로는 최대 값으로 설정되어 있다. 어두운환경이나 상황에 따라서 밝기를 조절해야 할 필요가 있다. 7 Segment 의 경우 내부 API 에서 16 단계로 밝기를 조절할 수 있는 기능을 제공하고 있다. LED 백 라이트의 경우, GPIO 실습 시 사용했던 소프트웨어 PWM 방식을 응용하여 사용자가 밝기를 조절하여 사용할 수 있게 하자. 설정된값은 로컬 저장소에 저장하여, 재 부팅 후 시작 시에도 설정 값을 이용할 수 있도록 반영한다. [ ch7_server.js ] … // LED 라이트 색상을 조정할 때 호출된다. app.post('/setBg', function(req,res){ console.log('Settring Bg : ' + req.body.bg); ext.setBg(parseInt(req.body.bg)); res.json({ result : true}); });
  • 182.
    // 7세그먼트의 글씨색을설정할 때 호출된다. app.post('/setFg', function(req,res){ console.log('Settring Fg : ' + req.body.fg); ext.setFg(parseInt(req.body.fg)); res.json({ result : true}); }); … 전경 밝기 및 배경 밝기를 조절할 수 있도록 호출 부를 구현해 준다. Ajax 요청을 통해 전달 되는 숫자 값은 문자 형태로 전환 되므로, parseInt 함수를 이용하여 숫자 형태 값을 전달할 수 있도록 한다. [ ch7_ext.js ] … var wpi = require ('wiring-pi'); wpi.setup('gpio'); wpi.wiringPiSetup(); wpi.softPwmCreate (6, 0, 200) ; wpi.softPwmWrite (6, 240); wpi.pinMode(11, wpi.OUTPUT); wpi.digitalWrite(11, wpi.HIGH); disp.setDecodeAll(); disp.setScanLimit(4); disp.setDisplayIntensity(15); disp.startup(); // 7세그먼트의 밝기를 설정한다. exports.setFg = function(val){ disp.setDisplayIntensity(val); } // LED 밝기를 소프트 PWM 방식으로 설정한다. exports.setBg = function(val){ wpi.softPwmWrite (24, val); } … 배경 밝기는 PWM 을 이용하여 값을 조절하고, 전경 밝기는 7 Segment 의 밝기를 조절하는 setDisplayIntensity 함수를 이용하여 값을 반영해 주도록 한다. [ ch7.html ] … <div data-role="page" id='option'> <div data-role='header' data-theme='b' data-position='fixed'> <h1>Config</h1> <a href='#option' data-icon='gear' class='ui-btn- right'>Option</a> </div> <div data-role='main' class='ui-content'> <label for='fg_brt'>Foreground Brightness</label>
  • 183.
    <input id='fg_brt' type="range"min="0" max="15" value="15" /> <label for='bg_brt'>Background Brightness</label> <input id='bg_brt' type="range" min="0" max="240" value="240" /> </div> <div data-role='footer' data-theme='b' data-position='fixed'> <div data-role='navbar'> <ul> <li><a href='#music' data-icon='grid' data- transition='flip'>Music</a></li> <li><a href='#radio' data-icon='star' data- transition='slidedown'>Radio</a></li> <li><a href='#history' data-icon='clock' data- transition='flow'>History</a></li> <li><a href='#alarm' data-icon='comment' data- transition='turn'>Alarm</a></li> </ul> </div> </div> </div> … 밝기를 조절할 수 있는 슬라이더를 2 개 만들도록 한다. 전경 밝기의 경우 0 부터 15 까지 값을 선택할 수 있도록 하고, 배경 밝기의 경우 0 부처 240 까지의 값을 선택할 수 있도록 최대 최소 값을 설정한다. 지정된 값을 초과하면 설정 값이 반영되지 않고 오류가 날 수 있으므로 유의하도록 한다. [ ch7.js ] … $(document).on('pageshow', function(){ var pageId = $.mobile.activePage.attr("id"); switch(pageId){ … case 'option': // 포그라운드 색상 슬라이더 값이 변경 시 호출된다. $('#fg_brt').change(function(){ $.post('/setFg',{ fg : $(this).val()}); }); // 백그라운드 색상 슬라이더 값이 변경 시 호출된다. $('#bg_brt').change(function(){ $.post('/setBg',{ bg : $(this).val()}); }); break; } }); …
  • 184.
    슬라이더로 지정한 두개의input 값이 변경 될 때 이를 감지하고, 변경된 값을 서버로 전송하는 코드를 작성한다. option 페이지에 접속하였을 때, 해당 이벤트를 감지하고 동작될 수 있도록, pageshow 이벤트 내에서 명령어를 작성하도록 하자. [그림] 리모콘을 이용하여 7 Segment 와 Background 밝기를 조절할 수 있다. 최신 가요 목록 보여주기 – 웹 크롤러 만들기 음악을 들을때 별달리 생각없이 음악을 듣고자 할때 별달리 노력없이 최신 가요를 듣는 경우가 많다. 우리가 만들 파이오에도 조회 시점의 최신 가요를 조회하여 사용자에게 알려주고자 한다. 순위를 알려주는 사이트는 멜론, 네이버, 엠넷등 다양한 사이트가 있는데, 여기서는 엠넷의 TOP100 웹 페이지를 이용하여 최신 가요의 정보를 이용할 것이다. 사용자가 사용할 모바일 리포콘 웹앱에 가져온 최신 가요 정보를 출력해 줄 것이다. 뉴스의 날씨의 경우 정제된 XML 형태로 제공되는 RSS 서비스를 이용하여 간단히 JSON 타입으로 변형하여 손쉽게 데이터를 사용할 수 있었지만, 최신 가요의 경우 RSS 형태의 서비스로 제공되지 않기 때문에, 수고스럽더라도 직접 웹 페이지를 크롤링하여 필요한 데이터를 추출하여 사용해야 한다.
  • 185.
    $ npm installstew-select <td class="MMLItemTitle"> <div class="MMLITitle_Wrap"> <div class="MMLITitle_Album"> <a href="/album/526289 " target="_self"><img src=http://cmsimg.mnet.com/clipimage/album/50/000/526/526289.jpg alt="시그널 OST Part 1 - 앨범"onerror="javascript:mnetImage.fnSetClipUrl(this, '0202' , '526289', '50')"/><span class="photoLine"></span></a> </div> <div class="MMLITitle_Box info"> <div class="MMLITitleSong_Box"> <a href="/track/5174234" target="_self" class="MMLI_SongInfo">회상 - 곡정보</a><a href="javascript:mnetCom.aodPlay('5174234');" title ="회상 새창" class="MMLI_Song">회상</a> </div> <div class="MMLITitle_Info"> <a href=" /artist/208752 " title="장범준 - 아티스트" class="MMLIInfo_Artist" target="_self">장범준</a> / <a href=" /album/526289 " target="_self" title ="시그널 OST Part 1 - 앨범" class="MMLIInfo_Album">시그널 OST Part 1</a> </div> <a href="javascript:mnetCom.aodPlay('5174234','add');" class="MMLI_Mp3Add" title="곡추가">곡추가</a> </div> </div> </td>
  • 186.
    [그림] 실제 MNETTOP100 사이트와 페이지 구조. 필요한 정보만을 빼서 화면에 보여줄 수 있도록 한다. 모바일 상에서 필요한 정보는 곡 대표 이미지, 곡 명, 가수, 앨범 명의 정보이다. 실제 태그를 보면, div 요소의 MMLITitle_Wrap 클래스에 해당 정보들을 담고 있음을 알수 있다. Stew-select 라이브러리는 CSS 의 요소 선택 방법을 이용하여, 원하는 데이터를 추출한다. [ ch7_server.js ] … var top = require('./ch7_1_top'); … // 최신가요 목록을 요청할 때 호출된다. app.post('/top', function(req,res){ top.getList(function(list){ console.log(list); res.json({ result : list}); }); }); … 인기 가요를 반환하는 호출부를 작성한다. 인기가요를 크롤링하여 정보를 추출하는 함수는 별도의 파일로 분류하여 복잡도를 낮추도록 한다. [ ch7_top.js ] // html 문서에서 특정 값을 가져오는데 활용되는 모듈을 불러온다. var stew = new (require('stew-select')).Stew(); // html 문서를 해석하는데 사용되는 모듈을 불러온다. var htmlparser = require("htmlparser"); var http = require('http'); // 인기가요 목록이 있는 웹 문서 정보를 설정한다. var options = { hostname: 'www.mnet.com', port: 80, path: '/chart/TOP100', method: 'GET' }; // 최신가요 목록을 가져온다. exports.getList = function(cb){ // 최신 가요 페이지를 호출한다. var req = http.request(options, function(res) { res.setEncoding('utf8'); var dom = ''; // 받아지는 데이터 조각을 합친다. res.on('data', function (chunk) { dom += chunk; }); // 웹 문서 호출이 끝났을 때 발생한다. res.on('end', function(){ var isFirst = true;
  • 187.
    // 웹 문서에서음악정보 객체를 가져온다. stew.select(dom, 'div.MMLITitle_Wrap', function(err,items) { if(err) { cb([]); console.error(err); } else { var list = []; // 음악 정보 목록을 반복하여 하나씩 추출한다. items.forEach(function(item){ if(stew.select(item,'img') && stew.select_first(item,'a.MMLIInfo_Artist') && stew.select_first(item,'a.MMLIInfo_Album').children[0]){ // 가요 목록을 저장한다. list.push({ img : stew.select_first(item,'img').attribs.src.replace('/50/','/80/'), artist : stew.select_first(item,'a.MMLIInfo_Artist').children[0].raw, title : stew.select_first(item,'a.MMLI_Song').children[0].raw, album : stew.select_first(item,'a.MMLIInfo_Album').children[0].raw }); } }); // 가요 목록을 콜백 함수로 반환한다. cb(list); } }); }); }); req.on('error', function(e) { cb([]); console.log('problem with request: ' + e.message); }); req.end(); } 실제 웹 페이지를 크롤링 하는 함수이다. http 요청을 통해 페이지 내용을 불러오고, stew- select 라이브러리를 이용하여, CSS 선택자 방식으로 데이터를 가져오게 된다. 원래 페이지 구조에서 div 태그의 클래스가 MMLITitle_Wrap 부분의 태그를 가져오고, 해당 태그에 소속되어있는 데이터로부터 이미지, 아티스트, 노래, 앨범 정보를 가져온 후, 배열 객체에 값을 저장하고 반환하는 구조로 구성되어 있다. 이미지 파일의 경우 기본적으로 50x50px 사이즈의 이미지 주소를 제공하는데, 모바일에서 보여지는 크기는 80x80px 이 될 수 있도록 문자열을 치환한다. [ ch7.html ] …
  • 188.
    <div data-role="page" id='music'class='ui-page-active'> <div data-role='header' data-theme='b' data-position='fixed'> <a href='#play' data-icon='action' class='ui-btn- left'>Play</a> <h1>Music</h1> <a href='#option' data-icon='gear' class='ui-btn- right'>Option</a> </div> ... </div> <div data-role="page" id='play'> <div data-role='header' data-theme='b' data-position='fixed'> <a href='#play' data-icon='action' class='ui-btn- left'>Play</a> <h1>Music Play</h1> <a href='#option' data-icon='gear' class='ui-btn- right'>Option</a> </div> ... </div> <div data-role="page" id='history'> <div data-role='header' data-theme='b' data-position='fixed'> <a href='#play' data-icon='action' class='ui-btn- left'>Play</a> <h1>History</h1> <a href='#option' data-icon='gear' class='ui-btn- right'>Option</a> <div data-role='navbar'> <ul> <li><a href='#' id='his'>History</a></li> <li><a href='#' id='recom'>Recommandation</a></li> </ul> </div> </div> ... </div> <div data-role="page" id='alarm'> <div data-role='header' data-theme='b' data-position='fixed'> <a href='#play' data-icon='action' class='ui-btn- left'>Play</a> <h1>Alarm</h1> <a href='#option' data-icon='gear' class='ui-btn- right'>Option</a> </div> ... </div> <div data-role="page" id='option'> <div data-role='header' data-theme='b' data-position='fixed'> <a href='#play' data-icon='action' class='ui-btn- left'>Play</a> <h1>Config</h1> <a href='#option' data-icon='gear' class='ui-btn- right'>Option</a> </div> ... </div> <div data-role="page" id='top'> <div data-role='header' data-theme='b' data-position='fixed'> <a href='#play' data-icon='action' class='ui-btn- left'>Play</a>
  • 189.
    <h1>Top Music</h1> <a href='#option'data-icon='gear' class='ui-btn- right'>Option</a> </div> <div data-role='main' class='ui-content'> <ul data-role='listview'> <li>No musics</li> </ul> </div> <div data-role='footer' data-theme='b' data-position='fixed'> <div data-role='navbar'> <ul> <li><a href='#music' data-icon='grid' data- transition='flip'>Music</a></li> <li><a href='#top' data-icon='star' data- transition='slidedown'>Top</a></li> <li><a href='#history' data-icon='clock' data- transition='flow'>History</a></li> <li><a href='#alarm' data-icon='comment' data- transition='turn'>Alarm</a></li> </ul> </div> </div> </div> … 인기 가요를 표시해 주는 페이지를 추가한다. 타 페이지와 동일하게 listview 를 이용하여 표시해 줄 수 있도록 한다. 어떤 화면에 있더라도 재생 페이지에 접근 할 수 있도록 모든 페이지의 상단 헤더 바에 Play 페이지로 접근할 수 있는 버튼을 추가하였다. [ ch7.js ] … $(document).on('pageshow', function(){ var pageId = $.mobile.activePage.attr("id"); switch(pageId){ ... case 'top': // 최신가요 목록 페이지로 들어올 때 호출된다. $.post('/top', function(data){ $('#top ul[data-role=listview]').empty(); // 가져온 최신가요 목록을 화면에 표시한다. for(var i in data.result){ var item = data.result[i]; // 화면에 표시할 객체를 만든다. var elem = $('<a>').attr({ 'data-transition' : 'pop', 'href' : '#music' }).addClass('music'); elem.data('item',item); var list = $('<li>').append(elem);
  • 190.
    elem.append($('<img>').attr('src',item.img)); elem.append($('<h2>').text(item.title)); elem.append($('<p>').text(item.artist + ' /' + item.album)); // 리스트 상단에 추가한다. $('#top ul[data- role=listview]').prepend(list); } // 리스트 화면을 jquerymobile 방식으로 갱신한다. $('#top ul[data- role=listview]').listview('refresh'); }); break; } }); ... // 최신 가요 목록에서 음악을 클릭시, 자동으로 해당 음악을 검색한다. $('ul[data-role=listview]').on('click','a.music', function(){ var item = $(this).data('item'); // 엔터 이벤트를 생성한다. var e = $.Event("keypress"); e.keyCode = 13; // 검색창에 제목을 넣고, 엔터 이벤트를 발생시킨다. $('#search').val(item.title).trigger(e); }); … 인기가요 페이지가 호출 될 때 라즈베리파이로부터 수집된 인기가요 정보를 받는 부분을 구현한다. Listview 에 보여지는 방식은 음악 검색이나 재생 목록을 확인하는 페이지와 동일한 방법으로 작성되어 있다. 단, 최신가요가 항상 SoundCloud 에 존재하는 것은 아니므로, 인기 가요 목록에 있는 아이템을 클릭 시에 SoundCloud 에 관련 음악이 있는지 검색 한 후 사용자가 재생할 수 있도록 구성 하도록 한다.
  • 191.
    [그림] M.NET 의최신 가요 목록을 보여주고, 클릭하면 음악 검색에서 곧바로 검색이 되도록 구현된다. 이번 장을 정리하며 이번 장에서는 라즈베리파이로 간단하 오디오를 구현해 보았다. 라즈베리파이에 서버를 구동하여 현재 시간 및 음악 재생 시간을 7 세그먼트로 표시하였고, jQueryMobile 로 구현된 모바일 웹을 이용하여 사용자가 오디오를 제어할 수 있도록 하였다. 하지만, 라즈베리파이가 접속한 네트워크 영역에 있는 장치로만 오디오를 제어할 수 있다. 즉, 집 밖에서 오디오를 제어할 수는 없는 구성인 것이다. 이는 9 장에서 가능하도록 현재 오디오를 업그레이드 할 것이다. 다음 장에서는 제품의 외관을 구성해 볼 수 있는 3D 프린터를 살펴볼 것이다.
  • 192.
    8. 외관을 생각대로만들기 – 3D 프린터 가장 중요한 외관, 어떻게 꾸밀까? 지금까지 라즈베리파이와 Javascript 를 이용하여 파이오를 만들어 보았다. 하지만 중요한 것 중 하나, 바로 외관 디자인이다. 칩이 보이고 핀이 보이는 현재의 파이오는 우리에게 이쁘게 보일지 몰라도, 실제로 받아서 사용하게 될 사람에게 불편하게 느껴지고, 때로는 핀이나 부품의 노출로 인하여 위험할 수도 있다. 파이오의 입장에서도 사람이 잘못만지면 선이 단선되거나, 정전기 또는 예상치 못한 일들로 인하여 파손이 일어날 수도 있는 것이다. 실제 제품은 플라스틱이나 금속류로 사출을 뜨거나 금형을 만들지만, 우리가 접할 수 있는 방식은 결코 아닐 것이다. 현실적으로 외관을 이쁘게 만들 수 있는 방법을 살펴보도록 한다. 1. 레고를 이용한 외관 조립 어린시절 한번쯤은 보았을 레고를 이용하여 외관을 꾸밀 수 있다. 레고의 시리즈중 크리에이터 세트는 사용자가 자유롭게 상상한 것을 만들 수 있는 다양한 블럭을 제공해 주는데, 이를 이용하여 자유롭게 외관을 꾸밀 수 있다. 또한 레고의 특성상 자유롭게 쌓았다가 원하는 디자인으로 바꾸기에 용이하다는 장점이있다. 하지만, 블럭의 최소 사이즈가 제한되어 있어 정밀하게 만들기 어렵고, 충격에 파손될 우려가 높다. 그리고, 무언가를 레고로 외관을 쌓는다는게 직접해보면 생각보다 어렵고 시간이 오래 걸린다. 무엇보다 우리의 발목을 잡는 부분은 레고 자체가 가격이 비싸다는 점이다. [그림] 단순한 프로토 타이핑에 좋으나 값이 비싸고 복잡한 프로토타이핑은 쉽지 않다. 2. 압축 스티로폼 이용한 가공
  • 193.
    보드지를 이용한 방법도있겠지만, 그것보다는 우드락 혹은 포맥스 등의 압축 스티로폼을 이용하는 것이 보다 나을 수 있다. 이러한 우드락이나 포맥스는 매끈한 면을 가지고, 3mm 이상의 두께에서는 (3T 라고 부른다) 꽤 괜찮은 강도를 보여준다. 보드지가 칼로 자르기가 쉽지 않은 반면 상대적으로 압축 스티로폼 계열은 칼을 이용한 커팅이 수월한 편이다. 일반 접착제를 사용하면, 스티로폼 재질이 녹는 경우도 발생하므로, 우드락용 전용 접착제를 이용하여 부착을 해야 한다. 평면의 가공이나 사각류의 가공에는 용이하나, 복잡한 면이나, 내부가 파인 구조의 외관을 꾸미고자 할 때는 많은 불편함이 따른다. 이러한 류로 구할 수 있는 것이 우드락, 시안보드, 포맥스이며, 강도 및 가공 용이성은 다음과 같다. (강도가 높을수록 일반적으로 가격또한 높다) 가공 용이성 : 우드락 > 시안보드 > 포맥스 강도및 가격 : 우드락 < 시안보드 < 포맥스 [그림] 압축 스티로폼은 가공이 용이하나, 평면류가 아닌 모델에는 부적합 하다. 3. 3D 프린터 21 세기의 연금술이라 불리우는 도구가 바로 3D 프린터이다. 3D 로 만든 도면을 내 눈앞에 보이는 물체로 만들어주는 기계 장치이다. 대부분이 플라스틱을 이용하여 한층씩 쌓는 형태로 원하는 조형물을 만들어 내는데, 근래들어 종이, 고무,, 나무, 금속에 이르기 까지 범위가 점차 넓어지고 있는 상황이다. 심지어는 식품을 가지고 3D 프린팅을 하는 제품도 나오고 있다. 우리는 여기서 3D 모델링을 이용하여 외관을 구성할 것이다. 3D 프린터가 없다고 걱정하지 않아도 된다. 수 많은 전국의 메이커 스페이스에서 직접 뽑아 볼 수 있을 것이다. 직접 사고 싶더라도 필자는 가급적이면 먼저 사용해 보고 구입할 것을 추천한다. 3D 프린터가 대중화 되기에는 일반적으로 접할 수 있는 3D 프린터는 비용이나 완성도 측면에서 아직 가야할 길이 먼 것으로 생각한다.
  • 194.
    [그림] 모델링 한결과를 그대로 출력해 주는 3D 프린터, 아직 대중화를 위해서 가격과 신뢰성이 보다 향상될 필요가 있지만, 복잡한 조형물도 뽑아내기에는 최선의 방법이다. 상상한 것을 현실로 만들어 주는 3D 프린터! 우리가 여기서 시도해 볼 방식은, 3D 도면을 만들어 이를 그대로 출력해 주는 3D 프린터이다. 이러한 3D 프린터를 구성하는 다양한 동작 방식이 있는데, 실제로 쉽게 접할 수 있는 3D 프린터 방식은 FDM(Fused Deposition Modeling) 이다. FDM 은 플라스틱 재료를 녹여서 미세한 노즐로 분사하여 쌓아가는 방식이다. 구성이 다른 3D 프린팅 기법에 비해 단순하고 특허가 일찍 만료되어 오픈소스(RepRap) 제품이 발달하여 상대적으로 저렴한 가격으로 출시되고 있다. 프린터에 따라서 Nylon 이나 나무 분말 혹은 플렉서블 소재를 이용할 수 있는 제품도 출시되고 있으나, 범용적으로 ABS 와 PLA 재질의 플라스틱을 많이 이용한다. 1.재료선정 쉽게 구입할 수 있는 재질은 ABS 와 PLA 일 것이다. 3D 프린터로 후가공 즉 연마후 도색을 하는 경우는 ABS 소재가 적합하고, 그렇지 않고 내구성과 실용성을 중심으로 사용하는 경우 PLA 가 적합하다. ABS 의 경우 인쇄하는 판이 수축 방지를 위해 가열이 되는 히팅베드가 필수 이므로, 3D 프린터에 따라서는 ABS 재질을 사용하지 못하는 경우도 있다. 후가공 부분까지 다루는 것은 범위를 벗어나는 부분이므로, 우리는 안전성과 편의성을 고려하여 PLA 를 사용하도록 한다. ABS (Acrylonitrile Butadiene Styrene copolymer) - 성형온도가 높아(220~240 도) PLA 보다 끈끈한 성질이 있다. - Heating Bed 가 반드시 필요하다. - 구조용 부품으로 강도가 우수하다. - 출력 후 사포나 샌딩등의 표면처리가 상대적으로 용이하다
  • 195.
    - 플라스틱용 도료나아크릴계 도료로 도장이 가능하다. - 두꼐가 얇은 출력물이나 큰 출력물은 열에 의한 수축으로 인해 휘는 경우가 발생할 수 있다. - 석유를 이용해 만들어, 프린팅시 냄새나 유해물질이 발생할 수 있다. PLA (Polylactic acid) - 성형 온도가 ABS 에 비해 낮아(180~210 도) 끈끈한 성질이 적고 견고하다. - 열에 의한 변형이 적고 비교적 큰 출력물도 만들기 쉽다. - 옥수수, 사탕수수, 고구마 등의 식물성 원료를 사용하여 자연 친화적이다. - 출력 후 후 가공, 도장 처리가 ABS 에 비해 용이하지 않다. - 출력 이후 내구성이나 내마모성이 ABS 에 비해 떨어진다. [ PLA 와 ABS 출력물 비교. 후가공이 필요하다면 ABS 재질을 써야 하나, 그렇지 않다면 PLA 를 선택하는 것이 무난하다. ] 2. 3D 디자인 툴 선정 3D 디자인을 위해 이미 다양한 디자인 유/무료의 툴이 공개되어 있다. 무료로 제공되는 대표적인 3 가지 툴은 다음과 같은데, 우리가 하려는 간단한 디자인을 위하여 3DS MAX 로 유명한 Autodesk 사의 123D Design 을 사용하도록 한다. Autodesk 123D Design 3D MAX 등의 3D 프로그램으로 유명한 Autodesk 사의 대표적인 3D 프린팅을 위한 프로그램이다. 3D MAX 가 비싼 가격의 전문가 용이라면, 123D Design 은 무료로 3D 프린팅을 위한 모델링을 할 수 있도록 도와준다. (상업적인 목적의 경우 유료버전을 사용해야 하며, 월 이용료가 1 만원 수준이다.) 국내 시판중인 3D 프린팅관련 도서가 대부분 이 123D Degisn 을
  • 196.
    다루고 있는경우가 많으며,관련 자료들을 찾기 수월하다. 보다 쓰기 쉬운 툴을 찾는다면 Thinkercad 가 적합할 수 있다. Sketchup Make 건축, 인테리어, 조경, 건설, 설비등의 3D 모델링에 활용되는 프로그램이였다. Google 에서 인수되었다가 현재는 Trimble 에서 관리되고 있다.개인 취미로 사용하는 것은 무료이나, 교육용이나 상업용으로 사용하는 경우 유로 라이선스를 사용해야 한다. 3 차원 공간에서 자유로운 스케치가 가능하고, 구조물을 구성하기에 적합한 기능을 제공하고 있으나, 간단한 조형물을 만들기에는 123D Design 에 비해 복잡한 구성을 가지고 있다. Blender 3D 오픈소스로 공개되어 3 차원 애니메이션이나 게임 개발용으로 사용되는 무료로 사용가능한 3D 툴이다. Maya, 3DS MAX, Rhino 와 같은 전문 3D 프로그램에 밀리지 않는 수준의 성능과 기능을 보유하고 있다. 전문적인 3D 와 완전 무료로 이용할 수 있다는 장점이 있지만, 처음
  • 197.
    배우는 진입장벽이 낮지않아, 단순한 모델링 용도로는 사용하기 어려운 단점이 있다. 자 이제 우리의 오디오를 모델링 하자 - 123D 설치하기 123D 는 PC, Mac, iPad 에서 사용가능하다. PC 용은 32bit 64bit 버전 중, 현재 사용중인 OS 에 맞추어 설치하도록 한다. Mac 용은 iTunes Store 에서 앱을 받아서 설치하면 된다. http://www.123dapp.com/design [그림] 123D Design 은 PC, Mac, iPad 만을 지원한다. 여기서 잠깐!
  • 198.
    3D 툴로 유명한Autodesk 사에서는 123D Desgin 이외에, 초보자들도 쉽게 사용할 수 있는 다양한 123D 시리즈를 비 상업적용도로는 무료로 사용할 수 있도록 배포하고 있다. 차후에 필요한 프로그램이 있다면, 별도로 설치하여 사용할 수 있다. 제품군 기능 123D Catch 360 도 여러방면에서 촬열한 사진을 합성해 3D 모델로 만드는 프로그램 123D Creature 점토로 조형하는 방식으로 3D 형상을 만드는 프로그램. 뻐대를 기반으로 씌우는 구조로 동물이나 캐릭터 만들기 쉬움 123D Desgin 초보자도 쉽게 다룰 수 있는 3D CAD 프로그램. 치수를 입력하여 형상을 구조화 할 수 있어, 제품 디자인을 간단하게 하기에 유용하다. 123D Make 타 123D 프로그램으로 만든 3D 모델을 얆게 슬라이스 하여, 단면 모양을 만드는 프로그램. 이렇게 만든 나무나 아크릴을 레이저 커터 등으로 잘라내서 조립하면 입체를 손쉽게 구현할 수 있다. 123D Sculpt 점토 덩어리를 손가락으로 밀고 당기고 쓰다듬는 방식과 같이 모델링하는 프로그램. 정확한 치수에 기반하지 않고 감각적인 형상을 만들 때 유용하다. 123D Circuit 전자횔를 설계할 수 있는 툴이다. 오픈소스 하드웨어와 브레드 보드를 이용하여 회로를 설계하고 시물레이션을 수행할 수 있다. 완성된 회로 PCB 를 주문할 수도 있다. Thinker CAD 123D Design 과 비슷하지만, 이미 정해진 모양을 이용하여 쉽게 형상을 만들 수 있다. Mesh Mixer 여러 3D 형상을 모아 붙이거나 모양을 수정할 수 있는 프로그램이다. 자 이제 우리가 만들 오디오의 외관은 다음과 같다. 간단하게 디자인이 되어 있으나, 내부에 들어가는 라즈베리파이와 각종 센서/배선을 고려하여 외관을 디자인 해야 한다. 간단하게 아래 면을 만들어 보도록 하자. 설계시 고려해야 하는 점은, 3D 프린터가 우리가 모델링한 결과를 출력해 주지만, 0.1mm 의 오차 없이 출력해 줄수는 없다는 점이다. 플라스틱 특성상 출력시 수축이나 프린팅 오차로 인하여 아래에서 만든 부품들을 제대로 연결할 수 없게 될 것이다. 처음 도면을 그릴 시에 이러한 오차를 감안하여 약간 여유를 두고 만들거나, 그럴 수 없다면 칼등의 도구를 이용하여 후 가공하여 결과물을 이용해야 한다.
  • 199.
    [그림] 123D 첫실행 화면. 처음 시작화면만 돌려보아도 기본적인 기능을 확인할 수 있다. 처음 설치된 123D Design 을 실행해 보도록 하자. Quick Start Tips 화면을 발견할 수 있을 것이다. 옆으로 넘겨 간단한 사용법을 확인하고, 프로젝트 생성을 위해 Start a New Project 를 클릭하도록 한다. Top 오디오 구조물 중 가장 쉬운 부분이면서, 이 부분을 완성하게 되면 다른 부분도 매우 쉽게 만들어 낼 수 있는 부분이 될 것이다. 다음 방법을 따라 상단을 만들고, 나머지 부분도 같은 방법으로 만들어 내도록 하자. 상단 메뉴의 Sketch 항목으로 마우스를 가져다 대면, 세부 상목이 하단에 표시된다. 첫번째 Sketch Rectangle 메뉴를 클릭한다. 작업화면의 0,0 부분을 클릭하여 드래그를 시작하고, 각각 85, 85 지점에 마우스를 뗀다. 만일 좌표가 보기 힘들면 마우스 우측을 클릭한 채 이동을 하면서 원하는 화면 각도로 조절하도록 한다.
  • 200.
    드래그 할 때가로 세로의 길이가 표시되며, 입력창이 보이는데, 여기에 직접 85, 85 를 입력해도 무방하다. 드래그가 완료되면 다시한번 클릭하고, 엔터를 누르거나 체크표시를 누르면 밑판 그리기가 완료된다. 밑판에 마우스를 대서 클릭하면, 설정을 할수 있는 버튼이 보여지게 된다. 버튼에 가져다 대면 여러 옵션이 나오는데, 4 번째 항목에 있는 Extrude 를 클릭한다. 상단 표시로 마우스를 가져다 대서 크기를 5 (mm) 사이즈로 조절하거나, 숫자를 5 를 입력하여 Enter 를 입력하면 5mm 의 네모 상단판을 만들 수 있다. 이렇게 Extrude 를 이용하면, 그림을 그린 후 올려서 구조물을 생성하거나, 반대로 파내기를 할 수 있는 유용한 도구이다. 이번에는 반대로 연결 부를 파내도록 한다. 다음의 각각 영역 위에 동일한 Sketch Rectangle 도구를 이용하여 네모 영역을 그려주도록 한다.
  • 201.
    상단에 5mm 가올라와 있어, 그리기가 어려우므로, 마우스 우측을 클릭한채 돌려서 화면을 뒤집어 준다. 뒤집은 이후 65mm x 5 mm 네모 2 개를 위 아래에, 10mm x 5 mm 네모 4 개를 좌우에 그려준다. 네모영역을 그린 부분을 CTRL 키를 누른 상태에서 모두 선택해 주도록 한다. 선택후, 설정아이콘을 다시 클릭하여 Extrude 를 누른다. 동일하게 5 (mm) 를 입력하고 Enter 를 누르면, 이번에는 해당 영역이 비워지는 것을 알수 있다. 파이오의 나머지 부분도 크게 다르지 않다. Top 을 만든 것과 같이 Rectangle Sketch 와 Extrude 만으로 나머지 영역도 모두 만들 수 있다. Bottom Bottom 영역에 구멍이 많이 뚫려 있고, 별도의 다리모양 구조물이 있는 것을 볼 수 있을 것이다. 이 다리모양의 구조물은 라즈베리파이를 고정하기 위한 내부 구조물로, 안쪽의 2 개 구멍에 딱 맞는 구조임을 알 수 있다. 나머지 구멍 4 개는 2 개씩 다리를 연결하기 위한 구조물이다.
  • 202.
    Leg 바닥면에 꽂아 주는구조물이다. 이 다리는 2 개가 필요하며, 하단부에 연결하여 오디오 구조물이 안정적으로 받쳐지도록 구성한다. Left 오디오의 왼쪽 구조물로써, 하단 중앙에 구멍이 나 있는 것을 벌견할 수 있을 것이다. 이는 무선 랜 카드가 노출되는 곳으로, 보다 전파 수신이 잘 되도록 뚫려 있는 구조이다.
  • 203.
    Right 왼쪽 구조물과 거의흡사하지만, 무선 랜을 위한 구멍이 별도로 없이 막힌 구조로 되어 있다. Back 파이오의 뒷면으로써, 빗 모양의 구조물은 내부에서 LED 를 고정하는데 사용하는 구조물이다. 하단의 두개의 구멍은 스피커와 전원선이 외부와 연결될 수 있도록 노출된 부분이다.
  • 204.
    Bone 7 Segment 를고정하는 구조물인 동시에 한지나 모시 천을 이용하여 내부를 보호할 수 있는 막을 부착하는 구조물이다. 7 Segment LED 를 연결한 후 Top, Left ,Right 구조물에 연결하도록 한다. Front 정면 구조물로서, 내부를 보호하는 동시에, 불빛이 나올 수 있도록 창살 구조로 보호되고 있다. 중앙 부분은 7 Segment LED 가 잘 보이도록 오픈된 구조를 지니고 있다.
  • 205.
    최종 결과물 저장 최종결과물은 일반적으로는 언제든지 재 편집할 수 있도록 123D Design 양식으로 저장하는 것이 일반적이다. 하지만, 3D 프린팅 혹은 레이저 커터를 이용할 시에는 다른 양식으로 저장해야 인쇄 및 커팅 작업을 손쉽게 할 수 있다. [그림] 실제 3D 프린팅과 레이저 커터를 사용할 때는 저장 형식에 차이가 있다. Save – To My Computer 가 결과물을 나의 컴퓨터에 저장하는 방식이다. To My Projects 는 Autodesk 123D 클라우드에 저장되는 것이므로, 간단하게 편집을 하고자 하는 경우는 나의 컴퓨터에 저장하는 것이 간편하다.
  • 206.
    3D 프린터에서 인쇄하는경우에는 대부분 STL(Stereo Lithography) 형식을 이용한다. 편집 목적이 아닌 출력 목적인 경우 STL 형식으로 저장한 후, 이 파일을 슬라이서(Slicer) 라는 프로그램으로 모델을 층층히 잘라 G-CODE 라는 XYZ 프린팅 좌표가 들어가 있는 데이터를 전송하여 프린팅이 이루어지게 된다. 레이저 커터로 작업하는 경우는 잘라내는 라인, 즉 선만 제공되면 되는데, SVG(Scalable Vector Graphic) 방식으로 저장하여, CAD 형식인 DXF(Drawing Interchange File)로 변환하여 인쇄하게 된다. 모델링 결과를 출력하기 - 3D 프린팅하기 실제 3D 프린터가 인쇄할 수 있는 데이터는 G-CODE 이다. 이것은 3D 프린터가 XYZ 축 이동하면서 적층식으로 필라멘트를 쌓을 좌표를 표시 한 데이터 이다. 이 형태로 변환하기 위해서는 STL 형식의 데이터를 슬라이서(Slicer)라는 프로그램으로 모델을 층층히 잘라 G- CODE 로 전환해야 한다. 슬라이서 프로그램은 보통 프린터 제조사로부터 제공이 되거나 별도의 소프트웨어를 사용하는 경우가 있다. 최고의 가정용 3D 프린터라 불리는 얼티메이커용 슬라이서인 Cura 는, 오픈소스로 공개되어 있어 수 많은 3D 프린터가 자사 프린터의 슬라이서로 널리 이용되고 있다. Cura 기준으로 설명하도록 한다. 사용하는 3D 프린터에 따라 타 프로그램을 사용할 수 있으므로, 세부적인 설정 및 인쇄 방법은 해당 3D 프린터 매뉴얼을 참고하거나, 프린팅 작업장의 관리자에게 문의하여 출력작업을 진행하도록 한다. 대표 슬라이서인 Cura 를 다운로드 받는 주소는 다음과 같다. Cura 다운로드 : https://ultimaker.com/en/products/cura-software [그림] Cura 는 오픈소스로, Windows, Mac OS, Linux 모두 지원하는 대표적인 슬라이서 이다. 내가 모델링 한 결과를 실제로 프린팅 하기 위해서는 3D 프린터가 필요로 하다. 하지만 아직까지는 대중적인 가격대가 아니고, 성능이나 편의성이 3D 프린터에 대해 가지고 있는
  • 207.
    생각을 충족해 주지않을 확률이 높다. 따라서, 3D 프린터를 직접 구입하는 것 보다는 3D 프린터를 무료로 활용할 수 있는 공간을 이용하여 3D 프린팅을 해 보는 것을 추천한다. 일반적으로 프린팅할때 다양한 옵션이 주어진다. 크게 적층 높이, 속도, 채우기의 요소가 있는데, 다음 요소는 인쇄할 때 다음과 같은 형항을 준다. 적층 높이 : 3D 프린터에서 쌓는 높이를 의미한다. 일반적으로 3D 프린팅한 결과물을 보면 플라스틱 층이 한층씩 쌓아올려진 모습을 볼 수 있는데, 당연히 이 쌓이는 높이가 낮을수록 정밀한 결과물을 보여준다. 하지만, 그만큼 여러번 쌓아야 완성되어 완성 시간이 기하 급수적으로 늘 수 있으니, 처음 인쇄할 때는 높게 인쇄하자. 분명 여러번 수정 및 인쇄를 반복하게 될 것이다. 이렇게 인쇄와 수정을 반복하고 최종 완성물을 출력할 때는 적층 높이를 낮게 하여 완성품 퀄리티를 높일 수 있다. 인쇄 속도 : 노즐이 이동하는 속도이다. 일반적으로 빠르게 움직이면, 인쇄속도가 단축될 뿐만 아니라 떠 있는 구조물을 지지대 없이 인쇄할 수 있는 확률 (실패할 확률이 높다. 일반적으로는 지지대를 갖추어 놓아야 한다.)이 높아진다. 하지만, 제대로 적층이 안되어 인쇄 실패할 확률이 높아지니 무조건 속도를 높이는 것은 좋지 않다. 채우기 : 프린팅 한 면이 있을 경우 내부를 몇 %로 채울지를 결정하는 수치이다. 벽 내부르를 벌집 모양처럼 채우는 비중을 뜻한다. 내부를 꽉 채운다면 튼튼하게 결과물을 뽑아낼 수 있지만, 출력하는데 드는 재료와 시간이 기하급수적으로 늘게되는 문제가 있다. 반대로 채우기가 너무 낮으면 견고하지 못하고 충격에 상대적으로 쉽게 부서지는 경우가 발생한다. 프로토타입은 10~15% 정도로 하고 완성품은 20~30%대를 선택하도록 하자.
  • 208.
    [그림] Cura 실행화면. Load 를 눌러 STL 파일을 불러오면, 자동으로 배치되고, 인쇄 예상 시간을 보여준다. 간단한 에제 모델링의 경우 별다른 속성 지정없이 쉽게 인쇄할 수 있으나, 복잡한 구조물이나 부품과 같은 요소는 인쇄에 실패할 수도 있다. 또한 ABS 재질로 인쇄하는 경우, 열로인한 팽창과 수축으로 모델링한 사이즈와 정확하게 일치하지 않는 경우가 발생 한다. 프린터마다 속성이 다르고, 재질과 뽑는 결과물의 구성에 따라 최적의 설정 값이 다른데, 이는 여러 번으 인쇄와 시행착오를 통해 익혀야 하는 부분이다. [그림] 결과물을 레이저 커터로 만든 결과 물. 특정 두께의 조립형 구조물은 3D 프린트 대신 레이저 커터를 이용하여 빠르게 결과물을 만들 수 있다. 여기서 잠깐! 레이저 커터 우리가 만들려는 외관은 3D 프린터의 대안으로 레이저 커터를 사용하는 방법이 있다. 레이저 커터는 레이저 절삭 방식(Laser Cutting)을 사용하는데, 레이저 빔을 최소 지름에 집중 시켜 물질을 가열하여 기화, 즉 재빠르게 태워서 절삭하는 방식으로 절삭한다. 일반적으로는 절삭용도로 사용할 수 있지만, 강도를 낮추면 살짝 그을릴 수 있어서 문양을 겉면에 표시하는 용도로 사용할 수도 있다. 3D 프린터가 적층 방식이기 때문에 시간이 오래걸리는 반면, 레이저 커터는 잘라내는 방식이기 때문에 상대적으로 빠르게 결과물을 볼수 있다. 하지만 피규어 형태나 재료의 두께를 벗어나지 못하는 한계가 있으므로, 모델링 형태와 상황에 맞추어 사용할 수 있도록 하자.
  • 209.
    [ 레이저 커터,제료를 잘라내는 식으로 만들 때 매우 유용하다. 메이커 스페이스에서 사용할 수 있다 ] 이번 장을 정리하며 이번 장에서는 프로토타이핑을 할 수 있는 방법들을 살펴보고, 3D 모델링한 파일을 3D 프린터로 출력하는 방법을 살펴보았다. 높은 가격, 느린 출력 속도와 부족한 품질, 적층형 방식의 프린팅 방식의 한계로 인하여, 대중화 되기 어려운 부분이 있으나, 자신의 생각을 초기에 프로토타입으로 구현하는 데 최적화 된 방법이다. 실제 3D 프린터나 레이저커터를 사용할 수 있도록 비치한 메이커 스페이스는 부록에서 다룰 예정이다. 다음 마지막 장에서는 외관도 완성된 라즈베리파이 오디오를 Circulus 플랫폼을 이용하여 IoT 제품으로 완성하는 법을 다룰 것이다.
  • 210.
    장비가 없을 때,메이커 스페이스로 프로토타이핑을 할 수 있는 방법들을 살펴보고, 3D 모델링한 파일을 3D 프린터로 출력하는 방법을 살펴보았다. 높은 가격, 느린 출력 속도와 부족한 품질, 적층형 방식의 프린팅 방식의 한계로 인하여, 대중화 되기 어려운 부분이 있으나, 자신의 생각을 초기에 프로토타입으로 구현하는 데 최적화 된 방법이다. 실제 3D 프린터나 레이저커터를 사용할 수 있도록 비치한 메이커 스페이스를 찾아가는 것이 방법이다 http://www.makeall.com/subpage.php?p=makerspace 한국과학창의재단에서 운영하는 MakeAll 에서는 프로젝트 제작 방법과 더불어, 전국의 메이커 스페이스를 소개하고 있다. 집과 가까운 메이커 스페이스를 방문하여 실제로 3D 프린터나 레이저 커터를 활용해 보도록 하자. [그림] 전국의 메이커스페이스 배치. Makeall.com 을 이용하여 집근처의 메이커 스페이스를 검색할 수 있다.
  • 211.
    9. 언제 어디서나동작하는 IoT – Circulus 집 밖에서도 동작되는 IoT 지금까지 하드웨어를 구성하고 소프트웨어와 3D 모델링을 통해 하나의 제품을 만들어 보았다. 집안에서 WiFi 로 연결되어 있다면 방 안에서도 거실에 있는 전등이나 오디오를 쉽게 제어할 수 있는 것이다. 단, 회사나 학교등 실외에서는 같은 WiFi 망에 연결되어 있지 않으므로, 이를 제어할 수가 없다. 근래에 부각되는 사물인터넷이라 불리는 IoT, 즉 Internet Of Things 은 구글, 네이버가 고유 IP 기반에 www.google.com, www.naver.com 같은 도메인을 지니고 있듯이, 자신을 구별할 수 있는 유일한 IP 를 가지고 인터넷에 연결되어야 사용이 가능하다. 현재 일반적으로 사용하는 ip 체제인 IPv4 로는 증가하는 모든 사물들의 주소를 할당하는데 어려움이 있어, 128 비트인 IPv6 의 필요성이 대두되고 있다. 이러한 공인 IP 를 가지고 있다면, 큰 고민 없이 외부에서도 접근할 수 있겠지만, 어떻게 바뀔지 모르는 무선 AP 에 동적으로 접속하는 라즈베리파이에 매달 비싼 이용료를 지불해야 하는 공인 IP 를 부여하는 것은 그리 경제적인 방법이 아니다. 우리는 이번 장에서 공인 IP 없이 IoT 를 구현할 수 있게 해주는 Circulus 플랫폼을 이용하여, IoT 를 가능하게 하면서 불가능 했던 기능들을 확장시켜 볼 것이다. [그림] 집 안 뿐만 아니라 밖에서도 제품을 제어할 수 있어야 한다. IoT MAKE, Circulus 플랫폼 이란 Circulus 는 교육용을 목적으로 만들어진 서비스로서, 클라우드 상에서 라즈베리파이를 개발할 수 있는 기능 또한 지원하고 있다. 즉, 라즈베리파이가 손상되거나 분실되어도 클라우드 상에 저장되어 있으므로 걱정하지 않아도 된다. Circulus 의 핵심은 공인 IP 가 없어도 IoT 가 가능하도록 중간 연결 통로 역할을 한다는 것이다. Circulus 롬이 탑재된 라즈베리파이는 Circulus Platform 에 접속해 있고, 모바일 리모콘으로 해당 라즈베리파이를 접근하려고 요청이 오게 되면, 두개 사이의 연결을 담당해 주는 역할을 한다. 이 기능이 확장되어 라즈베리파이와
  • 212.
    라즈베리파이가 통신할 수있도록 하고, 다수의 사용자가 하나의 라즈베리파이를 제어할 수 있도록 API 를 제공한다. 아울러 위치 파악, 한글 TTS 를 비롯하여 GPIO 를 통해 제어했던 7 Segment, LED, 온습도, 조도 센서와 같은 다양한 센서를 손 쉽게 개발할 수 있도록 다양한 API 를 지원하는 것이 특징이다. [그림] Circulus 를 이용하면 다양한 API 를 이용하여 IoT 제품을 손쉽게 개발할 수 있다. 라즈베리파이에서 Circulus 를 - ROM 설치 및 설정 Circulus 를 이용하여 개발하기 위해서는 라즈비안 기반의 전용 ROM 을 사용해야 한다. 롬을 다운로드 하여, MicroSD 카드에 기록해 주도록 하자. 다운로드 주소 : http://rom.circul.us 여기서 잠깐! SD 카드 백업 하기 SD 카드에 새로운 ROM 을쓰게 된다면, 이전 데이터가 날라가게 된다. 이미지 쓰기에사용되는 Win32DiskImager 에는 Write 기능과더불어 Read 기능이 있다. 이 Read 기능을 이용하면 기존에 저장된 데이터를 백업할 수 있고, 이를통해 다시 복원할 수 있다. 라즈베리파이를 인터넷과 연결하기 위해 처음에는 직접 설정 값을 입력해야 했다. 하지만, Circulus 환경으로 부팅하면, 스마트 폰을 인터넷에 연결하는 것과 같은 방식으로 간편하게 접속 설정을 할 수 있다. ROM 을 MicroSD 카드에 설치한 후, 라즈베리파이의 상태를 확인하기 위해 3.5mm 잭에 스피커나 이이폰을 연결하도록 한다. 라즈베리파이의 MicroSD 어댑터에
  • 213.
    장착하여 부팅을 시작하면,Disconnected Circulus 라는 소리가 나오는 것을 알수 있다. 이때, 스마트 폰이나 컴퓨터로 WiFi 목록을 확인하면, circulus_0000000 형태의 AP(Access Point) 가 새로 생겨난 것을 확인할 수 있다. 해당 AP 를 선택하여 접속을 시도하고, 비밀번호를 물으면 1234567890 을 입력한 후 확인을 선택하자. 그러면, AP 에 스마트 폰이 접속된 것을 확인할 수 있다. [ Circulus_시리얼명 으로 시작하는 AP 에 접속 후, 비밀번호를 입력하여 연결 ] 접속이 완료되면, 스마트 폰의 브라우저를 열고, 주소를 192.168.42.1 로 입력하도록 한다. 라즈베리파이의 인터넷 접속 연결 화면이 보여지고, 잠시후 접속 가능한 Wifi 목록이 표시되게 된다. 접속 가능한 Wifi 이름을 선택하면, 패스워드를 입력하는 화면으로 이동하는데, 패스워드 입력 후 Confirm 을 클릭하도록 한다. 정상적으로 입력하였다면, 잠시 후 스피커에서 Connected On Circulus 라는 소리와 함께 접속이 성공되었음을 알려주게 된다. AP 명에 Circulus_0000000 식으로 표시되고, 동일하게 접속 페이지 하단에 뜨는 7~8 자리 문자를 기록해 두도록 하자. 라즈베리파이의 일련 번호로서, Circulus 에서 사용하도록 해당 라즈베리파이를 등록할 때 사용하게 된다.
  • 214.
    [ 접속할 AP를 선택한 후 비밀 번호를 입력하고 Confirm 을 누르면 접속이 완료된다 ] Circulus 에 라즈베리파이 ID 등록하기 기존에는 라즈베리파이를 서버로 구동하여 하드웨어 제어 및 모바일 웹 모두를 탑재시켰었다. 이미 Circulus 에 접속된 라즈베리파이를 사용자가 개발하기 위해서는, 로그인 한 계정에 등록하는 작업을 수행해야 한다. 왼쪽 상단의 자신의 아이디 명을 클릭하면, Serial 번호를 입력하는 공간을 확인할 수 있다. 라즈베리파이의 Serial 번호를 입력 후 Update 를 수행해 주도록 한다. [ id 와 로그인에 사용할 비밀번호를 입력한 후 Sigin up & in 을 클릭하면 접속할수 있다.]
  • 215.
    이제, 첫 프로젝트를시작하고, 하드웨어를 동작해 보도록 한다. 상단 메뉴의 Project 로 이동하여, NodeJS 를 선택한다. Hello_Pi 로 프로젝트 명을 입력 한 후 Crete Project 를 눌러 프로젝트 공간으로 이동하도록 한다. [ hw 에 하드웨어 Serial 을 입력 한 후, NodeJS 프로젝트를 생성하도록 한다 ] 이제, 첫 프로젝트를 시작하고, 하드웨어를 동작해 보도록 한다. 상단 메뉴의 Project 로 이동하여, NodeJS 를 선택한다. Hello_Pi 로 프로젝트 명을 입력 한 후 Crete Project 를 눌러 프로젝트 공간으로 이동하도록 한다. 동작을 테스트 하기 위하여 Hello Pi 를 출력해 보도록 한다. 라즈베리파이에 스피커를 연결하고, 영문 및 한글 음성으로 소리를 듣는 예제이다. Index.js 에 다음의 코드를 입력하고 Run 버튼을 눌러 실행하도록 한다. [ ch9_tts.js ] // circulus api 모듈을 불러온다. var us = require('circulus'); // 영문으로 tts 를 생성한다. us.tts('Hello pi'); console.log('hello pi!'); setTimeout(function(){ // 한글로 tts 를 생성한다. us.tts('만나서 반갑습니다', true); }, 1000);
  • 216.
    [ 코드를 입력후 Run 버튼을 실행하면, 실행 결과가 라즈베리파이에 반영되고, 로그 데이터를 확인할 수 있다 ] 실행하면, 라즈베리파이로 음성 소리가 나는 것을 확인하고, 화면의 하단 콘솔에 결과가 출력되는 것을 알 수 있다. 이렇게 Circulus 플랫폼을 사용하면, 유선 연결 없이 인터넷에 연결된 라즈베리파이를 손쉽게 개발할 수 있는 기능들을 지원한다. 간단한 예제로 하나 더 실행해 보도록 한다. 접속한 라즈베리파이가 어디에서 접속되었는지 위치를 추척하는 방법이다. Wifi 에 접속한 정보를 토대로 대략적인 위/경도와 주소를 알 수 있게 해 준다. [ ch9_geolocation.js ] var us = require('circulus'); // 현재 접속한 대략적인 위치 정보를 가져온다. us.getGeolocation(function(data){ console.log(data); });
  • 217.
    [ 라즈베리파이가 연결된대략적인 위/경도와 근처 주소를 반환 ] Info 로그 콘솔창 위치 바꾸기 로그 콘솔창이 기본적으로는 하단에 배치되나, 사용시에 불편할 수가 있다. 하단의 Console 버튼을 한번 더 눌러주면 우측으로 콘솔창의 위치가 변경되고, 다시한번 눌러주면 사라지게 된다. Clear 버튼을 누르면, 콘솔창의 로그가 삭제가 된다. 필요에 따라 사용하도록 한다. Circulus 로 하드웨어 제어 하기
  • 218.
    [ Circulus 를이용한 하드웨어 제어 실습을 위한 구성 ] 우리는 GPIO 다루기에서 WiringPi 를 이용하여 LED, 초음파, 7 Segment Display, 온습도 등을 출력해 보았다. Circulus 를 이용하여 동일한 예제를 다시한번 실습해 보도록 한다. 실습을 위해 하드웨어 연결 구성을 다음과 같이 한다. [ 실습 하드웨어 실제 구성 ] LED 점등 LED 점등을 위해 사용할 핀을 설정해 주고, true/false 로 on/off 를 수행할 수 있다. 동작하기 위한 방법은 다음과 같이 간단하게 구성할 수 있다. [ ch9_led.js ] var us = require('circulus'); // 7번 핀의 LED를 초기화 한다. us.initLED(7); // LED 를 켠다. us.setLED(true); 단일 LED 의 경우, 위와 같이 간단하게 사용할 수 있지만, 1 개 이상의 LED 를 사용하여 제어하는 경우가 발생할 수 있다. 이와 같은 경우에는 명령어 뒤에 Key 값을 포함하여, 해당 Key 값을 제어할 수 있도록 구성할 수 있다. 여러 개의 LED 를 사용하기 위한 방법은 다음과 같다. [ ch9_led2.js ] var us = require('circulus');
  • 219.
    // 7번 핀의LED를 led1 이라는 이름으로 초기화 한다. us.initLED(7, 'led1'); // led1 이라는 이름의 LED를 끈다. us.setLED(false, 'led1'); // 5번핀의 LED를 led2 라는 이름으로 초기화 한다. us.initLED(5, 'led2'); // led2 이라는 이름의 led를 켠타. us.setLED(true, 'led2'); 7 Segment 동작 7 세그먼트를 동작하기 위해, 기존에는 모듈 설치 및 복잡한 코드를 작성하여 사용해여 했다. Circulus 를 이용해 다음과 같이 3 줄만으로 작성이 완료된다. initText 로 사용할 SPI 기반 7 세그먼트 모듈을 초기화 하고, 이후에 setText 함수로 출력할 내용을 입력해 주면 된다. [ ch9_7segment.js ] var us = require('circulus'); // SPI방시그이 7세그먼트를 초기화 한다. us.initText(); // 7세그먼트에 hipi 를 출력한다. us.setText('hipi'); 온습도확인 온습도 측정을 위해 설치해야 할 칩셋용 모듈과 NodeJS 용 모듈이 통합되었다. initTemp 함수로 GPIO 출력 핀을 설정해 주고, getTemp 함수로 온습도 값을 측정할 수 있다. [ ch9_temp.js ] var us = require('circulus'); // 4번 핀에 연결된 온습도 센서를 초기화 한다. us.initTemp(4); // 1초 간격으로 온습도 값을 측정하여 출력한다. setInterval(function(){ console.log(us.getTemp()); },1000); 거리측정 측정도 별도의 계산 공식이나 모듈 추가 없이 간단하게 사용할 수 있도록 지원해 준다. initDistance 함수로 ECHO 핀과 TRIGGER 핀을 설정해 주고, getDistance 함수를 이용하여 측정된 거리를 cm 단위로 반환받게 된다. [ ch9_distance.js ] var us = require('circulus'); // 27번 핀을 에코로, 22번 핀을 트리거로 설정한다.
  • 220.
    us.initDistance(27,22); // 1초 간격으로거리를 축정하여 출력한다. setInterval(function(){ console.log(us.getDistance()); },1000); 모바일로 하드웨어 제어 PWM 기능을 이용하여 모바일을 통해 LED 밝기를 조절하는 프로그램을 간단하게 구현해 보는 예제이다. 7 장에서 다룬 예제로는 라즈베리파이와 같은 인터넷 접속 영역에서만 제어를 할 수 있었다. 구현을 위해 웹 서버 모듈인 ExpressJS 와 실시간 통신 모듈인 Socket.IO 모듈을 이용했다. 하지만, Circulus 를 이용해서는 웹 페이지 프로젝트와 라즈베리파이 프로젝트를 분리하여 구현할 수 있다. 이를 통해 어디서나 Circulus 상에서 개발된 웹 페이지를 통해 역시 Circulus 에 연결된 라즈베리파이를 제어할 수 있도록 도와준다. 우선 서버 프로젝트를 진행한다. Circulus_Test 라는 명으로 NodeJS 프로젝트를 생성하여 라즈베리파이에서 실행하도록 한다. [ ch9_spwm.js ] var us = require('circulus'); // 7번 핀을 소프트웨어 PWM 방식으로 초기화한다. us.initSPWM(7); // 값을 입력한다. us.writeSPWM(240); // 클라이언트로부터 brightness 이벤트로 받아진 값을 입력한다. us.receive('brightness',function(val){ us.writeSPWM(parseInt(val)); });z 7 번 핀에 연결된 LED 에 소프트웨어 방식의 PWM 을 동작시키고, 최대한의 밝기로 불을 켜는 동작을 수행한다. Receice 함수를 통해, 모바일로부터 brightness 이벤트가 발생 할 경우, LED 밝기를 실시간으로 조절하는 역할을 한다. 이제 모바일 쪽 코드를 만들 차례이다. Circulus_Web 이라는 명칭으로 Mobile 프로젝트를 생성하도록 한다. 범위를 설정할 수 있는 입력 컴포넌트를 배치하고, 이 입력 값이 변경될 때 brightness 이벤트를 통해 변경된 값을 전송하는 에제이다. Init 함수를 이용하여, 접속할 라즈베리파이를 지정하게 되는데, 인터넷 접속시에 확인한 라즈베리파이 일련번호를 이곳에 입력하여 실행하도록 하자. [ ch9_spwm.html ] <html> <head> <title>Mobile Web</title>
  • 221.
    <meta name="viewport" content="width=device-width,initial- scale=1.0, user-scalable=no" /> <link rel="stylesheet" href="http://code.jquery.com/mobile/1.4.5/jquery.mobile- 1.4.5.min.css" /> <script src="http://code.jquery.com/jquery- 2.2.0.min.js"></script> <script src="http://code.jquery.com/mobile/1.4.5/jquery.mobile- 1.4.5.min.js"></script> <script src="http://www.circul.us/circulus.js"></script> <script> $c.ready(function(us){ // 라즈베리파이의 일련번호를 입력한다. us.init('시리얼번호 입력'); // 슬라이더 값이 변경 시, 라즈베리파이에 값을 전달한다. $('input').change(function(){ us.send('brightness', $(this).val()); }); }); </script> </head> <body> <div data-role='main' class='ui-content'> <input type="range" min="0" max="240" value="240" /> </div> </body> </html> [우측 하단 실행창의 하단에 언제나 접속할 수 있는 URL 이 표시되고, 상단에는 간단하게 접속할 수 있는 단축 URL 이 표시된다.]
  • 222.
    결과 웹 페이지에서제어할 수도 있지만, 우측 하단 결과창의 url 을 클릭하거나 직접 입력하여 동작을 확인해 볼 수 있다. 이렇게 만들어진 결과는 프로젝트를 Drop 하기 전 까지 제공되는 URL 을 통해 결과를 확인하고, 어디에서나 접속할 수 있는 환경이 제공되게 된다. Info 라즈베리파이 연결 상태 확인 Circulus 를 이용하여 라즈베리파이에 개발하기 위해서는 연결상태를 확인해 보아야 한다. 프로젝트 하단에 있는 Check 버튼을 클릭하면, 등록한 하드웨어 접속 상태를 확인할 수 있다. 이때, 라즈베리파이의 IP 가 반환되는 경우 Circulus 에 정상적으로 접속이 된 상태이다. 이 IP 를 이용하여 Putty 나 FTP 를 통해 파일을 주고 받을 수도 있다. Circulus 기반으로 리 메이크 기존에는 라즈베리파이를 서버로 구동하여 하드웨어 제어 및 모바일 웹 모두를 탑재시켰었다. 하지만 Circulus Platform 을 이용한 개발에서는 첫째, 모바일과하드웨어 제어 부분이 완벽하게 분리가 된다. 둘째, Circulus Platform 에 배포된 웹으로 언제어디서나 접근할 수 있다. 라즈베리파이의 Serial 번호를입력하면 Circulus Platform 에서 모바일과 라즈베리파이의 연동을 제공해 준다. 셋째, 기본적인 하드웨어 제어 및 타 디바이스와의 통신을 가능하게 하는 API 를 지원함으로서, 손쉽게 나만의 IoT 하드웨어를개발 및 운영할 수 있도록 도와준다. 기존 오디오 코드를 Circulus 에서 구동 가능하도록 프로그램을변경하도록 한다. 통합되어 있는 코드를 라즈베리파이 제어 부와 모바일 컨트롤러 부로 나누도록 할 것이다. 하드웨어 개발 라즈베리파이에서 처음 실행되는 부분이다. ExpressJS 를 활용하여 구성한 기존 예제와 달리, receive 함수를 이용하여, 모바일에서 전달된 명령이 전달받아 실행하는 구성을 가지고 있다. 실질적으로 음악이나 라디오를 재생하고, 복합 기능을 구현하는 것은 별도의 파일로 구현되어 있다. 라즈베리파이 오디오는 평상시에는 시계역할을 하므로, 코드의 마지막 부분에 매 1 분 마다 7 세그먼트에 표시되는 시간을 1 분마다 갱신하도록 구성되었다. [ index.js ] var us = require('circulus'); var ext = require('./extend'); var exec = require('child_process').exec; var recom = require('./recom'); var store = require('./store'); var top = require('./top'); var radio = require('./radio'); var talk = require('./talk'); isLoop = false; isRepeat = false; // 모바일로부터 play 이벤트를 수신한 경우 호출된다. us.receive('play', function(data){ var url = data.url; console.log(__dirname + ' / ' + url);
  • 223.
    ext.tts('음악을 다운로드 하고있습니다'); exec('sudo scdl --debug --path /home/pi/buffer -l ' + url, function(err, stderr, stdout){ if(stdout && stdout.indexOf('filename : ') > -1){ data.file = stdout.split('filename : ')[1].split('[0mn')[0]; store.add(data); ext.play(data, true); } else { console.log('Error occured ' + err); } }); }); // 음악 목록 호출시, 목록을 반환한다. us.receive('list', function(data){ us.send('list', { list : ext.get()}); }); // 볼륨을 조정한다. us.receive('volume', function(data){ var vol = data.volume; ext.setVolume(vol); }); // 라디오를 재생할 때 호출된다. us.receive('radio', function(data){ radio.play(data); }); // 뉴스를 읽어줄 때 호출된다. us.receive('news', function(data){ radio.news(); }); // 날씨를 읽어줄 때 호출된다. us.receive('weather', function(data){ radio.weather(); }); // 다시 재생이 호출될 때 호출된다. us.receive('resume', function(data){ ext.play(); }); // 다음 음악 재생을 선택했을 때 호출 된다. us.receive('next', function(data){ ext.next(); }); // 이전 음악 재생을 선택했을 때 호출 된다. us.receive('prev', function(data){ ext.prev(); }); // 대화하기를 선택했을 때 호출 된다. us.receive('talk', function(data){
  • 224.
    talk.say(data); }); // 정지를 선택했을때 호출된다. us.receive('stop', function(data){ exec('pkill mplayer'); }); // 기존 재생 목록을 선택했을 때 호출 된다. us.receive('history', function(data){ us.send('history', { result : store.gets()}); }); // 추천 음악 목록을 선택했을 때 호출된다. us.receive('recom', function(data){ us.send('recom', { result : recom.getParam()}); }); // 메시지 전달을 선택했을 때 호출된다. us.receive('message', function(data){ us.tts(data,true); }); // 알람 정보를 가져올 때 호출 된다. us.receive('getAlarm', function(data){ us.send('getAlarm', { result : store.getAlarm()}); }); // 알람 정보를 설정할 때 호출 된다. us.receive('setAlarm', function(data){ us.tts('알람을 설정 합니다', true); store.setAlarm(data); }); // 백라이트 LED 밝기를 설정할 때 호출 된다. us.receive('setBg', function(data){ console.log('Settring Bg : ' + data.bg); ext.setBg(parseInt(data.bg)); }); // 포그라운드 7세그먼트 밝기를 설정할 때 호출 된다. us.receive('setFg', function(data){ console.log('Settring Fg : ' + data.fg); ext.setFg(parseInt(data.fg)); }); // 최신가요 목록을 가져올 때 호출 된다. us.receive('top', function(data){ top.getList(function(list){ console.log(list); us.send('top',{ result : list}); }); }); // 프로그램 강제 업데이트 요청시 호출된다. us.receive('update',function(){ us.update(); });
  • 225.
    // 목록 반복요청 시 호출된다. us.receive('loop',function(data){ isLoop = data.value; if(isLoop){ us.tts('순환 재생을 시작합니다', true); } else { us.tts('순환 재생을 종료합니다', true); } }); // 현재 음악 반복시 호출된다. us.receive('repeat',function(data){ isRepeat = data.value; if(isRepeat){ us.tts('반복 재생을 시작합니다', true); } else { us.tts('반복 재생을 종료합니다', true); } }); // 첫 구동시 자기소개를 음성으로 읽어준다. us.tts('안녕하십니까 여러분의 소셜 오디오 파이오가 깨어났습니다', true); console.log('[20160220] piAu Stable v1 Initialized!'); // 현재 시간을 7세그먼트에 표시하고, 1분 간격으로 갱신한다. ext.setCurrentTime(); setInterval(function(){ ext.setCurrentTime(); },60000); 실제 음악 재생 시간을 표시하여 재생하고, 시간을 표시하는 기능등 핵심 기능을 제공하는 파일이다. 음악 재생을 담당하는 play 함수는 mplayer 프로그램을 이용하여 재생하고, 나오는 재생 정보를 실시간으로 캡쳐하여 7 세그먼트에 보여주게 된다. 다음 음악과 이전 음악 재생을 담당하는 next 와 prev 함수는 기존에 재생된 음악 리스트를 기준으로 현재 재생중인 음악의 과거 혹은 현재 값을 바탕으로 음악 재생을 처리한다. setVolume 함수는 라즈베리파이의 내장 명령어인 amixer 를 이용하여 볼륨을 조정한다. 마지막으로 현재 시간을 표시하는 setCurrentTime 함수는 현재 시간 표시뿐만 아니라, 알람이 설정된 경우, 설정된 알람 시간과 현재 시간을 비교하여 알람을 발생시키는 역할을 한다. [ extend.js ] var exec = require('child_process').exec; var us = require('circulus'); var spawn = require('child_process').spawn; var LocalStorage = require('node-localstorage').LocalStorage; var localStorage = new LocalStorage('./music_list'); var store = require('./store'); var isContinue = false;
  • 226.
    // 7세그먼트를 초기화하고, 초기 메시지를 출력한다. us.initText(); us.setText('piau'); // 백라이트용 LED를 소프트웨어 PWM 방식으로 초기화 하고 밝혀준다. us.initSPWM(7); us.writeSPWM(240); // 동작 표시용 LED를 초기화 하고 켠다. us.initLED(25); us.setLED(true); // 7세그먼트 밝기 조절 시 호출된다. exports.setFg = function(val){ us.setBrightness(val); } // 백그라운드 LED 밝기 조절을 소프트웨어 PWM 으로 조절한다. exports.setBg = function(val){ us.writeSPWM(val); } var volume = 100; // 현재 시간을 7세그먼트에 표시한다. var setTime = function(number1, number2){ var min1 = Math.floor(number1 / 10); var min2 = number1 % 10; var sec1 = Math.floor(number2 / 10); var sec2 = number2 % 10; us.setText(min1.toString() + min2.toString() + sec1.toString() + sec2.toString(),false,false,false,false); } // 입력받은 한글 음성을 출력한다. var tts = function(msg){ us.tts(msg, true); } // 저장소에 저장된 목록을 이용하여 다음 음악을 재생하고, 화면에 표시한다. exports.next = function(){ var item = store.next(); console.log(item); play(item,true); us.send('info', item); } // 저장소에 저장된 목록을 이용하여 이전 음악을 재생하고, 화면에 표시한다. exports.prev = function(){ var item = store.prev(); console.log(item); play(item, true); us.send('info', item); } // 선택한 음악을 다운로드 받고 재생한다. var play = function(item, isPlay){
  • 227.
    // 만일, 재생할음악을 전달받았다면, 해당 음악 재생을 준비한다. if(item){ var data = item; store.setMusic(data.file); if(data.title){ tts(data.title + '을 재생합니다'); } // 선택된 음악이 없다면, 마지막 재생 음악을 가져온다. } else { var data = { file : store.getMusic() }; } isContinue = isPlay; // 이전에 재생중인 것이 있다면, 강제종료 후 재생 준비를 한다. exec('pkill mplayer', function(){ console.log('Playing : ' + data.file); // 음악을 재생한다. var ps = spawn('mplayer',['/home/pi/buffer/' + data.file]); var before = 0; ps.stderr.on('data', function(data){ isContinue = false; // 한글 등 다국어를 위해 UTF8 방식으로 문자열을 변환한다. var progress = data.toString('utf8'); // 재생 시간 정보가 있는지 확인한다. if(progress && progress.indexOf('of') > -1){ var token = progress.split(' of '); // 재생 정보를 가져온다. var current = token[0].split(' ').slice(-2)[0]; var total = token[1].split(' ')[0] var remain = Math.round(total - current); // 현재 표시 시간과 다르다면, 7세그먼트의 표시 시간을 바꾸고 // 클라이언트에도 변경된 현재 재생 시간을 전달해 준다. if(before != remain){ before = remain; setTime(Math.floor(remain / 60), remain % 60); us.send('time', { total : total, current : current}); } } }); ps.on('close', function(code){ console.log(code + ' : ' + isRepeat + ' / ' + isLoop + ' / ' + isContinue); // 재생이 종료시 조건을 확인한다. if(!isLoop && !isRepeat){ // tts('재생이 종료되었습니다');
  • 228.
    // 현재 음악반복 재생인 경우, 현재 음악을 다시 재생한다. } else if(isRepeat && !isContinue){ console.log('repeat'); play(data); // 목록 반복의 경우 다음음악을 재생한다. } else if(isLoop && !isContinue){ console.log('next'); exports.next(); } // 클라이언트에 종료 정보를 전달하고, // 7세그먼트에 현재시각을 표시한다. us.send('finish'); setCurrentTime(true); }); }); } // 현재 시각을 7세그먼트에 표시하며, 알람 설정 정보를 확인하고 처리한다. var setCurrentTime = function(isDisplay){ var date = new Date(); // 알람 시간 정보를 저장소로부터 가져온다. var alarm = localStorage.getItem('alarm'); // 알람정보가 있는지 확인한다. if(alarm != null && !isDisplay){ alarm = JSON.parse(alarm); var day = date.getDay().toString(); // 알람정보가 존재하고, 현재 시각과 같다면 알람을 발생한다. if(alarm.days.indexOf(day) > -1){ if(alarm.hour == date.getHours() && alarm.minute == date.getMinutes()){ tts('주인님 어서 일어나세요'); play(); } } } // 현재 시각을 7세그먼트에 표시한다. setTime(date.getHours(), date.getMinutes()); } exports.play = play; exports.setCurrentTime = setCurrentTime; // 볼륨을 설정한다. exports.setVolume = function(vol){ if(vol == 'up'){ if(volume < 100){ volume += 5; } } else { if(volume > 0){ volume -= 5; }
  • 229.
    } tts('볼륨을 ' +volume + '% 로 조정하였습니다'); exec('amixer sset PCM ' + volume + '%'); } exports.tts = tts; 라디오 및 날씨와 뉴스 정보를 가져오는 모듈이다. Play 함수는 모바일 컨트롤러로 부터 받아지는 라디오 스트리밍 재생 주소로 라디오를 재생한다. News 와 weather 는 우선 RSS 서비스 주소로부터 받아진 XML 데이터를 JSON 으로 변환하고, 필요한 정보만을 tts 함수를 사용하여 스피커로 출력하게 된다. [ radio.js ] var stew = new (require('stew-select')).Stew(); var htmlparser = require("htmlparser"); var us = require('circulus'); var spawn = require('child_process').spawn; var exec = require('child_process').exec; var rsj = require('rsj'); // 인터넷 스트리밍 라디오 주소를 활용해 라디오를 재생한다. exports.play = function(data){ us.tts(data.name + ' 라디오를 재생합니다',true); exec('sudo pkill mplayer', function(){ spawn('mplayer',['-quiet',data.radio]); }); }; // JTBC RSS 최신 소식을 이용하여, TTS로 읽어준다. exports.news = function(data){ exec('sudo pkill mplayer'); // RSS XML 정보를 json 타입의 데이터로 변환한다. rsj.r2j('http://fs.jtbc.joins.com//RSS/newsflash.xml',function(json) { var data = JSON.parse(json); var script = ('안녕하세요 파이오가 최신 뉴스를 알려드리겠습니다 ' + data[0].title + data[0].description + '이상 오늘의 뉴스를 알려드렸습니다'); // TTS 로 정보를 읽어준다. us.tts(script,true); }); }; // 일기 예보 정보를 읽어준다. exports.weather = function(data){ exec('sudo pkill mplayer'); us.getGeolocation(function(data){ console.log(data);
  • 230.
    us.tts(data.addr +'에 있습니다',true); }); //일기예보 RSS XML 정보를 JSON 형태로 가져온다. rsj.r2j('http://www.kma.go.kr/weather/forecast/mid-term- rss3.jsp?stnId=109',function(json) { var data = JSON.parse(json); console.log(data.length); // 날씨 정보를 추출한다. var script = data[0]['rss:description'].header.wf['#'].replace(/<br />/gi, ' ').replace(/,/gi, '.'); // 날씨 정보를 TTS 로 읽어준다. us.tts('안녕하세요 파이오가 오늘의 날씨를 알려드리겠습니다 '+ data[0].title + script + ' 이상 오늘의 날씨를 알려드렸습니다', true); }); }; 음악을 추천해 주는 모듈로, 7 장에서 사용한 추천 모듈을 그대로 사용한다. 이미 들었던 음악의 장르, 길이, 비트 수를 바탕으로 유사한 패턴의 음악 형식을 반환 한다. [ recom.js ] var stats = require("stats-analysis"); var store = require('./store'); var genres = []; var durs = []; var bpms = []; // 해당 장르의 재생 횟수를 저장한다. var setCount = function(genre){ for(var i in genres){ if(genres[i].genre == genre){ genres[i].count++; return; } } genres.push({ genre : genre, count : 1}); } // 가장 많이 들은 장르를 파악한다. var getTopGenre = function(){ genres.sort(function(a, b){ return b.count - a.count; }); return genres[0]; } // 추천에 사용되는 인자 값을 추출한다. exports.getParam = function(){ var list = store.gets(); console.log(list); genres = [];
  • 231.
    durs = []; bpms= [] for(var i in list){ setCount(list[i].genre); } var item = getTopGenre(); for(var i in list){ if(list[i].genre == item.genre){ durs.push(list[i].dur); if(list[i].bpm != null){ bpms.push(list[i].bpm); } } } durs = stats.filterOutliers(durs); bpms = stats.filterOutliers(bpms); return { 'genres' : item.genre, 'bpm[from]' : Math.min(bpms), 'bpm[to]' : Math.max(bpms), 'duration[from]' : Math.min(durs), 'duration[to]' : Math.max(durs), } } 들었던 음악 리스트를 관리하는 모듈이다. Add 함수는 현재 들은 음악을 리스트에 저장하여 보관하고, next 와 prev 함수는 들은 음악 리스트를 이용하여 현재 들은 음악의 이전/다음 음악을 반환한다. setAlarm/getAlarm 함수는 알람 정보를 저장하고 불러오는 역할을 하며, setMusic/getMusic 은 현재 재생중인 음악 정보를 저장하고 가져오는 역할을 한다. [ store.js ] var LocalStorage = require('node-localstorage').LocalStorage; var exec = require('child_process').exec; var space = require('diskspace'); localStorage = new LocalStorage('./music_list'); // 저장된 음악 재생 목록을 가져온다. var getList = function(){ var list = localStorage.getItem('music_list'); if(list == null){ return []; } else { return JSON.parse(list); } } // 음악 재생 목록을 추가한다. exports.add = function(item){ var list = getList();
  • 232.
    for(var i inlist){ if(list[i].url == item.url){ list.splice(i, 1); } } list.push(item); space.check('/', function (err, total, free, status){ if(free * 100 / total < 5){ console.log(free * 100 / total); var last = list.shift(); exec('sudo rm /home/pi/buffer/' + last.file); } localStorage.setItem('music_list',JSON.stringify(list)); }); } // 사운드클라우드 URL 을 이용하여 음악 정보를 가져온다. exports.get = function(url){ var list = getList(); for(var i in list){ if(list[i].url == url){ return list[i]; } } } // 다음 음악 재생 목록을 가져온다. exports.next = function(){ var file = localStorage.getItem('music'); var list = getList(); for(var i = 0 ;i < list.length; i++){ if(list[i].file == file){ if(i == (list.length - 1)){ return list.shift(); } return list[(i + 1)]; } } } // 이전 음악 재생 목록을 가져온다. exports.prev = function(){ var file = localStorage.getItem('music'); var list = getList(); for(var i = 0 ;i < list.length; i++){ if(list[i].file == file){ if(i == 0){ return list.pop(); } return list[i - 1]; } }
  • 233.
    } // 재생 목록전체를 가져온다. exports.gets = function(){ return getList(); } // 알람 설정 값을 저장한다. exports.setAlarm = function(data){ localStorage.setItem('alarm',data.param); } // 저장한 알람 설정 값을 가져온다. exports.getAlarm = function(){ return localStorage.getItem('alarm'); } // 현재 재생 음악 정보를 저장한다. exports.setMusic = function(file){ localStorage.setItem('music', file); } // 저장한 현재 재생 음악 정보를 가져온다. exports.getMusic = function(){ return localStorage.getItem('music'); } 대화하는 기능을 처리하는 모듈이다. 사용자로부터 전달 받은 메시지를 Circulus API 인 talk 함수에 전달하여, 반환된 메시지를 TTS 로 읽어주고, 사용자에게 메시지를 전달하는 역할을 한다. [ talk.js ] var us = require('circulus'); // 입력 받은 메시지로 대화를 수행한다. exports.say = function(msg){ // 입력 메시지로 대답 내용을 반환한다. us.talk(msg, function(res){ console.log('Return : ' + res); // 대답 내용을 TTS 로 읽어준 후 클라이언트에 전달한다. us.tts(res, true); us.send('talk', res); }); }; 최신 가요를 추출하는 모듈이다. M.NET 의 TOP100 에서 정보를 추출하여, 우리가 사용할 앨범정보, 앨범 이미지, 가수, 음악명을 추출하여 전달하게 된다. [ top.js ] var stew = new (require('stew-select')).Stew(); var htmlparser = require("htmlparser"); var http = require('http');
  • 234.
    var options ={ hostname: 'www.mnet.com', port: 80, path: '/chart/TOP100', method: 'GET' }; // 최신 가요 목록을 가져온다. exports.getList = function(cb){ var req = http.request(options, function(res) { res.setEncoding('utf8'); var dom = ''; res.on('data', function (chunk) { dom += chunk; }); res.on('end', function(){ var isFirst = true; stew.select(dom, 'div.MMLITitle_Wrap', function(err,items) { if(err) { cb([]); console.error(err); } else { var list = []; items.forEach(function(item){ if(stew.select(item,'img') && stew.select_first(item,'a.MMLIInfo_Artist') && stew.select_first(item,'a.MMLIInfo_Album').children[0]){ list.push({ img : stew.select_first(item,'img').attribs.src.replace('/50/','/80/'), artist : stew.select_first(item,'a.MMLIInfo_Artist').children[0].raw, title : stew.select_first(item,'a.MMLI_Song').children[0].raw, album : stew.select_first(item,'a.MMLIInfo_Album').children[0].raw }); } }); cb(list); } }); }); }); req.on('error', function(e) { cb([]); console.log('problem with request: ' + e.message); }); req.end(); }
  • 235.
    모바일 개발 Index 페이지는처음 접속하여 보여지는 모바일 시작 페이지이다. 7 장에서 만든 구성과 유사하지만, 모든 화면을 한 파일에서 함께 관리했던 기존 구성과 달리, 각각의 화면을 별도의 파일로 구성하였다. 첫 화면은 음악을 검색하고, 들었던 음악을 관리하는 페이지이다. 하단에는 음악, 라디오, 대화, 알람, 메시지 전달 메뉴로 이동할 수 있는 버튼이 배치하고 있으며, 어느 메뉴에서나 쉽게 음악 관리를 할 수 있도록 jQueryMobile 에서 제공하는 Panel 속성으로 간이 컨트롤러를 내장하고 있다. [ index.html ] <html> <head> <title>Piau Mobile</title> <meta charset="utf-8"> <meta name="viewport" content="width=device-width, initial- scale=1.0, user-scalable=no" /> <link rel="stylesheet" href="http://code.jquery.com/mobile/1.4.5/jquery.mobile- 1.4.5.min.css" /> <link rel="stylesheet" href="play.css" /> <script src="http://code.jquery.com/jquery- 2.2.0.min.js"></script> <script src="http://code.jquery.com/mobile/1.4.5/jquery.mobile- 1.4.5.min.js"></script> <script src="https://connect.soundcloud.com/sdk/sdk- 3.0.0.js"></script> <script src="https://cdnjs.cloudflare.com/ajax/libs/jQuery- Knob/1.2.13/jquery.knob.min.js"></script> <script src="http://www.circul.us/circulus.js"></script> <script src='index.js'></script> <script src='radio.js'></script> <script src='play.js'></script> <script src='option.js'></script> <script src='alarm.js'></script> <script src='message.js'></script> <script src='talk.js'></script> </head> <body> <div data-role="page" id='music'> <div data-role='header' data-position='fixed' data-tap- toggle="false"> <a href='#panel' data-icon='action' class='ui-btn- left'>Menu</a> <h1>piAu</h1> <a href='option.html' data-icon='gear' data- transition='slideup' class='ui-btn-right'>Option</a> <div data-role='navbar'> <ul> <li><a href='#' id='find'>Search</a></li> <li><a href='#' id='his'>History</a></li> <li><a href='#' id='top'>Top 50</a></li> <li><a href='#' id='recom'>Recomm.</a></li> </ul> </div>
  • 236.
    </div> <div data-role='main' class='ui-content'> <divdata-role='fieldcontain' class='search'> <input type="search" name="search" id="search" placeholder="Search for content..." data-corners="false" /> </div> <div data-role='fieldcontain'> <ul data-role='listview'> </ul> </div> </div> <div data-role='footer' data-position='fixed' data-tap- toggle="false"> <div data-role='navbar'> <ul> <li><a href='index.html' data-icon='audio' data-transition='slide'>Music</a></li> <li><a href='radio.html' data-icon='grid' data-transition='slide'>Radio</a></li> <li><a href='talk.html' data-icon='phone' data-transition='slide'>Talk</a></li> <li><a href='alarm.html' data-icon='clock' data-transition='slide'>Alarm</a></li> <li><a href='message.html' data- icon='comment' data-transition='slide'>Message</a></li> </ul> </div> </div> </div> <div data-role="panel" style='width:5.5em;display:none;' data-position="left" data-display="overlay" id="panel" data- theme="a"> <h3>Quick</h3> <a href='#' name='vol_up' data-role='button' class="ui- btn ui-shadow ui-corner-all ui-btn-a">+</a> <a href='#' name='vol_down' data-role='button' class="ui-btn ui-shadow ui-corner-all ui-btn-a">-</a> <a href='#' name='resume' data-role='button' class="ui- btn ui-shadow ui-corner-all ui-btn-a">▶</a> <a href='#' name='stop' data-role='button' style='display:none;' class="ui-btn ui-shadow ui-corner-all ui-btn- a">■</a> <a href='#' name='prev' data-role='button' class="ui-btn ui-shadow ui-corner-all ui-btn-a">◁</a> <a href='#' name='next' data-role='button' class="ui-btn ui-shadow ui-corner-all ui-btn-a">▷</a> <h3>Loop</h3> <a href='#' name='loop_on' data-role='button' class="ui- btn ui-shadow ui-corner-all ui-btn-a">X</a> <a href='#' name='loop_off' style='display:none;' data- role='button' class="ui-btn ui-shadow ui-corner-all ui-btn-a">O</a> <h3>Repeat</h3> <a href='#' name='repeat_on' data-role='button' class="ui-btn ui-shadow ui-corner-all ui-btn-a">X</a> <a href='#' name='repeat_off' style='display:none;' data-role='button' class="ui-btn ui-shadow ui-corner-all ui-btn- a">O</a> </div><!-- /panel --> </body>
  • 237.
    </html> Index 모듈은 라즈베리파이에연결하기 위해 접속부와 화면을 초기화 하고, 음악을 검색하고 재생하는 기능을 포함하고 있다. 일련번호가 입력되지 않았다면, 입력창을 발생시켜 사용자로부터 라즈베리파이 일련번호를 받아 접속을 시도하게 된다. Check 함수는 연결 설정한 라즈베리파이가 Circulus 플랫폼에 제대로 접속되어 있는지 확인하는 함수이다. 텍스트 입력창에 검색할 음악명을 입력하거나 recom 함수를 통해 라즈베리파이에 저장된 음악 추천 지표를 이용하여 SoundCloud API 를 이용하여 음악을 검색한다. 검색된 결과가 화면에 표시되고, 해당 목록을 사용자가 클릭하게 되면, 음악 재생화면으로 이동하여 재생을 시작하게 된다. [ index.js] $c.ready(function(us){ $.mobile.ignoreContentEnabled = true; $('div[data-role=panel]').panel(); $('div[data-role=panel]').show(); SC.initialize({ client_id: '119c57cf677bb6a42180112605b1c053' }); // 접속할 라즈베리파이의 일련번호를 가져온다. var serial = localStorage.getItem('serial'); // 저장된 일련번호가 없다면, 새로운 일련번호를 입력하고 저장한다. if(serial == null || serial == 'null'){ serial = prompt("Input piAu's serial"); localStorage.setItem('serial', serial); } // 라즈베리파이의 일련번호로 접속한다. us.init(serial); // 접속할 라즈베리파이가 인터넷 망에 연결되었는지 확인한다. us.check(function(isOn){ if(!isOn){ alert('Please check on/off at piAu!'); } }) // 기본 배경화면을 설정한다. $('#music').css('background-image', 'url("http://webcdn.vshare.com/8ef134ae5c5e01c96a2ed808455e82e9")'); // 음악 검색 시 호출되어 검색 결과를 화면에 보여준다.. $('#search').keypress(function(e){ $.mobile.loading('show'); if (e.which == 13) {/* 13 == enter kfiey@ascii */ var query = $('#search').val(); $('body').trigger('click');
  • 238.
    $('#search').val(''); $('ul[data-role=listview]').empty(); SC.get('/tracks', { q:query }).then(function(tracks) { for(var i in tracks){ var item = tracks[i]; console.log(item); var elem = $('<a>').attr({ 'data-transition' : 'pop', 'href' : 'play.html' }); elem.data('item',{ title : item.title, img : item.artwork_url, url : item.permalink_url, desc : item.description, dur : item.duration, genre : item.genre, bpm : item.bpm, cnt : item.playback_count, time : Date.now() }).addClass('play'); var list = $('<li>').append(elem); elem.append($('<img>').attr('src',item.artwork_url)); elem.append($('<h2>').text(item.title)); elem.append($('<p>').text(item.description)); $('ul[data-role=listview]').prepend(list); } $('ul[data-role=listview]').listview('refresh'); $.mobile.loading('hide'); }); } }); // 재생 목록의 음악을 선택 했을 때 이를 표시하고 재생한다. $('ul[data-role=listview]').on('click','a.play', function(){ var item = $(this).data('item'); var img = $(this).attr('img'); var title = $(this).attr('title'); us.send('play', item); $('a[name=loop_off]').hide(); $('a[name=loop_on]').show(); $('a[name=repeat_off]').hide(); $('a[name=repeat_on]').show(); sessionStorage.setItem('img', item.img); sessionStorage.setItem('title', item.title); });
  • 239.
    // 재생 목록을수신 시, 화면에 목록을 표시해 준다. us.receive('history', function(data){ $('ul[data-role=listview]').empty(); for(var i in data.result){ var item = data.result[i]; var elem = $('<a>').attr({ 'data-transition' : 'pop', 'href' : 'play.html' }).addClass('play'); elem.data('item',item); var list = $('<li>').append(elem); elem.append($('<img>').attr('src',item.img)); elem.append($('<h2>').text(item.title)); elem.append($('<p>').text(item.desc)); $('ul[data-role=listview]').prepend(list); } $('ul[data-role=listview]').listview('refresh'); $.mobile.loading('hide'); }); // 검색 목록 메뉴 클릭 시 호출된다. $('#find').click(function(){ $('div.search').show(); $.mobile.loading('show'); us.send('history'); }); // 최신 가요 목록 메뉴 클릭 시 호출된다. $('#top').click(function(){ $('div.search').hide(); $.mobile.loading('show'); us.send('top'); }); // 재생 목록 메뉴 클릭 시 호출된다. $('#his').click(function(){ $('div.search').hide(); $.mobile.loading('show'); us.send('history'); }); // 추천 음악 목록 클릭 시 호출된다. $('#recom').click(function(){ $('div.search').hide(); $.mobile.loading('show'); us.send('recom'); }); // 추천음악을 라즈베리파이 서버로부터 전달받아 이를 클라이언트에 출력한다. us.receive('recom',function(data){ $('ul[data-role=listview]').empty();
  • 240.
    SC.get('/tracks', data).then(function(tracks) { for(vari in tracks){ var item = tracks[i]; console.log(item); var elem = $('<a>').attr({ 'data-transition' : 'pop', 'href' : '#music' }).addClass('music'); elem.data('item',{ title : item.title, img : item.artwork_url, url : item.permalink_url, desc : item.description, dur : item.duration, genre : item.genre, bpm : item.bpm, cnt : item.playback_count, time : Date.now() }) var list = $('<li>').append(elem); elem.append($('<img>').attr('src',item.artwork_url)); elem.append($('<h2>').text(item.title)); elem.append($('<p>').text(item.description)); $('ul[data-role=listview]').prepend(list); } $('ul[data-role=listview]').listview('refresh'); $.mobile.loading('hide'); }); }); // 최신 가요 목록을 라즈베리파이 서버로부터 전달받아 클라이언트에 출력한다. us.receive('top', function(data){ $('ul[data-role=listview]').empty(); for(var i in data.result){ var item = data.result[i]; var elem = $('<a>').attr({ 'data-transition' : 'pop', 'href' : '#music' }).addClass('music'); elem.data('item',item); var list = $('<li>').append(elem); elem.append($('<img>').attr('src',item.img)); elem.append($('<h2>').text(item.artist + ' ' + item.title)); elem.append($('<p>').text(item.album));
  • 241.
    $('ul[data-role=listview]').append(list); } $('ul[data-role=listview]').listview('refresh'); $.mobile.loading('hide'); }); // 음악 재생종료 시 호출된다. us.receive('finish', function(data){ $('a[name=resume]').show(); $('a[name=stop]').hide(); }); // 다시 재생 버튼 클릭 시 호출된다. $('body').on('click', 'a[name=resume]', function(){ us.send('resume'); }); // 정지 버튼 클릭 시 호출된다. $('body').on('click', 'a[name=stop]', function(){ us.send('stop'); }); // 다음 음악 버튼 클릭 시 호출된다. $('body').on('click', 'a[name=next]', function(){ us.send('next'); }); // 이전 음악 버튼 클릭 시 호출된다. $('body').on('click', 'a[name=prev]', function(){ us.send('prev'); }); // 볼륨 키우기 버튼 클릭 시 호출된다. $('body').on('click', 'a[name=vol_up]', function(){ us.send('volume', { volume : 'up' }); }); // 볼륨 낮추기 버튼 클릭 시 호출된다. $('body').on('click', 'a[name=vol_down]', function(){ us.send('volume', { volume : 'down' }); }); // 목록 반복 활성화 버튼 클릭 시 호출된다. $('a[name=loop_on]').click(function(){ $('a[name=loop_on]').hide(); $('a[name=loop_off]').show(); us.send('loop', { value : true }); }); // 목록 반복 비 활성화 버튼 클릭 시 호출된다. $('a[name=loop_off]').click(function(){ $('a[name=loop_off]').hide(); $('a[name=loop_on]').show(); us.send('loop', { value : false }); });
  • 242.
    // 현재 재생반복 활성화 버튼 클릭 시 호출된다 $('a[name=repeat_on]').click(function(){ $('a[name=repeat_on]').hide(); $('a[name=repeat_off]').show(); us.send('repeat', { value : true }); }); // 현재 재생 반복 비 활성화 버튼 클릭 시 호출된다. $('a[name=repeat_off]').click(function(){ $('a[name=repeat_off]').hide(); $('a[name=repeat_on]').show(); us.send('repeat', { value : false }); }); // 재생 목록을 클릭 시 음악을 재생하고, 재생 화면으로 이동한다. $('ul[data-role=listview]').on('click','a.music', function(){ var item = $(this).data('item'); $('#search').val(item.title).trigger($.Event( 'keypress', { keyCode: 13, which: 13 })); }); $(document).on('swipeleft','div[data-role=page]', function(){ if( $(".ui-panel").hasClass("ui-panel-open") == true ){ $("[data-role=panel]").panel("close"); } else { $.mobile.changePage('play.html', { transition: 'slide'}); } }); $(document).on('swiperight','div[data-role=page]', function(){ var page = $(':mobile- pagecontainer').pagecontainer('getActivePage')[0].id; if(page == 'play'){ $.mobile.changePage('index.html', { transition: 'slide', reverse: true }); } else { $( "#panel" ).panel( "open" ); } }); });
  • 243.
    [ 검색한 음악을재생하거나, 최신 가요 목록을 불러와 준다 ] Play 페이지는 실제 음악 재생을 보여주는 페이지이다. jQuery Knob 플러그인을 이용하여, 중앙의 원형 형태로 재생 진행률을 보여준다. 이전/다음 음악으로 재생하거나, 볼륨을 올리고 낮추는 역할, 음악을 재생하고 정지하는 기본적인 동작 UI 를 구성한다. [ play.html ] … <div data-role='main' class='ui-content'> <h2>Title</h2> <input class="knob" data-angleOffset="180" data- fgColor="rgba(18,255,255,0.5)" data-skin="tron" data-thickness=".2" data-min="0" data-max="100" value="100" data-displayInput="false" data-readOnly="true" data-enhance="false"/> <div class='ui-grid-d'> <div class='ui-block-a'> <a href='#' name='vol_up' data-role='button'>+</a> </div> <div class='ui-block-b'> <a href='#' name='vol_down' data-role='button'>-</a> </div> <div class='ui-block-c'> <a href='#' name='resume' style='display:none;' data-role='button'>▶</a> <a href='#' name='stop' data-role='button'>■</a>
  • 244.
    </div> <div class='ui-block-d'> <a href='#'name='prev' data-role='button'>◁</a> </div> <div class='ui-block-e'> <a href='#' name='next' data-role='button'>▷</a> </div> </div> </div> … Play 모듈은 음악 재생 정보를 표시해 주는 역할을 담당한다. Info 이벤트가 발생하면, 재생 음악 타이틀 정보를 화면에 표시하고, 앨범 이미지를 백 그라운드에 표시한다. 라즈베리파이로부터 재생 시간 정보를 받게 되면, 중앙의 원형 페이지를 채워가며 진행 상황을 시각적으로 보여준다. [ play.js ] // page 라는 id 명의 페이지가 활성화 될 때 호출된다. $(document).on('pageinit','#play', function(){ // 음악 제목과 배경 이미지 주소를 가져와서 화면에 표시한다. var img = sessionStorage.getItem('img'); var title = sessionStorage.getItem('title'); $('#play h2').text(title); $('#play').css('background-image', 'url("' + img + '")'); // 재생 정보를 라즈베리파이로부터 받아오는 경우 표시된다. us.receive('info', function(data){ sessionStorage.setItem('img', data.img); sessionStorage.setItem('title',data.title); $('#play h2').text(data.title); $('#play').css('background-image', 'url("' + data.img + '")'); }); // 재생 정보를 표시한다. us.receive('time', function(data){ $('.knob').val(Math.round(data.current * 100/ data.total)).trigger('change'); $('a[name=resume]').hide(); $('a[name=stop]').show(); }); var width = $(window).width() * 0.92; console.log(width); … }); CSS(Cascadig Style Sheet) 는 보여지는 화면을 다듬기 위해 사용된다. 음악 재생시 타이틀 명이 길면 화면이 넘어가는 문제가 있는데, h2 속성을 지정하여 넘어가는 문자는 “…”으로
  • 245.
    보여지도록 표시해 준다.Input 컴포넌트와 음악 검색/재생의 header 와 footer 를 반투명하게 지정하기 위해 rgb 컬러 값과 함께 alpha 값을 지정해 주도록 한다. 여기서 잠깐 !important 란? CSS 지정 시 !important 속성을 볼 수 있는데, CSS 는 가장 마지막에 정의된 값이 적용되게 된다. 하지만, !important 속성을 지정하면, 나중에 값이 적용되더라도 무시하게 된다. [ play.css ] #play h2 { white-space: nowrap; overflow: hidden; text-overflow: ellipsis; margin-bottom : -5px; } #music, #play { background-size : 100% 100%; } #music .ui-input-search { background-color : rgba(233,233,233,0.3) !important; } #play div[data-role=header], #play div[data-role=footer], #music div[data-role=header], #music div[data-role=footer]{ background-color : rgba(233,233,233,0.3) !important; border-color : rgba(221,221,221, 0.3) !important; } #play .ui-btn, #music .ui-btn { background-color : rgba(246,246,246,0.5) !important; border-color : rgba(221,221,221, 0.5) !important; }
  • 246.
    [ header 와footer 는 알파값 지정으로 인하여 반투명하게 보여지고,긴 문자열은 … 으로 표시되게 된다. ] Radio 페이지는 라디오 방송과 뉴스/날씨 정보를 확인할 수 있는 기능을 제공하는 페이지 이다. 라디오 목록의 경우, 실제 재생할 페이지의 주소를 name 속성에 포함하고 있어, 클릭 시 이 값을 라즈베리파이에 전달하여 재생할 수 있도록 구성되어 있다. 라디오 방송용으로 추가적으로 듣고 싶은 목록이 있다면, 이곳 페이지에 추가해 주면 된다. [ radio.html ] … <div data-role='main' class='ui-content'> <div class='ui-grid-b'> <div class='ui-block-a'> <a href='#' id='v_up' data-role='button'>+</a> </div> <div class='ui-block-b'> <a href='#' id='v_down' data-role='button'>-</a> </div> <div class='ui-block-c'> <a href='#' id='r_stop' data-role='button'>■</a> </div> </div> <div data-role='fieldcontain'> <ul data-role='listview'>
  • 247.
    <li data-role='list-divider'>News/Weather</li> <li><a id='weather'>TodayWeather</a></li> <li><a id='news'>Today News</a></li> <li data-role='list-divider'>Radio</li> <li><a class='radio' name='mms://210.105.237.100/mbcam'>MBC FM</a></li> <li><a class='radio' name='mms://114.108.140.39/magicfm_live'>SBS POWER FM</a></li> <li><a class='radio' name='mms://live.kbs.gscdn.com/world_rki3'>KBS WORLD</a></li> <li><a class='radio' name='http://89.16.185.174:8003/stream'>MUSIC Channel</a></li> … </ul> </div> </div> … Radio 모듈은 Radio 페이지에서 클릭 발생 시 이벤트를 라즈베리파이에 전달하는 역할을 한다. 날씨/뉴스와 볼륨 조절이벤트는 곧바로 호출하는 구조를 가지고 있다. 라디오 목록의 경우 name 속성으로부터 재생 링크를 전달 받아 라즈베리파이에 전달하여 재생하는 역할을 하게 된다. [ radio.js ] // radio 라는 id 명의 페이지가 활성화 될 때 호출된다. $(document).on('pageinit','#radio', function(){ // 날씨 버튼 클릭시 라즈베리파이 서버에 weather 이벤트를 전달한다. $('#weather').click(function(){ us.send('weather'); }); // 뉴스 버튼 클릭시 라즈베리파이 서버에 news 이벤트를 전달한다. $('#news').click(function(){ us.send('news'); }); // 라디오 정보 버튼을 클릭시 재생할 인터넷 라디오 정보를 전달한다. $('a.radio').click(function(){ var radio = $(this).attr('name'); var name = $(this).text(); us.send('radio', { radio : radio, name : name}); }); // 정지 버튼 클릭 시 호출된다. $('#r_stop').click(function(){ us.send('stop'); }); // 볼륨 올리기 버튼 클릭 시 호출된다. $('#v_up').click(function(){ us.send('volume', { volume : 'up' }); }); // 볼륨 낮추기 버튼 클릭 시 호출된다. $('#v_down').click(function(){
  • 248.
    us.send('volume', { volume: 'down' }); }); }); Talk 페이지는 오디오와 대화를 나눌 수 있는 공간이다. 메시지를 입력할 input 컴포넌트를 배치하고, 메시지가 배치될 영역을 구성하도록 한다. [ talk.html ] … <div data-role='main' class='ui-content'> <input id='tell' type="text" placeholder='Input message.' /> <div id='msgs'> </div> </div> … Talk 모듈은 메시지를 실질적으로 전달하고, 받은 메시지를 화면에 출력하는 역할을 한다. Receive 함수를 이용하여 talk 이벤트가 발생시, 받아진 메시지를 우측 정렬로 화면에 보여준다. 사용자가 대화할 메시지를 입력후 엔터/이동키를 누르게 되면, 사용자가 입력한 메시지를 화면에 왼쪽 정렬로 보여주고, 입력한 메시지를 라즈베리파이에 전달한다. 대화를 주고받는 시간을 표시하기 위해 현재 시간을 표시하는 now 함수를 구현한다. [ talk.js ] // talk 라는 id 명의 페이지가 활성화 될 때 호출된다. $(document).on('pageinit','#talk', function(){ // talk 이벤트를 수신하였을 때 호출된다. us.receive('talk', function(data){ // 받은 메시지를 html 객체로 생성한다. var $msg = $('<div/>').css({ width : '80%', float : 'right', 'text-align' : 'right'}) .addClass('ui-corner-all custom-corners'); var $title = $('<div/>').addClass('ui-bar ui-bar-b') .append($('<h3/>').text('piAu')); var $body = $('<div/>').addClass('ui-body ui-body- b').append($('<p/>') .text(data)); $msg.append($title); $msg.append($body); $msg.append($('<p />')); // 받은 메시지를 출력한다. $('#msgs').prepend($msg); }); // 입력 메시지를 화면에 표시하고, 라즈베리파이 서버에 전달한다. $('#tell').keypress(function(e){ if (e.which != 13) { return;
  • 249.
    } // 현재 시간을구한다. var currentTime = new Date(); var month = currentTime.getMonth() + 1; var day = currentTime.getDate(); var year = currentTime.getFullYear(); var seconds = currentTime.getSeconds(); var minutes = currentTime.getMinutes(); var hour = currentTime.getHours(); var time = toChar(hour) + ':' + toChar(minutes) + ':' + toChar(seconds); var key = year + '-' + toChar(month) + '-' + toChar(day) + ' ' + time; var data = $('#tell').val(); // 입력한 정보를 화면에 표시하기 위한 html 객체를 만든다. var $msg = $('<div/>').css({ width : '80%', float : 'left', 'text-align' : 'right'}) .addClass('ui-corner-all custom-corners'); var $title = $('<div/>').addClass('ui-bar ui-bar-a') .append($('<h3/>').text('User')); var $body = $('<div/>').addClass('ui-body ui-body- a').append($('<p/>') .text(data)); $msg.append($title); $msg.append($body); $msg.append($('<p />')); // 입력한 메시지를 화면에 표시한다. $('#msgs').prepend($msg); // 입력한 메시지를 라즈베리파이 서버에 전달한다. us.send('talk', data ); $('#tell').val(''); }); // 현재 시간을 구한다. var now = function(){ var currentTime = new Date(); var month = currentTime.getMonth() + 1; var day = currentTime.getDate(); var year = currentTime.getFullYear(); $('#now').text(year + '-' + toChar(month) + '-' + toChar(day)); refresh(); }; // 두자리 수 미만인 경우 앞에 0을 붙여준다. var toChar = function(val){ if(val < 10){ return '0' + val; } else { return val; }
  • 250.
    } }); Alarm 페이지는 알람설정을 하는 페이지이다. 요일을 체크박스로 배치하여, 다중으로 선택할 수 있도록 배치하고, 시간과 분 표시는 0~23, 0~59 까지 일일히 표시하기에는 양이 많다. select 영역만 지정하고, 실제 표시는 alarm 모듈로 구현해 주도록 한다. [ alarm.html ] … <div data-role='main' class='ui-content'> <fieldset data-role='controlgroup'> <legend>Choice day</legend> <input type='checkbox' name'day' id='cb0' value='0'/> <label for='cb0'>Sun</label> <input type='checkbox' name'day' id='cb1' value='1'/> <label for='cb1'>Mon</label> <input type='checkbox' name'day' id='cb2' value='2'/> <label for='cb2'>Tue</label> <input type='checkbox' name'day' id='cb3' value='3'/> … </fieldset> <div class='ui-grid-a'> <div class='ui-block-a'> <label for='alarm_hour'>Hour</label> <select id='alarm_hour'></select> </div> <div class='ui-block-b'> <label for='alarm_minute'>Minute</label> <select id='alarm_minute'></select> </div> </div> <a href='#' id='setAlarm' data-role='button'>Confirm</a> </div> … Alarm 모듈은 알람 설정 정보를 가져오고, 설정된 알람 정보를 전달하는 역할을 한다. 기존에 설정된 알람 정보를 가져오기 위해 getAlarm 이벤트로 라즈베리파이에 전달하고, 0~23 까지 시간과 0~59 까지 분을 표시한다. getAlarm 이벤트를 전달 받으면, 받아진 값을 이용하여 체크박스는 선택 상태로 바꾸고, select 메뉴에 시간과 분을 설정해 준다. setAlarm 클릭시에는 선택된 체크박스로부터 날짜를 가져오고, 시간과 분 정보를 가져와서 라즈베리파이에 setAlarm 함수로 값을 전달한다. [ alarm.js ] // alarm 이라는 id 명의 페이지가 활성화 될 때 호출된다. $(document).on('pageinit','#alarm', function(){ // 알람 정보를 라즈베리파이 서버에 요청한다. us.send('getAlarm'); // 시간, 분 정보를 초기화 한다. $('#alarm_hour').empty(); $('#alarm_minute').empty(); // 시간, 분 정보를 화면에 표시한다. for(var i = 0 ; i < 24 ; i++){
  • 251.
    $('#alarm_hour').append($('<option>').val(i).text(i)); } for(var i =0 ; i < 60 ; i++){ $('#alarm_minute').append($('<option>').val(i).text(i)); } // 설정한 알람 정보를 라즈베리파이 서버로부터 받았을 때 호출된다. us.receive('getAlarm', function(data){ var alarm = JSON.parse(data.result); console.log(alarm); // 설정된 알람정보와 일치하는 날짜가 있다면 화면에 표시해 준다. for(var i = 0; i < alarm.days.length ;i++){ var day = alarm.days[i]; $('#cb' + day).prop('checked', true).checkboxradio('refresh'); } // 설정된 알람정보와 일치하는 시간과 분을 표시해 준다. $('#alarm_hour').val(alarm.hour).selectmenu('refresh');; $('#alarm_minute').val(alarm.minute).selectmenu('refresh'); }); // 알람 설정 버튼 클릭 시 호출된다. $('#setAlarm').click(function(){ var days = []; // 선택된 요일 정보를 가져온다. $.when($("input[type=checkbox]:checked").each(function() { // 선택한 요일 정보를 days 배열에 보관한다. days.push($(this).val()); })).then(function(){ // 선택한 요일과 시/분을 data 객체로 생성한다. var data = { days : days, hour : $('#alarm_hour').val(), minute : $('#alarm_minute').val() }; // 선택한 알람 설정 정보를 라즈베리파이 서버에 전달한다. us.send('setAlarm', { param : JSON.stringify(data)}); }); }); });
  • 252.
    [오디오, 뉴스/날씨 정보를듣거나 오디오와 대화를 나누는 기능을 제공한다 ] Message 페이지는 메시지를 입력하여 오디오 상에서 TTS 로 읽어줄 수 있도록 텍스트 입력창을 제공하는 페이지이다. Textarea 컴포넌트로 사용자가 문자를 입력할 공간을 배치하고, 메시지가 전달될 수 있도록 send 버튼을 배치한다. [ message.html ] … <div data-role='main' class='ui-content'> <textarea id='msg' rows='5' data-autogrow='false' placeholder='Input message'></textarea> <a href='#' id='send' data-role='button'>Send</a> </div> …. Message 모듈은 메시지를 실제 오디오로 전달하여, TTS 로 읽어질 수 있도록 한다. Send 버튼을 클릭하면, textarea 에 입력된 메시지를 전달한다. [ message.js ] // message 라는 id 명의 페이지가 활성화 될 때 호출된다. $(document).on('pageinit','#message', function(){
  • 253.
    // send 버튼클릭 시 호출된다. $('#send').click(function(){ // 입력한 메시지를 라즈베리파이 서버에 전달한다. us.send('message', $('#msg').val()); // 전달 된 메시지는 화면에서 지운다. $('#msg').val(''); }); }); Option 페이지는 라즈베리파이 오디오의 연결 설정 및 밝기를 설정하는 기능을 제공하는 페이지이다. 7 Segment 와 LED 밝기를 조절할 수 있도록 두개의 range 타입의 input 컴포넌트를 배치한다. 시리얼 번호를 입력하는 용도로 input 컴포넌트를 배치하고, 업데이트 및 재부팅이 가능하도록 update 버튼을 배치한다. [ option.html ] … <div data-role='main' class='ui-content'> <label for='fg_brt'>Foreground Brightness</label> <input id='fg_brt' type="range" min="0" max="15" value="16" /> <label for='bg_brt'>Background Brightness</label> <input id='bg_brt' type="range" min="0" max="240" value="240" /> <label for='serial'>Serial</label> <input id='serial' type="text" /> <label for='update'>piAu Software Update</label> <a href='#' id='update' data-role='button'>Update piAu (Reboot)</a> </div> … Option 모듈은 실제로 밝기 설정한 값을 전달하고, 시리얼 포트 정보를 설정하는 기능을 제공한다. Ragen 입력 바를 사용자가 변경하면 변경시에 발생하는 값을 라즈베리파이 오디오에 전달하여 7 세그먼트와 LED 의 밝기를 조절하게 된다. 시리얼 번호 입력창을 클릭하면, 사용자가 시리얼 번호를 변경할 수 있도록 입력창이 보여진다. 입력받은 시리얼 번호로 Circulus 플랫폼에 다시 접속한다. [ option.js ] // option 이라는 id 명의 페이지가 활성화 될 때 호출된다. $(document).on('pageinit','#option', function(){ // 전경 밝기 슬라이더 변경 시 호출된다. $('#fg_brt').change(function(){ us.send('setFg',{ fg : $(this).val()}); }); // 배경 밝기 슬라이더 변경 시 호출된다. $('#bg_brt').change(function(){ us.send('setBg',{ bg : $(this).val()}); }); // 업데이트 버튼 클릭 시 호출된다. $('#update').click(function(){
  • 254.
    us.send('update'); }); // 설정된 라즈베리파이일련번호를 가져온다. var serial = localStorage.getItem('serial'); // 설정된 라즈베리파이 일련번호가 없다면, 입력창으로 입력 받는다. if(serial == null || serial == 'null'){ serial = prompt("Input piAu's serial"); localStorage.setItem('serial', serial); us.init(serial); } // 저장된 일련번호를 화면에 표시해 준다. $('#serial').val(serial); // 일련번호 입력창 클릭시 다시 일련번호를 입력받아 접속을 시도한다. $('#serial').click(function(){ serial = prompt("Input piAu's serial"); $('#serial').val(serial); localStorage.setItem('serial', serial); us.init(serial); }); });
  • 255.
    [ 메시지를 보내면오디오가 TTS 로 읽어주게 된다. 상황에 따라 전경/배경 색상의 밝기를 조절할 수 있다. ] 라즈베리파이 IoT 오디오 완성 Circulus 를 이용하여, 라즈베리파이와 모바일 웹 어플리케이션을 개발하였다. 별도의 클라우드 서비스나 복잡한 IoT 기술을 사용하지 않고, Circulus 에서 개발한 모바일 웹의 URL 로 언제 어디서나 사용자가 만든 라즈베리파이 오디오에 접근하여 사용할 수 있다. 라즈베리파이 IoT 오디오를 무선 인터넷이 되는 곳에 배치하고, 실제로 집 밖에서 제대로 동작이 되는지 실험해 보도록 하자. 만일 사용자가 사랑하는 연인 혹은 가족, 또는 고마운 친구가 있다면, 직접 만든 라즈베리파이 IoT 오디오를 선물하자. 본인도 가끔 음악 선물이나 메시지를 보내 보는 것도 좋을 것이다. [ 라즈베리파이 IoT 오디오 완성! 실제로 집에 두고 사용해 보도록 하자. ]
  • 256.
    10/마무리 마지막 장을 마무리하며 라즈베리파이를이용하여 IoT 오디오를 완성하는 것으로, 이 책의 과정이 마무리 되었다. 하드웨어와는 친숙하지 않은 자바스크립트와 운영체제가 탑재되는 라즈베리파이의 조합으로 우리는 스마트 폰으로 LED 제어부터 오디오까지 만들어 볼 수 있었다. 단순히 LED 를 껐다 켜는 수준이 아닌 실제로 집에서 사용할 수 있는 IoT 아이디어를 구현하기에 다루는 범위가 방대한 반면, 각각의 내용에 대해서 모든 내용을 다루지 못하였다. 이 책을 기반으로 자신의 아이디어를 실제로 구현해 보면서, 부족한 부분이나 모르는 부분은 관련 내용을 보충해야 할 것이다. 지속적으로 관련된 내용을 Circulus 를 통해 공유할 수 있도록 하겠다. 이제는 여러분도 Thinker 가 아닌 Maker 가 되었다. 앞으로는 타인이 공유한 다양한 프로젝트를 살펴보고, 자신만의 독창적인 아이디어를 하드웨어, 소프트웨어, 서비스 구분 없이 구현해 보는 것이다. 장담하건데, 여러분이 생각하는 비슷한 아이디어는 많이 있겠지만, 똑 같은 아이디어는 전 세계에 어디에도 없을 것이다. 생각하는게 없다? 그렇다면 만들고 공유하는 것이다. 메이커들이 공유하는 메이커 페어나 다양한 행사들에서 뵙길 기대하겠다. CIRCULUS Circulus 는 2013 년 Thinker to Maker, 즉 누구나 아이디어를 현실로 만들 수 있게 하자는 목적의 Social Code Learning 기반 커뮤니티로 시작되었다. 하지만, S/W 적인 부분만으로는 한계[가 있어, 물리적인 H/W 도 S/W 를 통해 만들 수 있게 하면 좋겠다라는 생각을 했고, 그 생각의 종착점은 로봇이라는 생각이 들었다. 2016 년 알파고 이슈와 더불어 가정용 반려 로봇으로 창업을 한 스타트 업이다. 앞으로도 초심을 잃지 않고, 누구나 로봇을 활용하고 자신의 아이디어를 현실로 만들고 공유할 수 있는 세상을 만들고자 한다. 의문사항은 아래의 커뮤니티에 남겨주면, 피드백을 줄수 있도록 하겠다. 공식 사이트(개발툴) - http://www.circul.us 관련 콘텐츠 – http://contens.circul.us 관련 영상 – http://video.circul.us Facebook – http://group.circul.us Café – http://cafe.circul.us Shop – http://shop.circul.us 메일 – circulus@circul.us