/// <summary> /// Processes a 2B "RadioText" frame type 0b00101 /// </summary> /// <param name="frame"></param> private void ProcessFramePayload_RadioTextB(RDSFrame frame) { //Read the header flags bool flagAb = 1 == ((frame.b >> (HEADER_OFFSET - 0)) & 0b0000000000000001); //Get the index and multiply it by 2, as that is the spacing int addressIndex = (frame.b & 0b0000000000001111) * 2; //Check if we should reset if (flagAb != _rtLastAbFlag && addressIndex == 0) { //Clear buffer for (int i = 0; i < rtBuffer.Length; i++) { rtBuffer[i] = (char)0x00; } //Update state _rtLastAbFlag = flagAb; //Send event OnRtBufferCleared?.Invoke(this); } //Write all rtBuffer[addressIndex + 0] = (char)((frame.d >> 8) & 0x00FF); rtBuffer[addressIndex + 1] = (char)((frame.d >> 0) & 0x00FF); //Search this string to see if we reached the end int endIndex = -1; for (int i = addressIndex; i < addressIndex + 4; i++) { if (rtBuffer[i] == (char)0x0A) { endIndex = i; break; } } //Send buffer updated events OnRtBufferUpdated?.Invoke(this, rtBuffer); //Handle ending if we did if (endIndex != -1) { //Convert to string and update state rtText = new string(rtBuffer, 0, endIndex); rtComplete = true; //Send event OnRtTextUpdated?.Invoke(this, rtText); } }
public void ProcessFrame(RDSFrame frame) { //Get the PI code from the first block. We flip this because that's how it's familiar ushort piCode = (ushort)((frame.a >> 8) | (frame.a << 8)); //Decode header data that will always exist byte groupTypeCode = (byte)((frame.b >> (16 - 5)) & 0b0000000000011111); bool trafficSupported = 1 == ((frame.b >> (16 - 6)) & 0b0000000000000001); byte programTypeCode = (byte)((frame.b >> (16 - 11)) & 0b0000000000011111); //Send updated events for these if (this.piCode != piCode) { this.piCode = piCode; OnPiCodeUpdated?.Invoke(this, piCode); } if (this.trafficSupported != trafficSupported) { this.trafficSupported = trafficSupported; OnTrafficSupportedUpdated?.Invoke(this, trafficSupported); } if (this.programTypeCode != programTypeCode) { this.programTypeCode = programTypeCode; OnProgramTypeUpdated?.Invoke(this, programTypeCode); } //We'll now handle decoding of this frame switch (groupTypeCode) { case 0b00000: ProcessFramePayload_BasicInfo(frame); break; case 0b00001: ProcessFramePayload_BasicInfo(frame); break; case 0b00100: ProcessFramePayload_RadioTextA(frame); break; case 0b00101: ProcessFramePayload_RadioTextB(frame); break; case 0b01000: ProcessFramePayload_ClockTime(frame); break; } }
/// <summary> /// Processes 0A and 0B "basic tuning and switching" frame types 0b00000 and 0b00001 /// </summary> private void ProcessFramePayload_BasicInfo(RDSFrame frame) { //Read the header flags bool flagTa = 1 == ((frame.b >> (HEADER_OFFSET - 0)) & 0b0000000000000001); bool flagMs = 1 == ((frame.b >> (HEADER_OFFSET - 1)) & 0b0000000000000001); bool flagDi = 1 == ((frame.b >> (HEADER_OFFSET - 2)) & 0b0000000000000001); //Get the code bits. These are just used for the text for the most part just for the PS name byte decoderControlCode = (byte)((frame.b >> (HEADER_OFFSET - 4)) & 0b0000000000000011); //Get the two PS code characters char psA = (char)((frame.d >> 8) & 0x00FF); char psB = (char)((frame.d >> 0) & 0x00FF); //Write to the PS buffer psBuffer[(decoderControlCode * 2) + 0] = psA; psBuffer[(decoderControlCode * 2) + 1] = psB; //Set flags for the characters updated if they're in sequential order if (decoderControlCode == psUpdatedFrameIndex) { psUpdatedFrameIndex++; } //Check if we've completed it if (psUpdatedFrameIndex == 4) { //Completed! //Update state psName = new string(psBuffer); psComplete = true; psUpdatedFrameIndex = 0; //Fire events OnPsNameUpdated?.Invoke(this, psName); } //Send updated events OnPsBufferUpdated?.Invoke(this, psBuffer); }
/// <summary> /// Processes a 4A "Clock-time and date" frame 0b01000 /// </summary> private void ProcessFramePayload_ClockTime(RDSFrame frame) { //Read julian day code uint dayCode = (uint)((frame.c >> 1) & 0b0111111111111111); dayCode |= (uint)(((frame.b >> (HEADER_OFFSET - 4)) & 0b0000000000000011) << 15); //Decode day code according to Annex G of the linked document //This is in UTC int year = (int)((dayCode - 15078.2f) / 365.25f); int month = (int)((dayCode - 14956.1f - (int)(year * 365.25f)) / 30.6001f); int day = (int)dayCode - 14956 - ((int)(year * 365.25f)) - ((int)(month * 30.6001f)); int k; if (month == 14 || month == 15) { k = 1; } else { k = 0; } year += 1900 + k; month -= 1 - (k * 12); //Get the hour int localHour = ((frame.d >> 12) & 0b0000000000001111); localHour |= ((frame.c & 0b0000000000000001) << 4); //Get the minute int localMinute = ((frame.d >> 6) & 0b0000000000111111); //Get the local time offset, in half hours. Also calculate the sign int localOffsetParts = (frame.d & 0b0000000000011111); if ((frame.d & 0b0000000000100000) != 0) { localOffsetParts = -localOffsetParts; } DateTime time; TimeSpan localOffset; DateTime localTime; try { //Create a UTC DateTime from the UTC portion time = new DateTime(year, month, day, 0, 0, 0, 0, DateTimeKind.Local); //Convert the local hour and minute to a TimeSpan we'll save localOffset = new TimeSpan(0, 30 * localOffsetParts, 0); //Convert to local time localTime = time.AddMinutes((localHour * 60) + (localOffsetParts * 30) + localMinute); } catch { //Ignore. If this was a corrupted RDS frame, this would've thrown an exception return; } //Set this.timeLast = localTime; this.timeOffset = localOffset; this.timeComplete = true; //Send event OnTimeUpdated?.Invoke(this, localTime, localOffset); }
/// <summary> /// Processes a 2A "RadioText" frame type 0b00100 /// </summary> /// <param name="frame"></param> private void ProcessFramePayload_RadioTextA(RDSFrame frame) { //Read the header flags bool flagAb = 1 == ((frame.b >> (HEADER_OFFSET - 0)) & 0b0000000000000001); //Get the index and multiply it by 4, as that is the spacing int addressIndex = (frame.b & 0b0000000000001111) * 4; //Check if we should reset if (flagAb != _rtLastAbFlag && addressIndex == 0) { //Clear buffer for (int i = 0; i < rtBuffer.Length; i++) { rtBuffer[i] = (char)0x00; } //Update state _rtLastAbFlag = flagAb; //Send event OnRtBufferCleared?.Invoke(this); } //Write all rtBuffer[addressIndex + 0] = (char)((frame.c >> 8) & 0x00FF); rtBuffer[addressIndex + 1] = (char)((frame.c >> 0) & 0x00FF); rtBuffer[addressIndex + 2] = (char)((frame.d >> 8) & 0x00FF); rtBuffer[addressIndex + 3] = (char)((frame.d >> 0) & 0x00FF); //Search this string to see if we reached the end int endIndex = -1; for (int i = addressIndex; i < addressIndex + 4; i++) { //Official spec claims that the character 0x0A should be used to indicate the end, but some stations in my area don't do that. //We also check \r for this reason. Cumulus Media stations are using \r instead. if (rtBuffer[i] == (char)0x0A || rtBuffer[i] == '\r') { endIndex = i; break; } } //Send buffer updated events OnRtBufferUpdated?.Invoke(this, rtBuffer); //Handle ending if we did if (endIndex != -1) { //Make sure this is really the end. We should have no null characters up to this point for (int i = 0; i < endIndex; i++) { if (rtBuffer[i] == (char)0x00) { return; } } //Convert to string and update state rtText = new string(rtBuffer, 0, endIndex); rtComplete = true; //Send event OnRtTextUpdated?.Invoke(this, rtText); } }