라즈베리 파이에서 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)
2019년 12월 12일 목요일
2018년 7월 12일 목요일
ESP32에서 JSON 파싱
ESP32에서 ArduinoJson 라이브러리를 사용해 JSON 메시지를 파싱 해 보겠다.
먼저 ArduinoJson 라이브러리가 설치되어 있어야 한다.
만일 설치되어 있지 않으면 메뉴의 스케치->라이브러리 포함하기->라이브러리 관리...를 선택해 준다.
라이브러리 메니저 창이 열리면 1번 부분에 ArduinoJson 을 입력하면 해당 키워드를 가진 라이브러리 목록이 보인다. 목록중에 ArduinoJson을 찾아 그 부분을 클릭하면 3과 같이 라이브러리 우측 하단에 '설치' 버튼이 나타난다. '설치' 버튼을 눌러 설치해주면 된다. 설치가 완료되면 '닫기' 버튼을 눌러준다.
이제 라이브러리를 가지고 실제 코드를 작성한다.
가장 먼저 ArduinoJson 라이브러리를 include 해 줘야 한다. 또한 loop 함수에서 실제 파싱을 수행할 것이므로 결과를 확인하기 위해 시리얼 포트를 열어준다.
#include <ArduinoJson.h>
void setup()
{
Serial.begin(115200);
Serial.println("JSON parsing using ArduinoJson library on ESP32");
}
여기서는 네트웍에서 JSON 데이터를 불러오지 않고 하드코딩 된 JSON 메시지를 파싱 해 볼 것이기 때문에 이를 위한 변수를 선언해 준다. 여기에서 '\' 은 메지시 내에 들어가는 따옴표(")에 대한 escape character이다. JSON의 name에 따옴표(")가 있어야만 하기 때문에 필요하다.
char JSONMsg[] = " {\"SensorType\", \"Temperature\", \"Value\", 10}";
Escape character가 없는 JSON 메시지의 구조는 다음과 같다.
{
"SensorType" : "Temperature",
"Value" : 10
}
주의) JSON 파서는 스트링 내용을 수정하기 때문에 내용을 재사용 할 수 없다. 그래서 여기서도 JSONMsg 변수를 전역변수가 아니고 loop 내에서 선언해 loop 함수가 끝나면 free 되고, 다음번에 다시 loop() 함수가 호출될 때 다시 할당되도록 해 놓았다.
또한 파싱한 결과인 JSON object tree를 저장하기 위해 미리 할당된 메모리 풀을 위해 StaticJsonBuffer 클래스 오브젝트를 선언했다. 이 오브젝트는 메모리 풀이기 때문에 크기를 지정해 줘야 한다. 크기는 템플릿 파라미터(코드에서 < > 사이의 값)에 바이트 단위로 지정해 주면 된다. 여기서는 300바이트를 지정했는데 이 정도면 위의 JSON 메시지를 파싱하는데는 충분하다.
StaticJsonBuffer<300> JSONBuffer; // memory pool for parsed JSON object tree
다음으로는 StaticJsonBuffer 오브젝트의 parseObject 메소드를 호출하면서 파싱하길 원하는 JSON 스트링을 넘겨준다.
JsonObject &parsed = JSONBuffer.parseObject(JSONMsg);
에러 없이 파싱이 끝났는지 확인하기 위해 JSONObject의 success 메소드를 호출할 수 있다.
if (!parsed.success()) { // check for errors in parsing
Serial.println("Parsing failed!");
delay(5000);
return;
}
이제 JsonObject에서 이름으로 그에 해당하는 값을 가져오기 위해 subscript operator를 사용할 수 있다. 즉 key에 해당하는 값을 가져오기 위해 원하는 파라미터의 key 이름을 [ ]으로 둘러싸면 된다. 이 예제에서 사용하고 있는 key 이름은 "SensorType"과 "Value" 두 가지다.
const char *sensorType = parsed["SensorType"]; // get sensor type value
int value = parsed["Value"] // get value of the sensor measurement
전체 코드는 다음과 같다.
위의 코드를 ESP32에 다운로드 해서 실행하고 시리얼모니터를 열면 다음과 같은 결과가 출력 될 것이다.
JSON parsing using ArduinoJson library on ESP32
Parsing start:
Sensor type: Temperature
Sensor value: 10
Parsing start:
Sensor type: Temperature
Sensor value: 10
Parsing start:
Sensor type: Temperature
Sensor value: 10
...
먼저 ArduinoJson 라이브러리가 설치되어 있어야 한다.
만일 설치되어 있지 않으면 메뉴의 스케치->라이브러리 포함하기->라이브러리 관리...를 선택해 준다.
라이브러리 메니저 창이 열리면 1번 부분에 ArduinoJson 을 입력하면 해당 키워드를 가진 라이브러리 목록이 보인다. 목록중에 ArduinoJson을 찾아 그 부분을 클릭하면 3과 같이 라이브러리 우측 하단에 '설치' 버튼이 나타난다. '설치' 버튼을 눌러 설치해주면 된다. 설치가 완료되면 '닫기' 버튼을 눌러준다.
이제 라이브러리를 가지고 실제 코드를 작성한다.
가장 먼저 ArduinoJson 라이브러리를 include 해 줘야 한다. 또한 loop 함수에서 실제 파싱을 수행할 것이므로 결과를 확인하기 위해 시리얼 포트를 열어준다.
#include <ArduinoJson.h>
void setup()
{
Serial.begin(115200);
Serial.println("JSON parsing using ArduinoJson library on ESP32");
}
여기서는 네트웍에서 JSON 데이터를 불러오지 않고 하드코딩 된 JSON 메시지를 파싱 해 볼 것이기 때문에 이를 위한 변수를 선언해 준다. 여기에서 '\' 은 메지시 내에 들어가는 따옴표(")에 대한 escape character이다. JSON의 name에 따옴표(")가 있어야만 하기 때문에 필요하다.
char JSONMsg[] = " {\"SensorType\", \"Temperature\", \"Value\", 10}";
Escape character가 없는 JSON 메시지의 구조는 다음과 같다.
{
"SensorType" : "Temperature",
"Value" : 10
}
주의) JSON 파서는 스트링 내용을 수정하기 때문에 내용을 재사용 할 수 없다. 그래서 여기서도 JSONMsg 변수를 전역변수가 아니고 loop 내에서 선언해 loop 함수가 끝나면 free 되고, 다음번에 다시 loop() 함수가 호출될 때 다시 할당되도록 해 놓았다.
또한 파싱한 결과인 JSON object tree를 저장하기 위해 미리 할당된 메모리 풀을 위해 StaticJsonBuffer 클래스 오브젝트를 선언했다. 이 오브젝트는 메모리 풀이기 때문에 크기를 지정해 줘야 한다. 크기는 템플릿 파라미터(코드에서 < > 사이의 값)에 바이트 단위로 지정해 주면 된다. 여기서는 300바이트를 지정했는데 이 정도면 위의 JSON 메시지를 파싱하는데는 충분하다.
StaticJsonBuffer<300> JSONBuffer; // memory pool for parsed JSON object tree
다음으로는 StaticJsonBuffer 오브젝트의 parseObject 메소드를 호출하면서 파싱하길 원하는 JSON 스트링을 넘겨준다.
JsonObject &parsed = JSONBuffer.parseObject(JSONMsg);
에러 없이 파싱이 끝났는지 확인하기 위해 JSONObject의 success 메소드를 호출할 수 있다.
if (!parsed.success()) { // check for errors in parsing
Serial.println("Parsing failed!");
delay(5000);
return;
}
이제 JsonObject에서 이름으로 그에 해당하는 값을 가져오기 위해 subscript operator를 사용할 수 있다. 즉 key에 해당하는 값을 가져오기 위해 원하는 파라미터의 key 이름을 [ ]으로 둘러싸면 된다. 이 예제에서 사용하고 있는 key 이름은 "SensorType"과 "Value" 두 가지다.
const char *sensorType = parsed["SensorType"]; // get sensor type value
int value = parsed["Value"] // get value of the sensor measurement
전체 코드는 다음과 같다.
#include <ArduinoJson.h>void setup() { Serial.begin(115200); Serial.println("JSON parsing using ArduinoJson library on ESP32");}void loop() {Serial.println("Parsing start: ");char JSONMsg[] = " {\"SensorType\": \"Temperature\", \"Value\": 10}"; //Original message StaticJsonBuffer<300> JSONBuffer; //Memory pool JsonObject& parsed = JSONBuffer.parseObject(JSONMsg); //Parse message if (!parsed.success()) { //Check for errors in parsing Serial.println("Parsing failed!"); delay(5000); return; } const char * sensorType = parsed["SensorType"]; //Get sensor type value int value = parsed["Value"]; //Get value of sensor measurement Serial.print("Sensor type: "); Serial.println(sensorType); Serial.print("Sensor value: "); Serial.println(value); Serial.println(); delay(5000);}위의 코드를 ESP32에 다운로드 해서 실행하고 시리얼모니터를 열면 다음과 같은 결과가 출력 될 것이다.
JSON parsing using ArduinoJson library on ESP32
Parsing start:
Sensor type: Temperature
Sensor value: 10
Parsing start:
Sensor type: Temperature
Sensor value: 10
Parsing start:
Sensor type: Temperature
Sensor value: 10
...
Labels:
arduino,
ArduinoJson,
ESP32,
JSON,
JSON 파싱,
JSON parsing,
REST
피드 구독하기:
글 (Atom)










