Files
rds95/src/rds95.c
T
2026-07-11 22:57:03 +02:00

238 lines
5.8 KiB
C

#include "common.h"
#include <signal.h>
#include <getopt.h>
#include <pthread.h>
#include <time.h>
#include "../inih/ini.h"
#include "rds.h"
#include "ipc_client.h"
#include "fs.h"
#include "udp_server.h"
#include "tcp_server.h"
#include "lib.h"
#define DEFAULT_CONFIG_PATH "/etc/rds95.conf"
static struct timespec next_deadline;
static int deadline_initialized = 0;
static void sleep_until_next_group(double bits_per_group, double bit_rate) {
double interval_ns = 1e9 * bits_per_group / bit_rate;
if (!deadline_initialized) {
clock_gettime(CLOCK_MONOTONIC, &next_deadline);
deadline_initialized = 1;
}
next_deadline.tv_nsec += (long)interval_ns;
while (next_deadline.tv_nsec >= 1000000000L) {
next_deadline.tv_nsec -= 1000000000L;
next_deadline.tv_sec += 1;
}
clock_nanosleep(CLOCK_MONOTONIC, TIMER_ABSTIME, &next_deadline, NULL);
}
static uint8_t stop_rds = 0;
static void stop() {
printf("Received an stopping signal\n");
stop_rds = 1;
}
static void *udp_server_worker() {
while (!stop_rds) {
poll_udp_server();
msleep(READ_TIMEOUT_MS);
}
close_udp_server();
pthread_exit(NULL);
}
static inline void show_help(char *name) {
printf(
"\n"
"Usage: %s [options]\n"
"\n"
"\t-c,--config\tSet the config path [default: %s]\n"
"\t-a,--asciig\tEnable asciig mode where the RDS IPC will be disabled and encoded RDS ASCII G data will be sent from stderr\n"
"\n",
name,
DEFAULT_CONFIG_PATH
);
}
typedef struct {
uint16_t udp_port;
uint16_t tcp_port;
uint8_t asciig : 1;
} RDS95_Config;
static int config_handler(void* user, const char* section, const char* name, const char* value) {
RDS95_Config* config = (RDS95_Config*)user;
#define MATCH(s, n) (strcmp(section, s) == 0 && strcmp(name, n) == 0)
if (MATCH("rds95", "udp_port")) config->udp_port = (uint16_t)atoi(value);
else if (MATCH("rds95", "tcp_port")) config->tcp_port = (uint16_t)atoi(value);
else if (MATCH("rds95", "asciig")) {
int asciig = atoi(value);
if (asciig > 1 || asciig < 0) return 0;
config->asciig = (uint8_t)asciig;
}
return 1;
}
int main(int argc, char **argv) {
printf("rds95 (a RDS encoder by radio95) version %s\n", VERSION);
char config_path[64] = DEFAULT_CONFIG_PATH;
RDS95_Config config = {
.udp_port = 0,
.tcp_port = 0,
.asciig = 0
};
pthread_attr_t attr;
pthread_t udp_server_thread;
const char *short_opt = "c:ah";
struct option long_opt[] =
{
{"config", required_argument, NULL, 'c'},
{"asciig", no_argument, NULL, 'a'},
{"help", no_argument, NULL, 'h'},
{0, 0, 0, 0}
};
int opt;
while((opt = getopt_long(argc, argv, short_opt, long_opt, NULL)) != -1) {
switch (opt) {
case 'c':
memcpy(config_path, optarg, 62);
config_path[48] = '\0';
break;
case 'a':
config.asciig = 1;
break;
case 'h':
show_help(argv[0]);
return 0;
default:
show_help(argv[0]);
return 1;
}
}
int res = ini_parse(config_path, config_handler, &config);
if(res != 0) {
fprintf(stderr, "Error: Could not read ini config, error code as return code.\n");
return res;
}
pthread_attr_init(&attr);
struct sigaction sa_stop;
sa_stop.sa_handler = stop;
sigemptyset(&sa_stop.sa_mask);
sa_stop.sa_flags = 0;
sigaction(SIGINT, &sa_stop, NULL);
sigaction(SIGTERM, &sa_stop, NULL);
RDSEncoder rdsEncoder = {0};
uint8_t err = init_lua(&rdsEncoder);
if(err == 1) {
fprintf(stderr, "Could not create lua state - not enough memory\n");
goto exit;
} else if (err == 2) {
fprintf(stderr, "Could not load lua script file\n");
goto exit;
}
init_rds_encoder(&rdsEncoder);
if(open_udp_server(config.udp_port) == 0) {
printf("Reading control commands on UDP:%d.\n", config.udp_port);
int r = pthread_create(&udp_server_thread, &attr, udp_server_worker, NULL);
if (r < 0) {
fprintf(stderr, "Could not create UDP server thread.\n");
config.udp_port = 0;
goto exit;
} else printf("Created UDP server thread.\n");
} else {
fprintf(stderr, "Failed to open UDP server\n");
config.udp_port = 0;
}
if(config.asciig == 1 && config.tcp_port > 0) {
if (init_tcp_server(config.tcp_port) < 0) {
fprintf(stderr, "Failed to initialize TCP server\n");
goto exit;
}
}
if(config.asciig == 0) {
IPC_Client client;
if (ipc_connect(&client, "/etc/fm95/ctl.socket") < 0) goto exit;
uint8_t sent_num = rdsEncoder.streams; ipc_send_streams(&client, sent_num);
while (!stop_rds) {
for (uint8_t s = 0; s < rdsEncoder.streams; s++) {
ipc_send_bits(&client, &rdsEncoder, s);
}
sleep_until_next_group(BITS_PER_GROUP, 1187.5);
if(rdsEncoder.streams != sent_num) {
sent_num = rdsEncoder.streams; ipc_send_streams(&client, sent_num);
}
}
ipc_close(&client);
} else {
RDSGroup group;
char output_buffer[1024];
char starts[4][5] = {"G:\r\n", "H:\r\n", "I:\r\n", "J:\r\n"};
setvbuf(stderr, NULL, _IONBF, 0);
while(!stop_rds) {
if (is_tcp_server_running()) accept_tcp_clients();
for(uint8_t i = 0; i < rdsEncoder.streams; i++) {
get_rds_group(&rdsEncoder, &group, i);
int offset = 0;
memset(output_buffer, 0, sizeof(output_buffer));
offset += snprintf(output_buffer + offset, sizeof(output_buffer) - offset, "%s", starts[i]);
for(uint8_t j = 0; j < GROUP_LENGTH; j++) offset += snprintf(output_buffer + offset, sizeof(output_buffer) - offset, "%04X", get_block_from_group(&group, j));
offset += snprintf(output_buffer + offset, sizeof(output_buffer) - offset, "\r\n\r\n");
fwrite(output_buffer, 1, offset, stderr);
fflush(stderr);
if (is_tcp_server_running()) send_to_tcp_clients(output_buffer, offset);
}
msleep(10);
}
}
exit:
if(config.udp_port) {
fprintf(stderr, "Waiting for UDP thread to shut down.\n");
pthread_join(udp_server_thread, NULL);
}
if (is_tcp_server_running()) close_tcp_server();
encoder_saveToFile(&rdsEncoder);
printf("Saved to file\n");
pthread_attr_destroy(&attr);
return 0;
}