The whole Mushrooms' Paradise blog
This project has 3 types of components: mushrooms specific, vegetables specific and common.
Mushrooms specific components are fans and a humidifier.
Common components are Enviro HAT and lights.
Vegetable specific components are soil moisture sensors and water pumps.
Mushrooms specific and common components are controlled by Raspberry PI. Vegetables specific - by ESP32.
This post is about RPI part of the software. It is written in Python and here is the whole program on GitHub
The whole Python code
from __future__ import print_function import paho.mqtt.publish as publish import psutil import string import random import sys from smbus import SMBus from bme280 import BME280 import RPi.GPIO as GPIO # Importing the GPIO library to use the GPIO pins of Raspberry pi from time import sleep # Importing the time library to provide the delays in program from datetime import datetime, timedelta # setup GPIO pins #GPIO.cleanup() # clean up GPIO.setmode(GPIO.BCM) humidifier_pin = 16 # Initializing pin light_pin = 17 fan_pin = 27 print_sensor_values = 0 # classes # controls humidifier class humidifier : Pin = humidifier_pin AirHumidity = 0 Temperature = 0 FlagRun = 0 FlagRunning = 0 CheckTimeCycle = timedelta(hours = 3) CheckTimeOnInterval = timedelta(minutes = 15) LastCheckTime = datetime.now() - CheckTimeCycle DesiredHumidity = 70 #setup enviro hat bus = SMBus(1) bme280 = BME280(i2c_dev=bus) def __init__(self, p=16): self.Pin = p GPIO.setup(self.Pin, GPIO.OUT) # set GPIO pin as output def ReadValues(self) : self.Temperature = self.bme280.get_temperature() self.AirHumidity = self.bme280.get_humidity() # there is a bug on Enviro HAT - # sometimes humidity sensor returns values over 90 when real value is not even close to that # in this case sleep for a second and reread the value if self.AirHumidity > 85: sleep(1) self.AirHumidity = self.bme280.get_humidity() if print_sensor_values == 1: print("Inside ReadValues") print ("Humidity is " + str(self.AirHumidity)) print ("Temperature is " + str(self.Temperature)) def Run(self) : Now = datetime.now() self.ReadValues() # if running currently if self.LastCheckTime + self.CheckTimeCycle < Now : # time to check print ("Humidity is " + str(self.AirHumidity)) print ("Temperature is " + str(self.Temperature)) if self.AirHumidity < self.DesiredHumidity : GPIO.output(self.Pin, 1) print ("humidifier is running") self.FlagRunning = 1 self.LastCheckTime = Now if self.FlagRunning : if self.AirHumidity > self.DesiredHumidity : GPIO.output(self.Pin, 0) print ("humidifier stopped") self.FlagRunning = 0 self.FlagRun = 1 if self.LastCheckTime + self.CheckTimeOnInterval < Now : # time to stop GPIO.output(self.Pin, 0) print ("humidifier stopped ") self.FlagRunning = 0 self.FlagRun = 1 def ShutDown(self): GPIO.output(self.Pin, 0) self.FlagRunning = 0 # controls led lights class light : Pin = light_pin CheckTimeCycle = timedelta(days = 1) CheckTimeOnInterval = timedelta(hours = 7) LastCheckTime = datetime.now() - CheckTimeCycle FlagRun = 0 FlagRunning = 0 def __init__(self, p=16): self.Pin = p GPIO.setup(self.Pin, GPIO.OUT) def Run(self): Now = datetime.now() # if running currently if self.LastCheckTime + self.CheckTimeCycle < Now : GPIO.output(self.Pin, 1) print ("lights are ON") self.FlagRunning = 1 self.LastCheckTime = Now if self.FlagRunning : if self.LastCheckTime + self.CheckTimeOnInterval < Now : # time to stop GPIO.output(self.Pin, 0) print ("lights are OFF") self.FlagRunning = 0 self.FlagRun = 1 def ShutDown(self): GPIO.output(self.Pin, 0) self.FlagRunning = 0 # controls fans class fan: Pin = fan_pin CheckTimeCycle = timedelta(hours = 1) CheckTimeOnInterval = timedelta(minutes = 5) LastCheckTime = datetime.now() - CheckTimeCycle FlagRun = 0 FlagRunning = 0 def __init__(self, p=11): self.Pin = p GPIO.setup(self.Pin, GPIO.OUT) def Run(self): Now = datetime.now() # if running currently if self.LastCheckTime + self.CheckTimeCycle < Now : GPIO.output(self.Pin, 1) print ("fan is running") self.FlagRunning = 1 self.LastCheckTime = Now if self.FlagRunning : if self.LastCheckTime + self.CheckTimeOnInterval < Now : # time to stop GPIO.output(self.Pin, 0) print ("fan stopped ") self.FlagRunning = 0 self.FlagRun = 1 def ShutDown(self): GPIO.output(self.Pin, 0) self.FlagRunning = 0 SoilHumidityR1 = 2000 SoilHumidityR2 = 2000 SoilHumidityL1 = 2000 SoilHumidityL2 = 2000 # init variables for ThingSpeak timeFormat = '%x %X' # The ThingSpeak Channel ID. # Replace <YOUR-CHANNEL-ID> with your channel ID. channelID = "1263910" # The write API key for the channel. # Replace <YOUR-CHANNEL-WRITEAPIKEY> with your write API key. writeAPIKey = "<YOUR-CHANNEL-WRITEAPIKEY>" # The hostname of the ThingSpeak MQTT broker. mqttHost = "mqtt.thingspeak.com" # Use any username. mqttUsername = "Mushrooms" # use any clientID. clientID = 'Test' # Your MQTT API key from Account > My Profile. mqttAPIKey = "<MQTT-API-KEY>" #Define the connection type as websockets, and set the port to 80. tTransport = "websockets" tPort = 80 # Create a topic string in the form shown in Publish to a Channel Feed that updates field 1 and field 2 of the specified channel simultaneously. topic = "channels/" + channelID + "/publish/" + writeAPIKey # parameters for publishing frequence PublishTimeCycle = timedelta(hours=1) # first publish will be in 2 minutes FirstPublishDelay = timedelta(minutes=2) LastPublished = datetime.now() - PublishTimeCycle + FirstPublishDelay # components objects Humidifier = humidifier(humidifier_pin) Light = light(light_pin) Fan = fan(fan_pin) # ************** main loop while(1): try: Now = datetime.now() Humidifier.Run() Fan.Run() Light.Run() # *********** for thingspeak # if time to publish if LastPublished + PublishTimeCycle < Now : # build the payload string. payload = "field1=" + str(Humidifier.AirHumidity) + "&field2=" + str(Humidifier.Temperature) payload += "&field3=" + str(SoilHumidityR1) + "&field4=" + str(SoilHumidityR2) payload += "&field5=" + str(SoilHumidityL1) + "&field6=" + str(SoilHumidityL2) payload += "&field7=" + str(Humidifier.FlagRun) + "&field8=" + str(Fan.FlagRun) # attempt to publish this data to the topic. publish.single(topic, payload, hostname=mqttHost, transport=tTransport, port=tPort,auth={'username':mqttUsername,'password':mqttAPIKey}) # print (" Published on " + datetime.now('%Y-%m-%d %H:%M', "EST")) LastPublished = datetime.now() print (" Published on " + LastPublished.strftime(timeFormat)) print("Values: Humidity: " + str(Humidifier.AirHumidity) + " Temperature: " + str(Humidifier.Temperature) + " Humidifier Run: " + str(Humidifier.FlagRun) + " Fan Run: " + str(Fan.FlagRun)) # reset Humidifier and Fan FlagRun so we can see if they run between this and next publish Humidifier.FlagRun = 0 Fan.FlagRun = 0 sleep(5) except (KeyboardInterrupt): print ("inside KeyboardInterrupt. Before ShutDown") Humidifier.ShutDown() Fan.ShutDown() Light.ShutDown() del Humidifier del Fan del Light break GPIO.cleanup() except: e = sys.exc_info()[0] print( "<p>Error: %s</p>" % e )
Code explanations
Imported Libraries and classes
paho.mqtt.publish to publish data on ThingSpeak
sys for error handling
smbus, bme280 to access Enviro HAT sensors
RPi.GPIO to access GPIO pins
datetime to work with time intervals
Classes
Raspberry PI is controlling humidifier, lights and fans. It also posts data to ThingSpeak channel so data can be monitored from anywhere, computers or mobile devices.
For each of these objects - humidifier, lights and fans - I created a separate class.
All classes have some common properties and methods.
Common properties for all the classes
Pin - RPI pin object is controlled by
LastCheckTime - the last time the object was checked
CheckTimeCycle - time cycle of the object
CheckTimeOnInterval - time interval the object should be in ON state
FlagRunning - flag indicating that object is in ON state
FlagRun - flag indicating that object was in ON state after data was published last
Common methods for all the classes
Run - the main object's method. For all the classes the code checks if it is time to run, and if run of the object is needed based on environmental parameters. If object need to run - signal 1 is sent to object's pin.
ShutDown - shutdowns the object. Basically this method sends 0 to the pin controlling the object. This method is called before exiting the program in case of exception.
Constructor for each object takes the pin number of the pin controlling the object.
If the setup needs to be customized in the future, for example light should be set for different duration for mushrooms and vegetables parts of enclosure, it will be easy to do. I will just create another instance of light class, set te pin and desired duration and run it in main loop togeter with other objects.
Code for lights
class light : Pin = light_pin CheckTimeCycle = timedelta(days = 1) CheckTimeOnInterval = timedelta(hours = 7) LastCheckTime = datetime.now() - CheckTimeCycle FlagRun = 0 FlagRunning = 0 def __init__(self, p=16): self.Pin = p GPIO.setup(self.Pin, GPIO.OUT) def Run(self): Now = datetime.now() # if running currently if self.LastCheckTime + self.CheckTimeCycle < Now : GPIO.output(self.Pin, 1) print ("lights are ON") self.FlagRunning = 1 self.LastCheckTime = Now if self.FlagRunning : if self.LastCheckTime + self.CheckTimeOnInterval < Now : # time to stop GPIO.output(self.Pin, 0) print ("lights are OFF") self.FlagRunning = 0 self.FlagRun = 1 def ShutDown(self): GPIO.output(self.Pin, 0) self.FlagRunning = 0
Lights run for 12 hours every day, but it is easily customizable either in the class code, or while creating an instance of the class.
Code for fan
class fan: Pin = fan_pin CheckTimeCycle = timedelta(hours = 1) CheckTimeOnInterval = timedelta(minutes = 5) LastCheckTime = datetime.now() - CheckTimeCycle FlagRun = 0 FlagRunning = 0 def __init__(self, p=11): self.Pin = p GPIO.setup(self.Pin, GPIO.OUT) def Run(self): Now = datetime.now() # if running currently if self.LastCheckTime + self.CheckTimeCycle < Now : GPIO.output(self.Pin, 1) print ("fan is running") self.FlagRunning = 1 self.LastCheckTime = Now if self.FlagRunning : if self.LastCheckTime + self.CheckTimeOnInterval < Now : # time to stop GPIO.output(self.Pin, 0) print ("fan stopped ") self.FlagRunning = 0 self.FlagRun = 1 def ShutDown(self): GPIO.output(self.Pin, 0) self.FlagRunning = 0
Fans run for 10 minutes every hour. Time intervals an GPIO pin are easily customizable either in the class code, or while creating an instance of the class.
Code for Humidifier
class humidifier : Pin = humidifier_pin AirHumidity = 0 Temperature = 0 FlagRun = 0 FlagRunning = 0 CheckTimeCycle = timedelta(hours = 3) CheckTimeOnInterval = timedelta(minutes = 15) LastCheckTime = datetime.now() - CheckTimeCycle DesiredHumidity = 70 #setup enviro hat bus = SMBus(1) bme280 = BME280(i2c_dev=bus) def __init__(self, p=16): self.Pin = p GPIO.setup(self.Pin, GPIO.OUT) # set GPIO pin as output def ReadValues(self) : self.Temperature = self.bme280.get_temperature() self.AirHumidity = self.bme280.get_humidity() # there is a bug on Enviro HAT - # sometimes humidity sensor returns values over 90 when real value is not even close to that # in this case sleep for a second and reread the value if self.AirHumidity > 85: sleep(1) self.AirHumidity = self.bme280.get_humidity() if print_sensor_values == 1: print("Inside ReadValues") print ("Humidity is " + str(self.AirHumidity)) print ("Temperature is " + str(self.Temperature)) def Run(self) : Now = datetime.now() self.ReadValues() # if running currently if self.LastCheckTime + self.CheckTimeCycle < Now : # time to check print ("Humidity is " + str(self.AirHumidity)) print ("Temperature is " + str(self.Temperature)) if self.AirHumidity < self.DesiredHumidity : GPIO.output(self.Pin, 1) print ("humidifier is running") self.FlagRunning = 1 self.LastCheckTime = Now if self.FlagRunning : if self.AirHumidity > self.DesiredHumidity : GPIO.output(self.Pin, 0) print ("humidifier stopped") self.FlagRunning = 0 self.FlagRun = 1 if self.LastCheckTime + self.CheckTimeOnInterval < Now : # time to stop GPIO.output(self.Pin, 0) print ("humidifier stopped ") self.FlagRunning = 0 self.FlagRun = 1 def ShutDown(self): GPIO.output(self.Pin, 0) self.FlagRunning = 0
Humidifier’s Run function is different from other objects because it does not run automatically when it is time to run, but checks humidity value from Enviro HAT and runs only if value is less than desired.
Humidifier runs for 15 minutes every 3 hours, if air humidity is less than 70%. Time intervals, minimum humidity value an GPIO pin are easily customizable either in the class code, or while creating an instance of the class.
ReadValues function read sensor values. There is an option to output all the values read, by setting flag print_sensor_values to 1.
Additionally, there seems to be a bug on Enviro HAT humidity sensor. First reading is ofter over 90%, when in reality humidity is much less, about 30%. As a workaround I check if humidity value is more than 85 and if so - sleep for a second and reread the value. It seems to work fine this way.
Initialization of ThingSpeak data
There are several ways to publish data on ThingSpeak, I decided to use websockets. Every method used MQTT protocol and library to do so is paho.mqtt. Description of it is here: Publish Using WebSockets in Python on a Raspberry Pi - MATLAB & Simulink
PBublishing is done in main loop, but before publishing ThingSpeak connection data need to be initialized
# The ThingSpeak Channel ID. # Replace <YOUR-CHANNEL-ID> with your channel ID. channelID = "1263910" # The write API key for the channel. # Replace <YOUR-CHANNEL-WRITEAPIKEY> with your write API key. writeAPIKey = "<YOUR-CHANNEL-WRITEAPIKEY>" # The hostname of the ThingSpeak MQTT broker. mqttHost = "mqtt.thingspeak.com" # Use any username. mqttUsername = "Mushrooms" # use any clientID. clientID = 'Test' # Your MQTT API key from Account > My Profile. mqttAPIKey = "<MQTT-API-KEY>" #Define the connection type as websockets, and set the port to 80. tTransport = "websockets" tPort = 80 # Create a topic string in the form shown in Publish to a Channel Feed that updates field 1 and field 2 of the specified channel simultaneously. topic = "channels/" + channelID + "/publish/" + writeAPIKey # parameters for publishing frequence PublishTimeCycle = timedelta(hours=1) # first publish will be in 2 minutes FirstPublishDelay = timedelta(minutes=2) LastPublished = datetime.now() - PublishTimeCycle + FirstPublishDelay
Main loop
In the main loop I run all the objects
Humidifier.Run() Fan.Run() Light.Run()
And publish data on ThingSpeak.
ThingSpeak channel for this project can be found here: Mushrooms Paradise
Data that is published from RPI includes Air Humidity, Air Temperature, and if humidifier and fans run at least once from the last publising. These need to be monitored closely, for example this data will show that the humidifier needs to be refilled with water.
if LastPublished + PublishTimeCycle < Now : # build the payload string. payload = "field1=" + str(Humidifier.AirHumidity) + "&field2=" + str(Humidifier.Temperature) payload += "&field3=" + str(SoilHumidityR1) + "&field4=" + str(SoilHumidityR2) payload += "&field5=" + str(SoilHumidityL1) + "&field6=" + str(SoilHumidityL2) payload += "&field7=" + str(Humidifier.FlagRun) + "&field8=" + str(Fan.FlagRun) # attempt to publish this data to the topic. publish.single(topic, payload, hostname=mqttHost, transport=tTransport, port=tPort,auth={'username':mqttUsername,'password':mqttAPIKey}) # print (" Published on " + datetime.now('%Y-%m-%d %H:%M', "EST")) LastPublished = datetime.now() print (" Published on " + LastPublished.strftime(timeFormat))
Error handling code
If an exception happens software will shutdown all the signals for all the objects, delete the objects, print the exception information and exit. Exceptions (including KeyboardInterrupt) are the only way to exit software
except (KeyboardInterrupt): print ("inside KeyboardInterrupt. Before ShutDown") Humidifier.ShutDown() Fan.ShutDown() Light.ShutDown() del Humidifier del Fan del Light break GPIO.cleanup() except: e = sys.exc_info()[0] print( "<p>Error: %s</p>" % e )
Top Comments