Xenophod
Published © CC BY-SA

Adding Voice Control to Omega2+

Turning my solar heater project into a pool cover pump, now with voice controls through Google Assistant.

IntermediateShowcase (no instructions)6 hours202
Adding Voice Control to Omega2+

Things used in this project

Hardware components

Omega2 Plus
Onion Corporation Omega2 Plus
×1
Expansion Dock
Onion Corporation Expansion Dock
×1
Relay Expansion
Onion Corporation Relay Expansion
×1

Software apps and online services

Adafruit IO

Story

Read more

Schematics

Diagram of Software workflow

I'm positive there is a more pythonic way to do this, but right now it's all cobbled together, sorry about that.

Code

start_pump.sh

SH
called by the pump_listen.py script when "ON" is received.
It checks for the temp file, if it does exist, it means the pump is already on so it exits.
If the temp file isn't there, it creates the temp file then starts a detached screen session named 'drain' and runs the script 'drain_on.sh' inside that session.
#!/bin/sh
if [ -f /tmp/foo.txt ]; then
    exit
else
    touch /tmp/foo.txt
    screen -S drain -d -m /root/drain_on.sh
fi

UPDATED: mqtt_subscribe.py

Python
I've updated all of the code to be more stable and "pythonic". Here's the mqtt_subscribe.py script, this does 99% of everything. If I could figure out non-blocking threads, it could do 100% but I haven't fallen down that rabbit hole yet. It still calls out a screen session to run the "stop" or "off" script after 20 minutes.
The base script is taken directly from the Adafruit examples.

I run it by piping outputs to /dev/null and run it in the background like this:
./mqtt_subscribe.py > /dev/null 2>&1 &

You can also launch it at start by adding it to the /etc/rc.local
https://docs.onion.io/omega2-docs/running-a-command-on-boot.html
# Example of using the MQTT client class to subscribe to a feed and print out
# any changes made to the feed.  Edit the variables below to configure the key,
# username, and feed to subscribe to for changes.

# Import standard python modules.
import sys
import subprocess  #import the subprocess to enable calling system commands, such as screen
from OmegaExpansion import relayExp  #import the OmegaExpansion commands for the relay board

# Import Adafruit IO MQTT client.
from Adafruit_IO import MQTTClient

# Set to your Adafruit IO key.
# Remember, your key is a secret,
# so make sure not to publish it when you publish this code!
ADAFRUIT_IO_USERNAME = "xxxxx"
ADAFRUIT_IO_KEY = "xxxxx"

# Set to the ID of the feed to subscribe to for updates.
FEED_ID = 'switch'
relayExp.driverInit(7)
print(relayExp.readChannel(7, 0))

# Define callback functions which will be called when certain events happen.
def connected(client):
    # Connected function will be called when the client is connected to Adafruit IO.
    # This is a good place to subscribe to feed changes.  The client parameter
    # passed to this function is the Adafruit IO MQTT client so you can make
    # calls against it easily.
    print('Connected to Adafruit IO!  Listening for {0} changes...'.format(FEED_ID))
    # Subscribe to changes on a feed named DemoFeed.
    client.subscribe(FEED_ID)

def disconnected(client):
    # Disconnected function will be called when the client disconnects.
    print('Disconnected from Adafruit IO!')
    sys.exit(1)

def message(client, feed_id, payload):
    # Message function will be called when a subscribed feed has a new value.
    # The feed_id parameter identifies the feed, and the payload parameter has
    # the new value.
    print('Feed {0} received new value: {1}'.format(feed_id, payload))
    if str(payload) == "ON":
         command1 = "screen -XS drain quit"   #Kills any running screen session named 'drain'
         process = subprocess.Popen(command1.split(), stdout=subprocess.PIPE)
         output, error = process.communicate()
         relayExp.driverInit(7)  #Initialize the relay board
         relayExp.setChannel(7, 0, 1)   #turn on the relay 
         command = "screen -S drain -d -m python3 digital_in.py" #start a screen session called 'drain' 
         process = subprocess.Popen(command.split(), stdout=subprocess.PIPE)
         output, error = process.communicate()
         print(relayExp.readChannel(7, 0))  #Print status of relay to standard out
    elif str(payload) == "OFF":
         relayExp.driverInit(7)  #Initialize the relay board
         relayExp.setChannel(7, 0, 0)   #turn off the relay
         command1 = "screen -XS drain quit"   #Kill any running screen session named 'drain'
         process = subprocess.Popen(command1.split(), stdout=subprocess.PIPE)
         output, error = process.communicate()
         print(relayExp.readChannel(7, 0))  #Print status of relay to standard out

# Create an MQTT client instance.
client = MQTTClient(ADAFRUIT_IO_USERNAME, ADAFRUIT_IO_KEY)

# Setup the callback functions defined above.
client.on_connect    = connected
client.on_disconnect = disconnected
client.on_message    = message

# Connect to the Adafruit IO server.
client.connect()

# Start a message loop that blocks forever waiting for MQTT messages to be
# received.  Note there are other options for running the event loop like doing
# so in a background thread--see the mqtt_client.py example to learn more.
client.loop_blocking()

pump_listen.py

Python
Python3
I edited the digital_out.py code from: https://github.com/adafruit/io-client-python/blob/master/examples/basics/digital_out.py

"""
'digital_out.py'
===================================
Example of turning on and off a LED
from the Adafruit IO Python Client
Author(s): Brent Rubell, Todd Treece
"""
import time
import subprocess

# import Adafruit IO REST client.
from Adafruit_IO import Client, Feed, RequestError

ADAFRUIT_IO_USERNAME = "xxxxxxxx"
ADAFRUIT_IO_KEY = "xxxxxxx"

# Create an instance of the REST client.
aio = Client(ADAFRUIT_IO_USERNAME, ADAFRUIT_IO_KEY)

digital = aio.feeds('grouptest.switch')

while True:
    data = aio.receive(digital.key)
    if str(data.value) == "ON":
        print('received <- ON\n')
        command = "/root/start_pump.sh"
        process = subprocess.Popen(command.split(), stdout=subprocess.PIPE)
        output, error = process.communicate()
    elif str(data.value) == "OFF":
        print('received <- OFF\n')
        command = "/root/drain_off.sh"
        process = subprocess.Popen(command.split(), stdout=subprocess.PIPE)
        output, error = process.communicate()
    time.sleep(2)

drain_on.sh

SH
called by the pump_on.sh script inside the Screen session called 'drain'.

This script tells the relay add-on to turn on relay 0, it then sleeps for 1280 seconds (21.333 minutes). It calls the python script off.py which sends an MQTT message toggling the switch to OFF, stops turns off relay 0, deletes the temp file then kills the screen session named 'drain' (which is the session it's running in)
#!/bin/sh
/usr/sbin/relay-exp 0 on
sleep 1280
python3 /root/off.py
/usr/sbin/relay-exp 0 off
rm /tmp/foo.txt
screen -XS drain quit

off.py

Python
Python3
this just sends OFF to the switch
import time
import subprocess

from Adafruit_IO import Client, Feed, RequestError

ADAFRUIT_IO_USERNAME = "xxxxx"
ADAFRUIT_IO_KEY = "xxxx"


aio = Client(ADAFRUIT_IO_USERNAME, ADAFRUIT_IO_KEY)

digital = aio.feeds('grouptest.switch')

aio.send_data('grouptest.switch', 'OFF')

drain_off.sh

SH
This script is called by the pump_listen.py when it receives the message "OFF".
It checks for the temp file, if it's not there, it exits because that means pump isn't running.
If the temp file is there, it tells the relay add-on board to turn off relay 0, deletes the temp file, then kills the screen session that is running the drain_on.sh script, killing that script too.
#!/bin/sh
if [ ! -f /tmp/foo.txt ]; then
    exit
else
    relay-exp 0 off
    rm /tmp/foo.txt
    screen -XS drain quit
fi

UPDATED: digital_in.py

Python
I've updated all of the code to be more stable and "pythonic". Here's the digital_in.py script, this does the last 1% of everything the other updated script does't do. It is what runs in the screen session.
This script is taken directly from the Adafruit examples.
# Import standard python modules
import time
from Adafruit_IO import Client, Feed, RequestError

ADAFRUIT_IO_USERNAME = "xxxx"
ADAFRUIT_IO_KEY = "xxxx"

# Create an instance of the REST client.
aio = Client(ADAFRUIT_IO_USERNAME, ADAFRUIT_IO_KEY)

time.sleep(1200)   #Sleep for 20 minutes
aio.send_data('switch', 'OFF')   #Send the "off" message to the feed "switch" via MQTT, this then gets registered by the mqtt_subscribe.py and then starts those commands

UPDATED: Omega2+ Setup

SH
These are the packages you'll need on your Omega2+ to get everything running.

On the Omega2+, opkg is the same as apt for Ubuntu or yum for CentOS/RHEL

When running the pip3 install adafruit-io command, I got errors when started installing paho-mqtt, the error was: OSError: [Errno 12] Out of memory
But don't worry, run the command again and it will skip everything already installed, install paho-mqtt and complete successfully. No problem!! You'll see:

Requirement already satisfied (use --upgrade to upgrade): chardet<3.1.0,>=3.0.2 in /usr/lib/python3.6/site-packages (from requests->adafruit-io)
Requirement already satisfied (use --upgrade to upgrade): idna<2.9,>=2.5 in /usr/lib/python3.6/site-packages (from requests->adafruit-io)
Requirement already satisfied (use --upgrade to upgrade): urllib3<1.25,>=1.21.1 in /usr/lib/python3.6/site-packages (from requests->adafruit-io)
Requirement already satisfied (use --upgrade to upgrade): certifi>=2017.4.17 in /usr/lib/python3.6/site-packages (from requests->adafruit-io)
Installing collected packages: paho-mqtt, adafruit-io
Running setup.py install for paho-mqtt ... done
Running setup.py install for adafruit-io ... done
Successfully installed adafruit-io-2.0.19 paho-mqtt-1.4.0
opkg install python3-light python3-pip python3-setuptools python3-relay-exp git screen 
pip3 install adafruit-io 

Credits

Xenophod

Xenophod

2 projects • 4 followers
Just a sysadmin kind of guy. I'm always learning new things and I enjoy long scrolls through the command line.

Comments