[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:
# ./template.py, Copyright 2012 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
# 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
least the FT-4, FT-25, FT-35, and FT-65. This driver will not work with
older Yaesu models.
least the FT-4X, FT-4V, FT-65, and FT-25. This driver will not work with older
Yaesu models.
"""
import logging
import struct
@ -261,7 +262,8 @@ def sendcmd(pipe, cmd, response_len):
msg = "Bad echo. Sent:" + util.hexprint(cmd) + ", "
msg += "Received:" + util.hexprint(echo)
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:
return variable_len_resp(pipe)
if response_len > 0:
@ -474,8 +476,6 @@ DUPLEX = ["+", "", "-", "", "off", "", "split"] # (0,2,4,5)= (+,-,0, auto)
SKIPS = ["", "S"]
BASETYPE_FT4 = ["FT-4XR", "FT-4XE"]
BASETYPE_FT65 = ["FT-65R"]
POWER_LEVELS = [chirp_common.PowerLevel("Low", watts=0.5),
chirp_common.PowerLevel("Mid", watts=2.5),
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
# 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
SPECIALS = [
SPECIALS_FT4 = [
("pms", PMSNAMES),
("vfo", ["VFO A UHF", "VFO A VHF", "VFO B FM", "VFO B VHF", "VFO B UHF"]),
("home", ["HOME FM", "HOME VHF", "HOME UHF"]),
("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_SPECIALS = list(SPECIALS) # a shallow copy works here
FT65_SPECIALS[-1] = FT65_PROGS # replace the last entry (P key names)
SPECIALS_FT65[-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
@ -620,8 +657,8 @@ class YaesuSC35GenericRadio(chirp_common.CloneModeRadio,
chirp_common.ExperimentalRadio):
"""
Base class for all Yaesu radios using the SCU-35 programming cable
and its protocol. Classes for specific radios extend this class and
are found at the end of this file.
and its protocol. Classes for sub families extend this class and
are found towards the end of this file.
"""
VENDOR = "Yaesu"
MODEL = "SCU-35Generic" # No radio directly uses the base class
@ -629,6 +666,12 @@ class YaesuSC35GenericRadio(chirp_common.CloneModeRadio,
MAX_MEM_SLOT = 200
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
def get_prompts(cls):
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_cross = True
rf.has_settings = True
rf.valid_tuning_steps = self.legal_steps
return rf
@ -1027,14 +1069,11 @@ class YaesuSC35GenericRadio(chirp_common.CloneModeRadio,
mem = chirp_common.Memory()
_mem, ndx, num, regtype, sname = self.slotloc(memref)
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)
mem.power = POWER_LEVELS[_mem.tx_pwr]
mem.mode = ["FM", "NFM"][_mem.tx_width]
mem.tuning_step = STEP_CODE[_mem.step]
# First, we need to know whether a channel is enabled,
# then we can process any channel parameters.
# It was found (at least on an FT-25) that channels might be
# uninitialized and memory is just completely filled with 0xFF.
if regtype == "pms":
mem.extd_number = sname
@ -1043,15 +1082,34 @@ class YaesuSC35GenericRadio(chirp_common.CloneModeRadio,
mem.name = clean_name(self._memobj.names[ndx].chrs)
mem.empty = not retrieve_bit(self._memobj.enable, 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:
mem.empty = False
mem.extd_number = sname
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
def enforce_band(self, memloc, freq, mem_num, sname):
@ -1105,28 +1163,16 @@ class YaesuSC35GenericRadio(chirp_common.CloneModeRadio,
return
@directory.register
class YaesuFT4Radio(YaesuSC35GenericRadio):
MODEL = "FT-4XR"
_basetype = BASETYPE_FT4
valid_bands = [
(65000000, 108000000), # broadcast FM, receive only
(144000000, 148000000), # VHF, US version, TX and RX
(430000000, 450000000) # UHF, US version, TX and RX
# 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'
class YaesuFT4GenericRadio(YaesuSC35GenericRadio):
"""
FT-4 sub family class. Classes for individual radios extend
these classes and are found at the end of this file.
"""
class_specials = SPECIALS_FT4
Pkeys = 2 # number of programmable keys
namelen = 6 # length of the mem name display on the front-panel
freq_offset_scale = 25000
legal_steps = US_LEGAL_STEPS
class_group_descs = YaesuSC35GenericRadio.group_descriptions
class_specials = SPECIALS
# names for the setmode function for the programmable keys. Mode zero means
# that the key is programmed for a memory not a setmode.
SETMODES = [
@ -1143,31 +1189,19 @@ class YaesuFT4Radio(YaesuSC35GenericRadio):
]
@directory.register
class YaesuFT65Radio(YaesuSC35GenericRadio):
MODEL = "FT-65R"
_basetype = BASETYPE_FT65
valid_bands = [
(65000000, 108000000), # broadcast FM, receive only
(144000000, 148000000), # VHF, US version, TX and RX
(430000000, 450000000) # UHF, US version, TX and RX
# 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'
class YaesuFT65GenericRadio(YaesuSC35GenericRadio):
"""
FT-65 sub family class. Classes for individual radios extend
these classes and are found at the end of this file.
"""
class_specials = SPECIALS_FT65
Pkeys = 4 # number of programmable keys
namelen = 8 # length of the mem name display on the front-panel
freq_offset_scale = 50000
legal_steps = US_LEGAL_STEPS
# we need a deep copy here because we are adding deeper than the top level.
class_group_descs = copy.deepcopy(YaesuSC35GenericRadio.group_descriptions)
add_paramdesc(
class_group_descs, "misc", ("compander", "Compander", ["OFF", "ON"]))
class_specials = FT65_SPECIALS
# names for the setmode function for the programmable keys. Mode zero means
# that the key is programmed for a memory not a setmode.
SETMODES = [
@ -1180,3 +1214,31 @@ class YaesuFT65Radio(YaesuSC35GenericRadio):
"step", "tot", "tx pwr", "tx save", "vfo.spl", # 30-34
"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