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:

 

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 )