/* 
 * ArduinoRaspberryMWMDev.ino, ver 1.0
 * 
 * An Arduino program that accepts strings of Made with Magic commands from Raspberry Pi over USB cable and
 * transmits infrared signals to legacy MWM merchandise
 * 
 * This version attempts to include valid checksums for both the 0x9X phrases and 0x55 phrases
 * 
 * 3 IR LEDs, 3mm or 5mm, may be linked in series and the anode connected to pin 8-13 of an Arduino Uno.
 * If connecting to all 6 possible pins, mA for each should not exceed 25mA.  Requires a 20+ ohm resistor.  
 * Some IR LEDs are only rated for 20mA and would need a 25+ ohm resistor.
 * Connecting a single set of LEDs to one pin should not exceed 40mA.  Requires a 13+ ohm resistor and higher power IR LEDs.
 * Look for the IR LEDs that can handle 100mA.
 * 
 * (C) 2018 Khyizang
 * Please note: this product is neither affiliated with nor endorsed by the Walt Disney Co.
 * Tested using IDE 1.8.0
 *   
 * This program is free software: you can redistribute it and/or modify
 * it under the terms of the GNU General Public License as published by
 * the Free Software Foundation, either version 3 of the License, or
 * (at your option) any later version.
 * 
 * This program is distributed in the hope that it will be useful,
 * but WITHOUT ANY WARRANTY; without even the implied warranty of
 * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
 * GNU General Public License for more details.
 * 
 * You should have received a copy of the GNU General Public License
 * along with this program.  If not, see <http://www.gnu.org/licenses/>.
 * 
 */


#if defined(__AVR_ATmega2560__)

#define ACTIVEPORT PORTC //for 2560

/*  http://playground.arduino.cc/Main/ArduinoPinCurrentLimitations
 *  the sum of all IOL, for ports C0-C7, G0-G1, D0-D7, L0-L7 should not exceed 200 mA/VCC. 800mA max
 *  DC Current max per I/O Pin 40.0 mA
 */

int pinTotal = 9;//extra pin for all
int ledPins[9] = {0, 37, 36, 35, 34, 33, 32, 31, 30}; //PORTC Mega 2560

byte ledPort[] = {B11111111, //led all leds on.
                  B00000001,//pin 37 PORTC, led 1
                  B00000010,//pin 36 PORTC, led 2
                  B00000100,//pin 35 PORTC, led 3
                  B00001000,//pin 34 PORTC, led 4
                  B00010000,//pin 33 PORTC, led 5
                  B00100000,//pin 32 PORTC, led 6
                  B01000000,//pin 31 PORTC, led 7
                  B10000000 //pin 30 PORTC, led 8
                 };

#endif
#if defined(__AVR_ATmega328P__) || defined(__AVR_ATmega328__)


/*  The sum of all IOH, for ports B0 - B5, D5 - D7, ADC6, XTAL1, XTAL2 should not exceed 150 mA (200mA)
 *  DC Current abs max per I/O Pin 40.0 mA
 *  6 pins => 25mA per pin at 5V, 3 in series => 18 LEDs
 */

#define ACTIVEPORT PORTB //for 328 can change this to PORTD and should work, if better for you
int pinTotal = 7;
int ledPins[7] = {0, 8, 9, 10, 11, 12, 13}; // for 328 chip

byte ledPort[] = {B00111111, //led all leds on. highest two bits for crystal
                  B00000001,//pin  8 PORTB, led 1
                  B00000010,//pin  9 PORTB, led 2
                  B00000100,//pin 10 PORTB, led 3
                  B00001000,//pin 11 PORTB, led 4
                  B00010000,//pin 12 PORTB, led 5
                  B00100000 //pin 13 PORTB, led 6
                 };
#endif

#if defined(__AVR_ATmega32U4__)

//https://learn.adafruit.com/assets/41533 100mA max/port, 20mA max per pin, 10mA rec

#define ACTIVEPORT PORTB //only three readily available.  The others would be sck, miso, mosi
int pinTotal = 4;
int ledPins[4] = {0, 9, 10, 11};
byte ledPort[] = {B11100000, //led all leds on.
                  B00100000,//pin  9 PORTB, led 1
                  B01000000,//pin 10 PORTB, led 2
                  B10000000 //pin 11 PORTB, led 3
                 };
#endif

#define ALLLEDS 0

byte buf[100];
const int bufsize = 254;
char dline[bufsize];
boolean done = false;
int p = 0;
//boolean showPrompts = true;//comment this out and uncomment the following line if controlling from external source
boolean showPrompts = false;

byte calcCRC(byte *data)
{
  byte length = data[0] - 0x8E;

  byte crc = 0;
  while (length--) {
    crc ^= *data++;
    byte n = 8;
    do crc = (crc & 1) ? (crc >> 1) ^ 0x8C : crc >> 1;
    while (--n);
  }
  return crc;
}//end calc_crc

byte calc55Chk(byte *data){
  int i;
  //4 bit val of data[2] to get length
  int len = data[2] & B00001111;
  byte chk = 0;
  //skip the 55 AA and simply add up the rest
  for(i=2;i<len;i++){
    chk += data[i];
  }
  return chk;
}

void sendbyte(byte b, int ledNum)
{

  /*  Send 8-N-1 data as the mouse ears communicate in.
   *  Data consists of 1 Start Bit, 8 Data Bits, 1 Stop Bit, and NO parity bit
   *  Signals are inverted for IR
   *  1 bit at 2400 baud = 417 usec.  417/26 = 16 cycles
   *  38kHZ is on for 13 usec and off for 13 usec
   */


  int bitlen = 417;

  //int halfcyclelen = 13;//theoretical value
  int halfcyclelen = 14;//Logic Analyzer gives mostly 12.5 usec. Must use this value for pro mini and 32u4

  int cycle;
  byte  mask = ledPort[ledNum];

  cli();
  //start bit
  for (cycle = 0; cycle < 16; cycle++) {
    ACTIVEPORT |= mask;
    delayMicroseconds(halfcyclelen);
    ACTIVEPORT &= ~mask;
    delayMicroseconds(halfcyclelen);
  }
  //process the actual data byte
  byte i = 0;
  while (i < 8)
  {
    if (b >> (i++) & 1) {
      ACTIVEPORT &= ~mask;
      delayMicroseconds(bitlen);
    } else {
      for (cycle = 0; cycle < 16; cycle++) {
        ACTIVEPORT |= mask;
        delayMicroseconds(halfcyclelen);
        ACTIVEPORT &= ~mask;
        delayMicroseconds(halfcyclelen);
      }
    }

  }
  //stop bit
  ACTIVEPORT &= ~mask;
  delayMicroseconds(bitlen);
  sei();

}//end sendbyte

void sendCodes(byte *codes, int ledNum) {
  int i=0;
  while(codes[i] != '\0'){
  //for (i = 0; i < codes[0] - 0x8D; i++) {
    sendbyte(codes[i], ledNum);
    i++;
  }
}

int intfromhex(char hexstr[2])
{
  byte val = 0;
  hexstr[0] -= '0'; hexstr[1] -= '0';
  if (hexstr[0] < 10) {
    val = hexstr[0] << 4;
  } else if (hexstr[0] > 16 && hexstr[0] < 23) {
    val = (hexstr[0] - 7) << 4;
  } else if (hexstr[0] > 48 && hexstr[0] < 55) {
    val = (hexstr[0] - 39) << 4;
  } else {
    return 256;//invalid code
  }
  if (hexstr[1] < 10) {
    val += hexstr[1];
  } else if (hexstr[1] > 16 && hexstr[1] < 23) {
    val += hexstr[1] - 7;
  } else if (hexstr[1] > 48 && hexstr[1] < 55) {
    val += hexstr[1] - 39;
  } else {
    return 256;//invalid code
  }
  return (int)val;
}

void printCodes(byte* codes) {

  int len = codes[0] - 0x8E;
  int i;
  //Serial.print("\nprinting codes");
  Serial.print("     ");
  for (i = 0; i < len; i++) {
    Serial.print(codes[i], HEX);
    Serial.print(' ');
  }
  Serial.println(codes[i], HEX);
}

byte* fixCodes(String str) {
  //use the global buffer buf to store the results and set the pointer to it
  int pos = 0;
  int b;
  int h = 0;
  int n = 0;
  char hexStr[2];
  int codeCount = 0;
  bool fixfirst = false;
  byte list[60];

  //list[0] = 0;
  //first, check to see if the codes start with '9'
  /*if (str[0] != '9') {
    pos = 1; //we come back later and fill in the value
    list[0] = 0x90;
    fixfirst = true;
  }*/

  //max single command length is 16 codes + start and chk.  with spaces => 18*3 = 54 max chars
  while (str[n] != '\0' && str[n] != '\r' && str[n] != '\n' && n < 60) {
    if (str[n] != ' ' && str[n] != ',') {
      if (h == 0) {
        hexStr[0] = str[n];
        h++;
      } else {
        hexStr[1] = str[n];
        b = intfromhex(hexStr);
        if (b < 256) {
          list[pos] = (byte)b;
        } else {
          //Serial.println("Bad code");
          list[0] = 0;
          return list;
        }
        pos++;
        h = 0;
        //Serial.print(hexStr);
      }
    }
    n++;
  }
  /*if (fixfirst) {
    list[0] += pos - 2;
  }*/
  if (list[0] >= 0x90 && list[0] <= 0x9F) {
     n = calcCRC((byte*)list);
     list[pos] = n; pos++;
     list[pos] = '\0';
     memcpy(buf, list, pos);
     return buf;
  }
  if (list[0] == 0x55){
     n = calc55Chk((byte*)list);
     int len = list[2] & B00001111;
     list[len+2]=n;
     list[len+3]='\0';
     memcpy(buf, list,len+4);
     return buf;
  }
 return NULL;//if all else fails
}//end of fixCodes


void setup() {
  int i;
 Serial.begin(230400);
  //Serial.begin(115200);//some programs/cpus may not be able to keep up with this rate
 //Serial.begin(57600);//seems well tolerated by external programs
  while (!Serial) {
    ; // wait for serial port to connect. Needed for 32u4 boards
  }
  for (i = 0; i < pinTotal; i++) {
    pinMode(ledPins[i], OUTPUT);
  }
  
  done = false;
  p = 0;
}//end setup

void loop() {
  
  char ch;
  while (Serial.available() > 0 && p < bufsize && done == false) {
    ch = Serial.read();
    if (ch >= '0' || ch == ' ') {
      dline[p] = ch;
      p++;
    }
    if (ch == '\r' || ch == '\n' ) {
      done = true;
    }
  }
  if (done) {
    dline[p] = '\0';
    byte *codes = fixCodes(dline);
    if(codes != NULL){
      sendCodes(codes, 0);
    }   
    if (showPrompts && codes != NULL) {
      printCodes(codes);
    }
    done = false;
    p = 0;
  }
 
}//end loop
