In the previous step we set up a Firebase database as a means of remote controlling our valves.
In this step we will combine the previous Firebase python script with the earlier Python script to control the TapHat(s).

Overview of all steps:
1. Preparing Raspberry Pi
2. Testing the TapHat / Pi connection – Arduino side
3. Testing the TapHat / Pi connection – Pi side
4. Connecting your Hardware
5. Configuring your TapHat and valves
6. Tap Control from the Internet
7. Finishing our Python Tap Control program

When programming, I like to learn to always work in smaller steps. First step is to be able to parse the JSON that you receive out of the Firebase database. I like more  flexibility than the pyrebase module. That is why we will start off by adding simplejson to our Python 3 arsenal.

pi@waterpi:~ $ sudo pip3 install simplejson

Make a copy of Showdatabase.py and name it Updatetaps.py (peek at step 6 if you forgot the linux command).

import pyrebase

#replace dbname with you dbname, watermylawn-c40c6 in my case

config = {
  "apiKey": "AIdiSyDElQzRPSXk1P3j1t5xjgkBNLP2KvziQzw",
  "authDomain": "dbname.firebaseapp.com", 
  "databaseURL": "https://dbname.firebaseio.com",
  "storageBucket": "dbname.appspot.com",
  "serviceAccount": "serviceAccountCredentials.json"
}

firebase = pyrebase.initialize_app(config)

auth = firebase.auth()
user = auth.sign_in_with_email_and_password('myemail@gmail.com', 'MySuperSecurePassword123')

db = firebase.database()

modules = db.child("").get()
jsonmodule = json.JSONEncoder().encode(modules.val())

json = json.loads(jsonmodule)

#print(json)  # the entire json from the Firebase db
for module in json["modules"]:
    print("Module: " + str(module["module"]))
    for tap in module["taps"]:
         print("Tap: " + str(tap["tap"]) + " state: " + str(tap["state"]))

If you run the Python program you should now see:

pi@waterpi:~ $ python3 Updatetaps.py
Module: 16
Tap: 1 state: 0
Tap: 3 state: 0
Module: 17
Tap: 1 state: 0
Tap: 2 state: 0

Now we essentially only need one more thing: connecting this code to the i2c code. And maybe some optimizations.

In step three we already saw how to connect to the Pi to the SAMD21 microcontroller of the Pi TapHat:

import smbus
import time
import struct

bus = smbus.SMBus(1)
address = 0x10

def toggleLed():
    bus.write_byte(address,0x3A)

while True:
    time.sleep(1)
    toggleLed()

No we combine the two scripts:

import pyrebase
import smbus
import time
import struct
    
#replace dbname with you dbname, watermylawn-c40c6 in my case

config = {
  "apiKey": "AIdiSyDElQzRPSXk1P3j1t5xjgkBNLP2KvziQzw",
  "authDomain": "dbname.firebaseapp.com", 
  "databaseURL": "https://dbname.firebaseio.com",
  "storageBucket": "dbname.appspot.com",
  "serviceAccount": "serviceAccountCredentials.json"
}

firebase = pyrebase.initialize_app(config)

auth = firebase.auth()
user = auth.sign_in_with_email_and_password('myemail@gmail.com', 'MySuperSecurePassword123')

db = firebase.database()

modules = db.child("").get()
jsonmodule = json.JSONEncoder().encode(modules.val())

json = json.loads(jsonmodule)

smbus = smbus.SMBus(1) # smbus / twi / i2c
address = 0x10  #default address
data = [0x30, 0x3A, 0x31, 0x3B] #default twi data message

def updateTapsFromFirebase():
    for module in json["modules"]:
        print("Module: " + str(module["module"]))
        for tap in module["taps"]:
            print("Tap: " + str(tap["tap"]) + " state: " + str(tap["state"]))))
            data[0] = tap["tap"]
            data[2] = tap["state"]
            bus.write_i2c_block_data(address, 0, data)
        
while True:
    time.sleep(10)
    updateTapsFromFirebase()
    

We have also rewritten part of the i2c code so that we can send four bytes at once. The four bytes adhere to our protocol from step 5.

While this code should work, there are still some issues:
a. Closing and opening taps requires some time.
b. We do not want to draw too much current by operating multiple valves at the same time.
c. It is a naive implementation: it will open valves that are already open, close valves that are already closed, etc.

How can we optimize:
– Only send an updateTapsFromFirebase() if something really changed (Python level).
– Keep track of taps internal states and ignore new tap states for taps that are already set in the particular state (Arduino level).
– Maybe in future implement a better queue system at the Arduino to schedule more tap events.

A better program

I don’t really want to keep polling the database but want to receive an update when something changes.

The pyrebase library actually has a neat way of doing so that makes our lives a lot easier:

def stream_handler(message):
    path = message['path']
    data = message['data']
    event = message['event']
    if event == 'put':
         if len(path) == 1:
             print("Data update: " + str(data))
         else:
             #print("Tap update: " + str(path) + " state to: " + str(data))
             _path = path.split('/')
             #print("Path items: " + str(len(_path)))
             modnum = db.child("/modules/" + _path[2] + "/module").shallow().get()
             tapnum = db.child("/modules/" + _path[2] + "/taps/" + _path[4] + "/tap").shallow().get()
             print("Module: " + str(modnum.val()))
             print("Tap: " + str(tapnum.val()))
             print("State: " + str(data))

my_stream = db.child("").stream(stream_handler)

We listen in on any changes to the entire firebase. If a change occurs we then parse it (at least if we receive a put event). When working out the final code I ran into one small problem: we should send bytes as ASCII values to the TapHat module. I fixed the issue by adding 48 (the decimal number for ‘0’) to both the tap and state numbers.

This is the final code for now, I tested it and it works for me. Changing the state in the database will send a open or close message to the TapHat SAMD21 and sets the particular tap in motion:

db = firebase.database()

modules = db.child("").get()
smbus = smbus.SMBus(1) # smbus / twi / i2c
address = 0x10  #default address
data = [0x31, 0x3A, 0x31, 0x3B] #default twi data message

def updateTap(module, tap, state):
    ascii_0 = 48
    data[0] = ascii_0 + tap
    data[2] = ascii_0 + state
    modaddr = module
    smbus.write_i2c_block_data(modaddr, 0, data)
    
def stream_handler(message):
    path = message['path']
    data = message['data']
    event = message['event']
    if event == 'put':
         if len(path) == 1:
             print("Data update: " + str(data))
         else:
             _path = path.split('/')
             modnum = db.child("/modules/" + _path[2] + "/module").shallow().get()
             tapnum = db.child("/modules/" + _path[2] + "/taps/" + _path[4] + "/tap").shallow().get()
             print("Module: " + str(modnum.val()))
             print("Tap: " + str(tapnum.val()))
             print("State: " + str(data))
             updateTap(int(modnum.val()), int(str(tapnum.val())), int(str(data)))

firetapstream = db.child("").stream(stream_handler)

Note there is one issue: if the module can’t be found on the SMBus (i2c / TWI), the python script will throw an exception (OSError: [Errno 121] Remote I/O error).

You could make it a little more robust to change the smbus.write_i2c line into:

try:
    smbus.write_i2c_block_data(modaddr, 0, data)
except OSError:
    print("Module " + str(modaddr) + " not found!")

That should do the job.

Update:
After writing my iOS App for controlling the taps from my phone I noticed an issue though: updating code in iOS apparently uses “patch” probably depending on how one does handle it in iOS. I changed the code a but so that it now also accepts patch messages. The iOS module sends a “{‘state’ : 0}” or 1 to the Firebase db, that is shown in Python in “data”. I chose to manually parse it although I could have also chosen to use the JSON approach which is preferable from a coding perspective I think.

def stream_handler(message):
    path = message['path']
    data = message['data']
    event = message['event']
    print(event)
    if ((event == 'put') or (event == 'patch')):
         if len(path) == 1:
             print("Data update: " + str(data))
         else:
             _path = path.split('/')
             modnum = db.child("/modules/" + _path[2] + "/module").shallow().get()
             tapnum = db.child("/modules/" + _path[2] + "/taps/" + _path[4] + "/tap").shallow().get()
             print("Module: " + str(modnum.val()))
             print("Tap: " + str(tapnum.val()))
             # it appears that 'put' data only shows the value but patch shows the entire patch as data
             data = str(data)
             if (data.find(": ") > 0):
                 data = data.replace("{", "")
                 data = data.replace("}", "")
                 data = data.replace(" ", "")
                 data = data.split(":")
                 if (len(data) == 2):
                      data = data[1]
             print("State: " + str(data))

             updateTap(int(modnum.val()), int(str(tapnum.val())), int(data))

firetapstream = db.child("").stream(stream_handler)

For now, that’s all folks! Happy watering your lawn or filling your pond.

Source code:
GitHub for all the examples can be found here: TapHat