본문 바로가기
라즈베리파이(Raspberry Pi)

[프로젝트] 라즈베리파이를 이용한 스마트팜 제작

by kingyejin 2024. 1. 23.

본 게시글의 라즈베리파이를 이용한 스마트팜은 'IOT 기초 프로그래밍' 수업의 조별 프로젝트를 통해 제작한 것이다. 스마트팜을 제작한 과정과 사용한 코드에 대해 정리해보자!

라즈베리파이를 이용한 스마트팜


[하드웨어]

1. 장치 구성


2. 공간분리

[스마트 온실 + 창고]

스마트 온실 | 식물이 생장하는 공간으로, 여러 센서/ 모듈로 환경을 실시간으로 모니터링하고 제어하는 공간
창고 | 관수통을 넣고 뺄 수 있는 공간으로, 멀티탭, 영양제 등을 넣어두는 창고의 역할도 수행

 

[컨트롤 박스]

환경 모니터링 및 제어 | 컨트롤 박스 덮개와 여닫이 문을 이용해 암조건을 줌으로써 원하는 광주기로 제어할 수 있으며, LCD 모니터 & 신호등 모듈로 문/덮개를 열지 않고도 스마트 온실 내 환경을 모니터링 할 수 있음.

 


3. 회로 연결

 

 

[센서/모듈]

 


[소프트웨어]

1. 자동 관수 시스템

 


-토양습도 센서-

 

-워터펌프 (점적 관수)-

 

점적 관수| 가는 구멍이 뚫린 관으로 물방울 형태로 물을 주는 방식

목적 | 고무 재질의 유연한 호스의 특징을 고려하여, 불필요한 물의 소비를 최소화하고 식물에게 집중적으로 물을 공급하기 위해 점적 관수 방법을 사용함.

 제조 방법 | 호스에 일정 간격을 두고 구멍을 뚫은 후, 점적 관수의 정확도를 위해 심지(빨대)를 꼽고 호스의 끝은 막아 호스 내부의 물이 외부로 점적되게 함


2. LED control


-명조건/암조건-


3. 온/습도 모니터링


-DHT11 온습도 센서-

-LCD monitor-

-신호등 LED 모듈-


4. 자동 환기 시스템


-환기팬 자동 ON/OFF-


5. Take picture


-Pi camera-


환경 제어와 바질 생육 측정 결과  [12/15(금)~17(일)]

환경 제어 및 바질 생육 측정은 경희대학교 생명과학대학 411호에서 진행하였으며

12월 13일 23시경부터 12월 18일 9시경까지 측정했으며, 측정된 환경 데이터 값은 다음과 같다.

 

1. 환경 제어 결과

전처리 전 그래프

 

전체적으로 온습도 측정값에서 급격히 튀는 값들이 있는 것을 확인할 수 있다.

우리는 이러한 값들을 센서의 오측정 또는 이상값이라고 판단하여, 이러한 이상치를 제거해 의미있는 데이터를

추려내고자 했다. 이 때 이상값 판단 기준은 사분위수를 이용했고, 데이터의 중간 50% 구간을 IQR로 잡

하한치 = Q1-1.5*IQR, 상한치 = Q3-1.5*IQR로 계산하여 하한값 이하의 값과 상한값 이상의 값을 삭제했다.

다음은 해당 작업을 수행한 코드와 그래프이다.

 

전처리(이상치 제거) 후 그래프

 


-온도-

전체적으로 22~25도 사이의 범위에서 증가하고 감소하는 반복 구조를 보이며, 3일 모두 약 20시부터 온도가 감소하는 형태를 보인다. 특히 16일에서 17일로 넘어가는 그래플르 보면 약 18시부터 06시 동안은 22~23도로 정상 범위(23~28도)를 벗어난 비교적 낮은 온도를 보이고 있다. 이에 발열 장치를 추가해 20:00~06:00 동안 켜주는 알고리즘을 추가해주면 정상적으로 온도 유지하는 데에 도움이 될 것으로 보인다.

-습도-

정상범위 50~60%에 비해 전체적으로 낮은 습도의 27.5~47.5% 범위를 보였다. 이는 측정 장소에 지속적으로 켜져있던 히터로 인해 습도가 전체적으로 낮게 유지된 것으로 보인다. 이에 습도가 높을 때 동작시키는 환기팬 알고리즘보다는 습도가 낮을 때 자동 가습 장치를 추가해주는 것이 더 필요할 것으로 보인다.

 

-토양 습도-

토양 수분은 습도 그래프와 매우 비슷하게 나타나, 습도가 감소할 때 토양습도도 감소하는 경향을 보여 습도와 연관성이 높은 것으로 보였다. 이 때 토양습도가 0.3 이하로 떨어지면 자동 관수해주는 시스템이 있어 0.3 이하로 떨어진 이후 다시 바로 높아지는 것을 볼 수 있다. 하지만 관수 후 12시간 내로 다시 0.3 이하로 떨어지는 것을 보아 낮은 환경 습도가 토양 습도에도 큰 영향을 주는 것으로 보인다. 이에 위에서도 언급했듯 낮은 습도를 제어해줄 알고리즘이 필요할 것으로 보인다.


2. 바질 생육 측정 결과

바질의 생육상태는 파이카메라를 이용해 12/15(금) ~ 12/17(일) 3일간 하루에 3번 10시,15시,20시에 촬영되어 자동으로 image 파일에 저장되도록 하였다.

 


1) leaf extraction

얻은 사진을 이용해 잎의 면적을 구해 바질의 생육과 연관짓는 작업이다.

해당과정에서 사용한 코드이다. 이는 HSV 값을 이용해 녹색 부분만 추출해 해당 부분을 masking 하는 작업으로 Original image -> HSV Mask -> Leaf 추출 3단계의 이미지 형태로 저장했으며 코드와 3단계 이미지는 다음과 같다.

 

leaf extraction 코드
Original image -> HSV Mask -> Leaf 추출


2) 잎면적 계산

위에서 추출한 잎의 masking 이미지의 픽셀값을 추출하여 계산해줌으로써 잎의 면적을 계산할 수 있다.

해당 사진들은 모두 동일한 위치의 파이카메라로 촬영한 것이므로 나타나는 잎면적의 변화가 의미있을 것이다.

다음은 잎면적 계산값들을 기록한 csv 파일과 이를 시각화한 그래프이다.

 


3) 환경과 바질 생육의 연관성 분석

 

환경 요인 중 습도와 토양 수분은 해당 표시 구간에서 모두 감소하여 정상 범위보다 낮아진 형태를 보였다. 이에 의도치 않은 환경 스트레스가 가해진 것으로 해석했을 때, 바질의 잎면적도 이와 비례하게 감소했으며 바질 생육이 습도(수분) 환경과 상관관계가 높다는 결과를 도출해낼 수 있었다. 그리고 16일 17시 부근에서 관수해줌에 따라 토양 습도값이 증가하여 다시 생육량이 증가한 것을 볼 수 있다. 즉, introduction에서 언급했던 대로 식물의 생육과 환경 요인, 특히 습도(수분)와 밀접한 연관이 있음을 증명할 수 있었다.


[통합 python 코드]

해당 스마트팜 제작에 사용한 scripts 파일의 python 코드이다.

이를 이용하려면 여기서 사용한 센서/모듈과 아래의 import된 패키지를 모두 설치한 후, 실행해줘야 한다.

import Adafruit_DHT
import time
import csv
import spidev
import RPi.GPIO as GPIO
from picamera import PiCamera
from datetime import datetime
import schedule
import RPi_I2C_driver
from rpi_ws281x import PixelStrip, Color
import argparse

# DHT11
sensor = Adafruit_DHT.DHT11
pin = 4
spi = spidev.SpiDev()
spi.open(0, 0)
spi.max_speed_hz = 1000000
mcp3008 = 0
SLOPE = 2.18
INTERCEPT = -0.79
mylcd=RPi_I2C_driver.lcd()

# LED strip configuration:
LED_COUNT = 48      # Number of LED pixels.
LED_PIN = 12          # GPIO pin connected to the pixels (18 uses PWM!).
# LED_PIN = 10        # GPIO pin connected to the pixels (10 uses SPI /dev/spidev0.0).
LED_FREQ_HZ = 800000  # LED signal frequency in hertz (usually 800khz)
LED_DMA = 10          # DMA channel to use for generating signal (try 10)
LED_BRIGHTNESS = 255  # Set to 0 for darkest and 255 for brightest
LED_INVERT = False    # True to invert the signal (when using NPN transistor level shift)
LED_CHANNEL = 0       # set to '1' for GPIOs 13, 19, 41, 45 or 53

parser = argparse.ArgumentParser()
parser.add_argument('-c', '--clear', action='store_true', help='clear the display on exit')
args = parser.parse_args()


strip = PixelStrip(LED_COUNT, LED_PIN, LED_FREQ_HZ, LED_DMA, LED_INVERT, LED_BRIGHTNESS, LED_CHANNEL)
strip.begin()

GPIO.setmode(GPIO.BCM)
GPIO.setup(27, GPIO.OUT)
GPIO.setup(18, GPIO.OUT)
GPIO.setup(23, GPIO.OUT)
GPIO.setup(24, GPIO.OUT)
GPIO.setup(16,GPIO.OUT)


camera = PiCamera()


csv_file = "sensor_data.csv"

def whiteLight(strip, duration_hours, off=False):
    end_time = time.time() + duration_hours * 3600
    last_read_time = time.time()

    while time.time() < end_time:
       
        for i in range(strip.numPixels()):
            color = Color(0, 0, 0) if off else Color(255, 255, 255)
            strip.setPixelColor(i, color)
        strip.show()
        
        current_time = time.time()
        if current_time - last_read_time > 120:
            read_and_log_sensor_data()
            last_read_time = current_time

       
        schedule.run_pending()
        
      
        time.sleep(1)



def colorWipe(strip, color, wait_ms=50):
    """Wipe color across display a pixel at a time."""
    for i in range(strip.numPixels()):
        strip.setPixelColor(i, color)
        strip.show()
        time.sleep(wait_ms / 1000.0)



def read_soil_moisture():
    adc = spi.xfer2([1, (8 + mcp3008) << 4, 0])
    data = ((adc[1] & 3) << 8) + adc[2]
    Vol = 3.3 * data / 1024
    water_contents = ((1.0 / Vol) * SLOPE) + INTERCEPT
    return water_contents


def take_picture():
	
    timestamp = datetime.now().strftime("%Y%m%d_%H%M%S")
    filename = f"/home/dteam/DLETSGO/real/image/image_{timestamp}.jpg"
    camera.start_preview()
    camera.resolution = (600, 450)
    GPIO.output(18,False)
    GPIO.output(23,False)
    GPIO.output(24,False)
    mylcd.lcd_display_string("taking picture...",3)
    time.sleep(10)
    camera.capture(filename)
    camera.stop_preview()
    GPIO.output(18,True)
    GPIO.output(23,True)
    GPIO.output(24,True)
    mylcd.lcd_display_string(" "*20,3)
    print(f"Picture taken: {timestamp}")
 
schedule.every().day.at("10:00").do(take_picture)
schedule.every().day.at("15:00").do(take_picture)
schedule.every().day.at("20:00").do(take_picture)

def read_and_log_sensor_data():
    humidity, temperature = Adafruit_DHT.read_retry(sensor, pin)
    soil_moisture = read_soil_moisture()
    pump_status = "Off"

    current_time = datetime.now().strftime("%Y-%m-%d %H:%M:%S")
    
    if humidity is not None and temperature is not None and soil_moisture is not None:
        print(f"Temperature={temperature:0.1f}*C Humidity={humidity:0.1f}% Soil Moisture = {soil_moisture:0.3f}%")
        mylcd.lcd_display_string(f"Tem={temperature:0.1f}*C Hum={humidity:0.1f}%",1)
        mylcd.lcd_display_string(f"S.M = {soil_moisture:0.2f}%",2)
        
        #green
        if 23 <= temperature <= 28 and 40 <= humidity <= 60 :
            GPIO.output(24,False)
       
        else:
            GPIO.output(24,True)
        
        #red
        if temperature < 23 or temperature >28 :
            GPIO.output(18,False)
	
        else:
            GPIO.output(18,True)
            
        #yellow
        if humidity <40 :
            GPIO.output(23,False)
            
        elif humidity > 60:
            GPIO.output(23,False)
            GPIO.output(16,False)
            time.sleep(30)
            GPIO.output(16,True)
            
        else:
            GPIO.output(23,True)
            
      

        #water pump control
        if soil_moisture < 0.3:
            GPIO.output(27, False)
            pump_status = "On"
            time.sleep(3)
            GPIO.output(27, True)
           
        else:
            GPIO.output(27, True)
            pump_status = "Off"
        
        #save csv
        with open(csv_file, mode='a', newline='') as file:
            writer = csv.writer(file)
            writer.writerow([current_time, temperature, humidity, soil_moisture, pump_status])
    else:
        print('Read error')


last_read_time = time.time()

try:
    with open(csv_file, mode='a', newline='') as file:
        writer = csv.writer(file)
        if file.tell() == 0:
            writer.writerow(["Time", "Temperature", "Humidity", "Soil Moisture", "Pump Status"])

    while True:
       whiteLight(strip,18)
       
       whiteLight(strip,6,off=True)


except KeyboardInterrupt:
    print("Terminated by Keyboard")

finally:
    colorWipe(strip, Color(0, 0, 0), 10)
    GPIO.cleanup()
    camera.close()
    print("End of Program")