//------------------------------------------------------------------------------
// Copyright (C) 2010-2011, Robert Johansson and contributors, Raditex AB
// All rights reserved.
//
// rSCADA 
// http://www.rSCADA.se
// info@rscada.se
//
// Contributors:
// Large parts of this file was contributed by Tomas Menzl.
//
//------------------------------------------------------------------------------

#include "mbus-protocol-aux.h"

#include <stdio.h>
#include <string.h>

#define MBUS_ERROR(...) fprintf (stderr, __VA_ARGS__)

#ifdef _DEBUG_
#define MBUS_DEBUG(...) fprintf (stderr, __VA_ARGS__)
#else
#define MBUS_DEBUG(...)
#endif

static int debug = 1;

typedef struct _mbus_variable_vif {
    unsigned     vif;
    double       exponent;
    const char * unit;
    const char * quantity;
} mbus_variable_vif;

mbus_variable_vif vif_table[] = {
/*  Primary VIFs (main table), range 0x00 - 0xFF */

    /*  E000 0nnn    Energy Wh (0.001Wh to 10000Wh) */
    { 0x00, 1.0e-3, "Wh", "Energy" },
    { 0x01, 1.0e-2, "Wh", "Energy" },
    { 0x02, 1.0e-1, "Wh", "Energy" },
    { 0x03, 1.0e0,  "Wh", "Energy" },
    { 0x04, 1.0e1,  "Wh", "Energy" },
    { 0x05, 1.0e2,  "Wh", "Energy" },
    { 0x06, 1.0e3,  "Wh", "Energy" },
    { 0x07, 1.0e4,  "Wh", "Energy" },

    /* E000 1nnn    Energy  J (0.001kJ to 10000kJ) */
    { 0x08, 1.0e0, "J", "Energy" },
    { 0x09, 1.0e1, "J", "Energy" },
    { 0x0A, 1.0e2, "J", "Energy" },
    { 0x0B, 1.0e3, "J", "Energy" },
    { 0x0C, 1.0e4, "J", "Energy" },
    { 0x0D, 1.0e5, "J", "Energy" },
    { 0x0E, 1.0e6, "J", "Energy" },
    { 0x0F, 1.0e7, "J", "Energy" },

    /* E001 0nnn    Volume m^3 (0.001l to 10000l) */
    { 0x10, 1.0e-6, "m^3", "Volume" },
    { 0x11, 1.0e-5, "m^3", "Volume" },
    { 0x12, 1.0e-4, "m^3", "Volume" },
    { 0x13, 1.0e-3, "m^3", "Volume" },
    { 0x14, 1.0e-2, "m^3", "Volume" },
    { 0x15, 1.0e-1, "m^3", "Volume" },
    { 0x16, 1.0e0,  "m^3", "Volume" },
    { 0x17, 1.0e1,  "m^3", "Volume" },

    /* E001 1nnn    Mass kg (0.001kg to 10000kg) */
    { 0x18, 1.0e-3, "kg", "Mass" },
    { 0x19, 1.0e-2, "kg", "Mass" },
    { 0x1A, 1.0e-1, "kg", "Mass" },
    { 0x1B, 1.0e0,  "kg", "Mass" },
    { 0x1C, 1.0e1,  "kg", "Mass" },
    { 0x1D, 1.0e2,  "kg", "Mass" },
    { 0x1E, 1.0e3,  "kg", "Mass" },
    { 0x1F, 1.0e4,  "kg", "Mass" },

    /* E010 00nn    On Time s */
    { 0x20,     1.0, "s", "On time" },  /* seconds */
    { 0x21,    60.0, "s", "On time" },  /* minutes */
    { 0x22,  3600.0, "s", "On time" },  /* hours   */
    { 0x23, 86400.0, "s", "On time" },  /* days    */

    /* E010 01nn    Operating Time s */
    { 0x24,     1.0, "s", "Operating time" },  /* seconds */
    { 0x25,    60.0, "s", "Operating time" },  /* minutes */
    { 0x26,  3600.0, "s", "Operating time" },  /* hours   */
    { 0x27, 86400.0, "s", "Operating time" },  /* days    */

    /* E010 1nnn    Power W (0.001W to 10000W) */
    { 0x28, 1.0e-3, "W", "Power" },
    { 0x29, 1.0e-2, "W", "Power" },
    { 0x2A, 1.0e-1, "W", "Power" },
    { 0x2B, 1.0e0,  "W", "Power" },
    { 0x2C, 1.0e1,  "W", "Power" },
    { 0x2D, 1.0e2,  "W", "Power" },
    { 0x2E, 1.0e3,  "W", "Power" },
    { 0x2F, 1.0e4,  "W", "Power" },

    /* E011 0nnn    Power J/h (0.001kJ/h to 10000kJ/h) */
    { 0x30, 1.0e0, "J/h", "Power" },
    { 0x31, 1.0e1, "J/h", "Power" },
    { 0x32, 1.0e2, "J/h", "Power" },
    { 0x33, 1.0e3, "J/h", "Power" },
    { 0x34, 1.0e4, "J/h", "Power" },
    { 0x35, 1.0e5, "J/h", "Power" },
    { 0x36, 1.0e6, "J/h", "Power" },
    { 0x37, 1.0e7, "J/h", "Power" },

    /* E011 1nnn    Volume Flow m3/h (0.001l/h to 10000l/h) */
    { 0x38, 1.0e-6, "m^3/h", "Volume flow" },
    { 0x39, 1.0e-5, "m^3/h", "Volume flow" },
    { 0x3A, 1.0e-4, "m^3/h", "Volume flow" },
    { 0x3B, 1.0e-3, "m^3/h", "Volume flow" },
    { 0x3C, 1.0e-2, "m^3/h", "Volume flow" },
    { 0x3D, 1.0e-1, "m^3/h", "Volume flow" },
    { 0x3E, 1.0e0,  "m^3/h", "Volume flow" },
    { 0x3F, 1.0e1,  "m^3/h", "Volume flow" },

    /* E100 0nnn     Volume Flow ext.  m^3/min (0.0001l/min to 1000l/min) */
    { 0x40, 1.0e-7, "m^3/min", "Volume flow" },
    { 0x41, 1.0e-6, "m^3/min", "Volume flow" },
    { 0x42, 1.0e-5, "m^3/min", "Volume flow" },
    { 0x43, 1.0e-4, "m^3/min", "Volume flow" },
    { 0x44, 1.0e-3, "m^3/min", "Volume flow" },
    { 0x45, 1.0e-2, "m^3/min", "Volume flow" },
    { 0x46, 1.0e-1, "m^3/min", "Volume flow" },
    { 0x47, 1.0e0,  "m^3/min", "Volume flow" },

    /* E100 1nnn     Volume Flow ext.  m^3/s (0.001ml/s to 10000ml/s) */
    { 0x48, 1.0e-9, "m^3/s", "Volume flow" },
    { 0x49, 1.0e-8, "m^3/s", "Volume flow" },
    { 0x4A, 1.0e-7, "m^3/s", "Volume flow" },
    { 0x4B, 1.0e-6, "m^3/s", "Volume flow" },
    { 0x4C, 1.0e-5, "m^3/s", "Volume flow" },
    { 0x4D, 1.0e-4, "m^3/s", "Volume flow" },
    { 0x4E, 1.0e-3, "m^3/s", "Volume flow" },
    { 0x4F, 1.0e-2, "m^3/s", "Volume flow" },

    /* E101 0nnn     Mass flow kg/h (0.001kg/h to 10000kg/h) */
    { 0x50, 1.0e-3, "kg/h", "Mass flow" },
    { 0x51, 1.0e-2, "kg/h", "Mass flow" },
    { 0x52, 1.0e-1, "kg/h", "Mass flow" },
    { 0x53, 1.0e0,  "kg/h", "Mass flow" },
    { 0x54, 1.0e1,  "kg/h", "Mass flow" },
    { 0x55, 1.0e2,  "kg/h", "Mass flow" },
    { 0x56, 1.0e3,  "kg/h", "Mass flow" },
    { 0x57, 1.0e4,  "kg/h", "Mass flow" },

    /* E101 10nn     Flow Temperature °C (0.001°C to 1°C) */
    { 0x58, 1.0e-3, "°C", "Flow temperature" },
    { 0x59, 1.0e-2, "°C", "Flow temperature" },
    { 0x5A, 1.0e-1, "°C", "Flow temperature" },
    { 0x5B, 1.0e0,  "°C", "Flow temperature" },

    /* E101 11nn Return Temperature °C (0.001°C to 1°C) */
    { 0x5C, 1.0e-3, "°C", "Return temperature" },
    { 0x5D, 1.0e-2, "°C", "Return temperature" },
    { 0x5E, 1.0e-1, "°C", "Return temperature" },
    { 0x5F, 1.0e0,  "°C", "Return temperature" },

    /* E110 00nn    Temperature Difference  K   (mK to  K) */
    { 0x60, 1.0e-3, "K", "Temperature difference" },
    { 0x61, 1.0e-2, "K", "Temperature difference" },
    { 0x62, 1.0e-1, "K", "Temperature difference" },
    { 0x63, 1.0e0,  "K", "Temperature difference" },

    /* E110 01nn     External Temperature °C (0.001°C to 1°C) */
    { 0x64, 1.0e-3, "°C", "External temperature" },
    { 0x65, 1.0e-2, "°C", "External temperature" },
    { 0x66, 1.0e-1, "°C", "External temperature" },
    { 0x67, 1.0e0,  "°C", "External temperature" },

    /* E110 10nn     Pressure bar (1mbar to 1000mbar) */
    { 0x68, 1.0e-3, "bar", "Pressure" },
    { 0x69, 1.0e-2, "bar", "Pressure" },
    { 0x6A, 1.0e-1, "bar", "Pressure" },
    { 0x6B, 1.0e0,  "bar", "Pressure" },

    /* E110 110n     Time Point */
    { 0x6C, 1.0e0, "-", "Time point" },     /* n = 0        date, data type G */
    { 0x6D, 1.0e0, "-", "Time point" },     /* n = 1 time & date, data type F */

    /* E110 1110     Units for H.C.A. dimensionless */
    { 0x6E, 1.0e0,  "Units for H.C.A.", "H.C.A." },

    /* E110 1111     Reserved */
    { 0x6E, 0.0,  "Reserved", "Reserved" },

    /* E111 00nn     Averaging Duration s */
    { 0x70,     1.0, "s", "Averaging Duration" },  /* seconds */
    { 0x71,    60.0, "s", "Averaging Duration" },  /* minutes */
    { 0x72,  3600.0, "s", "Averaging Duration" },  /* hours   */
    { 0x73, 86400.0, "s", "Averaging Duration" },  /* days    */

    /* E111 01nn     Actuality Duration s */
    { 0x74,     1.0, "s", "Averaging Duration" },  /* seconds */
    { 0x75,    60.0, "s", "Averaging Duration" },  /* minutes */
    { 0x76,  3600.0, "s", "Averaging Duration" },  /* hours   */
    { 0x77, 86400.0, "s", "Averaging Duration" },  /* days    */

    /* Fabrication No */
    { 0x78, 1.0, "", "Fabrication No" },

    /* E111 1001 (Enhanced) Identification */
    { 0x79, 1.0, "", "(Enhanced) Identification" },

    /* E111 1010 Bus Address */
    { 0x7A, 1.0, "", "Bus Address" },

    /* Manufacturer specific: 7Fh / FF */
    { 0x7F, 1.0, "", "Manufacturer specific" },
    { 0xFF, 1.0, "", "Manufacturer specific" },


/* Main VIFE-Code Extension table (following VIF=FDh for primary VIF)
   See 8.4.4 a, only some of them are here. Using range 0x100 - 0x1FF */

    /* E000 00nn   Credit of 10nn-3 of the nominal local legal currency units */
    { 0x100, 1.0e-3, "Currency units", "Credit" },
    { 0x101, 1.0e-2, "Currency units", "Credit" },
    { 0x102, 1.0e-1, "Currency units", "Credit" },
    { 0x103, 1.0e0,  "Currency units", "Credit" },

    /* E000 01nn   Debit of 10nn-3 of the nominal local legal currency units */
    { 0x104, 1.0e-3, "Currency units", "Debit" },
    { 0x105, 1.0e-2, "Currency units", "Debit" },
    { 0x106, 1.0e-1, "Currency units", "Debit" },
    { 0x107, 1.0e0,  "Currency units", "Debit" },

    /* E000 1000 Access Number (transmission count) */
    { 0x108, 1.0e0,  "", "Access Number (transmission count)" },

    /* E000 1001 Medium (as in fixed header) */
    { 0x109, 1.0e0,  "", "Device type" },
 
    /* E000 1010 Manufacturer (as in fixed header) */
    { 0x10A, 1.0e0,  "", "Manufacturer" },

    /* E000 1011 Parameter set identification */
    { 0x10B, 1.0e0,  "", "Parameter set identification" },

    /* E000 1100 Model / Version */
    { 0x10C, 1.0e0,  "", "Device type" },

    /* E000 1101 Hardware version # */
    { 0x10D, 1.0e0,  "", "Hardware version" },

    /* E000 1110 Firmware version # */
    { 0x10E, 1.0e0,  "", "Firmware version" },

    /* E000 1111 Software version # */
    { 0x10F, 1.0e0,  "", "Software version" },


    /* E001 0000 Customer location */
    { 0x110, 1.0e0,  "", "Customer location" },

    /* E001 0001 Customer */
    { 0x111, 1.0e0,  "", "Customer" },

    /* E001 0010 Access Code User */
    { 0x112, 1.0e0,  "", "Access Code User" },

    /* E001 0011 Access Code Operator */
    { 0x113, 1.0e0,  "", "Access Code Operator" },

    /* E001 0100 Access Code System Operator */
    { 0x114, 1.0e0,  "", "Access Code System Operator" },
    
    /* E001 0101 Access Code Developer */
    { 0x115, 1.0e0,  "", "Access Code Developer" },

    /* E001 0110 Password */
    { 0x116, 1.0e0,  "", "Password" },

    /* E001 0111 Error flags (binary) */
    { 0x117, 1.0e0,  "", "Error flags" },

    /* E001 1000 Error mask */
    { 0x118, 1.0e0,  "", "Error mask" },

    /* E001 1001 Reserved */
    { 0x119, 1.0e0,  "Reserved", "Reserved" },


    /* E001 1010 Digital Output (binary) */
    { 0x11A, 1.0e0,  "", "Digital Output" },

    /* E001 1011 Digital Input (binary) */
    { 0x11B, 1.0e0,  "", "Digital Input" },

    /* E001 1100 Baudrate [Baud] */
    { 0x11C, 1.0e0,  "Baud", "Baudrate" },

    /* E001 1101 Response delay time [bittimes] */
    { 0x11D, 1.0e0,  "Bittimes", "Response delay time" },

    /* E001 1110 Retry */
    { 0x11E, 1.0e0,  "", "Retry" },

    /* E001 1111 Reserved */
    { 0x11F, 1.0e0,  "Reserved", "Reserved" },


    /* E010 0000 First storage # for cyclic storage */
    { 0x120, 1.0e0,  "", "First storage # for cyclic storage" },

    /* E010 0001 Last storage # for cyclic storage */
    { 0x121, 1.0e0,  "", "Last storage # for cyclic storage" },

    /* E010 0010 Size of storage block */
    { 0x122, 1.0e0,  "", "Size of storage block" },

    /* E010 0011 Reserved */
    { 0x123, 1.0e0,  "Reserved", "Reserved" },

    /* E010 01nn Storage interval [sec(s)..day(s)] */
    { 0x124,        1.0,  "s", "Storage interval" },   /* second(s) */
    { 0x125,       60.0,  "s", "Storage interval" },   /* minute(s) */
    { 0x126,     3600.0,  "s", "Storage interval" },   /* hour(s)   */
    { 0x127,    86400.0,  "s", "Storage interval" },   /* day(s)    */
    { 0x128,  2629743.83, "s", "Storage interval" },   /* month(s)  */
    { 0x129, 31556926.0,  "s", "Storage interval" },   /* year(s)   */

    /* E010 1010 Reserved */
    { 0x12A, 1.0e0,  "Reserved", "Reserved" },

    /* E010 1011 Reserved */
    { 0x12B, 1.0e0,  "Reserved", "Reserved" },

    /* E010 11nn Duration since last readout [sec(s)..day(s)] */
    { 0x12C,     1.0, "s", "Duration since last readout" },  /* seconds */
    { 0x12D,    60.0, "s", "Duration since last readout" },  /* minutes */
    { 0x12E,  3600.0, "s", "Duration since last readout" },  /* hours   */
    { 0x12F, 86400.0, "s", "Duration since last readout" },  /* days    */

    /* E011 0000 Start (date/time) of tariff  */
    /* The information about usage of data type F (date and time) or data type G (date) can */
    /* be derived from the datafield (0010b: type G / 0100: type F). */
    { 0x130, 1.0e0,  "Reserved", "Reserved" }, /* ???? */

    /* E011 00nn Duration of tariff (nn=01 ..11: min to days) */
    { 0x131,       60.0,  "s", "Storage interval" },   /* minute(s) */
    { 0x132,     3600.0,  "s", "Storage interval" },   /* hour(s)   */
    { 0x133,    86400.0,  "s", "Storage interval" },   /* day(s)    */

    /* E011 01nn Period of tariff [sec(s) to day(s)]  */
    { 0x134,        1.0, "s", "Period of tariff" },  /* seconds  */
    { 0x135,       60.0, "s", "Period of tariff" },  /* minutes  */
    { 0x136,     3600.0, "s", "Period of tariff" },  /* hours    */
    { 0x137,    86400.0, "s", "Period of tariff" },  /* days     */
    { 0x138,  2629743.83,"s", "Period of tariff" },  /* month(s) */
    { 0x139, 31556926.0, "s", "Period of tariff" },  /* year(s)  */

    /* E011 1010 dimensionless / no VIF */
    { 0x13A, 1.0e0,  "", "Dimensionless" },

    /* E011 1011 Reserved */
    { 0x13B, 1.0e0,  "Reserved", "Reserved" },

    /* E011 11xx Reserved */
    { 0x13C, 1.0e0,  "Reserved", "Reserved" },
    { 0x13D, 1.0e0,  "Reserved", "Reserved" },
    { 0x13E, 1.0e0,  "Reserved", "Reserved" },
    { 0x13F, 1.0e0,  "Reserved", "Reserved" },
    
    /* E100 nnnn   Volts electrical units */
    { 0x140, 1.0e-9, "V", "Voltage" },
    { 0x141, 1.0e-8, "V", "Voltage" },
    { 0x142, 1.0e-7, "V", "Voltage" },
    { 0x143, 1.0e-6, "V", "Voltage" },
    { 0x144, 1.0e-5, "V", "Voltage" },
    { 0x145, 1.0e-4, "V", "Voltage" },
    { 0x146, 1.0e-3, "V", "Voltage" },
    { 0x147, 1.0e-2, "V", "Voltage" },
    { 0x148, 1.0e-1, "V", "Voltage" },
    { 0x149, 1.0e0,  "V", "Voltage" },
    { 0x14A, 1.0e1,  "V", "Voltage" },
    { 0x14B, 1.0e2,  "V", "Voltage" },
    { 0x14C, 1.0e3,  "V", "Voltage" },
    { 0x14D, 1.0e4,  "V", "Voltage" },
    { 0x14E, 1.0e5,  "V", "Voltage" },
    { 0x14F, 1.0e6,  "V", "Voltage" },

    /* E101 nnnn   A */
    { 0x150, 1.0e-12, "A", "Current" },
    { 0x151, 1.0e-11, "A", "Current" },
    { 0x152, 1.0e-10, "A", "Current" },
    { 0x153, 1.0e-9,  "A", "Current" },
    { 0x154, 1.0e-8,  "A", "Current" },
    { 0x155, 1.0e-7,  "A", "Current" },
    { 0x156, 1.0e-6,  "A", "Current" },
    { 0x157, 1.0e-5,  "A", "Current" },
    { 0x158, 1.0e-4,  "A", "Current" },
    { 0x159, 1.0e-3,  "A", "Current" },
    { 0x15A, 1.0e-2,  "A", "Current" },
    { 0x15B, 1.0e-1,  "A", "Current" },
    { 0x15C, 1.0e0,   "A", "Current" },
    { 0x15D, 1.0e1,   "A", "Current" },
    { 0x15E, 1.0e2,   "A", "Current" },
    { 0x15F, 1.0e3,   "A", "Current" },

    /* E110 0000 Reset counter */
    { 0x160, 1.0e0,  "", "Reset counter" },

    /* E110 0001 Cumulation counter */
    { 0x161, 1.0e0,  "", "Cumulation counter" },

    /* E110 0010 Control signal */
    { 0x162, 1.0e0,  "", "Control signal" },

    /* E110 0011 Day of week */
    { 0x163, 1.0e0,  "", "Day of week" },

    /* E110 0100 Week number */
    { 0x164, 1.0e0,  "", "Week number" },

    /* E110 0101 Time point of day change */
    { 0x165, 1.0e0,  "", "Time point of day change" },

    /* E110 0110 State of parameter activation */
    { 0x166, 1.0e0,  "", "State of parameter activation" },

    /* E110 0111 Special supplier information */
    { 0x167, 1.0e0,  "", "Special supplier information" },

    /* E110 10pp Duration since last cumulation [hour(s)..years(s)] */
    { 0x168,     3600.0, "s", "Duration since last cumulation" },  /* hours    */
    { 0x169,    86400.0, "s", "Duration since last cumulation" },  /* days     */
    { 0x16A,  2629743.83,"s", "Duration since last cumulation" },  /* month(s) */
    { 0x16B, 31556926.0, "s", "Duration since last cumulation" },  /* year(s)  */

    /* E110 11pp Operating time battery [hour(s)..years(s)] */
    { 0x16C,     3600.0, "s", "Operating time battery" },  /* hours    */
    { 0x16D,    86400.0, "s", "Operating time battery" },  /* days     */
    { 0x16E,  2629743.83,"s", "Operating time battery" },  /* month(s) */
    { 0x16F, 31556926.0, "s", "Operating time battery" },  /* year(s)  */

    /* E111 0000 Date and time of battery change */
    { 0x170, 1.0e0,  "", "Date and time of battery change" },

    /* E111 0001-1111 Reserved */
    { 0x171, 1.0e0,  "Reserved", "Reserved" },
    { 0x172, 1.0e0,  "Reserved", "Reserved" },
    { 0x173, 1.0e0,  "Reserved", "Reserved" },
    { 0x174, 1.0e0,  "Reserved", "Reserved" },
    { 0x175, 1.0e0,  "Reserved", "Reserved" },
    { 0x176, 1.0e0,  "Reserved", "Reserved" },
    { 0x177, 1.0e0,  "Reserved", "Reserved" },
    { 0x178, 1.0e0,  "Reserved", "Reserved" },
    { 0x179, 1.0e0,  "Reserved", "Reserved" },
    { 0x17A, 1.0e0,  "Reserved", "Reserved" },
    { 0x17B, 1.0e0,  "Reserved", "Reserved" },
    { 0x17C, 1.0e0,  "Reserved", "Reserved" },
    { 0x17D, 1.0e0,  "Reserved", "Reserved" },
    { 0x17E, 1.0e0,  "Reserved", "Reserved" },
    { 0x17F, 1.0e0,  "Reserved", "Reserved" },


/* Alternate VIFE-Code Extension table (following VIF=0FBh for primary VIF)
   See 8.4.4 b, only some of them are here. Using range 0x200 - 0x2FF */

    /* E000 000n Energy 10(n-1) MWh 0.1MWh to 1MWh */
    { 0x200, 1.0e5,  "Wh", "Energy" },
    { 0x201, 1.0e6,  "Wh", "Energy" },

    /* E000 001n Reserved */
    { 0x202, 1.0e0,  "Reserved", "Reserved" },
    { 0x203, 1.0e0,  "Reserved", "Reserved" },

    /* E000 01nn Reserved */
    { 0x204, 1.0e0,  "Reserved", "Reserved" },
    { 0x205, 1.0e0,  "Reserved", "Reserved" },
    { 0x206, 1.0e0,  "Reserved", "Reserved" },
    { 0x207, 1.0e0,  "Reserved", "Reserved" },

    /* E000 100n Energy 10(n-1) GJ 0.1GJ to 1GJ */
    { 0x208, 1.0e8,  "Reserved", "Energy" },
    { 0x209, 1.0e9,  "Reserved", "Energy" },

    /* E000 101n Reserved */
    { 0x20A, 1.0e0,  "Reserved", "Reserved" },
    { 0x20B, 1.0e0,  "Reserved", "Reserved" },

    /* E000 11nn Reserved */
    { 0x20C, 1.0e0,  "Reserved", "Reserved" },
    { 0x20D, 1.0e0,  "Reserved", "Reserved" },
    { 0x20E, 1.0e0,  "Reserved", "Reserved" },
    { 0x20F, 1.0e0,  "Reserved", "Reserved" },

    /* E001 000n Volume 10(n+2) m3 100m3 to 1000m3 */
    { 0x210, 1.0e2,  "m^3", "Volume" },
    { 0x211, 1.0e3,  "m^3", "Volume" },

    /* E001 001n Reserved */
    { 0x212, 1.0e0,  "Reserved", "Reserved" },
    { 0x213, 1.0e0,  "Reserved", "Reserved" },

    /* E001 01nn Reserved */
    { 0x214, 1.0e0,  "Reserved", "Reserved" },
    { 0x215, 1.0e0,  "Reserved", "Reserved" },
    { 0x216, 1.0e0,  "Reserved", "Reserved" },
    { 0x217, 1.0e0,  "Reserved", "Reserved" },

    /* E001 100n Mass 10(n+2) t 100t to 1000t */
    { 0x218, 1.0e5,  "kg", "Mass" },
    { 0x219, 1.0e6,  "kg", "Mass" },

    /* E001 1010 to E010 0000 Reserved */
    { 0x21A, 1.0e0,  "Reserved", "Reserved" },
    { 0x21B, 1.0e0,  "Reserved", "Reserved" },
    { 0x21C, 1.0e0,  "Reserved", "Reserved" },
    { 0x21D, 1.0e0,  "Reserved", "Reserved" },
    { 0x21E, 1.0e0,  "Reserved", "Reserved" },
    { 0x21F, 1.0e0,  "Reserved", "Reserved" },
    { 0x220, 1.0e0,  "Reserved", "Reserved" },

    /* E010 0001 Volume 0,1 feet^3 */
    { 0x221, 1.0e-1, "feet^3", "Volume" },

    /* E010 001n Volume 0,1-1 american gallon */
    { 0x222, 1.0e-1, "American gallon", "Volume" },
    { 0x223, 1.0e-0, "American gallon", "Volume" },

    /* E010 0100    Volume flow 0,001 american gallon/min */
    { 0x224, 1.0e-3, "American gallon/min", "Volume flow" },

    /* E010 0101 Volume flow 1 american gallon/min */
    { 0x225, 1.0e0,  "American gallon/min", "Volume flow" },

    /* E010 0110 Volume flow 1 american gallon/h */
    { 0x226, 1.0e0,  "American gallon/h", "Volume flow" },

    /* E010 0111 Reserved */
    { 0x227, 1.0e0, "Reserved", "Reserved" },

    /* E010 100n Power 10(n-1) MW 0.1MW to 1MW */
    { 0x228, 1.0e5, "W", "Power" },
    { 0x229, 1.0e6, "W", "Power" },

    /* E010 101n Reserved */
    { 0x22A, 1.0e0, "Reserved", "Reserved" },
    { 0x22B, 1.0e0, "Reserved", "Reserved" },

    /* E010 11nn Reserved */
    { 0x22C, 1.0e0, "Reserved", "Reserved" },
    { 0x22D, 1.0e0, "Reserved", "Reserved" },
    { 0x22E, 1.0e0, "Reserved", "Reserved" },
    { 0x22F, 1.0e0, "Reserved", "Reserved" },

    /* E011 000n Power 10(n-1) GJ/h 0.1GJ/h to 1GJ/h */
    { 0x230, 1.0e8, "J", "Power" },
    { 0x231, 1.0e9, "J", "Power" },

    /* E011 0010 to E101 0111 Reserved */
    { 0x232, 1.0e0, "Reserved", "Reserved" },
    { 0x233, 1.0e0, "Reserved", "Reserved" },
    { 0x234, 1.0e0, "Reserved", "Reserved" },
    { 0x235, 1.0e0, "Reserved", "Reserved" },
    { 0x236, 1.0e0, "Reserved", "Reserved" },
    { 0x237, 1.0e0, "Reserved", "Reserved" },

    /* E101 10nn Flow Temperature 10(nn-3) °F 0.001°F to 1°F */
    { 0x238, 1.0e-3, "°F", "Flow temperature" },
    { 0x239, 1.0e-2, "°F", "Flow temperature" },
    { 0x23A, 1.0e-1, "°F", "Flow temperature" },
    { 0x23B, 1.0e0,  "°F", "Flow temperature" },

    /* E101 11nn Return Temperature 10(nn-3) °F 0.001°F to 1°F */
    { 0x23C, 1.0e-3, "°F", "Return temperature" },
    { 0x23D, 1.0e-2, "°F", "Return temperature" },
    { 0x23E, 1.0e-1, "°F", "Return temperature" },
    { 0x23F, 1.0e0,  "°F", "Return temperature" },

    /* E110 00nn Temperature Difference 10(nn-3) °F 0.001°F to 1°F */
    { 0x240, 1.0e-3, "°F", "Temperature difference" },
    { 0x241, 1.0e-2, "°F", "Temperature difference" },
    { 0x242, 1.0e-1, "°F", "Temperature difference" },
    { 0x243, 1.0e0,  "°F", "Temperature difference" },

    /* E110 01nn External Temperature 10(nn-3) °F 0.001°F to 1°F */
    { 0x244, 1.0e-3, "°F", "External temperature" },
    { 0x245, 1.0e-2, "°F", "External temperature" },
    { 0x246, 1.0e-1, "°F", "External temperature" },
    { 0x247, 1.0e0,  "°F", "External temperature" },

    /* E110 1nnn Reserved */
    { 0x248, 1.0e0, "Reserved", "Reserved" },
    { 0x249, 1.0e0, "Reserved", "Reserved" },
    { 0x24A, 1.0e0, "Reserved", "Reserved" },
    { 0x24B, 1.0e0, "Reserved", "Reserved" },
    { 0x24C, 1.0e0, "Reserved", "Reserved" },
    { 0x24D, 1.0e0, "Reserved", "Reserved" },
    { 0x24E, 1.0e0, "Reserved", "Reserved" },
    { 0x24F, 1.0e0, "Reserved", "Reserved" },

    /* E111 00nn Cold / Warm Temperature Limit 10(nn-3) °F 0.001°F to 1°F */
    { 0x250, 1.0e-3, "°F", "Cold / Warm Temperature Limit" },
    { 0x251, 1.0e-2, "°F", "Cold / Warm Temperature Limit" },
    { 0x252, 1.0e-1, "°F", "Cold / Warm Temperature Limit" },
    { 0x253, 1.0e0,  "°F", "Cold / Warm Temperature Limit" },

    /* E111 01nn Cold / Warm Temperature Limit 10(nn-3) °C 0.001°C to 1°C */
    { 0x254, 1.0e-3, "°C", "Cold / Warm Temperature Limit" },
    { 0x255, 1.0e-2, "°C", "Cold / Warm Temperature Limit" },
    { 0x256, 1.0e-1, "°C", "Cold / Warm Temperature Limit" },
    { 0x257, 1.0e0,  "°C", "Cold / Warm Temperature Limit" },

    /* E111 1nnn cumul. count max power § 10(nnn-3) W 0.001W to 10000W */
    { 0x258, 1.0e-3, "W", "Cumul count max power" },
    { 0x259, 1.0e-3, "W", "Cumul count max power" },
    { 0x25A, 1.0e-1, "W", "Cumul count max power" },
    { 0x25B, 1.0e0,  "W", "Cumul count max power" },
    { 0x25C, 1.0e1,  "W", "Cumul count max power" },
    { 0x25D, 1.0e2,  "W", "Cumul count max power" },
    { 0x25E, 1.0e3,  "W", "Cumul count max power" },
    { 0x25F, 1.0e4,  "W", "Cumul count max power" },
    
/* End of array */
    { 0xFFFF, 0.0, "", "" },
};


mbus_variable_vif fixed_table[] = {
    /* 00, 01 left out */
    { 0x02, 1.0e0, "Wh", "Energy" },
    { 0x03, 1.0e1, "Wh", "Energy" },
    { 0x04, 1.0e2, "Wh", "Energy" },
    { 0x05, 1.0e3, "Wh", "Energy" },
    { 0x06, 1.0e4, "Wh", "Energy" },
    { 0x07, 1.0e5, "Wh", "Energy" },
    { 0x08, 1.0e6, "Wh", "Energy" },
    { 0x09, 1.0e7, "Wh", "Energy" },
    { 0x0A, 1.0e8, "Wh", "Energy" },

    { 0x0B, 1.0e3, "J", "Energy" },
    { 0x0C, 1.0e4, "J", "Energy" },
    { 0x0D, 1.0e5, "J", "Energy" },
    { 0x0E, 1.0e6, "J", "Energy" },
    { 0x0F, 1.0e7, "J", "Energy" },
    { 0x10, 1.0e8, "J", "Energy" },
    { 0x11, 1.0e9, "J", "Energy" },
    { 0x12, 1.0e10,"J", "Energy" },
    { 0x13, 1.0e11,"J", "Energy" },

    { 0x14, 1.0e0, "W", "Power" },
    { 0x15, 1.0e0, "W", "Power" },
    { 0x16, 1.0e0, "W", "Power" },
    { 0x17, 1.0e0, "W", "Power" },
    { 0x18, 1.0e0, "W", "Power" },
    { 0x19, 1.0e0, "W", "Power" },
    { 0x1A, 1.0e0, "W", "Power" },
    { 0x1B, 1.0e0, "W", "Power" },
    { 0x1C, 1.0e0, "W", "Power" },

    { 0x1D, 1.0e3, "J/h", "Energy" },
    { 0x1E, 1.0e4, "J/h", "Energy" },
    { 0x1F, 1.0e5, "J/h", "Energy" },
    { 0x20, 1.0e6, "J/h", "Energy" },
    { 0x21, 1.0e7, "J/h", "Energy" },
    { 0x22, 1.0e8, "J/h", "Energy" },
    { 0x23, 1.0e9, "J/h", "Energy" },
    { 0x24, 1.0e10,"J/h", "Energy" },
    { 0x25, 1.0e11,"J/h", "Energy" },

    { 0x26, 1.0e-6,"m^3", "Volume" },
    { 0x27, 1.0e-5,"m^3", "Volume" },
    { 0x28, 1.0e-4,"m^3", "Volume" },
    { 0x29, 1.0e-3,"m^3", "Volume" },
    { 0x2A, 1.0e-2,"m^3", "Volume" },
    { 0x2B, 1.0e-1,"m^3", "Volume" },
    { 0x2C, 1.0e0, "m^3", "Volume" },
    { 0x2D, 1.0e1, "m^3", "Volume" },
    { 0x2E, 1.0e2, "m^3", "Volume" },

    { 0x2F, 1.0e-5,"m^3/h", "Volume flow" },
    { 0x31, 1.0e-4,"m^3/h", "Volume flow" },
    { 0x32, 1.0e-3,"m^3/h", "Volume flow" },
    { 0x33, 1.0e-2,"m^3/h", "Volume flow" },
    { 0x34, 1.0e-1,"m^3/h", "Volume flow" },
    { 0x35, 1.0e0, "m^3/h", "Volume flow" },
    { 0x36, 1.0e1, "m^3/h", "Volume flow" },
    { 0x37, 1.0e2, "m^3/h", "Volume flow" },

    { 0x38, 1.0e-3, "°C", "Temperature" },

    { 0x39, 1.0e0,  "Units for H.C.A.", "H.C.A." },

    { 0x3A, 0.0,  "Reserved", "Reserved" },
    { 0x3B, 0.0,  "Reserved", "Reserved" },
    { 0x3C, 0.0,  "Reserved", "Reserved" },
    { 0x3D, 0.0,  "Reserved", "Reserved" },

    { 0x3E, 1.0e0,  "", "historic" },

    { 0x3F, 1.0e0,  "", "No units" },

/* end of array */
    { 0xFFFF, 0.0, "", "" },
};


int mbus_fixed_normalize(int medium_unit, long medium_value, char **unit_out, double *value_out, char **quantity_out)
{
    double exponent = 0.0;
    int i;
    medium_unit = medium_unit & 0x3F;

    switch (medium_unit)
    {
        case 0x00:    
            *unit_out = strdup("h,m,s"); /*  todo convert to unix time... */
            *quantity_out = strdup("Time");
            break;    
        case 0x01:    
            *unit_out = strdup("D,M,Y"); /*  todo convert to unix time... */
            *quantity_out = strdup("Time");
            break;    

    default:
        for(i=0; fixed_table[i].vif < 0xfff; ++i)
        {
            if (fixed_table[i].vif == medium_unit)
            {
                *unit_out = strdup(fixed_table[i].unit);
                *value_out = ((double) (medium_value)) * fixed_table[i].exponent;
                *quantity_out = strdup(fixed_table[i].quantity);
                return 0;
            }
        }

        *unit_out = strdup("Unknown");
        *quantity_out = strdup("Unknown");
        exponent = 0.0;
        *value_out = 0.0;
        return -1;
        break;
    }
    
    return -2;
}


int mbus_variable_value_decode(mbus_data_record *record, double *value_out_real, char **value_out_str, int *value_out_str_size)
{
    int result = 0;
    *value_out_real = 0.0;
    *value_out_str = NULL;
    *value_out_str_size = 0;

    if (record)
    {
        MBUS_DEBUG("coding = 0x%02X \n", record->drh.dib.dif);

        switch (record->drh.dib.dif & 0x0F)
        {
            case 0x00: /* no data */
                result = -1;
                break; 

            case 0x01: /* 1 byte integer (8 bit) */
                *value_out_real = mbus_data_int_decode(record->data, 1);
                result = 0;
                break; 

            case 0x02: /* 2 byte integer (16 bit) */
                *value_out_real = mbus_data_int_decode(record->data, 2);
                result = 0;
                break; 

            case 0x03: /* 3 byte integer (24 bit) */
                *value_out_real = mbus_data_int_decode(record->data, 3);
                result = 0;
                break; 
                
            case 0x04: /* 4 byte integer (32 bit) */
                *value_out_real = mbus_data_int_decode(record->data, 4);
                result = 0;
                break;  

            case 0x05: /* 32b real */
                result = -2;
                MBUS_ERROR("32b real not implemented yet\n");
                break;

            case 0x06: /* 6 byte integer (48 bit) */
                *value_out_real = mbus_data_long_decode(record->data, 6);
                result = 0;
                break;          

            case 0x07: /* 8 byte integer (64 bit) */
                *value_out_real = mbus_data_long_decode(record->data, 8);
                result = 0;
                break;          

            case 0x09: /* 2 digit BCD (8 bit) */
                *value_out_real = mbus_data_bcd_decode(record->data, 1);  
                result = 0;
                break;
                
            case 0x0A: /* 4 digit BCD (16 bit) */
                *value_out_real = mbus_data_bcd_decode(record->data, 2);  
                result = 0;
                break;

            case 0x0B: /* 6 digit BCD (24 bit) */
                *value_out_real = mbus_data_bcd_decode(record->data, 3);  
                result = 0;
                break;
                
            case 0x0C: /* 8 digit BCD (32 bit) */
                *value_out_real = mbus_data_bcd_decode(record->data, 4);  
                result = 0;
                break;
                                                                  
            case 0x0D: /* variable length */
            {
                if (record->data_len <= 0xBF) {
                    *value_out_str = (char*) malloc(record->data_len + 1);
                    *value_out_str_size = record->data_len;
                    mbus_data_str_decode((u_char*)(*value_out_str), record->data, record->data_len);
                    result = 0;
                    break;
                }
                result = -2;
                MBUS_ERROR("Non ASCII variable length not implemented yet\n");
                break;
            }

            case 0x0E: /* 12 digit BCD (40 bit) */
                result = -2;
                MBUS_ERROR("12 digit BCD (40 bit) not implemented yet\n");
                break;

            case 0x0F: /* Special functions */
                result = -2;
                MBUS_ERROR("Special functions not implemented yet");
                break;

            default:
                result = -2;
                MBUS_ERROR("Unknown DIF (0x%.2x)", record->drh.dib.dif);
                break;
        }
    }
    else
    {
        result = -3;
    }
    
    return result;
}

int
mbus_vif_unit_normalize(int vif, double value, char **unit_out, double *value_out, char **quantity_out)
{
    MBUS_DEBUG("vif_unit_normalize = 0x%03X \n", vif);

    double exponent = 1.0;

    unsigned newVif = vif & 0xF7F; /* clear extension bit */

    int i;
            
    switch (newVif) /* ignore the extension bit in this selection */
    {
        /* E110 110n Time Point
           n = 0        date
           n = 1 time & date
           data type G
           data type F          */
        case 0x6C:
        case 0x6C+1:
            if (vif & 0x1)
            {
                *unit_out = strdup("Time Point (time & date)");
                *quantity_out = strdup("Time Point (time & date)");
            }
            else
            {
                *unit_out = strdup("Time Point (date)");
                *quantity_out = strdup("Time Point (date)");
            }  
            break;
            
        /* Manufacturer specific: 7Fh / FF */
        case 0x7F:
        case 0xFF:
            *unit_out = strdup("Manufacturer specific");
            *quantity_out = strdup("Manufacturer specific");
            exponent = 0.0;
            *value_out = 0.0;
            return 0;
            break;
                       
        default:
            for(i=0; vif_table[i].vif < 0xfff; ++i)
            {
                if (vif_table[i].vif == newVif)
                {
                    *unit_out = strdup(vif_table[i].unit);
                    *value_out = value * vif_table[i].exponent;
                    *quantity_out = strdup(vif_table[i].quantity);
                    return 0;
                }
            }

            *unit_out = strdup("Unknown (VIF=0x%.2X)");
            *quantity_out = strdup("Unknown");
            exponent = 0.0;
            *value_out = 0.0;
            return -1;
            break;
    }
    
    return -2;
}


int
mbus_vib_unit_normalize(mbus_value_information_block *vib, double value, char **unit_out, double *value_out, char **quantity_out)
{   
    MBUS_DEBUG("%s: vib_unit_normalize - VIF=0x%02X\n", __PRETTY_FUNCTION__, vib->vif);
    if (vib->vif == 0xFD) /* first type of VIF extention: see table 8.4.4 a */
    {
        if (vib->nvife == 0)
        {
            MBUS_ERROR("%s: Missing VIF extension\n", __PRETTY_FUNCTION__);
            return -1;
        }

        int code = ((vib->vife[0]) & 0x7f) | 0x100;
        if (mbus_vif_unit_normalize(code, value, unit_out, value_out, quantity_out) != 0)
        {
            MBUS_ERROR("%s: Error mbus_vif_unit_normalize\n", __PRETTY_FUNCTION__);
            return -1;
        }
    } else {
        if (vib->vif == 0xFB) /* second type of VIF extention: see table 8.4.4 b */
        {
            if (vib->nvife == 0)
            {
                MBUS_ERROR("%s: Missing VIF extension\n", __PRETTY_FUNCTION__);
                return -1;
            }
            
            int code = ((vib->vife[0]) & 0x7f) | 0x200;
            if (0 != mbus_vif_unit_normalize(code, value, unit_out, value_out, quantity_out))
            {
                MBUS_ERROR("%s: Error mbus_vif_unit_normalize\n", __PRETTY_FUNCTION__);
                return -1;
            }
        } else {
            int code = (vib->vif) & 0x7f;
            if (0 != mbus_vif_unit_normalize(code, value, unit_out, value_out, quantity_out))
            {
                MBUS_ERROR("%s: Error mbus_vif_unit_normalize\n", __PRETTY_FUNCTION__);
                return -1;
            }
        }
    }

    return 0;
}


mbus_record *
mbus_record_new()
{
    mbus_record * record;

    if (!(record = (mbus_record *) malloc(sizeof(mbus_record))))
    {
        MBUS_ERROR("%s: memory allocation error\n", __PRETTY_FUNCTION__);
        return NULL;
    }

    record->value.real_val = 0.0;
    record->is_numeric = 1;
    record->unit = NULL;
    record->function_medium = NULL;
    record->quantity = NULL;
    return record;
}


void
mbus_record_free(mbus_record * rec)
{
    if (! rec->is_numeric)
    {
        free((rec->value).str_val.value);
        (rec->value).str_val.value = NULL;
    }

    if (rec->unit)
    {
        free(rec->unit);
        rec->unit = NULL;
    }

    if (rec->function_medium)
    {
        free(rec->function_medium);
        rec->function_medium = NULL;
    }

    if (rec->quantity)
    {
        free(rec->quantity);
        rec->quantity = NULL;
    }
    free(rec);
}


mbus_record *
mbus_parse_fixed_record(char status_byte, char medium_unit, u_char *data)
{
    mbus_record * record = NULL;

    if (!(record = mbus_record_new()))
    {
        MBUS_ERROR("%s: memory allocation error\n", __PRETTY_FUNCTION__);
        return NULL;
    }

    /* shared/static memory - get own copy */
    record->function_medium = strdup(mbus_data_fixed_function((int)status_byte));  /* stored / actual */

    long value = 0;
    if ((status_byte & MBUS_DATA_FIXED_STATUS_FORMAT_MASK) == MBUS_DATA_FIXED_STATUS_FORMAT_BCD)
    {
        value = mbus_data_bcd_decode(data, 4);
    }
    else
    {
        value = mbus_data_int_decode(data, 4);
    }

    record->unit = NULL;
    record->is_numeric = 1;
    if (0 != mbus_fixed_normalize(medium_unit, value, &(record->unit), &(record->value.real_val), &(record->quantity)))
    {
        MBUS_ERROR("Problem with mbus_fixed_normalize.\n");
        mbus_record_free(record);
        return NULL;
    }

    return record;
}


mbus_record *
mbus_parse_variable_record(mbus_data_record *data)
{
    mbus_record * record = NULL;

    if (!(record = mbus_record_new()))
    {
        MBUS_ERROR("%s: memory allocation error\n", __PRETTY_FUNCTION__);
        return NULL;
    }
    
    if (data->drh.dib.dif == 0x0F || data->drh.dib.dif == 0x1F) /* MBUS_DIB_DIF_VENDOR_SPECIFIC */
    {
        record->function_medium = strdup("Manufacturer specific");
        /* parsing of data not implemented yet
           manufacturer specific data structures to end of user data */
    }
    else
    {
        record->function_medium = strdup(mbus_data_record_function(data));
        MBUS_DEBUG("record->function_medium = %s \n", record->function_medium);
        
        double value_out_real    = 0.0;  /**< raw value */
        char * value_out_str     = NULL;
        int    value_out_str_size = 0;
        double real_val         = 0.0;  /**< normalized value */

        if (mbus_variable_value_decode(data, &value_out_real, &value_out_str, &value_out_str_size) != 0)
        {
            MBUS_ERROR("%s: problem with mbus_variable_value_decode\n", __PRETTY_FUNCTION__);
            mbus_record_free(record);
            return NULL;
        }
        MBUS_DEBUG("value_out_real = %lf \n", value_out_real);

        if (mbus_vib_unit_normalize(&(data->drh.vib), value_out_real, &(record->unit), &real_val, &(record->quantity)) != 0)
        {
            MBUS_ERROR("%s: problem with mbus_vib_unit_normalize\n", __PRETTY_FUNCTION__);
            mbus_record_free(record);
            record = NULL;
            return NULL;
        }
        MBUS_DEBUG("record->unit = %s \n", record->unit);
        MBUS_DEBUG("real_val = %lf \n", real_val);

        if (value_out_str != NULL)
        {
            record->is_numeric = 0;
            (record->value).str_val.value = value_out_str;
            (record->value).str_val.size = value_out_str_size;
        }
        else
        {
            record->is_numeric = 1;
            (record->value).real_val = real_val;
        }
    }
    return record;
}


mbus_handle *
mbus_connect_serial(const char * device)
{
    mbus_serial_handle * serial_handle;
    if ((serial_handle = mbus_serial_connect((char*)device)) == NULL)
    {
        MBUS_ERROR("%s: Failed to setup serial connection to M-bus gateway on %s.\n",
                   __PRETTY_FUNCTION__, 
                   device);
        return NULL;
    }

    mbus_handle * handle;
    if ((handle = (mbus_handle * ) malloc(sizeof(mbus_handle))) == NULL)
    {
        MBUS_ERROR("%s: Failed to allocate handle.\n", __PRETTY_FUNCTION__);
        return NULL;
    }
    handle->is_serial = 1;
    handle->m_serial_handle = serial_handle;
    return handle;
}


mbus_handle *
mbus_connect_tcp(const char * host, int port)
{
    mbus_tcp_handle * tcp_handle;
    if ((tcp_handle = mbus_tcp_connect((char*)host, port)) == NULL)
    {
        MBUS_ERROR("%s: Failed to setup tcp connection to M-bus gateway on %s, port %d.\n", 
                   __PRETTY_FUNCTION__,
                   host,
                   port);
        return NULL;
    }

    mbus_handle * handle;
    if ((handle = (mbus_handle * ) malloc(sizeof(mbus_handle))) == NULL)
    {
        MBUS_ERROR("%s: Failed to allocate handle.\n", __PRETTY_FUNCTION__);
        return NULL;
    }
    handle->is_serial = 0;
    handle->m_tcp_handle = tcp_handle;
    return handle;
}

int
mbus_disconnect(mbus_handle * handle)
{
    if (handle == NULL)
    {
        MBUS_ERROR("%s: Invalid M-Bus handle for disconnect.\n", __PRETTY_FUNCTION__);
        return 0;
    }

    if (handle->is_serial)
    {
        mbus_serial_disconnect(handle->m_serial_handle);
        handle->m_serial_handle = NULL;
    }
    else
    {
        mbus_tcp_disconnect(handle->m_tcp_handle);
        handle->m_tcp_handle = NULL;
    }
    free(handle);
    return 0;
}

int
mbus_recv_frame(mbus_handle * handle, mbus_frame *frame)
{
    if (handle == NULL)
    {
        MBUS_ERROR("%s: Invalid M-Bus handle for receive.\n", __PRETTY_FUNCTION__);
        return 0;
    }

    if (handle->is_serial)
    {
        return mbus_serial_recv_frame(handle->m_serial_handle, frame);
    }
    else
    {
        return mbus_tcp_recv_frame(handle->m_tcp_handle, frame);
    }
    return 0;
}

int
mbus_send_frame(mbus_handle * handle, mbus_frame *frame)
{
    if (handle == NULL)
    {
        MBUS_ERROR("%s: Invalid M-Bus handle for send.\n", __PRETTY_FUNCTION__);
        return 0;
    }

    if (handle->is_serial)
    {
        return mbus_serial_send_frame(handle->m_serial_handle, frame);
    }
    else
    {
        return mbus_tcp_send_frame(handle->m_tcp_handle, frame);
    }
    return 0;
}

//------------------------------------------------------------------------------
// send a data request packet to from master to slave: the packet selects
// a slave to be the active secondary addressed slave if the secondary address
// matches that of the slave.
//------------------------------------------------------------------------------
int
mbus_send_select_frame(mbus_handle * handle, const char *secondary_addr_str)
{
    mbus_frame *frame;
        
    frame = mbus_frame_new(MBUS_FRAME_TYPE_LONG);

    if (mbus_frame_select_secondary_pack(frame, (char*) secondary_addr_str) == -1)
    {
        MBUS_ERROR("%s: Failed to pack selection mbus frame.\n", __PRETTY_FUNCTION__);
        mbus_frame_free(frame);
        return -1;
    }    

    if (mbus_send_frame(handle, frame) == -1)
    {
        MBUS_ERROR("%s: Failed to send mbus frame.\n", __PRETTY_FUNCTION__);
        mbus_frame_free(frame);
        return -1;
    }    
	
    mbus_frame_free(frame);
    return 0;
}

//------------------------------------------------------------------------------
// send a user data packet from master to slave: the packet let the
// adressed slave(s) switch to the given baudrate
//------------------------------------------------------------------------------
int
mbus_send_switch_baudrate_frame(mbus_handle * handle, int address, int baudrate)
{
    int retval = 0;
    int control_information = 0;
    mbus_frame *frame;
    
    switch (baudrate)
    {
      case 300:
        control_information = MBUS_CONTROL_INFO_SET_BAUDRATE_300;
        break;
      case 600:
        control_information = MBUS_CONTROL_INFO_SET_BAUDRATE_600;
        break;
      case 1200:
        control_information = MBUS_CONTROL_INFO_SET_BAUDRATE_1200;
        break;
      case 2400:
        control_information = MBUS_CONTROL_INFO_SET_BAUDRATE_2400;
        break;
      case 4800:
        control_information = MBUS_CONTROL_INFO_SET_BAUDRATE_4800;
        break;
      case 9600:
        control_information = MBUS_CONTROL_INFO_SET_BAUDRATE_9600;
        break;
      case 19200:
        control_information = MBUS_CONTROL_INFO_SET_BAUDRATE_19200;
        break;
      case 38400:
        control_information = MBUS_CONTROL_INFO_SET_BAUDRATE_38400;
        break;
      default:
        MBUS_ERROR("%s: invalid baudrate %d\n", __PRETTY_FUNCTION__, baudrate);
        return -1;
    }
    
    frame = mbus_frame_new(MBUS_FRAME_TYPE_CONTROL);
    
    if (frame == NULL)
    {
        MBUS_ERROR("%s: failed to allocate mbus frame.\n", __PRETTY_FUNCTION__);
        return -1;
    }
    
    frame->control = MBUS_CONTROL_MASK_SND_UD | MBUS_CONTROL_MASK_DIR_M2S; 
    frame->address = address;
    frame->control_information = control_information;

    if (mbus_send_frame(handle, frame) == -1)
    {
        MBUS_ERROR("%s: failed to send mbus frame.\n", __PRETTY_FUNCTION__);
        retval = -1;
    }

    mbus_frame_free(frame);
    return retval;
}

//------------------------------------------------------------------------------
// send a request packet to from master to slave
//------------------------------------------------------------------------------
int
mbus_send_request_frame(mbus_handle * handle, int address)
{
    int retval = 0;
    mbus_frame *frame;

    frame = mbus_frame_new(MBUS_FRAME_TYPE_SHORT);
    
    if (frame == NULL)
    {
        MBUS_ERROR("%s: failed to allocate mbus frame.\n", __PRETTY_FUNCTION__);
        return -1;
    }
    
    frame->control = MBUS_CONTROL_MASK_REQ_UD2 | MBUS_CONTROL_MASK_DIR_M2S; 
    frame->address = address;

    if (mbus_send_frame(handle, frame) == -1)
    {
        MBUS_ERROR("%s: failed to send mbus frame.\n", __PRETTY_FUNCTION__);
        retval = -1;
    }

    mbus_frame_free(frame);
    return retval;
}

//------------------------------------------------------------------------------
// send a request from master to slave and collect the reply (replies)
// from the slave.
//------------------------------------------------------------------------------
int
mbus_sendrecv_request(mbus_handle *handle, int address, mbus_frame *reply, int max_frames)
{
    int retval = 0, more_frames = 1;
    mbus_frame_data reply_data;
    mbus_frame *frame, *next_frame;
    int frame_count = 0;

    frame = mbus_frame_new(MBUS_FRAME_TYPE_SHORT);
    
    if (frame == NULL)
    {
        MBUS_ERROR("%s: failed to allocate mbus frame.\n", __PRETTY_FUNCTION__);
        return -1;
    }
    
    frame->control = MBUS_CONTROL_MASK_REQ_UD2 | MBUS_CONTROL_MASK_DIR_M2S | MBUS_CONTROL_MASK_FCV; 
    frame->address = address;

    if (debug)
        printf("%s: debug: sending request frame\n", __PRETTY_FUNCTION__);
        
    if (mbus_send_frame(handle, frame) == -1)
    {
        MBUS_ERROR("%s: failed to send mbus frame.\n", __PRETTY_FUNCTION__);
        mbus_frame_free(frame);
        retval = -1;
    }
    
    //
    // continue to read until no more records are available (usually only one
    // reply frame, but can be more for so-called multi-telegram replies)
    //
    next_frame = reply;
    
    while (more_frames)
    {
        frame_count++;
        
        if ((max_frames  > 0) &&
            (frame_count > max_frames))
        {
            // only readout max_frames
            break;
        }
        
        if (debug)
            printf("%s: debug: receiving response frame #%d\n", __PRETTY_FUNCTION__, frame_count);
    
        if (mbus_recv_frame(handle, next_frame) == -1)
        {
            MBUS_ERROR("%s: Failed to receive M-Bus response frame.\n", __PRETTY_FUNCTION__);
            retval = 1;
            break;
        }
        
        //
        // We need to parse the data in the received frame to be able to tell
        // if more records are available or not.
        //       
        if (mbus_frame_data_parse(next_frame, &reply_data) == -1)
        {
            MBUS_ERROR("%s: M-bus data parse error.\n", __PRETTY_FUNCTION__);
            retval = 1;
            break;
        }
        
        //
        // Continue a cycle of sending requests and reading replies until the
        // reply do not have DIF=0x1F in the last record (which signals that 
        // more records are available.
        //
        
        if (reply_data.type == MBUS_DATA_TYPE_FIXED)
        {
            // only single frame replies for FIXED type frames
            more_frames = 0;
        }
        else
        {
            more_frames = 0;
    
            if (reply_data.data_var.more_records_follow)
            {
                if (debug)
                    printf("%s: debug: expecting more frames\n", __PRETTY_FUNCTION__);
                
                more_frames = 1;
                
                // allocate new frame and increment next_frame pointer
                next_frame->next = mbus_frame_new(MBUS_FRAME_TYPE_ANY);
                
                if (next_frame->next == NULL)
                {
                    MBUS_ERROR("%s: failed to allocate mbus frame.\n", __PRETTY_FUNCTION__);
                    retval = -1;
                    more_frames = 0;
                }
                
                next_frame = next_frame->next;
                        
                // need to send a new request and receive another reply
                             
                if (debug)
                    printf("%s: debug: resending request frame\n", __PRETTY_FUNCTION__);                

                // toogle FCB bit before
                frame->control ^= MBUS_CONTROL_MASK_FCB;
                if (mbus_send_frame(handle, frame) == -1)
                {
                    MBUS_ERROR("%s: failed to send mbus frame.\n", __PRETTY_FUNCTION__);
                    retval = -1;
                    more_frames = 0;
                }
            }
            else
            {
                if (debug)
                    printf("%s: debug: no more frames\n", __PRETTY_FUNCTION__);
            }
        }
        
        if (reply_data.data_var.record)
        {
            // free's up the whole list
            mbus_data_record_free(reply_data.data_var.record); 
        }        
    }

    mbus_frame_free(frame);
    return retval;
}


//------------------------------------------------------------------------------
// send a data request packet to from master to slave
//------------------------------------------------------------------------------
int
mbus_send_ping_frame(mbus_handle *handle, int address)
{
    int retval = 0;
    mbus_frame *frame;
        
    frame = mbus_frame_new(MBUS_FRAME_TYPE_SHORT);
    
    if (frame == NULL)
    {
        MBUS_ERROR("%s: failed to allocate mbus frame.\n", __PRETTY_FUNCTION__);
        return -1;
    }
    
    frame->control  = MBUS_CONTROL_MASK_SND_NKE | MBUS_CONTROL_MASK_DIR_M2S;
    frame->address  = address; 
   
    if (mbus_send_frame(handle, frame) == -1)
    {
        MBUS_ERROR("%s: failed to send mbus frame.\n", __PRETTY_FUNCTION__);
        retval = -1;
    }    
	
    mbus_frame_free(frame);
    return retval;
}

//------------------------------------------------------------------------------
// Prove for the presence of a device(s) using the supplied secondary address 
// (mask).
//------------------------------------------------------------------------------
int 
mbus_probe_secondary_address(mbus_handle * handle, const char *mask, char *matching_addr)
{
    int ret;
    mbus_frame reply;

    if (mask == NULL || matching_addr == NULL || strlen(mask) != 16)
    {
        MBUS_ERROR("%s: Invalid address masks.\n", __PRETTY_FUNCTION__);
        return MBUS_PROBE_ERROR;
    }

    /* send select command */
    if (mbus_send_select_frame(handle, mask) == -1)
    {
        MBUS_ERROR("%s: Failed to send selection frame: %s.\n",
                   __PRETTY_FUNCTION__,
                   mbus_error_str());
        return MBUS_PROBE_ERROR;
    }

    ret = mbus_recv_frame(handle, &reply);

    if (ret == -1)
    {
        return MBUS_PROBE_NOTHING;
    }    

    if (ret == -2)
    {
        return MBUS_PROBE_COLLISION;
    }

    if (mbus_frame_type(&reply) == MBUS_FRAME_TYPE_ACK)
    {
        /* send a data request command to find out the full address */
        if (mbus_send_request_frame(handle, 253) == -1)
	    {
            MBUS_ERROR("%s: Failed to send request to selected secondary device [mask %s]: %s.\n",
                       __PRETTY_FUNCTION__,
                       mask,
                       mbus_error_str());
            return MBUS_PROBE_ERROR;
        }

        if (mbus_recv_frame(handle, &reply) == -1)
        {
            return MBUS_PROBE_NOTHING;
        }

        if (mbus_frame_type(&reply) != MBUS_FRAME_TYPE_ACK)
        {
            snprintf(matching_addr, 17, "%s", mbus_frame_get_secondary_address(&reply));
            return MBUS_PROBE_SINGLE;
        }
        else
        {
            MBUS_ERROR("%s: Unexpected reply for address [mask %s]. Got ACK, expected data.\n",
                       __PRETTY_FUNCTION__, mask);
            return MBUS_PROBE_NOTHING;
        }
    }

    MBUS_ERROR("%s: Unexpected reply for address [%s].\n", __PRETTY_FUNCTION__, mask);

    return MBUS_PROBE_NOTHING;
}


int mbus_read_slave(mbus_handle * handle, mbus_address *address, mbus_frame * reply)
{
    if (address->is_primary)
    {
        if (mbus_send_request_frame(handle, address->primary) == -1)
        {
            MBUS_ERROR("%s: Failed to send M-Bus request frame.\n",
                       __PRETTY_FUNCTION__);
            return -1;
        }
    }
    else
    {
        /* secondary addressing */
        int probe_ret;
        char matching_addr[16];

        if (address->secondary == NULL)
        {
            MBUS_ERROR("%s: Secondary address not set.\n",
                       __PRETTY_FUNCTION__);
            return -1;
        }

        probe_ret = mbus_probe_secondary_address(handle, address->secondary, matching_addr);

        if (probe_ret == MBUS_PROBE_COLLISION)
        {
            MBUS_ERROR("%s: The address mask [%s] matches more than one device.\n",
                       __PRETTY_FUNCTION__,
                       address->secondary);
            return -1;
        }
        else if (probe_ret == MBUS_PROBE_NOTHING)
        {
            MBUS_ERROR("%s: The selected secondary address [%s] does not match any device.\n",
                       __PRETTY_FUNCTION__,
                       address->secondary);
            return -1;
        }
        else if (probe_ret == MBUS_PROBE_ERROR)
        {
            MBUS_ERROR("%s: Failed to probe secondary address [%s].\n",
                       __PRETTY_FUNCTION__,
                       address->secondary);
            return -1;    
        }
        /* else MBUS_PROBE_SINGLE */

        if (mbus_send_request_frame(handle, 253) == -1)
        {
            MBUS_ERROR("%s: Failed to send M-Bus request frame.\n",
                       __PRETTY_FUNCTION__);
            return -1;
        }
    } 

    if (mbus_recv_frame(handle, reply) == -1)
    {
        MBUS_ERROR("%s: Failed to receive M-Bus response frame.\n",
                   __PRETTY_FUNCTION__);
        return -1;
    }    

    return 0;
}

//------------------------------------------------------------------------------
// Iterate over all address masks according to the M-Bus probe algorithm.
//------------------------------------------------------------------------------
int
mbus_scan_2nd_address_range(mbus_handle * handle, int pos, char *addr_mask)
{
    int i, i_start, i_end, probe_ret;
    char *mask, matching_mask[17];

    if (strlen(addr_mask) != 16)
    {
        fprintf(stderr, "%s: Illegal address mask [%s]. Not 16 characters long.\n", __PRETTY_FUNCTION__, addr_mask);
        return -1;
    }

    if (pos < 0 || pos >= 16)
    {
        return 0;
    }

    if ((mask = strdup(addr_mask)) == NULL)
    {
        fprintf(stderr, "%s: Failed to allocate local copy of the address mask.\n", __PRETTY_FUNCTION__);
        return -1;
    }
    
    if (mask[pos] == 'f' || mask[pos] == 'F')
    {
        i_start = 0;
        i_end   = 9;
    }
    else
    {
        if (pos < 15)
        {
            mbus_scan_2nd_address_range(handle, pos+1, mask);
        }
        else
        {
            i_start = (int)(mask[pos] - '0');
            i_end   = (int)(mask[pos] - '0');
        }
    }

    for (i = 0; i <= 9; i++)
    {
        mask[pos] = '0'+i;
    
        probe_ret = mbus_probe_secondary_address(handle, mask, matching_mask);

        if (probe_ret == MBUS_PROBE_SINGLE)
        {
            printf("Found a device on secondary address %s [using address mask %s]\n", matching_mask, mask);
        }
        else if (probe_ret == MBUS_PROBE_COLLISION)
        {
            // collision, more than one device matching, restrict the search mask further
            mbus_scan_2nd_address_range(handle, pos+1, mask);
        }
        else if (probe_ret == MBUS_PROBE_NOTHING)
        {
             // nothing... move on to next address mask
        }
        else // MBUS_PROBE_ERROR
        {
            fprintf(stderr, "%s: Failed to probe secondary address [%s].\n", __PRETTY_FUNCTION__, mask);
            return -1;    
        }
    }

    free(mask);
    return 0;
}
