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; }