diff --git a/README.md b/README.md index f070be3..cede44e 100644 --- a/README.md +++ b/README.md @@ -1,3 +1,16 @@ # rdstmc -application to decode RDS and TMC data from an RDS data stream \ No newline at end of file +application to decode RDS and TMC data from an RDS data stream +This is a small project to RDS data reception and decoding. +Do not expect anything dramatically great or extremely usable :) + +decoder/ + Code for decoding RDS and TMC data streams + +driver-n900/ + Enhancement to the Nokia N900 FM radio driver to + actually output the raw RDS data in V4L compliant format + to the radio device (/dev/radio1) - kernel diff and + ready compiled kernel module (because it was nasty to get + the compile setup right - just for your convenience) + diff --git a/decoder/Makefile b/decoder/Makefile new file mode 100644 index 0000000..bff35c4 --- /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/Readme.txt b/decoder/Readme.txt new file mode 100644 index 0000000..3bea613 --- /dev/null +++ b/decoder/Readme.txt @@ -0,0 +1,4 @@ + +All files contained in here are licensed under the GPL where no other +license applies. +(I will clean this up a little later) diff --git a/decoder/bitstream.c b/decoder/bitstream.c new file mode 100644 index 0000000..222459c --- /dev/null +++ b/decoder/bitstream.c @@ -0,0 +1,82 @@ +/* + * Copyright (C) 2009, 2010 by Nicole Faerber + * + * This program is free software; you can redistribute it and/or + * modify it under the terms of the GNU General Public License + * as published by the Free Software Foundation; either version + * 2 of the License, or (at your option) any later version. + * + */ + +#include +#include +#include + +#include "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..1561681 --- /dev/null +++ b/decoder/bitstream.h @@ -0,0 +1,23 @@ +/* + * Copyright (C) 2009, 2010 by Nicole Faerber + * + * This program is free software; you can redistribute it and/or + * modify it under the terms of the GNU General Public License + * as published by the Free Software Foundation; either version + * 2 of the License, or (at your option) any later version. + * + */ + +#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..06ebdb4 --- /dev/null +++ b/decoder/rds.c @@ -0,0 +1,561 @@ +/* + * Copyright (C) 2009, 2010 by Nicole Faerber + * + * This program is free software; you can redistribute it and/or + * modify it under the terms of the GNU General Public License + * as published by the Free Software Foundation; either version + * 2 of the License, or (at your option) any later version. + * + */ + +#include +#include +#include +#include +#include +#include +#include +#include + +#include + +#include "rds.h" +#include "rds_consts.h" +#include "tmc.h" + +//#define DEBUG 1 + + +static struct rds_info_s rds_info; +static struct tm rds_time; + +static struct _RDS_Private { + void (*rds_PI_cb)(unsigned short PI, unsigned char ccode, unsigned char ptype, unsigned char pref, void *user_data); + void *rds_PI_cb_data; + void (*rds_sname_cb)(char *sname, void *user_data); + void *rds_sname_cb_data; + void (*rds_sinfo_cb)(unsigned char TP, unsigned char TA, unsigned char MS, unsigned char PTY, void *user_data); + void *rds_sinfo_cb_data; + void (*rds_radiotext_cb)(char *radio_text, void *user_data); + void *rds_radiotext_cb_data; + void (*rds_date_time_cb)(struct tm *dtime, void *user_data); + void *rds_date_time_cb_data; + void (*rds_afinfo_cb)(unsigned char cnt, double *AF, void *user_data); + void *rds_afinfo_cb_data; +} _rds_private; + +void rds_init(void) { + memset(&_rds_private, 0, sizeof(struct _RDS_Private)); +} + +void rds_set_PI_cb(void (*rds_PI_cb)(unsigned short PI, unsigned char ccode, unsigned char ptype, unsigned char pref, void *user_data), void *user_data) +{ + _rds_private.rds_PI_cb = rds_PI_cb; + _rds_private.rds_PI_cb_data = user_data; +} + +void rds_set_sname_cb(void (*rds_sname_cb)(char *sname, void *user_data), void *user_data) +{ + _rds_private.rds_sname_cb = rds_sname_cb; + _rds_private.rds_sname_cb_data = user_data; +} + +void rds_set_sinfo_cb(void (*rds_sinfo_cb)(unsigned char TP, unsigned char TA, unsigned char MS, unsigned char PTY, void *user_data), void *user_data) +{ + _rds_private.rds_sinfo_cb = rds_sinfo_cb; + _rds_private.rds_sinfo_cb_data = user_data; +} + +void rds_set_afinfo_cb(void (*rds_afinfo_cb)(unsigned char cnt, double *AF, void *user_data), void *user_data) +{ + _rds_private.rds_afinfo_cb = rds_afinfo_cb; + _rds_private.rds_afinfo_cb_data = user_data; +} + +void rds_set_radiotext_cb(void (*rds_radiotext_cb)(char *radio_text, void *user_data), void *user_data) +{ + _rds_private.rds_radiotext_cb = rds_radiotext_cb; + _rds_private.rds_radiotext_cb_data = user_data; +} + +void rds_set_date_time_cb(void (*rds_date_time_cb)(struct tm *dtime, void *user_data), void *user_data) +{ + _rds_private.rds_date_time_cb = rds_date_time_cb; + _rds_private.rds_date_time_cb_data = user_data; +} + + +int rds_receive_group(int rds_fd, unsigned short *rdsgroup) +{ +static unsigned char expected = 0; +int rb; +unsigned char rbuf[3]; +unsigned short block; +unsigned char offset; + + rb = read(rds_fd, rbuf, 3); + if (rb <= 0) + return 0; + if (rb != 3) { + printf("#read err rb=%d\n", rb); + return 0; + } + block = rbuf[0] | (rbuf[1] << 8); + offset = rbuf[2] & 0x07; + + if (rbuf[2] & 0x80) { + if (OutputFlags & RDS_RECEIVE_INDICATOR) + printf("E"); + } else { + // printf("group block=0x%04x offs=0x%02x\n", block, offset); + if (offset != expected) { + if (OutputFlags & RDS_RECEIVE_INDICATOR) + printf("block out of sync - resetting\n"); + expected = 0; + memset(rdsgroup, 0, sizeof(rdsgroup)); + } else { + rdsgroup[offset] = block; + expected++; + if (expected >= 4) { + expected = 0; + if (OutputFlags & RDS_RECEIVE_INDICATOR) + printf(".\n"); + return 1; + } + } + } + if (OutputFlags & RDS_RECEIVE_INDICATOR) + printf("."); + + return 0; +} + + +enum RDSGroupType { GROUP_0A=0, GROUP_0B, GROUP_1A, GROUP_1B, GROUP_2A, GROUP_2B, + GROUP_3A, GROUP_3B, GROUP_4A, GROUP_4B, GROUP_5A, GROUP_5B, + GROUP_6A, GROUP_6B, GROUP_7A, GROUP_7B, GROUP_8A, GROUP_8B, + GROUP_9A, GROUP_9B, GROUP_10A, GROUP_10B, GROUP_11A, GROUP_11B, + GROUP_12A, GROUP_12B, GROUP_13A, GROUP_13B, GROUP_14A, GROUP_14B, + GROUP_15A, GROUP_15B, GROUP_UNKNOWN }; + +void rds_radio_retuned(void) +{ + memset(&rds_info, 0, sizeof(rds_info)); + rds_info.LTN = -1; + memset(&rds_time, 0, sizeof(rds_time)); +} + +void rds_decode_group(unsigned short *rdsgroup) +{ +static unsigned short ogrp[4]; +static unsigned char grp_decoded = 0; +static unsigned char sname_rcvd = 0; +unsigned char grp_type = (rdsgroup[1] >> 11); +unsigned char offs; +static unsigned char otextAB = 0, newtext = 0; +unsigned short PI = rdsgroup[0]; +unsigned char textAB = 0; +int local_time_off = 0; +int day_code = 0; +int year_, mon_, K; + + /* we want to wait for at least two identical group transmits */ + if (ogrp[0] != rdsgroup[0] || ogrp[1] != rdsgroup[1] || ogrp[2] != rdsgroup[2] || ogrp[3] != rdsgroup[3]) { + ogrp[0] = rdsgroup[0]; + ogrp[1] = rdsgroup[1]; + ogrp[2] = rdsgroup[2]; + ogrp[3] = rdsgroup[3]; + //printf("grp: 0x%04x 0x%04x 0x%04x 0x%04x\n", rdsgroup[0], rdsgroup[1], rdsgroup[2], rdsgroup[3]); + grp_decoded = 0; + return; + } + if (grp_decoded) + return; + + if (rds_info.PI != PI) { + /* station change? */ + memset(&rds_info, 0, sizeof(rds_info)); + rds_info.LTN = -1; + memset(&rds_time, 0, sizeof(rds_time)); + rds_info.ccode = (PI & 0xf000) >> 12; + rds_info.ptype = (PI & 0x0f00) >> 8; + rds_info.pref = (PI & 0x00ff); + sname_rcvd = 0; + if (rds_info.pref == 0) /* something is wrong here */ + return; + rds_info.PI = rdsgroup[0]; + if (_rds_private.rds_PI_cb != NULL) + _rds_private.rds_PI_cb(rds_info.PI, rds_info.ccode, rds_info.ptype, rds_info.pref, _rds_private.rds_PI_cb_data); + if (OutputFlags & RDS_OUTPUT_RDSINFO) + printf("PI=%d ccode=%X ptype=%X '%s' '%s' pref=%d\n", rds_info.PI, rds_info.ccode, rds_info.ptype, ptype_stext[rds_info.ptype], ptype_ltext[rds_info.ptype], rds_info.pref); + } + + switch (grp_type) { + case GROUP_0A: { /* basic switching and tuning */ + unsigned char AFc1, AFc2; + float AF1=0, AF2=0; + + offs = (rdsgroup[1] & 0x03); + if (offs == 0) + sname_rcvd = 0; + if (offs == 1 && sname_rcvd == 0) + sname_rcvd = 1; + if (offs == 2 && sname_rcvd == 1) + sname_rcvd = 2; + if (offs == 3 && sname_rcvd == 2) + sname_rcvd = 3; + rds_info.sname[offs*2] = ((rdsgroup[3] & 0xff00) >> 8); + rds_info.sname[(offs*2)+1] = rdsgroup[3] & 0x00ff; + if (_rds_private.rds_sname_cb != NULL && sname_rcvd == 3) { + _rds_private.rds_sname_cb(rds_info.sname, _rds_private.rds_sname_cb_data); + sname_rcvd = 0; + } + + rds_info.TA = (rdsgroup[1] & 0x10) >> 4; + rds_info.TP = (rdsgroup[1] & 0x400) >> 10; + rds_info.MS = (rdsgroup[1] & 0x08) >> 3; + rds_info.PTY = (rdsgroup[1] & 0x3e0) >> 5; + if (_rds_private.rds_sinfo_cb != NULL) + _rds_private.rds_sinfo_cb(rds_info.TP, rds_info.TA, rds_info.MS, rds_info.PTY, _rds_private.rds_sinfo_cb_data); + + AFc1 = ((rdsgroup[2] & 0xff00) >> 8); + AFc2 = (rdsgroup[2] & 0x00ff); + if (AFc1 > 225 && AFc1 < 250) { + if (OutputFlags & RDS_OUTPUT_STATION_ID) + printf(" %d AFs follow:\n", AFc1-224); + } else if (AFc1 > 0 && AFc1 < 205) + AF1 = (float)87.5 + ((float)AFc1 * .1); + else if (AFc1 == 205) + printf(" filler- \n"); + if (AFc2 > 0 && AFc2 < 205) + AF2 = (float)87.5 + ((float)AFc2 * .1); + + if (OutputFlags & RDS_OUTPUT_STATION_ID) + printf("sname = '%s' %s %s %s AF1=%3.2f AF2=%3.2f PTY='%s'\n", rds_info.sname, rds_info.TP ? "TP" : "", rds_info.TA ? "TA" : "", rds_info.MS ? "M" : "S", AF1, AF2, PTY_text[rds_info.PTY]); + } + break; + case GROUP_0B: + if (OutputFlags & RDS_OUTPUT_UNKNGRP) + printf("group 0B\n"); + break; + case GROUP_1A: { /* Programme Item Number and slow labelling codes */ + unsigned char variant, day, hour, minute; + unsigned short tmc_id; + + variant = (rdsgroup[2] & (0x07 << 12)) >> 12; + day = (rdsgroup[3] & (0x1f << 11)) >> 11; + hour = (rdsgroup[3] & (0x1f << 6)) >> 6; + minute = (rdsgroup[3] & 0x3f); + if (OutputFlags & RDS_OUTPUT_UNKNGRP) + printf("1A var=%d day=%d %d:%d ", variant, day, hour, minute); + if (variant == 0) { + rds_info.ECC = (rdsgroup[2] & 0xff); + printf(" ccode=%d ECC=0x%02x '%s' ", rds_info.ccode, rds_info.ECC, ECC_text[((rds_info.ECC & 0x0f)*16)+(rds_info.ccode-1)]); + } + if (variant == 1) { + tmc_id = (rdsgroup[2] & 0x0fff); + printf(" TMC ID=0x%04x", tmc_id); + } + if (variant == 2) { + printf(" Paging ID=0x%04x", (rdsgroup[2] & 0x0fff)); + } + if (variant == 3) { + rds_info.lang_code = (rdsgroup[2] & 0xff); + printf(" language=0x%02x ", rds_info.lang_code); + switch (rds_info.lang_code) { + case 0x03: + printf("Catalan "); + break; + case 0x08: + printf("German "); + break; + case 0x0a: + printf("Spanish "); + break; + case 0x15: + printf("Italian "); + break; + case 0x18: + printf("Latvian "); + break; + case 0x1d: + printf("Dutch "); + break; + case 0x27: + printf("Finnish "); + break; + default: + printf("unknown "); + break; + }; + } + if (variant == 7) { + printf(" EWS Channel ID=0x%04x", (rdsgroup[2] & 0x0fff)); + } + printf("\n"); + } break; + case GROUP_1B: + if (OutputFlags & RDS_OUTPUT_UNKNGRP) + printf("group 1B\n"); + break; + case GROUP_2A: /* 2A has 64 chars, 4 per group */ + case GROUP_2B: /* 2B has 32 chars, 2 per group */ + offs = (rdsgroup[1] & 0x0f); + textAB = (rdsgroup[1] & 0x10); + if (textAB != otextAB) { + memset(rds_info.rtext, ' ', 64); + rds_info.rtext[64] = '\0'; + } + otextAB = textAB; + if (grp_type == GROUP_2A) { + rds_info.rtext[offs*4] = ((rdsgroup[2] & 0xff00) >> 8); + rds_info.rtext[(offs*4)+1] = (rdsgroup[2] & 0x00ff); + rds_info.rtext[(offs*4)+2] = ((rdsgroup[3] & 0xff00) >> 8); + rds_info.rtext[(offs*4)+3] = (rdsgroup[3] & 0x00ff); + } else { + rds_info.rtext[offs*2] = ((rdsgroup[3] & 0xff00) >> 8); + rds_info.rtext[(offs*2)+1] = (rdsgroup[3] & 0x00ff); + } + if (offs == 0) /* new text always starts at 0 */ + newtext = 1; + if ((offs == 15)) { /* all parts received */ + if (newtext == 1) { + if (_rds_private.rds_radiotext_cb != NULL) + _rds_private.rds_radiotext_cb(rds_info.rtext, _rds_private.rds_radiotext_cb_data); + if (OutputFlags & RDS_OUTPUT_RADIO_TEXT) + printf("RT '%s'\n", rds_info.rtext); + newtext = 0; + } + } + break; + case GROUP_3A: /* ODA - TMC alarm, */ + rds_info.AID = rdsgroup[3]; + /* TMC AID */ + if (rds_info.AID == 0xcd46 || rds_info.AID == 0x0d45) { + if ((rdsgroup[2] & 0xc000) == 0) { + rds_info.LTN = (rdsgroup[2] & 0x0fc0) >> 6; + rds_info.AFI = (rdsgroup[2] & 0x0020) >> 5; + rds_info.M = (rdsgroup[2] & 0x0010) >> 4; + rds_info.I = (rdsgroup[2] & 0x0008) >> 3; + rds_info.N = (rdsgroup[2] & 0x0004) >> 2; + rds_info.R = (rdsgroup[2] & 0x0002) >> 1; + rds_info.U = (rdsgroup[2] & 0x0001); + } + if (rdsgroup[2] & 0x4000) { + /* SID */ + if (rds_info.M) { + rds_info.G = (rdsgroup[2] & 0x3000) >> 12; + rds_info.Ta = (rdsgroup[2] & 0x0030) >> 4; + rds_info.Tw = (rdsgroup[2] & 0x000c) >> 2; + rds_info.Td = (rdsgroup[2] & 0x0003); + } else { + } + } + if (OutputFlags & RDS_OUTPUT_RDSINFO) { + printf("AID = 0x%04x\n", rds_info.AID); + printf("LTN ID = %d (0x%02x)\n", rds_info.LTN, rds_info.LTN); + printf("Traffic information: "); + if (rds_info.I) + printf("international "); + if (rds_info.N) + printf("national "); + if (rds_info.R) + printf("regional "); + if (rds_info.U) + printf("urban "); + printf("\n"); + } + } else if (rds_info.AID == 0x4bd7) { + if (OutputFlags & RDS_OUTPUT_RDSINFO) { + printf("RT+\nG2 = 0x%04x\n", rdsgroup[2]); + printf("template = 0x%02x\n", (rdsgroup[2] & 0x00ff)); + printf("SCB = 0x%02x\n", (rdsgroup[2] & 0x0f00)>>8); + printf("CB = 0x%02x\n", (rdsgroup[2] & 0x1000)>>12); + printf("rfu = 0x%02x\n", (rdsgroup[2] & 0xe000)>>13); + } + } else { + printf("3A AID=0x%04x\n", rds_info.AID); + } + break; + case GROUP_3B: + if (OutputFlags & RDS_OUTPUT_UNKNGRP) + printf("group 3B\n"); + break; + case GROUP_4A: + //printf("group 4A - time/date\n"); + rds_time.tm_sec = 0; // date/time message is sent at 00 secs +/- .2sec + rds_time.tm_min = ((rdsgroup[3] & (0x3f << 6)) >> 6); + rds_time.tm_hour = ((rdsgroup[3] & (0x0f << 12)) >> 12) | ((rdsgroup[2] & 0x01) << 4); + local_time_off = (rdsgroup[3] & 0x0f) * ((rdsgroup[3] & 0x10) ? -1 : 1); + rds_time.tm_hour += local_time_off / 2; + rds_time.tm_min = (rds_time.tm_min + ((local_time_off % 2) * 30)) % 60; + + day_code = (((rdsgroup[2] & 0xfffe) >> 1) | ((rdsgroup[1] & 0x03) << 15)) /*+ local_time_off*/; + + year_ = ((float)day_code - 15078.2) / 365.25; + mon_ = ((day_code - 14956.1) - (int)(year_ * 365.25)) / 30.6001; + rds_time.tm_mday = day_code - 14956 - (int)(year_ * 365.25) - (int)(mon_ * 30.6001); + if (mon_ == 14 || mon_ == 15) + K = 1; + else + K = 0; + rds_time.tm_year = year_ + K; + rds_time.tm_mon = mon_ - 1 - (K * 12) - 1; + + rds_time.tm_isdst = -1; + + if (_rds_private.rds_date_time_cb != NULL) + _rds_private.rds_date_time_cb(&rds_time, _rds_private.rds_date_time_cb_data); + + if (OutputFlags & RDS_OUTPUT_DATETIME) + printf("%s", asctime(&rds_time)); +#ifdef DEBUG + printf("%d:%02d - local time offset = %d, %s (dcode = %d)\n", rds_time.tm_hour, rds_time.tm_min, local_time_off, asctime(&rds_time), day_code); +#endif + //printf("day=%d month=%d year=%d\n", day, mon, year); + break; + case GROUP_4B: + if (OutputFlags & RDS_OUTPUT_UNKNGRP) + printf("GRP4B\n"); + break; + case GROUP_5A: + if (OutputFlags & RDS_OUTPUT_UNKNGRP) + printf("GRP5A\n"); + break; + case GROUP_5B: + if (OutputFlags & RDS_OUTPUT_UNKNGRP) + printf("GRP5B\n"); + break; + case GROUP_6A: + if (OutputFlags & RDS_OUTPUT_UNKNGRP) + printf("GRP6A\n"); + break; + case GROUP_6B: + if (OutputFlags & RDS_OUTPUT_UNKNGRP) + printf("GRP6B\n"); + break; + case GROUP_7A: + if (OutputFlags & RDS_OUTPUT_UNKNGRP) + printf("GRP7A\n"); + break; + case GROUP_7B: + if (OutputFlags & RDS_OUTPUT_UNKNGRP) + printf("GRP7B\n"); + break; + case GROUP_8A: + if (rds_info.LTN != 0) // non TMCpro + decode_tmc(rdsgroup); + if (rds_info.LTN == 0) + printf("TMCpro\n"); + break; + case GROUP_8B: + if (OutputFlags & RDS_OUTPUT_UNKNGRP) + printf("GRP8B\n"); + break; + case GROUP_9A: + if (OutputFlags & RDS_OUTPUT_UNKNGRP) + printf("GRP9A\n"); + break; + case GROUP_9B: + if (OutputFlags & RDS_OUTPUT_UNKNGRP) + printf("GRP9B\n"); + break; + case GROUP_10A: + if (OutputFlags & RDS_OUTPUT_UNKNGRP) + printf("GRP10A\n"); + break; + case GROUP_10B: + if (OutputFlags & RDS_OUTPUT_UNKNGRP) + printf("GRP10B\n"); + break; + case GROUP_11A: /* Open Data Application ODA */ + if (OutputFlags & RDS_OUTPUT_UNKNGRP) + printf("GRP11A ODA: #1=0x%02x #2=0x%04x #3=0x%04x\n", rdsgroup[1] & 0x1f, rdsgroup[2], rdsgroup[3]); + + /* we previously got an RT+ identifier, try RT+ decoding */ + if (rds_info.AID == 0x4bd7) { + printf("RT+\ntoggle: %s\n", (rdsgroup[1] & 0x10) ? "yes" : "no"); + printf("item running : %s\n", (rdsgroup[1] & 0x08) ? "yes" : "no"); + printf("content type 1 : %d\n", (rdsgroup[1] & 0x07) << 3 | (rdsgroup[2] & 0x0e00) >> 13); + printf("start marker 1 : %d\n", (rdsgroup[2] & 0x1f80) >> 7); + printf("length marker 1: %d\n", (rdsgroup[2] & 0x007e) >> 1); + printf("content type 2 : %d\n", (rdsgroup[1] & 0x01) << 4); + printf("start marker 2 : %d\n", (rdsgroup[2] & 0x07e0) >> 5); + printf("length marker 2: %d\n", (rdsgroup[2] & 0x001f)); + } + break; + case GROUP_11B: /* Open Data Application ODA */ + if (OutputFlags & RDS_OUTPUT_UNKNGRP) + printf("GRP11B\n"); + printf("11B ODA: #1=0x%02x PI=0x%04x #3=0x%04x\n", rdsgroup[1] & 0x1f, rdsgroup[2], rdsgroup[3]); + break; + case GROUP_12A: /* Open Data Application ODA */ + if (OutputFlags & RDS_OUTPUT_UNKNGRP) + printf("GRP12A\n"); + break; + case GROUP_12B: /* Open Data Application ODA */ + if (OutputFlags & RDS_OUTPUT_UNKNGRP) + printf("GRP12B\n"); + break; + case GROUP_13A: /* Open Data Application ODA or paging */ + if (OutputFlags & RDS_OUTPUT_UNKNGRP) + printf("GRP13A\n"); + break; + case GROUP_13B: /* Open Data Application ODA */ + if (OutputFlags & RDS_OUTPUT_UNKNGRP) + printf("GRP13B\n"); + break; + case GROUP_14A: /* EON */ { + unsigned char variant = rdsgroup[1] & 0x0f; + unsigned char AFc1=0, AFc2=0; + float AF1=0, AF2=0; + unsigned short PI; + + rds_info.PTY = (rdsgroup[1] & 0x3e0) >> 5; + rds_info.TPTN = (rdsgroup[1] & 0x400) >> 10; + rds_info.TPON = (rdsgroup[1] & 0x10) >> 4; + PI = rdsgroup[3]; + + if (variant <= 3) { // PS + rds_info.PS[variant*2] = ((rdsgroup[2] & 0xff00) >> 8); + rds_info.PS[(variant*2)+1] = (rdsgroup[2] & 0x00ff); + } else if (variant == 4) { + printf("EON variant 4\n"); + } else if (variant >= 5 && variant <= 8) { // AF - alternate frequency + AFc1 = ((rdsgroup[2] & 0xff00) >> 8); + AFc2 = (rdsgroup[2] & 0x00ff); + if (AFc1 > 0 && AFc1 < 205) + AF1 = (float)87.5 + ((float)AFc1 * .1); + else if (AFc1 >= 225 && AFc1 <= 249) + AF1 = AFc1 - 224; + if (AFc2 > 0 && AFc2 < 205) + AF2 = (float)87.5 + ((float)AFc2 * .1); + else if (AFc2 >= 225 && AFc2 <= 249) + AF2 = AFc2 - 224; + } + if (OutputFlags & RDS_OUTPUT_EON) { + printf("EON var=0x%01x PTY=%d ('%s') PS='%s' AF1=%3.2f (%d) AF2=%3.2f (%d) PI=%d\n", variant, rds_info.PTY, PTY_text[rds_info.PTY], rds_info.PS, AF1, AFc1, AF2, AFc2, PI); + } + } break; + case GROUP_14B: /* Enhanced Other Networks information */ + if (OutputFlags & RDS_OUTPUT_EON) + printf("GRP14B: Enhanced Other Networks information\n"); + break; + case GROUP_15A: + if (OutputFlags & RDS_OUTPUT_UNKNGRP) + printf("GRP15A\n"); + break; + case GROUP_15B: /* Fast basic tuning and switching information */ + if (OutputFlags & RDS_OUTPUT_UNKNGRP) + printf("GRP15B: Fast basic tuning and switching information\n"); + break; + default: + if (OutputFlags & RDS_OUTPUT_UNKNGRP) + printf("unkn. RDS group %d\n", grp_type); + break; + } + + /* done with this group */ + grp_decoded = 1; +} + diff --git a/decoder/rds.h b/decoder/rds.h new file mode 100644 index 0000000..4732204 --- /dev/null +++ b/decoder/rds.h @@ -0,0 +1,78 @@ +/* + * Copyright (C) 2009, 2010 by Nicole Faerber + * + * This program is free software; you can redistribute it and/or + * modify it under the terms of the GNU General Public License + * as published by the Free Software Foundation; either version + * 2 of the License, or (at your option) any later version. + * + */ + +#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_radio_retuned(void); +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..f6f5458 --- /dev/null +++ b/decoder/rds_consts.h @@ -0,0 +1,77 @@ +/* + * Copyright (C) 2009, 2010 by Nicole Faerber + * + * This program is free software; you can redistribute it and/or + * modify it under the terms of the GNU General Public License + * as published by the Free Software Foundation; either version + * 2 of the License, or (at your option) any later version. + * + */ + +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..b8f187b --- /dev/null +++ b/decoder/rds_test.c @@ -0,0 +1,129 @@ +/* + * Copyright (C) 2009, 2010 by Nicole Faerber + * + * This program is free software; you can redistribute it and/or + * modify it under the terms of the GNU General Public License + * as published by the Free Software Foundation; either version + * 2 of the License, or (at your option) any later version. + * + */ + +#include +#include +#include +#include +#include +#include +#include +#include + +#include + +#include "rds.h" +#include "tmc.h" + +sqlite3 *lcl_db; +int OutputFlags; + +void test_rds_PI_cb(unsigned short PI, unsigned char ccode, unsigned char ptype, unsigned char pref, void *udata) +{ + 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, void *udata) +{ + printf("New RT: '%s'\n", radio_text); +} + +void test_rds_date_time_cb(struct tm *rds_time, void *udata) +{ + printf("RDS date/time: %s", asctime(rds_time)); +} + +void test_rds_sname_cb(char *sname, void *udata) +{ + 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; + + rds_init(); + tmc_init(); + + 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; + rds_set_radiotext_cb(test_rds_radiotext_cb, NULL); + } + if (strncmp("-ri", argv[i], 3) == 0) { + // OutputFlags |= RDS_OUTPUT_STATION_ID; + rds_set_sname_cb(test_rds_sname_cb, NULL); + } + if (strncmp("-tmc", argv[i], 4) == 0) { + // OutputFlags |= RDS_OUTPUT_TMC; + tmc_set_msg_cb(test_tmc_msg_cb, NULL); + } + if (strncmp("-eon", argv[i], 4) == 0) + OutputFlags |= RDS_OUTPUT_EON; + if (strncmp("-dt", argv[i], 3) == 0) { + // OutputFlags |= RDS_OUTPUT_DATETIME; + rds_set_date_time_cb(test_rds_date_time_cb, NULL); + } + if (strncmp("-ug", argv[i], 3) == 0) + OutputFlags |= RDS_OUTPUT_UNKNGRP; + if (strncmp("-pi", argv[i], 3) == 0) { + // OutputFlags |= RDS_RECEIVE_INDICATOR; + rds_set_PI_cb(test_rds_PI_cb, NULL); + } + i++; + } + + if (!(rds_fd = open_radio(rdevname))) { + perror("open radio:"); + return -1; + } + + if (sqlite3_open("lcl.db", &lcl_db) != SQLITE_OK) { + perror("open LCL database:"); + close(rds_fd); + return -1; + } + + 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/Readme.txt b/decoder/test-dumps/Readme.txt new file mode 100644 index 0000000..4030c6d --- /dev/null +++ b/decoder/test-dumps/Readme.txt @@ -0,0 +1,11 @@ + +These are some dumps for interoperability testing. + +I am aware that the content is not mine - it belongs to the broadcasting +radio stations. I do not want in any form take ownership of it. I just want +to provide it here for the mere purpose of interoperability testing. + +If you find any data contained herein not belonging here or should be +removed please contact me: nicole.faerber@dpin.de +and I will promptly take care of it. + 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..cfeb3ab --- /dev/null +++ b/decoder/tmc.c @@ -0,0 +1,569 @@ +/* + * Copyright (C) 2009, 2010 by Nicole Faerber + * + * This program is free software; you can redistribute it and/or + * modify it under the terms of the GNU General Public License + * as published by the Free Software Foundation; either version + * 2 of the License, or (at your option) any later version. + * + */ + +#include +#include +#include +#include +#include +#include +#include +#include + +#include + +#include "rds.h" +#include "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; + + //fprintf(stderr, "mCI=%d CI=%d\n", cur_tmc_msg.CI, CI); + if (CI != cur_tmc_msg.CI || CI == -1) { +#ifdef DEBUG + printf("GF evt=%d loc=%d ext=%d CI=%d dir=%d", event, location, extent, CI, direction); + printf("ev=%d ", event); +#endif + 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]); +#ifdef DEBUG + printf(" type=%c %d stype=%d\n", typechr, typenr, subtype); +#endif + 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) { +#ifdef DEBUG + printf("dir=%d\n", direction); +#endif + 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); + // printf("(L)='%s'\n", evt_str); + 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..89a87b7 --- /dev/null +++ b/decoder/tmc.h @@ -0,0 +1,34 @@ +/* + * Copyright (C) 2009, 2010 by Nicole Faerber + * + * This program is free software; you can redistribute it and/or + * modify it under the terms of the GNU General Public License + * as published by the Free Software Foundation; either version + * 2 of the License, or (at your option) any later version. + * + */ + +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..1f24f54 --- /dev/null +++ b/decoder/tmc_consts.h @@ -0,0 +1,46 @@ +/* + * Copyright (C) 2009, 2010 by Nicole Faerber + * + * This program is free software; you can redistribute it and/or + * modify it under the terms of the GNU General Public License + * as published by the Free Software Foundation; either version + * 2 of the License, or (at your option) any later version. + * + */ + +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..f9d6fdc --- /dev/null +++ b/decoder/uberradio.c @@ -0,0 +1,227 @@ +/* + * Copyright (C) 2009, 2010 by Nicole Faerber + * + * This program is free software; you can redistribute it and/or + * modify it under the terms of the GNU General Public License + * as published by the Free Software Foundation; either version + * 2 of the License, or (at your option) any later version. + * + */ + +#include +#include +#include +#include +#include +#include +#include + +#include + +#include + +#include "rds.h" +#include "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 means that all previous + // 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/COPYING b/driver-n900/COPYING new file mode 100644 index 0000000..ca442d3 --- /dev/null +++ b/driver-n900/COPYING @@ -0,0 +1,356 @@ + + NOTE! This copyright does *not* cover user programs that use kernel + services by normal system calls - this is merely considered normal use + of the kernel, and does *not* fall under the heading of "derived work". + Also note that the GPL below is copyrighted by the Free Software + Foundation, but the instance of code that it refers to (the Linux + kernel) is copyrighted by me and others who actually wrote it. + + Also note that the only valid version of the GPL as far as the kernel + is concerned is _this_ particular version of the license (ie v2, not + v2.2 or v3.x or whatever), unless explicitly otherwise stated. + + Linus Torvalds + +---------------------------------------- + + GNU GENERAL PUBLIC LICENSE + Version 2, June 1991 + + Copyright (C) 1989, 1991 Free Software Foundation, Inc. + 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA + Everyone is permitted to copy and distribute verbatim copies + of this license document, but changing it is not allowed. + + Preamble + + The licenses for most software are designed to take away your +freedom to share and change it. By contrast, the GNU General Public +License is intended to guarantee your freedom to share and change free +software--to make sure the software is free for all its users. This +General Public License applies to most of the Free Software +Foundation's software and to any other program whose authors commit to +using it. (Some other Free Software Foundation software is covered by +the GNU Library General Public License instead.) You can apply it to +your programs, too. + + When we speak of free software, we are referring to freedom, not +price. Our General Public Licenses are designed to make sure that you +have the freedom to distribute copies of free software (and charge for +this service if you wish), that you receive source code or can get it +if you want it, that you can change the software or use pieces of it +in new free programs; and that you know you can do these things. + + To protect your rights, we need to make restrictions that forbid +anyone to deny you these rights or to ask you to surrender the rights. +These restrictions translate to certain responsibilities for you if you +distribute copies of the software, or if you modify it. + + For example, if you distribute copies of such a program, whether +gratis or for a fee, you must give the recipients all the rights that +you have. You must make sure that they, too, receive or can get the +source code. And you must show them these terms so they know their +rights. + + We protect your rights with two steps: (1) copyright the software, and +(2) offer you this license which gives you legal permission to copy, +distribute and/or modify the software. + + Also, for each author's protection and ours, we want to make certain +that everyone understands that there is no warranty for this free +software. If the software is modified by someone else and passed on, we +want its recipients to know that what they have is not the original, so +that any problems introduced by others will not reflect on the original +authors' reputations. + + Finally, any free program is threatened constantly by software +patents. We wish to avoid the danger that redistributors of a free +program will individually obtain patent licenses, in effect making the +program proprietary. To prevent this, we have made it clear that any +patent must be licensed for everyone's free use or not licensed at all. + + The precise terms and conditions for copying, distribution and +modification follow. + + GNU GENERAL PUBLIC LICENSE + TERMS AND CONDITIONS FOR COPYING, DISTRIBUTION AND MODIFICATION + + 0. This License applies to any program or other work which contains +a notice placed by the copyright holder saying it may be distributed +under the terms of this General Public License. The "Program", below, +refers to any such program or work, and a "work based on the Program" +means either the Program or any derivative work under copyright law: +that is to say, a work containing the Program or a portion of it, +either verbatim or with modifications and/or translated into another +language. (Hereinafter, translation is included without limitation in +the term "modification".) Each licensee is addressed as "you". + +Activities other than copying, distribution and modification are not +covered by this License; they are outside its scope. The act of +running the Program is not restricted, and the output from the Program +is covered only if its contents constitute a work based on the +Program (independent of having been made by running the Program). +Whether that is true depends on what the Program does. + + 1. You may copy and distribute verbatim copies of the Program's +source code as you receive it, in any medium, provided that you +conspicuously and appropriately publish on each copy an appropriate +copyright notice and disclaimer of warranty; keep intact all the +notices that refer to this License and to the absence of any warranty; +and give any other recipients of the Program a copy of this License +along with the Program. + +You may charge a fee for the physical act of transferring a copy, and +you may at your option offer warranty protection in exchange for a fee. + + 2. You may modify your copy or copies of the Program or any portion +of it, thus forming a work based on the Program, and copy and +distribute such modifications or work under the terms of Section 1 +above, provided that you also meet all of these conditions: + + a) You must cause the modified files to carry prominent notices + stating that you changed the files and the date of any change. + + b) You must cause any work that you distribute or publish, that in + whole or in part contains or is derived from the Program or any + part thereof, to be licensed as a whole at no charge to all third + parties under the terms of this License. + + c) If the modified program normally reads commands interactively + when run, you must cause it, when started running for such + interactive use in the most ordinary way, to print or display an + announcement including an appropriate copyright notice and a + notice that there is no warranty (or else, saying that you provide + a warranty) and that users may redistribute the program under + these conditions, and telling the user how to view a copy of this + License. (Exception: if the Program itself is interactive but + does not normally print such an announcement, your work based on + the Program is not required to print an announcement.) + +These requirements apply to the modified work as a whole. If +identifiable sections of that work are not derived from the Program, +and can be reasonably considered independent and separate works in +themselves, then this License, and its terms, do not apply to those +sections when you distribute them as separate works. But when you +distribute the same sections as part of a whole which is a work based +on the Program, the distribution of the whole must be on the terms of +this License, whose permissions for other licensees extend to the +entire whole, and thus to each and every part regardless of who wrote it. + +Thus, it is not the intent of this section to claim rights or contest +your rights to work written entirely by you; rather, the intent is to +exercise the right to control the distribution of derivative or +collective works based on the Program. + +In addition, mere aggregation of another work not based on the Program +with the Program (or with a work based on the Program) on a volume of +a storage or distribution medium does not bring the other work under +the scope of this License. + + 3. You may copy and distribute the Program (or a work based on it, +under Section 2) in object code or executable form under the terms of +Sections 1 and 2 above provided that you also do one of the following: + + a) Accompany it with the complete corresponding machine-readable + source code, which must be distributed under the terms of Sections + 1 and 2 above on a medium customarily used for software interchange; or, + + b) Accompany it with a written offer, valid for at least three + years, to give any third party, for a charge no more than your + cost of physically performing source distribution, a complete + machine-readable copy of the corresponding source code, to be + distributed under the terms of Sections 1 and 2 above on a medium + customarily used for software interchange; or, + + c) Accompany it with the information you received as to the offer + to distribute corresponding source code. (This alternative is + allowed only for noncommercial distribution and only if you + received the program in object code or executable form with such + an offer, in accord with Subsection b above.) + +The source code for a work means the preferred form of the work for +making modifications to it. For an executable work, complete source +code means all the source code for all modules it contains, plus any +associated interface definition files, plus the scripts used to +control compilation and installation of the executable. However, as a +special exception, the source code distributed need not include +anything that is normally distributed (in either source or binary +form) with the major components (compiler, kernel, and so on) of the +operating system on which the executable runs, unless that component +itself accompanies the executable. + +If distribution of executable or object code is made by offering +access to copy from a designated place, then offering equivalent +access to copy the source code from the same place counts as +distribution of the source code, even though third parties are not +compelled to copy the source along with the object code. + + 4. You may not copy, modify, sublicense, or distribute the Program +except as expressly provided under this License. Any attempt +otherwise to copy, modify, sublicense or distribute the Program is +void, and will automatically terminate your rights under this License. +However, parties who have received copies, or rights, from you under +this License will not have their licenses terminated so long as such +parties remain in full compliance. + + 5. You are not required to accept this License, since you have not +signed it. However, nothing else grants you permission to modify or +distribute the Program or its derivative works. These actions are +prohibited by law if you do not accept this License. Therefore, by +modifying or distributing the Program (or any work based on the +Program), you indicate your acceptance of this License to do so, and +all its terms and conditions for copying, distributing or modifying +the Program or works based on it. + + 6. Each time you redistribute the Program (or any work based on the +Program), the recipient automatically receives a license from the +original licensor to copy, distribute or modify the Program subject to +these terms and conditions. You may not impose any further +restrictions on the recipients' exercise of the rights granted herein. +You are not responsible for enforcing compliance by third parties to +this License. + + 7. If, as a consequence of a court judgment or allegation of patent +infringement or for any other reason (not limited to patent issues), +conditions are imposed on you (whether by court order, agreement or +otherwise) that contradict the conditions of this License, they do not +excuse you from the conditions of this License. If you cannot +distribute so as to satisfy simultaneously your obligations under this +License and any other pertinent obligations, then as a consequence you +may not distribute the Program at all. For example, if a patent +license would not permit royalty-free redistribution of the Program by +all those who receive copies directly or indirectly through you, then +the only way you could satisfy both it and this License would be to +refrain entirely from distribution of the Program. + +If any portion of this section is held invalid or unenforceable under +any particular circumstance, the balance of the section is intended to +apply and the section as a whole is intended to apply in other +circumstances. + +It is not the purpose of this section to induce you to infringe any +patents or other property right claims or to contest validity of any +such claims; this section has the sole purpose of protecting the +integrity of the free software distribution system, which is +implemented by public license practices. Many people have made +generous contributions to the wide range of software distributed +through that system in reliance on consistent application of that +system; it is up to the author/donor to decide if he or she is willing +to distribute software through any other system and a licensee cannot +impose that choice. + +This section is intended to make thoroughly clear what is believed to +be a consequence of the rest of this License. + + 8. If the distribution and/or use of the Program is restricted in +certain countries either by patents or by copyrighted interfaces, the +original copyright holder who places the Program under this License +may add an explicit geographical distribution limitation excluding +those countries, so that distribution is permitted only in or among +countries not thus excluded. In such case, this License incorporates +the limitation as if written in the body of this License. + + 9. The Free Software Foundation may publish revised and/or new versions +of the General Public License from time to time. Such new versions will +be similar in spirit to the present version, but may differ in detail to +address new problems or concerns. + +Each version is given a distinguishing version number. If the Program +specifies a version number of this License which applies to it and "any +later version", you have the option of following the terms and conditions +either of that version or of any later version published by the Free +Software Foundation. If the Program does not specify a version number of +this License, you may choose any version ever published by the Free Software +Foundation. + + 10. If you wish to incorporate parts of the Program into other free +programs whose distribution conditions are different, write to the author +to ask for permission. For software which is copyrighted by the Free +Software Foundation, write to the Free Software Foundation; we sometimes +make exceptions for this. Our decision will be guided by the two goals +of preserving the free status of all derivatives of our free software and +of promoting the sharing and reuse of software generally. + + NO WARRANTY + + 11. BECAUSE THE PROGRAM IS LICENSED FREE OF CHARGE, THERE IS NO WARRANTY +FOR THE PROGRAM, TO THE EXTENT PERMITTED BY APPLICABLE LAW. EXCEPT WHEN +OTHERWISE STATED IN WRITING THE COPYRIGHT HOLDERS AND/OR OTHER PARTIES +PROVIDE THE PROGRAM "AS IS" WITHOUT WARRANTY OF ANY KIND, EITHER EXPRESSED +OR IMPLIED, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES OF +MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE. THE ENTIRE RISK AS +TO THE QUALITY AND PERFORMANCE OF THE PROGRAM IS WITH YOU. SHOULD THE +PROGRAM PROVE DEFECTIVE, YOU ASSUME THE COST OF ALL NECESSARY SERVICING, +REPAIR OR CORRECTION. + + 12. IN NO EVENT UNLESS REQUIRED BY APPLICABLE LAW OR AGREED TO IN WRITING +WILL ANY COPYRIGHT HOLDER, OR ANY OTHER PARTY WHO MAY MODIFY AND/OR +REDISTRIBUTE THE PROGRAM AS PERMITTED ABOVE, BE LIABLE TO YOU FOR DAMAGES, +INCLUDING ANY GENERAL, SPECIAL, INCIDENTAL OR CONSEQUENTIAL DAMAGES ARISING +OUT OF THE USE OR INABILITY TO USE THE PROGRAM (INCLUDING BUT NOT LIMITED +TO LOSS OF DATA OR DATA BEING RENDERED INACCURATE OR LOSSES SUSTAINED BY +YOU OR THIRD PARTIES OR A FAILURE OF THE PROGRAM TO OPERATE WITH ANY OTHER +PROGRAMS), EVEN IF SUCH HOLDER OR OTHER PARTY HAS BEEN ADVISED OF THE +POSSIBILITY OF SUCH DAMAGES. + + END OF TERMS AND CONDITIONS + + How to Apply These Terms to Your New Programs + + If you develop a new program, and you want it to be of the greatest +possible use to the public, the best way to achieve this is to make it +free software which everyone can redistribute and change under these terms. + + To do so, attach the following notices to the program. It is safest +to attach them to the start of each source file to most effectively +convey the exclusion of warranty; and each file should have at least +the "copyright" line and a pointer to where the full notice is found. + + + Copyright (C) + + This program is free software; you can redistribute it and/or modify + it under the terms of the GNU General Public License as published by + the Free Software Foundation; either version 2 of the License, or + (at your option) any later version. + + This program is distributed in the hope that it will be useful, + but WITHOUT ANY WARRANTY; without even the implied warranty of + MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + GNU General Public License for more details. + + You should have received a copy of the GNU General Public License + along with this program; if not, write to the Free Software + Foundation, Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA + + +Also add information on how to contact you by electronic and paper mail. + +If the program is interactive, make it output a short notice like this +when it starts in an interactive mode: + + Gnomovision version 69, Copyright (C) year name of author + Gnomovision comes with ABSOLUTELY NO WARRANTY; for details type `show w'. + This is free software, and you are welcome to redistribute it + under certain conditions; type `show c' for details. + +The hypothetical commands `show w' and `show c' should show the appropriate +parts of the General Public License. Of course, the commands you use may +be called something other than `show w' and `show c'; they could even be +mouse-clicks or menu items--whatever suits your program. + +You should also get your employer (if you work as a programmer) or your +school, if any, to sign a "copyright disclaimer" for the program, if +necessary. Here is a sample; alter the names: + + Yoyodyne, Inc., hereby disclaims all copyright interest in the program + `Gnomovision' (which makes passes at compilers) written by James Hacker. + + , 1 April 1989 + Ty Coon, President of Vice + +This General Public License does not permit incorporating your program into +proprietary programs. If your program is a subroutine library, you may +consider it more useful to permit linking proprietary applications with the +library. If this is what you want to do, use the GNU Library General +Public License instead of this License. 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..f94fe80 --- /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) Nicole 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 Nicole 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 diff --git a/tuning/Makefile b/tuning/Makefile new file mode 100644 index 0000000..d018b3a --- /dev/null +++ b/tuning/Makefile @@ -0,0 +1,43 @@ +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 tuning +SOURCES = $(patsubst %,%.c,$(RDS_MEMBERS)) +OBJS = $(patsubst %,%.o,$(RDS_MEMBERS)) +DEPS = $(patsubst %,%.d,$(RDS_MEMBERS)) + +UR_MEMBERS = rds bitstream tmc tuning +UR_SOURCES = $(patsubst %,%.c,$(UR_MEMBERS)) +UR_OBJS = $(patsubst %,%.o,$(UR_MEMBERS)) +UR_DEPS = $(patsubst %,%.d,$(UR_MEMBERS)) + +all: tuning + +tuning: $(OBJS) + $(CC) -o $@ $^ $(LDFLAGS) + +clean: + rm -f *.o *.d tuning + +-include $(DEPS) diff --git a/tuning/bitstream.c b/tuning/bitstream.c new file mode 120000 index 0000000..d79e42e --- /dev/null +++ b/tuning/bitstream.c @@ -0,0 +1 @@ +../decoder/bitstream.c \ No newline at end of file diff --git a/tuning/bitstream.h b/tuning/bitstream.h new file mode 120000 index 0000000..b80c55f --- /dev/null +++ b/tuning/bitstream.h @@ -0,0 +1 @@ +../decoder/bitstream.h \ No newline at end of file diff --git a/tuning/rds.c b/tuning/rds.c new file mode 120000 index 0000000..913dcbf --- /dev/null +++ b/tuning/rds.c @@ -0,0 +1 @@ +../decoder/rds.c \ No newline at end of file diff --git a/tuning/rds.h b/tuning/rds.h new file mode 120000 index 0000000..1811aee --- /dev/null +++ b/tuning/rds.h @@ -0,0 +1 @@ +../decoder/rds.h \ No newline at end of file diff --git a/tuning/rds_consts.h b/tuning/rds_consts.h new file mode 120000 index 0000000..55bf61a --- /dev/null +++ b/tuning/rds_consts.h @@ -0,0 +1 @@ +../decoder/rds_consts.h \ No newline at end of file diff --git a/tuning/tmc.c b/tuning/tmc.c new file mode 120000 index 0000000..48f692a --- /dev/null +++ b/tuning/tmc.c @@ -0,0 +1 @@ +../decoder/tmc.c \ No newline at end of file diff --git a/tuning/tmc.h b/tuning/tmc.h new file mode 120000 index 0000000..2d9d6bd --- /dev/null +++ b/tuning/tmc.h @@ -0,0 +1 @@ +../decoder/tmc.h \ No newline at end of file diff --git a/tuning/tmc_consts.h b/tuning/tmc_consts.h new file mode 120000 index 0000000..f92cf8e --- /dev/null +++ b/tuning/tmc_consts.h @@ -0,0 +1 @@ +../decoder/tmc_consts.h \ No newline at end of file diff --git a/tuning/tuning.c b/tuning/tuning.c new file mode 100644 index 0000000..a4610dc --- /dev/null +++ b/tuning/tuning.c @@ -0,0 +1,152 @@ +#include +#include +#include +#include +#include +#include +#include +#include + +#include + +#include + +#include "rds.h" +#include "tmc.h" + +sqlite3 *lcl_db; +int OutputFlags; + +unsigned char pi_received = 0; +unsigned char sid_received = 0; + +void test_rds_PI_cb(unsigned short PI, unsigned char ccode, unsigned char ptype, unsigned char pref, void *udata) +{ + printf("New PI=%d ccode=%X ptype=%X '%s' '%s' pref=%d\n", PI, ccode, ptype, ptype_stext[ptype], ptype_ltext[ptype], pref); + pi_received = 1; +} + +void test_rds_sname_cb(char *sname, void *udata) +{ + printf("RDS sname='%s'\n", sname); + sid_received = 1; +} + + +void station_scan(int fd) +{ +int ret; +struct v4l2_frequency v4lfreq; +struct v4l2_tuner v4ltuner; +unsigned short rdsgroup[4]; + + v4ltuner.index = 0; + ret = ioctl(fd, VIDIOC_G_TUNER, &v4ltuner); + if (ret < 0) + return; + printf("tuner=%d\n", v4ltuner.index); + if (v4ltuner.type == V4L2_TUNER_RADIO) + printf(" is a radio tuner\n"); + printf("name='%s'\n", v4ltuner.name); + + v4lfreq.tuner = v4ltuner.index; + memset(&v4lfreq.reserved, 0, 32); + ret = ioctl(fd, VIDIOC_G_FREQUENCY, &v4lfreq); + if (ret < 0) + return; + + v4lfreq.frequency = v4ltuner.rangelow; + while (v4lfreq.frequency <= v4ltuner.rangehigh) { + ret = ioctl(fd, VIDIOC_S_FREQUENCY, &v4lfreq); + if (ret < 0) + break; + ret = ioctl(fd, VIDIOC_G_FREQUENCY, &v4lfreq); + if (ret < 0) + break; + ret = ioctl(fd, VIDIOC_G_TUNER, &v4ltuner); + if (ret < 0) + break; + printf("%lf %d %d %s %s\n", (double)v4lfreq.frequency * 62.5 / 1000000, + v4lfreq.frequency, + v4ltuner.signal, + v4ltuner.rxsubchans & V4L2_TUNER_SUB_STEREO ? "stereo" : "mono", + v4ltuner.rxsubchans & V4L2_TUNER_SUB_RDS ? "RDS" : "noRDS"); + + if (v4ltuner.signal > 30000) { + /* seems to be a strong signal, so try RDS */ + pi_received = 0; + sid_received = 0; + rds_radio_retuned(); + while (pi_received == 0 || sid_received == 0) { + if (rds_receive_group(fd, rdsgroup)) { + /* group complete, start decode */ + rds_decode_group(rdsgroup); + } + } + } + + v4lfreq.frequency += 1600; /* 1600 for .1MHz steps, 800 for 0.05MHz */ + } +} + + +int main(int argc, char **argv) +{ +int fd, ret; +struct v4l2_capability v4lcap; +struct v4l2_frequency v4lfreq; +struct v4l2_tuner v4ltuner; + + fd = open ("/dev/radio0", O_RDONLY); + if (fd < 0) { + printf("error opening fd\n"); + perror("open"); + return 1; + } + + ret = ioctl(fd, VIDIOC_QUERYCAP, &v4lcap); + if (ret < 0) + return 1; + + printf("driver = '%s'\n", v4lcap.driver); + printf("card = '%s'\n", v4lcap.card); + printf("bus_info = '%s'\n", v4lcap.bus_info); + printf("cap && RADIO = %s\n", (v4lcap.capabilities & V4L2_CAP_RADIO) ? "yes" : "no"); + printf("cap && TUNER = %s\n", (v4lcap.capabilities & V4L2_CAP_TUNER) ? "yes" : "no"); + printf("cap && RDS = %s\n", (v4lcap.capabilities & V4L2_CAP_RDS_CAPTURE) ? "yes" : "no"); + + v4ltuner.index = 0; + ret = ioctl(fd, VIDIOC_G_TUNER, &v4ltuner); + if (ret < 0) + return 1; + printf("tuner=%d\n", v4ltuner.index); + if (v4ltuner.type == V4L2_TUNER_RADIO) + printf(" is a radio tuner\n"); + printf("name='%s'\n", v4ltuner.name); + + v4lfreq.tuner = v4ltuner.index; + memset(&v4lfreq.reserved, 0, 32); + ret = ioctl(fd, VIDIOC_G_FREQUENCY, &v4lfreq); + + if (ret >= 0) { + if (v4ltuner.capability & V4L2_TUNER_CAP_LOW) { + printf("range %3.2lf MHz - %3.2lf MHz\n", (double)v4ltuner.rangelow * 62.5 / 1000000, (double)v4ltuner.rangehigh * 62.5 / 1000000); + printf("freq = %3.2lf MHz\n", (double)v4lfreq.frequency * 62.5 / 1000000); + } else + printf("freq = %lf kHz\n", (double)v4lfreq.frequency * 62.5); + } + printf("signal: %d %s\n", v4ltuner.signal, v4ltuner.rxsubchans & V4L2_TUNER_SUB_STEREO ? "stereo" : "mono"); + printf("RDS signal present: %s\n", v4ltuner.rxsubchans & V4L2_TUNER_SUB_RDS ? "yes" : "no"); + + if (argc > 1 && strcmp(argv[1], "-s")==0) { + rds_init(); + tmc_init(); + rds_set_sname_cb(test_rds_sname_cb, NULL); + rds_set_PI_cb(test_rds_PI_cb, NULL); + station_scan(fd); + } + + close(fd); + +return 0; +}