@@ 1,5 1,5 @@
--
--- Copyright (c) 2022 Josef 'Jeff' Sipek <jeffpc@josefsipek.net>
+-- Copyright (c) 2022,2024-2025 Josef 'Jeff' Sipek <jeffpc@josefsipek.net>
--
-- Permission is hereby granted, free of charge, to any person obtaining a copy
-- of this software and associated documentation files (the "Software"), to deal
@@ 20,6 20,9 @@
-- SOFTWARE.
--
+local MIN_FREQ = 50000000 -- Hz; lowest allowed freq in contest
+local MAX_FREQ_STEPS = 1300000000 -- Hz; highest freq which doesn't get max qso points
+
local BAND_POINTS = {
["ARRL-VHF-JAN"] = {
[6000] = 1,
@@ 28,7 31,7 @@ local BAND_POINTS = {
[700] = 2,
[330] = 4,
[230] = 4,
- -- TODO: 8 points for even higher bands
+ ["HIGHER"] = 8,
},
["ARRL-VHF-JUN"] = {
[6000] = 1,
@@ 37,7 40,7 @@ local BAND_POINTS = {
[700] = 2,
[330] = 3,
[230] = 3,
- -- TODO: 4 points for even higher bands
+ ["HIGHER"] = 4,
},
["ARRL-VHF-SEP"] = {
[6000] = 1,
@@ 46,13 49,10 @@ local BAND_POINTS = {
[700] = 2,
[330] = 3,
[230] = 3,
- -- TODO: 4 points for even higher bands
+ ["HIGHER"] = 4,
},
}
-local dup_table = {}
-local mults = {}
-
local function mkgrid(g)
if g == nil then
return ""
@@ 61,49 61,16 @@ local function mkgrid(g)
return g:sub(1,4)
end
-local function hash(mgrid, tgrid)
- return string.format("%s-%s", mkgrid(mgrid), mkgrid(tgrid))
+local function duphash(qso, tx, rx)
+ return string.format("%s-%s-%s-%s",
+ qso.rx.station_call,
+ qso.tx.band,
+ mkgrid(tx),
+ mkgrid(rx))
end
-local function check(band, who, mgrid, tgrid)
- local where = hash(mgrid, tgrid)
- local info = dup_table[band][who]
-
- if info == nil then
- return 1 -- callsign not seen before on this band ==> not dup
- end
-
- if info[where] then
- return 3 -- exact match ==> dup
- end
-
- for cur, _ in pairs(info) do
- if cur:find(where, 1, true) == 1 then
- return 2 -- where is a prefix of cur ==> maybe dup
- end
- end
-
- return 1 -- no prefix found ==> not dup
-end
-
-local function qso_done(qso)
- if not BAND_POINTS[contest.id][qso.tx.band] then
- return -- ignore out-of-band contacts
- end
-
- qso.additional['contest-id'] = contest.id
- qso.additional['contest-year'] = contest.year
-
- local where = hash(qso.tx.grid, qso.rx.grid)
- local mult = string.format("%u-%s", qso.tx.band, mkgrid(qso.rx.grid))
-
- if dup_table[qso.tx.band][qso.rx.station_call] == nil then
- dup_table[qso.tx.band][qso.rx.station_call] = {}
- end
- dup_table[qso.tx.band][qso.rx.station_call][where] = true
-
- mults[mult] = true
-end
+-- The object keeping all the state
+local vhf
--
-- Annunciator color
@@ 111,47 78,20 @@ end
-- There are three colors we can display: "not a dup", "maybe a dup", and
-- "definitely a dup". When we have full information (i.e., callsign and
-- grid), it is easy to check and return either "not a dup" or "definitely a
--- dup". Things get more complicated when we don't have all the info. In
--- general, we try to do a prefix match with the entered grid square. This
--- allows us to indicate whether or not there is some chance of a dup.
-local function is_dup(qso, band, color_none, color_warn, color_dup)
- local colors = { color_none, color_warn, color_dup }
- local who = qso.rx.station_call
- local where = qso.rx.grid
-
- --
- -- Out of band contacts cannot be dups. Instead of using
- -- color_none, we simply hide them.
- --
- if not BAND_POINTS[contest.id][qso.tx.band] then
- return "black"
- end
-
+-- dup". When we don't have full information (i.e., 4-digit grid), we just
+-- warn that it may be a duplicate.
+local function is_dup(qso, color_none, color_warn, color_dup)
--
-- complete information
--
- if where ~= nil and where:len() >= 4 then
- local colors = { color_none, nil, color_dup }
-
- return colors[check(band, who, qso.tx.grid, where)]
+ if qso.rx.grid ~= nil and qso.rx.grid:len() >= 4 then
+ return vhf:is_dup(qso) and color_dup or color_none
end
--
-- incomplete information
--
- if where ~= nil then
- return colors[check(band, who, qso.tx.grid, where)]
- end
-
- return colors[check(band, who, qso.tx.grid, "")]
-end
-
-local function is_oob(qso)
- if BAND_POINTS[contest.id][qso.tx.band] then
- return "black"
- else
- return "red"
- end
+ return color_warn
end
local function field_changed(qso, fields, field)
@@ 168,15 108,67 @@ local function field_changed(qso, fields
end
return nil, {
- ["2m"] = is_dup(qso, 2000, "green", "magenta", "off"),
- ["6m"] = is_dup(qso, 6000, "green", "magenta", "off"),
- ["70cm"] = is_dup(qso, 700, "green", "magenta", "off"),
- ["23cm"] = is_dup(qso, 230, "green", "magenta", "off"),
- ["DUP"] = is_dup(qso, qso.tx.band, "black", "black", "red"),
- ["OOB"] = is_oob(qso),
+ ["DUP"] = is_dup(qso, "black", "black", "red"),
+ ["OOB"] = qso.tx.freq >= MIN_FREQ and "black" or "red",
}
end
+local function qexchange(side, ex)
+ if ex == nil then
+ return "????", false
+ else
+ return mkgrid(ex), true
+ end
+end
+
+local function qpoints(qso, tx, rx)
+ if qso.tx.freq <= MAX_FREQ_STEPS then
+ return BAND_POINTS[contest.id][qso.tx.band]
+ else
+ return BAND_POINTS[contest.id]["HIGHER"]
+ end
+end
+
+local function qmult(qso, tx, rx)
+ return string.format("%u-%s", qso.tx.band, mkgrid(rx))
+end
+
+local function export(outfname)
+ local all
+ local station
+ local ops
+ local points
+ local mults
+
+ all, station, ops, points, mults = vhf:export_prep(contest.id, contest.year,
+ qpoints, qmult, qexchange)
+
+ if station:sub(-2) == "/R" then
+ -- rovers need a +1 mult for each grid visited
+ for _, rec in ipairs(all) do
+ local qso = rec[1]
+
+ mults:add(string.format("visited-%s", mkgrid(qso.tx.grid)))
+ end
+ end
+
+ hlog.cabrillo.print_header(contest.id,
+ station,
+ ops:as_array(),
+ points * mults:count_items())
+
+ for _, rec in ipairs(all) do
+ local qso = rec[1]
+ local tx = rec[2]
+ local rx = rec[3]
+ local xqso = not rec[4]
+
+ hlog.cabrillo.print_qso(qso, tx, rx, xqso)
+ end
+
+ hlog.cabrillo.print_footer()
+end
+
return {
labels = {
{ 0, 0, "St. Call" },
@@ 193,23 185,30 @@ return {
annunciators = {
{ 0, 31, "OOB", "black" },
{ 0, 35, "DUP", "black" },
- { 0, 39, "6m", "black" },
- { 0, 42, "2m", "black" },
- { 0, 45, "70cm", "black" },
- { 0, 50, "23cm", "black" },
- -- TODO: more band indicators?
},
events = {
+ export = export,
startup = function()
- for band, _ in pairs(BAND_POINTS[contest.id]) do
- dup_table[band] = {}
+ vhf = hlog.contest_helper.grid(contest.id, contest.year, duphash)
+
+ -- load previous contacts from history
+ for qso in hlog.index.history(true) do
+ if qso.additional['contest-id'] == contest.id and
+ qso.additional['contest-year'] == contest.year and
+ qso.tx.freq >= MIN_FREQ then
+ vhf:qso_load(qso)
+ end
end
end,
qso_init = function(qso)
contest.lcd(mkgrid(qso.tx.grid))
end,
- qso_done = qso_done,
+ qso_done = function(qso)
+ if qso.tx.freq >= MIN_FREQ then
+ vhf:qso_done(qso)
+ end
+ end,
field_changed = field_changed,
},
}