/* Copyright (c) 2011 - 2012 Daniel Thiele, 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$ */
/**
 * @file
 * @brief Application for antenna measurements
 * Sweep two remote nodes over a range of channels, send a frame each channel
 * and track ED level. The same hex-file applies to the remote nodes as well as
 * the Host node (connected via UART to PC)
 *
 * Terminal Keys:
 *  <N>:  ping node number <N>, where <N> is a key 0,...,9
 *  's':  do a sweep between nodes.
 *  'i':  enter initiator address for sweep
 *  'r':  enter responder address for sweep
 *
 * @ingroup
 */

/* === includes ============================================================ */

/* avr-libc inclusions */
#include <string.h>
#include <avr/io.h>
#include <util/delay.h>
#include <util/atomic.h>

/* uracoli inclusions */
#if defined(radiofaro)
# define DBG_PORT PORTE
# define DBG_DDR  DDRE
# define DBG_PIN (1<<PE0)
#endif

#include "board.h"
#include "ioutil.h"
#include "transceiver.h"
#include "radio.h"
#include "p2p.h"
#include "chsweep.h"

/* === macros ============================================================== */
#define APP_VERSION (0x02)
#define APP_NAME "chsweep"
#define TMO_VALUE MSEC(20)
/* === types =============================================================== */

/* === globals ============================================================= */

/* IEEE802.15.4 parameters for communication */
const node_config_t PROGMEM nc_flash = {
            .short_addr = 0xFECA,
            .pan_id = 0x3412,
            .channel = 17
        };

static node_config_t NodeConfig;

static uint8_t rxbuf[127];
static uint8_t txbuf[127];
static uint8_t rxstat[TRX_NB_CHANNELS];
static uint8_t txstat[TRX_NB_CHANNELS];

static uint16_t RxAddr; /* The device that sends sweep frames */
static uint16_t TxAddr; /* The device that sends sweep frames */
static uint16_t PingAddr; /* The device that is currently pinged */
static uint16_t CoordAddr;  /* The CoordAddr that initiates sweep and ping */
static uint8_t TxCnt;
static uint8_t CurrentChannel;
static uint8_t NextChannel;
static volatile uint8_t TmoCounter;
static volatile event_t TxDoneEvent;
static volatile event_queue_t EventQueue;
static volatile uint8_t RxEdValue;
static volatile uint8_t ResultMode;
static volatile uint8_t RxResult[TRX_NB_CHANNELS];
static uint8_t verbose = 0; /* verbose level, 0=Application output, 1=additional debug output */

static const p2p_ping_cnf_t PROGMEM PingReply = {
    .hdr.cmd= P2P_PING_CNF,
    .hdr.fcf = 0x8841, 				/* short addressing, frame type: data, no ACK requested */
    .version = APP_VERSION,
    .appname = APP_NAME,
    .boardname = BOARD_NAME,
};



/* === prototypes ========================================================== */
#if HIF_TYPE != HIF_NONE
static  uint16_t get_number(int8_t base);
#endif

/* === functions =========================================================== */

static inline void evq_put(event_t e)
{
    if (e == EVENT_NONE)
    {
        return;
    }
    ATOMIC_BLOCK(ATOMIC_RESTORESTATE)
    {
        if (EventQueue.queue[EventQueue.wr] == EVENT_NONE)
        {
            EventQueue.queue[EventQueue.wr] = e;
            EventQueue.wr ++;
            EventQueue.wr &= (EQ_SIZE-1);
        }
    }
}

static inline event_t evq_get(void)
{
event_t ret;

    ATOMIC_BLOCK(ATOMIC_RESTORESTATE)
    {
        ret = EventQueue.queue[EventQueue.rd];
        EventQueue.queue[EventQueue.rd] = EVENT_NONE;
        EventQueue.rd++;
        EventQueue.rd &= (EQ_SIZE-1);
    }
    return ret;
}

static void send_ping_reply(void)
{
    uint16_t tmp;
    memcpy_P(txbuf, &PingReply, sizeof(p2p_ping_cnf_t) + sizeof(BOARD_NAME));
    cli();
    tmp = PingAddr;
    PingAddr = 0xffff;
    sei();
    p2p_send(tmp, P2P_PING_CNF, P2P_ACK, txbuf,
             sizeof(p2p_ping_cnf_t) + sizeof(BOARD_NAME) + 2);
}

/**
 * Notify another node about its about its task.
 */
static void send_sweep_req(uint16_t txaddr, uint16_t rxaddr)
{
    p2p_chsweep_sweep_req_t *req = (p2p_chsweep_sweep_req_t*)txbuf;
    req->rx_addr = rxaddr;
    p2p_send(txaddr, P2P_CHSWEEP_SWEEP_REQ, P2P_ACK,
             txbuf, sizeof(p2p_chsweep_sweep_req_t));
}

static void send_exec_req(uint16_t rxaddr, uint8_t clearstat, uint8_t nextchannel)
{
    p2p_chsweep_exec_req_t *p;
    p = (p2p_chsweep_exec_req_t*)txbuf;
    p->clear_stat = clearstat;
    p->next_channel = nextchannel;
    p2p_send(rxaddr, P2P_CHSWEEP_SWEEP_REQ, P2P_ACK,
             txbuf, sizeof(p2p_chsweep_exec_req_t));
}

static void send_result_req(uint16_t dst_addr, uint8_t res_mode)
{
    p2p_chsweep_result_req_t *req = (p2p_chsweep_result_req_t*)txbuf;
    req->res_mode = res_mode;
    p2p_send(dst_addr, P2P_CHSWEEP_RESULT_REQ, P2P_ACK,
             txbuf, sizeof(p2p_chsweep_result_req_t));
}

static void send_result_cnf(uint16_t dst_addr, uint8_t res_mode)
{
    p2p_chsweep_result_cnf_t *frm = (p2p_chsweep_result_cnf_t*)txbuf;
    uint8_t size;
    frm->res_mode = res_mode;
    switch(res_mode)
    {
        case 'r':
            size = sizeof(rxstat);
            memcpy(frm->data, rxstat, size);
            break;
        case 't':
            size = sizeof(txstat);
            memcpy(frm->data, txstat, size);
            break;
        default:
            size = 8;
            memset(frm->data, 0x55, size);
            break;
    }
    p2p_send(dst_addr, P2P_CHSWEEP_RESULT_CNF, P2P_ACK,
             txbuf, sizeof(p2p_chsweep_result_cnf_t) + size);
}

/* radio receive call back function */
uint8_t * usr_radio_receive_frame(uint8_t len, uint8_t *frm, uint8_t lqi,
        int8_t ed, uint8_t crc_fail)
{
    static uint8_t lastseq = 255;

    p2p_hdr_t *hdr = (p2p_hdr_t*) frm;
    RxEdValue = ed;
    switch(hdr->cmd)
    {

        case P2P_PING_REQ:
            PingAddr = hdr->src;
            evq_put(EVENT_RX_PING);
            break;
        case P2P_PING_CNF:
            if (PingAddr == hdr->src)
            {
                evq_put(EVENT_RESULT_OK);
            }
            break;
        case P2P_CHSWEEP_SWEEP_REQ:
            TxAddr = NodeConfig.short_addr; /* this is myself */
            RxAddr = ((p2p_chsweep_sweep_req_t*)frm)->rx_addr; /* get from frame */
            evq_put(EVENT_SWEEP_TX_START);
            break;

        case P2P_CHSWEEP_EXEC_REQ:
            if (lastseq != hdr->seq)
            {
                NextChannel = ((p2p_chsweep_exec_req_t*)frm)->next_channel;
                if (((p2p_chsweep_exec_req_t*)frm)->clear_stat != 0)
                {
                    evq_put(EVENT_SWEEP_RX_START);
                }
                else
                {
                    evq_put(EVENT_SWEEP_RX);
                }
            }
            else
            {
                /* skip duplicate frames. */
            }
            break;

        case P2P_CHSWEEP_RESULT_REQ:
            ResultMode = ((p2p_chsweep_result_req_t*)frm)->res_mode;
            CoordAddr = hdr->src;
            evq_put(EVENT_RESULT_REQ);
            break;

        case P2P_CHSWEEP_RESULT_CNF:
            ResultMode = ((p2p_chsweep_result_cnf_t*)frm)->res_mode;
            memcpy((void *)RxResult,
                   (void *)((p2p_chsweep_result_cnf_t*)frm)->data,
                   sizeof(RxResult));
            evq_put(EVENT_RESULT_RX);
            break;

        case P2P_JUMP_BOOTL:
            jump_to_bootloader();
            break;

        default:
            break;
    }

    lastseq = hdr->seq;

    return frm;
}

void usr_radio_error(radio_error_t err)
{
    PRINTF("RADIO ERROR %d"EOL, err);
}

void usr_radio_tx_done(radio_tx_done_t status)
{
    /* do not use PRINTF in IRQ's */
    if (status != TX_OK)
    {
        evq_put(EVENT_TX_FAIL);
    }
    else if (TxDoneEvent != EVENT_NONE)
    {
        evq_put(TxDoneEvent);
        TxDoneEvent = EVENT_NONE;
    }

}

#if HIF_TYPE != HIF_NONE
static void do_dialog(void)
{
uint16_t c;

    if(EOF != (c = hif_getc()))
    {

        hif_putc(c);
        if( (c >= '1') && (c <= '9') )
        {
            PingAddr = c-'0';
            evq_put(EVENT_PING);
        }
        else
        {

            switch(c)
            {
                case 'i':
                    PRINT("[s] run sweep"EOL\
                          "[j] force rx nodes to jump to bootloader"EOL);
                    _delay_ms(10);
                    PRINTF("[t] tx_addr: 0x%04x"EOL\
                           "[r] rx_addr: 0x%04x"EOL,
                           TxAddr, RxAddr);
                    _delay_ms(10);
                    PRINTF("    my_addr: 0x%04x"EOL\
                           "    co_addr: 0x%04x"EOL,
                           NodeConfig.short_addr, CoordAddr);
                    _delay_ms(10);
                    PRINTF("[v] verbose level: %02X"EOL, verbose);
                    break;
                case 's':
                    evq_put(EVENT_SWEEP_TX_START);
                    break;

                case 'q':
                    PRINTF("EQ: r:%d w:%d e:%c %c %c %c"EOL,
                            EventQueue.rd, EventQueue.wr,
                            EventQueue.queue[0], EventQueue.queue[1],
                            EventQueue.queue[2], EventQueue.queue[3]);
                    break;
                case 'j':
                    p2p_send(0xFFFF, P2P_JUMP_BOOTL, P2P_ACK,
                             txbuf, sizeof(p2p_jump_bootl_t));
                    break;
                case 't':
                    PRINT("Enter address of TX device: 0x");
                    TxAddr = get_number(16);
                    break;
                case 'r':
                    PRINT("Enter address of RX device: 0x");
                    RxAddr = get_number(16);
                    break;
                case 'v':
                	PRINT("Verbose level: 0x");
                	verbose = get_number(16);
                	break;
                default:
                    break;
            }
        }
    }
}
#endif /* HIF_TYPE != HIF_NONE */

void chsweep_init(void)
{
    char cfg_location = '?';

    /* === read node configuration data ===================================== */
    /* 1st trial: read from EEPROM */
    if (get_node_config_eeprom(&NodeConfig, 0) == 0)
    {
        /* using EEPROM config */;
        cfg_location = 'E';
    }
    /* 2nd trial: read from FLASHEND */
    else if (get_node_config(&NodeConfig) == 0)
    {
        /* using FLASHEND config */;
        cfg_location = 'F';
    }
    /* 3rd trial: read default values compiled into the application */
    else
    {
        /* using application default config */;
        memcpy_P(&NodeConfig, (PGM_VOID_P)&nc_flash, sizeof(node_config_t) );
        cfg_location = 'D';
    }

    /* === setup global variables =========================================== */
    TxAddr = NodeConfig.short_addr;
    RxAddr = TxAddr ^ 1;
    TxCnt = 0;
    PingAddr = 0;
    memset((void *) &EventQueue, 0, sizeof(event_queue_t));


    /* === setup radio ====================================================== */
    radio_init(rxbuf, sizeof(rxbuf));
    radio_set_param(RP_CHANNEL(NodeConfig.channel));
    radio_set_param(RP_PANID(NodeConfig.pan_id));
    radio_set_param(RP_SHORTADDR(NodeConfig.short_addr));
    radio_set_param(RP_IDLESTATE(STATE_RXAUTO));

    p2p_init(NodeConfig.short_addr, NodeConfig.pan_id);

#if defined(SR_RND_VALUE)
    /* TODO: if the random value is not available in HW, e.g. AT86RF230A/B,
     * we need to find another method of seeding CSMA  */
    trx_reg_write(RG_CSMA_SEED_0, trx_bit_read(SR_RND_VALUE) | (trx_bit_read(SR_RND_VALUE) << 4));
    trx_reg_write(RG_CSMA_SEED_1, trx_bit_read(SR_RND_VALUE) | (trx_bit_read(SR_RND_VALUE) << 4));
#endif


    /* Welcome Blink */
    LED_SET(0);
    LED_SET(1);
    _delay_ms(20);
    LED_CLR(0);
    LED_CLR(1);

    PRINTF("chsweep v0.1 cfg %c"EOL
       "CHANNEL=0x%02X, PANID=0x%04X, SHORT_ADDR=0x%04X"EOL,
       cfg_location, NodeConfig.channel, NodeConfig.pan_id,
       NodeConfig.short_addr);
}

appstate_t process_idle(event_t ev)
{
appstate_t ret;

    ret = STATE_IDLE;

    if (CurrentChannel != NodeConfig.channel)
    {
        CurrentChannel = NodeConfig.channel;
        radio_set_state(STATE_TX);
        radio_set_param(RP_CHANNEL(CurrentChannel));
        radio_set_state(STATE_RXAUTO);
    }
    if (EVENT_PING == ev)
    {
        /* this is a shortcut - prepare next sweep */
        RxAddr = PingAddr;
        p2p_send(PingAddr, P2P_PING_REQ, P2P_ACK,
                 txbuf, sizeof(p2p_ping_req_t));
        TmoCounter = TMO_VALUE;
        ret = STATE_WAIT_PING;
    }
    else if (EVENT_SWEEP_TX_START == ev)
    {
        if (NodeConfig.short_addr == TxAddr)
        {
            DBG_SET();
            PRINT("local sweep"EOL);
            TxCnt = 17;
            CoordAddr = 0xffff;
            send_exec_req(RxAddr, 1, NodeConfig.channel);
            evq_put(EVENT_SWEEP_TX);
            ret = STATE_TX_SWEEP;
        }
        else
        {
            PRINT("remote sweep"EOL);
            send_sweep_req(TxAddr, RxAddr);
        }
    }
    else if (EVENT_SWEEP_RX_START == ev)
    {
        DBG_SET();
        PRINT("start rx sweep"EOL);
        memset(rxstat, 0xff, sizeof(rxstat));
        TmoCounter = TMO_VALUE;
        ret = STATE_RX_SWEEP;
    }

    return ret;
}

appstate_t process_ping(event_t ev)
{
appstate_t ret;
    ret = STATE_IDLE;
    if (EVENT_RESULT_OK == ev)
    {
        PRINTF("PING 0x%04x OK"EOL, PingAddr);
        TmoCounter = 0;
    }
    else if(EVENT_TX_FAIL == ev)
    {
        PRINTF("PING 0x%04x TX_FAIL"EOL, PingAddr);
    }
    else if(EVENT_TMO == ev)
    {
        PRINTF("PING 0x%04x TMO"EOL, PingAddr);
    }
    else
    {
        PRINTF("PING ignore event :%c:"EOL, ev);
    }
    return ret;
}

appstate_t process_txweep(event_t ev)
{
appstate_t ret;
    ret = STATE_TX_SWEEP;

    if (EVENT_SWEEP_TX == ev)
    {
        LED_TOGGLE(0);
        if(TxCnt > 0)
        {
            TxCnt --;
            /* in the last sweep, we switch back to default channel*/
            NextChannel = (TxCnt == 0) ?
                           NodeConfig.channel : TxCnt + 10;
            if (CurrentChannel != NextChannel)
            {
                TxDoneEvent = EVENT_SWEEP_TX_CHANNEL_SET;
            }
            else
            {
                TxDoneEvent = EVENT_SWEEP_TX;
            }
            TmoCounter = TMO_VALUE;
            send_exec_req(RxAddr, 0, NextChannel);
        }
        else
        {
            if (0xffff == CoordAddr) /* Broadcast address */
            {
                PRINT("wait rx_result"EOL);
                send_result_req(RxAddr, 'r');
                TmoCounter = TMO_VALUE;
                ret = STATE_IDLE;
            }
            else
            {
                PRINT("send tx sweep complete"EOL);
            }
        }
    }
    else if (EVENT_SWEEP_TX_CHANNEL_SET == ev)
    {
        cli();
        CurrentChannel = NextChannel;
        sei();
        radio_set_state(STATE_TX);
        radio_set_param(RP_CHANNEL(CurrentChannel));
        radio_set_state(STATE_RXAUTO);
        TmoCounter = TMO_VALUE;
        evq_put(EVENT_SWEEP_TX);
    }
    else if ( (EVENT_TX_FAIL == ev)  || (EVENT_TMO == ev))
    {
        PRINTF("tx sweep canceled:%s"EOL,
                ev == EVENT_TX_FAIL ? "txfail" : "tmo");
        radio_set_param(RP_CHANNEL(NodeConfig.channel));
        TxCnt = 0;
        TmoCounter = 0;
        ret = STATE_IDLE;
    }

    return ret;
}

appstate_t process_rxweep(event_t ev)
{
appstate_t ret;
    ret = STATE_RX_SWEEP;
    if (EVENT_SWEEP_RX == ev)
    {
        LED_TOGGLE(1);
        rxstat[CurrentChannel - 11] = RxEdValue;
        TmoCounter = TMO_VALUE;
        uint8_t nextch;

        cli();
        nextch = NextChannel;
        sei();
        if (nextch != CurrentChannel)
        {
            CurrentChannel = nextch;
            radio_set_state(STATE_TX);
            radio_set_param(RP_CHANNEL(CurrentChannel));
            radio_set_state(STATE_RXAUTO);
        }
    }
    else if (EVENT_TMO == ev)
    {
        PRINT("tx sweep canceled:tmo"EOL);
        radio_set_param(RP_CHANNEL(NodeConfig.channel));
        ret = STATE_IDLE;
    }
    return ret;
}

void process_async_events(event_t ev)
{
    /* handle in any state */
    if(EVENT_RESULT_REQ == ev)
    {
        send_result_cnf(CoordAddr, ResultMode);
    }
    else if (EVENT_RX_PING == ev)
    {
        send_ping_reply();
    }

    else if (EVENT_RESULT_RX == ev)
    {
#if HIF_TYPE != HIF_NONE
        char bar[22] = "=====================", *pbar;
        uint8_t baridx;
        PRINTF("RESULT: %c"EOL, ResultMode);
        for (uint8_t i = 0; i< sizeof(RxResult); i++)
        {
            baridx = 22 - (RxResult[i] / 4);
            pbar = baridx > 21 ? "?" : &bar[baridx];
            PRINTF("c=%d, ed=% 2d |%s"EOL, i+11, RxResult[i], pbar);
        }
#endif
    }
    else
    {
    	/* unhandled */
    }
}

int main()
{

event_t ev;
appstate_t appstate;

    LED_INIT();
    DBG_INIT();
    TIMER_INIT();
#if HIF_TYPE != HIF_NONE
    hif_init(38400);
#endif
    chsweep_init();
    appstate = STATE_IDLE;
    sei();

    do
    {
#if HIF_TYPE != HIF_NONE
        if (STATE_IDLE == appstate)
        {
            do_dialog();
        }
#endif /* #if HIF_TYPE != HIF_NONE && defined(SERIALDEBUG)*/
        ev = evq_get();
        if(verbose > 0){
        	PRINTF("* EV %c"EOL, ev);
        }
        if (EVENT_NONE == ev)
        {
            continue;
        }

        switch(appstate)
        {
            case STATE_IDLE:
                DBG_CLR();
                appstate = process_idle(ev);
                break;

            case STATE_WAIT_PING:
                appstate = process_ping(ev);
                break;

            case STATE_TX_SWEEP:
                appstate = process_txweep(ev);
                break;

            case STATE_RX_SWEEP:
                appstate = process_rxweep(ev);
                break;

            default:
                break;
        }
        process_async_events(ev);
    }
    while(1);
}

#if HIF_TYPE != HIF_NONE
/**
 * Enter a integer number.
 *
 * @param base
 *        base value for the number conversion,
 *        e.g. 10 for entering a decimal number.
 *
 * @return 16 bit unsigned integer value.
 */
static  uint16_t get_number(int8_t base)
{
char buf[8];
int idx = 0;
int inchar;

    buf[0] = 0;
    do
    {
        inchar = hif_getc();
        if (EOF != inchar)
        {
            buf[idx++] = (uint8_t) inchar;
            buf[idx] = 0;
            hif_putc(inchar);
        }
    }
    while( ('\n' != inchar) && ('\r' != inchar) && (idx < 7) );
    return (uint16_t)strtoul(buf, NULL, base);
}
#endif /* #if HIF_TYPE != HIF_NONE */

/**
 * Timer ISR
 */
ISR(TIMER_IRQ_vect)
{
    if (TmoCounter > 0)
    {
        TmoCounter--;
        if (TmoCounter == 0)
        {
            evq_put(EVENT_TMO);
        }
    }
}


/* EOF */
