[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