# Copyright 2013 Tom Hayward , Ran Giladi # # 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 . # FT-1900R Clone Proceedure # 1. Turn radio off. # 2. Connect cable to mic jack. # 3. Press and hold in the [LOW(A/N)] key while turning the radio on. # 4. In Chirp, choose Download from Radio. # 5. Press the [MHz(SET)] key to send image. # or # 4. Press the [D/MR(MW)] key ("--WAIT--" will appear on the LCD). # 5. In Chirp, choose Upload to Radio. from chirp import chirp_common, bitwise, directory, yaesu_clone from chirp import memmap, errors from chirp.settings import RadioSetting, RadioSettingGroup, \ RadioSettingValueBoolean from collections import defaultdict MEM_FORMAT = """ #seekto 0x36A; struct { u8 bname[6]; } bank_names[8]; #seekto 0x3CE; struct { u16 bitmap[50]; } bank_channels[8]; #seekto 0x06ee; struct { u8 odd_pskip:1, odd_skip:1, odd_visible:1, odd_valid:1, even_pskip:1, even_skip:1, even_visible:1, even_valid:1; } flags[100]; #seekto 0x076E; struct { u8 unknown1a:1, step_changed:1, narrow:1, clk_shift:1, unknown1b:4; u8 unknown2a:2, duplex:2, unknown2b:1, tune_step:3; bbcd freq[3]; u8 power:2, unknown3:3, tmode:3; u8 name[6]; bbcd offset[3]; u8 tone; u8 dtcs; u8 unknown4; u8 ttone; u8 unknown5; } memory[200]; """ MODES = ["FM", "NFM"] TMODES = ["", "Tone", "TSQL", "DTCS", "TSQL-R", "Cross"] CROSS_MODES = ["DTCS->", "Tone->Tone", "Tone->DTCS", "DTCS->Tone"] DUPLEX = ["", "-", "+", "split"] STEPS = [5.0, 10.0, 12.5, 15.0, 20.0, 25.0, 50.0, 100.0] POWER_LEVELS = [chirp_common.PowerLevel("LOW1", watts=5), chirp_common.PowerLevel("LOW2", watts=10), chirp_common.PowerLevel("LOW3", watts=25), chirp_common.PowerLevel("HIGH", watts=50), ] CHARSET = "0123456789ABCDEFGHIJKLMNOPQRSTUVWXYZ +-/?()?_" class FT1900Bank(chirp_common.BankModel): """Yaesu FT-1900 bank model""" def __init__(self, radio): chirp_common.BankModel.__init__(self, radio) self.__b2m_cache = defaultdict(list) self.__m2b_cache = defaultdict(list) def __precache(self): if self.__b2m_cache: return for bank in self.get_banks(): self.__b2m_cache[bank.index] = self._get_bank_memories(bank) # build a list of memories # for each bank, in b2M for memnum in self.__b2m_cache[bank.index]: # build a list of banks self.__m2b_cache[memnum].append(bank.index) # for each memory, in m2b def get_num_banks(self): return 8 def get_banks(self): # returns list of banks (names and index) in banks[] banks = [] for i in range(0, self.get_num_banks()): bn="" _bn = self._radio._memobj.bank_names[i].bname for j in _bn: if j == 0xFF: break if j & 0x80 == 0x80: # first bit in name is "show name" bn += CHARSET[0x80 ^ int(j)] else: bn += CHARSET[j] bank = chirp_common.Bank(self, "%i" % i, "%s" % bn) bank.index = i banks.append(bank) return banks def add_memory_to_bank(self, memory, bank): self.__precache() for i in range(0, 50): _bitmap = self._radio._memobj.bank_channels[bank.index].bitmap[i] if _bitmap > 200: self._radio._memobj.bank_channels[bank.index].bitmap[i] = memory.number self.__m2b_cache[memory.number].append(bank.index) self.__b2m_cache[bank.index].append(memory.number) break if _bitmap < 201: raise Exception("Adding Memory {num} into " + "bank {bank} failed, Bank is full!".format(num=memory.number, bank=bank)) def remove_memory_from_bank(self, memory, bank): self.__precache() ip = 51; il=-1; for i in range(0, 50): _bitmap = self._radio._memobj.bank_channels[bank.index].bitmap[i] if _bitmap == memory.number: ip = i # remember where it was found if _bitmap > 200: # we reached end of list il = i - 1 # remember the last valid memory in this bank if il < 0: # no memories in this bank! break _bitmap = self._radio._memobj.bank_channels[bank.index].bitmap[il] # Here we suppose to have ip<50 for memory, and il =>0 for last memory in the list # so check it if ((ip < 50) & (il >= 0)): self._radio._memobj.bank_channels[bank.index].bitmap[ip] = _bitmap #replace with the last one found self._radio._memobj.bank_channels[bank.index].bitmap[il] = 0xFFFF #replace with not occupied value if ((ip > 50) | (il < 0)): raise Exception("Memory {num} is " + "not in bank {bank}".format(num=memory.number, bank=bank)) self.__b2m_cache[bank.index].remove(memory.number) self.__m2b_cache[memory.number].remove(bank.index) def _get_bank_memories(self, bank): memories = [] for i in range(0, 50): _bitmap = self._radio._memobj.bank_channels[bank.index].bitmap[i] if _bitmap < 201: memories.append(int(_bitmap)) return memories return [self._radio.get_memory(n) for n in self.__b2m_cache[bank.index]] def get_memory_banks(self, memory): self.__precache() _banks = self.get_banks() return [_banks[b] for b in self.__m2b_cache[memory.number]] def get_index_bounds(self): return [0, 49]; @directory.register class FT1900Radio(yaesu_clone.YaesuCloneModeRadio): """Yaesu FT-1900""" VENDOR = "Yaesu" MODEL = "FT-1900R" BAUD_RATE = 19200 _model = "VC23" _block_lengths = [14, 8001] _memsize = 8011 # _num_banks = 8 # _bankstart = 0x36A _bank_class = FT1900Bank def get_bank_model(self): return FT1900Bank(self) def get_features(self): rf = chirp_common.RadioFeatures() rf.memory_bounds = (0, 199) rf.can_odd_split = True rf.has_ctone = True rf.has_tuning_step = True rf.has_dtcs_polarity = False # in radio settings, not per memory rf.has_bank = True # has banks, but not implemented rf.has_bank_index = True rf.has_bank_names = True rf.valid_tuning_steps = STEPS rf.valid_modes = MODES rf.valid_tmodes = TMODES rf.valid_bands = [(136000000, 174000000)] rf.valid_power_levels = POWER_LEVELS rf.valid_duplexes = DUPLEX rf.valid_skips = ["", "S", "P"] rf.valid_name_length = 6 rf.valid_characters = CHARSET rf.has_cross = True rf.valid_cross_modes = list(CROSS_MODES) return rf def _checksums(self): return [yaesu_clone.YaesuChecksum(0, self._memsize-2)] def process_mmap(self): self._memobj = bitwise.parse(MEM_FORMAT, self._mmap) def get_raw_memory(self, number): return repr(self._memobj.memory[number]) + \ repr(self._memobj.flags[number/2]) def get_memory(self, number): _mem = self._memobj.memory[number] _flag = self._memobj.flags[number/2] nibble = (number % 2) and "odd" or "even" visible = _flag["%s_visible" % nibble] valid = _flag["%s_valid" % nibble] pskip = _flag["%s_pskip" % nibble] skip = _flag["%s_skip" % nibble] mem = chirp_common.Memory() mem.number = number if not visible: mem.empty = True if not valid: mem.empty = True return mem mem.freq = chirp_common.fix_rounded_step(int(_mem.freq) * 1000) mem.offset = chirp_common.fix_rounded_step(int(_mem.offset) * 1000) mem.duplex = DUPLEX[_mem.duplex] mem.tuning_step = _mem.step_changed and STEPS[_mem.tune_step] or STEPS[0] if _mem.tmode < TMODES.index("Cross"): mem.tmode = TMODES[_mem.tmode] mem.cross_mode = CROSS_MODES[0] else: mem.tmode = "Cross" mem.cross_mode = CROSS_MODES[_mem.tmode - TMODES.index("Cross")] mem.rtone = chirp_common.TONES[_mem.tone] if _mem.tmode > TMODES.index(""): mem.ctone = chirp_common.TONES[_mem.ttone&0x7F] mem.dtcs = chirp_common.DTCS_CODES[_mem.dtcs] for i in _mem.name: if i == 0xFF: break if i & 0x80 == 0x80: # first bit in name is "show name" mem.name += CHARSET[0x80 ^ int(i)] else: mem.name += CHARSET[i] mem.name = mem.name.rstrip() mem.mode = _mem.narrow and "NFM" or "FM" mem.skip = pskip and "P" or skip and "S" or "" mem.power = POWER_LEVELS[_mem.power] mem.extra = RadioSettingGroup("extra", "Extra Settings") rs = RadioSetting("clk_shift", "Clock Shift", RadioSettingValueBoolean(_mem.clk_shift)) mem.extra.append(rs) return mem def set_memory(self, mem): _mem = self._memobj.memory[mem.number] _flag = self._memobj.flags[mem.number/2] nibble = (mem.number % 2) and "odd" or "even" valid = _flag["%s_valid" % nibble] visible = _flag["%s_visible" % nibble] if not mem.empty and not valid: _flag["%s_valid" % nibble] = True _mem.unknown1a = 0x00 _mem.clk_shift = 0x00 _mem.unknown1b = 0x00 _mem.unknown2a = 0x00 _mem.unknown2b = 0x00 _mem.unknown3 = 0x00 _mem.unknown4 = 0x00 _mem.unknown5 = 0x00 _mem.ttone = 0x00 if mem.empty and valid and not visible: _flag["%s_valid" % nibble] = False return _flag["%s_visible" % nibble] = not mem.empty if mem.empty: return _flag["%s_valid" % nibble] = True _mem.freq = mem.freq / 1000 _mem.offset = mem.offset / 1000 _mem.duplex = DUPLEX.index(mem.duplex) _mem.tune_step = STEPS.index(mem.tuning_step) _mem.step_changed = mem.tuning_step != STEPS[0] if mem.tmode != "Cross": _mem.tmode = TMODES.index(mem.tmode) else: _mem.tmode = TMODES.index("Cross") + CROSS_MODES.index(mem.cross_mode) _mem.tone = chirp_common.TONES.index(mem.rtone) _mem.dtcs = chirp_common.DTCS_CODES.index(mem.dtcs) # update ttone if mem.tmode != "": _mem.ttone = chirp_common.TONES.index[mem.ctone] if _mem.ttone != _mem.tone: _mem.ttone = _mem.ttone | 0x80 # _mem.name = [0xFF] * 6 for i in range(0, len(mem.name)): try: _mem.name[i] = CHARSET.index(mem.name[i]) except IndexError: raise Exception("Character `%s' not supported") if _mem.name[0] != 0xFF: _mem.name[0] += 0x80 # show name instead of frequency _mem.narrow = MODES.index(mem.mode) _mem.power = 3 if mem.power is None else POWER_LEVELS.index(mem.power) _flag["%s_pskip" % nibble] = mem.skip == "P" _flag["%s_skip" % nibble] = mem.skip == "S" for element in mem.extra: setattr(_mem, element.get_name(), element.value)