/*
  USB CW Keyboard

  By Glen Popiel - KW5GP

  Uses USB Mini Host Shield module, 2.2" ILI9341 TFT Display, and standard USB Keyboard

  Keyboard can be wireless with USB dongle - detected automatically


  Uses USB_Host_Shield_Library_2.0
  Copyright (C) 2011 Circuits At Home, LTD. All rights reserved.

  This software may be distributed and modified under the terms of the GNU
  General Public License version 2 (GPL2) as published by the Free Software
  Foundation and appearing in the file GPL2.TXT included in the packaging of
  this file. Please note that GPL2 Section 2[b] requires that all works based
  on this software must also be made publicly available under the terms of
  the GPL2 ("Copyleft").

  Contact information
  -------------------

  Circuits At Home, LTD
  Web      :  http://www.circuitsathome.com
  e-mail   :  support@circuitsathome.com

  Uses Lewis Interrupt-driven Morse Code Library

  Uses TimeOne Library to generate timer interrupts for Lewis Library

*/

//#define debug // Uncomment this to enable debugging information
//#define usb_debug  // Uncomment this line to enable USB Host LIbrary debug information - requires #define debug also uncommented
//#define debug1  // Uncomment this line for extra debug information

#include <hidboot.h>  // USB Host shield library for Human Interface Devices in boot protocol mode
#include <SPI.h>  // Library to drive SPI devices
#include <Adafruit_GFX.h> // The Adafruit Graphics Library
#include <Adafruit_ILI9341.h> // The ILI9341 TFT Display Library
#include <Lewis.h>  // The Lewis Interrupt driven Morse Code Library
#include <TimerOne.h> // Library to set timer interrupts

#define TFT_DC 7  // Define the TFT DC pin
#define TFT_CS 8  // Define the TFT CS pin
#define beep_pin 5  // Define the pin for the CW tone
#define key_pin 6 // Define the relay keying pin

#define cw_start_speed 10 // Set the keyer starting speed to 10 wpm

#define osc_fail "USB OSC Fail" // Define the USB oscillator failure error text
#define usb_ok "USB OK" // Define the USB oscillator ok status text
#define repeat_delay 100  // Set repeating key delay to 100ms

#define cw_font_size 4  // The CW text display font size
#define status_font_size 2  // The font size for the status line

String usb_status_msg;  // Variable for the current USB status message to display
String cw_data = "";  // String variable to hold cw output text data
String key_ascii = "";  // Single character string variable to hold converted key value
bool system_status = true;  // Flag indicating current system status - true = ok, false = bad
bool repeat_enabled = false;  // Flag indicating if we've met the requirements to repeat keys
int first_pass_delay = 10000; // Number of milliseconds to wait for USB Host to identify and begin communication with HID Keyboard
int repeat_timeout = 500; // Number of milliseconds a key must remain pressed to initiate repeat
int key_code; // Variable for the USB non-ASCII key code
int ascii_key_code; // Variable for the ASCII key code
int prev_key_code;  // Variable for the previous USB non-ASCII key code
int prev_ascii_key_code;  // Variable for the previous USB ASCII key code
unsigned long repeat_timer; // Timeout timer variable
int key_speed = cw_start_speed; // Define the initial keyer speed and set it to the defined start_speed
int last_speed = 0; // Variable for the previous keyer speed
bool key_mode = false;  // Used to select beep or key 0=beep 1=key
bool last_mode = true; // Set the previous mode opposite to the starting key mode so it will update the tft

// Use hardware SPI (on Uno, #13, #12, #11) and the above for CS/DC
Adafruit_ILI9341 tft = Adafruit_ILI9341(TFT_CS, TFT_DC);  // Create the TFT instance

class KbdRptParser : public KeyboardReportParser    // Define a class to parse the incoming keyboard data
{
    void PrintKey(uint8_t mod, uint8_t key);

  protected:
    void OnControlKeysChanged(uint8_t before, uint8_t after);

    void OnKeyDown  (uint8_t mod, uint8_t key);
    void OnKeyUp  (uint8_t mod, uint8_t key);
    void OnKeyPressed(uint8_t key);
};

USB     Usb;  // Create a USB instance

HIDBoot<USB_HID_PROTOCOL_KEYBOARD>    HidKeyboard(&Usb);  // Start keyboard in HID Boot protocol mode

KbdRptParser Prs; // Create a Keyboard Report Parser Instance

Lewis Morse;  // Create a Morse Library Instance

void setup()
{

  Morse.begin(-1, beep_pin, 10, true); // Morse.begin(rx_pin, tx_pin, words_per_minute, use_interrupts)

  Timer1.initialize(10000); // Initialize the timer interrupt to 10 milliseconds
  Timer1.attachInterrupt(myISR);  // Attach the Interrupt Service Routine

  tft.begin();  // Start the TFT display

#ifdef debug
  Serial.begin( 115200 );
  Serial.println("ILI9341 TFT Start");  // Verifies that the TFT is operational
  Serial.print("Display Power Mode: 0x");
  Serial.println(tft.readcommand8(ILI9341_RDMODE), HEX);
  Serial.println("");
  Serial.println("USB Start");
#endif


  clear_display();  // Clear the TFT display
  tft.setTextColor(ILI9341_CYAN, ILI9341_BLACK);  // Set the display text to Cyan. Black background color used to "clear" previous text
  tft.setRotation(3); // horizontal display // change rotation as needed/desired
  tft.setTextSize(4);  // large font
  tft.setCursor(90, 30);  //Set the Cursor and display the startup screen
  tft.print("KW5GP");
  tft.setCursor(80, 100);
  tft.print("USB CW");
  tft.setCursor(55, 160);
  tft.print("Keyboard");

  delay(5000);  //Wait 5 seconds then clear the startup message

  tft.setTextColor(ILI9341_GREEN, ILI9341_BLACK);  // Set the display text to Cyan. Black background color used to "clear" previous text
  clear_display();  // Clear the TFT display

  if (Usb.Init() == -1) // Check for USB initialization failure
  {
    usb_status_msg = osc_fail;  // Set the status message to oscillator fail
    system_status = false;  // Indicate a system error
    update_status();  // Update the status line on the bottom of the TFT display
    
#ifdef debug
    Serial.println(osc_fail);
#endif

    do  // Loop endlessly, no point in continuing
    {
      // do nothing further until reset
    } while (true);
  } else
  {
    usb_status_msg = usb_ok;  // Indicate USB started ok
    update_status();  // Update the status line on the bottom of the TFT display
  }

  delay( 200 ); // Wait for USB to come online

  HidKeyboard.SetReportParser(0, &Prs); // Set up the HID report parser

#ifdef debug
  Serial.println("USB Ready");
  Serial.println("");
#endif

  key_code = 0; // Clear the incoming key code
  ascii_key_code = 0; // Clear the incoming ASCII key code
  repeat_timer = millis() + first_pass_delay; // Set the repeat key timer to wait a bit on startup

  tft.setCursor(0, 10); // Set the TFT cursor to the top line

  mode_set();  // Set the CW keying mode (beep or key)
}

void loop()
{
  key_code = 0; // Clear the incoming key code
  ascii_key_code = 0; // Clear the incoming ASCII key code

  if (!repeat_enabled && (millis() > first_pass_delay)) // Check to see if we can repeat the key and it's past the startup delay time
  {
    repeat_timer = millis() + repeat_timeout; // Set the repeat timer timeout time
  }

  Usb.Task(); // Check the USB keyboard for data
  
  if (millis() >= repeat_timer) // if the key is still down and we've exceeded the repeat timeout
  {
    delay(repeat_delay);  // Delay the repeat delay
    key_code = prev_key_code; // Set the key code to last received key code
    ascii_key_code = prev_ascii_key_code; // Set the ASCII key code to last received ASCII key code
  }

  if (key_code != 0)  // If the key code is not zero, we have received a key 
  {
#ifdef debug1
    Serial.print("Key code: ");
    Serial.print(key_code);
#endif
    if (ascii_key_code != 0)  // If the ASCII key code is not zero, the key is an ASCII key
    {
      // It's an ASCII character

#ifdef debug1
      Serial.print("   ASCII code: ");
      Serial.print(ascii_key_code);
      Serial.print("   ASCII Character: ");
      Serial.println(char(ascii_key_code));
#endif

      prev_key_code = key_code; // Set the previous key code to current received key code
      prev_ascii_key_code = ascii_key_code; // Set the previous ASCII key code to current ASCII received key code
      
      // Verify the ASCII character is a valid International Morse Code character, display and send it

      switch (ascii_key_code) // Check for invalid keys and ignore
      {
        case 19:  // <Enter>
          break;

        case 35:  // #
          break;

        case 37:  // %
          break;

        case 42:  // *
          break;

        case 91:  // - [
          break;

        case 92:  // - \
          break;

        case 93:  // - ]
          break;

        case 94:  // ^
          break;

        case 96:  // `
          break;

        case 123:  // {
          break;

        case 124:  // |
          break;

        case 125:  // - }
          break;

        case 126:  // ~
          break;

        default:  // It's a valid character - send it
          update_cw_text(); // Update the TFT display with the new key

          send_cw();  // Send the CW character
          break;
      }
    } else
    {
      //It's a control code
#ifdef debug
      Serial.print(" - Control Code");
#endif
      switch (key_code)
      {
        case 82:
          // Up Arrow - Increase speed
#ifdef debug
          Serial.println("  Up Arrow");
#endif

          if (key_speed < 30) // Limit max speed to 30 wpm
          {
            key_speed = key_speed + 1;


          }
          mode_set();
          update_status();
          break;

        case 81:
          // Down Arrow - Decrease speed
#ifdef debug
          Serial.println("  Down Arrow");
#endif

          if (key_speed > 5)
          {
            key_speed = key_speed - 1;
          }
          mode_set();
          update_status();
          break;

        case 80:
          // Left Arrow - Set beep mode
#ifdef debug
          Serial.println("  Left Arrow");
#endif
          key_mode = false;  // Enable Beep mode
          mode_set();
          update_status();
          break;

        case 79:
          // Right Arrow - Set Key Mode
#ifdef debug
          Serial.println("  Right Arrow");
#endif

          key_mode = true; // Enable key mode
          mode_set();
          update_status();
          break;

        case 76:
          // Delete Key - Send 3 dits (EEE)
#ifdef debug
          Serial.println("  Delete/Error");
#endif

          for (int i = 1; i <= 3; i++)
          {
            ascii_key_code = 69;  // The ASCII code for "E"
            key_ascii = "E";

            tft.setTextColor(ILI9341_RED, ILI9341_BLACK);  // Red on black - background color required to "clear" previous text
            update_cw_text();
            tft.setTextColor(ILI9341_GREEN, ILI9341_BLACK);  // Red on black - background color required to "clear" previous text

            send_cw();  // Send the CW character
          }
          break;

        default:
          // If no match - ignore the key
#ifdef debug
          Serial.println();
#endif
          break;
      }
    }
  }
}

// clear_display function 
void clear_display()  // Clears the TFT display
{
  tft.fillScreen(ILI9341_BLACK);  // Fill the screen with the background color
}

// update_status Function
void update_status() // Prints status messages on bottom line of TFT display in green
{
  if (system_status)  // True if good, false if bad
  {
    //We're all good - update status in green
    tft.setTextColor(ILI9341_GREEN, ILI9341_BLACK);  // Green on black background color required to "clear" previous text
  } else
  {
    // There is a system error
    tft.setTextColor(ILI9341_RED, ILI9341_BLACK);  // Red on black background color required to "clear" previous text
  }
  tft.setTextSize(status_font_size);  // Set the font size to the status line font size
  tft.setCursor(10, 220); // Move to the bottom line of the display

  tft.print(usb_status_msg);  // Display the USB status

  if (key_mode != last_mode)  // Check to see if the keyer mode has changed - and if so, update the display
  {
    tft.setCursor(130, 220);
    if (key_mode)
    {
      tft.print("Key ");  
    } else
    {
      tft.print("Beep");
    }
    last_mode = key_mode;
  }

  if (key_speed != last_speed)  // Check to see if the keyer speed has changed - and if so, update the display
  {
    tft.setCursor(230, 220);
    tft.print("WPM: ");
    tft.print(key_speed);
    tft.print(" "); // Clear the second digit if less than 10 wpm
    last_speed = key_speed;
  }
  tft.setTextSize(cw_font_size);  // Set the display font size back to the CW text font size

  // Return the cursor to where we were
  // Set the cursor to line 0 and reprint the cw_data string - neat trick..it overwrites what is there without blinking and sets
  // the cursor back to where we were
  tft.setCursor(0, 10);
  tft.print(cw_data);
}

// update_cw_text function
void update_cw_text() // Updates the cw text on the display
{
  tft.setTextSize(cw_font_size);  // Set the cw text font size

  key_ascii = String(char(ascii_key_code)); // Convert the ASCII key code to a string
  key_ascii.toUpperCase();  // Make all the characters uppercase

  // Scroll bottom line up a line if at end of line 4
  if (cw_data.length() >= 52)
  {
    cw_data = cw_data.substring(13);  // Remove the first 13 characters
    tft.setCursor(0, 10); // Move the cursor to the top line
    tft.print(cw_data); // Print the cw data string variable
    for (int i = 1; i <= 13; i++) // Print a space 13 times to scroll the data up a line
    {
    tft.print(" "); 
    }
    tft.setCursor(0, 107);  // Set the cursor to the start of line 4
  }

  cw_data = cw_data + key_ascii;  // Add the character to the cw data string

  tft.print(key_ascii); // display the received ASCII character

#ifdef debug
  Serial.print("cw_data string length: ");
  Serial.print(cw_data.length());
#endif

}

// send_cw function
void send_cw()  // Sends the CW character
{
#ifdef debug
  Serial.print(" Sending: ");
  Serial.println(key_ascii);
#endif

  Morse.print(key_ascii); // Send the character to the Morse library
}

//mode_set function
void mode_set()   // Function to Set the mode to beep or keying and/or set keying speed
{
  if (key_mode) // Keying mode True = Key False = Beep
  {
    Morse.begin(-1, key_pin, key_speed, true); // Key on pin 6
  } else
  {
    Morse.begin(-1, beep_pin, key_speed, true); // Key on pin 5
  }

#ifdef debug
  Serial.print("Key Mode: ");
  Serial.print(key_mode);
  Serial.print("  WPM: ");
  Serial.println(key_speed);
#endif

  update_status();  // Update the information on the status line

}

// myISR function
void myISR()  // Function to handle interrupts for the Morse library
{
  Morse.timerISR();
}

// USB Host Library Functions ----------------------------------------------------------------------------------

void KbdRptParser::PrintKey(uint8_t m, uint8_t key)
{
  MODIFIERKEYS mod;
  *((uint8_t*)&mod) = m;

#ifdef usb_debug
  Serial.print((mod.bmLeftCtrl   == 1) ? "C" : " ");
  Serial.print((mod.bmLeftShift  == 1) ? "S" : " ");
  Serial.print((mod.bmLeftAlt    == 1) ? "A" : " ");
  Serial.print((mod.bmLeftGUI    == 1) ? "G" : " ");

  Serial.print(" > ");
  PrintHex<uint8_t>(key, 0x80);
  Serial.print(" < ");
#endif

#ifdef usb_debug
  Serial.print("Key = ");
  Serial.println(key);
#endif

#ifdef usb_debug
  Serial.print((mod.bmRightCtrl   == 1) ? "C" : " ");
  Serial.print((mod.bmRightShift  == 1) ? "S" : " ");
  Serial.print((mod.bmRightAlt    == 1) ? "A" : " ");
  Serial.println((mod.bmRightGUI    == 1) ? "G" : " ");
#endif
};

void KbdRptParser::OnKeyDown(uint8_t mod, uint8_t key)
{
#ifdef usb_debug
  Serial.print("DN ");
#endif

  repeat_enabled = true;
  PrintKey(mod, key);

  uint8_t c = OemToAscii(mod, key);

  if (c)
    OnKeyPressed(c);

  key_code = key;
  ascii_key_code = c;

#ifdef usb_debug
  Serial.print("Before OEM to ASCII: ");
  Serial.print(key, HEX);
  Serial.print("   After OEM to ASCII: ");
  Serial.println(c, HEX);
#endif
}

void KbdRptParser::OnControlKeysChanged(uint8_t before, uint8_t after)
{

  MODIFIERKEYS beforeMod;
  *((uint8_t*)&beforeMod) = before;

  MODIFIERKEYS afterMod;
  *((uint8_t*)&afterMod) = after;

#ifdef usb_debug

  if (beforeMod.bmLeftCtrl != afterMod.bmLeftCtrl)
  {
    Serial.println("LeftCtrl changed");
  }
  if (beforeMod.bmLeftShift != afterMod.bmLeftShift)
  {
    Serial.println("LeftShift changed");
  }
  if (beforeMod.bmLeftAlt != afterMod.bmLeftAlt)
  {
    Serial.println("LeftAlt changed");
  }
  if (beforeMod.bmLeftGUI != afterMod.bmLeftGUI)
  {
    Serial.println("LeftGUI changed");
  }

  if (beforeMod.bmRightCtrl != afterMod.bmRightCtrl)
  {
    Serial.println("RightCtrl changed");
  }
  if (beforeMod.bmRightShift != afterMod.bmRightShift)
  {
    Serial.println("RightShift changed");
  }
  if (beforeMod.bmRightAlt != afterMod.bmRightAlt)
  {
    Serial.println("RightAlt changed");
  }
  if (beforeMod.bmRightGUI != afterMod.bmRightGUI)
  {
    Serial.println("RightGUI changed");
  }

#endif

}

void KbdRptParser::OnKeyUp(uint8_t mod, uint8_t key)
{

#ifdef usb_debug
  Serial.print("UP ");
  PrintKey(mod, key);
#endif

  prev_key_code = 0;
  prev_ascii_key_code = 0;
  repeat_enabled = false;
}

void KbdRptParser::OnKeyPressed(uint8_t key)
{
#ifdef usb_debug
  Serial.print("ASCII value: ");
  Serial.print(key);
  Serial.print(" Character: ");
  Serial.println((char)key);
#endif
  // ascii_key_code = key;
};



