
Initial support for Yaesu FT-90R. Probably a bit buggy. #1087
diff -r 86910885e998 -r 98bdf0cae780 chirp/ft90.py --- /dev/nullThu Jan 01 00:00:00 1970 +0000 +++ b/chirp/ft90.pySat 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) + +