/* Copyright (c) 2007 Axel Wachtler
   All rights reserved.

   Redistribution and use in source and binary forms, with or without
   modification, are permitted provided that the following conditions
   are met:

   * Redistributions of source code must retain the above copyright
     notice, this list of conditions and the following disclaimer.
   * Redistributions in binary form must reproduce the above copyright
     notice, this list of conditions and the following disclaimer in the
     documentation and/or other materials provided with the distribution.
   * Neither the name of the authors nor the names of its contributors
     may be used to endorse or promote products derived from this software
     without specific prior written permission.

   THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS"
   AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE
   IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE
   ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT OWNER OR CONTRIBUTORS BE
   LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR
   CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF
   SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS
   INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN
   CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE)
   ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE
   POSSIBILITY OF SUCH DAMAGE. */

/* $Id: diagradio.c,v 1.7 2010/01/02 15:37:06 awachtler Exp $ */
/**
 * @file
 * @brief Implementation of the @ref grpAppRdiag
 *
 * @ingroup grpAppRdiag
 */
/**
 * This application provides some basic test functions of the radio chip.
 *
 */
#include <string.h>
#include "radio.h"
#include "transceiver.h"
#include "ioutil.h"
#include "timer.h"
/* === Types ===============================================*/

typedef struct
{
    uint8_t new;
    uint8_t len;
    uint8_t crc;
    uint8_t lqi;
    uint8_t rssi;
    uint8_t seq;
} rx_frame_t;


typedef struct
{
    uint32_t rxcnt;
    uint32_t crcerr;
    uint32_t rssi;
    uint32_t lqi;

    uint32_t txcnt;
    time_t   start;
} statistic_t;


typedef struct
{
    channel_t channel;
    txpwr_t   txpwr;
    ccamode_t ccamode;
    rxidle_t  rxidle;
} rdiag_ctx_t;

/* === Prototypes ==========================================*/
void rdiag_init(void);
void set_next_channel(channel_t chaninc);
void set_next_pwr(int8_t pwrinc);
void set_next_cca(void);

void toggle_rxon_idle(void);
void send_frame(uint8_t seq);
void send_continous(void);
void show_statistic(bool reset);
void help(void);
time_t blink(timer_arg_t t);

/* === Globals =============================================*/
static uint8_t RxBuf[MAX_FRAME_SIZE];
static uint8_t TxBuf[MAX_FRAME_SIZE];
static rx_frame_t rxfrm;

statistic_t RdiagStat;
static rdiag_ctx_t RdiagCtx;



int8_t verbose;
bool   conttx;
uint8_t tx_length = 42;
#define TBLINK_PERIOD (500)
#define NL "\n\r"
timer_hdl_t  th_blink;

/** factory defaults of radio parameter  */
trx_param_t PROGMEM trxp_flash = {chan: 13, txp: 0, cca: 1, edt: 11, clkm: 0};

/**
 * @brief Main function of diagradio application.
 *
 * This routine performs the initialization of the
 * hardware modules and stays in a endless loop, which
 * interpretes the commands, received from the host interface.
 *
 */
int main(void)
{

uint16_t inchar;
uint8_t tmp, seq, *p;
trx_param_t trxp;
#if 0
trx_cfg_t cfgt;
#endif

    rdiag_init();

    PRINT(NL"Radio Diag 0.20"NL);
    tmp = trx_reg_read(RG_TRX_STATUS);
    PRINTF(">RADIO INIT[%s]: %s (status=0x%02x)"NL, BOARD_NAME, (tmp == 8 ? "OK" : "FAIL"), tmp );
    PRINTF("Timer-Tick %lx"NL, TIMER_TICK);
    tmp = trx_reg_read(RG_PART_NUM);
    PRINTF("RADIO_PART_NUM = 0x%02x, RG_PART_NUM = 0x%02x"NL,
	   RADIO_PART_NUM, tmp);
    tmp = trx_reg_read(RG_VERSION_NUM);
    PRINTF("RADIO_VERSION_NUM = 0x%02x, RG_VERSION_NUM = 0x%02x"NL,
	   RADIO_VERSION_NUM, tmp);
    PRINT("Config: ");

    /* CONFIG */
#if 0
    cfgt = radio_config_recall();
    if (cfgt != CFG_NONE)
    {
        PRINTF("use settings from %s"NL, (cfgt == CFG_EEPROM ? "eeprom" : "flash"));
    }
#else
#warning "no radio_config_recall()"
#endif
    th_blink = timer_start(blink,TBLINK_PERIOD,0);
    seq = 0;

    while(1)
    {
        inchar = hif_getc();
        if(inchar<0x100)
        {
            switch ((char)inchar)
            {

                case '+':
                    set_next_channel(+1);
                    break;
                case '-':
                    set_next_channel(-1);
                    break;

                case 'p':
                    set_next_pwr(-1);
                    break;
                case 'P':
                    set_next_pwr(1);
                    break;

                case 'c':
                    tmp = radio_do_cca();
                    PRINTF(">CCA: status=%d"NL, tmp);
                    break;
                case 'C':
                    set_next_cca();
                    break;

                case 'R':
                    toggle_rxon_idle();
                    break;

                case 'r':
                    radio_set_state(STATE_RX);
                    PRINT(">SATE_RX"NL);
                    break;

                case 't':
                    radio_set_state(STATE_TX);
                    PRINT(">SATE_TX"NL);
                    break;

                case 'o':
                    radio_set_state(STATE_OFF);
                    PRINT(">SATE_OFF"NL);
                    break;

                case 'l':
                    tx_length += 1;
                    tx_length &= 0x7f;
                    PRINTF(">TX LEN = %d"NL,tx_length);
                    break;

                case 'L':
                    tx_length += 10;
                    tx_length &= 0x7f;
                    PRINTF(">TX LEN = %d"NL,tx_length);
                    break;

                case 's':
                    send_frame(tx_length);
                    break;

                case 'S':
                    send_continous();
                    break;

                case 'i':
                    show_statistic(0);
                    break;

                case 'I':
                    show_statistic(1);
                    break;

                case 'h':
                    help();
                    break;

                case 'v':
                    if(verbose>0) verbose --;
                    PRINTF(">VERBOSE: %d"NL,verbose);
                    break;

                case 'V':
                    if(verbose<2) verbose ++;
                    PRINTF(">VERBOSE: %d"NL,verbose);
                    break;

                case 'g':
                    trx_parms_get(&trxp);
                    PRINTF("{"\
                           "chan: %d, txp: %d,"\
                           " cca: %d,\n\r edt: %d,"\
                           " clkm: %d}"NL,
                            trxp.chan, trxp.txp, trxp.cca,
                            trxp.edt, trxp.clkm);
                    p = (uint8_t*)&trxp;
                    PRINTF("avrdude: w ee 8 0x%x 0x%x 0x%x 0x%x 0x%x"NL,
                           p[0],p[1],p[2],p[3],p[4]);
                    break;

#if 0
                case 'G':
                    radio_config_store();
                    PRINT("params stored in EEPROM"NL);
                    break;


                case 'b':
                    JUMP_BOOT_LOADER();
                    PRINT("There is no BootLoader defined"NL);
                    break;
#endif
            default:
                    PRINTF("unsuppored command: %s"NL, inchar);

            }
        }

        if(rxfrm.new == 1)
        {
            if (verbose > 0)
            {
                PRINTF("++FRAME len=%d, crc=%3s, lqi=%d rssi=%d seq=%d"NL,
                    rxfrm.len, rxfrm.crc ? "ERR" : "OK",
                    rxfrm.lqi, rxfrm.rssi, rxfrm.seq);
            }
            if (verbose > 1)
            {
                DUMP(rxfrm.len, RxBuf);
            }
            rxfrm.new = 0;
        }

    }
    return 0;

}


void rdiag_init(void)
{
    memset(TxBuf, 0x55, sizeof(TxBuf));
    memset(RxBuf, 0xff, sizeof(RxBuf));
    /* reset ressource  radio parameter */
    LED_INIT();
    KEY_INIT();
    timer_init();
    hif_init(9600);
    radio_init(RxBuf, MAX_FRAME_SIZE);

    sei();

    /* init radio parameter */

    RdiagCtx.channel = TRX_MIN_CHANNEL;
    radio_set_param(RP_CHANNEL(RdiagCtx.channel));

    RdiagCtx.txpwr = 0;
    radio_set_param(RP_TXPWR(RdiagCtx.txpwr));

    RdiagCtx.ccamode = 1;
    radio_set_param(RP_CCAMODE(RdiagCtx.ccamode));

    RdiagCtx.rxidle = false;
    radio_set_param(RP_IDLESTATE(STATE_OFF));
}

/**
 * @brief Increment/decrement channel.
 */
void set_next_channel(int8_t chaninc)
{

    if (chaninc > 0)
    {
        RdiagCtx.channel = TRX_NEXT_CHANNEL_WRAP(RdiagCtx.channel);
    }
    else if (chaninc < 0)
    {
        RdiagCtx.channel = TRX_PREV_CHANNEL_WRAP(RdiagCtx.channel);
    }

    radio_set_param(RP_CHANNEL(RdiagCtx.channel));
    PRINTF(">CHAN: %d"NL, RdiagCtx.channel);
}

/**
 * @brief Enable/disable mode RX_ON_IDL
 */
void toggle_rxon_idle(void)
{
    RdiagCtx.rxidle ^= 1;
    if (RdiagCtx.rxidle == 1)
    {
        radio_set_param(RP_IDLESTATE(STATE_RX));
    }
    else
    {
        radio_set_param(RP_IDLESTATE(STATE_OFF));
    }
    PRINTF(">RXON_IDLE: %d"NL, RdiagCtx.rxidle);
}

/**
 * @brief Increment/decrement TX power.
 */
void set_next_pwr(int8_t pwrinc)
{

    RdiagCtx.txpwr +=  pwrinc;
    if (RdiagCtx.txpwr > 15)
    {
        RdiagCtx.txpwr = 0;
    }
    else if (RdiagCtx.txpwr < 0)
    {
        RdiagCtx.txpwr = 15;
    }
    radio_set_param(RP_TXPWR(RdiagCtx.txpwr));
    PRINTF(">PWR: %d"NL, RdiagCtx.txpwr);
}


/**
 * @brief Select CCA Mode.
 */
void set_next_cca(void)
{

char *pcca[] = {"?","ED","CS","CS&ED"};

    RdiagCtx.ccamode += 1;
    if (RdiagCtx.ccamode > 3)
    {
        RdiagCtx.ccamode = 1;
    }

    radio_set_param(RP_CCAMODE(RdiagCtx.ccamode));

    PRINTF(">CCA: mode=%d (%s)"NL, RdiagCtx.ccamode, pcca[RdiagCtx.ccamode]);
}


/**
 * @brief Transmit a frame with a given payload length.
 */
void send_frame(uint8_t frmlen)
{
    static uint8_t seqnb;

    seqnb ++;
    if (frmlen < 5)
    {
        frmlen = 5;
    }
    if (frmlen > MAX_FRAME_SIZE)
    {
        frmlen = MAX_FRAME_SIZE;
    }
    TxBuf[0] = 1;
    TxBuf[1] = 0;
    TxBuf[2] = seqnb;
    TxBuf[frmlen - 2] = 0;
    TxBuf[frmlen - 1] = 0;
    radio_set_state(STATE_TX);
    radio_send_frame(frmlen, TxBuf, 0);

    PRINTF(">SEND len=%d"NL,frmlen);
    if (verbose > 1)
    {
        DUMP(frmlen, TxBuf);
    }
}

/**
 * @brief Send frames permanently (next frame is triggered at TX_END_IRQ).
 */
void send_continous(void)
{
static bool active = 0;

    active  ^= 1;

    if(active)
    {
        RdiagCtx.rxidle = 2;
        radio_set_param(RP_IDLESTATE(STATE_TX));
    }
    else
    {
        RdiagCtx.rxidle = 0;
        radio_set_param(RP_IDLESTATE(STATE_OFF));

    }
    PRINTF(">SEND CONTINOUS %s"NL, (RdiagCtx.rxidle ? "START":"STOP"));
    if(RdiagCtx.rxidle)
    {
        send_frame(tx_length);
    }
}



/**
 * @brief Callback function for frame reception.
 */
uint8_t * usr_radio_receive_frame(uint8_t len, uint8_t *frm, uint8_t lqi,
                                  uint8_t rssi, uint8_t crc)
{
    rxfrm.new = 1;
    rxfrm.len = len;
    rxfrm.crc = crc;
    rxfrm.lqi = lqi;
    rxfrm.rssi = rssi;
    if (rxfrm.len > 3)
    {
        rxfrm.seq = frm[2];
    }

    RdiagStat.crcerr += ( (crc != 0)||(len==0) ? 1 : 0);
    RdiagStat.rxcnt ++;
    RdiagStat.lqi += lqi;
    RdiagStat.rssi += rssi;
    return frm;
}

/**
 * @brief Callback function for TX_END IRQ.
 */
void usr_radio_tx_done(radio_tx_done_t status)
{
static uint8_t sn;
   RdiagStat.txcnt++;
   if(conttx)
   {
        /* force next frame sending */
        TRX_SLPTR_HIGH();
        TRX_SLPTR_LOW();
        trx_sram_write(3, 1, &sn);
        sn ++;
   }
}

/**
 * @brief Display RX/TX transceiver statistic and state.
 */
void show_statistic(bool reset)
{

time_t now;

    now = timer_systime();
    PRINTF(
        ">STAT"
        " duration: %ld ticks"NL,
            now - RdiagStat.start);

    PRINTF(
        " RX:"
        " frames: %ld"
        " crcerr: %ld"NL,
            RdiagStat.rxcnt, RdiagStat.crcerr);
    PRINTF(
        " RX: channel %d"
        " avg rssi: %ld"
        " avg lqi:  %ld"NL,
            RdiagCtx.channel,
            RdiagStat.rssi/RdiagStat.rxcnt, RdiagStat.lqi/RdiagStat.rxcnt);

    PRINTF(
        " TX:"
        " frames: %ld"NL,
            RdiagStat.txcnt);
    if (reset)
    {
        memset(&RdiagStat, 0, sizeof(RdiagStat));
        RdiagStat.start = timer_systime();
    }
}


/**
 * @brief Callback for errors in radio module functions.
 */
void usr_radio_error(radio_error_t err)
{
uint8_t regval, r2, r3;
uint16_t t;
    regval = trx_reg_read(RG_TRX_STATUS);
    r2 = trx_reg_read(0x30);
    r3 = trx_reg_read(RG_VREG_CTRL);
    while(1)
    {

        PRINTF("\n\rRADIO ERROR : %d radio status : 0x%02x:0x%02x:0x%02x\n\r",
                err, regval, r2, r3);
        t = 2000;
        while(t--)
        {
            DELAY_US(1000);
        }
    }
}



/**
 * @brief Print help for hotkeys.
 */
void help(void)
{
    PRINT("i/I : show / show and reset statistic"NL);
    PRINT("o   : set state OFF"NL);
    PRINT("r   : set state RX"NL);
    PRINT("t   : set state TX"NL);
    PRINT("+/- : incr./decr. channel"NL);
    PRINT("s   : send a test frame"NL);
    PRINT("S   : start/stop continous sending frames"NL);
    PRINT("R   : toggle RXON_IDLE parameter"NL);
    PRINT("P/p : incr./decr. power"NL);
    PRINT("c   : do CCA measurement"NL);
    PRINT("C   : change CCA mode"NL);
    PRINT("V/v : incr./decr. verbosity (0...2)"NL);
    PRINT("G/g : store/show seetings"NL);
}




/**
 *  @brief Life LED timer service routine.
 */
time_t blink(timer_arg_t t)
{
#if LED_NUMBER > 0
static volatile uint8_t btick;
#endif
    LED_SET_VALUE(btick++);
    /* restart the timer again */
    return  TBLINK_PERIOD;
}

