This change is precipitated by the requirement to support configurable civ's (#4547) such that we are no longer able to hard code these values into the code base. While I agree that it would be ideal to not include it as part of the refactoring effort, so as to reduce its scope, the refactoring itself would introduce a circular dependency on the hard coded classes and thus makes it a requirement of this change.
You are assuming that the change to probe_model() would be applied *after* splitting up the file if it's not embedded within the same patch. But there is no reason that a patch to change probe_model() could not be applied *before* any other possible refactoring. It would, as you imply, make the Icom detect mechanism more consistent with the Kenwood one insofar as they would use the same driver enumeration mechanism, and at the same time remove the reliance on the module-global dict of radio models.
The intent of this change is to reduce potential conflicts amongst developers working on similar radio models. Currently there are multiple implementations of icom live mode radios being introduced into the code base - this change should minimize the scope of those changes making it i) easier to develop and ii) reducing merge conflicts and iii) easier to test.
I don't really buy this. The rate of change in that file is very low. The addition of a radio involves (a) adding new classes that are not used in any way by other existing specific driver classes, and (b) *maybe* changing some common code if the new radio involves new features that none of the existing radios use. The former isn't going to introduce merge conflicts, and the latter will introduce them just as much if the common code is in a separate file as if it's in the same file. What changes is whether one or two files are involved, that's all.
As for easier testing, testing is a real issue with the Icom Live mode radios regardless of the changes you're proposing. I'm pretty sure that none of the developers has all of these radios. Any change to the common code - including yours - has the potential to break some or all of the others.
Another outcome of this change is to allow for a clear isolation of radio specific implementation details from the base class, thus reducing the potential for knock on effects from a developer introducing a new radio model while unknowingly breaking another.
Not really, no. Either the new radio involves changes to the common code or it doesn't. The potential for breakage doesn't change just because the common code is in a separate file.
The root of the impending changes to support #4547 stems from the current implementation of how CIV values are handled, in that they are currently implemented as static variables and will need to become class variables. As this is a significant deviation to the implementation of the base IcomCIVRadio class, this change will allow for the development of specific radio models mentioned above to continue unimpeded by that work.
I get the requirement to move to a configurable ID value, and I get that changing probe_model() is a first step, eliminating the need for the current static dict. I'm not arguing against making that change. What you seem to be arguing is that splitting the file means that a slew of changes to the base class won't affect development of subclasses. In theory, it does keep the changes in separate files. However, if I'm working on a new radio, and the base class is undergoing changes right underneath my feet, so to speak, I'm going to be a bit concerned anyway about whether or not my new radio is going to work. And what if a change in the base class creates a need to update all of the existing subclasses? Then there are going to be merge conflicts anyway.
This particular situation arises anytime new source files are introduced into the code base; there is however precedence for this type of change on this code base, for example:
Precedent doesn't mean it's a good thing. :-)
In our particular situation the history is not lost as in the previous revision. In fact, the icomciv.py file is retained in the repo along with its history.
It's there, but who will know it's there in a couple of years? You and I (and Dan) might know where to look to find the previous commit messages, but someone coming along and looking to implement a new radio would not know where to look.
Every one of us is welcome to an opinion, in my own personal view I simply find that the separation of implementation classes from their inherited base class to be cleaner and good programming practice which in the long run creates for a healthier code base and development experience overall. In this case however the intent was not imposed merely for aesthetic reasons but rather to avoid conflict and allow for concurrent development to take place moving forward. Given the scope of the changed code itself is relatively small I see a small risk impact.
Unless you tell me you have all of the Icom Live radios, I'm going to disagree on the level of risk. :-) How will you test?
Anyway, as you said, we're all entitled to our opinions. We've expressed ours, but of course the decisions belong to Dan. :-)
Martin.
KD6YAM
Personally, I would prefer to not see this patch applied at least until we understand why it might be helpful to the upcoming changes for #4547, since I just don't see the justification right now.
Martin.
KD6YAM
# HG changeset patch
# User Kosta A. <ve7kcy@gmail.com>
# Date 1619800737 25200
# Fri Apr 30 09:38:57 2021 -0700
# Node ID 941a95de942d7c263a6197bf115cc75bc3624de9
# Parent cd3e2444040876b4a19b41c6cfecedb79ff4a8fe
[icomciv] Restructuring of IcomCIVRadio radio classes; breakout each radio into its own module in preparetion for simplifying support for upcomming CIV feature #4547.
diff --git a/chirp/drivers/ic7000.py b/chirp/drivers/ic7000.py
new file mode 100644
--- /dev/null
+++ b/chirp/drivers/ic7000.py
@@ -0,0 +1,84 @@
+# Copyright 2008 Dan Smith <dsmith@danplanet.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
+# the Free Software Foundation, either version 3 of the License, or
+# (at your option) any later version.
+#
+# This program is distributed in the hope that it will be useful,
+# but WITHOUT ANY WARRANTY; without even the implied warranty of
+# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+# GNU General Public License for more details.
+#
+# You should have received a copy of the GNU General Public License
+# along with this program. If not, see <http://www.gnu.org/licenses/>.
+
+import struct
+import logging
+
+from chirp import chirp_common, errors, directory
+from chirp.drivers import icf, icomciv
+
+LOG = logging.getLogger(__name__)
+
+# http://www.vk4adc.com/
+# web/index.php/reference-information/49-general-ref-info/182-civ7400
+MEM_IC7000_FORMAT = """
+u8 bank;
+bbcd number[2];
+u8 spl:4,
+ skip:4;
+lbcd freq[5];
+u8 mode;
+u8 filter;
+u8 duplex:4,
+ tmode:4;
+bbcd rtone[3];
+bbcd ctone[3];
+u8 dtcs_polarity;
+bbcd dtcs[2];
+lbcd freq_tx[5];
+u8 mode_tx;
+u8 filter_tx;
+u8 duplex_tx:4,
+ tmode_tx:4;
+bbcd rtone_tx[3];
+bbcd ctone_tx[3];
+u8 dtcs_polarity_tx;
+bbcd dtcs_tx[2];
+char name[9];
+"""
+
+class IC7000MemFrame(icomciv.BankMemFrame):
+ FORMAT = MEM_IC7000_FORMAT
+
+@directory.register
+class Icom7000Radio(icomciv.IcomCIVRadio):
+ """Icom IC-7000"""
+ MODEL = "IC-7000"
+ _model = "\x70"
+ _template = 102
+
+ _num_banks = 5 # Banks A-E
+ _bank_index_bounds = (1, 99)
+ _bank_class = icf.IcomBank
+
+ def _initialize(self):
+ self._classes["mem"] = IC7000MemFrame
+ self._rf.has_bank = True
+ self._rf.has_dtcs_polarity = True
+ self._rf.has_dtcs = True
+ self._rf.has_ctone = True
+ self._rf.has_offset = True
+ self._rf.has_name = True
+ self._rf.has_tuning_step = False
+ self._rf.valid_modes = ["LSB", "USB", "AM", "CW", "RTTY", "FM", "WFM"]
+ self._rf.valid_tmodes = ["", "Tone", "TSQL", "DTCS"]
+ self._rf.valid_duplexes = ["", "-", "+", "split"]
+ self._rf.valid_bands = [(30000, 199999999), (400000000, 470000000)]
+ self._rf.valid_tuning_steps = []
+ self._rf.valid_skips = ["S", ""]
+ self._rf.valid_name_length = 9
+ self._rf.valid_characters = chirp_common.CHARSET_ASCII
+ self._rf.memory_bounds = (0, 99 * self._num_banks - 1)
+ self._rf.can_odd_split = True
diff --git a/chirp/drivers/ic7100.py b/chirp/drivers/ic7100.py
new file mode 100644
--- /dev/null
+++ b/chirp/drivers/ic7100.py
@@ -0,0 +1,88 @@
+# Copyright 2008 Dan Smith <dsmith@danplanet.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
+# the Free Software Foundation, either version 3 of the License, or
+# (at your option) any later version.
+#
+# This program is distributed in the hope that it will be useful,
+# but WITHOUT ANY WARRANTY; without even the implied warranty of
+# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+# GNU General Public License for more details.
+#
+# You should have received a copy of the GNU General Public License
+# along with this program. If not, see <http://www.gnu.org/licenses/>.
+
+import struct
+import logging
+
+from chirp import chirp_common, errors, directory
+from chirp.drivers import icf, icomciv
+
+LOG = logging.getLogger(__name__)
+
+
+MEM_IC7100_FORMAT = """
+u8 bank; // 1 bank number
+bbcd number[2]; // 2,3
+u8 splitSelect; // 4 split and select memory settings
+lbcd freq[5]; // 5-9 operating freq
+u8 mode; // 10 operating mode
+u8 filter; // 11 filter
+u8 dataMode; // 12 data mode setting (on or off)
+u8 duplex:4, // 13 duplex on/-/+
+ tmode:4; // 13 tone
+u8 dsql:4, // 14 digital squelch
+ unknown1:4; // 14 zero
+bbcd rtone[3]; // 15-17 repeater tone freq
+bbcd ctone[3]; // 18-20 tone squelch setting
+u8 dtcsPolarity; // 21 DTCS polarity
+u8 unknown2:4, // 22 zero
+ firstDtcs:4; // 22 first digit of DTCS code
+u8 secondDtcs:4, // 23 second digit DTCS
+ thirdDtcs:4; // 23 third digit DTCS
+u8 digitalSquelch; // 24 Digital code squelch setting
+lbcd duplexOffset[3]; // 25-27 duplex offset freq
+char destCall[8]; // 28-35 destination call sign
+char accessRepeaterCall[8];// 36-43 access repeater call sign
+char linkRepeaterCall[8]; // 44-51 gateway/link repeater call sign
+bbcd duplexSettings[47]; // repeat of 5-51 for duplex
+char name[16]; // 52-60 Name of station
+"""
+
+class IC7100MemFrame(icomciv.BankMemFrame):
+ FORMAT = MEM_IC7100_FORMAT
+
+@directory.register
+class Icom7100Radio(icomciv.IcomCIVRadio):
+ """Icom IC-7100"""
+ MODEL = "IC-7100"
+ _model = "\x88"
+ _template = 102
+
+ _num_banks = 5
+ _bank_index_bounds = (1, 99)
+ _bank_class = icf.IcomBank
+
+ def _initialize(self):
+ self._classes["mem"] = IC7100MemFrame
+ self._rf.has_bank = True
+ self._rf.has_bank_index = False
+ self._rf.has_bank_names = False
+ self._rf.has_dtcs_polarity = False
+ self._rf.has_dtcs = False
+ self._rf.has_ctone = True
+ self._rf.has_offset = False
+ self._rf.has_name = True
+ self._rf.has_tuning_step = False
+ self._rf.valid_modes = [
+ "LSB", "USB", "AM", "CW", "RTTY", "FM", "WFM", "CWR", "RTTYR", "DV"
+ ]
+ self._rf.valid_tmodes = ["", "Tone", "TSQL", "DTCS"]
+ self._rf.valid_duplexes = ["", "-", "+"]
+ self._rf.valid_bands = [(30000, 199999999), (400000000, 470000000)]
+ self._rf.valid_tuning_steps = []
+ self._rf.valid_skips = []
+ self._rf.valid_name_length = 16
+ self._rf.valid_characters = chirp_common.CHARSET_ASCII
+ self._rf.memory_bounds = (0, 99 * self._num_banks - 1)
diff --git a/chirp/drivers/ic7200.py b/chirp/drivers/ic7200.py
new file mode 100644
--- /dev/null
+++ b/chirp/drivers/ic7200.py
@@ -0,0 +1,48 @@
+# Copyright 2008 Dan Smith <dsmith@danplanet.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
+# the Free Software Foundation, either version 3 of the License, or
+# (at your option) any later version.
+#
+# This program is distributed in the hope that it will be useful,
+# but WITHOUT ANY WARRANTY; without even the implied warranty of
+# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+# GNU General Public License for more details.
+#
+# You should have received a copy of the GNU General Public License
+# along with this program. If not, see <http://www.gnu.org/licenses/>.
+
+import struct
+import logging
+
+from chirp import chirp_common, errors, directory
+from chirp.drivers import icf, icomciv
+
+LOG = logging.getLogger(__name__)
+
+
+@directory.register
+class Icom7200Radio(icomciv.IcomCIVRadio):
+ """Icom IC-7200"""
+ MODEL = "7200"
+ _model = "\x76"
+ _template = 201
+
+ _num_banks = 1 # Banks not supported
+
+ def _initialize(self):
+ self._rf.has_bank = False
+ self._rf.has_dtcs_polarity = False
+ self._rf.has_dtcs = False
+ self._rf.has_ctone = False
+ self._rf.has_offset = False
+ self._rf.has_name = False
+ self._rf.has_tuning_step = False
+ self._rf.valid_modes = ["LSB", "USB", "AM", "CW", "RTTY",
+ "CWR", "RTTYR"]
+ self._rf.valid_tmodes = []
+ self._rf.valid_duplexes = []
+ self._rf.valid_bands = [(30000, 60000000)]
+ self._rf.valid_skips = []
+ self._rf.memory_bounds = (1, 201)
diff --git a/chirp/drivers/ic7300.py b/chirp/drivers/ic7300.py
new file mode 100644
--- /dev/null
+++ b/chirp/drivers/ic7300.py
@@ -0,0 +1,103 @@
+# Copyright 2008 Dan Smith <dsmith@danplanet.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
+# the Free Software Foundation, either version 3 of the License, or
+# (at your option) any later version.
+#
+# This program is distributed in the hope that it will be useful,
+# but WITHOUT ANY WARRANTY; without even the implied warranty of
+# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+# GNU General Public License for more details.
+#
+# You should have received a copy of the GNU General Public License
+# along with this program. If not, see <http://www.gnu.org/licenses/>.
+
+import struct
+import logging
+
+from chirp import chirp_common, errors, directory
+from chirp.drivers import icf, icomciv
+
+LOG = logging.getLogger(__name__)
+
+
+MEM_IC7300_FORMAT = """
+bbcd number[2]; // 1,2
+u8 spl:4, // 3 split and select memory settings
+ select:4;
+lbcd freq[5]; // 4-8 receive freq
+u8 mode; // 9 operating mode
+u8 filter; // 10 filter 1-3 (undocumented)
+u8 dataMode:4, // 11 data mode setting (on or off)
+ tmode:4; // 11 tone type
+char pad1;
+bbcd rtone[2]; // 12-14 tx tone freq
+char pad2;
+bbcd ctone[2]; // 15-17 tone rx squelch setting
+lbcd freq_tx[5]; // 4-8 transmit freq
+u8 mode_tx; // 9 tx operating mode
+u8 filter_tx; // 10
+u8 dataMode_tx:4, // 11 tx data mode setting (on or off)
+ tmode_tx:4; // 11 tx tone type
+char pad3;
+bbcd rtone_tx[2]; // 12-14 repeater tone freq
+char pad4;
+bbcd ctone_tx[2]; // 15-17 tone squelch setting
+char name[10]; // 18-27 Callsign
+"""
+
+class IC7300MemFrame(icomciv.MemFrame):
+ FORMAT = MEM_IC7300_FORMAT
+
+@directory.register
+class Icom7300Radio(icomciv.IcomCIVRadio):
+ """Icom IC-7300"""
+ MODEL = "IC-7300"
+ _model = "\x94"
+ _template = 100 # Use P1 as blank template
+
+ _SPECIAL_CHANNELS = {
+ "P1": 100,
+ "P2": 101,
+ }
+ _SPECIAL_CHANNELS_REV = dict(zip(_SPECIAL_CHANNELS.values(),
+ _SPECIAL_CHANNELS.keys()))
+
+ def _is_special(self, number):
+ return number > 99 or isinstance(number, str)
+
+ def _get_special_info(self, number):
+ info = SpecialChannel()
+ if isinstance(number, str):
+ info.name = number
+ info.channel = self._SPECIAL_CHANNELS[number]
+ info.location = info.channel
+ else:
+ info.location = number
+ info.name = self._SPECIAL_CHANNELS_REV[number]
+ info.channel = info.location
+ return info
+
+ def _initialize(self):
+ self._classes["mem"] = IC7300MemFrame
+ self._rf.has_name = True
+ self._rf.has_dtcs = False
+ self._rf.has_dtcs_polarity = False
+ self._rf.has_bank = False
+ self._rf.has_tuning_step = False
+ self._rf.has_nostep_tuning = True
+ self._rf.can_odd_split = True
+ self._rf.memory_bounds = (1, 99)
+ self._rf.valid_modes = [
+ "LSB", "USB", "AM", "CW", "RTTY", "FM", "CWR", "RTTYR",
+ "Data+LSB", "Data+USB", "Data+AM", "N/A", "N/A", "Data+FM"
+ ]
+ self._rf.valid_tmodes = ["", "Tone", "TSQL"]
+ # self._rf.valid_duplexes = ["", "-", "+", "split"]
+ self._rf.valid_duplexes = [] # To prevent using memobj.duplex
+ self._rf.valid_bands = [(1800000, 70500000)]
+ self._rf.valid_skips = []
+ self._rf.valid_name_length = 10
+ self._rf.valid_characters = chirp_common.CHARSET_ASCII
+ self._rf.valid_special_chans = sorted(self._SPECIAL_CHANNELS.keys())
diff --git a/chirp/drivers/ic746.py b/chirp/drivers/ic746.py
new file mode 100644
--- /dev/null
+++ b/chirp/drivers/ic746.py
@@ -0,0 +1,76 @@
+# Copyright 2008 Dan Smith <dsmith@danplanet.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
+# the Free Software Foundation, either version 3 of the License, or
+# (at your option) any later version.
+#
+# This program is distributed in the hope that it will be useful,
+# but WITHOUT ANY WARRANTY; without even the implied warranty of
+# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+# GNU General Public License for more details.
+#
+# You should have received a copy of the GNU General Public License
+# along with this program. If not, see <http://www.gnu.org/licenses/>.
+
+import struct
+import logging
+
+from chirp import chirp_common, errors, directory
+from chirp.drivers import icf, icomciv
+
+LOG = logging.getLogger(__name__)
+
+
+MEM_IC746_FORMAT = """
+bbcd number[2];
+u8 unknown1;
+lbcd freq[5];
+u8 unknown2:5,
+ mode:3;
+u8 unknown1;
+u8 unknown2:2,
+ duplex:2,
+ unknown3:1,
+ tmode:3;
+u8 unknown4;
+bbcd rtone[2];
+u8 unknown5;
+bbcd ctone[2];
+u8 dtcs_polarity;
+bbcd dtcs[2];
+u8 unknown[11];
+char name[9];
+"""
+
+class IC746MemFrame(icomciv.MemFrame):
+ FORMAT = MEM_IC746_FORMAT
+
+@directory.register
+class Icom746Radio(icomciv.IcomCIVRadio):
+ """Icom IC-746"""
+ MODEL = "746"
+ BAUD_RATE = 9600
+ _model = "\x56"
+ _template = 102
+
+ _num_banks = 1 # Banks not supported
+
+ def _initialize(self):
+ self._classes["mem"] = IC746MemFrame
+ self._rf.has_bank = False
+ self._rf.has_dtcs_polarity = False
+ self._rf.has_dtcs = False
+ self._rf.has_ctone = True
+ self._rf.has_offset = False
+ self._rf.has_name = True
+ self._rf.has_tuning_step = False
+ self._rf.valid_modes = ["LSB", "USB", "AM", "CW", "RTTY", "FM"]
+ self._rf.valid_tmodes = ["", "Tone", "TSQL"]
+ self._rf.valid_duplexes = ["", "-", "+"]
+ self._rf.valid_bands = [(30000, 199999999)]
+ self._rf.valid_tuning_steps = []
+ self._rf.valid_skips = []
+ self._rf.valid_name_length = 9
+ self._rf.valid_characters = chirp_common.CHARSET_ASCII
+ self._rf.memory_bounds = (1, 99)
diff --git a/chirp/drivers/ic910.py b/chirp/drivers/ic910.py
new file mode 100644
--- /dev/null
+++ b/chirp/drivers/ic910.py
@@ -0,0 +1,142 @@
+# Copyright 2008 Dan Smith <dsmith@danplanet.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
+# the Free Software Foundation, either version 3 of the License, or
+# (at your option) any later version.
+#
+# This program is distributed in the hope that it will be useful,
+# but WITHOUT ANY WARRANTY; without even the implied warranty of
+# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+# GNU General Public License for more details.
+#
+# You should have received a copy of the GNU General Public License
+# along with this program. If not, see <http://www.gnu.org/licenses/>.
+
+import struct
+import logging
+
+from chirp import chirp_common, errors, directory
+from chirp.drivers import icf, icomciv
+
+LOG = logging.getLogger(__name__)
+
+
+MEM_IC910_FORMAT = """
+u8 bank; // 1 bank number
+bbcd number[2]; // 2,3
+lbcd freq[5]; // 4-8 operating freq
+u8 mode; // 9 operating mode
+u8 filter; // 10 filter
+u8 tmode:4, // 11 tone
+ duplex:4; // 11 duplex off/-/+
+bbcd rtone[3]; // 12-14 repeater tone freq
+bbcd ctone[3]; // 15-17 tone squelch setting
+lbcd duplexOffset[3]; // 18-20 duplex offset freq
+"""
+
+class IC910MemFrame(icomciv.BankMemFrame):
+ FORMAT = MEM_IC910_FORMAT
+
+@directory.register
+class Icom910Radio(icomciv.IcomCIVRadio):
+ """Icom IC-910"""
+ MODEL = "IC-910"
+ BAUD_RATE = 19200
+ _model = "\x60"
+ _template = 100
+
+ _num_banks = 3 # Banks for 2m, 70cm, 23cm
+ _bank_index_bounds = (1, 99)
+ _bank_class = icf.IcomBank
+
+ _SPECIAL_CHANNELS = {
+ "1A": 100,
+ "1b": 101,
+ "2A": 102,
+ "2b": 103,
+ "3A": 104,
+ "3b": 105,
+ "C": 106,
+ }
+ _SPECIAL_CHANNELS_REV = {v: k for k, v in _SPECIAL_CHANNELS.items()}
+
+ _SPECIAL_BANKS = {
+ "2m": 1,
+ "70cm": 2,
+ "23cm": 3,
+ }
+ _SPECIAL_BANKS_REV = {v: k for k, v in _SPECIAL_BANKS.items()}
+
+ def _get_special_names(self, band):
+ return sorted([band + "-" + key
+ for key in self._SPECIAL_CHANNELS.keys()])
+
+ def _is_special(self, number):
+ return number >= 1000 or isinstance(number, str)
+
+ def _get_special_info(self, number):
+ info = BankSpecialChannel()
+ if isinstance(number, str):
+ info.name = number
+ (band_name, chan_name) = number.split("-")
+ info.bank = self._SPECIAL_BANKS[band_name]
+ info.channel = self._SPECIAL_CHANNELS[chan_name]
+ info.location = info.bank * 1000 + info.channel
+ else:
+ info.location = number
+ (info.bank, info.channel) = divmod(number, 1000)
+ band_name = self._SPECIAL_BANKS_REV[info.bank]
+ chan_name = self._SPECIAL_CHANNELS_REV[info.channel]
+ info.name = band_name + "-" + chan_name
+ return info
+
+ # The IC-910 has a bank of memories for each band. The 23cm band is only
+ # available when the optional UX-910 unit is installed, but there is no
+ # direct means of detecting its presence. Instead, attempt to access the
+ # first memory in the 23cm bank. If that's successful, the unit is there,
+ # and we can present all 3 banks to the user. Otherwise, the unit is not
+ # installed, so we present 2 banks to the user, for 2m and 70cm.
+ def _detect_23cm_unit(self):
+ if not self.pipe:
+ return True
+ f = IC910MemFrame()
+ f.set_location(1, 3) # First memory in 23cm bank
+ self._send_frame(f)
+ f.read(self.pipe)
+ if f._cmd == 0xFA: # Error code lands in command field
+ self._num_banks = 2
+ LOG.debug("UX-910 unit is %sinstalled" %
+ ("not " if self._num_banks == 2 else ""))
+ return self._num_banks == 3
+
+ def _initialize(self):
+ self._classes["mem"] = IC910MemFrame
+ self._has_23cm_unit = self._detect_23cm_unit()
+ self._rf.has_bank = True
+ self._rf.has_dtcs_polarity = False
+ self._rf.has_dtcs = False
+ self._rf.has_ctone = True
+ self._rf.has_offset = True
+ self._rf.has_name = False
+ self._rf.has_tuning_step = False
+ self._rf.valid_modes = ["LSB", "USB", "CW", "NCW", "FM", "NFM"]
+ self._rf.valid_tmodes = ["", "Tone", "TSQL"]
+ self._rf.valid_duplexes = ["", "-", "+"]
+ self._rf.valid_bands = [(136000000, 174000000),
+ (420000000, 480000000)]
+ self._rf.valid_tuning_steps = []
+ self._rf.valid_skips = []
+ self._rf.valid_special_chans = (self._get_special_names("2m") +
+ self._get_special_names("70cm"))
+ self._rf.memory_bounds = (1, 99 * self._num_banks)
+
+ if self._has_23cm_unit:
+ self._rf.valid_bands.append((1240000000, 1320000000))
+ self._rf.valid_special_chans += self._get_special_names("23cm")
+
+ # Combine mode and filter into unified mode
+ self._unified_modes = True
+
+ # Use Chirp locations starting with 1
+ self._adjust_bank_loc_start = True
diff --git a/chirp/drivers/icomciv.py b/chirp/drivers/icomciv.py
--- a/chirp/drivers/icomciv.py
+++ b/chirp/drivers/icomciv.py
@@ -1,6 +1,21 @@
-# Latest update: March, 2021 RJ DeWitt added IC-7300
+# Copyright 2008 Dan Smith <dsmith@danplanet.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
+# the Free Software Foundation, either version 3 of the License, or
+# (at your option) any later version.
+#
+# This program is distributed in the hope that it will be useful,
+# but WITHOUT ANY WARRANTY; without even the implied warranty of
+# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+# GNU General Public License for more details.
+#
+# You should have received a copy of the GNU General Public License
+# along with this program. If not, see <http://www.gnu.org/licenses/>.
+
import struct
import logging
+
from chirp.drivers import icf
from chirp import chirp_common, util, errors, bitwise, directory
from chirp.memmap import MemoryMap
@@ -23,122 +38,6 @@
unknown_2:4;
"""
-
-# http://www.vk4adc.com/
-# web/index.php/reference-information/49-general-ref-info/182-civ7400
-MEM_IC7000_FORMAT = """
-u8 bank;
-bbcd number[2];
-u8 spl:4,
- skip:4;
-lbcd freq[5];
-u8 mode;
-u8 filter;
-u8 duplex:4,
- tmode:4;
-bbcd rtone[3];
-bbcd ctone[3];
-u8 dtcs_polarity;
-bbcd dtcs[2];
-lbcd freq_tx[5];
-u8 mode_tx;
-u8 filter_tx;
-u8 duplex_tx:4,
- tmode_tx:4;
-bbcd rtone_tx[3];
-bbcd ctone_tx[3];
-u8 dtcs_polarity_tx;
-bbcd dtcs_tx[2];
-char name[9];
-"""
-
-MEM_IC7100_FORMAT = """
-u8 bank; // 1 bank number
-bbcd number[2]; // 2,3
-u8 splitSelect; // 4 split and select memory settings
-lbcd freq[5]; // 5-9 operating freq
-u8 mode; // 10 operating mode
-u8 filter; // 11 filter
-u8 dataMode; // 12 data mode setting (on or off)
-u8 duplex:4, // 13 duplex on/-/+
- tmode:4; // 13 tone
-u8 dsql:4, // 14 digital squelch
- unknown1:4; // 14 zero
-bbcd rtone[3]; // 15-17 repeater tone freq
-bbcd ctone[3]; // 18-20 tone squelch setting
-u8 dtcsPolarity; // 21 DTCS polarity
-u8 unknown2:4, // 22 zero
- firstDtcs:4; // 22 first digit of DTCS code
-u8 secondDtcs:4, // 23 second digit DTCS
- thirdDtcs:4; // 23 third digit DTCS
-u8 digitalSquelch; // 24 Digital code squelch setting
-lbcd duplexOffset[3]; // 25-27 duplex offset freq
-char destCall[8]; // 28-35 destination call sign
-char accessRepeaterCall[8];// 36-43 access repeater call sign
-char linkRepeaterCall[8]; // 44-51 gateway/link repeater call sign
-bbcd duplexSettings[47]; // repeat of 5-51 for duplex
-char name[16]; // 52-60 Name of station
-"""
-
-MEM_IC910_FORMAT = """
-u8 bank; // 1 bank number
-bbcd number[2]; // 2,3
-lbcd freq[5]; // 4-8 operating freq
-u8 mode; // 9 operating mode
-u8 filter; // 10 filter
-u8 tmode:4, // 11 tone
- duplex:4; // 11 duplex off/-/+
-bbcd rtone[3]; // 12-14 repeater tone freq
-bbcd ctone[3]; // 15-17 tone squelch setting
-lbcd duplexOffset[3]; // 18-20 duplex offset freq
-"""
-
-mem_duptone_format = """
-bbcd number[2];
-u8 unknown1;
-lbcd freq[5];
-u8 unknown2:5,
- mode:3;
-u8 unknown1;
-u8 unknown2:2,
- duplex:2,
- unknown3:1,
- tmode:3;
-u8 unknown4;
-bbcd rtone[2];
-u8 unknown5;
-bbcd ctone[2];
-u8 dtcs_polarity;
-bbcd dtcs[2];
-u8 unknown[11];
-char name[9];
-"""
-
-MEM_IC7300_FORMAT = """
-bbcd number[2]; // 1,2
-u8 spl:4, // 3 split and select memory settings
- select:4;
-lbcd freq[5]; // 4-8 receive freq
-u8 mode; // 9 operating mode
-u8 filter; // 10 filter 1-3 (undocumented)
-u8 dataMode:4, // 11 data mode setting (on or off)
- tmode:4; // 11 tone type
-char pad1;
-bbcd rtone[2]; // 12-14 tx tone freq
-char pad2;
-bbcd ctone[2]; // 15-17 tone rx squelch setting
-lbcd freq_tx[5]; // 4-8 transmit freq
-u8 mode_tx; // 9 tx operating mode
-u8 filter_tx; // 10
-u8 dataMode_tx:4, // 11 tx data mode setting (on or off)
- tmode_tx:4; // 11 tx tone type
-char pad3;
-bbcd rtone_tx[2]; // 12-14 repeater tone freq
-char pad4;
-bbcd ctone_tx[2]; // 15-17 tone squelch setting
-char name[10]; // 18-27 Callsign
-"""
-
SPLIT = ["", "spl"]
@@ -207,6 +106,7 @@
class MemFrame(Frame):
"""A memory frame"""
+ FORMAT = MEM_FORMAT
_cmd = 0x1A
_sub = 0x00
_loc = 0
@@ -227,7 +127,7 @@
def get_obj(self):
"""Return a bitwise parsed object"""
self._data = MemoryMap(str(self._data)) # Make sure we're assignable
- return bitwise.parse(MEM_FORMAT, self._data)
+ return bitwise.parse(self.FORMAT, self._data)
def initialize(self):
"""Initialize to sane values"""
@@ -236,7 +136,7 @@
class BankMemFrame(MemFrame):
"""A memory frame for radios with multiple banks"""
- FORMAT = MEM_IC7000_FORMAT
+ FORMAT = None
_bnk = 0
def set_location(self, loc, bank=1):
@@ -256,28 +156,6 @@
return bitwise.parse(self.FORMAT, self._data)
-class IC7100MemFrame(BankMemFrame):
- FORMAT = MEM_IC7100_FORMAT
-
-
-class IC910MemFrame(BankMemFrame):
- FORMAT = MEM_IC910_FORMAT
-
-
-class DupToneMemFrame(MemFrame):
- def get_obj(self):
- self._data = MemoryMap(str(self._data))
- return bitwise.parse(mem_duptone_format, self._data)
-
-
-class IC7300MemFrame(MemFrame):
- FORMAT = MEM_IC7300_FORMAT
-
- def get_obj(self):
- self._data = MemoryMap(str(self._data))
- return bitwise.parse(self.FORMAT, self._data)
-
-
class SpecialChannel(object):
"""Info for special (named) channels"""
@@ -442,10 +320,10 @@
else:
return repr(f.get_obj())
-# We have a simple mapping between the memory location in the frequency
-# editor and (bank, channel) of the radio. The mapping doesn't
-# change so we use a little math to calculate what bank a location
-# is in. We can't change the bank a location is in so we just pass.
+ # We have a simple mapping between the memory location in the frequency
+ # editor and (bank, channel) of the radio. The mapping doesn't
+ # change so we use a little math to calculate what bank a location
+ # is in. We can't change the bank a location is in so we just pass.
def _get_bank(self, loc):
if self._adjust_bank_loc_start:
loc -= 1
@@ -562,6 +440,8 @@
if self._rf.can_odd_split and memobj.spl:
mem.duplex = "split"
+ if hasattr(memobj, "duplex"):
+ mem.duplex = "split"
mem.offset = int(memobj.freq_tx)
mem.immutable = []
elif hasattr(memobj, "duplexOffset"):
@@ -620,10 +500,10 @@
f.make_empty()
self._send_frame(f)
-# The next two lines accept the radio's status after setting the memory
-# and reports the results to the debug log. This is needed for the
-# IC-7000. No testing was done to see if it breaks memory delete on the
-# IC-746 or IC-7200.
+ # The next two lines accept the radio's status after setting the memory
+ # and reports the results to the debug log. This is needed for the
+ # IC-7000. No testing was done to see if it breaks memory delete on the
+ # IC-746 or IC-7200.
f = self._recv_frame()
LOG.debug("Result:\n%s" % util.hexprint(f.get_data()))
return
@@ -708,311 +588,28 @@
LOG.debug("Result:\n%s" % util.hexprint(f.get_data()))
-@directory.register
-class Icom7200Radio(IcomCIVRadio):
- """Icom IC-7200"""
- MODEL = "7200"
- _model = "\x76"
- _template = 201
-
- _num_banks = 1 # Banks not supported
-
- def _initialize(self):
- self._rf.has_bank = False
- self._rf.has_dtcs_polarity = False
- self._rf.has_dtcs = False
- self._rf.has_ctone = False
- self._rf.has_offset = False
- self._rf.has_name = False
- self._rf.has_tuning_step = False
- self._rf.valid_modes = ["LSB", "USB", "AM", "CW", "RTTY",
- "CWR", "RTTYR"]
- self._rf.valid_tmodes = []
- self._rf.valid_duplexes = []
- self._rf.valid_bands = [(30000, 60000000)]
- self._rf.valid_skips = []
- self._rf.memory_bounds = (1, 201)
-
-
-@directory.register
-class Icom7000Radio(IcomCIVRadio):
- """Icom IC-7000"""
- MODEL = "IC-7000"
- _model = "\x70"
- _template = 102
-
- _num_banks = 5 # Banks A-E
- _bank_index_bounds = (1, 99)
- _bank_class = icf.IcomBank
-
- def _initialize(self):
- self._classes["mem"] = BankMemFrame
- self._rf.has_bank = True
- self._rf.has_dtcs_polarity = True
- self._rf.has_dtcs = True
- self._rf.has_ctone = True
- self._rf.has_offset = True
- self._rf.has_name = True
- self._rf.has_tuning_step = False
- self._rf.valid_modes = ["LSB", "USB", "AM", "CW", "RTTY", "FM", "WFM"]
- self._rf.valid_tmodes = ["", "Tone", "TSQL", "DTCS"]
- self._rf.valid_duplexes = ["", "-", "+", "split"]
- self._rf.valid_bands = [(30000, 199999999), (400000000, 470000000)]
- self._rf.valid_tuning_steps = []
- self._rf.valid_skips = ["S", ""]
- self._rf.valid_name_length = 9
- self._rf.valid_characters = chirp_common.CHARSET_ASCII
- self._rf.memory_bounds = (0, 99 * self._num_banks - 1)
- self._rf.can_odd_split = True
-
-
-@directory.register
-class Icom7100Radio(IcomCIVRadio):
- """Icom IC-7100"""
- MODEL = "IC-7100"
- _model = "\x88"
- _template = 102
-
- _num_banks = 5
- _bank_index_bounds = (1, 99)
- _bank_class = icf.IcomBank
-
- def _initialize(self):
- self._classes["mem"] = IC7100MemFrame
- self._rf.has_bank = True
- self._rf.has_bank_index = False
- self._rf.has_bank_names = False
- self._rf.has_dtcs_polarity = False
- self._rf.has_dtcs = False
- self._rf.has_ctone = True
- self._rf.has_offset = False
- self._rf.has_name = True
- self._rf.has_tuning_step = False
- self._rf.valid_modes = [
- "LSB", "USB", "AM", "CW", "RTTY", "FM", "WFM", "CWR", "RTTYR", "DV"
- ]
- self._rf.valid_tmodes = ["", "Tone", "TSQL", "DTCS"]
- self._rf.valid_duplexes = ["", "-", "+"]
- self._rf.valid_bands = [(30000, 199999999), (400000000, 470000000)]
- self._rf.valid_tuning_steps = []
- self._rf.valid_skips = []
- self._rf.valid_name_length = 16
- self._rf.valid_characters = chirp_common.CHARSET_ASCII
- self._rf.memory_bounds = (0, 99 * self._num_banks - 1)
-
-
-@directory.register
-class Icom746Radio(IcomCIVRadio):
- """Icom IC-746"""
- MODEL = "746"
- BAUD_RATE = 9600
- _model = "\x56"
- _template = 102
-
- _num_banks = 1 # Banks not supported
-
- def _initialize(self):
- self._classes["mem"] = DupToneMemFrame
- self._rf.has_bank = False
- self._rf.has_dtcs_polarity = False
- self._rf.has_dtcs = False
- self._rf.has_ctone = True
- self._rf.has_offset = False
- self._rf.has_name = True
- self._rf.has_tuning_step = False
- self._rf.valid_modes = ["LSB", "USB", "AM", "CW", "RTTY", "FM"]
- self._rf.valid_tmodes = ["", "Tone", "TSQL"]
- self._rf.valid_duplexes = ["", "-", "+"]
- self._rf.valid_bands = [(30000, 199999999)]
- self._rf.valid_tuning_steps = []
- self._rf.valid_skips = []
- self._rf.valid_name_length = 9
- self._rf.valid_characters = chirp_common.CHARSET_ASCII
- self._rf.memory_bounds = (1, 99)
-
-
-@directory.register
-class Icom910Radio(IcomCIVRadio):
- """Icom IC-910"""
- MODEL = "IC-910"
- BAUD_RATE = 19200
- _model = "\x60"
- _template = 100
-
- _num_banks = 3 # Banks for 2m, 70cm, 23cm
- _bank_index_bounds = (1, 99)
- _bank_class = icf.IcomBank
-
- _SPECIAL_CHANNELS = {
- "1A": 100,
- "1b": 101,
- "2A": 102,
- "2b": 103,
- "3A": 104,
- "3b": 105,
- "C": 106,
- }
- _SPECIAL_CHANNELS_REV = {v: k for k, v in _SPECIAL_CHANNELS.items()}
-
- _SPECIAL_BANKS = {
- "2m": 1,
- "70cm": 2,
- "23cm": 3,
- }
- _SPECIAL_BANKS_REV = {v: k for k, v in _SPECIAL_BANKS.items()}
-
- def _get_special_names(self, band):
- return sorted([band + "-" + key
- for key in self._SPECIAL_CHANNELS.keys()])
-
- def _is_special(self, number):
- return number >= 1000 or isinstance(number, str)
-
- def _get_special_info(self, number):
- info = BankSpecialChannel()
- if isinstance(number, str):
- info.name = number
- (band_name, chan_name) = number.split("-")
- info.bank = self._SPECIAL_BANKS[band_name]
- info.channel = self._SPECIAL_CHANNELS[chan_name]
- info.location = info.bank * 1000 + info.channel
- else:
- info.location = number
- (info.bank, info.channel) = divmod(number, 1000)
- band_name = self._SPECIAL_BANKS_REV[info.bank]
- chan_name = self._SPECIAL_CHANNELS_REV[info.channel]
- info.name = band_name + "-" + chan_name
- return info
-
- # The IC-910 has a bank of memories for each band. The 23cm band is only
- # available when the optional UX-910 unit is installed, but there is no
- # direct means of detecting its presence. Instead, attempt to access the
- # first memory in the 23cm bank. If that's successful, the unit is there,
- # and we can present all 3 banks to the user. Otherwise, the unit is not
- # installed, so we present 2 banks to the user, for 2m and 70cm.
- def _detect_23cm_unit(self):
- if not self.pipe:
- return True
- f = IC910MemFrame()
- f.set_location(1, 3) # First memory in 23cm bank
- self._send_frame(f)
- f.read(self.pipe)
- if f._cmd == 0xFA: # Error code lands in command field
- self._num_banks = 2
- LOG.debug("UX-910 unit is %sinstalled" %
- ("not " if self._num_banks == 2 else ""))
- return self._num_banks == 3
-
- def _initialize(self):
- self._classes["mem"] = IC910MemFrame
- self._has_23cm_unit = self._detect_23cm_unit()
- self._rf.has_bank = True
- self._rf.has_dtcs_polarity = False
- self._rf.has_dtcs = False
- self._rf.has_ctone = True
- self._rf.has_offset = True
- self._rf.has_name = False
- self._rf.has_tuning_step = False
- self._rf.valid_modes = ["LSB", "USB", "CW", "NCW", "FM", "NFM"]
- self._rf.valid_tmodes = ["", "Tone", "TSQL"]
- self._rf.valid_duplexes = ["", "-", "+"]
- self._rf.valid_bands = [(136000000, 174000000),
- (420000000, 480000000)]
- self._rf.valid_tuning_steps = []
- self._rf.valid_skips = []
- self._rf.valid_special_chans = (self._get_special_names("2m") +
- self._get_special_names("70cm"))
- self._rf.memory_bounds = (1, 99 * self._num_banks)
-
- if self._has_23cm_unit:
- self._rf.valid_bands.append((1240000000, 1320000000))
- self._rf.valid_special_chans += self._get_special_names("23cm")
-
- # Combine mode and filter into unified mode
- self._unified_modes = True
-
- # Use Chirp locations starting with 1
- self._adjust_bank_loc_start = True
-
-
-@directory.register
-class Icom7300Radio(IcomCIVRadio): # Added March, 2021 by Rick DeWitt
- """Icom IC-7300"""
- MODEL = "IC-7300"
- _model = "\x94"
- _template = 100 # Use P1 as blank template
-
- _SPECIAL_CHANNELS = {
- "P1": 100,
- "P2": 101,
- }
- _SPECIAL_CHANNELS_REV = dict(zip(_SPECIAL_CHANNELS.values(),
- _SPECIAL_CHANNELS.keys()))
-
- def _is_special(self, number):
- return number > 99 or isinstance(number, str)
-
- def _get_special_info(self, number):
- info = SpecialChannel()
- if isinstance(number, str):
- info.name = number
- info.channel = self._SPECIAL_CHANNELS[number]
- info.location = info.channel
- else:
- info.location = number
- info.name = self._SPECIAL_CHANNELS_REV[number]
- info.channel = info.location
- return info
-
- def _initialize(self):
- self._classes["mem"] = IC7300MemFrame
- self._rf.has_name = True
- self._rf.has_dtcs = False
- self._rf.has_dtcs_polarity = False
- self._rf.has_bank = False
- self._rf.has_tuning_step = False
- self._rf.has_nostep_tuning = True
- self._rf.can_odd_split = True
- self._rf.memory_bounds = (1, 99)
- self._rf.valid_modes = [
- "LSB", "USB", "AM", "CW", "RTTY", "FM", "CWR", "RTTYR",
- "Data+LSB", "Data+USB", "Data+AM", "N/A", "N/A", "Data+FM"
- ]
- self._rf.valid_tmodes = ["", "Tone", "TSQL"]
- # self._rf.valid_duplexes = ["", "-", "+", "split"]
- self._rf.valid_duplexes = [] # To prevent using memobj.duplex
- self._rf.valid_bands = [(1800000, 70500000)]
- self._rf.valid_skips = []
- self._rf.valid_name_length = 10
- self._rf.valid_characters = chirp_common.CHARSET_ASCII
- self._rf.valid_special_chans = sorted(self._SPECIAL_CHANNELS.keys())
-
-
-CIV_MODELS = {
- (0x76, 0xE0): Icom7200Radio,
- (0x88, 0xE0): Icom7100Radio,
- (0x70, 0xE0): Icom7000Radio,
- (0x46, 0xE0): Icom746Radio,
- (0x60, 0xE0): Icom910Radio,
- (0x94, 0xE0): Icom7300Radio,
-}
-
-
def probe_model(ser):
"""Probe the radio attatched to @ser for its model"""
f = Frame()
f.set_command(0x19, 0x00)
- for model, controller in CIV_MODELS.keys():
- f.send(model, controller, ser)
+ models = {}
+ for rclass in directory.DRV_TO_RADIO.values():
+ if issubclass(rclass, IcomCIVRadio):
+ models[rclass.MODEL] = rclass
+
+ for rclass in models.values():
+ model = ord(rclass._model)
+ f.send(model, 0xE0, ser)
try:
f.read(ser)
except errors.RadioError:
continue
if len(f.get_data()) == 1:
- model = ord(f.get_data()[0])
- return CIV_MODELS[(model, controller)]
+ md = ord(f.get_data()[0])
+ if (md == model):
+ return rclass
if f.get_data():
LOG.debug("Got data, but not 1 byte:")
_______________________________________________
chirp_devel mailing list
chirp_devel@intrepid.danplanet.com
http://intrepid.danplanet.com/mailman/listinfo/chirp_devel
Developer docs: http://chirp.danplanet.com/projects/chirp/wiki/Developers