From: Nils Faerber Date: Fri, 5 Nov 2010 23:04:25 +0000 (+0100) Subject: Add new rdstmc X-Git-Url: https://git.kernelconcepts.de/?p=rdstmc.git;a=commitdiff_plain;h=a27782826a9f7706a7fff7b5a47ea6b03bb743c5 Add new rdstmc --- a27782826a9f7706a7fff7b5a47ea6b03bb743c5 diff --git a/decoder/Makefile b/decoder/Makefile new file mode 100644 index 0000000..b3d7bd1 --- /dev/null +++ b/decoder/Makefile @@ -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 index 0000000..82ba2ac --- /dev/null +++ b/decoder/bitstream.c @@ -0,0 +1,72 @@ +#include +#include +#include + +#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 index 0000000..901f705 --- /dev/null +++ b/decoder/bitstream.h @@ -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 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 index 0000000..ef24cd3 --- /dev/null +++ b/decoder/rds.c @@ -0,0 +1,498 @@ +#include +#include +#include +#include +#include +#include +#include +#include + +#include + +#include "rds.h" +#include "rds_consts.h" +#include "tmc.h" + +//#define DEBUG 1 + + +static struct rds_info_s rds_info; +static struct tm rds_time; + +static struct _RDS_Private { + void (*rds_PI_cb)(unsigned short PI, unsigned char ccode, unsigned char ptype, unsigned char pref, void *user_data); + void *rds_PI_cb_data; + void (*rds_sname_cb)(char *sname, void *user_data); + void *rds_sname_cb_data; + void (*rds_sinfo_cb)(unsigned char TP, unsigned char TA, unsigned char MS, unsigned char PTY, void *user_data); + void *rds_sinfo_cb_data; + void (*rds_radiotext_cb)(char *radio_text, void *user_data); + void *rds_radiotext_cb_data; + void (*rds_date_time_cb)(struct tm *dtime, void *user_data); + void *rds_date_time_cb_data; + void (*rds_afinfo_cb)(unsigned char cnt, double *AF, void *user_data); + void *rds_afinfo_cb_data; +} _rds_private; + +void rds_init(void) { + memset(&_rds_private, 0, sizeof(struct _RDS_Private)); +} + +void rds_set_PI_cb(void (*rds_PI_cb)(unsigned short PI, unsigned char ccode, unsigned char ptype, unsigned char pref, void *user_data), void *user_data) +{ + _rds_private.rds_PI_cb = rds_PI_cb; + _rds_private.rds_PI_cb_data = user_data; +} + +void rds_set_sname_cb(void (*rds_sname_cb)(char *sname, void *user_data), void *user_data) +{ + _rds_private.rds_sname_cb = rds_sname_cb; + _rds_private.rds_sname_cb_data = user_data; +} + +void rds_set_sinfo_cb(void (*rds_sinfo_cb)(unsigned char TP, unsigned char TA, unsigned char MS, unsigned char PTY, void *user_data), void *user_data) +{ + _rds_private.rds_sinfo_cb = rds_sinfo_cb; + _rds_private.rds_sinfo_cb_data = user_data; +} + +void rds_set_afinfo_cb(void (*rds_afinfo_cb)(unsigned char cnt, double *AF, void *user_data), void *user_data) +{ + _rds_private.rds_afinfo_cb = rds_afinfo_cb; + _rds_private.rds_afinfo_cb_data = user_data; +} + +void rds_set_radiotext_cb(void (*rds_radiotext_cb)(char *radio_text, void *user_data), void *user_data) +{ + _rds_private.rds_radiotext_cb = rds_radiotext_cb; + _rds_private.rds_radiotext_cb_data = user_data; +} + +void rds_set_date_time_cb(void (*rds_date_time_cb)(struct tm *dtime, void *user_data), void *user_data) +{ + _rds_private.rds_date_time_cb = rds_date_time_cb; + _rds_private.rds_date_time_cb_data = user_data; +} + + +int rds_receive_group(int rds_fd, unsigned short *rdsgroup) +{ +static unsigned char expected = 0; +int rb; +unsigned char rbuf[3]; +unsigned short block; +unsigned char offset; + + rb = read(rds_fd, rbuf, 3); + if (rb <= 0) + 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 index 0000000..fb56ffe --- /dev/null +++ b/decoder/rds.h @@ -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 index 0000000..e45d6bf --- /dev/null +++ b/decoder/rds_consts.h @@ -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 index 0000000..3488300 --- /dev/null +++ b/decoder/rds_test.c @@ -0,0 +1,114 @@ +#include +#include +#include +#include +#include +#include +#include +#include + +#include + +#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 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 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 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 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 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 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 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 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 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 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 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 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 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 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 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 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 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 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 index 0000000..6d38415 --- /dev/null +++ b/decoder/test-dumps/rds-swr3-expanded.txt @@ -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 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 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 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 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 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 index 0000000..5ab5083 --- /dev/null +++ b/decoder/tmc.c @@ -0,0 +1,550 @@ +#include +#include +#include +#include +#include +#include +#include +#include + +#include + +#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> 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> 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 index 0000000..906b51f --- /dev/null +++ b/decoder/tmc.h @@ -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 index 0000000..f4434cf --- /dev/null +++ b/decoder/tmc_consts.h @@ -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 index 0000000..f870807 --- /dev/null +++ b/decoder/uberradio.c @@ -0,0 +1,217 @@ +#include +#include +#include +#include +#include +#include +#include + +#include + +#include + +#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, "%s", 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, "%s", 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, "PI %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 index 0000000..00d8399 --- /dev/null +++ b/decoder/uberradio.ui @@ -0,0 +1,209 @@ + + + + + + + + + True + + + True + <big><b>???.?? MHz</b></big> + True + + + False + False + 2 + 0 + + + + + True + + + True + True + 10 + + + False + False + 0 + + + + + True + + + True + True + True + + + 0 + + + + + True + + + 1 + + + + + True + True + + + 2 + + + + + 1 + + + + + True + + + True + TP + + + 0 + + + + + True + TA + + + 1 + + + + + True + M + + + 2 + + + + + True + S + + + 3 + + + + + False + False + 2 + 2 + + + + + True + + + 2 + 3 + + + + + True + True + + + 2 + 4 + + + + + False + False + 1 + + + + + True + True + 66 + + + False + False + 2 + + + + + True + 0 + none + + + True + 12 + + + True + + + + + + + + + True + True + False + word + False + TMC_MSG_Buffer + + + 2 + + + + + + + + + True + <b>TMC</b> + True + + + + + 3 + + + + + + + diff --git a/driver-n900/Readme.txt b/driver-n900/Readme.txt new file mode 100644 index 0000000..68db733 --- /dev/null +++ b/driver-n900/Readme.txt @@ -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 index 0000000..d60ada5 --- /dev/null +++ b/driver-n900/radio-bcm2048-2.6.28.diff @@ -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 + * ++ * Copyright (C) Nils Faerber ++ * + * 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 ++ * Version 0.0.1 ++ * - Initial implementation ++ * 2010-02-21 Nils Faerber ++ * Version 0.0.2 ++ * - Add support for interrupt driven rds data reading ++ */ ++ + #include + #include + #include +@@ -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 index 0000000..9c3d306 Binary files /dev/null and b/driver-n900/radio-bcm2048.ko differ