Infrared Control of Your PC

by Gavin Smyth



Listing One

class Irman

{

public:

  // Initialise from registry (or take defaults)

  Irman( const TCHAR* regKey,   // Registry key under which to find values

         int numKeys );         // Number of keys on the Remote

  // Shutdown, and rewrite configuration to the registry 

 ~Irman();

  // Get and set the port name (COMx)

  const TCHAR* Port() const { return comPortName; }

  void Port( const TCHAR* comPort );

  // Get and set the inter-key delay (milliseconds)

  unsigned long Delay() const { return interKeyDelay; }

  void Delay( unsigned long d ) { interKeyDelay = d; }

  // Wait for a data packet to be received and return the index into

  //         the vector that represents it (or -1 if not recognised)

  int Key();

  // Trigger next received key sequence to be stored for indicated key code

  void SetKey( int key );

  // Interrupt the current read - used from a separate task

  void Interrupt();

private:

  // Each Irman key is decoded to a sequence of 6 bytes

  struct KeyCode

  {

    unsigned char code[ 6 ];

    bool operator==( const KeyCode& key )

    {

      for( int i = 0; i < sizeof( code ); ++i )

        if( key.code[ i ] != code[ i ] )

          return false;

      return true;

    }

  };

  TCHAR comPortName[ 5 ];  // contains COMx\0

  volatile HANDLE comPort; // Handle to the opened COM port

  HANDLE ioCompletion;     // Handle used for overlapped I/O

  // Where to read/write values - passed to constructor

  const TCHAR* regKey;

  // How many keys on the remote - passed to constructor

  int numKeys;

  // Codes corresponding to each key to be recognised (numKeys long)

  KeyCode* keyCodes;

  // Open and close the COM port

  void Open();

  void Close();

  // Read a complete Irman sequence

  bool Read( KeyCode& );

  // Time (ms) last Irman sequence read

  unsigned long keyTime;

  // Key code to which to set next read key

  volatile int setKey;

  // Low level (blocking, but interruptable via Interrupt() above)

  // COM port read and write

  bool ReadWait( void* data, unsigned long size );

  bool WriteWait( const void* data, unsigned long size );

  // Waggle the control lines to power up or down the Irman

  void PowerOn() const;

  void PowerOff() const;

  // Discard any characters in the COM port buffers

  void Flush();

  // Time to wait from reading one key ro the next

  volatile unsigned long interKeyDelay;

  // Disable copying

  Irman( const Irman& );

  Irman& operator=( const Irman& );

};





Listing Two

Irman::Irman( const TCHAR* regKey_,  int numKeys_ ) :

  regKey( regKey_ ), numKeys( numKeys_ ), keyCodes( new KeyCode[ numKeys_ ] ),

  comPort( INVALID_HANDLE_VALUE ), keyTime( GetTickCount() ), setKey( false ),

  ioCompletion( CreateEvent( NULL, TRUE, FALSE, NULL ) )

{

  // Read the port name, inter key delay and all the key codes from

  //      the registry, and then fire up the device

  RegistryKey reg( HKEY_LOCAL_MACHINE, regKey );

  reg.Read( RegPort, comPortName,

                   sizeof( comPortName ) / sizeof( TCHAR ), _T("COM2")

);

  interKeyDelay = reg.Read( RegDelay, 500 );

  for( int i = 0; i < numKeys; ++i )

  {

    static KeyCode blankKeyCode;

    TCHAR num[ 16 ];

    wsprintf( num, _T("%03d"), i );

    reg.Read( num, keyCodes[ i ].code, sizeof( KeyCode ), &blankKeyCode

);

#   ifdef VERBOSE

      TCHAR buff[ 40 ];

      wsprintf( buff, "Key %d is %02X %02X %02X %02X %02X %02X\n", i,

       keyCodes[ i ].code[0], keyCodes[ i ].code[1], keyCodes[ I ].code[2],

       keyCodes[ i ].code[3], keyCodes[ i ].code[4], keyCodes[ I ].code[5] );

      OutputDebugString( buff );

#   endif

  }

  Open();

}

Irman::~Irman()

{

  // Close everything down, and write back to the registry

  Close();

  if( ioCompletion != INVALID_HANDLE_VALUE )

  {

    CloseHandle( ioCompletion );

    ioCompletion = INVALID_HANDLE_VALUE;

  }

  RegistryKey reg( HKEY_LOCAL_MACHINE, regKey );

  reg.Write( RegPort, comPortName );

  reg.Write( RegDelay, interKeyDelay );

  for( int i = 0; i < numKeys; ++i )

  {

    TCHAR num[ 16 ];

    wsprintf( num, _T("%03d"), i );

    reg.Write( num, keyCodes[ i ].code, sizeof( KeyCode ) );

  }

}

void Irman::Open()

{

  const TCHAR* error = _T("Error opening device");

  comPort = CreateFile( comPortName, GENERIC_READ | GENERIC_WRITE,

                        0, NULL, OPEN_EXISTING, FILE_FLAG_OVERLAPPED, NULL );

  if( comPort == INVALID_HANDLE_VALUE )

    MessageBox( NULL, _T("Could not open COM port - maybe something

                    else is using it"), error, MB_ICONEXCLAMATION | MB_OK );

  else

  {

    DCB dcb;

    if( Verify( GetCommState( comPort, &dcb ) ) )

    {

      dcb.BaudRate    = CBR_9600;

      dcb.fParity     = 0;

      dcb.Parity      = NOPARITY;

      dcb.ByteSize    = 8;

      dcb.StopBits    = ONESTOPBIT;

      dcb.fDtrControl = DTR_CONTROL_DISABLE;

      dcb.fRtsControl = RTS_CONTROL_DISABLE;

      if( Verify( SetCommState( comPort, &dcb ) ) )

      {

        PowerOff();           // Just in case it was already on

        Sleep( 200 );

        PowerOn();

        Sleep( 100 );         // Time for the output to settle

        Flush();              // Remove power up garbage

        WriteWait( "I", 1 );  // These strings must be ASCII, not Unicode

        Sleep( 2 );           // Need to have >500us between the 'I' & the 'R'

        WriteWait( "R", 1 );



        char data[ 2 ];

        if( ReadWait( data, 2 ) && data[ 0 ] == 'O' && data[ 1 ] == 'K'

)

          return;

        else

          MessageBox( NULL, _T("Irman not responding"),

                               error, MB_ICONEXCLAMATION | MB_OK );

      }

    }

  }

  // To get this far, something must have gone wrong

  Close();

}

void Irman::Close()

{

  if( comPort != INVALID_HANDLE_VALUE )

  {

    Verify( CloseHandle( comPort ) );

    comPort = INVALID_HANDLE_VALUE;

  }

}

void Irman::Port( const TCHAR* comPort )

{

  _tcsnccpy( comPortName, comPort, sizeof( comPortName ) / sizeof( TCHAR ) );

  comPortName[ sizeof( comPortName ) / sizeof( TCHAR ) - 1 ] = 0;

  // Reopen the port if the name changed - I could have checked the new

  //    and old names and if they were the same, skip the reopen. However,

  //    this way, I can force a recover from a "stuck" I/O port...

  Close();

  Open();

}

void Irman::SetKey( int key )

{

  if( comPort == INVALID_HANDLE_VALUE )

  {

    MessageBox( NULL, _T("COM port not valid - can't configure"),

               _T("IR Configuration Error"), MB_ICONEXCLAMATION | MB_OK

);

    return;

  }

  if( key < 0 || key > numKeys )

  {

    return;

  }

  // Just indicate to the reading function that it should store the next

  //     sequence instead of matching it

  setKey = key;

}

int Irman::Key()

{

  if( comPort == INVALID_HANDLE_VALUE && !Open() )

    return -1;

  // The Irman reports a number of sequences for each key - chuck away

  //     old ones before reading the next key.

  Flush();

  KeyCode key;

  // Loop for a minimum time, to get rid of old duplicate/inverted messages

  unsigned long startTime = keyTime;

  do

  {

    if( !Read( key ) )

      return -1;

  } while( keyTime - startTime < interKeyDelay );



  // Now, key contains a valid code sequence, so do something with it



  if( setKey != -1 )

  {

    // If we're in record mode, just use this sequence for the relevant

    //      entry in keyCodes[]

    keyCodes[ setKey ] = key;

    int retVal = setKey;

    setKey = -1;

    return retVal;

  }

  else

  {

    // If we're not in record mode, scan the list to find a match,

    //     and repeat for up to the inter key period before giving up and

    //     admitting it's unrecognised - the reason for the loop is to

    //     catch any inversions along the way

    startTime = keyTime;

    do

    {

      for( int i = 0; i < numKeys; ++i )

        if( key == keyCodes[ i ] )

          return i;

      if( !Read( key ) )

        return -1;

    } while( GetTickCount() - startTime < interKeyDelay );

    return -1; // No key found

  }

}

bool Irman::Read( KeyCode& key )

{

  bool success = ReadWait( key.code, sizeof( key.code ) );

# ifdef VERBOSE

    if( success )

    {

      TCHAR buff[ 40 ];

      wsprintf( buff, "Code %02X %02X %02X %02X %02X %02X\n",

                      key.code[0], key.code[1], key.code[2],

                      key.code[3], key.code[4], key.code[5] );

      OutputDebugString( buff );

    }

# endif

  keyTime = GetTickCount();

  return success;

}











