/* * Copyright (C) 2009, 2010 by Nicole Faerber * * This program is free software; you can redistribute it and/or * modify it under the terms of the GNU General Public License * as published by the Free Software Foundation; either version * 2 of the License, or (at your option) any later version. * */ #include #include #include #include #include #include #include #include #include #include "rds.h" #include "rds_consts.h" #include "tmc.h" //#define DEBUG 1 static struct rds_info_s rds_info; static struct tm rds_time; static struct _RDS_Private { void (*rds_PI_cb)(unsigned short PI, unsigned char ccode, unsigned char ptype, unsigned char pref, void *user_data); void *rds_PI_cb_data; void (*rds_sname_cb)(char *sname, void *user_data); void *rds_sname_cb_data; void (*rds_sinfo_cb)(unsigned char TP, unsigned char TA, unsigned char MS, unsigned char PTY, void *user_data); void *rds_sinfo_cb_data; void (*rds_radiotext_cb)(char *radio_text, void *user_data); void *rds_radiotext_cb_data; void (*rds_date_time_cb)(struct tm *dtime, void *user_data); void *rds_date_time_cb_data; void (*rds_afinfo_cb)(unsigned char cnt, double *AF, void *user_data); void *rds_afinfo_cb_data; } _rds_private; void rds_init(void) { memset(&_rds_private, 0, sizeof(struct _RDS_Private)); } void rds_set_PI_cb(void (*rds_PI_cb)(unsigned short PI, unsigned char ccode, unsigned char ptype, unsigned char pref, void *user_data), void *user_data) { _rds_private.rds_PI_cb = rds_PI_cb; _rds_private.rds_PI_cb_data = user_data; } void rds_set_sname_cb(void (*rds_sname_cb)(char *sname, void *user_data), void *user_data) { _rds_private.rds_sname_cb = rds_sname_cb; _rds_private.rds_sname_cb_data = user_data; } void rds_set_sinfo_cb(void (*rds_sinfo_cb)(unsigned char TP, unsigned char TA, unsigned char MS, unsigned char PTY, void *user_data), void *user_data) { _rds_private.rds_sinfo_cb = rds_sinfo_cb; _rds_private.rds_sinfo_cb_data = user_data; } void rds_set_afinfo_cb(void (*rds_afinfo_cb)(unsigned char cnt, double *AF, void *user_data), void *user_data) { _rds_private.rds_afinfo_cb = rds_afinfo_cb; _rds_private.rds_afinfo_cb_data = user_data; } void rds_set_radiotext_cb(void (*rds_radiotext_cb)(char *radio_text, void *user_data), void *user_data) { _rds_private.rds_radiotext_cb = rds_radiotext_cb; _rds_private.rds_radiotext_cb_data = user_data; } void rds_set_date_time_cb(void (*rds_date_time_cb)(struct tm *dtime, void *user_data), void *user_data) { _rds_private.rds_date_time_cb = rds_date_time_cb; _rds_private.rds_date_time_cb_data = user_data; } int rds_receive_group(int rds_fd, unsigned short *rdsgroup) { static unsigned char expected = 0; int rb; unsigned char rbuf[3]; unsigned short block; unsigned char offset; rb = read(rds_fd, rbuf, 3); if (rb <= 0) return 0; if (rb != 3) { printf("#read err rb=%d\n", rb); return 0; } block = rbuf[0] | (rbuf[1] << 8); offset = rbuf[2] & 0x07; if (rbuf[2] & 0x80) { if (OutputFlags & RDS_RECEIVE_INDICATOR) printf("E"); } else { // printf("group block=0x%04x offs=0x%02x\n", block, offset); if (offset != expected) { if (OutputFlags & RDS_RECEIVE_INDICATOR) printf("block out of sync - resetting\n"); expected = 0; memset(rdsgroup, 0, sizeof(rdsgroup)); } else { rdsgroup[offset] = block; expected++; if (expected >= 4) { expected = 0; if (OutputFlags & RDS_RECEIVE_INDICATOR) printf(".\n"); return 1; } } } if (OutputFlags & RDS_RECEIVE_INDICATOR) printf("."); return 0; } enum RDSGroupType { GROUP_0A=0, GROUP_0B, GROUP_1A, GROUP_1B, GROUP_2A, GROUP_2B, GROUP_3A, GROUP_3B, GROUP_4A, GROUP_4B, GROUP_5A, GROUP_5B, GROUP_6A, GROUP_6B, GROUP_7A, GROUP_7B, GROUP_8A, GROUP_8B, GROUP_9A, GROUP_9B, GROUP_10A, GROUP_10B, GROUP_11A, GROUP_11B, GROUP_12A, GROUP_12B, GROUP_13A, GROUP_13B, GROUP_14A, GROUP_14B, GROUP_15A, GROUP_15B, GROUP_UNKNOWN }; void rds_radio_retuned(void) { memset(&rds_info, 0, sizeof(rds_info)); rds_info.LTN = -1; memset(&rds_time, 0, sizeof(rds_time)); } void rds_decode_group(unsigned short *rdsgroup) { static unsigned short ogrp[4]; static unsigned char grp_decoded = 0; static unsigned char sname_rcvd = 0; unsigned char grp_type = (rdsgroup[1] >> 11); unsigned char offs; static unsigned char otextAB = 0, newtext = 0; unsigned short PI = rdsgroup[0]; unsigned char textAB = 0; int local_time_off = 0; int day_code = 0; int year_, mon_, K; /* we want to wait for at least two identical group transmits */ if (ogrp[0] != rdsgroup[0] || ogrp[1] != rdsgroup[1] || ogrp[2] != rdsgroup[2] || ogrp[3] != rdsgroup[3]) { ogrp[0] = rdsgroup[0]; ogrp[1] = rdsgroup[1]; ogrp[2] = rdsgroup[2]; ogrp[3] = rdsgroup[3]; //printf("grp: 0x%04x 0x%04x 0x%04x 0x%04x\n", rdsgroup[0], rdsgroup[1], rdsgroup[2], rdsgroup[3]); grp_decoded = 0; return; } if (grp_decoded) return; if (rds_info.PI != PI) { /* station change? */ memset(&rds_info, 0, sizeof(rds_info)); rds_info.LTN = -1; memset(&rds_time, 0, sizeof(rds_time)); rds_info.ccode = (PI & 0xf000) >> 12; rds_info.ptype = (PI & 0x0f00) >> 8; rds_info.pref = (PI & 0x00ff); sname_rcvd = 0; if (rds_info.pref == 0) /* something is wrong here */ return; rds_info.PI = rdsgroup[0]; if (_rds_private.rds_PI_cb != NULL) _rds_private.rds_PI_cb(rds_info.PI, rds_info.ccode, rds_info.ptype, rds_info.pref, _rds_private.rds_PI_cb_data); if (OutputFlags & RDS_OUTPUT_RDSINFO) printf("PI=%d ccode=%X ptype=%X '%s' '%s' pref=%d\n", rds_info.PI, rds_info.ccode, rds_info.ptype, ptype_stext[rds_info.ptype], ptype_ltext[rds_info.ptype], rds_info.pref); } switch (grp_type) { case GROUP_0A: { /* basic switching and tuning */ unsigned char AFc1, AFc2; float AF1=0, AF2=0; offs = (rdsgroup[1] & 0x03); if (offs == 0) sname_rcvd = 0; if (offs == 1 && sname_rcvd == 0) sname_rcvd = 1; if (offs == 2 && sname_rcvd == 1) sname_rcvd = 2; if (offs == 3 && sname_rcvd == 2) sname_rcvd = 3; rds_info.sname[offs*2] = ((rdsgroup[3] & 0xff00) >> 8); rds_info.sname[(offs*2)+1] = rdsgroup[3] & 0x00ff; if (_rds_private.rds_sname_cb != NULL && sname_rcvd == 3) { _rds_private.rds_sname_cb(rds_info.sname, _rds_private.rds_sname_cb_data); sname_rcvd = 0; } rds_info.TA = (rdsgroup[1] & 0x10) >> 4; rds_info.TP = (rdsgroup[1] & 0x400) >> 10; rds_info.MS = (rdsgroup[1] & 0x08) >> 3; rds_info.PTY = (rdsgroup[1] & 0x3e0) >> 5; if (_rds_private.rds_sinfo_cb != NULL) _rds_private.rds_sinfo_cb(rds_info.TP, rds_info.TA, rds_info.MS, rds_info.PTY, _rds_private.rds_sinfo_cb_data); AFc1 = ((rdsgroup[2] & 0xff00) >> 8); AFc2 = (rdsgroup[2] & 0x00ff); if (AFc1 > 225 && AFc1 < 250) { if (OutputFlags & RDS_OUTPUT_STATION_ID) printf(" %d AFs follow:\n", AFc1-224); } else if (AFc1 > 0 && AFc1 < 205) AF1 = (float)87.5 + ((float)AFc1 * .1); else if (AFc1 == 205) printf(" filler- \n"); if (AFc2 > 0 && AFc2 < 205) AF2 = (float)87.5 + ((float)AFc2 * .1); if (OutputFlags & RDS_OUTPUT_STATION_ID) printf("sname = '%s' %s %s %s AF1=%3.2f AF2=%3.2f PTY='%s'\n", rds_info.sname, rds_info.TP ? "TP" : "", rds_info.TA ? "TA" : "", rds_info.MS ? "M" : "S", AF1, AF2, PTY_text[rds_info.PTY]); } break; case GROUP_0B: if (OutputFlags & RDS_OUTPUT_UNKNGRP) printf("group 0B\n"); break; case GROUP_1A: { /* Programme Item Number and slow labelling codes */ unsigned char variant, day, hour, minute; unsigned short tmc_id; variant = (rdsgroup[2] & (0x07 << 12)) >> 12; day = (rdsgroup[3] & (0x1f << 11)) >> 11; hour = (rdsgroup[3] & (0x1f << 6)) >> 6; minute = (rdsgroup[3] & 0x3f); if (OutputFlags & RDS_OUTPUT_UNKNGRP) printf("1A var=%d day=%d %d:%d ", variant, day, hour, minute); if (variant == 0) { rds_info.ECC = (rdsgroup[2] & 0xff); printf(" ccode=%d ECC=0x%02x '%s' ", rds_info.ccode, rds_info.ECC, ECC_text[((rds_info.ECC & 0x0f)*16)+(rds_info.ccode-1)]); } if (variant == 1) { tmc_id = (rdsgroup[2] & 0x0fff); printf(" TMC ID=0x%04x", tmc_id); } if (variant == 2) { printf(" Paging ID=0x%04x", (rdsgroup[2] & 0x0fff)); } if (variant == 3) { rds_info.lang_code = (rdsgroup[2] & 0xff); printf(" language=0x%02x ", rds_info.lang_code); switch (rds_info.lang_code) { case 0x03: printf("Catalan "); break; case 0x08: printf("German "); break; case 0x0a: printf("Spanish "); break; case 0x15: printf("Italian "); break; case 0x18: printf("Latvian "); break; case 0x1d: printf("Dutch "); break; case 0x27: printf("Finnish "); break; default: printf("unknown "); break; }; } if (variant == 7) { printf(" EWS Channel ID=0x%04x", (rdsgroup[2] & 0x0fff)); } printf("\n"); } break; case GROUP_1B: if (OutputFlags & RDS_OUTPUT_UNKNGRP) printf("group 1B\n"); break; case GROUP_2A: /* 2A has 64 chars, 4 per group */ case GROUP_2B: /* 2B has 32 chars, 2 per group */ offs = (rdsgroup[1] & 0x0f); textAB = (rdsgroup[1] & 0x10); if (textAB != otextAB) { memset(rds_info.rtext, ' ', 64); rds_info.rtext[64] = '\0'; } otextAB = textAB; if (grp_type == GROUP_2A) { rds_info.rtext[offs*4] = ((rdsgroup[2] & 0xff00) >> 8); rds_info.rtext[(offs*4)+1] = (rdsgroup[2] & 0x00ff); rds_info.rtext[(offs*4)+2] = ((rdsgroup[3] & 0xff00) >> 8); rds_info.rtext[(offs*4)+3] = (rdsgroup[3] & 0x00ff); } else { rds_info.rtext[offs*2] = ((rdsgroup[3] & 0xff00) >> 8); rds_info.rtext[(offs*2)+1] = (rdsgroup[3] & 0x00ff); } if (offs == 0) /* new text always starts at 0 */ newtext = 1; if ((offs == 15)) { /* all parts received */ if (newtext == 1) { if (_rds_private.rds_radiotext_cb != NULL) _rds_private.rds_radiotext_cb(rds_info.rtext, _rds_private.rds_radiotext_cb_data); if (OutputFlags & RDS_OUTPUT_RADIO_TEXT) printf("RT '%s'\n", rds_info.rtext); newtext = 0; } } break; case GROUP_3A: /* ODA - TMC alarm, */ rds_info.AID = rdsgroup[3]; /* TMC AID */ if (rds_info.AID == 0xcd46 || rds_info.AID == 0x0d45) { if ((rdsgroup[2] & 0xc000) == 0) { rds_info.LTN = (rdsgroup[2] & 0x0fc0) >> 6; rds_info.AFI = (rdsgroup[2] & 0x0020) >> 5; rds_info.M = (rdsgroup[2] & 0x0010) >> 4; rds_info.I = (rdsgroup[2] & 0x0008) >> 3; rds_info.N = (rdsgroup[2] & 0x0004) >> 2; rds_info.R = (rdsgroup[2] & 0x0002) >> 1; rds_info.U = (rdsgroup[2] & 0x0001); } if (rdsgroup[2] & 0x4000) { /* SID */ if (rds_info.M) { rds_info.G = (rdsgroup[2] & 0x3000) >> 12; rds_info.Ta = (rdsgroup[2] & 0x0030) >> 4; rds_info.Tw = (rdsgroup[2] & 0x000c) >> 2; rds_info.Td = (rdsgroup[2] & 0x0003); } else { } } if (OutputFlags & RDS_OUTPUT_RDSINFO) { printf("AID = 0x%04x\n", rds_info.AID); printf("LTN ID = %d (0x%02x)\n", rds_info.LTN, rds_info.LTN); printf("Traffic information: "); if (rds_info.I) printf("international "); if (rds_info.N) printf("national "); if (rds_info.R) printf("regional "); if (rds_info.U) printf("urban "); printf("\n"); } } else if (rds_info.AID == 0x4bd7) { if (OutputFlags & RDS_OUTPUT_RDSINFO) { printf("RT+\nG2 = 0x%04x\n", rdsgroup[2]); printf("template = 0x%02x\n", (rdsgroup[2] & 0x00ff)); printf("SCB = 0x%02x\n", (rdsgroup[2] & 0x0f00)>>8); printf("CB = 0x%02x\n", (rdsgroup[2] & 0x1000)>>12); printf("rfu = 0x%02x\n", (rdsgroup[2] & 0xe000)>>13); } } else { printf("3A AID=0x%04x\n", rds_info.AID); } break; case GROUP_3B: if (OutputFlags & RDS_OUTPUT_UNKNGRP) printf("group 3B\n"); break; case GROUP_4A: //printf("group 4A - time/date\n"); rds_time.tm_sec = 0; // date/time message is sent at 00 secs +/- .2sec rds_time.tm_min = ((rdsgroup[3] & (0x3f << 6)) >> 6); rds_time.tm_hour = ((rdsgroup[3] & (0x0f << 12)) >> 12) | ((rdsgroup[2] & 0x01) << 4); local_time_off = (rdsgroup[3] & 0x0f) * ((rdsgroup[3] & 0x10) ? -1 : 1); rds_time.tm_hour += local_time_off / 2; rds_time.tm_min = (rds_time.tm_min + ((local_time_off % 2) * 30)) % 60; day_code = (((rdsgroup[2] & 0xfffe) >> 1) | ((rdsgroup[1] & 0x03) << 15)) /*+ local_time_off*/; year_ = ((float)day_code - 15078.2) / 365.25; mon_ = ((day_code - 14956.1) - (int)(year_ * 365.25)) / 30.6001; rds_time.tm_mday = day_code - 14956 - (int)(year_ * 365.25) - (int)(mon_ * 30.6001); if (mon_ == 14 || mon_ == 15) K = 1; else K = 0; rds_time.tm_year = year_ + K; rds_time.tm_mon = mon_ - 1 - (K * 12) - 1; rds_time.tm_isdst = -1; if (_rds_private.rds_date_time_cb != NULL) _rds_private.rds_date_time_cb(&rds_time, _rds_private.rds_date_time_cb_data); if (OutputFlags & RDS_OUTPUT_DATETIME) printf("%s", asctime(&rds_time)); #ifdef DEBUG printf("%d:%02d - local time offset = %d, %s (dcode = %d)\n", rds_time.tm_hour, rds_time.tm_min, local_time_off, asctime(&rds_time), day_code); #endif //printf("day=%d month=%d year=%d\n", day, mon, year); break; case GROUP_4B: if (OutputFlags & RDS_OUTPUT_UNKNGRP) printf("GRP4B\n"); break; case GROUP_5A: if (OutputFlags & RDS_OUTPUT_UNKNGRP) printf("GRP5A\n"); break; case GROUP_5B: if (OutputFlags & RDS_OUTPUT_UNKNGRP) printf("GRP5B\n"); break; case GROUP_6A: if (OutputFlags & RDS_OUTPUT_UNKNGRP) printf("GRP6A\n"); break; case GROUP_6B: if (OutputFlags & RDS_OUTPUT_UNKNGRP) printf("GRP6B\n"); break; case GROUP_7A: if (OutputFlags & RDS_OUTPUT_UNKNGRP) printf("GRP7A\n"); break; case GROUP_7B: if (OutputFlags & RDS_OUTPUT_UNKNGRP) printf("GRP7B\n"); break; case GROUP_8A: if (rds_info.LTN != 0) // non TMCpro decode_tmc(rdsgroup); if (rds_info.LTN == 0) printf("TMCpro\n"); break; case GROUP_8B: if (OutputFlags & RDS_OUTPUT_UNKNGRP) printf("GRP8B\n"); break; case GROUP_9A: if (OutputFlags & RDS_OUTPUT_UNKNGRP) printf("GRP9A\n"); break; case GROUP_9B: if (OutputFlags & RDS_OUTPUT_UNKNGRP) printf("GRP9B\n"); break; case GROUP_10A: if (OutputFlags & RDS_OUTPUT_UNKNGRP) printf("GRP10A\n"); break; case GROUP_10B: if (OutputFlags & RDS_OUTPUT_UNKNGRP) printf("GRP10B\n"); break; case GROUP_11A: /* Open Data Application ODA */ if (OutputFlags & RDS_OUTPUT_UNKNGRP) printf("GRP11A ODA: #1=0x%02x #2=0x%04x #3=0x%04x\n", rdsgroup[1] & 0x1f, rdsgroup[2], rdsgroup[3]); /* we previously got an RT+ identifier, try RT+ decoding */ if (rds_info.AID == 0x4bd7) { printf("RT+\ntoggle: %s\n", (rdsgroup[1] & 0x10) ? "yes" : "no"); printf("item running : %s\n", (rdsgroup[1] & 0x08) ? "yes" : "no"); printf("content type 1 : %d\n", (rdsgroup[1] & 0x07) << 3 | (rdsgroup[2] & 0x0e00) >> 13); printf("start marker 1 : %d\n", (rdsgroup[2] & 0x1f80) >> 7); printf("length marker 1: %d\n", (rdsgroup[2] & 0x007e) >> 1); printf("content type 2 : %d\n", (rdsgroup[1] & 0x01) << 4); printf("start marker 2 : %d\n", (rdsgroup[2] & 0x07e0) >> 5); printf("length marker 2: %d\n", (rdsgroup[2] & 0x001f)); } break; case GROUP_11B: /* Open Data Application ODA */ if (OutputFlags & RDS_OUTPUT_UNKNGRP) printf("GRP11B\n"); printf("11B ODA: #1=0x%02x PI=0x%04x #3=0x%04x\n", rdsgroup[1] & 0x1f, rdsgroup[2], rdsgroup[3]); break; case GROUP_12A: /* Open Data Application ODA */ if (OutputFlags & RDS_OUTPUT_UNKNGRP) printf("GRP12A\n"); break; case GROUP_12B: /* Open Data Application ODA */ if (OutputFlags & RDS_OUTPUT_UNKNGRP) printf("GRP12B\n"); break; case GROUP_13A: /* Open Data Application ODA or paging */ if (OutputFlags & RDS_OUTPUT_UNKNGRP) printf("GRP13A\n"); break; case GROUP_13B: /* Open Data Application ODA */ if (OutputFlags & RDS_OUTPUT_UNKNGRP) printf("GRP13B\n"); break; case GROUP_14A: /* EON */ { unsigned char variant = rdsgroup[1] & 0x0f; unsigned char AFc1=0, AFc2=0; float AF1=0, AF2=0; unsigned short PI; rds_info.PTY = (rdsgroup[1] & 0x3e0) >> 5; rds_info.TPTN = (rdsgroup[1] & 0x400) >> 10; rds_info.TPON = (rdsgroup[1] & 0x10) >> 4; PI = rdsgroup[3]; if (variant <= 3) { // PS rds_info.PS[variant*2] = ((rdsgroup[2] & 0xff00) >> 8); rds_info.PS[(variant*2)+1] = (rdsgroup[2] & 0x00ff); } else if (variant == 4) { printf("EON variant 4\n"); } else if (variant >= 5 && variant <= 8) { // AF - alternate frequency AFc1 = ((rdsgroup[2] & 0xff00) >> 8); AFc2 = (rdsgroup[2] & 0x00ff); if (AFc1 > 0 && AFc1 < 205) AF1 = (float)87.5 + ((float)AFc1 * .1); else if (AFc1 >= 225 && AFc1 <= 249) AF1 = AFc1 - 224; if (AFc2 > 0 && AFc2 < 205) AF2 = (float)87.5 + ((float)AFc2 * .1); else if (AFc2 >= 225 && AFc2 <= 249) AF2 = AFc2 - 224; } if (OutputFlags & RDS_OUTPUT_EON) { printf("EON var=0x%01x PTY=%d ('%s') PS='%s' AF1=%3.2f (%d) AF2=%3.2f (%d) PI=%d\n", variant, rds_info.PTY, PTY_text[rds_info.PTY], rds_info.PS, AF1, AFc1, AF2, AFc2, PI); } } break; case GROUP_14B: /* Enhanced Other Networks information */ if (OutputFlags & RDS_OUTPUT_EON) printf("GRP14B: Enhanced Other Networks information\n"); break; case GROUP_15A: if (OutputFlags & RDS_OUTPUT_UNKNGRP) printf("GRP15A\n"); break; case GROUP_15B: /* Fast basic tuning and switching information */ if (OutputFlags & RDS_OUTPUT_UNKNGRP) printf("GRP15B: Fast basic tuning and switching information\n"); break; default: if (OutputFlags & RDS_OUTPUT_UNKNGRP) printf("unkn. RDS group %d\n", grp_type); break; } /* done with this group */ grp_decoded = 1; }