mirror of
https://github.com/radio95-rnt/rds95.git
synced 2026-02-26 20:33:53 +01:00
513 lines
15 KiB
C
513 lines
15 KiB
C
#include "common.h"
|
|
#include "rds.h"
|
|
#include "modulator.h"
|
|
#include "lib.h"
|
|
#include <time.h>
|
|
|
|
void saveToFile(const char *filename, RDSEncoder *emp) {
|
|
FILE *file = fopen(filename, "wb");
|
|
if (file == NULL) {
|
|
perror("Error opening file");
|
|
return;
|
|
}
|
|
fwrite(emp, sizeof(RDSEncoder), 1, file);
|
|
fclose(file);
|
|
}
|
|
|
|
void loadFromFile(const char *filename, RDSEncoder *emp) {
|
|
FILE *file = fopen(filename, "rb");
|
|
if (file == NULL) {
|
|
perror("Error opening file");
|
|
return;
|
|
}
|
|
fread(emp, sizeof(RDSEncoder), 1, file);
|
|
fclose(file);
|
|
}
|
|
|
|
int fileExists(const char *filename) {
|
|
FILE *file = fopen(filename, "rb");
|
|
if (file) {
|
|
fclose(file);
|
|
return 1;
|
|
}
|
|
return 0;
|
|
}
|
|
|
|
static void register_oda(RDSEncoder* enc, uint8_t group, uint16_t aid, uint16_t scb) {
|
|
if (enc->oda_state[enc->program].count >= MAX_ODAS) return;
|
|
|
|
enc->odas[enc->program][oda_state.count].group = group;
|
|
enc->odas[enc->program][oda_state.count].aid = aid;
|
|
enc->odas[enc->program][oda_state.count].scb = scb;
|
|
enc->oda_state[enc->program].count++;
|
|
}
|
|
|
|
static uint16_t get_next_af(RDSEncoder* enc) {
|
|
static uint8_t af_state;
|
|
uint16_t out;
|
|
|
|
if (enc->data[enc->program].af.num_afs) {
|
|
if (af_state == 0) {
|
|
out = (AF_CODE_NUM_AFS_BASE + enc->data[enc->program].af.num_afs) << 8;
|
|
out |= enc->data[enc->program].af.afs[0];
|
|
af_state += 1;
|
|
} else {
|
|
out = enc->data[enc->program].af.afs[af_state] << 8;
|
|
if (enc->data[enc->program].af.afs[af_state + 1])
|
|
out |= enc->data[enc->program].af.afs[af_state + 1];
|
|
else
|
|
out |= AF_CODE_FILLER;
|
|
af_state += 2;
|
|
}
|
|
if (af_state >= enc->data[enc->program].af.num_entries) af_state = 0;
|
|
} else {
|
|
out = AF_CODE_NO_AF << 8 | AF_CODE_FILLER;
|
|
}
|
|
|
|
return out;
|
|
}
|
|
// #endregion
|
|
|
|
// #region Group encoding
|
|
static void get_rds_ps_group(RDSEncoder* enc, uint16_t *blocks) {
|
|
static unsigned char ps_text[PS_LENGTH];
|
|
static unsigned char tps_text[PS_LENGTH];
|
|
static uint8_t ps_csegment;
|
|
|
|
if (ps_csegment == 0 && enc->state[enc->program][enc->program].ps_update) {
|
|
memcpy(ps_text, enc->data[enc->program].ps, PS_LENGTH);
|
|
enc->state[enc->program][enc->program].ps_update = 0;
|
|
}
|
|
if(ps_csegment == 0 && enc->state[enc->program][enc->program].tps_update) {
|
|
memcpy(tps_text, enc->data[enc->program].tps, PS_LENGTH);
|
|
enc->state[enc->program][enc->program].tps_update = 0;
|
|
}
|
|
|
|
blocks[1] |= enc->data[enc->program].ta << 4;
|
|
blocks[1] |= enc->data[enc->program].ms << 3;
|
|
blocks[1] |= ((enc->data[enc->program].di >> (3 - ps_csegment)) & INT8_0) << 2;
|
|
blocks[1] |= ps_csegment;
|
|
blocks[2] = get_next_af(enc);
|
|
if(enc->data[enc->program].ta && tps_text[0] != '\0') {
|
|
blocks[3] = tps_text[ps_csegment * 2] << 8 | tps_text[ps_csegment * 2 + 1];
|
|
} else {
|
|
/* TODO: Add DPS */
|
|
blocks[3] = ps_text[ps_csegment * 2] << 8 | ps_text[ps_csegment * 2 + 1];
|
|
}
|
|
ps_csegment++;
|
|
if (ps_csegment >= 4) ps_csegment = 0;
|
|
}
|
|
|
|
static uint8_t get_rds_rt_group(RDSEncoder* enc, uint16_t *blocks) {
|
|
static unsigned char rt_text[RT_LENGTH];
|
|
static uint8_t rt_state;
|
|
|
|
if (enc->state[enc->program].rt_update) {
|
|
memcpy(rt_text, enc->data[enc->program].rt1, RT_LENGTH);
|
|
enc->state[enc->program].rt_ab ^= 1;
|
|
enc->state[enc->program].rt_update = 0;
|
|
rt_state = 0;
|
|
}
|
|
|
|
blocks[1] |= 2 << 12;
|
|
blocks[1] |= enc->state[enc->program].rt_ab << 4;
|
|
blocks[1] |= rt_state;
|
|
blocks[2] = rt_text[rt_state * 4 ] << 8;
|
|
blocks[2] |= rt_text[rt_state * 4 + 1];
|
|
blocks[3] = rt_text[rt_state * 4 + 2] << 8;
|
|
blocks[3] |= rt_text[rt_state * 4 + 3];
|
|
|
|
rt_state++;
|
|
if (rt_state >= enc->state[enc->program].rt_segments) rt_state = 0;
|
|
return 1;
|
|
}
|
|
|
|
static void get_rds_oda_group(RDSEncoder* enc, uint16_t *blocks) {
|
|
RDSODA this_oda = enc->odas[enc->oda_state[enc->program].current];
|
|
|
|
blocks[1] |= 3 << 12;
|
|
|
|
blocks[1] |= GET_GROUP_TYPE(this_oda.group) << 1;
|
|
blocks[1] |= GET_GROUP_VER(this_oda.group);
|
|
blocks[2] = this_oda.scb;
|
|
blocks[3] = this_oda.aid;
|
|
|
|
enc->oda_state[enc->program].current++;
|
|
if (enc->oda_state[enc->program].current >= enc->oda_state[enc->program].count) enc->oda_state[enc->program].current = 0;
|
|
}
|
|
|
|
static uint8_t get_rds_ct_group(RDSEncoder* enc, uint16_t *blocks) {
|
|
static uint8_t latest_minutes;
|
|
struct tm *utc, *local_time;
|
|
time_t now;
|
|
uint8_t l;
|
|
uint32_t mjd;
|
|
int16_t offset;
|
|
|
|
now = time(NULL);
|
|
utc = gmtime(&now);
|
|
|
|
if (utc->tm_min != latest_minutes) {
|
|
latest_minutes = utc->tm_min;
|
|
|
|
l = utc->tm_mon <= 1 ? 1 : 0;
|
|
mjd = 14956 + utc->tm_mday +
|
|
(uint32_t)((utc->tm_year - l) * 365.25f) +
|
|
(uint32_t)((utc->tm_mon + (1+1) + l * 12) * 30.6001f);
|
|
|
|
blocks[1] |= 4 << 12 | (mjd >> 15);
|
|
blocks[2] = (mjd << 1) | (utc->tm_hour >> 4);
|
|
blocks[3] = (utc->tm_hour & INT16_L4) << 12 | utc->tm_min << 6;
|
|
|
|
local_time = localtime(&now);
|
|
|
|
offset = local_time->__tm_gmtoff / (30 * 60);
|
|
if (offset < 0) blocks[3] |= 1 << 5;
|
|
blocks[3] |= abs(offset);
|
|
|
|
return 1;
|
|
}
|
|
|
|
return 0;
|
|
}
|
|
|
|
static void get_rds_ptyn_group(RDSEncoder* enc, uint16_t *blocks) {
|
|
static unsigned char ptyn_text[PTYN_LENGTH];
|
|
static uint8_t ptyn_state;
|
|
|
|
if (ptyn_state == 0 && enc->state[enc->program].ptyn_update) {
|
|
memcpy(ptyn_text, enc->data[enc->program].ptyn, PTYN_LENGTH);
|
|
enc->state[enc->program].ptyn_ab ^= 1;
|
|
enc->state[enc->program].ptyn_update = 0;
|
|
}
|
|
|
|
blocks[1] |= 10 << 12 | ptyn_state;
|
|
blocks[1] |= enc->state[enc->program].ptyn_ab << 4;
|
|
blocks[2] = ptyn_text[ptyn_state * 4 + 0] << 8;
|
|
blocks[2] |= ptyn_text[ptyn_state * 4 + 1];
|
|
blocks[3] = ptyn_text[ptyn_state * 4 + 2] << 8;
|
|
blocks[3] |= ptyn_text[ptyn_state * 4 + 3];
|
|
|
|
ptyn_state++;
|
|
if (ptyn_state == 2) ptyn_state = 0;
|
|
}
|
|
|
|
static void get_rds_lps_group(RDSEncoder* enc, uint16_t *blocks) {
|
|
static unsigned char lps_text[LPS_LENGTH];
|
|
static uint8_t lps_state;
|
|
|
|
if (lps_state == 0 && enc->state[enc->program].lps_update) {
|
|
memcpy(lps_text, enc->data[enc->program].lps, LPS_LENGTH);
|
|
enc->state[enc->program].lps_update = 0;
|
|
}
|
|
|
|
blocks[1] |= 15 << 12 | lps_state;
|
|
blocks[2] = lps_text[lps_state * 4 ] << 8;
|
|
blocks[2] |= lps_text[lps_state * 4 + 1];
|
|
blocks[3] = lps_text[lps_state * 4 + 2] << 8;
|
|
blocks[3] |= lps_text[lps_state * 4 + 3];
|
|
|
|
lps_state++;
|
|
if (lps_state == enc->state[enc->program].lps_segments) lps_state = 0;
|
|
}
|
|
|
|
static void get_rds_ecc_group(RDSEncoder* enc, uint16_t *blocks) {
|
|
blocks[1] |= 1 << 12;
|
|
blocks[2] = enc->data[enc->program].ecc;
|
|
|
|
if(enc->data[enc->program].pin[0]) {
|
|
blocks[3] = enc->data[enc->program].pin[1] << 11; // day
|
|
blocks[3] |= enc->data[enc->program].pin[2] << 6; // hour
|
|
blocks[3] |= enc->data[enc->program].pin[3]; // minute
|
|
}
|
|
}
|
|
|
|
static void get_rds_lic_group(RDSEncoder* enc, uint16_t *blocks) {
|
|
blocks[1] |= 1 << 12;
|
|
blocks[2] = 0x3000; // 0b0011000000000000
|
|
blocks[2] |= enc->data[enc->program].lic;
|
|
|
|
if(enc->data[enc->program].pin[0]) {
|
|
blocks[3] = enc->data[enc->program].pin[1] << 11; // day
|
|
blocks[3] |= enc->data[enc->program].pin[2] << 6; // hour
|
|
blocks[3] |= enc->data[enc->program].pin[3]; // minute
|
|
}
|
|
}
|
|
static void get_rds_rtplus_group(RDSEncoder* enc, uint16_t *blocks) {
|
|
blocks[1] |= GET_GROUP_TYPE(enc->rtpData[enc->program].group) << 12;
|
|
blocks[1] |= GET_GROUP_VER(enc->rtpData[enc->program].group) << 11;
|
|
blocks[1] |= enc->rtpData[enc->program].toggle << 4 | enc->rtpData[enc->program].running << 3;
|
|
blocks[1] |= (enc->rtpData[enc->program].type[0] & INT8_U5) >> 3;
|
|
|
|
blocks[2] = (enc->rtpData[enc->program].type[0] & INT8_L3) << 13;
|
|
blocks[2] |= (enc->rtpData[enc->program].start[0] & INT8_L6) << 7;
|
|
blocks[2] |= (enc->rtpData[enc->program].len[0] & INT8_L6) << 1;
|
|
blocks[2] |= (enc->rtpData[enc->program].type[1] & INT8_U3) >> 5;
|
|
|
|
blocks[3] = (enc->rtpData[enc->program].type[1] & INT8_L5) << 11;
|
|
blocks[3] |= (enc->rtpData[enc->program].start[1] & INT8_L6) << 5;
|
|
blocks[3] |= enc->rtpData[enc->program].len[1] & INT8_L5;
|
|
}
|
|
// #endregion
|
|
|
|
static uint8_t get_rds_custom_groups(RDSEncoder* enc, uint16_t *blocks) {
|
|
if(enc->state[enc->program].custom_group[0] == 1) {
|
|
enc->state[enc->program].custom_group[0] = 0;
|
|
blocks[0] = enc->data[enc->program].pi;
|
|
blocks[1] = enc->state[enc->program].custom_group[1];
|
|
blocks[2] = enc->state[enc->program].custom_group[2];
|
|
blocks[3] = enc->state[enc->program].custom_group[3];
|
|
return 1;
|
|
}
|
|
return 0;
|
|
}
|
|
|
|
static void get_rds_group(RDSEncoder* enc, uint16_t *blocks) {
|
|
blocks[0] = enc->data[enc->program].pi;
|
|
blocks[1] = enc->data[enc->program].tp << 10;
|
|
blocks[1] |= enc->data[enc->program].pty << 5;
|
|
blocks[2] = 0;
|
|
blocks[3] = 0;
|
|
|
|
if (enc->data[enc->program].ct && get_rds_ct_group(enc, blocks)) {
|
|
goto group_coded;
|
|
}
|
|
|
|
if(get_rds_custom_groups(enc, blocks)) {
|
|
goto group_coded;
|
|
}
|
|
|
|
uint8_t good_group = 0;
|
|
uint8_t cant_find_group = 0;
|
|
char grp;
|
|
|
|
while(good_group == 0) {
|
|
uint8_t grp_sqc_idx = enc->state[enc->program].grp_seq_idx[0]++;
|
|
if(enc->data[enc->program].grp_sqc[grp_sqc_idx] == '\0') {
|
|
enc->state[enc->program].grp_seq_idx[0] = 0;
|
|
grp_sqc_idx = 0;
|
|
}
|
|
grp = enc->data[enc->program].grp_sqc[grp_sqc_idx];
|
|
|
|
if(grp == '0') good_group = 1;
|
|
if(grp == '1' && enc->data[enc->program].ecclic_enabled) good_group = 1;
|
|
if(grp == '2' && enc->data[enc->program].rt1_enabled) good_group = 1;
|
|
if(grp == 'A' && enc->state[enc->program].ptyn_enabled) good_group = 1;
|
|
if(grp == 'X' && enc->data[enc->program].udg1_len != 0) good_group = 1;
|
|
if(grp == 'Y' && enc->data[enc->program].udg2_len != 0) good_group = 1;
|
|
if(grp == 'R' && enc->rtpData[enc->program].enabled) good_group = 1;
|
|
if(grp == '3' && enc->oda_state[enc->program].count != 0) good_group = 1;
|
|
if(grp == 'F' && enc->data[enc->program].lps[0] != '\0') good_group = 1;
|
|
|
|
if(!good_group) cant_find_group++;
|
|
else cant_find_group = 0;
|
|
if(!good_group && cant_find_group == 23) {
|
|
cant_find_group = 0;
|
|
return;
|
|
break;
|
|
}
|
|
}
|
|
|
|
uint8_t idx;
|
|
switch (grp)
|
|
{
|
|
default:
|
|
case '0':
|
|
if(enc->state[enc->program].grp_seq_idx[1] != 3) enc->state[enc->program].grp_seq_idx[0]--;
|
|
else enc->state[enc->program].grp_seq_idx[1] = 0;
|
|
enc->state[enc->program].grp_seq_idx[1]++;
|
|
get_rds_ps_group(enc, blocks);
|
|
goto group_coded;
|
|
case '1':
|
|
if(enc->data[enc->program].ecc && enc->data[enc->program].lic) {
|
|
if(enc->state[enc->program].ecc_or_lic == 0) {
|
|
get_rds_ecc_group(enc, blocks);
|
|
} else {
|
|
get_rds_lic_group(enc, blocks);
|
|
}
|
|
enc->state[enc->program].ecc_or_lic ^= 1;
|
|
} else if(enc->data[enc->program].lic) {
|
|
get_rds_lic_group(enc, blocks);
|
|
} else {
|
|
get_rds_ecc_group(enc, blocks);
|
|
}
|
|
goto group_coded;
|
|
case '2':
|
|
get_rds_rt_group(enc, blocks);
|
|
goto group_coded;
|
|
case 'A':
|
|
get_rds_ptyn_group(enc, blocks);
|
|
goto group_coded;
|
|
// TODO: Add EON
|
|
case 'X':
|
|
idx = enc->state[enc->program].udg_idxs[0];
|
|
for(int i = 0; i < 3; i++) blocks[i+1] = enc->data[enc->program].udg1[idx][i];
|
|
enc->state[enc->program].udg_idxs[0]++;
|
|
if(enc->state[enc->program].udg_idxs[0] == enc->data[enc->program].udg1_len) enc->state[enc->program].udg_idxs[0] = 0;
|
|
goto group_coded;
|
|
case 'Y':
|
|
idx = enc->state[enc->program].udg_idxs[1];
|
|
for(int i = 0; i < 3; i++) blocks[i+1] = enc->data[enc->program].udg2[idx][i];
|
|
enc->state[enc->program].udg_idxs[1]++;
|
|
if(enc->state[enc->program].udg_idxs[1] == enc->data[enc->program].udg2_len) enc->state[enc->program].udg_idxs[1] = 0;
|
|
goto group_coded;
|
|
case 'R':
|
|
if(enc->state[enc->program].rtp_oda == 0) {
|
|
get_rds_rtplus_group(enc, blocks);
|
|
} else {
|
|
get_rds_oda_group(enc, blocks);
|
|
}
|
|
enc->state[enc->program].rtp_oda ^= 1;
|
|
goto group_coded;
|
|
// TODO: add uecp
|
|
case '3':
|
|
get_rds_oda_group(enc, blocks);
|
|
goto group_coded;
|
|
case 'F':
|
|
get_rds_lps_group(enc, blocks);
|
|
goto group_coded;
|
|
}
|
|
|
|
|
|
group_coded:
|
|
if (IS_TYPE_B(blocks)) {
|
|
blocks[2] = enc->data[enc->program].pi;
|
|
}
|
|
}
|
|
|
|
void get_rds_bits(RDSEncoder* enc, uint8_t *bits) {
|
|
static uint16_t out_blocks[GROUP_LENGTH];
|
|
get_rds_group(enc, out_blocks);
|
|
add_checkwords(enc, out_blocks, bits);
|
|
}
|
|
|
|
static void init_rtplus(RDSEncoder* enc, uint8_t group) {
|
|
register_oda(enc, group, ODA_AID_RTPLUS, 0);
|
|
enc->rtpData[enc->program].group = group;
|
|
enc->rtpData[enc->program].enabled = 0;
|
|
}
|
|
|
|
void init_rds_encoder(RDSEncoder* enc) {
|
|
memset(&enc->data[enc->program].af, 0, sizeof(RDSAFs));
|
|
enc->data[enc->program].ct = 1;
|
|
enc->data[enc->program].di = 1;
|
|
enc->data[enc->program].ecclic_enabled = 1;
|
|
enc->data[enc->program].grp_sqc = (unsigned char*)"002222\0";
|
|
enc->data[enc->program].ms = 1;
|
|
enc->data[enc->program].pi = 0xFFFF;
|
|
enc->data[enc->program].ps = (unsigned char*)"* RDS *";
|
|
enc->data[enc->program].rt1_enabled = 1;
|
|
|
|
enc->state[enc->program].rt_ab = 1;
|
|
enc->state[enc->program].ptyn_ab = 1;
|
|
|
|
init_rtplus(GROUP_11A);
|
|
|
|
if(fileExists("~/.rdsEncoder")) {
|
|
loadFromFile("~/.rdsEncoder", enc);
|
|
} else {
|
|
saveToFile("~/.rdsEncoder");
|
|
}
|
|
|
|
init_rds_objects();
|
|
}
|
|
|
|
void set_rds_rt1(RDSEncoder* enc, unsigned char *rt1) {
|
|
uint8_t i = 0, len = 0;
|
|
|
|
enc->state[enc->program].rt_update = 1;
|
|
|
|
memset(enc->data[enc->program].rt1, ' ', RT_LENGTH);
|
|
while (*rt1 != 0 && len < RT_LENGTH) enc->data[enc->program].rt1[len++] = *rt1++;
|
|
|
|
if (len < RT_LENGTH && enc->data[enc->program].shortrt) {
|
|
enc->state[enc->program].rt_segments = 0;
|
|
|
|
enc->data[enc->program].rt1[len++] = '\r';
|
|
|
|
while (i < len) {
|
|
i += 4;
|
|
enc->state[enc->program].rt_segments++;
|
|
}
|
|
} else {
|
|
enc->state[enc->program].rt_segments = 16;
|
|
}
|
|
}
|
|
|
|
void set_rds_ps(RDSEncoder* enc, unsigned char *ps) {
|
|
uint8_t len = 0;
|
|
|
|
enc->state[enc->program].ps_update = 1;
|
|
memset(enc->data[enc->program].ps, ' ', PS_LENGTH);
|
|
while (*ps != 0 && len < PS_LENGTH) enc->data[enc->program].ps[len++] = *ps++;
|
|
}
|
|
|
|
void set_rds_tps(RDSEncoder* enc, unsigned char *tps) {
|
|
uint8_t len = 0;
|
|
|
|
enc->state[enc->program].tps_update = 1;
|
|
if(tps[0] == '\0') {
|
|
memset(enc->data[enc->program].tps, 0, PS_LENGTH);
|
|
return;
|
|
}
|
|
|
|
memset(enc->data[enc->program].tps, ' ', PS_LENGTH);
|
|
while (*tps != 0 && len < PS_LENGTH) enc->data[enc->program].tps[len++] = *tps++;
|
|
}
|
|
|
|
void set_rds_lps(RDSEncoder* enc, unsigned char *lps) {
|
|
uint8_t i = 0, len = 0;
|
|
|
|
enc->state[enc->program].lps_update = 1;
|
|
if(lps[0] == '\0') {
|
|
memset(enc->data[enc->program].lps, 0, LPS_LENGTH);
|
|
return;
|
|
}
|
|
memset(enc->data[enc->program].lps, '\r', LPS_LENGTH);
|
|
while (*lps != 0 && len < LPS_LENGTH) enc->data[enc->program].lps[len++] = *lps++;
|
|
|
|
if (len < LPS_LENGTH) {
|
|
enc->state[enc->program].lps_segments = 0;
|
|
|
|
len++;
|
|
|
|
while (i < len) {
|
|
i += 4;
|
|
enc->state[enc->program].lps_segments++;
|
|
}
|
|
} else {
|
|
enc->state[enc->program].lps_segments = 8;
|
|
}
|
|
}
|
|
|
|
void set_rds_rtplus_flags(RDSEncoder* enc, uint8_t flags) {
|
|
enc->rtpData[enc->program].enabled = (flags==2);
|
|
enc->rtpData[enc->program].running = flags & INT8_0;
|
|
}
|
|
|
|
void set_rds_rtplus_tags(RDSEncoder* enc, uint8_t *tags) {
|
|
enc->rtpData[enc->program].type[0] = tags[0] & INT8_L6;
|
|
enc->rtpData[enc->program].start[0] = tags[1] & INT8_L6;
|
|
enc->rtpData[enc->program].len[0] = tags[2] & INT8_L6;
|
|
enc->rtpData[enc->program].type[1] = tags[3] & INT8_L6;
|
|
enc->rtpData[enc->program].start[1] = tags[4] & INT8_L6;
|
|
enc->rtpData[enc->program].len[1] = tags[5] & INT8_L5;
|
|
|
|
enc->rtpData[enc->program].toggle ^= 1;
|
|
enc->rtpData[enc->program].running = 1;
|
|
enc->rtpData[enc->program].enabled = 1;
|
|
}
|
|
|
|
void set_rds_ptyn(RDSEncoder* enc, unsigned char *ptyn) {
|
|
uint8_t len = 0;
|
|
|
|
enc->state[enc->program].ptyn_update = 1;
|
|
|
|
if(ptyn[0] == '\0') {
|
|
memset(enc->data[enc->program].ptyn, 0, PTYN_LENGTH);
|
|
return;
|
|
}
|
|
|
|
memset(enc->data[enc->program].ptyn, ' ', PTYN_LENGTH);
|
|
while (*ptyn != 0 && len < PTYN_LENGTH) enc->data[enc->program].ptyn[len++] = *ptyn++;
|
|
} |