금학기 전공 과목 기말 프로젝트의 일환으로, 방 안에서 관리할 수 있는 미니 원예 시스템을 기획하였습니다.
전체적인 코드들은 파이썬 기반으로 제작되었으며 라즈베리 OS 내부의 Tonny Python IDE를 통해 시스템을 구동하였습니다. 프로젝트 발표 당시 사용한 프레젠테이션을 통해 글을 정리하려 합니다.
목차입니다.
간단하게 말하면 식물이 생장하는 데에 최적의 환경을 만들어 주는 물건입니다. 적색/청색 파장대의 조명과, 온습도 등을 조정해 외부의 환경이나 공간에 구애받지 않고 식물을 키울 수 있게 도와줍니다. 뒤에 보이는 물통에 물만 채워주면 됩니다.
전체적인 흐름은 LG의 틔움과 유사합니다. 물론 대기업에서 뽑아내는 유려한 외관 디자인은 유사하지 않습니다...
프레임은 아크릴보드를 사용하였습니다. 우드락이나 폼보드보다 내구도 면에서 우세하며, 투명한 재질이라 내부의 식물을 쉽게 관찰하기 위함입니다. 설계도를 그려 주문제작하였습니다.
사용한 센서들입니다. 기본적으로 내부의 온습도 측정을 위한 DHT 온습도센서와 토양수분센서, 조명 제어를 위한 네오픽셀 LED, 습도 제어를 위한 가습기 센서와 급수를 위한 워터펌프로 구성돼 있습니다.
기본적으로 라즈베리파이에서는 adc값을 불러오기 위해 MCP3208이라는 컨버터가 필요합니다. 이번엔 컨버터 대신 보드를 추가 장착하여 씨리얼 통신으로 값을 읽어왔습니다.
또한 가습기 모듈은 USB형식으로 꽂기만 하면 전원이 공급되는 문제가 있어 스위치를 통해 수동 제어가 가능하게 하였습니다.
전체적인 흐름도입니다.
센서값들을 받아와 너무 습하거나 건조하면, 인터페이스에 메시지를 출력하고 상황에 맞는 명령을 실행합니다.
아래 더보기를 통해 전체적인 코드를 확인할 수 있습니다. 아래 깃허브에도 올려 놓았으니 필요하신 분은 참고하시면 됩니다.
https://github.com/w00ngja/minipot_with_RaspberryPi.git
import tkinter
import tkinter.ttk
import serial
import threading
import time
import os
import sys
import Adafruit_DHT
import RPi.GPIO as GPIO
import spidev
# 인터페이스 초기 설정
window=tkinter.Tk()
window.title("Smart Farm Information")
window.geometry("350x250+100+100")
window.resizable(False, False)
# 씨리얼 통신 초기 설정
rxHeader = [0x52, 0x58, 0x3D];
sendBuffer = [];
readBuffer = [0]*15;
sendSerial = 0;
read=bytearray();
# 온습도 센서 초기설정
onsupdo = Adafruit_DHT.DHT11
onsupdo_pin = 4
# 물펌프 초기설정
A1A = 5
A1B = 6
GPIO.setwarnings(False)
GPIO.setmode(GPIO.BCM)
GPIO.setup(A1A, GPIO.OUT)
GPIO.setup(A1B, GPIO.OUT)
GPIO.output(A1A, GPIO.LOW)
GPIO.output(A1B, GPIO.LOW)
# tkinter interface 콜백 함수
def led_on():
terminal_command = "sudo python3 test.py"
os.system(terminal_command)
def led_off():
terminal_command = "sudo python3 test1.py"
os.system(terminal_command)
def water_on():
GPIO.output(A1A,GPIO.HIGH)
GPIO.output(A1B,GPIO.LOW)
def water_off():
GPIO.output(A1A,GPIO.LOW)
GPIO.output(A1B,GPIO.LOW)
water_off()
# tkinter interface 레이아웃 설정
mainframe1= tkinter.Frame(window, relief= "solid", pady= 15)
mainframe2 = tkinter.Frame(window, relief="solid")
frame1=tkinter.Frame(mainframe1, relief="solid", padx=10)
frame2=tkinter.Frame(mainframe1, relief="solid", padx=10)
frame3=tkinter.Frame(mainframe1, relief="solid", padx=10)
frame4=tkinter.Frame(mainframe2, relief="solid", padx=10)
frame5=tkinter.Frame(mainframe2, relief="solid", padx=10)
frame6=tkinter.Frame(mainframe2, relief="solid", padx=10, pady = 15)
Temper1=tkinter.Label(frame1, text="온도")
Temper2=tkinter.ttk.Progressbar(frame1, maximum=100, length=200)
Tresult=tkinter.Label(frame1, relief="flat", bg="white", text=" 0 " )
Humidity1=tkinter.Label(frame2, text="습도")
Humidity2=tkinter.ttk.Progressbar(frame2, maximum=100, length=200)
Hresult=tkinter.Label(frame2, relief="flat", bg="white", text=" 0 " )
SoilHumidity1=tkinter.Label(frame3, text="토양 습도")
Sresult=tkinter.Label(frame3, relief="flat", bg="white", text=" 0 " )
Ledlight1=tkinter.Label(frame4, text="LED")
Ledlight2=tkinter.Button(frame4, text="ON", command=led_on, width=10)
Ledlight3=tkinter.Button(frame4, text="OFF", command=led_off, width=10)
WaterInjection1=tkinter.Label(frame5, text="물 주기")
WaterInjection2=tkinter.Button(frame5, text="ON", command=water_on, width=10)
WaterInjection3=tkinter.Button(frame5, text="OFF", command=water_off, width=10)
massgebox = tkinter.Label(frame6, bg="white", width= 50, height= 30)
massgebox.config(text=" ")
mainframe1.pack(side="top", fill="both")
mainframe2.pack(side="top", fill="both")
frame1.pack(side="top", fill="both")
Temper1.pack(side="left")
Temper2.pack(side="right")
Tresult.pack(side="right")
frame2.pack(side="top", fill="both")
Humidity1.pack(side="left")
Humidity2.pack(side="right")
Hresult.pack(side="right")
frame3.pack(side="top", fill="both")
SoilHumidity1.pack(side="left")
Sresult.pack(side="left")
frame4.pack(side="top", fill="both")
Ledlight1.pack(side="left")
Ledlight3.pack(side="right")
Ledlight2.pack(side="right")
frame5.pack(side="top", fill="both")
WaterInjection1.pack(side="left")
WaterInjection3.pack(side="right")
WaterInjection2.pack(side="right")
frame6.pack(side="top", fill="both")
massgebox.pack(side="bottom")
class sensory(object):
__slots__ = ('D2', 'D3', 'D11', 'light', 'mic', 'adc0', 'adc1', 'adc2', 'adc3');
sensor = sensory()
sensor.D2 = 0;
sensor.D3 = 0;
sensor.D11 = 0;
sensor.light = 0;
sensor.mic = 0;
sensor.adc0 = 0;
sensor.adc1 = 0;
sensor.adc2 = 0;
sensor.adc3 = 0;
# 씨리얼 통신 및 값제어 함수
SerialPort = serial.Serial("/dev/ttyUSB0", 115200, timeout = 0.0)
class Serial(threading.Thread):
def run (self):
count=0;
condition=True
SerialPort.close()
SerialPort.open()
while (condition):
try:
# 씨리얼 포트 오픈 및 ADC값 받아옴
# sensory.adc0 = 토양습도센서 값
read = SerialPort.read(15)
handleLocalData(read)
# 온습도 센서값 출력
h,t = Adafruit_DHT.read_retry(onsupdo,onsupdo_pin)
if h is not None and t is not None and h <= 100:
Tresult.config(text=str(round(t,1)))
Temper2['value'] = round(t,1)
Hresult.config(text=str(round(h,1)))
Humidity2['value'] = round(h,1)
if (round(h,1) < 55):
#print("너무 건조합니다. 가습기를 틀어 습도를 올려주세요.")
massgebox.config(text="너무 건조합니다.\n가습기를 틀어 습도를 올려주세요.")
elif (round(h,1) > 80):
#print("너무 습합니다. 가습기를 끄고 건조한 환경을 만들어주세요.")
massgebox.config(text="너무 습합니다.\n가습기를 끄고 건조한 환경을 만들어주세요.")
else:
massgebox.config(text="식물이 자라기 최적의 환경이에요.")
else:
#print("온습도 값을 불러올 수 없습니다.")
massgebox.config(text="온습도 값을 불러올 수 없습니다.")
#time.sleep(0.1)
Sresult.config(text=sensory.adc1)
print(sensory.adc1)
except:
print('EXCEPTION : SerialPort Is Close')
print('EXCEPTION : Please Kill & Restart')
SerialPort.close()
condition = False
serial = Serial(name='Port Open\n')
serial.start()
# adc value bit 처리 관련 함수
def handleLocalData(data):
buff = data;
fsize = len(data);
if fsize >= 15 :
index = ByteIndexOf(buff, rxHeader, 0, fsize);
if index != -1 :
imageSize = makeWord(buff[index+3], buff[index+4]);
imageBase = index + 5;
if imageSize == 10 and buff[imageBase+9] == 0x30 :
sensory.adc0 = buff[imageBase]
sensory.adc1 = buff[imageBase+1]
sensory.adc2 = buff[imageBase+2]
sensory.adc3 = buff[imageBase+3]
sensory.light = buff[imageBase+4]
sensory.mic = buff[imageBase+5]
sensory.D2 = buff[imageBase+6]
sensory.D3 = buff[imageBase+7]
sensory.D11 = buff[imageBase+8]
else:
print('Dead')
def ByteIndexOf(searched, find, start, end):
matched = False;
index = start;
for index in range(index,(end-len(find))+1,++1):
subIndex = 0;
matched = True;
for subIndex in range(subIndex, len(find),++1):
#print(index, subIndex)
if find[subIndex] != searched[index+subIndex]:
matched=False;
break;
if matched:
return index;
return -1;
def makeWord (hi, lo) :
return (((hi & 0x00ff) << 8) | (lo & 0xff));
def getLowByte(a):
return (a & 0xff);
def getHighByte(a):
return ((a >> 8) & 0xff);
window.mainloop()
아래는 시연 영상입니다.
생각보다 완성도 있게 잘 나왔고, 반응도 나쁘지 않았던 것 같습니다...ㅎ_ㅎ
물론 개선의 여지도 굉장하지만.. 발전 가능성이라고 해두겠습니다.
아쉬웠던 점을 좀 적어보자면
1. 환기 시스템의 부재 : 습도를 올리는 제어는 있지만, 내리는 제어를 생각 못 했었습니다.. 환기를 하려면 앞에 달린 문을 열어 주는 것 뿐입니다. 미니 쿨러 같은 것을 달았어도 괜찮았을 듯 합니다.
2. 프로그래밍 쪽이 조금 더 풍부했었다면 어땠을까 생각하였습니다. 예를 들어 환경에 맞는 프리셋을 제시하고, (열대/온대 등..) 사용자가 키우는 식물에 맞춰 알맞은 조건을 걸어주는 등의 방법도 좋을 것 같아요
프레젠테이션 템플릿은 Designtool의 템플릿을 사용하였습니다. (최고..)
https://www.designtool.org/ppt/?idx=8160841&bmode=view
감사합니다. 이상입니다.