/****************************************************************************
remote13.c (SWITCH VERSION)

USE AT YOUR OWN RISK!

This program is a remote control for Raytheon's Autohelm ST4000 autopilot using the
Seatalk(tm) network protocol.  It also switches the Raytheon instrument lamps on
and off.

The following Seatalk protocol is extracted from Thomas Knauf's web site:
www.thomasknauf.de/seatalk.htm

Message protocol
* Each message contains between 3 and 18 characters:
* COMMAND byte (the only byte with the command-bit set)
  ATTRIBUTE byte, specifying the total length of the message in the least significant nibble:
      Most  significant 4 bits: 0 or part of a data value
      Least significant 4 bits: Number of additional bytes beyond the mandatory data byte
  DATA byte (mandatory, meaning than the smallest message is 3 bytes)
  DATA bytes (optional, up to 15 additional data bytes, meaning that longest messages is 18 bytes)

Serial Data Transmission
11 bits are transmitted for each byte:
  * 1  Start bit (0V)
  * 8  Data Bits (least significant bit transmitted first, bit ON = +12V)
  * 1  Command/Data bit (+12V if command byte, 0V if other)
  * 1  Stop bit (+12V)

Collision Management
Bus should be idle for at least 2mS (+12V for at least 10/4800 seconds).
Listens to it's own transmission and recognizes when its message has
been corrupted by a second talker. In this case it abandons the remaining
bytes in the message, waits for the bus to become free again, and then
retransmits the whole message.

LAMP INTENSITY CODES
------------------------------
30 00 00          Lamp off
30 00 0C          Lamp full on

KEYSTROKE CODES
------------------------------
86 11 01 FE     AUTO
86 11 02 FD     STANDBY
86 11 03 FC     TRACK
86 11 05 FA     -1
86 11 06 F9    -10
86 11 07 F8      +1
86 11 08 F7    +10
86 11 09 F6    Set Response Level 1
86 11 0A F5    Set Response Level 2

                 +5   +5
                  |    |
                  14   4
                ----------
  +1     ----6-|          |-17-- out to Seatalk (transistor driver)
  -1     ----7-|          |-18-- in from Seatalk (transistor buffer)
  +10    ----8-|          |-1--- out to piezo beeper
  -10    ----9-|          |
  Stby   ---10-|  16F84   |
  Auto   ---11-|          |
  Track  ---12-|          |
  Lamp/Resp-13-|          |
  4MHz XTAL-15-|          |
       XTAL-16-|          |
                ----------
                     5
                     |
                    Gnd

***************************************************************************/

/* The following include should contain 16F84 or 16F628. */
#include < 16F628.h >

#if __device__==84
#fuses XT, NOPROTECT, PUT, WDT
#endif
/* The following has to be 627 for the 628 (compiler error?) */
#if __device__==627
#fuses XT, NOPROTECT, PUT, WDT, BROWNOUT, NOMCLR, NOLVP
#endif
#include < jonsinc.h >

#use fast_io ( A )
#use standard_io ( B )
#use delay ( clock = 4000000, restart_wdt )

#byte PORT_A  = 5
#byte PORT_B  = 6
#bit TX_OUT   = PORT_A.0
#bit RX_IN    = PORT_A.1
#bit BEEP_OUT = PORT_A.2

#define CAL_LOC        0
#define SW_PLUS_ONE    PIN_B0
#define SW_MINUS_ONE   PIN_B1
#define SW_PLUS_TEN    PIN_B2
#define SW_MINUS_TEN   PIN_B3
#define SW_TRACK       PIN_B4
#define SW_AUTO        PIN_B5
#define SW_STANDBY     PIN_B6
#define SW_LAMP_RESP   PIN_B7
#define RESP_AUTO      0x09
#define RESP_TIGHT     0x0A
#define TACK_PORT      0x21
#define TACK_STBD      0x22
#define LAMP_OFF       0x00
#define LAMP_ON        0x0C
#define PLUS_ONE       0x07
#define MINUS_ONE      0x05
#define PLUS_TEN       0x08
#define MINUS_TEN      0x06
#define STANDBY        0x02
#define AUTOM          0x01
#define TRACK          0x03
#define PITCH_MIN      3
#define PITCH_MAX      200
#define DEBOUNCE_DELAY 100

#rom 0x2100 = { PITCH_MIN }
void SendKeystrokeMsg ( char cData );
void SendLampMsg ( char cX );
char SendByte ( char cError, char cCommand, char cData );
char SendBit ( cBit );
void CheckBus ( void );
void Beep ( char cCnt );

static char cLampState, cResponseState, cCal;

void main ( void )
        {
        char cX, cDir;

        TX_OUT = LOW;                       // allow output to float
        BEEP_OUT = LOW;
        set_tris_a ( 0b11111010 );          // A0, A2 are outputs, A1 is input
        setup_counters ( RTCC_INTERNAL, WDT_2304MS );   // 256 * 4uS = 1.024mS timer wrap
        port_b_pullups ( TRUE );
        cLampState = LAMP_OFF;
        cResponseState = RESP_AUTO;

        // Piezo calibration routine
        if ( ( input ( SW_STANDBY ) == LOW ) && ( input ( SW_TRACK ) == LOW ) )
            {
            cCal = PITCH_MIN;
            while ( ( input ( SW_STANDBY ) == LOW ) || ( input ( SW_TRACK ) == LOW ) )
                {
                restart_wdt();
                Beep ( 3 );
                if ( input ( SW_STANDBY ) == LOW )      // decending pitch
                    {
                    if ( cCal < PITCH_MAX )
                        {
                        cCal++;
                        }
                    }
                if ( input ( SW_TRACK ) == LOW )        // ascending pitch
                    {
                    if ( cCal > PITCH_MIN )
                        {
                        cCal--;
                        }
                    }
                }
            write_eeprom ( CAL_LOC, cCal );
            }
        else         // or normal operation
            {
            cCal = read_eeprom ( CAL_LOC );
            for ( cX = 0; cX < 20; cX++ )
                {
                Beep ( 1 );
                }
            }
        while ( TRUE )         // do forever
            {
            restart_wdt();
            if ( input ( SW_PLUS_ONE ) == LOW )    // if switch pressed
                {
                Beep ( 1 );
                for ( cX = 0; cX < 255; cX++ )        // check how long switch is pressed
                    {
                    if ( input ( SW_PLUS_ONE ) == HIGH )
                        {
                        break;                         // jump out when switch released
                        }
                    delay_ms ( 4 );
                    }
                if ( cX < 255 )                        // if less than one second
                    {
                    SendKeystrokeMsg ( PLUS_ONE );
                    }
                else
                    {
                    Beep ( 5 );
                    for ( cX = 0; cX < 5; cX++ )
                        {
                        SendKeystrokeMsg ( PLUS_ONE );
                        }
                    }
                while ( input ( SW_PLUS_ONE ) == LOW )     // wait until switch up
                    {
                    delay_ms ( DEBOUNCE_DELAY );
                    }
                }
            if ( input ( SW_MINUS_ONE ) == LOW )    // if switch pressed
                {
                Beep ( 1 );
                for ( cX = 0; cX < 255; cX++ )        // check how long switch is pressed
                    {
                    if ( input ( SW_MINUS_ONE ) == HIGH )
                        {
                        break;                         // jump out when switch released
                        }
                    delay_ms ( 4 );
                    }
                if ( cX < 255 )                        // if less than one second
                    {
                    SendKeystrokeMsg ( MINUS_ONE );
                    }
                else
                    {
                    Beep ( 5 );
                    for ( cX = 0; cX < 5; cX++ )
                        {
                        SendKeystrokeMsg ( MINUS_ONE );
                        }
                    }
                while ( input ( SW_MINUS_ONE ) == LOW )     // wait until switch up
                    {
                    delay_ms ( DEBOUNCE_DELAY );
                    }
                }
            if ( input ( SW_PLUS_TEN ) == LOW )    // if switch pressed
                {
                Beep ( 1 );
                for ( cX = 0; cX < 255; cX++ )        // check how long switch is pressed
                    {
                    if ( input ( SW_PLUS_TEN ) == HIGH )
                        {
                        break;                         // jump out when switch released
                        }
                    delay_ms ( 4 );
                    }
                if ( cX < 255 )                        // if less than one second
                    {
                    SendKeystrokeMsg ( PLUS_TEN );
                    }
                else
                    {
                    Beep ( 5 );
                    SendKeystrokeMsg ( TACK_STBD );
                    }
                while ( input ( SW_PLUS_TEN ) == LOW )     // wait until switch up
                    {
                    delay_ms ( DEBOUNCE_DELAY );
                    }
                }
            if ( input ( SW_MINUS_TEN ) == LOW )    // if switch pressed
                {
                Beep ( 1 );
                for ( cX = 0; cX < 255; cX++ )        // check how long switch is pressed
                    {
                    if ( input ( SW_MINUS_TEN ) == HIGH )
                        {
                        break;                         // jump out when switch released
                        }
                    delay_ms ( 4 );
                    }
                if ( cX < 255 )                        // if less than one second
                    {
                    SendKeystrokeMsg ( MINUS_TEN );
                    }
                else
                    {
                    Beep ( 5 );
                    SendKeystrokeMsg ( TACK_PORT );
                    }
                while ( input ( SW_MINUS_TEN ) == LOW )     // wait until switch up
                    {
                    delay_ms ( 25 );
                    }
                }
            if ( input ( SW_STANDBY ) == LOW )    // if switch pressed
                {
                Beep ( 1 );
                SendKeystrokeMsg ( STANDBY );
                while ( input ( SW_STANDBY ) == LOW )     // wait until switch up
                    {
                    delay_ms ( DEBOUNCE_DELAY );
                    }
                }
            if ( input ( SW_AUTO ) == LOW )    // if switch pressed
                {
                Beep ( 1 );
                SendKeystrokeMsg ( AUTOM );
                while ( input ( SW_AUTO ) == LOW )     // wait until switch up
                    {
                    delay_ms ( DEBOUNCE_DELAY );
                    }
                }
            if ( input ( SW_TRACK ) == LOW )    // if switch pressed
                {
                Beep ( 1 );
                SendKeystrokeMsg ( TRACK );
                while ( input ( SW_TRACK ) == LOW )     // wait until switch up
                    {
                    delay_ms ( DEBOUNCE_DELAY );
                    }
                }
            if ( input ( SW_LAMP_RESP ) == LOW )    // if switch pressed
                {
                Beep ( 1 );
                for ( cX = 0; cX < 255; cX++ )        // check how long switch is pressed
                    {
                    if ( input ( SW_LAMP_RESP ) == HIGH )
                        {
                        break;                         // jump out when switch released
                        }
                    delay_ms ( 4 );
                    }
                if ( cX < 255 )                        // if less than one second
                    {
                    if ( cLampState == LAMP_OFF )
                        {
                        cLampState = LAMP_ON;
                        }
                    else
                        {
                        cLampState = LAMP_OFF;
                        }
                    SendLampMsg ( cLampState );
                    }
                else
                    {
                    Beep ( 5 );
                    if ( cResponseState == RESP_AUTO )
                        {
                        cResponseState = RESP_TIGHT;
                        }
                    else
                        {
                        cResponseState = RESP_AUTO;
                        }
                    SendKeystrokeMsg ( cResponseState );
                    }
                while ( input ( SW_LAMP_RESP ) == LOW )     // wait until switch up
                    {
                    delay_ms ( DEBOUNCE_DELAY );
                    }
                }
            }
        }

void SendKeystrokeMsg ( char cData )
    {
    char cError;

    do {
        CheckBus();                                //wait for bus to be idle
        cError = SendByte ( NO, YES, 0x86 );       // command: keystroke
        cError = SendByte ( cError, NO, 0x11 );    // data: remote control, 1 extra byte (4 total)
        cError = SendByte ( cError, NO, cData );   // data: PlusOne key
        cError = SendByte ( cError, NO,~cData );   // data: inverted data
        } while ( cError == YES );                 // repeat if message was corrupted
    }

void SendLampMsg ( char cX )
    {
    char cError;
    do {
        CheckBus();                                //wait for bus to be idle
        cError = SendByte ( NO, YES, 0x30 );       // command: lamp
        cError = SendByte ( cError, NO, 0x00 );    // data: 00
        cError = SendByte ( cError, NO, cLampState );   // data: lamp state
        } while ( cError == YES );                 // repeat if message was corrupted
    }

char SendByte ( char cError, char cCommand, char cData )
    {
    char cX;

    if ( cError != YES )
        {
        cError = SendBit ( HIGH );           // start bit (0V)
        for ( cX = 0; cX < 8; cX++ )
            {
            cError = SendBit ( ~cData & 0x01 );  // LSB data bit
            cData >>= 1;                 // shift right
            }
        cError = SendBit ( cCommand ? LOW : HIGH );    // set if command byte, clear if data byte
        cError = SendBit ( LOW );           // stop bit (+12V)
        }
    return ( cError );
    }

char SendBit ( cBit )
    {
    char cX, cY;
    // this code adjusted to give 208uS bit times (4800 baud)
    TX_OUT = cBit;                  // send bit to output
    for ( cX = 0; cX < 5; cX++ )
        {
        delay_us ( 15 );
        if ( RX_IN == !cBit )       // check if output bit is corrupted by another talker
            {
            return ( HIGH );        // return collision error
            }
        }
    return ( LOW );                 // return no error
    }

void CheckBus ( void )
    {
    char cX;

    for ( cX = 0; cX < 255; cX++ )  // assumes output is floating to +12V for ~5mS
        {
        if ( RX_IN == HIGH )        // check if output bit is corrupted by another talker
            {
            cX = 0;                 // reset count to zero
            restart_wdt();          // CCS compiler doesn't put CLRWDT into short delay_us, apparently
            }
        delay_us ( 7 );
        }
    }

void Beep ( char cCnt )
    {
    char cX, cY;

    for ( cY = 0; cY < cCnt; cY++ )
        {
        for ( cX = 0; cX < ( 30 + (  PITCH_MAX / cCal ) ); cX++ )
            {
            BEEP_OUT = HIGH;
            delay_us ( cCal );
            delay_us ( cCal );
            delay_us ( cCal );
            BEEP_OUT = LOW;
            delay_us ( cCal );
            delay_us ( cCal );
            delay_us ( cCal );
            }
        delay_ms ( 30 );
        }
    }