[chirp_devel] [PATCH] Add basic support for the Yaesu FT-8100

Eric Allen
Tue Jun 9 15:33:47 PDT 2015


# HG changeset patch
# User Eric Allen <eric at hackerengineer.net>
# Date 1433889222 25200
#      Tue Jun 09 15:33:42 2015 -0700
# Node ID 476b0a1f003596d3bbf56c6d8673a5f607cdaaf6
# Parent  25ce872ec0611c5916dc6f6ccb8576a838f8ae5b
Add basic support for the Yaesu FT-8100

This is somewhat incomplete, but it works well enough to get my FT-8100
programmed. I'd rather share it as-is then let it bitrot on my laptop, because
several users on the mailing list have asked about it.

diff -r 25ce872ec061 -r 476b0a1f0035 chirp/drivers/ft8100.py
--- /dev/null	Thu Jan 01 00:00:00 1970 +0000
+++ b/chirp/drivers/ft8100.py	Tue Jun 09 15:33:42 2015 -0700
@@ -0,0 +1,313 @@
+# Copyright 2010 Eric Allen <eric at hackerengineer.net>
+#
+# 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 time
+import os
+
+from chirp import chirp_common, yaesu_clone, directory
+from chirp import bitwise, errors
+
+TONES = chirp_common.OLD_TONES
+
+TMODES = ["", "Tone"]
+
+MODES = ['FM', 'AM']
+
+STEPS = [5.0, 10.0, 12.5, 15.0, 20.0, 25.0, 50.0]
+
+DUPLEX = ["", "-", "+", "split"]
+
+# "M" for masked memories, which are invisible until un-masked
+SKIPS = ["", "S", "M"]
+
+POWER_LEVELS_VHF = [chirp_common.PowerLevel("Low", watts=5),
+                    chirp_common.PowerLevel("Mid", watts=20),
+                    chirp_common.PowerLevel("High", watts=50)]
+
+POWER_LEVELS_UHF = [chirp_common.PowerLevel("Low", watts=5),
+                    chirp_common.PowerLevel("Mid", watts=20),
+                    chirp_common.PowerLevel("High", watts=35)]
+
+SPECIALS = {'1L': -1,
+            '1U': -2,
+            '2L': -3,
+            '2U': -4,
+            'Home': -5}
+
+MEM_FORMAT = """
+#seekto 0x{skips:X};
+u8 skips[13];
+
+#seekto 0x{enables:X};
+u8 enables[13];
+
+struct mem_struct {{
+    u8 unknown4:2,
+       baud9600:1,
+       am:1,
+       unknown4b:4;
+    u8 power:2,
+       duplex:2,
+       unknown1b:4;
+    u8 unknown2:1,
+       tone_enable:1,
+       tone:6;
+    bbcd freq[3];
+    bbcd offset[3];
+}};
+
+#seekto 0x{memories:X};
+struct mem_struct memory[99];
+"""
+
+
+ at directory.register
+class FT8100Radio(yaesu_clone.YaesuCloneModeRadio):
+    """Implementation for Yaesu FT-8100"""
+    MODEL = "FT-8100"
+
+    _memstart = 0
+    _memsize = 2968
+    _block_lengths = [10, 32, 114, 101, 101, 97, 128, 128, 128, 128, 128, 9, 9,
+                      9, 9, 9, 9, 9, 9, 9, 9, 9, 9, 9, 9, 9, 9, 9, 9, 9, 9, 9,
+                      9, 9, 9, 9, 9, 9, 9, 9, 9, 9, 9, 9, 9, 9, 9, 9, 9, 9, 9,
+                      9, 9, 9, 9, 9, 9, 9, 9, 9, 9, 9, 9, 9, 9, 9, 9, 9, 9, 9,
+                      9, 9, 9, 9, 9, 9, 9, 9, 9, 9, 9, 9, 9, 9, 9, 9, 9, 9, 9,
+                      9, 9, 9, 9, 9, 9, 9, 9, 9, 9, 9, 9, 9, 9, 9, 9, 9, 9, 9,
+                      9, 9, 9, 9, 9, 9, 9, 9, 9, 9, 9, 9, 9, 9, 9, 9, 9, 9, 9,
+                      9, 9, 9, 9, 9, 9, 9, 9, 9, 9, 9, 9, 9, 9, 9, 9, 9, 9, 9,
+                      9, 9, 9, 9, 9, 9, 9, 9, 9, 9, 9, 9, 9, 9, 9, 9, 9, 9, 9,
+                      9, 9, 9, 9, 9, 9, 9, 9, 9, 9, 9, 9, 9, 9, 9, 9, 9, 9, 9,
+                      9, 9, 9, 9, 9, 9, 9, 9, 9, 9, 9, 9, 9, 9, 9, 9, 9, 9, 9,
+                      9, 9, 9, 9, 9, 9, 9, 9, 9, 9, 9, 9, 9, 9, 9, 9, 1]
+
+    @classmethod
+    def match_model(cls, data, path):
+        if len(data) == cls._memsize and data[1:10] == '\x01\x01\x07\x08\x02\x01\x01\x00\x01':
+            return True
+
+        return False
+
+    def get_features(self):
+        rf = chirp_common.RadioFeatures()
+        rf.memory_bounds = (1, 99)
+        rf.has_ctone = False
+        rf.has_dtcs = False
+        rf.has_dtcs_polarity = False
+        rf.has_bank = False
+        rf.has_name = False
+
+        rf.valid_modes = list(MODES)
+        rf.valid_tmodes = list(TMODES)
+        rf.valid_duplexes = list(DUPLEX)
+        rf.valid_power_levels = POWER_LEVELS_VHF
+        rf.has_sub_devices = self.VARIANT == ''
+
+        rf.valid_tuning_steps = list(STEPS)
+
+        rf.valid_bands = [(110000000, 550000000),
+                          (750000000, 1300000000)]
+
+        rf.valid_skips = SKIPS
+
+        rf.can_odd_split = True
+
+        # TODO
+        #rf.valid_special_chans = SPECIALS.keys()
+
+        # TODO
+        #rf.has_tuning_step = False
+        return rf
+
+    def sync_in(self):
+        super(FT8100Radio, self).sync_in()
+        self.pipe.write(chr(yaesu_clone.CMD_ACK))
+        self.pipe.read(1)
+
+    def sync_out(self):
+        self.update_checksums()
+        return _clone_out(self)
+
+    def process_mmap(self):
+        if not self._memstart:
+            return
+
+        mem_format = MEM_FORMAT.format(memories=self._memstart,
+                                       skips=self._skipstart,
+                                       enables=self._enablestart
+                                       )
+
+        self._memobj = bitwise.parse(mem_format, self._mmap)
+
+    def get_sub_devices(self):
+        return [FT8100RadioVHF(self._mmap), FT8100RadioUHF(self._mmap)]
+
+    def get_memory(self, number):
+        bit, byte = self._bit_byte(number)
+
+        _mem = self._memobj.memory[number - 1]
+
+        mem = chirp_common.Memory()
+        mem.number = number
+
+        mem.freq = int(_mem.freq) * 1000
+        if _mem.tone >= len(TONES) or _mem.duplex >= len(DUPLEX):
+            mem.empty = True
+            return mem
+        else:
+            mem.rtone = TONES[_mem.tone]
+        mem.tmode = TMODES[_mem.tone_enable]
+        mem.mode = MODES[_mem.am]
+        mem.duplex = DUPLEX[_mem.duplex]
+
+        if _mem.duplex == DUPLEX.index("split"):
+            tx_freq = int(_mem.offset) * 1000
+            print self.VARIANT, number, tx_freq, mem.freq
+            mem.offset = tx_freq - mem.freq
+        else:
+            mem.offset = int(_mem.offset) * 1000
+
+        if int(mem.freq / 100) == 4:
+            mem.power = POWER_LEVELS_UHF[_mem.power]
+        else:
+            mem.power = POWER_LEVELS_VHF[_mem.power]
+
+        # M01 can't be disabled
+        if not self._memobj.enables[byte] & bit and number != 1:
+            mem.empty = True
+
+        print 'R', self.VARIANT, number, _mem.baud9600
+
+        return mem
+
+    def get_raw_memory(self, number):
+        return repr(self._memobj.memory[number - 1])
+
+    def set_memory(self, mem):
+        bit, byte = self._bit_byte(mem.number)
+
+        _mem = self._memobj.memory[mem.number - 1]
+
+        _mem.freq = int(mem.freq / 1000)
+        _mem.tone = TONES.index(mem.rtone)
+        _mem.tone_enable = TMODES.index(mem.tmode)
+        _mem.am = MODES.index(mem.mode)
+        _mem.duplex = DUPLEX.index(mem.duplex)
+
+        if mem.duplex == "split":
+            tx_freq = mem.freq + mem.offset
+            _mem.split_high = tx_freq / 10000000
+            _mem.offset = (tx_freq % 10000000) / 1000
+        else:
+            _mem.offset = int(mem.offset / 1000)
+
+        if mem.power:
+            _mem.power = POWER_LEVELS_VHF.index(mem.power)
+        else:
+            _mem.power = 0
+
+        if mem.empty:
+            self._memobj.enables[byte] &= ~bit
+        else:
+            self._memobj.enables[byte] |= bit
+
+        # TODO expose these options
+        _mem.baud9600 = 0
+        _mem.am = 0
+
+        # These need to be cleared, otherwise strange things happen
+        _mem.unknown4 = 0
+        _mem.unknown4b = 0
+        _mem.unknown1b = 0
+        _mem.unknown2 = 0
+
+    def _checksums(self):
+        return [yaesu_clone.YaesuChecksum(0x0000, 0x0B96)]
+
+    # I didn't believe this myself, but it seems that there's a bit for enabling
+    # VHF M01, but no bit for UHF01, and the enables are shifted down, so that
+    # the first bit is for M02
+    def _bit_byte(self, number):
+        if self.VARIANT == 'VHF':
+            bit = 1 << ((number - 1) % 8)
+            byte = (number - 1) / 8
+        else:
+            bit = 1 << ((number - 2) % 8)
+            byte = (number - 2) / 8
+
+        return bit, byte
+
+
+class FT8100RadioVHF(FT8100Radio):
+    """Yaesu FT-8100 VHF subdevice"""
+    VARIANT = "VHF"
+    _memstart = 0x447
+    _skipstart = 0x02D
+    _enablestart = 0x04D
+
+
+class FT8100RadioUHF(FT8100Radio):
+    """Yaesu FT-8100 UHF subdevice"""
+    VARIANT = "UHF"
+    _memstart = 0x7E6
+    _skipstart = 0x03A
+    _enablestart = 0x05A
+
+
+def _clone_out(radio):
+    try:
+        return __clone_out(radio)
+    except Exception, e:
+        raise errors.RadioError("Failed to communicate with the radio: %s" % e)
+
+
+def __clone_out(radio):
+    pipe = radio.pipe
+    block_lengths = radio._block_lengths
+    total_written = 0
+
+    def _status():
+        status = chirp_common.Status()
+        status.msg = "Cloning to radio"
+        status.max = sum(block_lengths)
+        status.cur = total_written
+        radio.status_fn(status)
+
+    start = time.time()
+
+    pos = 0
+    for block in radio._block_lengths:
+        if os.getenv("CHIRP_DEBUG"):
+            print "\nSending %i-%i" % (pos, pos + block)
+        out = radio.get_mmap()[pos:pos + block]
+
+        # need to chew byte-by-byte here or else we lose the ACK...not sure why
+        for b in out:
+            pipe.write(b)
+            pipe.read(1)  # chew the echo
+
+        ack = pipe.read(1)
+
+        if ack != chr(yaesu_clone.CMD_ACK):
+            raise Exception("block not ack'ed: %s" % repr(ack))
+
+        total_written += len(out)
+        _status()
+
+        pos += block
+
+    print "Clone completed in %i seconds" % (time.time() - start)
+
+    return True
diff -r 25ce872ec061 -r 476b0a1f0035 chirp/drivers/yaesu_clone.py
--- a/chirp/drivers/yaesu_clone.py	Sat Jun 06 21:49:11 2015 -0400
+++ b/chirp/drivers/yaesu_clone.py	Tue Jun 09 15:33:42 2015 -0700
@@ -63,6 +63,10 @@
 def __clone_in(radio):
     pipe = radio.pipe
 
+    status = chirp_common.Status()
+    status.msg = "Cloning from radio"
+    status.max = radio.get_memsize()
+
     start = time.time()
 
     data = ""
@@ -76,6 +80,9 @@
             pipe.write(chr(CMD_ACK))
         if not chunk:
             raise errors.RadioError("No response from radio")
+        if radio.status_fn:
+            status.cur = len(data)
+            radio.status_fn(status)
         data += chunk
 
     if len(data) != radio.get_memsize():



More information about the chirp_devel mailing list