//
// Sensor module common operations library
// Homelab library
//
// Department of Mechatronics
// Tallinn University of Technology
// ITT Group
// © Copyrights 2014
//

#if defined (__AVR_ATxmega128A1U__)
#include <avr/pgmspace.h>
#include <avr/interrupt.h>

#include "xmega/clksys_driver.h"
#include "xmega/TC_driver.h"
#include "module/sensors.h"
#include "adc.h"

//
// Temperature to ADC value conversion table(s)
//
const unsigned short THERMISTOR_TEMP_ADC_TABLE[] PROGMEM =
{
    90, 96, 102, 108, 114, 121, 128, 135, 142, 150, 159, 167, 177, 186, 196, 206,
    217, 228, 239, 251, 264, 277, 290, 304, 318, 333, 349, 364, 381, 398, 415,
    433, 451, 470, 490, 510, 530, 551, 573, 595, 617, 640, 663, 687, 712, 737,
    762, 788, 814, 840, 867, 894, 922, 950, 978, 1007, 1035, 1064, 1094, 1123,
    1153, 1182, 1212, 1242, 1273, 1303, 1333, 1363, 1394, 1424, 1454, 1484, 1514,
    1544, 1574, 1604, 1634, 1663, 1692, 1721, 1750, 1778, 1807, 1835, 1862, 1890,
    1917, 1944, 1970, 1996, 2022, 2048, 2073, 2098, 2122, 2146, 2170, 2193, 2216,
    2238, 2260, 2282, 2303, 2324, 2345, 2365, 2385, 2404, 2424, 2442, 2461, 2478,
    2496, 2513, 2530, 2547, 2563, 2579, 2594, 2609, 2624,
};

//
// Default IR distance sensors
//
const ir_distance_sensor GP2Y0A21YK = { 125000, 987, 20 };

//
// Ultrasonic distance sensor configuration
//
#define ULTRASONIC_SPEED_OF_SOUND    33000 // cm/s

//
// Instant ultrasonic distance measuring with SRF04
//
unsigned short ultrasonic_measure_srf04(pin trigger, pin echo)
{
    if(_sys_freq == 2)
        Homelab_clock_init();

    // Pin setup
    pin_setup_output(trigger);
    pin_setup_input_with_pullup(echo);

    // Reset timer
    TC_ClearOverflowFlag(&TCD1);
    TC_Restart(&TCD1);

    // Set timer D0 Normal mode
    // Top value 9000 (~18ms)
    // with clock freq of F_CPU / 64
    TC_SetPeriod(&TCD1,9000);
    TC1_ConfigClockSource( &TCD1, TC_CLKSEL_DIV64_gc );

    // Create trigger pulse
    pin_set(trigger);

    // Wait ~10 us
    while (TCD1.CNT < 10);

    // End trigger pulse
    pin_clear(trigger);

    // Wait for echo start
    while (!pin_get_value(echo))
    {
        // Timeout ?
        if (TC_GetOverflowFlag(&TCD1))
        {
            return 0;
        }
    }

    // Reset timer again
    TC_SetCount(&TCD1,0);

    // Wait for echo end
    while (pin_get_value(echo))
    {
        // Timeout ?
        if (TC_GetOverflowFlag(&TCD1))
        {
            return 0;
        }
    }

    // Convert time to distance:
    //   distance = timer * (1 / (F_CPU / 8)) * speed / 2
    return (unsigned long)TCD1.CNT / 29;
}

//
// Instant ultrasonic distance measuring with SRF05
//
unsigned short ultrasonic_measure_srf05(pin triggerecho)
{
    if(_sys_freq == 2)
        Homelab_clock_init();

    // Pin setup
    pin_setup_output(triggerecho);

    // Reset timer
    TC_ClearOverflowFlag(&TCD1);
    TC_Restart(&TCD1);

    // Set timer D0 Normal mode
    // Top value 9000 (~270cm)
    // with clock freq of F_CPU / 64
    TC_SetPeriod(&TCD1,9000);
    TC1_ConfigClockSource( &TCD1, TC_CLKSEL_DIV64_gc );

    // Create trigger pulse
    pin_set(triggerecho);

    // Wait ~10 us
    while (TCD1.CNT < 5);

    // End trigger pulse
    pin_clear(triggerecho);

    // Wait ~10 us
    while (TCD1.CNT < 100);

    // Wait for echo start
    pin_setup_input_with_pullup(triggerecho);
    while (TCD1.CNT < 200);
    while (!pin_get_value(triggerecho))
    {
        // Timeout ?
        if (TC_GetOverflowFlag(&TCD1))
        {
            return 0;
        }
    }

    // Reset timer again
    TC_SetCount(&TCD1,0);

    // Wait for echo end
    while (pin_get_value(triggerecho))
    {
        // Timeout ?
        if (TC_GetOverflowFlag(&TCD1))
        {
            return 0;
        }
    }

    // Convert time to distance:
    //   distance = timer * (1 / (F_CPU / 8)) * speed / 2
    return (unsigned long)TCD1.CNT / 29;
}

//
// Calculate temperature in celsius
// from the temperature to ADC value conversion table
//
signed short thermistor_calculate_celsius_from_table(
    const unsigned short *table_ptr,
    signed short min_temp,
    signed short max_temp,
    unsigned short adc_value)
{
    signed short celsius;

    // Get celsius temperature from table
    for (celsius = max_temp - min_temp; celsius >= 0; celsius--)
    {
        if (adc_value >= pgm_read_word(&table_ptr[celsius]))
        {
            return celsius + min_temp;
        }
    }

    // If value not in table, return minimum temperature
    return min_temp;
}

//
// Calculate IR distance sensor distance in centimeters from ADC value.
// Returns -1 if cannot calculate.
//
signed short ir_distance_calculate_cm(ir_distance_sensor sensor, unsigned short adc_value)
{
    if(adc_ref == 2)
    {
        return 19091/(adc_value-120);
    }
    if(adc_ref == 3)
    {
        return 11932/(adc_value-75);
    }
    return -1;
}

//
// DHT11/22 humidity and temperature sensor reading routines
//

// Global variables to hold data
uint16_t dht_hum = 0;
uint16_t dht_temp = 0;

// Wait while input is low with 100us timeout
int8_t waitlow(pin dht)
{
    uint16_t timeout = 0;
    while(!pin_get_value(dht))
    {
        _delay_us(1);
        timeout++;
        if(timeout > 100) return -1;
    }
    return 0;
}

// Wait while input is high with 100us timeout
int8_t waithigh(pin dht)
{
    uint16_t timeout = 0;
    while(pin_get_value(dht))
    {
        _delay_us(1);
        timeout++;
        if(timeout > 100) return -1;
    }
    return 0;
}

// Read high pulse length in us. over 30us = 1, under = 0;
int8_t GetPulse(pin dht)
{
    uint8_t time = 0;

    // Wait for pulse
    if(waitlow(dht) == -1)
        return -1;

    // Measure pulse width in ~us
    while(pin_get_value(dht))
    {
        time++;
        _delay_us(1);
        if(time > 100) return -1;
    }
    // Return value
    if(time > 30)
        return 1;
    return 0;
}

// Read humidity and temperature data and update global variables
// Input sensor type, I/O pin (DHT11, DHT22)
//
int8_t DHT_update(uint8_t sensor,pin dht)
{
    uint8_t humh = 0;
    uint8_t huml = 0;
    uint8_t temph = 0;
    uint8_t templ = 0;
    uint8_t crc = 0;
    uint8_t data_pos = 0;

    // Send start impulse of 20ms low signal
    pin_set(dht);
    pin_setup_output(dht);
    _delay_ms(1);

    pin_clear(dht);
    _delay_ms(20);
    pin_set(dht);
    _delay_us(20);

    // Start reading input
    pin_setup_input_with_pullup(dht);
    // Ignore first bit
    waitlow(dht);
    waithigh(dht);
    // Read humidity high byte
    for(data_pos = 8; data_pos > 0; data_pos--)
    {
        int8_t temp = GetPulse(dht);

        if (temp == -1) return -1;
        else humh |= (temp << (data_pos-1));
    }
    // Read humidity low byte
    for(data_pos = 8; data_pos > 0; data_pos--)
    {
        int8_t temp = GetPulse(dht);

        if (temp == -1) return -1;
        else huml |= (temp << (data_pos-1));
    }
    // Read temperature high byte
    for(data_pos = 8; data_pos > 0; data_pos--)
    {
        int8_t temp = GetPulse(dht);

        if (temp == -1) return -1;
        else temph |= (temp << (data_pos-1));
    }
    // Read temperature low byte
    for(data_pos = 8; data_pos > 0; data_pos--)
    {
        int8_t temp = GetPulse(dht);

        if (temp == -1) return -1;
        else templ |= (temp << (data_pos-1));
    }
    // Read CRC byte
    for(data_pos = 8; data_pos > 0; data_pos--)
    {
        int8_t temp = GetPulse(dht);

        if (temp == -1) return -1;
        else crc |= (temp << (data_pos-1));
    }
    // Check CRC and update temp and humidity values
    if(((uint16_t)(humh+huml+temph+templ) & 0xFF) == crc)
    {
        if(sensor == DHT11)
        {
            dht_hum = humh;
            dht_temp = temph;
        }
        else if(sensor == DHT22)
        {
            dht_hum = (humh << 8)| huml;
            dht_temp = (temph << 8)| templ;
        }
        return 1;
    }
    else
        return 0;
}

#endif