-- Redisplay recent DX-Cluster Announcement spots from the current band, in a pop-up window -- Assign to: Shift-F12 -- -- Time-stamp: "5 November 2013 02:07 UTC" -- -- Script Argument = Specify range of frequencies to search (numeric, not string) -- 0.5 = Search +/- 0.5 kHz from current frequency -- 1 = Search +/- 1 kHz of current frequency. This is the default. -- 10 = Search +/- 10 kHz from current frequency -- 500 = Search +/- 500 kHz from current frequency (same band). -- -- Note: This script reads the LogFileName.dxc file. This file is only available -- if "Incoming spots logging" is enabled. -- -- To enable spots logging: -- 1. Press Alt-A to display the Dx-Cluster Announcements window -- 2. Right click on the window to dispay the pop-up menu -- 3. Enable "Incoming spots logging". -- -- Written by N6TV local rc = 0 local MAXLINES = 5000 -- We're going to limit ourselves to the last 500 lines -- in the spots log (all bands) local DEFAULT_LIMIT = 1 -- Limit spots to within 1 kHz of current VFO A frequency -- (e.g. same band) MB_OK = 0x00000000 -- Displays "OK" button in modal MessageBox MB_ICONSTOP = 0x00000010 -- Displays red "x" stop sign icon in MessageBox MB_ICONWARNING = 0x00000030 -- Displays yellow "!" warning sign in Messagebox MB_ICONINFORMATION = 0x00000040 -- Displays blue "i" information icon in MessageBox -- Function to sort a table t by value, using optional compare function f -- (Copied from LUA reference) local function pairsByValues (t, f) local a = {} for k, v in pairs(t) do table.insert(a, v) end table.sort(a, f) local i = 0 -- iterator variable local iter = function () -- iterator function i = i + 1 if a[i] == nil then return nil else return a[i], t[a[i]] end end return iter end -- Compare function to sort lines by time (first column of each line) -- in reverse order, newest first local function sortByRevTime(val1, val2) return val1.l > val2.l end -- Define which frequencies to search local limit = DEFAULT_LIMIT -- If +/- kHz limit specified as an argument to this script, use it as limit instead of the default if wtArg ~= nil then limit = wtArg end -- We need to open logFileName.dxc, but we don't know the log file name. Use -- the new WT 4.11 function if available. if dxcFileName == nil and not (wtApp.GetLogFileName == nil) then logFileName = wtApp:GetLogFileName() -- Replace .wt4 with .dxc to get logFileName.dxc dxcFileName = wtApp:GetLogPath() .. "\\" .. string.gsub(logFileName, "%.[Ww][tt]4", ".dxc") end -- If .dxc file name not determined and saved (in global) yet, or wtApp:GetLogFileName -- not available, try scanning WT.INI for LastFileName=something.wt4 if dxcFileName == nil then local errmsg = "" local iniFile, errmsg = io.open(wtApp:GetCfgPath() .. "\\wt.ini", "r") -- If wt.ini file could not be opened if iniFile == nil then wtApp:MessageBox("Could not open WT.INI file\n" .. errmsg, MB_ICONWARNING, wtCurrentScript) return -1 end local iniData = iniFile:read("*all") iniFile:close() local logFileName = "" -- Find LastFileName=(something).wt4, where something is all alphanumeric characters and -- all punctuation -- local pat = "(%S+)=([%w%p]+)%.[Ww][Tt]4" local pat = "LastFileName=([%w%p]+)%.[Ww][Tt]4" for value in string.gfind(iniData, pat) do logFileName = value break end if logFileName == nil or logFileName == "" then -- No further keystroke processing rc = -1 else -- Prepend log file path and append ".dxc" to create the fully-qualified name of the Alt-A -- (DX Cluster Announcements) spot logging file dxcFileName = wtApp:GetLogPath() .. "\\" .. logFileName .. ".dxc" end end local byCall = {} -- Key = call, value = last spot line, count of spots for -- same call -- If we got the log file OK if rc == 0 then -- Open logfilename.dxc for reading, save error message if open fails dxcFile, errmsg = io.open(dxcFileName, "r") -- If .dxc file could not be opened if dxcFile == nil then wtApp:MessageBox("Could yyyyyy not open DX Cluster log file\n" .. errmsg, MB_ICONWARNING, wtCurrentScript) dxcFileName = nil rc = -1 else -- Move to end of the spot log file, then back up about MAXLINES lines -- (134-chacters/line max) dxcFile:seek("end", -MAXLINES * 134) -- Skip the first partial line dxcFile:read() -- Get active radio's current frequency local freqRadio = wtRadio:GetFreq() -- Parse the spot file lines for line in dxcFile:lines() do -- The lines should look like this: -- 2012/11/28 00:42:07 W8WTS-# 1818.0 0.0 CW C6AUM - 0042z "CW 02 dB 29 WPM CQ x" -- Where the last item "x" may be -- V = Good call (valid) -- B = Busted call -- Q = Good Call, New freq? (QSY in progress) -- ? = ?Spot (Unknown) -- Separate line into words of interest. Non-space characters (%S) are separated -- by white space characters (%s) local _, _, _, _, skimmer, freq, qsx, _, call, _, time, comments = string.find(line, '(%S+)%s+(%S+)%s+(%S+)%s+(%S+)%s+(%S+)%s+(%S+)%s+(%S+)%s+(%S+)%s+' .. '(%S+)%s+"(.+)"') -- If all required fields found if freq ~= nil and qsx ~= nil and call ~= nil and time ~= nil and skimmer ~= nil then -- Convert the spot and QSX frequencies from strings to numbers local freqSpot = tonumber(freq) local freqQSX = tonumber(qsx) -- Get the current frequency of the active radio local spacer = "" -- If we got both frequencies successfully if freqSpot ~= nil and freqRadio ~= nil then -- Calculate difference (in kHz) between radio's frequency and spot frequency freqDiff = math.abs(freqSpot - tonumber(freqRadio)) -- List anything within LIMIT kHz of current VFO A frequency if freqDiff <= limit then -- Insert a "#" before Skimmer spots, like Win-Test does in the Alt-A window -- CT1BOH if string.sub(skimmer,-1) == "#" then -- CT1BOH call = "#" .. call -- CT1BOH end -- Insert a "*" before QSX spots, like Win-Test does if freqQSX ~= 0 then call = "*" .. call end -- To fix alignment, insert two tabs if callsign length is less than 7 chars, -- otherwise just one tab with some leading white space (doesn't always work -- due to use of proportional fonts in Windows dialog boxes) if string.len(call) < 7 then spacer = "\t\t" else spacer = " \t" end -- Parse the comment string into words -- Could be in one of the following formats: -- 1. CW 08 dB 16 WPM CQ V -- 2. CW 19 dB 16 WPM CQ (SP9HZX) B. -- 3. CQ CQ QRS (anything really) if comments == nil then validFlag = " " else local startpos, endpos, _, _, _, _, _, _, lastWord = string.find(comments, '(%S+)%s+(%S+)%s+(%S+)%s+(%S+)%s+(%S+)%s+(%S+)%s+(%S+)+(%S+)') if startpos == nil then validFlag = " " else validFlag = lastWord end end -- Build output line --local line = freq .. "\t" .. call .. spacer .. validFlag .. " " .. time .. "\t" local line = time.." "..freq .. " ".. call.."\t"..validFlag.." " .. "\t" local key = call -- If key value (call) not in table yet if byCall[key] == nil then byCall[key] = { l = line; c = 0 } else -- Duplicate call, increment counter byCall[key] = { l = line; c = byCall[key].c + 1 } end end end end end local lines = "" -- Append +n counter for duplicate callsign spots for value in pairsByValues(byCall,sortByRevTime) do lines = lines .. value.l if value.c > 0 then lines = lines .. "\t+" .. tostring(value.c) end lines = lines .. "\n" end -- If we found no spots within LIMIT kHz of active radio if lines == "" then wtApp:MessageBox("No recent spots found within " .. limit .. " kHz of " .. freqRadio .. " in spots file\n\n" .. "(Right click in Alt-A window, check \"Incoming spots logging\")\n", MB_ICONINFORMATION, wtCurrentScript) else -- Display the matching recent spots wtApp:MessageBox(lines, MB_OK, wtCurrentScript) end -- Close the spot logging file dxcFile:close() end -- No further keystroke processing rc = -1 end return rc