sql commands for querying the frequency bands and segments
This commit is contained in:
+112
-2
@@ -4,6 +4,7 @@
|
||||
#include <stdbool.h>
|
||||
#include <stddef.h>
|
||||
#include <sqlite3.h>
|
||||
#include <stdint.h>
|
||||
|
||||
#include <hammy/types.h>
|
||||
|
||||
@@ -15,14 +16,30 @@
|
||||
|
||||
#define HAMMY_CALLSIGN_MAX 32
|
||||
#define HAMMY_ENTITY_NAME_MAX 64
|
||||
#define HAMMY_COUNTRY_MAX 4
|
||||
|
||||
// Longest code in the morse table is '$' ('...-..-'), seven characters.
|
||||
#define HAMMY_MORSE_MAX 16
|
||||
|
||||
// A country has at most a handful of licence classes, each with at most a
|
||||
// couple of segments covering one exact frequency. 24 is generous.
|
||||
#define HAMMY_FREQ_PRIVS_MAX 24
|
||||
#define HAMMY_FREQ_IARU_MAX 8
|
||||
#define HAMMY_FREQ_COUNTRIES_MAX 64
|
||||
|
||||
struct hammy_refdb_t {
|
||||
sqlite3* handle;
|
||||
|
||||
sqlite3_stmt* stExact;
|
||||
sqlite3_stmt* stPrefix;
|
||||
sqlite3_stmt* stMorse;
|
||||
sqlite3_stmt* stFreqMain;
|
||||
sqlite3_stmt* stFreqIaru;
|
||||
sqlite3_stmt* stFreqNearest;
|
||||
sqlite3_stmt* stFreqSegEdge;
|
||||
sqlite3_stmt* stCountryKnown;
|
||||
sqlite3_stmt* stCountryList;
|
||||
|
||||
char morseCode[HAMMY_MORSE_MAX]; // Scratch for the last hammy_refdb_get_morse() hit
|
||||
char version[64];
|
||||
};
|
||||
@@ -39,6 +56,64 @@ struct hammy_dxcc_t {
|
||||
char matchedPrefix[HAMMY_CALLSIGN_MAX];
|
||||
bool exact;
|
||||
};
|
||||
|
||||
// ---------------------------------------------------------------------------
|
||||
// Frequency lookup
|
||||
// ---------------------------------------------------------------------------
|
||||
struct hammy_freq_priv_t {
|
||||
char code[8]; // 'E', 'G', 'T'
|
||||
char name[48]; // 'Amateur Extra'
|
||||
int rank;
|
||||
bool permitted; // false = this class may NOT transmit here
|
||||
char modes[64]; // 'CW,DATA' - empty when !permitted
|
||||
int64_t segLowHz;
|
||||
int64_t segHighHz;
|
||||
int maxPowerW; // 0 = national default, no specific limit
|
||||
char notes[160];
|
||||
};
|
||||
|
||||
struct hammy_freq_iaru_t {
|
||||
int region; // 1, 2 or 3
|
||||
int64_t lowHz;
|
||||
int64_t highHz;
|
||||
char modes[32];
|
||||
};
|
||||
|
||||
struct hammy_freq_t {
|
||||
int64_t freqHz;
|
||||
|
||||
bool inBand;
|
||||
char band[16]; // '20m'
|
||||
int64_t bandLowHz;
|
||||
int64_t bandHighHz;
|
||||
|
||||
// The frequency sits exactly on a band or segment boundary. Worth saying
|
||||
// out loud: a signal of any width centred there straddles both sides.
|
||||
bool atBandEdge;
|
||||
bool atSegmentEdge;
|
||||
|
||||
// False when the bundle carries no licence data for this country at all.
|
||||
// The band and IARU results are still valid; only the privilege table is
|
||||
// missing. Say so rather than showing an empty table.
|
||||
bool countryKnown;
|
||||
char country[HAMMY_COUNTRY_MAX];
|
||||
|
||||
hammy_freq_priv_t privs[HAMMY_FREQ_PRIVS_MAX];
|
||||
size_t nPrivs;
|
||||
|
||||
hammy_freq_iaru_t iaru[HAMMY_FREQ_IARU_MAX];
|
||||
size_t nIaru;
|
||||
|
||||
// Only meaningful when !in_band.
|
||||
char nearestBand[16];
|
||||
int64_t nearestLowHz;
|
||||
int64_t nearestHighHz;
|
||||
int64_t nearestDistanceHz;
|
||||
};
|
||||
|
||||
// ---------------------------------------------------------------------------
|
||||
// Lifecycle
|
||||
// ---------------------------------------------------------------------------
|
||||
|
||||
// Opens the bundle read-only and prepares the hot statements.
|
||||
// Returns NULL and logs on failure.
|
||||
@@ -47,6 +122,13 @@ hammy_refdb_t* hammy_refdb_open(const char* path);
|
||||
// Finalises statements and closes the connection. NULLs the passing reference.
|
||||
bool hammy_refdb_close(hammy_refdb_t** db);
|
||||
|
||||
// Bundle version string from ref_meta, or NULL. Owned by the refdb.
|
||||
const char* hammy_refdb_version(hammy_refdb_t* db);
|
||||
|
||||
// ---------------------------------------------------------------------------
|
||||
// Queries
|
||||
// ---------------------------------------------------------------------------
|
||||
|
||||
// Resolves a callsign to a DXCC entity.
|
||||
//
|
||||
// Uses candidate-prefix equality seeks rather than "? GLOB prefix || '*'". The
|
||||
@@ -63,8 +145,36 @@ bool hammy_refdb_dxcc(hammy_refdb_t* db, const char* callsign, hammy_dxcc_t* out
|
||||
// On a hit *out points at storage owned by the refdb and is only valid until
|
||||
// the NEXT call on the same handle - copy it if it has to outlive that.
|
||||
bool hammy_refdb_get_morse(hammy_refdb_t* db, char c, const char** out);
|
||||
|
||||
// What band is freq_hz in, and who may transmit there.
|
||||
//
|
||||
// country is an ISO 3166-1 alpha-2 code; NULL or empty means "US". Always
|
||||
// returns true unless the arguments are bad: "not in a band" and "no data for
|
||||
// that country" are results, not failures. Check out->in_band and
|
||||
// out->country_known.
|
||||
bool hammy_refdb_freq(hammy_refdb_t* db, int64_t freqHz, const char* country,
|
||||
hammy_freq_t* out);
|
||||
|
||||
// Bundle version string from ref_meta, or NULL. Owned by the refdb.
|
||||
const char* hammy_refdb_version(hammy_refdb_t* db);
|
||||
// Which countries have licence data, for the "no data for XX yet" message.
|
||||
// Writes up to cap codes and returns how many were written.
|
||||
size_t hammy_refdb_countries(hammy_refdb_t* db,
|
||||
char out[][HAMMY_COUNTRY_MAX], size_t cap);
|
||||
|
||||
// ---------------------------------------------------------------------------
|
||||
// Input parsing
|
||||
// ---------------------------------------------------------------------------
|
||||
|
||||
// Parses a user-supplied frequency into integer Hz. Accepts "14.150",
|
||||
// "14150 kHz", "146.52 MHz", "1.2 GHz", "14150000 Hz". A bare number with no
|
||||
// unit is read as MHz, which is what people type.
|
||||
//
|
||||
// Deliberately avoids floating point: "14.150" is converted digit by digit to
|
||||
// 14150000 exactly. Going via double gives 14150000.000000002, which compares
|
||||
// wrong against an integer column at exactly a band edge - the one case where
|
||||
// being right matters most.
|
||||
//
|
||||
// Returns false on unparseable input or a value outside 1 Hz .. 300 GHz.
|
||||
bool hammy_freq_parse(const char* text, int64_t* outHz);
|
||||
|
||||
|
||||
#endif
|
||||
|
||||
@@ -8,5 +8,8 @@ typedef struct hammy_command_t hammy_command_t;
|
||||
typedef struct hammy_bot_t hammy_bot_t;
|
||||
typedef struct hammy_refdb_t hammy_refdb_t;
|
||||
typedef struct hammy_dxcc_t hammy_dxcc_t;
|
||||
typedef struct hammy_freq_priv_t hammy_freq_priv_t;
|
||||
typedef struct hammy_freq_iaru_t hammy_freq_iaru_t;
|
||||
typedef struct hammy_freq_t hammy_freq_t;
|
||||
|
||||
#endif
|
||||
|
||||
@@ -0,0 +1,33 @@
|
||||
#include <concord/discord.h>
|
||||
#include <inttypes.h>
|
||||
#include <stdio.h>
|
||||
#include <string.h>
|
||||
#include <stdlib.h>
|
||||
#include <stdint.h>
|
||||
|
||||
#include <hammy/command.h>
|
||||
#include <hammy/job.h>
|
||||
#include <hammy/refdb.h>
|
||||
|
||||
// Instant command: runs on the gateway thread, sends a fresh response.
|
||||
void hammy_cmd_freq(const hammy_job_t* job, struct discord* client, const hammy_refdb_t* refdb) {
|
||||
// Get the text argument from the job
|
||||
const char* freq = hammy_job_get_arg(job, "frequency");
|
||||
if (!freq) {
|
||||
hammy_job_respond(job, client, "Error", "No frequency provided for lookup.", true);
|
||||
return;
|
||||
}
|
||||
|
||||
char* endPtr;
|
||||
float freqMHz = strtof(freq, &endPtr);
|
||||
|
||||
if (*endPtr != '\0') {
|
||||
// Partial conversion, just error out
|
||||
hammy_job_respond(job, client, "Error", "An internal error occurred. Please try again.", true);
|
||||
return;
|
||||
}
|
||||
|
||||
uint64_t freqPlain = (uint64_t)freqMHz * 1000000; // Convert to raw Hz
|
||||
|
||||
|
||||
}
|
||||
@@ -8,6 +8,7 @@
|
||||
// table.
|
||||
void hammy_cmd_ping(const hammy_job_t* job, struct discord* client, const hammy_refdb_t* refdb);
|
||||
void hammy_cmd_morse(const hammy_job_t* job, struct discord* client, const hammy_refdb_t* refdb);
|
||||
void hammy_cmd_freq(const hammy_job_t* job, struct discord* client, const hammy_refdb_t* refdb);
|
||||
|
||||
// TODO: Temporary - move to a proper options system; e.g. a vector of heap-allocated options or something. For now, just point at static storage.
|
||||
static struct discord_application_command_option morse_opts[] = {
|
||||
@@ -20,6 +21,18 @@ static struct discord_application_command_options morse_opts_struct = {
|
||||
.array = morse_opts
|
||||
};
|
||||
|
||||
static struct discord_application_command_option freq_opts[] = {
|
||||
{ .type = DISCORD_APPLICATION_OPTION_STRING, .name = "frequency",
|
||||
.description = "Frequency in MHz", .required = true },
|
||||
{ .type = DISCORD_APPLICATION_OPTION_STRING, .name = "country",
|
||||
.description = "Country code to look up (e.g. US) - Defaults to US if none specified.", .required = false },
|
||||
};
|
||||
|
||||
static struct discord_application_command_options freq_opts_struct = {
|
||||
.size = 2,
|
||||
.array = freq_opts
|
||||
};
|
||||
|
||||
// The command table. Pure data - no allocation, no lifetime, no destructor.
|
||||
// Copying an entry into the bot's vector is a plain struct copy, and the vector
|
||||
// may be created with a NULL destructor hook.
|
||||
@@ -28,6 +41,7 @@ static struct discord_application_command_options morse_opts_struct = {
|
||||
static const hammy_command_t hammy_builtin_commands[] = {
|
||||
{ "ping", "Check whether Hammy is alive and how fast it is responding", NULL, &hammy_cmd_ping, true },
|
||||
{ "morse", "Text to and from Morse code", &morse_opts_struct, &hammy_cmd_morse, true },
|
||||
{ "freq", "Band, segment and who can transmit", &freq_opts_struct, &hammy_cmd_freq, true }
|
||||
|
||||
// Tier 0 commands go here as they land. All pure computation, so instant:
|
||||
// { "grid", "Maidenhead locator conversions", &grid_opts, &hammy_cmd_grid, true },
|
||||
|
||||
@@ -28,6 +28,61 @@ static const char* SQL_VERSION =
|
||||
static const char* SQL_MORSE =
|
||||
"SELECT code FROM morse WHERE character = UPPER(?1) LIMIT 1";
|
||||
|
||||
// Band plus per-class privileges in one pass.
|
||||
//
|
||||
// Both LEFT JOINs matter. They are what makes "Technician: not permitted here"
|
||||
// appear as a row rather than vanishing - a user excluded from a segment needs
|
||||
// to be told so, not shown a shorter list.
|
||||
//
|
||||
// Boundary convention differs between the tables on purpose:
|
||||
// bands inclusive at both ends. 14.350 is still "20m".
|
||||
// band_segments half-open [low, high). 14.150 is the START of the phone
|
||||
// segment, not the end of the CW one. BETWEEN would match
|
||||
// both and the command would print contradictory modes.
|
||||
static const char* SQL_FREQ_MAIN =
|
||||
"SELECT b.name, b.edge_low_hz, b.edge_high_hz,"
|
||||
" lc.code, lc.name, lc.rank,"
|
||||
" s.modes, s.low_hz, s.high_hz, s.max_power_w, s.notes"
|
||||
" FROM bands b"
|
||||
" LEFT JOIN license_classes lc"
|
||||
" ON lc.country = ?2"
|
||||
" LEFT JOIN band_segments s"
|
||||
" ON s.band_id = b.id"
|
||||
" AND s.class_id = lc.id"
|
||||
" AND s.country = ?2"
|
||||
" AND s.low_hz <= ?1"
|
||||
" AND ?1 < s.high_hz"
|
||||
" WHERE b.edge_low_hz <= ?1 AND ?1 <= b.edge_high_hz"
|
||||
" ORDER BY lc.rank DESC, s.low_hz";
|
||||
|
||||
// Regional allocation, independent of national licensing. country = '' and
|
||||
// class_id IS NULL mark these rows: they say what the band IS in a region, not
|
||||
// who may use it.
|
||||
static const char* SQL_FREQ_IARU =
|
||||
"SELECT s.iaru_region, s.low_hz, s.high_hz, s.modes"
|
||||
" FROM band_segments s"
|
||||
" WHERE s.country = '' AND s.class_id IS NULL"
|
||||
" AND s.low_hz <= ?1 AND ?1 < s.high_hz"
|
||||
" ORDER BY s.iaru_region";
|
||||
|
||||
// min() with two arguments is SQLite's scalar min, not the aggregate.
|
||||
static const char* SQL_FREQ_NEAREST =
|
||||
"SELECT name, edge_low_hz, edge_high_hz,"
|
||||
" min(abs(edge_low_hz - ?1), abs(edge_high_hz - ?1))"
|
||||
" FROM bands ORDER BY 4 LIMIT 1";
|
||||
|
||||
// Is the frequency exactly on a segment boundary for this country?
|
||||
static const char* SQL_FREQ_SEG_EDGE =
|
||||
"SELECT 1 FROM band_segments"
|
||||
" WHERE country = ?2 AND (low_hz = ?1 OR high_hz = ?1) LIMIT 1";
|
||||
|
||||
static const char* SQL_countryKnown =
|
||||
"SELECT 1 FROM license_classes WHERE country = ?1 LIMIT 1";
|
||||
|
||||
static const char* SQL_COUNTRY_LIST =
|
||||
"SELECT DISTINCT country FROM license_classes"
|
||||
" WHERE country <> '' ORDER BY country";
|
||||
|
||||
// Authorizer: the connection may read, and nothing else.
|
||||
//
|
||||
// SQLITE_OPEN_READONLY protects the MAIN database file. It does not stop
|
||||
@@ -300,3 +355,244 @@ bool hammy_refdb_get_morse(hammy_refdb_t* db, char c, const char** out) {
|
||||
|
||||
return hit;
|
||||
}
|
||||
|
||||
// ---------------------------------------------------------------------------
|
||||
// Frequency parsing
|
||||
// ---------------------------------------------------------------------------
|
||||
|
||||
// Copies a TEXT column into a fixed buffer, tolerating NULL.
|
||||
static void copy_text(sqlite3_stmt* st, int col, char* dst, size_t cap) {
|
||||
const unsigned char* v = sqlite3_column_text(st, col);
|
||||
|
||||
snprintf(dst, cap, "%s", v ? (const char*)v : "");
|
||||
}
|
||||
|
||||
bool hammy_freq_parse(const char* text, int64_t* outHz) {
|
||||
if (!text || !outHz) { return false; }
|
||||
|
||||
while (*text == ' ' || *text == '\t') { text++; }
|
||||
|
||||
// Integer part.
|
||||
int64_t whole = 0;
|
||||
bool anyDigit = false;
|
||||
|
||||
while (*text >= '0' && *text <= '9') {
|
||||
if (whole > INT64_MAX / 10 - 9) { return false; } // absurd input
|
||||
whole = whole * 10 + (*text - '0');
|
||||
anyDigit = true;
|
||||
text++;
|
||||
}
|
||||
|
||||
// Fractional part, accumulated as digits rather than a double. Six decimal
|
||||
// places of MHz is exactly 1 Hz, so anything beyond that is discarded.
|
||||
int64_t frac = 0;
|
||||
int fracDigits = 0;
|
||||
|
||||
if (*text == '.' || *text == ',') {
|
||||
text++;
|
||||
|
||||
while (*text >= '0' && *text <= '9') {
|
||||
if (fracDigits < 9) {
|
||||
frac = frac * 10 + (*text - '0');
|
||||
fracDigits++;
|
||||
}
|
||||
anyDigit = true;
|
||||
text++;
|
||||
}
|
||||
}
|
||||
|
||||
if (!anyDigit) { return false; }
|
||||
|
||||
while (*text == ' ' || *text == '\t') { text++; }
|
||||
|
||||
// Unit. A bare number means MHz, which is what people type.
|
||||
int64_t mult = 1000000; // Hz per unit
|
||||
|
||||
if (*text) {
|
||||
char unit[8] = {0};
|
||||
size_t i = 0;
|
||||
|
||||
while (text[i] && i + 1 < sizeof(unit) && text[i] != ' ') {
|
||||
char c = text[i];
|
||||
unit[i] = (c >= 'A' && c <= 'Z') ? (char)(c - 'A' + 'a') : c;
|
||||
i++;
|
||||
}
|
||||
|
||||
if (!strcmp(unit, "hz")) { mult = 1; }
|
||||
else if (!strcmp(unit, "khz") || !strcmp(unit, "k")) { mult = 1000; }
|
||||
else if (!strcmp(unit, "mhz") || !strcmp(unit, "m")) { mult = 1000000; }
|
||||
else if (!strcmp(unit, "ghz") || !strcmp(unit, "g")) { mult = 1000000000; }
|
||||
else { return false; }
|
||||
}
|
||||
|
||||
// Scale the fraction to the unit without ever touching a double.
|
||||
int64_t scale = 1;
|
||||
for (int i = 0; i < fracDigits; i++) {
|
||||
if (scale > INT64_MAX / 10) { return false; }
|
||||
scale *= 10;
|
||||
}
|
||||
|
||||
if (whole > INT64_MAX / mult) { return false; }
|
||||
|
||||
int64_t hz = whole * mult + (frac * mult) / scale;
|
||||
|
||||
if (hz <= 0 || hz > 300000000000LL) { return false; } // 1 Hz .. 300 GHz
|
||||
|
||||
*outHz = hz;
|
||||
|
||||
return true;
|
||||
}
|
||||
|
||||
// ---------------------------------------------------------------------------
|
||||
// Frequency lookup
|
||||
// ---------------------------------------------------------------------------
|
||||
|
||||
static void normalise_country(const char* in, char* out, size_t cap) {
|
||||
size_t j = 0;
|
||||
|
||||
if (!in || !*in) {
|
||||
snprintf(out, cap, "US"); // default
|
||||
return;
|
||||
}
|
||||
|
||||
for (size_t i = 0; in[i] && j + 1 < cap; i++) {
|
||||
char c = in[i];
|
||||
|
||||
if (c >= 'a' && c <= 'z') { c = (char)(c - 'a' + 'A'); }
|
||||
if (c >= 'A' && c <= 'Z') { out[j++] = c; }
|
||||
}
|
||||
|
||||
out[j] = '\0';
|
||||
|
||||
if (!out[0]) { snprintf(out, cap, "US"); }
|
||||
}
|
||||
|
||||
static bool step_bool(sqlite3_stmt* st) {
|
||||
bool got = (sqlite3_step(st) == SQLITE_ROW);
|
||||
sqlite3_reset(st);
|
||||
|
||||
return got;
|
||||
}
|
||||
|
||||
bool hammy_refdb_freq(hammy_refdb_t* db, int64_t freqHz, const char* country,
|
||||
hammy_freq_t* out) {
|
||||
if (!db || !out || freqHz <= 0) { return false; }
|
||||
|
||||
memset(out, 0, sizeof(*out));
|
||||
out->freqHz = freqHz;
|
||||
normalise_country(country, out->country, sizeof(out->country));
|
||||
|
||||
// Does the bundle know this country at all? Only US privileges are seeded so
|
||||
// far, so this is the common path and the message matters.
|
||||
sqlite3_reset(db->stCountryKnown);
|
||||
sqlite3_clear_bindings(db->stCountryKnown);
|
||||
sqlite3_bind_text(db->stCountryKnown, 1, out->country, -1, SQLITE_TRANSIENT);
|
||||
out->countryKnown = step_bool(db->stCountryKnown);
|
||||
|
||||
// Band and privileges.
|
||||
sqlite3_stmt* st = db->stFreqMain;
|
||||
sqlite3_reset(st);
|
||||
sqlite3_clear_bindings(st);
|
||||
sqlite3_bind_int64(st, 1, freqHz);
|
||||
sqlite3_bind_text(st, 2, out->country, -1, SQLITE_TRANSIENT);
|
||||
|
||||
while (sqlite3_step(st) == SQLITE_ROW) {
|
||||
if (!out->inBand) {
|
||||
out->inBand = true;
|
||||
copy_text(st, 0, out->band, sizeof(out->band));
|
||||
out->bandLowHz = sqlite3_column_int64(st, 1);
|
||||
out->bandHighHz = sqlite3_column_int64(st, 2);
|
||||
out->atBandEdge = (freqHz == out->bandLowHz ||
|
||||
freqHz == out->bandHighHz);
|
||||
}
|
||||
|
||||
// A NULL class means the country has no licence classes at all; the row
|
||||
// still carried the band, which is why it is read above first.
|
||||
if (sqlite3_column_type(st, 3) == SQLITE_NULL) { continue; }
|
||||
if (out->nPrivs >= HAMMY_FREQ_PRIVS_MAX) { continue; }
|
||||
|
||||
hammy_freq_priv_t* p = &out->privs[out->nPrivs++];
|
||||
|
||||
copy_text(st, 3, p->code, sizeof(p->code));
|
||||
copy_text(st, 4, p->name, sizeof(p->name));
|
||||
p->rank = sqlite3_column_int(st, 5);
|
||||
|
||||
p->permitted = (sqlite3_column_type(st, 6) != SQLITE_NULL);
|
||||
|
||||
if (p->permitted) {
|
||||
copy_text(st, 6, p->modes, sizeof(p->modes));
|
||||
p->segLowHz = sqlite3_column_int64(st, 7);
|
||||
p->segHighHz = sqlite3_column_int64(st, 8);
|
||||
p->maxPowerW = sqlite3_column_int(st, 9); // 0 when NULL
|
||||
copy_text(st, 10, p->notes, sizeof(p->notes));
|
||||
}
|
||||
}
|
||||
|
||||
sqlite3_reset(st);
|
||||
|
||||
// Not in any band: report the nearest one so the user can see how far off
|
||||
// they are. Usually a typo - 15.000 instead of 14.150.
|
||||
if (!out->inBand) {
|
||||
st = db->stFreqNearest;
|
||||
sqlite3_reset(st);
|
||||
sqlite3_clear_bindings(st);
|
||||
sqlite3_bind_int64(st, 1, freqHz);
|
||||
|
||||
if (sqlite3_step(st) == SQLITE_ROW) {
|
||||
copy_text(st, 0, out->nearestBand, sizeof(out->nearestBand));
|
||||
out->nearestLowHz = sqlite3_column_int64(st, 1);
|
||||
out->nearestHighHz = sqlite3_column_int64(st, 2);
|
||||
out->nearestDistanceHz = sqlite3_column_int64(st, 3);
|
||||
}
|
||||
|
||||
sqlite3_reset(st);
|
||||
|
||||
return true;
|
||||
}
|
||||
|
||||
// IARU regional allocations. Independent of country, so worth showing even
|
||||
// when the privilege table is empty.
|
||||
st = db->stFreqIaru;
|
||||
sqlite3_reset(st);
|
||||
sqlite3_clear_bindings(st);
|
||||
sqlite3_bind_int64(st, 1, freqHz);
|
||||
|
||||
while (sqlite3_step(st) == SQLITE_ROW && out->nIaru < HAMMY_FREQ_IARU_MAX) {
|
||||
hammy_freq_iaru_t* r = &out->iaru[out->nIaru++];
|
||||
|
||||
r->region = sqlite3_column_int(st, 0);
|
||||
r->lowHz = sqlite3_column_int64(st, 1);
|
||||
r->highHz = sqlite3_column_int64(st, 2);
|
||||
copy_text(st, 3, r->modes, sizeof(r->modes));
|
||||
}
|
||||
|
||||
sqlite3_reset(st);
|
||||
|
||||
// Sitting exactly on a segment boundary is worth flagging: a signal of any
|
||||
// width centred there straddles both sides.
|
||||
st = db->stFreqSegEdge;
|
||||
sqlite3_reset(st);
|
||||
sqlite3_clear_bindings(st);
|
||||
sqlite3_bind_int64(st, 1, freqHz);
|
||||
sqlite3_bind_text(st, 2, out->country, -1, SQLITE_TRANSIENT);
|
||||
out->atSegmentEdge = step_bool(st);
|
||||
|
||||
return true;
|
||||
}
|
||||
|
||||
size_t hammy_refdb_countries(hammy_refdb_t* db,
|
||||
char out[][HAMMY_COUNTRY_MAX], size_t cap) {
|
||||
if (!db || !out || cap == 0) return 0;
|
||||
|
||||
size_t n = 0;
|
||||
|
||||
sqlite3_reset(db->stCountryList);
|
||||
|
||||
while (n < cap && sqlite3_step(db->stCountryList) == SQLITE_ROW) {
|
||||
copy_text(db->stCountryList, 0, out[n++], HAMMY_COUNTRY_MAX);
|
||||
}
|
||||
|
||||
sqlite3_reset(db->stCountryList);
|
||||
|
||||
return n;
|
||||
}
|
||||
|
||||
Vendored
+1122
-2
File diff suppressed because it is too large
Load Diff
Reference in New Issue
Block a user