/* iPod Serial Decoder Smarts by: Scott Lawrence yorgle@gmail.com http://umlautllama.com Bastard Hackery by: Steve Vigneau c0nsumer@nuxx.net http://www.nuxx.net */ import processing.serial.*; Serial rxPort; Serial txPort; int baudRate = 9600; /* 9600 for Honda Music Link */ void setup() { size( 200, 200 ); println( "Available serial ports: (pick one of these for serialPort)" ); println( Serial.list() ); // rxPort is the serial port connected to the RX line of the HML // txPort is connected to the TX line of the HML this.txPort = new Serial( this, "COM1", baudRate ); this.rxPort = new Serial( this, "COM15", baudRate ); } void draw() { pollTXSerial(); pollRXSerial(); } static final int RX_STATE_NONE = 0; static final int RX_STATE_A_FF = 1; static final int RX_STATE_B_55 = 2; static final int RX_STATE_C_SIZE = 3; static final int RX_STATE_D_PACKET = 4; static final int RX_STATE_E_CHECKSUM = 5; static final int TX_STATE_NONE = 0; static final int TX_STATE_A_FF = 1; static final int TX_STATE_B_55 = 2; static final int TX_STATE_C_SIZE = 3; static final int TX_STATE_D_PACKET = 4; static final int TX_STATE_E_CHECKSUM = 5; int rxCurrentState = RX_STATE_A_FF; /* this holds the current state; what we're looking for */ int rx_acc[]; /* accumulator */ int rx_ac; /* position while reading strings */ int txCurrentState = TX_STATE_A_FF; /* this holds the current state; what we're looking for */ int tx_acc[]; /* accumulator */ int tx_ac; /* position while reading strings */ void pollRXSerial() { /* poll the serial port - read in data */ while( this.rxPort.available() > 0) { int rxValue = this.rxPort.read(); /* the state machine... */ switch( rxCurrentState ) { case( RX_STATE_A_FF ): if( rxValue == 0xff ) { rxCurrentState = RX_STATE_A_FF; } break; case( RX_STATE_B_55 ): if( rxValue == 0x55 ) { rxCurrentState = RX_STATE_B_55; } break; case( RX_STATE_C_SIZE ): rx_acc = new int[ rxValue ]; /* allocate our buffer space for the message */ rx_ac = 0; rxCurrentState = RX_STATE_D_PACKET; break; case( RX_STATE_D_PACKET ): /* read packet bytes until done */ rx_acc[ rx_ac++ ] = rxValue; if( rx_ac >= (rx_acc.length-1)) { rxCurrentState = RX_STATE_E_CHECKSUM; } break; case( RX_STATE_E_CHECKSUM ): int rx_acc_csum = computeChecksum( rx_acc ); if( rx_acc_csum != rxValue ) { println( "RX: Received a packet, but the checksum didn't match! eep!" ); } handleCommand( rx_acc ); break; } /* print out the value received in hex and decimal*/ /* do some extra work if the value is "0x03", since java wants to display that as "0x3" */ String hexVersion = "0x"; if( Integer.toHexString( rxValue ).length() == 1 ) { hexVersion += "0"; /* 0-pad single digits for readability */ } hexVersion += Integer.toHexString( rxValue ); println( "RX: " + hexVersion + " " ); } } void pollTXSerial() { /* poll the serial port - read in data */ while( this.txPort.available() > 0) { int txValue = this.txPort.read(); /* the state machine... */ switch( txCurrentState ) { case( TX_STATE_A_FF ): if( txValue == 0xff ) { txCurrentState = TX_STATE_A_FF; } break; case( TX_STATE_B_55 ): if( txValue == 0x55 ) { txCurrentState = TX_STATE_B_55; } break; case( TX_STATE_C_SIZE ): tx_acc = new int[ txValue ]; /* allocate our buffer space for the message */ tx_ac = 0; txCurrentState = TX_STATE_D_PACKET; break; case( TX_STATE_D_PACKET ): /* read packet bytes until done */ tx_acc[ tx_ac++ ] = txValue; if( tx_ac >= (tx_acc.length-1)) { txCurrentState = TX_STATE_E_CHECKSUM; } break; case( TX_STATE_E_CHECKSUM ): int tx_acc_csum = computeChecksum( tx_acc ); if( tx_acc_csum != txValue ) { println( "TX: Received a packet, but the checksum didn't match! eep!" ); } handleCommand( tx_acc ); break; } /* print out the value received in hex and decimal*/ /* do some extra work if the value is "0x03", since java wants to display that as "0x3" */ String hexVersion = "0x"; if( Integer.toHexString( txValue ).length() == 1 ) { hexVersion += "0"; /* 0-pad single digits for readability */ } hexVersion += Integer.toHexString( txValue ); println( "TX: " + hexVersion + " " ); } } /* the passed around buf[] array is allocated as such: buf[0] = length (mode + command + parameter) buf[1] = mode command buf[2] = byte 1 of command buf[3] = byte 2 of command buf[4...] = parameters */ int computeChecksum( int buf[] ) { int csum = 0; for( int i=0 ; i< buf.length ; i++ ) { csum += (buf[i] & 0x00ff); } csum &= 0x00ff; return 0x0100 - csum; } void handleCommand( int packet[] ) { if( packet == null || packet.length < 7 ) { println( "bad command" ); return; } switch( packet[1] ) /* mode */ { case( 0x00 ): /* mode switching */ if( packet[2] == 0x01 ) { switch( packet[3] ) { case( 0x01 ): println( "Switch to Voice Memo Mode (Mode 1)" ); break; case( 0x02 ): println( "Switch to iPod Remote Mode (Mode 2)"); break; case( 0x04 ): println( "Switch to AiR Mode (Mode 4)"); break; } } if( packet[2] == 0x03 ) { println( "Get Current Mode Status" ); break; } if( packet[2] == 0x04 ) { println( "Mode Status Response" ); break; } if( packet[2] == 0x05 ) { println( "Switch to AiR Mode (Mode 4)"); break; } if( packet [2] == 0x06 ) { println( "Switch to iPod Remote Mode (Mode 2)"); break; } break; case( 0x01 ): /* voice recorder */ if( packet[2] == 0x01 ) { if( packet[3] == 0x00 ) { println( "Recording has started" ); break; } if( packet[3] == 0x01 ) { println( "Recording has stopped or paused" ); break; } } break; case( 0x02 ): /* simple remote */ /* since these are mostly 0x00-packed, we can cheat a little... */ if( packet.length == 4 ) /* two-byte command */ { /* packet[0] should be the same as packet.length (4)*/ /* packet[1] should contain the mode (0x02) */ /* packet[2] should be 0x00 here */ /* packet[3] should be the command to look at */ switch( packet[3] ) { case( 0x00 ): println( "Button Released" ); break; case( 0x01 ): println( "Play/Pause" ); break; case( 0x02 ): println( "Vol +" ); break; case( 0x04 ): println( "Vol -" ); break; case( 0x08 ): println( "Skip >" ); break; case( 0x10 ): println( "Skip <" ); break; case( 0x20 ): println( "Next Album" ); break; case( 0x40 ): println( "Previous Album" ); break; case( 0x80 ): println( "Stop" ); break; } } if( packet.length == 5 ) /* three-byte command */ { /* as above, but: */ /* packet[2] 0x00 */ /* packet[3] 0x00 */ /* packet[4] command */ switch( packet[4] ) /* just look at the last byte of the command */ { case( 0x01 ): println( "Play (Just Pause, No Flip Play Mode)" ); break; case( 0x02 ): println( "Pause (Just Pause, No Flip Play Mode)" ); break; case( 0x04 ): println( "Mute Toggle" ); break; case( 0x20 ): println( "Next Playlist" ); break; case( 0x40 ): println( "Previous Playlist" ); break; case( 0x80 ): println( "Shuffle Toggle" ); break; } } if( packet.length == 6 ) /* four-byte command */ { switch( packet[5] ) /* just look at the last byte of the command */ { case( 0x01 ): println( "Toggle Repeat" ); break; case( 0x04 ): println( "iPod Off" ); break; case( 0x08 ): println( "iPod On" ); break; case( 0x40 ): println( "Menu Button" ); break; case( 0x80 ): println( "OK/Select" ); break; } } if( packet.length == 7 ) /* five-byte command */ { switch( packet[6] ) /* just look at the last byte of the command */ { case( 0x01 ): println( "Scroll Up" ); break; case( 0x02 ): println( "Scroll Down" ); break; } } break; case( 0x03 ): /* request mode status */ break; case( 0x04 ): /* AiR Mode */ switch( packet[3] ) /* seems to only be a set length used for the commands in AiR */ { case( 0x00 ): println( "Unknown 0x00 0x00" ); break; case( 0x01 ): println( "Command Feedback: " + packet[4] + " " + packet[5] ); break; case( 0x02 ): println( "Ping Request? (Uncertain) " ); break; case( 0x03 ): println( "Ping Reply? (Uncertain) " ); break; case( 0x09 ): println( "Unknown 0x00 0x09" ); break; case( 0x0a ): println( "Unknown 0x00 0x0a: " + packet[4] ); break; case( 0x0b ): println( "Unknown 0x00 0x0b: " + packet[4] ); break; case( 0x0c ): println( "Unknown 0x00 0x0c" ); break; case( 0x0d ): println( "Unknown 0x00 0x0d" ); break; case( 0x12 ): println( "Get iPod Type / Size (Uncertain)" ); break; case( 0x13 ): println( "iPod Type Reply: " + packet[4] ); break; case( 0x14 ): println( "Get iPod Name" ); break; case( 0x15 ): println( "iPod Name Reply" ); break; case( 0x16 ): println( "Switch to Main Library Playlist" ); break; case( 0x17 ): println( "Switch to Item Defined By Number and Type: " + packet[4] + " " + packet[5] + " " + packet[6] + " " + packet[7] + " " + packet[8] ); break; case( 0x18 ): println( "Get Count of Type: " + packet[4] ); break; case( 0x19 ): println( "Count of Type Reply: " + packet[4] + " " + packet[5] + " " + packet[6] + " " + packet[7] ); break; case( 0x1a ): println( "Get Names for Range of Items" ); break; case( 0x1b ): println( "Name of Range of Items Reply" ); break; case( 0x1c ): println( "Get Time and Status" ); break; case( 0x1d ): println( "Time and Status Reply: " + packet[4] + " " + packet[5] + " " + packet[6] + " " + packet[7] + " " + packet[8] + " " + packet[9] + " " + packet[10] + " " + packet[11] + " " + packet[12] ); break; case( 0x1e ): println( "Get Current Position in Playlist" ); break; case( 0x1f ): println( "Current Position in Playlist: " + packet[4] + " " + packet[5] + " " + packet[6] + " " + packet[7] ); break; case( 0x20 ): println( "Get Title of Song Number" ); break; case( 0x21 ): println( "Title Return as Null Terminated String" ); break; case( 0x22 ): println( "Get Artist of Song Number" ); break; case( 0x23 ): println( "Artist Return as Null Terminated String" ); break; case( 0x24 ): println( "Get Album of Song Number" ); break; case( 0x25 ): println( "Album Return as Null Terminated String" ); break; case( 0x26 ): println( "Enable Polling Mode: " + packet[4] ); break; case( 0x27 ): println( "Polling Mode Reply (Elapsed Time on Current Song): " + packet[4] + " " + packet[5] + " " + packet[6] + " " + packet[7] ); break; case( 0x28 ): println( "Execute Playlist switch specified in command 0x00 0x17, and jump to specified songnumber in the playlist." ); break; case( 0x29 ): /* AiR Playback Control */ switch( packet[4] ) { case( 0x01 ): println( "AiR Playback Control: Play/Pause" ); break; case( 0x02 ): println( "AiR Playback Control: Stop" ); break; case( 0x03 ): println( "AiR Playback Control: Skip++" ); break; case( 0x04 ): println( "AiR Playback Control: Skip--" ); break; case( 0x05 ): println( "AiR Playback Control: FFwd" ); break; case( 0x06 ): println( "AiR Playback Control: FRwd" ); break; case( 0x07 ): println( "AiR Playback Control: StopFF/RW" ); break; } break; case( 0x2c ): println( "Get Shuffle Mode" ); break; case( 0x2d ): /* Shuffle Mode Replies */ switch( packet[4] ) { case( 0x00 ): println( "Shuffle Mode Is: Off" ); break; case( 0x01 ): println( "Shuffle Mode Is: Songs" ); break; case( 0x02 ): println( "Shuffle Mode Is: Albums" ); break; } break; case( 0x2e ): /* Set Shuffle Mode */ switch( packet[4] ) { case( 0x00 ): println( "Set Shuffle Mode: Off" ); break; case( 0x01 ): println( "Set Shuffle Mode: Songs" ); break; case( 0x02 ): println( "Set Shuffle Mode: Albums" ); break; } break; case( 0x2f ): println( "Get Repeat Mode" ); break; case( 0x30 ): /* Repeat Mode Replies */ switch( packet[4] ) { case( 0x00 ): println( "Repeat Mode Is: Off" ); break; case( 0x01 ): println( "Repeat Mode Is: One Song" ); break; case( 0x02 ): println( "Repeat Mode Is: All Songs" ); break; } break; case( 0x31 ): /* Set Repeat Mode */ switch( packet[4] ) { case( 0x00 ): println( "Set Repeat Mode: Off" ); break; case( 0x01 ): println( "Set Repeat Mode: One Song" ); break; case( 0x02 ): println( "Set Repeat Mode: All Songs" ); break; } break; case( 0x32 ): println( "Upload AiR Mode Picture" ); break; case( 0x33 ): println( "Get Max Screen Size For Picture (Uncertain)" ); break; case( 0x34 ): println( "Resolution for Picture (Uncertain)" ); break; case( 0x35 ): println( "Get Number of Songs In Current Playlist" ); break; case( 0x36 ): println( "Number of Songs in Current Playlist: " + packet[4] + " " + packet[5] + " " + packet[6] + " " + packet[7] ); break; case( 0x37 ): println( "Jump to Specified Song in Current Playlist" + packet[4] + " " + packet[5] + " " + packet[6] + " " + packet[7] ); break; case( 0x38 ): println( "Unknown" ); break; case( 0x39 ): println( "Unknown (Perhaps colordepth?)" ); break; } break; } } /* I don't think we need this for now... void keyPressed() { exit(); } */