Measuring Water Consumption

A project log for analog.io - A full stack IoT platform

A full stack project dedicated to easily collecting, analyzing and sharing IoT sensor data.

Just a very quick note on a sensor that I have up and running in my home. It was inspired by John Schuch's Remote Water Consumption project last year. I'm using his same concept with using the MAG3110 Digital compass to measure the spinning magnet in my homes water meter. Here's a picture with some data.

The basic operation of the device is that it measures the oscillating magnetic field inside the meter and counts the number of oscillations. The through a calibration cycle I calculated the number of oscillations per gallon and that is how water consumption is measured.

The technique could be used to monitor for leaks or in general just to help home owners understand what activities that they are doing which drive water usage.

The hardware setup uses one of these sensors that I sell on Tinde:

Here's the Electric Imp Device code:

// Define a class for a ring buffer object with some analysis functions
class ring_buffer {
_depth = 0
_index = 0
_data = 0
_buff = 0
_fill = 0

constructor(depth) {
_depth = depth
_data = array(depth,0)
_index = 0
}

function insert(value) {
if (_index < _depth)
{
_data[_index] = value
_index++
}
else
{
_data = value
_index = 1
}
if (_fill<=_depth){
_fill++
}
}

function z(relative_index){
if ((_index-1-relative_index)>=0)
{
return _data[_index-1-relative_index]
}
else
{
return _data[_index-1-relative_index+_depth]
}
}

function dump(){
local i = 0
local buff = array(_fill,0)

for (i=0;i<_fill;i++) {
buff[i] = z(i)
}
return buff
}

function stringify(){
local d = dump()
local string = ""
local i=0
for (i=0;i<d.len();i++) {
string += d[i]+" "
}
return string
}

function stats(d){
local min = 32767
local max = -32767
local i
for (i=0;i<d;i++) {
local t = z(i)
if (t>max){max=t}
if (t<min){min=t}
}
return [min,max,max-min]
}

function fill(){
return _fill
}

function rolled_over(){
return _fill>_depth
}

function reset(){
_index = 0
_fill = 0
}
}

// Define the Sensor Address
const ADDR = 0x1C;

// Define register map
const OUT_X_MSB = "\x01";
const CTRL_REG1 = "\x10";
const CTRL_REG2 = "\x11";

// Some useful bitmasks
const ACTIVE = "\x01";
const AUTO_MRST_EN = "\x80";

// Define the i2c periphrial being used
i2c <- hardware.i2c89;

// Interupt pin
int <- hardware.pin7;

// Config the i2c periph
i2c.configure(CLOCK_SPEED_400_KHZ);

// Set to auto reset int flag on read

// Activate the MAG3110, sample at 80Hz

// Read the initial data to clear the isr flag

// create a buffer for storing data
data_buffer <- ring_buffer(800);

// We're going to do some global min and max calculations
// So init the variables
min <- 32767
max <- -32767

// Initialize the threshriold
thresh <- 0

// Debouncing parameter
debounce <- 120

// Some bools for oneshots
x_lf <- false
x_hf <- false

// Counters for counting the number of triggers
trigs <- 0
x_trigs_lf <- 0
x_trigs_hf <- 0
k <- 0

x_int <- 0
x_int_neg <- 0

// Interupt routine for getting MAG3110 measurements
function mag_isr() {
// Check for the rising edge
{
// Read data from i2c

// Combine the MSB and LSB into a 16bit value
local x = data<<8 | data

// Convert 2's compliment
if (x & 0x8000) {x = -((~x & 0x7FFF) + 1);}

// Detect for min and max, then calculate a new threshold
if (x<min) {min = x; thresh = min + (max-min)/2}
if (x>max) {max = x; thresh = min + (max-min)/2}

// Put data in buffer
data_buffer.insert(x)

// Get the count spread from the last 20 items
local stats = data_buffer.stats(20)
local spread = stats

// Detect if measured value is above or below the threshold
if (x>thresh) {
// Logic for oneshot to count the crossing
if (!x_hf){
x_hf = true

// Increment the high frequency counter
x_trigs_hf++

}

// Integrate the measurement, this is for the debounce algorithim
x_int += (x-thresh)

// If integral exceeds the debounce parameter, time for the lf count
if (x_int>debounce) {
// Logic for oneshot to count the crossing
if (!x_lf) {
x_lf=true

// Increment the high frequency counter
x_trigs_lf++

// Reset the negative integral
x_int_neg = 0
}
}
}
else {
// Reset the hf oneshot
x_hf = false

// Integrate the measurement under the threshold
x_int_neg += (thresh-x)

// If integral exceeds the threshold, time to reset the lf oneshot
if (x_int_neg>debounce) {
x_lf = false

// Reset the positive integral
x_int = 0
}
}

//determine if buffer has reached the end
if (data_buffer.rolled_over())
{

k = x_trigs_hf
}
else {
k = x_trigs_lf
}

// Make sure min, max and threshold are stabilized before sending data
{
// buffer is at end, send the data back to the agent
agent.send("buffer",[data_buffer.dump(),[trigs,x_trigs_hf,x_trigs_lf,k,stats,stats,stats,max,min,thresh]])
}

// Reset the trigger counters
x_trigs_hf = 0
x_trigs_lf = 0
trigs = 0

//reset the buffer and start all over again
data_buffer.reset()
}
}
}

// Configure the interrupt pin to run the mag_isr callback
int.configure(DIGITAL_IN, mag_isr)
And the Agent Code:
// Create a data stream at data.sparkfun.com, then enter your keys here:
local publicKey = "dAb2YJaZkKhz4kV53qla"
local privateKey = "...................."

data_array <- array(800,[0,0,0,0])
name_array <- ["x","y","z"]

total <- 0
conversion <- 4.95/476

local req = http.get("https://data.sparkfun.com/output/"+publicKey+"/latest.json");
local response = req.sendsync()
local data = http.jsondecode(response.body)
total = data.total_gal.tointeger()
server.log(total)

function bufferRx(data) {
data_array <- data

local gpm = data_array.tofloat()*conversion*6.0
local total_gal = total.tofloat()*conversion
// Prepare the phant headers
local phantHeaders = {"Phant-Private-Key": privateKey, "connection": "close"};

// Create a post request
local req = http.post("http://data.sparkfun.com/input/"+publicKey, phantHeaders,"gpm="+gpm+"&total_gal="+total_gal);

total += data_array
//Send out the request!
server.log(gpm+" | "+total_gal+" | "+total+" | "+data_array+" | "+data_array+" | "+data_array+" | "+req.sendsync().statuscode);
}

device.on("buffer",bufferRx);

Discussions 