본 포스팅에는 전체적인 코드설명보다 중요하다고 생각이 드는 부분만 따로 집어와 설명하였으니
전체 코드에 대한 설명은 아래에 있는 주소에 있는 파일의 주석을 통해 확인해주시기 바랍니다.
https://github.com/yoovin/Arduino-smartfarm
유튜브 알고리즘으로 유튜버들이 LG사의 틔운이라는 제품리뷰를 하는 영상들을 보게되었다.
오... 이쁜데?
마침 저번학기에 아두이노 수업도 들었겠다.
원래도 이런 자동화에 관심이있어 아두이노를 다룰 줄 알게되면 이것저것 만들어봐야지 하는 생각은 가지고 있었다.
그래서 만들어보고싶었다.
아두이노는 수업시간에 간단히 led 끄고켜기, 각종 센서 다루기 정도만 배워서 그 정도 수준만 알고있었고 종류도 대충만 알아서 큰 프로젝트를 하는건 아니니까 그냥 블루투스로 조작하고, 아두이노만 연결해서 센서랑 막 이으면 되지않을까? 하고 생각하고있었다.
일단 틔운에서 영감을 얻었으니 틔운이 어떻게 식물을 싹틔우고 키울 수 있는지 찾아보았다.
그렇다. 틔운은 수경재배였다.
양액?
어쩐지 키트 뿐만 아니라 영양제도 같이 팔더라.
그냥 막연히 화분에 물만 좀 뿌려주고 LED만 좀 켜주면 돌아갈거라 생각했는데
내가 수경재배까지 다룰줄 아는건 아니니까 일단은 흙으로 만들기로 했다.
사실 블루투스 리시버랑 다른 재료들은 한참 전부터 아두이노로 무언가 만들어보고싶어서 사서 쟁여두고있었다...
마침 알리에서 필요한 재료들은 다 팔고있길래 간단하게 구입할 수 있었다.
사고나서 만들다가 알게된건데 아두이노 대용으로 샀던 Nodemcu라는 제품은 그냥 짭두이노가 아닌 무려 ESP8266 모듈이 기본으로 장착되있는 개꿀템이였다.
작지만 우노를 훨씬 넘가하는 성능을 가진 너란녀석...
하지만 핀의개수도 적고 특히 아날로그핀이 하나밖에 없어서 다른 방식으로 처리하느라 고생 좀 했다.
그리고 Operating Voltage가 3.3V인데 이 때문에 디지털핀에서 출력이 3.3V가 나온다.
후에 서술하겠지만 이는 생각보다 귀찮은 일이 되었다.
- 5V를 사용하고싶으면 다른 핀(VU, VIN)에 꽂아주면 5V 출력이 나오기는 한다.
우선 간단하게 식물을 키우려면 어떤 시스템이 필요한가 생각해보았다.
자동이든 유저가 수동으로 하든 제어 할 수 있어야 하는게 LED와 화분에 물주는 모터 정도 될것같고
이들이 잘 동작하는지 모니터링이 되어야했다.
틔운처럼 큰 함안에 환경을 구성하면 좋을것같아 다이소를 둘러보던중 마음에 드는 제품이 있었다.
내부를 보고싶으면 위로 열어 보기도 좋을것같았고 필요시 아예 뚜껑을 뗄 수도 있기에 맘에 들었다.
내부에는 플라스틱통으로 화분을 만들어 두었고 토양센서와 급수관을 이어주었다.
벽면에 RHT03 센서를 붙여두어 통 내부의 온도와 습도를 확인할 수 있게 하였다.
급수는 통에 물을 받아두어 나가는 식으로 안에 모터를 넣고 선을 연결시켜두었다.
뚜껑에는 LED등을 이어붙여주었다.
깔끔하게 글루건으로 붙여주고싶었는데 LED 재질에 녹은 글루가 붙지않아 어쩔 수 없이 테이프로 붙여주었다.
프로젝트중에서도 개발자로써 제일 중요하고 재밌는 파트
(기존 알리 판매사이트의 이미지 설명)
4개를 다 사용할 수 있게 되어있었다.
배선을 정리하지않아 상당히 난장판이다.
회로는 위와 같이 구성하였다.
알리에서 구매한 관수 시스템 키트에는 4채널 릴레이모듈, 급수모터, 토양센서 각각 4개씩 들어있었는데,
화분을 좀 크게 두어서 그런가 통안에 4개까지 넣을 필요는 없을 듯 하여 모터는(급수용 2개, 물빠짐용 1개) 총 3개, 토양센서는 2개만 사용하는 구조로 만들었다.
사용한 모터와 LED는 5V에서 제대로 동작하는데 nodemcu의 디지털핀은 3.3V였기에 연결해두어도 동작을 안했다.
그래서 어쩔수 없이 릴레이모듈에 아두이노의 vin을 통해 5V 전원을 이어주고 이를 조작해주는식으로 ON/OFF를 관리하였다.
// Pin 선언
int motorPin[] = {D1, D2};
int ledPin = D3;
int submotorPin = D4;
int soilPin[] = {D5, D6};
int rhtPin = D7;
릴레이모듈 1번, 2번은 급수모터, 3번은 LED, 4번에는 물빠짐 모터를 연결해주었고
이는 아두이노의 D1 ~ D4까지 연결시켜 두었다.
D5, D6번 핀은 토양센서, D7번 핀은 RHT03 센서와 연결시켜두었다.
// 릴레이모듈을 사용하므로 HIGH가 꺼진상태
int isLedOn = 1;
int motorOn[] = {1, 1};
int isSubmotorOn = 1;
릴레이 모듈을 통해 조작하는 핀의 HIGH, LOW값을 관리하기위해 변수로 정의해두었다.
#include <ESP8266WiFi.h>
const char* ssid = ""; // 사용 중 인 와이파이 이름
const char* password = ""; // 와이파이 패스워드
WiFiServer server(80); // 서버
// void setup(){
WiFi.mode(WIFI_STA);
WiFi.begin(ssid, password);
while (WiFi.status() != WL_CONNECTED) {
delay(100);
Serial.print(".");
}
// 서버 시작
server.begin();
// }
// void loop() {
WiFiClient client = server.available();
// }
Nodemcu의 장점인 와이파이를 이용 해 보기 위해 설정을 해준다.
ssid와 password에 연결 가능 한 WiFi 설정을 넣어주면 무선랜이 잡힌다.
전체적인 모니터링 사이트 구성이다.
/*
====== HTML 선언부 ======
*/
client.println("HTTP/1.1 200 OK");
client.println("Content-Type: text/html");
client.println("Connection: close");
client.println();
client.println("<!DOCTYPE html>");
client.println("<html xmlns='http://www.w3.org/1999/xhtml'>");
client.println("<head>\n<meta http-equiv='Content-Type' content='text/html; charset=utf-8' />");
client.println("<script src=\"https://cdn.tailwindcss.com\"></script>");
client.println("<title>Smart Farm</title>"); // 웹 서버 페이지 제목 설정
client.println("</head>");
// body 태그 선언부
client.println("<body>");
client.println("<div class=\"text-8xl p-8 font-bold\"><a href=\"/\">Yoo Farm</a></div>");
client.println("<div class=\"border-4 border-gray-800 rounded m-2 p-2\">");
client.println("<div class=\"flex flex-row mb-6\">");
client.println("<div class=\"basis-1/2 relative mb-6\">");
// LED
client.print("<span class=\"text-2xl\">LED: ");
isLedOn
? client.println("<span class=\"text-2xl font-bold\">OFF</span>")
: client.println("<span class=\"text-2xl font-bold text-green-400\">ON</span>");
client.println("</span>");
client.println("<div class=\"flex\">\
<div class=\"inline-flex shadow-md hover:shadow-lg focus:shadow-lg\" role=\"group\">\
<button type=\"button\" class=\"rounded-l inline-block px-6 py-2.5 bg-blue-600 text-white font-medium text-xs leading-tight uppercase hover:bg-blue-700 focus:bg-blue-700 focus:outline-none focus:ring-0 active:bg-blue-800 transition duration-150 ease-in-out\" onclick=\"location.href='/led/on'\">On</button>\
<button type=\"button\" class=\" rounded-r inline-block px-6 py-2.5 bg-blue-600 text-white font-medium text-xs leading-tight uppercase hover:bg-blue-700 focus:bg-blue-700 focus:outline-none focus:ring-0 active:bg-blue-800 transition duration-150 ease-in-out\" onclick=\"location.href='/led/off'\">Off</button></div></div>"); // LED 끄고켜기 버튼
client.println("</div>"); // <div class="basis-1/2 relative mb-6">
...
// html 닫기
client.println("</html>");
와이파이 연결 후 server 및 client를 통해 브라우저로 접속 한 유저에게 html을 띄워줄 수 있고, 중간중간 필요에 따라 값을 바꾸어서 넣을 수도 있다.
디자인은 style태그를 이용하면 코드가 너무 길어지는것도 있고 요즘 Tailwindcss를 알게되어 이번 프로젝트에서 사용해보았다.
이를 이용해 모니터링 사이트를 만들어주었다.
#include <WiFiUdp.h>
#include <NTPClient.h>
// NTP 서버시간
const char* ntpServer = "pool.ntp.org";
uint8_t timeZone = 9;
uint8_t summerTime = 0;
WiFiUDP ntpUDP;
NTPClient timeClient(ntpUDP, ntpServer);
String formettedTime;
int year, month, day, hour, minute, second;
// void loop(){
timeClient.update(); // 시간 업데이트
time_t epochTime = timeClient.getEpochTime(); // 유닉스 시간을 가져옴
struct tm *ptm = gmtime ((time_t *)&epochTime);
formettedTime = timeClient.getFormattedTime();
hour = timeClient.getHours();
minute = timeClient.getMinutes();
second = timeClient.getSeconds();
year = ptm->tm_year+1900; // 현재 년도와 맞춰주기 위해 1900을 더해준다.
month = ptm->tm_mon+1;
day = ptm->tm_mday;
// }
아두이노에 NTP 서버시간을 불러와 이용해주기 위해 작성한 코드이다.
시간, 분, 초는 get함수를 통해 불러올 수 있지만 년도, 월, 일은 tm 구조체를 만들어 구조체 내의 변수값을 가져와야한다.
#include <SparkFun_RHT03.h>
// 온습도 선언
RHT03 rht;
float tempC;
float humidity;
// void loop(){
// 온습도 확인하기
int updateRht = rht.update(); // RHT를 통해 온, 습도가 불러진 경우 1을 반환 함
if(updateRht == 1){
humidity = rht.humidity();
tempC = rht.tempC();
}
// 온,습도
client.println("<div class=\"basis-1/4\">");
client.println("<div><span class=\"text-2xl\">온도: </span>");
client.print("<span class=\"text-2xl font-bold\">");
client.print(tempC); // 온도
client.println("<span class=\"text-red-600\">°C</span></span>");
client.println("</div>");
client.println("<div><span class=\"text-2xl\">습도: </span>");
client.print("<span class=\"text-2xl font-bold\">");
client.print(humidity); // 습도
client.println("<span>%</span></span>");
client.println("</div>");
// }
sparkfun사의 RHT03 센서를 이용하면서 라이브러리를 가져다가 온,습도를 확인 하고 html로 보내주었다.
// GET 요청
String req = client.readStringUntil('\r');
client.flush();
if(req.indexOf("led/on") != -1){
isLedOn = 0;
} else if (req.indexOf("led/off") != -1){
isLedOn = 1;
} else if (req.indexOf("motor/sub/on") != -1){
isSubmotorOn = 0;
}else if(req.indexOf("motor/sub/off") != -1){
isSubmotorOn = 1;
}
...
client.println("<div class=\"flex\">\
<div class=\"inline-flex shadow-md hover:shadow-lg focus:shadow-lg\" role=\"group\">\
<button type=\"button\" class=\"rounded-l inline-block px-6 py-2.5 bg-blue-600 text-white font-medium text-xs leading-tight uppercase hover:bg-blue-700 focus:bg-blue-700 focus:outline-none focus:ring-0 active:bg-blue-800 transition duration-150 ease-in-out\" onclick=\"location.href='/led/on'\">On</button>\
<button type=\"button\" class=\" rounded-r inline-block px-6 py-2.5 bg-blue-600 text-white font-medium text-xs leading-tight uppercase hover:bg-blue-700 focus:bg-blue-700 focus:outline-none focus:ring-0 active:bg-blue-800 transition duration-150 ease-in-out\" onclick=\"location.href='/led/off'\">Off</button></div></div>"); // LED 끄고켜기 버튼
client.println("</div>");
아두이노의 조작은 브라우저 접속을 통한 GET요청을 읽는 방식으로 진행하였다.
req변수에 GET요청을 불러오고 indexOf() 함수를 이용하여 어떤 요청인지 구분해주었다.
HTML로 전송되는 버튼에는 onclick 요소를 넣어 location.href를 이용해 필요한 URL로 리다이렉트 되게끔 해주어 브라우저 링크로 GET 요청을 보내게 하였다.
각 화분카드는 공급모터 on/off 여부 (이는 굳이 조작하지 않아도 수분에 따라 작동하기에 유저 조작을 넣지 않았음.)
현재 수분값과 1일간의 수분값을 차트로 나타내 주게 끔 하였다.
int soilValue(int pin){ // 아날로그 핀 하나로 여러 값 받아오는 방법
for(int i = 0; i < 2; i++){
if(i == pin){
digitalWrite(soilPin[i], HIGH);
}else{
digitalWrite(soilPin[i], LOW);
}
}
return analogRead(A0);
}
아날로그 핀을 통해 받아와야하는 센서값은 여러개인데 사용가능한 핀은 한개일 경우 사용하는 방법이다.
각 디지털 핀마다 센서 vcc를 연결해주고 하나씩 살리고 다른 나머지는 일시적으로 내려 하나의 센서에만 전류를 통하게 해 그 값을 아날로그 핀으로 받아온다.
int soilValues[2] = {0};
int soilPercents[2] = {0};
for(int i = 0; i < 2; i++){
soilValues[i] = soilValue(i); // 아날로그 핀을 통해 값을 받아옴
delay(100);
}
for(int i = 0; i < 2; i++){
if(soilValues[i] > 500){ // 물주는값 약 수분 50%
motorOn[i] = 0; // 모터켜짐
}else{
if(!motorOn[i]){
motorOn[i] = 1; // 모터꺼짐
}
digitalWrite(motorPin[i], motorOn[i]);
}
for(int i = 0; i < 2; i++){
soilPercents[i] = map(soilValues[i], 1024, 0, 0, 100);
}
아날로그핀을 통해 받아온 값을 map() 함수를 이용하여 간단하게 %로 바꿔주었고
#include <EEPROM.h>
/* ==== EEPROM 관련 함수 ====
EEPROM.begin()
EEPROM.write()
EEPROM.commit()
EEPROM.read()
*/
// 수분차트 -EEPROM을 이용할것이기에 byte로 배열을 만들어두었음.-
byte soilHumidity1[24] = {0};
byte soilHumidity2[24] = {0};
// 24개씩 2 화분의 값을 넣어주면 되기에 48개만 지정해줌
EEPROM.begin(48);
// 수분 값 불러오기
for(int i = 0; i < EEPROM.length(); i++){
if(EEPROM.read(i) > 100) continue; // 초기화 된 EEPROM의 초기값이 255였음.
if(i < 24){
soilHumidity1[i] = EEPROM.read(i);
}else{
soilHumidity2[i - 24] = EEPROM.read(i);
}
}
하루동안의 수분값은 비활성 메모리인 EEPROM에 저장해두고 아두이노가 다시 시작하더라도 값을 가져올 수 있게 끔 해두었다.
Nodemcu는 실제 EEPROM이 있는것이 아닌 플래시메모리에서 영역을 가져와 사용하는것이기에 어느정도를 쓸건지 EEPROM.begin()을 통해 크기를 지정해준다.
EEPROM은 write() 함수를 통해 주소당 1byte의 값을 작성할 수 있고 commit() 함수를 통해 값을 영구히 저장해둔다. 이후 read() 함수를 통해 값을 읽어들일 수 있다.
bool doItJustOnce = false;
if(timeClient.getMinutes() == 1 && !doItJustOnce){ // 정시에 한번만 실행 (실행시차를 맞추기위해 1분에 실행 함)
if(!hour){ // 정각이라면
// EEPROM 초기화
for(int i = 0; i < EEPROM.length(); i++){
EEPROM.write(i, 0);
}
// 수분값 초기화
for(int i = 0; i < 24; i++){
soilHumidity1[i] = 0;
soilHumidity2[i] = 0;
}
}
if(hour >= 6 && hour <= 19){ // 낮시간동안 켜짐
if(isLedOn){
isLedOn = 0;
}
}else{
if(!isLedOn){ // 밤에는 꺼짐
isLedOn = 1;
}
}
// 수분 EEPROM에 저장
// 0 ~ 23은 1번 수분
EEPROM.write(hour, soilPercents[0]);
soilHumidity1[hour] = soilPercents[0];
// 24 ~ 47은 2번 수분
EEPROM.write(hour + 24, soilPercents[1]);
soilHumidity2[hour] = soilPercents[1];
EEPROM.commit();
doItJustOnce = true;
}else if(timeClient.getMinutes() == 2 && doItJustOnce){ // 다음 정시에 다시 실행될 수 있게 값을 바꿔 줌
doItJustOnce = false;
}
위에서 불러온 시간값을 이용해 일정 시간에 맞추어 해야하는일을 작성 해 주었다.
loop가 도는 시간동안 정시 0분이 되기 때문에 getMinutes() 함수로 인해 if문이 실행되지만, 미리 변수값을 지정해둔 hour값이 이전 시간으로 남아있는 경우 EEPROM의 이전 시간 주소에 overwrite가 일어나는 경우가 있어 시차를 생각해 1분이 되면 실행되게 코드를 작성하였다.
// Line 차트 html
client.print("<div class=\"p-5 shadow-lg rounded-lg overflow-hidden\">\
<div class=\"py-3 px-5 bg-gray-50 font-bold\">");
client.print(String(year) + "-" + String(month) + "-" + String(day));
client.println(" 1번 화분 수분량</div>\
<canvas class=\"p-1\" id=\"chartLine1\"></canvas>\
</div>");
client.println("</div>");
// Line 차트 JS
client.println("<script src=\"https://cdn.jsdelivr.net/npm/chart.js\"></script>");
client.println("<script>");
client.print("const humidity = ");
client.print("[[");
for(int i = 0; i < 24; i++){
client.print(soilHumidity1[i]);
client.print(",");
}
client.print("],[");
for(int i = 0; i < 24; i++){
client.print(soilHumidity2[i]);
client.print(",");
}
client.println("]]");
client.println("const labels = [\"0시\", \"1시\", \"2시\", \"3시\", \"4시\", \"5시\", \"6시\", \"7시\", \"8시\", \"9시\", \"10시\", \"11시\", \"12시\", \"13시\", \"14시\", \"15시\", \"16시\", \"17시\", \"18시\", \"19시\", \"20시\", \"21시\", \"22시\", \"23시\"]");
client.println("const data = [{");
client.println("labels: labels,");
client.println("datasets: [{");
client.println("label: \"수분량\",");
client.println("backgroundColor: \"hsl(252, 82.9%, 67.8%)\",");
client.println("borderColor: \"hsl(252, 82.9%, 67.8%)\",");
client.println("data: humidity[0],},");
client.println("],},{");
client.println("labels: labels,");
client.println("datasets: [{");
client.println("label: \"수분량\",");
client.println("backgroundColor: \"hsl(252, 82.9%, 67.8%)\",");
client.println("borderColor: \"hsl(252, 82.9%, 67.8%)\",");
client.println("data: humidity[1],},],}]");
client.println("var chartLine = new Chart(");
client.println("document.getElementById(\"chartLine1\"),");
client.println("{type: \"line\",");
client.println("data:data[0],");
client.println("options: {},})");
client.println("var chartLine2 = new Chart(");
client.println("document.getElementById(\"chartLine2\"),");
client.println("{type: \"line\",");
client.println("data:data[1],");
client.println("options: {},})");
client.println("</script>");
차트 파트는 아무래도 arduino코드로 읽기에는 복잡하니 이 부분은 원본 html파일(main.html)을 참고해주면 좋다.
Line 차트는 Chart.js를 이용해 만들어 주었다.
라벨은 어차피 시간은 바뀌지않으니 그대로 문자열로 작성 해 주었고 값을 넣어야하는 const humidity = [] 부분을 C코드와 함께 작성해주었다.
미리 EEPROM에서 읽어서 배열로 저장해 둔 값을 for문을 통해 하나씩 불러와 JS 배열로 client.print를 통해 뿌려주었다.
브라우저에서 확인하면 이런식으로 잘 들어온다.
화분카드 아래에는 아두이노가 켜지고부터 지속적으로 무슨 활동을 했는지 로그를 찍어 확인 할 수 있게 해두었다.
// Log를 저장하기위한 배열
String logs[1000];
int logcount = 0;
void writeLog(String text){
if(logcount > 999) logcount = 0; // 로그 배열이 다 차면 다시 0으로 돌아감
String currentTime = String(year) + "-" + String(month) + "-" + String(day) + " " + formettedTime;
logs[logcount] = currentTime + " | " + text;
logcount += 1;
}
String으로 배열을 미리 1000개 만들어 두고 로그를 작성하는 writeLog() 함수를 만들었다.
// void setup(){
// 기본 설정
writeLog("서버 켜짐");
pinMode(A0,INPUT);
pinMode(ledPin, OUTPUT);
pinMode(submotorPin, OUTPUT);
for(int i = 0; i < 2; i++){
pinMode(motorPin[i], OUTPUT);
pinMode(soilPin[i], OUTPUT);
}
rht.begin(rhtPin);
EEPROM.begin(48);
writeLog("핀 설정 및 RHT, EEPROM 시작 완료");
// 습도 값 불러오기
for(int i = 0; i < EEPROM.length(); i++){
if(EEPROM.read(i) > 100) continue; // 초기화 된 EEPROM의 초기값이 255였음.
if(i < 24){
soilHumidity1[i] = EEPROM.read(i);
}else{
soilHumidity2[i - 24] = EEPROM.read(i);
}
}
writeLog("EEPROM에 저장되어있는 습도 값 불러오기 완료");
...
// }
// Log창
client.println("<div class=\"m-2 mb-0 p-2 pl-4 rounded-t-lg bg-slate-400\">\
<span class=\"text-4xl font-bold\">Log</span>\
</div>");
client.println("<div class=\"m-2 mt-0 p-2 pl-4 h-full bg-slate-600 font-bold text-white overflow-scroll\" style=\"height: 30vh;\">");
for(int i = logcount; i >= 0; i--){
client.print("<div>");
client.print(logs[i]);
client.println("</div>");
}
client.println("</div>");
중간중간 작업이 완료 된 후 로그를 작성해두고 client에게 html을 전송해줄때
for문으로 logs 배열에 써둔 값을 하나씩 읽어들여 div값으로 보내준다.
당연하겠지만 logs배열은 heap영역에 들어간 휘발성 메모리이기에 아두이노가 꺼졌다 켜지면 로그는 다시 초기화되어버린다.
저장되지않고 그 실행동안만 확인할 수 있는 로그이다.
이렇게 해서 만들어 둔 기능들이 잘 동작하는지 실행해 볼 차례이다.
밤에 LED를 켜보니 그럴싸한 무드등이 되었다.
전자회로는 중학교 이후 배워본적도, 다루어본적도 없어 코드를 짜는것보다 회로구성과 직접 작품을 제작하는게 좀 고됐던것 같다.
그래도 중학생 시절 과학시간에서 배운 간단한 지식으로도 만들 수 있다는것이 아두이노가 얼마나 교육용으로 간단하고 쉽게 만들 수 있도록 한건지 신기하긴하다.
아두이노를 사용하면서 EEPROM이나 Flash메모리 용량을 신경써서 코드를 짜는 등 일반 pc로 코드를 짜면서는 생각해보지 않을 문제들도 걱정해서 코드를 짜보니까 옛날로 돌아간듯한 기분도 들고 재밌었다.
아직 C는 완벽히 익숙하지 않아 문자열 사용이나 포인터등이 좀 걱정되기도 했는데 아두이노에서 String 객체도 만들어 여러 함수를 지원하는 등 코드를 짜면서도 좀 쉽게 만들 수 있도록 해두어 의외로 어려운면은 크게 없었다.
아두이노로 무언가를 만들어보려고 하니 일일이 배선도 해야하고, 배선을 하기위해 드릴로 구멍도 내야하고 배선이 잘 된줄 알았는데 회로상 문제가 생겨 기능이 잘못 동작하기도하고... 혹은 부품 고장으로 잘못 동작하는데 이유를 모를땐 제일 힘들었다.
무엇보다 전자회로를 다루다보니 혹시라도 잘못연결하여 부품들이 고장나지않을까 걱정이 많이 들었다.
내가 평소 web과 app개발을 좋아했던 이유는 모니터상에서 무언가를 만드는건 현실에서 만드는것에 비하면 훨씬 쉬웠기 때문이다. 뭘 하든 컴퓨터가 좋아... 코드만 뚝딱뚝딱써도 웬만한 일은 다 할수 있으니까...
하지만 실제로 현실에서 무언가 편하게 하기 위해서는 전자적인 지식도 다룰 줄 알아야 하는게 맞는 것 같다.
만드는건 힘들었지만 만드느라 시간가는줄도 모르고 즐겁게 열중할 수 있었던것 같다.
앞으로도 이 스마트팜에 기능들을 조금씩 추가해가면서 나만의 텃밭을 꾸밀것같다. 과연 수확해서 작물을 먹어볼 수 있을지는 모르겠지만, 된다면 그것 나름대로 좋은 성과일 것이다.