//============================================================================================
//      tc_cmds.c             ver 2.2   (x/x/08)
//
//      copyright (c) 2001-2008 Vaux Electronics, Inc.
//============================================================================================


//============================================================================================
//
//  Public routine process_TW_cmd() processes TTY-Connect-Write (/.TW) commands
//  defined by the command-id byte in tc_buf[0].
//
//  Public routine process_TR_cmd() processes TTY-Connect-Read (/.TR) commands
//  defined by the command-id byte in tc_buf[0].
//
//  Private routine tc_buf_param_test(int8 num_params) is called to verify number of
//  parameters in tc command buffer -- expected number of params is passed.
//  If command has one extra parameter, it is presumed to be a checksum.
//  FALSE is returned if number of params is wrong, and/or the checksum test fails,
//  else TRUE is returned.
//
//  Public routine c=get_sci_rx_byte_and_process_tc_cmd() allows main loop to get next
//  char (if avail) from serial port buffer, and parse char.  When complete TTY-Connect
//  command has been parsed, the command is executed.  Also returns char, or 0 if no char available.
//
//  In main prog:
//
//          #define   DEBUG_TC_PARSE    OFF   // set ON to turn on TC parsing debugging
//          #define   DEBUG_TC_EXE      OFF   // set ON to turn on TC execution debugging
//          ...
//          #include  "vaux_std.h"            // TRUE, FALSE, LF, XON, isdigit()...
//          #include  "tc_sci_isr.c"          // interrupt routines for sci (usart) i/o
//                                            //  (8-bit isr cannot have #device *=16 above)
//                                            //  (see tc_sci_16bit_isr.c for additional requirements)
//          #include  "tc_cmds.c"             // routines for TTY-Connect commands
//          ...
//          void main()
//          {
//            ...
//            echo_chars_flg = OFF;           // option: ON to echo chars for testing
//            ...
//            while (1)                       // main loop
//            {
//              ...
//              c = get_sci_rx_byte_and_process_tc_cmd();  // get next serial rx char (if avail), and
//                                                         //    process TTY-Connect command, if complete
//              ...
//            }
//          }
//
//============================================================================================
//
//      to do:
//          -
//
//      tc_cmds.c             ver 2.2   (x/x/08)
//          - added clock commands
//
//      tc_cmds.c             ver 2.0   (6/20/06)
//          - replaced short (was typedef) with int1
//          - replaced byte (was typedef) with int8
//
//      tc_cmds.c             ver 1.0   (9/8/04)
//          - created from lattis mx_tc_cmds.c and vp_parse.c
//
//============================================================================================

//  ----- prototypes:
#separate
void process_TW_cmd(void);                                                      // process TTY-Connect-Write (/.TW) commands

#separate
void process_TR_cmd(void);                                                      // process TTY-Connect-Read (/.TR) commands

int8 tc_buf_param_test(int8 num_params);                                        // check number of params (and opt checksum)

#separate
int8 get_sci_rx_byte_and_process_tc_cmd(void);                                  // get next sci rx char & execute TC command if done


// ----- defines:
#define       TC_BUF_SIZE                 16                                    // max number of data bytes after header
                                                                                //  (from cmd-id to optional checksum)
#ifndef       DEBUG_TC_PARSE
  #define     DEBUG_TC_PARSE              OFF                                   // main sets ON to turn on TC parsing debug
#endif

#ifndef       DEBUG_TC_EXE
  #define     DEBUG_TC_EXE                OFF                                   // main sets ON to turn on TC execution debug
#endif


// ----- unique states for tc_buf_state:
#define       TC_STBY                     0
#define       TC_GET_SLASH_HDR            1
#define       TC_GET_SLASH_DAT            2
#define       TC_EXECUTE                  9


// ----- unique XOR results for tc_buf_type (define explicitly to ensure uniqueness):
#define       TC_TW                       0x02                                  //   /^.^T^W  =  /^.^t^w
#define       TC_TR                       0x07                                  //   /^.^T^R  =  /^.^t^r


// ----- globals:
int8   tc_buf[TC_BUF_SIZE];                                                     // data byte buffer for current tc command
int8   tc_buf_index = 0;                                                        // index into tc_buf array

int8   tc_cmd_state = TC_STBY;                                                  // parsing state
int8   tc_cmd_type;                                                             // type is xor of header chars (/.TW, /.TR...)

int1   echo_chars_flg=OFF;                                                      // option: ON to echo chars for testing



//--------------------------------------------------------------------------------------------
//  void process_TW_cmd(void)                       process TTY-Connect-Write (/.TW) commands
//
//  Public routine process_TW_cmd() processes TTY-Connect-Write (/.TW) commands
//  defined by the command-id byte in tc_buf[0].
//
//  The TTY-Connect command data params are in the tc_buf[] array, and tc_buf_index
//  will contain number of data bytes in the array.  The tc_buf[] array can hold a max
//  of TC_BUF_SIZE bytes, and is indexed from 0 to tc_buf_index-1.
//  The first byte is a "command-id" number, defining the specific action for this type
//  of command, and which parameters follow to complete the command.  The second byte is
//  the "number" of params to follow (up until cr), and the last byte is an optional
//  checksum.  The checksum can be truncated to 1-byte (overflow discarded) before being
//  sent, or can simply be the sum (allowing the parser to truncate it to 1-byte).

#separate
void process_TW_cmd(void)
{
  int1 enb;
  int8 i, j;

  // ----- Check number of params (and opt checksum), adding 2 for cmd-id and num params:
  if (tc_buf_param_test(tc_buf[1] + 2))                                         // tc_buf[1] is num
  {

    // ----- which command-id number?  (tc_buf[0] = cmd_id):
    switch (tc_buf[0])                                                          // tc_buf[0] is command-id
    {


    // ----- connect mode-1:  Ascii PC to Baudot TTY/TU -----------------  /.TW,1,2,ptu,spd
    case 1:
      if(tc_buf[1] == 2)                                                        // check num params for this cmd
      {
        port_A_bits = 0;                                                        // (not used)
        port_A_port = 0;                                                        // (not used)
        port_A_rate = 0;                                                        // (not used)
        port_B_bits = 5;                                                        // (always baudot)
        port_B_port = limit(tc_buf[2], 1, 4);                                   // ptu
        port_B_rate = wpm_limit(tc_buf[3]);                                     // spd
        connect(1);
        send_tc_connect_mode_mssg();
        return;
      }
      break;


    // ----- connect mode-2:  Ascii PC to 110-Ascii TTY/TU --------------  /.TW,2,1,ptu
    case 2:
      if(tc_buf[1] == 1)                                                        // check num params for this cmd
      {
        port_A_bits = 0;                                                        // (not used)
        port_A_port = 0;                                                        // (not used)
        port_A_rate = 0;                                                        // (not used)
        port_B_bits = 8;                                                        // (always ascii)
        port_B_port = limit(tc_buf[2], 1, 4);                                   // ptu
        port_B_rate = 110;                                                      // (always 110-baud)
        connect(2);
        send_tc_connect_mode_mssg();
        return;
      }
      break;


    // ----- connect mode-3:  Raw PC to Raw TTY/TU ----------------------  /.TW,3,3,ptu,spx,bit
    case 3:
      if(tc_buf[1] == 3)                                                        // check num params for this cmd
      {
        port_A_bits = 0;                                                        // (not used)
        port_A_port = 0;                                                        // (not used)
        port_A_rate = 0;                                                        // (not used)
        port_B_bits = limit(tc_buf[4], 5, 8);                                   // bit
        port_B_port = limit(tc_buf[2], 1, 4);                                   // ptu
        port_B_rate = limit(tc_buf[3], 4, 255);                                 // spx
        connect(3);
        send_tc_connect_mode_mssg();
        return;
      }
      break;


    // ----- connect mode-4:  Baudot TU to Baudot TTY -------------------  /.TW,4,2,pty,spd
    case 4:
      if(tc_buf[1] == 2)                                                        // check num params for this cmd
      {
        port_A_bits = 5;                                                        // (always baudot)
        port_A_port = 4;                                                        // (always TU)
        port_A_rate = wpm_limit(tc_buf[3]);                                     // spd
        port_B_bits = 5;                                                        // (always baudot)
        port_B_port = limit(tc_buf[2], 1, 3);                                   // pty
        port_B_rate = port_A_rate;                                              // spd
        connect(4);
        send_tc_connect_mode_mssg();
        return;
      }
      break;


    // ----- connect mode-5:  Baudot TU to 110-Ascii TTY ----------------  /.TW,5,2,spd,pty
    case 5:
      if(tc_buf[1] == 2)                                                        // check num params for this cmd
      {
        port_A_bits = 5;                                                        // (always baudot)
        port_A_port = 4;                                                        // (always TU)
        port_A_rate = wpm_limit(tc_buf[2]);                                     // spd (baudot port)
        port_B_bits = 8;                                                        // (always ascii)
        port_B_port = limit(tc_buf[3], 1, 3);                                   // pty (ascii port)
        port_B_rate = 110;                                                      // (always 110-baud)
        connect(5);
        send_tc_connect_mode_mssg();
        return;
      }
      break;


    // ----- connect mode-6:  Baudot ports w/ speed change --------------  /.TW,6,4,pta,spa,ptb,spb
    case 6:
      if(tc_buf[1] == 4)                                                        // check num params for this cmd
      {
        port_A_bits = 5;                                                        // (always baudot)
        port_A_port = limit(tc_buf[2], 1, 4);                                   // pta
        port_A_rate = wpm_limit(tc_buf[3]);                                     // spa
        port_B_bits = 5;                                                        // (always baudot)
        port_B_port = limit(tc_buf[4], 1, 4);                                   // ptb
        port_B_rate = wpm_limit(tc_buf[5]);                                     // spb

        // --- not valid unless ports and speeds different:
        if ((port_A_port != port_B_port) && (port_A_rate != port_B_rate))
        {
          connect(6);
          send_tc_connect_mode_mssg();
          return;
        }
        else
        {
          // ----- ports or speeds cannot have same value:
          send_tc_error_mssg(ERR_CMD_PORTSPEED_PARAM);
          return;
        }
      }
      break;


    // ----- connect mode-10: No-connection; tty loops to space ---------  /.TW,10,0
    // ----- connect mode-11: No-connection; tty loops to mark ----------  /.TW,11,0
    case 10:
    case 11:
      if(tc_buf[1] == 0)                                                        // check num params for this cmd
      {
        port_A_bits = 0;                                                        // (not used)
        port_A_port = 0;                                                        // (not used)
        port_A_rate = 0;                                                        // (not used)
        port_B_bits = 0;                                                        // (not used)
        port_B_port = 0;                                                        // (not used)
        port_B_rate = 0;                                                        // (not used)
        connect(tc_buf[0]);                                                     // mode 10 or 11
        send_tc_connect_mode_mssg();
        return;
      }
      break;


    // ----- change state of output signal ---  /.TW,20,2,out,sta
    case 20:
      if(tc_buf[1] == 2)                                                        // check num params for this cmd
      {
        change_output_signal(tc_buf[2], tc_buf[3]);
        return;
      }
      break;


    // ----- programmable flags:
    case 40:   // ----- unconn_loops_mark -----------------  /.TW,40,1,enb
    case 41:   // ----- tu_data_inversion -----------------  /.TW,41,1,enb
    case 49:   // ----- rx_auto_crlf_insert ---------------  /.TW,49,1,enb

    case 70:   // ----- rx_unshift_on_space ---------------  /.TW,70,1,enb
    case 71:   // ----- rx_unshift_on_cr ------------------  /.TW,71,1,enb
    case 72:   // ----- rx_diddle_filter ------------------  /.TW,72,1,enb
    case 73:   // ----- tx_diddle_insertion ---------------  /.TW,73,1,enb
    case 74:   // ----- si_so_for_ltrs_figs ---------------  /.TW,74,1,enb
    case 75:   // ----- wru_reply -------------------------  /.TW,75,1,enb
      if(tc_buf[1] == 1)                                                        // check num params for this cmd
      {
        if (tc_buf[2])
           enb = 1;
        else
           enb = 0;

        switch(tc_buf[0])
        {
        case 40:
          unconn_loops_mark = enb;
          break;
        case 41:
          tu_data_inversion = enb;
          break;
        case 49:
          rx_auto_crlf_insert = enb;
          break;

        case 70:
          rx_unshift_on_space = enb;
          break;
        case 71:
          rx_unshift_on_cr = enb;
          break;
        case 72:
          rx_diddle_filter = enb;
          break;
        case 73:
          tx_diddle_insertion = enb;
          break;
        case 74:
          si_so_for_ltrs_figs = enb;
          break;
        case 75:
          wru_reply = enb;
          break;
        }
        write_prog_params_to_eeprom();
        // ----- send mssg:   -.TC,10x,1,enb <cr>
        vp_msg[0] = tc_buf[0];                                                  // mssg-id
        vp_msg[1] = 1;                                                          // num params to follow
        vp_msg[2] = (int8)(enb);                                                // enb
        send_tc_mssg_params(3);                                                 // send mssg and flash status led
        return;
      }
      break;


    // ----- max_chars_per_line --------------  /.TW,50,1,val
    // ----- motor_power_mode ----------------  /.TW,53,1,val
    case 50:
    case 53:
      if(tc_buf[1] == 1)                                                        // check num params for this cmd
      {
        switch(tc_buf[0])
        {
        case 50:
          max_chars_per_line = limit(tc_buf[2], 10, 80);
          vp_msg[2] = max_chars_per_line;                                       // val
          break;
        case 53:
          motor_power_mode = limit(tc_buf[2], 0, 6);
          vp_msg[2] = motor_power_mode;                                         // val
          break;
        }
        write_prog_params_to_eeprom();
        // ----- send mssg:   -.TC,13x,1,val <cr>
        vp_msg[0] = tc_buf[0];                                                  // mssg-id
        vp_msg[1] = 1;                                                          // num params to follow
        send_tc_mssg_params(3);                                                 // send mssg and flash status led
        return;
      }
      break;


    // ----- load 8-char auto_crlf_str string -------  /.TW,90,8,p1,p2,p3,p4,p5,p6,p7,p8
    // ----- load 8-char start_of_mssg_str string ---  /.TW,91,8,p1,p2,p3,p4,p5,p6,p7,p8
    // ----- load 8-char end_of_mssg_str string -----  /.TW,92,8,p1,p2,p3,p4,p5,p6,p7,p8
    // ----- load 8-char cond_code_str string -------  /.TW,93,8,p1,p2,p3,p4,p5,p6,p7,p8
    // ----- load 8-char selcal_station_str string --  /.TW,94,8,p1,p2,p3,p4,p5,p6,p7,p8
    // ----- load 8-char selcal_all_str string ------  /.TW,95,8,p1,p2,p3,p4,p5,p6,p7,p8
    case 90:
#if (STREAM_OPTIONS)
    case 91:
    case 92:
    case 93:
    case 94:
    case 95:
#endif
      if(tc_buf[1] == 8)                                                        // check num params for this cmd
      {
        for (i=0; i<=7; i++)
        {
          j = i + 2;
          switch(tc_buf[0])
          {
          case 90:
            auto_crlf_str[i] = tc_buf[j];
            vp_msg[j] = auto_crlf_str[i];
            break;
#if (STREAM_OPTIONS)
          case 91:
            start_of_mssg_str[i] = tc_buf[j];
            vp_msg[j] = start_of_mssg_str[i];
            break;
          case 92:
            end_of_mssg_str[i] = tc_buf[j];
            vp_msg[j] = end_of_mssg_str[i];
            break;
          case 93:
            cond_code_str[i] = tc_buf[j];
            vp_msg[j] = cond_code_str[i];
            break;
          case 94:
            selcal_station_str[i] = tc_buf[j];
            vp_msg[j] = selcal_station_str[i];
            break;
          case 95:
            selcal_all_str[i] = tc_buf[j];
            vp_msg[j] = selcal_all_str[i];
            break;
#endif
          }
        }
        write_prog_params_to_eeprom();
        // ----- send mssg:   -.TC,9x,8,p1,p2,p3,p4,p5,p6,p7,p8 <cr>
        vp_msg[0] = tc_buf[0];                                                  // mssg-id
        vp_msg[1] = 8;                                                          // num params to follow
        send_tc_mssg_params(10);                                                // send mssg and flash status led
        return;
      }
      break;


    // ----- set time ------------------------  /.TW,100,3,tmh,tmm,tms
    case 100:
      if(tc_buf[1] == 3)                                                        // check num params for this cmd
      {
        hour = limit(tc_buf[2], 0, 23);                                         // hours
        minute = limit(tc_buf[3], 0, 59);                                       // minutes
        second = limit(tc_buf[4], 0, 59);                                       // seconds
        return;
      }
      break;


    // ----- set date ------------------------  /.TW,101,4,dtm,dtd,dth,dty
    case 101:
      if(tc_buf[1] == 4)                                                        // check num params for this cmd
      {
        month = limit(tc_buf[2], 1, 12);                                        // month
        day = limit(tc_buf[3], 1, 31);                                          // day
        year_h = limit(tc_buf[4], 19, 20);                                      // year-high
        year_l = limit(tc_buf[5], 0, 99);                                       // year-low
        return;
      }
      break;


    // ----- set clock options ---------------  /.TW,102,4,ckm,ckl,tmf,dtf
    case 102:
      if(tc_buf[1] == 4)                                                        // check num params for this cmd
      {
        clock_mode = limit(tc_buf[2], 0, 1);                                    // clock_mode 0: clock off
                                                                                // clock_mode 1: print clock every-hour
                                                                                // clock_mode 2: print clock every-15-min
                                                                                // clock_mode 3: print clock every-1-min

        clock_loop = limit(tc_buf[3], 0, 4);                                    // clock_loop 0: test (FP-display only)
                                                                                // clock_loop x: print clock on loop-x
                                                                                //  (1=HV1, 2=HV2, 3=LV, 4=TU)
        if(tc_buf[4])
          ck_12_hour_time_format = 1;                                           // using 12-hr format
        else
          ck_12_hour_time_format = 0;                                           // using 24-hr format

        if(tc_buf[5])
          ck_standard_date_format = 1;                                          // using month/day/year standard date format
        else
          ck_standard_date_format = 0;                                          // using day/month/year european date format

        return;
      }
      break;


    // ----- set Demo-Message ----------------  /.TW,120,1,dem
    //                                                (followed by demo text;  4095-bytes max)
    //                                          `               (grave accent 0x60 terminates text)
    //
    //         We are using a 24LC512 eeprom, which stores 64K bytes (0x0000 to 0xFFFF)
    //         and using it to store sixteen 4K-byte demo messages:
    //              mssg-1:  0x0000 to 0FFF
    //              mssg-2:  0x1000 to 1FFF
    //              ...
    //              mssg-16: 0xF000 to FFFF
    case 120:
      if(tc_buf[1] == 1)                                                        // check num params for this cmd
      {
        demo_mssg_num = limit(tc_buf[2], 1, 16);                                // demo-mssg number 1-16 (points to ext eeprom mssg)
        demo_mssg_char_addr = ((int16)(demo_mssg_num) - 1) << 12;               // init to base address of mssg in ext eeprom
        demo_mssg_chars_remaining = 4096;                                       // init to max demo-mssg size
        demo_mssg_loading = 1;                                                  // flag for serial input routine
        return;
      }
      break;


    // ----- restore factory settings --------  /.TW,250,0
    case 250:
      if(tc_buf[1] == 0)                                                        // check num params for this cmd
      {
        restore_factory_settings();
        return;
      }
      break;


    // ----- invalid cmd-id:
    default:
      send_tc_error_mssg(ERR_CMD_ID);
      return;

    }
    // ----- invalid number of params for specific command:
    send_tc_error_mssg(ERR_CMD_NUMPARAM_CS);

  }
  else
  {
    // ----- did not get num params, or checksum was wrong:
    send_tc_error_mssg(ERR_CMD_NUMPARAM_CS);
  }
}


//--------------------------------------------------------------------------------------------
//  void process_TR_cmd(void)                       process TTY-Connect-Read (/.TR) commands
//
//  Public routine process_TR_cmd() processes TTY-Connect-Read (/.TR) commands
//  defined by the command-id byte in tc_buf[0].
//
//  The TTY-Connect command data params are in the tc_buf[] array, and tc_buf_index
//  will contain number of data bytes in the array.  The tc_buf[] array can hold a max
//  of TC_BUF_SIZE bytes, and is indexed from 0 to tc_buf_index-1.
//  The first byte is a "command-id" number, defining the specific action for this type
//  of command, and which parameters follow to complete the command.  The second byte is
//  the "number" of params to follow (up until cr), and the last byte is an optional
//  checksum.  The checksum can be truncated to 1-byte (overflow discarded) before being
//  sent, or can simply be the sum (allowing the parser to truncate it to 1-byte).

#separate
void process_TR_cmd(void)
{
  int1 enb;
  int8 i, j, c;

  // ----- Check number of params (and opt checksum), adding 2 for cmd-id and num params:
  if (tc_buf_param_test(tc_buf[1] + 2))                                         // tc_buf[1] is num
  {

    // ----- which command-id number?  (tc_buf[0] = cmd_id):
    switch (tc_buf[0])                                                          // tc_buf[0] is command-id
    {


    // ----- get sys-info mssg -------------- /.TR,0,0  >>>  -.TC,0,4,0,0,svh,svl
    case 0:
      if(tc_buf[1] == 0)                                                        // check num params for this cmd
      {
        send_tc_sysinfo_mssg();
        return;
      }
      break;


    // ----- get current connection mode ---- /.TR,1,0  >>>  -.TC,mod,num...
    case 1:
      if(tc_buf[1] == 0)                                                        // check num params for this cmd
      {
        send_tc_connect_mode_mssg();
        return;
      }
      break;


    // ----- read state of output signal ---- /.TR,20,1,out
    case 20:
      if(tc_buf[1] == 1)                                                        // check num params for this cmd
      {
        if (tc_buf[2] <= 4)
        {
          // ----- send mssg:   -.TC,20,2,out,sta <cr>
          vp_msg[0] = 20;                                                       // mssg-id
          vp_msg[1] = 2;                                                        // num params to follow
          vp_msg[2] = tc_buf[2];                                                // out
          vp_msg[3] = query_output_signal(tc_buf[2]);                           // sta
          send_tc_mssg_params(4);                                               // send mssg and flash status led
        }
        else
        {
          //  --- send  -.TC,240,1,err_id <cr> error message:
          vp_msg[0] = 240;                                                      // mssg-id = error mssg
          vp_msg[1] = 1;                                                        // num params
          vp_msg[2] = ERR_CMD_PARAM;
          send_tc_mssg_params(3);                                               // send mssg and flash status led
        }
        return;
      }
      break;


    // ----- programmable flags:
    case 40:   // ----- unconn_loops_mark ----------------- /.TR,40,0
    case 41:   // ----- tu_data_inversion ----------------- /.TR,41,0
    case 49:   // ----- rx_auto_crlf_insert --------------- /.TR,49,0

    case 70:   // ----- rx_unshift_on_space --------------- /.TR,70,0
    case 71:   // ----- rx_unshift_on_cr ------------------ /.TR,71,0
    case 72:   // ----- rx_diddle_filter ------------------ /.TR,72,0
    case 73:   // ----- tx_diddle_insertion --------------- /.TR,73,0
    case 74:   // ----- si_so_for_ltrs_figs --------------- /.TR,74,0
    case 75:   // ----- wru_reply ------------------------- /.TR,75,0
      if(tc_buf[1] == 0)                                                        // check num params for this cmd
      {
        switch(tc_buf[0])
        {
        case 40:
          enb = unconn_loops_mark;
          break;
        case 41:
          enb = tu_data_inversion;
          break;
        case 49:
          enb = rx_auto_crlf_insert;
          break;

        case 70:
          enb = rx_unshift_on_space;
          break;
        case 71:
          enb = rx_unshift_on_cr;
          break;
        case 72:
          enb = rx_diddle_filter;
          break;
        case 73:
          enb = tx_diddle_insertion;
          break;
        case 74:
          enb = si_so_for_ltrs_figs;
          break;
        case 75:
          enb = wru_reply;
          break;
        }
        // ----- send mssg:   -.TC,10x,1,enb <cr>
        vp_msg[0] = tc_buf[0];                                                  // mssg-id
        vp_msg[1] = 1;                                                          // num params to follow
        vp_msg[2] = (int8)(enb);                                                // enb
        send_tc_mssg_params(3);                                                 // send mssg and flash status led
        return;
      }
      break;


    // ----- max_chars_per_line -------------- /.TR,50,0
    // ----- motor_power_mode ---------------- /.TR,53,0
    case 50:
    case 53:
      if(tc_buf[1] == 0)                                                        // check num params for this cmd
      {
        switch(tc_buf[0])
        {
        case 50:
          vp_msg[2] = max_chars_per_line;
          break;
        case 53:
          vp_msg[2] = motor_power_mode;
          break;
        }
        // ----- send mssg:   -.TC,13x,1,val <cr>
        vp_msg[0] = tc_buf[0];                                                  // mssg-id
        vp_msg[1] = 1;                                                          // num params to follow
        send_tc_mssg_params(3);                                                 // send mssg and flash status led
        return;
      }
      break;


    // ----- get 8-char auto_crlf_str string -------- /.TR,90,0
    // ----- get 8-char start_of_mssg_str string ---- /.TR,91,0
    // ----- get 8-char end_of_mssg_str string ------ /.TR,192,0
    // ----- get 8-char cond_code_str string -------- /.TR,93,0
    // ----- get 8-char selcal_station_str string --- /.TR,94,0
    // ----- get 8-char selcal_all_str string ------- /.TR,95,0
    case 90:
#if (STREAM_OPTIONS)
    case 91:
    case 92:
    case 93:
    case 94:
    case 95:
#endif
      if(tc_buf[1] == 0)                                                        // check num params for this cmd
      {
        for (i=0; i<=7; i++)
        {
          j = i + 2;
          switch(tc_buf[0])
          {
          case 90:
            vp_msg[j] = auto_crlf_str[i];
            break;
#if (STREAM_OPTIONS)
          case 91:
            vp_msg[j] = start_of_mssg_str[i];
            break;
          case 92:
            vp_msg[j] = end_of_mssg_str[i];
            break;
          case 93:
            vp_msg[j] = cond_code_str[i];
            break;
          case 94:
            vp_msg[j] = selcal_station_str[i];
            break;
          case 95:
            vp_msg[j] = selcal_all_str[i];
            break;
#endif
          }
        }
        vp_msg[0] = tc_buf[0];                                                  // mssg-id
        vp_msg[1] = 8;                                                          // num params to follow
        send_tc_mssg_params(10);                                                // send mssg and flash status led
        return;
      }
      break;


    // ----- get time ------------------------  /.TR,100,0
    case 100:
      if(tc_buf[1] == 0)                                                        // check num params for this cmd
      {
        // ----- send mssg:   -.TC,100,3,tmh,tmm,tms <cr>
        vp_msg[0] = 100;                                                        // mssg-id
        vp_msg[1] = 3;                                                          // num params to follow
        vp_msg[2] = hour;
        vp_msg[3] = minute;
        vp_msg[4] = second;
        send_tc_mssg_params(5);                                                 // send mssg and flash status led
        return;
      }
      break;


    // ----- get date ------------------------  /.TR,101,0
    case 101:
      if(tc_buf[1] == 0)                                                        // check num params for this cmd
      {
        // ----- send mssg:   -.TC,101,4,dtm,dtd,dth,dty <cr>
        vp_msg[0] = 101;                                                        // mssg-id
        vp_msg[1] = 4;                                                          // num params to follow
        vp_msg[2] = month;
        vp_msg[3] = day;
        vp_msg[4] = year_h;
        vp_msg[5] = year_l;
        send_tc_mssg_params(6);                                                 // send mssg and flash status led
        return;
      }
      break;


    // ----- get clock options ---------------  /.TR,102,0
    case 102:
      if(tc_buf[1] == 0)                                                        // check num params for this cmd
      {
        // ----- send mssg:   -.TC,102,4,ckm,ckl,tmf,dtf <cr>
        vp_msg[0] = 102;                                                        // mssg-id
        vp_msg[1] = 4;                                                          // num params to follow
        vp_msg[2] = clock_mode;
        vp_msg[3] = clock_loop;
        vp_msg[4] = (int8)(ck_12_hour_time_format);
        vp_msg[5] = (int8)(ck_standard_date_format);
        send_tc_mssg_params(6);                                                 // send mssg and flash status led
        return;
      }
      break;


    // ----- get Demo-Message ----------------  /.TR,120,1,dem
    //
    //         We are using a 24LC512 eeprom, which stores 64K bytes (0x0000 to 0xFFFF)
    //         and using it to store sixteen 4K-byte demo messages:
    //              mssg-1:  0x0000 to 0FFF
    //              mssg-2:  0x1000 to 1FFF
    //              ...
    //              mssg-16: 0xF000 to FFFF
    case 120:
      if(tc_buf[1] == 1)                                                        // check num params for this cmd
      {
        demo_mssg_num = limit(tc_buf[2], 1, 16);                                // demo-mssg number 1-16 (points to ext eeprom mssg)
        demo_mssg_char_addr = ((int16)(demo_mssg_num) - 1) << 12;               // init to base address of mssg in ext eeprom
        demo_mssg_chars_remaining = 4096;                                       // init to max demo-mssg size

        // --- send mssg id:   -.TC,120,1,dem <cr>
        printf("\r\n-.TC,120,1,%u\r\n", demo_mssg_num);


        // --- send text of mssg:
        while(demo_mssg_chars_remaining--);
        {
          c = read_ext_eeprom(demo_mssg_char_addr++);

          // --- test for '`' grave-accent (end-of-text-marker) 0x60 and exit:
          if ( c == '`')
          {
            printf("\r\n\r\n");
            return;
          }
          send_sci_tx_byte(c);
        }
        // --- reached max mssg size:
        printf("\r\n\r\n");
        return;
      }
      break;


    // ----- invalid cmd-id:
    default:
      send_tc_error_mssg(ERR_CMD_ID);
      return;

    }
    // ----- invalid number of params for specific command:
    send_tc_error_mssg(ERR_CMD_NUMPARAM_CS);

  }
  else
  {
    // ----- did not get num params, or checksum was wrong:
    send_tc_error_mssg(ERR_CMD_NUMPARAM_CS);
  }
}


//--------------------------------------------------------------------------------------------
//  int8 tc_buf_param_test(int8 num_params)         check number of params (and opt checksum)
//
//    Private routine tc_buf_param_test(int8 num_params) is called to verify number of
//    parameters in tc command buffer -- expected number of params is passed.
//    If command has one extra parameter, it is presumed to be a checksum.
//    FALSE is returned if number of params is wrong, and/or the checksum test fails,
//    else TRUE is returned.
//
//    The TTY-Connect command data params are in the tc_buf[] array, and tc_buf_index
//    will contain number of data bytes in the array.  The tc_buf[] array can hold a max
//    of TC_BUF_SIZE bytes, and is indexed from 0 to tc_buf_index-1.
//    The first byte is a "command-id" number, defining the specific action for this type
//    of command, and which parameters follow to complete the command.  The second byte is
//    the "number" of params to follow (up until cr), and the last byte is an optional
//    checksum.  The checksum can be truncated to 1-byte (overflow discarded) before being
//    sent, or can simply be the sum (allowing the parser to truncate it to 1-byte).


int8 tc_buf_param_test(int8 num_params)
{
  int8    i, checksum = 0;

  if (tc_buf_index == num_params)                                               // tc_buf_index is num params (incl cmd-id)
  {                                                                             // num_params is what non-cs command is expecting
    return (TRUE);
  }
  else
  {
    if (tc_buf_index == num_params + 1)                                         // is there an extra checksum byte?
    {
      // add all but last byte (which is the checksum sent in the command)
      for (i=0; i < tc_buf_index-1; i++)
        checksum += tc_buf[i];

      #if (DEBUG_TC_EXE)
        printf(send_sci_tx_byte, "DEBUG_TC_EXE: tc_tx_checksum:  %u,  calc:  %u\r\n", tc_buf[tc_buf_index-1], checksum);
      #endif

      // if checksums match, return TRUE, else return FALSE:
      if (checksum == tc_buf[tc_buf_index-1])
        return (TRUE);
      else
        return(FALSE);
    }
    else
    {
      return (FALSE);                                                           // wrong number of params -- bad cmd
    }
  }
}


//--------------------------------------------------------------------------------------------
//  int8 get_sci_rx_byte_and_process_tc_cmd(void)   get next sci rx char & execute TC command if done
//
//  Public routine c=get_sci_rx_byte_and_process_tc_cmd() allows main loop to get next
//  char (if avail) from serial port buffer, and parse char.  When complete TTY-Connect
//  command has been parsed, the command is executed.  Also returns char, or 0 if no char available.
//
//  TTY-Connect Commands begin with a slash, followed by a period, then two letters defining the
//  "type" of command, then one or more comma-delimited decimal-number parameters.
//
//  Parsing in get_sci_rx_byte_and_process_tc_cmd() will decompose the TTY-Connect command into:
//    - a unique byte in tc_cmd_type (an XOR of all header chars, eg: /^.^T^W)
//    - convert ascii-encoded-decimal data params to binary and put into tc_buf[] array
//    - tc_buf_index will contain number of data bytes in tc_buf[] array
//        (there must be at least one byte in buffer, the command-id)
//
//  The parser XORs each ascii header char into tc_cmd_type, for unique header IDs:
//  For example, for /.TW header, tc_cmd_type =  /^.^T^W = 0x2F ^ 0x2F ^ 0x54 ^ 0x57 = 0x03.
//  TTY-Connect Commands only define two chars after /., but parser allows any number of chars.
//
//  The intermediate XOR results are arbitrary:
//
//        /         0x2F
//        .         0x2E
//        T         0x54
//        W         0x57
//        R         0x52
//        /^.^T     0x54
//
//  The final XOR result in tc_cmd_type must be unique:
//  (this limits selection of header chars)
//
//        /^.^T^W   0x02
//        /^.^T^R   0x07
//
//  Note that we pass both upper and lower case here.  It so happens that both are
//  accepted (but not mixed lower/upper):
//
//        t         0x74
//        w         0x77
//        r         0x72
//        /^.^T^W   0x02  =  /^.^t^w
//        /^.^T^R   0x07  =  /^.^t^r
//
//  Main prog should set use_XONXOFF_flg to ON to prevent buffer overflows.
//
//  The main prog may define DEBUG_TC_PARSE to be ON, for debug output of parsing.
//  The main prog may define DEBUG_TC_EXE to be ON, for debug output of execution.

#separate
int8 get_sci_rx_byte_and_process_tc_cmd(void)
{
  int8 c, i;

  // ----- get next rx char (returns 0 if no char avail):
  c = get_sci_rx_byte();

  if(echo_chars_flg)                                                            // if echo enabled
  {
    if (c)
      send_sci_tx_byte(c);                                                      // echo char
    if (c == CR)
      send_sci_tx_byte(LF);                                                     // add lf to cr
  }

  #if (DEBUG_TC_PARSE)
    if (c)
      printf(send_sci_tx_byte, "DEBUG_TC_PARSE: tc_rx_byte:  %c  0x%X\r\n", c, c);
  #endif


  // ----- start parsing by checking for special chars:
  switch (c)
  {
  case 0:                                                                       // no char avail, so just return
    return(0);



// ----- if in demo-message set mode, indicated by demo_mssg_loading = 1, the following globals are used here:
//         demo_mssg_char_addr:         ext-eeprom current write address for this char (was initialized to base address of mssg)
//         demo_mssg_chars_remaining:   decremented here to min of 1 (last char to write)  (was initialized to 4096)
//         demo_mssg_num:               will be 1 to 16, but we only need the address at this point in code

///////// get char here and poke it into eeprom.  inc demo_mssg_char_addr and decr demo_mssg_chars_remaining
///////// if char is grave '`' then write it and we are done (clear flag).  if 4k written, also done



//////////////////  `               (grave accent 0x60 terminates text) !!!!!!!!!!!!

// later



  case '/':                                                                     // got /, so start looking for header
    tc_cmd_state = TC_GET_SLASH_HDR;
    tc_cmd_type = '/';
    #if (DEBUG_TC_PARSE)
      printf(send_sci_tx_byte, "DEBUG_TC_PARSE: tc_cmd_type (init):  0x%X\r\n", tc_cmd_type);
    #endif
    return(c);

// case ':':                                                                    // got :, so...
//    tc_cmd_state = TC_COLON_HDR;
//    tc_cmd_type = ':';
//    return(c);

  case CR:
  case LF:
    if (tc_cmd_state == TC_GET_SLASH_DAT)
    {
      tc_buf_index++;                                                           // inc index for num params in tc_buf array
      tc_cmd_state = TC_EXECUTE;                                                // should have tc command now, so fall-through
      break;
    }
    else
    {
      #if (DEBUG_TC_PARSE)
        printf(send_sci_tx_byte, "DEBUG_TC_PARSE: CR or LF during header - cmd discarded\r\n");
      #endif
      tc_cmd_state = TC_STBY;                                                   // CR or LF occured during header -- discard cmd
      return(c);
    }
  }


  // ----- more parsing:
  switch (tc_cmd_state)
  {

  // ----- get the slash header (/.TW, /.TR...) by XORing each header char into tc_cmd_type:
  //        (normally only two chars after /., but this allows any number of chars)
  //        (final XOR result in tc_cmd_type must be unique)
  //
  case TC_GET_SLASH_HDR:
    if (c == ',')                                                               // got entire header, so discard this comma
    {
      tc_cmd_state = TC_GET_SLASH_DAT;                                          //  and get first data param
      tc_buf_index = 0;                                                         // init index to start of tc_buf array,
      tc_buf[0] = 0;                                                            //  and clear first data byte
      #if (DEBUG_TC_PARSE)
        printf(send_sci_tx_byte, "DEBUG_TC_PARSE: get first data param\r\n");
      #endif
      return(c);
    }
    else
    {
      tc_cmd_type ^= c;                                                         // else, XOR this header char into tc_cmd_type
      #if (DEBUG_TC_PARSE)
        printf(send_sci_tx_byte, "DEBUG_TC_PARSE: tc_cmd_type (xor):  0x%X\r\n", tc_cmd_type);
      #endif
      return(c);
    }


  // ----- get the slash data params and load into tc_buf[] array:
  //        (normally 1 to 3 chars in param, but this allows any number of chars)
  //        (zero chars will result in param = 0, and anything over 255 will be truncated to byte)
  //
  case TC_GET_SLASH_DAT:
    if (c == ',')                                                               // got entire param, so discard this comma
    {
      ++tc_buf_index;                                                           // inc index to next param in tc_buf array

      if (tc_buf_index == TC_BUF_SIZE)                                          // too many params, but try to execute anyway
      {
        tc_cmd_state = TC_STBY;                                                 // reset state for next time
        break;                                                                  // fall-through to execute
      }

      tc_buf[tc_buf_index] = 0;                                                 // clear next data byte
      #if (DEBUG_TC_PARSE)
        printf(send_sci_tx_byte, "DEBUG_TC_PARSE: get next data param\r\n");
      #endif
      return(c);
    }
    else                                                                        // merge this digit into param
    {
      if (isdigit(c))
      {
        tc_buf[tc_buf_index] = 10 * tc_buf[tc_buf_index] + (c - '0');
        #if (DEBUG_TC_PARSE)
          printf(send_sci_tx_byte, "DEBUG_TC_PARSE: tc_buf[%u] = %u\r\n", tc_buf_index, tc_buf[tc_buf_index]);
        #endif
        return(c);
      }

      else if (c == 'X')                                                        // X is alias for 255 (ignore param)
      {
        tc_buf[tc_buf_index] = 255;
        #if (DEBUG_TC_PARSE)
          printf(send_sci_tx_byte, "DEBUG_TC_PARSE: got X -- inserting 255\r\n");
        #endif
        return(c);
      }

      else                                                                      // bogus char -- discard this command
      {
        #if (DEBUG_TC_PARSE)
          printf(send_sci_tx_byte, "DEBUG_TC_PARSE: bogus char during data - cmd discarded\r\n");
        #endif
        tc_cmd_state = TC_STBY;
        return(c);
      }
    }


  // ----- got a presumed-complete command, so fall-through to execute it:
  //
  case TC_EXECUTE:
    tc_cmd_state = TC_STBY;                                                     // reset state for next time
    break;                                                                      // fall-through to execute cmd


  // ----- at this point, typically TC_STBY with bogus char:
  //
  default:
    tc_cmd_state = TC_STBY;                                                     // should not have invalid state, but...
    return(c);

  }


  // ========== execute TTY-Connect Command:
  //
  //  When we fall-through the parser to this point, a presumed-complete command has been
  //  been found -- qualification of the command is still necessary, since number of data
  //  parameters may not be correct, etc.  If the command is valid, it is executed.
  //
  //  Parsing (above) has decomposed the TTY-Connect command into:
  //    - a unique byte in tc_cmd_type (an XOR of all header chars, eg: /^.^T^W)
  //    - converted ascii-encoded-decimal data params to binary and put into tc_buf[] array
  //    - tc_buf_index contains number of data bytes in tc_buf[] array
  //        (there must be at least one byte in buffer, the command-id)
  //
  //  The TTY-Connect command data params are in the tc_buf[] array, and tc_buf_index
  //  will contain number of data bytes in the array.  The tc_buf[] array can hold a max
  //  of TC_BUF_SIZE bytes, and is indexed from 0 to tc_buf_index-1.
  //  The first byte is a "command-id" number, defining the specific action for this type
  //  of command, and which parameters follow to complete the command.  The second byte is
  //  the "number" of params to follow (up until cr), and the last byte is an optional
  //  checksum.  The checksum can be truncated to 1-byte (overflow discarded) before being
  //  sent, or can simply be the sum (allowing the parser to truncate it to 1-byte).



  #if (DEBUG_TC_EXE)
    printf(send_sci_tx_byte, "DEBUG_TC_EXE: tc_cmd_type:  0x%X\r\n", tc_cmd_type);
    printf(send_sci_tx_byte, "DEBUG_TC_EXE: number of data params (tc_buf_index) = %u\r\n", tc_buf_index);
    for (i=0; i < tc_buf_index; i++)
      printf(send_sci_tx_byte, "  tc_buf[%u] = %u\r\n", i, tc_buf[i]);
  #endif


  // ----- tc_cmd_type is command type byte ( xor of /.TW, /.TR...).
  // ----- tc_buf[0] is command-id byte, and defines specific command within command-type.
  //
  switch (tc_cmd_type)
  {

  // ==================== TTY-Connect-Write (/.TW) commands:
  case TC_TW:
    process_TW_cmd();
    break;


  // ==================== TTY-Connect-Read (/.TR) commands:
  case TC_TR:
    process_TR_cmd();
    break;


  // ----- command-type error:
  default:
    send_tc_error_mssg(ERR_CMD_TYPE);
    break;

  }

  return(CRLF);

}



