#include <math.h>  // but not all functions require it to be linked

#include "chip_conv.h"
#include "pow.h"
#include "quantity.h"

#include <stdio.h>

//#include <stdio.h> 

extern int g_deep_trace;
#ifndef _WIN32
    #define DEBUG_PRINT(fmt, args...) if (g_deep_trace) \
        fprintf(stderr, "TRACE: %s:%d:%s(): " fmt, \
                __FILE__, __LINE__, __func__, ##args)
#else
    #define DEBUG_PRINT(fmt, ...) ((void)0)
#endif

#ifdef ENABLE_ALL_CHIPS

    // Not on firmware; can read static arrays directly
    #define PROGMEM
    #define PF(array, i) ((array)[(i)])

#else

    // On firmware; static arrays are in PROGMEM
    #include <avr/pgmspace.h>

    #if __SIZEOF_DOUBLE__ == 4

        #define PF(array, i) (pgm_read_float((array) + (i)))

    #elif __SIZEOF_DOUBLE__ == 8

        #pragma GCC diagnostic push
        #pragma GCC diagnostic ignored "-Wunused-function"

        static double PF(const void *array, size_t i) {
            union {
                double value;
                struct {
                    uint32_t lo;
                    uint32_t hi;
                };
            } u;
            const void *p = array + (i*8);
            u.lo = pgm_read_dword(p);
            u.hi = pgm_read_dword(p + 4);
            return u.value;
        }

        #pragma GCC diagnostic pop

    #else
        #error Unsupported sizeof(double)
    #endif

#endif

#ifndef ARRAY_SIZE
    #define ARRAY_SIZE(arr) (sizeof((arr)) / sizeof((arr)[0]))
#endif


#ifdef ENABLE_ALL_CHIPS

chip_conv_raw_fn_t chip_conv_raw_fn(uint16_t chip_id) {

    switch (chip_id) {

        case USBTENKI_CHIP_SHT31_T:
        case USBTENKI_CHIP_SHT31_T_INTERNAL:
        case USBTENKI_CHIPX_SHT3X_T:
            return sht31_t_conv_raw;

        case USBTENKI_CHIP_MCP9800:
            return mcp9800_conv_raw;

        case USBTENKI_CHIP_SHT31_RH:
        case USBTENKI_CHIP_SHT31_RH_INTERNAL:
        case USBTENKI_CHIPX_SHT3X_RH:
            return sht31_rh_conv_raw;

        case USBTENKI_CHIP_CC2_T:
            return cc2_t_conv_raw;

        case USBTENKI_CHIP_CC2_RH:
            return cc2_rh_conv_raw;

        case USBTENKI_CHIP_CCS811_TVOC:
            return ccs811_tvoc_conv_raw;

        case USBTENKI_CHIP_CCS811_eCO2:
            return ccs811_eco2_conv_raw;

        case USBTENKI_CHIP_MS5611_P:
        case USBTENKI_CHIPX_MS5611_P:        
            return ms5611_p_conv_raw;

        case USBTENKI_CHIP_MS5611_T:
        case USBTENKI_CHIPX_MS5611_T:
            return ms5611_t_conv_raw;

        case USBTENKI_CHIP_SCD30_T:
            return scd30_t_conv_raw;

        case USBTENKI_CHIP_SCD30_RH:
            return scd30_rh_conv_raw;

        case USBTENKI_CHIP_SCD30_CO2:
        case USBTENKI_CHIP_SCD30_CO2_CAL:
            return scd30_co2_conv_raw;

        case USBTENKI_CHIP_SPS30_MC_PM1_0:
        case USBTENKI_CHIP_SPS30_MC_PM2_5:
        case USBTENKI_CHIP_SPS30_MC_PM4_0:
        case USBTENKI_CHIP_SPS30_MC_PM10:
            return sps30_mc_conv_raw;

        case USBTENKI_CHIP_SPS30_NC_PM0_5:
        case USBTENKI_CHIP_SPS30_NC_PM1_0:
        case USBTENKI_CHIP_SPS30_NC_PM2_5:
        case USBTENKI_CHIP_SPS30_NC_PM4_0:
        case USBTENKI_CHIP_SPS30_NC_PM10:
            return sps30_nc_conv_raw;

        case USBTENKI_CHIP_SPS30_TYP_PART_SIZE:
            return sps30_part_size_conv_raw;

        case USBTENKI_CHIP_THC_TYPE_K:
        case USBTENKI_CHIP_THC_TYPE_J:
        case USBTENKI_CHIP_THC_TYPE_T:
        case USBTENKI_CHIP_THC_TYPE_N:
        case USBTENKI_CHIP_THC_TYPE_S:
        case USBTENKI_CHIP_THC_TYPE_E:
        case USBTENKI_CHIP_THC_TYPE_B:
        case USBTENKI_CHIP_THC_TYPE_R:
        case USBTENKI_CHIP_THC_HOT:
        case USBTENKI_CHIP_THC_COLD:
            return thc_conv_raw;

        case USBTENKI_CHIP_PT100_RTD:
            return pt100_rtd_conv_raw;

        case USBTENKI_CHIP_RTD300_PT100_3W:
        case USBTENKI_CHIPX_PT100_TMP:
            return rtd300_pt100_3w_conv_raw;

        case USBTENKI_CHIP_RTD300_PT100_2W:
            return rtd300_pt100_2w_conv_raw;

        case USBTENKI_CHIP_RTD300_PT1000_3W:
            return rtd300_pt1000_3w_conv_raw;

        case USBTENKI_CHIP_TMC200_TYPE_K:
            return tmc200_type_k_conv_raw;

        case USBTENKI_CHIP_TMC200_TYPE_N:
            return tmc200_type_n_conv_raw;
        
        case USBTENKI_CHIP_TMC200_TYPE_T:
            return tmc200_type_t_conv_raw;

        case USBTENKI_CHIP_TMC200_TYPE_E:
            return tmc200_type_e_conv_raw;

        case USBTENKI_CHIP_TMC200_TYPE_J:
            return tmc200_type_j_conv_raw;

        case USBTENKI_CHIP_TMC200_COLD:
            return tmc200_cold_conv_raw;

        case USBTENKI_CHIP_IR:
        case USBTENKI_CHIP_GREEN:
        case USBTENKI_CHIP_BLUE:
        case USBTENKI_CHIP_RED:
            return apds9250_conv_raw;

        case USBTENKI_CHIP_VEML6030_ALS:
            return veml6030_als_conv_raw;

        case USBTENKI_CHIP_VEML6030_WHITE:
            return veml6030_white_conv_raw;

        case USBTENKI_CHIP_VEML6075_UVA:
            return veml6075_uva_conv_raw;

        case USBTENKI_CHIP_VEML6075_UVB:
            return veml6075_uvb_conv_raw;

        case USBTENKI_CHIP_CO2_DXC200_FILTERED:
        case USBTENKI_CHIP_CO2_DXC200_INSTANT:
        case USBTENKI_CHIP_CO2_DXC300_COMPENSATED:
            return co2_dxc200_conv_raw;

        case USBTENKI_CHIP_CO2_DXC300_RAW:
            return co2_dxc300_conv_raw;

        case USBTENKI_CHIP_TSL2561_IR:
        case USBTENKI_CHIP_TSL2561_IR_16X:
        case USBTENKI_CHIP_TSL2561_IR_VISIBLE:
        case USBTENKI_CHIP_TSL2561_IR_VISIBLE_16X:
            return tsl2561_conv_raw;

        case USBTENKI_CHIP_TSL2568_IR:
        case USBTENKI_CHIP_TSL2568_IR_16X:
        case USBTENKI_CHIP_TSL2568_IR_VISIBLE:
        case USBTENKI_CHIP_TSL2568_IR_VISIBLE_16X:
            return tsl2568_conv_raw;

        case USBTENKI_CHIP_CO2_DXC300_P_COMP:
            return p_comp_dxc300_conv_raw;

        case USBTENKI_CHIP_CO2_DXC300_T_COMP:
            return t_comp_dxc300_conv_raw;

        case USBTENKI_CHIP_CO2_DXC300_RH_COMP:
            return rh_comp_dxc300_conv_raw;
    }

    return unknown_chip_conv_raw;

}

#endif


#define RETURN_ERROR(error) { \
    result->value_error = (error); \
    result->type = QUANTITY_TYPE_ERROR; \
    return (error); \
}


/**
 * Return an error without setting the result
 */
_CHIP_CONV_RAW(unknown_chip) {

    RETURN_ERROR(CHIP_ERROR_UNKNOWN_CHIP);

}

#if defined ENABLE_CHIP_SHT31_T || defined ENABLE_CHIP_SHT31_T_INTERNAL || defined ENABLE_CHIPX_SHT3X_T
_CHIP_CONV_RAW(sht31_t) {

    if (raw_len < 2) {
        RETURN_ERROR(CHIP_ERROR_INVALID_DATA);
    }

    uint32_t t_reg = ((uint32_t)((raw_data[0] & 0xFF) << 8)) | (uint32_t)(raw_data[1] & 0xFF);
    if (!t_reg) {
        RETURN_ERROR(CHIP_ERROR_SENSOR_ERROR);
    }
    result->value_float = ((175 * t_reg) / 65535.0) - 45.0;
    result->type = QUANTITY_TYPE_FLOAT;
    result->unit = UNIT_CELSIUS;

    return 0;

}
#endif

#if defined ENABLE_CHIP_SHT31_RH || defined ENABLE_CHIP_SHT31_RH_INTERNAL || defined ENABLE_CHIPX_SHT3X_RH
_CHIP_CONV_RAW(sht31_rh) {

    if (raw_len < 2) {
        RETURN_ERROR(CHIP_ERROR_INVALID_DATA);
    }

    uint16_t rh_reg = ((uint16_t)((raw_data[0] & 0xFF) << 8)) | (uint16_t)(raw_data[1] & 0xFF);
    if (!rh_reg) {
        RETURN_ERROR(CHIP_ERROR_SENSOR_ERROR);
    }
    result->value_float = 100.0 * ((double)rh_reg / 65535.0);
    result->type = QUANTITY_TYPE_FLOAT;
    result->unit = UNIT_RH;

    return 0;

}
#endif

#if defined ENABLE_CHIP_MCP9800
_CHIP_CONV_RAW(mcp9800) {

    if (raw_len < 2) {
        RETURN_ERROR(CHIP_ERROR_INVALID_DATA);
    }

    // 12-bit signed integer (two's complement)
    uint8_t hi = raw_data[0];
    uint8_t lo = raw_data[1];
    int16_t word = ((uint16_t)hi << 4) | (lo >> 4);

    if (hi & 0x80) {
        // sign bit is set, indicating a negative value
        // restore two's complement notation by setting the top 4 bits
        word |= 0xf000;
    }

    result->value_float = ((double)word) * 0x1p-4;
    result->type = QUANTITY_TYPE_FLOAT;
    result->unit = UNIT_CELSIUS;

    return 0;

}
#endif

#if defined ENABLE_CHIP_CC2_T
_CHIP_CONV_RAW(cc2_t) {

    if (raw_len < 2) {
        RETURN_ERROR(CHIP_ERROR_INVALID_DATA);
    }

    // ChipCap2 Application guide
    uint16_t t_reg;
    t_reg = ((uint16_t)raw_data[0]) << 6;
    t_reg |= ((uint16_t)raw_data[1]) >> 2;
    double temperature = t_reg / 0x1p14 * 165 - 40;

    result->value_float = temperature;
    result->type = QUANTITY_TYPE_FLOAT;
    result->unit = UNIT_CELSIUS;

    return 0;

}
#endif

#if defined ENABLE_ALL_CHIPS || defined ENABLE_CHIP_CC2_RH
_CHIP_CONV_RAW(cc2_rh) {

    if (raw_len < 2) {
        RETURN_ERROR(CHIP_ERROR_INVALID_DATA);
    }

    // ChipCap2 Application guide
    uint16_t rh_reg;
    rh_reg = ((uint16_t)(raw_data[0] & 0x3f)) << 8;
    rh_reg |= (uint16_t)raw_data[1];
    double rh = rh_reg / 0x1p14 * 100;

    result->value_float = rh;
    result->type = QUANTITY_TYPE_FLOAT;
    result->unit = UNIT_RH;

    return 0;

}
#endif

#if defined ENABLE_CHIP_CCS811_TVOC
_CHIP_CONV_RAW(ccs811_tvoc) {

    if (raw_len < 4) {
        RETURN_ERROR(CHIP_ERROR_INVALID_DATA);
    }

    uint8_t data_valid = raw_data[0];

    if (!data_valid) {
        RETURN_ERROR(CHIP_ERROR_INVALID_DATA);
    }

    uint16_t TVOC = raw_data[1]<<8 | raw_data[2];

    result->value_uint32 = TVOC;
    result->type = QUANTITY_TYPE_UINT32;
    result->unit = UNIT_PPB;

    return 0;

}
#endif

#if defined ENABLE_CHIP_CCS811_eCO2
_CHIP_CONV_RAW(ccs811_eco2) {

    if (raw_len < 4) {
        RETURN_ERROR(CHIP_ERROR_INVALID_DATA);
    }

    uint8_t data_valid = raw_data[0];

    if (!data_valid) {
        RETURN_ERROR(CHIP_ERROR_INVALID_DATA);
    }

    uint16_t eCO2 = ((uint16_t)raw_data[1] << 8) | (uint16_t)raw_data[2];

    result->value_uint32 = eCO2;
    result->type = QUANTITY_TYPE_UINT32;
    result->unit = UNIT_PPM;

    return 0;

}
#endif

#if defined ENABLE_CHIP_MS5611_P || defined ENABLE_CHIPX_MS5611_P
_CHIP_CONV_RAW(ms5611_p) {

    if (raw_len < 6 || cal_len < 12) {
        RETURN_ERROR(CHIP_ERROR_INVALID_DATA);
    }

    uint32_t D1 = ((uint32_t)raw_data[0]) | (((uint32_t)raw_data[1]) << 8) | (((uint32_t)raw_data[2]) << 16);
    uint32_t D2 = ((uint32_t)raw_data[3]) | (((uint32_t)raw_data[4]) << 8) | (((uint32_t)raw_data[5]) << 16);

    uint16_t c1 = (uint16_t)cal_data[0]  | ((uint16_t)cal_data[1] << 8);
    uint16_t c2 = (uint16_t)cal_data[2]  | ((uint16_t)cal_data[3] << 8);
    uint16_t c3 = (uint16_t)cal_data[4]  | ((uint16_t)cal_data[5] << 8);
    uint16_t c4 = (uint16_t)cal_data[6]  | ((uint16_t)cal_data[7] << 8);
    uint16_t c5 = (uint16_t)cal_data[8]  | ((uint16_t)cal_data[9] << 8);
    uint16_t c6 = (uint16_t)cal_data[10] | ((uint16_t)cal_data[11] << 8);

    int64_t dT = (int64_t)D2 - ((int64_t)c5 * 256);
    int64_t TEMP = 2000LL + dT * ((int64_t)c6) / (1LL << 23);

    int64_t OFF = (int64_t)c2 * 65536LL + ((int64_t)c4 * dT) / 128;
    int64_t SENS = (int64_t)c1 * 32768LL + ((int64_t)c3 * dT) / 256;

    if (TEMP < 2000) {
        // Low temperature
        //int64_t T2 = dT * dT / (1LL << 31);
        int64_t OFF2 = 5 * ((TEMP - 2000) * (TEMP - 2000)) / 2;
        int64_t SENS2 = OFF2 / 2;
        // Very low temperature
        if (TEMP < -1500) {
            OFF2 = OFF2 + 7 * (TEMP + 1500) * (TEMP + 1500);
            SENS2 = SENS2 + 11 * ((TEMP + 1500) * (TEMP + 1500)) / 2;
        }
        //TEMP = TEMP - T2;
        OFF = OFF - OFF2;
        SENS = SENS - SENS2;
    }

    int64_t P = ((int64_t)D1 * SENS / (1LL << 21) - OFF) / (1LL << 15);

    result->value_int32 = P;
    result->type = QUANTITY_TYPE_INT32;
    result->unit = UNIT_PA;

    return 0;

}
#endif

#if defined ENABLE_CHIP_MS5611_T || defined ENABLE_CHIPX_MS5611_T
_CHIP_CONV_RAW(ms5611_t) {

    if (raw_len < 3 || cal_len < 12) {
        RETURN_ERROR(CHIP_ERROR_INVALID_DATA);
    }

    uint32_t D2 = (uint32_t)raw_data[0] | (((uint32_t)raw_data[1]) << 8) | (((uint32_t)raw_data[2]) << 16);

    uint16_t c5 = (uint16_t)cal_data[8]  | ((uint16_t)cal_data[9] << 8);
    uint16_t c6 = (uint16_t)cal_data[10] | ((uint16_t)cal_data[11] << 8);

    int64_t dT = (int64_t)D2 - ((int64_t)c5 * 256);
    int64_t TEMP = 2000LL + dT * ((int64_t)c6) / (1LL << 23);

    if (TEMP < 2000) {
        // Low temperature
        int64_t T2 = dT * dT / (1LL << 31);
        TEMP = TEMP - T2;
    }

    result->value_float = TEMP / 100.0;
    result->type = QUANTITY_TYPE_FLOAT;
    result->unit = UNIT_CELSIUS;

    return 0;

}
#endif

#if \
    defined ENABLE_CHIP_SCD30_T || \
    defined ENABLE_CHIP_SCD30_RH || \
    defined ENABLE_CHIP_SCD30_CO2 || \
    defined ENABLE_CHIP_SCD30_CO2_CAL || \
    defined ENABLE_CHIP_SPS30_MC_PM1_0 || \
    defined ENABLE_CHIP_SPS30_MC_PM2_5 || \
    defined ENABLE_CHIP_SPS30_MC_PM4_0 || \
    defined ENABLE_CHIP_SPS30_MC_PM10 || \
    defined ENABLE_CHIP_SPS30_NC_PM0_5 || \
    defined ENABLE_CHIP_SPS30_NC_PM1_0 || \
    defined ENABLE_CHIP_SPS30_NC_PM2_5 || \
    defined ENABLE_CHIP_SPS30_NC_PM4_0 || \
    defined ENABLE_CHIP_SPS30_NC_PM10 || \
    defined ENABLE_CHIP_SPS30_TYP_PART_SIZE
static inline uint32_t raw_be32toh(uint8_t *raw_data) {

    uint32_t bits =
        ((uint32_t)raw_data[0] << 24) |
        ((uint32_t)raw_data[1] << 16) |
        ((uint32_t)raw_data[2] <<  8) |
        ((uint32_t)raw_data[3]      ) ;

    return bits;

}
#endif

#if defined ENABLE_CHIP_SCD30_T
_CHIP_CONV_RAW(scd30_t) {

    if (raw_len < 4) {
        RETURN_ERROR(CHIP_ERROR_INVALID_DATA);
    }

    result->value_uint32 = raw_be32toh(raw_data);  // actually a float
    result->type = QUANTITY_TYPE_FLOAT;
    result->unit = UNIT_CELSIUS;

    return 0;

}
#endif

#if defined ENABLE_CHIP_SCD30_RH
_CHIP_CONV_RAW(scd30_rh) {

    if (raw_len < 4) {
        RETURN_ERROR(CHIP_ERROR_INVALID_DATA);
    }

    result->value_uint32 = raw_be32toh(raw_data);  // actually a float
    result->type = QUANTITY_TYPE_FLOAT;
    result->unit = UNIT_RH;

    return 0;

}
#endif

#if defined ENABLE_CHIP_SCD30_CO2 || defined ENABLE_CHIP_SCD30_CO2_CAL
_CHIP_CONV_RAW(scd30_co2) {

    if (raw_len < 4) {
        RETURN_ERROR(CHIP_ERROR_INVALID_DATA);
    }

    result->value_uint32 = raw_be32toh(raw_data);  // actually a float
    result->type = QUANTITY_TYPE_FLOAT;
    result->unit = UNIT_PPM;

    return 0;

}
#endif

#if \
    defined ENABLE_CHIP_SPS30_MC_PM1_0 || \
    defined ENABLE_CHIP_SPS30_MC_PM2_5 || \
    defined ENABLE_CHIP_SPS30_MC_PM4_0 || \
    defined ENABLE_CHIP_SPS30_MC_PM10
_CHIP_CONV_RAW(sps30_mc) {

    if (raw_len < 4) {
        RETURN_ERROR(CHIP_ERROR_INVALID_DATA);
    }

    result->value_uint32 = raw_be32toh(raw_data);  // actually a float
    result->type = QUANTITY_TYPE_FLOAT;
    result->unit = UNIT_uG_PER_M3;

    return 0;

}
#endif

#if \
    defined ENABLE_CHIP_SPS30_NC_PM0_5 || \
    defined ENABLE_CHIP_SPS30_NC_PM1_0 || \
    defined ENABLE_CHIP_SPS30_NC_PM2_5 || \
    defined ENABLE_CHIP_SPS30_NC_PM4_0 || \
    defined ENABLE_CHIP_SPS30_NC_PM10
_CHIP_CONV_RAW(sps30_nc) {

    if (raw_len < 4) {
        RETURN_ERROR(CHIP_ERROR_INVALID_DATA);
    }

    result->value_uint32 = raw_be32toh(raw_data);  // actually a float
    result->type = QUANTITY_TYPE_FLOAT;
    result->unit = UNIT_COUNT_PER_CM3;

    return 0;

}
#endif

#if defined ENABLE_CHIP_SPS30_TYP_PART_SIZE
_CHIP_CONV_RAW(sps30_part_size) {

    if (raw_len < 4) {
        RETURN_ERROR(CHIP_ERROR_INVALID_DATA);
    }

    result->value_uint32 = raw_be32toh(raw_data);  // actually a float
    result->type = QUANTITY_TYPE_FLOAT;
    result->unit = UNIT_MICROMETER;

    return 0;

}
#endif

#if \
    defined ENABLE_CHIP_THC_TYPE_K || \
    defined ENABLE_CHIP_THC_TYPE_J || \
    defined ENABLE_CHIP_THC_TYPE_T || \
    defined ENABLE_CHIP_THC_TYPE_N || \
    defined ENABLE_CHIP_THC_TYPE_S || \
    defined ENABLE_CHIP_THC_TYPE_E || \
    defined ENABLE_CHIP_THC_TYPE_B || \
    defined ENABLE_CHIP_THC_TYPE_R || \
    defined ENABLE_CHIP_THC_HOT || \
    defined ENABLE_CHIP_THC_COLD
_CHIP_CONV_RAW(thc) {

    if (raw_len < 2) {
        RETURN_ERROR(CHIP_ERROR_INVALID_DATA);
    }

    uint16_t t_reg = ((uint16_t)raw_data[0]) << 8 | (uint16_t)raw_data[1];

    result->value_float = ((int16_t)t_reg) * 0.0625;
    result->type = QUANTITY_TYPE_FLOAT;
    result->unit = UNIT_CELSIUS;

    return 0;

}
#endif

// RTD helper functions
#if \
    defined ENABLE_CHIP_PT100_RTD || \
    defined ENABLE_CHIPX_PT100_TMP || \
    defined ENABLE_CHIP_RTD300_PT100_3W || \
    defined ENABLE_CHIP_RTD300_PT100_2W || \
    defined ENABLE_CHIP_TMC200_TYPE_K || \
    defined ENABLE_CHIP_TMC200_TYPE_T || \
    defined ENABLE_CHIP_TMC200_TYPE_N || \
    defined ENABLE_CHIP_TMC200_TYPE_E || \
    defined ENABLE_CHIP_TMC200_TYPE_J || \
    defined ENABLE_CHIP_TMC200_COLD

/* Calculate the theoric resistance of an RTD for a given temperature. */
static double temp_to_pt100_r(double temp)
{
    // DIN 43760
    static const double r0 PROGMEM = 100;
    static const double a PROGMEM = 3.9080e-3;
    static const double b PROGMEM = -5.8019e-7;
    static const double c PROGMEM = -4.2735e-12;

    double temp_2 = temp * temp;

    if (temp > 0) {
        return r0 * (1 + a*temp + b*temp_2);
    } else {
        double temp_3 = temp_2 * temp;
        return r0 * (1 + a*temp + b*temp_2 + c*temp_3);
    }
}

/* Recusively zoom-in a temperature to resistance match. */
static double _searchTempFromR(double r, double t_start, double step) {

    double t;
    double sr;

    sr = temp_to_pt100_r(t_start);
    if (sr > r) {
        return -999;
    }

    // looks like we are close enough.
    if (step < 0.00001)
        return t_start;

    for (t=t_start; t<1000; t+=step)
    {
        sr = temp_to_pt100_r(t);

        if (sr > r) {
            return _searchTempFromR(r, t-step, step/10.0);
        }
    }

    return -999;
}

/* Using a recursive algorithm, find the RTD temperature from its resistance. */
static inline double searchTempFromR(double r) {

    static const double a PROGMEM = 3.9080e-3;
    static const double b PROGMEM = -5.8019e-7;

    if (r > 100) {
        return (-a + sqrt((a*a)-4*b*(1-(r/100.0))) ) / (2*b);
    }

    // completes after approx. 40 calls to temp_to_pt100_r
    return _searchTempFromR(r, -274, 100);

}

#endif

#if defined ENABLE_CHIP_PT100_RTD

_CHIP_CONV_RAW(pt100_rtd) {

    uint64_t raw_ch0, raw_ch1;
    double volts_ch0, volts_ch1;
    double i_src = 0.001; // 1mA
    double r_wire;
    double rt;
    double r_pt100;
    static const double lsb PROGMEM = 15.625e-6; // 15.625 uV
    static const double chn0_gain PROGMEM = 2.0;
    static const double chn1_gain PROGMEM = 8.0;
    static const double ferrite_r PROGMEM = 0.0;

    if (raw_len < 6) {
        RETURN_ERROR(CHIP_ERROR_INVALID_DATA);
    }

    // Assign with most significant bits aligned (signed).
    // Even though the negative inputs of the ADC are at GND,
    // the conversion can still result venture below 0 (values of -1 or -2). This
    // happens when using 2 wire RTDs because we short CH1 to GND.
    raw_ch0 = ( (((uint64_t)raw_data[0] & 0x03) << 30) | ((uint64_t)raw_data[1] << 22) | ((uint64_t)raw_data[2] << 14) );
    raw_ch0 >>= 14;

    raw_ch1 = ( (((uint64_t)raw_data[3] & 0x03) << 30) | ((uint64_t)raw_data[4] << 22) | ((uint64_t)raw_data[5] << 14) );
    raw_ch1 >>= 14;

    // CALIBRATION
    //
    // The most significant factor seems to be the current source precision
    // of 1%. The ADC gain error and offset has almost no influence.
    //
    if (cal_len > 0) {
        if (cal_len < 2) {
            RETURN_ERROR(CHIP_ERROR_INVALID_DATA);
        }
        double current_error = 0;
        signed short cal_value = cal_data[0] | (cal_data[1]<<8);
        current_error = (double)(cal_value) / 10000000.0;
        i_src += current_error;
    }

    volts_ch0 = raw_ch0 * lsb / chn0_gain;
    volts_ch1 = raw_ch1 * lsb / chn1_gain;
    r_wire = volts_ch1 / i_src;
    rt = volts_ch0 / i_src;
    r_pt100 = rt - r_wire * 2  - ferrite_r;

    result->value_float = searchTempFromR(r_pt100);
    result->type = QUANTITY_TYPE_FLOAT;
    result->unit = UNIT_CELSIUS;

    return 0;

}
#endif

#if defined ENABLE_CHIP_RTD300_PT100_3W || defined ENABLE_CHIPX_PT100_TMP
_CHIP_CONV_RAW(rtd300_pt100_3w) {

    uint32_t output_code;
    uint16_t ref_res_base;
    int16_t ref_trim;
    double rtd_res;
    uint16_t gain;
    double ref_res;

    if (raw_len < 8) {
        RETURN_ERROR(CHIP_ERROR_INVALID_DATA);
    }

    output_code = ((uint32_t)raw_data[0] << 16) | ((uint32_t)raw_data[1] << 8) | (uint32_t)(raw_data[2]);
    ref_res_base = (uint16_t)raw_data[3] << 8;
    ref_res_base |= (uint16_t)raw_data[4];
    gain = 1 << (raw_data[5]>>4);  // between 1 and 2^15
    ref_trim = (uint16_t)raw_data[6] << 8;
    ref_trim |= (uint16_t)raw_data[7];
    ref_res = ref_res_base + ref_trim / 8192.0;
    rtd_res = ref_res * output_code / (0x1p23 * gain);

    result->value_float = searchTempFromR(rtd_res);
    result->type = QUANTITY_TYPE_FLOAT;
    result->unit = UNIT_CELSIUS;

    return 0;

}
#endif

#if \
    defined ENABLE_CHIP_RTD300_PT100_2W || \
    defined ENABLE_CHIP_TMC200_TYPE_K || \
    defined ENABLE_CHIP_TMC200_TYPE_N || \
    defined ENABLE_CHIP_TMC200_TYPE_T || \
    defined ENABLE_CHIP_TMC200_TYPE_E || \
    defined ENABLE_CHIP_TMC200_TYPE_J || \
    defined ENABLE_CHIP_TMC200_COLD

static const double REF PROGMEM = 2.5;
static const double IREFS[10] PROGMEM = { 0,10,50,100,250,500,750,1000,1500,2000 };

#endif

#if defined ENABLE_CHIP_RTD300_PT100_2W
_CHIP_CONV_RAW(rtd300_pt100_2w) {

    uint32_t output_code;
    double rtd_res;
    uint16_t gain;
    double iref = 0.000500;

    if (raw_len < 6) {
        RETURN_ERROR(CHIP_ERROR_INVALID_DATA);
    }

    output_code = ((uint32_t)raw_data[0] << 16) | ((uint32_t)raw_data[1] << 8) | ((uint32_t)raw_data[2]);
    gain = 1 << (raw_data[5]>>4);  // between 1 and 2^15

    if ((raw_data[5] & 0xf) >= 10) {
        RETURN_ERROR(CHIP_ERROR_INVALID_DATA);
    }

    iref = PF(IREFS, raw_data[5] & 0xf) / 1000000.0;
    rtd_res = (output_code / 0x1p23 / gain * REF) / iref;

    result->value_float = searchTempFromR(rtd_res);
    result->type = QUANTITY_TYPE_FLOAT;
    result->unit = UNIT_CELSIUS;

    return 0;

}
#endif

#if defined ENABLE_CHIP_RTD300_PT1000_3W
_CHIP_CONV_RAW(rtd300_pt1000_3w) {

    uint32_t output_code;
    uint16_t ref_res_base;
    int16_t ref_trim;
    double rtd_res;
    uint16_t gain;
    double ref_res;

    if (raw_len < 8) {
        RETURN_ERROR(CHIP_ERROR_INVALID_DATA);
    }

    output_code = ((uint32_t)raw_data[0] << 16) | ((uint32_t)raw_data[1] << 8) | ((uint32_t)raw_data[2]);
    ref_res_base = (uint16_t)raw_data[3] << 8;
    ref_res_base |= (uint16_t)raw_data[4];
    gain = 1 << (raw_data[5]>>4);  // between 1 and 2^15
    ref_trim = (uint16_t)raw_data[6] << 8;
    ref_trim |= (uint16_t)raw_data[7];
    ref_res = ref_res_base + ref_trim / 8192.0;
    rtd_res = ref_res * output_code / (0x1p23 * gain);

    result->value_float = rtd_res;
    result->type = QUANTITY_TYPE_FLOAT;
    result->unit = UNIT_CELSIUS;

    return 0;

}
#endif

#if \
    defined ENABLE_CHIP_TMC200_TYPE_K || \
    defined ENABLE_CHIP_TMC200_TYPE_N || \
    defined ENABLE_CHIP_TMC200_TYPE_T || \
    defined ENABLE_CHIP_TMC200_TYPE_E || \
    defined ENABLE_CHIP_TMC200_TYPE_J || \
    defined ENABLE_CHIP_TMC200_COLD

static double getColdTempAlt(uint8_t *raw_data)
{
    uint32_t output_code;
    double rtd_res;
    uint16_t gain;
    double temp_raw;
    double temperature;
    int16_t ref_trim;

    double Iref;

    output_code = ((uint32_t)raw_data[1] << 16) | ((uint32_t)raw_data[2] << 8) | (uint32_t)raw_data[3];
    gain = 1 << (raw_data[6]>>4);
    Iref = PF(IREFS, raw_data[6] & 0xf) / 1.0e6;
    ref_trim = (uint16_t)raw_data[7] << 8;
    ref_trim |= (uint16_t)raw_data[8];

    rtd_res = ((double)output_code / 0x1p23 / (double)gain * REF) / Iref;
    rtd_res += ref_trim / 1000.0;
    
    temp_raw = searchTempFromR(rtd_res);
    temperature = temp_raw;
    return temperature;
}

#endif

#if defined ENABLE_CHIP_TMC200_TYPE_K

static double typeK_temp_to_mv(double t90) {

    uint8_t i;
    double mv = 0.0;

    static const double a0 PROGMEM = 0.118597600000E+00;
    static const double a1 PROGMEM = -0.118343200000E-03;
    static const double a2 PROGMEM = 0.126968600000E+03;
    static const double coefficients_above_zero[] PROGMEM = {
        -0.176004136860E-01,
        0.389212049750E-01,
        0.185587700320E-04,
        -0.994575928740E-07,
        0.318409457190E-09,
        -0.560728448890E-12,
        0.560750590590E-15,
        -0.320207200030E-18,
        0.971511471520E-22,
        -0.121047212750E-25
    };
    static const double coefficients_below_zero[] PROGMEM = {
        0.000000000000E+00,
        0.394501280250E-01,
        0.236223735980E-04,
        -0.328589067840E-06,
        -0.499048287770E-08,
        -0.675090591730E-10,
        -0.574103274280E-12,
        -0.310888728940E-14,
        -0.104516093650E-16,
        -0.198892668780E-19,
        -0.163226974860E-22
    };
   
    if (t90 > 0) {
        for (i=0; i<ARRAY_SIZE(coefficients_above_zero); i++) {
            mv += PF(coefficients_above_zero, i) * powui(t90, i);
        }
        mv += a0 * exp(a1 * powui(t90 - a2, 2));
    } else {
        for (i=0; i<ARRAY_SIZE(coefficients_below_zero); i++) {
            mv += PF(coefficients_below_zero, i) * powui(t90, i);
        }
    }
    return mv;

}

static double typeK_mv_to_temp(double mv) {

    double t90 = 0;
    uint8_t i;

    // -5.891 to 0 mv
    static const double range1_d[] PROGMEM = {
        0.0000000E+00,
        2.5173462E+01,
        -1.1662878E+00,
        -1.0833638E+00,
        -8.9773540E-01,
        -3.7342377E-01,
        -8.6632643E-02,
        -1.0450598E-02,
        -5.1920577E-04,
        0.0000000E+00,
    };
    static const double range2_d[] PROGMEM = {
        0.000000E+00,
        2.508355E+01,
        7.860106E-02,
        -2.503131E-01,
        8.315270E-02,
        -1.228034E-02,
        9.804036E-04,
        -4.413030E-05,
        1.057734E-06,
        -1.052755E-08
    };
    static const double range3_d[] PROGMEM = {
        -1.318058E+02,
        4.830222E+01,
        -1.646031E+00,
        5.464731E-02,
        -9.650715E-04,
        8.802193E-06,
        -3.110810E-08,
        0.000000E+00,
        0.000000E+00,
        0.000000E+00
    };

    if (mv < 0) {
        for (i=0; i<ARRAY_SIZE(range1_d); i++) {
            t90 += PF(range1_d, i) * powui(mv, i);
        }
    } else if (mv < 20.644) {
        for (i=0; i<ARRAY_SIZE(range2_d); i++) {
            t90 += PF(range2_d, i) * powui(mv, i);
        }
    } else {
        for (i=0; i<ARRAY_SIZE(range3_d); i++) {
            t90 += PF(range3_d, i) * powui(mv, i);
        }
    }
    return t90;
}

_CHIP_CONV_RAW(tmc200_type_k) {

    uint8_t status;
    double cold_temp, cold_mv, comp_mv, t_v;
    int32_t output_code;
    uint16_t gain;

    if (raw_len < 15) {
        RETURN_ERROR(CHIP_ERROR_INVALID_DATA);
    }

    status = raw_data[14];
    if (status & 0x3C) {
        RETURN_ERROR(CHIP_ERROR_PROBE_DISCONNECTED);
    }

    cold_temp = getColdTempAlt(raw_data);
    if ((cold_temp < -40) || (cold_temp > 90)) {
        RETURN_ERROR(CHIP_ERROR_SENSOR_ERROR);
    }

    cold_mv = typeK_temp_to_mv(cold_temp);
    gain = 1 << (raw_data[13]>>4);  // between 1 and 2^15
    output_code = ((uint32_t)raw_data[10] << 24) | ((uint32_t)raw_data[11] << 16) | ((uint32_t)raw_data[12]<<8);
    output_code >>= 8;
    t_v = output_code * (2.0 * REF / gain) / 0x1p24;

    comp_mv = cold_mv + (t_v * 1000);
    double temperature = typeK_mv_to_temp(comp_mv);
    // Limit output temperature to what is *possible* for a working Type-K thermocouple +/- 5C
    if ((temperature < (-270-5)) || (temperature > (1372+5))) {
        RETURN_ERROR(CHIP_ERROR_OUT_OF_RANGE);
    }
    result->value_float = temperature;
    result->type = QUANTITY_TYPE_FLOAT;
    result->unit = UNIT_CELSIUS;

    return 0;

}

#endif


#if defined ENABLE_CHIP_TMC200_TYPE_N

static double typeN_temp_to_mv(double t90) {

    uint8_t i;
    double mv = 0.0;

   /* range:  -270.000 TO 0 */ 
    static const double coefficients_below_zero[] PROGMEM = {   
    0.000000000000E+00,
    0.261591059620E-01,
    0.109574842280E-04,
    -0.938411115540E-07,
    -0.464120397590E-10,
    -0.263033577160E-11,
    -0.226534380030E-13,
    -0.760893007910E-16,
    -0.934196678350E-19
    };
  

/* range:  0 TO 1300 */ 
static const double coefficients_above_zero[] PROGMEM = {
    0.000000000000E+00,
    .259293946010E-01,
    0.157101418800E-04,	
    0.438256272370E-07,	
    -0.252611697940E-09,
    0.643118193390E-12,
    -0.100634715190E-14,
    0.997453389920E-18,
    -0.608632456070E-21,
    0.208492293390E-24,
    -0.306821961510E-28
    };
  
    if (t90 > 0) {
        for (i=0; i<ARRAY_SIZE(coefficients_above_zero); i++) {
            mv += PF(coefficients_above_zero, i) * powui(t90, i);
        }
    } else {
        for (i=0; i<ARRAY_SIZE(coefficients_below_zero); i++) {
           mv += PF(coefficients_below_zero, i) * powui(t90, i);
        }
    }
    
    return mv;

}

static double typeN_mv_to_temp(double mv) {

    double t90 = 0;
    uint8_t i;
   
    // -3.990 to 0 (mv)
    static const double range1_d[] PROGMEM = {
        0.0000000E+00,
        3.8436847E+01,
        1.1010485E+00,
        5.2229312E+00,
        7.2060525E+00,
        5.8488586E+00,
        2.7754916E+00,
        7.7075166E-01,
        1.1582665E-01,
        7.3138868E-03
    };

    // 0 to 20.613 (mv)
    static const double range2_d[] PROGMEM = {
        0.00000E+00,
        3.86896E+01,
        -1.08267E+00,
        4.70205E-02,
        -2.12169E-06,
        -1.17272E-04,
        5.39280E-06,
        -7.98156E-08,
        0.00000E+00,
        0.00000E+00
    };
    
    // 20.613 to 47.513 (mv)
    static const double range3_d[] PROGMEM = {    
        1.972485E+01,
        3.300943E+01,
        -3.915159E-01,
        9.855391E-03,
        -1.274371E-04,
        7.767022E-07,
        0.000000E+00,
        0.000000E+00,
        0.000000E+00,
        0.000000E+00
    };
    if (mv < 0) {
        for (i=0; i<ARRAY_SIZE(range1_d); i++) {
            t90 += PF(range1_d, i) * powui(mv, i);
        }
    } else if (mv < 20.613) {
        for (i=0; i<ARRAY_SIZE(range2_d); i++) {
            t90 += PF(range2_d, i) * powui(mv, i);
        }
    } else {
        for (i=0; i<ARRAY_SIZE(range3_d); i++) {
            t90 += PF(range3_d, i) * powui(mv, i);
        }
    }
    return t90;
}

_CHIP_CONV_RAW(tmc200_type_n) {

    uint8_t status;
    double cold_temp, cold_mv, comp_mv, t_v;
    int32_t output_code;
    uint16_t gain;
 
    DEBUG_PRINT("\nFunction Entry:\n\n");
    if (raw_len < 15) {
        RETURN_ERROR(CHIP_ERROR_INVALID_DATA);
    }

    status = raw_data[14];
   DEBUG_PRINT("\nSensor status reading Hot junction=[0x%02x]\n\n",status);
    if (status & 0x3C) {
         RETURN_ERROR(CHIP_ERROR_PROBE_DISCONNECTED);
    }

    cold_temp = getColdTempAlt(raw_data);
    DEBUG_PRINT("\nCold junction=[%f celcius]\n\n",cold_temp);
   
    if ((cold_temp < -40) || (cold_temp > 90)) {
        RETURN_ERROR(CHIP_ERROR_SENSOR_ERROR);
    }

    cold_mv = typeN_temp_to_mv(cold_temp);
    DEBUG_PRINT("\nCold junction=[%f mv]\n\n",cold_mv);
    gain = 1 << (raw_data[13]>>4);  // between 1 and 2^15
    output_code = ((uint32_t)raw_data[10] << 24) | ((uint32_t)raw_data[11] << 16) | ((uint32_t)raw_data[12]<<8);
    output_code >>= 8;
    t_v = output_code * (2.0 * REF / gain) / 0x1p24;
    DEBUG_PRINT("\nHot junction=[%f mv]\n\n",t_v * 1000);
    DEBUG_PRINT("\nThermocouple N gain=[%d]\n\n",gain);
    comp_mv = cold_mv + t_v * 1000;
    double temperature = typeN_mv_to_temp(comp_mv);
    DEBUG_PRINT("\nCold junction + Hot junction=[%f mv]\n\n",comp_mv);
    DEBUG_PRINT("\nThermocouple N temperature=[%f celsius]\n\n",temperature);
   
    // Limit output temperature to what is *possible* for a working Type-K thermocouple +/- 5C
    if ((temperature < (-270-5)) || (temperature > (1300+5))) {
        RETURN_ERROR(CHIP_ERROR_OUT_OF_RANGE);
    }

    result->value_float = temperature;
    result->type = QUANTITY_TYPE_FLOAT;
    result->unit = UNIT_CELSIUS;

    return 0;

}

#endif

#if defined ENABLE_CHIP_TMC200_TYPE_T

static double typeT_temp_to_mv(double t90) {

    uint8_t i;
    double mv = 0.0;

   /* range:  -270.000 TO 0 */ 
    static const double coefficients_below_zero[] PROGMEM = {    
        0.000000000000E+00,
        0.387481063640E-01,
        0.441944343470E-04,
        0.118443231050E-06,
        0.200329735540E-07,
        0.901380195590E-09,
        0.226511565930E-10,
        0.360711542050E-12,
        0.384939398830E-14,
        0.282135219250E-16,
        0.142515947790E-18,
        0.487686622860E-21,
        0.107955392700E-23,
        0.139450270620E-26,
        0.797951539270E-30
    };

/* range:  0 TO 400 */ 
    static const double coefficients_above_zero[] PROGMEM = {
        0.000000000000E+00,
        0.387481063640E-01,
        0.332922278800E-04,
        0.206182434040E-06,
        -0.218822568460E-08,
        0.109968809280E-10,
        -0.308157587720E-13,
        0.454791352900E-16,
        -0.275129016730E-19
        };

    if (t90 > 0) {
        for (i=0; i<ARRAY_SIZE(coefficients_above_zero); i++) {
            mv += PF(coefficients_above_zero, i) * powui(t90, i);
        }
    } else {
        for (i=0; i<ARRAY_SIZE(coefficients_below_zero); i++) {
           mv += PF(coefficients_below_zero, i) * powui(t90, i);
        }
    }
    return mv;
}

static double typeT_mv_to_temp(double mv) {

    double t90 = 0;
    uint8_t i;

    // -5.603 to 0 (mv)
    static const double range1_d[] PROGMEM = {
        0.0000000E+00,  
        2.5949192E+01, 
        -2.1316967E-01, 
        7.9018692E-01,  
        4.2527777E-01, 
        1.3304473E-01,  
        2.0241446E-02, 
        1.2668171E-03  
        };

    // 0 to 20.872 (mv)
    static const double range2_d[] PROGMEM = {
        0.000000E+00,
        2.592800E+01,
        -7.602961E-01,
        4.637791E-02,
        -2.165394E-03,
        6.048144E-05,
        -7.293422E-07,
        0.000000E+00
        };
        
    if (mv < 0) {
        for (i=0; i<ARRAY_SIZE(range1_d); i++) {
            t90 += PF(range1_d, i) * powui(mv, i);
        }
    } else {
        for (i=0; i<ARRAY_SIZE(range2_d); i++) {
            t90 += PF(range2_d, i) * powui(mv, i);
        }
    } 
    return t90;
}

_CHIP_CONV_RAW(tmc200_type_t) {

    uint8_t status;
    double cold_temp, cold_mv, comp_mv, t_v;
    int32_t output_code;
    uint16_t gain;
   
    DEBUG_PRINT("\nFunction Entry:\n\n");
    if (raw_len < 15) {
        RETURN_ERROR(CHIP_ERROR_INVALID_DATA);
    }

    status = raw_data[14];
    DEBUG_PRINT("\nSensor status reading Hot junction=[0x%02x]\n\n",status);
    if (status & 0x3C) {
         RETURN_ERROR(CHIP_ERROR_PROBE_DISCONNECTED);
    }

    cold_temp = getColdTempAlt(raw_data);
    DEBUG_PRINT("\nCold junction=[%f celcius]\n\n",cold_temp);
    if ((cold_temp < -40) || (cold_temp > 90)) {
        RETURN_ERROR(CHIP_ERROR_SENSOR_ERROR);
    }

    cold_mv = typeT_temp_to_mv(cold_temp);
    DEBUG_PRINT("\ncold junction=[%f mv]\n\n",cold_mv);  
    gain = 1 << (raw_data[13]>>4);  // between 1 and 2^15
    output_code = ((uint32_t)raw_data[10] << 24) | ((uint32_t)raw_data[11] << 16) | ((uint32_t)raw_data[12]<<8);
    output_code >>= 8;
    t_v = output_code * (2.0 * REF / gain) / 0x1p24;
    DEBUG_PRINT("\nThermocouple T gain=[%d]\n\n",gain);
    DEBUG_PRINT("\nHot junction=[%f mv]\n\n",t_v * 1000);
    comp_mv = cold_mv + t_v * 1000;
    double temperature = typeT_mv_to_temp(comp_mv);
    DEBUG_PRINT("\ncold junction + Hot junction=[%f mv]\n\n",comp_mv);
    DEBUG_PRINT("\nThermocouple T temperature=[%f celsius]\n\n",temperature);
   
    // Limit output temperature to what is *possible* for a working Type-K thermocouple +/- 5C
    if ((temperature < (-270-5)) || (temperature > (400+5))) {
        RETURN_ERROR(CHIP_ERROR_OUT_OF_RANGE);
    }

    result->value_float = temperature;
    result->type = QUANTITY_TYPE_FLOAT;
    result->unit = UNIT_CELSIUS;

    return 0;

}

#endif

#if defined ENABLE_CHIP_TMC200_TYPE_E

static double typeE_temp_to_mv(double t90) {

    uint8_t i;
    double mv = 0.0;

   /* range:  -200.000 TO 0 */ 
    static const double coefficients_below_zero[] PROGMEM = {    
       0.000000000000E+00,
       0.586655087080E-01,
       0.454109771240E-04,
       -0.779980486860E-06,
       -0.258001608430E-07,
       -0.594525830570E-09,
       -0.932140586670E-11,
       -0.102876055340E-12,
       -0.803701236210E-15,
       -0.439794973910E-17,
       -0.164147763550E-19,
       -0.396736195160E-22,
       -0.558273287210E-25,
        -0.346578420130E-28
        };

/* range:  0 TO 1000 */ 
    static const double coefficients_above_zero[] PROGMEM = {
        0.000000000000E+00,
        0.586655087100E-01,
        0.450322755820E-04,
        0.289084072120E-07,
        -0.330568966520E-09,
        0.650244032700E-12,
        -0.191974955040E-15,
        -0.125366004970E-17,
        0.214892175690E-20,
        -0.143880417820E-23,
        0.359608994810E-27
        };

    if (t90 > 0) {
        for (i=0; i<ARRAY_SIZE(coefficients_above_zero); i++) {
            mv += PF(coefficients_above_zero, i) * powui(t90, i);
        }
    } else {
        for (i=0; i<ARRAY_SIZE(coefficients_below_zero); i++) {
           mv += PF(coefficients_below_zero, i) * powui(t90, i);
        }
    }
    return mv;
}

static double typeE_mv_to_temp(double mv) {

    double t90 = 0;
    uint8_t i;

    // -8.825 to 0 (mv)
    static const double range1_d[] PROGMEM = {
        0.0000000E+00,
        1.6977288E+01,
        -4.3514970E-01,
        -1.5859697E-01,
        -9.2502871E-02,
        -2.6084314E-02,
        -4.1360199E-03,
        -3.4034030E-04,
        -1.1564890E-05,
        0.0000000E+00
        };

    // 0 to 76.373 (mv)
    static const double range2_d[] PROGMEM = {
        0.0000000E+00,
        1.7057035E+01,
        -2.3301759E-01,
        6.5435585E-03,
        -7.3562749E-05,
        -1.7896001E-06,
        8.4036165E-08,
        -1.3735879E-09,
        1.0629823E-11,
        -3.2447087E-14
        };
        
    if (mv < 0) {
        for (i=0; i<ARRAY_SIZE(range1_d); i++) {
            t90 += PF(range1_d, i) * powui(mv, i);
        }
    } else {
        for (i=0; i<ARRAY_SIZE(range2_d); i++) {
            t90 += PF(range2_d, i) * powui(mv, i);
        }
    }   
    return t90;
}

_CHIP_CONV_RAW(tmc200_type_e) {

    uint8_t status;
    double cold_temp, cold_mv, comp_mv, t_v;
    int32_t output_code;
    uint16_t gain;

    DEBUG_PRINT("\nFunction Entry:\n\n");
    if (raw_len < 15) {
        RETURN_ERROR(CHIP_ERROR_INVALID_DATA);
    }

    status = raw_data[14];
    DEBUG_PRINT("\nSensor status reading Hot junction=[0x%02x]\n\n",status);
    if (status & 0x3C) {
        RETURN_ERROR(CHIP_ERROR_PROBE_DISCONNECTED);
    }

    cold_temp = getColdTempAlt(raw_data);
    DEBUG_PRINT("\nCold junction=[%f celcius]\n\n",cold_temp);
    if ((cold_temp < -40) || (cold_temp > 90)) {
        RETURN_ERROR(CHIP_ERROR_SENSOR_ERROR);
    }

    cold_mv = typeE_temp_to_mv(cold_temp);

    gain = 1 << (raw_data[13]>>4);  // between 1 and 2^15
    output_code = ((uint32_t)raw_data[10] << 24) | ((uint32_t)raw_data[11] << 16) | ((uint32_t)raw_data[12]<<8);
    output_code >>= 8;
    t_v = output_code * (2.0 * REF / gain) / 0x1p24;
    DEBUG_PRINT("\nThermocouple E gain=[%d]\n\n",gain);
    DEBUG_PRINT("\nHot junction=[%f mv]\n\n",t_v * 1000);
    comp_mv = cold_mv + t_v * 1000;
    double temperature = typeE_mv_to_temp(comp_mv);
    DEBUG_PRINT("\ncold junction + Hot junction=[%f mv]\n\n",comp_mv);
    DEBUG_PRINT("\nThermocouple E temperature=[%f celsius]\n\n",temperature);

    // Limit output temperature to what is *possible* for a working Type-E thermocouple +/- 5C
    if ((temperature < (-200-5)) || (temperature > (1000+5))) {
        RETURN_ERROR(CHIP_ERROR_OUT_OF_RANGE);
    }

    result->value_float = temperature;
    result->type = QUANTITY_TYPE_FLOAT;
    result->unit = UNIT_CELSIUS;

    return 0;

}
#endif


#if defined ENABLE_CHIP_TMC200_TYPE_J

static double typeJ_temp_to_mv(double t90) {

    uint8_t i;
    double mv = 0.0;

/* range:  -210.000 TO 760.000 */ 
 static const double coefficients_below_760[] PROGMEM = {   
    0.000000000000E+00,
    0.503811878150E-01,
    0.304758369300E-04,
    -0.856810657200E-07,
    0.132281952950E-09,
    -0.170529583370E-12,
    0.209480906970E-15,
    -0.125383953360E-18,
    0.156317256970E-22
    };

/* range:  760.000 TO 1200.000 */ 
static const double coefficients_from_760[] PROGMEM = {
    0.296456256810E+03,
    -0.149761277860E+01,
    0.317871039240E-02,
    -0.318476867010E-05,
    0.157208190040E-08,
    -0.306913690560E-12
    };

    if (t90 >= 760) {
        for (i=0; i<ARRAY_SIZE(coefficients_from_760); i++) {
            mv += PF(coefficients_from_760, i) * powui(t90, i);
        }
    } else {
        for (i=0; i<ARRAY_SIZE(coefficients_below_760); i++) {
           mv += PF(coefficients_below_760, i) * powui(t90, i);
        }
    }
    return mv;
}

static double typeJ_mv_to_temp(double mv) {

    double t90 = 0;
    uint8_t i;

    // -8.095 to 0 (mv)
    static const double range1_d[] PROGMEM = {
        0.0000000E+00,
        1.9528268E+01,
        -1.2286185E+00,
        -1.0752178E+00,
        -5.9086933E-01,
        -1.7256713E-01,
        -2.8131513E-02,
        -2.3963370E-03,
        -8.3823321E-05
        };

    // 0 to 42.919 (mv)
static const double range2_d[] PROGMEM = {
        0.000000E+00,
        1.978425E+01,
        -2.001204E-01,
        1.036969E-02,
        -2.549687E-04,
        3.585153E-06,
        -5.344285E-08,
        5.099890E-10,
        0.000000E+00
        };
 
     // 42.919 to 69.553 (mv)
static const double range3_d[] PROGMEM = {
        -3.11358187E+03,
        3.00543684E+02,
        -9.94773230E+00,
        1.70276630E-01,
        -1.43033468E-03,
        4.73886084E-06,
        0.00000000E+00,
        0.00000000E+00,
        0.00000000E+00
        };

        
    if (mv < 0) {
        for (i=0; i<ARRAY_SIZE(range1_d); i++) {
            t90 += PF(range1_d, i) * powui(mv, i);
        }
    }
    else if (mv >= 0 && mv <= 42.919) {
        for (i=0; i<ARRAY_SIZE(range2_d); i++) {
            t90 += PF(range2_d, i) * powui(mv, i);
        }
    }   
    else {
        for (i=0; i<ARRAY_SIZE(range3_d); i++) {
            t90 += PF(range3_d, i) * powui(mv, i);
        }
    }
    return t90;
}

_CHIP_CONV_RAW(tmc200_type_j) {

    uint8_t status;
    double cold_temp, cold_mv, comp_mv, t_v;
    int32_t output_code;
    uint16_t gain;

    DEBUG_PRINT("\nFunction Entry:\n\n");
    if (raw_len < 15) {
        RETURN_ERROR(CHIP_ERROR_INVALID_DATA);
    }

    status = raw_data[14];
    DEBUG_PRINT("\nSensor status reading Hot junction=[0x%02x]\n\n",status);
    if (status & 0x3C) {
        RETURN_ERROR(CHIP_ERROR_PROBE_DISCONNECTED);
    }

    cold_temp = getColdTempAlt(raw_data);
    DEBUG_PRINT("\nCold junction=[%f celcius]\n\n",cold_temp);
    if ((cold_temp < -40) || (cold_temp > 90)) {
        RETURN_ERROR(CHIP_ERROR_SENSOR_ERROR);
    }

    cold_mv = typeJ_temp_to_mv(cold_temp);
    gain = 1 << (raw_data[13]>>4);  // between 1 and 2^15
    output_code = ((uint32_t)raw_data[10] << 24) | ((uint32_t)raw_data[11] << 16) | ((uint32_t)raw_data[12]<<8);
    output_code >>= 8;
    t_v = output_code * (2.0 * REF / gain) / 0x1p24;
    DEBUG_PRINT("\nThermocouple J gain=[%d]\n\n",gain);
    DEBUG_PRINT("\nHot junction=[%f mv]\n\n",t_v * 1000);
    comp_mv = cold_mv + t_v * 1000;
    double temperature = typeJ_mv_to_temp(comp_mv);
    DEBUG_PRINT("\ncold junction + Hot junction=[%f mv]\n\n",comp_mv);
    DEBUG_PRINT("\nThermocouple J temperature=[%f celsius]\n\n",temperature);

    // Limit output temperature to what is *possible* for a working Type-J thermocouple +/- 5C
    if ((temperature < (-210-5)) || (temperature > (1200+5))) {
        RETURN_ERROR(CHIP_ERROR_OUT_OF_RANGE);
    }

    result->value_float = temperature;
    result->type = QUANTITY_TYPE_FLOAT;
    result->unit = UNIT_CELSIUS;

    return 0;

}
#endif



#if defined ENABLE_CHIP_TMC200_COLD
_CHIP_CONV_RAW(tmc200_cold) {

    if (raw_len < 9) {
        RETURN_ERROR(CHIP_ERROR_INVALID_DATA);
    }

    double temperature = getColdTempAlt(raw_data);

    // This is an on-board cold junction sensor. Reasonable limits
    // are expected. Anything else strongly suggest defective components
    // rather than a true temperature.
    if ((temperature < -40) || (temperature > 90)) {
        RETURN_ERROR(CHIP_ERROR_OUT_OF_RANGE);
    }

    result->value_float = temperature;
    result->type = QUANTITY_TYPE_FLOAT;
    result->unit = UNIT_CELSIUS;

    return 0;

}
#endif

#if \
    defined ENABLE_CHIP_IR || \
    defined ENABLE_CHIP_GREEN || \
    defined ENABLE_CHIP_BLUE || \
    defined ENABLE_CHIP_RED

_CHIP_CONV_RAW(apds9250) {

    if (raw_len < 3) {
        RETURN_ERROR(CHIP_ERROR_INVALID_DATA);
    }

    uint32_t value = \
        ((uint32_t)raw_data[0]) | \
        ((uint32_t)raw_data[1] << 8) | \
        ((uint32_t)raw_data[2] << 16);

    result->value_uint32 = value;
    result->type = QUANTITY_TYPE_UINT32;
    result->unit = UNIT_ARBITRARY;

    return 0;

}

#endif

#if defined ENABLE_CHIP_VEML6030_ALS
_CHIP_CONV_RAW(veml6030_als) {

    uint16_t steps = ((uint16_t)raw_data[0]) | ((uint16_t)raw_data[1] << 8);

    int gain_factor = 1;
    switch (raw_data[2]) {
        case 0x02: gain_factor <<= 1;   // 1/8x
        case 0x03: gain_factor <<= 2;   // 1/4x
        case 0x00: gain_factor <<= 1;   // 1x
        case 0x01: break;               // 2x
        default:
            RETURN_ERROR(CHIP_ERROR_INVALID_DATA);
    }

    int it_factor = 1;
    switch (raw_data[3]) {
        case 0x0C: it_factor <<= 1;     // 25 ms
        case 0x08: it_factor <<= 1;     // 50 ms
        case 0x00: it_factor <<= 1;     // 100 ms
        case 0x01: it_factor <<= 1;     // 200 ms
        case 0x02: it_factor <<= 1;     // 400 ms
        case 0x03: break;               // 800 ms
        default:
            RETURN_ERROR(CHIP_ERROR_INVALID_DATA);
    }

    static const float minstep PROGMEM = 0.0042;
    result->value_float = steps * (minstep * gain_factor * it_factor);
    result->type = QUANTITY_TYPE_FLOAT;
    result->unit = UNIT_LUX;

    return 0;

}
#endif

#if defined ENABLE_CHIP_VEML6030_WHITE
_CHIP_CONV_RAW(veml6030_white) {

    if (raw_len < 2) {
        RETURN_ERROR(CHIP_ERROR_INVALID_DATA);
    }

    uint32_t value = ((uint32_t)raw_data[0]) | ((uint32_t)raw_data[1] << 8);

    result->value_uint32 = value;
    result->type = QUANTITY_TYPE_UINT32;
    result->unit = UNIT_ARBITRARY;

    return 0;

}
#endif

#if defined ENABLE_CHIP_VEML6075_UVA
_CHIP_CONV_RAW(veml6075_uva) {

    if (raw_len < 2) {
        RETURN_ERROR(CHIP_ERROR_INVALID_DATA);
    }

    static const double coefficient PROGMEM = 0.93;

    uint16_t value = ((uint16_t)raw_data[0]) | ((uint16_t)raw_data[1] << 8);

    result->value_float = value * coefficient;
    result->type = QUANTITY_TYPE_FLOAT;
    result->unit = UNIT_uW_PER_CM2;

    return 0;

}
#endif

#if defined ENABLE_CHIP_VEML6075_UVB
_CHIP_CONV_RAW(veml6075_uvb) {

    if (raw_len < 2) {
        RETURN_ERROR(CHIP_ERROR_INVALID_DATA);
    }

    static const double coefficient PROGMEM = 2.1;

    uint16_t value = ((uint16_t)raw_data[0]) | ((uint16_t)raw_data[1] << 8);

    result->value_float = value * coefficient;
    result->type = QUANTITY_TYPE_FLOAT;
    result->unit = UNIT_uW_PER_CM2;

    return 0;

}
#endif

#if \
    defined ENABLE_ALL_CHIPS || \
    defined ENABLE_CHIP_CO2_DXC200_FILTERED || \
    defined ENABLE_CHIP_CO2_DXC200_INSTANT || \
    defined ENABLE_CHIP_CO2_DXC300_COMPENSATED || \
    defined ENABLE_CHIP_CO2_DXC300_RAW
_CHIP_CONV_RAW(co2_dxc200) {

    uint8_t status;
    uint16_t multiplier;
    uint32_t value;

    if (raw_len < 5) {
        RETURN_ERROR(CHIP_ERROR_INVALID_DATA);
    }

    status = raw_data[0];
    if (status != 0x01) {
        RETURN_ERROR(CHIP_ERROR_SENSOR_ERROR);
    }

    multiplier = ((uint16_t)raw_data[3]<<8) | (uint16_t)raw_data[4];
    if (multiplier == 0) {
        RETURN_ERROR(CHIP_ERROR_SENSOR_ERROR);
    }

    value = ((uint32_t)raw_data[1]<<8) | (uint32_t)raw_data[2];

    result->value_float = (value * multiplier) / 10000.0;
    result->type = QUANTITY_TYPE_FLOAT;
    result->unit = UNIT_PERCENT;

    return 0;

}
#endif

#if \
    defined ENABLE_ALL_CHIPS || \
    defined ENABLE_CHIP_CO2_DXC300_RAW
_CHIP_CONV_RAW(co2_dxc300) {

    uint8_t status;
    uint16_t multiplier;
    int32_t offset;
    int32_t value;

    if (raw_len < 5) {
        RETURN_ERROR(CHIP_ERROR_INVALID_DATA);
    }

    status = raw_data[0];
    if (status != 0x01) {
        RETURN_ERROR(CHIP_ERROR_SENSOR_ERROR);
    }

    value = ((uint32_t)raw_data[1]<<8) | (uint32_t)raw_data[2];

    multiplier = ((uint16_t)raw_data[3]<<8) | (uint16_t)raw_data[4];
    if (multiplier == 0) {
        RETURN_ERROR(CHIP_ERROR_SENSOR_ERROR);
    }

    offset = ((uint16_t)raw_data[5]<<8) | ((uint16_t)raw_data[6] & 0xFF);

    result->value_float = ((value - offset) * multiplier) / 10000.0;
    result->type = QUANTITY_TYPE_FLOAT;
    result->unit = UNIT_PERCENT;

    return 0;

}
#endif


#if \
    defined ENABLE_ALL_CHIPS || \
    defined ENABLE_CHIP_CO2_DXC300_P_COMP || \
    defined ENABLE_CHIP_CO2_DXC300_T_COMP || \
    defined ENABLE_CHIP_CO2_DXC300_RH_COMP
static float* comp_coeffs(float* coeffs)
{
	for (uint8_t i = 0; i < 3; i++) {
		if (!isfinite(coeffs[i])) {
			return NULL;
		}
	}

    return coeffs;
}
#endif

#if \
    defined ENABLE_ALL_CHIPS || \
    defined ENABLE_CHIP_CO2_DXC300_P_COMP
_CHIP_CONV_RAW(p_comp_dxc300) {
    if (!result || !raw_data || !cal_data) {
        return -1;
    }

    if (raw_len < 4 || cal_len < 16) {
        return -1;
    }

    float factor = NAN;
    float value = *((float *)raw_data);

    if (value >= 90000.0 && value <= 115000.0) {
        float *coeffs = comp_coeffs((float*)&cal_data[4]);

        if (coeffs) {
            factor = coeffs[0]
                + (coeffs[1] * value)
                + (coeffs[2] * value * value);
        }
    } else {
        RETURN_ERROR(CHIP_ERROR_OUT_OF_RANGE);
    }

    if (!isfinite(factor)) {
        RETURN_ERROR(CHIP_ERROR_NO_DATA);
        return 0;
    }

    result->value_float = factor;
    result->type = QUANTITY_TYPE_FLOAT;
    result->unit = UNIT_ARBITRARY;

    return 0;
}
#endif

#if \
    defined ENABLE_ALL_CHIPS || \
    defined ENABLE_CHIP_CO2_DXC300_T_COMP
_CHIP_CONV_RAW(t_comp_dxc300) {
    if (!result || !raw_data || !cal_data) {
        return -1;
    }

    if (raw_len < 4 || cal_len < 16) {
        return -1;
    }

    float factor = NAN;
    float value = *((float *)raw_data);

    if (value >= 0.0 && value <= 50.0) {
        float *coeffs = comp_coeffs((float*)&cal_data[4]);

        if (coeffs) {
            factor = coeffs[0]
                + (coeffs[1] * value)
                + (coeffs[2] * value * value);
        }
    } else {
        RETURN_ERROR(CHIP_ERROR_OUT_OF_RANGE);
    }

    if (!isfinite(factor)) {
        RETURN_ERROR(CHIP_ERROR_NO_DATA);
        return 0;
    }

    result->value_float = factor;
    result->type = QUANTITY_TYPE_FLOAT;
    result->unit = UNIT_ARBITRARY;

    return 0;
}
#endif

#if \
    defined ENABLE_ALL_CHIPS || \
    defined ENABLE_CHIP_CO2_DXC300_RH_COMP
_CHIP_CONV_RAW(rh_comp_dxc300) {
    if (!result || !raw_data || !cal_data) {
        return -1;
    }

    if (raw_len < 4 || cal_len < 16) {
        return -1;
    }

    float factor = NAN;
    float value = *((float *)raw_data);

    if (value >= 0.0 && value <= 100.0) {
        float *coeffs = comp_coeffs((float*)&cal_data[4]);

        if (coeffs) {
            factor = coeffs[0]
                + (coeffs[1] * value)
                + (coeffs[2] * value * value);
        }
    } else {
        RETURN_ERROR(CHIP_ERROR_OUT_OF_RANGE);
    }

    if (!isfinite(factor)) {
        RETURN_ERROR(CHIP_ERROR_NO_DATA);
        return 0;
    }

    result->value_float = factor;
    result->type = QUANTITY_TYPE_FLOAT;
    result->unit = UNIT_ARBITRARY;

    return 0;
}
#endif

#if defined ENABLE_CHIP_TSL2561_IR || defined ENABLE_CHIP_TSL2561_IR_16X || \
    defined ENABLE_CHIP_TSL2561_IR_VISIBLE || defined ENABLE_CHIP_TSL2561_IR_VISIBLE_16X
_CHIP_CONV_RAW(tsl2561) {

    result->value_uint32 = (((uint32_t)raw_data[1]) << 8) | raw_data[0];
    result->type = QUANTITY_TYPE_UINT32;
    result->unit = UNIT_RAW;

    return 0;

}
#endif

#if defined ENABLE_CHIP_TSL2568_IR || defined ENABLE_CHIP_TSL2568_IR_16X || \
    defined ENABLE_CHIP_TSL2568_IR_VISIBLE || defined ENABLE_CHIP_TSL2568_IR_VISIBLE_16X
_CHIP_CONV_RAW(tsl2568) {

    result->value_uint32 = (((uint32_t)raw_data[1]) << 8) | raw_data[0];
    result->type = QUANTITY_TYPE_UINT32;
    result->unit = UNIT_RAW;

    return 0;

}
#endif
