Water Meter

Z-Uno Water Meter count ticks for both hot and cold water from water meters via dry contacts output.
  • Z-Uno board
  • Breadboard
  • 2 water meters with dry contacts output
// This is a simple 2 channels meter example

#include "EEPROM.h"

// channel number
#define ZUNO_CHANNEL_NUMBER_COLD   1
#define ZUNO_CHANNEL_NUMBER_HOT    2

#define PIN_COLD    0
#define PIN_HOT     1

#define PIN_PULSE   13

#define EEPROM_ADDR             0x800
#define EEPROM_UPDATE_INTERVAL  120000

#define TICK_VALUE              10 // in liters

struct meter_data {
    unsigned long ticks_cold;
    unsigned long ticks_hot;
    byte          crc8;
};

meter_data my_meter_data;

unsigned long last_update_millis = 0;
byte data_updated  = FALSE;

#define DEBOUNCE_COUNT  3
byte cold_triggered = 0;
byte cold_debounce = DEBOUNCE_COUNT;
byte hot_triggered = 0;
byte hot_debounce = DEBOUNCE_COUNT;

ZUNO_SETUP_CHANNELS(ZUNO_METER(ZUNO_METER_TYPE_WATER, METER_RESET_ENABLE, ZUNO_METER_WATER_SCALE_METERS3, METER_SIZE_FOUR_BYTES, METER_PRECISION_THREE_DECIMALS, getterCold, resetterCold),
                    ZUNO_METER(ZUNO_METER_TYPE_WATER, METER_RESET_ENABLE, ZUNO_METER_WATER_SCALE_METERS3, METER_SIZE_FOUR_BYTES, METER_PRECISION_THREE_DECIMALS, getterHot, resetterHot));

byte my_crc8(byte * data, byte count) {
    byte result = 0xDF;

    while(count--) {
        result ^= *data;
        data++;
    }
    return result;
}

void update_meter_data() { 
    my_meter_data.crc8 = my_crc8((byte*)&my_meter_data, sizeof(meter_data) - 1);
    EEPROM.put(EEPROM_ADDR, &my_meter_data, sizeof(meter_data));
}

void setup() {
  // Dry contacts of meters connect to these pins
  pinMode(PIN_COLD, INPUT_PULLUP);
  pinMode(PIN_HOT, INPUT_PULLUP);

  // Get last meter values from EEPROM
  EEPROM.get(EEPROM_ADDR,  &my_meter_data, sizeof(meter_data));

  // Check data
  if (my_crc8((byte*)&my_meter_data, sizeof(meter_data) - 1) != my_meter_data.crc8) {
    // Invalid data - reset all
    my_meter_data.ticks_cold = 0;
    my_meter_data.ticks_hot  = 0;
    update_meter_data();
  }
}

void loop() {
  if (!digitalRead(PIN_COLD)) {
    if (!cold_triggered) {
      cold_debounce--;
      if (!cold_debounce) {
        my_meter_data.ticks_cold++;
        data_updated = true;
        cold_triggered = true;
        zunoSendReport(ZUNO_CHANNEL_NUMBER_COLD); 
      }
    }
  } else {
    cold_debounce = DEBOUNCE_COUNT;
    cold_triggered = false;
  }
    
  if (!digitalRead(PIN_HOT)) {
    if (!hot_triggered) {
      hot_debounce--;
      if (!hot_debounce) {
        my_meter_data.ticks_hot++;
        data_updated = true;
        hot_triggered = true;
        zunoSendReport(ZUNO_CHANNEL_NUMBER_HOT); 
      }
    }
  } else {
    hot_debounce = DEBOUNCE_COUNT;
    hot_triggered = false;
  }

  // To save EEPROM from a lot of r/w operation 
  // write it once in EEPROM_UPDATE_INTERVAL if data was updated
  if (data_updated && (millis() - last_update_millis) > EEPROM_UPDATE_INTERVAL) {
      update_meter_data();
      data_updated = false;
      last_update_millis =  millis();
  }
  delay(100);   
}

void resetterCold(byte v) {
  my_meter_data.ticks_cold = 0;
  update_meter_data();
}

void resetterHot(byte v) {
  my_meter_data.ticks_hot  = 0;
  update_meter_data();
}

unsigned long getterCold(void) {
  return my_meter_data.ticks_cold * TICK_VALUE;
}
unsigned long getterHot(void) {
  return my_meter_data.ticks_hot * TICK_VALUE;
}
Download this sketch
// This is a simple 2 channels meter example

#include "EEPROM.h"

// channel number
#define ZUNO_CHANNEL_NUMBER_COLD   1
#define ZUNO_CHANNEL_NUMBER_HOT    2

#define PIN_COLD    0
#define PIN_HOT     1

#define PIN_PULSE   13

#define EEPROM_ADDR             0x800
#define EEPROM_UPDATE_INTERVAL  120000

#define TICK_VALUE              10 // in liters

struct meter_data {
    unsigned long ticks_cold;
    unsigned long ticks_hot;
    byte          crc8;
} __attribute__((aligned(1),packed));

meter_data my_meter_data;

unsigned long last_update_millis = 0;
byte data_updated  = FALSE;

#define DEBOUNCE_COUNT  3
byte cold_triggered = 0;
byte cold_debounce = DEBOUNCE_COUNT;
byte hot_triggered = 0;
byte hot_debounce = DEBOUNCE_COUNT;

ZUNO_SETUP_CHANNELS(ZUNO_METER(ZUNO_METER_TYPE_WATER, METER_RESET_ENABLE, ZUNO_METER_WATER_SCALE_METERS3, METER_SIZE_FOUR_BYTES, METER_PRECISION_THREE_DECIMALS, getterCold, resetterCold),
                    ZUNO_METER(ZUNO_METER_TYPE_WATER, METER_RESET_ENABLE, ZUNO_METER_WATER_SCALE_METERS3, METER_SIZE_FOUR_BYTES, METER_PRECISION_THREE_DECIMALS, getterHot, resetterHot));
// ZUNO_ENABLE(LOGGING_DBG);

byte my_crc8(byte * data, byte count) {
    byte result = 0xDF;

    while(count--) {
        result ^= *data;
        data++;
    }
    return result;
}

void update_meter_data() { 
    my_meter_data.crc8 = my_crc8((byte*)&my_meter_data, sizeof(meter_data) - 1);
    EEPROM.put(EEPROM_ADDR, &my_meter_data, sizeof(meter_data));
}

void setup() {
  // Dry contacts of meters connect to these pins
  pinMode(PIN_COLD, INPUT_PULLUP);
  pinMode(PIN_HOT, INPUT_PULLUP);

  // Get last meter values from EEPROM
  EEPROM.get(EEPROM_ADDR,  &my_meter_data, sizeof(meter_data));

  // Check data
  if (my_crc8((byte*)&my_meter_data, sizeof(meter_data) - 1) != my_meter_data.crc8) {
    // Invalid data - reset all
    // Serial0.println("***INVALIDCRC***");
    my_meter_data.ticks_cold = 0;
    my_meter_data.ticks_hot  = 0;
    update_meter_data();
  }
}

void loop() {
  if (!digitalRead(PIN_COLD)) {
    if (!cold_triggered) {
      cold_debounce--;
      if (!cold_debounce) {
        my_meter_data.ticks_cold++;
        data_updated = true;
        cold_triggered = true;
        zunoSendReport(ZUNO_CHANNEL_NUMBER_COLD); 
      }
    }
  } else {
    cold_debounce = DEBOUNCE_COUNT;
    cold_triggered = false;
  }
    
  if (!digitalRead(PIN_HOT)) {
    if (!hot_triggered) {
      hot_debounce--;
      if (!hot_debounce) {
        my_meter_data.ticks_hot++;
        data_updated = true;
        hot_triggered = true;
        zunoSendReport(ZUNO_CHANNEL_NUMBER_HOT); 
      }
    }
  } else {
    hot_debounce = DEBOUNCE_COUNT;
    hot_triggered = false;
  }

  // To save EEPROM from a lot of r/w operation 
  // write it once in EEPROM_UPDATE_INTERVAL if data was updated
  if (data_updated && (millis() - last_update_millis) > EEPROM_UPDATE_INTERVAL) {
      update_meter_data();
      data_updated = false;
      last_update_millis =  millis();
  }
  delay(100);   
}

void resetterCold(byte v) {
  my_meter_data.ticks_cold = 0;
  update_meter_data();
}

void resetterHot(byte v) {
  my_meter_data.ticks_hot  = 0;
  update_meter_data();
}

unsigned long getterCold(void) {
  return my_meter_data.ticks_cold * TICK_VALUE;
}
unsigned long getterHot(void) {
  return my_meter_data.ticks_hot * TICK_VALUE;
}
Download this sketch