/* ********************************************
   Modul : tools.c

   Hilfsroutinen

   Autor : Christian Julius
   ******************************************** */

/* -------------- Standard Include Files ----------------- */
#include <stdbool.h>
#include <stdint.h>
#include <stdio.h>

/* --------------- Special Include Files / Librarys ------ */
#include "tm_stm32f4_rtc.h"
#include "tm_stm32f4_disco.h"
#include "tm_stm32f4_nrf24l01_cj.h"
#include "tm_stm32f4_rtc.h"
#include "tm_stm32f4_delay.h"

/* -------------- Projekt   Include Files ---------------- */
#include "config.h"
#include "tools.h"
#include "sensor.h"

/* -------------- Typen Deklarationen--------------------- */
typedef struct mydata {
    float    Pressure_Now,                               // Aktueller Druckwert
             Temperature_Now;                            // Aktueller Temperaturwert

    float    T_History_Max,                              // Maximaler gemessener Temperaturwert
             T_History_Min;                              // Minimaler gemessener Temperaturwert

    uint32_t P_History_Max,                              // Maximaler gemessener Druckwert
             P_History_Min;                              // Minimaler gemessener Druckwert
    float    P_History_Avg;
} mydata_t;

/* -------------- Defintionen ---------------------------- */
#define MBAR_STURMTIEF       970     // kleiner 980 mBar
#define MBAR_TIEF            990     // 990 - 1010
#define MBAR_NEUTRAL         1010    // 1010 - 1016
#define MBARHOCH             1016    // > 1016
#define RF_CHANNEL           10

/* -------------- Private globale Variablen --------------- */
uint8_t leds[5] = {LED_OFF,LED_OFF,LED_OFF,LED_OFF,LED_OFF};
gebiet_t            Wetter;                     // Enum für den Typ: Hoch, Tief, Sturmtief
volatile uint32_t   millis;                     // Millisekunden seit Systemstart
volatile uint32_t   delaytimer;
event_timer_t       timer;                      // Zeiten (ISR)
TimeFromStart_t     TimeFromStart;


///////////////////////////////////////////////////////////////
// Interrupthandler für die RTC Wakeup, 1s Zyklus
// Holt die CPU aus dem Stop Modus heraus
///////////////////////////////////////////////////////////////

const uint16_t color[5] = {WLED_ROT1, WLED_ROT2, WLED_GLB, WLED_GRN, WLED_BLAU};

void TM_RTC_RequestHandler()
{
    /* Bedienung der LEDs auf dem Board
       0 =  aus
       1 =  ein
       2 = blinkt mit 1Hz
   */

    for (uint16_t i = 0; i < 5; i++) {
        if      (leds[i] == LED_OFF)   GPIO_ResetPin(GPIOE,color[i]);
        else if (leds[i] == LED_ON)    GPIO_SetPin(GPIOE,color[i]);
        else if (leds[i] == LED_BLINK) GPIO_TogglePin(GPIOE,color[i]);
    }

    // Datum & Zeit in Struct auslesen
    TM_RTC_GetDateTime(&Time, TM_RTC_Format_BIN);
    flags.rtc_1s = true;

    GPIO_TogglePin(HWPORT,LED_GREEN);

    // Timer bereits resettet?
    if (timer.init != 0xaffe)
    {
        timer.init = 0xaffe;
        timer.sec  = Time.seconds;
        timer.min  = Time.minutes;
        timer.min15 = Time.minutes;
        timer.min30 = 0;
        timer.hour = 0;
        timer.day  = 0;

        flags.rtc_1s    = false;
        flags.rtc_1min  = false;
        flags.rtc_1hr   = false;
        flags.rtc_30min = false;
        flags.rtc_15min = false;
        flags.rtc_1day  = false;
        return;
    }

#ifdef DISPLAY_CUTOFF_AT_NIGHT
     if (LDRValue < 1400) {
         GPIO_ResetPin(HWPORT,RELAIS);
     }
     if (LDRValue > 2500) {
         GPIO_SetPin(HWPORT,RELAIS);
     }
#endif

    // Zeiten im 24h Format hochzaehlen, Flags setzen
    if (++timer.sec % 60 == 0) {
        timer.sec = 0;
        flags.rtc_1min = true;

        // 15 Minuten Timer
        if (++timer.min15 % 15 == 0) {
              flags.rtc_15min = true;
              timer.min15 = 0;
        }

        // 30 Minuten Timer
        if (++timer.min30 % 30 == 0 ) {
              flags.rtc_30min = true;
              timer.min30 = 0;
        }

        // 60 Minuten Timer
        if (++timer.min % 60 == 0) {
            timer.min = 0;
            flags.rtc_1hr = true;
            if (++timer.hour > 23)
                timer.hour = 0;
                flags.rtc_1day = true;
                if (++timer.day > 99)
                    timer.day = 0;
        }
    }
}


// Der obligatorische Systick Interrupt als Betriebsstundenzaehler
// Frequenz: 1 Millisekunde
void SysTick_Handler() {

    delaytimer++;

    millis++;
    if (millis % 1000 == 0) {
        TimeFromStart.seconds++;
        if (TimeFromStart.seconds > 59) {
            TimeFromStart.seconds = 0;
            TimeFromStart.minutes++;
            if (TimeFromStart.minutes > 59) {
                TimeFromStart.minutes = 0;
                TimeFromStart.hours++;
            }
        }
    }
}

// Verzögert die Ausführung um N Millisekunden
void DelayMs(uint32_t msec) {

    delaytimer = 0;
    while (delaytimer < msec);
}

//////////////////////////////////////////////
// Sendet einen Datensatz über das Funkmodul
//////////////////////////////////////////////

static uint8_t TxAddress[] = { 0x02, 0x02, 0x19, 0x68, 0x00 };   // Adresse des Empfänger Moduls
static uint8_t MyAddress[] = { 0x30, 0x09, 0x20, 0x02, 0x00 };  // Meine Moduladresse



uint8_t tls_RFSendData()
{
    TM_NRF24L01_Transmit_Status_t transmissionStatus;
    mydata_t tx;

    if (sizeof(tx) > 32)
        return ERROR;

    TM_NRF24L01_Init(RF_CHANNEL,sizeof(tx));
    TM_NRF24L01_SetRF(TM_NRF24L01_DataRate_250k,TM_NRF24L01_OutputPower_0dBm);    // RF Modul einstellen

    // Datensatz zusammenbauen
    tx.Pressure_Now     = Work.Pressure_Now;
    tx.Temperature_Now  = Work.Temperature_Now;
    tx.P_History_Avg    = Work.P_History_Avg;
    tx.P_History_Max    = Work.P_History_Max;
    tx.P_History_Min    = Work.P_History_Min;
    tx.T_History_Max    = Work.T_History_Max;
    tx.T_History_Min    = Work.T_History_Min;

    TM_NRF24L01_SetMyAddress(MyAddress);        // Setze RX Adresse
    TM_NRF24L01_SetTxAddress(TxAddress);        // Setze TX Adresse

    // Senden und warten bis fertig
    GPIO_SetPin(HWPORT,LED_ORANGE);

    uint32_t timeout = 20000;
    TM_NRF24L01_Transmit((uint8_t*)&tx);
    do {
        transmissionStatus = TM_NRF24L01_GetTransmissionStatus();
        if (--timeout == 0) {
            GPIO_ResetPin(HWPORT,LED_ORANGE);
            return ERROR;
        }
    } while (transmissionStatus == TM_NRF24L01_Transmit_Status_Sending);

    // LED aus, Sender aus
    GPIO_ResetPin(HWPORT,LED_ORANGE);
    TM_NRF24L01_PowerDown();

    return SUCCESS;

}

//////////////////////////////////////////////////////
// Sendet einen großen Datenblock als einzelne Pakete
//////////////////////////////////////////////////////

uint8_t tls_RFTransmitMultiBytes(uint8_t* data, uint16_t count)
{
    #define PAKETSIZE   32
    TM_NRF24L01_Transmit_Status_t transmissionStatus;
    uint32_t timeout;
    uint16_t blocks, rest;

    uint8_t *p = data;
    blocks     = count / PAKETSIZE;
    rest       = count - blocks * PAKETSIZE;

    // RF Modul einstellen
    TM_NRF24L01_Init(RF_CHANNEL,PAKETSIZE);
    TM_NRF24L01_SetRF(TM_NRF24L01_DataRate_1M,TM_NRF24L01_OutputPower_0dBm);
    TM_NRF24L01_SetMyAddress(MyAddress);
    TM_NRF24L01_SetTxAddress(TxAddress);

    // Ganze Blocks übertragen
    GPIO_SetPin(HWPORT,LED_ORANGE);
    for (uint16_t i = 0;i < blocks; i++) {
        TM_NRF24L01_Transmit(p);
        timeout = 20000;
        do {
            transmissionStatus = TM_NRF24L01_GetTransmissionStatus();
            if (--timeout == 0) {
                GPIO_ResetPin(HWPORT,LED_ORANGE);
                return ERROR;
            }
        } while (transmissionStatus == TM_NRF24L01_Transmit_Status_Sending);
        p = p + PAKETSIZE;
        // Empfänger Zeit lassen zu verarbeiten
        Delayms(1);
    }

    // Rest übertragen
    TM_NRF24L01_Init(RF_CHANNEL,rest);
    TM_NRF24L01_SetRF(TM_NRF24L01_DataRate_1M,TM_NRF24L01_OutputPower_0dBm);

    TM_NRF24L01_Transmit(p);
    timeout = 20000;
    do {
        transmissionStatus = TM_NRF24L01_GetTransmissionStatus();
        if (--timeout == 0) {
            GPIO_ResetPin(HWPORT,LED_ORANGE);
            return ERROR;
        }
    } while (transmissionStatus == TM_NRF24L01_Transmit_Status_Sending);

    GPIO_ResetPin(HWPORT,LED_ORANGE);
    return SUCCESS;
}

///////////////////////////////////////////////////
// Berechnet eine Ausgleichsgerade
///////////////////////////////////////////////////

uint8_t tls_CalcRegression() {

      #define ZEITRAHMEN    20          // Zeitrahmen 12 Stunden

      double    Xi_Xq, Yi_Yq, Xi_Xq2, XiXqYiYq,
                Xq, Yq,
                m;     // Steigung, Offset

      // Ausreichend Werte vorhanden?
      if (Work.ptr < ZEITRAHMEN) {
            Work.RegrSteigung = 0.0;
            return 0;
      }

      // Berechnung der Mittelwerte des Druckes
      Yq= 0;
      for (uint16_t i = (Work.ptr - ZEITRAHMEN); i < Work.ptr;i++)
          Yq = Yq + Work.History[i].Pressure;

      Yq /= ZEITRAHMEN;

      // Mittelwert [xq] des Zeitrahmens ist fix
      //Xq    = (2 * Work.ptr - ZEITRAHMEN) / 2.0;
      Xq    = ZEITRAHMEN / 2.0;

      // Summe [Xi - Xq], [Yi - yq] und [Xi - Xq]2 berechnen
      Xi_Xq  = 0;
      Xi_Xq2 = 0;
      XiXqYiYq = 0;
      for (int16_t i = (Work.ptr-ZEITRAHMEN); i < Work.ptr;i++) {
        Xi_Xq2 += pow(i - Xq,2);
        Xi_Xq  += (i - Xq);
        Yi_Yq = Work.History[i].Pressure - Yq;
        XiXqYiYq += (Xi_Xq * Yi_Yq);
      }

     m = XiXqYiYq / Xi_Xq2;

     // Steigung eintragen
     Work.RegrSteigung = (float)m;

     return 1;
}


void tls_SetWetterLED(uint8_t nr, uint8_t status)
{
    if (nr > 4) return;
    leds[nr] = status;
}

/*  **************************************************************************

    Bestimmung, ob eine steigende oder fallende Tendenz des Luftdruckes
    vorliegt. Dazu werden folgende Faktoren ausgewertet:

    1. Absoluter Luftdruck
    2. Steigung des Druckes der letzten 12 Stunden > 0.3 mbar/h  > 1 mBar/h

    Orkantief   : 2 rote LED blinkend
    Tief        : 1 rote LED
    Neutral     : 1 gelbe LED
    Hoch:       : 1 grüne LED
    Extremhoch  : 1 blaue LED blinkend

***************************************************************************** */

void gfx_Wettervorhersage()
{
    #define  TESTED_HOURS  12

    int8_t wertung = 0;

    __disable_irq();

    // Alle LEDs löschen
    GPIO_ResetPin(GPIOD,LED_ROT);           // Hoch / Tief Anzeige
    GPIO_ResetPin(GPIOD,LED_GRN);
    GPIO_ResetPin(GPIOD,LED_GLB);

    tls_SetWetterLED(LROT1,LED_OFF);        // Wettertendenz
    tls_SetWetterLED(LROT1,LED_OFF);
    tls_SetWetterLED(LGLB,LED_OFF);
    tls_SetWetterLED(LGRN,LED_OFF);
    tls_SetWetterLED(LBLAU,LED_OFF);

    // ---- Zustand: Hoch/Tief setzen ------
    if (Work.Pressure_Now > (PRESSURE_NORMAL + PRES_TOLERANZ)) {
        Wetter = Hoch;
        GPIO_SetPin(GPIOD,LED_GRN);
    } else
    if (Work.Pressure_Now < (PRESSURE_NORMAL - PRES_TOLERANZ)) {
        Wetter = Tief;
        GPIO_SetPin(HWPORT,LED_ROT);
    } else {
        Wetter = Neutral;
        GPIO_SetPin(HWPORT,LED_GLB);
    }

    // ---- Tendenz bewerten ------
    switch (Wetter) {
        case Tief      : wertung = -1;
                         break;
        case Neutral   : wertung = 0;
                         break;
        case Hoch      : wertung = 1;
                         break;
        default        : wertung = 0;
                         break;
    }

    // Aktueller Druck über Mittelwert?
    if (Work.Pressure_Now > Work.P_History_Avg)
        wertung++;
    else
        wertung--;

    // Steigung der Ausgleichsgeraden
    if (Work.RegrSteigung > 0.25) {
        wertung++;
        if (Work.RegrSteigung > 0.5) {
            wertung++;
         }
    } else
    if (Work.RegrSteigung < -0.25) {
        wertung++;
        if (Work.RegrSteigung < -0.5) {
            wertung++;
        }
    }

    /* -4 < Wertung < +4 */
    switch (wertung) {

    case -4:    tls_SetWetterLED(LROT1,LED_BLINK);
                tls_SetWetterLED(LROT2,LED_BLINK);
                break;
    case -3:    tls_SetWetterLED(LROT1,LED_BLINK);
                tls_SetWetterLED(LROT2,LED_ON);
                break;
    case -2:    tls_SetWetterLED(LROT1,LED_ON);
                tls_SetWetterLED(LROT2,LED_ON);
                break;
    case -1:    tls_SetWetterLED(LROT1,LED_ON);
                break;
    case  0:    tls_SetWetterLED(LGLB,LED_ON);
                break;
    case  1:    tls_SetWetterLED(LGRN,LED_ON);
                break;
    case  2:    tls_SetWetterLED(LGRN,LED_ON);
                tls_SetWetterLED(LBLAU,LED_ON);
                break;
    case  3:    tls_SetWetterLED(LGRN,LED_ON);
                tls_SetWetterLED(LBLAU,LED_BLINK);
                break;
    case  4:    tls_SetWetterLED(LGRN,LED_BLINK);
                tls_SetWetterLED(LBLAU,LED_BLINK);
                break;
    default:    tls_SetWetterLED(LGLB,LED_ON);
                break;
    }

    __enable_irq();

}


/*
    enum {kleiner,groesser} last_value;     // Vergleichstyp für Druck
    uint16_t stunden;

    uint16_t pres_bigger  = 0;
    uint16_t pres_smaller = 0;

    // Suchschleife
    last_value = groesser;
    for (uint16_t i = (Work.ptr - stunden); i < Work.ptr; i++)
    {
        // Druck steigend?
        if (Work.History[i+1].Pressure > Work.History[i].Pressure) {
           pres_bigger++;
           last_value = groesser;
           continue;
        }
        if (Work.History[i+1].Pressure < Work.History[i].Pressure) {
           pres_smaller++;
           last_value = kleiner;
           continue;
        }

        // Druck gleich? Dann Vorgänger gleichsetzen
        if (last_value == kleiner)
            pres_smaller++;
         else
            pres_bigger++;
    }

    // Setzen der LEDs
    TM_GPIO_SetPinLow(GPIOD,LED_GLB);
    TM_GPIO_SetPinLow(GPIOD,LED_GRN);
    TM_GPIO_SetPinLow(GPIOD,LED_ROT);

    // Schönes Wetter
    if (Work.Pressure_Now > (Work.History[Work.ptr - stunden].Pressure + 1)) {
        if (pres_bigger >= (0.7 * (float)stunden)) {        // 3/4 aller Werte grün?
            TM_GPIO_SetPinHigh(GPIOD,LED_GRN);
            return;
        }
    }

    // Schlechtes Wetter (wie oben)
    if (Work.Pressure_Now < (Work.History[Work.ptr - stunden].Pressure -1 )) {
        if (pres_smaller >= (0.7 * (float)stunden)) {
            TM_GPIO_SetPinHigh(GPIOD,LED_ROT);
            return;
        }
    }

    TM_GPIO_SetPinHigh(GPIOD,LED_GLB);

*/
