[chirp_devel] chirp_devel Digest, Vol 46, Issue 20
Richard Cochran
Sat Feb 28 22:56:00 PST 2015
# HG changeset patch
# User Richard Cochran <ag6qr at sonic.net>
# Date 1425192393 28800
# Sat Feb 28 22:46:33 2015 -0800
# Node ID b843872d30884b166ecf26d2bae334c28289ea6f
# Parent 9deae1ac816a7a0efd7a754b8e50546f95a97b1a
Support new model Yaesu FT-2900R, new driver ft2900.py (#358)
This adds driver ft2900.py to support the Yaesu FT-2900R radio.
At this time, support is limited to the 200 standard memories. No
banks, no settings, no band edge limits. Driver is loosely based
on FT-2800 driver, with some code borrowed from VX-7 and FT-60 drivers.
A sample .img file is being e-mailed to the dev list in a separate email.
diff -r 9deae1ac816a -r b843872d3088 chirp/ft2900.py
--- /dev/null Thu Jan 01 00:00:00 1970 +0000
+++ b/chirp/ft2900.py Sat Feb 28 22:46:33 2015 -0800
@@ -0,0 +1,426 @@
+# Copyright 2011 Dan Smith <dsmith at danplanet.com>
+#
+# FT-2900-specific modifications by Richard Cochran, <ag6qr at sonic.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
+import logging
+
+from chirp import util, memmap, chirp_common, bitwise, directory, errors
+from chirp.yaesu_clone import YaesuCloneModeRadio
+
+from textwrap import dedent
+
+LOG = logging.getLogger(__name__)
+
+
+def _send(s, data):
+ s.write(data)
+ echo = s.read(len(data))
+ if data != echo:
+ raise Exception("Failed to read echo")
+ LOG.debug("got echo\n%s\n" % util.hexprint(echo))
+
+IDBLOCK = "\x56\x43\x32\x33\x00\x02\x46\x01\x01\x01"
+ACK = "\x06"
+INITIAL_CHECKSUM = 73
+
+
+def _download(radio):
+
+ blankChunk = ""
+ for _i in range(0, 32):
+ blankChunk += "\xff"
+
+ LOG.debug("in _download\n")
+
+ data = ""
+ for _i in range(0, 20):
+ data = radio.pipe.read(20)
+ LOG.debug("Header:\n%s" % util.hexprint(data))
+ LOG.debug("len(header) = %s\n" % len(data))
+
+ if data == IDBLOCK:
+ break
+
+ if data != IDBLOCK:
+ raise Exception("Failed to read header")
+
+ _send(radio.pipe, ACK)
+
+ # initialize data, the big var that holds all memory
+ data = ""
+
+ _blockNum = 0
+
+ while len(data) < radio._block_sizes[1]:
+ _blockNum += 1
+ time.sleep(0.03)
+ chunk = radio.pipe.read(32)
+ LOG.debug("Block %i " % (_blockNum))
+ if chunk == blankChunk:
+ LOG.debug("blank chunk\n")
+ else:
+ LOG.debug("Got: %i:\n%s" % (len(chunk), util.hexprint(chunk)))
+ if len(chunk) != 32:
+ LOG.debug("len chunk is %i\n" % (len(chunk)))
+ raise Exception("Failed to get full data block")
+ break
+ else:
+ data += chunk
+
+ if radio.status_fn:
+ status = chirp_common.Status()
+ status.max = radio._block_sizes[1]
+ status.cur = len(data)
+ status.msg = "Cloning from radio"
+ radio.status_fn(status)
+
+ LOG.debug("Total: %i" % len(data))
+
+ # radio should send us one final termination byte, containing
+ # checksum
+ chunk = radio.pipe.read(32)
+ if len(chunk) != 1:
+ LOG.debug("len(chunk) is %i\n" % len(chunk))
+ raise Exception("radio sent extra unknown data")
+ LOG.debug("Got: %i:\n%s" % (len(chunk), util.hexprint(chunk)))
+
+ # compute checksum
+ cs = INITIAL_CHECKSUM
+ for byte in data:
+ cs += ord(byte)
+ LOG.debug("calculated checksum is %x\n" % (cs & 0xff))
+
+ if (cs & 0xff) != ord(chunk[0]):
+ raise Exception("Failed checksum on read.")
+
+ # for debugging purposes, dump the channels, in hex.
+ for _i in range(0, 200):
+ _startData = 1892 + 20 * _i
+ chunk = data[_startData:_startData + 20]
+ LOG.debug("channel %i:\n%s" % (_i, util.hexprint(chunk)))
+
+ return memmap.MemoryMap(data)
+
+
+def _upload(radio):
+ for _i in range(0, 10):
+ data = radio.pipe.read(256)
+ if not data:
+ break
+ LOG.debug("What is this garbage?\n%s" % util.hexprint(data))
+ raise Exception("Radio sent unrecognized data")
+
+ _send(radio.pipe, IDBLOCK)
+ time.sleep(.2)
+ ack = radio.pipe.read(300)
+ LOG.debug("Ack was (%i):\n%s" % (len(ack), util.hexprint(ack)))
+ if ack != ACK:
+ raise Exception("Radio did not ack ID")
+
+ block = 0
+ cs = INITIAL_CHECKSUM
+
+ while block < (radio.get_memsize() / 32):
+ data = radio.get_mmap()[block*32:(block+1)*32]
+
+ LOG.debug("Writing block %i:\n%s" % (block, util.hexprint(data)))
+
+ _send(radio.pipe, data)
+ time.sleep(0.03)
+
+ for byte in data:
+ cs += ord(byte)
+
+ if radio.status_fn:
+ status = chirp_common.Status()
+ status.max = radio._block_sizes[1]
+ status.cur = block * 32
+ status.msg = "Cloning to radio"
+ radio.status_fn(status)
+ block += 1
+
+ _send(radio.pipe, chr(cs & 0xFF))
+
+MEM_FORMAT = """
+#seekto 0x00ef;
+ u8 currentTone;
+
+#seekto 0x00f0;
+ u8 curChannelMem[20];
+
+#seekto 0x0127;
+ u8 curChannelNum;
+
+#seekto 0x15f;
+ u8 checksum1;
+
+#seekto 0x16f;
+ u8 curentTone2;
+
+#seekto 0x1a7;
+ u8 curChannelMem2[20];
+
+#seekto 0x1df;
+ u8 checksum2;
+
+#seekto 0x06e4;
+struct {
+ u8 even_pskip:1,
+ even_skip:1,
+ even_valid:1,
+ even_masked:1,
+ odd_pskip:1,
+ odd_skip:1,
+ odd_valid:1,
+ odd_masked:1;
+} flags[225];
+
+#seekto 0x0764;
+struct {
+ u8 unknown0:2,
+ isnarrow:1,
+ unknown1:5;
+ u8 unknown2:2,
+ duplex:2,
+ unknown3:1,
+ step:3;
+ bbcd freq[3];
+ u8 power:2,
+ unknown4:3,
+ tmode:3;
+ u8 name[6];
+ u8 unknown5;
+ bbcd offset[2];
+ u8 tone;
+ u8 dtcs;
+ u8 unknown6;
+ u8 tone2;
+ u8 dtcs2;
+} memory[200];
+
+"""
+
+MODES = ["FM", "NFM"]
+TMODES = ["", "Tone", "TSQL", "DTCS", "TSQL-R"]
+DUPLEX = ["", "-", "+", ""]
+POWER_LEVELS = [chirp_common.PowerLevel("Low1", watts=5),
+ chirp_common.PowerLevel("Low2", watts=10),
+ chirp_common.PowerLevel("Low3", watts=30),
+ chirp_common.PowerLevel("Hi", watts=75),
+ ]
+
+CHARSET = "0123456789ABCDEFGHIJKLMNOPQRSTUVWXYZ +-/?C[] _"
+STEPS = [5.0, 10.0, 12.5, 15.0, 20.0, 25.0, 50.0, 100.0]
+
+
+def _decode_name(mem):
+ name = ""
+ for i in mem:
+ if (i & 0x7F) == 0x7F:
+ break
+ try:
+ name += CHARSET[i & 0x7F]
+ except IndexError:
+ LOG.debug("Unknown char index: %x " % (i))
+ name = name.strip()
+ return name
+
+
+def _encode_name(mem):
+ if(mem == " " or mem == ""):
+ return [0x7f, 0xff, 0xff, 0xff, 0xff, 0xff]
+
+ name = [None]*6
+ for i in range(0, 6):
+ try:
+ name[i] = CHARSET.index(mem[i])
+ except IndexError:
+ name[i] = CHARSET.index(" ")
+
+ name[0] = name[0] | 0x80
+ return name
+
+
+def _wipe_memory(mem):
+ mem.set_raw("\xff" * (mem.size() / 8))
+ mem.empty = True
+
+
+ at directory.register
+class FT2900Radio(YaesuCloneModeRadio):
+ """Yaesu FT-2900"""
+ VENDOR = "Yaesu"
+ MODEL = "FT-2900R"
+ BAUD_RATE = 19200
+
+ _memsize = 8000
+ _block_sizes = [8, 8000]
+
+ def get_features(self):
+ rf = chirp_common.RadioFeatures()
+
+ rf.memory_bounds = (0, 199)
+
+ rf.has_ctone = False
+ rf.has_dtcs_polarity = False
+ rf.has_bank = False
+
+ 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
+
+ return rf
+
+ def sync_in(self):
+ start = time.time()
+ try:
+ self._mmap = _download(self)
+ except errors.RadioError:
+ raise
+ except Exception, e:
+ raise errors.RadioError("Failed to communicate with radio: %s" % e)
+ LOG.info("Downloaded in %.2f sec" % (time.time() - start))
+ self.process_mmap()
+
+ def sync_out(self):
+ self.pipe.setTimeout(1)
+ start = time.time()
+ try:
+ _upload(self)
+ except errors.RadioError:
+ raise
+ except Exception, e:
+ raise errors.RadioError("Failed to communicate with radio: %s" % e)
+ LOG.info("Uploaded in %.2f sec" % (time.time() - start))
+
+ def process_mmap(self):
+ self._memobj = bitwise.parse(MEM_FORMAT, self._mmap)
+
+ def get_raw_memory(self, number):
+ return repr(self._memobj.memory[number])
+
+ def get_memory(self, number):
+ _mem = self._memobj.memory[number]
+ _flag = self._memobj.flags[(number)/2]
+
+ nibble = ((number) % 2) and "even" or "odd"
+ used = _flag["%s_masked" % nibble]
+ valid = _flag["%s_valid" % nibble]
+ pskip = _flag["%s_pskip" % nibble]
+ skip = _flag["%s_skip" % nibble]
+
+ mem = chirp_common.Memory()
+
+ mem.number = number
+
+ if _mem.get_raw()[0] == "\xFF" or not valid or not used:
+ mem.empty = True
+ return mem
+
+ mem.tuning_step = STEPS[_mem.step]
+ mem.freq = int(_mem.freq) * 1000
+
+ # compensate for 12.5 kHz tuning steps, add 500 Hz if needed
+ if(mem.tuning_step == 12.5):
+ lastdigit = int(_mem.freq) % 10
+ if (lastdigit == 2 or lastdigit == 7):
+ mem.freq += 500
+
+ mem.offset = int(_mem.offset) * 1000
+ mem.duplex = DUPLEX[_mem.duplex]
+ mem.tmode = TMODES[_mem.tmode]
+ mem.rtone = chirp_common.TONES[_mem.tone]
+ mem.dtcs = chirp_common.DTCS_CODES[_mem.dtcs]
+ if (int(_mem.name[0]) & 0x80) != 0:
+ mem.name = _decode_name(_mem.name)
+
+ mem.mode = _mem.isnarrow and "NFM" or "FM"
+ mem.skip = pskip and "P" or skip and "S" or ""
+ mem.power = POWER_LEVELS[_mem.power]
+
+ 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 "even" or "odd"
+
+ valid = _flag["%s_valid" % nibble]
+ used = _flag["%s_masked" % nibble]
+
+ if not valid:
+ _wipe_memory(_mem)
+
+ if mem.empty and valid and not used:
+ _flag["%s_valid" % nibble] = False
+ return
+
+ _flag["%s_masked" % 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.tmode = TMODES.index(mem.tmode)
+ _mem.tone = chirp_common.TONES.index(mem.rtone)
+ _mem.tone2 = chirp_common.TONES.index(mem.rtone)
+ _mem.dtcs = chirp_common.DTCS_CODES.index(mem.dtcs)
+ _mem.dtcs2 = chirp_common.DTCS_CODES.index(mem.dtcs)
+ _mem.isnarrow = MODES.index(mem.mode)
+ _mem.step = STEPS.index(mem.tuning_step)
+ _flag["%s_pskip" % nibble] = mem.skip == "P"
+ _flag["%s_skip" % nibble] = mem.skip == "S"
+ if mem.power:
+ _mem.power = POWER_LEVELS.index(mem.power)
+ else:
+ _mem.power = 3
+
+ _mem.name = _encode_name(mem.name)
+
+ LOG.debug("encoded mem\n%s\n" % (util.hexprint(_mem.get_raw()[0:20])))
+
+ @classmethod
+ def match_model(cls, filedata, filename):
+ return len(filedata) == cls._memsize
+
+ @classmethod
+ def get_prompts(cls):
+ rp = chirp_common.RadioPrompts()
+ rp.pre_download = _(dedent("""\
+ 1. Turn Radio off.
+ 2. Connect data cable.
+ 3. While holding "A/N LOW" button, turn radio on.
+ 4. <b>After clicking OK</b>, press "SET MHz" to send image."""))
+ rp.pre_upload = _(dedent("""\
+ 1. Turn Radio off.
+ 2. Connect data cable.
+ 3. While holding "A/N LOW" button, turn radio on.
+ 4. Press "MW D/MR" to receive image.
+ 5. Click OK to dismiss this dialog and start transfer."""))
+ return rp
More information about the chirp_devel
mailing list