Support library for reading the Kamstrup FlowIQ2000 water meter via its serial interface.
This library provides support for reading a Kamstrup FlowIQ2000 water meter's serial interface. Interfacing to the meter from the C027 board requires a small interface circuit (note that this diagram also includes a water pump control circuit, which is needed by the IotMessage library):

This library is intended for use with the WaterMeterDemo project.
Revision 0:0730cba56168, committed 2015-05-22
- Comitter:
- RobMeades
- Date:
- Fri May 22 11:40:35 2015 +0000
- Commit message:
- Initial commit of FlowIQ2000 water meter support to mbed cloud.
Changed in this revision
| FlowIq2100.cpp | Show annotated file Show diff for this revision Revisions of this file |
| WaterMeterApi.hpp | Show annotated file Show diff for this revision Revisions of this file |
--- /dev/null Thu Jan 01 00:00:00 1970 +0000
+++ b/FlowIq2100.cpp Fri May 22 11:40:35 2015 +0000
@@ -0,0 +1,633 @@
+/* FlowIQ Water Meter HW driver for Water Meter Demo
+ *
+ * Copyright (C) u-blox Melbourn Ltd
+ * u-blox Melbourn Ltd, Melbourn, UK
+ *
+ * All rights reserved.
+ *
+ * This source file is the sole property of u-blox Melbourn Ltd.
+ * Reproduction or utilization of this source in whole or part is
+ * forbidden without the written consent of u-blox Melbourn Ltd.
+ */
+
+/**
+ * @file flowiq_2100.cpp
+ * This file implements the water meter API to a Kamstrup
+ * FlowIQ 2100 water meter for the MWC demo 2015.
+ *
+ * The meter follows a single subset of the fixed length
+ * message format defined by the Sensus UI-1203R20 interace.
+ * The pwrClk line is first toggled from low to high
+ * a number of times to power up the meter.
+ * The meter indicates that it is powered up by pulling
+ * the data line low. What follows is a fixed length
+ * message, data bits being toggled out of the meter by
+ * sending the pwrClk line from low to high. Each byte
+ * is preceded by a low start bit (the first of which
+ * is the one triggered by the pwrClk operation), seven
+ * data bits, an even parity bit and a high stop bit.
+ * There are 25 bytes of message as follows:
+ *
+ * 0x56, 'V' Start message
+ * 0x3B, ';' Start field
+ * 0x52, 'R' Register field type
+ * 0x42, 'B' Start numeric data
+ * 0x30, '0' Volume: 020550019
+ * 0x32, '2'
+ * 0x30, '0'
+ * 0x35, '5'
+ * 0x35, '5'
+ * 0x30, '0'
+ * 0x30, '0'
+ * 0x31, '1'
+ * 0x39, '9'
+ * 0x3B, ';' Start field
+ * 0x49, 'I' Serial field type
+ * 0x42, 'B' Start numeric data
+ * 0x36, '6' Serial: 63268874
+ * 0x33, '3'
+ * 0x32, '2'
+ * 0x36, '6'
+ * 0x38, '8'
+ * 0x38, '8'
+ * 0x37, '7'
+ * 0x34, '4'
+ * 0x0D, 'CR' End Message
+ *
+ */
+
+#include <stdint.h>
+#include <stdio.h>
+#include <mbed.h>
+#ifdef TARGET_UBLOX_C027
+ #include "C027_api.h"
+#else
+ #error "This example is targeted for the C027N platform"
+#endif
+#include <math.h> // for pow()
+#include <WaterMeterApi.hpp>
+
+// ----------------------------------------------------------------
+// GENERAL COMPILE-TIME CONSTANTS
+// ----------------------------------------------------------------
+
+/// The parity the meter uses (if false then it's ODD).
+#define PARITY_IS_EVEN true
+
+/// The default clock rate for the meter in Hz
+#define METER_DEFAULT_CLOCK_RATE_HZ 1000
+
+/// The length of half a clock period in microseconds used when
+// powering up the meter.
+#define HALF_CLOCK_PERIOD_PWR_ON_US 1000
+
+/// The maximum number of clocks that are sent to power up the
+// meter.
+#define MAX_PWR_ON_CLOCKS 10
+
+/// The time for which pwr/clk must be kept low to effect
+// a reset of the interface.
+#define METER_RESET_PERIOD_US 200000
+
+/// The length of the fixed-length message from the meter.
+#define METER_FIXED_MSG_LEN 25
+
+/// The number of characters into the fixed length message that the
+// volume reading can be found.
+#define VOLUME_READING_OFFSET 4
+
+/// The number of characters in the volume reading.
+#define VOLUME_READING_LEN 9
+
+/// The number of characters into the fixed length message that the
+// serial number can be found.
+#define SERIAL_NUMBER_OFFSET 16
+
+/// The number of characters in the serial number.
+#define SERIAL_NUMBER_LEN 8
+
+// ----------------------------------------------------------------
+// PRIVATE VARIABLES
+// ----------------------------------------------------------------
+
+// ----------------------------------------------------------------
+// GENERIC PRIVATE FUNCTIONS
+// ----------------------------------------------------------------
+
+/// Toggle the pwrClk line to wake the meter up, which is
+// indicated by the data line going low, the start bit
+// (so this function swallows the first start bit).
+bool WaterMeterHandler::pwrOn (void)
+{
+ bool success = false;
+ uint32_t x;
+
+ if (ready)
+ {
+ // Start things up by pumping the power/clock line until
+ // the data line goes low or we run out of tries.
+ if (debug)
+ {
+ printf ("\nToggling clock at startup... ");
+ }
+
+ for (x = 0; (*pData == 1) && (x < MAX_PWR_ON_CLOCKS); x++)
+ {
+ *pPwrClk = 0;
+ wait_us (HALF_CLOCK_PERIOD_PWR_ON_US);
+ *pPwrClk = 1;
+ wait_us (HALF_CLOCK_PERIOD_PWR_ON_US);
+ }
+
+ if (x < MAX_PWR_ON_CLOCKS)
+ {
+ success = true;
+ if (debug)
+ {
+ printf (" success after %d toggle(s).\n", x);
+ }
+ }
+ else
+ {
+ if (debug)
+ {
+ printf (" failed.\n");
+ }
+ }
+ }
+ else
+ {
+ printf ("!!! pwrOn(): not initialised, call init() first.\n");
+ }
+
+ return success;
+}
+
+/// Power down the meter interface by setting
+// the pwrClk line low.
+void WaterMeterHandler::pwrOff (void)
+{
+ if (ready)
+ {
+ if (debug)
+ {
+ printf ("\nSetting pwrClk low for %d ms to power off meter interface.\n", METER_RESET_PERIOD_US / 1000);
+ }
+
+ *pPwrClk = 0;
+ }
+ wait_us (METER_RESET_PERIOD_US);
+}
+
+/// Read a start bit from the water meter (should be 0).
+bool WaterMeterHandler::readStartBit (void)
+{
+ bool success = false;
+
+ if (ready)
+ {
+ // Toggle power/clock to get the start bit.
+ *pPwrClk = 0;
+ wait_us (halfClkPeriodUs);
+ *pPwrClk = 1;
+ wait_us (halfClkPeriodUs);
+ if (!*pData)
+ {
+ success = true;
+ }
+ }
+ else
+ {
+ printf ("!!! readStartBit(): not initialised, call init() first.\n");
+ }
+
+ return success;
+}
+
+/// Read a stop bit from the water meter (should be 1).
+bool WaterMeterHandler::readStopBit (void)
+{
+ bool success = false;
+
+ if (ready)
+ {
+ // Toggle power/clock to get the stop bit.
+ *pPwrClk = 0;
+ wait_us (halfClkPeriodUs);
+ *pPwrClk = 1;
+ wait_us (halfClkPeriodUs);
+ if (*pData)
+ {
+ success = true;
+ }
+ }
+ else
+ {
+ printf ("!!! readStopBit(): not initialised, call init() first.\n");
+ }
+
+ return success;
+}
+
+/// Read a bit from the water meter by toggling
+// the pwrClk line low and then high.
+bool WaterMeterHandler::readDataBit (void)
+{
+ bool bitValue = false;
+
+ if (ready)
+ {
+ // Toggle power/clock to get a data bit.
+ *pPwrClk = 0;
+ wait_us (halfClkPeriodUs);
+ *pPwrClk = 1;
+ wait_us (halfClkPeriodUs);
+ if (*pData)
+ {
+ bitValue = true;
+ }
+ }
+ else
+ {
+ printf ("!!! readDataBit: not initialised, call init() first.\n");
+ }
+
+ return bitValue;
+}
+
+/// Check that parity is good.
+bool WaterMeterHandler::parityIsGood (uint8_t numOnes)
+{
+ bool success = false;
+
+#if PARITY_IS_EVEN
+ if ((numOnes & 0x01) == 0)
+ {
+ success = true;
+ }
+#else
+ if ((numOnes & 0x01) == 1)
+ {
+ success = true;
+ }
+#endif
+
+ return success;
+}
+
+/// Read a character from the water meter and check parity.
+bool WaterMeterHandler::readChar (char *pChar)
+{
+ bool success = false;
+ bool bitValue;
+ uint8_t numOnes = 0;
+ char dataByte = 0;
+ uint8_t x;
+
+ // Read the 7 data bits plus parity
+ for (x = 0; x < 8; x++)
+ {
+ bitValue = readDataBit();
+ if (bitValue)
+ {
+ numOnes++; // Keep track of this for parity
+ }
+ dataByte |= ((bitValue && 0x01) << x);
+ }
+
+ // Mask off the parity bit
+ dataByte &= 0x7F;
+
+if (debug)
+{
+ printf ("data 0x%.2x ", dataByte);
+
+ if ((dataByte & 0x7F) >= 0x20)
+ {
+ printf ("(%c) ", dataByte);
+ }
+ else
+ {
+ printf (" ");
+ }
+}
+
+ if (parityIsGood (numOnes))
+ {
+ if (debug)
+ {
+ printf ("(parity GOOD) ");
+ }
+
+ success = true;
+ if (pChar != NULL)
+ {
+ *pChar = dataByte;
+ }
+ }
+ else
+ {
+ if (debug)
+ {
+ printf ("(parity BAD) ");
+ }
+ }
+
+ return success;
+}
+
+/// Read a fixed length message from the water meter.
+uint32_t WaterMeterHandler::readFixedLengthMsg (char *pChars, uint32_t numChars)
+{
+ uint32_t x = 0;
+
+ // First, power the meter up until we get a start bit
+ if (pwrOn())
+ {
+ bool dataIsGood = true;
+ // Now read the data
+ for (x = 0; (x < numChars) && dataIsGood; x++)
+ {
+ if (debug)
+ {
+ printf ("Char: %.2d ", x + 1);
+ }
+
+ // Read a data byte with parity
+ dataIsGood = readChar (&(pChars[x]));
+ if (dataIsGood)
+ {
+ // If parity was good, read a stop bit
+ dataIsGood = readStopBit();
+ if (debug)
+ {
+ if (dataIsGood)
+ {
+ printf ("StopBit ");
+ }
+ else
+ {
+ printf (" ");
+ }
+ }
+
+ if (dataIsGood && (x < numChars - 1))
+ {
+ // If the stop bit was good and we aren't at
+ // the end, read the next start bit
+ dataIsGood = readStartBit();
+ if (debug)
+ {
+ if (dataIsGood)
+ {
+ printf ("StartBit \n");
+ }
+ else
+ {
+ printf (" \n");
+ }
+ }
+ }
+ }
+ }
+ }
+
+ // Power the meter off afterwards
+ pwrOff();
+
+ return x;
+}
+
+// Convert a series of ASCII numeric characters into a uint32_t value.
+bool WaterMeterHandler::decCharsToUint32 (char * pChars, uint32_t numChars, uint32_t * pValue)
+{
+ bool success = true;
+ double value = 0;
+
+ if ((numChars > 0) && (pChars != NULL))
+ {
+ double multiplier;
+ uint32_t x;
+
+ multiplier = pow ((double) 10, double (numChars - 1));
+
+ for (x = 0; (x < numChars) && success; x++)
+ {
+ if ((pChars[x] >= 0x30) && (pChars[x] <= 0x39))
+ {
+ value += (pChars[x] - 0x30) * multiplier;
+ multiplier /= 10;
+ if (multiplier == 0)
+ {
+ multiplier = 1;
+ }
+ }
+ else
+ {
+ success = false;
+ }
+ }
+ }
+ else
+ {
+ success = false;
+ }
+
+ if (success && (pValue != NULL))
+ {
+ if (value > 0xFFFFFFFF)
+ {
+ success = false;
+ if (debug)
+ {
+ printf ("Result is too big for an uint32_t (%f).\n", value);
+ }
+ }
+ else
+ {
+ *pValue = (uint32_t) int (value);
+ }
+ }
+
+ return success;
+}
+
+// ----------------------------------------------------------------
+// PUBLIC FUNCTIONS
+// ----------------------------------------------------------------
+
+/// Constructor
+WaterMeterHandler::WaterMeterHandler(void)
+{
+ ready = false;
+ debug = false;
+ pData = NULL;
+ pPwrClk = NULL;
+ halfClkPeriodUs = (1000000 / METER_DEFAULT_CLOCK_RATE_HZ) / 2;
+ if (debug)
+ {
+ printf ("WaterMeterHandler DEBUG prints are on.\n");
+ }
+}
+
+/// Destructor
+WaterMeterHandler::~WaterMeterHandler(void)
+{
+ ready = false;
+ pData = NULL;
+ pPwrClk = NULL;
+}
+
+/// Set debug output
+void WaterMeterHandler::setDebugOn (bool onNotOff)
+{
+ debug = false;
+
+ if (onNotOff)
+ {
+ debug = true;
+ }
+
+ if (debug)
+ {
+ printf ("\nWaterMeterHandler DEBUG prints are on.\n");
+ }
+ else
+ {
+ printf ("\nWaterMeterHandler DEBUG prints are off.\n");
+ }
+}
+
+/// Initialise the water meter and set alternative
+// values for the data/pwrClk pins and the clock
+// rate used when reading data. Then read a character
+// from the meter to ensure it's there.
+bool WaterMeterHandler::init (PinName dataPin, PinName pwrClkPin, uint32_t clkRateHz)
+{
+ // Set context data
+ pData = new DigitalIn (dataPin);
+ pPwrClk = new DigitalOut (pwrClkPin);
+
+ if (clkRateHz > 1000000)
+ {
+ clkRateHz = 1000000;
+ }
+ halfClkPeriodUs = (1000000 / clkRateHz) / 2;
+
+ ready = true;
+ // Power up the module and read a character.
+ if (pwrOn())
+ {
+ if (!readChar (NULL))
+ {
+ ready = false;
+ }
+ }
+
+ // Power it off again.
+ pwrOff();
+
+ return ready;
+}
+
+/// Take a water volume reading from the meter.
+bool WaterMeterHandler::readLitres (uint32_t *pValue)
+{
+ bool success = false;
+ char buffer[METER_FIXED_MSG_LEN];
+ uint32_t charsRead;
+
+ if (debug)
+ {
+ printf ("Reading volume from meter...\n");
+ }
+ charsRead = readFixedLengthMsg (buffer, sizeof (buffer));
+
+ if (debug)
+ {
+ printf ("\n%d byte(s) read of %d needed,\n", charsRead, VOLUME_READING_OFFSET + VOLUME_READING_LEN);
+ }
+
+ if (charsRead >= VOLUME_READING_OFFSET + VOLUME_READING_LEN)
+ {
+ if (debug)
+ {
+ printf ("Converting %.*s to ", VOLUME_READING_LEN, &(buffer[VOLUME_READING_OFFSET]));
+ }
+
+ success = decCharsToUint32 (&(buffer[VOLUME_READING_OFFSET]), VOLUME_READING_LEN, pValue);
+
+ if (debug)
+ {
+ if (success)
+ {
+ if (pValue != NULL)
+ {
+ printf ("%d.\n", *pValue);
+ }
+ else
+ {
+ printf ("[success].\n");
+ }
+ }
+ else
+ {
+ printf ("[conversion failed].\n");
+ }
+ }
+ }
+
+ return success;
+}
+
+/// Read the serial number from the meter.
+bool WaterMeterHandler::readSerialNumber (uint32_t *pValue)
+{
+ bool success = false;
+ char buffer[METER_FIXED_MSG_LEN];
+ uint32_t charsRead;
+
+ if (debug)
+ {
+ printf ("Reading serial number from meter...\n");
+ }
+ charsRead = readFixedLengthMsg (buffer, sizeof (buffer));
+
+ if (debug)
+ {
+ printf ("\n%d byte(s) read of %d needed,\n", charsRead, SERIAL_NUMBER_OFFSET + SERIAL_NUMBER_LEN);
+ }
+
+ if (charsRead >= SERIAL_NUMBER_OFFSET + SERIAL_NUMBER_LEN)
+ {
+ if (debug)
+ {
+ printf ("Converting %.*s to ", SERIAL_NUMBER_LEN, &(buffer[SERIAL_NUMBER_OFFSET]));
+ }
+
+ success = decCharsToUint32 (&(buffer[SERIAL_NUMBER_OFFSET]), SERIAL_NUMBER_LEN, pValue);
+
+ if (debug)
+ {
+ if (success)
+ {
+ if (pValue != NULL)
+ {
+ printf ("%d.\n", *pValue);
+ }
+ else
+ {
+ printf ("[success].\n");
+ }
+ }
+ else
+ {
+ printf ("[conversion failed].\n");
+ }
+ }
+ }
+
+ return success;
+}
+
+// End Of File
--- /dev/null Thu Jan 01 00:00:00 1970 +0000
+++ b/WaterMeterApi.hpp Fri May 22 11:40:35 2015 +0000
@@ -0,0 +1,166 @@
+/* Water Meter HW driver for Water Meter Demo
+ *
+ * Copyright (C) u-blox Melbourn Ltd
+ * u-blox Melbourn Ltd, Melbourn, UK
+ *
+ * All rights reserved.
+ *
+ * This source file is the sole property of u-blox Melbourn Ltd.
+ * Reproduction or utilization of this source in whole or part is
+ * forbidden without the written consent of u-blox Melbourn Ltd.
+ */
+
+#ifndef WATER_METER_API_HPP
+#define WATER_METER_API_HPP
+
+/**
+ * @file water_meter_api.h
+ * This file defines the API to the water meter HW
+ * for the MWC demo 2015.
+ */
+
+// ----------------------------------------------------------------
+// HARDWARE
+// The code in this library is setup to expect a FlowIQ2100 water
+// meter to be connected through this circuit:
+//
+// C027N board Water Meter
+//
+// 5V o-------------------o-------------o------o Red (pwr/clk)
+// | |
+// | |
+// | |
+// | |
+// | |
+// | |
+// | |
+// | | |
+// | | 10k Ohm |
+// | | |
+// | |
+// 1k Ohm o-------------|------o Black (gnd)
+// ____ / |
+// D4 o--|____|--------| (transistor) |
+// _\/ |
+// | | |
+// | | | 10k Ohm
+// | | |
+// | |
+// D2 o--------------------------------o------o Green (data)
+// |
+// |
+// Gnd o------------------o
+//
+// ----------------------------------------------------------------
+
+// ----------------------------------------------------------------
+// GENERAL COMPILE-TIME CONSTANTS
+// ----------------------------------------------------------------
+
+// ----------------------------------------------------------------
+// CLASSES
+// ----------------------------------------------------------------
+
+class WaterMeterHandler {
+public:
+ /// Constructor.
+ WaterMeterHandler (void);
+ /// Destructor.
+ ~WaterMeterHandler(void);
+ /// Initialise the water meter, ensuring that there
+ // is an electrical connection and that at least
+ // a single character can be correctly received from
+ // it (by checking parity).
+ // \param data pin to use for receiving data on the
+ // C027N board.
+ // \param pwrClk pin to use to send pwr/clk on the
+ // C027N board.
+ // \param clkRateHz the clock rate to use when
+ // reading out data bits.
+ // \return true if successful, otherwise false.
+ bool init (PinName dataPin = D2, PinName pwrClkPin = D4, uint32_t clkRateHz = 1000);
+
+ /// Take a water volume reading from the meter.
+ // \param a pointer to somewhere to put the reading.
+ // Only written-to if the outcome is true. May be NULL,
+ // in which case the success check is still carried out.
+ // \\return success if true, otherwise false.
+ bool readLitres (uint32_t * pValue);
+
+ /// Read the serial number from the meter.
+ // \param a pointer to somewhere to put the serial number.
+ // Only written-to if the outcome is true. May be NULL,
+ // in which case the success check is still carried out.
+ // \\return success if true, otherwise false.
+ bool readSerialNumber (uint32_t * pValue);
+
+ /// Set debug to bool.
+ // \param onNotOff debug will come out if this is true.
+ // Default is no debug.
+ void setDebugOn (bool onNotOff);
+
+private:
+ /// Set this to true once initialised.
+ bool ready;
+ /// Set this to true for debug.
+ bool debug;
+ /// The pin on the C027N board to use for data reception
+ // from the meter.
+ DigitalIn *pData;
+ /// The pin on the C027N board to use for pwr/clock to
+ // the meter.
+ DigitalOut *pPwrClk;
+ /// The length of half a clock period in microseconds to
+ // use when reading data off the water meter.
+ uint32_t halfClkPeriodUs;
+ /// Power on the meter (by toggling the pwrClk line until
+ // the data line goes low).
+ // \return true if successful, otherwise false.
+ bool pwrOn (void);
+ /// Power off the meter (by setting the pwrClk line low).
+ void pwrOff (void);
+ /// Read a start bit from the meter.
+ // \return true if successful, otherwise false.
+ bool readStartBit (void);
+ /// Read a stop bit from the meter.
+ // \return true if successful, otherwise false.
+ bool readStopBit (void);
+ /// Toggle the pwrClk line to read a data bit from the water
+ // meter.
+ // \return true if a 1 is read, false if a 0 is read.
+ bool readDataBit (void);
+ /// Check that parity is good
+ // \return true if successful, otherwise false.
+ bool parityIsGood (uint8_t numOnes);
+ /// Read a character from the meter and check parity.
+ // \param pChar a pointer to a location where the received
+ // character can be stored. Only filled in if the read is
+ // successful. May be NULL, in which case the parity check
+ // is still carried out.
+ // \return true if successful, otherwise false.
+ bool readChar (char *pChar);
+ /// Read a whole fixed-length message from the water meter.
+ // Reading stops when a parity error occurs or when the
+ // requested number of bytes has been read.
+ // \param pChars a pointer to a location where the received
+ // data bytes can be stored. Must be at least numBytes big.
+ // \return the number of bytes read.
+ // \param numChars the number of bytes to read.
+ uint32_t readFixedLengthMsg (char *pChars, uint32_t numChars);
+ /// Convert a number of characters representing a decimal
+ // value into a uint32_t value.
+ // Characters are assumed to be ASCII, single byte values.
+ // If a non-numeric character (including a decimal point)
+ // is found then fail is returned.
+ // \param pChars a pointer to the start of the character array.
+ // \param numChars the number of characters.
+ // \param pValue the uint32_t value, only written-to if
+ // success is returned. May be NULL in which case the success
+ // check is still carried out.
+ // \return true if successful, otherwise false.
+ bool decCharsToUint32 (char * pChars, uint32_t numChars, uint32_t * pValue);
+};
+
+#endif
+
+// End Of File