Status update on water meter project

- testdetect24vac-mcp23017-a3700.png (53.54 KiB) Viewed 5009 times
This post started with a project I have where I would like to determine
which of 16 possible home irrigation lines are running. I am in Northern
California and it does rain here but only from October to March and rarely
the rest of the year. From March to October, we use water that comes from
the snow pack in the Sierra's. The Lake Tahoe area is a good ski area and
provides our water for the following year. Because some years do not get
good snowfall, we must always be careful to use what we need but be as
frugal as we can when we use it. My idea has been to us a Smart
Irrigation Controller (I have a Rachio). It can check the weather via the
internet and has a rain detector to skip cycles.
What it does not do is to tell me the volume of water actually dispensed
through each irrigation valve. If cute but pesky squirrels have chewed a
hole in a line or we have some other kind of leak, we may use too much
water. Similarly, we might have a clogged line or broken solenoid and not
be dispensing enough water.
I plan to add a water meter with a flow sensor that will close a circuit each time a unit of water passes through it. Monitoring that, I can know the total amount of water used by the system. Using the circuit I am working on (once it works properly, I can calculate when each value goes off and on and thereby attribute the water used to the line that was on. Only one line is on a time because of the water pressure.
Included in this post is the current state of my circuit. At this time, it only implements 1 24vac input through a single HCPL3700 (A3700). Thanks to the help of tlfong01 and Brandon92, I think we are getting close here. Their posts have provided a lot of insight and suggestions that has help me tremendously. (Thank you guys). It has been entertaining as well.
In particular, I might not have used the MCP23017 had it not been for their posts. This item greatly simplifies the circuit and the code to handle it as well.
One of my concerns is to make sure we do not exceed the power available on the Raspberry pi. It might even be nice if we can implement all of this on a raspberry pi zero-w.
Code: Select all
#!/usr/bin/env python3
import smbus
import sys
import RPi.GPIO as RPIGPIO
import time
import traceback
from datetime import datetime
from datetime import timedelta
bus = None
starttime = None
state = None
GPIOINT = 22 # gpio connected to port A interrupt line.
RELAY = 23 # relay to turn on 24vac
DEVICE_ADDRESS = 0x20
# default addresses
BANKA = 0
BANKB = 1
IODIR = 0x00 # Pin direction register A
IPOL = 0x02 # Input polarity regsiter A
GPENTEN = 0x04 # Interrupt on change pins A
IOCON = 0x0a # Config register A Bank, Mirror, SEQOP, DISSLW, HAEN, ODR, TPOL, N/U
GPPU = 0x0c # 100k ohm pullups A
GPIO = 0x12 # GPIO pins
INTF = 0x0e # interupt flag register
def readREG(port, intbits = 0xFFFF):
# need to separate bus reads because we don't want to clear an interupt we aren't handling
# intbits will be non-zero in top 8 and/or bottom 8
pinAdata = 0
pinBdata = 0
if (intbits >> 8) & 0xFF:
pinBdata = bus.read_byte_data(DEVICE_ADDRESS, port + BANKB)
if intbits & 0xFF:
pinAdata = bus.read_byte_data(DEVICE_ADDRESS, port + BANKA)
return (pinBdata << 8) | pinAdata
def writeREG(port, data):
bus.write_byte_data(DEVICE_ADDRESS, port + BANKB, (data >> 8) & 0xFF)
bus.write_byte_data(DEVICE_ADDRESS, port + BANKA, data & 0xFF)
return None
def my_callback(channel):
# Note: Multiple interupts can occur simultaneously
global bus
gpiolevel = RPIGPIO.input(GPIOINT)
intfBA = readREG(INTF)
if gpiolevel: # if port 25 == 1
print("level up clear interrupt")
pinBAbits = readREG(GPIO, intfBA)
print("----------level up infBA= 0x%04x, pinBA= 0x%04x" %(intfBA, pinBAbits))
else: # if port 25 != 1
print("level dn clear interrupt")
pinBAbits = readREG(GPIO, intfBA)
print("----------level dn infBA= 0x%04x, pinBA= 0x%04x" %(intfBA, pinBAbits))
return
def init():
global bus
bus = smbus.SMBus(1) # 0 = /dev/i2c-0 (port I2C0), 1 = /dev/i2c-1 (port I2C1)
# port A & B all in excpet for low order 3
writeREG(IODIR, 0xFFF8)
# reverse input values. This is because A3700 is low when upper threshold is detected
writeREG(IPOL, 0xFFF8)
# Activate all internal pullup resistors at Port A & B for output:
writeREG(GPPU, 0xFFF8)
# disable interupts on change
writeREG(GPENTEN, 0x0000)
# clear any previous interrupts
pinBAbits = readREG(GPIO)
# enable interupts on change
writeREG(GPENTEN, 0xFFF8)
# I/O Expander config register. Set INT pins internally connected, Interupt polarity Active-high
writeREG(IOCON, 0x4242)
time.sleep(.0100)
RPIGPIO.setwarnings(False)
RPIGPIO.setmode(RPIGPIO.BCM)
RPIGPIO.setup(GPIOINT, RPIGPIO.IN) # set interrupt from MCP23017
RPIGPIO.setup(RELAY, RPIGPIO.OUT) # set GPIO24 as output
RPIGPIO.output(RELAY, False)
# when a changing edge is detected on OPTO1 port, regardless of whatever
# else is happening in the program, the function my_callback will be run
RPIGPIO.add_event_detect(GPIOINT, RPIGPIO.RISING, callback=my_callback)
# RPIGPIO.add_event_detect(GPIOINT, RPIGPIO.BOTH, callback=my_callback)
# RPIGPIO.add_event_detect(GPIOINT, RPIGPIO.FALLING, callback=my_callback)
pass
def main():
global bus
global starttime
global state # 0 = not started, 1 = on, 2 = off, 3= zero reached
voltimelist = []
sleeptime = 0.005 # 5ms
print("start=========================================================")
init()
starttime = datetime.utcnow()
# turn on lights
print ("---------- 0x07 lights on")
writeREG(GPIO, 0x0007)
pinData = readREG(GPIO)
print ("0x07 lights on 0x%04x"%pinData)
time.sleep(2)
print ("----------- 0x07 lights off")
writeREG(GPIO, 0x0000)
pinData = readREG(GPIO)
print ("lights off 0x%04x"%pinData)
starttime = datetime.utcnow()
measuretime = datetime.utcnow() - starttime
# voltimelist.append([datetime.utcnow(), -1, state, GPIO.input(OPTO1), measuretime])
print ("----------- Relay on")
RPIGPIO.output(RELAY, True)
state = 1
print("ON, state = " + str(state))
while state < 3:
elapsedtime = datetime.utcnow() - starttime
if elapsedtime.seconds > 4 and state != 2:
print ("----------- Relay off")
RPIGPIO.output(RELAY, False)
state = 2
print("OFF, state = " + str(state))
if elapsedtime.seconds > 5:
print("TIMEOUT, state = " + str(state))
state = 3
time.sleep(sleeptime)
# if elapsedtime.seconds > 4:
# print("TIMEOUT, state = " + str(state))
# break
pass
# while (True):
# pin = RPIGPIO.input(GPIOINT)
# print( "pin = %i"%pin)
# time.sleep(1)
#
pass
if __name__ =='__main__':
try:
main()
except KeyboardInterrupt:
pass
except:
traceback.print_exc()
pass
RPIGPIO.cleanup()
print ("Done")
This code does implements interrupt driven handling of the circuits. It is only demo code at this point and there is no logic to actually do anything except print out that a 24vac circuit came on and then shut off.
It uses plain ol' GPIO code and plain ol' smbus. Other libraries are available but these work pretty nicely. Interrupts are implemented by writing the MCP23017 INTA output to GPIO-22 on the raspberry pi. The input GPA and GPB pins on the MCP23017 are reversed in polarity to make it a little more natural to deal with the fact that the A3700 is high when the 24vac is off. This lets us see a "1" on a GPA/B port when the 24vac is on and "0" when off. By setting a callback on a RISING GPIO-22, we can get the interrupt as it occurs.
I also set the MCP23017 to OR the interrupt A and B together so that a change on any input on Bank A or B would result in a change in INT A connection.
Lastly, I reversed the "active" state of the of the INTA line so that it would RISE when the interrupt is to occur and FALL when the interrupt is cleared.
Hopefully, this all becomes more clear when looking at the code and the MCP23017 datasheet. Note that are both I2C versions and SPI versions and I am using the I2C version.
More to follow ---