mirror of
https://github.com/dcs-retribution/dcs-retribution.git
synced 2025-11-10 15:41:24 +00:00
Add modulation to RadioFrequency
- This adds the information about the modulation of the RadioFrequency. - Updated all Radios with the capabale modulation - Show Modulation on Kneeboard - Defaulting to AM Modulation as this is also the default used by pydcs. - Force AM Modulation for JTAC tasking We currently do not force the modulation in the code anywhere other than JTAC. Pydcs defaults to AM (modulation=0). So this change is more a preparation for upcoming features which allow to use more frequencies like VHF FM or similar.
This commit is contained in:
parent
aae314ae1d
commit
69a5b4f227
@ -12,6 +12,7 @@ from typing import Any, ClassVar, Dict, Optional, TYPE_CHECKING, Tuple
|
||||
|
||||
import yaml
|
||||
from dcs.terrain import Airport
|
||||
from dcs.task import Modulation
|
||||
|
||||
from game.radio.radios import RadioFrequency
|
||||
from game.radio.tacan import TacanChannel
|
||||
@ -33,10 +34,10 @@ class AtcData:
|
||||
if atc_data is None:
|
||||
return None
|
||||
return AtcData(
|
||||
RadioFrequency.parse(atc_data["hf"]),
|
||||
RadioFrequency.parse(atc_data["vhf_low"]),
|
||||
RadioFrequency.parse(atc_data["vhf_high"]),
|
||||
RadioFrequency.parse(atc_data["uhf"]),
|
||||
RadioFrequency.parse(atc_data["hf"], Modulation.FM),
|
||||
RadioFrequency.parse(atc_data["vhf_low"], Modulation.FM),
|
||||
RadioFrequency.parse(atc_data["vhf_high"], Modulation.AM),
|
||||
RadioFrequency.parse(atc_data["uhf"], Modulation.AM),
|
||||
)
|
||||
|
||||
|
||||
@ -108,7 +109,10 @@ class AirfieldData:
|
||||
|
||||
vor = None
|
||||
if (vor_data := data.get("vor")) is not None:
|
||||
vor = (vor_data["callsign"], RadioFrequency.parse(vor_data["frequency"]))
|
||||
vor = (
|
||||
vor_data["callsign"],
|
||||
RadioFrequency.parse(vor_data["frequency"], Modulation.FM),
|
||||
)
|
||||
|
||||
rsbn = None
|
||||
if (rsbn_data := data.get("rsbn")) is not None:
|
||||
@ -122,7 +126,7 @@ class AirfieldData:
|
||||
if (ils_data := runway_data.get("ils")) is not None:
|
||||
ils[name] = (
|
||||
ils_data["callsign"],
|
||||
RadioFrequency.parse(ils_data["frequency"]),
|
||||
RadioFrequency.parse(ils_data["frequency"], Modulation.FM),
|
||||
)
|
||||
|
||||
if (prmg_data := runway_data.get("prmg")) is not None:
|
||||
@ -131,13 +135,13 @@ class AirfieldData:
|
||||
if (outer_ndb_data := runway_data.get("outer_ndb")) is not None:
|
||||
outer_ndb[name] = (
|
||||
outer_ndb_data["callsign"],
|
||||
RadioFrequency.parse(outer_ndb_data["frequency"]),
|
||||
RadioFrequency.parse(outer_ndb_data["frequency"], Modulation.AM),
|
||||
)
|
||||
|
||||
if (inner_ndb_data := runway_data.get("inner_ndb")) is not None:
|
||||
inner_ndb[name] = (
|
||||
inner_ndb_data["callsign"],
|
||||
RadioFrequency.parse(inner_ndb_data["frequency"]),
|
||||
RadioFrequency.parse(inner_ndb_data["frequency"], Modulation.AM),
|
||||
)
|
||||
|
||||
return AirfieldData(
|
||||
|
||||
@ -165,7 +165,11 @@ class FlotGenerator:
|
||||
maintask=AFAC,
|
||||
)
|
||||
jtac.points[0].tasks.append(
|
||||
FAC(callsign=len(self.air_support.jtacs) + 1, frequency=int(freq.mhz))
|
||||
FAC(
|
||||
callsign=len(self.air_support.jtacs) + 1,
|
||||
frequency=int(freq.mhz),
|
||||
modulation=freq.modulation,
|
||||
)
|
||||
)
|
||||
jtac.points[0].tasks.append(SetInvisibleCommand(True))
|
||||
jtac.points[0].tasks.append(SetImmortalCommand(True))
|
||||
|
||||
@ -6,18 +6,24 @@ import logging
|
||||
import re
|
||||
from dataclasses import dataclass
|
||||
from typing import Dict, FrozenSet, Iterator, List, Set, Tuple
|
||||
from dcs.task import Modulation
|
||||
|
||||
|
||||
@dataclass(frozen=True)
|
||||
class RadioFrequency:
|
||||
"""A radio frequency.
|
||||
|
||||
Not currently concerned with tracking modulation, just the frequency.
|
||||
"""
|
||||
"""A radio frequency and the modulation used"""
|
||||
|
||||
#: The frequency in kilohertz.
|
||||
hertz: int
|
||||
|
||||
#: The frequency modulation (AM or FM)
|
||||
# Pydcs defaults currently to modultion=0 which is equal to AM
|
||||
# Modulation is currently only used to tell the User the Modulation via the
|
||||
# kneeboard. We do not force any modulation from pydcs. We just set the
|
||||
# frequency with the set_frequency function which does not allow to set the
|
||||
# modulation yet. It defaults to modulation=0 which is equal to forcing AM
|
||||
modulation: Modulation = Modulation.AM
|
||||
|
||||
def __str__(self) -> str:
|
||||
if self.hertz >= 1000000:
|
||||
return self.format("MHz", 1000000)
|
||||
@ -26,8 +32,8 @@ class RadioFrequency:
|
||||
def format(self, units: str, divisor: int) -> str:
|
||||
converted = self.hertz / divisor
|
||||
if converted.is_integer():
|
||||
return f"{int(converted)} {units}"
|
||||
return f"{converted:0.3f} {units}"
|
||||
return f"{int(converted)} {units} {self.modulation.name}"
|
||||
return f"{converted:0.3f} {units} {self.modulation.name}"
|
||||
|
||||
@property
|
||||
def mhz(self) -> float:
|
||||
@ -39,7 +45,7 @@ class RadioFrequency:
|
||||
return self.hertz / 1000000
|
||||
|
||||
@classmethod
|
||||
def parse(cls, text: str) -> RadioFrequency:
|
||||
def parse(cls, text: str, modulation: Modulation = Modulation.AM) -> RadioFrequency:
|
||||
match = re.match(r"""^(\d+)(?:\.(\d{1,3}))? (MHz|kHz)$""", text)
|
||||
if match is None:
|
||||
raise ValueError(f"Could not parse radio frequency from {text}")
|
||||
@ -57,18 +63,22 @@ class RadioFrequency:
|
||||
partial *= 10
|
||||
|
||||
if units == "MHz":
|
||||
return MHz(whole, partial)
|
||||
return MHz(whole, partial, modulation)
|
||||
if units == "kHz":
|
||||
return kHz(whole, partial)
|
||||
return kHz(whole, partial, modulation)
|
||||
raise ValueError(f"Unexpected units in radio frequency: {units}")
|
||||
|
||||
|
||||
def MHz(num: int, khz: int = 0) -> RadioFrequency:
|
||||
return RadioFrequency(num * 1000000 + khz * 1000)
|
||||
def MHz(
|
||||
num: int, khz: int = 0, modulation: Modulation = Modulation.AM
|
||||
) -> RadioFrequency:
|
||||
return RadioFrequency(num * 1000000 + khz * 1000, modulation)
|
||||
|
||||
|
||||
def kHz(num: int, hz: int = 0) -> RadioFrequency:
|
||||
return RadioFrequency(num * 1000 + hz)
|
||||
def kHz(
|
||||
num: int, hz: int = 0, modulation: Modulation = Modulation.AM
|
||||
) -> RadioFrequency:
|
||||
return RadioFrequency(num * 1000 + hz, modulation)
|
||||
|
||||
|
||||
@dataclass(frozen=True)
|
||||
@ -84,25 +94,29 @@ class RadioRange:
|
||||
#: The spacing between adjacent frequencies.
|
||||
step: RadioFrequency
|
||||
|
||||
#: Modulation, AM or FM. Defaulting to AM as it is more used for comms
|
||||
# Overrides the modulation setting of the min and max frequency for the whole range
|
||||
modulation: Modulation = Modulation.AM
|
||||
|
||||
#: Specific frequencies to exclude. (e.g. Guard channels)
|
||||
excludes: FrozenSet[RadioFrequency] = frozenset()
|
||||
|
||||
def range(self) -> Iterator[RadioFrequency]:
|
||||
"""Returns an iterator over the usable frequencies of this radio."""
|
||||
return (
|
||||
RadioFrequency(x)
|
||||
RadioFrequency(x, self.modulation)
|
||||
for x in range(self.minimum.hertz, self.maximum.hertz, self.step.hertz)
|
||||
if RadioFrequency(x) not in self.excludes
|
||||
if RadioFrequency(x, self.modulation) not in self.excludes
|
||||
)
|
||||
|
||||
@property
|
||||
def last_channel(self) -> RadioFrequency:
|
||||
return next(
|
||||
RadioFrequency(x)
|
||||
RadioFrequency(x, self.modulation)
|
||||
for x in reversed(
|
||||
range(self.minimum.hertz, self.maximum.hertz, self.step.hertz)
|
||||
)
|
||||
if RadioFrequency(x) not in self.excludes
|
||||
if RadioFrequency(x, self.modulation) not in self.excludes
|
||||
)
|
||||
|
||||
|
||||
@ -141,58 +155,88 @@ class ChannelInUseError(RuntimeError):
|
||||
# TODO: Figure out appropriate steps for each radio. These are just guesses.
|
||||
#: List of all known radios used by aircraft in the game.
|
||||
RADIOS: List[Radio] = [
|
||||
Radio("AN/ARC-164", (RadioRange(MHz(225), MHz(400), step=MHz(1)),)),
|
||||
Radio("AN/ARC-186(V) AM", (RadioRange(MHz(116), MHz(152), step=MHz(1)),)),
|
||||
Radio("AN/ARC-186(V) FM", (RadioRange(MHz(30), MHz(76), step=MHz(1)),)),
|
||||
Radio("AN/ARC-164", (RadioRange(MHz(225), MHz(400), MHz(1), Modulation.AM),)),
|
||||
Radio("AN/ARC-186(V) AM", (RadioRange(MHz(116), MHz(152), MHz(1), Modulation.AM),)),
|
||||
Radio("AN/ARC-186(V) FM", (RadioRange(MHz(30), MHz(76), MHz(1), Modulation.FM),)),
|
||||
Radio(
|
||||
"AN/ARC-210",
|
||||
(
|
||||
RadioRange(MHz(225), MHz(400), MHz(1), frozenset((MHz(243),))),
|
||||
RadioRange(MHz(136), MHz(155), MHz(1)),
|
||||
RadioRange(MHz(156), MHz(174), MHz(1)),
|
||||
RadioRange(MHz(118), MHz(136), MHz(1)),
|
||||
RadioRange(MHz(30), MHz(88), MHz(1)),
|
||||
RadioRange(
|
||||
MHz(225),
|
||||
MHz(400),
|
||||
MHz(1),
|
||||
Modulation.AM,
|
||||
frozenset((MHz(243),)),
|
||||
),
|
||||
RadioRange(MHz(136), MHz(155), MHz(1), Modulation.AM),
|
||||
RadioRange(MHz(156), MHz(174), MHz(1), Modulation.FM),
|
||||
RadioRange(MHz(118), MHz(136), MHz(1), Modulation.AM),
|
||||
RadioRange(MHz(30), MHz(88), MHz(1), Modulation.FM),
|
||||
# The AN/ARC-210 can also use 225-400 and 136-155 with FM Modulation
|
||||
RadioRange(
|
||||
MHz(225),
|
||||
MHz(400),
|
||||
MHz(1),
|
||||
Modulation.FM,
|
||||
frozenset((MHz(243),)),
|
||||
),
|
||||
RadioRange(MHz(136), MHz(155), MHz(1), Modulation.FM),
|
||||
),
|
||||
),
|
||||
Radio("AN/ARC-222", (RadioRange(MHz(116), MHz(152), step=MHz(1)),)),
|
||||
Radio("SCR-522", (RadioRange(MHz(100), MHz(156), step=MHz(1)),)),
|
||||
Radio("A.R.I. 1063", (RadioRange(MHz(100), MHz(156), step=MHz(1)),)),
|
||||
Radio("BC-1206", (RadioRange(kHz(200), kHz(400), step=kHz(10)),)),
|
||||
# Note: The M2000C V/UHF can operate in both ranges, but has a gap between
|
||||
# 150 MHz and 225 MHz. We can't allocate in that gap, and the current
|
||||
# system doesn't model gaps, so just pretend it ends at 150 MHz for now. We
|
||||
# can model gaps later if needed.
|
||||
Radio("TRT ERA 7000 V/UHF", (RadioRange(MHz(118), MHz(150), step=MHz(1)),)),
|
||||
Radio("TRT ERA 7200 UHF", (RadioRange(MHz(225), MHz(400), step=MHz(1)),)),
|
||||
Radio("AN/ARC-222", (RadioRange(MHz(116), MHz(152), MHz(1), Modulation.AM),)),
|
||||
Radio("SCR-522", (RadioRange(MHz(100), MHz(156), MHz(1), Modulation.AM),)),
|
||||
Radio("A.R.I. 1063", (RadioRange(MHz(100), MHz(156), MHz(1), Modulation.AM),)),
|
||||
Radio("BC-1206", (RadioRange(kHz(200), kHz(400), kHz(10), Modulation.AM),)),
|
||||
Radio(
|
||||
"TRT ERA 7000 V/UHF",
|
||||
(
|
||||
RadioRange(MHz(118), MHz(150), MHz(1), Modulation.AM),
|
||||
RadioRange(MHz(225), MHz(400), MHz(1), Modulation.AM),
|
||||
),
|
||||
),
|
||||
Radio("TRT ERA 7200 UHF", (RadioRange(MHz(225), MHz(400), MHz(1), Modulation.AM),)),
|
||||
# Tomcat radios
|
||||
# # https://www.heatblur.se/F-14Manual/general.html#an-arc-159-uhf-1-radio
|
||||
Radio("AN/ARC-159", (RadioRange(MHz(225), MHz(400), step=MHz(1)),)),
|
||||
# AN/ARC-182 can also operate from 30 MHz to 88 MHz, as well as from 225 MHz
|
||||
# to 400 MHz range, but we can't model gaps with the current implementation.
|
||||
Radio("AN/ARC-159", (RadioRange(MHz(225), MHz(400), MHz(1), Modulation.AM),)),
|
||||
# https://www.heatblur.se/F-14Manual/general.html#an-arc-182-v-uhf-2-radio
|
||||
Radio("AN/ARC-182", (RadioRange(MHz(108), MHz(174), step=MHz(1)),)),
|
||||
# Also capable of [103, 156) at 25 kHz intervals, but we can't do gaps.
|
||||
Radio("FR 22", (RadioRange(MHz(225), MHz(400), step=kHz(50)),)),
|
||||
Radio(
|
||||
"AN/ARC-182",
|
||||
(
|
||||
RadioRange(MHz(225), MHz(400), MHz(1), Modulation.AM),
|
||||
RadioRange(MHz(108), MHz(174), MHz(1), Modulation.AM),
|
||||
# The Range from 30-88MHz should be FM but its modeled as AM in dcs
|
||||
RadioRange(MHz(30), MHz(88), MHz(1), Modulation.AM),
|
||||
),
|
||||
),
|
||||
Radio(
|
||||
"FR 22",
|
||||
(
|
||||
RadioRange(MHz(225), MHz(400), kHz(50), Modulation.AM),
|
||||
RadioRange(MHz(103), MHz(156), kHz(25), Modulation.AM),
|
||||
),
|
||||
),
|
||||
# P-51 / P-47 Radio
|
||||
# 4 preset channels (A/B/C/D)
|
||||
Radio("SCR522", (RadioRange(MHz(100), MHz(156), step=kHz(25)),)),
|
||||
Radio("R&S M3AR VHF", (RadioRange(MHz(120), MHz(174), step=MHz(1)),)),
|
||||
Radio("R&S M3AR UHF", (RadioRange(MHz(225), MHz(400), step=MHz(1)),)),
|
||||
Radio("SCR522", (RadioRange(MHz(100), MHz(156), kHz(25), Modulation.AM),)),
|
||||
# JF-17 Radios should use AM
|
||||
Radio("R&S M3AR VHF", (RadioRange(MHz(120), MHz(174), MHz(1), Modulation.AM),)),
|
||||
Radio("R&S M3AR UHF", (RadioRange(MHz(225), MHz(400), MHz(1), Modulation.AM),)),
|
||||
# MiG-15bis
|
||||
Radio("RSI-6K HF", (RadioRange(MHz(3, 750), MHz(5), step=kHz(25)),)),
|
||||
Radio("RSI-6K HF", (RadioRange(MHz(3, 750), MHz(5), kHz(25), Modulation.AM),)),
|
||||
# MiG-19P
|
||||
Radio("RSIU-4V", (RadioRange(MHz(100), MHz(150), step=MHz(1)),)),
|
||||
Radio("RSIU-4V", (RadioRange(MHz(100), MHz(150), MHz(1), Modulation.AM),)),
|
||||
# MiG-21bis
|
||||
Radio("RSIU-5V", (RadioRange(MHz(118), MHz(140), step=MHz(1)),)),
|
||||
Radio("RSIU-5V", (RadioRange(MHz(118), MHz(140), MHz(1), Modulation.AM),)),
|
||||
# Ka-50
|
||||
# Note: Also capable of 100MHz-150MHz, but we can't model gaps.
|
||||
Radio("R-800L1", (RadioRange(MHz(220), MHz(400), step=kHz(25)),)),
|
||||
Radio("R-828", (RadioRange(MHz(20), MHz(60), step=kHz(25)),)),
|
||||
Radio("R-800L1", (RadioRange(MHz(220), MHz(400), kHz(25), Modulation.AM),)),
|
||||
Radio("R-828", (RadioRange(MHz(20), MHz(60), kHz(25), Modulation.FM),)),
|
||||
# UH-1H
|
||||
Radio("AN/ARC-51BX", (RadioRange(MHz(225), MHz(400), step=kHz(50)),)),
|
||||
Radio("AN/ARC-131", (RadioRange(MHz(30), MHz(76), step=kHz(50)),)),
|
||||
Radio("AN/ARC-134", (RadioRange(MHz(116), MHz(150), step=kHz(25)),)),
|
||||
Radio("R&S Series 6000", (RadioRange(MHz(100), MHz(156), step=kHz(25)),)),
|
||||
Radio("AN/ARC-51BX", (RadioRange(MHz(225), MHz(400), kHz(50), Modulation.AM),)),
|
||||
Radio("AN/ARC-131", (RadioRange(MHz(30), MHz(76), kHz(50), Modulation.FM),)),
|
||||
Radio("AN/ARC-134", (RadioRange(MHz(116), MHz(150), kHz(25), Modulation.AM),)),
|
||||
# JAS39
|
||||
Radio("R&S Series 6000", (RadioRange(MHz(100), MHz(156), kHz(25), Modulation.AM),)),
|
||||
]
|
||||
|
||||
|
||||
@ -233,7 +277,10 @@ class RadioRegistry:
|
||||
|
||||
# Not a real radio, but useful for allocating a channel usable for
|
||||
# inter-flight communications.
|
||||
BLUFOR_UHF = Radio("BLUFOR UHF", (RadioRange(MHz(225), MHz(400), step=MHz(1)),))
|
||||
# Uses AM as default Modulation
|
||||
BLUFOR_UHF = Radio(
|
||||
"BLUFOR UHF", (RadioRange(MHz(225), MHz(400), MHz(1), Modulation.AM),)
|
||||
)
|
||||
|
||||
def __init__(self) -> None:
|
||||
self.allocated_channels: Set[RadioFrequency] = set()
|
||||
|
||||
Loading…
x
Reference in New Issue
Block a user