From e7cb6d05ec98623705526682e2d97bee1106f2e7 Mon Sep 17 00:00:00 2001 From: Zoltan Csahok Date: Tue, 18 Jul 2023 20:45:48 +0200 Subject: [PATCH] Make band limits configurable (#392) * make band limits configurable * allow showing out-of-band spots on bandmap --- src/bandmap.c | 80 ++++++++++++++++++++++-------------- src/bandmap.h | 14 ++++--- src/bands.c | 6 +-- src/bands.h | 6 +-- src/parse_logcfg.c | 89 +++++++++++++++++++++++++++++++++++----- test/test_parse_logcfg.c | 26 ++++++++---- tlf.1.in | 23 +++++++++++ 7 files changed, 184 insertions(+), 60 deletions(-) diff --git a/src/bandmap.c b/src/bandmap.c index 6a9176ae1..7b9fd72a4 100644 --- a/src/bandmap.c +++ b/src/bandmap.c @@ -70,12 +70,13 @@ GPtrArray *spots; bm_config_t bm_config = { - 1, /* show all bands */ - 1, /* show all mode */ - 1, /* show dupes */ - 1, /* skip dupes during grab */ - 900,/* default lifetime */ - 0 /* DO NOT show ONLY multipliers */ + .allband = true, /* show all bands */ + .allmode = true, /* show all mode */ + .showdupes = true, /* show dupes */ + .skipdupes = true, /* skip dupes during grab */ + .lifetime = 900, /* default lifetime */ + .onlymults = false, /* DO NOT show ONLY multipliers */ + .show_out_of_band = false, /* do not show out-of-band spots */ }; static bool bm_initialized = false; @@ -110,10 +111,10 @@ void bmdata_write_file() { fprintf(fp, "%d\n", (int)tv.tv_sec); while (found != NULL) { sp = found->data; - fprintf(fp, "%s;%d;%d;%d;%c;%u;%d;%d;%d;%s\n", + fprintf(fp, "%s;%d;%d;%d;%c;%u;%d;%d;%d;%s;%d\n", sp->call, sp->freq, sp->mode, sp->bandindex, sp->node, sp->timeout, sp->dupe, sp->cqzone, - sp->ctynr, g_strchomp(sp->pfx)); + sp->ctynr, g_strchomp(sp->pfx), sp->mult); found = found->next; } @@ -156,13 +157,15 @@ void bmdata_read_file() { break; case 2: sscanf(token, "%hhd", &entry->mode); break; - case 3: sscanf(token, "%hd", &entry->bandindex); + case 3: // re-evaluate band index + entry->bandindex = freq2bandindex(entry->freq); break; case 4: sscanf(token, "%c", &entry->node); break; case 5: sscanf(token, "%u", &entry->timeout); break; - case 6: sscanf(token, "%hhd", &entry->dupe); + case 6: // dupe is transient, not read back + entry->dupe = false; break; case 7: sscanf(token, "%d", &entry->cqzone); break; @@ -170,6 +173,9 @@ void bmdata_read_file() { break; case 9: entry->pfx = g_strdup(token); break; + case 10: // mult is transient, not read back + entry->mult = false; + break; } fc++; token = strtok(NULL, ";"); @@ -306,9 +312,6 @@ void bandmap_addspot(char *call, freq_t freq, char node) { return; bandindex = freq2bandindex(freq); - if (bandindex == BANDINDEX_OOB) /* no ham band */ - return; - mode = freq2mode(freq, bandindex); /* acquire bandmap mutex */ @@ -351,6 +354,7 @@ void bandmap_addspot(char *call, freq_t freq, char node) { entry -> node = node; entry -> timeout = SPOT_NEW; entry -> dupe = 0; /* Dupe will be determined later. */ + entry -> mult = false; /* Mult will be determined later. */ lastexch = NULL; dxccindex = getctynr(entry->call); @@ -585,6 +589,17 @@ char *format_spot(spot *data) { return temp; } +static char get_spot_marker(spot *data) { + if (data->bandindex == BANDINDEX_OOB) { + return 'X'; + } + if (data->mult) { + return 'M'; + } + + return ' '; +} + /* helper function for bandmap display * shows formatted spot on actual cursor position @@ -594,12 +609,13 @@ void show_spot(spot *data) { printw("%7.1f%c", (data->freq / 1000.), (data->node == thisnode ? '*' : data->node)); - if (bm_ismulti(data)) { + char marker = get_spot_marker(data); + if (marker != ' ') { attrset(COLOR_PAIR(CB_NORMAL)); - printw("M"); + } + addch(marker); + if (marker != ' ') { attrset(COLOR_PAIR(CB_DUPE) | A_BOLD); - } else { - printw(" "); } char *temp = format_spot(data); @@ -616,7 +632,7 @@ void show_spot_on_qrg(spot *data) { printw("%7.1f%c%c ", (data->freq / 1000.), (data->node == thisnode ? '*' : data->node), - bm_ismulti(data) ? 'M' : ' '); + get_spot_marker(data)); char *temp = format_spot(data); printw("%-12s", temp); @@ -675,7 +691,6 @@ static bool mode_matches(spot *data) { void filter_spots() { GList *list; spot *data; - bool dupe, multi; /* acquire mutex * do not add new spots to allspots during * - aging and @@ -687,7 +702,7 @@ void filter_spots() { if (spots) g_ptr_array_free(spots, TRUE); /* free spot array */ - /* allocate new one */ + /* allocate new one */ spots = g_ptr_array_new_full(128, (GDestroyNotify)free_spot); @@ -695,22 +710,26 @@ void filter_spots() { data = list->data; /* check and mark spot as dupe */ - dupe = bm_isdupe(data->call, data->bandindex); - data -> dupe = dupe; + data->dupe = bm_isdupe(data->call, data->bandindex); /* ignore spots on WARC bands if in contest mode */ if (iscontest && IsWarcIndex(data->bandindex)) continue; /* ignore dupes if not forced */ - if (dupe && !bm_config.showdupes) + if (data->dupe && !bm_config.showdupes) continue; /* Ignore non-multis if we want to show only multis */ - multi = bm_ismulti(data); - if (!multi && bm_config.onlymults) + data->mult = bm_ismulti(data); + if (!data->mult && bm_config.onlymults) continue; + /* ignore out-of-band spots if configured so */ + if (data->bandindex == BANDINDEX_OOB && !bm_config.show_out_of_band) { + continue; + } + /* if spot is allband or allmode is set or band or mode matches * than add to the filtered 'spot' array */ @@ -903,19 +922,19 @@ void bm_menu() { c = toupper(key_get()); switch (c) { case 'B': - bm_config.allband = 1 - bm_config.allband; + bm_config.allband = !bm_config.allband; break; case 'M': - bm_config.allmode = 1 - bm_config.allmode; + bm_config.allmode = !bm_config.allmode; break; case 'D': - bm_config.showdupes = 1 - bm_config.showdupes; + bm_config.showdupes = !bm_config.showdupes; break; case 'O': - bm_config.onlymults = 1 - bm_config.onlymults; + bm_config.onlymults = !bm_config.onlymults; break; } bandmap_show(); /* refresh display */ @@ -939,6 +958,7 @@ spot *copy_spot(spot *data) { result -> node = data -> node; result -> timeout = data -> timeout; result -> dupe = data -> dupe; + result -> mult = data -> mult; result -> cqzone = data -> cqzone; result -> ctynr = data -> ctynr; result -> pfx = g_strdup(data -> pfx); @@ -1087,7 +1107,7 @@ void get_spot_on_qrg(char *dest, freq_t freq) { data = g_ptr_array_index(spots, i); if ((fabs(data->freq - freq) < TOLERANCE) && - (!bm_config.skipdupes || data->dupe == 0)) { + (!bm_config.skipdupes || !data->dupe)) { strcpy(dest, data->call); break; } diff --git a/src/bandmap.h b/src/bandmap.h index 72f8b18a3..eb0a66a18 100644 --- a/src/bandmap.h +++ b/src/bandmap.h @@ -31,7 +31,8 @@ typedef struct { short bandindex; char node; unsigned int timeout;/* time (in seconds) left in bandmap */ - char dupe; /* only used internal in bm_show() */ + bool dupe; /* only used internally in bm_show() */ + bool mult; /* only used internally in bm_show() */ int cqzone; /* CQ zone */ int ctynr; /* Country nr */ char *pfx; /* prefix */ @@ -42,12 +43,13 @@ typedef struct { #define SPOT_OLD (SPOT_NEW * 2) / 3 typedef struct { - short allband; - short allmode; - short showdupes; - short skipdupes; + bool allband; + bool allmode; + bool showdupes; + bool skipdupes; short lifetime; - short onlymults; + bool onlymults; + bool show_out_of_band; } bm_config_t; extern bm_config_t bm_config; diff --git a/src/bands.c b/src/bands.c index 0f45665a6..0a0a0812a 100644 --- a/src/bands.c +++ b/src/bands.c @@ -28,7 +28,7 @@ const static int bandnr[NBANDS] = { 160, 80, 60, 40, 30, 20, 17, 15, 12, 10, 0 }; -const unsigned int bandcorner[NBANDS][2] = { +unsigned int bandcorner[NBANDS][2] = { { 1800000, 2000000 }, // band bottom, band top { 3500000, 4000000 }, { 5250000, 5450000 }, // 5351500-5356500 worldwide @@ -42,7 +42,7 @@ const unsigned int bandcorner[NBANDS][2] = { { 0, 0 } }; -const unsigned int cwcorner[NBANDS] = { +unsigned int cwcorner[NBANDS] = { 1838000, 3580000, 5354000, @@ -56,7 +56,7 @@ const unsigned int cwcorner[NBANDS] = { 0 }; -const unsigned int ssbcorner[NBANDS] = { +unsigned int ssbcorner[NBANDS] = { 1840000, 3600000, 5354000, diff --git a/src/bands.h b/src/bands.h index ff072cff1..306a69267 100644 --- a/src/bands.h +++ b/src/bands.h @@ -35,9 +35,9 @@ #define BAND_DOWN -1 extern int inxes[NBANDS]; /**< conversion from BANDINDEX to BAND-mask */ -extern const unsigned int bandcorner[NBANDS][2]; -extern const unsigned int cwcorner[NBANDS]; -extern const unsigned int ssbcorner[NBANDS]; +extern unsigned int bandcorner[NBANDS][2]; +extern unsigned int cwcorner[NBANDS]; +extern unsigned int ssbcorner[NBANDS]; /** \brief converts bandnumber to bandindex * diff --git a/src/parse_logcfg.c b/src/parse_logcfg.c index 9481a01c6..df30522c3 100644 --- a/src/parse_logcfg.c +++ b/src/parse_logcfg.c @@ -32,6 +32,7 @@ #include "audio.h" #include "bandmap.h" +#include "bands.h" #include "cabrillo_utils.h" #include "change_rst.h" #include "cw_utils.h" @@ -320,6 +321,70 @@ static int cfg_tlfcolor(const cfg_arg_t arg) { return rc; } +// returns 0 on error +static unsigned int parse_frequency(char *input) { + char *value = g_strdup(input); + str_toupper(value); // normalize to upper case + /* must be digits and an optional K suffix */ + if (!g_regex_match_simple("^\\s*[0-9]+K?\\s*$", + value, (GRegexCompileFlags)0, (GRegexMatchFlags)0)) { + g_free(value); + return 0; + } + + int freq = atoi(value); + if (strchr(value, 'K') != NULL) { + freq *= 1000; + } + + g_free(value); + return freq; +} + +static int cfg_band(const cfg_arg_t arg) { + gchar *index = g_match_info_fetch(match_info, 1); + int band = atoi(index); + int bi = bandnr2index(band); // get band index + g_free(index); + + if (bi == BANDINDEX_OOB) { + error_details = g_strdup("invalid band"); + return PARSE_ERROR; + } + + int rc = PARSE_OK; + char **fields = g_strsplit(parameter, ",", 4); + unsigned int values[4]; + for (int i = 0; i < 4 && rc == PARSE_OK; ++i) { + if (fields[i] == NULL) { + error_details = g_strdup("too few arguments"); + rc = PARSE_WRONG_PARAMETER; + break; + } + values[i] = parse_frequency(fields[i]); + if (values[i] == 0) { + error_details = g_strdup_printf("invalid frequency %s", fields[i]); + rc = PARSE_WRONG_PARAMETER; + } + // values may not be decreasing + else if (i > 0 && values[i] < values[i - 1]) { + error_details = g_strdup_printf("wrong frequency %s", fields[i]); + rc = PARSE_WRONG_PARAMETER; + } + } + + if (rc == PARSE_OK) { + bandcorner[bi][0] = values[0]; + cwcorner[bi] = values[1]; + ssbcorner[bi] = values[2]; + bandcorner[bi][1] = values[3]; + } + + g_strfreev(fields); + + return rc; +} + static int cfg_call(const cfg_arg_t arg) { int rc = cfg_string((cfg_arg_t) { .char_p = my.call, .size = sizeof(my.call), @@ -396,12 +461,13 @@ static int cfg_bandmap(const cfg_arg_t arg) { cluster = MAP; /* init bandmap filtering */ - bm_config.allband = 1; - bm_config.allmode = 1; - bm_config.showdupes = 1; - bm_config.skipdupes = 0; + bm_config.allband = true; + bm_config.allmode = true; + bm_config.showdupes = true; + bm_config.skipdupes = false; bm_config.lifetime = 900; - bm_config.onlymults = 0; + bm_config.onlymults = false; + bm_config.show_out_of_band = false; /* Allow configuration of bandmap display if keyword * is followed by a '=' @@ -415,15 +481,17 @@ static int cfg_bandmap(const cfg_arg_t arg) { char *ptr = bm_fields[0]; while (*ptr != '\0') { switch (*ptr++) { - case 'B': bm_config.allband = 0; + case 'B': bm_config.allband = false; + break; + case 'M': bm_config.allmode = false; break; - case 'M': bm_config.allmode = 0; + case 'D': bm_config.showdupes = false; break; - case 'D': bm_config.showdupes = 0; + case 'S': bm_config.skipdupes = true; break; - case 'S': bm_config.skipdupes = 1; + case 'O': bm_config.onlymults = true; break; - case 'O': bm_config.onlymults = 1; + case 'X': bm_config.show_out_of_band = true; break; default: break; @@ -1208,6 +1276,7 @@ static config_t logcfg_configs[] = { {"QS_VKSPM", CFG_MESSAGE(qtc_phsend_message, SP_TU_MSG) }, {"TLFCOLOR([1-6])", NEED_PARAM, cfg_tlfcolor}, + {"BAND_([0-9]+)", NEED_PARAM, cfg_band}, {"LAN_PORT", CFG_INT(lan_port, 1000, INT32_MAX)}, {"TIME_OFFSET", CFG_INT(timeoffset, -23, 23)}, diff --git a/test/test_parse_logcfg.c b/test/test_parse_logcfg.c index 871bb66b7..1a574d874 100644 --- a/test/test_parse_logcfg.c +++ b/test/test_parse_logcfg.c @@ -6,6 +6,7 @@ #include #include "../src/audio.h" +#include "../src/bands.h" #include "../src/parse_logcfg.h" #include "../src/lancode.h" #include "../src/bandmap.h" @@ -326,31 +327,31 @@ void test_keyer_device_too_long(void **state) { void test_vk_play_cmd(void **state) { int rc = call_parse_logcfg("VK_PLAY_COMMAND= sox -q $1 -d\n"); - assert_int_equal(rc,0); + assert_int_equal(rc, 0); assert_string_equal(vk_play_cmd, "sox -q $1 -d"); } void test_vk_record_cmd(void **state) { int rc = call_parse_logcfg("VK_RECORD_COMMAND= sox -r 8000 -q -d $1 &\n"); - assert_int_equal(rc,0); + assert_int_equal(rc, 0); assert_string_equal(vk_record_cmd, "sox -r 8000 -q -d $1 &"); } void test_soundlog_play_cmd(void **state) { int rc = call_parse_logcfg("SOUNDLOG_PLAY_COMMAND= sox -q $1 -d\n"); - assert_int_equal(rc,0); + assert_int_equal(rc, 0); assert_string_equal(soundlog_play_cmd, "sox -q $1 -d"); } void test_soundlog_record_cmd(void **state) { int rc = call_parse_logcfg("SOUNDLOG_RECORD_COMMAND= ./soundlog"); - assert_int_equal(rc,0); + assert_int_equal(rc, 0); assert_string_equal(soundlog_record_cmd, "./soundlog"); } void test_soundlog_directory(void **state) { int rc = call_parse_logcfg("SOUNDLOG_DIRECTORY= ~/soundlogs"); - assert_int_equal(rc,0); + assert_int_equal(rc, 0); assert_string_equal(soundlog_dir, "~/soundlogs"); } @@ -396,7 +397,7 @@ void test_usepartials_wrong_arg(void **state) { int rc = call_parse_logcfg("USEPARTIALS=abc\n"); assert_int_equal(rc, PARSE_ERROR); assert_string_equal(showmsg_spy, - "Wrong parameter format for keyword 'USEPARTIALS'. See man page.\n"); + "Wrong parameter format for keyword 'USEPARTIALS'. See man page.\n"); } @@ -977,7 +978,7 @@ void test_bandmap(void **state) { int rc = call_parse_logcfg("BANDMAP\n"); assert_int_equal(rc, 0); assert_int_equal(cluster, MAP); - assert_int_equal(bm_config.showdupes, 1); + assert_true(bm_config.showdupes); assert_int_equal(bm_config.lifetime, 900); } @@ -985,7 +986,7 @@ void test_bandmap_d100(void **state) { int rc = call_parse_logcfg("BANDMAP=D,100\n"); assert_int_equal(rc, 0); assert_int_equal(cluster, MAP); - assert_int_equal(bm_config.showdupes, 0); + assert_false(bm_config.showdupes); assert_int_equal(bm_config.lifetime, 100); } @@ -1473,3 +1474,12 @@ void test_digi_rig_mode_rttyr(void **state) { assert_int_equal(rc, PARSE_OK); assert_int_equal(digi_mode, RIG_MODE_RTTYR); } + +void test_band40_ok(void **state) { + int rc = call_parse_logcfg("BAND_40=7000k,7040k,7050k,7200k"); + assert_int_equal(rc, PARSE_OK); + assert_int_equal(bandcorner[3][0], 7000 * 1000); + assert_int_equal(bandcorner[3][1], 7200 * 1000); + assert_int_equal(cwcorner[3], 7040 * 1000); + assert_int_equal(ssbcorner[3], 7050 * 1000); +} diff --git a/tlf.1.in b/tlf.1.in index 9aa9d5b35..6b3302eae 100644 --- a/tlf.1.in +++ b/tlf.1.in @@ -1734,6 +1734,27 @@ See the relevant Xplanet documentation. .IP Use azimuthal projection and center the map on your QTH (station location). . +.TP +\fBBAND_\fINN\fR=\fIbottom,cw_end,ssb_start,top\fR +Define band edges. \fINN\fR is the band designator (wavelength), allowed values are: +160, 80, 60, 40, 30, 20, 17, 15, 12, 10. +. +.IP +All 4 comma separated parameters are mandatory and +shall be specified in integer Hz units with an optional +\fIk\fR suffix. +.IP +Example specification for Region\ 1 40\ m band: +.IP + \fBBAND_40\fR = \fI7000k, 7040k, 7050k, 7200k\fR +. +.IP +Internal default band definitions are based on a superset of all Regions. +For example, 40\ m ends by default at 7300\ kHz. +In addition to configuring the legal limits +\fBBAND_\fINN\fR can also be used to fine tune actual band configurations +for a specific contest. +. .SS Morse Code Keyer Commands . .TP @@ -2080,6 +2101,8 @@ string parsed for: .RB ( Ctrl-G ) .br \(lqO\(rq - show only new multipliers +.br + \(lqX\(rq - show also out-of-band spots when in all-band mode .br .RI < number > lifetime for new spots in seconds (number >=