/* DCF77 main * * (C) 2022 by Andreas Eversberg * All Rights Reserved * * 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 3 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, see . */ #include #include #include #include #include #include #include #include #include #include #include #include "../liblogging/logging.h" #include "../liboptions/options.h" #include "../libsample/sample.h" #include "../libsound/sound.h" #include "../libaaimage/aaimage.h" #include "dcf77.h" #include "cities.h" int num_kanal = 1; dcf77_t *dcf77 = NULL; static void *soundif = NULL; static const char *dsp_device = ""; static int dsp_samplerate = 192000; static int dsp_buffer = 50; static int rx = 0, tx = 0; static double timestamp = -1; static int weather = 0; static int weather_day; static int weather_night; static int extreme; static int rain; static int wind_dir; static int wind_bft; static int temperature_day; static int temperature_night; static int region = -1, region_advance; static int double_amplitude = 0; static int test_tone = 0; static int dsp_interval = 1; /* ms */ static int rt_prio = 0; static int fast_math = 0; /* not static, in case we add libtimer some day, then compiler hits an error */ double get_time(void) { static struct timespec tv; clock_gettime(CLOCK_REALTIME, &tv); return (double)tv.tv_sec + (double)tv.tv_nsec / 1000000000.0; } static time_t parse_time(char **argv) { time_t t; struct tm *tm; int val; t = get_time(); tm = localtime(&t); if (!tm) return -1; val = atoi(argv[0]); if (val < 1900) return -1; tm->tm_year = val - 1900; val = atoi(argv[1]); if (val < 1 || val > 12) return -1; tm->tm_mon = val - 1; val = atoi(argv[2]); if (val < 1 || val > 31) return -1; tm->tm_mday = val; val = atoi(argv[3]); if (val < 0 || val > 23) return -1; tm->tm_hour = val; val = atoi(argv[4]); if (val < 0 || val > 59) return -1; tm->tm_min = val; val = atoi(argv[5]); if (val < 0 || val > 59) return -1; tm->tm_sec = val; tm->tm_isdst = -1; return mktime(tm); } static time_t feierabend_time() { time_t t; struct tm *tm; t = get_time(); tm = localtime(&t); tm->tm_hour = 17; tm->tm_min = 0; tm->tm_sec = 0; tm->tm_isdst = -1; return mktime(tm); } static void print_usage(const char *app) { printf("Usage: %s [-a hw:0,0] []\n", app); } void print_help(void) { /* - - */ printf(" -h --help\n"); printf(" This help\n"); printf(" --config [~/]\n"); printf(" Give a config file to use. If it starts with '~/', path is at home dir.\n"); printf(" Each line in config file is one option, '-' or '--' must not be given!\n"); logging_print_help(); printf(" -a --audio-device hw:,\n"); printf(" Sound card and device number (default = '%s')\n", dsp_device); printf(" -s --samplerate \n"); printf(" Sample rate of sound device (default = '%d')\n", dsp_samplerate); printf(" -b --buffer \n"); printf(" How many milliseconds are processed in advance (default = '%d')\n", dsp_buffer); printf(" A buffer below 10 ms requires low interval like 0.1 ms.\n"); printf(" -T --tx\n"); printf(" Transmit time signal (default)\n"); printf(" -R --rx\n"); printf(" Receive time signal\n"); printf(" -F --fake\n"); printf(" Use given time stamp: .\n"); printf(" All values have to be numerical. The year must have 4 digits.\n"); printf(" --feierabend\n"); printf(" --end-of-working-day\n"); printf(" Use fake time stamp that equals 5 O'Clock PM.\n"); printf(" --geburtstag\n"); printf(" --birthday\n"); printf(" Use fake time stamp that equals birth of the author.\n"); printf(" -W --weather \n"); printf(" Send these weather info for all regions / all days.\n"); printf(" See -L for infos on values.\n"); printf(" weather = 1..15 for common day and night weather.\n"); printf(" weather = 1..15,1..15 for specific day and night weather.\n"); printf(" extreme = 0..15 for extreme weather conditions.\n"); printf(" rain = 0..100 for rain/show probability. (closest is used)\n"); printf(" wind dir = N | NE | E | SE | S | SW | W | NW | 0 for wind direction.\n"); printf(" wind bft = for wind speed in bft. (closest is used)\n"); printf(" temerature = for common min and max temperature.\n"); printf(" temerature = , for specific min and max temperature.\n"); printf(" --beach-party\n"); printf(" Beach weather, equivalent to -W 1 0 0 0 2 35,20\n"); printf(" --santa-claus\n"); printf(" --muenster-2005\n"); printf(" Deep snow, equivalent to -W 7 1 100 E 3 1,-1\n"); printf(" -A --at-region \n"); printf(" Alter time, so that the weather of the given region is transmitted.\n"); printf(" To allow the receiver to sync, give time to advance in minutes.\n"); printf(" -L --list\n"); printf(" List all regions / weather values.\n"); printf(" -C --city \n"); printf(" Search for city (case insensitive) and display its region code.\n"); printf(" -D --double-amplitude\n"); printf(" Transmit with double amplitude by using differential stereo output.\n"); printf(" --test-tone\n"); printf(" Transmit a test tone (10%% level, 1000 Hz) with the carrier.\n"); printf(" -r --realtime \n"); printf(" Set prio: 0 to disable, 99 for maximum (default = %d)\n", rt_prio); printf(" --fast-math\n"); printf(" Use fast math approximation for slow CPU / ARM based systems.\n"); printf("\n"); printf("Press 'w' key to toggle display of RX wave form.\n"); printf("Press 'm' key to toggle display of measurement values.\n"); } #define OPT_F1 1001 #define OPT_F2 1002 #define OPT_G1 1003 #define OPT_G2 1004 #define OPT_BEACH 1005 #define OPT_SANTA 1006 #define OPT_MUENSTER 1007 #define OPT_TEST_TONE 1008 #define OPT_FAST_MATH 1009 static void add_options(void) { option_add('h', "help", 0); option_add('v', "verbose", 1); option_add('a', "audio-device", 1); option_add('s', "samplerate", 1); option_add('b', "buffer", 1); option_add('T', "tx", 0); option_add('R', "rx", 0); option_add('F', "fake", 6); option_add(OPT_F1, "feierabend", 0); option_add(OPT_F2, "end-of-working-day", 0); option_add(OPT_G1, "geburtstag", 0); option_add(OPT_G2, "birthday", 0); option_add('W', "weather", 6); option_add(OPT_BEACH, "beach-party", 0); option_add(OPT_SANTA, "santa-claus", 0); option_add(OPT_MUENSTER, "muenster-2005", 0); option_add('A', "at-region", 2); option_add('L', "list", 0); option_add('C', "city", 1); option_add('D', "double-amplitude", 0); option_add(OPT_TEST_TONE, "test-tone", 0); option_add('r', "realtime", 1); option_add(OPT_FAST_MATH, "fast-math", 0); } static const char *wind_dirs[8] = { "N", "NE", "E", "SE", "S", "SW", "W", "NW" }; static int handle_options(int short_option, int argi, char **argv) { char *string, *string1; int rc, i; switch (short_option) { case 'h': print_usage(argv[0]); print_help(); return 0; case 'v': rc = parse_logging_opt(argv[argi]); if (rc > 0) return 0; if (rc < 0) { fprintf(stderr, "Failed to parse logging option, please use -h for help.\n"); return rc; } break; case 'a': dsp_device = options_strdup(argv[argi]); break; case 's': dsp_samplerate = atoi(argv[argi]); break; case 'b': dsp_buffer = atoi(argv[argi]); break; case 'T': tx = 1; break; case 'R': rx = 1; break; case 'F': timestamp = parse_time(argv + argi); if (timestamp < 0) { fprintf(stderr, "Given time stamp is invalid, please use -h for help.\n"); return -EINVAL; } break; case OPT_F1: case OPT_F2: timestamp = feierabend_time() - 70; break; case OPT_G1: case OPT_G2: timestamp = 115099200 - 70; break; case 'W': if (weather) { no_multiple_weathers: fprintf(stderr, "You cannot define more than one weather situation.\n"); return -EINVAL; } weather = 1; string = options_strdup(argv[argi++]); string1 = strsep(&string, ","); weather_day = atoi(string1); if (string) weather_night = atoi(string); else weather_night = weather_day; extreme = atoi(argv[argi++]); rain = atoi(argv[argi++]); /* if wind is not found, wind 8 (changable) is selected */ string = options_strdup(argv[argi++]); for (i = 0; i < 8; i++) { if (!strcasecmp(string, wind_dirs[i])) break; } wind_dir = i; wind_bft = atoi(argv[argi++]); string = options_strdup(argv[argi++]); string1 = strsep(&string, ","); temperature_day = atoi(string1); if (string) temperature_night = atoi(string); else temperature_night = temperature_day; break; case OPT_BEACH: if (weather) goto no_multiple_weathers; weather = 1; weather_day = 1; weather_night = 1; extreme = 0; rain = 0; wind_dir = 8; wind_bft = 2; temperature_day = 35; temperature_night = 20; /* tropical night >= 20 */ break; case OPT_SANTA: case OPT_MUENSTER: if (weather) goto no_multiple_weathers; weather = 1; weather_day = 7; weather_night = 7; extreme = 0; rain = 100; wind_dir = 6; wind_bft = 3; temperature_day = 1; temperature_night = -1; /* freezing a little */ break; case 'A': region = atoi(argv[argi++]); if (region < 0 || region > 89) { fprintf(stderr, "Given region number is is invalid, please use -L for list of valid regions.\n"); return -EINVAL; } region_advance = atoi(argv[argi++]); break; case 'L': list_weather(); return 0; case 'C': display_city(argv[argi++]); return 0; case OPT_TEST_TONE: test_tone = 1; break; case 'D': double_amplitude = 1; break; case 'r': rt_prio = atoi(argv[argi]); break; case OPT_FAST_MATH: fast_math = 1; break; default: return -EINVAL; } return 1; } static int quit = 0; static void sighandler(int sigset) { if (sigset == SIGHUP || sigset == SIGPIPE) return; fprintf(stderr, "\nSignal %d received.\n", sigset); quit = 1; } static int get_char() { struct timeval tv = {0, 0}; fd_set fds; char c = 0; int __attribute__((__unused__)) rc; FD_ZERO(&fds); FD_SET(0, &fds); select(0+1, &fds, NULL, NULL, &tv); if (FD_ISSET(0, &fds)) { rc = read(0, &c, 1); return c; } else return -1; } int soundif_open(const char *audiodev, int samplerate, int buffer_size) { if (!audiodev || !audiodev[0]) { LOGP(DDSP, LOGL_ERROR, "No audio device given!\n"); return -EINVAL; } /* open audiodev */ soundif = sound_open(audiodev, NULL, NULL, NULL, (double_amplitude) ? 2 : 1, 0.0, samplerate, buffer_size, 1.0, 1.0, 0.0, 2.0); if (!soundif) { LOGP(DDSP, LOGL_ERROR, "Failed to open sound device!\n"); return -EIO; } return 0; } void soundif_start(void) { sound_start(soundif); LOGP(DDSP, LOGL_DEBUG, "Starting audio stream!\n"); } void soundif_close(void) { /* close audiodev */ if (soundif) { sound_close(soundif); soundif = NULL; } } void soundif_work(int buffer_size) { int count; sample_t buff1[buffer_size], buff2[buffer_size], *samples[2] = { buff1, buff2 }; double rf_level_db[2]; int rc; int i; /* encode and write */ count = sound_get_tosend(soundif, buffer_size); if (count < 0) { LOGP(DDSP, LOGL_ERROR, "Failed to get number of samples in buffer (rc = %d)!\n", count); return; } if (count) { dcf77_encode(dcf77, samples[0], count); if (double_amplitude) { for (i = 0; i < count; i++) samples[1][i] = -samples[0][i]; } rc = sound_write(soundif, samples, NULL, count, NULL, NULL, (double_amplitude) ? 2 : 1); if (rc < 0) { LOGP(DDSP, LOGL_ERROR, "Failed to write TX data to audio device (rc = %d)\n", rc); return; } } /* read */ count = sound_read(soundif, samples, buffer_size, 1, rf_level_db); if (count < 0) { LOGP(DDSP, LOGL_ERROR, "Failed to read from audio device (rc = %d)!\n", count); return; } /* decode */ dcf77_decode(dcf77, samples[0], count); } int main(int argc, char *argv[]) { int rc, argi; int buffer_size; struct termios term, term_orig; double begin_time, now, sleep; char c; logging_init(); /* handle options / config file */ add_options(); rc = options_config_file(argc, argv, "~/.osmocom/dcf77/dcf77.conf", handle_options); if (rc < 0) return 0; argi = options_command_line(argc, argv, handle_options); if (argi <= 0) return argi; if (dsp_samplerate < 192000) { fprintf(stderr, "The sample rate must be at least 192000 to TX or RX 77.5 kHz. Quitting!\n"); goto error; } /* default to TX, if --tx and --rx was not set */ if (!tx && !rx) tx = 1; /* inits */ dcf77_init(fast_math); /* size of dsp buffer in samples */ buffer_size = dsp_samplerate * dsp_buffer / 1000; rc = soundif_open(dsp_device, dsp_samplerate, buffer_size); if (rc < 0) { printf("Failed to open sound for DCF77, use '-h' for help.\n"); goto error; } dcf77 = dcf77_create(dsp_samplerate, tx, rx, test_tone); if (!dcf77) { fprintf(stderr, "Failed to create \"DCF77\" instance. Quitting!\n"); goto error; } if (weather) dcf77_set_weather(dcf77, weather_day, weather_night, extreme, rain, wind_dir, wind_bft, temperature_day, temperature_night); /* no time stamp given, so we use our clock */ if (tx) { if (timestamp < 0 && region < 0) printf("No alternative time given, so you might not notice the difference between our transmission and the real DCF77 transmission.\n"); if (timestamp < 0) timestamp = get_time(); if (region >= 0 && weather) timestamp = dcf77_start_weather((time_t)timestamp, region, region_advance); } /* set real time prio */ if (rt_prio) { struct sched_param schedp; memset(&schedp, 0, sizeof(schedp)); schedp.sched_priority = rt_prio; rc = sched_setscheduler(0, SCHED_RR, &schedp); if (rc) { fprintf(stderr, "Error setting SCHED_RR with prio %d\n", rt_prio); goto error; } } print_aaimage(); printf("DCF77 ready.\n"); /* prepare terminal */ tcgetattr(0, &term_orig); term = term_orig; term.c_lflag &= ~(ISIG|ICANON|ECHO); term.c_cc[VMIN]=1; term.c_cc[VTIME]=2; tcsetattr(0, TCSANOW, &term); signal(SIGINT, sighandler); signal(SIGHUP, sighandler); signal(SIGTERM, sighandler); signal(SIGPIPE, sighandler); soundif_start(); if (tx) dcf77_tx_start(dcf77, (time_t)timestamp, fmod(timestamp, 1.0)); while (!quit) { int w; begin_time = get_time(); soundif_work(buffer_size); do { w = 0; } while (w); c = get_char(); switch (c) { case 3: printf("CTRL+c received, quitting!\n"); quit = 1; break; case 'w': /* toggle wave display */ display_measurements_on(0); display_wave_on(-1); break; case 'm': /* toggle measurements display */ display_wave_on(0); display_measurements_on(-1); break; default: break; } display_measurements(dsp_interval / 1000.0); now = get_time(); /* sleep interval */ sleep = ((double)dsp_interval / 1000.0) - (now - begin_time); if (sleep > 0) usleep(sleep * 1000000.0); } signal(SIGINT, SIG_DFL); signal(SIGTSTP, SIG_DFL); signal(SIGHUP, SIG_DFL); signal(SIGTERM, SIG_DFL); signal(SIGPIPE, SIG_DFL); /* reset real time prio */ if (rt_prio > 0) { struct sched_param schedp; memset(&schedp, 0, sizeof(schedp)); schedp.sched_priority = 0; sched_setscheduler(0, SCHED_OTHER, &schedp); } /* reset terminal */ tcsetattr(0, TCSANOW, &term_orig); error: /* destroy UK0 instances */ if (dcf77) dcf77_destroy(dcf77); soundif_close(); dcf77_exit(); options_free(); return 0; } void osmo_cc_set_log_cat(void) {}