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