Adding rig control via Hamlib.

This commit is contained in:
Marat Fayzullin 2024-09-25 20:58:53 -04:00
parent daec4bf9a4
commit 0e56edc1d2
5 changed files with 404 additions and 4 deletions

View File

@ -363,5 +363,9 @@ defaultConfig = PropertyLayer(
cw_showcw=False,
dsc_show_errors=True,
gps_updates=False,
bandplan_region=0
bandplan_region=0,
rig_enabled=False,
rig_model=2,
rig_device="127.0.0.1:4533",
rig_address=0
).readonly()

View File

@ -1,11 +1,11 @@
from owrx.controllers.settings import SettingsFormController, SettingsBreadcrumb
from owrx.form.section import Section
from owrx.form.input.converter import OptionalConverter
from owrx.form.input.converter import OptionalConverter, IntConverter
from owrx.form.input.aprs import AprsBeaconSymbols, AprsAntennaDirections
from owrx.form.input import TextInput, CheckboxInput, DropdownInput, NumberInput, PasswordInput
from owrx.form.input import TextInput, CheckboxInput, DropdownInput, NumberInput, PasswordInput, Option
from owrx.form.input.validator import AddressAndOptionalPortValidator
from owrx.breadcrumb import Breadcrumb, BreadcrumbItem
from owrx.rigcontrol import RigControl
class ReportingController(SettingsFormController):
def getTitle(self):
@ -128,5 +128,28 @@ class ReportingController(SettingsFormController):
infotext="MQTT topic to publish reports to (default: openwebrx)",
converter=OptionalConverter(),
),
),
Section(
"RigControl settings",
CheckboxInput(
"rig_enabled",
"Enable sending changes to a standalone transceiver",
),
DropdownInput(
"rig_model",
"Transceiver model",
options=[Option(str(RigControl.RIGS[x]), x) for x in RigControl.RIGS.keys()],
converter=IntConverter(),
),
TextInput(
"rig_device",
"Transceiver CAT device",
infotext="Device or IP address:port used to control transceiver",
),
NumberInput(
"rig_address",
"Transceiver CI-V address",
infotext="Optional transceiver CI-V address (used by Icom)",
),
)
]

View File

@ -2,6 +2,7 @@ from owrx.source import SdrSourceEventClient, SdrSourceState, SdrClientClass
from owrx.property import PropertyStack, PropertyLayer, PropertyValidator, PropertyDeleted, PropertyDeletion
from owrx.property.validators import OrValidator, RegexValidator, BoolValidator
from owrx.modes import Modes, DigitalMode
from owrx.rigcontrol import RigControl
from csdr.chain import Chain
from csdr.chain.demodulator import BaseDemodulatorChain, FixedIfSampleRateChain, FixedAudioRateChain, HdAudio, \
SecondaryDemodulator, DialFrequencyReceiver, MetaProvider, SlotFilterChain, SecondarySelectorChain, \
@ -569,6 +570,9 @@ class DspManager(SdrSourceEventClient, ClientDemodulatorSecondaryDspEventClient)
self.sdrSource.addClient(self)
self.rigControl = RigControl(self.props)
def setSecondaryFftSize(self, size):
self.chain.setSecondaryFftSize(size)
self.handler.write_secondary_dsp_config({"secondary_fft_size": size})
@ -854,6 +858,7 @@ class DspManager(SdrSourceEventClient, ClientDemodulatorSecondaryDspEventClient)
for sub in self.subscriptions:
sub.cancel()
self.subscriptions = []
self.rigControl.stop()
def setProperties(self, props):
for k, v in props.items():

View File

@ -100,6 +100,7 @@ class FeatureDetector(object):
"dab": ["csdreti", "dablin"],
"mqtt": ["paho_mqtt"],
"hdradio": ["nrsc5"],
"rigcontrol": ["hamlib"],
}
def feature_availability(self):
@ -766,3 +767,11 @@ class FeatureDetector(object):
from the OpenWebRX+ repositories.
"""
return self.command_is_runnable("nrsc5 -v")
def has_hamlib(self):
"""
OpenWebRX uses the [Hamlib](https://github.com/Hamlib/Hamlib) `rigctl`
tool to synchronize frequency and modulation with external transceivers.
The `hamlib` package is available in most Linux distributions.
"""
return self.command_is_runnable("rigctl -V")

359
owrx/rigcontrol.py Normal file
View File

@ -0,0 +1,359 @@
from owrx.feature import FeatureDetector
from owrx.property import PropertyStack
from owrx.config import Config
import subprocess
import logging
logger = logging.getLogger(__name__)
class RigControl():
RIGS = {
# "Hamlib Dummy" : 1,
"Hamlib" : 2,
"FLRig" : 4,
"TRXManager 5.7.630+" : 5,
# "Hamlib Dummy No VFO" : 6,
"ADAT ADT-200A" : 29001,
"AE9RB Si570 Peaberry V1" : 25016,
"AE9RB Si570 Peaberry V2" : 25017,
"Alinco DX-77" : 17001,
"Alinco DX-SR8" : 17002,
"AmQRP DDS-60" : 25006,
"AMSAT-UK FUNcube Dongle" : 25013,
"AMSAT-UK FUNcube Dongle Pro+" : 25018,
"ANAN Thetis" : 2048,
"AOR AR3000A" : 5006,
"AOR AR3030" : 5005,
"AOR AR5000" : 5004,
"AOR AR2700" : 5008,
"AOR AR8600" : 5013,
"AOR AR5000A" : 5014,
"AOR AR7030" : 5003,
"AOR AR7030 Plus" : 5015,
"AOR AR8000" : 5002,
"AOR AR8200" : 5001,
"AOR SR2200" : 5016,
"Barrett 2050" : 32001,
"Barrett 950" : 32002,
"Coding Technologies Digital World Traveller" : 25003,
"Dorji DRA818V" : 31001,
"Dorji DRA818U" : 31002,
"Drake R-8A" : 9002,
"Drake R-8B" : 9003,
"DTTS Microwave Society DttSP IPC" : 23003,
"DTTS Microwave Society DttSP UDP" : 23004,
"ELAD FDM-DUO" : 33001,
"Elecraft K2" : 2021,
"Elecraft K3" : 2029,
"Elecraft K3S" : 2043,
"Elecraft K4" : 2047,
"Elecraft KX2" : 2044,
"Elecraft KX3" : 2045,
"Elecraft XG3" : 2038,
"Elektor SDR-USB" : 25007,
"Elektor 3/04" : 25001,
"FiFi FiFi-SDR" : 25012,
"FlexRadio 6xxx" : 2036,
"FlexRadio PowerSDR" : 2048,
"FlexRadio SDR-1000" : 23001,
"Funkamateur FA-SDR" : 25015,
"Hilberling PT-8000A" : 2046,
"HobbyPCB RS-HFIQ" : 25019,
"Icom IC-92D" : 3065,
"Icom IC-271" : 3003,
"Icom IC-275" : 3004,
"Icom IC-471" : 3006,
"Icom IC-475" : 3007,
"Icom IC-575" : 3008,
"Icom IC-703" : 3055,
"Icom IC-706" : 3009,
"Icom IC-706MkII" : 3010,
"Icom IC-706MkIIG" : 3011,
"Icom IC-705" : 3085,
"Icom IC-707" : 3012,
"Icom IC-718" : 3013,
"Icom IC-725" : 3014,
"Icom IC-726" : 3015,
"Icom IC-728" : 3016,
"Icom IC-729" : 3017,
"Icom IC-735" : 3019,
"Icom IC-736" : 3020,
"Icom IC-737" : 3021,
"Icom IC-738" : 3022,
"Icom IC-746" : 3023,
"Icom IC-746PRO" : 3046,
"Icom IC-751" : 3024,
"Icom IC-756" : 3026,
"Icom IC-756PRO" : 3027,
"Icom IC-756PROII" : 3047,
"Icom IC-756PROIII" : 3057,
"Icom IC-761" : 3028,
"Icom IC-765" : 3029,
"Icom IC-775" : 3030,
"Icom IC-78" : 3045,
"Icom IC-781" : 3031,
"Icom IC-820H" : 3032,
"Icom IC-821H" : 3034,
"Icom IC-910" : 3044,
"Icom IC-970" : 3035,
"Icom IC-1275" : 3002,
"Icom IC-2730" : 3072,
"Icom IC-7000" : 3060,
"Icom IC-7100" : 3070,
"Icom IC-7300" : 3073,
"Icom IC-7200" : 3061,
"Icom IC-7410" : 3067,
"Icom IC-7700" : 3062,
"Icom IC-7600" : 3063,
"Icom IC-7610" : 3078,
"Icom IC-7800" : 3056,
"Icom IC-785x" : 3075,
"Icom IC-9100" : 3068,
"Icom IC-9700" : 3081,
"Icom IC-M700PRO" : 30001,
"Icom IC-M710" : 30003,
"Icom IC-M802" : 30002,
"Icom IC-M803" : 30004,
"Icom IC-R6" : 3077,
"Icom IC-R10" : 3036,
"Icom IC-R20" : 3058,
"Icom IC-R30" : 3080,
"Icom IC-R71" : 3037,
"Icom IC-R72" : 3038,
"Icom IC-R75" : 3039,
"Icom IC-R7000" : 3040,
"Icom IC-R7100" : 3041,
"Icom IC-R8500" : 3042,
"Icom IC-R8600" : 3079,
"Icom IC-R9000" : 3043,
"Icom IC-R9500" : 3066,
"Icom IC-RX7" : 3069,
"Icom IC-PCR1000" : 4001,
"Icom IC-PCR100" : 4002,
"Icom IC-PCR1500" : 4003,
"Icom IC-PCR2500" : 4004,
"Icom ID-1" : 3054,
"Icom ID-31" : 3083,
"Icom ID-51" : 3084,
"Icom ID-4100" : 3082,
"Icom ID-5100" : 3071,
"JRC NRD-525" : 6005,
"JRC NRD-535D" : 6006,
"JRC NRD-545 DSP" : 6007,
"Kachina 505DSP" : 18001,
"Kenwood R-5000" : 2015,
"Kenwood TH-D7A" : 2017,
"Kenwood TH-D72A" : 2033,
"Kenwood TH-D74" : 2042,
"Kenwood TH-F6A" : 2019,
"Kenwood TH-F7E" : 2020,
"Kenwood TH-G71" : 2023,
"Kenwood TM-D700" : 2026,
"Kenwood TM-D710(G)" : 2034,
"Kenwood TM-V7" : 2027,
"Kenwood TRC-80" : 2030,
"Kenwood TS-50S" : 2001,
"Kenwood TS-440S" : 2002,
"Kenwood TS-450S" : 2003,
"Kenwood TS-480" : 2028,
"Kenwood TS-570D" : 2004,
"Kenwood TS-570S" : 2016,
"Kenwood TS-590S" : 2031,
"Kenwood TS-590SG" : 2037,
"Kenwood TS-690S" : 2005,
"Kenwood TS-711" : 2006,
"Kenwood TS-790" : 2007,
"Kenwood TS-811" : 2008,
"Kenwood TS-850" : 2009,
"Kenwood TS-870S" : 2010,
"Kenwood TS-890S" : 2041,
"Kenwood TS-940S" : 2011,
"Kenwood TS-950S" : 2012,
"Kenwood TS-950SDX" : 2013,
"Kenwood TS-990S" : 2039,
"Kenwood TS-2000" : 2014,
"Kenwood TS-930" : 2022,
"Kenwood TS-680S" : 2024,
"Kenwood TS-140S" : 2025,
"KTH-SDR Si570 PIC-USB" : 25011,
"Lowe HF-235" : 10004,
"Malachite DSP" : 2049,
"Microtelecom Perseus" : 3074,
"mRS miniVNA" : 25008,
"N2ADR HiQSDR" : 25014,
"OpenHPSDR PiHPSDR" : 2040,
"Optoelectronics OptoScan535" : 3052,
"Optoelectronics OptoScan456" : 3053,
"Philips/Simoco PRM8060" : 28001,
"Racal RA3702" : 11005,
"Racal RA6790/GM" : 11003,
"RadioShack PRO-2052" : 8004,
"RFT EKD-500" : 24001,
"Rohde & Schwarz EB200" : 27002,
"Rohde & Schwarz ESMC" : 27001,
"Rohde & Schwarz XK2100" : 27003,
"SAT-Schneider DRT1" : 25002,
"SigFox Transfox" : 2032,
"Skanti TRP8000" : 14002,
"Skanti TRP8255SR" : 14004,
"SoftRock Si570 AVR-USB" : 25009,
"TAPR DSP-10" : 22001,
"Ten-Tec Delta II" : 3064,
"Ten-Tec Omni VI Plus" : 3051,
"Ten-Tec RX-320" : 16003,
"Ten-Tec RX-331" : 16012,
"Ten-Tec RX-340" : 16004,
"Ten-Tec RX-350" : 16005,
"Ten-Tec TT-516 Argonaut V" : 16007,
"Ten-Tec TT-538 Jupiter" : 16002,
"Ten-Tec TT-550" : 16001,
"Ten-Tec TT-565 Orion" : 16008,
"Ten-Tec TT-585 Paragon" : 16009,
"Ten-Tec TT-588 Omni VII" : 16011,
"Ten-Tec TT-599 Eagle" : 16013,
"Uniden BC245xlt" : 8002,
"Uniden BC250D" : 8006,
"Uniden BC780xlt" : 8001,
"Uniden BC895xlt" : 8003,
"Uniden BC898T" : 8012,
"Uniden BCD-396T" : 8010,
"Uniden BCD-996T" : 8011,
"Vertex Standard VX-1700" : 1033,
"Video4Linux SW/FM Radio" : 26001,
"Video4Linux2 SW/FM Radio" : 26002,
"Watkins-Johnson WJ-8888" : 12004,
"Winradio WR-1000" : 15001,
"Winradio WR-1500" : 15002,
"Winradio WR-1550" : 15003,
"Winradio WR-3100" : 15004,
"Winradio WR-3150" : 15005,
"Winradio WR-3500" : 15006,
"Winradio WR-3700" : 15007,
"Winradio WR-G313" : 15009,
"Xiegu X108G" : 3076,
"Yaesu FRG-100" : 1017,
"Yaesu FRG-8800" : 1019,
"Yaesu FRG-9600" : 1018,
"Yaesu FT-100" : 1021,
"Yaesu FT-450" : 1027,
"Yaesu FT-600" : 1039,
"Yaesu FT-736R" : 1010,
"Yaesu FT-747GX" : 1005,
"Yaesu FT-757GX" : 1006,
"Yaesu FT-757GXII" : 1007,
"Yaesu FT-767GX" : 1009,
"Yaesu FT-817" : 1020,
"Yaesu FT-818" : 1041,
"Yaesu FT-840" : 1011,
"Yaesu FT-847" : 1001,
"Yaesu FT-847UNI" : 1038,
"Yaesu FT-857" : 1022,
"Yaesu FT-890" : 1015,
"Yaesu FT-891" : 1036,
"Yaesu FT-897" : 1023,
"Yaesu FT-897D" : 1043,
"Yaesu FT-900" : 1013,
"Yaesu FT-920" : 1014,
"Yaesu FT-950" : 1028,
"Yaesu FT-980" : 1031,
"Yaesu FT-990" : 1016,
"Yaesu FT-991" : 1035,
"Yaesu FT-1000D" : 1003,
"Yaesu FT-1000MP" : 1024,
"Yaesu FT-1000MP Mark-V" : 1004,
"Yaesu FT-1000MP Mark-V Field" : 1025,
"Yaesu FT-2000" : 1029,
"Yaesu FTDX-10" : 1042,
"Yaesu FTDX-101D" : 1040,
"Yaesu FTDX-101MP" : 1044,
"Yaesu FTDX-1200" : 1034,
"Yaesu FTDX-3000" : 1037,
"Yaesu FTDX-5000" : 1032,
"Yaesu FTDX-9000" : 1030,
"Yaesu VR-5000" : 1026,
}
MODES = {
"nfm" : "FM", "wfm" : "WFM",
"am" : "AM", "sam" : "SAM",
"lsb" : "LSB", "usb" : "USB",
"cw" : "CW",
}
def __init__(self, props: PropertyStack):
self.mod = None
self.fCenter = 0
self.fOffset = 0
self.subscriptions = [
props.wireProperty("offset_freq", self.setFrequencyOffset),
props.wireProperty("center_freq", self.setCenterFrequency),
props.wireProperty("mod", self.setDemodulator),
]
super().__init__()
def stop(self):
for sub in self.subscriptions:
sub.cancel()
self.subscriptions = []
def setFrequencyOffset(self, offset: int) -> None:
if offset != self.fOffset and self.rigFrequency(self.fCenter + offset):
self.fOffset = offset
def setCenterFrequency(self, center: int) -> None:
if center != self.fCenter and self.rigFrequency(center + self.fOffset):
self.fCenter = center
def setDemodulator(self, mod: str) -> None:
if mod != self.mod and self.rigModulation(mod):
self.mod = mod
def rigTX(self, active: bool) -> bool:
return self.rigCommand(["set_ptt", "1" if active else "0"])
def rigFrequency(self, freq: int) -> bool:
return self.rigCommand(["set_freq", str(freq)])
def rigModulation(self, mod: str) -> bool:
if mod in self.MODES:
return self.rigCommand(["set_mode", self.MODES[mod], "0"])
else:
return False
def rigCommand(self, cmd: list[str]) -> bool:
# Must have Hamlib/Rigctl installed
if not FeatureDetector().is_available("rigcontrol"):
return False
# Must have rig control enabled
pm = Config.get()
if not pm["rig_enabled"]:
return False
# Compose Rigctl command
rigctl = ["rigctl", "-m", str(pm["rig_model"]), "-r", pm["rig_device"]]
address = pm["rig_address"]
if address > 0 and address < 256:
rigctl += ["-c", str(address)]
# Rigctl must return 0 to indicate success
return subprocess.run(rigctl + cmd).returncode == 0