[chirp_devel] [PATCH] Add basic support for the Yaesu FT-8100
# HG changeset patch # User Eric Allen eric@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@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]; +""" + + +@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():
Looks like this is busted due to a merge fail. Re-submitting in a moment.
On Tue, Jun 9, 2015, at 03:33 PM, Eric Allen wrote:
# HG changeset patch # User Eric Allen eric@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@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]; +"""
+@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():
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
participants (1)
-
Eric Allen