[ft4] driver restructure (#7615)

While implementing two more radios of the Yaesu FT-4 family (the FT-25, see #7543, and the FT-4V, see #7387), I found that with some moderate reorganization, implementation of new radios will become easier. The proposed reorganization implements one more interstitial layer of inheritance to support the sub families of FT-4 (containing the FT-4X and FT-4V) and FT-65 (containing FT-65 and FT-25). Also, some variable assignments have been moved from the individual radio classes to the SCU-35 base class and to the interstitial classes named above.

This change also adds the infrastructure for adding European or Asian models, and as such prepare for addressing a number of currently open issues (6.25kHz tune step issues on EU models, frequency limitations; see #6619, #6651, #6677, #6761, #6869). It will also make it easier for a few additional fixes for issues which were found during my work on this driver (#7601, #7603, #7605). I will submit these fixes in additional patches.

Fixes: #7615
This commit is contained in:
Bernhard Hailer 2020-02-01 14:59:19 -08:00
parent be114d0d5a
commit a77699ff8c

View File

@ -2,6 +2,7 @@
# Derives loosely from two sources released under GPLv2: # Derives loosely from two sources released under GPLv2:
# ./template.py, Copyright 2012 Dan Smith <dsmith@danplanet.com> # ./template.py, Copyright 2012 Dan Smith <dsmith@danplanet.com>
# ./ft60.py, Copyright 2011 Dan Smith <dsmith@danplanet.com> # ./ft60.py, Copyright 2011 Dan Smith <dsmith@danplanet.com>
# Edited 2020 Bernhard Hailer AE6YN <ham73tux@gmail.com>
# #
# This program is free software: you can redistribute it and/or modify # This program is free software: you can redistribute it and/or modify
# it under the terms of the GNU General Public License as published by # it under the terms of the GNU General Public License as published by
@ -18,8 +19,8 @@
""" """
CHIRP driver for Yaesu radios that use the SCU-35 cable. This includes at CHIRP driver for Yaesu radios that use the SCU-35 cable. This includes at
least the FT-4, FT-25, FT-35, and FT-65. This driver will not work with least the FT-4X, FT-4V, FT-65, and FT-25. This driver will not work with older
older Yaesu models. Yaesu models.
""" """
import logging import logging
import struct import struct
@ -261,7 +262,8 @@ def sendcmd(pipe, cmd, response_len):
msg = "Bad echo. Sent:" + util.hexprint(cmd) + ", " msg = "Bad echo. Sent:" + util.hexprint(cmd) + ", "
msg += "Received:" + util.hexprint(echo) msg += "Received:" + util.hexprint(echo)
LOG.debug(msg) LOG.debug(msg)
raise errors.RadioError("Incorrect echo on serial port. Bad cable?") raise errors.RadioError(
"Incorrect echo on serial port. Radio off? Bad cable?")
if response_len is None: if response_len is None:
return variable_len_resp(pipe) return variable_len_resp(pipe)
if response_len > 0: if response_len > 0:
@ -474,8 +476,6 @@ DUPLEX = ["+", "", "-", "", "off", "", "split"] # (0,2,4,5)= (+,-,0, auto)
SKIPS = ["", "S"] SKIPS = ["", "S"]
BASETYPE_FT4 = ["FT-4XR", "FT-4XE"]
BASETYPE_FT65 = ["FT-65R"]
POWER_LEVELS = [chirp_common.PowerLevel("Low", watts=0.5), POWER_LEVELS = [chirp_common.PowerLevel("Low", watts=0.5),
chirp_common.PowerLevel("Mid", watts=2.5), chirp_common.PowerLevel("Mid", watts=2.5),
chirp_common.PowerLevel("High", watts=5.0)] chirp_common.PowerLevel("High", watts=5.0)]
@ -560,16 +560,53 @@ PMSNAMES = ["%s%02d" % (c, i) for i in range(1, 11) for c in ('L', 'U')]
# Each special has unique constrants: band, name yes/no, and pms L/U # Each special has unique constrants: band, name yes/no, and pms L/U
# The FT-65 class replaces the "prog" entry in this list. # The FT-65 class replaces the "prog" entry in this list.
# The name field must be the name of a slot array in MEM_FORMAT # The name field must be the name of a slot array in MEM_FORMAT
SPECIALS = [ SPECIALS_FT4 = [
("pms", PMSNAMES), ("pms", PMSNAMES),
("vfo", ["VFO A UHF", "VFO A VHF", "VFO B FM", "VFO B VHF", "VFO B UHF"]), ("vfo", ["VFO A UHF", "VFO A VHF", "VFO B FM", "VFO B VHF", "VFO B UHF"]),
("home", ["HOME FM", "HOME VHF", "HOME UHF"]), ("home", ["HOME FM", "HOME VHF", "HOME UHF"]),
("prog", ["P1", "P2"]) ("prog", ["P1", "P2"])
] ]
BAND_ASSIGNMENTS = [2, 1, 0, 1, 2, 0, 1, 2] # bands for the vfos and homes SPECIALS_FT65 = SPECIALS_FT4
FT65_PROGS = ("prog", ["P1", "P2", "P3", "P4"]) FT65_PROGS = ("prog", ["P1", "P2", "P3", "P4"])
FT65_SPECIALS = list(SPECIALS) # a shallow copy works here SPECIALS_FT65[-1] = FT65_PROGS # replace the last entry (P key names)
FT65_SPECIALS[-1] = FT65_PROGS # replace the last entry (P key names)
# I wonder whether we should simply open the bands to what the radios allow
# for RX? The radios do take care of allowing or prohibiting TX on their own.
# In that case, the ASIA settings can be used for any region model.
# To be discussed with Dan Clemmensen. [AE6YN]
VALID_BANDS_US_DUAL = [
(65000000, 108000000), # broadcast FM, receive only
(144000000, 148000000), # VHF, US version, TX and RX
(430000000, 450000000) # UHF, US version, TX and RX
]
VALID_BANDS_EU_DUAL = [
(65000000, 108000000), # broadcast FM, receive only
(144000000, 146000000), # VHF, EU version, TX and RX
(430000000, 440000000) # UHF, EU version, TX and RX
]
VALID_BANDS_ASIA_DUAL = [
(65000000, 108000000), # broadcast FM, receive only
(136000000, 174000000), # VHF, Asia version, TX and RX
(400000000, 480000000) # UHF, Asia version, TX and RX
]
VALID_BANDS_US_VHF = [
(65000000, 108000000), # broadcast FM, receive only
(144000000, 148000000), # VHF, US version, TX and RX
]
VALID_BANDS_EU_VHF = [
(65000000, 108000000), # broadcast FM, receive only
(144000000, 146000000), # VHF, EU version, TX and RX
]
VALID_BANDS_ASIA_VHF = [
(65000000, 108000000), # broadcast FM, receive only
(136000000, 174000000), # VHF, Asia version, TX and RX
]
# bands for the five VFO and three home channel memories
BAND_ASSIGNMENTS_DUALBAND = [2, 1, 0, 1, 2, 0, 1, 2] # all locations used
BAND_ASSIGNMENTS_MONO_VHF = [1, 1, 0, 1, 1, 0, 1, 1] # UHF locations unused
# None, and 50 Tones. Use this explicit array because the # None, and 50 Tones. Use this explicit array because the
@ -620,8 +657,8 @@ class YaesuSC35GenericRadio(chirp_common.CloneModeRadio,
chirp_common.ExperimentalRadio): chirp_common.ExperimentalRadio):
""" """
Base class for all Yaesu radios using the SCU-35 programming cable Base class for all Yaesu radios using the SCU-35 programming cable
and its protocol. Classes for specific radios extend this class and and its protocol. Classes for sub families extend this class and
are found at the end of this file. are found towards the end of this file.
""" """
VENDOR = "Yaesu" VENDOR = "Yaesu"
MODEL = "SCU-35Generic" # No radio directly uses the base class MODEL = "SCU-35Generic" # No radio directly uses the base class
@ -629,6 +666,12 @@ class YaesuSC35GenericRadio(chirp_common.CloneModeRadio,
MAX_MEM_SLOT = 200 MAX_MEM_SLOT = 200
NEEDS_COMPAT_SERIAL = False NEEDS_COMPAT_SERIAL = False
# These settings are common to all radios in this family.
_valid_chars = chirp_common.CHARSET_ASCII
numblocks = 0x215 # number of 16-byte blocks in the radio
_memsize = 16 * numblocks # used by CHIRP file loader to guess radio type
MAX_MEM_SLOT = 200
@classmethod @classmethod
def get_prompts(cls): def get_prompts(cls):
rp = chirp_common.RadioPrompts() rp = chirp_common.RadioPrompts()
@ -669,7 +712,6 @@ class YaesuSC35GenericRadio(chirp_common.CloneModeRadio,
rf.has_dtcs_polarity = False # REV TN reverses the tone, not the dcs rf.has_dtcs_polarity = False # REV TN reverses the tone, not the dcs
rf.has_cross = True rf.has_cross = True
rf.has_settings = True rf.has_settings = True
rf.valid_tuning_steps = self.legal_steps
return rf return rf
@ -1027,14 +1069,11 @@ class YaesuSC35GenericRadio(chirp_common.CloneModeRadio,
mem = chirp_common.Memory() mem = chirp_common.Memory()
_mem, ndx, num, regtype, sname = self.slotloc(memref) _mem, ndx, num, regtype, sname = self.slotloc(memref)
mem.number = num mem.number = num
mem.freq = int(_mem.freq) * 10
mem.offset = int(_mem.offset) * self.freq_offset_scale
mem.duplex = DUPLEX[_mem.duplex]
self.decode_sql(mem, _mem) # First, we need to know whether a channel is enabled,
mem.power = POWER_LEVELS[_mem.tx_pwr] # then we can process any channel parameters.
mem.mode = ["FM", "NFM"][_mem.tx_width] # It was found (at least on an FT-25) that channels might be
mem.tuning_step = STEP_CODE[_mem.step] # uninitialized and memory is just completely filled with 0xFF.
if regtype == "pms": if regtype == "pms":
mem.extd_number = sname mem.extd_number = sname
@ -1043,15 +1082,34 @@ class YaesuSC35GenericRadio(chirp_common.CloneModeRadio,
mem.name = clean_name(self._memobj.names[ndx].chrs) mem.name = clean_name(self._memobj.names[ndx].chrs)
mem.empty = not retrieve_bit(self._memobj.enable, ndx) mem.empty = not retrieve_bit(self._memobj.enable, ndx)
mem.skip = SKIPS[retrieve_bit(self._memobj.scan, ndx)] mem.skip = SKIPS[retrieve_bit(self._memobj.scan, ndx)]
txfreq = int(self._memobj.txfreqs[ndx].freq) * 10
if (txfreq != 0) and (txfreq != mem.freq):
mem.duplex = "split"
mem.offset = txfreq
else: else:
mem.empty = False mem.empty = False
mem.extd_number = sname mem.extd_number = sname
mem.immutable = ["number", "extd_number", "name", "skip"] mem.immutable = ["number", "extd_number", "name", "skip"]
# So, now if channel is not empty, we can do the evaluation of
# all parameters. Otherwise we set them to defaults.
if mem.empty:
mem.freq = 0
mem.offset = 0
mem.duplex = "off"
mem.power = POWER_LEVELS[0] # "High"
mem.mode = "FM"
mem.tuning_step = 0
else:
mem.freq = int(_mem.freq) * 10
txfreq = int(self._memobj.txfreqs[ndx].freq) * 10
if (txfreq != 0) and (txfreq != mem.freq):
mem.duplex = "split"
mem.offset = txfreq
else:
mem.offset = int(_mem.offset) * self.freq_offset_scale
mem.duplex = DUPLEX[_mem.duplex]
self.decode_sql(mem, _mem)
mem.power = POWER_LEVELS[2 - _mem.tx_pwr]
mem.mode = ["FM", "NFM"][_mem.tx_width]
mem.tuning_step = STEP_CODE[_mem.step]
return mem return mem
def enforce_band(self, memloc, freq, mem_num, sname): def enforce_band(self, memloc, freq, mem_num, sname):
@ -1105,28 +1163,16 @@ class YaesuSC35GenericRadio(chirp_common.CloneModeRadio,
return return
@directory.register class YaesuFT4GenericRadio(YaesuSC35GenericRadio):
class YaesuFT4Radio(YaesuSC35GenericRadio): """
MODEL = "FT-4XR" FT-4 sub family class. Classes for individual radios extend
_basetype = BASETYPE_FT4 these classes and are found at the end of this file.
valid_bands = [ """
(65000000, 108000000), # broadcast FM, receive only class_specials = SPECIALS_FT4
(144000000, 148000000), # VHF, US version, TX and RX Pkeys = 2 # number of programmable keys
(430000000, 450000000) # UHF, US version, TX and RX namelen = 6 # length of the mem name display on the front-panel
# VHF, RX (136000000, 174000000)
# UHF, RX (400000000, 480000000)
]
_valid_chars = chirp_common.CHARSET_ASCII
numblocks = 0x215 # number of 16-byte blocks in the radio
_memsize = 16 * numblocks # used by CHIRP file loader to guess radio type
MAX_MEM_SLOT = 200
Pkeys = 2 # number of programmable keys on the FT-4
namelen = 6 # length of the mem name display on the FT-4 front-panel
id_str = b'IFT-35R\x00\x00V100\x00\x00'
freq_offset_scale = 25000 freq_offset_scale = 25000
legal_steps = US_LEGAL_STEPS
class_group_descs = YaesuSC35GenericRadio.group_descriptions class_group_descs = YaesuSC35GenericRadio.group_descriptions
class_specials = SPECIALS
# names for the setmode function for the programmable keys. Mode zero means # names for the setmode function for the programmable keys. Mode zero means
# that the key is programmed for a memory not a setmode. # that the key is programmed for a memory not a setmode.
SETMODES = [ SETMODES = [
@ -1143,31 +1189,19 @@ class YaesuFT4Radio(YaesuSC35GenericRadio):
] ]
@directory.register class YaesuFT65GenericRadio(YaesuSC35GenericRadio):
class YaesuFT65Radio(YaesuSC35GenericRadio): """
MODEL = "FT-65R" FT-65 sub family class. Classes for individual radios extend
_basetype = BASETYPE_FT65 these classes and are found at the end of this file.
valid_bands = [ """
(65000000, 108000000), # broadcast FM, receive only class_specials = SPECIALS_FT65
(144000000, 148000000), # VHF, US version, TX and RX Pkeys = 4 # number of programmable keys
(430000000, 450000000) # UHF, US version, TX and RX namelen = 8 # length of the mem name display on the front-panel
# VHF, RX (136000000, 174000000)
# UHF, RX (400000000, 480000000)
]
_valid_chars = chirp_common.CHARSET_ASCII
numblocks = 0x215 # number of 16-byte blocks in the radio
_memsize = 16 * numblocks # used by CHIRP file loader to guess radio type
MAX_MEM_SLOT = 200
Pkeys = 4 # number of programmable keys on the FT-65
namelen = 8 # length of the mem name display on the FT-65 front panel
id_str = b'IH-420\x00\x00\x00V100\x00\x00'
freq_offset_scale = 50000 freq_offset_scale = 50000
legal_steps = US_LEGAL_STEPS
# we need a deep copy here because we are adding deeper than the top level. # we need a deep copy here because we are adding deeper than the top level.
class_group_descs = copy.deepcopy(YaesuSC35GenericRadio.group_descriptions) class_group_descs = copy.deepcopy(YaesuSC35GenericRadio.group_descriptions)
add_paramdesc( add_paramdesc(
class_group_descs, "misc", ("compander", "Compander", ["OFF", "ON"])) class_group_descs, "misc", ("compander", "Compander", ["OFF", "ON"]))
class_specials = FT65_SPECIALS
# names for the setmode function for the programmable keys. Mode zero means # names for the setmode function for the programmable keys. Mode zero means
# that the key is programmed for a memory not a setmode. # that the key is programmed for a memory not a setmode.
SETMODES = [ SETMODES = [
@ -1180,3 +1214,31 @@ class YaesuFT65Radio(YaesuSC35GenericRadio):
"step", "tot", "tx pwr", "tx save", "vfo.spl", # 30-34 "step", "tot", "tx pwr", "tx save", "vfo.spl", # 30-34
"vox", "wfm.rcv", "wide/nar", "wx alert", "scramble" # 35-39 "vox", "wfm.rcv", "wide/nar", "wx alert", "scramble" # 35-39
] ]
# Classes for each individual radio.
@directory.register
class YaesuFT4XRRadio(YaesuFT4GenericRadio):
"""
FT-4X dual band, US version
"""
MODEL = "FT-4XR"
id_str = b'IFT-35R\x00\x00V100\x00\x00'
valid_bands = VALID_BANDS_US_DUAL
legal_steps = US_LEGAL_STEPS
BAND_ASSIGNMENTS = BAND_ASSIGNMENTS_DUALBAND
@directory.register
class YaesuFT65RRadio(YaesuFT65GenericRadio):
"""
FT-65 dual band, US version
"""
MODEL = "FT-65R"
id_str = b'IH-420\x00\x00\x00V100\x00\x00'
valid_bands = VALID_BANDS_US_DUAL
legal_steps = US_LEGAL_STEPS
BAND_ASSIGNMENTS = BAND_ASSIGNMENTS_DUALBAND