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.

Revision:
0:0730cba56168
--- /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