Add new rdstmc
authorNils Faerber <nils.faerber@kernelconcepts.de>
Fri, 5 Nov 2010 23:04:25 +0000 (00:04 +0100)
committerNils Faerber <nils.faerber@kernelconcepts.de>
Fri, 5 Nov 2010 23:04:25 +0000 (00:04 +0100)
40 files changed:
decoder/Makefile [new file with mode: 0644]
decoder/bitstream.c [new file with mode: 0644]
decoder/bitstream.h [new file with mode: 0644]
decoder/lcl.db.bz2 [new file with mode: 0644]
decoder/rds.c [new file with mode: 0644]
decoder/rds.h [new file with mode: 0644]
decoder/rds_consts.h [new file with mode: 0644]
decoder/rds_test.c [new file with mode: 0644]
decoder/test-dumps/7.7-radio-rds.dump [new file with mode: 0644]
decoder/test-dumps/ch-drs1_rds.dump [new file with mode: 0644]
decoder/test-dumps/ch-drs3_rds.dump [new file with mode: 0644]
decoder/test-dumps/ch-energy_rds.dump [new file with mode: 0644]
decoder/test-dumps/ch-radio24_rds.dump [new file with mode: 0644]
decoder/test-dumps/ch-rete-uno_rds.dump [new file with mode: 0644]
decoder/test-dumps/global-fm-rds.dump [new file with mode: 0644]
decoder/test-dumps/jac-fm-rds.dump [new file with mode: 0644]
decoder/test-dumps/kiss_fm_rssi-77.bin [new file with mode: 0644]
decoder/test-dumps/mix191fm-rds.dump [new file with mode: 0644]
decoder/test-dumps/r.ewtn-89.00-rds.dump [new file with mode: 0644]
decoder/test-dumps/radio-anuncia-rds.dump [new file with mode: 0644]
decoder/test-dumps/radio-faycan-rds.dump [new file with mode: 0644]
decoder/test-dumps/radio-las-arenas-rds.dump [new file with mode: 0644]
decoder/test-dumps/radio-nueve-rds.dump [new file with mode: 0644]
decoder/test-dumps/radio-sol-rds.dump [new file with mode: 0644]
decoder/test-dumps/radio_nova_rssi-88.bin [new file with mode: 0644]
decoder/test-dumps/radio_yle_rssi-83.bin [new file with mode: 0644]
decoder/test-dumps/rds-swr3-expanded.txt [new file with mode: 0644]
decoder/test-dumps/rds-swr3.dump [new file with mode: 0644]
decoder/test-dumps/rds-test2.dump [new file with mode: 0644]
decoder/test-dumps/rds.dump [new file with mode: 0644]
decoder/test-dumps/ylex_rssi-88.bin [new file with mode: 0644]
decoder/test-dumps/zon-holland-rds.dump [new file with mode: 0644]
decoder/tmc.c [new file with mode: 0644]
decoder/tmc.h [new file with mode: 0644]
decoder/tmc_consts.h [new file with mode: 0644]
decoder/uberradio.c [new file with mode: 0644]
decoder/uberradio.ui [new file with mode: 0644]
driver-n900/Readme.txt [new file with mode: 0644]
driver-n900/radio-bcm2048-2.6.28.diff [new file with mode: 0644]
driver-n900/radio-bcm2048.ko [new file with mode: 0644]

diff --git a/decoder/Makefile b/decoder/Makefile
new file mode 100644 (file)
index 0000000..b3d7bd1
--- /dev/null
@@ -0,0 +1,46 @@
+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)
diff --git a/decoder/bitstream.c b/decoder/bitstream.c
new file mode 100644 (file)
index 0000000..82ba2ac
--- /dev/null
@@ -0,0 +1,72 @@
+#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;
+}
+
+
diff --git a/decoder/bitstream.h b/decoder/bitstream.h
new file mode 100644 (file)
index 0000000..901f705
--- /dev/null
@@ -0,0 +1,13 @@
+#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
+
diff --git a/decoder/lcl.db.bz2 b/decoder/lcl.db.bz2
new file mode 100644 (file)
index 0000000..da1c624
Binary files /dev/null and b/decoder/lcl.db.bz2 differ
diff --git a/decoder/rds.c b/decoder/rds.c
new file mode 100644 (file)
index 0000000..ef24cd3
--- /dev/null
@@ -0,0 +1,498 @@
+#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;
+       }
+}
+
diff --git a/decoder/rds.h b/decoder/rds.h
new file mode 100644 (file)
index 0000000..fb56ffe
--- /dev/null
@@ -0,0 +1,67 @@
+#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);
+
+
diff --git a/decoder/rds_consts.h b/decoder/rds_consts.h
new file mode 100644 (file)
index 0000000..e45d6bf
--- /dev/null
@@ -0,0 +1,67 @@
+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"
+};
+
diff --git a/decoder/rds_test.c b/decoder/rds_test.c
new file mode 100644 (file)
index 0000000..3488300
--- /dev/null
@@ -0,0 +1,114 @@
+#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;
+}
+
diff --git a/decoder/test-dumps/7.7-radio-rds.dump b/decoder/test-dumps/7.7-radio-rds.dump
new file mode 100644 (file)
index 0000000..307676c
Binary files /dev/null and b/decoder/test-dumps/7.7-radio-rds.dump differ
diff --git a/decoder/test-dumps/ch-drs1_rds.dump b/decoder/test-dumps/ch-drs1_rds.dump
new file mode 100644 (file)
index 0000000..cab6dc0
Binary files /dev/null and b/decoder/test-dumps/ch-drs1_rds.dump differ
diff --git a/decoder/test-dumps/ch-drs3_rds.dump b/decoder/test-dumps/ch-drs3_rds.dump
new file mode 100644 (file)
index 0000000..69a7ab3
Binary files /dev/null and b/decoder/test-dumps/ch-drs3_rds.dump differ
diff --git a/decoder/test-dumps/ch-energy_rds.dump b/decoder/test-dumps/ch-energy_rds.dump
new file mode 100644 (file)
index 0000000..5bc2ede
Binary files /dev/null and b/decoder/test-dumps/ch-energy_rds.dump differ
diff --git a/decoder/test-dumps/ch-radio24_rds.dump b/decoder/test-dumps/ch-radio24_rds.dump
new file mode 100644 (file)
index 0000000..99df76f
Binary files /dev/null and b/decoder/test-dumps/ch-radio24_rds.dump differ
diff --git a/decoder/test-dumps/ch-rete-uno_rds.dump b/decoder/test-dumps/ch-rete-uno_rds.dump
new file mode 100644 (file)
index 0000000..c50b25d
Binary files /dev/null and b/decoder/test-dumps/ch-rete-uno_rds.dump differ
diff --git a/decoder/test-dumps/global-fm-rds.dump b/decoder/test-dumps/global-fm-rds.dump
new file mode 100644 (file)
index 0000000..116a2a7
Binary files /dev/null and b/decoder/test-dumps/global-fm-rds.dump differ
diff --git a/decoder/test-dumps/jac-fm-rds.dump b/decoder/test-dumps/jac-fm-rds.dump
new file mode 100644 (file)
index 0000000..4097934
Binary files /dev/null and b/decoder/test-dumps/jac-fm-rds.dump differ
diff --git a/decoder/test-dumps/kiss_fm_rssi-77.bin b/decoder/test-dumps/kiss_fm_rssi-77.bin
new file mode 100644 (file)
index 0000000..3739e52
Binary files /dev/null and b/decoder/test-dumps/kiss_fm_rssi-77.bin differ
diff --git a/decoder/test-dumps/mix191fm-rds.dump b/decoder/test-dumps/mix191fm-rds.dump
new file mode 100644 (file)
index 0000000..aaae23e
Binary files /dev/null and b/decoder/test-dumps/mix191fm-rds.dump differ
diff --git a/decoder/test-dumps/r.ewtn-89.00-rds.dump b/decoder/test-dumps/r.ewtn-89.00-rds.dump
new file mode 100644 (file)
index 0000000..a52e5f7
Binary files /dev/null and b/decoder/test-dumps/r.ewtn-89.00-rds.dump differ
diff --git a/decoder/test-dumps/radio-anuncia-rds.dump b/decoder/test-dumps/radio-anuncia-rds.dump
new file mode 100644 (file)
index 0000000..7b5c936
Binary files /dev/null and b/decoder/test-dumps/radio-anuncia-rds.dump differ
diff --git a/decoder/test-dumps/radio-faycan-rds.dump b/decoder/test-dumps/radio-faycan-rds.dump
new file mode 100644 (file)
index 0000000..e38adb3
Binary files /dev/null and b/decoder/test-dumps/radio-faycan-rds.dump differ
diff --git a/decoder/test-dumps/radio-las-arenas-rds.dump b/decoder/test-dumps/radio-las-arenas-rds.dump
new file mode 100644 (file)
index 0000000..4505593
Binary files /dev/null and b/decoder/test-dumps/radio-las-arenas-rds.dump differ
diff --git a/decoder/test-dumps/radio-nueve-rds.dump b/decoder/test-dumps/radio-nueve-rds.dump
new file mode 100644 (file)
index 0000000..12e64c8
Binary files /dev/null and b/decoder/test-dumps/radio-nueve-rds.dump differ
diff --git a/decoder/test-dumps/radio-sol-rds.dump b/decoder/test-dumps/radio-sol-rds.dump
new file mode 100644 (file)
index 0000000..3cfe817
Binary files /dev/null and b/decoder/test-dumps/radio-sol-rds.dump differ
diff --git a/decoder/test-dumps/radio_nova_rssi-88.bin b/decoder/test-dumps/radio_nova_rssi-88.bin
new file mode 100644 (file)
index 0000000..60b23cd
Binary files /dev/null and b/decoder/test-dumps/radio_nova_rssi-88.bin differ
diff --git a/decoder/test-dumps/radio_yle_rssi-83.bin b/decoder/test-dumps/radio_yle_rssi-83.bin
new file mode 100644 (file)
index 0000000..db50955
Binary files /dev/null and b/decoder/test-dumps/radio_yle_rssi-83.bin differ
diff --git a/decoder/test-dumps/rds-swr3-expanded.txt b/decoder/test-dumps/rds-swr3-expanded.txt
new file mode 100644 (file)
index 0000000..6d38415
--- /dev/null
@@ -0,0 +1,59 @@
+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]
diff --git a/decoder/test-dumps/rds-swr3.dump b/decoder/test-dumps/rds-swr3.dump
new file mode 100644 (file)
index 0000000..07faa56
Binary files /dev/null and b/decoder/test-dumps/rds-swr3.dump differ
diff --git a/decoder/test-dumps/rds-test2.dump b/decoder/test-dumps/rds-test2.dump
new file mode 100644 (file)
index 0000000..c687022
Binary files /dev/null and b/decoder/test-dumps/rds-test2.dump differ
diff --git a/decoder/test-dumps/rds.dump b/decoder/test-dumps/rds.dump
new file mode 100644 (file)
index 0000000..47963ae
Binary files /dev/null and b/decoder/test-dumps/rds.dump differ
diff --git a/decoder/test-dumps/ylex_rssi-88.bin b/decoder/test-dumps/ylex_rssi-88.bin
new file mode 100644 (file)
index 0000000..56c9606
Binary files /dev/null and b/decoder/test-dumps/ylex_rssi-88.bin differ
diff --git a/decoder/test-dumps/zon-holland-rds.dump b/decoder/test-dumps/zon-holland-rds.dump
new file mode 100644 (file)
index 0000000..580c907
Binary files /dev/null and b/decoder/test-dumps/zon-holland-rds.dump differ
diff --git a/decoder/tmc.c b/decoder/tmc.c
new file mode 100644 (file)
index 0000000..5ab5083
--- /dev/null
@@ -0,0 +1,550 @@
+#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
+               }
+       }
+}
+
diff --git a/decoder/tmc.h b/decoder/tmc.h
new file mode 100644 (file)
index 0000000..906b51f
--- /dev/null
@@ -0,0 +1,24 @@
+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);
+
diff --git a/decoder/tmc_consts.h b/decoder/tmc_consts.h
new file mode 100644 (file)
index 0000000..f4434cf
--- /dev/null
@@ -0,0 +1,36 @@
+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..."
+};
+
+
diff --git a/decoder/uberradio.c b/decoder/uberradio.c
new file mode 100644 (file)
index 0000000..f870807
--- /dev/null
@@ -0,0 +1,217 @@
+#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;
+}
diff --git a/decoder/uberradio.ui b/decoder/uberradio.ui
new file mode 100644 (file)
index 0000000..00d8399
--- /dev/null
@@ -0,0 +1,209 @@
+<?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">&lt;big&gt;&lt;b&gt;???.?? MHz&lt;/b&gt;&lt;/big&gt;</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">&lt;b&gt;TMC&lt;/b&gt;</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>
diff --git a/driver-n900/Readme.txt b/driver-n900/Readme.txt
new file mode 100644 (file)
index 0000000..68db733
--- /dev/null
@@ -0,0 +1,5 @@
+
+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).
+
diff --git a/driver-n900/radio-bcm2048-2.6.28.diff b/driver-n900/radio-bcm2048-2.6.28.diff
new file mode 100644 (file)
index 0000000..d60ada5
--- /dev/null
@@ -0,0 +1,191 @@
+--- ../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");
diff --git a/driver-n900/radio-bcm2048.ko b/driver-n900/radio-bcm2048.ko
new file mode 100644 (file)
index 0000000..9c3d306
Binary files /dev/null and b/driver-n900/radio-bcm2048.ko differ