Add radio tuning test app
[rdstmc.git] / decoder / rds.c
1 /*
2  * Copyright (C) 2009, 2010 by Nils Faerber <nils.faerber@kernelconcepts.de>
3  *
4  * This program is free software; you can redistribute it and/or
5  * modify it under the terms of the GNU General Public License
6  * as published by the Free Software Foundation; either version
7  * 2 of the License, or (at your option) any later version.
8  *
9  */
10
11 #include <stdio.h>
12 #include <string.h>
13 #include <stdlib.h>
14 #include <time.h>
15 #include <unistd.h>
16 #include <sys/stat.h>
17 #include <fcntl.h>
18 #include <limits.h>
19
20 #include <sqlite3.h>
21
22 #include "rds.h"
23 #include "rds_consts.h"
24 #include "tmc.h"
25
26 //#define DEBUG 1
27
28
29 static struct rds_info_s rds_info;
30 static struct tm rds_time;
31
32 static struct _RDS_Private {
33         void (*rds_PI_cb)(unsigned short PI, unsigned char ccode, unsigned char ptype, unsigned char pref, void *user_data);
34         void *rds_PI_cb_data;
35         void (*rds_sname_cb)(char *sname, void *user_data);
36         void *rds_sname_cb_data;
37         void (*rds_sinfo_cb)(unsigned char TP, unsigned char TA, unsigned char MS, unsigned char PTY, void *user_data);
38         void *rds_sinfo_cb_data;
39         void (*rds_radiotext_cb)(char *radio_text, void *user_data);
40         void *rds_radiotext_cb_data;
41         void (*rds_date_time_cb)(struct tm *dtime, void *user_data);
42         void *rds_date_time_cb_data;
43         void (*rds_afinfo_cb)(unsigned char cnt, double *AF, void *user_data);
44         void *rds_afinfo_cb_data;
45 } _rds_private;
46
47 void rds_init(void) {
48         memset(&_rds_private, 0, sizeof(struct _RDS_Private));
49 }
50
51 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)
52 {
53         _rds_private.rds_PI_cb = rds_PI_cb;
54         _rds_private.rds_PI_cb_data = user_data;
55 }
56
57 void rds_set_sname_cb(void (*rds_sname_cb)(char *sname, void *user_data), void *user_data)
58 {
59         _rds_private.rds_sname_cb = rds_sname_cb;
60         _rds_private.rds_sname_cb_data = user_data;
61 }
62
63 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)
64 {
65         _rds_private.rds_sinfo_cb = rds_sinfo_cb;
66         _rds_private.rds_sinfo_cb_data = user_data;
67 }
68
69 void rds_set_afinfo_cb(void (*rds_afinfo_cb)(unsigned char cnt, double *AF, void *user_data), void *user_data)
70 {
71         _rds_private.rds_afinfo_cb = rds_afinfo_cb;
72         _rds_private.rds_afinfo_cb_data = user_data;
73 }
74
75 void rds_set_radiotext_cb(void (*rds_radiotext_cb)(char *radio_text, void *user_data), void *user_data)
76 {
77         _rds_private.rds_radiotext_cb = rds_radiotext_cb;
78         _rds_private.rds_radiotext_cb_data = user_data;
79 }
80
81 void rds_set_date_time_cb(void (*rds_date_time_cb)(struct tm *dtime, void *user_data), void *user_data)
82 {
83         _rds_private.rds_date_time_cb = rds_date_time_cb;
84         _rds_private.rds_date_time_cb_data = user_data;
85 }
86
87
88 int rds_receive_group(int rds_fd, unsigned short *rdsgroup)
89 {
90 static unsigned char expected = 0;
91 int rb;
92 unsigned char rbuf[3];
93 unsigned short block;
94 unsigned char offset;
95
96         rb = read(rds_fd, rbuf, 3);
97         if (rb <= 0)
98                 return 0;
99         if (rb != 3) {
100                 printf("#read err rb=%d\n", rb);
101                 return 0;
102         }
103         block = rbuf[0] | (rbuf[1] << 8);
104         offset = rbuf[2] & 0x07;
105
106         if (rbuf[2] & 0x80) {
107                 if (OutputFlags & RDS_RECEIVE_INDICATOR)
108                         printf("E");
109         } else {
110                 // printf("group block=0x%04x offs=0x%02x\n", block, offset);
111                 if (offset != expected) {
112                         if (OutputFlags & RDS_RECEIVE_INDICATOR)
113                                 printf("block out of sync - resetting\n");
114                         expected = 0;
115                         memset(rdsgroup, 0, sizeof(rdsgroup));
116                 } else {
117                         rdsgroup[offset] = block;
118                         expected++;
119                         if (expected >= 4) {
120                                 expected = 0;
121                                 if (OutputFlags & RDS_RECEIVE_INDICATOR)
122                                         printf(".\n");
123                                 return 1;
124                         }
125                 }
126         }
127         if (OutputFlags & RDS_RECEIVE_INDICATOR)
128                 printf(".");
129
130         return 0;
131 }
132
133
134 enum RDSGroupType { GROUP_0A=0, GROUP_0B, GROUP_1A, GROUP_1B, GROUP_2A, GROUP_2B, 
135                     GROUP_3A, GROUP_3B, GROUP_4A, GROUP_4B, GROUP_5A, GROUP_5B, 
136                     GROUP_6A, GROUP_6B, GROUP_7A, GROUP_7B, GROUP_8A, GROUP_8B, 
137                     GROUP_9A, GROUP_9B, GROUP_10A, GROUP_10B, GROUP_11A, GROUP_11B, 
138                     GROUP_12A, GROUP_12B, GROUP_13A, GROUP_13B, GROUP_14A, GROUP_14B, 
139                     GROUP_15A, GROUP_15B, GROUP_UNKNOWN };
140
141 void rds_radio_retuned(void)
142 {
143         memset(&rds_info, 0, sizeof(rds_info));
144         rds_info.LTN = -1;
145         memset(&rds_time, 0, sizeof(rds_time));
146 }
147
148 void rds_decode_group(unsigned short *rdsgroup)
149 {
150 static unsigned short ogrp[4];
151 static unsigned char grp_decoded = 0;
152 static unsigned char sname_rcvd = 0;
153 unsigned char grp_type = (rdsgroup[1] >> 11);
154 unsigned char offs;
155 static unsigned char otextAB = 0, newtext = 0;
156 unsigned short PI = rdsgroup[0];
157 unsigned char textAB = 0;
158 int local_time_off = 0;
159 int day_code = 0;
160 int year_, mon_, K;
161
162         /* we want to wait for at least two identical group transmits */
163         if (ogrp[0] != rdsgroup[0] || ogrp[1] != rdsgroup[1] || ogrp[2] != rdsgroup[2] || ogrp[3] != rdsgroup[3]) {
164                 ogrp[0] = rdsgroup[0];
165                 ogrp[1] = rdsgroup[1];
166                 ogrp[2] = rdsgroup[2];
167                 ogrp[3] = rdsgroup[3];
168                 //printf("grp: 0x%04x 0x%04x 0x%04x 0x%04x\n", rdsgroup[0], rdsgroup[1], rdsgroup[2], rdsgroup[3]);
169                 grp_decoded = 0;
170                 return;
171         }
172         if (grp_decoded)
173                 return;
174
175         if (rds_info.PI != PI) {
176                 /* station change? */
177                 memset(&rds_info, 0, sizeof(rds_info));
178                 rds_info.LTN = -1;
179                 memset(&rds_time, 0, sizeof(rds_time));
180                 rds_info.ccode = (PI & 0xf000) >> 12;
181                 rds_info.ptype = (PI & 0x0f00) >> 8;
182                 rds_info.pref = (PI & 0x00ff);
183                 sname_rcvd = 0;
184                 if (rds_info.pref == 0) /* something is wrong here */
185                         return;
186                 rds_info.PI = rdsgroup[0];
187                 if (_rds_private.rds_PI_cb != NULL)
188                         _rds_private.rds_PI_cb(rds_info.PI, rds_info.ccode, rds_info.ptype, rds_info.pref, _rds_private.rds_PI_cb_data);
189                 if (OutputFlags & RDS_OUTPUT_RDSINFO)
190                         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);
191         }
192
193         switch (grp_type) {
194                 case GROUP_0A: { /* basic switching and tuning */
195                         unsigned char AFc1, AFc2;
196                         float AF1=0, AF2=0;
197
198                         offs = (rdsgroup[1] & 0x03);
199                         if (offs == 0)
200                                 sname_rcvd = 0;
201                         if (offs == 1 && sname_rcvd == 0)
202                                 sname_rcvd = 1;
203                         if (offs == 2 && sname_rcvd == 1)
204                                 sname_rcvd = 2;
205                         if (offs == 3 && sname_rcvd == 2)
206                                 sname_rcvd = 3;
207                         rds_info.sname[offs*2] = ((rdsgroup[3] & 0xff00) >> 8);
208                         rds_info.sname[(offs*2)+1] = rdsgroup[3] & 0x00ff;
209                         if (_rds_private.rds_sname_cb != NULL && sname_rcvd == 3) {
210                                 _rds_private.rds_sname_cb(rds_info.sname, _rds_private.rds_sname_cb_data);
211                                 sname_rcvd = 0;
212                         }
213
214                         rds_info.TA = (rdsgroup[1] & 0x10) >> 4;
215                         rds_info.TP = (rdsgroup[1] & 0x400) >> 10;
216                         rds_info.MS = (rdsgroup[1] & 0x08) >> 3;
217                         rds_info.PTY = (rdsgroup[1] & 0x3e0) >> 5;
218                         if (_rds_private.rds_sinfo_cb != NULL)
219                                 _rds_private.rds_sinfo_cb(rds_info.TP, rds_info.TA, rds_info.MS, rds_info.PTY, _rds_private.rds_sinfo_cb_data);
220
221                         AFc1 = ((rdsgroup[2] & 0xff00) >> 8);
222                         AFc2 = (rdsgroup[2] & 0x00ff);
223                         if (AFc1 > 225 && AFc1 < 250) {
224                                 if (OutputFlags & RDS_OUTPUT_STATION_ID)
225                                         printf(" %d AFs follow:\n", AFc1-224);
226                         } else if (AFc1 > 0 && AFc1 < 205)
227                                 AF1 = (float)87.5 + ((float)AFc1 * .1);
228                         else if (AFc1 == 205)
229                                 printf(" filler- \n");
230                         if (AFc2 > 0 && AFc2 < 205)
231                                 AF2 = (float)87.5 + ((float)AFc2 * .1);
232
233                         if (OutputFlags & RDS_OUTPUT_STATION_ID)
234                                 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]);
235                         }
236                         break;
237                 case GROUP_0B:
238                         if (OutputFlags & RDS_OUTPUT_UNKNGRP)
239                                 printf("group 0B\n");
240                         break;
241                 case GROUP_1A: { /* Programme Item Number and slow labelling codes */
242                         unsigned char variant, day, hour, minute;
243                         unsigned short tmc_id;
244
245                         variant = (rdsgroup[2] & (0x07 << 12)) >> 12;
246                         day = (rdsgroup[3] & (0x1f << 11)) >> 11;
247                         hour = (rdsgroup[3] & (0x1f << 6)) >> 6;
248                         minute = (rdsgroup[3] & 0x3f);
249                         if (OutputFlags & RDS_OUTPUT_UNKNGRP)
250                                 printf("1A var=%d day=%d %d:%d ", variant, day, hour, minute);
251                         if (variant == 0) {
252                                 rds_info.ECC = (rdsgroup[2] & 0xff);
253                                 printf(" ccode=%d ECC=0x%02x '%s' ", rds_info.ccode, rds_info.ECC, ECC_text[((rds_info.ECC & 0x0f)*16)+(rds_info.ccode-1)]);
254                         }
255                         if (variant == 1) {
256                                 tmc_id = (rdsgroup[2] & 0x0fff);
257                                 printf(" TMC ID=0x%04x", tmc_id);
258                         }
259                         if (variant == 2) {
260                                 printf(" Paging ID=0x%04x", (rdsgroup[2] & 0x0fff));
261                         }
262                         if (variant == 3) {
263                                 rds_info.lang_code = (rdsgroup[2] & 0xff);
264                                 printf(" language=0x%02x ", rds_info.lang_code);
265                                 switch (rds_info.lang_code) {
266                                         case 0x03:
267                                                 printf("Catalan ");
268                                                 break;
269                                         case 0x08:
270                                                 printf("German ");
271                                                 break;
272                                         case 0x0a:
273                                                 printf("Spanish ");
274                                                 break;
275                                         case 0x15:
276                                                 printf("Italian ");
277                                                 break;
278                                         case 0x18:
279                                                 printf("Latvian ");
280                                                 break;
281                                         case 0x1d:
282                                                 printf("Dutch ");
283                                                 break;
284                                         case 0x27:
285                                                 printf("Finnish ");
286                                                 break;
287                                         default:
288                                                 printf("unknown ");
289                                                 break;
290                                 };
291                         }
292                         if (variant == 7) {
293                                 printf(" EWS Channel ID=0x%04x", (rdsgroup[2] & 0x0fff));
294                         }
295                         printf("\n");
296                         } break;
297                 case GROUP_1B:
298                         if (OutputFlags & RDS_OUTPUT_UNKNGRP)
299                                 printf("group 1B\n");
300                         break;
301                 case GROUP_2A: /* 2A has 64 chars, 4 per group */
302                 case GROUP_2B: /* 2B has 32 chars, 2 per group */
303                         offs = (rdsgroup[1] & 0x0f);
304                         textAB = (rdsgroup[1] & 0x10);
305                         if (textAB != otextAB) {
306                                 memset(rds_info.rtext, ' ', 64);
307                                 rds_info.rtext[64] = '\0';
308                         }
309                         otextAB = textAB;
310                         if (grp_type == GROUP_2A) {
311                                 rds_info.rtext[offs*4] = ((rdsgroup[2] & 0xff00) >> 8);
312                                 rds_info.rtext[(offs*4)+1] = (rdsgroup[2] & 0x00ff);
313                                 rds_info.rtext[(offs*4)+2] = ((rdsgroup[3] & 0xff00) >> 8);
314                                 rds_info.rtext[(offs*4)+3] = (rdsgroup[3] & 0x00ff);
315                         } else {
316                                 rds_info.rtext[offs*2] = ((rdsgroup[3] & 0xff00) >> 8);
317                                 rds_info.rtext[(offs*2)+1] = (rdsgroup[3] & 0x00ff);
318                         }
319                         if (offs == 0) /* new text always starts at 0 */
320                                 newtext = 1;
321                         if ((offs == 15)) { /* all parts received */
322                                 if (newtext == 1) {
323                                         if (_rds_private.rds_radiotext_cb != NULL)
324                                                 _rds_private.rds_radiotext_cb(rds_info.rtext, _rds_private.rds_radiotext_cb_data);
325                                         if (OutputFlags & RDS_OUTPUT_RADIO_TEXT)
326                                                 printf("RT '%s'\n", rds_info.rtext);
327                                         newtext = 0;
328                                 }
329                         }
330                         break;
331                 case GROUP_3A: /* ODA - TMC alarm, */
332                         rds_info.AID = rdsgroup[3];
333                         /* TMC AID */
334                         if (rds_info.AID == 0xcd46 || rds_info.AID == 0x0d45) {
335                                 if ((rdsgroup[2] & 0xc000) == 0) {
336                                         rds_info.LTN = (rdsgroup[2] & 0x0fc0) >> 6;
337                                         rds_info.AFI = (rdsgroup[2] & 0x0020) >> 5;
338                                         rds_info.M = (rdsgroup[2] & 0x0010) >> 4;
339                                         rds_info.I = (rdsgroup[2] & 0x0008) >> 3;
340                                         rds_info.N = (rdsgroup[2] & 0x0004) >> 2;
341                                         rds_info.R = (rdsgroup[2] & 0x0002) >> 1;
342                                         rds_info.U = (rdsgroup[2] & 0x0001);
343                                 }
344                                 if (rdsgroup[2] & 0x4000) {
345                                         /* SID */
346                                         if (rds_info.M) {
347                                                 rds_info.G = (rdsgroup[2] & 0x3000) >> 12;
348                                                 rds_info.Ta = (rdsgroup[2] & 0x0030) >> 4;
349                                                 rds_info.Tw = (rdsgroup[2] & 0x000c) >> 2;
350                                                 rds_info.Td = (rdsgroup[2] & 0x0003);
351                                         } else {
352                                         }
353                                 }
354                                 if (OutputFlags & RDS_OUTPUT_RDSINFO) {
355                                         printf("AID = 0x%04x\n", rds_info.AID);
356                                         printf("LTN ID = %d (0x%02x)\n", rds_info.LTN, rds_info.LTN);
357                                         printf("Traffic information: ");
358                                         if (rds_info.I)
359                                                 printf("international ");
360                                         if (rds_info.N)
361                                                 printf("national ");
362                                         if (rds_info.R)
363                                                 printf("regional ");
364                                         if (rds_info.U)
365                                                 printf("urban ");
366                                         printf("\n");
367                                 }
368                         } else if (rds_info.AID == 0x4bd7) {
369                                 if (OutputFlags & RDS_OUTPUT_RDSINFO) {
370                                         printf("RT+\nG2 = 0x%04x\n", rdsgroup[2]);
371                                         printf("template = 0x%02x\n", (rdsgroup[2] & 0x00ff));
372                                         printf("SCB = 0x%02x\n", (rdsgroup[2] & 0x0f00)>>8);
373                                         printf("CB = 0x%02x\n", (rdsgroup[2] & 0x1000)>>12);
374                                         printf("rfu = 0x%02x\n", (rdsgroup[2] & 0xe000)>>13);
375                                 }
376                         } else {
377                                 printf("3A AID=0x%04x\n", rds_info.AID);
378                         }
379                         break;
380                 case GROUP_3B:
381                         if (OutputFlags & RDS_OUTPUT_UNKNGRP)
382                                 printf("group 3B\n");
383                         break;
384                 case GROUP_4A:
385                         //printf("group 4A - time/date\n");
386                         rds_time.tm_sec = 0; // date/time message is sent at 00 secs +/- .2sec
387                         rds_time.tm_min = ((rdsgroup[3] & (0x3f << 6)) >> 6);
388                         rds_time.tm_hour = ((rdsgroup[3] & (0x0f << 12)) >> 12) | ((rdsgroup[2] & 0x01) << 4);
389                         local_time_off = (rdsgroup[3] & 0x0f) * ((rdsgroup[3] & 0x10) ? -1 : 1);
390                         rds_time.tm_hour += local_time_off / 2;
391                         rds_time.tm_min = (rds_time.tm_min + ((local_time_off % 2) * 30)) % 60;
392
393                         day_code = (((rdsgroup[2] & 0xfffe) >> 1) | ((rdsgroup[1] & 0x03) << 15)) /*+ local_time_off*/;
394
395                         year_ = ((float)day_code - 15078.2) / 365.25;
396                         mon_ = ((day_code - 14956.1) - (int)(year_ * 365.25)) / 30.6001;
397                         rds_time.tm_mday = day_code - 14956 - (int)(year_ * 365.25) - (int)(mon_ * 30.6001);
398                         if (mon_ == 14 || mon_ == 15)
399                                 K = 1;
400                         else
401                                 K = 0;
402                         rds_time.tm_year = year_ + K;
403                         rds_time.tm_mon = mon_ - 1 - (K * 12) - 1;
404
405                         rds_time.tm_isdst = -1;
406
407                         if (_rds_private.rds_date_time_cb != NULL)
408                                 _rds_private.rds_date_time_cb(&rds_time, _rds_private.rds_date_time_cb_data);
409
410                         if (OutputFlags & RDS_OUTPUT_DATETIME)
411                                 printf("%s", asctime(&rds_time));
412 #ifdef DEBUG
413                         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);
414 #endif
415                         //printf("day=%d month=%d year=%d\n", day, mon, year);
416                         break;
417                 case GROUP_4B:
418                         if (OutputFlags & RDS_OUTPUT_UNKNGRP)
419                                 printf("GRP4B\n");
420                         break;
421                 case GROUP_5A:
422                         if (OutputFlags & RDS_OUTPUT_UNKNGRP)
423                                 printf("GRP5A\n");
424                         break;
425                 case GROUP_5B:
426                         if (OutputFlags & RDS_OUTPUT_UNKNGRP)
427                                 printf("GRP5B\n");
428                         break;
429                 case GROUP_6A:
430                         if (OutputFlags & RDS_OUTPUT_UNKNGRP)
431                                 printf("GRP6A\n");
432                         break;
433                 case GROUP_6B:
434                         if (OutputFlags & RDS_OUTPUT_UNKNGRP)
435                                 printf("GRP6B\n");
436                         break;
437                 case GROUP_7A:
438                         if (OutputFlags & RDS_OUTPUT_UNKNGRP)
439                                 printf("GRP7A\n");
440                         break;
441                 case GROUP_7B:
442                         if (OutputFlags & RDS_OUTPUT_UNKNGRP)
443                                 printf("GRP7B\n");
444                         break;
445                 case GROUP_8A:
446                         if (rds_info.LTN != 0) // non TMCpro
447                                 decode_tmc(rdsgroup);
448                         if (rds_info.LTN == 0)
449                                 printf("TMCpro\n");
450                         break;
451                 case GROUP_8B:
452                         if (OutputFlags & RDS_OUTPUT_UNKNGRP)
453                                 printf("GRP8B\n");
454                         break;
455                 case GROUP_9A:
456                         if (OutputFlags & RDS_OUTPUT_UNKNGRP)
457                                 printf("GRP9A\n");
458                         break;
459                 case GROUP_9B:
460                         if (OutputFlags & RDS_OUTPUT_UNKNGRP)
461                                 printf("GRP9B\n");
462                         break;
463                 case GROUP_10A:
464                         if (OutputFlags & RDS_OUTPUT_UNKNGRP)
465                                 printf("GRP10A\n");
466                         break;
467                 case GROUP_10B:
468                         if (OutputFlags & RDS_OUTPUT_UNKNGRP)
469                                 printf("GRP10B\n");
470                         break;
471                 case GROUP_11A: /* Open Data Application ODA */
472                         if (OutputFlags & RDS_OUTPUT_UNKNGRP)
473                                 printf("GRP11A ODA: #1=0x%02x #2=0x%04x #3=0x%04x\n", rdsgroup[1] & 0x1f, rdsgroup[2], rdsgroup[3]);
474
475                         /* we previously got an RT+ identifier, try RT+ decoding */
476                         if (rds_info.AID == 0x4bd7) {
477                                 printf("RT+\ntoggle: %s\n", (rdsgroup[1] & 0x10) ? "yes" : "no");
478                                 printf("item running   : %s\n", (rdsgroup[1] & 0x08) ? "yes" : "no");
479                                 printf("content type 1 : %d\n", (rdsgroup[1] & 0x07) << 3 | (rdsgroup[2] & 0x0e00) >> 13);
480                                 printf("start marker 1 : %d\n", (rdsgroup[2] & 0x1f80) >> 7);
481                                 printf("length marker 1: %d\n", (rdsgroup[2] & 0x007e) >> 1);
482                                 printf("content type 2 : %d\n", (rdsgroup[1] & 0x01) << 4);
483                                 printf("start marker 2 : %d\n", (rdsgroup[2] & 0x07e0) >> 5);
484                                 printf("length marker 2: %d\n", (rdsgroup[2] & 0x001f));
485                         }
486                         break;
487                 case GROUP_11B: /* Open Data Application ODA */
488                         if (OutputFlags & RDS_OUTPUT_UNKNGRP)
489                                 printf("GRP11B\n");
490                         printf("11B ODA: #1=0x%02x PI=0x%04x #3=0x%04x\n", rdsgroup[1] & 0x1f, rdsgroup[2], rdsgroup[3]);
491                         break;
492                 case GROUP_12A: /* Open Data Application ODA */
493                         if (OutputFlags & RDS_OUTPUT_UNKNGRP)
494                                 printf("GRP12A\n");
495                         break;
496                 case GROUP_12B: /* Open Data Application ODA */
497                         if (OutputFlags & RDS_OUTPUT_UNKNGRP)
498                                 printf("GRP12B\n");
499                         break;
500                 case GROUP_13A: /* Open Data Application ODA or paging */
501                         if (OutputFlags & RDS_OUTPUT_UNKNGRP)
502                                 printf("GRP13A\n");
503                         break;
504                 case GROUP_13B: /* Open Data Application ODA */
505                         if (OutputFlags & RDS_OUTPUT_UNKNGRP)
506                                 printf("GRP13B\n");
507                         break;
508                 case GROUP_14A: /* EON */ {
509                         unsigned char variant = rdsgroup[1] & 0x0f;
510                         unsigned char AFc1=0, AFc2=0;
511                         float AF1=0, AF2=0;
512                         unsigned short PI;
513
514                         rds_info.PTY = (rdsgroup[1] & 0x3e0) >> 5;
515                         rds_info.TPTN = (rdsgroup[1] & 0x400) >> 10;
516                         rds_info.TPON = (rdsgroup[1] & 0x10) >> 4;
517                         PI = rdsgroup[3];
518
519                         if (variant <= 3) { // PS
520                                 rds_info.PS[variant*2] = ((rdsgroup[2] & 0xff00) >> 8);
521                                 rds_info.PS[(variant*2)+1] = (rdsgroup[2] & 0x00ff);
522                         } else if (variant == 4) {
523                                 printf("EON variant 4\n");
524                         } else  if (variant >= 5 && variant <= 8) { // AF - alternate frequency
525                                 AFc1 = ((rdsgroup[2] & 0xff00) >> 8);
526                                 AFc2 = (rdsgroup[2] & 0x00ff);
527                                 if (AFc1 > 0 && AFc1 < 205)
528                                         AF1 = (float)87.5 + ((float)AFc1 * .1);
529                                 else if (AFc1 >= 225 && AFc1 <= 249)
530                                         AF1 = AFc1 - 224;
531                                 if (AFc2 > 0 && AFc2 < 205)
532                                         AF2 = (float)87.5 + ((float)AFc2 * .1);
533                                 else if (AFc2 >= 225 && AFc2 <= 249)
534                                         AF2 = AFc2 - 224;
535                         }
536                         if (OutputFlags & RDS_OUTPUT_EON) {
537                                 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);
538                         }
539                         } break;
540                 case GROUP_14B: /* Enhanced Other Networks information */
541                         if (OutputFlags & RDS_OUTPUT_EON)
542                                 printf("GRP14B: Enhanced Other Networks information\n");
543                         break;
544                 case GROUP_15A:
545                         if (OutputFlags & RDS_OUTPUT_UNKNGRP)
546                                 printf("GRP15A\n");
547                         break;
548                 case GROUP_15B: /* Fast basic tuning and switching information */
549                         if (OutputFlags & RDS_OUTPUT_UNKNGRP)
550                                 printf("GRP15B: Fast basic tuning and switching information\n");
551                         break;
552                 default:
553                         if (OutputFlags & RDS_OUTPUT_UNKNGRP)
554                                 printf("unkn. RDS group %d\n", grp_type);
555                         break;
556         }
557
558         /* done with this group */
559         grp_decoded = 1;
560 }
561