#include "TEF6686.h" #include #include #include #include "SPIFFS.h" #include "constants.h" unsigned long rdstimer = 0; unsigned long bitStartTime = 0; bool lastBitState = false; uint16_t TEF6686::getBlockA(void) { uint16_t blockA, dummy; devTEF_Radio_Get_RDS_Status(&dummy, &blockA, &dummy, &dummy, &dummy, &dummy); return blockA; } void TEF6686::TestAFEON() { uint16_t status; uint16_t dummy1; uint16_t dummy2; int8_t dummy3; int16_t aflevel; uint16_t afusn; uint16_t afwam; int16_t afoffset; byte timing; if (af_counter != 0) { setMute(); for (int x = 0; x < af_counter; x++) { timing = 0; devTEF_Set_Cmd(TEF_FM, Cmd_Tune_To, 7, 3, af[x].frequency); while (timing == 0 && !bitRead(timing, 15)) { devTEF_Radio_Get_Quality_Status(&status, &aflevel, &afusn, &afwam, &afoffset, &dummy1, &dummy2, &dummy3); timing = lowByte(status); } if (afoffset > -125 || afoffset < 125) { devTEF_Set_Cmd(TEF_FM, Cmd_Tune_To, 7, 4, af[x].frequency); delay(187); devTEF_Radio_Get_RDS_Status(&rds.rdsStat, &rds.rdsA, &rds.rdsB, &rds.rdsC, &rds.rdsD, &rds.rdsErr); if (rds.rdsStat & (1 << 9)) { if ((afmethodB && rds.afreg ? (((rds.rdsA >> 8) & 0xF) > 2 && ((rds.correctPI >> 8) & 0xF) > 2 && ((rds.rdsA >> 12) & 0xF) == ((rds.correctPI >> 12) & 0xF) && (rds.rdsA & 0xFF) == (rds.correctPI & 0xFF)) || rds.rdsA == rds.correctPI : rds.rdsA == rds.correctPI)) { af[x].checked = true; af[x].afvalid = true; } else { af[x].checked = false; af[x].afvalid = false; } } else { af[x].checked = false; } } } } devTEF_Set_Cmd(TEF_FM, Cmd_Tune_To, 7, 4, currentfreq); setUnMute(); } uint16_t TEF6686::TestAF() { if (af_counter != 0) { uint16_t status; uint16_t dummy1; uint16_t dummy2; int8_t dummy3; int16_t aflevel; uint16_t afusn; uint16_t afwam; int16_t afoffset; int16_t currentlevel; uint16_t currentusn; uint16_t currentwam; int16_t currentoffset; byte timing; devTEF_Radio_Get_Quality_Status(&status, ¤tlevel, ¤tusn, ¤twam, ¤toffset, &dummy1, &dummy2, &dummy3); devTEF_Radio_Get_RDS_Status(&rds.rdsStat, &rds.rdsA, &rds.rdsB, &rds.rdsC, &rds.rdsD, &rds.rdsErr); for (int x = 0; x < af_counter; x++) { timing = 0; devTEF_Set_Cmd(TEF_FM, Cmd_Tune_To, 7, 3, af[x].frequency); while (timing == 0 && !bitRead(timing, 15)) { devTEF_Radio_Get_Quality_Status(&status, &aflevel, &afusn, &afwam, &afoffset, &dummy1, &dummy2, &dummy3); timing = lowByte(status); } af[x].score = aflevel - afusn - afwam; if (afoffset < -125 || afoffset > 125) af[x].score = -32767; } int16_t highestValue = af[0].score; int highestIndex = 0; for (int i = 1; i < af_counter; i++) { if (af[i].score > highestValue) { highestValue = af[i].score; highestIndex = i; } } if (af_counter != 0 && af[highestIndex].afvalid && af[highestIndex].score > (currentlevel - currentusn - currentwam) && (af[highestIndex].score - (currentlevel - currentusn - currentwam)) >= 70) { devTEF_Set_Cmd(TEF_FM, Cmd_Tune_To, 7, 4, af[highestIndex].frequency); delay(187); devTEF_Radio_Get_RDS_Status(&rds.rdsStat, &rds.rdsA, &rds.rdsB, &rds.rdsC, &rds.rdsD, &rds.rdsErr); if (rds.rdsStat & (1 << 9)) { if ((afmethodB && rds.afreg ? (((rds.rdsA >> 8) & 0xF) > 2 && ((rds.correctPI >> 8) & 0xF) > 2 && ((rds.rdsA >> 12) & 0xF) == ((rds.correctPI >> 12) & 0xF) && (rds.rdsA & 0xFF) == (rds.correctPI & 0xFF)) || rds.rdsA == rds.correctPI : rds.rdsA == rds.correctPI)) { currentfreq = af[highestIndex].frequency; for (byte y = 0; y < 50; y++) { af[y].frequency = 0; af[y].score = -32767; af[y].afvalid = true; af[y].checked = false; } af_counter = 0; } else { af[highestIndex].afvalid = false; devTEF_Set_Cmd(TEF_FM, Cmd_Tune_To, 7, 4, currentfreq); } } else { devTEF_Set_Cmd(TEF_FM, Cmd_Tune_To, 7, 4, currentfreq); } } } return currentfreq; } void TEF6686::init(byte TEF) { uint8_t bootstatus; int xtalADC = 0; Tuner_I2C_Init(); devTEF_APPL_Get_Operation_Status(&bootstatus); if (bootstatus == 0) { Tuner_Patch(TEF); delay(50); xtalADC = analogRead(15); if (xtalADC < XTAL_0V_ADC + XTAL_ADC_TOL) { Tuner_Init(tuner_init_tab9216); log_d("TEF668X XTAL : 9.216M"); } else if (xtalADC > XTAL_1V_ADC - XTAL_ADC_TOL && xtalADC < XTAL_1V_ADC + XTAL_ADC_TOL) { Tuner_Init(tuner_init_tab12000); log_d("TEF668X XTAL : 12M"); } else if (xtalADC > XTAL_2V_ADC - XTAL_ADC_TOL && xtalADC < XTAL_2V_ADC + XTAL_ADC_TOL) { Tuner_Init(tuner_init_tab55000); log_d("TEF668X XTAL : 55M"); } else { Tuner_Init(tuner_init_tab4000); log_d("TEF668X XTAL : 4M"); } power(1); Tuner_Init(tuner_init_tab); } } void TEF6686::getIdentification(uint16_t *device, uint16_t *hw_version, uint16_t *sw_version) { uint8_t buf[6]; uint16_t r = devTEF_Get_Cmd(TEF_APPL, Cmd_Get_Identification, buf, sizeof(buf)); *device = Convert8bto16b(buf); *hw_version = Convert8bto16b(buf + 2); *sw_version = Convert8bto16b(buf + 4); } void TEF6686::power(bool mode) { devTEF_Set_Cmd(TEF_APPL, Cmd_Set_OperationMode, 5, mode); if (mode == 0) devTEF_Set_Cmd(TEF_FM, Cmd_Tune_To, 7, 1, 10000); } void TEF6686::extendBW(bool yesno) { devTEF_Set_Cmd(TEF_FM, Cmd_Set_Bandwidth_Options, 5, (yesno ? 400 : 950)); } void TEF6686::SetFreq(uint16_t frequency) { devTEF_Set_Cmd(TEF_FM, Cmd_Tune_To, 7, 4, frequency); currentfreq = ((frequency + 5) / 10) * 10; currentfreq2 = frequency; } void TEF6686::SetFreqAM(uint16_t frequency) { devTEF_Set_Cmd(TEF_AM, Cmd_Tune_To, 7, 1, frequency); } void TEF6686::SetFreqAIR(uint16_t frequency) { devTEF_Set_Cmd(TEF_AM, Cmd_Tune_To, 7, 1, 10700); } void TEF6686::setOffset(int8_t offset) { devTEF_Set_Cmd(TEF_FM, Cmd_Set_LevelOffset, 5, (offset * 10) - 70); } void TEF6686::setAMOffset(int8_t offset) { devTEF_Set_Cmd(TEF_AM, Cmd_Set_LevelOffset, 5, (offset * 10) - 70); } void TEF6686::setFMBandw(uint16_t bandwidth) { devTEF_Set_Cmd(TEF_FM, Cmd_Set_Bandwidth, 7, 0, bandwidth); } void TEF6686::setAMBandw(uint16_t bandwidth) { devTEF_Set_Cmd(TEF_AM, Cmd_Set_Bandwidth, 7, 0, bandwidth * 10); } void TEF6686::setAMCoChannel(uint16_t start, uint8_t level) { uint8_t mode = 1; if(start == 0) mode = 0; devTEF_Set_Cmd(TEF_AM, Cmd_Set_CoChannelDet, 11, mode, 2, start * 10, 1000, level); } void TEF6686::setSoftmuteAM(uint8_t mode) { devTEF_Set_Cmd(TEF_AM, Cmd_Set_Softmute_Max, 7, mode, 250); } void TEF6686::setSoftmuteFM(uint8_t mode) { devTEF_Set_Cmd(TEF_FM, Cmd_Set_Softmute_Max, 7, mode, 200); } void TEF6686::setAMNoiseBlanker(uint16_t start) { if (start == 0) devTEF_Radio_Set_Noiseblanker_AM(0, 1000); else devTEF_Radio_Set_Noiseblanker_AM(1, start * 10); } void TEF6686::setAMAttenuation(uint16_t start) { devTEF_Set_Cmd(TEF_AM, Cmd_Set_Antenna, 5, start * 10); } void TEF6686::setFMABandw() { devTEF_Set_Cmd(TEF_FM, Cmd_Set_Bandwidth, 7, 1, 3110); } void TEF6686::setiMS(bool mph) { devTEF_Set_Cmd(TEF_FM, Cmd_Set_MphSuppression, 5, mph); } void TEF6686::setEQ(bool eq) { devTEF_Set_Cmd(TEF_FM, Cmd_Set_ChannelEqualizer, 5, eq); } bool TEF6686::getStereoStatus() { uint16_t status; bool stereo = 0; if (1 == devTEF_Radio_Get_Stereo_Status(&status)) stereo = ((status >> 15) & 1) ? 1 : 0; return stereo; } void TEF6686::setMono(bool mono) { devTEF_Set_Cmd(TEF_FM, Cmd_Set_Stereo_Min, 7, mono ? 2 : 0); } void TEF6686::setVolume(int8_t volume) { devTEF_Set_Cmd(TEF_AUDIO, Cmd_Set_Volume, 5, volume * 10); } void TEF6686::setMute() { mute = true; if (mpxmode) devTEF_Set_Cmd(TEF_FM, Cmd_Set_Specials, 5, 0); devTEF_Set_Cmd(TEF_AUDIO, Cmd_Set_Mute, 5, 1); } void TEF6686::setUnMute() { mute = false; if (mpxmode) devTEF_Set_Cmd(TEF_FM, Cmd_Set_Specials, 5, 1); devTEF_Set_Cmd(TEF_AUDIO, Cmd_Set_Mute, 5, 0); } void TEF6686::setAGC(uint8_t agc) { devTEF_Set_Cmd(TEF_FM, Cmd_Set_RFAGC, 7, agc * 10, 0); } void TEF6686::setAMAGC(uint8_t agc) { devTEF_Set_Cmd(TEF_AM, Cmd_Set_RFAGC, 7, agc * 10, 0); } void TEF6686::setDeemphasis(uint8_t timeconstant) { switch (timeconstant) { case 1: devTEF_Set_Cmd(TEF_FM, Cmd_Set_Deemphasis, 5, 500); break; case 2: devTEF_Set_Cmd(TEF_FM, Cmd_Set_Deemphasis, 5, 750); break; default: devTEF_Set_Cmd(TEF_FM, Cmd_Set_Deemphasis, 5, 0); break; } } void TEF6686::setAudio(uint8_t audio) { devTEF_Set_Cmd(TEF_FM, Cmd_Set_Specials, 5, audio); mpxmode = (audio != 0); } void TEF6686::setFMSI(uint8_t mode) { if(mode > 2) mode = 2; if(mode < 1) mode = 1; devTEF_Set_Cmd(TEF_FM, Cmd_Set_StereoImprovement, 5, mode-1); } void TEF6686::setFMSI_Time(uint16_t attack, uint16_t decay) { devTEF_Set_Cmd(TEF_FM, Cmd_Set_StBandBlend_Time, 7, attack, decay); } void TEF6686::setFMSI_Gain(uint16_t band1, uint16_t band2, uint16_t band3, uint16_t band4) { devTEF_Set_Cmd(TEF_FM, Cmd_Set_StBandBlend_Gain, 11, band1 * 10, band2 * 10, band3 * 10, band4 * 10); } void TEF6686::setFMSI_Bias(int16_t band1, int16_t band2, int16_t band3, int16_t band4) { devTEF_Set_Cmd(TEF_FM, Cmd_Set_StBandBlend_Bias, 11, band1 - 250, band2 - 250, band3 - 250, band4 - 250); } void TEF6686::setFMNoiseBlanker(uint16_t start) { devTEF_Set_Cmd(TEF_FM, Cmd_Set_NoiseBlanker, 7, (start == 0) ? 0 : 1, (start == 0) ? 1000 : (start * 10)); } void TEF6686::setStereoLevel(uint8_t start) { if (start == 0) { devTEF_Set_Cmd(TEF_FM, Cmd_Set_Stereo_Level, 9, 0, start * 10, 60); devTEF_Set_Cmd(TEF_FM, Cmd_Set_Stereo_Noise, 9, 0, 240, 200); devTEF_Set_Cmd(TEF_FM, Cmd_Set_Stereo_Mph, 9, 0, 240, 200); } else { devTEF_Set_Cmd(TEF_FM, Cmd_Set_Stereo_Level, 9, 3, start * 10, 60); devTEF_Set_Cmd(TEF_FM, Cmd_Set_Stereo_Noise, 9, 3, 240, 200); devTEF_Set_Cmd(TEF_FM, Cmd_Set_Stereo_Mph, 9, 3, 240, 200); } } void TEF6686::setHighCutOffset(uint8_t start) { if (start == 0) { devTEF_Set_Cmd(TEF_FM, Cmd_Set_Highcut_Level, 9, 0, start * 10, 300); devTEF_Set_Cmd(TEF_FM, Cmd_Set_Highcut_Noise, 9, 0, 360, 300); devTEF_Set_Cmd(TEF_FM, Cmd_Set_Highcut_Mph, 9, 0, 360, 300); } else { devTEF_Set_Cmd(TEF_FM, Cmd_Set_Highcut_Level, 9, 3, start * 10, 300); devTEF_Set_Cmd(TEF_FM, Cmd_Set_Highcut_Noise, 9, 3, 360, 300); devTEF_Set_Cmd(TEF_FM, Cmd_Set_Highcut_Mph, 9, 3, 360, 300); } } void TEF6686::setHighCutLevel(uint16_t limit) { devTEF_Set_Cmd(TEF_FM, Cmd_Set_Highcut_Max, 7, 1, limit * 100); } void TEF6686::setStHiBlendLevel(uint16_t limit) { devTEF_Set_Cmd(TEF_FM, Cmd_Set_StHiBlend_Max, 7, 1, limit * 100); } void TEF6686::setStHiBlendOffset(uint8_t start) { if (start == 0) { devTEF_Set_Cmd(TEF_FM, Cmd_Set_StHiBlend_Level, 9, 0, start * 10, 300); devTEF_Set_Cmd(TEF_FM, Cmd_Set_StHiBlend_Noise, 9, 0, 360, 300); devTEF_Set_Cmd(TEF_FM, Cmd_Set_StHiBlend_Mph, 9, 0, 360, 300); } else { devTEF_Set_Cmd(TEF_FM, Cmd_Set_StHiBlend_Level, 9, 3, start * 10, 300); devTEF_Set_Cmd(TEF_FM, Cmd_Set_StHiBlend_Noise, 9, 3, 360, 300); devTEF_Set_Cmd(TEF_FM, Cmd_Set_StHiBlend_Mph, 9, 3, 360, 300); } } void TEF6686::getStatus(int16_t &level, uint16_t &USN, uint16_t &WAM, int16_t &offset, uint16_t &bandwidth, uint16_t &modulation, int8_t &snr) { uint16_t status; devTEF_Radio_Get_Quality_Status(&status, &level, &USN, &WAM, &offset, &bandwidth, &modulation, &snr); } void TEF6686::getStatusAM(int16_t &level, uint16_t &noise, uint16_t &cochannel, int16_t &offset, uint16_t &bandwidth, uint16_t &modulation, int8_t &snr) { devTEF_Radio_Get_Quality_Status_AM(&level, &noise, &cochannel, &offset, &bandwidth, &modulation, &snr); } void TEF6686::readRDS(byte showrdserrors) { uint8_t offset; if (rds.filter && ps_process) { devTEF_Radio_Get_RDS_Status(&rds.rdsStat, &rds.rdsA, &rds.rdsB, &rds.rdsC, &rds.rdsD, &rds.rdsErr); } else { if (millis() >= rdstimer + 87) { rdstimer += 87; devTEF_Radio_Get_RDS_Data(&rds.rdsStat, &rds.rdsA, &rds.rdsB, &rds.rdsC, &rds.rdsD, &rds.rdsErr); if ((rds.rdsStat & (1 << 14))) { for (int i = 0; i < 22; i++) devTEF_Radio_Get_RDS_Data(&rds.rdsStat, &rds.rdsA, &rds.rdsB, &rds.rdsC, &rds.rdsD, &rds.rdsErr); } } } if (bitRead(rds.rdsStat, 9)) { rds.hasRDS = true; // RDS decoder synchronized and data available bitStartTime = 0; } else { if (bitStartTime == 0) { bitStartTime = millis(); } else if (millis() - bitStartTime >= 87) { rds.hasRDS = false; } } rdsAerrorThreshold = (((rds.rdsErr >> 14) & 0x03) > showrdserrors); rdsBerrorThreshold = (((rds.rdsErr >> 12) & 0x03) > showrdserrors); rdsCerrorThreshold = (((rds.rdsErr >> 10) & 0x03) > showrdserrors); rdsDerrorThreshold = (((rds.rdsErr >> 8) & 0x03) > showrdserrors); if (bitRead(rds.rdsStat, 9) && (rds.rdsA != previous_rdsA || rds.rdsB != previous_rdsB || rds.rdsC != previous_rdsC || rds.rdsD != previous_rdsD)) { rds.rdsAerror = (((rds.rdsErr >> 14) & 0x03) > 1); // We have all data to decode... let's go... rds.rdsBerror = (((rds.rdsErr >> 12) & 0x03) > 1); rds.rdsCerror = (((rds.rdsErr >> 10) & 0x03) > 1); rds.rdsDerror = (((rds.rdsErr >> 8) & 0x03) > 1); //PI decoder if (!rdsAerrorThreshold && afreset) { rds.correctPI = rds.rdsA; afreset = false; } if (((!rdsAerrorThreshold && !rdsBerrorThreshold && !rdsCerrorThreshold && !rdsDerrorThreshold) || (rds.pierrors && !errorfreepi))) { if (piold == 0 || rds.rdsA != piold) { piold = rds.rdsA; rds.picode[0] = (rds.rdsA >> 12) & 0xF; rds.picode[1] = (rds.rdsA >> 8) & 0xF; rds.picode[2] = (rds.rdsA >> 4) & 0xF; rds.picode[3] = rds.rdsA & 0xF; for (int i = 0; i < 4; i++) { if (rds.picode[i] < 10) { rds.picode[i] += '0'; } else { rds.picode[i] += 'A' - 10; } } } if (ps_process && ((rds.rdsErr >> 14) & 0x03) == 0) { rds.picode[4] = ' '; rds.picode[5] = ' '; errorfreepi = true; } if (!errorfreepi) { if (((rds.rdsErr >> 14) & 0x03) > 1) rds.picode[5] = '?'; else rds.picode[5] = ' '; if (((rds.rdsErr >> 14) & 0x03) > 0) rds.picode[4] = '?'; else rds.picode[4] = ' '; } else { rds.picode[4] = ' '; rds.picode[5] = ' '; } rds.picode[6] = '\0'; if (strncmp(rds.picode, "0000", 4) == 0 && rds.rdsB == 0 && rds.stationName.length() == 0) memset(rds.picode, 0, sizeof(rds.picode)); // USA Station callsign decoder if ((rds.region == 1 ? ps_process : true) && rds.correctPI != 0 && rds.region > 0 && correctPIold != rds.correctPI) { bool foundMatch = false; fs::File file; if (rds.region == 1 && SPIFFS.begin(true)) { delay(5); if (currentfreq2 < 9000) { file = SPIFFS.open("/USA_87-90.csv"); } else if (currentfreq2 > 9000 && currentfreq2 < 9200) { file = SPIFFS.open("/USA_90-92.csv"); } else if (currentfreq2 > 9200 && currentfreq2 < 9400) { file = SPIFFS.open("/USA_92-94.csv"); } else if (currentfreq2 > 9400 && currentfreq2 < 9600) { file = SPIFFS.open("/USA_94-96.csv"); } else if (currentfreq2 > 9600 && currentfreq2 < 9800) { file = SPIFFS.open("/USA_96-98.csv"); } else if (currentfreq2 > 9800 && currentfreq2 < 10000) { file = SPIFFS.open("/USA_98-100.csv"); } else if (currentfreq2 > 10000 && currentfreq2 < 10200) { file = SPIFFS.open("/USA_100-102.csv"); } else if (currentfreq2 > 10200 && currentfreq2 < 10400) { file = SPIFFS.open("/USA_102-104.csv"); } else if (currentfreq2 > 10400 && currentfreq2 < 10600) { file = SPIFFS.open("/USA_104-106.csv"); } else if (currentfreq2 > 10600) { file = SPIFFS.open("/USA_106-108.csv"); } delay(5); if (file) { int i = 0; while (file.available() && !isprint(file.peek())) { file.read(); i++; } char buffer[25]; while (file.available()) { int bytesRead = file.readBytesUntil('\n', buffer, sizeof(buffer) - 1); buffer[bytesRead] = '\0'; char *token = strtok(buffer, ";"); int firstColumnValue = 0; uint16_t frequencyValue = 0; char stationID[8]; char stationState[8]; if (token) { firstColumnValue = atoi(token); token = strtok(NULL, ";"); frequencyValue = atoi(token); token = strtok(NULL, ";"); strncpy(stationID, token, sizeof(stationID) - 1); stationID[sizeof(stationID) - 1] = '\0'; token = strtok(NULL, ";"); strncpy(stationState, token, sizeof(stationState) - 1); stationState[sizeof(stationState) - 1] = '\0'; } if (frequencyValue == currentfreq2 && static_cast(firstColumnValue) == rds.correctPI) { strncpy(rds.stationID, stationID, 7); strncpy(rds.stationState, stationState, 2); foundMatch = true; break; } } } } if (!foundMatch) { uint16_t stationID = rds.rdsA; // If stationID is greater than 4096 if (stationID > 4096) { // Adjust stationID based on specific conditions if (stationID > 21671) { if ((stationID & 0xF00U) == 0) stationID = ((uint16_t)(0xA0 + ((stationID & 0xF000U) >> 12)) << 8) + lowByte(stationID); // C0DE -> ACDE else if (lowByte(stationID) == 0) stationID = 0xAF00 + uint8_t(highByte(stationID)); // CD00 -> AFCD } // Check if the station has a fixed callsign for Canada // Here we are skipping the `isFixed` and directly checking if the station is in Canada if (!(rds.region == 3 && stationID == 0xAF00)) { // Example condition for a fixed callsign (AF00 is just an example) // Determine prefix: 'W' or 'K' for USA, 'C' for Canada if (rds.region == 1 || rds.region == 2) rds.stationID[0] = (stationID > 21671) ? 'W' : 'K'; else if (rds.region == 3) rds.stationID[0] = 'C'; // Canadian stations start with 'C' // Adjust stationID based on the region and PI code if (stationID < 39247) stationID -= (stationID > 21671) ? 21672 : 4096; else stationID -= 4835; // Decode the last 3 letters of the callsign rds.stationID[1] = char(stationID / 676 + 'A'); // Using 'A' for readability stationID %= 676; // Reduce stationID to fit within the last 676 options rds.stationID[2] = char(stationID / 26 + 'A'); // Using 'A' for readability rds.stationID[3] = char(stationID % 26 + 'A'); // Using 'A' for readability } } // Validate callsign and handle faulty IDs bool faultyID = false; for (byte i = 0; i < 4; i++) { if (!(rds.stationID[i] >= 'A' && rds.stationID[i] <= 'Z')) { faultyID = true; break; } } // If any character is faulty, mark it as "Unknown" if (faultyID) strcpy(rds.stationID, "Unknown"); else rds.stationID[7] = '?'; // If not faulty, append '?' rds.stationID[8] = '\0'; // Null terminate the callsign } correctPIold = rds.correctPI; rds.stationIDtext = rds.stationID; rds.stationStatetext = rds.stationState; } } if (!rds.rdsBerror || showrdserrors == 3) rdsblock = rds.rdsB >> 11; else return; rds.blockcounter[rdsblock]++; processed_rdsblocks++; switch (rdsblock) { case RDS_GROUP_0A: case RDS_GROUP_0B: { //PS decoder if (showrdserrors == 3 || (!rdsBerrorThreshold && (!rdsDerrorThreshold))) { offset = rds.rdsB & 0x03; // Let's get the character offset for PS switch (offset) { case 0: if (((rds.rdsErr >> 8) & 0x03) > 1) rds.ps12error = true; else rds.ps12error = false; break; case 1: if (((rds.rdsErr >> 8) & 0x03) > 1) rds.ps34error = true; else rds.ps34error = false; break; case 2: if (((rds.rdsErr >> 8) & 0x03) > 1) rds.ps56error = true; else rds.ps56error = false; break; case 3: if (((rds.rdsErr >> 8) & 0x03) > 1) rds.ps78error = true; else rds.ps78error = false; break; } ps_buffer2[(offset * 2) + 0] = ps_buffer[(offset * 2) + 0]; // Make a copy of the PS buffer ps_buffer2[(offset * 2) + 1] = ps_buffer[(offset * 2) + 1]; ps_buffer2[8] = '\0'; // Endmarker ps_buffer[(offset * 2) + 0] = rds.rdsD >> 8; // First character of segment ps_buffer[(offset * 2) + 1] = rds.rdsD & 0xFF; // Second character of segment ps_buffer[8] = '\0'; // Endmarker if (ps_process || rds.fastps == 0) { if (offset == 0) { packet0 = true; packet1 = false; packet2 = false; packet3 = false; } if (offset == 1) packet1 = true; if (offset == 2) packet2 = true; if (offset == 3) packet3 = true; } if (packet0 && packet1 && packet2 && packet3 && (ps_process || (rds.fastps == 0 && rds.fastps != 2))) { // Last chars are received if (strcmp(ps_buffer, ps_buffer2) == 0) { // When no difference between current and buffer, let's go... ps_process = true; RDScharConverter(ps_buffer2, PStext, sizeof(PStext) / sizeof(wchar_t), (underscore > 0 ? true : false)); // Convert 8 bit ASCII to 16 bit ASCII String utf8String = convertToUTF8(PStext); // Convert RDS characterset to ASCII rds.stationName = extractUTF8Substring(utf8String, 0, 8, (underscore > 0 ? true : false)); // Make sure PS does not exceed 8 characters for (byte x = 0; x < 8; x++) { ps_buffer[x] = '\0'; ps_buffer2[x] = '\0'; } } } if ((!ps_process && rds.fastps > 0 && rds.fastps != 2) || rds.fastps == 2) { // Let's get 2 runs of 8 PS characters fast and without refresh if (offset == 0) packet0 = true; if (offset == 1) packet1 = true; if (offset == 2) packet2 = true; if (offset == 3) packet3 = true; RDScharConverter(ps_buffer, PStext, sizeof(PStext) / sizeof(wchar_t), (underscore > 0 ? true : false)); // Convert 8 bit ASCII to 16 bit ASCII String utf8String = convertToUTF8(PStext); // Convert RDS characterset to ASCII rds.stationName = extractUTF8Substring(utf8String, 0, 8, (underscore > 0 ? true : false)); if (packet0 && packet1 && packet2 && packet3) ps_process = true; // OK, we had one runs, now let's go the idle PS writing } if (offset == 0) rds.hasDynamicPTY = bitRead(rds.rdsB, 2) & 0x1F; // Dynamic PTY flag if (offset == 1) rds.hasCompressed = bitRead(rds.rdsB, 2) & 0x1F; // Compressed flag if (offset == 2) rds.hasArtificialhead = bitRead(rds.rdsB, 2) & 0x1F; // Artificial head flag if (offset == 3) rds.hasStereo = bitRead(rds.rdsB, 2) & 0x1F; // Stereo flag } if (!rdsBerrorThreshold) { rds.stationTypeCode = (rds.rdsB >> 5) & 0x1F; // Get 5 PTY bits from Block B if (rds.region == 0) strcpy(rds.stationType, PTY_EU[rds.stationTypeCode]); else strcpy(rds.stationType, PTY_USA[rds.stationTypeCode]); rds.hasTA = (bitRead(rds.rdsB, 4)); // Read TA flag } rds.hasTP = (bitRead(rds.rdsB, 10)); // Read TP flag if (!rdsCerrorThreshold) { //AF decoder if (rdsblock == 0 && rds.rdsC != rdsCold) { // Only when in GROUP 0A if ((rds.rdsC >> 8) > 224 && (rds.rdsC >> 8) < 250) { afinit = true; // AF detected rds.hasAF = true; } if (afinit) { if ((rds.rdsC >> 8) > 224 && (rds.rdsC >> 8) < 250 && ((rds.rdsC & 0xFF) * 10 + 8750) == currentfreq && rds.hasAF) { if (afmethodBtrigger) afmethodB = true; // Check for AF method B afmethodBprobe = true; af_counterb = (rds.rdsC >> 8) - 224; af_number = (rds.rdsC >> 8) - 224; af_counterbcheck = 1; doublecounter = 0; doubletestfreq = (rds.rdsC & 0xFF) * 10 + 8750; } else if ((rds.rdsC >> 8) > 224 && (rds.rdsC >> 8) < 250 && ((rds.rdsC & 0xFF) * 10 + 8750) != currentfreq && rds.hasAF) { afmethodBprobe = false; afmethodBtrigger = true; af_counterb = 0; af_number = (rds.rdsC >> 8) - 224; af_counterbcheck = 0; doublecounter = 0; doubletestfreq = (rds.rdsC & 0xFF) * 10 + 8750; } if (((rds.rdsC >> 8) > 0 && (rds.rdsC >> 8) < 205) && ((rds.rdsC >> 8) > 0 && (rds.rdsC >> 8) < 205)) { if (afmethodBprobe) af_counterbcheck += 2; } if ((rds.rdsB >> 11) == 0 && af_counter < 50) { uint16_t buffer0; uint16_t buffer1; if ((rds.rdsC >> 8) > 0 && (rds.rdsC >> 8) < 205) buffer0 = (rds.rdsC >> 8) * 10 + 8750; else buffer0 = 0; if ((rds.rdsC & 0xFF) > 0 && (rds.rdsC & 0xFF) < 205) buffer1 = (rds.rdsC & 0xFF) * 10 + 8750; else buffer1 = 0; if (((rds.rdsC >> 8) > 0 && (rds.rdsC >> 8) < 205) && (buffer0 == doubletestfreq || buffer1 == doubletestfreq)) doublecounter++; if (doublecounter > (af_number / 2)) afmethodB = true; // If signed frequency also appears more than once in the AF list, AF Method B detected if (afmethodBprobe && af_counterbcheck > af_counterb) afmethodBprobe = false; // If more than counter received disable probe flag if (afmethodBprobe) { // Check for Reg. flags if (buffer1 == currentfreq && buffer0 > buffer1) { for (int x = 0; x < af_counter; x++) { if (af[x].frequency == buffer0 && !af[x].regional) { af[x].regional = true; af_updatecounter++; break; } } } else if (buffer1 == currentfreq && buffer0 < buffer1) { for (int x = 0; x < af_counter; x++) { if (af[x].frequency == buffer0 && !af[x].same) { af[x].same = true; af_updatecounter++; break; } } } if (buffer0 == currentfreq && buffer0 > buffer1) { for (int x = 0; x < af_counter; x++) { if (af[x].frequency == buffer1 && !af[x].regional) { af[x].regional = true; af_updatecounter++; break; } } } else if (buffer0 == currentfreq && buffer0 < buffer1) { for (int x = 0; x < af_counter; x++) { if (af[x].frequency == buffer1 && !af[x].same) { af[x].same = true; af_updatecounter++; break; } } } } if (buffer0 != currentfreq && buffer1 != currentfreq && afmethodB && afmethodBprobe) { afmethodBprobe = false; // Remove faulty Reg. flags for (int x = 0; x < af_counter; x++) { if (af[x].frequency == buffer0 || af[x].frequency == buffer1) { if (af[x].same) { af[x].same = false; af_updatecounter++; } if (af[x].regional) { af[x].regional = false; af_updatecounter++; } } break; } } bool isValuePresent = false; for (int i = 0; i < 50; i++) { // Check if already in list if ((rds.sortaf && (buffer0 == currentfreq)) || buffer0 == 0 || af[i].frequency == buffer0) { isValuePresent = true; break; } } if (!isValuePresent) { // Add frequency to list af[af_counter].frequency = buffer0; if (af_counter < 50) af_counter++; af_updatecounter++; } isValuePresent = false; for (int i = 0; i < 50; i++) { // Check if already in list if ((rds.sortaf && (buffer1 == currentfreq)) || buffer1 == 0 || af[i].frequency == buffer1) { isValuePresent = true; break; } } if (!isValuePresent) { af[af_counter].frequency = buffer1; // Add frequency to list if (af_counter < 50) af_counter++; af_updatecounter++; } if (rds.sortaf) { // Sort AF list (low to high) for (int i = 0; i < 50; i++) { for (int j = 0; j < 50 - i; j++) { if (af[j].frequency == 0) continue; if (af[j].frequency > af[j + 1].frequency && af[j + 1].frequency != 0) { uint16_t temp = af[j].frequency; bool temp3 = af[j].afvalid; bool temp4 = af[j].checked; bool temp5 = af[j].regional; bool temp6 = af[j].same; af[j].frequency = af[j + 1].frequency; af[j].afvalid = af[j + 1].afvalid; af[j].checked = af[j + 1].checked; af[j].regional = af[j + 1].regional; af[j].same = af[j + 1].same; af[j + 1].frequency = temp; af[j + 1].afvalid = temp3; af[j + 1].checked = temp4; af[j + 1].regional = temp5; af[j + 1].same = temp6; } } } } } } rdsCold = rds.rdsC; } } } break; case RDS_GROUP_1A: case RDS_GROUP_1B: { if (!rdsCerrorThreshold) { if (((rds.rdsC >> 12) & 0x07) == 0 && rdsblock == RDS_GROUP_1A) { // ECC code readout rds.ECC = rds.rdsC & 0xff; rds.hasECC = true; switch (rds.picode[0]) { case '1': { if (rds.ECC == 160) rds.ECCtext = ECCtext[226]; if (rds.ECC == 162) rds.ECCtext = ECCtext[129]; if (rds.ECC == 163) rds.ECCtext = ECCtext[136]; if (rds.ECC == 164) rds.ECCtext = ECCtext[152]; if (rds.ECC == 208) rds.ECCtext = ECCtext[104]; if (rds.ECC == 209) rds.ECCtext = ECCtext[73]; if (rds.ECC == 210) rds.ECCtext = ECCtext[83]; if (rds.ECC == 224) rds.ECCtext = ECCtext[0]; if (rds.ECC == 225) rds.ECCtext = ECCtext[1]; if (rds.ECC == 226) rds.ECCtext = ECCtext[2]; if (rds.ECC == 227) rds.ECCtext = ECCtext[59]; if (rds.ECC == 228) rds.ECCtext = ECCtext[3]; if (rds.ECC == 240) rds.ECCtext = ECCtext[179]; if (rds.ECC == 241) rds.ECCtext = ECCtext[197]; if (rds.ECC == 242) rds.ECCtext = ECCtext[124]; if (rds.ECC == 243) rds.ECCtext = ECCtext[200]; break; } case '2': { if (rds.ECC == 160) rds.ECCtext = ECCtext[226]; if (rds.ECC == 162) rds.ECCtext = ECCtext[130]; if (rds.ECC == 163) rds.ECCtext = ECCtext[141]; if (rds.ECC == 164) rds.ECCtext = ECCtext[155]; if (rds.ECC == 208) rds.ECCtext = ECCtext[106]; if (rds.ECC == 209) rds.ECCtext = ECCtext[126]; if (rds.ECC == 210) rds.ECCtext = ECCtext[95]; if (rds.ECC == 224) rds.ECCtext = ECCtext[4]; if (rds.ECC == 225) rds.ECCtext = ECCtext[5]; if (rds.ECC == 226) rds.ECCtext = ECCtext[6]; if (rds.ECC == 227) rds.ECCtext = ECCtext[7]; if (rds.ECC == 228) rds.ECCtext = ECCtext[8]; if (rds.ECC == 240) rds.ECCtext = ECCtext[180]; if (rds.ECC == 241) rds.ECCtext = ECCtext[187]; if (rds.ECC == 242) rds.ECCtext = ECCtext[77]; if (rds.ECC == 243) rds.ECCtext = ECCtext[218]; break; } case '3': { if (rds.ECC == 160) rds.ECCtext = ECCtext[226]; if (rds.ECC == 162) rds.ECCtext = ECCtext[224]; if (rds.ECC == 163) rds.ECCtext = ECCtext[156]; if (rds.ECC == 164) rds.ECCtext = ECCtext[132]; if (rds.ECC == 208) rds.ECCtext = ECCtext[112]; if (rds.ECC == 209) rds.ECCtext = ECCtext[119]; if (rds.ECC == 210) rds.ECCtext = ECCtext[72]; if (rds.ECC == 211) rds.ECCtext = ECCtext[92]; if (rds.ECC == 224) rds.ECCtext = ECCtext[9]; if (rds.ECC == 225) rds.ECCtext = ECCtext[10]; if (rds.ECC == 226) rds.ECCtext = ECCtext[11]; if (rds.ECC == 227) rds.ECCtext = ECCtext[12]; if (rds.ECC == 228) rds.ECCtext = ECCtext[16]; if (rds.ECC == 229) rds.ECCtext = ECCtext[63]; if (rds.ECC == 240) rds.ECCtext = ECCtext[181]; if (rds.ECC == 241) rds.ECCtext = ECCtext[227]; if (rds.ECC == 242) rds.ECCtext = ECCtext[189]; if (rds.ECC == 243) rds.ECCtext = ECCtext[219]; break; } case '4': { if (rds.ECC == 160) rds.ECCtext = ECCtext[226]; if (rds.ECC == 162) rds.ECCtext = ECCtext[148]; if (rds.ECC == 163) rds.ECCtext = ECCtext[157]; if (rds.ECC == 208) rds.ECCtext = ECCtext[127]; if (rds.ECC == 209) rds.ECCtext = ECCtext[69]; if (rds.ECC == 210) rds.ECCtext = ECCtext[91]; if (rds.ECC == 211) rds.ECCtext = ECCtext[103]; if (rds.ECC == 224) rds.ECCtext = ECCtext[13]; if (rds.ECC == 225) rds.ECCtext = ECCtext[14]; if (rds.ECC == 226) rds.ECCtext = ECCtext[15]; if (rds.ECC == 240) rds.ECCtext = ECCtext[182]; if (rds.ECC == 241) rds.ECCtext = ECCtext[210]; if (rds.ECC == 242) rds.ECCtext = ECCtext[213]; break; } case '5': { if (rds.ECC == 160) rds.ECCtext = ECCtext[226]; if (rds.ECC == 162) rds.ECCtext = ECCtext[133]; if (rds.ECC == 164) rds.ECCtext = ECCtext[159]; if (rds.ECC == 208) rds.ECCtext = ECCtext[68]; if (rds.ECC == 209) rds.ECCtext = ECCtext[79]; if (rds.ECC == 210) rds.ECCtext = ECCtext[88]; if (rds.ECC == 211) rds.ECCtext = ECCtext[78]; if (rds.ECC == 224) rds.ECCtext = ECCtext[17]; if (rds.ECC == 225) rds.ECCtext = ECCtext[18]; if (rds.ECC == 226) rds.ECCtext = ECCtext[19]; if (rds.ECC == 227) rds.ECCtext = ECCtext[65]; if (rds.ECC == 240) rds.ECCtext = ECCtext[183]; if (rds.ECC == 241) rds.ECCtext = ECCtext[191]; if (rds.ECC == 242) rds.ECCtext = ECCtext[193]; break; } case '6': { if (rds.ECC == 160) rds.ECCtext = ECCtext[226]; if (rds.ECC == 162) rds.ECCtext = ECCtext[134]; if (rds.ECC == 163) rds.ECCtext = ECCtext[163]; if (rds.ECC == 164) rds.ECCtext = ECCtext[171]; if (rds.ECC == 208) rds.ECCtext = ECCtext[96]; if (rds.ECC == 209) rds.ECCtext = ECCtext[105]; if (rds.ECC == 210) rds.ECCtext = ECCtext[123]; if (rds.ECC == 211) rds.ECCtext = ECCtext[125]; if (rds.ECC == 224) rds.ECCtext = ECCtext[20]; if (rds.ECC == 225) rds.ECCtext = ECCtext[21]; if (rds.ECC == 226) rds.ECCtext = ECCtext[22]; if (rds.ECC == 228) rds.ECCtext = ECCtext[24]; if (rds.ECC == 240) rds.ECCtext = ECCtext[184]; if (rds.ECC == 241) rds.ECCtext = ECCtext[76]; if (rds.ECC == 242) rds.ECCtext = ECCtext[201]; break; } case '7': { if (rds.ECC == 160) rds.ECCtext = ECCtext[226]; if (rds.ECC == 162) rds.ECCtext = ECCtext[139]; if (rds.ECC == 163) rds.ECCtext = ECCtext[161]; if (rds.ECC == 164) rds.ECCtext = ECCtext[164]; if (rds.ECC == 208) rds.ECCtext = ECCtext[113]; if (rds.ECC == 209) rds.ECCtext = ECCtext[81]; if (rds.ECC == 210) rds.ECCtext = ECCtext[84]; if (rds.ECC == 224) rds.ECCtext = ECCtext[25]; if (rds.ECC == 225) rds.ECCtext = ECCtext[26]; if (rds.ECC == 226) rds.ECCtext = ECCtext[27]; if (rds.ECC == 228) rds.ECCtext = ECCtext[62]; if (rds.ECC == 240) rds.ECCtext = ECCtext[185]; if (rds.ECC == 241) rds.ECCtext = ECCtext[207]; if (rds.ECC == 242) rds.ECCtext = ECCtext[221]; break; } case '8': { if (rds.ECC == 160) rds.ECCtext = ECCtext[226]; if (rds.ECC == 162) rds.ECCtext = ECCtext[142]; if (rds.ECC == 164) rds.ECCtext = ECCtext[170]; if (rds.ECC == 208) rds.ECCtext = ECCtext[116]; if (rds.ECC == 209) rds.ECCtext = ECCtext[117]; if (rds.ECC == 210) rds.ECCtext = ECCtext[74]; if (rds.ECC == 211) rds.ECCtext = ECCtext[82]; if (rds.ECC == 224) rds.ECCtext = ECCtext[28]; if (rds.ECC == 225) rds.ECCtext = ECCtext[29]; if (rds.ECC == 226) rds.ECCtext = ECCtext[30]; if (rds.ECC == 227) rds.ECCtext = ECCtext[31]; if (rds.ECC == 228) rds.ECCtext = ECCtext[32]; if (rds.ECC == 240) rds.ECCtext = ECCtext[186]; if (rds.ECC == 241) rds.ECCtext = ECCtext[195]; if (rds.ECC == 242) rds.ECCtext = ECCtext[212]; if (rds.ECC == 243) rds.ECCtext = ECCtext[190]; break; } case '9': { if (rds.ECC == 160) rds.ECCtext = ECCtext[226]; if (rds.ECC == 162) rds.ECCtext = ECCtext[143]; if (rds.ECC == 163) rds.ECCtext = ECCtext[162]; if (rds.ECC == 164) rds.ECCtext = ECCtext[174]; if (rds.ECC == 208) rds.ECCtext = ECCtext[120]; if (rds.ECC == 209) rds.ECCtext = ECCtext[102]; if (rds.ECC == 210) rds.ECCtext = ECCtext[107]; if (rds.ECC == 224) rds.ECCtext = ECCtext[33]; if (rds.ECC == 225) rds.ECCtext = ECCtext[34]; if (rds.ECC == 226) rds.ECCtext = ECCtext[35]; if (rds.ECC == 227) rds.ECCtext = ECCtext[36]; if (rds.ECC == 228) rds.ECCtext = ECCtext[37]; if (rds.ECC == 240) rds.ECCtext = ECCtext[80]; if (rds.ECC == 241) rds.ECCtext = ECCtext[209]; if (rds.ECC == 242) rds.ECCtext = ECCtext[196]; if (rds.ECC == 243) rds.ECCtext = ECCtext[211]; if (rds.ECC == 244) rds.ECCtext = ECCtext[190]; break; } case 'A': { if (rds.ECC == 160) rds.ECCtext = ECCtext[226]; if (rds.ECC == 162) rds.ECCtext = ECCtext[131]; if (rds.ECC == 163) rds.ECCtext = ECCtext[144]; if (rds.ECC == 164) rds.ECCtext = ECCtext[166]; if (rds.ECC == 208) rds.ECCtext = ECCtext[85]; if (rds.ECC == 209) rds.ECCtext = ECCtext[97]; if (rds.ECC == 210) rds.ECCtext = ECCtext[121]; if (rds.ECC == 211) rds.ECCtext = ECCtext[70]; if (rds.ECC == 212) rds.ECCtext = ECCtext[86]; if (rds.ECC == 224) rds.ECCtext = ECCtext[38]; if (rds.ECC == 225) rds.ECCtext = ECCtext[39]; if (rds.ECC == 226) rds.ECCtext = ECCtext[40]; if (rds.ECC == 227) rds.ECCtext = ECCtext[41]; if (rds.ECC == 228) rds.ECCtext = ECCtext[60]; if (rds.ECC == 240) rds.ECCtext = ECCtext[178]; if (rds.ECC == 241) rds.ECCtext = ECCtext[215]; if (rds.ECC == 242) rds.ECCtext = ECCtext[214]; break; } case 'B': { if (rds.ECC == 160) rds.ECCtext = ECCtext[226]; if (rds.ECC == 161) rds.ECCtext = ECCtext[138]; if (rds.ECC == 162) rds.ECCtext = ECCtext[137]; if (rds.ECC == 163) rds.ECCtext = ECCtext[145]; if (rds.ECC == 164) rds.ECCtext = ECCtext[167]; if (rds.ECC == 165) rds.ECCtext = ECCtext[158]; if (rds.ECC == 208) rds.ECCtext = ECCtext[101]; if (rds.ECC == 210) rds.ECCtext = ECCtext[109]; if (rds.ECC == 209) rds.ECCtext = ECCtext[100]; if (rds.ECC == 224) rds.ECCtext = ECCtext[42]; if (rds.ECC == 225) rds.ECCtext = ECCtext[43]; if (rds.ECC == 226) rds.ECCtext = ECCtext[44]; if (rds.ECC == 227) rds.ECCtext = ECCtext[61]; if (rds.ECC == 228) rds.ECCtext = ECCtext[66]; if (rds.ECC == 240) rds.ECCtext = ECCtext[206]; if (rds.ECC == 241) rds.ECCtext = ECCtext[188]; if (rds.ECC == 242) rds.ECCtext = ECCtext[203]; if (rds.ECC == 243) rds.ECCtext = ECCtext[93]; if (rds.ECC == 244) rds.ECCtext = ECCtext[204]; break; } case 'C': { if (rds.ECC == 161) rds.ECCtext = ECCtext[138]; if (rds.ECC == 162) rds.ECCtext = ECCtext[223]; if (rds.ECC == 163) rds.ECCtext = ECCtext[140]; if (rds.ECC == 164) rds.ECCtext = ECCtext[146]; if (rds.ECC == 165) rds.ECCtext = ECCtext[169]; if (rds.ECC == 208) rds.ECCtext = ECCtext[110]; if (rds.ECC == 209) rds.ECCtext = ECCtext[108]; if (rds.ECC == 210) rds.ECCtext = ECCtext[111]; if (rds.ECC == 211) rds.ECCtext = ECCtext[87]; if (rds.ECC == 224) rds.ECCtext = ECCtext[45]; if (rds.ECC == 225) rds.ECCtext = ECCtext[46]; if (rds.ECC == 226) rds.ECCtext = ECCtext[47]; if (rds.ECC == 227) rds.ECCtext = ECCtext[48]; if (rds.ECC == 228) rds.ECCtext = ECCtext[118]; if (rds.ECC == 240) rds.ECCtext = ECCtext[190]; if (rds.ECC == 241) rds.ECCtext = ECCtext[216]; if (rds.ECC == 242) rds.ECCtext = ECCtext[194]; break; } case 'D': { if (rds.ECC == 160) rds.ECCtext = ECCtext[226]; if (rds.ECC == 161) rds.ECCtext = ECCtext[138]; if (rds.ECC == 162) rds.ECCtext = ECCtext[225]; if (rds.ECC == 163) rds.ECCtext = ECCtext[150]; if (rds.ECC == 164) rds.ECCtext = ECCtext[154]; if (rds.ECC == 165) rds.ECCtext = ECCtext[158]; if (rds.ECC == 208) rds.ECCtext = ECCtext[90]; if (rds.ECC == 209) rds.ECCtext = ECCtext[89]; if (rds.ECC == 224) rds.ECCtext = ECCtext[0]; if (rds.ECC == 225) rds.ECCtext = ECCtext[49]; if (rds.ECC == 226) rds.ECCtext = ECCtext[23]; if (rds.ECC == 227) rds.ECCtext = ECCtext[122]; if (rds.ECC == 240) rds.ECCtext = ECCtext[198]; if (rds.ECC == 241) rds.ECCtext = ECCtext[217]; if (rds.ECC == 242) rds.ECCtext = ECCtext[128]; break; } case 'E': { if (rds.ECC == 160) rds.ECCtext = ECCtext[226]; if (rds.ECC == 161) rds.ECCtext = ECCtext[138]; if (rds.ECC == 162) rds.ECCtext = ECCtext[151]; if (rds.ECC == 163) rds.ECCtext = ECCtext[172]; if (rds.ECC == 164) rds.ECCtext = ECCtext[175]; if (rds.ECC == 165) rds.ECCtext = ECCtext[158]; if (rds.ECC == 208) rds.ECCtext = ECCtext[99]; if (rds.ECC == 209) rds.ECCtext = ECCtext[115]; if (rds.ECC == 210) rds.ECCtext = ECCtext[94]; if (rds.ECC == 224) rds.ECCtext = ECCtext[50]; if (rds.ECC == 225) rds.ECCtext = ECCtext[51]; if (rds.ECC == 226) rds.ECCtext = ECCtext[52]; if (rds.ECC == 227) rds.ECCtext = ECCtext[53]; if (rds.ECC == 228) rds.ECCtext = ECCtext[64]; if (rds.ECC == 240) rds.ECCtext = ECCtext[98]; if (rds.ECC == 241) rds.ECCtext = ECCtext[199]; if (rds.ECC == 242) rds.ECCtext = ECCtext[208]; if (rds.ECC == 243) rds.ECCtext = ECCtext[205]; break; } case 'F': { if (rds.ECC == 161) rds.ECCtext = ECCtext[149]; if (rds.ECC == 162) rds.ECCtext = ECCtext[222]; if (rds.ECC == 163) rds.ECCtext = ECCtext[153]; if (rds.ECC == 164) rds.ECCtext = ECCtext[176]; if (rds.ECC == 165) rds.ECCtext = ECCtext[158]; if (rds.ECC == 166) rds.ECCtext = ECCtext[168]; if (rds.ECC == 208) rds.ECCtext = ECCtext[67]; if (rds.ECC == 209) rds.ECCtext = ECCtext[75]; if (rds.ECC == 210) rds.ECCtext = ECCtext[114]; if (rds.ECC == 224) rds.ECCtext = ECCtext[54]; if (rds.ECC == 225) rds.ECCtext = ECCtext[55]; if (rds.ECC == 226) rds.ECCtext = ECCtext[56]; if (rds.ECC == 227) rds.ECCtext = ECCtext[57]; if (rds.ECC == 228) rds.ECCtext = ECCtext[58]; if (rds.ECC == 240) rds.ECCtext = ECCtext[202]; if (rds.ECC == 241) rds.ECCtext = ECCtext[192]; if (rds.ECC == 242) rds.ECCtext = ECCtext[220]; if (rds.ECC == 243) rds.ECCtext = ECCtext[71]; break; } default: { rds.ECCtext = ""; break; } } } } if (rds.rdsC >> 12 == 1 && rdsblock == RDS_GROUP_1A) rds.hasTMC = true; // TMC flag } break; case RDS_GROUP_2A: { if (showrdserrors == 3 || (!rdsBerrorThreshold && !rdsCerrorThreshold && !rdsDerrorThreshold)) { // RT decoder (64 characters) rds.hasRT = true; rds.rtAB = (bitRead(rds.rdsB, 4)); // Get AB flag if (initab) { rtABold = rds.rtAB; initab = false; } byte endmarkerRT64 = 64; for (byte i = 0; i < endmarkerRT64; i++) { if (rt_buffer[i] == 0x0d) { endmarkerRT64 = i; break; } } if (rds.rtAB != rtABold) { // Erase old RT, because of AB change initrt = false; if (rds.rtbuffer) { char rt_buffer_temp[129]; bool found = false; strcpy(rt_buffer_temp, rt_buffer); for (int i = 0; i < 129; i++) { if (rt_buffer_temp[i] == 0x0D) { found = true; } if (found) { rt_buffer_temp[i] = '\0'; } } wchar_t RTtext[65] = L""; // Create 16 bit char buffer for Extended ASCII RDScharConverter(rt_buffer_temp, RTtext, sizeof(RTtext) / sizeof(wchar_t), (underscore > 1 ? true : false)); // Convert 8 bit ASCII to 16 bit ASCII rds.stationText = convertToUTF8(RTtext); // Convert RDS characterset to ASCII rds.stationText = extractUTF8Substring(rds.stationText, 0, endmarkerRT64, (underscore > 1 ? true : false)); // Make sure RT does not exceed 64 characters rds.stationText = trimTrailingSpaces(rds.stationText); // Trim empty spaces at the end } for (byte i = 0; i < 64; i++) { rt_buffer[i] = 0x20; } rt_buffer[64] = '\0'; rtABold = rds.rtAB; } offset = (rds.rdsB & 0xf) * 4; // Get RT character segment rt_buffer[offset + 0] = rds.rdsC >> 8; // First character of segment rt_buffer[offset + 1] = rds.rdsC & 0xff; // Second character of segment rt_buffer[offset + 2] = rds.rdsD >> 8; // Thirth character of segment rt_buffer[offset + 3] = rds.rdsD & 0xff; // Fourth character of segment if (initrt || !rds.rtbuffer) { char rt_buffer_temp[129]; bool found = false; strcpy(rt_buffer_temp, rt_buffer); for (int i = 0; i < 129; i++) { if (rt_buffer_temp[i] == 0x0D) { found = true; } if (found) { rt_buffer_temp[i] = '\0'; } } wchar_t RTtext[65] = L""; // Create 16 bit char buffer for Extended ASCII RDScharConverter(rt_buffer_temp, RTtext, sizeof(RTtext) / sizeof(wchar_t), (underscore > 1 ? true : false)); // Convert 8 bit ASCII to 16 bit ASCII rds.stationText = convertToUTF8(RTtext); // Convert RDS characterset to ASCII rds.stationText = extractUTF8Substring(rds.stationText, 0, endmarkerRT64, (underscore > 1 ? true : false)); // Make sure RT does not exceed 64 characters rds.stationText = trimTrailingSpaces(rds.stationText); // Trim empty spaces at the end } for (int i = 0; i < 64; i++) rt_buffer2[i] = rt_buffer[i]; } } break; case RDS_GROUP_2B: { if (showrdserrors == 3 || (!rdsBerrorThreshold && !rdsDerrorThreshold)) { // RT decoder (32 characters) rds.hasRT = true; rds.rtAB32 = (bitRead(rds.rdsB, 4)); // Get AB flag if (rds.rtAB32 != rtAB32old) { // Erase old RT, because of AB change for (byte i = 0; i < 33; i++) { rt_buffer32[i] = 0x20; } rt_buffer32[32] = '\0'; rtAB32old = rds.rtAB32; } offset = (rds.rdsB & 0xf) * 2; // Get RT character segment rt_buffer32[offset + 0] = rds.rdsD >> 8; // First character of segment rt_buffer32[offset + 1] = rds.rdsD & 0xff; // Second character of segment byte endmarkerRT32 = 32; for (byte i = 0; i < endmarkerRT32; i++) { if (rt_buffer[i] == 0x0d) { endmarkerRT32 = i; break; } } char rt_buffer_temp[129]; bool found = false; strcpy(rt_buffer_temp, rt_buffer32); for (int i = 0; i < 129; i++) { if (rt_buffer_temp[i] == 0x0D) { found = true; } if (found) { rt_buffer_temp[i] = '\0'; } } wchar_t RTtext[33] = L""; // Create 16 bit char buffer for Extended ASCII RDScharConverter(rt_buffer_temp, RTtext, sizeof(RTtext) / sizeof(wchar_t), (underscore > 1 ? true : false)); // Convert 8 bit ASCII to 16 bit ASCII rds.stationText32 = convertToUTF8(RTtext); // Convert RDS characterset to ASCII rds.stationText32 = extractUTF8Substring(rds.stationText32, 0, endmarkerRT32, (underscore > 1 ? true : false));// Make sure RT does not exceed 32 characters rds.stationText32 = trimTrailingSpaces(rds.stationText32); // Trim empty spaces at the end } } break; case RDS_GROUP_3A: { if (!rdsDerrorThreshold) { if (rds.rdsD != 0) rds.hasAID = true; bool isValuePresent = false; for (int i = 0; i < 10; i++) { if (rds.aid[i] == rds.rdsD) { isValuePresent = true; break; } } if (!isValuePresent) { rds.aid[rds.aid_counter] = rds.rdsD; rds.aid_counter++; } if (rds.rdsD == 0xCD46) { // Check for TMC application rds.hasTMC = true; // TMC flag } // RT+ init if (rds.rdsD == 0x4BD7) { // Check for RT+ application rds.hasRDSplus = true; // Set flag rtplusblock = ((rds.rdsB & 0x1F) >> 1) * 2; // Get RT+ Block } if (rds.rdsD == 0x0093) { // Check for DAB+ AF application rds.hasDABAF = true; // Set flag DABAFblock = ((rds.rdsB & 0x1F) >> 1) * 2; // Get DAB AF Block } if (rds.rdsD == 0x6552) { // Check for Enhanced RT application _hasEnhancedRT = true; // Set flag eRTblock = ((rds.rdsB & 0x1F) >> 1) * 2; // Get eRT block eRTcoding = bitRead(rds.rdsC, 0); // 0 = UCS-2, 1 = UTF-8 } } } break; case RDS_GROUP_4A: { if (!rdsBerrorThreshold && !rdsCerrorThreshold && !rdsDerrorThreshold && rds.ctupdate && (rds.PICTlock == rds.rdsA || rds.PICTlock == 0)) { // CT uint32_t mjd; mjd = (rds.rdsB & 0x03); mjd <<= 15; mjd += ((rds.rdsC >> 1) & 0x7FFF); uint16_t hour, minute, day = 1, month = 1, year = 2020; // Set default values for day, month, and year int32_t timeoffset; long J, C, Y, M; J = mjd + 2400001 + 68569; C = 4 * J / 146097; J = J - (146097 * C + 3) / 4; Y = 4000 * (J + 1) / 1461001; J = J - 1461 * Y / 4 + 31; M = 80 * (J + 0) / 2447; if ((J - 2447 * M / 80) < 32) day = J - 2447 * M / 80; J = M / 11; if ((M + 2 - (12 * J)) < 13) month = M + 2 - (12 * J); if ((100 * (C - 49) + Y + J) > 2022) year = 100 * (C - 49) + Y + J; hour = ((rds.rdsD >> 12) & 0x000f); hour += ((rds.rdsC << 4) & 0x0010); timeoffset = rds.rdsD & 0x001f; if (bitRead(rds.rdsD, 5)) timeoffset *= -1; timeoffset *= 1800; minute = (rds.rdsD & 0x0fc0) >> 6; if (year < 2024 || hour > 23 || minute > 59 || timeoffset > 55800 || timeoffset < -55800) break; struct tm tm; tm.tm_year = year - 1900; tm.tm_mon = month - 1; tm.tm_mday = day; tm.tm_hour = hour; tm.tm_min = minute; tm.tm_sec = 0; tm.tm_isdst = -1; time_t rdstime = mktime(&tm); if (lastrdstime == 0) { lastrdstime = rdstime; lasttimeoffset = timeoffset; } if ((rdstime == lastrdstime + 60 && timeoffset == lasttimeoffset) || showrdserrors != 0) { rds.hasCT = true; rds.time = rdstime; rds.offset = timeoffset; } else { rds.hasCT = false; } lastrdstime = rdstime; lasttimeoffset = timeoffset; } } break; case RDS_GROUP_10A: { if (!rdsCerrorThreshold && !rdsDerrorThreshold) { // PTYN offset = bitRead(rds.rdsB, 0); // Get char offset if (rds.rdsC != 0 && rds.rdsD != 0) { ptyn_buffer[(offset * 4) + 0] = rds.rdsC >> 8; // Get position 1 and 5 ptyn_buffer[(offset * 4) + 1] = rds.rdsC & 0xFF; // Get position 2 and 6 ptyn_buffer[(offset * 4) + 2] = rds.rdsD >> 8; // Get position 3 and 7 ptyn_buffer[(offset * 4) + 3] = rds.rdsD & 0xFF; // Get position 4 and 8 for (byte i = 0; i < 8; i++) PTYNtext[i] = L'\0'; RDScharConverter(ptyn_buffer, PTYNtext, sizeof(PTYNtext) / sizeof(wchar_t), false); // Convert 8 bit ASCII to 16 bit ASCII String utf8String = convertToUTF8(PTYNtext); // Convert RDS characterset to ASCII rds.PTYN = extractUTF8Substring(utf8String, 0, 8, false); // Make sure text is not longer than 8 chars rds.hasPTYN = true; } } } break; case RDS_GROUP_5A: case RDS_GROUP_6A: case RDS_GROUP_7A: case RDS_GROUP_8A: case RDS_GROUP_9A: case RDS_GROUP_11A: case RDS_GROUP_12A: case RDS_GROUP_13A: { // RT+ decoding if ((!rdsBerrorThreshold && !rdsCerrorThreshold && !rdsDerrorThreshold) && rtplusblock == rdsblock && rds.hasRDSplus) { rds.rdsplusTag1 = ((rds.rdsB & 0x07) << 3) + (rds.rdsC >> 13); // Are we in the right RT+ block and is all ok to go? rds.rdsplusTag2 = ((rds.rdsC & 0x01) << 5) + (rds.rdsD >> 11); uint16_t start_marker_1 = (rds.rdsC >> 7) & 0x3F; uint16_t length_marker_1 = (rds.rdsC >> 1) & 0x3F; uint16_t start_marker_2 = (rds.rdsD >> 5) & 0x3F; uint16_t length_marker_2 = (rds.rdsD & 0x1F); togglebit = bitRead(lowByte(rds.rdsB), 4); runningbit = bitRead(lowByte(rds.rdsB), 3); switch (rds.rdsplusTag1) { case 0: rds.rdsplusTag1 = 169; break; case 1 ... 53: rds.rdsplusTag1 += 111; break; case 59 ... 63: rds.rdsplusTag1 += 105; break; default: rds.rdsplusTag1 = 169; break; } switch (rds.rdsplusTag2) { case 0: rds.rdsplusTag2 = 169; break; case 1 ... 53: rds.rdsplusTag2 += 111; break; case 59 ... 63: rds.rdsplusTag2 += 105; break; default: rds.rdsplusTag2 = 169; break; } if (togglebit) { for (int i = 0; i < 44; i++) { RDSplus1[i] = 0x20; RDSplus2[i] = 0x20; } RDSplus1[44] = 0; RDSplus2[44] = 0; } if (rds.rtAB == rtABold) { for (int i = 0; i <= length_marker_1; i++)RDSplus1[i] = rt_buffer2[i + start_marker_1]; RDSplus1[length_marker_1 + 1] = 0; for (int i = 0; i <= length_marker_2; i++)RDSplus2[i] = rt_buffer2[i + start_marker_2]; RDSplus2[length_marker_2 + 1] = 0; } wchar_t RTtext1[45] = L""; // Create 16 bit char buffer for Extended ASCII RDScharConverter(RDSplus1, RTtext1, sizeof(RTtext1) / sizeof(wchar_t), false); // Convert 8 bit ASCII to 16 bit ASCII rds.RTContent1 = convertToUTF8(RTtext1); // Convert RDS characterset to ASCII rds.RTContent1 = extractUTF8Substring(rds.RTContent1, 0, 44, false); // Make sure RT does not exceed 32 characters wchar_t RTtext2[45] = L""; // Create 16 bit char buffer for Extended ASCII RDScharConverter(RDSplus2, RTtext2, sizeof(RTtext2) / sizeof(wchar_t), false); // Convert 8 bit ASCII to 16 bit ASCII rds.RTContent2 = convertToUTF8(RTtext2); // Convert RDS characterset to ASCII rds.RTContent2 = extractUTF8Substring(rds.RTContent2, 0, 44, false); // Make sure RT does not exceed 32 characters } // eRT decoding if ((!rdsBerrorThreshold && !rdsCerrorThreshold && !rdsDerrorThreshold) && eRTblock == rdsblock && _hasEnhancedRT) { offset = (rds.rdsB & 0x1f) * 4; eRT_buffer[offset + 0] = rds.rdsC >> 8; // First character of segment eRT_buffer[offset + 1] = rds.rdsC & 0xff; // Second character of segment eRT_buffer[offset + 2] = rds.rdsD >> 8; // Thirth character of segment eRT_buffer[offset + 3] = rds.rdsD & 0xff; // Fourth character of segment eRT_buffer[127] = '\0'; byte endmarkereRT = 127; bool foundendmarker = false; for (byte i = 0; i < endmarkereRT; i++) { if (eRT_buffer[i] == 0x0d) { foundendmarker = true; endmarkereRT = i; break; } } if (offset == 0 || foundendmarker) { if (eRTcoding) rds.enhancedRTtext = eRT_buffer; else rds.enhancedRTtext = ucs2ToUtf8(eRT_buffer); // Convert to UTF-8 or UCS-2 rds.enhancedRTtext = trimTrailingSpaces(rds.enhancedRTtext); if (rds.enhancedRTtext.length() > 0) rds.hasEnhancedRT = true; } } if (!rdsBerrorThreshold && rdsblock == 16 && (bitRead(rds.rdsB, 15))) rds.hasTMC = true; // TMC flag if ((!rdsBerrorThreshold && !rdsCerrorThreshold && !rdsDerrorThreshold) && DABAFblock == rdsblock && rds.hasDABAF) { rds.dabaffreq = (rds.rdsC * 16); for (size_t i = 0; i < sizeof(DABfrequencyTable) / sizeof(DABfrequencyTable[0]); ++i) { if (DABfrequencyTable[i].frequency == rds.dabaffreq) strcpy(rds.dabafchannel, DABfrequencyTable[i].label); } rds.dabafeid[0] = (rds.rdsD >> 12) & 0xF; rds.dabafeid[1] = (rds.rdsD >> 8) & 0xF; rds.dabafeid[2] = (rds.rdsD >> 4) & 0xF; rds.dabafeid[3] = rds.rdsD & 0xF; for (int i = 0; i < 4; i++) { if (rds.dabafeid[i] < 10) { rds.dabafeid[i] += '0'; // Add ASCII offset for decimal digits } else { rds.dabafeid[i] += 'A' - 10; // Add ASCII offset for hexadecimal letters A-F } } rds.dabafeid[4] = 0; } } break; case RDS_GROUP_14A: { // EON if (!rdsAerrorThreshold && !rdsBerrorThreshold && !rdsCerrorThreshold && !rdsDerrorThreshold) { rds.hasEON = true; // Group is there, so we have EON bool isValuePresent = false; int eonIndex = -1; int i = 0; for (; i < 20; i++) { if (eon[i].pi == rds.rdsD || rds.rdsA == rds.rdsD) { // Check if EON is already in array isValuePresent = true; eonIndex = i; break; } } if (!isValuePresent) { eon[eon_counter].picode[0] = (rds.rdsD >> 12) & 0xF; eon[eon_counter].picode[1] = (rds.rdsD >> 8) & 0xF; eon[eon_counter].picode[2] = (rds.rdsD >> 4) & 0xF; eon[eon_counter].picode[3] = rds.rdsD & 0xF; for (int j = 0; j < 4; j++) { if (eon[eon_counter].picode[j] < 10) { eon[eon_counter].picode[j] += '0'; // Add ASCII offset for decimal digits } else { eon[eon_counter].picode[j] += 'A' - 10; // Add ASCII offset for hexadecimal letters A-F } } eon[eon_counter].pi = rds.rdsD; // Store PI on next array if (eon_counter < 20) eon_counter++; eonIndex = eon_counter - 1; } else { eonIndex = i; } offset = rds.rdsB & 0x0F; // Read offset if (offset < 4 && eon[eonIndex].pi == rds.rdsD) { eon_buffer[eonIndex][(offset * 2) + 0] = rds.rdsC >> 8; // First character of segment eon_buffer[eonIndex][(offset * 2) + 1] = rds.rdsC & 0xFF; // Second character of segment } if (eon[eonIndex].pi == rds.rdsD) { // Last chars are received RDScharConverter(eon_buffer[eonIndex], EONPStext[eonIndex], sizeof(EONPStext[eonIndex]) / sizeof(wchar_t), false); // Convert 8 bit ASCII to 16 bit ASCII String utf8String = convertToUTF8(EONPStext[eonIndex]); // Convert RDS characterset to ASCII eon[eonIndex].ps = extractUTF8Substring(utf8String, 0, 8, false); // Make sure PS does not exceed 8 characters } if (offset == 13 && eon[eonIndex].pi == rds.rdsD) { eon[eonIndex].taset = true; eon[eonIndex].ta = bitRead(rds.rdsC, 0); eon[eonIndex].pty = (rds.rdsC >> 11) & 0xF; eon[eonIndex].ptyset = true; } if (bitRead(rds.rdsB, 4) && eon[eonIndex].pi == rds.rdsD) eon[eonIndex].tp = true; if (offset > 4 && offset < 9 && eon[eonIndex].pi == rds.rdsD) { // Check if mapped frequency belongs to current frequency if (((rds.rdsC >> 8) * 10 + 8750) == currentfreq && (rds.rdsC >> 8) > 0 && (rds.rdsC >> 8) < 205) { if (eon[eonIndex].mappedfreq == 0) { eon[eonIndex].mappedfreq = ((rds.rdsC & 0xFF) * 10 + 8750); // Add mapped frequency to array } else if (eon[eonIndex].mappedfreq2 == 0 && eon[eonIndex].mappedfreq != ((rds.rdsC & 0xFF) * 10 + 8750) && (rds.rdsC >> 8) > 0 && (rds.rdsC >> 8) < 205) { eon[eonIndex].mappedfreq2 = ((rds.rdsC & 0xFF) * 10 + 8750); } else if (eon[eonIndex].mappedfreq3 == 0 && eon[eonIndex].mappedfreq2 != ((rds.rdsC & 0xFF) * 10 + 8750) && eon[eonIndex].mappedfreq != ((rds.rdsC & 0xFF) * 10 + 8750) && (rds.rdsC >> 8) > 0 && (rds.rdsC >> 8) < 205) { eon[eonIndex].mappedfreq3 = ((rds.rdsC & 0xFF) * 10 + 8750); } } } for (int i = 0; i < 20; i++) { for (int j = 0; j < 20 - i - 1; j++) { if (eon[j].pi == 0) continue; if (eon[j].pi > eon[j + 1].pi && eon[j + 1].pi != 0) { std::swap(eon[j].pi, eon[j + 1].pi); std::swap(eon[j].mappedfreq, eon[j + 1].mappedfreq); std::swap(eon[j].mappedfreq2, eon[j + 1].mappedfreq2); std::swap(eon[j].mappedfreq3, eon[j + 1].mappedfreq3); char temp6[6]; strncpy(temp6, eon[j].picode, sizeof(temp6) - 1); temp6[sizeof(temp6) - 1] = '\0'; strncpy(eon[j].picode, eon[j + 1].picode, sizeof(eon[j].picode) - 1); eon[j].picode[sizeof(eon[j].picode) - 1] = '\0'; strncpy(eon[j + 1].picode, temp6, sizeof(eon[j + 1].picode) - 1); eon[j + 1].picode[sizeof(eon[j + 1].picode) - 1] = '\0'; std::swap(eon[j].pty, eon[j + 1].pty); std::swap(eon[j].ps, eon[j + 1].ps); std::swap(eon[j].ta, eon[j + 1].ta); std::swap(eon[j].tp, eon[j + 1].tp); std::swap(eon[j].taset, eon[j + 1].taset); std::swap(eon[j].ptyset, eon[j + 1].ptyset); std::swap(eon_buffer[j], eon_buffer[j + 1]); std::swap(EONPStext[j], EONPStext[j + 1]); } } } } } break; case RDS_GROUP_15A: { if (showrdserrors == 3 || (!rdsBerrorThreshold && !rdsCerrorThreshold && !rdsDerrorThreshold)) { if (pslong_process && rds.stationNameLong.length() > 0) rds.hasLongPS = true; offset = (rds.rdsB & 0xf) * 4; // Get Long PS character segment pslong_buffer2[offset + 0] = pslong_buffer[offset + 0]; // Copy PS long buffer pslong_buffer2[offset + 1] = pslong_buffer[offset + 1]; pslong_buffer2[offset + 2] = pslong_buffer[offset + 2]; pslong_buffer2[offset + 3] = pslong_buffer[offset + 3]; pslong_buffer2[32] = '\0'; pslong_buffer[offset + 0] = rds.rdsC >> 8; // First character of segment pslong_buffer[offset + 1] = rds.rdsC & 0xff; // Second character of segment pslong_buffer[offset + 2] = rds.rdsD >> 8; // Thirth character of segment pslong_buffer[offset + 3] = rds.rdsD & 0xff; // Fourth character of segment pslong_buffer[32] = '\0'; byte endmarkerLPS = 32; bool foundendmarker = false; for (byte i = 0; i < endmarkerLPS; i++) { if (pslong_buffer[i] == 0x0d) { foundendmarker = true; endmarkerLPS = i; break; } } if ((offset == 0 || foundendmarker) && (pslong_process || !rds.fastps)) { // Last chars are received if (strcmp(pslong_buffer, pslong_buffer2) == 0) { // When no difference between current and buffer, let's go... pslong_process = true; PSLongtext = pslong_buffer; rds.stationNameLong = extractUTF8Substring(pslong_buffer, 0, endmarkerLPS, true); // Make sure PS Long does not exceed 32 characters rds.stationNameLong = trimTrailingSpaces(rds.stationNameLong); } } if (!pslong_process && rds.fastps) { // Let's get 2 runs of 32 PS characters fast and without refresh if (offset == 0) packet0long = true; if (offset == 4) packet1long = true; if (offset == 8) packet2long = true; if (offset == 16) packet3long = true; PSLongtext = pslong_buffer; rds.stationNameLong = extractUTF8Substring(pslong_buffer, 0, endmarkerLPS, true); rds.stationNameLong = trimTrailingSpaces(rds.stationNameLong); if ((packet0long && packet1long && packet2long && packet3long) || foundendmarker) pslong_process = true; // OK, we had one runs, now let's go the idle PS Long writing } } } break; } previous_rdsA = rds.rdsA; previous_rdsB = rds.rdsB; previous_rdsC = rds.rdsC; previous_rdsD = rds.rdsD; } } void TEF6686::clearRDS(bool fullsearchrds) { devTEF_Set_Cmd(TEF_FM, Cmd_Set_RDS, 9, fullsearchrds ? 3 : 1, 1, 0); rds.piBuffer.clear(); rds.stationName = ""; rds.stationText = ""; rds.stationNameLong = ""; rds.stationText32 = ""; rds.RTContent1 = ""; rds.RTContent2 = ""; rds.PTYN = ""; rds.ECCtext = ""; rds.stationIDtext = ""; rds.stationStatetext = ""; rds.enhancedRTtext = ""; PSLongtext = ""; uint8_t i; for (i = 0; i < 8; i++) { ps_buffer[i] = 0x20; PStext[i] = L'\0'; ptyn_buffer[i] = 0x20; PTYNtext[i] = L'\0'; } ps_buffer[8] = 0; ptyn_buffer[8] = 0; PStext[8] = L'\0'; PTYNtext[8] = L'\0'; for (i = 0; i < 64; i++) rt_buffer[i] = 0x20; rt_buffer[64] = 0; for (i = 0; i < 128; i++) eRT_buffer[i] = 0x20; eRT_buffer[128] = '\0'; for (i = 0; i < 32; i++) { rt_buffer32[i] = 0x20; pslong_buffer[i] = 0x20; pslong_buffer2[i] = 0x20; // PSLongtext[i] = L'\0'; } rt_buffer32[32] = 0; pslong_buffer[32] = 0; // PSLongtext[32] = L'\0'; for (i = 0; i < 17; i++) rds.stationType[i] = 0x20; rds.stationType[17] = 0; for (i = 0; i < 6; i++) rds.picode[i] = 0x20; rds.picode[6] = 0; for (i = 0; i < 4; i++) rds.dabafeid[i] = 0x20; rds.dabafeid[4] = 0; for (i = 0; i < 3; i++) rds.dabafchannel[i] = 0x20; rds.dabafchannel[3] = 0; for (i = 0; i < 8; i++) rds.stationID[i] = 0x20; rds.stationID[8] = 0; for (i = 0; i < 2; i++) rds.stationState[i] = 0x20; rds.stationState[2] = 0; for (i = 0; i < 50; i++) { af[i].frequency = 0; af[i].score = -32767; af[i].afvalid = true; af[i].checked = false; af[i].regional = false; af[i].same = false; } for (i = 0; i < 20; i++) { eon[i].pty = 254; eon[i].pi = 0; eon[i].ps = ""; eon[i].mappedfreq = 0; eon[i].mappedfreq2 = 0; eon[i].mappedfreq3 = 0; eon[i].ta = false; eon[i].tp = false; eon[i].taset = false; eon[i].ptyset = false; for (int y = 0; y < 5; y++) { eon[i].picode[y] = '\0'; } for (int j = 0; j < 9; j++) { EONPStext[i][j] = L'\0'; eon_buffer[i][j] = '\0'; } } for (i = 0; i < 45; i++) { RDSplus1[i] = 0; RDSplus2[i] = 0; } for (i = 0; i < 33; i++) rds.blockcounter[i] = 0; for (i = 0; i < 10; i++) rds.aid[i] = 0; rdsblock = 254; processed_rdsblocks = 0; piold = 0; rds.correctPI = 0; rds.ECC = 254; rds.stationTypeCode = 32; rds.dabaffreq = 0; rds.hasECC = false; rds.hasRT = false; rds.hasRDS = false; rds.hasTP = false; rds.hasAF = false; rds.hasTA = false; rds.hasEON = false; rds.hasCT = false; rds.hasTMC = false; rds.hasAID = false; rds.hasPTYN = false; rds.hasLongPS = false; rds.hasRDSplus = false; rds.hasDABAF = false; rds.hasEnhancedRT = false; rt_process = false; ps_process = false; pslong_process = false; rds.rdsreset = true; rds.hasArtificialhead = false; rds.hasCompressed = false; rds.hasDynamicPTY = false; rds.hasStereo = false; af_counter = 0; af_updatecounter = 0; eon_counter = 0; afreset = true; rds.rdsAerror = true; rds.rdsBerror = true; rds.rdsCerror = true; rds.rdsDerror = true; initrt = true; initab = true; rds.rdsplusTag1 = 169; rds.rdsplusTag2 = 169; afinit = false; errorfreepi = false; afmethodB = false; packet0 = false; packet1 = false; packet2 = false; packet3 = false; packet0long = false; packet1long = false; packet2long = false; packet3long = false; rds.aid_counter = 0; afmethodBprobe = false; afmethodBtrigger = false; correctPIold = 0; af_number = 0; _hasEnhancedRT = false; rds.ps12error = true; rds.ps34error = true; rds.ps56error = true; rds.ps78error = true; } void TEF6686::tone(uint16_t time, int16_t amplitude, uint16_t frequency) { devTEF_Set_Cmd(TEF_AUDIO, Cmd_Set_Mute, 5, 0); devTEF_Radio_Set_Wavegen(1, amplitude, frequency); delay(time); devTEF_Radio_Set_Wavegen(0, 0, 0); } void TEF6686::I2Sin(bool mode) { devTEF_Set_Cmd(TEF_AUDIO, Cmd_Set_Input, 5, mode ? 32 : 0); } String TEF6686::convertToUTF8(const wchar_t* input) { String output; while (*input) { uint32_t unicode = *input; if (unicode < 0x80) { output += (char)unicode; } else if (unicode < 0x800) { output += (char)(0xC0 | (unicode >> 6)); output += (char)(0x80 | (unicode & 0x3F)); } else if (unicode < 0x10000) { output += (char)(0xE0 | (unicode >> 12)); output += (char)(0x80 | ((unicode >> 6) & 0x3F)); output += (char)(0x80 | (unicode & 0x3F)); } else { output += (char)(0xF0 | (unicode >> 18)); output += (char)(0x80 | ((unicode >> 12) & 0x3F)); output += (char)(0x80 | ((unicode >> 6) & 0x3F)); output += (char)(0x80 | (unicode & 0x3F)); } input++; } return output; } String TEF6686::extractUTF8Substring(const String & utf8String, size_t start, size_t length, bool under) { String substring; size_t utf8Length = utf8String.length(); size_t utf8Index = 0; size_t charIndex = 0; while (utf8Index < utf8Length && charIndex < start + length) { uint8_t currentByte = utf8String.charAt(utf8Index); uint8_t numBytes = 0; if (currentByte < 0x80) numBytes = 1; else if ((currentByte >> 5) == 0x6) numBytes = 2; else if ((currentByte >> 4) == 0xE) numBytes = 3; else if ((currentByte >> 3) == 0x1E) numBytes = 4; if (charIndex >= start) substring += utf8String.substring(utf8Index, utf8Index + numBytes); utf8Index += numBytes; charIndex++; } if (under) { while (substring.length() < length) substring += '_'; } return substring; } void TEF6686::RDScharConverter(const char* input, wchar_t* output, size_t size, bool under) { for (size_t i = 0; i < size - 1; i++) { char currentChar = input[i]; switch (currentChar) { case 0x0A: if (under) output[i] = L'_'; else output[i] = L' '; break; case 0x20: if (under) output[i] = L'_'; else output[i] = L' '; break; case 0x21 ... 0x5D: output[i] = static_cast(currentChar); break; case 0x5E: output[i] = L'―'; break; case 0x5F: output[i] = L'_'; break; case 0x60: output[i] = L'`'; break; case 0x61 ... 0x7d: output[i] = static_cast(currentChar); break; case 0x7E: output[i] = L'¯'; break; case 0x7F: output[i] = L' '; break; case 0x80: output[i] = L'á'; break; case 0x81: output[i] = L'à'; break; case 0x82: output[i] = L'é'; break; case 0x83: output[i] = L'è'; break; case 0x84: output[i] = L'í'; break; case 0x85: output[i] = L'ì'; break; case 0x86: output[i] = L'ó'; break; case 0x87: output[i] = L'ò'; break; case 0x88: output[i] = L'ú'; break; case 0x89: output[i] = L'ù'; break; case 0x8A: output[i] = L'Ñ'; break; case 0x8B: output[i] = L'Ç'; break; case 0x8C: output[i] = L'Ş'; break; case 0x8D: output[i] = L'β'; break; case 0x8E: output[i] = L'¡'; break; case 0x8F: output[i] = L'IJ'; break; case 0x90: output[i] = L'â'; break; case 0x91: output[i] = L'ä'; break; case 0x92: output[i] = L'ê'; break; case 0x93: output[i] = L'ë'; break; case 0x94: output[i] = L'î'; break; case 0x95: output[i] = L'ï'; break; case 0x96: output[i] = L'ô'; break; case 0x97: output[i] = L'ö'; break; case 0x98: output[i] = L'û'; break; case 0x99: output[i] = L'ü'; break; case 0x9A: output[i] = L'ñ'; break; case 0x9B: output[i] = L'ç'; break; case 0x9C: output[i] = L'ş'; break; case 0x9D: output[i] = L'ǧ'; break; case 0x9E: output[i] = L'ı'; break; case 0x9F: output[i] = L'ij'; break; case 0xA0: output[i] = L'ª'; break; case 0xA1: output[i] = L'α'; break; case 0xA2: output[i] = L'©'; break; case 0xA3: output[i] = L'‰'; break; case 0xA4: output[i] = L'Ǧ'; break; case 0xA5: output[i] = L'ě'; break; case 0xA6: output[i] = L'ň'; break; case 0xA7: output[i] = L'ő'; break; case 0xA8: output[i] = L'π'; break; case 0xA9: output[i] = L'€'; break; case 0xAA: output[i] = L'£'; break; case 0xAB: output[i] = L'$'; break; case 0xAC: output[i] = L'←'; break; case 0xAD: output[i] = L'↑'; break; case 0xAE: output[i] = L'→'; break; case 0xAF: output[i] = L'↓'; break; case 0xB0: output[i] = L'º'; break; case 0xB1: output[i] = L'¹'; break; case 0xB2: output[i] = L'²'; break; case 0xB3: output[i] = L'³'; break; case 0xB4: output[i] = L'±'; break; case 0xB5: output[i] = L'İ'; break; case 0xB6: output[i] = L'ń'; break; case 0xB7: output[i] = L'ű'; break; case 0xB8: output[i] = L'µ'; break; case 0xB9: output[i] = L'¿'; break; case 0xBA: output[i] = L'÷'; break; case 0xBB: output[i] = L'°'; break; case 0xBC: output[i] = L'¼'; break; case 0xBD: output[i] = L'½'; break; case 0xBE: output[i] = L'¾'; break; case 0xBF: output[i] = L'§'; break; case 0xC0: output[i] = L'Á'; break; case 0xC1: output[i] = L'À'; break; case 0xC2: output[i] = L'É'; break; case 0xC3: output[i] = L'È'; break; case 0xC4: output[i] = L'Í'; break; case 0xC5: output[i] = L'Ì'; break; case 0xC6: output[i] = L'Ó'; break; case 0xC7: output[i] = L'Ò'; break; case 0xC8: output[i] = L'Ú'; break; case 0xC9: output[i] = L'Ù'; break; case 0xCA: output[i] = L'Ř'; break; case 0xCB: output[i] = L'Č'; break; case 0xCC: output[i] = L'Š'; break; case 0xCD: output[i] = L'Ž'; break; case 0xCE: output[i] = L'Ð'; break; case 0xCF: output[i] = L'Ŀ'; break; case 0xD0: output[i] = L'Â'; break; case 0xD1: output[i] = L'Ä'; break; case 0xD2: output[i] = L'Ê'; break; case 0xD3: output[i] = L'Ë'; break; case 0xD4: output[i] = L'Î'; break; case 0xD5: output[i] = L'Ï'; break; case 0xD6: output[i] = L'Ô'; break; case 0xD7: output[i] = L'Ö'; break; case 0xD8: output[i] = L'Û'; break; case 0xD9: output[i] = L'Ü'; break; case 0xDA: output[i] = L'ř'; break; case 0xDB: output[i] = L'č'; break; case 0xDC: output[i] = L'š'; break; case 0xDD: output[i] = L'ž'; break; case 0xDE: output[i] = L'đ'; break; case 0xDF: output[i] = L'ŀ'; break; case 0xE0: output[i] = L'Ã'; break; case 0xE1: output[i] = L'Å'; break; case 0xE2: output[i] = L'Æ'; break; case 0xE3: output[i] = L'Œ'; break; case 0xE4: output[i] = L'ŷ'; break; case 0xE5: output[i] = L'Ý'; break; case 0xE6: output[i] = L'Õ'; break; case 0xE7: output[i] = L'Ø'; break; case 0xE8: output[i] = L'Þ'; break; case 0xE9: output[i] = L'Ŋ'; break; case 0xEA: output[i] = L'Ŕ'; break; case 0xEB: output[i] = L'Ć'; break; case 0xEC: output[i] = L'Ś'; break; case 0xED: output[i] = L'Ź'; break; case 0xEE: output[i] = L'Ŧ'; break; case 0xEF: output[i] = L'ð'; break; case 0xF0: output[i] = L'ã'; break; case 0xF1: output[i] = L'å'; break; case 0xF2: output[i] = L'æ'; break; case 0xF3: output[i] = L'œ'; break; case 0xF4: output[i] = L'ŵ'; break; case 0xF5: output[i] = L'ý'; break; case 0xF6: output[i] = L'õ'; break; case 0xF7: output[i] = L'ø'; break; case 0xF8: output[i] = L'þ'; break; case 0xF9: output[i] = L'ŋ'; break; case 0xFA: output[i] = L'ŕ'; break; case 0xFB: output[i] = L'ć'; break; case 0xFC: output[i] = L'ś'; break; case 0xFD: output[i] = L'ź'; break; case 0xFE: output[i] = L'ŧ'; break; case 0xFF: output[i] = L' '; break; } } output[size - 1] = L'\0'; } String TEF6686::trimTrailingSpaces(String str) { int end = str.length() - 1; while (end >= 0 && isspace(str[end])) end--; return str.substring(0, end + 1); } String TEF6686::ucs2ToUtf8(const char* ucs2Input) { String utf8Output; size_t length = 0; while (ucs2Input[length] != '\0' || ucs2Input[length + 1] != '\0') length += 2; for (size_t i = 0; i < length; i += 2) { uint16_t ucs2Char = ((uint8_t)ucs2Input[i] << 8) | (uint8_t)ucs2Input[i + 1]; if (ucs2Char <= 0x7F) utf8Output += (char)ucs2Char; else if (ucs2Char <= 0x7FF) { utf8Output += (char)(0xC0 | (ucs2Char >> 6)); utf8Output += (char)(0x80 | (ucs2Char & 0x3F)); } else { utf8Output += (char)(0xE0 | (ucs2Char >> 12)); utf8Output += (char)(0x80 | ((ucs2Char >> 6) & 0x3F)); utf8Output += (char)(0x80 | (ucs2Char & 0x3F)); } } return utf8Output; } bool TEF6686::isFixedCallsign(uint16_t stationID, char* stationIDStr) { for (int i = 0; i < sizeof(fixedPI) / sizeof(fixedPI[0]); i++) { if (stationID == fixedPI[i]) { strcpy(stationIDStr, fixedCalls[i]); // Assign fixed callsign return true; } } return false; }