//
// Title        : Denver UI fix
// Author       : Lars Pontoppidan Larsen
// Date         : February 14, 2006
// Version      : 1.00
// Target MCU   : Atmel AVR Series
//
// DESCRIPTION:
// Main program logic of Denver DVD-802 UI fix
//
// 
//
// DISCLAIMER:
// The author is in no way responsible for any problems or damage caused by
// using this code. Use at your own risk.
//
// LICENSE:
// This code is distributed under the GNU Public License
// which can be found at http://www.gnu.org/licenses/gpl.txt
//


#include <avr/io.h>
#include <avr/pgmspace.h>
#include <avr/interrupt.h>
#include "ir.h"
#include "irsend.h"
#include "kbd.h"
#include "pwmled.h"

/* DELAY FUNCTIONS */

#define LOOPS_PER_MS (F_CPU/1000/4)  //!< Loops pr ms

/* Spin for ms milliseconds. */
void ms_spin(unsigned short ms)
{
        if (!ms)
                return;

        /* the inner loop takes 4 cycles per iteration */
        __asm__ __volatile__ (
                "1:                     \n"
                "       ldi r26, %3     \n"
                "       ldi r27, %2     \n"
                "2:     sbiw r26, 1     \n"
                "       brne 2b         \n"
                "       sbiw %0, 1      \n"
                "       brne 1b         \n"
                : "=w" (ms)
                : "w" (ms), "i" (LOOPS_PER_MS >> 8), "i" (0xff & LOOPS_PER_MS)
                );
}


/******************************************************************************
*                                                                             *
*                               MESSAGE CONSTANTS                             *
*                                                                             *
******************************************************************************/


/* Denver IR messages.
   All are 4 bytes, starting with denver_id, followed by the msg 
 */
#define DMSG_PREAMBLE_1 0x20
#define DMSG_PREAMBLE_2 0xDF
#define DMSG_1          0x8877
#define DMSG_2          0x708F
#define DMSG_3          0xF00F
#define DMSG_4          0xA857
#define DMSG_5          0x48B7
#define DMSG_6          0xC837
#define DMSG_7          0x9867
#define DMSG_8          0x6897
#define DMSG_9          0xE817
#define DMSG_0          0x58A7
#define DMSG_P10        0xAA55
#define DMSG_EJECT      0xB04F
#define DMSG_MUTE       0xB24D
#define DMSG_UP         0x2AD5
#define DMSG_DOWN       0xBA45
#define DMSG_LEFT       0x9A65
#define DMSG_RIGHT      0x3AC5
#define DMSG_PLAY       0xB847
#define DMSG_PAUSE      0xE01F
#define DMSG_STOP       0xF807
#define DMSG_SKIPBACK   0x18E7
#define DMSG_SKIPFWD    0x38C7
#define DMSG_SCANBACK   0x08F7
#define DMSG_SCANFWD    0x28D7


/* Technics IR messages */
#define TMSG_PREAMBLE_1 0x40
#define TMSG_PREAMBLE_2 0x04
#define TMSG_PREAMBLE_3 0x05 

#define TMSG_PRESETDOWN 0x20AC89
#define TMSG_PRESETUP   0x202C09
#define TMSG_BAND       0x202500

#define TMSG_DECK       0x10A9BC

/* for cd: */
#define TMSG_CD_1          0x50085D
#define TMSG_CD_2          0x5088DD
#define TMSG_CD_3          0x50481D
#define TMSG_CD_4          0x50C89D
#define TMSG_CD_5          0x50287D 
#define TMSG_CD_6          0x50A8FD 
#define TMSG_CD_7          0x50683D 
#define TMSG_CD_8          0x50E8BD 
#define TMSG_CD_9          0x50184D 
#define TMSG_CD_0          0x5098CD 
#define TMSG_CD_P10        0x502174 
#define TMSG_CD_DISC       0x502570
#define TMSG_CD_SKIPBACK   0x5092C7
#define TMSG_CD_SKIPFWD    0x505207 
#define TMSG_CD_SCANBACK   0x504015 
#define TMSG_CD_SCANFWD    0x50C095
#define TMSG_CD_PAUSE      0x506035
#define TMSG_CD_STOP       0x500055 
#define TMSG_CD_PLAY       0x505005

/* for tuner: */
#define TMSG_1          0x20082D
#define TMSG_2          0x2088AD
#define TMSG_3          0x20486D
#define TMSG_4          0x20C8ED
#define TMSG_5          0x20280D
#define TMSG_6          0x20A88D
#define TMSG_7          0x20684D
#define TMSG_8          0x20E8CD
#define TMSG_9          0x20183D 
#define TMSG_0          0x2098BD
#define TMSG_P10        0x202104


/* Keyboard messages (or button presses)  */


#define KMSG_LEFT        1
#define KMSG_ARROWLEFT   2
#define KMSG_EJECT       4
#define KMSG_RIGHT       16
#define KMSG_ARROWRIGHT  32

/******************************************************************************
*                                                                             *
*                               MODULE SETUP                                  *
*                                                                             *
******************************************************************************/

/* Keyboard translation */
#define KMSG_TRANS 5

const struct {
  unsigned char kmsg;   // from: keyboard msg
  unsigned short dmsg;  // to:   denver ir msg
} kmsg_trans[KMSG_TRANS] PROGMEM = \
    {{KMSG_EJECT,      DMSG_EJECT},
     {KMSG_ARROWLEFT,  DMSG_STOP},
     {KMSG_LEFT,       DMSG_SKIPBACK},
     {KMSG_RIGHT,      DMSG_SKIPFWD},
     {KMSG_ARROWRIGHT, DMSG_PLAY}};

/* Technics IR translation */
#define TMSG_TRANS 19

const struct {
  unsigned long tmsg;   // from: technics msg
  unsigned short dmsg;  // to:   denver ir msg
} tmsg_trans[TMSG_TRANS] PROGMEM = \
    {{TMSG_CD_1        ,DMSG_1     },
     {TMSG_CD_2        ,DMSG_2     },
     {TMSG_CD_3        ,DMSG_3     },
     {TMSG_CD_4        ,DMSG_4     },
     {TMSG_CD_5        ,DMSG_5     },
     {TMSG_CD_6        ,DMSG_6     },
     {TMSG_CD_7        ,DMSG_7     },
     {TMSG_CD_8        ,DMSG_8     },
     {TMSG_CD_9        ,DMSG_9     },
     {TMSG_CD_0        ,DMSG_0     },
     {TMSG_CD_P10      ,DMSG_P10   },
     {TMSG_CD_DISC     ,DMSG_EJECT },
     {TMSG_CD_SKIPBACK ,DMSG_SKIPBACK },
     {TMSG_CD_SKIPFWD  ,DMSG_SKIPFWD  },
     {TMSG_CD_SCANBACK ,DMSG_LEFT  }, 
     {TMSG_CD_SCANFWD  ,DMSG_RIGHT }, 
     {TMSG_CD_PAUSE    ,DMSG_PAUSE }, 
     {TMSG_CD_STOP     ,DMSG_STOP  }, 
     {TMSG_CD_PLAY     ,DMSG_PLAY  }};
     
/******************************************************************************
*                                                                             *
*                               IMPLEMENTATION                                *
*                                                                             *
******************************************************************************/


unsigned char denver_msg[4] = {DMSG_PREAMBLE_1, DMSG_PREAMBLE_2, 0, 0};

void send_denver_msg(unsigned short msg)
{
  denver_msg[2] = msg >> 8;
  denver_msg[3] = msg & 0xFF;
 
  irsend_send(denver_msg);
  
}


/* Translates a keyboard msg code to denver msg */
unsigned short translate_kmsg(unsigned char kmsg)
{
  unsigned char c;
  
  for (c=0; c<KMSG_TRANS; c++) {
    if (pgm_read_byte_near(&(kmsg_trans[c].kmsg)) == kmsg)
      return pgm_read_word_near(&(kmsg_trans[c].dmsg));
  }
  
  return 0;
}



/* Translates a technics ir msg to denver msg, if possible */
unsigned short translate_tmsg(unsigned long tmsg)
{
  unsigned char c;
  
  for (c=0; c<TMSG_TRANS; c++) {
    if (pgm_read_dword_near(&(tmsg_trans[c].tmsg)) == tmsg)
      return pgm_read_word_near(&(tmsg_trans[c].dmsg));
  }
  
  return 0;
}



int main(void)
{
  unsigned short msg;
  
  /* Initialization */
  ir_init();
  irsend_init();
  kbd_init();
  pwmled_init();
    
  asm("sei");
    
  pwmled_on_hold();
  ms_spin(200);
  pwmled_off();
  
  while(1) {
    
    // Limit main cycle
    ms_spin(1);
    
    /* Check for keyboard msg */
    kbd_handler();
    if (kbd_status != 0) {

      msg = translate_kmsg(kbd_status);
      
      /* Only treat button once */
      kbd_status = 0;
      
      if (msg) {
        send_denver_msg(msg);
        pwmled_flash();
      }
    } 
    
    /* Check for IR msg */
    if (ir_msg_pending) {
      
      /* Analyse preamble */
      msg = 0;
    
      if (ir_msg[0] == TMSG_PREAMBLE_1) {
        if ((ir_msg[1] == TMSG_PREAMBLE_2) && (ir_msg[2] == TMSG_PREAMBLE_3)) {
          /* Technics msg, try to translate */
          msg = translate_tmsg(((unsigned long)ir_msg[3] << 16) | 
                               ((unsigned short)ir_msg[4] << 8) | ir_msg[5]);
        }
      }
      else if (ir_msg[0] == DMSG_PREAMBLE_1) {
        if (ir_msg[1] == DMSG_PREAMBLE_2) {
          /* Denver msg. Send it directly */
          msg = ((unsigned short)ir_msg[2] << 8) | ir_msg[3];
        }
        
      }
      
      /* Indicate ir_msg has been processed */
      ir_msg_pending = 0;
      
      if (msg) {
        pwmled_flash();
        send_denver_msg(msg);
      }
      
    }
    
  }
  
}

