--- /dev/null
+CC = gcc
+
+# prefix for installation and search path (like icons)
+PREFIX = /usr/local/
+
+# for normal desktop GTK+
+CCFLAGS = -Wall -O2 -g
+
+SQLITECFLAGS = `pkg-config --cflags sqlite3`
+GTKCFLAGS = `pkg-config --cflags gtk+-2.0`
+
+CFLAGS = $(CCFLAGS) $(SQLITECFLAGS) $(GTKCFLAGS)
+
+SQLITELDFLAGS = `pkg-config --libs sqlite3`
+GTKLDFLAGS = `pkg-config --libs gtk+-2.0`
+
+# no need to change anything below this line
+# ------------------------------------------
+
+.SUFFIXES: .d .c
+
+CFLAGS += -MD -DPREFIX=\"$(PREFIX)\" $(OPTIONS)
+LDFLAGS = $(CLDFLAGS) $(SQLITELDFLAGS)
+
+RDS_MEMBERS = rds bitstream tmc rds_test
+SOURCES = $(patsubst %,%.c,$(RDS_MEMBERS))
+OBJS = $(patsubst %,%.o,$(RDS_MEMBERS))
+DEPS = $(patsubst %,%.d,$(RDS_MEMBERS))
+
+UR_MEMBERS = rds bitstream tmc uberradio
+UR_SOURCES = $(patsubst %,%.c,$(UR_MEMBERS))
+UR_OBJS = $(patsubst %,%.o,$(UR_MEMBERS))
+UR_DEPS = $(patsubst %,%.d,$(UR_MEMBERS))
+
+all: rds_test
+
+rds_test: $(OBJS)
+ $(CC) -o $@ $^ $(LDFLAGS)
+
+uberradio: $(UR_OBJS)
+ $(CC) -o $@ $^ $(LDFLAGS) $(GTKLDFLAGS)
+
+clean:
+ rm -f *.o *.d rdsread rds_test uberradio
+
+-include $(DEPS)
--- /dev/null
+#include <stdio.h>
+#include <stdlib.h>
+#include <string.h>
+
+#include "bitstream.h"
+
+static unsigned char *_BITBUF;
+static unsigned int _BITPOS;
+static unsigned int _BUFLEN;
+
+void printbits(unsigned int val)
+{
+unsigned char i=0;
+
+ while (i<32)
+ if (val & (1<<(31-i++)))
+ printf("1 ");
+ else
+ printf("0 ");
+// printf("\n");
+}
+
+void printbuf_head(unsigned char *buf)
+{
+ //printbits(buf[3]<<24 | buf[2]<<16 | buf[1]<<8 | buf[0]);
+ printbits(buf[0]<<24 | buf[1]<<16 | buf[2]<<8 | buf[3]);
+}
+
+unsigned int bitstream_get_bits(unsigned char *data, unsigned int bitOffset, unsigned int numBits)
+{
+unsigned int val;
+unsigned int mask = (1 << numBits) - 1;
+
+ data += ((bitOffset / 8) -3);
+ val = *((int *)data);
+// printf("0x%08x ", val);
+
+ val = val << (bitOffset % 8);
+// printf("0x%08x ", val);
+
+ val = val >> (32-numBits);
+// printf("0x%08x %d ", val, (32-numBits));
+
+ val &= mask;
+// printf("0x%08x\n", val);
+
+return val;
+}
+
+int bitstream_bits_remaining(void)
+{
+ return (_BUFLEN - _BITPOS);
+}
+
+unsigned int bitstream_get_next_bits(unsigned int nbits)
+{
+unsigned int res;
+
+ res = bitstream_get_bits(_BITBUF, _BITPOS, nbits);
+ _BITPOS += nbits;
+
+ return res;
+}
+
+void bitstream_start(unsigned char *buf, unsigned int len)
+{
+ _BITBUF = buf;
+ _BITPOS = 0;
+ _BUFLEN = len;
+}
+
+
--- /dev/null
+#ifndef _BITSTREAM_H
+#define _BITSTREAM_H
+
+unsigned int bitstream_get_bits(unsigned char *data, unsigned int bitOffset, unsigned int numBits);
+
+unsigned int bitstream_get_next_bits(unsigned int nbits);
+
+int bitstream_bits_remaining(void);
+
+void bitstream_start(unsigned char *buf, unsigned int len);
+
+#endif
+
--- /dev/null
+#include <stdio.h>
+#include <string.h>
+#include <stdlib.h>
+#include <time.h>
+#include <unistd.h>
+#include <sys/stat.h>
+#include <fcntl.h>
+#include <limits.h>
+
+#include <sqlite3.h>
+
+#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)
+ exit(0); // just for testing
+ if (rb != 3)
+ printf("#read err rb=%d\n", rb);
+ 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_decode_group(unsigned short *rdsgroup)
+{
+static unsigned short ogrp[4];
+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;
+
+ if (ogrp[0] == rdsgroup[0] && ogrp[1] == rdsgroup[1] && ogrp[2] == rdsgroup[2] && ogrp[3] == rdsgroup[3])
+ return;
+ else {
+ 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]);
+ }
+ 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);
+ 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);
+ 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)
+ _rds_private.rds_sname_cb(rds_info.sname, _rds_private.rds_sname_cb_data);
+
+ 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");
+ }
+ }
+ 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:
+ if (OutputFlags & RDS_OUTPUT_UNKNGRP)
+ printf("GRP11A\n");
+ break;
+ case GROUP_11B:
+ if (OutputFlags & RDS_OUTPUT_UNKNGRP)
+ printf("GRP11B\n");
+ break;
+ case GROUP_12A:
+ if (OutputFlags & RDS_OUTPUT_UNKNGRP)
+ printf("GRP12A\n");
+ break;
+ case GROUP_12B:
+ if (OutputFlags & RDS_OUTPUT_UNKNGRP)
+ printf("GRP12B\n");
+ break;
+ case GROUP_13A:
+ if (OutputFlags & RDS_OUTPUT_UNKNGRP)
+ printf("GRP13A\n");
+ break;
+ case GROUP_13B:
+ 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;
+ }
+}
+
--- /dev/null
+#define RDS_RECEIVE_INDICATOR 1 << 0
+#define RDS_OUTPUT_STATION_ID 1 << 1
+#define RDS_OUTPUT_RADIO_TEXT 1 << 2
+#define RDS_OUTPUT_TMC 1 << 3
+#define RDS_OUTPUT_DATETIME 1 << 4
+#define RDS_OUTPUT_RDSINFO 1 << 5
+#define RDS_OUTPUT_EON 1 << 6
+#define RDS_OUTPUT_UNKNGRP 1 << 7
+
+extern int OutputFlags;
+
+/* defined in rds_consts.h */
+extern const char *PTY_text[];
+extern const char *ECC_text[];
+extern const char *ptype_stext[];
+extern const char *ptype_ltext[];
+
+struct rds_info_s {
+ struct tm dtime;
+ char sname[9];
+ char rtext[65];
+ unsigned short AID;
+ char LTN; /* location table number */
+ unsigned char AFI;
+ unsigned char M;
+ unsigned char I;
+ unsigned char N;
+ unsigned char R;
+ unsigned char U;
+ unsigned char G;
+ unsigned char SID;
+ unsigned char Ta;
+ unsigned char Tw;
+ unsigned char Td;
+ unsigned char PTY;
+ unsigned char TPTN;
+ unsigned char TPON;
+ unsigned char PS[9];
+ unsigned short PI;
+ unsigned char ccode, ptype, pref;
+ unsigned char ECC;
+ unsigned char lang_code;
+ unsigned char MS;
+ unsigned char TP;
+ unsigned char TA;
+};
+
+int rds_receive_group(int rds_fd, unsigned short *rdsgroup);
+void rds_decode_group(unsigned short *rdsgroup);
+
+void rds_init(void);
+
+/* with every group but only once reported when PI changes */
+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);
+
+/* group 0A */
+void rds_set_sname_cb(void (*rds_sname_cb)(char *sname, void *user_data), void *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);
+void rds_set_afinfo_cb(void (*rds_afinfo_cb)(unsigned char cnt, double *AF, void *user_data), void *user_data);
+/* group 2A/B */
+void rds_set_radiotext_cb(void (*rds_radiotext_cb)(char *radio_text, void *user_data), void *user_data);
+/* group 3A */
+
+/* group 4A */
+void rds_set_date_time_cb(void (*rds_date_time_cb)(struct tm *rds_time, void *user_data), void *user_data);
+
+
--- /dev/null
+const char *PTY_text[] = {
+ "No programme type or undefined",
+ "News",
+ "Current Affairs",
+ "Information",
+ "Sport",
+ "Education",
+ "Drama",
+ "Culture",
+ "Science",
+ "Varied",
+ "Pop Music",
+ "Rock Music",
+ "Easy Listening Music",
+ "Light classical",
+ "Serious classical",
+ "Other Music",
+ "Weather",
+ "Finance",
+ "Children's programmes",
+ "Social Affairs",
+ "Religion",
+ "Phone In",
+ "Travel",
+ "Leisure",
+ "Jazz Music",
+ "Country Music",
+ "National Music",
+ "Oldies Music",
+ "Folk Music",
+ "Documentary",
+ "Alarm Test",
+ "Alarm"
+};
+
+const char *ECC_text[] = {
+/* 0 1 2 3 4 5 6 7 8 9 A B C D E F */
+/*E0*/ "DE", "DZ", "AD", "IL", "IT", "BE", "RU", "PS","AL", "AT", "HU", "MT", "DE", "" , "EG", "",
+/*E1*/ "GR", "CY", "SM", "CH", "JO", "FI", "LU", "BG","DK", "GI", "IQ", "GB", "LY", "RO", "FR", "",
+/*E2*/ "MA", "CZ", "PL", "VA", "SK", "SY", "TN", "" ,"LI", "IS", "MC", "LT", "YU", "ES", "NO", "",
+/*E3*/ "" , "IE", "TR", "MK", "" , "" , "" , "NL","LV", "LB", "" , "HR", "" , "SE", "BY", "",
+/*E4*/ "MD", "EE", "" , "" , "" , "UA", "" , "PT","SI", "" , "" , "" , "" , "" , "BA", "",
+};
+
+const char *ptype_stext[] = {
+ "L", "I", "N", "S", "R1", "R2", "R3", "R4", "R5", "R6","R7", "R8", "R9", "R10", "R11", "R12"
+};
+
+const char *ptype_ltext[] = {
+ "Local",
+ "International",
+ "National",
+ "Supra-regional",
+ "Regional 1",
+ "Regional 2",
+ "Regional 3",
+ "Regional 4",
+ "Regional 5",
+ "Regional 6",
+ "Regional 7",
+ "Regional 8",
+ "Regional 9",
+ "Regional 10",
+ "Regional 11",
+ "Regional 12"
+};
+
--- /dev/null
+#include <stdio.h>
+#include <string.h>
+#include <stdlib.h>
+#include <time.h>
+#include <unistd.h>
+#include <sys/stat.h>
+#include <fcntl.h>
+#include <limits.h>
+
+#include <sqlite3.h>
+
+#include "rds.h"
+#include "tmc.h"
+
+sqlite3 *lcl_db;
+int OutputFlags;
+
+void test_rds_PI_cb(unsigned short PI, unsigned char ccode, unsigned char ptype, unsigned char pref)
+{
+ printf("New PI=%d ccode=%X ptype=%X '%s' '%s' pref=%d\n", PI, ccode, ptype, ptype_stext[ptype], ptype_ltext[ptype], pref);
+}
+
+void test_rds_radiotext_cb(char *radio_text)
+{
+ printf("New RT: '%s'\n", radio_text);
+}
+
+void test_rds_date_time_cb(struct tm *rds_time)
+{
+ printf("RDS date/time: %s", asctime(rds_time));
+}
+
+void test_rds_sname_cb(char *sname)
+{
+ printf("RDS sname='%s'\n", sname);
+}
+
+void test_tmc_msg_cb(char *msg, void *user_data)
+{
+ printf("\nTMC msg:\n%s", msg);
+}
+
+int open_radio(const char *name)
+{
+int fd;
+
+ fd = open(name, O_RDONLY);
+ if (fd > 0)
+ return fd;
+ else
+ return 0;
+}
+
+int main(int argc, char **argv)
+{
+char rdevname[PATH_MAX] = "/dev/radio0";
+unsigned short rdsgroup[4];
+int rds_fd, i;
+
+ i = 1;
+ while (i < argc) {
+ if (strncmp("-all", argv[i], 4) == 0)
+ OutputFlags |= ~0;
+ if (strncmp("-di", argv[i], 3) == 0)
+ OutputFlags |= RDS_OUTPUT_RDSINFO;
+ if (strncmp("-rd", argv[i], 3) == 0)
+ if (argc > i)
+ strcpy(rdevname, argv[++i]);
+ if (strncmp("-rt", argv[i], 3) == 0)
+ OutputFlags |= RDS_OUTPUT_RADIO_TEXT;
+ if (strncmp("-ri", argv[i], 3) == 0)
+ OutputFlags |= RDS_OUTPUT_STATION_ID;
+ if (strncmp("-tmc", argv[i], 4) == 0)
+ OutputFlags |= RDS_OUTPUT_TMC;
+ if (strncmp("-eon", argv[i], 4) == 0)
+ OutputFlags |= RDS_OUTPUT_EON;
+ if (strncmp("-dt", argv[i], 3) == 0)
+ OutputFlags |= RDS_OUTPUT_DATETIME;
+ if (strncmp("-ug", argv[i], 3) == 0)
+ OutputFlags |= RDS_OUTPUT_UNKNGRP;
+ if (strncmp("-pi", argv[i], 3) == 0)
+ OutputFlags |= RDS_RECEIVE_INDICATOR;
+ i++;
+ }
+ if (!(rds_fd = open_radio(rdevname))) {
+ perror("open radio:");
+ return -1;
+ }
+ if (sqlite3_open("lcl.db", &lcl_db) != SQLITE_OK) {
+ perror("open radio:");
+ close(rds_fd);
+ return -1;
+ }
+
+ rds_init();
+ tmc_init();
+
+ //rds_set_PI_cb(test_rds_PI_cb);
+ //rds_set_radiotext_cb(test_rds_radiotext_cb);
+ //rds_set_date_time_cb(test_rds_date_time_cb);
+ //rds_set_sname_cb(test_rds_sname_cb);
+
+ tmc_set_msg_cb(test_tmc_msg_cb, NULL);
+
+ while (1) {
+ if (rds_receive_group(rds_fd, rdsgroup)) {
+ // group complete, start decode
+ rds_decode_group(rdsgroup);
+ }
+ }
+
+return 0;
+}
+
--- /dev/null
+Autobahnen
+
+A45 Auf der A45 Aschaffenburg Richtung Gießen zwischen Florstadt und
+Wölfersheim steht ein defekter LKW, die rechte Spur ist blockiert. [AS:
+37/38] - Stand: 22:10 [Detailkarte]
+
+A5 Karlsruhe Richtung Basel vor dem Grenzübergang Weil am Rhein/Basel 1 km
+LKW-Stau. [AS: 70/70] - Stand: 20:35 [Detailkarte]
+
+A60 Auf der A60 Mainz Richtung Rüsselsheim ist die Ausfahrt Mainz-Weisenau
+wegen einer Baustelle bis 30.04.2010 gesperrt. Eine Umleitung ist
+eingerichtet. [AS: 23/23] - Stand: 00:56 [Detailkarte]
+
+A61 Auf der A61 Koblenz Richtung Ludwigshafen ist Dreieck Nahetal wegen
+Brückenarbeiten die Überleitung zur A60 Richtung Mainz bis 13.04.2010
+gesperrt. Eine Umleitung ist eingerichtet. [AS: 50/50] - Stand: 07:36
+[Detailkarte]
+
+A61 Ludwigshafen Richtung Koblenz zwischen Alzey und Gau-Bickelheim
+Dauerbaustelle, Fahrbahnverengung [AS: 52/55] - Stand: 11:07 [Detailkarte]
+
+A62 Landstuhl Richtung Pirmasens wegen Instandhaltungsarbeiten im
+Hörnchenbergtunnel ist nur eine Spur frei. Eine Umleitung ist eingerichtet.
+(bis 15. Juni) - Stand: 07:42 [Detailkarte]
+
+A62 An der A62 Landstuhl Richtung Pirmasens ist die Einfahrt Landstuhl-Atzel
+und der Tunnel gesperrt. Bitte folgen Sie der Umleitungsbeschilderung.
+(Dauer: bis 15. Juni) [AS: 11/11] - Stand: 07:47 [Detailkarte]
+
+A661 Auf der A661 Bad Homburg - Egelsbach ist die Anschlußstelle
+Frankfurt-Heddernheim in beiden Richtungen wegen Brückenarbeiten bis etwa
+23:00 Uhr gesperrt. [AS: 6/6] - Stand: 22:06 [Detailkarte]
+
+A661 Die A661 Bad Homburg Richtung Egelsbach ist in Höhe
+Frankfurt-Heddernheim wegen Brückenarbeiten bis etwa 00:15 Uhr gesperrt.
+[AS: 6/6] - Stand: 22:06 [Detailkarte]
+
+A8 An der A8 Stuttgart Richtung München ist die Ausfahrt Kirchheim
+(Teck)-West wegen Instandhaltungsarbeiten bis voraussichtlich 23. April
+gesperrt. [AS: 56/56] - Stand: 23:11 [Detailkarte]
+
+A8 Auf der A8 Luxemburg Richtung Saarlouis zwischen Perl und Perl-Borg ist
+die rechte Spur wegen Fahrbahnbeschädigungen nach einem LKW-Brand gesperrt.
+[AS: 3/2] - Stand: 13:05 [Detailkarte]
+Bundesstraßen
+
+B10 Auf der B10 in Stuttgart zwischen Pragtunnel und Borsigstraße Baustelle,
+eine Spur ist gesperrt bis 12.04.2010 05:00 Uhr. - 20:13 [Detailkarte]
+Sonstiges
+
+Im Kreis Tuttlingen ist die Ortsdurchfahrt Buchheim wegen Kanal- und
+Straßenbauarbeiten bis 01.06.2010 in beiden Richtungen gesperrt. Umleitung
+über Leibertingen-Thalheim, Leibertingen, Beuron und Fridingen. - 06:26
+[Detailkarte]
+
+In Mainz auf der Zwerchallee zwischen Am Schützenweg und Rheinallee in
+beiden Richtungen bis 15.04.2010 14:00 Uhr Behinderungen durch
+Tiefbauarbeiten. Umleitungsempfehlung: über Mombacher Straße und
+Kaiser-Karl-Ring. - 16:50 [Detailkarte]
--- /dev/null
+#include <stdio.h>
+#include <string.h>
+#include <stdlib.h>
+#include <time.h>
+#include <unistd.h>
+#include <sys/stat.h>
+#include <fcntl.h>
+#include <limits.h>
+
+#include <sqlite3.h>
+
+#include "rds.h"
+#include "bitstream.h"
+#include "tmc.h"
+#include "tmc_consts.h"
+
+// #define DEBUG 1
+
+static struct TMC_info tmc_info;
+static struct TMC_msg cur_tmc_msg;
+static char tmc_msg_str[4096];
+static char *tmc_msg_ptr = tmc_msg_str;
+
+static struct _TMC_Private {
+ void (*tmc_sid_cb)(char *sid_text, void *user_data);
+ void *tmc_sid_cb_data;
+ void (*tmc_msg_cb)(char *msg, void *user_data);
+ void *tmc_msg_cb_data;
+} _tmc_private;
+
+void tmc_init(void)
+{
+ memset(&_tmc_private, 0, sizeof(struct _TMC_Private));
+}
+
+void tmc_set_sid_cb(void (*tmc_sid_cb)(char *sid_text, void *user_data), void *user_data)
+{
+ _tmc_private.tmc_sid_cb = tmc_sid_cb;
+ _tmc_private.tmc_sid_cb_data = user_data;
+}
+
+static void reset_msg_buf(void)
+{
+ tmc_msg_ptr = tmc_msg_str;
+ memset(tmc_msg_str, 0, 4096);
+}
+
+void tmc_set_msg_cb(void (*tmc_msg_cb)(char *msg, void *user_data), void *user_data)
+{
+ reset_msg_buf();
+ _tmc_private.tmc_msg_cb = tmc_msg_cb;
+ _tmc_private.tmc_msg_cb_data = user_data;
+}
+
+void interpret_tmc(int event, int location, int extent, int CI, int direction)
+{
+char sql[256]="";
+sqlite3_stmt *ppstmt;
+int neg_off=0, pos_off=0, lin_ref=0;
+char road_nr[256]="", fst_name[256]="";
+char rdir1[256]="", rdir2[256]="";
+char last_name[256]="";
+char evt_str[256]="";
+char type_str1[128]="";
+char type_str2[128]="";
+char typechr;
+int typenr, subtype;
+
+ if (CI != cur_tmc_msg.CI || CI == -1) {
+ //printf("GF evt=%d loc=%d ext=%d CI=%d dir=%d", event, location, extent, CI, direction);
+ //printf("ev=%d ", event);
+ snprintf(sql, 256, "select ROADNUMBER,FIRST_NAME,NEGATIVE_OFFSET,POSITIVE_OFFSET,LINEAR_REFERENCE,TYPE,SUBTYPE from lcl where LOCATIONCODE=%d", location);
+ sqlite3_prepare(lcl_db, sql, 256, &ppstmt, NULL);
+ if (sqlite3_step(ppstmt) != SQLITE_ROW) {
+#ifdef DEBUG
+ printf("lcl.db failed to get location %d event %d\n", location, event);
+#endif
+ // return; // we cannot do much without location
+ } else {
+ neg_off = sqlite3_column_int(ppstmt, 2);
+ pos_off = sqlite3_column_int(ppstmt, 3);
+ lin_ref = sqlite3_column_int(ppstmt, 4);
+ strncpy(road_nr, (char *)sqlite3_column_text(ppstmt, 0), 255);
+ strncpy(fst_name, (char *)sqlite3_column_text(ppstmt, 1), 255);
+ strncpy(type_str1, (char *)sqlite3_column_text(ppstmt, 5), 128);
+ subtype = sqlite3_column_int(ppstmt, 6);
+ typechr = type_str1[0];
+ typenr = atoi(&type_str1[1]);
+ // printf(" type=%c %d stype=%d\n", typechr, typenr, subtype);
+ snprintf(sql, 256, "select SNATDESC from lcl_supc where CLASS='%c' AND TCD=%d AND STCD=%d", typechr, typenr, subtype);
+ sqlite3_prepare(lcl_db, sql, 256, &ppstmt, NULL);
+ if (sqlite3_step(ppstmt) != SQLITE_ROW) {
+#ifdef DEBUG
+ printf("lcl.db failed to get suptype %c %d stype=%d\n", typechr, typenr, subtype);
+#endif
+ } else {
+ strncpy(type_str1, (char *)sqlite3_column_text(ppstmt, 0), 128);
+ }
+ if (lin_ref != 0) {
+ snprintf(sql, 256, "select FIRST_NAME,SECOND_NAME from lcl where LOCATIONCODE=%d", lin_ref);
+ sqlite3_prepare(lcl_db, sql, 256, &ppstmt, NULL);
+ if (sqlite3_step(ppstmt) != SQLITE_ROW) {
+#ifdef DEBUG
+ printf("lcl.db failed to get linear ref %d\n", lin_ref);
+#endif
+ } else {
+ if (!direction) {
+ strncpy(rdir1, (char *)sqlite3_column_text(ppstmt, 1), 255);
+ strncpy(rdir2, (char *)sqlite3_column_text(ppstmt, 0), 255);
+ } else {
+ strncpy(rdir1, (char *)sqlite3_column_text(ppstmt, 0), 255);
+ strncpy(rdir2, (char *)sqlite3_column_text(ppstmt, 1), 255);
+ }
+ }
+ }
+ if (extent > 0) {
+ // printf("%s ab %s ", road_nr, fst_name);
+ if (direction) {
+ /* neg */
+ while (extent--) {
+ snprintf(sql, 256, "select ROADNUMBER,FIRST_NAME,NEGATIVE_OFFSET,POSITIVE_OFFSET,TYPE,SUBTYPE from lcl where LOCATIONCODE=%d", neg_off);
+ sqlite3_prepare(lcl_db, sql, 256, &ppstmt, NULL);
+ if (sqlite3_step(ppstmt) != SQLITE_ROW) {
+#ifdef DEBUG
+ printf("lcl.db failed to get location %d\n", location);
+#endif
+ break;
+ } else
+ neg_off = sqlite3_column_int(ppstmt, 2);
+ }
+ //printf("bis %s\n", (char *)sqlite3_column_text(ppstmt, 1));
+ } else {
+ /* pos */
+ while (extent--) {
+ snprintf(sql, 256, "select ROADNUMBER,FIRST_NAME,NEGATIVE_OFFSET,POSITIVE_OFFSET,TYPE,SUBTYPE from lcl where LOCATIONCODE=%d", pos_off);
+ sqlite3_prepare(lcl_db, sql, 256, &ppstmt, NULL);
+ if (sqlite3_step(ppstmt) != SQLITE_ROW) {
+#ifdef DEBUG
+ printf("lcl.db failed to get location %d\n", location);
+#endif
+ break;
+ } else
+ pos_off = sqlite3_column_int(ppstmt, 3);
+ }
+ //printf("bis %s\n", (char *)sqlite3_column_text(ppstmt, 1));
+ }
+ strncpy(last_name, (char *)sqlite3_column_text(ppstmt, 1), 255);
+ extent=1;
+ strncpy(type_str2, (char *)sqlite3_column_text(ppstmt, 4), 128);
+ subtype = sqlite3_column_int(ppstmt, 5);
+ typechr = type_str2[0];
+ typenr = atoi(&type_str2[1]);
+ // printf(" type=%c %d stype=%d\n", typechr, typenr, subtype);
+ snprintf(sql, 256, "select SNATDESC from lcl_supc where CLASS='%c' AND TCD=%d AND STCD=%d", typechr, typenr, subtype);
+ sqlite3_prepare(lcl_db, sql, 256, &ppstmt, NULL);
+ if (sqlite3_step(ppstmt) != SQLITE_ROW) {
+#ifdef DEBUG
+ printf("lcl.db failed to get suptype %c %d stype=%d\n", typechr, typenr, subtype);
+#endif
+ } else {
+ strncpy(type_str2, (char *)sqlite3_column_text(ppstmt, 0), 128);
+ }
+ }
+ }
+ snprintf(sql, 256, "select TEXT_DE_NOQUANT from evl where CODE=%d", event);
+ sqlite3_prepare(lcl_db, sql, 256, &ppstmt, NULL);
+ if (sqlite3_step(ppstmt) != SQLITE_ROW) {
+#ifdef DEBUG
+ printf("lcl.db failed to get event %d\n", event);
+#endif
+ } else {
+ strncpy(evt_str, (char *)sqlite3_column_text(ppstmt, 0), 255);
+ }
+ if (CI != -1)
+ cur_tmc_msg.CI = CI;
+
+ // here goes the message ;)
+ if (lin_ref != 0)
+ tmc_msg_ptr += sprintf(tmc_msg_ptr, "%s %s - %s,\n", road_nr, rdir1, rdir2);
+ if (location == 64777) {
+ tmc_msg_ptr += sprintf(tmc_msg_ptr, "%s\n", evt_str);
+ } else {
+ if (extent)
+ if (direction)
+ tmc_msg_ptr += sprintf(tmc_msg_ptr, "zwischen %s %s und %s %s\n%s\n", type_str1, fst_name, type_str2, last_name, evt_str);
+ else
+ tmc_msg_ptr += sprintf(tmc_msg_ptr, "zwischen %s %s und %s %s\n%s\n", type_str2, last_name, type_str1, fst_name, evt_str);
+ else
+ tmc_msg_ptr += sprintf(tmc_msg_ptr, "in Höhe %s %s\n%s\n", type_str1, fst_name, evt_str);
+ }
+ }
+}
+
+void parse_tmc_single(unsigned short *rdsgroup)
+{
+unsigned short event = cur_tmc_msg.msgs[0][1] & 0x7FF;
+unsigned short location = cur_tmc_msg.msgs[0][2];
+unsigned char extent = (cur_tmc_msg.msgs[0][1] & ((0x07) << 11)) >> 11;
+unsigned char dir = (cur_tmc_msg.msgs[0][1] & ((0x01) << 14)) >> 14;
+static unsigned short oevent=0;
+static unsigned short olocation=0;
+
+#ifdef DEBUG
+ printf(" single group\n");
+#endif
+ if (olocation != location && oevent != event) {
+ interpret_tmc(event, location, extent, -1, dir);
+ olocation = location;
+ oevent = event;
+ if (_tmc_private.tmc_msg_cb != NULL) {
+ _tmc_private.tmc_msg_cb(tmc_msg_str, _tmc_private.tmc_msg_cb_data);
+ }
+ reset_msg_buf();
+ }
+}
+
+char *replace_str(char *str, char *orig, char *rep)
+{
+static char buffer[4096];
+char *p;
+
+ if(!(p = strstr(str, orig))) // Is 'orig' even in 'str'?
+ return str;
+
+ strncpy(buffer, str, p-str); // Copy characters from 'str' start to 'orig' st$
+ buffer[p-str] = '\0';
+
+ sprintf(buffer+(p-str), "%s%s", rep, p+strlen(orig));
+
+return buffer;
+}
+
+void interpret_tmc_multi(void)
+{
+int i;
+unsigned short event = cur_tmc_msg.msgs[0][1] & 0x7FF;
+unsigned short location = cur_tmc_msg.msgs[0][2];
+unsigned char extent = (cur_tmc_msg.msgs[0][1] & (0x07 << 11)) >> 11;
+unsigned char dir = (cur_tmc_msg.msgs[0][1] & (0x01 << 14)) >> 14;
+unsigned char label;
+unsigned short aevent;
+char sql[256]="";
+sqlite3_stmt *ppstmt;
+char evt_str[256]="";
+char lofrstr[16]="";
+unsigned char ext_buf[256];
+
+#ifdef DEBUG
+ printf("interpret_tmc_multi: sz=%d\n", cur_tmc_msg.msgsz);
+ for (i=0; i<cur_tmc_msg.msgsz; i++) {
+ printf(" #%d 0x%04x 0x%04x 0x%04x\n", i, cur_tmc_msg.msgs[i][0], cur_tmc_msg.msgs[i][1], cur_tmc_msg.msgs[i][2]);
+ }
+
+ printf("TMC event=%d loc=%d ext=%d dir=%d\n", event, location, extent, dir);
+#endif
+
+ interpret_tmc(event, location, extent, -1, dir);
+
+
+ for (i=0; i < cur_tmc_msg.msgsz; i++) {
+ ext_buf[i*4] = (cur_tmc_msg.msgs[i+1][1] & 0xff00) >> 8;
+ ext_buf[(i*4)+1] = cur_tmc_msg.msgs[i+1][1] & 0xff;
+ ext_buf[(i*4)+2] = (cur_tmc_msg.msgs[i+1][2] & 0xff00) >> 8;
+ ext_buf[(i*4)+3] = cur_tmc_msg.msgs[i+1][2] & 0xff;
+ };
+ bitstream_start(ext_buf, cur_tmc_msg.msgsz*4);
+
+ bitstream_get_next_bits(4);
+
+ while (bitstream_bits_remaining() > 0) {
+ label = bitstream_get_next_bits(4);
+#ifdef DEBUG
+ if (OutputFlags & RDS_OUTPUT_TMC) {
+ printf("o '%s' %x\n", EVNT_LABEL[label], label);
+ }
+#endif
+ if (label == 0) {
+ unsigned char dur=0;
+
+ dur = bitstream_get_next_bits(3);
+ tmc_msg_ptr += sprintf(tmc_msg_ptr, " dur=%d\n", dur);
+ }
+ if (label == 1) {
+ unsigned char ctrlcd=0;
+
+ ctrlcd = bitstream_get_next_bits(3);
+ if (ctrlcd == 2)
+ tmc_msg_ptr += sprintf(tmc_msg_ptr, "in beiden Richtungen\n");
+ else
+ tmc_msg_ptr += sprintf(tmc_msg_ptr, " ctrlcd=%d\n", ctrlcd);
+ }
+ if (label == 2) {
+ unsigned int lofr=0;
+
+ lofr = bitstream_get_next_bits(5);
+ if (lofr == 0) {
+ printf("shit - more than 100km\n");
+ strcpy(lofrstr, ">100 km");
+ }
+ if (lofr>0 && lofr<11) {
+ printf("lofr = %dkm\n", lofr);
+ sprintf(lofrstr, "%d km", lofr);
+ }
+ if (lofr>10 && lofr<16) {
+ printf("lofr = %dkm\n", 12 + ((lofr-11)*2));
+ sprintf(lofrstr, "%d km", 12 + ((lofr-11)*2));
+ }
+ if (lofr>15 && lofr<32)
+ printf("lofr = %dkm\n", 25 + ((lofr-16)*5));
+ sprintf(lofrstr, "%d km", 25 + ((lofr-16)*5));
+ }
+ if (label == 3) {
+ unsigned int slim=0;
+
+ slim = bitstream_get_next_bits(5);
+ if (slim > 0)
+ tmc_msg_ptr += sprintf(tmc_msg_ptr, " slim = %d km/h\n", slim*5);
+ }
+ if (label == 4) {
+ unsigned int quant5=0;
+
+ quant5 = bitstream_get_next_bits(5);
+ tmc_msg_ptr += sprintf(tmc_msg_ptr, " quant5 = %d km\n", quant5);
+ }
+ if (label == 5) {
+ unsigned int quant8=0;
+
+ quant8 = bitstream_get_next_bits(8);
+ tmc_msg_ptr += sprintf(tmc_msg_ptr, " quant8 = %d km\n", quant8);
+ }
+ if (label == 6) {
+ unsigned char supinfo=0;
+
+ supinfo = bitstream_get_next_bits(8);
+#ifdef DEBUG
+ printf(" supinfo = %d\n", supinfo);
+#endif
+ snprintf(sql, 256, "select TEXT_DE from ev_sup where CODE=%d", supinfo);
+ sqlite3_prepare(lcl_db, sql, 256, &ppstmt, NULL);
+ if (sqlite3_step(ppstmt) != SQLITE_ROW) {
+#ifdef DEBUG
+ printf("lcl.db failed to get ev_sup %d\n", supinfo);
+#endif
+ } else {
+ strncpy(evt_str, (char *)sqlite3_column_text(ppstmt, 0), 255);
+ tmc_msg_ptr += sprintf(tmc_msg_ptr, "%s\n", evt_str);
+ }
+ }
+ if (label == 7) {
+ unsigned int stime=0;
+
+ stime = bitstream_get_next_bits(8);
+ // printf(" stime = %d\n", stime);
+ if (stime < 96) {
+ stime *= 15; // 15 min internval
+ tmc_msg_ptr += sprintf(tmc_msg_ptr, " ab %02d:%02d\n", (stime / 60), (stime % 60));
+ } else if (stime < 201) {
+ stime -= 96;
+ tmc_msg_ptr += sprintf(tmc_msg_ptr, " ab %d Tage, %d:00 Uhr\n", (stime / 24), (stime % 24));
+ } else if (stime < 232) {
+ stime -= 201;
+ tmc_msg_ptr += sprintf(tmc_msg_ptr, " ab dem %d.\n", stime+1);
+ } else {
+ stime -= 232;
+ tmc_msg_ptr += sprintf(tmc_msg_ptr, " ab %s. %s\n", (stime % 2) ? "31":"15", monthname[stime / 2]);
+ }
+ }
+ if (label == 8) {
+ unsigned int etime=0;
+
+ etime = bitstream_get_next_bits(8);
+ // printf(" etime = %d\n", etime);
+ if (etime < 96) {
+ etime *= 15; // 15 min internval
+ tmc_msg_ptr += sprintf(tmc_msg_ptr, " bis vor. %02d:%02d\n", (etime / 60), (etime % 60));
+ } else if (etime < 201) {
+ etime -= 96;
+ tmc_msg_ptr += sprintf(tmc_msg_ptr, " bis %d Tage, %d:00 Uhr\n", (etime / 24), (etime % 24));
+ } else if (etime < 232) {
+ etime -= 201;
+ tmc_msg_ptr += sprintf(tmc_msg_ptr, " bis zum %d.\n", etime+1);
+ } else {
+ etime -= 232;
+ tmc_msg_ptr += sprintf(tmc_msg_ptr, " bis etwa %s. %s\n", (etime % 2) ? "31":"15", monthname[etime / 2]);
+ }
+ }
+ if (label == 9) {
+ aevent = bitstream_get_next_bits(11);
+#ifdef DEBUG
+ printf(" aevent = %d ", aevent);
+#endif
+ snprintf(sql, 256, "select TEXT_DE_NOQUANT from evl where CODE=%d", aevent);
+ sqlite3_prepare(lcl_db, sql, 256, &ppstmt, NULL);
+ if (sqlite3_step(ppstmt) != SQLITE_ROW) {
+#ifdef DEBUG
+ printf("-lcl.db failed to get event %d\n", aevent);
+#endif
+ } else {
+ strncpy(evt_str, (char *)sqlite3_column_text(ppstmt, 0), 255);
+ if (strlen(lofrstr) != 0 && strstr(evt_str,"(L)")!=NULL) {
+ replace_str(evt_str, "(L)", lofrstr);
+ }
+ tmc_msg_ptr += sprintf(tmc_msg_ptr, "%s\n", evt_str);
+ }
+ }
+ if (label == 10) {
+ unsigned char div;
+
+ div = bitstream_get_next_bits(16);
+ tmc_msg_ptr += sprintf(tmc_msg_ptr, " div = %d\n", div);
+ }
+ if (label == 11) {
+ unsigned char dest;
+
+ dest = bitstream_get_next_bits(16);
+ tmc_msg_ptr += sprintf(tmc_msg_ptr, " dest = %d\n", dest);
+ }
+ if (label == 12) {
+ unsigned char res;
+
+ res = bitstream_get_next_bits(16);
+ tmc_msg_ptr += sprintf(tmc_msg_ptr, " res = %d\n", res);
+ }
+ if (label == 13) {
+ unsigned char clink;
+
+ clink = bitstream_get_next_bits(16);
+ tmc_msg_ptr += sprintf(tmc_msg_ptr, " clink = %d\n", clink);
+ }
+ if (label == 14) {
+ // tmc_msg_ptr += sprintf(tmc_msg_ptr, " seperator\n");
+ }
+ if (label == 15) {
+#ifdef DEBUG
+ printf(" res\n");
+#endif
+ }
+ }
+#ifdef DEBUGx
+ if (cur_tmc_msg.msgsz > 2) {
+ for (i=2; i<cur_tmc_msg.msgsz; i++) {
+ printf(" #%d 0x%04x 0x%04x 0x%04x\n", i, cur_tmc_msg.msgs[i][0], cur_tmc_msg.msgs[i][1], cur_tmc_msg.msgs[i][2]);
+ }
+ }
+#endif
+ if (_tmc_private.tmc_msg_cb != NULL) {
+ _tmc_private.tmc_msg_cb(tmc_msg_str, _tmc_private.tmc_msg_cb_data);
+ }
+ reset_msg_buf();
+}
+
+void parse_tmc_multi(unsigned short *rdsgroup)
+{
+unsigned char GSI = (rdsgroup[2] & 0x3000) >> 12;
+static unsigned char oGSI = 0;
+#ifdef DEBUG
+unsigned char CI = rdsgroup[1] & 0x07;
+#endif
+
+#ifdef DEBUG
+ printf(" multi CI=%d ", CI);
+#endif
+ if (rdsgroup[2] & 0x8000) {
+ if (cur_tmc_msg.msgsz > 0) {
+ interpret_tmc_multi();
+ cur_tmc_msg.msgsz = 0;
+ }
+#ifdef DEBUG
+ printf(" #1 GSI=%d\n", GSI);
+#endif
+ oGSI = 0;
+ memset(&cur_tmc_msg.msgs, 0, 6*3*sizeof(short));
+ cur_tmc_msg.msgs[0][0] = rdsgroup[1];
+ cur_tmc_msg.msgs[0][1] = rdsgroup[2];
+ cur_tmc_msg.msgs[0][2] = rdsgroup[3];
+ //msgsz = 1;
+ } else {
+ if (rdsgroup[2] & 0x4000) {
+ // printf(" #2 GSI=%d\n", GSI);
+ cur_tmc_msg.msgs[1][0] = rdsgroup[1];
+ cur_tmc_msg.msgs[1][1] = rdsgroup[2];
+ cur_tmc_msg.msgs[1][2] = rdsgroup[3];
+ cur_tmc_msg.msgsz = 2;
+ } else {
+ if (oGSI == 0) {
+ oGSI = GSI;
+ }
+ // printf(" #%d GSI=%d (oGSI=%d)\n", 2+(oGSI-GSI), GSI, oGSI);
+ cur_tmc_msg.msgs[2+(oGSI-GSI)][0] = rdsgroup[1];
+ cur_tmc_msg.msgs[2+(oGSI-GSI)][1] = rdsgroup[2];
+ cur_tmc_msg.msgs[2+(oGSI-GSI)][2] = rdsgroup[3];
+ cur_tmc_msg.msgsz = 2+(oGSI-GSI);
+ }
+ }
+ // printf(" 0x%04x 0x%04x\n", rdsgroup[2], rdsgroup[3]);
+}
+
+enum TMCtype { TMC_GROUP=0, TMC_SINGLE, TMC_SYSTEM, TMC_TUNING };
+
+void decode_tmc(unsigned short *rdsgroup)
+{
+unsigned char X4 = (rdsgroup[1] & 0x10) >> 4;
+unsigned char X3 = (rdsgroup[1] & 0x08) >> 3;
+unsigned char X0X2 = rdsgroup[1] & 0x07;
+//static unsigned char tmc_provider[9] = "";
+
+ if (X4 == 0) { // User message
+ if (X3 == 1) { // single group msg
+ parse_tmc_single(rdsgroup);
+ } else { // multi group msg
+ if (X0X2 > 0) {
+ parse_tmc_multi(rdsgroup);
+ }
+#ifdef DEBUG
+ else
+ printf("err... X0X2(CI) = %d\n", X0X2);
+#endif
+ }
+ } else {
+ if (X3 == 1) {
+ //printf(" tuning?\n");
+ } else {
+ // printf(" system ");
+ if (X0X2 == 4) {
+ tmc_info.provider_str[0] = (rdsgroup[2] & 0xff00) >> 8;
+ tmc_info.provider_str[1] = rdsgroup[2] & 0xff;
+ tmc_info.provider_str[2] = (rdsgroup[3] & 0xff00) >> 8;
+ tmc_info.provider_str[3] = rdsgroup[3] & 0xff;
+ if (_tmc_private.tmc_sid_cb != NULL)
+ _tmc_private.tmc_sid_cb(tmc_info.provider_str, _tmc_private.tmc_sid_cb_data);
+ if (OutputFlags & RDS_OUTPUT_TMC)
+ printf("provider = '%s'\n", tmc_info.provider_str);
+ } else if (X0X2 == 5) {
+ tmc_info.provider_str[4] = (rdsgroup[2] & 0xff00) >> 8;
+ tmc_info.provider_str[5] = rdsgroup[2] & 0xff;
+ tmc_info.provider_str[6] = (rdsgroup[3] & 0xff00) >> 8;
+ tmc_info.provider_str[7] = rdsgroup[3] & 0xff;
+ if (_tmc_private.tmc_sid_cb != NULL)
+ _tmc_private.tmc_sid_cb(tmc_info.provider_str, _tmc_private.tmc_sid_cb_data);
+ if (OutputFlags & RDS_OUTPUT_TMC)
+ printf("provider = '%s'\n", tmc_info.provider_str);
+ }
+#ifdef DEBUG
+ else
+ printf("X0X2(CI)=%d \n", X0X2);
+#endif
+ }
+ }
+}
+
--- /dev/null
+struct TMC_info {
+ char provider_str[9];
+};
+
+struct TMC_msg {
+ int CI;
+ int neg_off, pos_off, lin_ref;
+ char road_nr[256], fst_name[256];
+ char rdir1[256], rdir2[256];
+ char last_name[256];
+ char evt_str[256];
+ unsigned char msgsz;
+ unsigned short msgs[6][3];
+};
+
+extern sqlite3 *lcl_db;
+
+void decode_tmc(unsigned short *rdsgroup);
+
+void tmc_init(void);
+
+void tmc_set_sid_cb(void (*tmc_sid_cb)(char *sid_text, void *user_data), void *user_data);
+void tmc_set_msg_cb(void (*tmc_msg_cb)(char *msg, void *user_data), void *user_data);
+
--- /dev/null
+const char *EVNT_LABEL[] = {
+ "Duration",
+ "Control code",
+ "Length of route affected",
+ "Speed limit advice",
+ "Quantifier",
+ "Quantifier",
+ "Supplimetary information code",
+ "Explicit start time",
+ "Explicit stop time",
+ "Additional event",
+ "Detailed diversion instructions",
+ "Destination",
+ "reserved",
+ "Cross linkage to source of problem, on another route",
+ "Separator",
+ "reserved"
+};
+
+const char *monthname[] = {
+ "Januar",
+ "Februar",
+ "März",
+ "April",
+ "Mai",
+ "Juni",
+ "Juli",
+ "August",
+ "September",
+ "Oktober",
+ "November",
+ "Dezember",
+ "Oh oh..."
+};
+
+
--- /dev/null
+#include <stdio.h>
+#include <string.h>
+#include <stdlib.h>
+#include <time.h>
+#include <unistd.h>
+#include <sys/stat.h>
+#include <fcntl.h>
+
+#include <gtk/gtk.h>
+
+#include <sqlite3.h>
+
+#include "rds.h"
+#include "tmc.h"
+
+sqlite3 *lcl_db;
+int OutputFlags;
+
+typedef struct {
+ int radio_fd;
+ guint r_input_id;
+ GtkBuilder *builder;
+ GtkWidget *MainWin;
+ GtkWidget *RDS_TP_Label;
+ GtkWidget *RDS_TA_Label;
+ GtkWidget *RDS_M_Label;
+ GtkWidget *RDS_S_Label;
+ GtkWidget *RDS_SID_Label;
+ GtkWidget *RDS_RT_Label;
+ GtkWidget *RDS_PTY_Label;
+ GtkWidget *RDS_Date_Time_Label;
+ GtkWidget *RDS_PI_Label;
+ GtkWidget *RDS_PType_Label;
+ GtkWidget *TMC_View;
+ GtkTextBuffer *TMC_MSG_Buffer;
+} uberradio_ui;
+
+
+int open_radio(const char *name)
+{
+int fd;
+
+ fd = open(name, O_RDONLY | O_NONBLOCK);
+ if (fd > 0)
+ return fd;
+ else
+ return 0;
+}
+
+
+void handle_r_input(gpointer user_data, gint source, GdkInputCondition condition)
+{
+uberradio_ui *rui = (uberradio_ui *)user_data;
+static unsigned short rdsgroup[4];
+
+ if (rds_receive_group(rui->radio_fd, rdsgroup)) {
+ rds_decode_group(rdsgroup);
+ }
+}
+
+void rds_sname_cb(char *sname, void *user_data)
+{
+uberradio_ui *rui = (uberradio_ui *)user_data;
+static char pbuf[128];
+
+ snprintf(pbuf, 128, "<tt>%s</tt>", sname);
+ gtk_label_set_markup(GTK_LABEL(rui->RDS_SID_Label), pbuf);
+}
+
+
+void rds_radiotext_cb(char *rtext, void *user_data)
+{
+uberradio_ui *rui = (uberradio_ui *)user_data;
+static char pbuf[128];
+
+ snprintf(pbuf, 128, "<tt>%s</tt>", rtext);
+ gtk_label_set_markup(GTK_LABEL(rui->RDS_RT_Label), pbuf);
+}
+
+void rds_sinfo_cb(unsigned char TP, unsigned char TA, unsigned char MS, unsigned char PTY, void *user_data)
+{
+uberradio_ui *rui = (uberradio_ui *)user_data;
+
+ if (TP) {
+ gtk_widget_set_sensitive(rui->RDS_TP_Label, TRUE);
+ } else {
+ gtk_widget_set_sensitive(rui->RDS_TP_Label, FALSE);
+ }
+ if (TA) {
+ gtk_widget_set_sensitive(rui->RDS_TA_Label, TRUE);
+ } else {
+ gtk_widget_set_sensitive(rui->RDS_TA_Label, FALSE);
+ }
+ if (MS) {
+ gtk_widget_set_sensitive(rui->RDS_M_Label, TRUE);
+ gtk_widget_set_sensitive(rui->RDS_S_Label, FALSE);
+ } else {
+ gtk_widget_set_sensitive(rui->RDS_M_Label, FALSE);
+ gtk_widget_set_sensitive(rui->RDS_S_Label, TRUE);
+ }
+ gtk_label_set_text(GTK_LABEL(rui->RDS_PTY_Label), PTY_text[PTY]);
+}
+
+void rds_date_time_cb(struct tm *rds_time, void *user_data)
+{
+uberradio_ui *rui = (uberradio_ui *)user_data;
+char pbuf[128];
+char pbuf2[128];
+gchar *pstr;
+GDate mgdate;
+
+ mgdate.julian = 0;
+ mgdate.dmy = 1;
+ mgdate.day = rds_time->tm_mday;
+ mgdate.month = rds_time->tm_mon + 1;
+ mgdate.year = rds_time->tm_year + 1900;
+ g_date_strftime(pbuf, 128, "%A %d. %B %Y ", &mgdate);
+ strftime(pbuf2, 128, "%R", rds_time);
+ pstr = g_strconcat(pbuf, pbuf2, NULL);
+ gtk_label_set_markup(GTK_LABEL(rui->RDS_Date_Time_Label), pstr);
+ g_free(pstr);
+}
+
+void rds_PI_cb(unsigned short PI, unsigned char ccode, unsigned char ptype, unsigned char pref, void *user_data)
+{
+uberradio_ui *rui = (uberradio_ui *)user_data;
+char pbuf[128];
+
+ // we get this callback if PI changed which also means that
+ // the station changed, which again mean that allprevious
+ // information in the display is invalid now
+
+ gtk_widget_set_sensitive(rui->RDS_TP_Label, FALSE);
+ gtk_widget_set_sensitive(rui->RDS_TA_Label, FALSE);
+ gtk_widget_set_sensitive(rui->RDS_M_Label, FALSE);
+ gtk_widget_set_sensitive(rui->RDS_S_Label, FALSE);
+ gtk_label_set_markup(GTK_LABEL(rui->RDS_RT_Label), "");
+ gtk_label_set_text(GTK_LABEL(rui->RDS_PTY_Label), "");
+ gtk_label_set_markup(GTK_LABEL(rui->RDS_SID_Label), "");
+ gtk_text_buffer_set_text(rui->TMC_MSG_Buffer, "", -1);
+
+ snprintf(pbuf, 128, "<b>PI</b> %d", PI);
+ gtk_label_set_markup(GTK_LABEL(rui->RDS_PI_Label), pbuf);
+ gtk_label_set_markup(GTK_LABEL(rui->RDS_PType_Label), ptype_ltext[ptype]);
+}
+
+void tmc_msg_cb(char *msg, void *user_data)
+{
+uberradio_ui *rui = (uberradio_ui *)user_data;
+
+ gtk_text_buffer_set_text(rui->TMC_MSG_Buffer, msg, -1);
+}
+
+int main(int argc, char **argv)
+{
+GtkWidget *w;
+uberradio_ui rui;
+
+ gtk_init (&argc, &argv);
+
+ rui.builder = gtk_builder_new();
+ if (!gtk_builder_add_from_file(rui.builder, "uberradio.ui", NULL)) {
+ fprintf(stderr, "Error creating GtkBuilder\n");
+ return 0;
+ }
+ gtk_builder_connect_signals(rui.builder, NULL);
+
+ rui.RDS_TP_Label = GTK_WIDGET(gtk_builder_get_object(rui.builder, "RDS_TP_Label"));
+ gtk_widget_set_sensitive(rui.RDS_TP_Label, FALSE);
+ rui.RDS_TA_Label = GTK_WIDGET(gtk_builder_get_object(rui.builder, "RDS_TA_Label"));
+ gtk_widget_set_sensitive(rui.RDS_TA_Label, FALSE);
+ rui.RDS_M_Label = GTK_WIDGET(gtk_builder_get_object(rui.builder, "RDS_M_Label"));
+ gtk_widget_set_sensitive(rui.RDS_M_Label, FALSE);
+ rui.RDS_S_Label = GTK_WIDGET(gtk_builder_get_object(rui.builder, "RDS_S_Label"));
+ gtk_widget_set_sensitive(rui.RDS_S_Label, FALSE);
+ rui.RDS_SID_Label = GTK_WIDGET(gtk_builder_get_object(rui.builder, "RDS_SID_Label"));
+ rui.RDS_RT_Label = GTK_WIDGET(gtk_builder_get_object(rui.builder, "RDS_RT_Label"));
+ rui.RDS_PTY_Label = GTK_WIDGET(gtk_builder_get_object(rui.builder, "RDS_PTY_Label"));
+ rui.RDS_Date_Time_Label = GTK_WIDGET(gtk_builder_get_object(rui.builder, "RDS_Date_Time_Label"));
+ rui.RDS_PI_Label = GTK_WIDGET(gtk_builder_get_object(rui.builder, "RDS_PI_Label"));
+ rui.RDS_PType_Label = GTK_WIDGET(gtk_builder_get_object(rui.builder, "RDS_PType_Label"));
+
+ rui.TMC_View = GTK_WIDGET(gtk_builder_get_object(rui.builder, "TMC_View"));
+ rui.TMC_MSG_Buffer = GTK_TEXT_BUFFER(gtk_builder_get_object(rui.builder, "TMC_MSG_Buffer"));
+
+ rui.MainWin = GTK_WIDGET(gtk_builder_get_object(rui.builder, "MainWin"));
+
+ gtk_widget_show_all(rui.MainWin);
+
+ if (!(rui.radio_fd = open_radio("/dev/radio0"))) {
+ perror("open radio:");
+ return -1;
+ }
+
+ if (sqlite3_open("lcl.db", &lcl_db) != SQLITE_OK) {
+ perror("open lcl.db");
+ close(rui.radio_fd);
+ return -1;
+ }
+
+ rds_init();
+ tmc_init();
+
+ rui.r_input_id = gdk_input_add (rui.radio_fd, GDK_INPUT_READ, handle_r_input, &rui);
+
+ rds_set_sname_cb(rds_sname_cb, &rui);
+ rds_set_radiotext_cb(rds_radiotext_cb, &rui);
+ rds_set_date_time_cb(rds_date_time_cb, &rui);
+ rds_set_sinfo_cb(rds_sinfo_cb, &rui);
+ rds_set_PI_cb(rds_PI_cb, &rui);
+
+ tmc_set_msg_cb(tmc_msg_cb, &rui);
+
+ gtk_main();
+
+return 0;
+}
--- /dev/null
+<?xml version="1.0"?>
+<interface>
+ <requires lib="gtk+" version="2.16"/>
+ <!-- interface-naming-policy project-wide -->
+ <object class="GtkWindow" id="MainWin">
+ <signal name="destroy" handler="gtk_main_quit"/>
+ <child>
+ <object class="GtkVBox" id="vbox1">
+ <property name="visible">True</property>
+ <child>
+ <object class="GtkLabel" id="Radio_Freq">
+ <property name="visible">True</property>
+ <property name="label" translatable="yes"><big><b>???.?? MHz</b></big></property>
+ <property name="use_markup">True</property>
+ </object>
+ <packing>
+ <property name="expand">False</property>
+ <property name="fill">False</property>
+ <property name="padding">2</property>
+ <property name="position">0</property>
+ </packing>
+ </child>
+ <child>
+ <object class="GtkVBox" id="vbox2">
+ <property name="visible">True</property>
+ <child>
+ <object class="GtkLabel" id="RDS_SID_Label">
+ <property name="visible">True</property>
+ <property name="use_markup">True</property>
+ <property name="width_chars">10</property>
+ </object>
+ <packing>
+ <property name="expand">False</property>
+ <property name="fill">False</property>
+ <property name="position">0</property>
+ </packing>
+ </child>
+ <child>
+ <object class="GtkHBox" id="hbox2">
+ <property name="visible">True</property>
+ <child>
+ <object class="GtkLabel" id="RDS_PI_Label">
+ <property name="visible">True</property>
+ <property name="use_markup">True</property>
+ <property name="selectable">True</property>
+ </object>
+ <packing>
+ <property name="position">0</property>
+ </packing>
+ </child>
+ <child>
+ <object class="GtkLabel" id="RDS_Ccode_Label">
+ <property name="visible">True</property>
+ </object>
+ <packing>
+ <property name="position">1</property>
+ </packing>
+ </child>
+ <child>
+ <object class="GtkLabel" id="RDS_PType_Label">
+ <property name="visible">True</property>
+ <property name="use_markup">True</property>
+ </object>
+ <packing>
+ <property name="position">2</property>
+ </packing>
+ </child>
+ </object>
+ <packing>
+ <property name="position">1</property>
+ </packing>
+ </child>
+ <child>
+ <object class="GtkHBox" id="hbox1">
+ <property name="visible">True</property>
+ <child>
+ <object class="GtkLabel" id="RDS_TP_Label">
+ <property name="visible">True</property>
+ <property name="label" translatable="yes">TP</property>
+ </object>
+ <packing>
+ <property name="position">0</property>
+ </packing>
+ </child>
+ <child>
+ <object class="GtkLabel" id="RDS_TA_Label">
+ <property name="visible">True</property>
+ <property name="label" translatable="yes">TA</property>
+ </object>
+ <packing>
+ <property name="position">1</property>
+ </packing>
+ </child>
+ <child>
+ <object class="GtkLabel" id="RDS_M_Label">
+ <property name="visible">True</property>
+ <property name="label" translatable="yes">M</property>
+ </object>
+ <packing>
+ <property name="position">2</property>
+ </packing>
+ </child>
+ <child>
+ <object class="GtkLabel" id="RDS_S_Label">
+ <property name="visible">True</property>
+ <property name="label" translatable="yes">S</property>
+ </object>
+ <packing>
+ <property name="position">3</property>
+ </packing>
+ </child>
+ </object>
+ <packing>
+ <property name="expand">False</property>
+ <property name="fill">False</property>
+ <property name="padding">2</property>
+ <property name="position">2</property>
+ </packing>
+ </child>
+ <child>
+ <object class="GtkLabel" id="RDS_PTY_Label">
+ <property name="visible">True</property>
+ </object>
+ <packing>
+ <property name="padding">2</property>
+ <property name="position">3</property>
+ </packing>
+ </child>
+ <child>
+ <object class="GtkLabel" id="RDS_Date_Time_Label">
+ <property name="visible">True</property>
+ <property name="use_markup">True</property>
+ </object>
+ <packing>
+ <property name="padding">2</property>
+ <property name="position">4</property>
+ </packing>
+ </child>
+ </object>
+ <packing>
+ <property name="expand">False</property>
+ <property name="fill">False</property>
+ <property name="position">1</property>
+ </packing>
+ </child>
+ <child>
+ <object class="GtkLabel" id="RDS_RT_Label">
+ <property name="visible">True</property>
+ <property name="use_markup">True</property>
+ <property name="width_chars">66</property>
+ </object>
+ <packing>
+ <property name="expand">False</property>
+ <property name="fill">False</property>
+ <property name="position">2</property>
+ </packing>
+ </child>
+ <child>
+ <object class="GtkFrame" id="frame1">
+ <property name="visible">True</property>
+ <property name="label_xalign">0</property>
+ <property name="shadow_type">none</property>
+ <child>
+ <object class="GtkAlignment" id="alignment1">
+ <property name="visible">True</property>
+ <property name="left_padding">12</property>
+ <child>
+ <object class="GtkVBox" id="vbox3">
+ <property name="visible">True</property>
+ <child>
+ <placeholder/>
+ </child>
+ <child>
+ <placeholder/>
+ </child>
+ <child>
+ <object class="GtkTextView" id="TMC_View">
+ <property name="visible">True</property>
+ <property name="can_focus">True</property>
+ <property name="editable">False</property>
+ <property name="wrap_mode">word</property>
+ <property name="cursor_visible">False</property>
+ <property name="buffer">TMC_MSG_Buffer</property>
+ </object>
+ <packing>
+ <property name="position">2</property>
+ </packing>
+ </child>
+ </object>
+ </child>
+ </object>
+ </child>
+ <child type="label">
+ <object class="GtkLabel" id="TMC_Frame_Label">
+ <property name="visible">True</property>
+ <property name="label" translatable="yes"><b>TMC</b></property>
+ <property name="use_markup">True</property>
+ </object>
+ </child>
+ </object>
+ <packing>
+ <property name="position">3</property>
+ </packing>
+ </child>
+ </object>
+ </child>
+ </object>
+ <object class="GtkTextBuffer" id="TMC_MSG_Buffer"/>
+</interface>
--- /dev/null
+
+Patch against release kernel source and binary kernel module (drop-in
+replacement) to enable V4L compliant RDS data output on Nokia N900, comes
+through /dev/radio1 (/dev/radio0 is the FM transmitter).
+
--- /dev/null
+--- ../linux-2.6.28/drivers/media/radio/radio-bcm2048.c 2010-02-18 00:59:33.000000000 +0100
++++ kernel-2.6.28/drivers/media/radio/radio-bcm2048.c 2010-05-24 22:59:54.000000000 +0200
+@@ -6,6 +6,8 @@
+ * Copyright (C) Nokia Corporation
+ * Contact: Eero Nurkkala <ext-eero.nurkkala@nokia.com>
+ *
++ * Copyright (C) Nils Faerber <nils.faerber@kernelconcepts.de>
++ *
+ * This program is free software; you can redistribute it and/or
+ * modify it under the terms of the GNU General Public License
+ * version 2 as published by the Free Software Foundation.
+@@ -21,6 +23,16 @@
+ * 02110-1301 USA
+ */
+
++/*
++ * History:
++ * Eero Nurkkala <ext-eero.nurkkala@nokia.com>
++ * Version 0.0.1
++ * - Initial implementation
++ * 2010-02-21 Nils Faerber <nils.faerber@kernelconcepts.de>
++ * Version 0.0.2
++ * - Add support for interrupt driven rds data reading
++ */
++
+ #include <linux/kernel.h>
+ #include <linux/module.h>
+ #include <linux/init.h>
+@@ -283,6 +295,12 @@
+ u8 fifo_size;
+ u8 scan_state;
+ u8 mute_state;
++
++ /* for rds data device read */
++ wait_queue_head_t read_queue;
++ unsigned int users;
++ unsigned char rds_data_available;
++ unsigned int rd_index;
+ };
+
+ static int radio_nr = -1; /* radio device minor (-1 ==> auto assign) */
+@@ -1756,6 +1774,8 @@
+ bcm2048_parse_rds_ps(bdev);
+
+ mutex_unlock(&bdev->mutex);
++
++ wake_up_interruptible(&bdev->read_queue);
+ }
+
+ static int bcm2048_get_rds_data(struct bcm2048_device *bdev, char *data)
+@@ -1869,6 +1889,11 @@
+
+ err = bcm2048_set_power_state(bdev, BCM2048_POWER_OFF);
+
++ init_waitqueue_head(&bdev->read_queue);
++ bdev->rds_data_available = 0;
++ bdev->rd_index = 0;
++ bdev->users = 0;
++
+ unlock:
+ return err;
+ }
+@@ -1903,7 +1928,8 @@
+ bcm2048_send_command(bdev, BCM2048_I2C_FM_RDS_MASK1,
+ flags);
+ }
+-
++ bdev->rds_data_available = 1;
++ bdev->rd_index = 0; /* new data, new start */
+ }
+ }
+
+@@ -2139,6 +2165,100 @@
+ return err;
+ }
+
++
++static int bcm2048_fops_open(struct inode *inode, struct file *file)
++{
++ struct bcm2048_device *bdev = video_drvdata(file);
++
++ bdev->users++;
++ bdev->rd_index = 0;
++ bdev->rds_data_available = 0;
++
++return 0;
++}
++
++static int bcm2048_fops_release(struct inode *inode, struct file *file)
++{
++ struct bcm2048_device *bdev = video_drvdata(file);
++
++ bdev->users--;
++
++return 0;
++}
++
++static unsigned int bcm2048_fops_poll(struct file *file,
++ struct poll_table_struct *pts)
++{
++ struct bcm2048_device *bdev = video_drvdata(file);
++ int retval = 0;
++
++ poll_wait(file, &bdev->read_queue, pts);
++
++ if (bdev->rds_data_available) {
++ retval = POLLIN | POLLRDNORM;
++ }
++
++ return retval;
++}
++
++static ssize_t bcm2048_fops_read(struct file *file, char __user *buf,
++ size_t count, loff_t *ppos)
++{
++ struct bcm2048_device *bdev = video_drvdata(file);
++ int i;
++ int retval = 0;
++
++ /* we return at least 3 bytes, one block */
++ count = (count / 3) * 3; /* only multiples of 3 */
++ if (count < 3)
++ return -ENOBUFS;
++
++ while (!bdev->rds_data_available) {
++ if (file->f_flags & O_NONBLOCK) {
++ retval = -EWOULDBLOCK;
++ goto done;
++ }
++ //interruptible_sleep_on(&bdev->read_queue);
++ if (wait_event_interruptible(bdev->read_queue,
++ bdev->rds_data_available) < 0) {
++ retval = -EINTR;
++ goto done;
++ }
++ }
++
++ mutex_lock(&bdev->mutex);
++ /* copy data to userspace */
++ i = bdev->fifo_size - bdev->rd_index;
++ if (count > i)
++ count = (i / 3) * 3;
++
++ i = 0;
++ while (i < count) {
++ unsigned char tmpbuf[3];
++ tmpbuf[i] = bdev->rds_info.radio_text[bdev->rd_index+i+2];
++ tmpbuf[i+1] = bdev->rds_info.radio_text[bdev->rd_index+i+1];
++ tmpbuf[i+2] = ((bdev->rds_info.radio_text[bdev->rd_index+i] & 0xf0) >> 4);
++ if ((bdev->rds_info.radio_text[bdev->rd_index+i] & BCM2048_RDS_CRC_MASK) == BCM2048_RDS_CRC_UNRECOVARABLE)
++ tmpbuf[i+2] |= 0x80;
++ if (copy_to_user(buf+i, tmpbuf, 3)) {
++ retval = -EFAULT;
++ break;
++ };
++ i += 3;
++ }
++
++ bdev->rd_index += i;
++ if (bdev->rd_index >= bdev->fifo_size)
++ bdev->rds_data_available = 0;
++
++ mutex_unlock(&bdev->mutex);
++ if (retval == 0)
++ retval = i;
++
++done:
++ return retval;
++}
++
+ /*
+ * bcm2048_fops - file operations interface
+ */
+@@ -2147,6 +2267,11 @@
+ .llseek = no_llseek,
+ .ioctl = video_ioctl2,
+ .compat_ioctl = v4l_compat_ioctl32,
++ /* for RDS read support */
++ .open = bcm2048_fops_open,
++ .release = bcm2048_fops_release,
++ .read = bcm2048_fops_read,
++ .poll = bcm2048_fops_poll
+ };
+
+ /*
+@@ -2609,4 +2734,4 @@
+ MODULE_LICENSE("GPL");
+ MODULE_AUTHOR(BCM2048_DRIVER_AUTHOR);
+ MODULE_DESCRIPTION(BCM2048_DRIVER_DESC);
+-MODULE_VERSION("0.0.1");
++MODULE_VERSION("0.0.2");