# SmartFarm
# DHT11
# DHT 11의 SIG 핀 하나를 가지고 양 방향 통신을 하는 Single Wire Two Way 통신 프로토콜을 사용 / 라이브러리 사용
# 전압으로 정보를 얻는게 아니고, Single-bus data format 사용 한 Serial Interface 통신 방식 (입출력 같이 사용)
# Pico 보드에서 starting signal을 보내면 DHT11에서 대응 정보를 제공하는 방식으로 data에 연결된 pinMode는 OUTPUT이 된다.
# sig 신호값 출력의 안정성을 위해 5K pull-up 저항을 사용하거나 이 저항이 장착된 모듈을 사용 할 수 있다.
# 단선 직렬 통신(단선 양방향)은 master가 slave를 호출하면 slave가 응답하는 방식인데, 다른 장치가 사용되는 동안은
# pull-up 으로 High 상태를 유지해야 한다.
# 센서의 전압 변동인 아날로그 신호는 디지털 형식으로 ADC 사용 안 하고 그냥 디지털 핀에 연결할 수 있다.
from machine import Pin, ADC
import network, time
import dht, neopixel # micropython 내장 라이브러리 이용
import _thread
# 필요한 라이브러리 추가
from lcd1602 import LCD # https://toptechboy.com/lcd1602-display-library-for-micropython-and-the-raspberry-pi-pico-w/
# BlynkLib : Blynk 회사가 아닌 다른 community에서 배포한 라이브러리/ 최신 버젼이고 많은 프로젝트에서 사용되고 있다.
# blynklib : Blynk 회사에서 배포한 라이브러리/ 오래된 버전으로 별로 사용 안 되고 있는 듯...
import BlynkLib
from BlynkTimer import BlynkTimer # 주기적으로 센서값을 읽어 들이는 게 while loop의 다른 실행과 지장주지 않도록
# 주기성 timer로 운용하기 위함
#wifi 연결
WIFI_SSID = 'KT_GiGA_10AD' # wokwi 환경에서는 가상으로 설정해도 된다.
WIFI_PASS = '9cbf3xb977' # wokwi 환경에서는 가상으로 설정해도 된다.
wifi = network.WLAN(network.STA_IF)
if not wifi.isconnected():
print("Connecting to WiFi...")
wifi.active(True)
wifi.connect( WIFI_SSID, WIFI_PASS)
cnt = 0
while True:
print('.',end='_')
time.sleep(1)
cnt += 1
if wifi.isconnected():
break
if cnt > 5:
wifi.connect( WIFI_SSID, WIFI_PASS) # 프로그램 처음 실행 시 시간만 가고 연결이 안 될 때 대비해서
# 한 번 씩 .connect() 다시 실행
cnt = 0
print('\nWifi connected, IP:', wifi.ifconfig()[0])
# Blynk 연결
BLYNK_AUTH = 'PGPJ-t086gt4PNG6-2ZXBJi0YV68Www3'
blynk = BlynkLib.Blynk(BLYNK_AUTH, insecure=True)
#soil_moisture : 센서값에 절대적인 의미가 있는게 아니므로 상황에 맞게 그 값의 의미를 운영해야 한다.
soil_moisture_pin = ADC(26)
soil_max = 54000 # 대기 중
soil_min = 24000 # 물 속에서 (값의 유동성이 크다)
def read_soil_moisture():
soil_moisture = soil_moisture_pin.read_u16() # 24000(충분히 젖은 땅) ~ 54000(마른 땅)
soil_moisture_percent = (1- (soil_moisture - soil_min)/(soil_max - soil_min))*100
if soil_moisture_percent < 1:
soil_moisture_percent = 0
elif soil_moisture_percent > 99:
soil_moisture_percent = 100
blynk.virtual_write(4,int(soil_moisture_percent)) # blynk 게이지로 출력
print(f'{int(soil_moisture_percent)} %')
return int(soil_moisture_percent)
# DHT11 + LCD Display
dhts = dht.DHT11(Pin(16)) #DHT22 사용 시 변경
#센서와의 통신 방식에 따라 signal pin은 output으로 사용된다
lcd = LCD() # sda, scl 핀 연결 번호는 라이브러리 내용 참조
def read_dhts():
dhts.measure()
temp = int(dhts.temperature())
humi = int(dhts.humidity())
blynk.virtual_write(2,temp)
blynk.virtual_write(3,humi)
print(f'Temperature : {temp} degree')
print(f'Humidity : {humi} %')
return temp, humi
def read_sensors():
t,h = read_dhts()
s_m = read_soil_moisture()
return t,h,s_m
timer = BlynkTimer()
timer.set_interval(3, read_sensors) # mail while loop의 sequence와 별개로 주기적 실행!
#NeoPixel LED
neo = neopixel.NeoPixel(Pin(15),12) # (Pin(), pixel갯수) strip이나 원형이나 차이 없는 듯
# Fan: 2핀 제품으로 PWM 제어 불가능! 그리고 반대 회전도 불가능하다!
fan1_a = Pin(4, Pin.OUT); fan1_b = Pin(5, Pin.OUT);
fan2_a = Pin(6, Pin.OUT); fan2_b = Pin(7, Pin.OUT)
def fan(): # 쿨링팬 연결 시 극성 주의. 잘못 연결 시 방향만 바뀌지 않고 과전류 흐를 수 있다.
# 모터 드라이버 A/B 위치 잘 확인하고 High 신호에 꼭 fan 양극 연결한다.
fan1_a.value(1); fan1_b.value(0)
fan2_a.value(1); fan2_b.value(0)
time.sleep(5)
fan1_a.value(0); fan1_b.value(0)
fan2_a.value(0); fan2_b.value(0)
@blynk.on('connected') #'connected' 값이 변했을 때 한 번 실행
def connected(): # 함수의 이름은 아무 의미 없다.
print('Blynk Server is Connected, READY.')
@blynk.on('disconnected')
def disconnected():
print('Blynk Server is Disconnected.')
@blynk.on('V0') # 'V0' 값이 변하면 실행
def mode(value): # value 인자는 value[0]이 문자열 '1'이나 '0'으로 주어진다.
global autoMode
if value[0]=='1':
autoMode = 1
else:
autoMode = 0
@blynk.on('V1')
def fan_a(value):
if autoMode == 0:
if value[0]=='1':
fan1_a.value(1); fan1_b.value(0)
fan2_a.value(1); fan2_b.value(0)
else:
fan1_a.value(0); fan1_b.value(0)
fan2_a.value(0); fan2_b.value(0)
else:
blynk.virtual_write(1, 0 if value[0]=='1' else 1)
@blynk.on('V5')
def fan_a(value):
if autoMode == 0:
if value[0]=='1':
neo.fill((0,255,255))
neo.write()
else:
neo.fill((0,0,0))
neo.write()
else:
blynk.virtual_write(5, 0 if value[0]=='1' else 1)
# thread로 독립 운용
# pico 보드에서는 일단 시작된 thread는 끝난 다음에는 다시 시작할 수 없다.
def warning_led():
global warning # global 설정 필요
warning = True
while warning:
for i in range(12):
neo.fill((255,0,0))
neo[i]=(0,255,0)
neo.write()
time.sleep(0.1)
if not warning:
neo.fill((0,0,0))
neo.write()
while not warning: # 외부 while문이 for문을 반복하다가 상황에 따라 내부 while 문에 의해 멈추었다, 다시 외부 while 문 반복하게 함
pass # 다시 외부 while 문 반복하게 하는 trick!!!
# thread를 죽이지 않으면서 어떤 일을 수행하게 하거나 멈추는 작동이 가능하다.
autoMode = 0
blynk.virtual_write(1,0)
neo.fill((0,255,0))
neo.write()
_thread.start_new_thread(warning_led,()) # thread 실행
time.sleep(1)
warning = False
blynk.virtual_write(0,0)
blynk.virtual_write(1,0)
blynk.virtual_write(5,0)
while True:
blynk.run()
timer.run()
t,h,s_m = read_sensors()
if autoMode == 1: # manual 모드에서는 blynk 버튼 명령 수행
if h > 40:
blynk.virtual_write(1,1)
fan1_a.value(1); fan1_b.value(0)
fan2_a.value(1); fan2_b.value(0)
neo.fill((0,255,0))
neo.write()
blynk.virtual_write(5,1)
if h < 37:
blynk.virtual_write(1,0)
fan1_a.value(0); fan1_b.value(0)
fan2_a.value(0); fan2_b.value(0)
if not warning:
neo.fill((0,0,0))
neo.write()
blynk.virtual_write(5,0)
if s_m < 30:
warning = True
lcd.clear()
lcd.write(0,0,'Not Enough Water')
lcd.write(0,1,'Supply Water!')
else:
warning=False
time.sleep(0.5)
lcd.write(0,0, f'T : {t} C, H : {h} %')
lcd.write(0,1, f'S_M : {s_m} %')
time.sleep(1) # timer의 interval과의 충돌은??????