<div dir="ltr">Attached:<div>- factory image to test against</div><div>- patch file in case the &quot;hg email tip&quot; version does not apply</div><div><br></div><div>Jim KC9HI</div></div><br><div class="gmail_quote"><div dir="ltr" class="gmail_attr">On Mon, Nov 16, 2020 at 4:08 PM Jim Unroe via chirp_devel &lt;<a href="mailto:chirp_devel@intrepid.danplanet.com">chirp_devel@intrepid.danplanet.com</a>&gt; wrote:<br></div><blockquote class="gmail_quote" style="margin:0px 0px 0px 0.8ex;border-left:1px solid rgb(204,204,204);padding-left:1ex"># HG changeset patch<br># User Jim Unroe <u></u><br># Date 1605557938 18000<br>#      Mon Nov 16 15:18:58 2020 -0500<br># Node ID 3fd7ed9a0de01f7897286c9ac274237a45cc7831<br># Parent  d5e496f563fdfc9ea89dea5f119357235b82db6f<br>[TH-UV88] New Model: TYT TH-UV88<br><br>This patch adds support for the TYT TH-UV88<br><br>Initial radio protocol decode, channels and memory layout<br>by James Berry <u></u>, Summer 2020<br><br>Related to #7817<br><br>diff -r d5e496f563fd -r 3fd7ed9a0de0 chirp/drivers/th_uv88.py<br>--- /dev/null        Thu Jan 01 00:00:00 1970 +0000<br>+++ b/chirp/drivers/th_uv88.py        Mon Nov 16 15:18:58 2020 -0500<br>@@ -0,0 +1,917 @@<br>+# Version 1.0 for TYT-UV88<br>+# Initial radio protocol decode, channels and memory layout<br>+# by James Berry <u></u>, Summer 2020<br>+# Additional configuration and help, Jim Unroe <u></u><br>+#<br>+# This program is free software: you can redistribute it and/or modify<br>+# it under the terms of the GNU General Public License as published by<br>+# the Free Software Foundation, either version 2 of the License, or<br>+# (at your option) any later version.<br>+#<br>+# This program is distributed in the hope that it will be useful,<br>+# but WITHOUT ANY WARRANTY; without even the implied warranty of<br>+# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the<br>+# GNU General Public License for more details.<br>+#<br>+# You should have received a copy of the GNU General Public License<br>+# along with this program.  If not, see <a href="https://cgafehc.r.bh.d.sendibt3.com/tr/cl/QpgoPSH5r0NV4hvIlBzrvEcq4oc9bR7ML-hY3nUq1zcKpuB6PoNuxRO5sk1uyvjY6xNti_2BBKjXrXX0srodnvuyJCJoyWI2-Yvxp23eOYPzyGJ9YGcERkgxudTdeOKMmfxyCn3t2CYacNXoU-fUTH2l-Z72EkLeHxcFk6bRAjewSghXhQJGJrU87xwkmenKqH8swQaX2VY" target="_blank">http://www.gnu.org/licenses/</a>.<br>+<br>+import time<br>+import struct<br>+import logging<br>+import re<br>+import math<br>+from chirp import chirp_common, directory, memmap<br>+from chirp import bitwise, errors, util<br>+from chirp.settings import RadioSettingGroup, RadioSetting, \<br>+    RadioSettingValueBoolean, RadioSettingValueList, \<br>+    RadioSettingValueString, RadioSettingValueInteger, \<br>+    RadioSettingValueFloat, RadioSettings, InvalidValueError<br>+from textwrap import dedent<br>+<br>+LOG = logging.getLogger(__name__)<br>+<br>+MEM_FORMAT = &quot;&quot;&quot;<br>+struct chns {<br>+  ul32 rxfreq;<br>+  ul32 txfreq;<br>+  ul16 scramble:4<br>+       rxtone:12; //decode:12<br>+  ul16 decodeDSCI:1<br>+       encodeDSCI:1<br>+       unk1:1<br>+       unk2:1<br>+       txtone:12; //encode:12<br>+  u8   power:2<br>+       wide:2<br>+       b_lock:2<br>+       unk3:2;<br>+  u8   unk4:3<br>+       signal:2<br>+       displayName:1<br>+       unk5:2;<br>+  u8   unk6:2<br>+       pttid:2<br>+       step:4;               // not required<br>+  u8   name[6];<br>+};<br>+<br>+struct vfo {<br>+  ul32 rxfreq;<br>+  ul32 txfreq;  // displayed as an offset<br>+  ul16 scramble:4<br>+       rxtone:12; //decode:12<br>+  ul16 decodeDSCI:1<br>+       encodeDSCI:1<br>+       unk1:1<br>+       unk2:1<br>+       txtone:12; //encode:12<br>+  u8   power:2<br>+       wide:2<br>+       b_lock:2<br>+       unk3:2;<br>+  u8   unk4:3<br>+       signal:2<br>+       displayName:1<br>+       unk5:2;<br>+  u8   unk6:2<br>+       pttid:2<br>+       step:4;<br>+  u8   name[6];<br>+};<br>+<br>+struct chname {<br>+  u8  extra_name[10];<br>+};<br>+<br>+#seekto 0x0000;<br>+struct chns chan_mem[199];<br>+<br>+#seekto 0x1960;<br>+struct chname chan_name[199];<br>+<br>+#seekto 0x1180;<br>+struct {<br>+  u8 bitmap[26];    // one bit for each channel marked in use<br>+} chan_avail;<br>+<br>+#seekto 0x11A0;<br>+struct {<br>+  u8 bitmap[26];    // one bit for each channel skipped<br>+} chan_skip;<br>+<br>+#seekto 0x1140;<br>+struct {<br>+  u8 autoKeylock:1,       // 0x1140 [18] *OFF, On<br>+     unk_bit6_5:2,        //<br>+     vfomrmode:1,         //        *VFO, MR<br>+     unk_bit3_0:4;        //<br>+  u8 unk_1141;            // 0x1141<br>+  u8 unk_1142;            // 0x1142<br>+  u8 unk_bit7_3:5,        //<br>+     ab:1,                //        * A, B<br>+     unk_bit1_0:2;        //<br>+} workmodesettings;<br>+<br>+#seekto 0x1160;<br>+struct {<br>+  u8 introScreen1[12];    // 0x1160 *Intro Screen Line 1(truncated to 12 alpha<br>+                          //         text characters)<br>+  u8 offFreqVoltage : 3,  // 0x116C unknown referred to in code but not on<br>+                          //        screen<br>+     unk_bit4 : 1,        //<br>+     sqlLevel : 4;        //        [05] *OFF, 1-9<br>+  u8 beep : 1             // 0x116D [09] *OFF, On<br>+     callKind : 2,        //        code says 1750,2100,1000,1450 as options<br>+                          //        not on screen<br>+     introScreen: 2,      //        [20] *OFF, Voltage, Char String<br>+     unkstr2: 2,          //<br>+     txChSelect : 1;      //        [02] *Last CH, Main CH<br>+  u8 autoPowOff : 3,      // 0x116E not on screen? OFF, 30Min, 1HR, 2HR<br>+     unk : 1,             //<br>+     tot : 4;             //        [11] *OFF, 30 Second, 60 Second, 90 Second,<br>+                          //              ... , 270 Second<br>+  u8 unk_bit7:1,          // 0x116F<br>+     roger:1,             //        [14] *OFF, On<br>+     dailDef:1,           //        Unknown - &#39;Volume, Frequency&#39;<br>+     language:1,          //        ?Chinese, English<br>+     unk_bit3:1,          //<br>+     endToneElim:1,       //        *OFF, Frequency<br>+     unkCheckBox1:1,      //<br>+     unkCheckBox2:1;      //<br>+  u8 scanResumeTime : 2,  // 0x1170 2S, 5S, 10S, 15S (not on screen)<br>+     disMode : 2,         //        [33] *Frequency, Channel, Name<br>+     scanType: 2,         //        [17] *To, Co, Se<br>+     ledMode: 2;          //        [07] *Off, On, Auto<br>+  u8 unky;                // 0x1171<br>+  u8 str6;                // 0x1172 Has flags to do with logging - factory<br>+                          // enabled (bits 16,64,128)<br>+  u8 unk;                 // 0x1173<br>+  u8 swAudio : 1,         // 0x1174 [19] *OFF, On<br>+     radioMoni : 1,       //        [34]*OFF, On<br>+     keylock : 1,         //        *OFF, Auto<br>+     dualWait : 1,        //        [06] *OFF, On<br>+     unk_bit3 : 1,        //<br>+     light : 3;           //        [08] *1, 2, 3, 4, 5, 6, 7<br>+  u8 voxSw : 1,           // 0x1175 [13] *OFF, On<br>+     voxDelay: 4,         //        *0.5S, 1.0S, 1.5S, 2.0S, 2.5S, 3.0S, 3.5S,<br>+                          //         4.0S, 4.5S, 5.0S<br>+     voxLevel : 3;        //        [03] *1, 2, 3, 4, 5, 6, 7<br>+  u8 str9 : 4,            // 0x1176<br>+     saveMode : 2,        //        [16] *OFF, 1:1, 1:2, 1:4<br>+     keyMode : 2;         //        [32] *ALL, PTT, KEY, Key &amp; Side Key<br>+  u8 unk2;                // 0x1177<br>+  u8 unk3;                // 0x1178<br>+  u8 unk4;                // 0x1179<br>+  u8 name2[6];            // 0x117A unused<br>+} basicsettings;<br>+<br>+#seekto 0x1940;<br>+struct {<br>+  u8 name1[16];           // Intro Screen Line 1 (16 alpha text characters)<br>+  u8 name2[16];           // Intro Screen Line 2 (16 alpha text characters)<br>+} openradioname;<br>+<br>+&quot;&quot;&quot;<br>+<br>+MEM_SIZE = 0x22A0<br>+BLOCK_SIZE = 0x20<br>+STIMEOUT = 2<br>+BAUDRATE = 57600<br>+<br>+# Channel power: 3 levels<br>+POWER_LEVELS = [chirp_common.PowerLevel(&quot;High&quot;, watts=5.00),<br>+                chirp_common.PowerLevel(&quot;Mid&quot;, watts=2.50),<br>+                chirp_common.PowerLevel(&quot;Low&quot;, watts=0.50)]<br>+<br>+SCRAMBLE_LIST = [&quot;OFF&quot;, &quot;1&quot;, &quot;2&quot;, &quot;3&quot;, &quot;4&quot;, &quot;5&quot;, &quot;6&quot;, &quot;7&quot;, &quot;8&quot;]<br>+B_LOCK_LIST = [&quot;OFF&quot;, &quot;Sub&quot;, &quot;Carrier&quot;]<br>+OPTSIG_LIST = [&quot;OFF&quot;, &quot;DTMF&quot;, &quot;2TONE&quot;, &quot;5TONE&quot;]<br>+PTTID_LIST = [&quot;Off&quot;, &quot;BOT&quot;, &quot;EOT&quot;, &quot;Both&quot;]<br>+STEPS = [2.5, 5.0, 6.25, 10.0, 12.5, 25.0, 50.0, 100.0]<br>+LIST_STEPS = [str(x) for x in STEPS]<br>+<br>+<br>+def _clean_buffer(radio):<br>+    radio.pipe.timeout = 0.005<br>+    junk = radio.pipe.read(256)<br>+    radio.pipe.timeout = STIMEOUT<br>+    if junk:<br>+        LOG.debug(&quot;Got %i bytes of junk before starting&quot; % len(junk))<br>+<br>+<br>+def _rawrecv(radio, amount):<br>+    &quot;&quot;&quot;Raw read from the radio device&quot;&quot;&quot;<br>+    data = &quot;&quot;<br>+    try:<br>+        data = radio.pipe.read(amount)<br>+    except Exception:<br>+        _exit_program_mode(radio)<br>+        msg = &quot;Generic error reading data from radio; check your cable.&quot;<br>+        raise errors.RadioError(msg)<br>+<br>+    if len(data) != amount:<br>+        _exit_program_mode(radio)<br>+        msg = &quot;Error reading from radio: not the amount of data we want.&quot;<br>+        raise errors.RadioError(msg)<br>+<br>+    return data<br>+<br>+<br>+def _rawsend(radio, data):<br>+    &quot;&quot;&quot;Raw send to the radio device&quot;&quot;&quot;<br>+    try:<br>+        radio.pipe.write(data)<br>+    except Exception:<br>+        raise errors.RadioError(&quot;Error sending data to radio&quot;)<br>+<br>+<br>+def _make_read_frame(addr, length):<br>+    frame = &quot;\xFE\xFE\xEE\xEF\xEB&quot;<br>+    &quot;&quot;&quot;Pack the info in the header format&quot;&quot;&quot;<br>+    frame += struct.pack(&quot;&gt;ih&quot;, addr, length)<br>+<br>+    frame += &quot;\xFD&quot;<br>+    # Return the data<br>+    return frame<br>+<br>+<br>+def _make_write_frame(addr, length, data=&quot;&quot;):<br>+    frame = &quot;\xFE\xFE\xEE\xEF\xE4&quot;<br>+<br>+    &quot;&quot;&quot;Pack the info in the header format&quot;&quot;&quot;<br>+    output = struct.pack(&quot;&gt;ih&quot;, addr, length)<br>+    # Add the data if set<br>+    if len(data) != 0:<br>+        output += data<br>+<br>+    frame += output<br>+    frame += _calculate_checksum(output)<br>+<br>+    frame += &quot;\xFD&quot;<br>+    # Return the data<br>+    return frame<br>+<br>+<br>+def _calculate_checksum(data):<br>+    num = 0<br>+    for x in range(0, len(data)):<br>+        num = (num + ord(data[x])) % 256<br>+<br>+    if num == 0:<br>+        return chr(0)<br>+<br>+    return chr(256 - num)<br>+<br>+<br>+def _recv(radio, addr, length):<br>+    &quot;&quot;&quot;Get data from the radio &quot;&quot;&quot;<br>+<br>+    data = _rawrecv(radio, length)<br>+<br>+    # DEBUG<br>+    LOG.info(&quot;Response:&quot;)<br>+    LOG.debug(util.hexprint(data))<br>+<br>+    return data<br>+<br>+<br>+def _do_ident(radio):<br>+    &quot;&quot;&quot;Put the radio in PROGRAM mode &amp; identify it&quot;&quot;&quot;<br>+    radio.pipe.baudrate = BAUDRATE<br>+    radio.pipe.parity = &quot;N&quot;<br>+    radio.pipe.timeout = STIMEOUT<br>+<br>+    # Flush input buffer<br>+    _clean_buffer(radio)<br>+<br>+    # Ident radio<br>+    magic = &quot;\xFE\xFE\xEE\xEF\xE0\x55\x56\x38\x38\xFD&quot;<br>+    _rawsend(radio, magic)<br>+    ack = _rawrecv(radio, 36)<br>+<br>+    if not ack.startswith(&quot;\xFE\xFE\xEF\xEE\xE1\x55\x56\x38\x38&quot;<br>+                          ) or not ack.endswith(&quot;\xFD&quot;):<br>+        _exit_program_mode(radio)<br>+        if ack:<br>+            LOG.debug(repr(ack))<br>+        raise errors.RadioError(&quot;Radio did not respond as expected (A)&quot;)<br>+<br>+    return True<br>+<br>+<br>+def _exit_program_mode(radio):<br>+    # This may be the last part of a read<br>+    magic = &quot;\xFE\xFE\xEE\xEF\xE5\x55\x56\x38\x38\xFD&quot;<br>+    _rawsend(radio, magic)<br>+    ack = _rawrecv(radio, 7)<br>+    if ack != &quot;\xFE\xFE\xEF\xEE\xE6\x00\xFD&quot;:<br>+        _exit_program_mode(radio)<br>+        if ack:<br>+            LOG.debug(repr(ack))<br>+        raise errors.RadioError(&quot;Radio did not respond as expected (B)&quot;)<br>+<br>+<br>+def _download(radio):<br>+    &quot;&quot;&quot;Get the memory map&quot;&quot;&quot;<br>+<br>+    # Put radio in program mode and identify it<br>+    _do_ident(radio)<br>+<br>+    # Enter read mode<br>+    magic = &quot;\xFE\xFE\xEE\xEF\xE2\x55\x56\x38\x38\xFD&quot;<br>+    _rawsend(radio, magic)<br>+    ack = _rawrecv(radio, 7)<br>+    if ack != &quot;\xFE\xFE\xEF\xEE\xE6\x00\xFD&quot;:<br>+        _exit_program_mode(radio)<br>+        if ack:<br>+            LOG.debug(repr(ack))<br>+        raise errors.RadioError(&quot;Radio did not respond to enter read mode&quot;)<br>+<br>+    # UI progress<br>+    status = chirp_common.Status()<br>+    status.cur = 0<br>+    status.max = MEM_SIZE / BLOCK_SIZE<br>+    status.msg = &quot;Cloning from radio...&quot;<br>+    radio.status_fn(status)<br>+<br>+    data = &quot;&quot;<br>+    for addr in range(0, MEM_SIZE, BLOCK_SIZE):<br>+        frame = _make_read_frame(addr, BLOCK_SIZE)<br>+        # DEBUG<br>+        LOG.debug(&quot;Frame=&quot; + util.hexprint(frame))<br>+<br>+        # Sending the read request<br>+        _rawsend(radio, frame)<br>+<br>+        # Now we read data<br>+        d = _recv(radio, addr, BLOCK_SIZE + 13)<br>+<br>+        LOG.debug(&quot;Response Data= &quot; + util.hexprint(d))<br>+<br>+        if not d.startswith(&quot;\xFE\xFE\xEF\xEE\xE4&quot;):<br>+            LOG.warning(&quot;Incorrect start&quot;)<br>+        if not d.endswith(&quot;\xFD&quot;):<br>+            LOG.warning(&quot;Incorrect end&quot;)<br>+        # could validate the block data<br>+<br>+        # Aggregate the data<br>+        data += d[11:-2]<br>+<br>+        # UI Update<br>+        status.cur = addr / BLOCK_SIZE<br>+        status.msg = &quot;Cloning from radio...&quot;<br>+        radio.status_fn(status)<br>+<br>+    _exit_program_mode(radio)<br>+<br>+    return data<br>+<br>+<br>+def _upload(radio):<br>+    &quot;&quot;&quot;Upload procedure&quot;&quot;&quot;<br>+    # Put radio in program mode and identify it<br>+    _do_ident(radio)<br>+<br>+    magic = &quot;\xFE\xFE\xEE\xEF\xE3\x55\x56\x38\x38\xFD&quot;<br>+    _rawsend(radio, magic)<br>+    ack = _rawrecv(radio, 7)<br>+    if ack != &quot;\xFE\xFE\xEF\xEE\xE6\x00\xFD&quot;:<br>+        _exit_program_mode(radio)<br>+        if ack:<br>+            LOG.debug(repr(ack))<br>+        raise errors.RadioError(&quot;Radio did not respond to enter write mode&quot;)<br>+<br>+    # UI progress<br>+    status = chirp_common.Status()<br>+    status.cur = 0<br>+    status.max = MEM_SIZE / BLOCK_SIZE<br>+    status.msg = &quot;Cloning to radio...&quot;<br>+    radio.status_fn(status)<br>+<br>+    # The fun starts here<br>+    for addr in range(0, MEM_SIZE, BLOCK_SIZE):<br>+        # Official programmer skips writing these memory locations<br>+        if addr &gt;= 0x1680 and addr &lt; 0x1940:<br>+            continue<br>+<br>+        # Sending the data<br>+        data = radio.get_mmap()[addr:addr + BLOCK_SIZE]<br>+<br>+        frame = _make_write_frame(addr, BLOCK_SIZE, data)<br>+        LOG.warning(&quot;Frame:%s:&quot; % util.hexprint(frame))<br>+        _rawsend(radio, frame)<br>+<br>+        ack = _rawrecv(radio, 7)<br>+        LOG.debug(&quot;Response Data= &quot; + util.hexprint(ack))<br>+<br>+        if not ack.startswith(&quot;\xFE\xFE\xEF\xEE\xE6\x00\xFD&quot;):<br>+            LOG.warning(&quot;Unexpected response&quot;)<br>+            _exit_program_mode(radio)<br>+            msg = &quot;Bad ack writing block 0x%04x&quot; % addr<br>+            raise errors.RadioError(msg)<br>+<br>+        # UI Update<br>+        status.cur = addr / BLOCK_SIZE<br>+        status.msg = &quot;Cloning to radio...&quot;<br>+        radio.status_fn(status)<br>+<br>+    _exit_program_mode(radio)<br>+<br>+<br>+def _do_map(chn, sclr, mary):<br>+    &quot;&quot;&quot;Set or Clear the chn (1-128) bit in mary[] word array map&quot;&quot;&quot;<br>+    # chn is 1-based channel, sclr:1 = set, 0= = clear, 2= return state<br>+    # mary[] is u8 array, but the map is by nibbles<br>+    ndx = int(math.floor((chn - 1) / 8))<br>+    bv = (chn - 1) % 8<br>+    msk = 1 &lt;&lt; bv<br>+    mapbit = sclr<br>+    if sclr == 1:    # Set the bit<br>+        mary[ndx] = mary[ndx] | msk<br>+    elif sclr == 0:  # clear<br>+        mary[ndx] = mary[ndx] &amp; (~ msk)     # ~ is complement<br>+    else:       # return current bit state<br>+        mapbit = 0<br>+        if (mary[ndx] &amp; msk) &gt; 0:<br>+            mapbit = 1<br>+    return mapbit<br>+<br>+<br>+@directory.register<br>+class THUV88Radio(chirp_common.CloneModeRadio):<br>+    &quot;&quot;&quot;TYT UV88 Radio&quot;&quot;&quot;<br>+    VENDOR = &quot;TYT&quot;<br>+    MODEL = &quot;TH-UV88&quot;<br>+    MODES = [&#39;WFM&#39;, &#39;FM&#39;, &#39;NFM&#39;]<br>+    TONES = chirp_common.TONES<br>+    DTCS_CODES = chirp_common.DTCS_CODES<br>+    NAME_LENGTH = 10<br>+    DTMF_CHARS = list(&quot;0123456789ABCD*#&quot;)<br>+    # 136-174, 400-480<br>+    VALID_BANDS = [(136000000, 174000000), (400000000, 480000000)]<br>+<br>+    # Valid chars on the LCD<br>+    VALID_CHARS = chirp_common.CHARSET_ALPHANUMERIC + \<br>+        &quot;`!\&quot;#$%&amp;&#39;()*+,-./:;&lt;=&gt;?@[]^_&quot;<br>+<br>+    @classmethod<br>+    def get_prompts(cls):<br>+        rp = chirp_common.RadioPrompts()<br>+        <a href="http://rp.info" target="_blank">rp.info</a> = \<br>+            (&#39;TYT UV-88\n&#39;)<br>+<br>+        rp.pre_download = _(dedent(&quot;&quot;&quot;\<br>+            This is an early stage beta driver<br>+            &quot;&quot;&quot;))<br>+        rp.pre_upload = _(dedent(&quot;&quot;&quot;\<br>+            This is an early stage beta driver - upload at your own risk<br>+            &quot;&quot;&quot;))<br>+        return rp<br>+<br>+    def get_features(self):<br>+        rf = chirp_common.RadioFeatures()<br>+        rf.has_settings = True<br>+        rf.has_bank = False<br>+        rf.has_comment = False<br>+        rf.has_tuning_step = False      # Not as chan feature<br>+        rf.valid_tuning_steps = STEPS<br>+        rf.can_odd_split = False<br>+        rf.has_name = True<br>+        rf.has_offset = True<br>+        rf.has_mode = True<br>+        rf.has_dtcs = True<br>+        rf.has_rx_dtcs = True<br>+        rf.has_dtcs_polarity = True<br>+        rf.has_ctone = True<br>+        rf.has_cross = True<br>+        rf.has_sub_devices = False<br>+        rf.valid_name_length = self.NAME_LENGTH<br>+        rf.valid_modes = self.MODES<br>+        rf.valid_characters = self.VALID_CHARS<br>+        rf.valid_duplexes = [&quot;-&quot;, &quot;+&quot;, &quot;off&quot;, &quot;&quot;]<br>+        rf.valid_tmodes = [&#39;&#39;, &#39;Tone&#39;, &#39;TSQL&#39;, &#39;DTCS&#39;, &#39;Cross&#39;]<br>+        rf.valid_cross_modes = [&quot;Tone-&gt;Tone&quot;, &quot;DTCS-&gt;&quot;, &quot;-&gt;DTCS&quot;,<br>+                                &quot;Tone-&gt;DTCS&quot;, &quot;DTCS-&gt;Tone&quot;, &quot;-&gt;Tone&quot;,<br>+                                &quot;DTCS-&gt;DTCS&quot;]<br>+        rf.valid_skips = []<br>+        rf.valid_power_levels = POWER_LEVELS<br>+        rf.valid_dtcs_codes = chirp_common.ALL_DTCS_CODES  # this is just to<br>+        # get it working, not sure this is right<br>+        rf.valid_bands = self.VALID_BANDS<br>+        rf.memory_bounds = (1, 199)<br>+        rf.valid_skips = [&quot;&quot;, &quot;S&quot;]<br>+        return rf<br>+<br>+    def sync_in(self):<br>+        &quot;&quot;&quot;Download from radio&quot;&quot;&quot;<br>+        try:<br>+            data = _download(self)<br>+        except errors.RadioError:<br>+            # Pass through any real errors we raise<br>+            raise<br>+        except Exception:<br>+            # If anything unexpected happens, make sure we raise<br>+            # a RadioError and log the problem<br>+            LOG.exception(&#39;Unexpected error during download&#39;)<br>+            raise errors.RadioError(&#39;Unexpected error communicating &#39;<br>+                                    &#39;with the radio&#39;)<br>+        self._mmap = memmap.MemoryMap(data)<br>+        self.process_mmap()<br>+<br>+    def sync_out(self):<br>+        &quot;&quot;&quot;Upload to radio&quot;&quot;&quot;<br>+<br>+        try:<br>+            _upload(self)<br>+        except Exception:<br>+            # If anything unexpected happens, make sure we raise<br>+            # a RadioError and log the problem<br>+            LOG.exception(&#39;Unexpected error during upload&#39;)<br>+            raise errors.RadioError(&#39;Unexpected error communicating &#39;<br>+                                    &#39;with the radio&#39;)<br>+<br>+    def process_mmap(self):<br>+        &quot;&quot;&quot;Process the mem map into the mem object&quot;&quot;&quot;<br>+        self._memobj = bitwise.parse(MEM_FORMAT, self._mmap)<br>+<br>+    def get_raw_memory(self, number):<br>+        return repr(self._memobj.memory[number - 1])<br>+<br>+    def set_memory(self, memory):<br>+        &quot;&quot;&quot;A value in a UI column for chan &#39;number&#39; has been modified.&quot;&quot;&quot;<br>+        # update all raw channel memory values (_mem) from UI (mem)<br>+        _mem = self._memobj.chan_mem[memory.number - 1]<br>+        _name = self._memobj.chan_name[memory.number - 1]<br>+<br>+        if memory.empty:<br>+            _do_map(memory.number, 0, self._memobj.chan_avail.bitmap)<br>+            return<br>+<br>+        _do_map(memory.number, 1, self._memobj.chan_avail.bitmap)<br>+<br>+        if memory.skip == &quot;&quot;:<br>+            _do_map(memory.number, 1, self._memobj.chan_skip.bitmap)<br>+        else:<br>+            _do_map(memory.number, 0, self._memobj.chan_skip.bitmap)<br>+<br>+        return self._set_memory(memory, _mem, _name)<br>+<br>+    def get_memory(self, number):<br>+        # radio first channel is 1, mem map is base 0<br>+        _mem = self._memobj.chan_mem[number - 1]<br>+        _name = self._memobj.chan_name[number - 1]<br>+        mem = chirp_common.Memory()<br>+        mem.number = number<br>+<br>+        # Determine if channel is empty<br>+<br>+        if _do_map(number, 2, self._memobj.chan_avail.bitmap) == 0:<br>+            mem.empty = True<br>+            return mem<br>+<br>+        if _do_map(mem.number, 2, self._memobj.chan_skip.bitmap) &gt; 0:<br>+            mem.skip = &quot;&quot;<br>+        else:<br>+            mem.skip = &quot;S&quot;<br>+<br>+        return self._get_memory(mem, _mem, _name)<br>+<br>+    def _get_memory(self, mem, _mem, _name):<br>+        &quot;&quot;&quot;Convert raw channel memory data into UI columns&quot;&quot;&quot;<br>+        mem.extra = RadioSettingGroup(&quot;extra&quot;, &quot;Extra&quot;)<br>+<br>+        mem.empty = False<br>+        # This function process both &#39;normal&#39; and Freq up/down&#39; entries<br>+        mem.freq = int(_mem.rxfreq) * 10<br>+<br>+        if _mem.txfreq == 0xFFFFFFFF:<br>+            # TX freq not set<br>+            mem.duplex = &quot;off&quot;<br>+            mem.offset = 0<br>+        elif int(_mem.rxfreq) == int(_mem.txfreq):<br>+            mem.duplex = &quot;&quot;<br>+            mem.offset = 0<br>+        else:<br>+            mem.duplex = int(_mem.rxfreq) &gt; int(_mem.txfreq) \<br>+                and &quot;-&quot; or &quot;+&quot;<br>+            mem.offset = abs(int(_mem.rxfreq) - int(_mem.txfreq)) * 10<br>+<br>+        <a href="http://mem.name" target="_blank">mem.name</a> = &quot;&quot;<br>+        for i in range(6):   # 0 - 6<br>+            <a href="http://mem.name" target="_blank">mem.name</a> += chr(_<a href="http://mem.name" target="_blank">mem.name</a>[i])<br>+        for i in range(10):<br>+            <a href="http://mem.name" target="_blank">mem.name</a> += chr(_name.extra_name[i])<br>+<br>+        <a href="http://mem.name" target="_blank">mem.name</a> = mem.name.rstrip()    # remove trailing spaces<br>+<br>+        # ########## TONE ##########<br>+<br>+        if _mem.txtone &gt; 2600:<br>+            # All off<br>+            txmode = &quot;&quot;<br>+        elif _mem.txtone &gt; 511:<br>+            txmode = &quot;Tone&quot;<br>+            mem.rtone = int(_mem.txtone) / 10.0<br>+        else:<br>+            # DTSC<br>+            txmode = &quot;DTCS&quot;<br>+            mem.dtcs = int(format(int(_mem.txtone), &#39;o&#39;))<br>+<br>+        if _mem.rxtone &gt; 2600:<br>+            rxmode = &quot;&quot;<br>+        elif _mem.rxtone &gt; 511:<br>+            rxmode = &quot;Tone&quot;<br>+            mem.ctone = int(_mem.rxtone) / 10.0<br>+        else:<br>+            rxmode = &quot;DTCS&quot;<br>+            mem.rx_dtcs = int(format(int(_mem.rxtone), &#39;o&#39;))<br>+<br>+        mem.dtcs_polarity = (&quot;N&quot;, &quot;R&quot;)[_mem.encodeDSCI] + (<br>+                             &quot;N&quot;, &quot;R&quot;)[_mem.decodeDSCI]<br>+<br>+        mem.tmode = &quot;&quot;<br>+        if txmode == &quot;Tone&quot; and not rxmode:<br>+            mem.tmode = &quot;Tone&quot;<br>+        elif txmode == rxmode and txmode == &quot;Tone&quot; and mem.rtone == mem.ctone:<br>+            mem.tmode = &quot;TSQL&quot;<br>+        elif txmode == rxmode and txmode == &quot;DTCS&quot; and mem.dtcs == mem.rx_dtcs:<br>+            mem.tmode = &quot;DTCS&quot;<br>+        elif rxmode or txmode:<br>+            mem.tmode = &quot;Cross&quot;<br>+            mem.cross_mode = &quot;%s-&gt;%s&quot; % (txmode, rxmode)<br>+<br>+        # ########## TONE ##########<br>+<br>+        mem.mode = self.MODES[_mem.wide]<br>+        mem.power = POWER_LEVELS[int(_mem.power)]<br>+<br>+        b_lock = RadioSetting(&quot;b_lock&quot;, &quot;B_Lock&quot;,<br>+                              RadioSettingValueList(B_LOCK_LIST,<br>+                                                    B_LOCK_LIST[_mem.b_lock]))<br>+        mem.extra.append(b_lock)<br>+<br>+        b_lock = RadioSetting(&quot;step&quot;, &quot;Step&quot;,<br>+                              RadioSettingValueList(LIST_STEPS,<br>+                                                    LIST_STEPS[_mem.step]))<br>+        mem.extra.append(b_lock)<br>+<br>+        scramble_value = _mem.scramble<br>+        if scramble_value &gt;= 8:     # Looks like OFF is 0x0f ** CONFIRM<br>+            scramble_value = 0<br>+        scramble = RadioSetting(&quot;scramble&quot;, &quot;Scramble&quot;,<br>+                                RadioSettingValueList(SCRAMBLE_LIST,<br>+                                                      SCRAMBLE_LIST[<br>+                                                          scramble_value]))<br>+        mem.extra.append(scramble)<br>+<br>+        optsig = RadioSetting(&quot;signal&quot;, &quot;Optional signaling&quot;,<br>+                              RadioSettingValueList(<br>+                                  OPTSIG_LIST,<br>+                                  OPTSIG_LIST[_mem.signal]))<br>+        mem.extra.append(optsig)<br>+<br>+        rs = RadioSetting(&quot;pttid&quot;, &quot;PTT ID&quot;,<br>+                          RadioSettingValueList(PTTID_LIST,<br>+                                                PTTID_LIST[_mem.pttid]))<br>+        mem.extra.append(rs)<br>+<br>+        return mem<br>+<br>+    def _set_memory(self, mem, _mem, _name):<br>+        # &quot;&quot;&quot;Convert UI column data (mem) into MEM_FORMAT memory (_mem).&quot;&quot;&quot;<br>+<br>+        _mem.rxfreq = mem.freq / 10<br>+        if mem.duplex == &quot;off&quot;:<br>+            _mem.txfreq = 0xFFFFFFFF<br>+        elif mem.duplex == &quot;+&quot;:<br>+            _mem.txfreq = (mem.freq + mem.offset) / 10<br>+        elif mem.duplex == &quot;-&quot;:<br>+            _mem.txfreq = (mem.freq - mem.offset) / 10<br>+        else:<br>+            _mem.txfreq = _mem.rxfreq<br>+<br>+        out_name = mem.name.ljust(16)<br>+<br>+        for i in range(6):   # 0 - 6<br>+            _<a href="http://mem.name" target="_blank">mem.name</a>[i] = ord(out_name[i])<br>+        for i in range(10):<br>+            _name.extra_name[i] = ord(out_name[i+6])<br>+<br>+        if <a href="http://mem.name" target="_blank">mem.name</a> != &quot;&quot;:<br>+            _mem.displayName = 1    # Name only displayed if this is set on<br>+        else:<br>+            _mem.displayName = 0<br>+<br>+        rxmode = &quot;&quot;<br>+        txmode = &quot;&quot;<br>+<br>+        if mem.tmode == &quot;Tone&quot;:<br>+            txmode = &quot;Tone&quot;<br>+        elif mem.tmode == &quot;TSQL&quot;:<br>+            rxmode = &quot;Tone&quot;<br>+            txmode = &quot;TSQL&quot;<br>+        elif mem.tmode == &quot;DTCS&quot;:<br>+            rxmode = &quot;DTCSSQL&quot;<br>+            txmode = &quot;DTCS&quot;<br>+        elif mem.tmode == &quot;Cross&quot;:<br>+            txmode, rxmode = mem.cross_mode.split(&quot;-&gt;&quot;, 1)<br>+<br>+        if mem.dtcs_polarity[1] == &quot;N&quot;:<br>+            _mem.decodeDSCI = 0<br>+        else:<br>+            _mem.decodeDSCI = 1<br>+<br>+        if rxmode == &quot;&quot;:<br>+            _mem.rxtone = 0xFFF<br>+        elif rxmode == &quot;Tone&quot;:<br>+            _mem.rxtone = int(float(mem.ctone) * 10)<br>+        elif rxmode == &quot;DTCSSQL&quot;:<br>+            _mem.rxtone = int(str(mem.dtcs), 8)<br>+        elif rxmode == &quot;DTCS&quot;:<br>+            _mem.rxtone = int(str(mem.rx_dtcs), 8)<br>+<br>+        if mem.dtcs_polarity[0] == &quot;N&quot;:<br>+            _mem.encodeDSCI = 0<br>+        else:<br>+            _mem.encodeDSCI = 1<br>+<br>+        if txmode == &quot;&quot;:<br>+            _mem.txtone = 0xFFF<br>+        elif txmode == &quot;Tone&quot;:<br>+            _mem.txtone = int(float(mem.rtone) * 10)<br>+        elif txmode == &quot;TSQL&quot;:<br>+            _mem.txtone = int(float(mem.ctone) * 10)<br>+        elif txmode == &quot;DTCS&quot;:<br>+            _mem.txtone = int(str(mem.dtcs), 8)<br>+<br>+        _mem.wide = self.MODES.index(mem.mode)<br>+        _mem.power = 0 if mem.power is None else POWER_LEVELS.index(mem.power)<br>+<br>+        for element in mem.extra:<br>+            setattr(_mem, element.get_name(), element.value)<br>+<br>+        return<br>+<br>+    def get_settings(self):<br>+        &quot;&quot;&quot;Translate the MEM_FORMAT structs into setstuf in the UI&quot;&quot;&quot;<br>+        _settings = self._memobj.basicsettings<br>+        _workmode = self._memobj.workmodesettings<br>+<br>+        basic = RadioSettingGroup(&quot;basic&quot;, &quot;Basic Settings&quot;)<br>+        group = RadioSettings(basic)<br>+<br>+        # Menu 02 - TX Channel Select<br>+        options = [&quot;Last Channel&quot;, &quot;Main Channel&quot;]<br>+        rx = RadioSettingValueList(options, options[_settings.txChSelect])<br>+        rset = RadioSetting(&quot;basicsettings.txChSelect&quot;,<br>+                            &quot;Priority Transmit&quot;, rx)<br>+        basic.append(rset)<br>+<br>+        # Menu 03 - VOX Level<br>+        rx = RadioSettingValueInteger(1, 7, _settings.voxLevel - 1)<br>+        rset = RadioSetting(&quot;basicsettings.voxLevel&quot;, &quot;Vox Level&quot;, rx)<br>+        basic.append(rset)<br>+<br>+        # Menu 05 - Squelch Level<br>+        options = [&quot;OFF&quot;] + [&quot;%s&quot; % x for x in range(1, 10)]<br>+        rx = RadioSettingValueList(options, options[_settings.sqlLevel])<br>+        rset = RadioSetting(&quot;basicsettings.sqlLevel&quot;, &quot;Squelch Level&quot;, rx)<br>+        basic.append(rset)<br>+<br>+        # Menu 06 - Dual Wait<br>+        rx = RadioSettingValueBoolean(_settings.dualWait)<br>+        rset = RadioSetting(&quot;basicsettings.dualWait&quot;, &quot;Dual Wait/Standby&quot;, rx)<br>+        basic.append(rset)<br>+<br>+        # Menu 07 - LED Mode<br>+        options = [&quot;Off&quot;, &quot;On&quot;, &quot;Auto&quot;]<br>+        rx = RadioSettingValueList(options, options[_settings.ledMode])<br>+        rset = RadioSetting(&quot;basicsettings.ledMode&quot;, &quot;LED Display Mode&quot;, rx)<br>+        basic.append(rset)<br>+<br>+        # Menu 08 - Light<br>+        options = [&quot;%s&quot; % x for x in range(1, 8)]<br>+        rx = RadioSettingValueList(options, options[_settings.light])<br>+        rset = RadioSetting(&quot;basicsettings.light&quot;,<br>+                            &quot;Background Light Color&quot;, rx)<br>+        basic.append(rset)<br>+<br>+        # Menu 09 - Beep<br>+        rx = RadioSettingValueBoolean(_settings.beep)<br>+        rset = RadioSetting(&quot;basicsettings.beep&quot;, &quot;Keypad Beep&quot;, rx)<br>+        basic.append(rset)<br>+<br>+        # Menu 11 - TOT<br>+        options = [&quot;Off&quot;] + [&quot;%s seconds&quot; % x for x in range(30, 300, 30)]<br>+        rx = RadioSettingValueList(options, options[_settings.tot])<br>+        rset = RadioSetting(&quot;basicsettings.tot&quot;,<br>+                            &quot;Transmission Time-out Timer&quot;, rx)<br>+        basic.append(rset)<br>+<br>+        # Menu 13 - VOX Switch<br>+        rx = RadioSettingValueBoolean(_settings.voxSw)<br>+        rset = RadioSetting(&quot;basicsettings.voxSw&quot;, &quot;Vox Switch&quot;, rx)<br>+        basic.append(rset)<br>+<br>+        # Menu 14 - Roger<br>+        rx = RadioSettingValueBoolean(_settings.roger)<br>+        rset = RadioSetting(&quot;basicsettings.roger&quot;, &quot;Roger Beep&quot;, rx)<br>+        basic.append(rset)<br>+<br>+        # Menu 16 - Save Mode<br>+        options = [&quot;Off&quot;, &quot;1:1&quot;, &quot;1:2&quot;, &quot;1:4&quot;]<br>+        rx = RadioSettingValueList(options, options[_settings.saveMode])<br>+        rset = RadioSetting(&quot;basicsettings.saveMode&quot;, &quot;Battery Save Mode&quot;, rx)<br>+        basic.append(rset)<br>+<br>+        # Menu 33 - Display Mode<br>+        options = [&#39;Frequency&#39;, &#39;Channel&#39;, &#39;Name&#39;]<br>+        rx = RadioSettingValueList(options, options[_settings.disMode])<br>+        rset = RadioSetting(&quot;basicsettings.disMode&quot;, &quot;LED Display Mode&quot;, rx)<br>+        basic.append(rset)<br>+<br>+        advanced = RadioSettingGroup(&quot;advanced&quot;, &quot;Advanced Settings&quot;)<br>+        group.append(advanced)<br>+<br>+        # software only<br>+        options = [&#39;0.5S&#39;, &#39;1.0S&#39;, &#39;1.5S&#39;, &#39;2.0S&#39;, &#39;2.5S&#39;, &#39;3.0S&#39;, &#39;3.5S&#39;,<br>+                   &#39;4.0S&#39;, &#39;4.5S&#39;, &#39;5.0S&#39;]<br>+        rx = RadioSettingValueList(options, options[_settings.voxDelay])<br>+        rset = RadioSetting(&quot;basicsettings.voxDelay&quot;, &quot;VOX Delay&quot;, rx)<br>+        advanced.append(rset)<br>+<br>+        # software only<br>+        name = &quot;&quot;<br>+        for i in range(16):  # 0 - 16<br>+            name += chr(self._memobj.openradioname.name1[i])<br>+        name = name.rstrip()  # remove trailing spaces<br>+<br>+        rx = RadioSettingValueString(0, 16, name)<br>+        rset = RadioSetting(&quot;openradioname.name1&quot;, &quot;Intro Line 1&quot;, rx)<br>+        advanced.append(rset)<br>+<br>+        # software only<br>+        name = &quot;&quot;<br>+        for i in range(16):  # 0 - 16<br>+            name += chr(self._memobj.openradioname.name2[i])<br>+        name = name.rstrip()  # remove trailing spaces<br>+<br>+        rx = RadioSettingValueString(0, 16, name)<br>+        rset = RadioSetting(&quot;openradioname.name2&quot;, &quot;Intro Line 2&quot;, rx)<br>+        advanced.append(rset)<br>+<br>+        workmode = RadioSettingGroup(&quot;workmode&quot;, &quot;Work Mode Settings&quot;)<br>+        group.append(workmode)<br>+<br>+        # Toggle with [#] key<br>+        options = [&quot;Frequency&quot;, &quot;Channel&quot;]<br>+        rx = RadioSettingValueList(options, options[_workmode.vfomrmode])<br>+        rset = RadioSetting(&quot;workmodesettings.vfomrmode&quot;, &quot;VFO/MR Mode&quot;, rx)<br>+        workmode.append(rset)<br>+<br>+        # Toggle with [A/B] key<br>+        options = [&quot;A&quot;, &quot;B&quot;]<br>+        rx = RadioSettingValueList(options, options[_workmode.ab])<br>+        rset = RadioSetting(&quot;workmodesettings.ab&quot;, &quot;A/B Select&quot;, rx)<br>+        workmode.append(rset)<br>+<br>+        return group       # END get_settings()<br>+<br>+    def set_settings(self, settings):<br>+        return<br>+        _settings = self._memobj.settings<br>+        _mem = self._memobj<br>+        for element in settings:<br>+            if not isinstance(element, RadioSetting):<br>+                self.set_settings(element)<br>+                continue<br>+            else:<br>+                try:<br>+                    name = element.get_name()<br>+                    if &quot;.&quot; in name:<br>+                        bits = name.split(&quot;.&quot;)<br>+                        obj = self._memobj<br>+                        for bit in bits[:-1]:<br>+                            if &quot;/&quot; in bit:<br>+                                bit, index = bit.split(&quot;/&quot;, 1)<br>+                                index = int(index)<br>+                                obj = getattr(obj, bit)[index]<br>+                            else:<br>+                                obj = getattr(obj, bit)<br>+                        setting = bits[-1]<br>+                    else:<br>+                        obj = _settings<br>+                        setting = element.get_name()<br>+<br>+                    if element.has_apply_callback():<br>+                        LOG.debug(&quot;Using apply callback&quot;)<br>+                        element.run_apply_callback()<br>+                    elif setting == &quot;voxLevel&quot;:<br>+                        setattr(obj, setting, int(element.value) + 1)<br>+                    elif element.value.get_mutable():<br>+                        LOG.debug(&quot;Setting %s = %s&quot; % (setting, element.value))<br>+                        setattr(obj, setting, element.value)<br>+                except Exception, e:<br>+                    LOG.debug(element.get_name())<br>+                    raise<br>diff -r d5e496f563fd -r 3fd7ed9a0de0 tools/cpep8.manifest<br>--- a/tools/cpep8.manifest        Fri Nov 13 08:07:04 2020 -0500<br>+++ b/tools/cpep8.manifest        Mon Nov 16 15:18:58 2020 -0500<br>@@ -77,6 +77,7 @@<br> ./chirp/drivers/th_uv3r.py<br> ./chirp/drivers/th_uv3r25.py<br> ./chirp/drivers/th_uv8000.py<br>+./chirp/drivers/th_uv88.py<br> ./chirp/drivers/th_uvf8d.py<br> ./chirp/drivers/thd72.py<br> ./chirp/drivers/thuv1f.py<br><div><img width="1" height="1" alt=""></div>
_______________________________________________<br>
chirp_devel mailing list<br>
<a href="mailto:chirp_devel@intrepid.danplanet.com" target="_blank">chirp_devel@intrepid.danplanet.com</a><br>
<a href="http://intrepid.danplanet.com/mailman/listinfo/chirp_devel" rel="noreferrer" target="_blank">http://intrepid.danplanet.com/mailman/listinfo/chirp_devel</a><br>
Developer docs: <a href="http://chirp.danplanet.com/projects/chirp/wiki/Developers" rel="noreferrer" target="_blank">http://chirp.danplanet.com/projects/chirp/wiki/Developers</a></blockquote></div>