'완료된 프로젝트'에 해당되는 글 6건

약 1년 만에(?) 블로그 포스팅을 하는데... 전의 내용에 이어서 최종 결과까지 올리자면 이렇다.

우선 제작 사진으로,

 

이 것이 실제로 시연에 사용된 로봇의 모습이다. 전반적인 뼈대는 EV3를 사용했고, 그 위에 3개의 USB 마이크가 연결된 라즈베리 파이를 얹었다. 라즈베리 파이의 전원으로 EV3의 USB 포트를 사용했고, 서로 블루투스를 이용해 통신을 한다.


라즈베리 파이는 3으로, 내장된 블루투스가 있고 Raspbian Jessie를 사용했다. 프로그램은 systemd에 데몬으로 등록해두어 시스템 시작 시에 같이 시작되도록 했고, ALSA로 마이크로부터 값을 읽어서 계산해서 연결된 EV3로 값을 전송한다.


EV3는 통신으로 방향을 받아 모터를 제어하며, 정면에 설치된 초음파 센서를 이용해 앞 대상까지의 거리를 측정해서 정지 및 장애물 회피 기능도 수행한다. 앞의 물체와의 거리가 일정 이상 가까우면, 마이크로 인식한 주인의 방향이 정면이라면 (앞의 물체가 주인으로 인식해서) 정지하고, 아니라면 장애물이라고 인식해 크게 방향을 전환한다.


아래는 작동하는 모습을 촬영한 영상이다. 약 2미터 정도의 거리까지는 위치를 인식해서 방향을 찾아온다.



'완료된 프로젝트 > Homing Robot' 카테고리의 다른 글

완성품 & 작동 영상  (0) 2017.01.07
프로그램 제작기  (2) 2016.12.10
로봇 따라가기의 기본 원리  (3) 2016.12.09
프로젝트 개요  (5) 2016.12.08
블로그 이미지

예비컴공돌이

각종 프로젝트 진행중! 생각날때마다 블로그 업데이트합니다.

이번 글에서는 앞선 글의 내용들을 이용해 어떻게 프로그램을 짰는지 설명한다.


프로그램의 구조는 아래와 같다.

  1. 백그라운드에서 3개의 마이크에서 음성 신호를 받는대로 고속 푸리에 변환(FFT)을 수행해 특정 주파수 대역의 크기(Magnitude)를 구해 방향을 찾는다
  2. 일정 주기로 1의 결과를 읽어와, EV3가 수신할 수 있는 데이터 포맷으로 인코딩해 전송한다.
  3. EV3 로봇은 그 데이터를 수신해 방향을 향해 회전하고 이동한다.


1, 2, 3번 프로그램은 각각 별개로 실행되고, 서로 데이터를 주고받는 방법을 가지게 한다.



1번 프로그램


우선 1번 프로그램은 시스템 의존적인 부분이 대부분이고, 또 그것들은 C 라이브러리로 제공되기에 C언어로 작성했다.


마이크에서 값을 받아오는 부분은 리눅스 환경의 ALSA를 이용해 녹음하는 예제(링크)를 이용했다. 복잡한 기능 없이 소리만 읽어오면 되므로 위의 간단한 코드로 큰 문제 없이 값을 얻어올 수 있었다.


라즈베리 파이에서 FFT를 수행하기 위해 검색한 결과, 내장된 GPU를 이용해 연산을 수행하는 라이브러리가 공개되어 있었다.(라즈베리 파이 블로그, GPU_FFT 프로젝트 페이지) 이 라이브러리를 이용해 빠르게 계산할 수 있고, 여러 개의 작업을 모아 한번에 수행(batch)하는 기능도 있어 3개의 마이크를 이용하는 데 적합하다고 생각했다.


마지막으로, 계속 연산을 수행하면서도 2번 프로그램과 통신을 하기 위해 pipe와 fork를 사용했다.

1번 프로그램은 실행되면 pipe를 열고 fork를 해서 자식 프로세스를 만든다. 부모 프로세스는 stdin에서 입력을 대기하고, 입력이 들어온 경우 자식 프로세스에 SIGUSR1을 발생시킨 뒤 pipe read를 대기한다.

자식 프로세스는 계속해서 마이크에서 값을 읽어와 연산을 수행하고, SIGUSR1을 받았을 때 pipe로 현재 각도를 전송(write)한다.

그러면 부모 프로세스는 pipe에서 각도를 읽어오게 되고, 그 각도를 stdout으로 출력한다.



2번 프로그램


2번 프로그램은 파이썬으로 되어있는데, 이 프로그램이 맡는 역할은 이렇다.


우선 블루투스 SPP 프로파일이 사용되도록 설정된 라즈베리 파이에서(참고),

이 프로그램은 우선 subprocess 모듈로 "rfcomm watch hci0" 프로세스를 실행해 EV3가 라즈베리 파이에 연결되어  "/dev/rfcomm0" 포트가 생기기를 기다린다.

포트가 생겼다면, serial 모듈로 /dev/rfcomm0 과 연결해 EV3와의 통신 연결을 만든다.


그 후, subprocess로 stdin과 stdout을 PIPE시킨 1번 프로그램을 실행한다.

다음으로 1초 간격으로 루프를 돌면서, 1번 프로그램의 stdin으로 데이터 하나를 입력해서, stdout으로 출력되는 각도를 가져온다.

그 각도(-180 ~ 180)을 EV3 모터 입력으로 들어갈 방향의 범위(-100 ~ 100)로 변환한 뒤, struct 모듈을 이용해 EV3 메시지 포맷에 맞는 형태로 감싸서 serial로 전송한다.(참고, 이 글에서는 EV3로부터의 수신이지만, EV3로의 송신도 가능함을 확인했다. EV3로의 송신 관련해서는 자료가 전무해 힘들었다.)


만약 1번 프로그램이 종료되거나 시리얼 포트로 자료가 전송되지 않았다면, 1번 프로그램에 문제가 발생했거나 EV3와의 연결이 종료된 것으로 판단, 반복문을 빠져나가 /dev/rfcomm0 포트 대기 부분으로 돌아간다.



3번 프로그램


마지막으로 3번 EV3 프로그램은 복잡한 동작은 없어 레고 EV3 블록 프로그래밍 툴로 제작했다.

라즈베리 파이와 블루투스 연결을 하고 블루투스로 각도 정보를 읽어오는 부분, 저장된 각도 정보대로 모터를 조종해 이동하는 부분으로 구성되어있다.



결론


2번 프로그램의 EV3로의 송신 관련해서는 나중에 또 글을 올릴 예정이다. 구글링을 해봐도 자료가 라즈베리 파이에서 수신하는 것 하나만 나와서, 혹시라도 같은 주제로 헤맬 사람이 있을지 몰라 정보를 공유하려고 한다.

'완료된 프로젝트 > Homing Robot' 카테고리의 다른 글

완성품 & 작동 영상  (0) 2017.01.07
프로그램 제작기  (2) 2016.12.10
로봇 따라가기의 기본 원리  (3) 2016.12.09
프로젝트 개요  (5) 2016.12.08
블로그 이미지

예비컴공돌이

각종 프로젝트 진행중! 생각날때마다 블로그 업데이트합니다.

(이전 글) 2016/12/08 - [진행중인 프로젝트/Homing Robot] - 프로젝트 개요


지난 포스트에서 프로그램의 흐름을 말했는데, 여기서는 좀 더 구체적으로 방법을 설명하려고 한다.


우선 소리의 크기는 거리가 멀어질 수록 작아진다는 점에서 착안해, 마이크에 인식되는 소리의 크기는 그 마이크의 주인으로부터의 거리와 비례한다고 가정했다. (여기서 소리의 반사, 2개 이상의 음원, 거리에 따른 역자승 법칙 등은 고려하지 않았다.)


이 때 로봇에 마이크를 3개 장착하는데, 그 마이크들은 서로 120˚의 각도를 두고 각각 전방 왼쪽, 전방 오른쪽, 후방에 배치한다.

그러면 각 위치로부터 들리는 소리의 크기를 마이크 방향 벡터와 곱해서, 그 합이 음원의 위치일 것이라고 생각하고, 식을 계산했다.


이것을 구현할 때, 단순히 소리의 크기로만 계산하면 다른 소리나 잡음에 취약하기도 하고, 주인만을 찾아가기가 힘들 것이라고 생각했다.

그래서 주인은 특정한 신호를 내고, 로봇은 그 특정한 신호만 인식하기 위해 푸리에 변환을 사용했다. 푸리에 변환으로 일정한 주기의 소리를 주파수 대역별 음량으로 변환해서, 그 중 원하는 주파수 대역만 골라 사용하는 것이다.

이걸 이용해 주인, 집, 다른 사람 등 서로 다른 주파수 대역을 사용해 대상을 바꾸는 것도 생각했는데 미처 구현하지는 못하고 주인의 주파수만 따라오도록 했다.


정리하면, 주인이 특정 주파수를 내는 비콘(스마트폰의 사인파 소리 생성기 앱)을 들고있으며, 로봇은 마이크별로 다르게 들려올 그 주파수 대역의 음량을 이용해 방향을 찾는다.


여기서 주인이 내는 주파수는 사람에게 잘 안들리는, 들려도 심하게 거슬리지는 않을 18000Hz로 사용했었는데, 실험해본 결과 마이크 감도 문제인지 방향을 잘 잡지 못해서 15000Hz로 내리게 되었다.

'완료된 프로젝트 > Homing Robot' 카테고리의 다른 글

완성품 & 작동 영상  (0) 2017.01.07
프로그램 제작기  (2) 2016.12.10
로봇 따라가기의 기본 원리  (3) 2016.12.09
프로젝트 개요  (5) 2016.12.08
블로그 이미지

예비컴공돌이

각종 프로젝트 진행중! 생각날때마다 블로그 업데이트합니다.

이 프로젝트는 「신호및시스템」 과목에서 팀 프로젝트로 진행했던 작품이다.

팀 프로젝트 과제 자체는 레고 마인드스톰 EV3, 드론, 라즈베리파이 등을 이용해 자유롭게 제작하는 것이었고, 우리 프로젝트의 주제는 "애완 로봇 만들기"로, 목표는 "특정 신호를 찾아가는 로봇을 제작"으로 설정했다.


주제의 구상 과정은 이렇다.

어쩌다가 팀에서 강아지 이야기가 나오고, 그걸 중심으로 강아지에 필요한 기능들을 토론해서, 주인 명령 듣기, 주인 따라가기, 쓰다듬기 등 행동을 하면 반응하기와 같은 내용이 도출됐다.

이러한 주제 중 가장 프로젝트로서 가치있을 내용인 '주인을 따라가는 행동'을 주된 목적으로 삼았고 쓰다듬기, 꼬리 만지기에 대한 간단한 동작을 추가했으며 주인 명령 듣기는 구현을 하려 했으나 미처 완성하지 못했다.

주인을 따라가는 법에 대해, 동물은 귀가 2개라는 점에서 착안해서 2개의 마이크로 방향을 찾기로 생각했다. 그러나 생각해보니, 2개만으로는 앞/뒤를 특정할 수 없어서 일단 한 방향으로 진행해보고 멀어진다 싶으면 반대로 진행해야 함을 알 수 있었고, 따라서 마이크를 하나 더 늘려 3개를 사용하기로 했다. (삼각측량에서 평면 상의 한 점을 특정하려면 3개의 점으로부터의 정보가 필요하다는 상식이 구상에 도움이 됐다.)


주인을 찾아가는 방법은 다음과 같다.

  • 라즈베리 파이에 마이크 3개를 장착한다.
  • 라즈베리 파이에 탑재해 실행할 프로그램에서 마이크 간의 소리 크기 차이로 방향을 추정하고, 그 추정된 방향을 레고로봇에 알려줘 그 방향으로 조금씩 움직인다.
  • 방향 추정과 이동을 동시에, 반복적으로 수행해서 결과적으로 소리원(주인)에게 다가간다.


자세한 기술적, 프로그램적 내용은 다음 포스트에서 이어서 올릴 예정이다.

'완료된 프로젝트 > Homing Robot' 카테고리의 다른 글

완성품 & 작동 영상  (0) 2017.01.07
프로그램 제작기  (2) 2016.12.10
로봇 따라가기의 기본 원리  (3) 2016.12.09
프로젝트 개요  (5) 2016.12.08
블로그 이미지

예비컴공돌이

각종 프로젝트 진행중! 생각날때마다 블로그 업데이트합니다.

2013년도에 제작했던 학교 앱이다.

배재고등학교 컴퓨터 동아리 SPACE로 활동할 때 제작해서, 그 해 진행됐던 교내 동아리 발표회 대상을 받았던 것이기도 하다.


내가 처음으로 공식적으로 배포했던 앱이다. 초기 버전에서는 서버 없이 자체적으로 데이터를 받아와 보여줬고, 이후 버전을 올리면서 앱 서버를 두게 되었다.


서버는 자바 서블릿으로 작성되어, HTTP를 통해 JSON 데이터를 주고받게 했다. 그리고 시간표, 식단표 등 데이터를 저장할 때에는 MySQL 데이터베이스를 사용했다.


식단표

식단표 데이터는 NEIS 홈페이지를 파싱해서 정보를 가져왔다.

처음에는 앱의 자체 서버가 없어서 앱 내에서 홈페이지 정보를 파싱했는데, 그 시간이 꽤 오래 걸려서 불편했고 트래픽을 너무 많이 발생시키진 않을까 하는 우려도 있었다.


그래서 나중에 자체 서버를 만들고 난 뒤에는, 서버에서 주기적으로 데이터를 파싱해서 가지고 있도록 했다.

앱이 서버에 접속해서 특정 달의 식단표 타임스탬프를 서버에게 전송하면, 만약 변동이 있으면 새 식단표와 타임스탬프를 보내주고 아니라면 OK를 보내주는 식이다.


시간표

시간표는 교내 선생님별, 학급별 시간표를 관리하던 서비스에서 파싱해왔다.

다만 처음에는 로딩 시간을 이유로 앱에서 파싱하면 저장해뒀는데, 파싱한 때가 마침 변동된 시간표이거나, 파싱 이후 변동된 임시 시간표가 있을 경우 시간표 정보가 정확하지 않았다.

따라서 식단표와 마찬가지로 자체 서버를 운용한 이후는 서버측에서 모든 파싱을 담당했다. 공지사항 개발 이후에 변동된 시간표 GCM 알림 기능도 구상했었지만...


지하철 시간표

1.6버전으로 올리면서 추가했던 기능이다. 공공데이터포털(https://www.data.go.kr) 지하철 시간표 API를 사용했다. 처음으로 외부 서버에서 제공하는 API를 사용해볼 수 있던 기회였다.


공지사항

이후 버전 2.0부터는 공지사항 기능도 추가했는데, 그때부터는 사용자 권한 기능도 추가해서 '선생님' 권한이 있는 사용자면 글쓰기 버튼이 나타나고 공지를 올릴 수 있도록 했다. 공지에는 수신 대상 설정도 있고 수신 대상에 자신이 포함되면 GCM으로 알림이 오도록 야심차게 설계하고 제작했던 기능이다.

... 다만 문제는 그 때는 2014년 초 내가 막 고3이 되었을 때이고 그 이후로 홍보와 앱 관리를 잘 하지 못했다는 점. 이 때 제대로 활성화를 시키지 못했던게 아쉽다.



추억의 구글 플레이 스토어 다운로드 화면, 이때는 초기에 서버가 없을 적 버전이었다.



식단표, 왼쪽 버튼을 누르면 새로 불러오며, 오른쪽 버튼을 누르면 날짜를 변경할 수 있다.

NEIS에 식단표 데이터가 있기만 하면 몇달~몇년 전 식단표도 확인할 수 있었다.



일간 시간표, 위의 왼쪽 버튼을 누르면 줌아웃되면서 일주일 전체 시간표가 나타난다.

이 앱을 만들던 당시는 안드로이드 2.1 버전을 쓰는 사람이 아직 많아서 뷰 애니메이션을 넣기가 까다로웠던 기억이 있다.


버전 1.6부터 추가된 지하철 시간표 기능, 옆으로 넘기면 하루 시간표도 확인할 수 있다.


이후 버전에 추가된 공지사항 기능, 공지사항을 올리면 GCM으로 알림이 왔다.



학교에서 운영하던 방과후 학교 수강, 공지 사이트도 접속할 수 있었다. 소소하게 보안코드 자동 입력 기능(...)은 덤

블로그 이미지

예비컴공돌이

각종 프로젝트 진행중! 생각날때마다 블로그 업데이트합니다.

Node.js용으로 하루 정도 시간 내서 만들어봤던 프로그램이다.


전체 소스 코드는 https://github.com/WKBae/Node-Naver-Webtoon-Downloader에 있고, 클론해서 npm install을 실행해서 의존 파일들을 받아야 실행된다.


사용법은 "node download.js <네이버 웹툰 ID>"로, 예를 들어 조석 작가님의 마음의소리는 웹툰 주소가 "http://comic.naver.com/webtoon/list.nhn?titleId=20853&weekday=tue"이고, 다운받기 위해서는 "node download.js 20853"라고 하면 된다. (회차 범위 지정은 아직 안넣었으니 저 많은 회차를 전부 다운로드하지는 말기를.. 약 300회 가량 업로드된 웹툰은 1GB정도의 공간을 차지하니 남은 공간도 잘 봐야 한다.)


그리고 웹툰을 다운로드 받고자 한다면 무선랜보다는 유선랜에 연결된 상태에서 하는 것이 좋다. 회차 당 여러 이미지들, 여러 회차(최대 10회까지)를 동시에 받으니 인터넷이 좋지 않다면 네트워크 오류를 뿜으며 종료된다. 다운로드 받는 동안은 인터넷이 매우 느려지니 공용 네트워크에서도 사용하지 말자.


이 프로그램은 기능 테스트이며, 개인적인 사용에 한합니다. 다운로드 받은 웹툰의 배포, 웹툰과 프로그램의 영리적인 사용은 엄격히 금지합니다.



Node.js가 지원하는 자바스크립트의 새 기능들을 많이 써보기도 했고, 기타 라이브러리나 HTML 파싱, HTTP 쿠키 관련해서도 많이 건드려봤다.

특별히 쓰인 코드들은 다음과 같다.


1. 람다 식


특별히 어디랄 데 없이 광범위하게 사용했다. 콜백 함수를 넘겨줄 때, 이름을 굳이 지어주지 않아도 될 함수들은 전부 람다 식으로 작성했다.


2. async.js 함수들


주로 콜백으로 이어지는 자바스크립트 I/O 특성상, 이런 툴을 쓰니 이어지는 작업이 더 간단해졌다.

특히 주 작업 함수에서 사용한 async.waterfall() 함수는 순차적인 작업을 수행할 때 콜백 지옥을 피할 수 있게 해줬으며, 다운로드 작업 시 사용한 async.parallel()과 async.parallelLimit() 함수로 동시 다운로드가 되게 해서 속도를 높일 수 있었다.


3. Generator function, Spread operator


download.js 60번째 줄

function* webtoonTaskGenerator(titleId, last) { ... for(let i = 1; i <= last; i++) { yield function webtoonGetter(callback) { ...

download.js 390번째 줄

async.parallelLimit([...webtoonTaskGenerator(titleId, toon.last)], 10, (err, results) => {

download.js 170번째 줄

function* imageTaskGenerator(items, referer, dir) { for(let i = 0; i < items.length; i++) { let elem = items[i]; yield (callback) => { ...

download.js 116번째 줄

async.parallel([...imageTaskGenerator($images, url, path.resolve(OUTPUT_DIR, titleId, i+""))], callback);

async,js에게 넘겨줄 작업을 생성하는 함수이다. async.js에서도 iterable을 지원하는데, 내가 쓸 땐 뭔가 문제가 생겨서 spread operator을 쓴 것으로 기억한다.


4. 네이버 로그인 세션 쿠키 입력


download.js 129번째 줄

} else if(res.statusCode == 302) { if(!doingAuthInput) { doingAuthInput = true; console.log("Failed to load toon#" + i + ", trying with user auth info can help."); rl.question("NID_AUT: ", (aut) => { rl.question("NID_SES: ", (ses) => { jar.setCookie(request.cookie("NID_AUT=" + aut), "http://comic.naver.com"); jar.setCookie(request.cookie("NID_SES=" + ses), "http://comic.naver.com"); doingAuthInput = false; webtoonGetter(callback); // Retry with session information }) }); } else { //console.log("Failed to load toon#" + i + ". Waiting until input is done..."); (function waitInputDone() { if(doingAuthInput) { async.setImmediate(waitInputDone); } else { webtoonGetter(callback); } })(); } }

이게 가장 최근에 추가했던 기능이다.


성인 웹툰은 사용자 로그인이 필요하다. 로그인되지 않은 상태에서 성인 웹툰 보기 페이지에 들어가면 로그인 페이지로 302 리디렉션을 넘겨주는데, 로그인을 직접 구현하기에는 귀찮고 힘들다.(난 OTP까지 걸어놔서 특히!)

그런 고로 사용자에게 현재 로그인된 세션의 쿠키를 입력하도록 질의한다.


해본 결과 생각보다 간단하게 해결되서 놀랐다. 이렇게 금방 될 줄은 몰랐는데, 아무튼 성공적으로 로그인 인증이 되어서 성인 웹툰도 다운로드받을 수 있다.


이 프로그램은 여러 회차를 동시에 다운로드 받으므로 여러 회차가 동시에 302 리디렉션 응답을 받을 것이다. 동시에 여러 개의 질문에 응답할 수는 없고, node.js의 readline도 꼬이므로 doingAuthInput 변수에 지금 이미 쿠키 입력을 받고 있는지를 저장한다.


입력을 받고있지 않으면 쿠키 정보를 입력받고, 이미 입력을 받고있다면 아래의 else문을 실행한다.

else문에서는 waitInputDone() 함수가 있는데, 여기서는 입력을 받을때까지 setImmediate() 함수를 이용해서 다른 동시작업이 먼저 실행되도록 자기 자신을 미뤄서, 입력을 받는 부분이 먼저 실행되어 입력을 완료하도록 대기한다.

그러다가 입력이 완료되면 다운로드를 재시작(자기 자신의 함수를 다시 호출 - webtoonGetter(callback))한다.


이 병렬 처리 과정도 일반적인 자바스크립트 코딩을 하면 볼 일이 없겠지만 어쩌다보니 쓰게 되어서 신기했다. 타 언어의 스레딩과는 달리 데이터 완전성 이런건 확인을 안해도 되어서 좋았지만, synchronized block 같은 기능을 생각하고 저 부분을 짜다가 나중에 보니 비슷한 기능은 없고 setImmeriate() 함수를 찾게 되어 그걸 사용하게 되었다.

블로그 이미지

예비컴공돌이

각종 프로젝트 진행중! 생각날때마다 블로그 업데이트합니다.