/** * Arduino Library for JQ6500 MP3 Module * * Copyright (C) 2014 James Sleeman, * * Permission is hereby granted, free of charge, to any person obtaining a * copy of this software and associated documentation files (the "Software"), * to deal in the Software without restriction, including without limitation * the rights to use, copy, modify, merge, publish, distribute, sublicense, * and/or sell copies of the Software, and to permit persons to whom the * Software is furnished to do so, subject to the following conditions: * * The above copyright notice and this permission notice shall be included in * all copies or substantial portions of the Software. * * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE * AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, * OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN * THE SOFTWARE. * * @author James Sleeman, http://sparks.gogo.co.nz/ * @license MIT License * @file */ #include #include "JQ6500_Serial.h" #include void JQ6500_Serial::play() { this->sendCommand(0x0D); } void JQ6500_Serial::restart() { byte oldVolume = this->getVolume(); this->setVolume(0); this->next(); this->pause(); this->setVolume(oldVolume); this->prev(); } void JQ6500_Serial::pause() { this->sendCommand(0x0E); } void JQ6500_Serial::next() { this->sendCommand(0x01); } void JQ6500_Serial::prev() { this->sendCommand(0x02); } void JQ6500_Serial::playFileByIndexNumber(unsigned int fileNumber) { this->sendCommand(0x03, (fileNumber>>8) & 0xFF, fileNumber & (byte)0xFF); } void JQ6500_Serial::nextFolder() { this->sendCommand(0x0F, 0x01); } void JQ6500_Serial::prevFolder() { this->sendCommand(0x0F, 0x00); } void JQ6500_Serial::playFileNumberInFolderNumber(unsigned int folderNumber, unsigned int fileNumber) { this->sendCommand(0x12, folderNumber & 0xFF, fileNumber & 0xFF); } void JQ6500_Serial::volumeUp() { this->sendCommand(0x04); } void JQ6500_Serial::volumeDn() { this->sendCommand(0x05); } void JQ6500_Serial::setVolume(byte volumeFrom0To30) { this->sendCommand(0x06, volumeFrom0To30); } void JQ6500_Serial::setEqualizer(byte equalizerMode) { this->sendCommand(0x07, equalizerMode); } void JQ6500_Serial::setLoopMode(byte loopMode) { this->sendCommand(0x11, loopMode); } void JQ6500_Serial::setSource(byte source) { this->sendCommand(0x09, source); } void JQ6500_Serial::sleep() { this->sendCommand(0x0A); } void JQ6500_Serial::reset() { this->sendCommand(0x0C); delay(500); // We need some time for the reset to happen } byte JQ6500_Serial::getStatus() { byte statTotal = 0; byte stat = 0; do { statTotal = 0; for(byte x = 0; x < MP3_STATUS_CHECKS_IN_AGREEMENT; x++) { stat = this->sendCommandWithUnsignedIntResponse(0x42); if(stat == 0) return 0; // STOP is fairly reliable statTotal += stat; } } while (statTotal != 1 * MP3_STATUS_CHECKS_IN_AGREEMENT && statTotal != 2 * MP3_STATUS_CHECKS_IN_AGREEMENT); return statTotal / MP3_STATUS_CHECKS_IN_AGREEMENT; } byte JQ6500_Serial::getVolume() { return this->sendCommandWithUnsignedIntResponse(0x43); } byte JQ6500_Serial::getEqualizer() { return this->sendCommandWithUnsignedIntResponse(0x44); } byte JQ6500_Serial::getLoopMode() { return this->sendCommandWithUnsignedIntResponse(0x45); } unsigned int JQ6500_Serial::getVersion() { return this->sendCommandWithUnsignedIntResponse(0x46); } unsigned int JQ6500_Serial::countFiles(byte source) { if(source == MP3_SRC_SDCARD) { return this->sendCommandWithUnsignedIntResponse(0x47); } else if (source == MP3_SRC_BUILTIN) { return this->sendCommandWithUnsignedIntResponse(0x49); } return 0; } unsigned int JQ6500_Serial::countFolders(byte source) { if(source == MP3_SRC_SDCARD) { return this->sendCommandWithUnsignedIntResponse(0x53); } return 0; } unsigned int JQ6500_Serial::currentFileIndexNumber(byte source) { if(source == MP3_SRC_SDCARD) { return this->sendCommandWithUnsignedIntResponse(0x4B); } else if (source == MP3_SRC_BUILTIN) { return this->sendCommandWithUnsignedIntResponse(0x4D)+1; // CRAZY! } return 0; } unsigned int JQ6500_Serial::currentFilePositionInSeconds() { return this->sendCommandWithUnsignedIntResponse(0x50); } unsigned int JQ6500_Serial::currentFileLengthInSeconds() { return this->sendCommandWithUnsignedIntResponse(0x51); } void JQ6500_Serial::currentFileName(char *buffer, unsigned int bufferLength) { this->sendCommand(0x52, 0, 0, buffer, bufferLength); } // Used for the status commands, they mostly return an 8 to 16 bit integer // and take no arguments unsigned int JQ6500_Serial::sendCommandWithUnsignedIntResponse(byte command) { char buffer[5]; this->sendCommand(command, 0, 0, buffer, sizeof(buffer)); return (unsigned int) strtoul(buffer, NULL, 16); } void JQ6500_Serial::sendCommand(byte command) { this->sendCommand(command, 0, 0, 0, 0); } void JQ6500_Serial::sendCommand(byte command, byte arg1) { this->sendCommand(command, arg1, 0, 0, 0); } void JQ6500_Serial::sendCommand(byte command, byte arg1, byte arg2) { this->sendCommand(command, arg1, arg2, 0, 0); } void JQ6500_Serial::sendCommand(byte command, byte arg1, byte arg2, char *responseBuffer, unsigned int bufferLength) { // Command structure // [7E][number bytes following including command and terminator][command byte][?arg1][?arg2][EF] // Most commands do not have arguments byte args = 0; // These ones do switch(command) { case 0x03: args = 2; break; case 0x06: args = 1; break; case 0x07: args = 1; break; case 0x09: args = 1; break; case 0x0F: args = 1; break; case 0x11: args = 1; break; case 0x12: args = 2; break; } #if MP3_DEBUG char buf[4]; Serial.println(); Serial.print("7E "); itoa(2+args, buf, 16); Serial.print(buf); Serial.print(" "); memset(buf, 0, sizeof(buf)); itoa(command, buf, 16); Serial.print(buf); Serial.print(" "); memset(buf, 0, sizeof(buf)); if(args>=1) itoa(arg1, buf, 16); Serial.print(buf); Serial.print(" "); memset(buf, 0, sizeof(buf)); if(args>=2) itoa(arg2, buf, 16); Serial.print(buf); Serial.print(" "); memset(buf, 0, sizeof(buf)); Serial.print("EF"); #endif // The device appears to send some sort of status information (namely "STOP" when it stops playing) // just discard this right before we send the command while(this->waitUntilAvailable(10)) this->read(); this->write((byte)0x7E); this->write(2+args); this->write(command); if(args>=1) this->write(arg1); if(args==2) this->write(arg2); this->write((byte)0xEF); unsigned int i = 0; char j = 0; if(responseBuffer && bufferLength) { memset(responseBuffer, 0, bufferLength); } // Allow some time for the device to process what we did and // respond, up to 1 second, but typically only a few ms. this->waitUntilAvailable(1000); #if MP3_DEBUG Serial.print(" ==> ["); #endif while(this->waitUntilAvailable(150)) { j = (char)this->read(); #if MP3_DEBUG Serial.print(j); #endif if(responseBuffer && (i<(bufferLength-1))) { responseBuffer[i++] = j; } } #if MP3_DEBUG Serial.print("]"); Serial.println(); #endif } // as readBytes with terminator character // terminates if length characters have been read, timeout, or if the terminator character detected // returns the number of characters placed in the buffer (0 means no valid data found) size_t JQ6500_Serial::readBytesUntilAndIncluding(char terminator, char *buffer, size_t length, byte maxOneLineOnly) { if (length < 1) return 0; size_t index = 0; while (index < length) { int c = timedRead(); if (c < 0) break; *buffer++ = (char)c; index++; if(c == terminator) break; if(maxOneLineOnly && ( c == '\n') ) break; } return index; // return number of characters, not including null terminator } // Waits until data becomes available, or a timeout occurs int JQ6500_Serial::waitUntilAvailable(unsigned long maxWaitTime) { unsigned long startTime; int c = 0; startTime = millis(); do { c = this->available(); if (c) break; } while(millis() - startTime < maxWaitTime); return c; }