// The next line makes it play the first track in the list for // a few seconds, then the next, looping at the end. // Comment it out to make track changes RFID-activated. //#define IPOD_TESTING // // iPod Stuff // #include AdvancedRemote advancedRemote; // The index in the current playlist of the currently-playing track. unsigned long currentTrackIndex = 0UL; // True once the playlist has been selected and switched to // at startup. Before this point changing tracks won't // actually play them. bool jumpedToPlaylist = false; // True if polling (which makes the iPod send back elapsed time // and track change information) has been enabled. bool pollingEnabled = false; // Enable polling, unless it's already been enabled. void enablePolling() { if (pollingEnabled) { // nothing to do } else { pollingEnabled = true; Serial.println("Turning on polling"); advancedRemote.setPollingMode(AdvancedRemote::POLLING_START); } } // Disable polling, unless it's already been disabled. void disablePolling() { if (pollingEnabled) { pollingEnabled = false; Serial.println("Turning off polling"); advancedRemote.setPollingMode(AdvancedRemote::POLLING_STOP); } else { // nothing to do } } // Begin the process of requesting information for // the specified track. // First, we'll ask for the title. // When we get that back we'll ask for the artist. // Lastly, when we get back the artist we'll ask // for the album. void getTrackInfo(unsigned long trackIndex) { // start by getting the title advancedRemote.getTitle(currentTrackIndex); // wait for title handler to get called. // From there we'll ask for the artist, etc. } // This function will be called back by AdvancedRemote when it // gets a response from the iPad to a command that we issue. void feedbackHandler(AdvancedRemote::Feedback feedback, byte cmd) { if (feedback != AdvancedRemote::FEEDBACK_SUCCESS) { Serial.println("Last command failed: feedback wasn't SUCCESS"); return; } switch (cmd) { case AdvancedRemote::CMD_SWITCH_TO_ITEM: Serial.println("Switched to an item."); // Presume we're getting this from a switch-to-playlist // request, so now start it playing. advancedRemote.executeSwitch(0xFFFFFFFF); break; case AdvancedRemote::CMD_SWITCH_TO_MAIN_LIBRARY_PLAYLIST: // as above, but for the the "main" library playlist. Serial.println("Switched to main library playlist."); // now start it playing advancedRemote.executeSwitch(0xFFFFFFFF); break; case AdvancedRemote::CMD_EXECUTE_SWITCH: Serial.println("Execute switched."); // jumping will work now jumpedToPlaylist = true; break; case AdvancedRemote::CMD_JUMP_TO_SONG_IN_CURRENT_PLAYLIST: Serial.println("Jumped to a song"); if (pollingEnabled) { // polling will get the track info for us. } else { // we'd better ask for the track info getTrackInfo(currentTrackIndex); // turn on polling; it will get the elapsed time // and track info for subsequent tracks. enablePolling(); } break; case AdvancedRemote::CMD_PLAYBACK_CONTROL: // play/pause etc Serial.println("playback control complete"); break; case AdvancedRemote::CMD_POLLING_MODE: Serial.println("polling mode change complete"); // from experimentation it starts playing automatically break; default: Serial.print("got feedback we didn't expect: 0x"); Serial.println(cmd, HEX); break; } } // Called when the iPod returns the name of a track to us. void titleHandler(const char *name) { Serial.print("Title: "); Serial.println(name); Serial1.print(12, BYTE); //clears screen delay(100); Serial1.print("Track: "); Serial1.print(name); //name of song being played on LCD display advancedRemote.getArtist(currentTrackIndex); // wait for artist handler to get called } // Called when the iPod returns the name of an artist to us. void artistHandler(const char *name) { Serial.print("Artist: "); Serial.println(name); Serial1.print('\r', BYTE); Serial1.print("Artist: "); Serial1.println(name); // name of artist of song being played on LCD display advancedRemote.getAlbum(currentTrackIndex); // wait for album handler to get called } // Called when the iPod returns the name of an album to us. void albumHandler(const char *name) { Serial.print("Album: "); Serial.println(name); } // Helper function to convert elapsed time in milliseconds // to "1m 2s" format. void printTime(const unsigned long ms) { const unsigned long totalSecs = ms / 1000; const unsigned int mins = totalSecs / 60; const unsigned int partialSecs = totalSecs % 60; Serial.print(mins, DEC); Serial.print("m "); Serial.print(partialSecs, DEC); Serial.println("s"); } // Called when the iPod returns elapsed-time information to us, // which should be every 500ms, or when a track ends and the // next begins. void pollingHandler(AdvancedRemote::PollingCommand command, unsigned long playlistPositionOrElapsedTimeMs) { Serial.print("Poll: "); switch (command) { case AdvancedRemote::POLLING_TRACK_CHANGE: Serial.print("track change to: "); Serial.println(playlistPositionOrElapsedTimeMs, DEC); // we just naturally went to the next track, // so update the song we're at and get its details currentTrackIndex = playlistPositionOrElapsedTimeMs; getTrackInfo(currentTrackIndex); break; case AdvancedRemote::POLLING_ELAPSED_TIME: Serial.print("elapsed time: "); printTime(playlistPositionOrElapsedTimeMs); break; default: Serial.print("unknown: "); Serial.println(playlistPositionOrElapsedTimeMs, DEC); break; } } // // RFID Stuff // const uint8_t RFID_ENABLE_PIN = 2; const unsigned long RFID_DELAY_BETWEEN_READS_MS = 10000UL; //10 sec const int HEADER_BYTE = 10; const int STOP_BYTE = 13; const size_t TAG_LENGTH = 10; const int REQUIRED_CONSECUTIVE_MATCHES = 3; struct Track { const char *code; // The RFID tag's code for this track const char *title1; // Title 1 message to display for this track const char *title2; // Title 2 message to display for this track long index; // index of this track }; Track tracks[] = { // RFID Code, Title (20 chars max), 2ndLine (20 chars max), Playlist position, (of the first track on the album) { "0100F24EC7", "ACE OF BASE SINGLES", "OF THE 90's", 2 }, //1 { "0100F348EE", "FLOOR 3 FILLERS", "CLUB CLASSICS", 18 }, //2 { "0100F35884", "100 HITS", "DANCE CD4", 37 } //3 no comma at end of this last line // Put as many in here as you like, // Note that the last entry shouldn't have a comma after it. }; const size_t NUM_TRACKS = sizeof(tracks) / sizeof(tracks[0]); void rfidActivate() { Serial.println("Activating RFID"); digitalWrite(RFID_ENABLE_PIN, LOW); } void rfidDeactivate() { Serial.println("Deactivating RFID"); digitalWrite(RFID_ENABLE_PIN, HIGH); } int rfidReadByte() { // wait for a byte to become available while (Serial2.available() == 0) { // Delay a little bit just so we don't sit and spin. delay(100); } // now return the first available byte, // since we know from the avoid that at least one is // now waiting for us return Serial2.read(); } void rfidReadHeader() { Serial.println("Waiting for header"); while (rfidReadByte() != HEADER_BYTE) { // rfidReadByte does all the waiting // for us, so we can just go round again } Serial.println("Header found"); } // buffer must be at least TAG_LENGTH+1 bytes long bool rfidReadTagOnce(char *buffer) { rfidReadHeader(); int bytesRead = 0; for (; bytesRead < TAG_LENGTH; bytesRead++) { buffer[bytesRead] = rfidReadByte(); if ((buffer[bytesRead] == HEADER_BYTE) || (buffer[bytesRead] == STOP_BYTE)) { Serial.println("Giving up on read due to HEADER/STOP byte."); break; } } // null-terminate the string buffer[bytesRead] = '\0'; // we'll get here either with a <10 length // string in buffer or a 10-length // string. return strlen(buffer) == TAG_LENGTH; } bool rfidReadTag(char *buffer) { char compareBuffer[TAG_LENGTH+1]; compareBuffer[0] = '\0'; if (!rfidReadTagOnce(buffer)) { // failed on first attempt, so give up return false; } // remember the tag we just read strcpy(compareBuffer, buffer); // start loop at 1 since we've read once above for (int i = 1; i < REQUIRED_CONSECUTIVE_MATCHES; i++) { if (rfidReadTagOnce(buffer) && (strcmp(compareBuffer, buffer) == 0)) { // read a tag and it matches, so go around again } else { // failed to read or it didn't match; // either way we give up return false; } } // yay, we read the same code REQUIRED_CONSECUTIVE_MATCHES // times in a row return true; } void jumpToSongAtTrackIndex(long index) { Serial.print("Jumping to track at index "); Serial.println(index, DEC); advancedRemote.jumpToSongInCurrentPlaylist(index); } void switchToTrackForCode(const char *code) { Serial.print("Looking for track with code "); Serial.println(code); for (size_t i = 0; i < NUM_TRACKS; ++i) { if (strcmp(tracks[i].code, code) == 0) { Serial1.print(12, BYTE); Serial1.print("Playing........."); Serial1.print('\r', BYTE); Serial1.print(tracks[i].title1); delay(100); Serial1.print('\r', BYTE); Serial1.print(tracks[i].title2); delay(100); Serial1.print('\r', BYTE); currentTrackIndex = tracks[i].index; jumpToSongAtTrackIndex(currentTrackIndex); return; } } Serial.print("Failed to find track matching code."); } void rfidCheck() { static char lastCode[TAG_LENGTH + 1]; char code[TAG_LENGTH + 1]; rfidActivate(); if (rfidReadTag(code) && (strcmp(code, lastCode) != 0)) { switchToTrackForCode(code); // remember this code for next time strcpy(lastCode, code); } rfidDeactivate(); } // // Standard sketch stuff // void setup() { // Debug Serial.begin(9600); // Display Serial1.begin(9600); Serial1.print(19, BYTE); //backlight on delay(100); Serial1.print(12, BYTE); //clears screen delay(100); Serial1.print(4, BYTE); //puts off flashing cursor delay(100); // RFID Serial2.begin(2400); // RFID reader SOUT pin connected to Serial RX pin at 2400bps pinMode(RFID_ENABLE_PIN, OUTPUT); // Set digital pin 2 as OUTPUT to connect it to the RFID /ENABLE pin rfidDeactivate(); Serial1.print("READY........"); Serial1.print('\r', BYTE); Serial1.print("Place card in tray"); delay(100); Serial1.print('\r', BYTE); Serial1.print("and press button."); delay (2000); // iPod advancedRemote.setSerial(Serial3); // Register callback functions for the things we're going to get callbacks for advancedRemote.setFeedbackHandler(feedbackHandler); advancedRemote.setPollingHandler(pollingHandler); advancedRemote.setTitleHandler(titleHandler); advancedRemote.setArtistHandler(artistHandler); advancedRemote.setAlbumHandler(albumHandler); // Let the library set itself up, now we've done our configuration of it advancedRemote.setup(); // Start in advanced remote mode advancedRemote.enable(); // Switch to the playlist we wan't to use. // For now we rely on that being the first user-specified playlist, // playlist number 1. (0 is always the main library playlist.) //advancedRemote.switchToMainLibraryPlaylist(); advancedRemote.switchToItem(AdvancedRemote::ITEM_PLAYLIST, 1); } // Used to remember we when last asked the RFID reader // to look for a tag. unsigned long timeOfLastRead = 0UL; void loop() { // Let the AdvancedRemote do its thing. // (basically, respond to data coming back from the iPod) advancedRemote.loop(); // See if it's time to try a read. unsigned long now = millis(); if (jumpedToPlaylist && ((timeOfLastRead == 0) || ((now - timeOfLastRead) >= RFID_DELAY_BETWEEN_READS_MS))) { #if defined(IPOD_TESTING) // just pick a fake track index static size_t i = 0UL; i = ++i % NUM_TRACKS; currentTrackIndex = tracks[i].index; jumpToSongAtTrackIndex(currentTrackIndex); #else // see if RFID tag wants us to change tracks rfidCheck(); #endif // Remember the end of this read for delaying of the next one timeOfLastRead = millis(); } }