/*
 * main.c
 *
 * Copyright 2017 Edward V. Emelianoff <eddy@sao.ru, edward.emelianoff@gmail.com>
 *
 * This program is free software; you can redistribute it and/or modify
 * it under the terms of the GNU General Public License as published by
 * the Free Software Foundation; either version 2 of the License, or
 * (at your option) any later version.
 *
 * This program is distributed in the hope that it will be useful,
 * but WITHOUT ANY WARRANTY; without even the implied warranty of
 * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
 * GNU General Public License for more details.
 *
 * You should have received a copy of the GNU General Public License
 * along with this program; if not, write to the Free Software
 * Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston,
 * MA 02110-1301, USA.
 */

#include "hardware.h"
#include "i2c.h"
#include "proto.h"
#include "ssd1306.h"
#include "usb.h"

static uint16_t coefficients[2][5]; // Coefficients for given sensors
static const uint8_t Taddr[2] = {TSYS01_ADDR0, TSYS01_ADDR1};
volatile uint32_t Tms = 0;

#define TBUFLEN 11
static char Tbuf[2][TBUFLEN] = {0}; // buffer for temperatures
static uint8_t refreshdisplay = 0;

/* Called when systick fires */
void sys_tick_handler(void){
    ++Tms;
}

uint8_t sprintu(char *buf, uint8_t buflen, uint32_t val){
    uint8_t i, l = 0, bpos = 0;
    char rbuf[10];
    if(!val){
        buf[0] = '0';
        buf[1] = 0;
        return 1;
    }else{
        while(val){
            rbuf[l++] = val % 10 + '0';
            val /= 10;
        }
        if(buflen < l+1) return 0;
        bpos = l;
        buf[l] = 0;
        for(i = 0; i < l; ++i){
            buf[--bpos] = rbuf[i];
        }
    }
    return l;
}

void getcoeffs(uint8_t addr){ // get norm coefficiens
    int i;
    const uint8_t regs[5] = {0xAA, 0xA8, 0xA6, 0xA4, 0xA2}; // commands for coefficients
    uint32_t K;
    char numbr = (addr == TSYS01_ADDR0) ? '0' : '1';
    uint16_t *coef = coefficients[numbr-'0'];
    for(i = 0; i < 5; ++i){
        if(write_i2c(addr, regs[i])){
            if(read_i2c(addr, &K, 2)){
                coef[i] = K;
            }
        }
    }
}

/**
 * Get temperature & calculate it by polinome
 * T =    (-2) * k4 * 10^{-21} * ADC16^4
 *      +   4  * k3 * 10^{-16} * ADC16^3
 *      + (-2) * k2 * 10^{-11} * ADC16^2
 *      +   1  * k1 * 10^{-6}  * ADC16
 *      +(-1.5)* k0 * 10^{-2}
 * k0*(-1.5e-2) + 1e-6*val*(k1 + 1e-5*val*(-2*k2 + 1e-5*val*(4*k3 + -2e-5*k4*val)))
 * answer is in 100th
 */
uint8_t calc_t(uint32_t t, int i){
    if(coefficients[i][0] == 0){
        getcoeffs(Taddr[i]);
    }
    if(coefficients[i][0] == 0){
        return 0;
    }
    if (t < 6500000 || t > 13000000) return 0; // wrong value - too small or too large
    int j;
    double d = (double)t/256., tmp = 0.;
    // k0*(-1.5e-2) + 0.1*1e-5*val*(1*k1 + 1e-5*val*(-2.*k2 + 1e-5*val*(4*k3 + 1e-5*val*(-2*k4))))
    const double mul[5] = {-1.5e-2, 1., -2., 4., -2.};
    for(j = 4; j > 0; --j){
        tmp += mul[j] * (double)coefficients[i][j];
        tmp *= 1e-5*d;
    }
    tmp = tmp/10. + mul[0]*coefficients[i][0];
    Tbuf[i][0] = 'T'; Tbuf[i][1] = i + '0';
    Tbuf[i][2] = '='; Tbuf[i][3] = ' ';
    char *ptr = &Tbuf[i][4];
    uint8_t l = TBUFLEN - 4, s;
    if(tmp < 0.){
        *ptr++ = '-';
        tmp = -tmp;
    }
    uint32_t x = (uint32_t)tmp;
    if(x > 120){
        *ptr = 0;
        return 0; // wrong value
    }
    s = sprintu(ptr, l, x);
    ptr += s; l -= s;
    tmp -= x;
    *ptr++ = '.'; --l;
    x = (uint32_t)(tmp*100);
    if(x < 10){
        *ptr++ = '0'; --l;
    }
    s += sprintu(ptr, l, x);
    refreshdisplay = 1;
    return 1;
}

int main(void){
    uint32_t lastT = 0;
    int16_t uptime = 0;
    uint8_t i;
    uint32_t started[2] = {0, 0}; // time of measurements for given sensor started
    uint32_t tgot[2] = {0, 0};  // time of last measurements
    uint8_t errcnt[2] = {0,0};
    sysreset();
    SysTick_Config(6000, 1);
    gpio_setup();
    LED_on(LED0);
    LEDT_off(0);
    LEDT_off(1);
    USB_setup();
    i2c_setup(LOW_SPEED);
    spi_setup();
    // reset on start
    write_i2c(TSYS01_ADDR0, TSYS01_RESET);
    write_i2c(TSYS01_ADDR1, TSYS01_RESET);

    ssd1306_Fill(0);

    while (1){
        IWDG->KR = IWDG_REFRESH; // refresh watchdog
        if(refreshdisplay && uptime < UPTIME - 3){
            ssd1306_Fill(0);
            ssd1306_WriteString(Tbuf[0], Font_11x18, 1);
            ssd1306_SetCursor(0, 32);
            ssd1306_WriteString(Tbuf[1], Font_11x18, 1);
            ssd1306_UpdateScreen();
            refreshdisplay = 0;
        }
        if(lastT > Tms || Tms - lastT > 499){
            LED_blink(LED0);
            lastT = Tms;
            ssd1306_UpdateScreen();
            if(USBisOn()){
                LED_on(LED1);
                uptime = 0;
            }else{
                LED_off(LED1);
                if(++uptime > UPTIME - 3){
                    ssd1306_Fill(0);
                    ssd1306_WriteString("Power\n off!", Font_16x26, 1);
                    ssd1306_UpdateScreen();
                }
                if(uptime > UPTIME) POWEROFF();
            }
        }
        ssd1306_process();
        for(i = 0; i < 2; ++i){
            uint8_t err = 1;
            if(started[i]){
                if(Tms - started[i] > CONV_TIME){ // poll sensor i
                    if(write_i2c(Taddr[i], TSYS01_ADC_READ)){
                        uint32_t t;
                        if(read_i2c(Taddr[i], &t, 3) && t){
                            if(!calc_t(t, i)){
                                write_i2c(Taddr[i], TSYS01_RESET);
                            }else{
                                err = 0;
                                tgot[i] = Tms;
                                if(i) LEDT_on(1);
                                else LEDT_on(0);
                            }
                            started[i] = 0;
                        }
                    }
                }else{
                    err = 0;
                }
            }else{
                if(Tms - tgot[i] > WAIT_TIME){
                    if(write_i2c(Taddr[i], TSYS01_START_CONV)){
                        started[i] = Tms ? Tms : 1;
                        err = 0;
                    }else{
                        started[i] = 0;
                    }
                }
            }
            if(err){
                if(!write_i2c(Taddr[i], TSYS01_RESET)){ // sensor's absent? clear its coeff.
                    i2c_setup(LOW_SPEED);
                    if(++errcnt[i] > 10){
                        errcnt[i] = 0;
                        Tbuf[i][0] = '-';
                        tgot[i] = 0;
                        Tbuf[i][1] = 0;
                        coefficients[i][0] = 0;
                        refreshdisplay = 1;
                        if(i) LEDT_off(1);
                        else LEDT_off(0);
                    }
                }
            }else errcnt[i] = 0;
        }
        usb_proc();
        uint8_t r = 0;
        char inbuf[256];
        if((r = USB_receive(inbuf, 255))){
            inbuf[r] = 0;
            cmd_parser(inbuf);
        }
    }
    return 0;
}

