week__15 (NodeRed 및 ArduinoJson) - JINEEH/SmartDevice_JinHee GitHub Wiki
NodeRed 및 ArduinoJson 활용
1. 학습목표
1-1. NodeRed 설치 및 활용
1-2. ArduinoJson 라이브러리 사용 및 토양 습도 센서 실습
2. NodeRed
2-1. 의미: 하드웨어 장치들, API, 온라인 서비스를 IoT의 일부로 와이어링시키기 위해 IBM이 개발한 시각 프로그래밍을 위한 플로 기반 개발 도구
- 노드를 기본으로 하며 각 노드를 선으로 연결하여 플로우를 만드는 방식
- 노드는 특정 기능을 수행하는 프로그램 묶음 => 플로우
2-2. 특징: 프로토타입을 빠른 시간 내에 만들어서 기능/서비스를 구현 가능함
- IoT 와이어링을 위해 데이터들은 가시화 과정 필요(GUI 기능)
- 브라우저 기반 플로 편집기를 제공 및 자바스크립트 함수 개발 가능
- 컨트롤러를 구현하는데 가장 적합한 개발 환경임
- 기본 제공 외에 오픈소스로 제공되는 노드 => 노드의 확장성(2020년 기준 약 2,600여개 이상)
- 로우코드(Low-code) 기반 개발 플랫폼 => 코드를 적게 작성하고도 응용 시스템을 빠르게 개발 가능
- 개인이나 소규모 조직에서 IoT 기기 제어용으로 사용하는 경우에는 라즈베리 파이에 설치하는 것이 좋음 => 전력 사용량 및 비용을 고려했을 때 라즈베리 파이가 합리적
2-3. 작동환경: 대부분의 OS에서 작동 가능함
- Node.js: 인터넷 브라우저인 크롬의 자바스크립트 런타임 엔진을 일반적인 컴퓨터에서 작동 가능
- Node-RED는 Node.js 환경에서 작동함
- Node.js 설치 후 Node-RED 설치 순서로 진행
- Node-RED 편집기와 Node-RED 대시보드는 인터넷 브라우저를 사용하여 접속 가능
3. Node-Red 활용
3-1. Node-RED 설치 방법
- [Node-RED 설치](https://nodejs.org/en) 에 접속하여 윈도우용 Node-RED 다운로드
- 다운로드 후 cmd 창에 'npm install -g --unsafe-perm node-red' 입력하여 설치
- 설치 후 cmd 창에 'npm install node-red-dashboard' 입력하여 대시보드 설치 또는 Node-RED 접속 후 직접 설치 가능
- cmd 창에 'node-red' 입력하여 실행
- 주소창에 localhost:1880 입력하여 Node-RED 실행 가능
Node-RED에서 직접 대시보드 설치하는 방법
node-red-dashboard 검색하여 다운로드
Node-RED를 사용한 간단한 예제
inject와 debug 사용('안녕하세요!' 입력 후 debug 통해 출력 확인)
4. Json(JavaScript Object Notation)
4-1. 의미: 데이터 전송 및 교환을 위한 표준화된 텍스트 형식
- 전송하고자 하는 데이터를 속성과 값으로 나열하여 표현
4-2. 특징: 웹 브라우저와 웹 서버간의 데이터를 교환할 때 사용
- XML에 비해 가볍고 용량을 적게 차지함
- 자바스크립트 형식을 따름
5. ArduinoJson 라이브러리 설치 및 실습
5-1. Json 문자열의 데이터 파싱 후의 값 출력
5-2. Json 데이터의 길이가 길어지거나 사용할 키 값의 종류가 많아지면 코드가 복잡해지고 가독성이 떨어짐
=> 해결방안: ArduinoJson 라이브러리를 사용하여 버퍼 사이즈 확인 및 불필요한 크기 사용 X
Arduino IDE에서 [Arduinojson] 설치
Arduino Json 에서 Processor, Mode, Input type 설정(JSON 디코딩)
Processor: ESP32
Mode: Deserialize
Input type: char[N]
Deserialize(역직렬화): 문자열을 자바스크립트 객체로 변환, 저수준의 형식을 읽을 수 있는 객체나 기타 데이터 구조로 변환
권장 버퍼 크기 확인
권장 버퍼 사이즈 96으로 확인 가능함
실행코드(json(1) 코드 - 크기 할당 96으로 변경해야 업로드 가능)
#include <ArduinoJson.h>
void setup() {
// Initialize serial port
Serial.begin(115200);
while (!Serial) continue;
delay(3000);
// JSON 문서 할당
//
// 괄호 안의 200은 메모리 풀의 용량(바이트)입니다.
// JSON 문서와 일치하도록 이 값을 변경하는 것을 잊지 마십시오.
// https://arduinojson.org/v6/assistant 를 사용하여 용량을 계산합니다.
StaticJsonDocument<96> doc; //크기가 96바이트인 JSON 문서 생성
// StaticJsonDocument<N>은 스택에 메모리를 할당합니다.
// 힙에 할당하는 DynamicJsonDocument로 대체할 수 있습니다.
// DynamicJsonDocument doc(200);
// JSON 입력 문자열.
char json[] = R"rawliteral({
"token_type":"bearer",
"access_token":"c281d73b097",
"expires_in":43199,
"refresh_token":"0a0c90af08f",
"refresh_token_expires_in":5184000,
"scope":"account_email profile"
})rawliteral";
Serial.println(json);
// Deserialize the JSON document
DeserializationError error = deserializeJson(doc, json);
// Test if parsing succeeds.
if (error) {
Serial.print(F("deserializeJson() failed: "));
Serial.println(error.f_str());
return;
}
// Fetch values.
const char* access_token = doc["access_token"];
const char* refresh_token = doc["refresh_token"];
long expires_in = doc["expires_in"];
// Print values.
Serial.print("Access token : ");
Serial.println(access_token);
Serial.print("Refresh token : ");
Serial.println(refresh_token);
Serial.print("Expire time : ");
Serial.println(expires_in);
}
void loop() {
// not used in this example
}
실습 결과
시리얼 모니터를 통해 출력 값 확인
Arduino Json 에서 Processor, Mode, Input type 설정(JSON 인코딩)
Processor: ESP32
Mode: Serialize
Input type: char[N]
Serialize(직렬화): 컴퓨터 메모리 상에 존재하는 객체를 문자열로 변환, 객체나 데이터 구조를 네트워크나 저장소를 통한 전송에 적합한 형식으로 변환
권장 버퍼 크기 확인
권장 버퍼 사이즈 192로 확인 가능함
실행코드(json(2) 코드 - 크기 할당 192로 변경해야 업로드 가능)
#include <ArduinoJson.h>
void setup() {
// Initialize Serial port
Serial.begin(115200);
while (!Serial) continue;
delay(3000);
// JSON 문서 할당
//
// 괄호 안의 200은 이 문서에 할당된 RAM입니다.
// 요구 사항에 맞게 이 값을 변경하는 것을 잊지 마십시오.
// https://arduinojson.org/v6/assistant를 사용하여 용량을 계산합니다.
StaticJsonDocument<192> doc;
// StaticJsonObject는 스택에 메모리를 할당하고,
// 힙에 할당하는 DynamicJsonDocument로 대체할 수 있습니다.
//
// DynamicJsonDocument doc(200);
// 문서에 값 추가
doc["object_type"] = "text";
doc["text"] = "텍스트 영역입니다. 최대 " + String(200)+ "자 표시 가능합니다.";
doc["link"]["web_url"] = "https://developers.kakao.com";
doc["link"]["mobile_web_url"] = "https://developers.kakao.com";
doc["button_title"] = "바로 확인";
String jsonString ;
String jsonPretyString ;
// 최소화된 JSON을 생성하여 직렬 포트로 보냅니다.
// 객체를 JSON 문자열로 직렬화하여 jsonString에 저장
serializeJson(doc, jsonString);
Serial.println(jsonString);
// The above line prints:
/*
{ "object_type": "text", "text": "텍스트 영역입니다. 최대 200자 표시 가능합니다.", "link": { "web_url": "https://developers.kakao.com", "mobile_web_url": "https://developers.kakao.com" }, "button_title": "바로 확인" }
*/
// Start a new line
Serial.println();
// prettified JSON을 생성하고 직렬 포트로 보냅니다.
//
serializeJsonPretty(doc, jsonPretyString);
Serial.println(jsonPretyString);
/*
{
"object_type": "text",
"text": "텍스트 영역입니다. 최대 200자 표시 가능합니다.",
"link": {
"web_url": "https://developers.kakao.com",
"mobile_web_url": "https://developers.kakao.com"
},
"button_title": "바로 확인"
}
*/
}
void loop() {
// not used in this example
}
실습 결과
최소화된 JSON 문자열을 시리얼 모니터를 통해 출력 값 확인
6. 토양 습도 센서 실습
6-1. JSON 사용하여 14주차 실습 수정
6-2. 실습 준비물: ESP32, ESP32 확장 실드, 토양 습도 센서, 점퍼 케이블
6-3. 센서 값 해석: 0 ~1500 건조한 토양 / 1500~2000 정상 / 2000이상 물 속
실행코드
시간 변경하여 30분(1800초) 간격으로 전송
#include <WiFi.h>
#include <HTTPClient.h>
#include <ArduinoJson.h>
const char* ssid = "zineeh"; // 사용하는 WiFi 네트워크 이름 (SSID)
const char* password = "SMARTDEVICE"; // 사용하는 WiFi 네트워크 비밀번호
const String rest_api_key = "";
String access_token = "";
String refresh_token = "";
#define MsgSendInterval 1800 // 30분 단위로 임의 변경
long timeout = 3600; //시간을 초로 나타냄
int sensorValue = 0;
int sensorPin = 34;
void setup() {
Serial.begin(115200);
WiFi.begin(ssid, password);
Serial.println("Connecting");
while (WiFi.status() != WL_CONNECTED) {
Serial.print(".");
delay(1000);
}
Serial.print("\nConnected to WiFi : ");
Serial.println(WiFi.localIP());
}
void loop() {
if (timeout++ > MsgSendInterval) //
{
if (isAccessTokenExpired() == true) {
if (update_access_token() == false) {
Serial.println("Access token update failed");
}
}
sensorValue = analogRead(sensorPin);
send_message();
timeout = 0;
}
delay(1000);
}
// str문자열에서 start_string와 end_string사이의 문자열을 추출하는 함수
/*
String extract_string(String str, String start_string, String end_string)
{
int index1 = str.indexOf(start_string) + start_string.length();
int index2 = str.indexOf(end_string, index1);
String value = str.substring(index1, index2);
return value;
}
*/
bool isAccessTokenExpired() {
HTTPClient http;
bool returnVal = true;
StaticJsonDocument<100> doc;
/*
curl -v -X GET "https://kapi.kakao.com/v1/user/access_token_info" \
-H "Authorization: Bearer ${ACCESS_TOKEN}"
*/
if (!http.begin("https://kapi.kakao.com/v1/user/access_token_info")) {
Serial.println("\nfailed to begin http\n");
}
http.addHeader("Authorization", "Bearer " + access_token);
int httpCode = http.GET();
// httpCode will be negative on error
if (httpCode > 0) {
// file found at server
if (httpCode == HTTP_CODE_OK) {
String payload = http.getString();
Serial.println(payload);
// Deserialize the JSON document
DeserializationError error = deserializeJson(doc, payload);
long expire_time = doc["expires_in"];
Serial.println(expire_time);
if (expire_time > 0) {
returnVal = false;
} else {
returnVal = true;
}
}
} else {
Serial.printf("[HTTP] GET... failed, error: %s\n",
http.errorToString(httpCode).c_str());
}
http.end();
return returnVal;
}
void send_message() {
HTTPClient http;
StaticJsonDocument<300> doc;
String url = "https://kapi.kakao.com/v2/api/talk/memo/default/send";
if (!http.begin(url)) {
Serial.println("\nfailed to begin http\n");
}
http.addHeader("Authorization", "Bearer " + access_token);
http.addHeader("Content-Type", "application/x-www-form-urlencoded");
int http_code;
/* 카카오톡 default 템플릿
template_object={
"object_type": "text",
"text": "텍스트 영역입니다. 최대 200자 표시 가능합니다.",
"link": {
"web_url": "https://developers.kakao.com",
"mobile_web_url": "https://developers.kakao.com"
},
"button_title": "바로 확인"
}
*/
doc["object_type"] = "text";
doc["text"] = "토양 센서 값 :" + String(sensorValue);
//doc["link"]["web_url"] = "https://www.naver.com";
JsonObject obj = doc.createNestedObject();
doc["link"] = obj;
String data;
// Generate the minified JSON and send it to the Serial port.
//
serializeJson(doc, data);
Serial.println(data);
http_code = http.POST("template_object=" + data);
Serial.print("HTTP Response code: ");
Serial.println(http_code);
String response;
if (http_code > 0) {
response = http.getString();
Serial.println(response);
}
http.end();
}
/*
curl -v -X POST "https://kauth.kakao.com/oauth/token" \
-H "Content-Type: application/x-www-form-urlencoded" \
-d "grant_type=refresh_token" \
-d "client_id=${REST_API_KEY}" \
-d "refresh_token=${USER_REFRESH_TOKEN}"
*/
bool update_access_token() {
HTTPClient http;
bool retVal = false;
StaticJsonDocument<400> doc;
String url = "https://kauth.kakao.com/oauth/token";
String nrefresh_token = "";
if (!http.begin(url)) {
Serial.println("\nfailed to begin http\n");
}
http.addHeader("Content-Type", "application/x-www-form-urlencoded");
int http_code;
String data = "grant_type=refresh_token&client_id=" + rest_api_key + "&refresh_token=" + refresh_token;
Serial.println(data);
http_code = http.POST(data);
Serial.print("HTTP Response code: ");
Serial.println(http_code);
String response;
if (http_code > 0) {
response = http.getString();
Serial.println(response);
// Deserialize the JSON document
DeserializationError error = deserializeJson(doc, response);
const char* atoken = doc["access_token"];
const char* rtoken = doc["refresh_token"];
Serial.print("Access token : ");
Serial.println(atoken);
Serial.print("New refresh token : ");
Serial.println(rtoken);
access_token = atoken;
//만료 1개월전부터 갱신되므로 data가 없을 수도 있음
if (rtoken != NULL) {
refresh_token = rtoken;
}
retVal = true;
} else {
retVal = false;
}
http.end();
return retVal;
}
실습 결과
첫번째 토양 센서 값이 1349로 건조한 토양에 속했으나, 토양에 물을 준 이후인 네번째 토양 센서 값(1786)으로 정상적인 토양 상태로 변화함