mirror of
https://github.com/radio95-rnt/rds95.git
synced 2026-07-29 08:19:17 +02:00
238 lines
5.8 KiB
C
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;
|
|
} |