[chirp_devel] [PATCH] [FTM-3200D] Add support for Yaesu FTM-3200D
# HG changeset patch # User Wade Simmons wade@wades.im # Date 1495638955 14400 # Wed May 24 11:15:55 2017 -0400 # Node ID 8745d861ed0d38ecf45017a6ff1ba2f57ac5590d # Parent 93e58a8a1a2ac3e50b255cca410821f756bac4a9 [FTM-3200D] Add support for Yaesu FTM-3200D
This patch adds basic CHIRP support for programming the FTM-3200D. This radio shares most of its memory layout with the FT1D, so it has been created as a subclass of that radio.
#4279
diff -r 93e58a8a1a2a -r 8745d861ed0d chirp/drivers/ft1d.py --- a/chirp/drivers/ft1d.py Wed May 17 16:54:45 2017 -0400 +++ b/chirp/drivers/ft1d.py Wed May 24 11:15:55 2017 -0400 @@ -27,7 +27,7 @@
LOG = logging.getLogger(__name__)
-MEM_FORMAT = """ +MEM_SETTINGS_FORMAT = """ #seekto 0x049a; struct { u8 vfo_a; @@ -141,17 +141,21 @@ u8 unknown[2]; u8 name[16]; } bank_info[24]; +"""
+MEM_FORMAT = """ #seekto 0x2D4A; struct { - u8 unknown1; + u8 unknown0:2, + mode_alt:1, // mode for FTM-3200D + unknown1:5; u8 mode:2, duplex:2, tune_step:4; bbcd freq[3]; u8 power:2, - unknown2:4, - tone_mode:2; + unknown2:2, + tone_mode:4; u8 charsetbits[2]; char label[16]; bbcd offset[3]; @@ -160,7 +164,7 @@ u8 unknown6:1, dcs:7; u8 unknown7[3]; -} memory[900]; +} memory[%d];
#seekto 0x280A; struct { @@ -170,8 +174,10 @@ skip:1, used:1, valid:1; -} flag[900]; +} flag[%d]; +"""
+MEM_APRS_FORMAT = """ #seekto 0xbeca; struct { u8 rx_baud; @@ -334,7 +340,9 @@ char path_and_body[66]; u8 unknown[70]; } aprs_message_pkt[60]; +"""
+MEM_CHECKSUM_FORMAT = """ #seekto 0x1FDC9; u8 checksum; """ @@ -500,11 +508,7 @@ return banks
-def _wipe_memory(mem): - mem.set_raw("\x00" * (mem.size() / 8)) - mem.unknown1 = 0x05 - - +# Note: other radios like FTM3200Radio subclass this radio @directory.register class FT1Radio(yaesu_clone.YaesuCloneModeRadio): """Yaesu FT1DR""" @@ -517,7 +521,9 @@ _memsize = 130507 _block_lengths = [10, 130497] _block_size = 32 - _mem_params = (0xFECA, # APRS beacon metadata address. + _mem_params = (900, # size of memories array + 900, # size of flags array + 0xFECA, # APRS beacon metadata address. 60, # Number of beacons stored. 0x1064A, # APRS beacon content address. 134, # Length of beacon data stored. @@ -610,7 +616,9 @@ return rp
def process_mmap(self): - self._memobj = bitwise.parse(MEM_FORMAT % self._mem_params, self._mmap) + mem_format = MEM_SETTINGS_FORMAT + MEM_FORMAT + MEM_APRS_FORMAT + \ + MEM_CHECKSUM_FORMAT + self._memobj = bitwise.parse(mem_format % self._mem_params, self._mmap)
def get_features(self): rf = chirp_common.RadioFeatures() @@ -632,7 +640,8 @@ return rf
def get_raw_memory(self, number): - return repr(self._memobj.memory[number]) + return "\n".join([repr(self._memobj.memory[number - 1]), + repr(self._memobj.flag[number - 1])])
def _checksums(self): return [yaesu_clone.YaesuChecksum(0x064A, 0x06C8), @@ -666,21 +675,53 @@ mem.freq = chirp_common.fix_rounded_step(int(_mem.freq) * 1000) mem.offset = int(_mem.offset) * 1000 mem.rtone = mem.ctone = chirp_common.TONES[_mem.tone] - mem.tmode = TMODES[_mem.tone_mode] + self._get_tmode(mem, _mem) mem.duplex = DUPLEX[_mem.duplex] if mem.duplex == "split": mem.offset = chirp_common.fix_rounded_step(mem.offset) - mem.mode = MODES[_mem.mode] + mem.mode = self._decode_mode(_mem) mem.dtcs = chirp_common.DTCS_CODES[_mem.dcs] mem.tuning_step = STEPS[_mem.tune_step] - mem.power = POWER_LEVELS[3 - _mem.power] + mem.power = self._decode_power_level(_mem) mem.skip = flag.pskip and "P" or flag.skip and "S" or ""
- charset = ''.join(CHARSET).ljust(256, '.') - mem.name = str(_mem.label).rstrip("\xFF").translate(charset) + mem.name = self._decode_label(_mem)
return mem
+ def _decode_label(self, mem): + charset = ''.join(CHARSET).ljust(256, '.') + return str(mem.label).rstrip("\xFF").translate(charset) + + def _encode_label(self, mem): + label = "".join([chr(CHARSET.index(x)) for x in mem.name.rstrip()]) + return self._add_ff_pad(label, 16) + + def _encode_charsetbits(self, mem): + # We only speak english here in chirpville + return [0x00, 0x00] + + def _decode_power_level(self, mem): + return POWER_LEVELS[3 - mem.power] + + def _encode_power_level(self, mem): + return 3 - POWER_LEVELS.index(mem.power) + + def _decode_mode(self, mem): + return MODES[mem.mode] + + def _encode_mode(self, mem): + return MODES.index(mem.mode) + + def _get_tmode(self, mem, _mem): + mem.tmode = TMODES[_mem.tone_mode] + + def _set_tmode(self, _mem, mem): + _mem.tone_mode = TMODES.index(mem.tmode) + + def _set_mode(self, _mem, mem): + _mem.mode = self._encode_mode(mem) + def _debank(self, mem): bm = self.get_bank_model() for bank in bm.get_memory_mappings(mem): @@ -693,7 +734,7 @@ self._debank(mem)
if not mem.empty and not flag.valid: - _wipe_memory(_mem) + self._wipe_memory(_mem)
if mem.empty and flag.valid and not flag.used: flag.valid = False @@ -714,25 +755,28 @@ _mem.freq = int(mem.freq / 1000) _mem.offset = int(mem.offset / 1000) _mem.tone = chirp_common.TONES.index(mem.rtone) - _mem.tone_mode = TMODES.index(mem.tmode) + self._set_tmode(_mem, mem) _mem.duplex = DUPLEX.index(mem.duplex) - _mem.mode = MODES.index(mem.mode) + self._set_mode(_mem, mem) _mem.dcs = chirp_common.DTCS_CODES.index(mem.dtcs) _mem.tune_step = STEPS.index(mem.tuning_step) if mem.power: - _mem.power = 3 - POWER_LEVELS.index(mem.power) + _mem.power = self._encode_power_level(mem) else: _mem.power = 0
- label = "".join([chr(CHARSET.index(x)) for x in mem.name.rstrip()]) - _mem.label = self._add_ff_pad(label, 16) - # We only speak english here in chirpville - _mem.charsetbits[0] = 0x00 - _mem.charsetbits[1] = 0x00 + _mem.label = self._encode_label(mem) + charsetbits = self._encode_charsetbits(mem) + _mem.charsetbits[0], _mem.charsetbits[1] = charsetbits
flag.skip = mem.skip == "S" flag.pskip = mem.skip == "P"
+ @classmethod + def _wipe_memory(cls, mem): + mem.set_raw("\x00" * (mem.size() / 8)) + mem.unknown1 = 0x05 + def get_bank_model(self): return FT1BankModel(self)
diff -r 93e58a8a1a2a -r 8745d861ed0d chirp/drivers/ftm3200d.py --- /dev/null Thu Jan 01 00:00:00 1970 +0000 +++ b/chirp/drivers/ftm3200d.py Wed May 24 11:15:55 2017 -0400 @@ -0,0 +1,201 @@ +# Copyright 2010 Dan Smith dsmith@danplanet.com +# Copyright 2017 Wade Simmons wade@wades.im +# +# 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 logging +from textwrap import dedent + +from chirp.drivers import yaesu_clone, ft1d +from chirp import chirp_common, directory, bitwise +from chirp.settings import RadioSettings + +LOG = logging.getLogger(__name__) + +POWER_LEVELS = [chirp_common.PowerLevel("Low", watts=5), + chirp_common.PowerLevel("Mid", watts=30), + chirp_common.PowerLevel("Hi", watts=65)] + +TMODES = ["", "Tone", "TSQL", "DTCS", "TSQL-R", None, None, "Pager", "Cross"] +CROSS_MODES = [None, "DTCS->", "Tone->DTCS", "DTCS->Tone"] + +MODES = ["FM", "NFM"] +STEPS = [0, 5, 6.25, 10, 12.5, 15, 20, 25, 50, 100] # 0 = auto +RFSQUELCH = ["OFF", "S1", "S2", "S3", "S4", "S5", "S6", "S7", "S8"] + +# Charset is subset of ASCII + some unknown chars \x80-\x86 +VALID_CHARS = ["%i" % int(x) for x in range(0, 10)] + \ + list(":>=<?@") + \ + [chr(x) for x in range(ord("A"), ord("Z") + 1)] + \ + list("[\]_") + \ + [chr(x) for x in range(ord("a"), ord("z") + 1)] + \ + list("%*+,-/=$ ") + +MEM_FORMAT = """ +#seekto 0xceca; +struct { + u8 unknown5; + u8 unknown3; + u8 unknown4:6, + dsqtype:2; + u8 dsqcode; + u8 unknown1[2]; + char mycall[10]; + u8 unknown2[368]; +} settings; + +#seekto 0xfec9; +u8 checksum; +""" + + +@directory.register +class FTM3200Radio(ft1d.FT1Radio): + """Yaesu FTM-3200D""" + BAUD_RATE = 38400 + VENDOR = "Yaesu" + MODEL = "FTM-3200D" + VARIANT = "R" + + _model = "AH52N" + _memsize = 65227 + _block_lengths = [10, 65217] + _has_vibrate = False + _has_af_dual = False + + _mem_params = (199, # size of memories array + 199) # size of flags array + + @classmethod + def get_prompts(cls): + rp = chirp_common.RadioPrompts() + rp.pre_download = _(dedent("""\ + 1. Turn radio off. + 2. Connect cable to DATA terminal. + 3. Press and hold in the [MHz(SETUP)] key while turning the radio + on ("CLONE" will appear on the display). + 4. <b>After clicking OK</b>, press the [REV(DW)] key + to send image.""")) + rp.pre_upload = _(dedent("""\ + 1. Turn radio off. + 2. Connect cable to DATA terminal. + 3. Press and hold in the [MHz(SETUP)] key while turning the radio + on ("CLONE" will appear on the display). + 4. Press the [MHz(SETUP)] key + ("-WAIT-" will appear on the LCD).""")) + return rp + + def process_mmap(self): + mem_format = ft1d.MEM_FORMAT + MEM_FORMAT + self._memobj = bitwise.parse(mem_format % self._mem_params, self._mmap) + + def get_features(self): + rf = chirp_common.RadioFeatures() + rf.has_dtcs_polarity = False + rf.valid_modes = list(MODES) + rf.valid_tmodes = [x for x in TMODES if x is not None] + rf.valid_cross_modes = [x for x in CROSS_MODES if x is not None] + rf.valid_duplexes = list(ft1d.DUPLEX) + rf.valid_tuning_steps = list(STEPS) + rf.valid_bands = [(136000000, 174000000)] + # rf.valid_skips = SKIPS + rf.valid_power_levels = POWER_LEVELS + rf.valid_characters = "".join(VALID_CHARS) + rf.valid_name_length = 8 + rf.memory_bounds = (1, 199) + rf.can_odd_split = True + rf.has_ctone = False + rf.has_bank = False + rf.has_bank_names = False + # disable until implemented + rf.has_settings = False + return rf + + def _decode_label(self, mem): + # TODO preserve the unknown \x80-x86 chars? + return str(mem.label).rstrip("\xFF").decode('ascii', 'replace') + + def _encode_label(self, mem): + label = mem.name.rstrip().encode('ascii', 'ignore') + return self._add_ff_pad(label, 16) + + def _encode_charsetbits(self, mem): + # TODO this is a setting to decide if the memory should be displayed + # as a name or frequency. Should we expose this setting to the user + # instead of autoselecting it (and losing their preference)? + if mem.name.rstrip() == '': + return [0x00, 0x00] + return [0x00, 0x80] + + def _decode_power_level(self, mem): + return POWER_LEVELS[mem.power - 1] + + def _encode_power_level(self, mem): + return POWER_LEVELS.index(mem.power) + 1 + + def _decode_mode(self, mem): + return MODES[mem.mode_alt] + + def _encode_mode(self, mem): + return MODES.index(mem.mode) + + def _get_tmode(self, mem, _mem): + if _mem.tone_mode > 8: + tmode = "Cross" + mem.cross_mode = CROSS_MODES[_mem.tone_mode - 8] + else: + tmode = TMODES[_mem.tone_mode] + + if tmode == "Pager": + # TODO chirp_common does not allow 'Pager' + # Expose as a different setting? + mem.tmode = "" + else: + mem.tmode = tmode + + def _set_tmode(self, _mem, mem): + if mem.tmode == "Cross": + _mem.tone_mode = 8 + CROSS_MODES.index(mem.cross_mode) + else: + _mem.tone_mode = TMODES.index(mem.tmode) + + def _set_mode(self, _mem, mem): + _mem.mode_alt = self._encode_mode(mem) + + def get_bank_model(self): + return None + + def _debank(self, mem): + return + + def _checksums(self): + return [yaesu_clone.YaesuChecksum(0x064A, 0x06C8), + yaesu_clone.YaesuChecksum(0x06CA, 0x0748), + yaesu_clone.YaesuChecksum(0x074A, 0x07C8), + yaesu_clone.YaesuChecksum(0x07CA, 0x0848), + yaesu_clone.YaesuChecksum(0x0000, 0xFEC9)] + + def _get_settings(self): + # TODO + top = RadioSettings() + return top + + @classmethod + def _wipe_memory(cls, mem): + mem.set_raw("\x00" * (mem.size() / 8)) + + def sync_out(self): + # Need to give enough time for the radio to ACK after writes + self.pipe.timeout = 1 + return super(FTM3200Radio, self).sync_out()
# HG changeset patch # User Wade Simmons wade@wades.im # Date 1495638955 14400 # Wed May 24 11:15:55 2017 -0400 # Node ID 8745d861ed0d38ecf45017a6ff1ba2f57ac5590d # Parent 93e58a8a1a2ac3e50b255cca410821f756bac4a9 [FTM-3200D] Add support for Yaesu FTM-3200D
This patch adds basic CHIRP support for programming the FTM-3200D. This radio shares most of its memory layout with the FT1D, so it has been created as a subclass of that radio.
#4279
Thanks a lot for having a image already in the tracker, tests passing, proper commit message and code reuse. Nicely done :)
--Dan
participants (2)
-
Dan Smith
-
Wade Simmons