forked from TFro3/Raspberry-Pi-Grow-Tent-Controller
-
Notifications
You must be signed in to change notification settings - Fork 0
/
Copy pathGrowController.py
480 lines (407 loc) · 20.3 KB
/
GrowController.py
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
198
199
200
201
202
203
204
205
206
207
208
209
210
211
212
213
214
215
216
217
218
219
220
221
222
223
224
225
226
227
228
229
230
231
232
233
234
235
236
237
238
239
240
241
242
243
244
245
246
247
248
249
250
251
252
253
254
255
256
257
258
259
260
261
262
263
264
265
266
267
268
269
270
271
272
273
274
275
276
277
278
279
280
281
282
283
284
285
286
287
288
289
290
291
292
293
294
295
296
297
298
299
300
301
302
303
304
305
306
307
308
309
310
311
312
313
314
315
316
317
318
319
320
321
322
323
324
325
326
327
328
329
330
331
332
333
334
335
336
337
338
339
340
341
342
343
344
345
346
347
348
349
350
351
352
353
354
355
356
357
358
359
360
361
362
363
364
365
366
367
368
369
370
371
372
373
374
375
376
377
378
379
380
381
382
383
384
385
386
387
388
389
390
391
392
393
394
395
396
397
398
399
400
401
402
403
404
405
406
407
408
409
410
411
412
413
414
415
416
417
418
419
420
421
422
423
424
425
426
427
428
429
430
431
432
433
434
435
436
437
438
439
440
441
442
443
444
445
446
447
448
449
450
451
452
453
454
455
456
457
458
459
460
461
462
463
464
465
466
467
468
469
470
471
472
473
474
475
476
477
478
479
480
import RPi.GPIO as GPIO
import datetime
import time
import threading
#import Adafruit_DHT
import smbus
import sqlite3
import cv2
import numpy as np
import logging
import os
#define directories and filenames
WORKING_DIR = '/var/www/html/grow/'
IMG_SUBDIR = 'images/'
LOG_FILE = 'grow.log'
SQLITE_FILE = "grow.db"
# Define pin numbers
LIGHTS_PIN = 5
FAN_PIN = 6
HUMIDIFIER_PIN = 10
HEATER_PIN = 9
DEHUMIDIFIER_PIN = 7
PUMP_PIN = 8
# DHT22 Sensor //not connected in this instance
SENSOR_PIN = 17
# Define I2C bus number
IIC_BUS = 1
# Define the Cameras
CAM_ID_TOP = 0
CAM_ID_BOTTOM = 2
REBOOT_CAM_FAILURE_THRESHOLD=5
global rebootcount = 0
#set up logging
logger = logging.getLogger(__name__)
logging.basicConfig(filename=WORKING_DIR + LOG_FILE,encoding='utf-8',level=logging.DEBUG,format='%(asctime)s - %(levelname)s - %(message)s')
logger.debug("GrowController.py was started")
# Set up the database connection and cursor
try:
con = sqlite3.connect(WORKING_DIR + SQLITE_FILE)
con.row_factory = sqlite3.Row
cur = con.cursor()
#print("Database connected")
logger.debug("Database connected")
except Exception as e:
#print("Err while trying to connect to the database :" + e)
logger.error("Err while trying to connect to the database :" + str(e))
# Set up the Tables if this is a new instance
tablecreatequery = "CREATE TABLE IF NOT EXISTS environment (time TEXT, temp TEXT, humid TEXT, relaymask TEXT, picfile TEXT);"
cur.execute(tablecreatequery)
tablecreatequery = "CREATE TABLE IF NOT EXISTS settings ("
tablecreatequery += "lightson TEXT DEFAULT \"5:00AM\" , "
tablecreatequery += "lightsoff TEXT DEFAULT \"8:00PM\", "
tablecreatequery += "pumprun INTEGER DEFAULT 90, "
tablecreatequery += "pumpinter INTEGER DEFAULT 600, "
tablecreatequery += "fantemp INTEGER DEFAULT 85, "
tablecreatequery += "heattemp INTEGER DEFAULT 75, "
tablecreatequery += "fanhumid INTEGER DEFAULT 85, "
tablecreatequery += "humidhumid INTEGER DEFAULT 70, "
tablecreatequery += "dehumidhumid INTEGER DEFAULT 80, "
tablecreatequery += "lightsenab INTEGER DEFAULT 1, "
tablecreatequery += "fanenab INTEGER DEFAULT 1, "
tablecreatequery += "humidenab INTEGER DEFAULT 0, "
tablecreatequery += "heatenab INTEGER DEFAULT 1, "
tablecreatequery += "dehumidenab INTEGER DEFAULT 0, "
tablecreatequery += "pumpenab INTEGER DEFAULT 0, "
tablecreatequery += "sensortype TEXT DEFAULT \"SHT30\");"
cur.execute(tablecreatequery)
con.commit()
# populate the settings if it has never been done
cur.execute("SELECT * FROM settings;")
checkset = cur.fetchone()
if checkset is None:
settingcreatequery = "INSERT INTO settings DEFAULT VALUES;"
cur.execute(settingcreatequery)
con.commit()
#print("The settings table doesn't exist, so it was created!")
logger.error("The settings table doesn't exist, so it was created!")
cur.execute("SELECT * FROM settings;")
settings = cur.fetchone()
# Define lights on and off times (in 12-hour format with AM/PM)
global lights_on_time
lights_on_time = datetime.datetime.strptime(settings['lightson'],'%I:%M%p').time() # lights on time
global lights_off_time
lights_off_time = datetime.datetime.strptime(settings['lightsoff'],'%I:%M%p').time() # lights off time
# Define pump runtime and interval (in seconds) //pump not connected here
global pump_runtime
pump_runtime = settings['pumprun'] # pump runtime in seconds (90 seconds = 1.5 minutes)
global pump_interval
pump_interval = settings['pumpinter'] # pump interval in seconds (600 seconds = 10 minutes)
# Define temperature and humidity thresholds
global Temperature_Threshold_Fan
Temperature_Threshold_Fan = settings['fantemp'] # Will turn on Fan if temperature in Fahrenheit (F) is above this value.
global Temperature_Threshold_Heat
Temperature_Threshold_Heat = settings['heattemp'] # Will turn on Heat if temperature in Fahrenheit (F) is below this value.
global Humidity_Threshold_Fan
Humidity_Threshold_Fan = settings['fanhumid'] # Will turn on Fan once humidity is above this percentage (%) to try and move lower humidity air in. Disable this if humidity outside the tent/room is higher.
global Humidity_Threshold_Humidifier
Humidity_Threshold_Humidifier = settings['humidhumid'] # Will turn on Humidifier once humidity is below this percentage (%).
global Humidity_Threshold_Dehumidifier
Humidity_Threshold_Dehumidifier = settings['dehumidhumid'] # Will turn on Dehumidifier once humidity is above this percentage (%).
# Define appliance control flags (True: Enabled, False: Disabled)
global lights_enabled
lights_enabled = bool(settings['lightsenab']) # Change to True or False
global fan_enabled
fan_enabled = bool(settings['fanenab']) # Change to True or False
global humidifier_enabled
humidifier_enabled = bool(settings['humidenab']) # Change to True or False
global heater_enabled
heater_enabled = bool(settings['heatenab']) # Change to True or False
global dehumidifier_enabled
dehumidifier_enabled = bool(settings['dehumidenab']) # Change to True or False
global pump_enabled
pump_enabled = bool(settings['pumpenab']) # Change to True or False
global sensor_type
sensor_type = settings['sensortype'] #DHT22 or SHT30
logquery=''
#Load settings from database file")
for setting in settings:
logquery += str(setting) + ", "
logger.debug("Settings Loaded from database file")
logger.debug(logquery);
# Set up GPIO
GPIO.setmode(GPIO.BCM)
GPIO.setup([LIGHTS_PIN, FAN_PIN, HUMIDIFIER_PIN, HEATER_PIN, DEHUMIDIFIER_PIN, PUMP_PIN], GPIO.OUT)
# Set up I2C bus
bus = smbus.SMBus(IIC_BUS)
# Function to update settings from the database
def update_settings():
global lights_on_time
global lights_off_time
global pump_runtime
global pump_interval
global Temperature_Threshold_Fan
global Temperature_Threshold_Heat
global Humidity_Threshold_Fan
global Humidity_Threshold_Humidifier
global Humidity_Threshold_Dehumidifier
global lights_enabled
global fan_enabled
global humidifier_enabled
global heater_enabled
global dehumidifier_enabled
global pump_enabled
global sensor_type
cur.execute("SELECT * FROM settings;")
upsettings = cur.fetchone()
logquery=''
for upsetting in upsettings:
logquery += str(upsetting) + ", "
logger.debug("The updated settings were fetched from the database")
logger.debug(logquery)
# Define lights on and off times (in 12-hour format with AM/PM)
lights_on_time = datetime.datetime.strptime(upsettings['lightson'],'%I:%M%p').time() # lights on time
lights_off_time = datetime.datetime.strptime(upsettings['lightsoff'],'%I:%M%p').time() # lights off time
# Define pump runtime and interval (in seconds) //pump not connected here
pump_runtime = upsettings['pumprun'] # pump runtime in seconds (90 seconds = 1.5 minutes)
pump_interval = upsettings['pumpinter'] # pump interval in seconds (600 seconds = 10 minutes)
# Define temperature and humidity thresholds
Temperature_Threshold_Fan = upsettings['fantemp'] # Will turn on Fan if temperature in Fahrenheit (F) is above this value.
Temperature_Threshold_Heat = upsettings['heattemp'] # Will turn on Heat if temperature in Fahrenheit (F) is below this value.
Humidity_Threshold_Fan = upsettings['fanhumid'] # Will turn on Fan once humidity is above this percentage (%) to try and move lower humidity >
Humidity_Threshold_Humidifier = upsettings['humidhumid'] # Will turn on Humidifier once humidity is below this percentage (%).
Humidity_Threshold_Dehumidifier = upsettings['dehumidhumid'] # Will turn on Dehumidifier once humidity is above this percentage (%).
# Define appliance control flags (True: Enabled, False: Disabled)
lights_enabled = bool(upsettings['lightsenab']) # Change to True or False
fan_enabled = bool(upsettings['fanenab']) # Change to True or False
humidifier_enabled = bool(upsettings['humidenab']) # Change to True or False
heater_enabled = bool(upsettings['heatenab']) # Change to True or False
dehumidifier_enabled = bool(upsettings['dehumidenab']) # Change to True or False
pump_enabled = bool(upsettings['pumpenab']) # Change to True or False
sensor_type = upsettings['sensortype'] #DHT22 or SHT30
logger.debug("The update_settings function believes that: "+str(lights_on_time) + str(lights_off_time) + str(pump_runtime) + str(pump_interval) + str(Temperature_Threshold_Fan) + str(Temperature_Threshold_Heat) + str(Humidity_Threshold_Fan) + str(Humidity_Threshold_Humidifier) + str(Humidity_Threshold_Dehumidifier) + str(lights_enabled) + str(fan_enabled) + str(humidifier_enabled) + str(heater_enabled) + str(dehumidifier_enabled) + str(pump_enabled) + str(sensor_type))
# Function to print status with device and status information
def print_status(device, status):
if device == "Lights" and not lights_enabled:
print(f"{device}: \033[91mDisabled\033[0m")
elif device == "Fan" and not fan_enabled:
print(f"{device}: \033[91mDisabled\033[0m")
elif device == "Humidifier" and not humidifier_enabled:
print(f"{device}: \033[91mDisabled\033[0m")
elif device == "Dehumidifier" and not dehumidifier_enabled:
print(f"{device}: \033[91mDisabled\033[0m")
elif device == "Heater" and not heater_enabled:
print(f"{device}: \033[91mDisabled\033[0m")
elif device == "Pump" and not pump_enabled:
print(f"{device}: \033[91mDisabled\033[0m")
elif device == "Sensor" and not sensor_type == "DHT22" and not sensor_type == "SHT30":
print(f"{device}: \033[91mIncompatible\033[0m")
else:
print(f"{device}: {status}")
# Function to create a binary mask for the state of the relays
def get_relay_mask():
return str(GPIO.input(LIGHTS_PIN)) + str(GPIO.input(FAN_PIN)) + str(GPIO.input(HUMIDIFIER_PIN)) + str(GPIO.input(HEATER_PIN)) + str(GPIO.input(DEHUMIDIFIER_PIN)) + str(GPIO.input(PUMP_PIN))
# Function to read temperature from DHT22 sensor
def get_temperature():
if sensor_type == "DHT22":
sensor = Adafruit_DHT.DHT22
humidity, temperature = Adafruit_DHT.read_retry(sensor, SENSOR_PIN)
if temperature is not None:
return temperature * 9/5.0 + 32 # Convert Celsius to Fahrenheit
else:
return None # Return None if reading failed
elif sensor_type == "SHT30":
# SHT30 address, 0x44(68)
# Send measurement command, 0x2C(44)
# 0x06(06) High repeatability measurement
bus.write_i2c_block_data(0x44, 0x2C, [0x06])
time.sleep(1)
# SHT30 address, 0x44(68)
# Read data back from 0x00(00), 6 bytes
# cTemp MSB, cTemp LSB, cTemp CRC, Humididty MSB, Humidity LSB, Humidity CRC
#trying to read the reply
data = bus.read_i2c_block_data(0x44, 0x00, 6)
# Convert the data
cTemp = ((((data[0] * 256.0) + data[1]) * 175) / 65535.0) - 45
fTemp = cTemp * 1.8 + 32
return fTemp
else:
return None #Return none if sensor type is not set correctly
# Function to read humidity from DHT22 sensor
def get_humidity():
if sensor_type == "DHT22":
sensor = Adafruit_DHT.DHT22
humidity, temperature = Adafruit_DHT.read_retry(sensor, SENSOR_PIN)
if humidity is not None:
return humidity
else:
return None # Return None if reading failed
elif sensor_type == "SHT30":
# SHT30 address, 0x44(68)
# Send measurement command, 0x2C(44)
# 0x06(06) High repeatability measurement
bus.write_i2c_block_data(0x44, 0x2C, [0x06])
time.sleep(0.5)
# SHT30 address, 0x44(68)
# Read data back from 0x00(00), 6 bytes
# cTemp MSB, cTemp LSB, cTemp CRC, Humididty MSB, Humidity LSB, Humidity CRC
data = bus.read_i2c_block_data(0x44, 0x00, 6)
# Convert the data
humidity = 100 * (data[3] * 256 + data[4]) / 65535.0
return humidity
else:
return None #Return none if sensor type is not set correctly
# Function to control the pump based on configured runtime and interval
def control_pump():
while True:
if pump_enabled:
timestamp = datetime.datetime.now().strftime("%Y-%m-%d %I:%M:%S %p")
#print(f"Current Pump Status:\nPump: \033[92mON\033[0m for {pump_runtime} seconds\nTimestamp: {timestamp}\n")
GPIO.output(PUMP_PIN, GPIO.HIGH) # Turn on the pump relay
time.sleep(pump_runtime) # Run the pump for the specified duration
GPIO.output(PUMP_PIN, GPIO.LOW) # Turn off the pump relay
timestamp = datetime.datetime.now().strftime("%Y-%m-%d %I:%M:%S %p")
#print(f"Current Pump Status:\nPump: \033[93mOFF\033[0m for {pump_interval} seconds\nTimestamp: {timestamp}\n")
time.sleep(pump_interval) # Wait for the remaining interval
else:
timestamp = datetime.datetime.now().strftime("%Y-%m-%d %I:%M:%S %p")
#print(f"Current Pump Status:\nPump: \033[91mOFF\033[0m\nTimestamp: {timestamp}\n")
time.sleep(60) # Wait for a minute if the pump is disabled
#Function to save a picture with the webcam
def snap_shot():
global rebootcount
img_name = 'noimage'
top = snap_single(CAM_ID_TOP)
bottom = snap_single(CAM_ID_BOTTOM)
#glue both images together top to bottom
try:
both = np.concatenate((top,bottom), axis=0)
img_name = "growbox_" + datetime.datetime.now().strftime("%Y%m%d_%H%M%S") + ".jpg"
write_status = cv2.imwrite(WORKING_DIR + IMG_SUBDIR + img_name, both, [int(cv2.IMWRITE_JPEG_QUALITY), 30])
logger.debug(img_name)
if write_status is True:
logger.debug("Writing image successful")
else:
logger.error("Writing image Failed!")
img_name = 'noimage'
except Exception as e:
logger.error("An error occured while trying to concatonate and write the images " + str(e))
logger.error("Attempting reboot " + str(datetime.datetime.now().time()))
#There is a bug in the RPi3B USB controller that cause it to disconnect from all devices.
#Currently only fixed by a reboot. So. Here we are. Find a way to remove this ASAP.
rebootcount++
if rebootcount = REBOOT_CAM_FAILURE_THRESHOLD
os.system('sudo systemctl reboot -i')
return img_name
#function to capture a single frame
def snap_single(index):
# intialize the webcam
snapped = False;
cam = cv2.VideoCapture(index, cv2.CAP_V4L2)
cam.set(cv2.CAP_PROP_FOURCC, cv2.VideoWriter_fourcc('M', 'J', 'P', 'G'))
width = 2592
height = 1944
#img_name = "noimagecaptured"
cam.set(cv2.CAP_PROP_FRAME_WIDTH, width)
cam.set(cv2.CAP_PROP_FRAME_HEIGHT, height)
#Webcam Initialized
logger.debug("Webcam Initialized")
# intializing frame
while not snapped:
ret, frame = cam.read()
if not ret:
logger.error("Failed to capture frame")
break
# name the image files with a timestamp
#img_name = "growbox_" + str(datetime.datetime.now().time()).replace(':','').replace('.','') + ".png"
#flip and rotate the image
frame = cv2.flip(frame,0)
frame = cv2.rotate(frame,cv2.ROTATE_90_CLOCKWISE)
snapped = True
# release the camera
cam.release()
#print("Camera Released")
logger.debug("Camera Released")
return frame
# Print current settings
#print("LIGHTS ON:" + str(lights_on_time))
#print("LIGHTS OFF:" + str(lights_off_time))
#print("TIMENOW: " + str(datetime.datetime.now().time()))
#print("LIGHTS?" + str(bool(lights_enabled and lights_on_time <= datetime.datetime.now().time() < lights_off_time)))
# Start the pump control loop in a separate thread
pump_thread = threading.Thread(target=control_pump)
pump_thread.daemon = True # Daemonize the thread to allow main program exit
pump_thread.start()
try:
# Startup sequence to test relay functionality
# print("\033[92m\nRaspberry Pi Grow Tent/Room Controller - Version 1.0\033[0m")
# print("\033[94mDedicated to Emma. My dog who loved to smell flowers and eat vegetables right off the plants.\nMay she rest in peace.\n\033[0m")
# print("OpenCV2 version: " + str(cv2.__version__))
# time.sleep(5)
# print("Startup Sequence: \033[93mTesting Relays...\033[0m")
GPIO.output([LIGHTS_PIN, FAN_PIN, HUMIDIFIER_PIN, HEATER_PIN, DEHUMIDIFIER_PIN, PUMP_PIN], GPIO.HIGH) # Turn on all relays except the pump
time.sleep(1) # Keep all relays on for 1 second
GPIO.output([LIGHTS_PIN, FAN_PIN, HUMIDIFIER_PIN, HEATER_PIN, DEHUMIDIFIER_PIN, PUMP_PIN], GPIO.LOW) # Turn off all relays except the pump
# print("Startup Sequence: \033[92mRelay Test Complete.\033[0m\n")
# print_status("Sensor: ", sensor_type)
time.sleep(1)
# Main loop for controlling relays based on thresholds and snapping timelapse pics from the webcam...
while True:
update_settings()
logger.debug("The Main Loop believes that: "+str(lights_on_time) + str(lights_off_time) + str(pump_runtime) + str(pump_interval) + str(Temperature_Threshold_Fan) + str(Temperature_Threshold_Heat) + str(Humidity_Threshold_Fan) + str(Humidity_Threshold_Humidifier) + str(Humidity_Threshold_Dehumidifier) + str(lights_enabled) + str(fan_enabled) + str(humidifier_enabled) + str(heater_enabled) + str(dehumidifier_enabled) + str(pump_enabled) + str(sensor_type))
# print("Current Status:")
check_time = datetime.datetime.now().time()
filename="nopicture"
if lights_enabled and lights_on_time <= check_time < lights_off_time:
GPIO.output(LIGHTS_PIN, GPIO.HIGH)
# print_status("Lights", "\033[92mON\033[0m")
# get a webcam snapshot
#print("Attempting snapshot")
logger.debug("Attempting snapshot")
filename = snap_shot()
else:
GPIO.output(LIGHTS_PIN, GPIO.LOW)
# print_status("Lights", "\033[93mOFF\033[0m")
temperature = get_temperature()
humidity = get_humidity()
if fan_enabled and (temperature >= Temperature_Threshold_Fan or humidity >= Humidity_Threshold_Fan):
GPIO.output(FAN_PIN, GPIO.HIGH)
#print_status("Fan", "\033[92mON\033[0m")
else:
GPIO.output(FAN_PIN, GPIO.LOW)
#print_status("Fan", "\033[93mOFF\033[0m")
if humidifier_enabled and humidity < Humidity_Threshold_Humidifier:
GPIO.output(HUMIDIFIER_PIN, GPIO.HIGH)
#print_status("Humidifier", "\033[92mON\033[0m")
else:
GPIO.output(HUMIDIFIER_PIN, GPIO.LOW)
#print_status("Humidifier", "\033[93mOFF\033[0m")
if dehumidifier_enabled and humidity > Humidity_Threshold_Dehumidifier:
GPIO.output(DEHUMIDIFIER_PIN, GPIO.HIGH)
#print_status("Dehumidifier", "\033[92mON\033[0m")
else:
GPIO.output(DEHUMIDIFIER_PIN, GPIO.LOW)
#print_status("Dehumidifier", "\033[93mOFF\033[0m")
if heater_enabled and temperature < Temperature_Threshold_Heat:
GPIO.output(HEATER_PIN, GPIO.HIGH)
#print_status("Heater", "\033[92mON\033[0m")
else:
GPIO.output(HEATER_PIN, GPIO.LOW)
#print_status("Heater", "\033[93mOFF\033[0m")
#if not pump_enabled:
#print_status("Pump", "\033[91mDisabled\033[0m")
#else:
#print_status("Pump", "\033[92mEnabled\033[0m")
if temperature is not None:
logger.debug("Temperature: " + str(temperature) + " vs " + str(Temperature_Threshold_Heat))
if humidity is not None:
logger.debug("Humidity: " + str(humidity) + " vs " + str(Humidity_Threshold_Fan))
timestamp = datetime.datetime.now().strftime("%Y-%m-%d %I:%M:%S %p")
#print(f"Timestamp: {timestamp}\n")
logger.debug("Timestamp: " + timestamp)
query = "INSERT INTO environment (time,temp,humid,relaymask,picfile) VALUES ('" + str(timestamp) + "', '" + str(get_temperature()) + "', '" + str(get_humidity()) + "', '" + str(get_relay_mask()) + "', '" + filename + "');"
#print("Trying query: " + query)
logger.debug("Trying query: " + query)
cur.execute(query)
con.commit()
time.sleep(60) # Adjust this sleep duration as needed
except KeyboardInterrupt:
timestamp = datetime.datetime.now().strftime("%Y-%m-%d %I:%M:%S %p")
logger.error("The program was interuppted by the user at: " + timestamp)
GPIO.cleanup()
con.close()
except Exception as e:
#print(f"An error occurred: {str(e)}")
logger.error("An error occured: " + str(e))
GPIO.cleanup()
con.close()