diff --git a/DXClusterClient.pas b/DXClusterClient.pas index b9aac1d..a2d6fcc 100644 --- a/DXClusterClient.pas +++ b/DXClusterClient.pas @@ -263,6 +263,15 @@ begin Spot.Comment := Trim(Rest); Spot.Mode := DXModeFromComment(Spot.Comment); + // Комментарий молчит — берём моду с участка бэндплана: в CW и SSB её сплошь + // и рядом просто не пишут, а участок эти две моды разделяет надёжно. + // Помечаем такой спот: угаданное не должно выглядеть как сказанное спотером. + Spot.ModeGuessed := False; + if Spot.Mode = dxmUnknown then + begin + Spot.Mode := DXModeFromFreq(Spot.FreqHz); + Spot.ModeGuessed := Spot.Mode <> dxmUnknown; + end; Spot.Stamp := Now; Result := True; end; diff --git a/DXClusterForm.pas b/DXClusterForm.pas index f6f427b..55ec7b7 100644 --- a/DXClusterForm.pas +++ b/DXClusterForm.pas @@ -260,8 +260,11 @@ var begin FreqStr := FormatFloat('0.0', S.FreqHz / 1000.0); // кГц, как в кластере while Length(FreqStr) < 10 do FreqStr := ' ' + FreqStr; + // Мода, выведенная из бэндплана, помечена '?': спотер её не называл, и + // видеть разницу полезно — на границах участков таблица может ошибаться. Md := DXModeName(S.Mode); - while Length(Md) < 4 do Md := Md + ' '; + if (Md <> '') and S.ModeGuessed then Md := Md + '?'; + while Length(Md) < 5 do Md := Md + ' '; Result := Format('%s %-12s %s %-5s %-10s %s', [FreqStr, S.Call, Md, S.TimeUTC, S.Spotter, S.Comment]); end; diff --git a/DXSpotStore.pas b/DXSpotStore.pas index 9a389ff..33d5079 100644 --- a/DXSpotStore.pas +++ b/DXSpotStore.pas @@ -48,6 +48,7 @@ type TimeUTC: string; // 'HHMM' как пришло от кластера Stamp: TDateTime; // локальное время приёма — для TTL и затухания Mode: TDXMode; + ModeGuessed: Boolean; // мода не из комментария, а из бэндплана (см. ниже) end; TDXSpotArray = array of TDXSpot; @@ -94,6 +95,10 @@ type // Мода по комментарию кластера ('FT8 -12 dB' → dxmFT8). Пусто/непонятно — // dxmUnknown. function DXModeFromComment(const Comment: string): TDXMode; +// Мода по участку бэндплана — запасной путь, когда комментарий молчит (в CW и +// SSB его сплошь и рядом просто не пишут). dxmUnknown = участок спорный или +// вне таблицы; см. DX_BAND_PLAN о том, где мы намеренно не гадаем. +function DXModeFromFreq(FreqHz: Double): TDXMode; function DXModeName(M: TDXMode): string; implementation @@ -145,6 +150,114 @@ begin Result := dxmDigi; end; +{ ── Мода по бэндплану ────────────────────────────────────────────────────── } + +type + TDXBandSeg = record + LoKHz, HiKHz: Double; // [Lo; Hi) + Mode: TDXMode; + end; + +const + // Участки бэндплана для запасного определения моды. Читается СВЕРХУ ВНИЗ, + // первое попадание выигрывает — поэтому узкие «водопои» цифры стоят раньше + // широких сегментов, внутрь которых они попадают (FT8 на 7074 живёт посреди + // телефонного участка R1, FT4 на 21140 — посреди 15-метрового). + // + // ★ Границы — по плану IARU Region 1 (наш регион): у R2/R3 они другие, и + // спорные куски мы НАМЕРЕННО оставляем пустыми, а не гадаем — пропуск честнее + // ошибки. Отсюда дырки: 1843-1850 (R1 SSB против R2 CW), 7053-7125 у R2 — + // данные, а не телефон, и т.п. Мода из комментария всегда важнее этой + // таблицы, сюда попадают только споты, где комментарий промолчал. + DX_BAND_PLAN: array[0..47] of TDXBandSeg = ( + // ── узкие окна FT8/FT4 (перекрывают широкие сегменты ниже) ── + (LoKHz: 1840.0; HiKHz: 1843.0; Mode: dxmFT8), + (LoKHz: 3573.0; HiKHz: 3576.0; Mode: dxmFT8), + (LoKHz: 7047.0; HiKHz: 7049.0; Mode: dxmFT4), + (LoKHz: 7074.0; HiKHz: 7078.0; Mode: dxmFT8), + (LoKHz: 10136.0; HiKHz: 10139.0; Mode: dxmFT8), + (LoKHz: 10140.0; HiKHz: 10142.0; Mode: dxmFT4), + (LoKHz: 14074.0; HiKHz: 14078.0; Mode: dxmFT8), + (LoKHz: 14080.0; HiKHz: 14082.0; Mode: dxmFT4), + (LoKHz: 18100.0; HiKHz: 18102.0; Mode: dxmFT8), + (LoKHz: 18104.0; HiKHz: 18106.0; Mode: dxmFT4), + (LoKHz: 21074.0; HiKHz: 21078.0; Mode: dxmFT8), + (LoKHz: 21140.0; HiKHz: 21142.0; Mode: dxmFT4), + (LoKHz: 24915.0; HiKHz: 24917.0; Mode: dxmFT8), + (LoKHz: 24919.0; HiKHz: 24921.0; Mode: dxmFT4), + (LoKHz: 28074.0; HiKHz: 28078.0; Mode: dxmFT8), + (LoKHz: 28180.0; HiKHz: 28182.0; Mode: dxmFT4), + (LoKHz: 50313.0; HiKHz: 50323.0; Mode: dxmFT8), + (LoKHz: 144174.0; HiKHz: 144180.0; Mode: dxmFT8), + (LoKHz: 432174.0; HiKHz: 432180.0; Mode: dxmFT8), + + // ── КВ, широкие сегменты ── + (LoKHz: 1800.0; HiKHz: 1838.0; Mode: dxmCW), + (LoKHz: 1838.0; HiKHz: 1843.0; Mode: dxmDigi), + (LoKHz: 1850.0; HiKHz: 2000.0; Mode: dxmSSB), + + (LoKHz: 3500.0; HiKHz: 3570.0; Mode: dxmCW), + (LoKHz: 3570.0; HiKHz: 3600.0; Mode: dxmDigi), + (LoKHz: 3600.0; HiKHz: 4000.0; Mode: dxmSSB), + + (LoKHz: 7000.0; HiKHz: 7040.0; Mode: dxmCW), + (LoKHz: 7040.0; HiKHz: 7053.0; Mode: dxmDigi), + (LoKHz: 7053.0; HiKHz: 7300.0; Mode: dxmSSB), + + (LoKHz: 10100.0; HiKHz: 10130.0; Mode: dxmCW), + (LoKHz: 10130.0; HiKHz: 10150.0; Mode: dxmDigi), + + (LoKHz: 14000.0; HiKHz: 14070.0; Mode: dxmCW), + (LoKHz: 14070.0; HiKHz: 14099.0; Mode: dxmDigi), + (LoKHz: 14101.0; HiKHz: 14350.0; Mode: dxmSSB), + + (LoKHz: 18068.0; HiKHz: 18095.0; Mode: dxmCW), + (LoKHz: 18095.0; HiKHz: 18109.0; Mode: dxmDigi), + (LoKHz: 18111.0; HiKHz: 18168.0; Mode: dxmSSB), + + (LoKHz: 21000.0; HiKHz: 21070.0; Mode: dxmCW), + (LoKHz: 21070.0; HiKHz: 21120.0; Mode: dxmDigi), + (LoKHz: 21151.0; HiKHz: 21450.0; Mode: dxmSSB), + + (LoKHz: 24890.0; HiKHz: 24915.0; Mode: dxmCW), + (LoKHz: 24915.0; HiKHz: 24929.0; Mode: dxmDigi), + (LoKHz: 24931.0; HiKHz: 24990.0; Mode: dxmSSB), + + (LoKHz: 28000.0; HiKHz: 28070.0; Mode: dxmCW), + (LoKHz: 28070.0; HiKHz: 28190.0; Mode: dxmDigi), + (LoKHz: 28300.0; HiKHz: 29100.0; Mode: dxmSSB), + (LoKHz: 29100.0; HiKHz: 29700.0; Mode: dxmFM), + + // ── УКВ (минимум, где регионы согласны) ── + (LoKHz: 50000.0; HiKHz: 50100.0; Mode: dxmCW), + (LoKHz: 50100.0; HiKHz: 50500.0; Mode: dxmSSB)); + +function DXModeFromFreq(FreqHz: Double): TDXMode; +var + K: Double; + i: Integer; +begin + Result := dxmUnknown; + if FreqHz <= 0 then Exit; + + // QO-100 (узкополосный транспондер, DOWNLINK). Отдельно от КВ-таблицы: план + // AMSAT-DL, границы те же, что рисует BandPlanOverlay. Маяки (…500-505 и + // …745-755) и «mixed modes» (…850-990) — не гадаем. + K := FreqHz / 1000.0; + if (K >= 10489500.0) and (K < 10490000.0) then + begin + if (K >= 10489505.0) and (K < 10489540.0) then Result := dxmCW + else if (K >= 10489540.0) and (K < 10489650.0) then Result := dxmDigi + else if (K >= 10489650.0) and (K < 10489745.0) then Result := dxmSSB + else if (K >= 10489755.0) and (K < 10489850.0) then Result := dxmSSB; + Exit; + end; + + for i := Low(DX_BAND_PLAN) to High(DX_BAND_PLAN) do + if (K >= DX_BAND_PLAN[i].LoKHz) and (K < DX_BAND_PLAN[i].HiKHz) then + Exit(DX_BAND_PLAN[i].Mode); +end; + { ── TDXSpotStore ─────────────────────────────────────────────────────────── } constructor TDXSpotStore.Create;