레이블이 RPi인 게시물을 표시합니다. 모든 게시물 표시
레이블이 RPi인 게시물을 표시합니다. 모든 게시물 표시

2019년 12월 12일 목요일

라즈베리 파이에서 REST API 사용해서 스마트폰으로 제어하기 (Use REST API in Raspberry Pi for controlling from smartphone)

라즈베리 파이에서 flask를 사용해 REST API를 구현하여 스마트폰이나 컴퓨터에서 원격으로 센서값을 읽거나 장치를 제어할 수 있도록 간단하게 구현 해 보았다.
HTTP GET과 POST method를 사용할 수 있도록 해 놓았고 POST method에서 파라미터는 JSON으로 넘겨주면 된다.
아래 코드가 구현해 놓은 API는 다음과 같다.

* /api/led :
    method: POST
    parameter: {'ledId': [id], 'val': [OnOff]}  - [id]는 조작하고자 하는 LED번호, [OnOff]에는 LED를 켜고 싶으면 1, 끄고 싶으면 0
    return: 명령이 실행되고난 후 LED 상태


* /api/led :
    method: POST
    parameter: {'ledId': [id], 'val': [OnOff]}  - [id]는 조작하고자 하는 LED번호, [OnOff]에는 LED를 켜고 싶으면 1, 끄고 싶으면 0
    return: 명령이 실행되고난 후 LED 상태

* /api/sw :
    method: POST
    parameter: {'id': [id]}  - [id]는 상태를 알고 싶은 스위치 번호
    return: 현재 스위치 상태

* /api/sensor/[sensorName] :현재 sensorName에 올 수 있는 센서는 temperature, humidity, pressure, light
    method:GET
    return:지정한 센서에서 읽은 값

* /api/sensors :
    method: GET
    return: 전체 센서 값 (JSON 포맷)

* /api/sensors :
    method: POST
    parameter: {'sensorList': [sensor list]}  -값을 읽고 싶은 센서 이름. 한개 이상이 올 수 있고 복수개일 경우 ','로 구분
    return: 지정한 센서에서 읽은 값 (JSON 포맷)

* /api/exec :
    method: POST
    parameter: {'cmd': [command line]}  - [commnad line]은 라즈베리 파이에서 실행할 명령
    return: 명령 실행 결과

코드를 실행하면 라즈베리 파이에 다음과 같은 화면이 나오게 된다.

PC의 브라우저에 [ip]:5000/api/sensors 를 입력해 주면 다음과 같은 결과를 얻을 수 있다. 이 경우 4개의 센서값을 모두 JSON 포맷으로 리턴해 준다.


[ip]:5000/api/sensor/temperature 를 읿력해 주면 다음과 같이 온도센서 값만 리턴 해 준다.


지금까지는 브라우저의 주소창에 입력했는데 이 경우 GET method를 사용하는 것이다. POST method를 확인하려면 curl, postman, rested 같은 툴을 사용해야 한다. 여기서는 파이어폭스에 설치한 rested를 사용했다.

앞으로 파란색 부분은 저 값으로 고정시켜 놓고 빨간색 부분만 바꿔주면서 테스트 하면 된다. 위의 사진은 api/led 를 호출해 원하는 LED를 on/off 하는 예제이다. 소스코드의 @app.route('/api/led', methods = ['POST']) 에서 빨간색 굵은 부분이 주소가 된다. 단 여기서는 flask 서버가 5000번 포트를 사용하고 있으므로 주소 뒤에 ':5000'을 추가해 줘야 한다.
Request Body 부분에 id와 val 이라는 두개의 키가 있어 id의 값 부분에 조작할 LED 번호를 넣어주고, val의 값 부분에 LED on/off 를 넣어주면 된다.
실행이 되면 바뀐 LED 상태값이 리턴된다. 위의 예에서는 4번 LED를 ON 시킨 것이다. 맨 마지막 부분에 결과로 'ON'이 리턴되었다.


라즈베리 파이 화면을 보면 이번엔 /api/led 주소로 POST emethod를 사용했음을 확인할 수 있다.

위의 화면은 LED를 끄는 것이다. 나머지 부분은 동일하고 val 의 값만 '0'으로 보내주면 된다.

다음은 /api/sw를 통해 스위치 상태를 확인해 본 것이다. URL 부분이 /api/led에서 /api/sw로 바뀌었다. 또한 이번엔 파라미터로 id만 보내주면 된다. 여기서는 2번 스위치의 상태를 확인해 본 것이다. 현재 스위치 상태 값이 리턴된 걸 확인할 수 있다.


다음은 /api/sensors를 호출하는데 위쪽에서는 GET method로 호출해서 모든 센서 값을 읽어왔는데 여기서는 POST method를 사용해 값을 읽기를 원하는 센서만 지정할 수 있다. sensorList 의 값 부분에 원하는 센서 이름을 넣어주면 된다. 두개 이상을 넣을 땐 센서 이름 사이에 ','로 구분해 주면 된다. 여기서는 temperature와 humidity 센서 값을 읽도록 해 보았다. 결과값으로 두 센서값이 JSON 포맷으로 리턴되었다.


마지막으로는 라즈베리 파이에서 명령을 실행할 수도 있다. /api/exe에 POST method로 실행하기 원하는 명령을 보내주면 된다. 실행된 결과가 리턴된다. 이 방법을 이용하면 라즈베리 파이에 연결해 놓은 장치가 API를 제공해 주지 않거나 python module이 없고 CLI로 제어하는 프로그램만 있어도 원격에서 제어가 가능해 진다.


전체 코드는 다음과 같다. 이 코드는 Raspberry Pi 4에서 python3를 사용해 동작시켜 보았는데 python3만 지원하는 환경이면 어디에서나 동작하는데 문제는 없을 것이다.

* 여기서는 Raspberry Pi에서 실행되는 서버 코드만 설명하고 있다. 스마트폰 앱에서 제어하려면 해당하는 GET, POST 메소드로 API를 사용하면 된다. 해당 부분 코드 작성은 직접 하지 않아도 postman같이 자동으로 생성해주는 툴을 사용하면 매우 간단하게 사용할 수 있다. 다음 포스트에서는 postman을 사용해 클라이언트의 http request 부분의 코드를 자동으로 생성하는걸 설명하겠다.


#!/usr/bin/python3

from flask import Flask, request, jsonify
import os, ntpath
import json
import subprocess

app = Flask(__name__)

# 실제로는 GPIO library를 사용해 읽은 스위치 값을 리턴해줘야 함
def readSwitch(id):
  print ("# readSwitch is called for switch %d" % id)
  return 1

# 실제로는 온도센서 값을 읽어 리턴해 줘야 함
def readTemp():
    return str(25.3)

# 실제로는 습도센서 값을 읽어 리턴해 줘야 함
def readHumid():
    return str(73)

# 실제로는 압력센서 값을 읽어 리턴해 줘야 함
def readPressure():
    return str(1025)

# 실제로는 조도센서 값을 읽어 리턴해 줘야 함
def readLight():
    return str(419)

def readSensor(s):
    return sensorFunc[s]()

sensorFunc = {'temperature': readTemp, 'humidity': readHumid, 'pressure': readPressure, 'light': readLight}

@app.route('/api/led', methods = ['POST'])
def ledSvc():
    data = request.json
    #ledId = data['id']
    #onOff = data['val']
    ledId = int(data['id'])
    onOff = int(data['val'])
    if onOff == 0:
      newLedState = 'OFF'
      # 여기에 해당 LED(LED번호는 변수 ledId에 들어 있음)를 끄는 코드를 집어 넣어 줌
    else:
      newLedState = 'ON'
      # 여기에 해당 LED(LED번호는 변수 ledId에 들어 있음)를 켜는 코드를 집어 넣어 줌
    print ("Turn LED %d to %s" % (ledId, newLedState))
    return newLedState

@app.route('/api/sw', methods = ['POST'])
def swStatus():
    data = request.json
    ledId = int(data['id'])
    swState = readSwitch(ledId)  # readSwitch() 함수에서 스위치(ledId) 값을 읽어 리턴해 줌
    print ("Switch %d state : %d" % (ledId,swState))
    return str(swState)

@app.route('/api/sensor/<string:sensorName>', methods = ['GET'])
def readSensor(sensorName):
    try:
        ret = sensorFunc[sensorName]()
        return ret
    except:
        return 'Wrong Sensor'

@app.route('/api/sensors', methods = ['GET', 'POST'])
def readSensors():
    if request.method == 'GET':
        result = {}
        for s in sensorFunc:
            result[s] = sensorFunc[s]()
    elif request.method == 'POST':
        result = {}
        sl = request.json['sensorList']
        print (sl)
        sl = sl.split(',')
        for s in sl:
            print (s)
            result[s] = readSensor(s)

    return json.dumps(result)

@app.route('/api/exec', methods = ['POST'])
def externalPgm():
    data = request.json
    cmds = data['cmd']
    print (cmds)
    cmdl = cmds.split(' ')
    result = subprocess.check_output(cmdl)
    return result

if __name__=='__main__':
    app.run(host='0.0.0.0', debug=True)











2017년 5월 11일 목요일

라즈베리 파이에서 MQTT를 이용해 ESP8266 제어하기

여기서는 라즈베리 파이, ESP8266(또는 와이파이 쉴드를 붙인 아두이노), PC를 MQTT로 연결해 보도록 하겠다.

이번에 하고자 하는 것은 라즈베리파이가 python을 사용해 PC에 있는 MQTT 브로커에 메시지를 보내면 그 메시지가 ESP8266으로 전달되어 거기에 있는 LED를 제어하는 것이다.

MQTT for PC


MQTT server(브로커)로 사용할 수 있는 옵션이 몇가지 있다. Amazon의 AWS IoT나 AdafruitIO 같은 서비스들은 클라우드 기반의 브로커이다. 각 서비스는 여러가지 장점이 있다. 프로젝트의 필요성에 따라 클라우드 기반 서비스를 사용할 필요가 있을수도 있다.

Mosquitto


자신의 호스트에 브로커를 실행하고 싶으면 오픈소스인 moqsuitto가 훌륭한 옵션이다. 이 브로커는 MQTT를 완전히 지원할 뿐 아니라 pub/sub를 위한 커맨드라인 유틸리티까지 제공한다.

윈도우를 사용하면 mosquitto.org에서 바이너리 인스톨러를 받을 수 있다. 리눅스의 경우 패키지 매니져에서 'mosquitto' 또는 'mosquitto-mqtt'를 찾으면 된다. 맥에서는 homebrew를 사용해 mosquitto를 설치할 수 있다.

* 맥에서 설치한 경우 '/usr/local/sbin'을 PATH에 넣어줘야 한다.

Running Mosquitto


Mosquitto는 디폴트로 1883번 포트를 사용하고 보안기능은 다 꺼져 있다. 처음 시작할 때는 이것으로 충분하지만 결국에는 보안기능을 추가해 줄 필요가 있다.

Mosquitto를 verbose 모드로 실행하길 권장한다. 그러면 출력을 보고 어떤 일이 일어나는지에 대한 아이디어를 얻을 수 있을 것이다.

MacMan:~ james$ mosquitto -v 
1456272937: mosquitto version 1.4.5 (build date 2015-11-09 14:23:46-0800) starting 1456272937: Using default config. 
1456272937: Opening ipv4 listen socket on port 1883. 
1456272937: Opening ipv6 listen socket on port 1883.

Testing Mosquitto


서버 코드를 verbose 모드에서 실행시키고, 추가로 두개의 명령어 프롬프트를 열어준다. 각각의 명령어 프롬프트에서 메시지 전송 테스트를 위해 mosquitto_pub와 mosquitto_sub를 사용할 것이다.

Subscribing to MQTT Topic with Mosquitto


먼저 클라이언트가"debug"라는 토픽을 subscribe하게 해 준다. 첫번째 명령어 프롬프트에서 아래 명령을 입력해 주면 된다.

$ mosquitto_sub -h 127.0.0.1 -i testSub -t debug

* -h : host flag는 mosquitto가 실행되고 있는 서버를 지정. 여기서는 localhost
* -i : identity flag 는 옵션이다. Client ID가 지정되지 않으면 mosquitto_sub가 임의로 만듬
* -t : topic flag 는 subscribe 하려고 하는 topic을 지정. 여기서는 "debug"

서버에서는 topic을 만들지 않았다는걸 주목해라. Topic은 subscriber나 publisher가 처음 지정할 때 만들어진다.

Publish to MQTT Topic with Mosquitto


이제 topic을 기다리는 클라이언트를 가지고 있으므로, 다른 클라이언트가 그 topic을 publish하게 해 본다.

$ mosquitto_pub -h 127.0.0.1 -i testPublish -t debug -m 'Hello World'


이제 첫번째 명령어 프롬프트 화면을 보면 publish된 메시지가 와 있는것을 확인할 수 있다. mosquitto_pub와 mosquitto_sub 명렁 모두 --help 플래그로 확인 해 봐라. 매우 여러가지 유용한 옵션들을 찾아볼 수 있을 것이다.

이제 mosquitto 브로커가 정상 동작하고 있는것이 확인되었으므로 디바이스와 통신을 하게 해 보자.

MQTT for Raspberry Pi (Python)


라즈베리 파이에서 MQTT 메지시를 보내고 받을 수 있는 옵션으로는 mosquitto를 설치하는 것이다. 그러면 라즈베리 파이에서도 동일하게 mosquitto_pub와 mosquitto_sub 명령어를 사용할 수 있다.

다른 방법으로는 자신이 선호하는 언어용 MQTT 라이브러리를 사용하는 것이다. 여기서는 파이선을 사용한다.

Install MQTT for Python


여기서 사용할 MQTT library는 Paho Python Client이다. 오픈소스로 가장 최신의 MQTT를 지원한다.

설치는 간단하다. 먼저 'pip'를 설치하고 나서 아래 명령을 실행한다.

$ pip install paho-mqtt

* 만일 python3를 사용한다면 'pip' 대신 'pip3'를 사용해야 하는걸 잊지 마라.

Example MQTT Python Code for Raspberry Pi


Paho는 MQTT 서버와 통신을 매우 쉽게 만들어 준다.

import paho.mqtt.publish as publish
import time
print ("Sending 0...")
publish.single("ledStatus", "0", hostname="macman")
time.sleep(1)
print ("Sending 1...")
publish.single("ledStatus", "1", hostname="macman")

진짜 간단하다. 스크립트에 서버가 실행되고 있는 호스트 이름(여기서는 'macman'), Topic (여기서는 'ledStatus'), 메시지(여기서는 '0' 또는 '1')만 알려주면 된다.

이 MQTT 튜토리얼 예제에서는 '0'을 보내고 1초 기다린 후에 '1'을 보낸다. 이 동작은 ESP8266에 있는 LED가 잠깐동안 꺼졌다 다시 켜지게 만든다.


이제 RPi가 MQTT 메시지를 보내게 만들었으니 그 메시지를 받도록 만들어 보자. 다음 섹션은 ESP8266이 topic "ledStatus"의 메시지를 받아 실제로 LED를 깜빡이게 만들 것이다.

MQTT for ESP8266


PubSubClient는 아두이노 기반의 MQTT 클라이언트이다. 단지 몇 줄의 코드만으로 매우 쉽게 topic을 subscribe하거나 새로운 topic을 publish할 수 있다.

여기서는 ESP8266을 프로그래밍 하기 위해 아두이노 IDE를 사용했다.

Installing MQTT for Arduino IDE


아두이노 IDE에 PubSubClient를 설치하는건 간단하다. Github에서 수동으로 설치하거나 또는 Arduino Package Installer를 사용할 수 있다.


Arduino ESP8266 and Uno Note


Ethernet 쉴드가 장착된 Uno와 ESP8266간에 스위칭에는 별도의 추가적인 노력이 필요 없다. 라이브러리는 양쪽 모두 잘 동작한다.

Code Example for Arduino MQTT


PubSubCilent에 포함되어 있는 예제는 매우 훌륭하다. 예제에는 특히 MQTT에 대한 ESP8266 코드도 들어있다. 처음 시작할 때 그것들도 꼭 확인해 보기 바란다.

아래는 WiFi에 연결하고 "ledStatus" topic을 subscribe하는 코드이다. ESP8266이 메시지를 받으면 그에 따라 ledPin을 on/off한다.

#include <EEPROM.h>
#include <ESP8266WiFi.h>
#include <PubSubClient.h>
#include <Wire.h>
#include <Adafruit_INA219.h>

// Connect to the WiFi
const char* ssid = "Dear John";
const char* password = "password123";
const char* mqtt_server = "macman";

WiFiClient espClient;
PubSubClient client(espClient);

const byte ledPin = 0; // Pin with LED on Adafruit Huzzah

void callback(char* topic, byte* payload, unsigned int length) {
 Serial.print("Message arrived [");
 Serial.print(topic);
 Serial.print("] ");
 for (int i=0;i<length;i++) {
  char receivedChar = (char)payload[i];
  Serial.print(receivedChar);
  if (receivedChar == '0')
  // ESP8266 Huzzah outputs are "reversed"
  digitalWrite(ledPin, HIGH);
  if (receivedChar == '1')
   digitalWrite(ledPin, LOW);
  }
  Serial.println();
}


void reconnect() {
 // Loop until we're reconnected
 while (!client.connected()) {
 Serial.print("Attempting MQTT connection...");
 // Attempt to connect
 if (client.connect("ESP8266 Client")) {
  Serial.println("connected");
  // ... and subscribe to topic
  client.subscribe("ledStatus");
 } else {
  Serial.print("failed, rc=");
  Serial.print(client.state());
  Serial.println(" try again in 5 seconds");
  // Wait 5 seconds before retrying
  delay(5000);
  }
 }
}

void setup()
{
 Serial.begin(115200);

 client.setServer(mqtt_server, 1883);
 client.setCallback(callback);

 pinMode(ledPin, OUTPUT);
}

void loop()
{
 if (!client.connected()) {
  reconnect();
 }
 client.loop();
}


모든 마법은 "callback" 함수 안에서 일어난다. 실제 코드에서는 받은 메시지를 처리하는 함수를 작성하고 싶을 것이다. 여기서는 topic에 대해 한글자 메시지만을 보내기 때문에 이 간단한 코드로 충분하다.

Conclusion


이 MQTT 튜토리얼은 매우 기초적이다. 하지만 라즈베리 파이, ESP8266을 MQTT 브로커가 실행되고 있는 PC에 연결하는데 필요한 최소한의 것을 보여준다.

다음에는 MQTT를 통해 어떻게 더 복잡한 메시지를 보낼 수 있는가를 설명하겠다.




2017년 1월 5일 목요일

라즈베리 파이 제로 테스트 패드 배치 (Raspberry Pi Test Pad Layout)

라즈베리 파이 제로는 크기가 작다 보니 제대로(?) 활용을 하려면 주변에 몇가지 장치들을 붙여줘야 한다. 특히 USB 허브를 붙이려고 할 때 USB 케이블을 이용하면 케이블이 옆으로 튀어나와 보기에도 안좋고 케이블이 빠지기도 쉽다.
그래서 해결책은 보드의 아래쪽 면에 테스트 패드들을 활용하는 것이다. 기본적인 전원 신호, USB 데이터 신호선, SD카드 신호선들이 나와 있어 이 패드에 납땜을 하거나 아니면 좀 더 깔끔한 방법으로는 스프링이 들어있는 pogo pin을 이용하면 바깥쪽으로 튀어나온 케이블 없이 깔끔하게 주변회로를 부착할 수 있다.

USB 무선랜 동글을 직접 납땜





스프링이 달려 있는 pogo pin으로 납땜하지 않고 연결



Test pad의 위치는 다음과 같다.

라즈베리 파이 제로 v1.2 (클릭하면 크게 확대해 볼 수 있음)

라즈베리 파이 제로 v1.3 (클릭하면 크게 확대해 볼 수 있음)

주의할 것은 라즈베리 파이 v1.2와 v1.3의 테스트 패드 위치가 다르다. v1.2는 카메라 커넥터가 없는 초기모델이고 v1.3은 카메라 커넥터가 붙어있는 모델이다. 최근에 구입한 사람이라면 대부분 v1.3이겠지만 인터넷에서 pogo pin으로 연결하는 보드를 구입하는 경우 버젼을 꼭 확인하고 구입해야만 한다.

USB 허브를 연결하려면 PP1, PP6, PP22, PP23에 각각 +5V, GND, D+, D- 선을 연결해 주면 된다.
반대로 전원 공급을 마이크로USB 케이블을 이용하지 않고 라즈베리 파이에 직접 공급하고 싶은 경우에도 PP1, PP6에 +5V, GND를 연결해 줘도 된다.

2016년 8월 3일 수요일

라즈베리 파이 카메라 V2.1

라즈베리 파이 카메라 신 버젼이 나왔다. 가장 큰 차이점은 카메라 센서가 OmniVision의 OV5647에서 Sony의 IMX219로 변경되어 화소수가 500만 화소에서 800만 화소로 늘어난 것이다.
외관상 크기는 큰 차이가 없다.



이전 버젼과 새 버젼의 비교 사진이다. 오른쪽이 기존의 카메라(V1.3)이고 왼쪽이 신형 카메라(V2.1)이다.

카메라의 사양은 위의 표와 같다.

표에서 볼 수 있는것처럼 이미지 크기가 더 커지고 (최대 3280x2464) 렌즈도 더 밝아졌다.

2016년 5월 13일 금요일

라즈베리 파이에서 systemd를 사용해 부팅시 프로그램을 자동 실행 (Execute script on boot using systemd)




Raspbian 최신버젼이 릴리즈 되면서 cron이나 rc.local을 사용해 파이선 스크립트를 부팅시 자동실행 시키는 데서 고생하기 시작하였다. Raspbian의 부트 순서가 변경되어 이 프로세스들이 다른 시점에서 실행되기 때문인 것으로 보인다. 이것이 얼마나 문제가 되는지는 실행할 파이선 스크립트가 어떤 일을 하고 어떤 리소스를 필요로 하는가에 따라 치명적일 수도 있고 별 문제가 되지 않을 수도 있다.

부팅시 실행될 파이선 스크립트가 실행 시점에서 어떤 시스템 기능을 필요로 한다면 이 문제는 매우 치명적이다. 일반적으로 다음과 같은 것들이 포함된다.
  • 네트웍이 연결되어 있어야 함
  • /home/pi 디렉토리가 마운트 되어 읽을 수 있어야 함
  • 시스템의 시간이 NTP로 업데이트 되어야 함
그래서 커스텀 기능을 실행시키는데 권자오디는 방법이라 여려 리눅스 패키지에서 적용하고 있는 ’systemd’를 사용하기로 했다. systemd는 리눅스 시스템의 설정을 관리하기 위한 소프트웨어 툴로 이전에 동일한 목적으로 사용되던 여러 툴들을 대체하기 위한 목적으로 만들어 졌다.

systemd는 각자의 수준에 따라 매우 쉽거나 또는 매우 공포스러울 수 있다. 여기서는 최소한의 수고로 단지 스크립트 하나를 실행되도록 하는것이 목표이다.

Step 1 - 파이선 스크립트
 
여기서 사용할 예제 스크립트는 /home/pi 디렉토리에 저장되어 있고 이름은 ‘myscript.py’이다.  

Step 2 - Unit 파일 만들기
 
다음으로 해야 할 일은 설정 파일(aka Unit file)을 만들어 systemd에게 언제 무엇을 해야 하는지 알려줘야 한다.

$ sudo vi /lib/systemd/system/myscript.service

에디터를 실행한 다음 아래의 내용을 입력한다.

[Unit] Description=My Script Service
After=multi-user.target

[Service]
Type=idle
ExecStart=/usr/bin/python /home/pi/myscript.py

[Install]
WantedBy=multi-user.target

위의 파일은 “My Script Service”라는 이름의 새 서비스를 정의하고, ‘multi-user’ 환경이 사용 가능해 졌을 때 서비스를 한번만 실행하도록 요구한다. “ExecStart” 파라미터는 실행하길 원하는 명령어를 지정한다. “Type”은 모든 것이 다 로드 된 후에만 ExecStart 명령이 실행되는걸 보장하기 위해 ‘idle’로 설정한다. 예제에 사용한 GPIO 기반의 스크립트는 디폴트인 ‘simple’에서는 동작하지 않는다. 

파일의 패스는 절대주소여야 하고 스크립트의 위치 뿐 아니고 파이선의 위치까지도 완전히 지정해 줘야만 한다.

스크립트의 텍스트 출력을 로그파일에 저장하고 싶으면 ExecStart 라인을 다음과 같이 바꾸면 된다.

ExecStart=/usr/bin/python /home/pi/myscript.py > /home/pi/myscript.log 2>&1

Unit 파일의 퍼미션은 644로 설정해야 한다.

$ sudo chmod 644 /lib/systemd/system/myscript.service

Step 3 - systemd 설정
 
unit파일이 정의되었으므로 systemd에게 부팅시 이것을 사용하도록 알려줘야 한다.

$ sudo systemctl daemon-reload
$ sudo systemctl enable myscript.service

이제 시스템을 리부트 시키면 myscript.py가 자동으로 실행될 것이다.

$ sudo reboot

Step 4 - 서비스 상태 확인
 
서버스의 상태는 다음 명령으로 확인해 볼 수 있다.

$ sudo systemctl status myscript.service

2016년 5월 12일 목요일

라즈베리 파이를 비콘으로 사용하기 (Using Raspberry Pi as a Beacon)

독립적으로 비콘 모듈을 사용할 수도 있지만 이미 라즈베리 파이를 가지고 있다면 별도의 비콘 모듈 없이 라즈베리 파이를 비콘으로 설정해 사용할 수 있다.


라즈베리 파이 3의 경우 무선랜/블루투스가 기본 내장되어 있어 별도의 추가 하드웨어가 필요 없어 더 경제적이다. 라즈베리 파이 1/2의 경우는 USB타입의 블루투스 동글을 추가해 주면 된다.


 비콘은 특별한 것이 아니고 BLE(Bluetooth Low Energy)의 advertising packet 안에 옵션 필드인 Manufacturer Specific Data 에 특정 값을 집어넣어 주기적으로 advertising 하는 것이다. 그러므로 이 필드에 들어갈 값을 알아야 한다.

필드의 구조는 다음과 같다.

  • ID (uint8_t) : 이 값은 항상 0x02
  • Data Len (uint8_t) : payload 나머지 부분의 길이 → 0x15 (21 bytes)
  • 128-bit UUID (uint8_t[16]) : 128-bit 식별자
  • Major (uint16_t) : Major value
  • Minor (uint16_t) : Minor value
  • TX Power (uint8_t) : RSSI 기반으로 거리를 추정하기 위해 사용됨
 예를 들어 다음은 유효한 iBeacon payload의 값이다.


블루투스 표준에 의하면 Company Identifier가 Manufacturer Specific Data 필드 앞에 와야만 한다. 여기서 사용할 Apple의 company identifier는 0x004C 이다.

라즈베리 파이가 비콘 데이터를 보내게 하려면 몇가지 툴을 설치해 줘야만 한다. 여기서 핵심은 Bluez(리눅스 블루투스 스택)이고 부가적으로 libusb와 몇개의 helper library가 필요하다.

순서대로 필요한 툴들을 설치, bluez를 다운받아 컴파일 및 설치 해 주면 된다.

pi@raspberrypi ~ $ sudo apt-get install libusb-dev libdbus-1-dev libglib2.0-dev libudev-dev libical-dev libreadline-def

pi@raspberrypi ~ $ sudo mkdir bluez
pi@raspberrypi ~ $ cd bluez
pi@raspberrypi ~ $ sudo wget www.kernel.org/pub/linux/bluetooth/bluez-5.11.tar.xz
 
pi@raspberrypi ~ $ sudo unzx bluez-5.11.tar.xz 
pi@raspberrypi ~ $ sudo tar xvf bluez-5.11.tar
pi@raspberrypi ~ $ cd bluez-5.11
pi@raspberrypi ~ $ sudo ./configure --disable-systemd
pi@raspberrypi ~ $ sudo make
pi@raspberrypi ~ $ sudo make install



설치가 완료되었으면 블루투스 동글을 꼽고 재부팅 시켜주면 된다.

재부팅이 완료되면 먼저 시스템에 설치된 디바이스 목록을 확인한다.


pi@raspberrypi ~ $ hciconfig
hci0:  Type: BR/EDR    Bus: USB
       BD Address: 00:1A:7D:DA:71:12 ACL MTU: 310:10 SCO MTU: 64:8
       DOWN
       RX bytes: 495 acl:0 sco:0 events:21 errors: 0
       TX bytes: 89 acl:0 sco:0 commands: 21 errors: 0
pi@raspberrypi ~ $

만일 모든것이 정상적으로 설정되어 있으면 위와 같이 hci 디바이스가 보이게 된다. 이제 다음 명령으로 디바이스를 활성화 시켜 준다. 또한 advertising을 할 때 문제를 일으킬 수도 있으므로 device scanning은 꺼 준다.

pi@raspberrypi ~ $ sudo hciconfig hci0 up 
pi@raspberrypi ~ $ sudo hciconfig hci0 leadv3
pi@raspberrypi ~ $ sudo hciconfig hci0 noscan

이제 다시 hciconfig 명령을 수행해 보면 'DOWN' 대신 'UP  RUNNING'이 보여야 한다.

pi@raspberrypi ~ $ hciconfig
hci0:  Type: BR/EDR    Bus: USB
       BD Address: 00:1A:7D:DA:71:12 ACL MTU: 310:10 SCO MTU: 64:8
       UP RUNNING
       RX bytes: 1008 acl:0 sco:0 events:45 errors: 0
       TX bytes: 236 acl:0 sco:0 commands: 45 errors: 0
pi@raspberrypi ~ $

이제 마지막으로 해 줘야 할 일은 advertising data에 beacon 값을 넣어주는 것이다. 다음과 같이 명령을 입력해 준다. (좀 길지만 한 줄에 다 입력해 준다.)


pi@raspberrypi ~ $ sudo tools/hcitool -i hci0 cmd 0x08 0x0008 1E 02 01 1A 1A 
FF 4C 00 02 15 E2 0A 39 F4 73 F5 4B C4 A1 2F 17 D1 AD 07 A9 61 00 00 00 00 C8 00

위의 값들 중에서 FF는 Manufacturer Specific Data의 시작을 알려 주고, 4C 00은 Apple의 Company ID(0x004C), 그리고 나머지 부분부터 C8까지가 beacon의 payload이다.

이제 라즈베리 파이는 비콘으로 동작하게 된다. 확인해 보려면 iOS7 이후 버젼을 OS를 사용하는 아이폰/아이패드/아이팟 터치 또는 안드로이드 폰에 beacon toolkit 또는 유사한 앱을 설치해 실행하면 된다.

앱을 시작해 listen 모드로 들어가면 아래와 유사한 화면을 볼 수 있을 것이다.



이제 동작하는건 확인했지만 라즈베리 파이를 부팅할 때 마다 매번 위의 명령을 입력하기 힘드니 스크립트를 사용하는것이 편하다.

pi@raspberrypi ~ $ cat /home/pi/beacon.sh

#!/bin/bash
# ref: http://www.theregister.co.uk/Print/2013/11/29/feature_diy_apple_ibeacons/
set -x
# inquiry local bluetooth device
#hcitool dev
export BLUETOOTH_DEVICE=hci0
#sudo hcitool -i hcix cmd <OGF> <OCF> <No. Significant Data Octets> <iBeacon Prefix> <UUID> <Major> <Minor> <Tx Power> <Placeholder Octets>

#OGF = Operation Group Field = Bluetooth Command Group = 0x08
#OCF = Operation Command Field = HCI_LE_Set_Advertising_Data = 0x0008
#No. Significant Data Octets (Max of 31) = 1E (Decimal 30)
#iBeacon Prefix (Always Fixed) = 02 01 1A 1A FF 4C 00 02 15

export OGF="0x08"
export OCF="0x0008"
export IBEACONPROFIX="1E 02 01 1A 1A FF 4C 00 02 15"

#uuidgen  could gerenate uuid
export UUID="4a 4e ce 60 7e b0 11 e4 b4 a9 08 00 20 0c 9a 66"

export MAJOR="00 02"
export MINOR="00 01"
export POWER="C5 00"


# initialize device
sudo hciconfig $BLUETOOTH_DEVICE up
# disable advertising
sudo hciconfig $BLUETOOTH_DEVICE noleadv
# stop the dongle looking for other Bluetooth devices
sudo hciconfig $BLUETOOTH_DEVICE noscan

sudo hciconfig $BLUETOOTH_DEVICE pscan


sudo hciconfig $BLUETOOTH_DEVICE leadv

# advertise
sudo hcitool -i $BLUETOOTH_DEVICE cmd 0x08 0x0008 $IBEACONPROFIX $UUID $MAJOR $MINOR $POWER
sudo hcitool -i $BLUETOOTH_DEVICE cmd 0x08 0x0006 A0 00 A0 00 00 00 00 00 00 00 00 00 00 07 00
sudo hcitool -i $BLUETOOTH_DEVICE cmd 0x08 0x000a 01

echo "complete"


pi@raspberrypi ~ $

/home/pi/beacon.sh 라는 파일을 만들어 위의 파란색 부분의 내용을 넣어주면 된다. 위의 스크립트 중에 hcitool cmd 0x08 0x0006 a0 00 a0 00 .. 에서 첫번째/두번째 바이트 (0xa0 0x00)은 minimum interval, 세번째/네번째 바이트(0xa0 0x00)은 maximum interval이다. 0xa0 0x00은 0x00a0 (big endian)이 되므로 10진수로는 160이 된다. BLE에서 시간 granularity는 0.625ms이므로 최소/최대 인터벌은 모두 160 * 0.625ms = 100ms가 된다. 즉 매 0.1초마다 한번씩 비콘 신호를 advertising하게 된다.

파일을 만들었으면 chmod 명령으로 실행 퍼미션을 추가해 준다.

pi@raspberrypi ~ $ chmod +x beacon.sh

이제부터는 ./beacon.sh 만 입력해 주면 라즈베리 파이가 비콘으로 동작한다.

pi@raspberrypi ~ $ ./beacon.sh
+ export BLUETOOTH_DEVICE=hci0
+ BLUETOOTH_DEVICE=hci0
+ export OGF=0x08
+ OGF=0x08
+ export OCF=0x0008
+ OCF=0x0008
+ export 'IBEACONPROFIX=1E 02 01 1A 1A FF 4C 00 02 15'
+ IBEACONPROFIX='1E 02 01 1A 1A FF 4C 00 02 15'
+ export 'UUID=4a 4e ce 60 7e b0 11 e4 b4 a9 08 00 20 0c 9a 66'
+ UUID='4a 4e ce 60 7e b0 11 e4 b4 a9 08 00 20 0c 9a 66'
+ export 'MAJOR=00 01'
+ MAJOR='00 01'
+ export 'MINOR=00 00'
+ MINOR='00 00'
+ export 'POWER=C5 00'
+ POWER='C5 00'
+ sudo hciconfig hci0 up
+ sudo hciconfig hci0 noleadv
+ sudo hciconfig hci0 noscan
+ sudo hciconfig hci0 pscan
+ sudo hciconfig hci0 leadv
+ sudo hcitool -i hci0 cmd 0x08 0x0008 1E 02 01 1A 1A FF 4C 00 02 15 4a 4e ce 60 7e b0 11 e4 b4 a9 08 00 20 0c 9a 66 00 01 00 00 C5 00
< HCI Command: ogf 0x08, ocf 0x0008, plen 32
  1E 02 01 1A 1A FF 4C 00 02 15 4A 4E CE 60 7E B0 11 E4 B4 A9
  08 00 20 0C 9A 66 00 01 00 00 C5 00
> HCI Event: 0x0e plen 4
  01 08 20 00
+ sudo hcitool -i hci0 cmd 0x08 0x0006 A0 00 A0 00 00 00 00 00 00 00 00 00 00 07 00
< HCI Command: ogf 0x08, ocf 0x0006, plen 15
  A0 00 A0 00 00 00 00 00 00 00 00 00 00 07 00
> HCI Event: 0x0e plen 4
  01 06 20 0C
+ sudo hcitool -i hci0 cmd 0x08 0x000a 01
< HCI Command: ogf 0x08, ocf 0x000a, plen 1
  01
> HCI Event: 0x0e plen 4
  01 0A 20 0C
+ echo complete
complete 

pi@raspberrypi ~ $

이제 라즈베리 파이를 비콘으로 사용하려면 부팅될 때 마다 자동으로 위의 스크립트가 실행되도록 설정해 주면 된다.

라즈베리 파이에서 특정 프로그램이 부팅될 때 실행되게 하는 방법은 몇가지가 있지만 /etc/rc.local을 수정하는 방법 또는 cron을 사용하는 방법은 문제가 있다. 라즈베리 파이에서 블루투스가 활성화 된 이후에 위의 스크립트가 실행되어야만 하는데 두가지 방법은 스크립트가 먼저 실행되어 버리기 때문에 비콘으로 동작하지 못한다.

그러므로 systemd를 사용해 준다. 먼저 /lib/systemd/system 디렉토리에 두개의 파일을 만들어 준다. 각각 파일의 내용은 아래와 같다.

pi@raspberrypi ~ $ cat /lib/systemd/system/beacon.service
[Unit]
Description=Beacon service
After=bluetooth.target

[Service]
Type=forking
ExecStart=/home/pi/beacon.sh
ExecReload=/home/pi/beacon.sh

[Install]
WantedBy=bluetooth.target


pi@raspberrypi ~ $ cat /lib/systemd/system/beacon.target
#  This file is part of systemd.
#
#  systemd is free software; you can redistribute it and/or modify it
#  under the terms of the GNU Lesser General Public License as published by
#  the Free Software Foundation; either version 2.1 of the License, or
#  (at your option) any later version.

[Unit]
Description=Beacon
Documentation=man:systemd.special(7)
StopWhenUnneeded=yes


pi@raspberrypi ~ $ sudo systemctl daemon-reload
pi@raspberrypi ~ $ sudo systemctl enable beacon.service
Synchronizing state for beacon.service with sysvinit using update-rc.d...
Executing /usr/sbin/update-rc.d beacon defaults
Executing /usr/sbin/update-rc.d beacon enable


pi@raspberrypi ~ $

이제 시스템을 리부팅 하면 항상 자동으로 비콘이 시작된다.





아이폰에서 Beecon 앱으로 확인해 본 결과이다. 스크립트에서 설정한 UUID(4a4e......9a66) , Major(2), Minor(1) 를 확인할 수 있다.






2016년 5월 9일 월요일

슈퍼캡을 사용해 라즈베리 파이용 간이 UPS 만들기

아두이노같이 OS/파일 시스템이 없는 경우는 갑자기 전원이 끊어져도 크게 망가질 부분은 없다. 하지만 라즈베리 파이의 경우 SD 카드에 파일시스템을 관리하고 있기 때문에 정상적으로 shutdown을 하지 않고 갑자기 전원이 나가는 경우 최악의 경우 파일시스템이 망가져 다음번에 정상 부팅이 안 될 가능성이 있다.

그래서 서버를 운영하는데 필수장비 중에 하나가 UPS(Uninterruptible Power Supply)이다.



 내부 동작 원리는 다음과 같다.


정상적으로 전기가 공급되고 있으면 배터리를 충전하면서 동시에 UPS에 연결되어 있는 장비에 전원을 공급해 준다. 하지만 정전이 되면 배터리에 충전되어 있는 전기를 이용해 장비들에 전원을 공급해주게 된다.

정전이 되면 매우 짧은 시간에 배터리에서 전원이 공급되므로 연결되어 있는 장비들은 끊어지지 않고 계속 전력을 공급받을 수 있게 된다. 물론 배터리에 무한정의 전기를 저장할 수 없으므로 짧은 시간 (서버등이 정상적으로 shutdown을 할 수 있는 시간 정도)만 전원을 공급해 줄 수 있다. 그러므로 위의 그림에는 생략되어 있지만 USB 또는 시리얼 통신 등을 통해 정전이 되었다는걸 서버에 알려 줘 안전하게 shutdown 할 수 있게 해 주는 기능을 가지고 있다.

위와 같은 일반적인 상용 UPS는 무겁고 가격도 비싸며 처음 배터리에 충전되는데에도 꽤 시간이 걸린다.

그래서 라즈베리 파이에 사용할 수 있도록 배터리 대신 슈퍼 캐패시터를 사용해 간이 UPS를 만들어 보았다. 10F  용량의 슈퍼캐패시터를 사용했기 때문에 라즈베리 파이 3의 경우 대략 약 1분 정도의 시간동안 전력을 공급해 줄 수 있다.

회로 구성은 다음과 같다.

회로도는 아래에 있다. 대부분의 슈퍼캡은 내압이 2.7V이기 때문에 내압을 5V 이상으로 올리기 위해 두개의 슈퍼캡을 직렬로 연결해 주었다.

PowerIn 쪽에 5V 전원 아답터를 연결해 주고 PowerOut 쪽을 라즈베리 파이의 전원 커넥터에 연결해 주면 된다. 그리고 PwrFail 커넥터(J3)의 +3V3은 라즈베리 파이 커넥터의 17번 핀, PwrFail은 커넥터의 11번 핀(GPIO17)에 연결해 주면 된다.

정상적으로 전원이 공급되면 PwrFail 신호가 'H'를 유지하지만 전원 공급이 끊어지고 슈퍼 캐패시터에서 전원이 공급되면 PwrFail 신호가 'L'로 바뀌게 된다. 그러므로 라즈베리 파이에서 GPIO17번 핀을 감시하다 'L'가 되면 곧바로 shutdown 명령을 수행하면 정전이 되더라도 정상 종료가 되어 파일 시스템이 망가질 일이 없어진다.


먼저 동작 테스트를 위해 위의 회로를 간단하게 만능기판 위에 꾸며 보았다. 위의 사진에서 아래쪽의 작은 LED가 PwrFail 에 연결된 LED로 전원이 공급되고 있으면 불이 켜지고, 전원이 끊어지면 불이 꺼지게 된다. 큰 LED와 DC 모터는 PowerOut에 연결해 놓은 부하(load)이다. 여기서는 라즈베리 파이 대신 전력을 사용하게 된다.


위의 동작 비디오를 보면 처음에는 작은 LED에 빨간 색 불이 켜져 있다. 즉 전원이 공급되고 있다는 것이다. 전원이 공급되고 있으니 당연히 큰 LED에 불이 들어오고 모터가 돌고 있다. 하지만 약 17초 정도쯤에서 빨간색 LED가 꺼진다. 즉 전원 공급이 중단 된 것이다. 하지만 파란색 LED는 계속 켜져 있고 모터도 계속 돌고 있다. 이 시점에서는 외부에서 전원 공급은 없지만 슈퍼 캐패시터에 충전되어 있는 전기로 출력 단자 쪽으로 전기를 공급하고 있는 것이다. 비디오는 짧게 끊었지만 약 3~4분 정도까지 모터가 회전을 지속하였다.

이제 회로 동작 검증은 끝났고 다음번 포스트에서는 직접 라즈베리 파이에 연결한 다음 전원 공급이 끊어질 때 부터 어느 정도까지 버틸 수 있는지 테스트 해 볼 것이다.

2016년 4월 28일 목요일

맥에서 라즈베리 파이 3 사용하기 (Start using Raspberry Pi 3 in Mac OS X)

라즈베리 파이를 사용하려면 먼저 micro SD 카드에 OS를 설치해 줘야 한다. 라즈베리 파이에 사용할 수 있는 OS들은 https://www.raspberrypi.org에서 다운받을 수 있다.


위의 DOWNLOADS를 선택해 준다.


사용할 수 있는 OS는 몇 가지 있지만 여기서는 가장 많이 사용되는 Raspbian을 사용한다.


현재 Raspbian은 Jessie 버젼이 최신이다. ZIP 파일을 다운받는다.


다운 받은 ZIP 파일 압축을 해제하면 위와 같은 img 파일이 생성된다. 이 img 파일을 microSD 카드에 써 주면 되는 것이다.


microSD 카드를 맥에 연결한 다음 먼저 터미널 창을 열고 'diskutil list' 명령을 입력해서 microSD 카드가 어떤 디바이스 이름으로 식별되었나 확인해 준다. 위의 츌력 결과를 보면 여기서는 16GB microSD 카드를 사용하고 있으므로 '/dev/disk3' 라는 이름으로 식별되었다는걸 알 수 있다.


이제 microSD 카드를 unmount 시켜 준다. 'diskutil unmountDisk /dev/disk3' 와 같이 빨간색 부분은 위에서 확인한 microSD 카드의 디바이스명을 쳐 줘야 한다.


microSD 카드가 unmount 되었으면 이제 이미지를 microSD 카드에 써 준다. 'sudo dd bs=1m if=~/Desktop/2016-03-18-raspbian-jessie.img of=/dev/rdisk3' 에서 빨간색 부분은 img 파일의 위치이고 파란색 부분은 microSD 카드의 디바이스 명인데 위와 달리 '/dev/disk3' 가 아니고 '/dev/rdisk3'를 사용해 줘야 한다. 명령을 입력하면 루트로 실행하기 위해 암호를 물어본다. 자신의 맥 계정의 암호를 입력해 주면 된다. 명령이 실행되면 microSD카드에 기록하는데 상당한 시간이 걸린다. (약 10분 가량) 화면에 아무 변화가 없지만 실행되고 있는 것이므로 실행이 끝날 때 까지 가만히 놔두면 된다.

이제 OS가 준비되었는데 라즈베리 파이에 키보드/마우스/모니터를 연결해 사용하는 경우는 OS가 부팅된 후에 네트웍 설정을 변경해 주면 되지만 키보드/마우스/모니터 없이 headless 환경에서 사용하는 경우라면 처음에 ssh로 접속을 해야만 하는데 DHCP로 IP를 받아오면 ssh로 접속할 주소를 확인하기가 힘들다. 그래서 처음부터 부팅할 때 IP 주소를 static으로 설정을 변경해 주면 편하다.


microSD카드를 보면 cmdline.txt 라는 파일이 있다. 이 파일의 내용을 수정해 주면 된다.


파일을 열어 보면 기본적으로 위와 같은 내용이 들어 있다.


파일의 맨 뒷 부분에 위와 같이 자신이 원하는 라즈베리파이의 IP 주소를 추가해 주고 파일을 저장한다.

이제 맥에서 microSD 카드를 뽑아 라즈베리 파이에 넣어주고 케이블들을 연결한 다음 전원을 공급해 부팅 시키면 된다.





위의 비디오에서 볼 수 있는 것 처럼 라즈베리 파이3의 경우 부팅이 완료될 때 까지 대략 40초 정도가 소요된다.

이제 맥의 터미널에서 ssh로 라즈베리에 접속해 본다.


디폴트 유저명은 'pi' 이고 암호는 'raspberry' 이다. 그러므로 'ssh pi@192.168.145.10' 명령으로 연결하면 된다. 물론 명령에서 ip 주소(여기서는 '192.168.145.10')는 각자 자신이 설정해 준 주소로 바꿔줘야 한다.

맨 처음 접속하는 것이므로 라즈베리 파이의 authenticity가 확인되지 않으므로 믿을 수 있는 서버인가를 확인하는 메시지가 나온다. 여기에 'yes'를 입력해 주면 된다. 처음 한번만 해 주면 다음번 부터는 다시 나타나지 않는다. 그리고 암호(여기서는 초기 디폴트 암호인 'raspberry')를 넣어주면 된다. 암호는 연결되자 마자 바로 바꿔주는게 좋다.



접속되면 'sudo raspi-config' 명령을 입력해 설정을 변경해 준다.



프로그램에서 먼저 '1 Expand Filesystem'을 선택해서 엔터를 누른다. 그럼 몇가지가 실행된 후 라즈베리 파이가 리부팅 된다. 리부팅 되면 ssh 연결이 끊어지게 되므로 조금 후 다시 연결해 주면 된다.





그리고 'sudo apt-get update', 'sudo apt-get dist-upgrade', 'sudo rpi-update' 명령을 순서대로 실행해서 설치된 패키지와 라즈베리 파이 펌웨어를 최신으로 업그레이드 해 주면 된다.