2016년 2월 25일 목요일

웹 브라우저에서 Raspberry Pi 조작하기

스터디의 이번 과제는, 웹브라우저에서 Raspberry Pi의 주소로 접근하여 텍스트필드에 문자열을 입력하면, 그 문자열을 PiFaceCAD에 출력하는 것이었다. 서버는 node.js를 사용하여 구현하기로 했다.

node.js의 웹프레임워크인 express 모듈을 사용했고, PiFaceCAD를 다루는 Python 코드를 호출하기 위해 child_process 모듈을 사용했다.

server.js

var express = require('express');
var app = express();

app.use(express.static('public'));

app.get('/', function (req, res) {
   res.sendFile( __dirname + "/" + "form.html" );
})

app.get('/lcd_write', function (req, res) {
   text = "\"" + req.query.text + "\""
   var exec = require('child_process').exec;
   function puts(error, stdout, stderr) { console.log(stdout) }
   cmd = "python3 -c \"import sys, pifacecad; pifacecad.PiFaceCAD().lcd.write(sys.argv[1])\""
   exec(cmd + " " + text, puts);
   console.log(text);
   res.end(text);
})

var server = app.listen(8000, function () {

  var host = server.address().address
  var port = server.address().port

  console.log("Example app listening at http://%s:%s", host, port)

})

form.html

<html>
<body>
<form action="/lcd_write" method="GET">
Input text here: <input type="text" name="text">  <br>
<input type="submit" value="Submit">
</form>
</body>
</html>


2016년 2월 22일 월요일

Raspberry Pi에 node.js 설치

Raspberry Pi(Raspbian Wheezy)에 node.js를 설치해서 테스트해보았다. 강좌(http://pyrasis.com/nodejs/nodejs-HOWTO)를 따라서 콘솔 메시지로 Hello World까지는 출력을 했지만, express 모듈을 설치하고 사용하는 것이 그리 순탄치 않다.

npm ERR! Error: failed to fetch from registry: express

위와 같은 메시지가 나타나며 패키지 설치에 실패한다든지, 세그멘테이션 오류가 발생한다든지 하는 문제가 생긴다.

다음의 명령으로 패키지를 제거한 뒤에,

sudo apt-get purge nodejs npm

다음의 명령으로 4.2.4 버전을 다시 설치했다.

wget http://nodejs.org/dist/v4.2.4/node-v4.2.4-linux-armv6l.tar.gz
cd /usr/local
sudo tar xzvf ~/node-v4.2.4-linux-armv6l.tar.gz --strip=1


참고

2016년 2월 20일 토요일

Edx.org 수강을 위한 최소 사양

Edx.org의 Body101x 강좌를 듣기 시작했다.

지난 번에 수리를 했던, 사무실에 둔 IBM A22e 2655 노트북은 펜티엄3 CPU에 현재 256MB 램을 갖고 있는데, 동영상 스트리밍으로 강좌를 보기에는 어려움이 있었다.

Lubuntu 14.04를 설치한 HP tx1000을 집에서 시험해보니, 파이어폭스 브라우저에서 동영상 강좌를 보고 문제를 푸는 데에 지장이 없다. AMD (Turion 64 X2 Mobile Technology) CPU에 RAM은 512MB만 남겨둔 상태. 다음에 사무실에 들릴 때 이걸로 바꿔야겠다.


tx1000



아이나비 네비게이션 SD 카드 문제 해결

5년째 사용하고 있는 차량용 네비게이션이 얼마 전부터 자꾸 먹통이 되어서 용산에 있는 팅크웨어 서비스센터에 방문했다(곧 다른 곳으로 이전한다고).
수리기사는 네비게이션 문제가 아니라 SD 카드 문제로 진단했다. 그러고보니, 라즈베리 파이에 쓰려고 구입했던 삼성 8GB 마이크로 SD 카드를 네비게이션에 쓰고, 원래 네비게이션에 쓰던 4GB 마이크로 SD 카드는 파이에 사용하고 있었다.

집에 와서, OpenELEC을 설치해서 쓰던 4GB SD 카드를 SD Formatter 프로그램으로 파티션 조정 및 포맷("FORMAT SIZE ADJUSTMENT" 옵션을 "ON"으로 설정하고 실행)한 뒤에, 아이나비 업데이트 프로그램으로 지도 데이터를 다시 넣었다.
오늘 카드를 바꿔끼고 두어 시간 운전을 해보니 아무 문제가 없다. :)

2016년 2월 16일 화요일

Blogger에 코드 구문 강조 적용

이 블로그(http://sk8erchoi-tech.blogspot.com/)에 쓰는 글에는 프로그램 소스 코드라든지, 명령행에서 입력하는 내용이 종종 포함된다. 본문과 코드가 뒤섞이다보면 읽기가 불편하고, 글을 쓰는 입장에서도 어떻게 처리해야할 지 난감하다. 그래서 검색을 좀 해보니 구문 강조(syntax highlighting)를 해주는 JavaScript 패키지가 몇 가지 있다.

이번에 적용한 것은 highlight.js이다. 다음과 같이 블로거에 적용했다.

1. 블로거의 템플릿 메뉴에서 HTML 편집 버튼 클릭. </head> 윗줄에 다음의 코드를 삽입.
<link rel="stylesheet" href="//cdnjs.cloudflare.com/ajax/libs/highlight.js/9.1.0/styles/default.min.css">
<script src="//cdnjs.cloudflare.com/ajax/libs/highlight.js/9.1.0/highlight.min.js"></script>

 2. 게시물의 코드 부분을 다음과 같이 pre와 code 태그를 사용하여 감싸기.
<pre><code class="python">print("hello")</code></pre>

3. 스타일(테마)을 변경하고 싶으면, 데모 페이지에서 원하는 스타일의 이름을 알아둔 다음, 스타일 디렉터리에서 해당되는 파일명을 확인하고, 1.의 템플릿 HTML을 수정. 예를 들어, Tomorrow Night 스타일을 적용하려면 default.min.css를 tomorrow-night.min.css로 변경한다.

2016년 2월 15일 월요일

Lenovo X230 Windows 10 Wi-Fi 문제 해결

Lenovo X230 노트북에 Windows 10을 설치하여 사용하다보니, Wi-Fi 네트워크에 연결이 되어도 "제한됨"으로 표시되고 인터넷이 잘 되지 않는 문제가 있다. 그때마다 연결을 끊었다가 다시 붙이기를 몇 번 씩 해서 사용해왔는데, 아무래도 불편하다.
Intel 사이트에서 Intel® Centrino® Advanced-N 6205, Dual Band 용 소프트웨어 및 드라이버(Wireless_18.21.0_e164.exe)를 다운로드하여 설치하고, 결과를 지켜보는 중.
https://downloadcenter.intel.com/download/25511/Intel-PROSet-Wireless-Software-for-Windows-8-1-

2016년 2월 14일 일요일

Raspberry Pi MP3 Player

Raspberry Pi와 PiFaceCAD를 사용하여 MP3 플레이어를 만들어보았다. 이것도 역시 스터디 숙제. 음악 파일의 재생을 위해 pygame.mixerpygame.mixer.music을 사용했다.



소스 코드:

#!/usr/bin/python3

from pygame import mixer
import time

class Player:
    def __init__(self):
        self.isOn = True
        self.isPlaying = False
        self.isPaused = False

        show("MP3 Player")
        mixer.init()
        self.musicVolume = round(mixer.music.get_volume(), 1)
        import glob
        self.musicFiles = tuple(sorted(glob.glob("*.mp3")))
        self.musicNum = 0
        filename = self.musicFiles[self.musicNum]
        show(filename + "\nReady")

    def listen(self, sw_id, func, always=False):
        if cad.switches[sw_id].value == 1 and (always or self.isOn):
            while cad.switches[sw_id].value == 1: pass
            print('self.%s()' % func)
            exec('self.%s()' % func)

    def playPause(self):
        if self.isPlaying:
            self.pause()
        else:
            if self.isPaused:
                self.resume()
            else:
                self.play()

    def play(self):
        filename = self.musicFiles[self.musicNum]
        mixer.music.load(filename)
        self.musicTitle = filename
        show(self.musicTitle + "\nPlaying")
        mixer.music.play()
        self.isPlaying = True

    def pause(self):
        self.isPlaying = False
        mixer.music.pause()
        show(self.musicTitle + "\nPaused")
        self.isPaused = True

    def resume(self):
        self.isPlaying = True
        show(self.musicTitle + "\nPlaying")
        mixer.music.unpause()
        self.isPaused = False

    def prev(self):
        self.musicNum = (self.musicNum - 1) % len(self.musicFiles)
        self.play()

    def next(self):
        self.musicNum = (self.musicNum + 1) % len(self.musicFiles)
        self.play()

    def randomPlay(self):
        import random
        self.musicNum = random.choice(range(len(self.musicFiles)))
        self.play()

    def volumeUp(self):
        self.setVolume("up")

    def volumeDown(self):
        self.setVolume("down")

    def setVolume(self, action):
        min, max, delta = 0.0, 1.0, 0.1
        vol = mixer.music.get_volume()
        vol = round(vol, 1)

        if action is "up" and vol < max:
            vol = round(vol + delta, 1)
            mixer.music.set_volume(vol)
            show(self.musicTitle + "\nvol: " + str(vol))
            self.musicVolume = vol
        elif action is "down" and vol > min:
            vol = round(vol - delta, 1)
            mixer.music.set_volume(vol)
            show(self.musicTitle + "\nvol: " + str(vol))
            self.musicVolume = vol
        elif action is "mute":
            if vol == min:
                vol = self.musicVolume
                mixer.music.set_volume(vol)
                show(self.musicTitle + "\nvol: " + str(vol))
            else:
                vol = min
                mixer.music.set_volume(vol)
                show(self.musicTitle + "\nMuted")
        else:
            pass

    def mute(self):
        self.setVolume("mute")

    def onOff(self):
        if self.isOn:
            if self.isPlaying:
                mixer.music.stop()
            show('Bye...')
            time.sleep(2)
            cad.lcd.backlight_off()
            time.sleep(1)
            cad.lcd.clear()
            self.isOn = False
        else:
            self.__init__()

import pifacecad

cad = pifacecad.PiFaceCAD()
cad.lcd.backlight_on()
cad.lcd.cursor_off()
cad.lcd.blink_off()

def show(text):
    cad.lcd.clear()
    cad.lcd.write(text)

p = Player()

while True:
    p.listen(0, 'playPause')
    p.listen(1, 'prev')
    p.listen(2, 'next')
    p.listen(3, 'randomPlay')
    p.listen(4, 'onOff', True)
    p.listen(5, 'mute')
    p.listen(6, 'volumeDown')
    p.listen(7, 'volumeUp')

2016년 2월 10일 수요일

line.co.kr

line.co.kr 도메인을 둘러싼 송사에 관한 소식이 들리기에 간단히 생각을 써둔다.

라인 코퍼레이션이 법적으로 잘못한 것은 없다. 그렇다면 도덕적으로는 잘못했나? 내 생각엔 그렇지 않다. 협상이 잘 안되니 조정을 거쳤을 것이다. 오히려 도메인 이름을 선점한 것을 가지고 한몫 잡아보려한 사람이 뜻대로 일이 풀리지 않으니 송사와 언론 플레이를 시도했지만, 사리에 어둡고 과정이 치밀하지 못했기 때문에 제 풀에 넘어진 것이다.

도메인을 내놓게 된 그 사람은 이제 일본 회사가 한국의 개인을 등쳐먹는 듯한 구도를 억지로 만들어가려고 하고, 이에 몇몇 사람들은 케케묵은 일본에 대한 앙금을 토해내고, 또 어떤 이들은 악덕 기업과 헬조선을 건져올린다. 신물 난다.

한국인은 늘 감정을 앞세우다가 이성을 잃고 손해를 보는 듯하다. 그 와중에 이성적인 판단을 했던 이들까지 지탄을 받는다.

한 줄 요약: 그냥 라인이 십만 달러 지르고 생색 냈으면(그 일을 마케팅 소재로 삼았으면) 차라리 좋았을 것을...

PiFaceCAD

올해 초부터 우분투 한국 사용자모임에서 시작한 Raspberry Pi 스터디에 매주 나가고 있다. Pi에서 node.js를 구동하여 웹을 통해 이런저런 장치를 제어하는 것을 목표로 하는데, 작업의 편의를 위해 PiFaceCAD(PiFace Control and Display)라는 제품을 구입해서 사용하기로 했다. 들고다니다가 망가뜨리지 않을까 걱정이 되어, Raspberry Pi에 PiFaceCAD를 꽂은 채로 넣을 수 있는 케이스도 함께 샀다.

PiFaceCAD에 친숙해지기 위해 Python으로 스코어보드를 만들어보는 숙제가 있었다. LCD에 두 팀의 점수를 보여주는 기능을 하며, 각 팀의 점수를 올리기 위한 스위치 한 개 씩과, 점수를 리셋하는 용도의 스위치 한 개를 사용한다.


위의 영상과 같이 동작하며, 내가 짠 코드는 다음과 같다. (먼저 셋업이 필요하다)

#!/usr/bin/python3

import pifacecad

cad = pifacecad.PiFaceCAD()
cad.lcd.backlight_on()

def show():
    cad.lcd.clear()
    cad.lcd.write('A team:' + str(score[0]) + '\n'
                  + 'B team:' + str(score[1]))

score = [0, 0]
show()

while True:
    if cad.switches[0].value == 1:
        score[0] = score[0] + 1
        while cad.switches[0].value == 1: pass
        show()
    elif cad.switches[1].value == 1:
        score[1] = score[1] + 1
        while cad.switches[1].value == 1: pass
        show()
    elif cad.switches[4].value == 1:
        score = [0, 0]
        while cad.switches[4].value == 1: pass
        show()

스위치를 한 번 누르는 동안 여러 점이 올라가는 것을 막기 위해, 손을 뗄 때까지 pass시키는 작은 while 문을 넣었다. 다른 사람들은 time.sleep()을 사용해서 처리했다. 이때, 일반적으로 0.25 초 정도의 sleep이 적당하다고 한다.

작성해놓고 보니 while 블록에서 중복으로 나타나는 것들을 정리하고 싶어져서 아래와 같이 바꾸었다.

#!/usr/bin/python3

import pifacecad

cad = pifacecad.PiFaceCAD()
cad.lcd.backlight_on()

def show():
    cad.lcd.clear()
    cad.lcd.write('A team:' + str(score[0]) + '\n'
                  + 'B team:' + str(score[1]))

def listen(i, func):
    global score
    if cad.switches[i].value == 1:
        if func == 'add': score[i] = score[i] + 1
        elif func == 'reset': score = [0, 0]
        while cad.switches[i].value == 1: pass
        show()

score = [0, 0]
show()

while True:
    listen(0, 'add')
    listen(1, 'add')
    listen(4, 'reset')

다음의 코드는, 위쪽에 붙은 로커 스위치를 테스트해보기 위해 수정한 버전이다.

#!/usr/bin/python3

import pifacecad

cad = pifacecad.PiFaceCAD()
cad.lcd.backlight_on()

def show():
    cad.lcd.clear()
    cad.lcd.write('A team:' + str(score[0]) + '\n'
                  + 'B team:' + str(score[1]))

def listen(i, func, team=None):
    global score
    if cad.switches[i].value == 1:
        if func == 'add': score[team] = score[team] + 1
        elif func == 'reset': score = [0, 0]
        while cad.switches[i].value == 1: pass
        show()

score = [0, 0]
show()

while True:
    listen(5, 'reset')
    listen(6, 'add', 0)
    listen(7, 'add', 1)

2016년 2월 3일 수요일

PyAudio

앞의 글에서는 Raspberry Pi에 USB 마이크를 연결하고 arecord와 aplay 명령을 사용하여 소리의 녹음과 재생을 해보았는데, 이번에는 같은 일을 Python에서 시도해 보았다. 사용한 라이브러리는 PyAudio라는 것으로, PortAudio의 Python 바인딩이다.


PyAudio 설치


다음의 명령으로 라이브러리를 설치한다.

$ sudo apt-get install python-pyaudio


Wav 파일 재생


PyAudio 홈페이지에 소개된, wav 파일을 재생하는 예제를 실행해보았다.
http://people.csail.mit.edu/hubert/pyaudio/#play-wave-example

경고 메시지가 많이 뜨긴 하지만 일단 무시했다. 소리가 많이 끊어져 들렸는데, CHUNK 값을 2048로 조정하니 조금 좋아졌지만 여전히 끊기는 듯한 느낌이 든다.


Wav 파일 녹음


이번에는 소리를 wav 파일로 녹음하는 예제를 실행해보았다.
http://people.csail.mit.edu/hubert/pyaudio/#record-example

IOError 메시지가 떴는데, 역시 CHUNK 값을 조정하여 해결했다. 이번에는 4096으로 설정. 그리고, 모노 마이크를 사용하기 때문에 CHANNELS = 1로 설정했다.

2016년 2월 1일 월요일

Raspberry Pi에서 소리 입력 및 녹음하기

Raspberry Pi는 소리를 출력할 수 있지만, 아날로그 마이크를 연결해서 소리를 녹음할 수 없다. 그래서 ① 마이크가 내장된 USB 카메라를 사용하거나,  ② USB 사운드카드에 마이크를 연결한다. 내가 선택한 것은 ③ USB 동글 형태의, 사운드카드와 마이크 일체형(?)으로, "USB Microphone" 비슷한 이름이 붙은 물건이다. eBay에서 1.65 달러에 구입했다.

http://www.ebay.com/itm/231668535198?_trksid=p2057872.m2749.l2649&ssPageName=STRK%3AMEBIDX%3AIT

USB 마이크


구성 및 테스트

  1. USB 마이크를 Pi에 꽂은 후, Pi에 전원을 넣는다.

  2. USB 오디오 녹음장치를 찾아본다.

    $ arecord -l
    **** List of CAPTURE Hardware Devices ****
    card 1: Device [USB PnP Sound Device], device 0: USB Audio [USB Audio]
      Subdevices: 1/1
      Subdevice #0: subdevice #0

    위에서는 card 번호가 1, device 번호가 0이다.

  3. 녹음해본다. 앞에서 알아낸 번호를 -D hw: 인자에 써준다.

    $ arecord -D hw:1,0 -f S16_LE -d 5 -r 44100 test.wav
    Recording WAVE 'test.wav' : Signed 16 bit Little Endian, Rate 44100 Hz, Mono

    만약 장치(-D 옵션)를 올바로 지정하지 않으면 다음과 같은 오류가 발생한다.

    arecord: main:682: audio open error: No such file or directory

    비트율(-r 옵션)을 생략하거나 부적당한 값을 지정하면 경고가 발생할 수 있다. 'got = '에 표시된 값으로 바꿔서 다시 녹음해본다.

    Warning: rate is not accurate (requested = 8000Hz, got = 44100Hz)
                    please, try the plug plugin

  4. 녹음된 파일을 재생해본다.

    $ aplay test.wav
    Playing WAVE 'test.wav' : Signed 16 bit Little Endian, Rate 44100 Hz, Mono

  5. 음량을 조절하려면 alsamixer를 실행한다. 처음 보이는 화면은 재생 음량이고, 탭을 한 번 누르면 녹음 음량이 보인다. 이때, <F6> 키를 누르고 사운드카드를 선택한 다음, 위아래 화살표 키로 음량을 조절한다. 스페이스 바를 눌러 "CAPTURE"라는 빨간 글씨가 보이도록 해야한다. <esc> 키로 종료한다.
    설정 값을 저장해두하려면 sudo alsactl store를 실행한다.

alsamixer

Raspberry Pi에서 MP3 오디오 재생하는 방법은 공식 문서(번역)를 참조.

참고