diff --git a/.gitignore b/.gitignore index 64bf1ff..1f5144d 100644 --- a/.gitignore +++ b/.gitignore @@ -19,6 +19,8 @@ *.log logs/ +#local +*.local* # MISC camera.sh manual_transfer.py diff --git a/ControlPanel/README.md b/ControlPanel/README.md index a5e9fbb..0b8ee62 100644 --- a/ControlPanel/README.md +++ b/ControlPanel/README.md @@ -1 +1,7 @@ -This is an website built to directly interface with the garden and display status from the various modules in the system. \ No newline at end of file +# Control Panel + +This is an website built to directly interface with the garden and display status from the various modules in the system. + +## **Warning:** This will probably be replaced with [Home Assistant](https://www.home-assistant.io/) in the future. + +![alt text](https://raw.github.com/ataffe/smartGarden/Dev/images/ControlPanel.PNG) diff --git a/GardenModules/README.md b/GardenModules/README.md new file mode 100644 index 0000000..96f9103 --- /dev/null +++ b/GardenModules/README.md @@ -0,0 +1,3 @@ +# Garden Modules + +This folder houses the gardent modules. Each module represents and manages some form of input or output, e.g. a soil moisture sensor. The idea behind this approach is to make adding features as simple as adding a new module. \ No newline at end of file diff --git a/GardenModules/artificalLight/README.md b/GardenModules/artificalLight/README.md new file mode 100644 index 0000000..befcf3d --- /dev/null +++ b/GardenModules/artificalLight/README.md @@ -0,0 +1,3 @@ +# Artificial Light Sensor + +This is a module to control a relay that controls artificial lighting for plants. This module was built for the [SunFounder 2 Channel DC 5V Relay Module with Optocoupler Low Level Trigger Expansion Board for Arduino](https://www.amazon.com/gp/product/B00E0NTPP4/ref=ppx_yo_dt_b_search_asin_title?ie=UTF8&psc=1) \ No newline at end of file diff --git a/GardenModules/gardenServer/README.md b/GardenModules/gardenServer/README.md new file mode 100644 index 0000000..b509106 --- /dev/null +++ b/GardenModules/gardenServer/README.md @@ -0,0 +1,3 @@ +# Garden Server + +This is a server that uses Flask to remotely interface with the garden. This includes a heartbeat REST endpoint, that can be used to detect if a sensor is still working. \ No newline at end of file diff --git a/GardenModules/gardenServer/gardenServer.py b/GardenModules/gardenServer/gardenServer.py index c4fb05e..bb4ab3b 100644 --- a/GardenModules/gardenServer/gardenServer.py +++ b/GardenModules/gardenServer/gardenServer.py @@ -5,7 +5,7 @@ import logging import time -app = Flask(__name__, template_folder='/home/pi/Desktop/smartGarden/smartGarden/ControlPanel', static_folder="/home/pi/Desktop/smartGarden/smartGarden/ControlPanel") +app = Flask(__name__, template_folder='./ControlPanel', static_folder="./ControlPanel") CORS(app) Debug(app) @@ -105,9 +105,56 @@ def get_light(): print(e) +@app.route('/soil') +def soil_route(): + try: + with open("./soilLog.txt") as file: + #lines = file.readlines() + table = "" + for count, line in reversed(list(enumerate(file))): + fields = line.split() + raw_value = fields[8] + percent = fields[3] + date = fields[4] + time = fields[5] + + if count % 2 == 0: + table = table + "" + else: + table = table + "" + + table = table + "" + date + "" + table = table + "" + time + "" + table = table + "" + percent + "" + table = table + "" + raw_value + "" + table = table + "" + return ''' + + + Soil Moisture - Smart Garden + + +

Soil Moisture Data

+ + + + + + + + ''' + table + ''' +
DateTimeSoil Moisture PercentSoil Moisture Raw Value
+ + + ''' + except Exception as e: + logging.warn("There was an exception returning soil data to rest endpoint: " + str(e)) + return "There was an exception: " + str(e) + + @app.route('/garden') def garden_route(): - with open("/home/pi/Desktop/smartGarden/smartGarden/logs/smartGardenLog.txt") as file: + with open("./logs/smartGardenLog.txt") as file: return file.read() @@ -119,31 +166,31 @@ def control_panel(): @app.route('/water') def control_panel_water(): - with open('/home/pi/Desktop/smartGarden/smartGarden/ControlPanel/water.html') as file: + with open('./ControlPanel/water.html') as file: return file.read() @app.route('/light') def control_panel_light(): - with open('/home/pi/Desktop/smartGarden/smartGarden/ControlPanel/light.html') as file: + with open('./ControlPanel/light.html') as file: return file.read() @app.route('/soilMoisture') def control_panel_soil_moisture(): - with open('/home/pi/Desktop/smartGarden/smartGarden/ControlPanel/soilMoisture.html') as file: + with open('./ControlPanel/soilMoisture.html') as file: return file.read() @app.route('/sun_css') def sun_css(): - with open('/home/pi/Desktop/smartGarden/smartGarden/ControlPanel/sun.css') as file: + with open('./ControlPanel/sun.css') as file: return file.read() @app.route('/status_css') def status_css(): - with open('/home/pi/Desktop/smartGarden/smartGarden/ControlPanel/status.css') as file: + with open('./ControlPanel/status.css') as file: return file.read() # End Control Panel Endpoints diff --git a/GardenModules/luxSensor/README.md b/GardenModules/luxSensor/README.md new file mode 100644 index 0000000..21f33b5 --- /dev/null +++ b/GardenModules/luxSensor/README.md @@ -0,0 +1,3 @@ +# Lux Sensor + +This is a module for a [Adafruit TSL2591 High Dynamic Range Digital Light Sensor.](https://www.amazon.com/gp/product/B00XW2OFWW/ref=ppx_yo_dt_b_search_asin_title?ie=UTF8&psc=1) \ No newline at end of file diff --git a/GardenModules/luxSensor/luxSensor.py b/GardenModules/luxSensor/luxSensor.py index 805ffd6..49b2b9f 100644 --- a/GardenModules/luxSensor/luxSensor.py +++ b/GardenModules/luxSensor/luxSensor.py @@ -19,7 +19,7 @@ def __init__(self, log, queue): self._sensor.gain = 0 self._grow_light_lux = 535 self._lux_interval = 120 - self._data_file = "/home/pi/Desktop/smartGarden/smartGarden/Data/luxData.csv" + self._data_file = "./Data/luxData.csv" self._log.info("Lux sensor start up successful") except Exception as exception: self._log.error("Lux sensor failed to start up.") diff --git a/GardenModules/prune/prune.py b/GardenModules/prune/prune.py index b4ba474..9142b21 100644 --- a/GardenModules/prune/prune.py +++ b/GardenModules/prune/prune.py @@ -3,7 +3,7 @@ def prune(file): lines = [] try: - logFile = open("/home/pi/Desktop/smartGarden/smartGarden/logs/" + file, "r") + logFile = open("./logs/" + file, "r") for line in logFile: lines.append(line) except Exception as e: @@ -12,7 +12,7 @@ def prune(file): logFile.close() try: - logFile = open("/home/pi/Desktop/smartGarden/smartGarden/logs/" + file, "w") + logFile = open("./logs/" + file, "w") if len(lines) > 5000: # Only keep the last 5000 lines for x in range(5000): diff --git a/GardenModules/pump/README.md b/GardenModules/pump/README.md new file mode 100644 index 0000000..130604b --- /dev/null +++ b/GardenModules/pump/README.md @@ -0,0 +1,3 @@ +# Water Pump Module + +This module is to control a DC water pump connected to a [DROK 200203 DC 5-36V 400W Dual Large Power MOS Transistor Driving Module](https://www.amazon.com/gp/product/B01J78FX9S/ref=ppx_yo_dt_b_search_asin_title?ie=UTF8&psc=1) [Here](https://www.amazon.com/gp/product/B07DW4WRV8/ref=ppx_yo_dt_b_search_asin_title?ie=UTF8&psc=1) is the pump used. \ No newline at end of file diff --git a/GardenModules/soilMoisture/README.md b/GardenModules/soilMoisture/README.md new file mode 100644 index 0000000..aef895c --- /dev/null +++ b/GardenModules/soilMoisture/README.md @@ -0,0 +1,3 @@ +# Soil Moisture Sensor + +This is a module to control a [Gikfun Capacitive Soil Moisture Sensor](https://www.amazon.com/gp/product/B07H3P1NRM/ref=ppx_yo_dt_b_search_asin_title?ie=UTF8&psc=1) via I2C. The soil moisture sensor is connected to a [Onyehn ADS1115 16 Byte 4 Channel I2C IIC Analog-to-Digital ADC Converter](https://www.amazon.com/gp/product/B07L3Q7N7T/ref=ppx_yo_dt_b_search_asin_title?ie=UTF8&psc=1) \ No newline at end of file diff --git a/GardenModules/soilMoisture/soil.py b/GardenModules/soilMoisture/soil.py index ba057c4..0b9684d 100644 --- a/GardenModules/soilMoisture/soil.py +++ b/GardenModules/soilMoisture/soil.py @@ -23,7 +23,7 @@ def __init__(self, log, queue): self.soilInterval = 120 # 1800 self._log.info("Channel: " + str(self.channel)) self.setName("soilThread") - self._data_file = "/home/pi/Desktop/smartGarden/smartGarden/Data/soilMoistureData.csv" + self._data_file = "./Data/soilMoistureData.csv" # Set Gain to 16 bits # Gain = 1 # Wet: 13884 Dry: 21680 @@ -69,7 +69,7 @@ def _checkSoil(self): self._log.exception("Error calculating soil moisture") try: - with open("/home/pi/Desktop/smartGarden/smartGarden/logs/soilLog.txt", "a+") as logFile: + with open("./logs/soilLog.txt", "a+") as logFile: logFile.write("Soil Moisture Level: " + str(self.getSoilPercentage()) + "% " + str( datetime.now()) + " Raw Value: " + str(self.channel.value) + "\n") except Exception as exception: diff --git a/GardenModules/tempSensor/README.md b/GardenModules/tempSensor/README.md new file mode 100644 index 0000000..fc4d7e5 --- /dev/null +++ b/GardenModules/tempSensor/README.md @@ -0,0 +1,3 @@ +# Temperature Sensor + +This is a module to read data from a [DFRobot Waterproof DS18B20 Temperature Sensor Kit](https://www.amazon.com/gp/product/B07434MB77/ref=ppx_yo_dt_b_search_asin_title?ie=UTF8&psc=1) \ No newline at end of file diff --git a/ModuleTests/Data/luxData.csv b/Tests/Data/luxData.csv similarity index 97% rename from ModuleTests/Data/luxData.csv rename to Tests/Data/luxData.csv index 84f47c5..f4528b7 100644 --- a/ModuleTests/Data/luxData.csv +++ b/Tests/Data/luxData.csv @@ -1,3 +1,3 @@ -527.978462833967,2020-11-29 02:55:51.918946 -528.5072462839585,2020-11-29 02:56:02.070627 -529.5646851648721,2020-11-29 02:56:12.221543 +527.978462833967,2020-11-29 02:55:51.918946 +528.5072462839585,2020-11-29 02:56:02.070627 +529.5646851648721,2020-11-29 02:56:12.221543 diff --git a/ModuleTests/GardenModules/GardenModule.py b/Tests/GardenModules/GardenModule.py similarity index 100% rename from ModuleTests/GardenModules/GardenModule.py rename to Tests/GardenModules/GardenModule.py diff --git a/ModuleTests/GardenModules/artificalLight/artificalLight.py b/Tests/GardenModules/artificalLight/artificalLight.py similarity index 100% rename from ModuleTests/GardenModules/artificalLight/artificalLight.py rename to Tests/GardenModules/artificalLight/artificalLight.py diff --git a/ModuleTests/GardenModules/email/email.py b/Tests/GardenModules/email/email.py similarity index 97% rename from ModuleTests/GardenModules/email/email.py rename to Tests/GardenModules/email/email.py index 2620bbe..362f38a 100644 --- a/ModuleTests/GardenModules/email/email.py +++ b/Tests/GardenModules/email/email.py @@ -1,163 +1,163 @@ -from email.mime.text import MIMEText -from email.mime.multipart import MIMEMultipart -from email.mime.base import MIMEBase -from email.utils import formatdate -from email import encoders -from datetime import datetime -from datetime import timedelta -import smtplib, ssl -import logging - -def send_email(): - port = 465 # For SSL - password = "al.EX.91.27" - sender_email = "raspberry.pi.taffe@gmail.com" - receiver_email = "taffeAlexander@gmail.com" - message = MIMEMultipart("alternative") - message["Subject"] = "Garden update: " + formatdate(localtime=True) - message["From"] = sender_email - message["To"] = receiver_email - message["Date"] = formatdate(localtime=True) - soilMoisture = "No Data" - soilTimeStamp = "No Data" - soilIterator = 0 - soilMoistureArray = [] - soilTimeStampArray = [] - currentYMD = str(datetime.now()).split()[0] - - # Create the body of the message (a plain-text and an HTML version). - text = "Garden update plan text" - - try: - html = """\ - - - - - -

Garden Update

- - - - - - - - """ - soilLogArray = [] - with open("/home/pi/Desktop/smartGarden/smartGarden/logs/soilLog.txt", "r") as fp2: - for count, line in enumerate(fp2): - soilLogArray.append(line) - - for line in soilLogArray: - try: - splitLine = line.split() - if (currentYMD == splitLine[4]) and (len(splitLine) > 5): - soilMoistureArray.append(splitLine[3]) - soilTimeStampArray.append(splitLine[4] + " " + splitLine[5]) - except Exception as e: - logging.warn("Unable to parse soil moisture or time stamp for email") - logging.warn(e) - print("Error parsing soil log: " + str(e)) - - with open("/home/pi/Desktop/smartGarden/smartGarden/logs/sunlightLog.txt", "r") as fp: - for cnt, line in enumerate(fp): - lineArray = line.split() - highlightedRow = "" + highlightedRow + lineArray[0] + " " + lineArray[1] + "" - else: - row = "" + regularRow + lineArray[0] + " " + lineArray[1] + "" - - row = row + regularRow + lineArray[3] + " " + lineArray[4]+ "" - row = row + regularRow + soilMoisture +"%" - row = row + regularRow + soilTimeStamp + "" - else: - if "YES" in lineArray[0]: - row = "" + highlightedRow + lineArray[0] + " " + lineArray[1] + "" - else: - row = "" + greyRow + lineArray[0] + " " + lineArray[1] + "" - row = row + greyRow + lineArray[3] + " " + lineArray[4]+ "" - row = row + greyRow + soilMoisture + "%" - row = row + greyRow + soilTimeStamp + "" - html = html + row - elif currentYMD == lineArray[4]: - if cnt % 2 == 0: - if "YES" in lineArray[0]: - row = "" + highlightedRow + lineArray[0] + " " + lineArray[1] + " " + lineArray[2] + "" - else: - row = "" + regularRow + lineArray[0] + " " + lineArray[1] + "" - row = row + regularRow + lineArray[4] + " " + lineArray[5]+ "" - row = row + regularRow + soilMoisture + "%" - row = row + regularRow + soilTimeStamp + "" - else: - if "YES" in lineArray[0]: - row = "" + highlightedRow + lineArray[0] + " " + lineArray[1] + " " + lineArray[2] + "" - else: - row = "" + greyRow + lineArray[0] + " " + lineArray[1] + "" - row = row + greyRow + lineArray[4] + " " + lineArray[5]+ "" - row = row + greyRow + soilMoisture + "%" - row = row + greyRow + soilTimeStamp + "" - html = html + row - html = html + """\ -
Sunlight TimeStamp Soil Moisture TimeStamp
" - regularRow = "" - greyRow = "" - - if currentYMD == lineArray[3] or currentYMD == lineArray[4]: - if soilIterator < len(soilMoistureArray): - soilMoisture = soilMoistureArray[soilIterator] - soilTimeStamp = soilTimeStampArray[soilIterator] - soilIterator = soilIterator + 1 - print("Setting moisture: " + soilMoisture) - else: - soilMoisture = "NO DATA" - soilTimeStamp = "NO DATA" - if cnt % 2 == 0: - if "YES" in lineArray[0]: - row = "
- - - """ - except Exception as e: - logging.warn("There was an error reading html file. Defaulting to basic html page") - html = """\ - - - -

Garden Update

- - - """ - logging.warn(e) - print("Error sending email: " + str(e)) - - try: - #Open the file to be sent - attachment = open("/home/pi/Desktop/smartGarden/smartGarden/logs/smartGardenLog.txt", "rb") - p = MIMEBase('application', 'octet-stream') - p.set_payload((attachment).read()) - encoders.encode_base64(p) - - p.add_header('Content-Disposition', "attachment; filename=%s" % "GardenLog.txt") - message.attach(p) - except Exception as e: - logging.warn("There was an error opening attachment.") - logging.warn(e) - finally: - attachment.close() - - # Record the MIME types of both parts - text/plain and text/html. - part1 = MIMEText(text, 'plain') - part2 = MIMEText(html, 'html') - - # Attach parts into message container. - # According to RFC 2046, the last part of a multipart message, in this case - # the HTML message, is best and preferred. - message.attach(part1) - message.attach(part2) - - # Create a secure SSL context - context = ssl.create_default_context() - with smtplib.SMTP_SSL("smtp.gmail.com", port) as server: - server.login("raspberry.pi.taffe@gmail.com", password) - server.sendmail(sender_email, receiver_email, message.as_string()) +from email.mime.text import MIMEText +from email.mime.multipart import MIMEMultipart +from email.mime.base import MIMEBase +from email.utils import formatdate +from email import encoders +from datetime import datetime +from datetime import timedelta +import smtplib, ssl +import logging + +def send_email(): + port = 465 # For SSL + password = "al.EX.91.27" + sender_email = "raspberry.pi.taffe@gmail.com" + receiver_email = "taffeAlexander@gmail.com" + message = MIMEMultipart("alternative") + message["Subject"] = "Garden update: " + formatdate(localtime=True) + message["From"] = sender_email + message["To"] = receiver_email + message["Date"] = formatdate(localtime=True) + soilMoisture = "No Data" + soilTimeStamp = "No Data" + soilIterator = 0 + soilMoistureArray = [] + soilTimeStampArray = [] + currentYMD = str(datetime.now()).split()[0] + + # Create the body of the message (a plain-text and an HTML version). + text = "Garden update plan text" + + try: + html = """\ + + + + + +

Garden Update

+ + + + + + + + """ + soilLogArray = [] + with open("/home/pi/Desktop/smartGarden/smartGarden/logs/soilLog.txt", "r") as fp2: + for count, line in enumerate(fp2): + soilLogArray.append(line) + + for line in soilLogArray: + try: + splitLine = line.split() + if (currentYMD == splitLine[4]) and (len(splitLine) > 5): + soilMoistureArray.append(splitLine[3]) + soilTimeStampArray.append(splitLine[4] + " " + splitLine[5]) + except Exception as e: + logging.warn("Unable to parse soil moisture or time stamp for email") + logging.warn(e) + print("Error parsing soil log: " + str(e)) + + with open("/home/pi/Desktop/smartGarden/smartGarden/logs/sunlightLog.txt", "r") as fp: + for cnt, line in enumerate(fp): + lineArray = line.split() + highlightedRow = "" + highlightedRow + lineArray[0] + " " + lineArray[1] + "" + else: + row = "" + regularRow + lineArray[0] + " " + lineArray[1] + "" + + row = row + regularRow + lineArray[3] + " " + lineArray[4]+ "" + row = row + regularRow + soilMoisture +"%" + row = row + regularRow + soilTimeStamp + "" + else: + if "YES" in lineArray[0]: + row = "" + highlightedRow + lineArray[0] + " " + lineArray[1] + "" + else: + row = "" + greyRow + lineArray[0] + " " + lineArray[1] + "" + row = row + greyRow + lineArray[3] + " " + lineArray[4]+ "" + row = row + greyRow + soilMoisture + "%" + row = row + greyRow + soilTimeStamp + "" + html = html + row + elif currentYMD == lineArray[4]: + if cnt % 2 == 0: + if "YES" in lineArray[0]: + row = "" + highlightedRow + lineArray[0] + " " + lineArray[1] + " " + lineArray[2] + "" + else: + row = "" + regularRow + lineArray[0] + " " + lineArray[1] + "" + row = row + regularRow + lineArray[4] + " " + lineArray[5]+ "" + row = row + regularRow + soilMoisture + "%" + row = row + regularRow + soilTimeStamp + "" + else: + if "YES" in lineArray[0]: + row = "" + highlightedRow + lineArray[0] + " " + lineArray[1] + " " + lineArray[2] + "" + else: + row = "" + greyRow + lineArray[0] + " " + lineArray[1] + "" + row = row + greyRow + lineArray[4] + " " + lineArray[5]+ "" + row = row + greyRow + soilMoisture + "%" + row = row + greyRow + soilTimeStamp + "" + html = html + row + html = html + """\ +
Sunlight TimeStamp Soil Moisture TimeStamp
" + regularRow = "" + greyRow = "" + + if currentYMD == lineArray[3] or currentYMD == lineArray[4]: + if soilIterator < len(soilMoistureArray): + soilMoisture = soilMoistureArray[soilIterator] + soilTimeStamp = soilTimeStampArray[soilIterator] + soilIterator = soilIterator + 1 + print("Setting moisture: " + soilMoisture) + else: + soilMoisture = "NO DATA" + soilTimeStamp = "NO DATA" + if cnt % 2 == 0: + if "YES" in lineArray[0]: + row = "
+ + + """ + except Exception as e: + logging.warn("There was an error reading html file. Defaulting to basic html page") + html = """\ + + + +

Garden Update

+ + + """ + logging.warn(e) + print("Error sending email: " + str(e)) + + try: + #Open the file to be sent + attachment = open("/home/pi/Desktop/smartGarden/smartGarden/logs/smartGardenLog.txt", "rb") + p = MIMEBase('application', 'octet-stream') + p.set_payload((attachment).read()) + encoders.encode_base64(p) + + p.add_header('Content-Disposition', "attachment; filename=%s" % "GardenLog.txt") + message.attach(p) + except Exception as e: + logging.warn("There was an error opening attachment.") + logging.warn(e) + finally: + attachment.close() + + # Record the MIME types of both parts - text/plain and text/html. + part1 = MIMEText(text, 'plain') + part2 = MIMEText(html, 'html') + + # Attach parts into message container. + # According to RFC 2046, the last part of a multipart message, in this case + # the HTML message, is best and preferred. + message.attach(part1) + message.attach(part2) + + # Create a secure SSL context + context = ssl.create_default_context() + with smtplib.SMTP_SSL("smtp.gmail.com", port) as server: + server.login("raspberry.pi.taffe@gmail.com", password) + server.sendmail(sender_email, receiver_email, message.as_string()) logging.info("Email Sent "+ str(datetime.now())) \ No newline at end of file diff --git a/ModuleTests/GardenModules/gardenServer/gardenServer.py b/Tests/GardenModules/gardenServer/gardenServer.py similarity index 100% rename from ModuleTests/GardenModules/gardenServer/gardenServer.py rename to Tests/GardenModules/gardenServer/gardenServer.py diff --git a/ModuleTests/GardenModules/luxSensor/luxSensor.py b/Tests/GardenModules/luxSensor/luxSensor.py similarity index 96% rename from ModuleTests/GardenModules/luxSensor/luxSensor.py rename to Tests/GardenModules/luxSensor/luxSensor.py index 56d1c79..905eae8 100644 --- a/ModuleTests/GardenModules/luxSensor/luxSensor.py +++ b/Tests/GardenModules/luxSensor/luxSensor.py @@ -1,54 +1,54 @@ -import adafruit_tsl2561 -import busio -import board -import threading -from GardenModules.GardenModule import GardenModule -from datetime import datetime -import csv -import os - - -class LuxSensor(GardenModule): - def __init__(self, log, queue): - super().__init__(queue) - self._logging = log - self._i2c = busio.I2C(board.SCL, board.SDA) - self._sensor = adafruit_tsl2561.TSL2561(self._i2c) - self._sensor.gain = 0 - self._grow_light_lux = 535 - self._lux_interval = 10 - self._data_file = "/home/pi/Desktop/smartGarden/smartGarden/ModuleTests/Data/luxData.csv" - - def getLux(self): - return self._sensor.lux - - def isSunlight(self): - return self._sensor.lux > self._grow_light_lux - - def _saveReading(self): - if not os.path.exists(self._data_file): - with open(self._data_file, 'w+'): - pass - - with open(self._data_file, mode='a') as file: - writer = csv.writer(file, delimiter=',', quotechar='"', quoting=csv.QUOTE_MINIMAL) - writer.writerow([self._sensor.lux, datetime.now()]) - - def run(self): - print("Starting lux sensor thread") - timer = threading.Event() - while not timer.wait(self._lux_interval): - self._logging.info(self) - print(self) - self._saveReading() - if self._sentinel.get(block=True): - print("Sentinel was triggered in soil thread.") - self._sentinel.put(True) - self._sentinel.task_done() - break - self._sentinel.put(False) - self._sentinel.task_done() - - def __str__(self): - return "Current lux reading: {}".format(self.getLux()) - +import adafruit_tsl2561 +import busio +import board +import threading +from GardenModules.GardenModule import GardenModule +from datetime import datetime +import csv +import os + + +class LuxSensor(GardenModule): + def __init__(self, log, queue): + super().__init__(queue) + self._logging = log + self._i2c = busio.I2C(board.SCL, board.SDA) + self._sensor = adafruit_tsl2561.TSL2561(self._i2c) + self._sensor.gain = 0 + self._grow_light_lux = 535 + self._lux_interval = 10 + self._data_file = "/home/pi/Desktop/smartGarden/smartGarden/ModuleTests/Data/luxData.csv" + + def getLux(self): + return self._sensor.lux + + def isSunlight(self): + return self._sensor.lux > self._grow_light_lux + + def _saveReading(self): + if not os.path.exists(self._data_file): + with open(self._data_file, 'w+'): + pass + + with open(self._data_file, mode='a') as file: + writer = csv.writer(file, delimiter=',', quotechar='"', quoting=csv.QUOTE_MINIMAL) + writer.writerow([self._sensor.lux, datetime.now()]) + + def run(self): + print("Starting lux sensor thread") + timer = threading.Event() + while not timer.wait(self._lux_interval): + self._logging.info(self) + print(self) + self._saveReading() + if self._sentinel.get(block=True): + print("Sentinel was triggered in soil thread.") + self._sentinel.put(True) + self._sentinel.task_done() + break + self._sentinel.put(False) + self._sentinel.task_done() + + def __str__(self): + return "Current lux reading: {}".format(self.getLux()) + diff --git a/ModuleTests/GardenModules/prune/prune.py b/Tests/GardenModules/prune/prune.py similarity index 96% rename from ModuleTests/GardenModules/prune/prune.py rename to Tests/GardenModules/prune/prune.py index b4ba474..310657a 100644 --- a/ModuleTests/GardenModules/prune/prune.py +++ b/Tests/GardenModules/prune/prune.py @@ -1,26 +1,26 @@ -import logging - -def prune(file): - lines = [] - try: - logFile = open("/home/pi/Desktop/smartGarden/smartGarden/logs/" + file, "r") - for line in logFile: - lines.append(line) - except Exception as e: - print("Error reading log file: " + file + " for pruning") - finally: - logFile.close() - - try: - logFile = open("/home/pi/Desktop/smartGarden/smartGarden/logs/" + file, "w") - if len(lines) > 5000: - # Only keep the last 5000 lines - for x in range(5000): - index = len(lines) - 5001 + x - logFile.write(lines[index]) - except Exception as e: - print("Error writing log file: " + file) - finally: - logFile.close() - logging.info("Pruned Log: " + file) +import logging + +def prune(file): + lines = [] + try: + logFile = open("/home/pi/Desktop/smartGarden/smartGarden/logs/" + file, "r") + for line in logFile: + lines.append(line) + except Exception as e: + print("Error reading log file: " + file + " for pruning") + finally: + logFile.close() + + try: + logFile = open("/home/pi/Desktop/smartGarden/smartGarden/logs/" + file, "w") + if len(lines) > 5000: + # Only keep the last 5000 lines + for x in range(5000): + index = len(lines) - 5001 + x + logFile.write(lines[index]) + except Exception as e: + print("Error writing log file: " + file) + finally: + logFile.close() + logging.info("Pruned Log: " + file) print("Pruned log: " + file) \ No newline at end of file diff --git a/ModuleTests/GardenModules/pump/pump.py b/Tests/GardenModules/pump/pump.py similarity index 97% rename from ModuleTests/GardenModules/pump/pump.py rename to Tests/GardenModules/pump/pump.py index 6c147ca..b218331 100644 --- a/ModuleTests/GardenModules/pump/pump.py +++ b/Tests/GardenModules/pump/pump.py @@ -1,91 +1,91 @@ -import RPi.GPIO as GPIO -import time -import threading -from GardenModules.GardenModule import GardenModule -from GardenModules.soilMoisture.soil import SoilMoisture -from datetime import datetime - - -class WaterPump(GardenModule): - def __init__(self, log, queue, soil_moisture_sensor): - super().__init__(queue) - self.logging = log - self._pwm = 70 - self._pin = 18 - self._pumpInterval = 60 - self.setName("pumpThread") - self.soilMoisture = soil_moisture_sensor - - def _run(self, runtime=None, dutyCycle=50): - if runtime == None: - raise Exception("The value of run_time for the water pump was none.") - try: - self._setup(self._pin) - self._togglePin(self._pin) - p = GPIO.PWM(self._pin, self._pwm) - p.start(dutyCycle) - time.sleep(runtime) - GPIO.output(self._pin, GPIO.LOW) - p.stop() - self.logging.info("Watered plants at: " + str(datetime.now())) - except Exception as exception: - self.logging.warn("There was an error watering the plants.") - self.logging.warn(exception) - self.logging.info(self._printWatered()) - - def _run_sequence(self): - self._run(10, 100) - time.sleep(5) - self._run(3, 70) - time.sleep(5) - self._run(2, 50) - - def run(self): - try: - print("Starting pump thread.") - # self._run_sequence() - timer = threading.Event() - while not timer.wait(self._pumpInterval): - if self.soilMoisture.getSoilPercentage() < 40: - # self._run_sequence() - print("Watering because soil moisture is at: {}\n".format(self.soilMoisture.getSoilPercentage()) - + self._printWatered()) - else: - print("Skipping watering because soil moisture is at: {:.2f}%".format( - self.soilMoisture.getSoilPercentage())) - # TODO Refactor sentinel to be part of the while loop so that when it is triggered the loop ends. - if self._sentinel.get(block=True): - self.logging.info("Sentinel was triggered in pump thread.") - self._sentinel.put(True) - self._sentinel.task_done() - break - self._sentinel.put(False) - self._sentinel.task_done() - except Exception as exception: - self.logging.info("There was an exception in the pump thread: ") - self.logging.info(exception) - - def setInterval(self, interval): - self._pumpInterval = interval - - def getInterval(self): - return self._pumpInterval - - def _togglePin(self, pin): - GPIO.output(pin, GPIO.HIGH) - GPIO.output(pin, GPIO.LOW) - - def _setup(self, pin): - GPIO.setmode(GPIO.BCM) - GPIO.setup(pin, GPIO.OUT) - - def _printWatered(self): - return """ - ,d - 88 - 8b db d8 ,adPPYYba, MM88MMM ,adPPYba, 8b,dPPYba, - `8b d88b d8' "" `Y8 88 a8P_____88 88P' "Y8 - `8b d8'`8b d8' ,adPPPPP88 88 8PP""""""" 88 - `8bd8' `8bd8' 88, ,88 88, "8b, ,aa 88 - YP YP `"8bbdP"Y8 "Y888 `"Ybbd8"' 88 - """ +import RPi.GPIO as GPIO +import time +import threading +from GardenModules.GardenModule import GardenModule +from GardenModules.soilMoisture.soil import SoilMoisture +from datetime import datetime + + +class WaterPump(GardenModule): + def __init__(self, log, queue, soil_moisture_sensor): + super().__init__(queue) + self.logging = log + self._pwm = 70 + self._pin = 18 + self._pumpInterval = 60 + self.setName("pumpThread") + self.soilMoisture = soil_moisture_sensor + + def _run(self, runtime=None, dutyCycle=50): + if runtime == None: + raise Exception("The value of run_time for the water pump was none.") + try: + self._setup(self._pin) + self._togglePin(self._pin) + p = GPIO.PWM(self._pin, self._pwm) + p.start(dutyCycle) + time.sleep(runtime) + GPIO.output(self._pin, GPIO.LOW) + p.stop() + self.logging.info("Watered plants at: " + str(datetime.now())) + except Exception as exception: + self.logging.warn("There was an error watering the plants.") + self.logging.warn(exception) + self.logging.info(self._printWatered()) + + def _run_sequence(self): + self._run(10, 100) + time.sleep(5) + self._run(3, 70) + time.sleep(5) + self._run(2, 50) + + def run(self): + try: + print("Starting pump thread.") + # self._run_sequence() + timer = threading.Event() + while not timer.wait(self._pumpInterval): + if self.soilMoisture.getSoilPercentage() < 40: + # self._run_sequence() + print("Watering because soil moisture is at: {}\n".format(self.soilMoisture.getSoilPercentage()) + + self._printWatered()) + else: + print("Skipping watering because soil moisture is at: {:.2f}%".format( + self.soilMoisture.getSoilPercentage())) + # TODO Refactor sentinel to be part of the while loop so that when it is triggered the loop ends. + if self._sentinel.get(block=True): + self.logging.info("Sentinel was triggered in pump thread.") + self._sentinel.put(True) + self._sentinel.task_done() + break + self._sentinel.put(False) + self._sentinel.task_done() + except Exception as exception: + self.logging.info("There was an exception in the pump thread: ") + self.logging.info(exception) + + def setInterval(self, interval): + self._pumpInterval = interval + + def getInterval(self): + return self._pumpInterval + + def _togglePin(self, pin): + GPIO.output(pin, GPIO.HIGH) + GPIO.output(pin, GPIO.LOW) + + def _setup(self, pin): + GPIO.setmode(GPIO.BCM) + GPIO.setup(pin, GPIO.OUT) + + def _printWatered(self): + return """ + ,d + 88 + 8b db d8 ,adPPYYba, MM88MMM ,adPPYba, 8b,dPPYba, + `8b d88b d8' "" `Y8 88 a8P_____88 88P' "Y8 + `8b d8'`8b d8' ,adPPPPP88 88 8PP""""""" 88 + `8bd8' `8bd8' 88, ,88 88, "8b, ,aa 88 + YP YP `"8bbdP"Y8 "Y888 `"Ybbd8"' 88 + """ diff --git a/ModuleTests/GardenModules/soilMoisture/soil.py b/Tests/GardenModules/soilMoisture/soil.py similarity index 97% rename from ModuleTests/GardenModules/soilMoisture/soil.py rename to Tests/GardenModules/soilMoisture/soil.py index 25775ec..434af72 100644 --- a/ModuleTests/GardenModules/soilMoisture/soil.py +++ b/Tests/GardenModules/soilMoisture/soil.py @@ -1,76 +1,76 @@ -import board -import busio -import adafruit_ads1x15.ads1115 as ADS -import threading -from adafruit_ads1x15.analog_in import AnalogIn -from GardenModules.GardenModule import GardenModule -from datetime import datetime -from queue import Queue - - -class SoilMoisture(GardenModule): - def __init__(self, log, queue): - super().__init__(queue) - self.log = log - self._i2c = busio.I2C(board.SCL, board.SDA) - self._ads = ADS.ADS1115(self._i2c) - self._ads.gain = 1 - self.channel = AnalogIn(self._ads, ADS.P0) - self.soilInterval = 1800 # 1800 - self.log.info("Channel: " + str(self.channel)) - self.setName("soilThread") - - # Set Gain to 16 bits - # Gain = 1 # Wet: 13884 Dry: 21680 - # Gain = 2/3 # Wet: 11031 Dry: 14989 - self.queue = Queue() - self.sum = 0 - self.window_size = 5 - self.percentage = 0 - self.average_soil_value = 0 - for x in range(0,5): - self._checkSoil() - def _checkSoil(self): - try: - # I am using a moving average to remove sensor noise. - value = self.channel.value - if self.queue.qsize() >= self.window_size: - self.sum += value - self.sum -= self.queue.get() - self.average_soil_value = self.sum / self.window_size - self.percentage = ((self.sum / self.window_size) / 21680) * 100 - self.log.info("Soil Moisture Level: {} | Averaged Value: {:.2f}%".format(self.sum / self.window_size, self.getSoilPercentage())) - print("Soil Moisture Level: {} | Averaged Value: {:.2f}%".format(self.sum / self.window_size, self.getSoilPercentage())) - else: - self.queue.put(value) - self.sum += value - - except Exception as exception: - self.log.exception("Error calculating soil moisture") - - try: - with open("/home/pi/Desktop/smartGarden/smartGarden/logs/soilLog.txt", "a+") as logFile: - logFile.write("Soil Moisture Level: " + str(self.getSoilPercentage()) + "% " + str( - datetime.now()) + " Raw Value: " + str(self.channel.value) + "\n") - except Exception as exception: - self.log.exception("Error writing soil moisture level") - - def run(self): - self._checkSoil() - timer = threading.Event() - while not timer.wait(self.soilInterval): - self._checkSoil() - # TODO create a function for the sentinel - if self._sentinel.get(block=True): - print("Sentinel was triggered in soil thread.") - self._sentinel.put(True) - self._sentinel.task_done() - break - self._sentinel.put(False) - self._sentinel.task_done() - - def getSoilPercentage(self): - return 100 - self.percentage - - def getSoilValue(self): - return self.sum / self.window_size +import board +import busio +import adafruit_ads1x15.ads1115 as ADS +import threading +from adafruit_ads1x15.analog_in import AnalogIn +from GardenModules.GardenModule import GardenModule +from datetime import datetime +from queue import Queue + + +class SoilMoisture(GardenModule): + def __init__(self, log, queue): + super().__init__(queue) + self.log = log + self._i2c = busio.I2C(board.SCL, board.SDA) + self._ads = ADS.ADS1115(self._i2c) + self._ads.gain = 1 + self.channel = AnalogIn(self._ads, ADS.P0) + self.soilInterval = 1800 # 1800 + self.log.info("Channel: " + str(self.channel)) + self.setName("soilThread") + + # Set Gain to 16 bits + # Gain = 1 # Wet: 13884 Dry: 21680 + # Gain = 2/3 # Wet: 11031 Dry: 14989 + self.queue = Queue() + self.sum = 0 + self.window_size = 5 + self.percentage = 0 + self.average_soil_value = 0 + for x in range(0,5): + self._checkSoil() + def _checkSoil(self): + try: + # I am using a moving average to remove sensor noise. + value = self.channel.value + if self.queue.qsize() >= self.window_size: + self.sum += value + self.sum -= self.queue.get() + self.average_soil_value = self.sum / self.window_size + self.percentage = ((self.sum / self.window_size) / 21680) * 100 + self.log.info("Soil Moisture Level: {} | Averaged Value: {:.2f}%".format(self.sum / self.window_size, self.getSoilPercentage())) + print("Soil Moisture Level: {} | Averaged Value: {:.2f}%".format(self.sum / self.window_size, self.getSoilPercentage())) + else: + self.queue.put(value) + self.sum += value + + except Exception as exception: + self.log.exception("Error calculating soil moisture") + + try: + with open("/home/pi/Desktop/smartGarden/smartGarden/logs/soilLog.txt", "a+") as logFile: + logFile.write("Soil Moisture Level: " + str(self.getSoilPercentage()) + "% " + str( + datetime.now()) + " Raw Value: " + str(self.channel.value) + "\n") + except Exception as exception: + self.log.exception("Error writing soil moisture level") + + def run(self): + self._checkSoil() + timer = threading.Event() + while not timer.wait(self.soilInterval): + self._checkSoil() + # TODO create a function for the sentinel + if self._sentinel.get(block=True): + print("Sentinel was triggered in soil thread.") + self._sentinel.put(True) + self._sentinel.task_done() + break + self._sentinel.put(False) + self._sentinel.task_done() + + def getSoilPercentage(self): + return 100 - self.percentage + + def getSoilValue(self): + return self.sum / self.window_size diff --git a/ModuleTests/GardenModules/sunlightSensor/sunlight.py b/Tests/GardenModules/sunlightSensor/sunlight.py similarity index 97% rename from ModuleTests/GardenModules/sunlightSensor/sunlight.py rename to Tests/GardenModules/sunlightSensor/sunlight.py index d99843a..621e0fa 100644 --- a/ModuleTests/GardenModules/sunlightSensor/sunlight.py +++ b/Tests/GardenModules/sunlightSensor/sunlight.py @@ -1,78 +1,78 @@ -import logging -import sqlite3 -from datetime import datetime -from datetime import timedelta -import RPi.GPIO as GPIO - -def insertSunlightRecord(message,time1, time2): - insert_command = "INSERT INTO sunlight VALUES (\'" + message + "\',\'"+ time1 + "\',\'" + time2 + "\');" - try: - conn = sqlite3.connect('/home/pi/Desktop/smartGarden/smartGarden/gardenDatabase.db') - cursor = conn.cursor() - cursor.execute(insert_command) - conn.commit() - logging.info("Inserted sunlight record") - return cursor.lastrowid - except Exception as e: - logging.warn(e) - finally: - conn.close() - -def prune(): - time = datetime.now() - pruneDate = str(time - timedelta(days=30)).split() - - delete_command = "DELETE FROM sunlight WHERE record_timestamp LIKE '" + pruneDate[0] + "%';" - #select_command = "SELECT * FROM sunlight WHERE record_timestamp LIKE '" + pruneDate[0] + "%';" - #print("Prune Date: " + str(pruneDate)) - try: - conn = sqlite3.connect('/home/pi/Desktop/smartGarden/smartGarden/gardenDatabase.db') - cursor = conn.cursor() - cursor.execute(delete_command) - conn.commit() - except Exception as e: - print("Error: " + str(e)) - finally: - conn.close() - - -def check_sunlight(): - artificialLightHours = False - f = None - try: - GPIO.setmode(GPIO.BCM) - GPIO.setup(4, GPIO.IN, pull_up_down=GPIO.PUD_DOWN) - f = open("/home/pi/Desktop/smartGarden/smartGarden/logs/sunlightLog.txt", "a+") - time = datetime.now() - dateTimeString = str(time) - cleanDateString = str(time + timedelta(days=30)) - currentTimeStamp = str(datetime.now()).split()[1] - currentHour = currentTimeStamp.split(':')[0] - hourAsInt = int(currentHour) - - if hourAsInt >= 18: - f.write("YES Artificial Sunlight at: " + dateTimeString + "\n") - insertSunlightRecord("YES Artificial Sunlight",dateTimeString,cleanDateString) - artificialLightHours = True - logging.info("Checked artificial sunlight") - if hourAsInt >=6 and hourAsInt <= 12: - f.write("YES Artificial Sunlight at: " + dateTimeString + "\n") - insertSunlightRecord("YES Artificial Sunlight",dateTimeString,cleanDateString) - artificialLightHours = True - logging.info("Checked artificial sunlight") - - if not artificialLightHours: - if not GPIO.input(4): - f.write("YES Natural Sunlight at: " + dateTimeString + "\n") - insertSunlightRecord("Yes Sunlight",dateTimeString,cleanDateString) - logging.info("Checked natural sunlight") - else: - f.write("NO Sunlight at: " + dateTimeString + "\n") - insertSunlightRecord("No Sunlight", dateTimeString, cleanDateString) - logging.info("Checked natural sunlight") - except Exception as e: - logging.warn("There was an error writing to file.") - logging.warn(e) - finally: - f.close() - +import logging +import sqlite3 +from datetime import datetime +from datetime import timedelta +import RPi.GPIO as GPIO + +def insertSunlightRecord(message,time1, time2): + insert_command = "INSERT INTO sunlight VALUES (\'" + message + "\',\'"+ time1 + "\',\'" + time2 + "\');" + try: + conn = sqlite3.connect('/home/pi/Desktop/smartGarden/smartGarden/gardenDatabase.db') + cursor = conn.cursor() + cursor.execute(insert_command) + conn.commit() + logging.info("Inserted sunlight record") + return cursor.lastrowid + except Exception as e: + logging.warn(e) + finally: + conn.close() + +def prune(): + time = datetime.now() + pruneDate = str(time - timedelta(days=30)).split() + + delete_command = "DELETE FROM sunlight WHERE record_timestamp LIKE '" + pruneDate[0] + "%';" + #select_command = "SELECT * FROM sunlight WHERE record_timestamp LIKE '" + pruneDate[0] + "%';" + #print("Prune Date: " + str(pruneDate)) + try: + conn = sqlite3.connect('/home/pi/Desktop/smartGarden/smartGarden/gardenDatabase.db') + cursor = conn.cursor() + cursor.execute(delete_command) + conn.commit() + except Exception as e: + print("Error: " + str(e)) + finally: + conn.close() + + +def check_sunlight(): + artificialLightHours = False + f = None + try: + GPIO.setmode(GPIO.BCM) + GPIO.setup(4, GPIO.IN, pull_up_down=GPIO.PUD_DOWN) + f = open("/home/pi/Desktop/smartGarden/smartGarden/logs/sunlightLog.txt", "a+") + time = datetime.now() + dateTimeString = str(time) + cleanDateString = str(time + timedelta(days=30)) + currentTimeStamp = str(datetime.now()).split()[1] + currentHour = currentTimeStamp.split(':')[0] + hourAsInt = int(currentHour) + + if hourAsInt >= 18: + f.write("YES Artificial Sunlight at: " + dateTimeString + "\n") + insertSunlightRecord("YES Artificial Sunlight",dateTimeString,cleanDateString) + artificialLightHours = True + logging.info("Checked artificial sunlight") + if hourAsInt >=6 and hourAsInt <= 12: + f.write("YES Artificial Sunlight at: " + dateTimeString + "\n") + insertSunlightRecord("YES Artificial Sunlight",dateTimeString,cleanDateString) + artificialLightHours = True + logging.info("Checked artificial sunlight") + + if not artificialLightHours: + if not GPIO.input(4): + f.write("YES Natural Sunlight at: " + dateTimeString + "\n") + insertSunlightRecord("Yes Sunlight",dateTimeString,cleanDateString) + logging.info("Checked natural sunlight") + else: + f.write("NO Sunlight at: " + dateTimeString + "\n") + insertSunlightRecord("No Sunlight", dateTimeString, cleanDateString) + logging.info("Checked natural sunlight") + except Exception as e: + logging.warn("There was an error writing to file.") + logging.warn(e) + finally: + f.close() + diff --git a/Tests/README.md b/Tests/README.md new file mode 100644 index 0000000..d6e2759 --- /dev/null +++ b/Tests/README.md @@ -0,0 +1,3 @@ +# Module Tests + +This folder is used to test new modules or just anything to do with the garden. \ No newline at end of file diff --git a/test/alex.txt b/Tests/alex.txt similarity index 100% rename from test/alex.txt rename to Tests/alex.txt diff --git a/test/artificial_light_test.py b/Tests/artificial_light_test.py similarity index 100% rename from test/artificial_light_test.py rename to Tests/artificial_light_test.py diff --git a/test/camera_test.py b/Tests/camera_test.py similarity index 100% rename from test/camera_test.py rename to Tests/camera_test.py diff --git a/test/database_test.py b/Tests/database_test.py similarity index 100% rename from test/database_test.py rename to Tests/database_test.py diff --git a/test/email_desktop.py b/Tests/email_desktop.py similarity index 100% rename from test/email_desktop.py rename to Tests/email_desktop.py diff --git a/test/email_test.py b/Tests/email_test.py similarity index 100% rename from test/email_test.py rename to Tests/email_test.py diff --git a/test/i2c-test.py b/Tests/i2c-test.py similarity index 96% rename from test/i2c-test.py rename to Tests/i2c-test.py index 179dacb..599e29c 100644 --- a/test/i2c-test.py +++ b/Tests/i2c-test.py @@ -1,20 +1,20 @@ -import board -import busio -import adafruit_ads1x15.ads1115 as ADS -from adafruit_ads1x15.analog_in import AnalogIn -import time - -i2c = busio.I2C(board.SCL, board.SDA) -# Soil moisture is address 0x4a -# Light is address 48 -soil_ads = ADS.ADS1115(i2c, address=0x4a) - -light_ads = ADS.ADS1115(i2c) -light_ads.gain = 2/3 -chan1 = AnalogIn(soil_ads, ADS.P0) -chan2 = AnalogIn(light_ads, ADS.P0) - -while(True): - #print("soil value: " + str(chan1.value) + " soil voltage: " + str(chan1.voltage)) - print("light value: " + str(chan2.value) + " light voltage: " + str(chan2.voltage)) +import board +import busio +import adafruit_ads1x15.ads1115 as ADS +from adafruit_ads1x15.analog_in import AnalogIn +import time + +i2c = busio.I2C(board.SCL, board.SDA) +# Soil moisture is address 0x4a +# Light is address 48 +soil_ads = ADS.ADS1115(i2c, address=0x4a) + +light_ads = ADS.ADS1115(i2c) +light_ads.gain = 2/3 +chan1 = AnalogIn(soil_ads, ADS.P0) +chan2 = AnalogIn(light_ads, ADS.P0) + +while(True): + #print("soil value: " + str(chan1.value) + " soil voltage: " + str(chan1.voltage)) + print("light value: " + str(chan2.value) + " light voltage: " + str(chan2.voltage)) time.sleep(1) \ No newline at end of file diff --git a/test/image.jpeg b/Tests/image.jpeg similarity index 100% rename from test/image.jpeg rename to Tests/image.jpeg diff --git a/test/lightTest.py b/Tests/lightTest.py similarity index 100% rename from test/lightTest.py rename to Tests/lightTest.py diff --git a/test/light_sensor_test.py b/Tests/light_sensor_test.py similarity index 96% rename from test/light_sensor_test.py rename to Tests/light_sensor_test.py index bd74ff7..69d8133 100644 --- a/test/light_sensor_test.py +++ b/Tests/light_sensor_test.py @@ -1,16 +1,16 @@ -import board -import busio -import adafruit_tsl2561 -import time - -i2c = busio.I2C(board.SCL, board.SDA) -sensor = adafruit_tsl2561.TSL2561(i2c) -sensor.gain = 0 - -while True: - print('Lux: {}'.format(sensor.lux)) - print('Broadband: {}'.format(sensor.broadband)) - print('Infrared: {}'.format(sensor.infrared)) - print('gain: {}'.format(sensor.gain)) - print('Luminosity: {}\n'.format(sensor.luminosity)) - time.sleep(1) +import board +import busio +import adafruit_tsl2561 +import time + +i2c = busio.I2C(board.SCL, board.SDA) +sensor = adafruit_tsl2561.TSL2561(i2c) +sensor.gain = 0 + +while True: + print('Lux: {}'.format(sensor.lux)) + print('Broadband: {}'.format(sensor.broadband)) + print('Infrared: {}'.format(sensor.infrared)) + print('gain: {}'.format(sensor.gain)) + print('Luminosity: {}\n'.format(sensor.luminosity)) + time.sleep(1) diff --git a/ModuleTests/luxTest.py b/Tests/luxTest.py similarity index 95% rename from ModuleTests/luxTest.py rename to Tests/luxTest.py index a24e660..0500e32 100644 --- a/ModuleTests/luxTest.py +++ b/Tests/luxTest.py @@ -1,21 +1,21 @@ -import logging -import queue - -from GardenModules.luxSensor.luxSensor import LuxSensor - -logging.basicConfig(filename="testLog.log", level=logging.INFO) -sentinel = queue.Queue() -sentinel.put(False) - -sensor = LuxSensor(logging, sentinel) -sensor.start() - -while True: - try: - pass - except KeyboardInterrupt: - sentinel.put(True) - print("Ending test") - break - -sensor.join() +import logging +import queue + +from GardenModules.luxSensor.luxSensor import LuxSensor + +logging.basicConfig(filename="testLog.log", level=logging.INFO) +sentinel = queue.Queue() +sentinel.put(False) + +sensor = LuxSensor(logging, sentinel) +sensor.start() + +while True: + try: + pass + except KeyboardInterrupt: + sentinel.put(True) + print("Ending test") + break + +sensor.join() diff --git a/test/modules/soilMoisture/soil.py b/Tests/modules/soilMoisture/soil.py similarity index 96% rename from test/modules/soilMoisture/soil.py rename to Tests/modules/soilMoisture/soil.py index fb4ce22..cc85195 100644 --- a/test/modules/soilMoisture/soil.py +++ b/Tests/modules/soilMoisture/soil.py @@ -1,83 +1,83 @@ -import board -import busio -import adafruit_ads1x15.ads1115 as ADS -from adafruit_ads1x15.analog_in import AnalogIn -import sys -import time -import keyboard -from queue import Queue - -i2c = busio.I2C(board.SCL, board.SDA) -print("i2c configured") -ads = ADS.ADS1115(i2c, address=0x48) -ads.gain = 1 -print("ADS configured with gain: " + str(ads.gain)) -chan = AnalogIn(ads, ADS.P0) - -def test_min_max(): - min_value = sys.maxsize - max_value = -sys.maxsize - try: - while True: - value = chan.value - voltage = chan.voltage - max_value = max([value, int(max_value)]) - min_value = min([value, int(min_value)]) - print("value: {}\nvoltage: {}\nmin: {}\nmax: {}\n".format(value,voltage, min_value, max_value)) - time.sleep(0.5) - except KeyboardInterrupt: - print("\nfinal min: {}\n final max: {}".format(min_value, max_value)) - -def test_soil_moving_avg(): - window_size = int(input("Please input the window size: ")) - sum = 0 - buffer = Queue() - while True: - value = chan.value - try: - if buffer.qsize() >= window_size: - sum += value - sum -= buffer.get() - percentage = ((sum / window_size) / 21680) * 100 - print("Moisture level: {}\n percentage: {:.2f}".format(sum/window_size, percentage)) - buffer.put(value) - time.sleep(0.2) - else: - buffer.put(value) - sum += value - - if keyboard.is_pressed('q'): - print("Exiting...") - sys.exit() - except Exception as e: - print(e) - break - -def test_soil(): - - lowest = chan.value - highest = lowest - min = 1061 - max = 14667 - started = False - weight = 40 - valInt = 0 - while True: - if started: - rawVal = chan.value - val = (rawVal - min) / (max - min) - val = val * 100 - newValInt = round(val, 5) - rawVal = (weight * newValInt) + ((1 - weight) * valInt) - else: - rawVal = chan.value - #Normalization - val = (rawVal - min) / (max - min) - #Convert to a percentage - val = val * 100 - valInt = round(val, 5) - - print("Voltage: " + str(chan.voltage) + "\t\tValue: " + str(chan.value)) - - if not started: - started = True +import board +import busio +import adafruit_ads1x15.ads1115 as ADS +from adafruit_ads1x15.analog_in import AnalogIn +import sys +import time +import keyboard +from queue import Queue + +i2c = busio.I2C(board.SCL, board.SDA) +print("i2c configured") +ads = ADS.ADS1115(i2c, address=0x48) +ads.gain = 1 +print("ADS configured with gain: " + str(ads.gain)) +chan = AnalogIn(ads, ADS.P0) + +def test_min_max(): + min_value = sys.maxsize + max_value = -sys.maxsize + try: + while True: + value = chan.value + voltage = chan.voltage + max_value = max([value, int(max_value)]) + min_value = min([value, int(min_value)]) + print("value: {}\nvoltage: {}\nmin: {}\nmax: {}\n".format(value,voltage, min_value, max_value)) + time.sleep(0.5) + except KeyboardInterrupt: + print("\nfinal min: {}\n final max: {}".format(min_value, max_value)) + +def test_soil_moving_avg(): + window_size = int(input("Please input the window size: ")) + sum = 0 + buffer = Queue() + while True: + value = chan.value + try: + if buffer.qsize() >= window_size: + sum += value + sum -= buffer.get() + percentage = ((sum / window_size) / 21680) * 100 + print("Moisture level: {}\n percentage: {:.2f}".format(sum/window_size, percentage)) + buffer.put(value) + time.sleep(0.2) + else: + buffer.put(value) + sum += value + + if keyboard.is_pressed('q'): + print("Exiting...") + sys.exit() + except Exception as e: + print(e) + break + +def test_soil(): + + lowest = chan.value + highest = lowest + min = 1061 + max = 14667 + started = False + weight = 40 + valInt = 0 + while True: + if started: + rawVal = chan.value + val = (rawVal - min) / (max - min) + val = val * 100 + newValInt = round(val, 5) + rawVal = (weight * newValInt) + ((1 - weight) * valInt) + else: + rawVal = chan.value + #Normalization + val = (rawVal - min) / (max - min) + #Convert to a percentage + val = val * 100 + valInt = round(val, 5) + + print("Voltage: " + str(chan.voltage) + "\t\tValue: " + str(chan.value)) + + if not started: + started = True diff --git a/test/opencv_test.py b/Tests/opencv_test.py similarity index 94% rename from test/opencv_test.py rename to Tests/opencv_test.py index e4ef9a9..2971d2e 100644 --- a/test/opencv_test.py +++ b/Tests/opencv_test.py @@ -1,15 +1,15 @@ -import cv2 - -vid_cap = cv2.VideoCapture(0) -vid_cap.set(3, 1280) -vid_cap.set(4, 720) - -if not vid_cap.isOpened(): - raise Exception("could not open video device") - -for x in range(10): - ret, frame = vid_cap.read() - -cv2.imwrite("test.jpg", frame) - +import cv2 + +vid_cap = cv2.VideoCapture(0) +vid_cap.set(3, 1280) +vid_cap.set(4, 720) + +if not vid_cap.isOpened(): + raise Exception("could not open video device") + +for x in range(10): + ret, frame = vid_cap.read() + +cv2.imwrite("test.jpg", frame) + vid_cap.release() \ No newline at end of file diff --git a/test/pump.py b/Tests/pump.py similarity index 100% rename from test/pump.py rename to Tests/pump.py diff --git a/ModuleTests/pumpTest.py b/Tests/pumpTest.py similarity index 95% rename from ModuleTests/pumpTest.py rename to Tests/pumpTest.py index 3154bfe..9599473 100644 --- a/ModuleTests/pumpTest.py +++ b/Tests/pumpTest.py @@ -1,25 +1,25 @@ -import queue -import logging - -from GardenModules.pump.pump import WaterPump -from GardenModules.soilMoisture.soil import SoilMoisture - -logging.basicConfig(filename="testLog.log", level=logging.INFO) -sentinel = queue.Queue() -sentinel.put(False) - -soilMoistureSensor = SoilMoisture(logging, sentinel) -pump = WaterPump(logging, sentinel, soilMoistureSensor) - -soilMoistureSensor.start() -pump.start() - -while True: - try: - pass - except KeyboardInterrupt: - sentinel.put(True) - print("Ending test") - break - -pump.join() +import queue +import logging + +from GardenModules.pump.pump import WaterPump +from GardenModules.soilMoisture.soil import SoilMoisture + +logging.basicConfig(filename="testLog.log", level=logging.INFO) +sentinel = queue.Queue() +sentinel.put(False) + +soilMoistureSensor = SoilMoisture(logging, sentinel) +pump = WaterPump(logging, sentinel, soilMoistureSensor) + +soilMoistureSensor.start() +pump.start() + +while True: + try: + pass + except KeyboardInterrupt: + sentinel.put(True) + print("Ending test") + break + +pump.join() diff --git a/test/soil_test.py b/Tests/soil_test.py similarity index 100% rename from test/soil_test.py rename to Tests/soil_test.py diff --git a/test/sunlightLog.txt b/Tests/sunlightLog.txt similarity index 100% rename from test/sunlightLog.txt rename to Tests/sunlightLog.txt diff --git a/test/tempSensor.py b/Tests/tempSensor.py similarity index 100% rename from test/tempSensor.py rename to Tests/tempSensor.py diff --git a/test/test.jpg b/Tests/test.jpg similarity index 100% rename from test/test.jpg rename to Tests/test.jpg diff --git a/build_table.py b/build_table.py deleted file mode 100644 index 2c9e865..0000000 --- a/build_table.py +++ /dev/null @@ -1,106 +0,0 @@ -def build_table(ymd): - soilLogArray = [] - with open("/home/pi/Desktop/smartGarden/smartGarden/soilLog.txt", "r") as fp2: - for count, line in enumerate(fp2): - soilLogArray.append(line) - - with open("/home/pi/Desktop/smartGarden/smartGarden/sunlightLog.txt", "r") as fp: - for cnt, line in enumerate(fp): - lineArray = line.split() - currentYMD = str(datetime.now()).split()[0] - soilMoisture = "No Data" - soilTimeStamp = "No Data" - if cnt < len(soilLogArray): - try: - splitLine = soilLogArray[cnt].split() - soilMoisture = splitLine[3] - soilTimeStamp = splitLine[4] + " " + splitLine[5] - print("soilMoisture: " + soilMoisture) - print("soil time stamp: " + soilTimeStamp) - except Exception as e: - logging.warn("Unable to parse soil moisture or time stamp for email.") - logging.warn(e) - - if currentYMD == lineArray[3]: - if cnt % 2 == 0: - if "YES" in lineArray[0]: - row = "" + lineArray[0] + " " + lineArray[1] + "" - else: - row = "" + lineArray[0] + " " + lineArray[1] + "" - - row = row + "" + lineArray[3] + " " + lineArray[4]+ "" - row = row + "" + soilMoisture + "" - row = row + "" + soilTimeStamp + "" - else: - if "YES" in lineArray[0]: - row = "" + lineArray[0] + " " + lineArray[1] + "" - else: - row = "" + lineArray[0] + " " + lineArray[1] + "" - row = row + "" + lineArray[3] + " " + lineArray[4]+ "" - row = row + "" + soilMoisture + "" - row = row + "" + soilTimeStamp + "" - html = html + row - elif currentYMD == lineArray[4]: - if cnt % 2 == 0: - if "YES" in lineArray[0]: - row = "" + lineArray[0] + " " + lineArray[1] + " " + lineArray[2] + "" - else: - row = "" + lineArray[0] + " " + lineArray[1] + "" - row = row + "" + lineArray[4] + " " + lineArray[5]+ "" - row = row + "" + soilMoisture + "" - row = row + "" + soilTimeStamp + "" - else: - if "YES" in lineArray[0]: - row = "" + lineArray[0] + " " + lineArray[1] + " " + lineArray[2] + "" - else: - row = "" + lineArray[0] + " " + lineArray[1] + "" - row = row + "" + lineArray[4] + " " + lineArray[5]+ "" - row = row + "" + soilMoisture + "" - row = row + "" + soilTimeStamp + "" - html = html + row - html = html + """\ - - - - """ - -def build_sunlight_row(line, lineCount, currentYMD): - -lineArray = line.split() -sunlightText = "" -date = "" - -if currentYMD == lineArray[3]: - sunlightText = lineArray[0] + " " + lineArray[1] -elif currentYMD == lineArray[4]: - sunlightText = lineArray[0] + " " + lineArray[1] + " " + lineArray[2] - -hightlighted_row = "" -regular_row = "" -grey_row = "" -html = "" - - -if currentYMD == lineArray[3]: - if cnt % 2 == 0: - if "YES" in lineArray[0]: - row = hightlighted_row + sunlightText + "" - else: - row = regular_row + sunlightText + "" - else: - if "YES" in lineArray[0]: - row = "" + sunlightText + "" - else: - row = "" + sunlightText + "" - html = html + row -elif currentYMD == lineArray[4]: - if cnt % 2 == 0: - if "YES" in lineArray[0]: - row = "" + lineArray[0] + " " + lineArray[1] + " " + lineArray[2] + "" - else: - row = "" + lineArray[0] + " " + lineArray[1] + "" - else: - if "YES" in lineArray[0]: - row = "" + lineArray[0] + " " + lineArray[1] + " " + lineArray[2] + "" - else: - row = "" + lineArray[0] + " " + lineArray[1] + "" \ No newline at end of file diff --git a/images/ControlPanel.PNG b/images/ControlPanel.PNG new file mode 100644 index 0000000..4b634f9 Binary files /dev/null and b/images/ControlPanel.PNG differ diff --git a/install.sh b/install.sh index 9472e70..f43b2be 100755 --- a/install.sh +++ b/install.sh @@ -14,5 +14,5 @@ sudo apt-get install libjasper-dev sudo apt-get install libqtgui4 sudo apt-get install libqt4-test #paper trail -wget -qO - --header="X-Papertrail-Token: 5z2e1Jt2DIcHx9gjLcE" \ -https://papertrailapp.com/destinations/17877732/setup.sh | sudo bash +wget -qO - --header="X-Papertrail-Token: " \ +https://papertrailapp.com/destinations//setup.sh | sudo bash diff --git a/rc.local b/rc.local deleted file mode 100755 index 5105a66..0000000 --- a/rc.local +++ /dev/null @@ -1,25 +0,0 @@ -#!/bin/sh -e -# -# rc.local -# -# This script is executed at the end of each multiuser runlevel. -# Make sure that the script will "exit 0" on success or any other -# value on error. -# -# In order to enable or disable this script just change the execution -# bits. -# -# By default this script does nothing. - -# Print the IP address -_IP=$(hostname -I) || true -if [ "$_IP" ]; then - printf "My IP address is %s\n" "$_IP" -fi -exec 2> /home/pi/rc_local_logs.log -exec 1>&2 -set -x -sudo ./home/pi/go/src/github.com/papertrail/remote_syslog2/build/remote_syslog2/remote_syslog -p 35718 -d logs4.papertrailapp.com --pid-file=/var/run/remote_syslog.pid /home/pi/Desktop/smartGarden/smartGarden/logs/smartGardenLog.log & -sudo python3 /home/pi/Desktop/smartGarden/smartGarden/smartGarden.py &>> /home/pi/Desktop/smartGarden/smartGarden/smartGardenLog.txt & -#sudo /home/pi/go/src/github.com/elastic/beats/filebeat/filebeat -c /home/pi/go/src/github.com/elastic/beats/filebeat/filebeat.yml -e & -exit 0 diff --git a/smartGarden.py b/smartGarden.py index 5c5ec8b..36c59e2 100644 --- a/smartGarden.py +++ b/smartGarden.py @@ -31,128 +31,12 @@ image_count = 0 SHUTDOWN_FLAG = False -app = Flask(__name__, template_folder='/home/pi/Desktop/smartGarden/smartGarden/ControlPanel', - static_folder="/home/pi/Desktop/smartGarden/smartGarden/ControlPanel") +app = Flask(__name__, template_folder='./ControlPanel', + static_folder="./ControlPanel") CORS(app) Debug(app) -def create_folder(): - filename = str(datetime.now()).replace(" ", "-") - dateArray = filename.split('-') - ymd = dateArray[0] + "-" + dateArray[1] + "-" + dateArray[2] - try: - os.mkdir("/home/pi/Desktop/smartGarden/smartGarden/images/" + ymd) - except FileExistsError: - pass - finally: - return ymd - - -def zipdir(path, ziph): - logging.info("zipping path: " + path) - for root, dirs, files in os.walk(path): - logging.info("root: " + root) - for file in files: - ziph.write(os.path.join(root, file)) - - -def send_folder(ymd): - time1 = datetime.now() - logging.info("Zipping File... " + str(datetime.now())) - baseFolder = ymd - baseFolder = "/home/pi/Desktop/smartGarden/smartGarden/images/" + baseFolder - ymd = baseFolder + ".zip" - currentDirectory = os.path.dirname(os.path.realpath(__file__)) - os.chdir("/home/pi/Desktop/smartGarden/smartGarden/images") - zf = zipfile.ZipFile(ymd, mode='w', compression=zipfile.ZIP_LZMA) - try: - zipdir(baseFolder, zf) - finally: - zf.close() - logging.info("Sending images...") - try: - scp_command = "SSHPASS='al.EX.91.27' sshpass -e scp " + ymd + " alext@192.168.0.20:D:\\\\smartGarden\\\\Images" - except Exception as e: - logging.warn("There was an error sending the file to Cacutar") - logging.warn(e) - try: - os.system(scp_command) - os.system("rm " + ymd) - # os.system("rm -r " + baseFolder) - os.chdir(currentDirectory) - time2 = datetime.now() - diff = time2 - time1 - logging.info("It took (mins, seconds): " + str(divmod(diff.total_seconds(), 60)) + " to transfer " + str(ymd)) - except Exception as e: - logging.warn("There was an error deleting the folders and moving back a directory") - logging.warn(e) - - -def take_pics(ymd, number=1): - for x in range(number): - logging.info("Taking image " + str(x + 1) + " out of " + str(number)) - filename = str(datetime.now()).replace(" ", "-") - filename = filename.replace(":", "-") - filename = filename.replace(".", "-") - filename = filename + ".jpg" - # Take image - # old was 800x600 - vid_cap = cv2.VideoCapture(0) - vid_cap.set(3, 1280) - vid_cap.set(4, 720) - if not vid_cap.isOpened(): - logging.warn("Error opening video device using opencv") - else: - print("Taking picture") - for x in range(10): - ret, image = vid_cap.read() - cv2.imwrite("/home/pi/Desktop/smartGarden/smartGarden/images/" + ymd + "/" + str(filename), image) - vid_cap.release() - # print("Sending picture to: " + "/home/pi/Desktop/smartGarden/smartGarden/images/" + ymd + "/" + str(filename)) - # myCmd = 'fswebcam -q -i 0 -r 1280x720 /home/pi/Desktop/smartGarden/smartGarden/images/' + ymd + "/" + str(filename) - # os.system(myCmd) - - -def run_camera(send_folder): - ymd = create_folder() - if send_folder: - logging.info("Sending folder") - yesterday = datetime.now() - timedelta(days=1) - filename = str(yesterday).replace(" ", "-") - dateArray = filename.split('-') - ymd = dateArray[0] + "-" + dateArray[1] + "-" + dateArray[2] - send_folder(ymd) - ymd = create_folder() - take_pics(ymd) - - -def camera_thread(): - # TODO ADD ANOTHER THREAD FOR SENDING IMAGES TO CACTUAR PC - ymd = create_folder() - timer = threading.Event() - # run_camera(send_folder=False) - send_folder = False - sent_folder = False - while not timer.wait(CAMERA_TIME_SECONDS) and not SHUTDOWN_FLAG: - try: - time = str(datetime.now()).split() - hour = str(time[1].split(':')[0]) - except Exception as e: - print("Error parsing date for camera.") - print(e) - if hour == "00" and not sent_folder: - send_folder = True - sent_folder = True - elif hour != "00" and sent_folder: - sent_folder = False - else: - send_folder = False - run_camera(False) - if SHUTDOWN_FLAG: - break - - def prune_logs_thread(): # prune.prune("smartGardenLog.txt") timer = threading.Event() @@ -167,7 +51,7 @@ def prune_logs_thread(): if __name__ == "__main__": - logFile = "/home/pi/Desktop/smartGarden/smartGarden/logs/smartGardenLog.log" + logFile = "./logs/smartGardenLog.log" if not os.path.exists(logFile): with open(logFile, 'w+'): pass @@ -181,17 +65,12 @@ def prune_logs_thread(): pump = WaterPump(logging, sentinel, soilMoistureSensor) luxSensor = None - try: - luxSensor = LuxSensor(logging, sentinel) - except Exception as e: - logging.error("Failed to start lux sensor") - logging.error(e) + luxSensor = LuxSensor(logging, sentinel) tempSensor = TempSensor(logging, sentinel) artificialLight = ArtificialLight(logging, sentinel) server = GardenServer(sentinel, pump, luxSensor, soilMoistureSensor, tempSensor) signal.signal(signal.SIGINT, server.shutDownGarden) - # thread4 = threading.Thread(target=camera_thread) thread8 = threading.Thread(target=prune_logs_thread) print("Starting threads at time: " + str(datetime.now()) + "...") diff --git a/test/modules/soilMoisture/__pycache__/soil.cpython-35.pyc b/test/modules/soilMoisture/__pycache__/soil.cpython-35.pyc deleted file mode 100644 index 2eca134..0000000 Binary files a/test/modules/soilMoisture/__pycache__/soil.cpython-35.pyc and /dev/null differ