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):

/media/uploads/RobMeades/c027n_interface.png

This library is intended for use with the WaterMeterDemo project.

Files at this revision

API Documentation at this revision

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
diff -r 000000000000 -r 0730cba56168 FlowIq2100.cpp
--- /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
diff -r 000000000000 -r 0730cba56168 WaterMeterApi.hpp
--- /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