/*
    ChibiOS/RT - Copyright (C) 2006-2013 Giovanni Di Sirio

    Licensed under the Apache License, Version 2.0 (the "License");
    you may not use this file except in compliance with the License.
    You may obtain a copy of the License at

        http://www.apache.org/licenses/LICENSE-2.0

    Unless required by applicable law or agreed to in writing, software
    distributed under the License is distributed on an "AS IS" BASIS,
    WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
    See the License for the specific language governing permissions and
    limitations under the License.
*/

#include "ch.h"
#include "hal.h"
#include "test.h"
#include "serial.h"
#include "gpt.h"
#include <string.h>
#include "keil/GPS_dekoduj.h"

static void pwmpcb(PWMDriver *pwmp);
static void adccb(ADCDriver *adcp, adcsample_t *buffer, size_t n);

/* Total number of channels to be sampled by a single ADC operation.*/
#define ADC_GRP1_NUM_CHANNELS   2

/* Depth of the conversion buffer, channels are sampled four times each.*/
#define ADC_GRP1_BUF_DEPTH      4

/*MAX delka prikazu, ktery uzivatel muze zadat*/
#define MAX_DELKA_PRIKAZU                               10

/*Velikost GPS bufferu*/
#define GPS_BUFFER                                                      500

extern NMEA_GPGGA GPGGA_informace;
Thread *tp_odpal = NULL;
/*
 * ADC samples buffer.
 */
static adcsample_t samples[ADC_GRP1_NUM_CHANNELS * ADC_GRP1_BUF_DEPTH];

/*
 * ADC conversion group.
 * Mode:        Linear buffer, 4 samples of 2 channels, SW triggered.
 * Channels:    IN10   (48 cycles sample time)
 *              Sensor (192 cycles sample time)
 */
static const ADCConversionGroup adcgrpcfg = {
  FALSE,
  ADC_GRP1_NUM_CHANNELS,
  adccb,
  NULL,
  /* HW dependent part.*/
  0,                        /* CR1 */
  ADC_CR2_SWSTART,          /* CR2 */
  0,
  ADC_SMPR2_SMP_AN10(ADC_SAMPLE_48) | ADC_SMPR2_SMP_SENSOR(ADC_SAMPLE_192),
  0,
  ADC_SQR1_NUM_CH(ADC_GRP1_NUM_CHANNELS),
  0,
  0,
  0,
  ADC_SQR5_SQ2_N(ADC_CHANNEL_IN10) | ADC_SQR5_SQ1_N(ADC_CHANNEL_SENSOR)
};
/*
 * Konfigurace USART2
 */

static const SerialConfig USART2_config =
{
        /*Speed*/
  9600,
        /*Initialization value for the CR1 register.*/
  0,
        /*Initialization value for the CR2 register.*/
  USART_CR2_STOP1_BITS | USART_CR2_LINEN,
        /*Initialization value for the CR3 register.*/
  0
};
//GPTDriver GPTD2;
/*
* GPT2 callback.
*/
static void gpt2cb(GPTDriver *gptp)
{
  (void)gptp;
                palTogglePad(GPIOB, GPIOB_LED4);
         /* Wakes up the thread.*/
  chSysLockFromIsr();
  if (tp_odpal != NULL) {
    //tp_odpal->p_u.rdymsg = (msg_t)55;     /* Sending the message, optional.*/
    chSchReadyI(tp_odpal);
    tp_odpal = NULL;
  }
  chSysUnlockFromIsr();
}       

 /*
  *Konfigurace casovace 2
  */
        static const GPTConfig gpt2cfg = 
        {
                 1000, /*1000Hz f*/
                 gpt2cb /*callback fce*/
        };



/*
 * PWM configuration structure.
 * Cyclic callback enabled, channels 1 and 2 enabled without callbacks,
 * the active state is a logic one.
 */
static PWMConfig pwmcfg = {
  10000,                                    /* 10kHz PWM clock frequency.   */
  10000,                                    /* PWM period 1S (in ticks).    */
  pwmpcb,
  {
    {PWM_OUTPUT_ACTIVE_HIGH, NULL},
    {PWM_OUTPUT_ACTIVE_HIGH, NULL},
    {PWM_OUTPUT_DISABLED, NULL},
    {PWM_OUTPUT_DISABLED, NULL}
  },
  /* HW dependent part.*/
  0
};

/*
* USART konfigurace
*/


/*
 * PWM cyclic callback.
 * A new ADC conversion is started.
 */
static void pwmpcb(PWMDriver *pwmp) {

  (void)pwmp;

  /* Starts an asynchronous ADC conversion operation, the conversion
     will be executed in parallel to the current PWM cycle and will
     terminate before the next PWM cycle.*/
  chSysLockFromIsr();
  adcStartConversionI(&ADCD1, &adcgrpcfg, samples, ADC_GRP1_BUF_DEPTH);
  chSysUnlockFromIsr();
}

/*
 * ADC end conversion callback.
 * The PWM channels are reprogrammed using the latest ADC samples.
 * The latest samples are transmitted into a single SPI transaction.
 */
void adccb(ADCDriver *adcp, adcsample_t *buffer, size_t n) {

  (void) buffer; (void) n;
  /* Note, only in the ADC_COMPLETE state because the ADC driver fires an
     intermediate callback when the buffer is half full.*/
  if (adcp->state == ADC_COMPLETE) {
    adcsample_t avg_ch1, avg_ch2;

    /* Calculates the average values from the ADC samples.*/
    avg_ch1 = (samples[0] + samples[2] + samples[4] + samples[6]) / 4;
    avg_ch2 = (samples[1] + samples[3] + samples[5] + samples[7]) / 4;

    chSysLockFromIsr();

    /* Changes the channels pulse width, the change will be effective
       starting from the next cycle.*/
    pwmEnableChannelI(&PWMD4, 0, PWM_FRACTION_TO_WIDTH(&PWMD4, 4096, avg_ch1));
    pwmEnableChannelI(&PWMD4, 1, PWM_FRACTION_TO_WIDTH(&PWMD4, 4096, avg_ch2));


    chSysUnlockFromIsr();
  }
}

/*
* Vlakno pro ovladani odpalovaci sekvence
*/
        static WORKING_AREA(waThread_odpal, 128);
        static msg_t Thread_odpal(void *arg) {
        uint8_t stav = 0; // rika, ve ktere fazi je odpalovani
        uint8_t odpal_povolen = 0;
        (void)arg;
  chRegSetThreadName("Odpal_vlakno");
        while (TRUE)
        {
    msg_t msg;

    /* Waiting for the IRQ to happen.*/
    chSysLock();
    tp_odpal = chThdSelf();
    chSchGoSleepS(THD_STATE_SUSPENDED);
    msg = chThdSelf()->p_u.rdymsg;  /* Retrieving the message, optional.*/
    chSysUnlock();
    /* Perform processing here.*/
                if(msg == 1)
                {
                        odpal_povolen = 1;
                }
                if (odpal_povolen == 1)
                {                       
                        switch (stav)
                        {
                                case 0:
                                        sdWrite(&SD1,"0",2);
                                        gptStartOneShot(&GPTD2,1000);
                                break;                  
                                case 1:
                                        sdWrite(&SD1,"1",2);
                                        gptStartOneShot(&GPTD2,1500);
                                break;
                                case 2: 
                                        sdWrite(&SD1,"2",2);
                                        gptStartOneShot(&GPTD2,2000);
                                break;
                                case 3: 
                                        sdWrite(&SD1,"3",2);
                                        gptStartOneShot(&GPTD2,3000);
                                break;
                                case 4: 
                                        sdWrite(&SD1,"4",2);
                                        gptStartOneShot(&GPTD2,5000);
                                break;
                                default: 
                                        sdWrite(&SD1,"konec",6); //posledni krok
                                odpal_povolen = 0;
                                stav = 0;
                                break;
                        }
                        stav++;
                }       
}
}


/*
 * Vlakno pro obsluhu GPS prijimace
 */
static WORKING_AREA(waThread_GPS, 768);
static msg_t Thread_GPS(void *arg) {
        /*
        * Nacita se jen nekolik NMEA zprav, aby se neplytvalo pameti na ulozeni kompletniho
        * setu s tím rizikem, ze se nekdy nenacte aktualni informace o poloze.
        */
  uint8_t inBuffer[GPS_BUFFER];
        char *zacatek_retezce;
        char *konec_retezce;
        uint8_t pocet_znaku;
        uint8_t NMEA_zprava[100];

  (void)arg;
  chRegSetThreadName("GPS_NMEA");
  /*
   * Activates the serial driver 2 using the driver default configuration.
   * PA2 and PA3 are routed to USART2.
   */

  sdStart(&SD2, &USART2_config);
  palSetPadMode(GPIOA, 2, PAL_MODE_ALTERNATE(7));       //TX
  palSetPadMode(GPIOA, 3, PAL_MODE_ALTERNATE(7));       //RX
        

  while (TRUE) {
    chThdSleepMilliseconds(1000); //neni potreba data vycitat rychleji
                sdRead(&SD2,inBuffer,GPS_BUFFER);
                
        /*
         *Nejprve se vycte cast NMEA dat, pote se vyhleda retezec GPGGA zpravy, ta se vyparsuje a pomoci fce
         *dekoduj_zpravu_GPS, ktera vyparsuje data o poloze a jine, a ulozi je do struktury GPGGA_informace.
         */

                if ((zacatek_retezce = strstr((char *)inBuffer,"$GPGGA")) != NULL)
                {
                        if ((konec_retezce = strstr(zacatek_retezce,"*")) != NULL)
                        {       
                                pocet_znaku = (konec_retezce-zacatek_retezce)/sizeof(char);
                                if (pocet_znaku > 100)
                                {
                                        pocet_znaku = 100;
                                }
                                strncpy((char *)NMEA_zprava,zacatek_retezce,pocet_znaku);
                                dekoduj_zpravu_GPS(&NMEA_zprava[0],pocet_znaku);
                                sdWrite(&SD2,"Latitude: ",sizeof("Latitude: ")/sizeof(char));
                                sdWrite(&SD2,GPGGA_informace.Latitude,sizeof(GPGGA_informace.Latitude)/sizeof(uint8_t));
                                sdWrite(&SD2,"\r\n",2);
                                sdWrite(&SD2,"Longitude: ",sizeof("Longitude: ")/sizeof(char));
                                sdWrite(&SD2,GPGGA_informace.Longitude,sizeof(GPGGA_informace.Longitude)/sizeof(uint8_t));
                                sdWrite(&SD2,"\r\n",2);
                                sdWrite(&SD2,"Altitude: ",sizeof("Altitude: ")/sizeof(char));                   
                                sdWrite(&SD2,GPGGA_informace.Altitude,sizeof(GPGGA_informace.Altitude)/sizeof(uint8_t));
                                sdWrite(&SD2,"\r\n",2);                 
                                sdWrite(&SD2,"Status: ",sizeof("Status: ")/sizeof(char));                       
                                sdWrite(&SD2,&GPGGA_informace.Status_GPS,sizeof(GPGGA_informace.Status_GPS)/sizeof(uint8_t));
                                sdWrite(&SD2,"\r\n",2); 
                                sdWrite(&SD2,NMEA_zprava,pocet_znaku);
                                sdWrite(&SD2,"\r\n",2);
                        }
                        else
                        {                       
                                sdWrite(&SD2,"\r\n",2);
                                sdWrite(&SD2,"Nenalezen ukoncovaci znak NMEA zpravy *",sizeof("Nenalezen ukoncovaci znak NMEA zpravy *")/sizeof(char));
                                sdWrite(&SD2,inBuffer,GPS_BUFFER);
                                sdWrite(&SD2,"\r\n",2);
                        }
                }
                else
                        sdWrite(&SD2,"Nenalezen zacatek GPGGA zpravy",sizeof("Nenalezen zacatek GPGGA zpravy")/sizeof(char));
                
  }
}

/*
 * This is a periodic thread that does absolutely nothing except increasing
 * a seconds counter.
 */
static WORKING_AREA(waThread1, 128);
static msg_t Thread1(void *arg) {
  static uint32_t seconds_counter;

  (void)arg;
  chRegSetThreadName("counter");
  while (TRUE) {
    chThdSleepMilliseconds(1000);
    seconds_counter++;
  }
}

void dekodujPrikaz(char *prikaz)
{
        //int8_t porov;
        //porov = strcmp(prikaz,"ahoj");
        if(strcmp(prikaz,"odpal") == 0)
        {
                /* Wakes up the thread.*/
                chSysLockFromIsr();
                if (tp_odpal != NULL) {
                        tp_odpal->p_u.rdymsg = (msg_t)1;     /* odpal povolen*/
                        chSchReadyI(tp_odpal);
                        tp_odpal = NULL;
                }
                chSysUnlockFromIsr();           
                palTogglePad(GPIOB, GPIOB_LED4);
        }
        else if (strcmp(prikaz,"zdar") == 0)
        {
                palTogglePad(GPIOB, GPIOB_LED3);
        }
        else 
        {
                uint8_t zp_neplatny[] = "Neplatny prikaz, spravny format *<prikaz>\n\r";
                sdWrite(&SD1,zp_neplatny,sizeof(zp_neplatny)/sizeof(uint8_t));
                palTogglePad(GPIOB, GPIOB_LED3);
        }
}
        


/*
 * Application entry point.
 */
int main(void) {
        uint8_t znaky[20];
        char prikaz[MAX_DELKA_PRIKAZU + 1];
        uint8_t uk_pri = 0;
        uint8_t zapis = 0;      // pokud je prijata '*', zacina se s rozpoznanim prikazu az do '\n'

        /*
   * System initializations.
   * - HAL initialization, this also initializes the configured device drivers
   *   and performs the board-specific initializations.
   * - Kernel initialization, the main() function becomes a thread and the
   *   RTOS is active.
   */
  halInit();
  chSysInit();  

  /*
   * Activates the serial driver 1 using the driver default configuration.
   * PA9 and PA10 are routed to USART1.
   */
  sdStart(&SD1, NULL);
  palSetPadMode(GPIOA, 9, PAL_MODE_ALTERNATE(7));       //TX
  palSetPadMode(GPIOA, 10, PAL_MODE_ALTERNATE(7)); //RX

        //gptObjectInit(&GPTD2);
 /*
        * aktivuje timer2 a prejde tak do aktivniho stavu
        */
        gptStart(&GPTD2,&gpt2cfg);
        
  /*
   * If the user button is pressed after the reset then the test suite is
   * executed immediately before activating the various device drivers in
   * order to not alter the benchmark scores.
   */
  if (palReadPad(GPIOA, GPIOA_BUTTON))
    TestThread(&SD1);

  /*
   * Initializes the SPI driver 2. The SPI2 signals are routed as follow:
   * PB12 - NSS.
   * PB13 - SCK.
   * PB14 - MISO.
   * PB15 - MOSI.
   */
  palSetPad(GPIOB, 12);
  palSetPadMode(GPIOB, 12, PAL_MODE_OUTPUT_PUSHPULL |
                           PAL_STM32_OSPEED_HIGHEST);           /* NSS.     */
  palSetPadMode(GPIOB, 13, PAL_MODE_ALTERNATE(5) |
                           PAL_STM32_OSPEED_HIGHEST);           /* SCK.     */
  palSetPadMode(GPIOB, 14, PAL_MODE_ALTERNATE(5));              /* MISO.    */
  palSetPadMode(GPIOB, 15, PAL_MODE_ALTERNATE(5) |
                           PAL_STM32_OSPEED_HIGHEST);           /* MOSI.    */

  /*
   * Initializes the ADC driver 1 and enable the thermal sensor.
   * The pin PC0 on the port GPIOC is programmed as analog input.
   */
  adcStart(&ADCD1, NULL);
  adcSTM32EnableTSVREFE();
  palSetPadMode(GPIOC, 0, PAL_MODE_INPUT_ANALOG);

  /*
   * Initializes the PWM driver 4, routes the TIM4 outputs to the board LEDs.
   */
  pwmStart(&PWMD4, &pwmcfg);
  //palSetPadMode(GPIOB, GPIOB_LED4, PAL_MODE_ALTERNATE(2));
  palSetPadMode(GPIOB, GPIOB_LED3, PAL_MODE_ALTERNATE(2));
        /*
         * Zelena led aktivace portu
         */

        palSetPadMode(GPIOB, GPIOB_LED4, PAL_MODE_OUTPUT_PUSHPULL);
        palSetPadMode(GPIOB, GPIOB_LED3, PAL_MODE_OUTPUT_PUSHPULL);
        //test_println("ahoj");
 /*sdRead(&SD1,znaky,5);
        sdWrite(&SD1,"\n",1);   
        sdWrite(&SD1,znaky,2);
        palWritePad(GPIOB, GPIOB_LED4, PAL_HIGH);
  */
        /*
   * Creates the example thread.
   */
  chThdCreateStatic(waThread1, sizeof(waThread1), NORMALPRIO, Thread1, NULL);

        /*
   * Vytvori vlakno pro prijem dat z GPS modulu
   */
  chThdCreateStatic(waThread_GPS, sizeof(waThread_GPS), NORMALPRIO, Thread_GPS, NULL);
 /*
        * Vytvori vlakno pro odpalovaci sekvenci
        */
                (void)chThdCreateStatic (waThread_odpal, sizeof(waThread_odpal), NORMALPRIO, Thread_odpal, NULL);


  /*
   * Normal main() thread activity, in this demo it does nothing except
   * sleeping in a loop and check the button state, when the button is
   * pressed the test procedure is launched with output on the serial
   * driver 1.
   */
  while (TRUE) {

                        sdRead(&SD1,znaky,1);
                /*Kdyz uzivatel stiskne enter -> dekoduj a vykonej prikaz*/
                        if (znaky[0] == '\r')
                        {
                                uk_pri = 0;
                                zapis = 0;
                                dekodujPrikaz(prikaz);
                                prikaz[0] = 0;
                        }
                        /*Uklada prikaz*/
                        if (zapis == 1 && uk_pri < MAX_DELKA_PRIKAZU)
                        {
                                prikaz[uk_pri++] = znaky[0];
                                prikaz[uk_pri + 1] = 0;
                        }
                        /*Pokud je prikaz delsi, nez by mel byt, prestane ukladat a upozorni uzivatele*/
                        else if (zapis == 1 && uk_pri == MAX_DELKA_PRIKAZU)
                        {
                                uk_pri = 0;
                                zapis = 0;
                                dekodujPrikaz(prikaz);
                        }
                        /*Uzivatel zacal zadavat prikaz*/
                        if(znaky[0] == '*' && zapis == 0)
                                zapis = 1;

                        
    if (palReadPad(GPIOA, GPIOA_BUTTON))

      TestThread(&SD1);
    chThdSleepMilliseconds(500);
  }
}