14주 차 토양 센서 데이터 모니터링 및 알림 시스템 구축 실습 - park-02/2024-1_Smart-Devices GitHub Wiki
- REST
- CRUD Operation
- REST API
- RESTful
- 옴의 범칙과 전압 분배 법칙
- 실습[1]
- 실습[2]
- 후기
- REST(Representational State Transfer)는 웹 서비스의 아키텍처 스타일로, 클라이언트와 서버 간의 상호작용을 규정하는 규칙 집합입니다.
- 자원(Resource) 기반:
- REST는 모든 것을 자원으로 간주하고, 각 자원은 고유한 URI(Uniform Resource Identifier)로 식별됩니다.
- 표현(Representation):
- 클라이언트는 자원을 요청할 때 자원의 상태를 표현하는 표현(Representation)을 받습니다.
- 클라이언트-서버 구조 (Client-Server Architecture):
- 클라이언트와 서버 간의 역할이 명확히 분리됩니다.
- 무상태 (Statelessness):
- 각 요청은 클라이언트의 세션 정보를 서버에 저장하지 않습니다.
- 캐시 처리 가능성 (Cacheability):
- 서버는 응답을 캐시할 수 있어야 합니다.
- 계층화 (Layered System):
- 클라이언트는 서버와 직접 통신하는 대신 중간 계층 (로드 밸런서, 프록시 등)을 통해 통신할 수 있습니다.
- 인터페이스 일관성 (Uniform Interface):
- 일관된 인터페이스를 통해 리소스에 접근하고 조작합니다.
-
장점
- 간단한 인터페이스: REST는 HTTP 프로토콜을 기반으로하므로, HTTP 메서드를 사용하여 간단하게 CRUD 작업을 수행할 수 있어 개발과 이해가 용이합니다.
- 유연성과 확장성: 리소스 기반의 아키텍처로, 서버와 클라이언트 간의 독립성을 유지하면서 시스템을 확장하고 변경할 수 있습니다.
- 가시성과 이해도: 각 리소스는 고유한 URI를 가지며, HTTP 메서드를 통해 조작할 수 있어 직관적이고 이해하기 쉽습니다.
-
단점
- 성능 문제: HTTP를 기반으로하기 때문에 불필요한 오버헤드가 발생할 수 있으며, 대량의 데이터를 처리하는 경우에 성능 문제가 발생할 수 있습니다.
- 보안 취약점: 보안 취약점을 내포하고 있을 수 있으며, 적절한 보안 메커니즘을 구현하지 않으면 보안 문제가 발생할 수 있습니다.
- API 설계의 어려움: 올바른 URI, HTTP 메서드, 상태 코드를 선택하는 것이 어려울 수 있으며, 잘못된 설계는 API 사용자에게 혼란을 줄 수 있습니다.
- CRUD는 데이터베이스 및 웹 서비스에서 가장 기본적인 데이터 조작 기능을 나타내는 약어로, Create(생성), Read(읽기), Update(갱신), Delete(삭제)의 네 가지 작업을 나타냅니다. 각 작업은 데이터의 다양한 조작을 가능하게 합니다.
- Create : 데이터 생성(POST)
- Read : 데이터 조회(GET)
- Update : 데이터 수정(PUT, PATCH)
- Delete : 데이터 삭제(DELETE)
REST API는 HTTP를 통해 클라이언트와 서버 간 통신을 위한 인터페이스로, URI를 사용하여 리소스를 나타내고, HTTP 메서드를 활용하여 해당 리소스에 대한 작업을 수행합니다. 이는 간결하고 표준화된 방식으로 서비스를 제공하며, 다양한 플랫폼 간의 상호 운용성을 촉진하여 개발 및 통합을 용이하게 합니다.
-
URI는 명사 형태: 동사보다는 명사를 사용하여 리소스를 식별합니다. 예를 들어, /run 대신 /running을 사용하지 않습니다.
-
슬래시 미포함: URI 마지막에 슬래시를 포함하지 않습니다. 예를 들어, /test 대신 /test/를 사용하지 않습니다.
-
하이픈 사용: 언더바 대신 하이픈을 사용하여 가독성을 높입니다. 예를 들어, /test-blog 대신 /test_blog를 사용하지 않습니다.
-
확장자 미포함: URI에 파일 확장자를 포함하지 않습니다. 예를 들어, /photo 대신 /photo.jpg를 사용하지 않습니다.
-
행위 미포함: URI에 행위를 나타내는 단어를 포함하지 않습니다. 예를 들어, /post/1은 삭제 동작을 나타내지 않고, 그냥 해당 게시물의 리소스를 식별합니다.
RESTful은 REST의 원칙을 따르는 시스템을 의미하는데, 모든 REST API가 RESTful하지는 않습니다. 올바른 RESTful 시스템은 REST API의 설계 규칙을 준수하며, 이를 통해 자원을 URI로 표현하고 CRUD 작업을 올바른 HTTP 메서드로 처리합니다. 이에 반해, 모든 CRUD 기능을 POST로 처리하거나 URI 규칙을 지키지 않는 API는 REST API를 사용하지만 RESTful하지 않은 시스템입니다.
- 옴의 법칙은 전압(V), 전류(I), 저항(R) 간의 관계를 설명합니다. 이 법칙은 다음과 같이 표현됩니다: V = I * R
- 여기서 V는 전압(Volt), I는 전류(Ampere), R은 저항(Ohm)을 나타냅니다. 즉, 전압은 전류와 저항의 곱에 비례합니다.
- 전압 분배 법칙은 병렬 회로에서 전압이 분배되는 방법을 설명합니다.
- 병렬 회로에서 각 분기점에서의 전압은 해당 분기점에 연결된 전체 저항 중 해당 분기점에 연결된 저항의 비율에 따라 나뉩니다. 즉, 전압 분배 법칙은 전압이 각 저항에 비례하여 분배된다는 원리를 설명합니다.
이러한 법칙들은 전기 회로에서 전압, 전류 및 저항의 상호 작용을 이해하고 회로를 설계하는 데 중요한 역할을 합니다.
이번 실습은 일정 시간 동안 토양 센서 값을 실시간으로 확인하는 실습입니다.
ESP32, ESP32 확장 실드, 토양 습도 센서
- 토양 센서
- 기능: 토양의 수분 함량을 측정합니다.
- 사용 목적: 식물이 필요로 하는 물의 양을 정확하게 공급하여 과도한 관개나 물 부족을 방지합니다.
const int soilSensorPin = 34;
void setup(){
Serial.begin(115200);
}
void loop(){
Serial.print("토양습도센서값:");
Serial.println(analogRead(soilSensorPin));
delay(1000);
}
-
토양 습도 센서 핀 정의: const int soilSensorPin = 34; // 토양 습도 센서가 연결된 핀을 34번으로 설정합니다.
-
설정 함수 (setup): void setup() { Serial.begin(115200); } // 시리얼 통신을 115200 보드레이트로 초기화합니다. 이를 통해 ESP32와 컴퓨터 간의 데이터 전송 속도를 설정합니다.
-
루프 함수 (loop):
void loop() { // 무한 반복되는 함수로, 이 함수 내의 코드가 지속적으로 실행됩니다.
Serial.print("토양습도센서값:"); // 시리얼 모니터에 "토양습도센서값:"이라는 문자열을 출력합니다.
Serial.println(analogRead(soilSensorPin)); // 토양 습도 센서 핀에서 아날로그 값을 읽고, 그 값을 시리얼 모니터에 출력합니다.
delay(1000); // 1초(1000 밀리초) 동안 대기합니다. 이를 통해 센서 값을 1초 간격으로 읽고 출력합니다.
- ESP32가 부팅되면 setup 함수가 한 번 실행되어 시리얼 통신을 초기화합니다.
- 이후 loop 함수가 무한히 반복되면서 다음 작업을 수행합니다:
- 토양 습도 센서에서 아날로그 값을 읽어옵니다.
- 읽어온 값을 시리얼 모니터에 출력합니다.
- 1초 동안 대기합니다.
- ESP32 보드를 컴퓨터에 연결합니다.
- Arduino IDE를 열고 해당 소스 코드를 컴파일하고 업로드합니다.
- 토양 센서 값이 일정한 시간 간격으로 시리얼 모니터에 전송되는 것을 확인할 수 있습니다.
이번 실습은 일정 시간 간격으로 토양 센서의 값을 읽고, 카카오톡 API를 통해 메시지를 전송하여 확인하는 실습입니다.
ESP32, ESP32 확장 실드, 토양 습도 센서
- https://developers.kakao.com/ 카카오톡 개발자 사이트에 가입합니다.
- 애플리케이션 추가합니다.
- 메뉴에 들어가서 앱키에 들어가서 REST API 키 복사합니다.
- 메뉴에 플랫폼에 들어가서 Web 플랫폼 등록합니다.
- 사이트 도메인에 "http:localhost" 등록합니다.
- 메뉴에 카카오 로그인에 들어가서 활성화 설정에 상태를 ON 합니다.
- Redirect URL에 “https://www.example.com/oauth” 를 저장합니다.
- "동의 항목” 메뉴 -> “카카오톡 메시지 전송” 항목 설정합니다.
- “선택 동의”로 선택 후 동의 목적을 적고 저장합니다.
- Redirect URI로 인가 코드(Authorization code) 전달받기 주소창에 https://kauth.kakao.com/oauth/authorize?response_type=code&client_id=??????????????????????&redirect_uri=https://www.example.com/oauth 붙여넣기 후에 ???부분에 REST_API 값을 넣은 뒤 브라우저 url에 입력하면 소셜 로그인 부분이 뜹니다. 이를 완료하면 주소창 상단에 인증 코드값을 받을 수 있습니다. 브라우져 주소창에서 code= 이후의 값을 복사해 둡니다.
- Refresh 토큰 및 Access 토큰 받기 cmd 창에 들어가서 curl -v -X POST "https://kauth.kakao.com/oauth/token" -H "Content-Type: application/x-www-form-urlencoded" -d "grant_type=authorization_code" -d "client_id=???" --data-urlencode "redirect_uri=https://www.example.com/oauth" -d "code=???" 붙여넣기 후에 "client_id=???"에는 REST_API 키 값 "code=???"에는 브라우져 주소창에 code= 이후의 복사한 값을 복사 붙여넣기 후 실행 후에
"access_token"이랑 "refresh_token"을 발급받을 수 있습니다. 발급받은 토큰은 소스코드에 사용됩니다.
#include <WiFi.h>
#include <HTTPClient.h>
const char *ssid = ".."; // 사용하는 WiFi 네트워크 이름 (SSID)
const char *password = "123456799"; // 사용하는 WiFi 네트워크 비밀번호
const String rest_api_key = ""; // API키
String access_token = ""; // cmd창 에서 받은 "access_token"
String refresh_token = ""; // cmd창 에서 받은 "refresh_token"
#define MsgSendInterval 3600 // 60 * 60 초, 즉 한시간 간격으로 전송
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) // 1시간(60 * 60)에 1번씩 전송
{
if (isAccessTokenExpired() == true) { //access token 만료 여부 확인
if (update_access_token() == false) { // access token 재발급
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;
/*
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);
String expireT = extract_string(payload, "\"expires_in\":", ",");
Serial.println(expireT.toInt());
if (expireT.toInt() > 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;
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;
/*
template_object={
"object_type": "text",
"text": "텍스트 영역입니다. 최대 200자 표시 가능합니다.",
"link": {
"web_url": "https://developers.kakao.com",
"mobile_web_url": "https://developers.kakao.com"
},
"button_title": "바로 확인"
}
*/
String data = String("template_object={") +
String("\"object_type\": \"text\",") +
String("\"text\": \"") + String("토양 센서 값 :") +
String(sensorValue) + //토양 센서 값
String("\",\"link\": {}}"); //link가 없으면 오류메세지 받음
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);
}
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;
String url = "https://kauth.kakao.com/oauth/token";
String new_refresh_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);
access_token = extract_string(response, "{\"access_token\":\"", "\"");
new_refresh_token = extract_string(response, "\"refresh_token\":\"", "\"");
//만료 1개월전부터 갱신되므로 data가 없을 수도 있음
if (new_refresh_token != "") {
refresh_token = new_refresh_token;
}
retVal = true;
} else {
retVal = false;
}
http.end();
return retVal;
}
- 이 코드는 Wifi에 연결하여 일정 시간 간격으로 토양 센서의 값을 읽고, 카카오톡 API를 통해 메시지를 전송하는 기능을 제공합니다. 또한 엑세스 토큰의 만료 여부를 확인하고, 필요 시 갱신하는 기능도 포함되어 있습니다.
- WiFi 연결: setup 함수에서 WiFi 네트워크에 연결하고 IP 주소를 출력합니다.
- 토양 센서 값 읽기: loop 함수에서 1시간 간격으로 토양 센서 값을 읽어오고 메시지를 전송합니다.
- 카카오톡 API 사용: 토큰 유효성을 검사하고 갱신하며, 토양 센서 값을 포함한 메시지를 카카오톡으로 전송합니다.
- ESP32 보드를 컴퓨터에 연결합니다.
- Arduino IDE를 열고 해당 소스 코드를 컴파일하고 업로드합니다.
- API랑 cmd 창에서 받은 "access_token"이랑 "refresh_token"을 복사 붙여넣기 하고 실행합니다.
- 토양 센서 값이 일정한 시간 간격으로 카카오톡 메시지로 전송되는 것을 확인할 수 있습니다.
이번 실습은 토양 센서 값을 카카오톡으로 보내는 방법을 배우는 것이었습니다. 토양 센서로부터 데이터를 수집하고 이를 카카오톡 메시지로 전송하는 과정을 직접 경험할 수 있어서 신기하고 재미있었습니다. 이를 통해 IoT 디바이스와 메신저 API를 연동하여 실시간으로 데이터를 모니터링하고 카카오톡으로 알림을 받을 수 있는 시스템을 구축하는 방법을 익혔습니다.