# HG changeset patch
# User David Fannin <dfannin@sushisoft.com>
# Date 1380435487 25200
# Node ID 1116e67dca66f0bbdd33a6296210d87785e2fa57
# Parent  ac65038940f227d46ba28186627b42108dae6447
[TYT TH-9000VHF]  Initial TYT TH9000 model file and memory map skeleton file

New Model #1035

This is the memory map and base model class for the TYT-TH9000 VHF 2 meter radio.

It includes the memory map description (for about 80% of the parameters) and the 200 channel bank.
I've tried to include the description of the fields, and varified the parameters via the serial capture/decode method
(I used the TYT 1.05 windows from TYT).

It also includes a base class definition for the model.

It compiles cleanly, but it is not working code ; this should be used as a starting point for further development for this model.

The TYT shares a number of similar memory map features to the Anytone 5888 model, so you can use that model for comparison. However,
it has a number of substantial differences from that model, especially in the skip/channel flags, and also a number of parameters are different from
the Anytone - It did not make sense to combine the two models directly. However, there are a number of methods that are the same.   I copied those methods
over from the Anytone file - I'll look at the option of creating a base abstract class where these functions can be shared.

diff -r ac65038940f2 -r 1116e67dca66 chirp/th9000.py
--- /dev/null	Thu Jan 01 00:00:00 1970 +0000
+++ b/chirp/th9000.py	Sat Sep 28 23:18:07 2013 -0700
@@ -0,0 +1,497 @@
+
+
+
+
+# Copyright 2013 Dan Smith <dsmith@danplanet.com>
+#
+# 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 os
+import struct
+import time
+
+from chirp import bitwise
+from chirp import chirp_common
+from chirp import directory
+from chirp import errors
+from chirp import memmap
+from chirp import util
+
+"""
+ Description: Chirp Data Structure of the Memory Map for TYT TH-9000/VHF 2Meter 144-148Mhz Radio
+ Version: 0.6
+ Date: 9/28/2013
+ Status: Imcomplete
+    - This is the initial, incomplete version of the channel data structure. 
+    - The memory map looks a lot like the Anytone 5888 model, however, these does appear to be significant differences.
+
+
+
+Overall Memory Map:
+
+    Memory Map (Range 0x0100-3FF0, step 0x10):
+
+        Field                   Start  End  Size   
+                                (hex)  (hex) (hex)  
+        
+        1 Channel Set Flag        0100  011F   20 
+        2 Channel Skip Flag       0120  013F   20 
+        3 Blank/Unknown           0140  01EF   B0 
+        4 Unknown                 01F0  01FF   10
+        5 TX/RX Range             0200  020F   10    
+        6 Bootup Passwd           0210  021F   10 
+        7 Options, Radio          0220  023F   20
+        8 Unknown                 0240  019F   
+        9 Channel Bank            2000  38FF 1900  
+             Channel 000          2000  201F   20  
+             Channel 001          2020  202F   20  
+             ... 
+             Channel 199          38E0  38FF   20 
+        10 Blank/Unknown          3900  3FFF  6FF  14592  16383    1792   
+            Total Map Size           16128 (2^8 = 16384)
+
+
+   Protocol for Upload Sequence, Only Writes shown:
+
+   1) Start 
+      "Program"                   50 52 4F 47 52 41 4D
+      0x02                        02
+      "R\0x01\0x40\x10"           52 01 40 10  (?means "Read" address 1040, 16 bytes, but doesn't match write data field) 
+
+   2) Write Records, Loop from address 0100 to 3900, step 0x10
+     "W"<address><length><data><checksum>"\0x06"   57 .. .. 10 ..(*16)  .. 06  (write record of 16 bytes)  
+       header[4 bytes]:
+         command[1] = Write ("W")
+         address[2] = 0100 to 3900
+         length[1] = 0x10 (16 bytes length)
+       data[16 bytes]:
+         data[16] = 16 byte payload
+       end[2 bytes]:
+         checksum[1] = 1 byte,  uses digit check checksum ( sum of header and data bytes, modulo 256)
+         end of record[1] = 0x06 (ascii ACK char)
+
+   3) END
+     "END"   45 4E 44
+
+     
+
+"""
+
+
+"""
+  TH9000/VHF  memory map 
+  section: 1 and 2:  Channel Set/Skip Flags
+
+    Channel Set (starts 0x100) : Channel Set  bit is value 0 if a memory location in the channel bank is active.
+    Channel Skip (starts 0x120): Channel Skip bit is value 0 if a memory location in the channel bank is active.
+
+    Both flag maps are a total 24 bytes in length, aligned on 32 byte records.
+    bit = 0 channel set/no skip,  1 is channel not set/skip
+
+    to index a channel:
+        cbyte = channel / 8 ;
+        cbit  = channel % 8 ;
+        setflag  = csetflag[cbyte].c[cbit] ;
+        skipflag = cskipflag[cbyte].c[cbit] ;
+
+    channel range is 0-199, range is 32 bytes (last 7 unknown)
+"""
+
+mem_format = """
+#seekto 0x0100;
+struct {
+   bit c[8];
+} csetflag[24];
+
+struct {
+   u8 unknown0100[7] ;
+}
+
+#seekto 0x0120
+struct {
+   bit c[8];
+} cskipflag[24] ;
+
+struct {
+   u8 unknown0120[7] ;
+}
+"""
+
+"""
+  TH9000/VHF  memory map 
+  section: 5  TX/RX Range
+     used to set the TX/RX range of the radio (e.g.  144-148Mhz for 2 meter)
+     possible to set range to 136-172Mhz for tx/rx 
+
+"""
+mem_format = """
+#seekto 0x0200;
+struct {
+    bbcd txrangelow[4];
+    bbcd txrangehi[4];
+    bbcd rxrangelow[4];
+    bbcd rxrangehi[4];
+}
+"""
+
+"""
+  TH9000/VHF  memory map 
+  section: 6  bootup_passwd
+     used to set bootup passwd (see boot_passwd checkbox option)
+
+  options - bootup password
+
+  bytes:bit   type                 description
+  ---------------------------------------------------------------------------
+  6         u8 bootup_passwd[6]     bootup passwd, 6 chars, numberic chars 30-39 , see boot_passwd checkbox to set
+  10        u8 unknown;  
+
+"""
+
+mem_format = mem_format + """
+#seekto 0x0210
+struct {
+   u8 bootup_passwd[6];
+   u8 unknown2010[10];
+};
+"""
+
+"""
+  TH9000/VHF  memory map 
+  section: 7  Radio Options  
+        used to set a number of radio options 
+
+  bytes:bit   type                 description
+  ---------------------------------------------------------------------------
+  1         u8 display_mode     display mode, range 0-2, 0=freq,1=channel,2=name (selecting name affects vfo_mr)
+  1         u8 vfo_mr;          vfo_mr , 0=vfo, mr=1 
+  1         u8 unknown;  
+  1         u8 squelch;         squelch level, range 0-19, hex for menu
+  1         u8 unknown[2]; 
+  1         u8 channel_lock;    if display_mode[channel] selected, then lock=1,no lock =0
+  1         u8 unknown; 
+  1         u8 bg_brightness ;  background brightness, range 0-21, hex, menu index 
+  1         u8 unknown;     
+  1         u8 bg_color ;       bg color, menu index,  blue 0 , orange 1, purple 2
+  1         u8 tbst_freq ;      tbst freq , menu 0 = 1750Hz, 1=2100 , 2=1000 , 3=1450hz 
+  1         u8 timeout_timer;   timeout timer, hex, value = minutes, 0= no timeout
+  1         u8 unknown; 
+  1         u8 auto_power_off;   auto power off, range 0-3, off,30min, 1hr, 2hr, hex menu index
+  1         u8 voice_prompt;     voice prompt, value 0,1 , Beep ON = 1, Beep Off = 2
+"""
+mem_format = mem_format + """
+#seekto 0x0220
+struct {
+   u8 display_mode; 
+   u8 vfo_mr; 
+   u8 unknown0220A; 
+   u8 squelch; 
+   u8 unknown0220B[2]; 
+   u8 channel_lock; 
+   u8 unknown0220C; 
+   u8 bg_brightness; 
+   u8 unknown0220D; 
+   u8 bg_color;
+   u8 tbst_freq;
+   u8 timeout_timer;
+   u8 unknown0220E;
+   u8 auto_power_off;
+   u8 voice_prompt; 
+};
+"""
+
+"""
+  description of function setup options, starting at 0x0230
+
+  bytes:bit   type                 description
+  ---------------------------------------------------------------------------
+  1         u8  // 0
+   :4       unknown:6
+   :1       elim_sql_tail:1   eliminate squelsh tail when no ctcss checkbox (1=checked)
+   :1       sql_key_function  "squelch off" 1 , "squelch momentary off" 0 , menu index
+  2         u8 unknown[2] /1-2  
+  1         u8 // 3
+   :4       unknown:4
+   :1       inhibit_init_ops:1 //bit 5
+   :1       unknownD:1
+   :1       inhibit_setup_bg_chk:1 //bit 7
+   :1       unknown:1
+  1         u8 tail_elim_type    menu , (off=0,120=1,180=2),  // 4
+  1         u8 choose_tx_power    menu , (60w=0,25w=1) // 5
+  2         u8 unknown[2]; // 6-7 
+  1         u8 bootup_passwd_flag  checkbox 1=on, 0=off // 8
+  7         u8 unknown[7]; // 9-F 
+  
+"""
+
+mem_format = mem_format + """
+#seekto 0x0230
+struct {
+   u8 unknown0230A:6,
+      elim_sql_tail:1,
+      sql_key_function:1;
+   u8 unknown0230B[2];
+   u8 unknown0230C:4, 
+      inhibit_init_ops:1,
+      unknown0230D:1,
+      inhibit_setup_bg_chk,
+      unknown0230E:1;
+   u8 tail_elim_type;
+   u8 choose_tx_power;
+   u8 unknown0230F[2];
+   u8 bootup_passwd_flag;
+   u8 unknown0230G[7];
+};
+"""
+
+"""
+  TH9000/VHF  memory map 
+  section: 9  Channel Bank
+         description of channel bank (200 channels , range 0-199)
+         Each 32 Byte (0x20 hex)  record:
+  bytes:bit   type                 description
+  ---------------------------------------------------------------------------
+  4         bbcd freq[4]        receive frequency in packed binary coded decimal  
+  4         bbcd offset[4]      transmit offset in packed binary coded decimal (note: plus/minus direction set by 'duplex' field)
+  1         u8
+   :4       unknown:4
+   :4       tune_step:4         tuning step, menu index value from 0-9
+  1         u8
+   :4       unknown:4          not yet decoded, used for DCS coding?
+   :2       channel_width:2     channel spacing, menu index value from 0-3
+   :1       reverse:1           reverse flag, 0=off, 1=on (reverses tx and rx freqs)
+   :1       txoff:1             transmitt off flag, 0=transmit , 1=do not transmit 
+  1         u8
+   :1       talkaround:1        talkaround flag, 0=off, 1=on (bypasses repeater) 
+   :1       compander:1         compander flag, 0=off, 1=on (turns on/off voice compander option)  
+   :2       unknown:2          
+   :2       power:2             tx power setting, value range 0-2, 0=hi,1=med,2=lo 
+   :2       duplex:2            duplex settings, 0=simplex,1= minus(-) offset, 2= plus (+) offset (see offset field) 
+  1         u8 
+   :4       unknown:4
+   :2       rxtmode:2           rx tone mode, value range 0-2, 0=none, 1=CTCSS, 2=DCS  (ctcss tone in field rxtone)
+   :2       txtmode:2           tx tone mode, value range 0-2, 0=none, 1=CTCSS, 3=DCS  (ctcss tone in field txtone)
+  1         u8 
+   :2       unknown:2
+   :6       txtone:6            tx ctcss tone, menu index
+  1         u8 
+   :2       unknown:2 
+   :6       rxtone:6            rx ctcss tone, menu index
+  1         u8 txcode           ?, not used for ctcss
+  1         u8 rxcode           ?, not used for ctcss
+  3         u8 unknown[3]
+  7         char name[7]        7 byte char string for channel name
+  1         u8 
+   :6       unknown:6,
+   :2       busychannellockout:2 busy channel lockout option , 0=off, 1=repeater, 2=busy  (lock out tx if channel busy)
+  4         u8 unknownI[4];
+  1         u8 
+   :7       unknown:7 
+   :1       scrambler:1         scrambler flag, 0=off, 1=on (turns on tyt scrambler option)
+"""
+
+mem_format = mem_format + """
+#seekto 0x2000;
+struct memory {
+  bbcd freq[4];
+  bbcd offset[4];
+  u8 unknown2000A:4,
+     tune_step:4;
+  u8 unknown2000B:4,
+     channel_width:2,
+     reverse:1,
+     txoff:1;
+  u8 talkaround:1,
+     compander:1,
+     unknown2000C:2,
+     power:2,
+     duplex:2;
+  u8 unknown2000D:4,
+     rxtmode:2,
+     txtmode:2;
+  u8 unknown2000E:2,
+     txtone:6;
+  u8 unknown2000F:2,
+     rxtone:6;
+  u8 txcode;
+  u8 rxcode;
+  u8 unknown2000G[3];
+  char name[7];
+  u8 unknown2000H:6,
+     busychannellockout:2;
+  u8 unknown2000I[4];
+  u8 unknown2000J:7,
+     scrambler:1; 
+};
+struct memory memory[200] ;
+"""
+
+
+def _debug(string):   
+    if "CHIRP_DEBUG" in os.environ or True:
+        print string
+
+def _echo_write(radio, data):
+    try:
+        radio.pipe.write(data)
+        radio.pipe.read(len(data))
+    except Exception, e:
+        print "Error writing to radio: %s" % e
+        raise errors.RadioError("Unable to write to radio")
+
+def _read(radio, length):
+    try:
+        data = radio.pipe.read(length)
+    except Exception, e:
+        print "Error reading from radio: %s" % e
+        raise errors.RadioError("Unable to read from radio")
+
+    if len(data) != length:
+        print "Short read from radio (%i, expected %i)" % (len(data),
+                                                           length)
+        print util.hexprint(data)
+        raise errors.RadioError("Short read from radio")
+    return data
+
+def _ident(radio):
+    radio.pipe.setTimeout(1)
+    _echo_write(radio, "PROGRAM")
+    response = radio.pipe.read(3)
+    if response != "QX\x06":
+        print "Response was:\n%s" % util.hexprint(response)
+        raise errors.RadioError("Unsupported model")
+    _echo_write(radio, "\x02")
+    response = radio.pipe.read(16)
+    _debug(util.hexprint(response))
+    if response[1:8] != "ITH-9000":
+        print "Response was:\n%s" % util.hexprint(response)
+        raise errors.RadioError("Unsupported model")
+
+def _finish(radio):
+    endframe = "\x45\x4E\x44"
+    _echo_write(radio, endframe)
+    result = radio.pipe.read(1)
+    if result != "\x06":
+        print "Got:\n%s" % util.hexprint(result)
+        raise errors.RadioError("Radio did not finish cleanly")
+
+def _checksum(data):
+    cs = 0
+    for byte in data:
+        cs += ord(byte)
+    return cs % 256
+
+def _send(radio, cmd, addr, length, data=None):
+    frame = struct.pack(">cHb", cmd, addr, length)
+    if data:
+        frame += data
+        frame += chr(_checksum(frame[1:]))
+        frame += "\x06"
+    _echo_write(radio, frame)
+    _debug("Sent:\n%s" % util.hexprint(frame))
+    if data:
+        result = radio.pipe.read(1)
+        if result != "\x06":
+            print "Ack was: %s" % repr(result)
+            raise errors.RadioError("Radio did not accept block at %04x" % addr)
+        return
+    result = _read(radio, length + 6)
+    _debug("Got:\n%s" % util.hexprint(result))
+    header = result[0:4]
+    data = result[4:-2]
+    ack = result[-1]
+    if ack != "\x06":
+        print "Ack was: %s" % repr(ack)
+        raise errors.RadioError("Radio NAK'd block at %04x" % addr)
+    _cmd, _addr, _length = struct.unpack(">cHb", header)
+    if _addr != addr or _length != _length:
+        print "Expected/Received:"
+        print " Length: %02x/%02x" % (length, _length)
+        print " Addr: %04x/%04x" % (addr, _addr)
+        raise errors.RadioError("Radio send unexpected block")
+    cs = _checksum(result[1:-2])
+    if cs != ord(result[-2]):
+        print "Calculated: %02x" % cs
+        print "Actual:     %02x" % ord(result[-2])
+        raise errors.RadioError("Block at 0x%04x failed checksum" % addr)
+    return data
+
+def _download(radio):
+    _ident(radio)
+
+    memobj = None
+
+    data = ""
+    for start, end in radio._ranges:
+        for addr in range(start, end, 0x10):
+            block = _send(radio, 'R', addr, 0x10)
+            data += block
+
+            status = chirp_common.Status()
+            status.cur = len(data)
+            status.max = end
+            status.msg = "Cloning from radio"
+            radio.status_fn(status)
+
+    memobj = bitwise.parse(_mem_format, data)
+    _finish(radio)
+
+    return memmap.MemoryMap(data)
+
+def _upload(radio):
+    _ident(radio)
+
+    for start, end in radio._ranges:
+        for addr in range(start, end, 0x10):
+            block = radio._mmap[addr:addr + 0x10]
+            _send(radio, 'W', addr, len(block), block)
+
+            status = chirp_common.Status()
+            status.cur = addr
+            status.max = end
+            status.msg = "Cloning to radio"
+            radio.status_fn(status)
+
+    _finish(radio)
+
+
+
+@directory.register
+class TytTh9000VHFRadio(chirp_common.CloneModeRadio,
+                         chirp_common.ExperimentalRadio):
+    """TYT TH-9000 VHF"""
+    VENDOR = "TYT"
+    MODEL = "TH9000"
+    BAUD_RATE = 9600
+
+    # May try to mirror the OEM behavior later
+    _ranges = [
+        (0x0100, 0x3FF0),
+        ]
+
+    @classmethod
+    def get_experimental_warning(cls):
+        return "FOO"
+
+    def get_features(self):
+        rf = chirp_common.RadioFeatures()
+        rf.valid_bands = [(136000000)]
+        rf.valid_characters = chirp_common.CHARSET_UPPER_NUMERIC
+        rf.valid_name_length = 7
+        rf.memory_bounds = (0, 199)
+        return rf
+
+    @classmethod
+    def match_model(cls, filedata, filename):
+        return filedata[0x21:0x28] == "ITH-9000"
