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.

FlowIq2100.cpp

Committer:
RobMeades
Date:
2015-05-22
Revision:
0:0730cba56168

File content as of revision 0:0730cba56168:

/* 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