[chirp_devel] [PATCH] [JT270M] Add support for Jetstream JT270M. #1979
Tom Hayward
Fri Oct 17 22:22:51 PDT 2014
# HG changeset patch
# User Tom Hayward <tom at tomh.us>
# Date 1413609759 25200
# Fri Oct 17 22:22:39 2014 -0700
# Node ID 770dee243ad3f9b8ed087d5ecc4adda0a0a69ee3
# Parent 74639d39c27da0953899c833652996fc9aa9cc86
[JT270M] Add support for Jetstream JT270M. #1979
diff -r 74639d39c27d -r 770dee243ad3 chirp/leixen.py
--- /dev/null Thu Jan 01 00:00:00 1970 +0000
+++ b/chirp/leixen.py Fri Oct 17 22:22:39 2014 -0700
@@ -0,0 +1,328 @@
+# Copyright 2014 Tom Hayward <tom at tomh.ux
+#
+# 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 2 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 struct
+
+from chirp import chirp_common, directory, memmap, errors, util
+from chirp import bitwise
+
+MEM_FORMAT = """
+struct channel {
+ bbcd rx_freq[4];
+ bbcd tx_freq[4];
+ u8 rx_tone;
+ u8 rx_tmode;
+ u8 tx_tone;
+ u8 tx_tmode;
+ u8 unknown5;
+ u8 pttidoff:1,
+ dtmfoff:1,
+ unknown6:1,
+ tailcut:1,
+ aliasop:1,
+ talkaroundoff:1,
+ voxoff:1,
+ skip:1;
+ u8 power:1,
+ mode:1
+ reverseoff:1,
+ blckoff:1,
+ unknown7:4;
+ u8 unknown8;
+};
+
+struct name {
+ char name[7];
+ u8 pad;
+};
+
+#seekto 0x0d00;
+struct channel default[3];
+struct channel memory[199];
+
+#seekto 0x19b0;
+struct name defaultname[3];
+struct name name[199];
+"""
+
+
+POWER_LEVELS = [chirp_common.PowerLevel("Low", watts=4),
+ chirp_common.PowerLevel("High", watts=10)]
+MODES = ["NFM", "FM"]
+WTFTONES = map(float, xrange(56, 64))
+TONES = WTFTONES + chirp_common.TONES
+DTCS_CODES = [17, 50, 645] + chirp_common.DTCS_CODES
+DTCS_CODES.sort()
+TMODES = ["", "Tone", "DTCS", "DTCS"]
+
+
+def checksum(frame):
+ x = 0
+ for b in frame:
+ x ^= ord(b)
+ return chr(x)
+
+def make_frame(cmd, addr, data=""):
+ payload = struct.pack(">H", addr) + data
+ header = struct.pack(">BB", ord(cmd), len(payload))
+ frame = header + payload
+ return frame + checksum(frame)
+
+def send(radio, frame):
+ # print "%04i P>R: %s" % (len(frame), util.hexprint(frame).replace("\n", "\n "))
+ try:
+ radio.pipe.write(frame)
+ except Exception, e:
+ raise errors.RadioError("Failed to communicate with radio: %s" % e)
+
+def recv(radio, readdata=True):
+ hdr = radio.pipe.read(4)
+ # print "%04i P<R: %s" % (len(hdr), util.hexprint(hdr).replace("\n", "\n "))
+ if hdr == "\x09\x00\x09":
+ raise errors.RadioError("Radio rejected command.")
+ cmd, length, addr = struct.unpack(">BBH", hdr)
+ length -= 2
+ if readdata:
+ data = radio.pipe.read(length)
+ # print " P<R: %s" % util.hexprint(hdr + data).replace("\n", "\n ")
+ if len(data) != length:
+ raise errors.RadioError("Radio sent %i bytes (expected %i)" % (
+ len(data), length))
+ chk = radio.pipe.read(1)
+ else:
+ data = ""
+ return addr, data
+
+def do_ident(radio):
+ send(radio, "\x02\x06LEIXEN\x17")
+ ident = radio.pipe.read(9)
+ print " P<R: %s" % util.hexprint(ident).replace("\n", "\n ")
+ if ident != "\x06\x06leixen\x13":
+ raise errors.RadioError("Radio refused program mode")
+ radio.pipe.write("\x06\x00\x06")
+ ack = radio.pipe.read(3)
+ if ack != "\x06\x00\x06":
+ raise errors.RadioError("Radio did not ack.")
+
+def do_download(radio):
+ do_ident(radio)
+
+ data = ""
+ for start, end in radio._ranges:
+ data += "\xFF" * (start - len(data))
+ for addr in range(start, end, 0x10):
+ send(radio, make_frame("R", addr, chr(0x10)))
+ _addr, _data = recv(radio)
+ if _addr != addr:
+ raise errors.RadioError("Radio sent unexpected address")
+ data += _data
+
+ status = chirp_common.Status()
+ status.cur = addr
+ status.max = radio._memsize
+ status.msg = "Cloning from radio"
+ radio.status_fn(status)
+
+ finish(radio)
+
+ return memmap.MemoryMap(data)
+
+def do_upload(radio):
+ do_ident(radio)
+
+ for addr in range(0x0d00, 0x2000, 0x10):
+ frame = make_frame("W", addr, radio._mmap[addr:addr + 0x10])
+ send(radio, frame)
+ # print " P<R: %s" % util.hexprint(frame).replace("\n", "\n ")
+ radio.pipe.write("\x06\x00\x06")
+ ack = radio.pipe.read(3)
+ if ack != "\x06\x00\x06":
+ raise errors.RadioError("Radio refused block at %04x" % addr)
+
+ status = chirp_common.Status()
+ status.cur = addr
+ status.max = radio._memsize
+ status.msg = "Cloning to radio"
+ radio.status_fn(status)
+
+ finish(radio)
+
+def finish(radio):
+ send(radio, "\x64\x01\x6F\x0A")
+ ack = radio.pipe.read(8)
+
+
+ at directory.register
+class JetstreamJT270MRadio(chirp_common.CloneModeRadio):
+ """Jetstream JT270M"""
+ VENDOR = "Jetstream"
+ MODEL = "JT270M"
+ BAUD_RATE = 9600
+
+ _memsize = 0x2000
+ _ranges = [
+ (0x0900, 0x0910),
+ (0x0d00, 0x2000),
+ ]
+
+ def get_features(self):
+ rf = chirp_common.RadioFeatures()
+ rf.has_settings = True
+ rf.has_cross = True
+ rf.has_bank = False
+ rf.has_tuning_step = False
+ rf.has_rx_dtcs = True
+ rf.valid_tmodes = ['', 'Tone', 'TSQL', 'DTCS', 'Cross']
+ rf.valid_modes = MODES
+ rf.valid_cross_modes = [
+ "Tone->Tone",
+ "DTCS->",
+ "->DTCS",
+ "Tone->DTCS",
+ "DTCS->Tone",
+ "->Tone",
+ "DTCS->DTCS"]
+ rf.valid_characters = chirp_common.CHARSET_ASCII
+ rf.valid_name_length = 7
+ rf.valid_power_levels = POWER_LEVELS
+ rf.valid_skips = ["", "S"]
+ rf.valid_bands = [(136000000, 174000000),
+ (400000000, 470000000)]
+ rf.memory_bounds = (1, 199)
+ return rf
+
+ def sync_in(self):
+ try:
+ self._mmap = do_download(self)
+ except Exception, e:
+ finish(self)
+ raise errors.RadioError("Failed to download from radio: %s" % e)
+ self.process_mmap()
+
+ def process_mmap(self):
+ self._memobj = bitwise.parse(MEM_FORMAT, self._mmap)
+
+ def sync_out(self):
+ try:
+ do_upload(self)
+ except errors.RadioError:
+ finish(self)
+ raise
+ except Exception, e:
+ raise errors.RadioError("Failed to upload to radio: %s" % e)
+
+ def get_raw_memory(self, number):
+ return repr(self._memobj.name[number - 1]) + \
+ repr(self._memobj.memory[number - 1])
+
+ def _get_tone(self, mem, _mem):
+ rx_tone = tx_tone = None
+
+ tx_tmode = TMODES[_mem.tx_tmode]
+ rx_tmode = TMODES[_mem.rx_tmode]
+
+ if tx_tmode == "Tone":
+ tx_tone = TONES[_mem.tx_tone - 1]
+ elif tx_tmode == "DTCS":
+ tx_tone = DTCS_CODES[_mem.tx_tone - 1]
+
+ if rx_tmode == "Tone":
+ rx_tone = TONES[_mem.rx_tone - 1]
+ elif rx_tmode == "DTCS":
+ rx_tone = DTCS_CODES[_mem.rx_tone - 1]
+
+ tx_pol = _mem.tx_tmode == 0x03 and "R" or "N"
+ rx_pol = _mem.rx_tmode == 0x03 and "R" or "N"
+
+ chirp_common.split_tone_decode(mem, (tx_tmode, tx_tone, tx_pol),
+ (rx_tmode, rx_tone, rx_pol))
+
+ def get_memory(self, number):
+ _mem = self._memobj.memory[number - 1]
+ _name = self._memobj.name[number - 1]
+
+ mem = chirp_common.Memory()
+ mem.number = number
+
+ if _mem.get_raw()[:4] == "\xFF\xFF\xFF\xFF":
+ mem.empty = True
+ return mem
+
+ mem.freq = int(_mem.rx_freq) * 10
+ offset = (int(_mem.tx_freq) * 10) - mem.freq
+ if offset < 0:
+ mem.offset = abs(offset)
+ mem.duplex = "-"
+ elif offset > 0:
+ mem.offset = offset
+ mem.duplex = "+"
+ else:
+ mem.offset = 0
+
+ mem.name = str(_name.name).rstrip()
+
+ self._get_tone(mem, _mem)
+ mem.mode = MODES[_mem.mode]
+ mem.power = POWER_LEVELS[_mem.power]
+ mem.skip = _mem.skip and "S" or ""
+
+ return mem
+
+ def _set_tone(self, mem, _mem):
+ ((txmode, txtone, txpol),
+ (rxmode, rxtone, rxpol)) = chirp_common.split_tone_encode(mem)
+
+ _mem.tx_tmode = TMODES.index(txmode)
+ _mem.rx_tmode = TMODES.index(rxmode)
+ if txmode == "Tone":
+ _mem.tx_tone = TONES.index(txtone) + 1
+ elif txmode == "DTCS":
+ _mem.tx_tmode = txpol == "R" and 0x03 or 0x02
+ _mem.tx_tone = DTCS_CODES.index(txtone) + 1
+ if rxmode == "Tone":
+ _mem.rx_tone = TONES.index(rxtone) + 1
+ elif rxmode == "DTCS":
+ _mem.rx_tmode = rxpol == "R" and 0x03 or 0x02
+ _mem.rx_tone = DTCS_CODES.index(rxtone) + 1
+
+ def set_memory(self, mem):
+ _mem = self._memobj.memory[mem.number - 1]
+ _name = self._memobj.name[mem.number - 1]
+
+ if mem.empty:
+ _mem.set_raw("\xFF" * 16)
+ return
+ elif _mem.get_raw() == ("\xFF" * 16):
+ _mem.set_raw("\xFF" * 8 + "\xFF\x00\xFF\x00\xFF\xFE\xF0\xFC")
+
+ _mem.rx_freq = mem.freq / 10
+ if mem.duplex == "+":
+ _mem.tx_freq = (mem.freq + mem.offset) / 10
+ elif mem.duplex == "-":
+ _mem.tx_freq = (mem.freq - mem.offset) / 10
+ else:
+ _mem.tx_freq = mem.freq / 10
+
+ self._set_tone(mem, _mem)
+
+ _mem.power = mem.power and POWER_LEVELS.index(mem.power) or 1
+ _mem.mode = MODES.index(mem.mode)
+ _mem.skip = mem.skip == "S"
+ _name.name = mem.name.ljust(7)
+
+ @classmethod
+ def match_model(cls, filedata, filename):
+ model = filedata[0x900:0x906]
+ return model == cls.MODEL
More information about the chirp_devel
mailing list