Initial support for Yaesu FT-90R. Probably a bit buggy. #1087
diff -r 86910885e998 -r 98bdf0cae780 chirp/ft90.py
--- /dev/null Thu Jan 01 00:00:00 1970 +0000
+++ b/chirp/ft90.py Sat Aug 24 19:31:50 2013 -0500
@@ -0,0 +1,317 @@
+# Copyright 2011 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/>.
+
+from chirp import chirp_common, memmap, directory, errors, util, yaesu_clone
+from chirp import bitwise
+import time, os, traceback
+
+CHIRP_DEBUG=True
+CMD_ACK = chr(0x06)
+
+@directory.register
+class FT90Radio(yaesu_clone.YaesuCloneModeRadio):
+ VENDOR = "Yaesu"
+ MODEL = "FT-90"
+ ID = "ft90"
+
+ STEPS = [5, 10, 12.5,
15, 20, 25, 50]
+ MODES = ["AM", "FM", "Auto"]
+ TMODES = ["", "Tone", "TSQL", "Bell", "DTCS"]
+ TONES = [ 67.0, 69.3, 71.9, 74.4, 77.0, 79.7, 82.5,
+ 85.4, 88.5, 91.5, 94.8, 97.4, 100.0, 103.5,
+ 107.2, 110.9, 114.8, 118.8, 123.0, 127.3,
+ 131.8, 136.5, 141.3, 146.2, 151.4, 156.7,
+ 159.8, 162.2, 167.9, 173.8, 179.9, 183.5,
+ 186.2, 189.9, 192.8, 196.6, 199.5, 203.5,
+ 206.5, 210.7, 218.1, 225.7, 229.1, 233.6,
+ 241.8, 250.3, 254.1,
+ ]
+
+ POWER_LEVELS
= ["Hi","Mid1","Mid2","Low"]
+ #DUPLEX = ["", "-", "+", "split", "Auto"]
+ DUPLEX = ["", "-", "+", "split"]
+
+ _memsize = 4063
+ # block 03 (200 Bytes long) repeats 18 times; channel memories
+ _block_lengths = [ 2, 232, 24, 200, 205]
+
+ mem_format = """
+ #seekto 0x102;
+ struct {
+ u8 mode:2,
+ isUhf1:1,
+ unknown1:2,
+ step:3;
+ u8 artsmode:2,
+ unknown2:1,
+ isUhf2:1
+ power:2,
+
shift:2;
+ u8 skip:1,
+ showname:1,
+ unknown3:1,
+ isUhfHi:1,
+ unknown4:1,
+ tmode:3;
+ u32 rxfreq;
+ u32 txfreqoffset;
+ u8 UseDefaultName:1,
+ ars:1,
+ tone:6;
+ u8 packetmode:1,
+ unknown5:1,
+ dcstone:6;
+ char name[7];
+ } memory[180];
+ """
+
+ @classmethod
+ def match_model(cls, filedata, filename):
+
return len(filedata) == cls._memsize
+
+ def get_features(self):
+ rf = chirp_common.RadioFeatures()
+ rf.has_ctone = False
+ rf.has_bank = False
+ rf.has_dtcs_polarity = False
+ rf.valid_modes = list(set(self.MODES))
+ rf.valid_tmodes = list(self.TMODES)
+ rf.valid_duplexes = list(self.DUPLEX)
+ rf.valid_tuning_steps = list(self.STEPS)
+ rf.valid_power_levels = self.POWER_LEVELS
+ rf.valid_name_length = 7
+ rf.valid_characters = chirp_common.CHARSET_ASCII
+ rf.valid_skips = ["",
"S"]
+ rf.memory_bounds = (1, 180)
+ rf.valid_bands = [(100000000, 230000000),
+ (300000000, 530000000), (810000000, 999975000)]
+
+ return rf
+
+ def _read(self, blocksize, blocknum):
+ data = self.pipe.read(blocksize+2)
+
+ # chew echo'd ack
+ self.pipe.write(CMD_ACK)
+ time.sleep(0.02)
+ self.pipe.read(1) # chew echoed ACK from 1-wire serial
+
+ if len(data) == blocksize+2 and data[0] == chr(blocknum):
+
checksum = yaesu_clone.YaesuChecksum(1, blocksize)
+ if checksum.get_existing(data) != checksum.get_calculated(data):
+ raise Exception("Checksum Failed [%02X<>%02X] block %02X, data len: %i" %
+ (checksum.get_existing(data),
+ checksum.get_calculated(data), blocknum, len(data) ))
+ data = data[1:blocksize+1] # Chew blocknum and checksum
+
+ else:
+ raise Exception("Unable to
read blocknum %02X expected blocksize %i got %i." %
+ (blocknum, blocksize+2, len(data)))
+
+ return data
+
+ def _clone_in(self):
+ # Be very patient with the radio
+ self.pipe.setTimeout(4)
+ start = time.time()
+
+ data = ""
+ blocknum = 0
+ status = chirp_common.Status()
+ status.msg = "Cloning from radio.\nPut radio into clone mode then\npress SET to send"
+ self.status_fn(status)
+
status.max = len(self._block_lengths) + 18
+ for blocksize in self._block_lengths:
+ if blocksize == 200:
+ # repeated read of 200 block same size (memory area)
+ repeat = 18
+ else:
+ repeat = 1
+ for _i in range(0, repeat):
+ data += self._read(blocksize, blocknum)
+
+ blocknum += 1
+ status.cur = blocknum
+
self.status_fn(status)
+
+ status.msg = "Clone completed."
+ self.status_fn(status)
+
+ print "Clone completed in %i seconds, blocks read: %i" % (time.time() - start, blocknum)
+
+ return memmap.MemoryMap(data)
+
+ def _clone_out(self):
+ delay = 0.2
+ start = time.time()
+
+ blocknum = 0
+ pos = 0
+ status = chirp_common.Status()
+ status.msg = "Cloning to radio.\nPut radio into clone
mode and press DISP/SS\n to start receive within 3 secs..."
+ self.status_fn(status)
+ # radio likes to have port open
+ self.pipe.open()
+ time.sleep(3)
+ status.max = len(self._block_lengths) + 18
+
+
+ for blocksize in self._block_lengths:
+ if blocksize == 200:
+ # repeat channel blocks
+ repeat = 18
+ else:
+ repeat = 1
+ for _i in range(0, repeat):
+
time.sleep(0.1)
+ checksum = yaesu_clone.YaesuChecksum(pos, pos+blocksize-1)
+ blocknumbyte = chr(blocknum)
+ payloadbytes = self.get_mmap()[pos:pos+blocksize]
+ checksumbyte = chr(checksum.get_calculated(self.get_mmap()))
+ if os.getenv("CHIRP_DEBUG") or CHIRP_DEBUG:
+ print "Block %i - will send from %i to %i byte " % \
+ (blocknum, pos, pos + blocksize)
+
print util.hexprint(blocknumbyte)
+ print util.hexprint(payloadbytes)
+ print util.hexprint(checksumbyte)
+ # send wrapped bytes
+ self.pipe.write(blocknumbyte)
+ self.pipe.write(payloadbytes)
+ self.pipe.write(checksumbyte)
+ tmp = self.pipe.read(blocksize+2) #chew echo
+ if os.getenv("CHIRP_DEBUG") or CHIRP_DEBUG:
+
print "bytes echoed: "
+ print util.hexprint(tmp)
+ # radio is slow to write/ack:
+ time.sleep(0.9)
+ buf = self.pipe.read(1)
+ if os.getenv("CHIRP_DEBUG") or CHIRP_DEBUG:
+ print "ack recd:"
+ print util.hexprint(buf)
+ if buf != CMD_ACK:
+
raise Exception("Radio did not ack block %i" % blocknum)
+ pos += blocksize
+ blocknum += 1
+ status.cur = blocknum
+ self.status_fn(status)
+
+ print "Clone completed in %i seconds" % (time.time() - start)
+
+ def sync_in(self):
+ try:
+ self._mmap = self._clone_in()
+ except errors.RadioError:
+ raise
+ except Exception,
e:
+ trace = traceback.format_exc()
+ raise errors.RadioError("Failed to communicate with radio: %s" % trace)
+ self.process_mmap()
+
+ def sync_out(self):
+ try:
+ self._clone_out()
+ except errors.RadioError:
+ raise
+ except Exception, e:
+ trace = traceback.format_exc()
+ raise errors.RadioError("Failed to communicate with radio: %s" % trace)
+
+ def process_mmap(self):
+ self._memobj =
bitwise.parse(self.mem_format, self._mmap)
+
+ def get_memory(self, number):
+ _mem = self._memobj.memory[number-1]
+
+ mem = chirp_common.Memory()
+ mem.number = number
+ mem.freq = _mem.rxfreq * 10
+ mem.offset = _mem.txfreqoffset * 10
+ if not _mem.tmode < len(self.TMODES):
+ _mem.tmode = 0
+ mem.tmode = self.TMODES[_mem.tmode]
+ mem.rtone = self.TONES[_mem.tone]
+ mem.mode = self.MODES[_mem.mode]
+
+ '''
+ # ars mode note
yet working...
+ # ARS mode:
+ if _mem.ars and _mem.shift == 0:
+ mem.duplex = self.DUPLEX[4]
+ else:
+ mem.duplex = self.DUPLEX[_mem.shift]
+ '''
+ mem.duplex = self.DUPLEX[_mem.shift]
+ mem.power = self.POWER_LEVELS[_mem.power]
+ # radio has a known bug with 5khz step and squelch
+ if _mem.step == 0:
+ _mem.step = 2
+ mem.tuning_step = self.STEPS[_mem.step]
+ mem.skip = _mem.skip and "S" or ""
+
mem.name = _mem.name
+ return mem
+
+ def get_raw_memory(self, number):
+ return repr(self._memobj.memory[number-1])
+
+ def set_memory(self, mem):
+
+ _mem = self._memobj.memory[mem.number - 1]
+ _mem.skip = mem.skip == "S"
+ # radio has a known bug with 5khz step and dead squelch
+ if mem.tuning_step == self.STEPS[0]:
+ _mem.step = 2
+ else:
+ _mem.step = self.STEPS.index(mem.tuning_step)
+ _mem.rxfreq = mem.freq / 10
+ # vfo will
unlock if not in right band?
+ if mem.freq > 300000000:
+ # uhf
+ _mem.isUhf1 = 1
+ _mem.isUhf2 = 1
+ if mem.freq > 810000000:
+ # uhf hiband
+ _mem.isUhfHi = 1
+ else:
+ _mem.isUhfHi = 0
+ else:
+ # vhf
+ _mem.isUhf1 = 0
+ _mem.isUhf2 = 0
+
_mem.isUhfHi = 0
+ _mem.txfreqoffset = mem.offset / 10
+ _mem.tone = self.TONES.index(mem.rtone)
+ _mem.tmode = self.TMODES.index(mem.tmode)
+ _mem.mode = self.MODES.index(mem.mode)
+ '''
+ # ars not yet working
+ # ARS mode:
+ if mem.duplex == 4:
+ _mem.shift = 0
+ _mem.ars = 1
+ else:
+ _mem.shift = self.DUPLEX.index(mem.duplex)
+ '''
+ _mem.shift = self.DUPLEX.index(mem.duplex)
+ #_mem.dtcs = chirp_common.DTCS_CODES.index(mem.dtcs)
+ if self.get_features().has_tuning_step:
+ _mem.step = self.STEPS.index(mem.tuning_step)
+ _mem.shift = self.DUPLEX.index(mem.duplex)
+ if mem.power:
+ _mem.power = self.POWER_LEVELS.index(mem.power)
+ else:
+ _mem.power = 3 # default to low power
+ _mem.name = mem.name.ljust(7)
+
+