/*
 * Copyright (c) 2004  Brian S. Dean <bsd@bdmicro.com>
 * 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.
 *
 * THIS SOFTWARE IS PROVIDED BY BRIAN S. DEAN ``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 BRIAN S. DEAN 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: main.c,v 1.1 2010/10/19 05:18:47 bsd Exp $
 */

/*
 * This is a simple program for testing the ROBIN multidrop RS485
 * network protocol.  ROBIN is intended to provide a standard
 * protocol so that users and vendors can utilize RS485 communications
 * bus with a reasonable expectation that their devices can
 * interoperate with one another. 
 */

#include <avr/io.h>
#include <avr/pgmspace.h>
#include <avr/interrupt.h>

#include <inttypes.h>
#include <stdio.h>
#include <stdlib.h>
#include <string.h>
#include <ctype.h>

#include "hexdump.h"
#include "led.h"
#include "ringbuf.h"
#include "rs485.h"
#include "pkt.h"
#include "uart.h"

#define MCU_FREQ 14745600L

#if MCU_FREQ == 8000000L
#define OCR  62
#elif MCU_FREQ == 14745600L
#define OCR 115
#elif MCU_FREQ == 16000000L
#define OCR 125
#endif


#define BRR(f,b) ((f % (16L*b) >= (16L*b)/2) ? (f / (16L*b)) : (f / (16L*b)) - 1)




uint8_t verbose = 1;

volatile uint16_t ms_count;
volatile uint16_t ping_count;

uint32_t rbaud = 115200;

uint8_t rs485_monitor; /* 1 = we listen to our own transmissions */

FILE * def_uart;

uint8_t pbuf[PKT_MAX_PLEN];

uint8_t dbuf[PKT_MAX_DLEN];

RSTATE rmem; /* ROBIN state */
RSTATE * r = &rmem;

/*
 * constant strings stored in FLASH
 */
const char s_ok[]            PROGMEM = "OK\n";
const char s_error[]         PROGMEM = "ERROR\n";
const char s_invalid[]       PROGMEM = "INVALID\n";
const char s_bksp[]          PROGMEM = "\b \b";
const char s_prompt[]        PROGMEM = "ROBIN> ";
const char s_robinid[]       PROGMEM = "BDMICRO : MAVRIC-IIB : ROBIN Test Case Version 2";
const char s_annc[]          PROGMEM = "\n\nROBot Independent Network (ROBIN)\n\n";
const char s_help[]          PROGMEM =
"\n"
"valid commands:\n"
"\n"
"  pkt[<flags>] <id> [<data>]\n"
"\n"
"    Send a packet to node <id>, all data folling the is sent as the\n"
"    packet data.  Flags can be 'c' = collision detect, 'a' = request ack,\n"
"    or 'i' - request id.  Examples:\n"
"\n"
"      pkt B test packet - send normal packet to node B\n"
"\n"
"      pkti B - request an ID response from node B\n"
"\n"
"      pktci B - request an ID response from node B, use collision detection\n"
"\n"
"      pktc B test packet - send normal packet to node B, use collision detection\n"
"\n"
"      pkta B test packet - send normal packet to node B, request ACK response\n"
"\n"
"  id [<id>]\n"
"\n"
"    Set the current node's id to <id>.  If <id> is omitted, the nodes current\n"
"    id is displayed.  Example:\n"
"\n"
"      id A    - set the node id to 'A' (hex 0x41)\n"
"\n"
"  collide <node1> <node2> - attempt to cause a collision on the network\n"
"\n"
"    First send an ID request to <node1> to elicit a response and\n"
"    ensure that a packet will soon be present on the bus.  Then,\n"
"    immediately begin indiscriminately blasting packets to <node2> to\n"
"    try and generate a collision with the ID rewponse from <node1>.\n"
"\n"
"    The node <node1> needs to be on the bus and operational, but\n"
"    <node2> need not be present for this test.\n"
"\n"
"  scan - scan the bus looking for nodes by issuing an ID request for\n"
"         each address except our own and looking for responses.\n"
"\n"
"  ping <node> - send 100 packets to the specified node, eliciting a\n"
"         response back; calculate the total time taken and report any\n"
"         errors\n"
"\n"
"  v - toggle verbose mode; when enabled, incoming packets are echoed\n"
"      to the terminal\n"
"\n";


void packet_dump(uint8_t * pbuf);

void set_rbaud(uint32_t baud);


/*
 * wait for the requested number of ms
 */
void ms_sleep(uint16_t ms)
{
  uint16_t t;

  t = ms_count;
  while (ms_count != (t+ms+1))
    ;
}



/*
 * character output routine for default UART called by stdio routines
 */
int def_putc(char ch, FILE * f)
{
  uart_putc(DEF_UART, ch);
  return 0;
}


/*
 * timer0 compare match interrupt handler
 *
 * Runs ever 0.977 ms (close enough to 1 ms) using an RTC crystal
 * connected to timer0 and clocked asynchronously from the main crystal.
 *
 * This handler keeps track of a millisecond counter, and calls the
 * thread_tick() scheduling routine to see if another thread should be
 * woken up or scheduled to run.
 */
SIGNAL(SIG_OUTPUT_COMPARE0)
{
  ms_count++;
  ping_count++;
}



/*
 * Initialize timers - init timer 0 to use the real time clock crystal
 * connected to TOSC1 and TOSC2.  Use this timer as a 1 ms thread
 * scheduling timer.  
 */
void init_timers(void)
{
  /*
   * Initialize timer0 to use the 32.768 kHz real-time clock crystal
   * attached to TOSC1 & 2.  Enable output compare interrupt and set
   * the output compare register to 32 which will cause an interrupt
   * to be generated every 0.9765625 milliseconds - close enough to a
   * millisecond. 
   */
  TCNT0  = 0;
  TIFR  |= _BV(OCIE0)|_BV(TOIE0);
  TIMSK |= _BV(OCIE0);    /* enable output compare interrupt */
  TCCR0  = _BV(WGM01)|_BV(CS02)|_BV(CS00); /* CTC, prescale = 128 */
  OCR0   = OCR; /* match in aprox 1 ms assuming a 16 MHz clock */
  TCNT0  = 0;
}


/*
 * decode and execute simple commands
 */
static char do_cmdbuf[20];
static __inline__ void do_cmd(char * s)
{
  static char * cmd = do_cmdbuf;
  uint8_t       index;
  char        * args;
  uint8_t       plen, rlen, to, flags, collision_detect, n;
  int16_t       i;
  int8_t        rc;

  if (s[0] == 0)
    return;

  /* parse the command line, seperating the command from arguments */
  cmd[0] = 0;
  index = 0;
  while ((index < sizeof(do_cmdbuf)) && s[index] && (s[index] != ' ')) {
    cmd[index] = s[index];
    index++;
  }
  if (index < sizeof(do_cmdbuf)) {
    cmd[index] = 0;
    args = &s[index];
    while (*args && (*args == ' '))
      args++;
    if (*args == 0)
      args = NULL;
  }
  else {
    cmd[sizeof(do_cmdbuf)-1] = 0;
    args = NULL;
  }

  if (cmd[0] == 0) {
    return;
  }

  if (strcmp(cmd, "?")==0) {
    printf_P(s_help);
  }
  else if (strncmp(cmd, "pkt", 3)==0) { /* send a packet */
    if (!args) {
      return;
    }

    to = *args;

    flags = 0;
    collision_detect = 0;

    if (strlen(cmd) <= 6) {
      if (strchr(cmd, 'c') != NULL)
        collision_detect = 1;
      if (strchr(cmd, 'a') != NULL)
        flags |= PKTF_REQACK;
      if (strchr(cmd, 'i') != NULL)
        flags |= PKTF_REQID;
    }

    args++;
    while (*args == ' ')
      args++;
    plen = strlen(args);
    rc = pkt_send(r, collision_detect, to, r->myid, flags, (uint8_t *)args, plen);
    if (rc < 0)
      printf("pkt send error, rc=%d\n", rc);
    else 
      printf("pkt sent OK\n");
  }
  else if (strncmp(cmd, "ping", 4)==0) { /* test packet turn around time */
    uint16_t recv, errs, time, timeouts, n, timeout;
    uint8_t cksum;
    if (!args) {
      printf("Usage: ping[ci] <node> [<data>]\n");
      return;
    }

    to = *args;
    args++;
    while (*args == ' ')
      args++;
    plen = strlen(args);

    flags = PKTF_REQACK;
    collision_detect = 0;

    if (strlen(cmd) > 4) {
      if (strchr(cmd+4, 'c') != NULL)
        collision_detect = 1;
      if (strchr(cmd+4, 'i') != NULL)
        flags |= PKTF_REQID;
    }

    recv = 0;
    errs = 0;
    timeouts = 0;
    rlen = 0;

    /* wait timeout is dependent on the baud rate and the max packet
     * size of 64 bytes and is the time to transmit 9 bits per byte,
     * 64 bytes, + 10% for a little buffer then and return the result
     * in milliseconds */
    timeout = (double)((1.0 / (double)rbaud)*12.0*64.0*1000.0*1.15+1);

    if (rbaud >= 115200) {
      n = 1000;
    }
    else {
      n = 100;
    }

    ping_count = 0;
    for (i=0; i<n; i++) {
      /* send our packet */
      rc = pkt_send_wait_ack(r, 0, to, r->myid, flags, (uint8_t *)args, plen,
                             1, timeout, 1);
      if (rc >= 0) {
        recv++;
        cksum = pkt_compute_cksum(r->rbuf);
        if (PKT_CKSUM(r->rbuf) != cksum) {
          errs++;
        }
        if (rlen == 0)
          rlen = PKT_LEN(r->rbuf);
      }
      else {
        timeouts++;
      }
    }
    time = ping_count;
    printf("\n"
           "packet send size = %d bytes, packet recv size = %d bytes\n"
           "%d packets sent, %d recvd, %d errs, %d timeouts, total of %u ms\n"
           "%f transactions / second\n"
           "%f ms for round trip\n"
           "%f ms one way\n"
           "\n",
           plen+5, rlen, n, recv, errs, timeouts, time, ((double)n) / ((double)time) * 1000.0,
           ((double)time) / (double)n, (((double)time) / (double)n) / 2.0);
  }
  else if (strcmp(cmd, "collide")==0) { /* attempt to cause a collision on the network */
    if (!args) {
      printf("usage: collide <node1> <node2>\n");
      return;
    }
    to = *args;
    args++;
    while (isspace(*args))
      args++;
    if (*args == 0) {
      printf("usage: collide <node1> <node2>\n");
      return;
    }

    /*
     * transmit a packet to elicit an ID request from the first node
     * specified.  This will guarantee a packet will be on the bus
     * very shortly
     */
    rc = pkt_send(r, 0, to, r->myid, PKTF_REQID|PKTF_REQID, NULL, 0);

    /*
     * now indiscriminately blast packets to the second node
     * specified.  One of them is bound to result in a collision as
     * the first node responds with it's ID string; assuming the
     * resend is working we may never know that the collision occured,
     * so the 'pkt_send()' needs to be modified to notify us of when a
     * collision occurs.  In the test case used for this code, it
     * prints out a hexdump of whant it received as compared to what
     * it sent.
     */
    for (i=0; i<20; i++) {
      /* check to see if we've received a packet */
      if (r->rx) {
        printf("got packet:\n");
        packet_dump(r->rbuf);
        r->rx = 0;
      }

      /* send our packet */
      rc = pkt_send(r, 1, *args, r->myid, PKTF_REQACK,
                    (uint8_t *)"TEST PACKET COLLISION DATA", 26);
      printf("%2d: pkt_send() rc=%d", i, rc);
      if (rc > 0)
        printf(", %d collisions detected and recovered", rc);
      printf("\n");
    }
  }
  else if (strcmp(cmd, "scan")==0) { /* scan the network, seeing who is on */
    uint8_t cksum;
    uint16_t timeout;
    n = 0;
    /* wait timeout is dependent on the baud rate and the max packet
     * size of 64 bytes and is the time to transmit 9 bits per byte,
     * 64 bytes, + 10% for a little buffer then and return the result
     * in milliseconds */
    timeout = (double)((1.0 / (double)rbaud)*9.0*64.0*1000.0*1.10+1);
    printf("scanning nodes, timeout=%d\n", timeout);
    /* scan nodes A through Z */
    for (i=0; i<=254; i++) {
      if (i != r->myid) {
        printf("\r0x%02x ", i);
        /* request an ID string from each node */
        flags = PKTF_REQACK|PKTF_REQID;
        rc = pkt_send_wait_ack(r, 0, i, r->myid, flags, NULL, 0,
                               1, timeout, 1);
        if (rc >= 0) {
          cksum = pkt_compute_cksum(r->rbuf);
          if (PKT_CKSUM(r->rbuf) != cksum) {
            printf("\n!bad checksum from node %c, 0x%2x != 0x%2x\n",
                   PKT_SRC(r->rbuf), PKT_CKSUM(r->rbuf), cksum);
            hexdump_buf(0, r->rbuf, PKT_LEN(r->rbuf));
          }
          else {
            strncpy((char *)dbuf, (char *)PKT_DATA(r->rbuf), PKT_DLEN(r->rbuf));
            if (isprint(i))
              printf(" ('%c') ", i);
            else
              printf(" (...) ");
              
            printf("\"%s\"\n", dbuf);
            n++;
          }
        }
      }
    }
    printf("\r     \r");
    printf("%u nodes found\n", n);
  }
  else if (strcmp(cmd, "id")==0) { /* set network node id */
    if (!args) {
      printf("RS485 node address is 0x%02x\n", r->myid);
      return;
    }
    r->myid = *args;
    printf("RS485 node address set to 0x%02x\n", r->myid);      
  }
  else if (strcmp(cmd, "baud")==0) { /* set network baud rate */
    uint32_t tbaud;
    char * e;
    if (!args) {
      printf("current baud rate is: %ld\n", rbaud);
      return;
    }
    tbaud = strtol(args, &e, 0);
    if ((e == args)||(*e != 0)) {
      printf("can't read baud rate from '%s'\n", args);
      return;
    }
    rbaud = tbaud;
    set_rbaud(rbaud);
    printf("RS485 baud rate set to %ld\n", rbaud);
    printf("baud rate register set to %u\n", (uint16_t)BRR(MCU_FREQ, rbaud));
  }
  else if (strcmp(cmd, "v")==0) { /* toggle verbose */
    verbose = !verbose;
    if (verbose)
      printf("verbose mode enabled\n");
    else
      printf("verbose mode disabled\n");
  }
  else {
    printf_P(s_error);
  }
}


/*
 * accept characters and build a command line, when return is pressed,
 * pass the command to 'do_cmd()' 
 */
#define CMD_BUF_LEN 128
static char recv_input_cmdbuf[CMD_BUF_LEN];
void recv_input(uint8_t ch)
{
  static uint8_t idx=0;

  if ((ch == '\r')||(ch == '\n')) {
    def_putc('\n', stdout);
    recv_input_cmdbuf[idx] = 0;
    do_cmd(recv_input_cmdbuf);
    printf_P(s_prompt);
    idx = 0;
  }
  else if (ch == '\b') {
    if (idx) {
      printf_P(s_bksp);
      idx--;
      recv_input_cmdbuf[idx] = 0;
    }
  }
  else {
    def_putc(ch, stdout);
    recv_input_cmdbuf[idx++] = ch;
    if (idx == CMD_BUF_LEN) {
      idx = 0;
      recv_input_cmdbuf[idx] = 0;
      printf_P(s_invalid);
      printf_P(s_prompt);
    }
  }
}


uint32_t new_baud;
void pkt_cmd_decode(RSTATE * r)
{
  uint8_t * b;
  
  if (PKT_COMMAND(r->rbuf)) {
    /* packet is a protocol command packet */
    if (PKT_LEN(r->rbuf)) {
      b = PKT_DATA(r->rbuf);
      if (b[0] == 0x01) {
        /* set node id */
      }
      else if (b[0] == 0x02) {
        if (b[1] == 0x08) {
          /* 8-bit data mode */
        }
        else if (b[1] == 0x09) {
          /* 9-bit data mode */
        }
      }
      else if (b[0] == 0x03) {
        if (b[1] == 0x00) {
          /* baud rate is next 4 bytes */
        }
        else if (b[1] == 0x01) {
          new_baud = 2400L;
        }
        else if (b[1] == 0x02) {
          new_baud = 4800L;
        }
        else if (b[1] == 0x03) {
          new_baud = 9600L;
        }
        else if (b[1] == 0x04) {
          new_baud = 19200L;
        }
        else if (b[1] == 0x05) {
          new_baud = 38400L;
        }
        else if (b[1] == 0x06) {
          new_baud = 57600L;
        }
        else if (b[1] == 0x07) {
          new_baud = 115200L;
        }
        else if (b[1] == 0x08) {
          new_baud = 230400L;
        }
        else if (b[1] == 0x09) {
          new_baud = 460800L;
        }
        else if (b[1] == 0x0a) {
          new_baud = 921600L;
        }          
      }
      else if (b[0] == 0x04) {
        /* save changes */
      }
      else if (b[0] == 0x05) {
        /* apply changes */
      }
      else if (b[0] == 0x06) {
        /* sleep mode */
      }
      else if (b[0] == 0x07) {
        /* wake up */
      }
      else if (b[0] == 0x08) {
        /* halt */
      }
      else if (b[0] == 0x09) {
        /* reset */
      }
    }
  }
}


void packet_dump(uint8_t * pbuf)
{
  printf("\nINCOMING PKT: TO=0x%02x FRM=0x%02x FL=0x%02x DATA LEN=%d "
         "CKSUM: r=0x%02x l=0x%02x\n",
         PKT_DST(pbuf),
         PKT_SRC(pbuf),
         PKT_FLAGS(pbuf),
         PKT_DLEN(pbuf),
         PKT_CKSUM(pbuf), pkt_compute_cksum(pbuf));
  
  if (PKT_FLAGS(pbuf)) {
    uint8_t f;
    f = PKT_FLAGS(pbuf);
    printf("              FLAGS =");
    if (f & PKTF_ACK)
      printf(" ACK");
    if (f & PKTF_NACK)
      printf(" NACK");
    if (f & PKTF_REQACK)
      printf(" REQACK");
    if (f & PKTF_REQID)
      printf(" REQID");
    if (f & PKTF_COMMAND)
      printf(" COMMAND");
    if (f & PKTF_RESERVED)
      printf(" RESERVED");
    if (f & PKTF_USER1)
      printf(" USER1");
    if (f & PKTF_USER2)
      printf(" USER2");
    printf("\n");
  }
  
  hexdump_buf(0, pbuf, PKT_LEN(pbuf));
}


void set_rbaud(uint32_t baud)
{
  uint16_t brr;

  brr = BRR(MCU_FREQ, baud);
  UBRR1H  = (brr >> 8) & 0xff;
  UBRR1L  = brr & 0xff;
}

int main(void)
{
  uint8_t ch;
  uint16_t uart0_brr, uart1_brr;
  uint8_t * pdata, pdata_len;

  init_timers();

  led_init();
  led_on();

  /* initialize RS485 transceiver */
  rs485_init();
  rs485_tx_dis();
  rs485_rx_ena();
  rs485_monitor = 0;

  /* initialize first ROBIN protocol stack */
  i1r = r;
  pkt_init(r);
  r->tx_ena   = rs485_tx_ena;
  r->tx_dis   = rs485_tx_dis;
  r->rx_ena   = rs485_rx_ena;
  r->rx_dis   = rs485_rx_dis;
  r->tx_start = rs485_putc;

  /* initialize UARTs */
  uart0_brr = BRR(MCU_FREQ, DEF_UART_BAUD);
  uart1_brr = BRR(MCU_FREQ, rbaud);
  uarts_init(uart0_brr, uart1_brr);

  /* interrupts are ok now */
  sei();

  /*
   * initialize stdin, stdout, stderr; be sure not to use printf() or
   * any other output routines that use these until the UARTs are
   * initialized.
   */
  fdevopen(def_putc, NULL);

  led_off();

  /*
   * it is now save to call printf()
   */

  /* output startup announcement message and prompt */ 
  printf_P(s_annc);
  printf_P(s_prompt);

  while (1) {
    
    while (r->rx || uart0_rx
#if PKT_TRACE
           || uart1_rx
#endif
      ) {



#if PKT_TRACE
      if (uart1_rx) {
        uint8_t b9;
        /*
         * received a character from the RS485 bus
         */
        cli();
        uart1_rx = 0;
        sei();
        while (uart1buf.bufcnt) {
          cli();
          ch = ringbuf8_get(&uart1buf);
          sei();
          printf("%02x ", ch);
        }
      }
#else
      if (r->rx) {
        uint8_t lck;   /* locally computed checksum */
        uint8_t rck;   /* remotely computed checksum */
        uint8_t flags; /* packet flags */
        int8_t rc, err, send_response = 0;
        
        /*
         * received an RS485 packet, copy it to our local buffer and
         * reset the rx flag so that new packets can be received.
         */
        cli();
        memcpy(pbuf, r->rbuf, PKT_LEN(r->rbuf));
        err = r->err;
        r->rx = 0;
        sei();

        lck = pkt_compute_cksum(pbuf);
        rck = PKT_CKSUM(pbuf);

        flags = 0;
        pdata = NULL;
        pdata_len = 0;
        
        if (PKT_FLAGS(pbuf) & PKTF_REQACK) {
          if (lck == rck)
            flags = PKTF_ACK;
          else
            flags = PKTF_NACK;

          /* by default, return the data we received in the ACK or
           * NACK response */
          pdata = dbuf;
          pdata_len = PKT_DLEN(pbuf);
          memcpy(dbuf, PKT_DATA(pbuf), pdata_len);
          send_response = 1;
        }

        if (PKT_FLAGS(pbuf) & PKTF_REQID) {
          /* return our id string and perhaps ACK if it was requested */
          //printf("preparing to return our id string\n");
          pdata = dbuf;
          strcpy_P((char *)dbuf, s_robinid);
          pdata_len = strlen((char *)pdata);
          //printf("id string = \"%s\"\n", dbuf);
          send_response = 1;
        }

        if (send_response) {
          /* provide a little time to turn-around the half-duplex line */
          __asm__ volatile ("nop\n");
          rc = pkt_send(r, 0, PKT_SRC(pbuf), r->myid, flags, pdata,
                        pdata_len);
          if (rc < 0) {
            printf("response to node %c failed, rc=%d\n", PKT_SRC(pbuf), rc);
          }
        }

        if (verbose)
          packet_dump(pbuf);
      }
#endif

      if (uart0_rx) {
        /*
         * received a character from the UART
         */
        cli();
        uart0_rx = 0;
        sei();
        led_off();
        while ((ch = ringbuf8_get(&uartbuf)) != 0) {
          recv_input(ch);
        }
      }
    }
    
  }

}


