/*
 * Copyright (c) 2005, Swedish Institute of Computer Science
 * All rights reserved.
 *
 * Redistribution and use in source and binary forms, with or without
 * modification, are permitted provided that the following conditions
 * are met:
 * 1. Redistributions of source code must retain the above copyright
 *    notice, this list of conditions and the following disclaimer.
 * 2. 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.
 * 3. Neither the name of the Institute 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 INSTITUTE 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 INSTITUTE 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.
 *
 * This file is part of the Contiki operating system.
 *
 * @(#)$Id: tr1001.c,v 1.6 2007/03/28 09:52:35 joxe Exp $
 */
/**
 * \addtogroup esb
 * @{
 */

/**
 * \defgroup tr1001 TR1001 radio tranciever device driver
 * @{
 */

/**
 * \file
 * Device driver and packet framing for the RFM-TR1001 radio module.
 * \author Adam Dunkels <adam@sics.se>
 *
 * This file implements a device driver for the RFM-TR1001 radio
 * tranciever.
 *
 */

#include "contiki-esb.h"

#include "lib/me.h"
#include "lib/crc16.h"
#include "net/tr1001-drv.h"

#include <io.h>
#include <signal.h>
#include <string.h>

#define RXSTATE_READY    0
#define RXSTATE_RECEVING 1
#define RXSTATE_FULL     2

#define SYNCH1 0x3c
#define SYNCH2 0x03

#if TR1001_STATISTICS
static unsigned short sstrength_dropped,
  sstrength_max, sstrength_min, tmp_sstrength_max, tmp_sstrength_min;
/* The number of dropped packets */
static unsigned short packets_err;
/* The number of accepted packets */
static unsigned short packets_ok;
#endif /* TR1001_STATISTICS */


/*
 * The buffer which holds incoming data.
 */
#define RXBUFSIZE UIP_BUFSIZE
unsigned char tr1001_rxbuf[RXBUFSIZE];

/*
 * The length of the packet that currently is being received.
 */
static unsigned short tr1001_rxlen = 0;

/*
 * The reception state.
 */
volatile unsigned char tr1001_rxstate = RXSTATE_READY;

static u8_t radio_active;

static u16_t rxcrc, rxcrctmp;

/*
 * The structure of the packet header.
 */
struct tr1001_hdr {
  u8_t len[2];    /**< The 16-bit length of the packet in network byte
		     order. */
};

/*
 * The length of the packet header.
 */
#define TR1001_HDRLEN sizeof(struct tr1001_hdr)

#define OFF 0
#define ON 1
static u8_t onoroff = ON;

#define NUM_SYNCHBYTES 4

void tr1001_default_rxhandler(unsigned char c);
PT_THREAD(tr1001_default_rxhandler_pt(unsigned char c));
static struct pt rxhandler_pt;

/*
 * This timer is used to keep track of when the last byte was received
 * over the radio. If the inter-byte time is too large, the packet
 * currently being received is discarded and a new packet reception is
 * initiated.
 */
static struct timer rxtimer;

static unsigned short tmp_sstrength, sstrength;

#define DEBUG 0
#if DEBUG
#include <stdio.h>
#define LOG(...) printf(__VA_ARGS__)
#else
#define LOG(...)
#endif

/*---------------------------------------------------------------------------*/
#if TR1001_STATISTICS
#define PACKET_DROPPED(bytes) do { \
  if(packets_err < ~0) {\
    packets_err++;\
  }\
  sstrength_dropped = ((bytes) == 0 ? 0 : ((tmp_sstrength / (bytes)) << 1));\
} while(0)
#define PACKET_ACCEPTED() do {\
  if(packets_ok < ~0) {\
    packets_ok++;\
  }\
} while(0);
#else
#define PACKET_DROPPED(bytes)
#define PACKET_ACCEPTED()
#endif /* TR1001_STATISTICS */
/*---------------------------------------------------------------------------*/
/*
 * Turn on data transmission in On-Off-Keyed mode.
 */
static void
txook(void)
{
  P3SEL = 0xf0;
  P5OUT |= 0x40;
  P5OUT &= 0x7f;
}
/*---------------------------------------------------------------------------*/
/*
 * Turn on data reception for the radio tranceiver.
 */
static void
rxon(void)
{
  P3SEL = 0xe0;
  P5OUT |= 0xc0;

  /* Enable the receiver. */
  ME1 |= URXE0;

  /* Turn on receive interrupt. */
  IE1 |= URXIE0;

}
/*---------------------------------------------------------------------------*/
/*
 * Turn off data reception for the radio tranceiver.
 */
static void
rxoff(void)
{
  P5OUT &= 0x3f;

  /* Disable the receiver. */
  ME1 &= ~URXE0;

  /* Turn off receive interrupt. */
  IE1 &= ~URXIE0;
}
/*---------------------------------------------------------------------------*/
/*
 * Clear the recevie buffer and reset the receiver state.
 */
static void
rxclear(void)
{
  tr1001_rxstate = RXSTATE_READY;
}
/*---------------------------------------------------------------------------*/
/*
 * Turn TR1001 radio transceiver off.
 */
/*---------------------------------------------------------------------------*/
void
radio_off(void)
{
  onoroff = OFF;
  rxoff();
  rxclear();
}
/*---------------------------------------------------------------------------*/
/*
 * Turn TR1001 radio transceiver on.
 */
/*---------------------------------------------------------------------------*/
void
radio_on(void)
{
  onoroff = ON;
  rxon();
  rxclear();
}
/*---------------------------------------------------------------------------*/
/*
 * Send a byte of data over the radio.
 *
 * \param b The byte to be sent.
 */
static void
send(unsigned char b)
{
  clock_time_t start;

  start = clock_time();

  /* Wait until the USART0 TX buffer is ready. */
  while((IFG1 & UTXIFG0) == 0) {
    /* Wait no more than one second. */
    if((clock_time_t)(clock_time() - start) > (clock_time_t)CLOCK_SECOND) {
      break;
    }
  }

  /* Send the byte. */
  TXBUF0 = b;
}
/*---------------------------------------------------------------------------*/
/*
 * Send a byte of data and its logical negation (all bits inverted)
 * over the radio.
 *
 * \param b The byte to be sent.
 */
static void
send2(unsigned char b)
{
  u16_t m;
  m = me_encode(b);
  send(m >> 8);
  send(m & 0xff);
}
static u16_t
send2_crc16(unsigned char b, u16_t crcacc)
{
  u16_t m;
  m = me_encode(b);
  send(m >> 8);
  send(m & 0xff);
  return crc16_add(b, crcacc);
}
/*---------------------------------------------------------------------------*/
void
tr1001_set_txpower(unsigned char p)
{
  int i;

  /* Clamp maximum power. */
  if(p > 100) {
    p = 100;
  }

  /* First, run the potentiometer down to zero so that we know the
     start value of the potentiometer. */
  P2OUT &= 0xDF;                                /* P25 = 0 (down selected) */
  P2OUT &= 0xBF;                                /* P26 = 0 (chipselect on) */
  for(i = 0; i < 102; ++i) {
    P2OUT &= 0xEF;                              /* P24 = 0 (inc) */
    P2OUT |= 0x10;
  }

  /* Now, start to increase the value of the potentiometer until it
     reaches the desired value.*/

  P2OUT |= 0x20;                                /* P25 = 1 (up selected) */
  for(i = 0; i < p; ++i) {
    P2OUT &= 0xEF;                              /* P24 = 0 (inc) */
    P2OUT |= 0x10;
  }
  P2OUT |= 0x40;                                /* P26 = 1 (chipselect off) */
}
/*---------------------------------------------------------------------------*/
void
tr1001_init(void)
{

  PT_INIT(&rxhandler_pt);

#if TR1001_STATISTICS
  packets_ok = packets_err = 0;
  sstrength_dropped = 0;
  sstrength_min = 0xFFFF;
  sstrength_max = 0;
#endif /* TR1001_STATISTICS */

  UCTL0 = CHAR;                         /* 8-bit character */
  UTCTL0 = SSEL1;                       /* UCLK = SMCLK */

  tr1001_set_speed(TR1001_19200);

  ME1 |= UTXE0 + URXE0;                 /* Enable USART0 TXD/RXD */

  /* Turn on receive interrupt. */
  IE1 |= URXIE0;

  timer_set(&rxtimer, CLOCK_SECOND / 4);


  radio_on();
  tr1001_set_txpower(100);

  /* Reset reception state. */
  rxclear();

}
/*---------------------------------------------------------------------------*/
interrupt (UART0RX_VECTOR)
     tr1001_rxhandler(void)
{
  tr1001_default_rxhandler_pt(RXBUF0);
  if(tr1001_rxstate == RXSTATE_FULL) {
    LPM_AWAKE();
  }
}
/*---------------------------------------------------------------------------*/
static void
dump_packet(int len)
{
  int i;
  for(i = 0; i < len; ++i) {
    LOG("%d: 0x%02x\n", i, tr1001_rxbuf[i]);
  }
}
/*---------------------------------------------------------------------------*/
PT_THREAD(tr1001_default_rxhandler_pt(unsigned char incoming_byte))
{
  static unsigned char rxtmp, tmppos;

  if(timer_expired(&rxtimer) && tr1001_rxstate != RXSTATE_FULL) {
    PT_INIT(&rxhandler_pt);
  }

  timer_restart(&rxtimer);

  if(tr1001_rxstate == RXSTATE_RECEVING) {
    unsigned short signal = radio_sensor_signal;
    tmp_sstrength += (signal >> 2);
    if(signal < tmp_sstrength_min) {
      tmp_sstrength_min = signal;
    }
    if(signal > tmp_sstrength_max) {
      tmp_sstrength_max = signal;
    }
  }

  PT_BEGIN(&rxhandler_pt);

  while(1) {

    /* Reset reception state. */
    rxclear();

    /* Wait until we receive the first syncronization byte. */
    PT_WAIT_UNTIL(&rxhandler_pt, incoming_byte == SYNCH1);

    tr1001_rxstate = RXSTATE_RECEVING;

    /* Read all incoming syncronization bytes. */
    PT_WAIT_WHILE(&rxhandler_pt, incoming_byte == SYNCH1);

    /* We should receive the second synch byte by now, otherwise we'll
       restart the protothread. */
    if(incoming_byte != SYNCH2) {
      PT_RESTART(&rxhandler_pt);
    }

    /* Start signal strength measurement */
    tmp_sstrength = 0;
    tmp_sstrength_max = 0;
    tmp_sstrength_min = 0xFFFF;

    /* Reset the CRC. */
    rxcrc = 0xffff;

    /* Read packet header. */
    for(tmppos = 0; tmppos < TR1001_HDRLEN; ++tmppos) {

      /* Wait for the first byte of the packet to arrive. */
      PT_YIELD(&rxhandler_pt);

      /* If the incoming byte isn't a valid Manchester encoded byte,
	 we start again from the beinning. */
      if(!me_valid(incoming_byte)) {
	beep_beep(1000);
	LOG("Incorrect manchester in header at byte %d/1\n", tmppos);
	PACKET_DROPPED(tmppos);
	PT_RESTART(&rxhandler_pt);
      }

      rxtmp = me_decode8(incoming_byte);

      /* Wait for the next byte to arrive. */
      PT_YIELD(&rxhandler_pt);

      if(!me_valid(incoming_byte)) {
	beep_beep(1000);
	LOG("Incorrect manchester in header at byte %d/2\n", tmppos);
	PACKET_DROPPED(tmppos + 1);
	PT_RESTART(&rxhandler_pt);
      }

      /* Put together the two bytes into a single Manchester decoded
	 byte. */

      tr1001_rxbuf[tmppos] = (rxtmp << 4) | me_decode8(incoming_byte);

      /* Calculate the CRC. */
      rxcrc = crc16_add(tr1001_rxbuf[tmppos], rxcrc);


    }

    /* Since we've got the header, we can grab the length from it. */
    tr1001_rxlen = ((((struct tr1001_hdr *)tr1001_rxbuf)->len[0] << 8) +
		    ((struct tr1001_hdr *)tr1001_rxbuf)->len[1]);

    /* If the length is longer than we can handle, we'll start from
       the beginning. */
    if(tmppos + tr1001_rxlen > sizeof(tr1001_rxbuf)) {
      PACKET_DROPPED(tmppos);
      PT_RESTART(&rxhandler_pt);
    }

    /* Read packet data. */
    for(; tmppos < tr1001_rxlen + TR1001_HDRLEN; ++tmppos) {
      PT_YIELD(&rxhandler_pt);

      if(!me_valid(incoming_byte)) {
	LOG("Incorrect manchester 0x%02x at byte %d/1\n", incoming_byte,
	    tmppos - TR1001_HDRLEN);
	beep_beep(1000);
	PACKET_DROPPED(tmppos);
	PT_RESTART(&rxhandler_pt);
      }

      rxtmp = me_decode8(incoming_byte);

      PT_YIELD(&rxhandler_pt);

      if(!me_valid(incoming_byte)) {
	LOG("Incorrect manchester at byte %d/2\n", tmppos - TR1001_HDRLEN);
	beep_beep(1000);
	PACKET_DROPPED(tmppos + 1);
	PT_RESTART(&rxhandler_pt);
      }

      tr1001_rxbuf[tmppos] = (rxtmp << 4) | me_decode8(incoming_byte);
      rxcrc = crc16_add(tr1001_rxbuf[tmppos], rxcrc);

    }

    /* Read the frame CRC. */
    for(tmppos = 0; tmppos < 4; ++tmppos) {

      PT_YIELD(&rxhandler_pt);

      if(!me_valid(incoming_byte)) {
	beep_beep(1000);
	PACKET_DROPPED(tr1001_rxlen + TR1001_HDRLEN);
	PT_RESTART(&rxhandler_pt);
      }

      rxcrctmp = (rxcrctmp << 4) | me_decode8(incoming_byte);
    }

    if(rxcrctmp == rxcrc) {
      /* A full packet has been received and the CRC checks out. We'll
	 request the driver to take care of the incoming data. */

      PACKET_ACCEPTED();
      tr1001_drv_request_poll();

      /* We'll set the receive state flag to signal that a full frame
	 is present in the buffer, and we'll wait until the buffer has
	 been taken care of. */
      tr1001_rxstate = RXSTATE_FULL;
      PT_WAIT_UNTIL(&rxhandler_pt, tr1001_rxstate != RXSTATE_FULL);

    } else {
      LOG("Incorrect CRC");
      beep_beep(1000);
      PACKET_DROPPED(tr1001_rxlen + TR1001_HDRLEN);
    }
  }
  PT_END(&rxhandler_pt);
}
/*---------------------------------------------------------------------------*/
/*
 * Prepare a transmission.
 *
 * This function does the necessary setup before a packet can be sent
 * out.
 */
static void
prepare_transmission(int synchbytes)
{
  int i;

  /* Delay the transmission for a short random duration. */
  clock_delay(random_rand() & 0x3ff);


  /* Check that we don't currently are receiveing a packet, and if so
     we wait until the reception has been completed. Reception is done
     with interrupts so it is OK for us to wait in a while() loop. */

  while(tr1001_rxstate == RXSTATE_RECEVING &&
	!timer_expired(&rxtimer)) {
    /* Delay the transmission for a short random duration. */
    clock_delay(random_rand() & 0x7ff);
  }


  /* Turn on OOK mode with transmission. */
  txook();

  /* According to the datasheet, the transmitter must wait for 12 us
     in order to settle. Empirical tests show that is it better to
     wait for something like 283 us... */
  clock_delay(200);


  /* Transmit preamble and synch bytes. */


  for(i = 0; i < 20; ++i) {
    send(0xaa);
  }
  /*  send(0xaa);
      send(0xaa);*/
  send(0xff);

  for(i = 0; i < synchbytes; ++i) {
    send(SYNCH1);
  }
  send(SYNCH2);

}
/*---------------------------------------------------------------------------*/
u8_t
tr1001_send(u8_t *packet, u16_t len)
{
  int i;
  u16_t crc16;

  LOG("tr1001_send: sending %d bytes\n", len);

  /* Prepare the transmission. */
  prepare_transmission(NUM_SYNCHBYTES);

  radio_active = 1;

  crc16 = 0xffff;

  /* Send packet header. */
  crc16 = send2_crc16(len >> 8, crc16);
  crc16 = send2_crc16(len & 0xff, crc16);

  /* Send packet data. */
  for(i = 0; i < len; ++i) {
    crc16 = send2_crc16(packet[i], crc16);
  }

  /* Send CRC */
  send2(crc16 >> 8);
  send2(crc16 & 0xff);

  /* Send trailing bytes. */
  send(0x33);
  send(0xcc);
  send(0x33);
  send(0xcc);

  /* Turn on (or off) reception again. */
  if(onoroff == ON) {
    rxon();
    rxclear();
  } else {
    rxoff();
    rxclear();
  }

  return UIP_FW_OK;
}
/*---------------------------------------------------------------------------*/
unsigned short
tr1001_poll(u8_t *buf, u16_t bufsize)
{
  unsigned short tmplen;

  if(tr1001_rxstate == RXSTATE_FULL) {

    dump_packet(tr1001_rxlen + 2);

    tmplen = tr1001_rxlen;

    /*    if(tmplen > UIP_BUFSIZE - (UIP_LLH_LEN - TR1001_HDRLEN)) {
      tmplen = UIP_BUFSIZE - (UIP_LLH_LEN - TR1001_HDRLEN);
      }*/
    if(tmplen > bufsize - TR1001_HDRLEN) {
      tmplen = bufsize - TR1001_HDRLEN;
    }

    memcpy(buf, &tr1001_rxbuf[TR1001_HDRLEN], tmplen);

    /* header + content + CRC */
    sstrength = (tmp_sstrength / (TR1001_HDRLEN + tr1001_rxlen + 2)) << 1;
    sstrength_max = tmp_sstrength_max;
    sstrength_min = tmp_sstrength_min;

    rxclear();

    LOG("tr1001_poll: got %d bytes\n", tmplen);

    return tmplen;
  }
  return 0;
}
/*---------------------------------------------------------------------------*/
void
tr1001_set_speed(unsigned char speed)
{

  if(speed == TR1001_19200) {
    /* Set TR1001 to 19200 */
    UBR00 = 0x80;                         /* 2,457MHz/19200 = 128 -> 0x80 */
    UBR10 = 0x00;                         /* */
    UMCTL0 = 0x00;                        /* no modulation  */
  } else if(speed == TR1001_38400) {
    /* Set TR1001 to 38400 */
    UBR00 = 0x40;                         /* 2,457MHz/38400 = 64 -> 0x40 */
    UBR10 = 0x00;                         /* */
    UMCTL0 = 0x00;                        /* no modulation  */
  } else if(speed == TR1001_57600) {
    UBR00 = 0x2a;                         /* 2,457MHz/57600 = 42.7 -> 0x2A */
    UBR10 = 0x00;                         /* */
    UMCTL0 = 0x5b;                        /* */
  } else if(speed == TR1001_115200) {
    UBR00 = 0x15;                         /* 2,457MHz/115200 = 21.4 -> 0x15 */
    UBR10 = 0x00;                         /* */
    UMCTL0 = 0x4a;                        /* */
  } else {
    tr1001_set_speed(TR1001_19200);
  }
}
/*---------------------------------------------------------------------------*/
unsigned short
tr1001_sstrength(void)
{
  return sstrength;
}
/*---------------------------------------------------------------------------*/
#if TR1001_STATISTICS
unsigned short
tr1001_packets_ok(void)
{
  return packets_ok;
}
#endif /* TR1001_STATISTICS */
/*---------------------------------------------------------------------------*/
#if TR1001_STATISTICS
unsigned short
tr1001_packets_dropped(void)
{
  return packets_err;
}
#endif /* TR1001_STATISTICS */
/*---------------------------------------------------------------------------*/
#if TR1001_STATISTICS
void
tr1001_clear_packets(void)
{
  packets_ok = packets_err = 0;
}
#endif /* TR1001_STATISTICS */
/*---------------------------------------------------------------------------*/
#if TR1001_STATISTICS
unsigned short
tr1001_sstrength_value(unsigned int type)
{
  switch(type) {
  case TR1001_SSTRENGTH_DROPPED:
    return sstrength_dropped;
  case TR1001_SSTRENGTH_MAX:
    return sstrength_max;
  case TR1001_SSTRENGTH_MIN:
    return sstrength_min < sstrength_max ? sstrength_min : 0;
  default:
    return 0;
  }
}
#endif /* TR1001_STATISTICS */
/*---------------------------------------------------------------------------*/
unsigned char
tr1001_active(void)
{
  return radio_active;
}
/*--------------------------------------------------------------------------*/
void
tr1001_clear_active(void)
{
  radio_active = 0;
}
/*---------------------------------------------------------------------------*/
/** @} */
/** @} */
