Hydroponics, Tile Buttons to run scripts for pump/relay

I am going to show you the code I use to make my pumps work to get the ph + and ph- solution into my mixing bucket. you can add more buttons to control more relays ( Timed for pumps )
I know cayanne can control relays and such but I wanted to be able to hit a button and have my pump turn on for X amount of seconds. I can easily time with my measuring glass how many seconds = how much ML of fluid pushes out. Also I’m pretty sure if you have pumps that push a lot that you could probably use a potentiometer ( search DC motor controller $2 ) to slow them down since trying to get the pwm chip to work with mqtt was more of a PIA than I felt like messing with.
This was quick and easy a few hours of messing around and here is the how to.

First setup the Cayanne Button Code.
Go to your dashboard then click add device and the image
This will being you to a screen showing your username, password and client ID.
Replace the XXX in the code with your information.

This is the code I am using


import cayenne.client
import time

MQTT_USERNAME = “XXX”
MQTT_PASSWORD = “XXX”
MQTT_CLIENT_ID = “XXX”

def on_message(message):
print("message received: " + str(message))
if (message.channel == 7) and (message.value == “0”):
execfile(“gpio12.py”)
if (message.channel == 8) and (message.value == “0”):
execfile(“gpio20.py”)

client = cayenne.client.CayenneMQTTClient()
client.on_message = on_message
client.begin(MQTT_USERNAME, MQTT_PASSWORD, MQTT_CLIENT_ID)

i=0
timestamp = 0

while True:
client.loop()
if (time.time() > timestamp + 10):
timestamp = time.time()
i = i+1


In the main tile button Script you see 2 .py/2 buttons\actions.
you can delete one if you only need 1 button.
I am doing 2 buttons so I can ph+ and ph- remotely.

Below is the scripts for the buttons with timers built in for 5 seconds. ( GPIO20.py)
these 2 scripts can be named whatever you want. just make sure to change the code in the first script to point to the button timer script\file.


import RPi.GPIO as GPIO
import time
GPIO.setmode(GPIO.BCM)
GPIO.setup(20, GPIO.OUT)
GPIO.output(20, 1)
time.sleep(0.5)
GPIO.setmode(GPIO.BCM)
GPIO.setup(20, GPIO.OUT)
GPIO.output(20, 0)
GPIO.cleanup()


Now run the first script once this is set up.
If you get any errors then you probbaly need to update paho-mqtt
run “sudo pip install paho-mqtt==1.3.0` to install 1.3.0”

it will stay running and scrolling your username/password and some other mqtt stuff thats what its supposed to do.

go back yo your dashboard and click on add - Devices Now scroll to the bottom and where it shows custom widget and click on button.

This is what it should look like.
If the add button is greyed out and not letting you add then the script isnt currently running and connected to cayenne. Cayenne will not let you add a button to that mqtt string/connection unless it see’s it is working currently.


Pay attention to where the “9” is on this picture.
In the first script/main mqtt script you see the line " i[ (message.channel == 9) and (message.value == “1”):] the message.channel is pretty much like a virtual button # so make all of these different as you are adding them.

Here is what mine looked like when I was done.

The problem\bug I see is that this is a toggle button so you have to make a trigger that tells it when I turn this on turn this virtual button back off ( this happens after the gpioxx.py script has ran )
image

Now they work as buttons that turn on and show the on icon\status until they have run the course then turn to off status with the grey/off icon.

HELP!
1st problem: is when I tell it to run other scripts say tell it to run a script that stays running it will kill all of the other buttons. the script stays running so this script does not let any other commands work.
1.5 problem: running this on startup I’ve tried crontab and it isn’t reliable I’ve went through about 3 pages of google and still haven’t gotten a reliable way to start the button script on startup. services etc… nothing is reliable so if you have a way to start the script on startup that works for you please share the code with me.

2nd problem:
Is there a way to make a virtual button that when pressed can set a schedule for a button/relay?
I would love a button that says VEG and one that says Bloom to change the lighting schedule ( based on time of day not seconds in case of a power outage etc… ). Is there a script any of the super familar cayenne people know about or could help me with to make this happen? I could always buy $10 timers but I already have a 10amp 8 channel relay hooked to this pi project.

Rough picture I have a waterproof enclosure for this but its still in desk-testing phase

1 Like

Nice write up @pihydro.0001 . I will be linking many people to this post.

wow, this is something new. Now we can have a push button on cayenne. you could have also done this from code by sending back OFF to the button.

this is because when you are in infinte loop of another script, it stops the main cayenne code. thus making the device offline.

waiting to see a complete working solution.

I figured i could possibly do it ( send back a off command ) with code but I am not the best coder just taught myself enough to play around with the pi’s so I just used triggers to get around it and make sure it worked.

I know why it stops the script I was hoping someone could send me some code or clean up the code so I could start a script like an SH script that runs the on-going script on another terminal and then closes out so the button acts like it was done.

can you share the second script which you want to run alongwith the cayenne script. for multitasking you will need extra coding Python - Multithreaded Programming

I do not have one right now since I want the other script to be the one that sets the lights to VEG and Bloom cycles.

I could use some help adding all of my mqtt scripts together the button script + dht11 script and the ph sensor script.
My dual dht11


import paho.mqtt.client as mqtt
import time
import sys
import Adafruit_DHT

username = ""
password = ""
clientid = ""

mqttc = mqtt.Client(client_id=clientid)
mqttc.username_pw_set(username, password=password)
mqttc.connect("mqtt.mydevices.com", port=1883, keepalive=60)
mqttc.loop_start()

topic_dht11_temp = "v1/" + username + "/things/" + clientid + "/data/1"
topic_dht11_humidity = "v1/" + username + "/things/" + clientid + "/data/2"
topic_dht12_temp = "v1/" + username + "/things/" + clientid + "/data/3"
topic_dht12_humidity = "v1/" + username + "/things/" + clientid + "/data/4"

while True:
    try:
        humidity11, temp11 = Adafruit_DHT.read_retry(11, 22) #11 is the se$
        humidity12, temp12 = Adafruit_DHT.read_retry(11, 27) #22 is the se$

        if temp11 is not None:
            temp11 = "temp,c=" + str(temp11)
            mqttc.publish(topic_dht11_temp, payload=temp11, retain=True)
        if humidity11 is not None:
            humidity11 = "rel_hum,p=" + str(humidity11)
            mqttc.publish(topic_dht11_humidity, payload=humidity11, retain$
        if temp12 is not None:
            temp12 = "temp,c=" + str(temp12)
            mqttc.publish(topic_dht12_temp, payload=temp12, retain=True)
        if humidity12 is not None:
            humidity12 = "rel_hum,p=" + str(humidity12)
            mqttc.publish(topic_dht12_humidity, payload=humidity12, retain$
        time.sleep(5)
    except (EOFError, SystemExit, KeyboardInterrupt):
        mqttc.disconnect()
        sys.exit()

and my PH sensor code


#!/usr/bin/env python

import cayenne.client

import io # used to create file streams
import fcntl # used to access I2C parameters like addresses
import time
import string # helps parse strings

class atlas_i2c:
    long_timeout = 3 # the timeout needed to query readings and calibrations
    short_timeout = .5 # timeout for regular commands
    default_bus = 1 # the default bus for I2C on the newer Raspberry Pis, certain older board$
    default_address = 99 # the default address for the pH sensor

    def __init__(self, address = default_address, bus = default_bus):
        # open two file streams, one for reading and one for writing
        # the specific I2C channel is selected with bus
        # it is usually 1, except for older revisions where its 0
        # wb and rb indicate binary read and write
        self.file_read = io.open("/dev/i2c-"+str(bus), "rb", buffering = 0)
        self.file_write = io.open("/dev/i2c-"+str(bus), "wb", buffering = 0)

        # initializes I2C to either a user specified or default address
        self.set_i2c_address(address)

    def set_i2c_address(self, addr):
        # set the I2C communications to the slave specified by the address
        # The commands for I2C dev using the ioctl functions are specified in
        # the i2c-dev.h file from i2c-tools
        I2C_SLAVE = 0x703
        fcntl.ioctl(self.file_read, I2C_SLAVE, addr)
        fcntl.ioctl(self.file_write, I2C_SLAVE, addr)

    def write(self, string):
        # appends the null character and sends the string over I2C
        string += "\00"
        self.file_write.write(string)

    def read(self, num_of_bytes = 31):
        # reads a specified number of bytes from I2C, then parses and displays the result
        res = self.file_read.read(num_of_bytes) # read from the board
        response = filter(lambda x: x != '\x00', res) # remove the null characters to get the$
        if(ord(response[0]) == 1): # if the response isnt an error
            char_list = map(lambda x: chr(ord(x) & ~0x80), list(response[1:])) # change MSB t$
            # NOTE: having to change the MSB to 0 is a glitch in the raspberry pi, and you sh$
            return "Command succeeded " + ''.join(char_list) # convert the char list to a str$
        else:
            return "Error " + str(ord(response[0]))

    def query(self, string):
        # write a command to the board, wait the correct timeout, and read the response
        self.write(string)

# the read and calibration commands require a longer timeout
        if((string.upper().startswith("R")) or
           (string.upper().startswith("CAL"))):
            time.sleep(self.long_timeout)
        elif((string.upper().startswith("SLEEP"))):
            return "sleep mode"
        else:
            time.sleep(self.short_timeout)

        return self.read()

    def close(self):
        self.file_read.close()
        self.file_write.close()

def main():
    device = atlas_i2c() # creates the I2C port object, specify the address or bus if necessa$

    # Cayenne authentication info. This should be obtained from the Cayenne Dashboard.
    MQTT_USERNAME  = ""
    MQTT_PASSWORD  = ""
    MQTT_CLIENT_ID = ""


    # The callback for when a message is received from Cayenne.
    def on_message(message):
        print("message received: " + str(message))
        # If there is an error processing the message return an error string, otherwise retur$

    client = cayenne.client.CayenneMQTTClient()
    client.on_message = on_message
    client.begin(MQTT_USERNAME, MQTT_PASSWORD, MQTT_CLIENT_ID)


    i=0
    timestamp = 0
    output = "0.00"
    phvalue = 7

    while True:
        client.loop()

        if (time.time() > timestamp + 10):

            try:
               output = device.query("R")
               print(output);
            except IOError:
               print("Query failed")

            phvalue = float(string.split(output, ' ')[2])

            client.virtualWrite(1, phvalue, dataType='PH', dataUnit='PH')
            timestamp = time.time()
            i = i+1

if __name__ == '__main__':
    main()

I know there is probably a way to combine these 2 with the Button mqtt I just need to get better at coding and figure it out or get someone to help.

Without the code, i cant help you much.

are all individual code working fine and you are able to read sensor data?

yes, the PH.py and my humid.PY both work fine or seems to be working fine on the bench.

assuming that you have both the code working fine and you are able to read sensor data.
so next is that you need to send the sensor data from this two code to cayenne.
Below is the basic code which is used to send data to cayenne.

#!/usr/bin/env python
import cayenne.client
import time
//include any library/packages you want to use.
# Cayenne authentication info. This should be obtained from the Cayenne Dashboard.
MQTT_USERNAME  = "MQTT_USERNAME"
MQTT_PASSWORD  = "MQTT_PASSWORD"
MQTT_CLIENT_ID = "MQTT_CLIENT_ID"


client = cayenne.client.CayenneMQTTClient()
client.begin(MQTT_USERNAME, MQTT_PASSWORD, MQTT_CLIENT_ID)
# For a secure connection use port 8883 when calling client.begin:
# client.begin(MQTT_USERNAME, MQTT_PASSWORD, MQTT_CLIENT_ID, port=8883)

i=0
timestamp = 0

while True:
    client.loop()
    //code to read your sensor.
    if (time.time() > timestamp + 10):
        //this sends data every 10 seconds. 
        client.celsiusWrite(1, i)
        client.luxWrite(2, i*10)
        client.hectoPascalWrite(3, i+800)
        timestamp = time.time()
        i = i+1

So there are three main things to consider:

have a look at this Data types for Cayenne MQTT API

@pihydro.0001 In order to start a long running process using an actuator message you need to have it run on a separate thread. That way the main thread can continue to process the Cayenne messages so it can stay connected and communicate with the server.

Below is some example thread code you could try adding into your Cayenne script. You can replace the do_stuff function with your own code that you would like to run. I also have it check if the thread is currently running so you don’t keep starting extra threads each time you press a button.

import threading

def do_stuff():
    while True:
        print('doing stuff')
        time.sleep(5)
do_stuff_thread = threading.Thread(target=do_stuff)
do_stuff_thread.setDaemon(True)

# The callback for when a message is received from Cayenne.
def on_message(message):
    print("message received: " + str(message))
    if (message.channel == 7) and (message.value == '0'):
        if not do_stuff_thread.isAlive():
            print('start thread')
            do_stuff_thread.start()
    # If there is an error processing the message return an error string, otherwise return nothing.
2 Likes

I may try to do this in the future but for now I have decided to not add the scripts together as I want to produce these for tons of grow houses I plan on doing and depending which pi is where in the grow house It might be better to just try and make a .sh script that runs the other scripts in different threads.
so depending what the room needs ( soil or hydro ) i just ssh in then filezilla over the gpio.py, buttons.py,humid.ph,PH.py etc… whatever devices it is going to be using then throw together a startup.sh.
Right now I have the startup.sh with the correct permissions it runs when i sudo sh startup.sh but I cannot for thelife of me get the GD pi to run it on startup. I have 3 other ubuntu boxes running the same script perfect butfor whatever reason the pi just never executes it… still looking into that but I think that might be the easier/better solution for when I start rolling these out in my grow houses.

How are you trying to get the script to run on startup? For the Cayenne Pi agent we use systemd which works without issues, but there are other methods that should work as well.

yea just on startup, tried cron, init.d method, rc.local method etc…

Maybe a permissions issue is preventing the startup.sh script or the py scripts from running? Maybe you need to launch them with sudo? Also, are you sure they are in a location that can be accessed at startup along with the full path to them? Does the startup.sh script specify the full path to the py scripts when it tries to launch them? If not, and you startup.sh doesn’t change directories it could be running from some other directory than the py scripts and that could cause issues.

yep, changed permission, works on my ubuntu VM just fine just something about the pi wont work. tried sudo, tried full paths, tried adding the sub scripts from the startup.sh into the same directory and not using sub path, its killing me I might have to actually hook it to a screen and run the GUI and download a program… its really pissing me off since the same methods I’ve tried 100% identical work on other distros.

Not sure what would cause that. I guess I’d try just using a script that only outputs some log message somewhere just to see if it’s getting run at all. Or checking the syslog for any error messages. You might also try using systemd to launch it if you haven’t already.

1 Like