<div dir="ltr">Attached:<div>- factory image to test against</div><div>- patch file in case the "hg email tip" 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 <<a href="mailto:chirp_devel@intrepid.danplanet.com">chirp_devel@intrepid.danplanet.com</a>> 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 = """<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 - 'Volume, Frequency'<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 & 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>+"""<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("High", watts=5.00),<br>+ chirp_common.PowerLevel("Mid", watts=2.50),<br>+ chirp_common.PowerLevel("Low", watts=0.50)]<br>+<br>+SCRAMBLE_LIST = ["OFF", "1", "2", "3", "4", "5", "6", "7", "8"]<br>+B_LOCK_LIST = ["OFF", "Sub", "Carrier"]<br>+OPTSIG_LIST = ["OFF", "DTMF", "2TONE", "5TONE"]<br>+PTTID_LIST = ["Off", "BOT", "EOT", "Both"]<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("Got %i bytes of junk before starting" % len(junk))<br>+<br>+<br>+def _rawrecv(radio, amount):<br>+ """Raw read from the radio device"""<br>+ data = ""<br>+ try:<br>+ data = radio.pipe.read(amount)<br>+ except Exception:<br>+ _exit_program_mode(radio)<br>+ msg = "Generic error reading data from radio; check your cable."<br>+ raise errors.RadioError(msg)<br>+<br>+ if len(data) != amount:<br>+ _exit_program_mode(radio)<br>+ msg = "Error reading from radio: not the amount of data we want."<br>+ raise errors.RadioError(msg)<br>+<br>+ return data<br>+<br>+<br>+def _rawsend(radio, data):<br>+ """Raw send to the radio device"""<br>+ try:<br>+ radio.pipe.write(data)<br>+ except Exception:<br>+ raise errors.RadioError("Error sending data to radio")<br>+<br>+<br>+def _make_read_frame(addr, length):<br>+ frame = "\xFE\xFE\xEE\xEF\xEB"<br>+ """Pack the info in the header format"""<br>+ frame += struct.pack(">ih", addr, length)<br>+<br>+ frame += "\xFD"<br>+ # Return the data<br>+ return frame<br>+<br>+<br>+def _make_write_frame(addr, length, data=""):<br>+ frame = "\xFE\xFE\xEE\xEF\xE4"<br>+<br>+ """Pack the info in the header format"""<br>+ output = struct.pack(">ih", 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 += "\xFD"<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>+ """Get data from the radio """<br>+<br>+ data = _rawrecv(radio, length)<br>+<br>+ # DEBUG<br>+ LOG.info("Response:")<br>+ LOG.debug(util.hexprint(data))<br>+<br>+ return data<br>+<br>+<br>+def _do_ident(radio):<br>+ """Put the radio in PROGRAM mode & identify it"""<br>+ radio.pipe.baudrate = BAUDRATE<br>+ radio.pipe.parity = "N"<br>+ radio.pipe.timeout = STIMEOUT<br>+<br>+ # Flush input buffer<br>+ _clean_buffer(radio)<br>+<br>+ # Ident radio<br>+ magic = "\xFE\xFE\xEE\xEF\xE0\x55\x56\x38\x38\xFD"<br>+ _rawsend(radio, magic)<br>+ ack = _rawrecv(radio, 36)<br>+<br>+ if not ack.startswith("\xFE\xFE\xEF\xEE\xE1\x55\x56\x38\x38"<br>+ ) or not ack.endswith("\xFD"):<br>+ _exit_program_mode(radio)<br>+ if ack:<br>+ LOG.debug(repr(ack))<br>+ raise errors.RadioError("Radio did not respond as expected (A)")<br>+<br>+ return True<br>+<br>+<br>+def _exit_program_mode(radio):<br>+ # This may be the last part of a read<br>+ magic = "\xFE\xFE\xEE\xEF\xE5\x55\x56\x38\x38\xFD"<br>+ _rawsend(radio, magic)<br>+ ack = _rawrecv(radio, 7)<br>+ if ack != "\xFE\xFE\xEF\xEE\xE6\x00\xFD":<br>+ _exit_program_mode(radio)<br>+ if ack:<br>+ LOG.debug(repr(ack))<br>+ raise errors.RadioError("Radio did not respond as expected (B)")<br>+<br>+<br>+def _download(radio):<br>+ """Get the memory map"""<br>+<br>+ # Put radio in program mode and identify it<br>+ _do_ident(radio)<br>+<br>+ # Enter read mode<br>+ magic = "\xFE\xFE\xEE\xEF\xE2\x55\x56\x38\x38\xFD"<br>+ _rawsend(radio, magic)<br>+ ack = _rawrecv(radio, 7)<br>+ if ack != "\xFE\xFE\xEF\xEE\xE6\x00\xFD":<br>+ _exit_program_mode(radio)<br>+ if ack:<br>+ LOG.debug(repr(ack))<br>+ raise errors.RadioError("Radio did not respond to enter read mode")<br>+<br>+ # UI progress<br>+ status = chirp_common.Status()<br>+ status.cur = 0<br>+ status.max = MEM_SIZE / BLOCK_SIZE<br>+ status.msg = "Cloning from radio..."<br>+ radio.status_fn(status)<br>+<br>+ data = ""<br>+ for addr in range(0, MEM_SIZE, BLOCK_SIZE):<br>+ frame = _make_read_frame(addr, BLOCK_SIZE)<br>+ # DEBUG<br>+ LOG.debug("Frame=" + 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("Response Data= " + util.hexprint(d))<br>+<br>+ if not d.startswith("\xFE\xFE\xEF\xEE\xE4"):<br>+ LOG.warning("Incorrect start")<br>+ if not d.endswith("\xFD"):<br>+ LOG.warning("Incorrect end")<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 = "Cloning from radio..."<br>+ radio.status_fn(status)<br>+<br>+ _exit_program_mode(radio)<br>+<br>+ return data<br>+<br>+<br>+def _upload(radio):<br>+ """Upload procedure"""<br>+ # Put radio in program mode and identify it<br>+ _do_ident(radio)<br>+<br>+ magic = "\xFE\xFE\xEE\xEF\xE3\x55\x56\x38\x38\xFD"<br>+ _rawsend(radio, magic)<br>+ ack = _rawrecv(radio, 7)<br>+ if ack != "\xFE\xFE\xEF\xEE\xE6\x00\xFD":<br>+ _exit_program_mode(radio)<br>+ if ack:<br>+ LOG.debug(repr(ack))<br>+ raise errors.RadioError("Radio did not respond to enter write mode")<br>+<br>+ # UI progress<br>+ status = chirp_common.Status()<br>+ status.cur = 0<br>+ status.max = MEM_SIZE / BLOCK_SIZE<br>+ status.msg = "Cloning to radio..."<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 >= 0x1680 and addr < 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("Frame:%s:" % util.hexprint(frame))<br>+ _rawsend(radio, frame)<br>+<br>+ ack = _rawrecv(radio, 7)<br>+ LOG.debug("Response Data= " + util.hexprint(ack))<br>+<br>+ if not ack.startswith("\xFE\xFE\xEF\xEE\xE6\x00\xFD"):<br>+ LOG.warning("Unexpected response")<br>+ _exit_program_mode(radio)<br>+ msg = "Bad ack writing block 0x%04x" % addr<br>+ raise errors.RadioError(msg)<br>+<br>+ # UI Update<br>+ status.cur = addr / BLOCK_SIZE<br>+ status.msg = "Cloning to radio..."<br>+ radio.status_fn(status)<br>+<br>+ _exit_program_mode(radio)<br>+<br>+<br>+def _do_map(chn, sclr, mary):<br>+ """Set or Clear the chn (1-128) bit in mary[] word array map"""<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 << 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] & (~ msk) # ~ is complement<br>+ else: # return current bit state<br>+ mapbit = 0<br>+ if (mary[ndx] & msk) > 0:<br>+ mapbit = 1<br>+ return mapbit<br>+<br>+<br>+@directory.register<br>+class THUV88Radio(chirp_common.CloneModeRadio):<br>+ """TYT UV88 Radio"""<br>+ VENDOR = "TYT"<br>+ MODEL = "TH-UV88"<br>+ MODES = ['WFM', 'FM', 'NFM']<br>+ TONES = chirp_common.TONES<br>+ DTCS_CODES = chirp_common.DTCS_CODES<br>+ NAME_LENGTH = 10<br>+ DTMF_CHARS = list("0123456789ABCD*#")<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>+ "`!\"#$%&'()*+,-./:;<=>?@[]^_"<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>+ ('TYT UV-88\n')<br>+<br>+ rp.pre_download = _(dedent("""\<br>+ This is an early stage beta driver<br>+ """))<br>+ rp.pre_upload = _(dedent("""\<br>+ This is an early stage beta driver - upload at your own risk<br>+ """))<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 = ["-", "+", "off", ""]<br>+ rf.valid_tmodes = ['', 'Tone', 'TSQL', 'DTCS', 'Cross']<br>+ rf.valid_cross_modes = ["Tone->Tone", "DTCS->", "->DTCS",<br>+ "Tone->DTCS", "DTCS->Tone", "->Tone",<br>+ "DTCS->DTCS"]<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 = ["", "S"]<br>+ return rf<br>+<br>+ def sync_in(self):<br>+ """Download from radio"""<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('Unexpected error during download')<br>+ raise errors.RadioError('Unexpected error communicating '<br>+ 'with the radio')<br>+ self._mmap = memmap.MemoryMap(data)<br>+ self.process_mmap()<br>+<br>+ def sync_out(self):<br>+ """Upload to radio"""<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('Unexpected error during upload')<br>+ raise errors.RadioError('Unexpected error communicating '<br>+ 'with the radio')<br>+<br>+ def process_mmap(self):<br>+ """Process the mem map into the mem object"""<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>+ """A value in a UI column for chan 'number' has been modified."""<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 == "":<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) > 0:<br>+ mem.skip = ""<br>+ else:<br>+ mem.skip = "S"<br>+<br>+ return self._get_memory(mem, _mem, _name)<br>+<br>+ def _get_memory(self, mem, _mem, _name):<br>+ """Convert raw channel memory data into UI columns"""<br>+ mem.extra = RadioSettingGroup("extra", "Extra")<br>+<br>+ mem.empty = False<br>+ # This function process both 'normal' and Freq up/down' entries<br>+ mem.freq = int(_mem.rxfreq) * 10<br>+<br>+ if _mem.txfreq == 0xFFFFFFFF:<br>+ # TX freq not set<br>+ mem.duplex = "off"<br>+ mem.offset = 0<br>+ elif int(_mem.rxfreq) == int(_mem.txfreq):<br>+ mem.duplex = ""<br>+ mem.offset = 0<br>+ else:<br>+ mem.duplex = int(_mem.rxfreq) > int(_mem.txfreq) \<br>+ and "-" or "+"<br>+ mem.offset = abs(int(_mem.rxfreq) - int(_mem.txfreq)) * 10<br>+<br>+ <a href="http://mem.name" target="_blank">mem.name</a> = ""<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 > 2600:<br>+ # All off<br>+ txmode = ""<br>+ elif _mem.txtone > 511:<br>+ txmode = "Tone"<br>+ mem.rtone = int(_mem.txtone) / 10.0<br>+ else:<br>+ # DTSC<br>+ txmode = "DTCS"<br>+ mem.dtcs = int(format(int(_mem.txtone), 'o'))<br>+<br>+ if _mem.rxtone > 2600:<br>+ rxmode = ""<br>+ elif _mem.rxtone > 511:<br>+ rxmode = "Tone"<br>+ mem.ctone = int(_mem.rxtone) / 10.0<br>+ else:<br>+ rxmode = "DTCS"<br>+ mem.rx_dtcs = int(format(int(_mem.rxtone), 'o'))<br>+<br>+ mem.dtcs_polarity = ("N", "R")[_mem.encodeDSCI] + (<br>+ "N", "R")[_mem.decodeDSCI]<br>+<br>+ mem.tmode = ""<br>+ if txmode == "Tone" and not rxmode:<br>+ mem.tmode = "Tone"<br>+ elif txmode == rxmode and txmode == "Tone" and mem.rtone == mem.ctone:<br>+ mem.tmode = "TSQL"<br>+ elif txmode == rxmode and txmode == "DTCS" and mem.dtcs == mem.rx_dtcs:<br>+ mem.tmode = "DTCS"<br>+ elif rxmode or txmode:<br>+ mem.tmode = "Cross"<br>+ mem.cross_mode = "%s->%s" % (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("b_lock", "B_Lock",<br>+ RadioSettingValueList(B_LOCK_LIST,<br>+ B_LOCK_LIST[_mem.b_lock]))<br>+ mem.extra.append(b_lock)<br>+<br>+ b_lock = RadioSetting("step", "Step",<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 >= 8: # Looks like OFF is 0x0f ** CONFIRM<br>+ scramble_value = 0<br>+ scramble = RadioSetting("scramble", "Scramble",<br>+ RadioSettingValueList(SCRAMBLE_LIST,<br>+ SCRAMBLE_LIST[<br>+ scramble_value]))<br>+ mem.extra.append(scramble)<br>+<br>+ optsig = RadioSetting("signal", "Optional signaling",<br>+ RadioSettingValueList(<br>+ OPTSIG_LIST,<br>+ OPTSIG_LIST[_mem.signal]))<br>+ mem.extra.append(optsig)<br>+<br>+ rs = RadioSetting("pttid", "PTT ID",<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>+ # """Convert UI column data (mem) into MEM_FORMAT memory (_mem)."""<br>+<br>+ _mem.rxfreq = mem.freq / 10<br>+ if mem.duplex == "off":<br>+ _mem.txfreq = 0xFFFFFFFF<br>+ elif mem.duplex == "+":<br>+ _mem.txfreq = (mem.freq + mem.offset) / 10<br>+ elif mem.duplex == "-":<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> != "":<br>+ _mem.displayName = 1 # Name only displayed if this is set on<br>+ else:<br>+ _mem.displayName = 0<br>+<br>+ rxmode = ""<br>+ txmode = ""<br>+<br>+ if mem.tmode == "Tone":<br>+ txmode = "Tone"<br>+ elif mem.tmode == "TSQL":<br>+ rxmode = "Tone"<br>+ txmode = "TSQL"<br>+ elif mem.tmode == "DTCS":<br>+ rxmode = "DTCSSQL"<br>+ txmode = "DTCS"<br>+ elif mem.tmode == "Cross":<br>+ txmode, rxmode = mem.cross_mode.split("->", 1)<br>+<br>+ if mem.dtcs_polarity[1] == "N":<br>+ _mem.decodeDSCI = 0<br>+ else:<br>+ _mem.decodeDSCI = 1<br>+<br>+ if rxmode == "":<br>+ _mem.rxtone = 0xFFF<br>+ elif rxmode == "Tone":<br>+ _mem.rxtone = int(float(mem.ctone) * 10)<br>+ elif rxmode == "DTCSSQL":<br>+ _mem.rxtone = int(str(mem.dtcs), 8)<br>+ elif rxmode == "DTCS":<br>+ _mem.rxtone = int(str(mem.rx_dtcs), 8)<br>+<br>+ if mem.dtcs_polarity[0] == "N":<br>+ _mem.encodeDSCI = 0<br>+ else:<br>+ _mem.encodeDSCI = 1<br>+<br>+ if txmode == "":<br>+ _mem.txtone = 0xFFF<br>+ elif txmode == "Tone":<br>+ _mem.txtone = int(float(mem.rtone) * 10)<br>+ elif txmode == "TSQL":<br>+ _mem.txtone = int(float(mem.ctone) * 10)<br>+ elif txmode == "DTCS":<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>+ """Translate the MEM_FORMAT structs into setstuf in the UI"""<br>+ _settings = self._memobj.basicsettings<br>+ _workmode = self._memobj.workmodesettings<br>+<br>+ basic = RadioSettingGroup("basic", "Basic Settings")<br>+ group = RadioSettings(basic)<br>+<br>+ # Menu 02 - TX Channel Select<br>+ options = ["Last Channel", "Main Channel"]<br>+ rx = RadioSettingValueList(options, options[_settings.txChSelect])<br>+ rset = RadioSetting("basicsettings.txChSelect",<br>+ "Priority Transmit", rx)<br>+ basic.append(rset)<br>+<br>+ # Menu 03 - VOX Level<br>+ rx = RadioSettingValueInteger(1, 7, _settings.voxLevel - 1)<br>+ rset = RadioSetting("basicsettings.voxLevel", "Vox Level", rx)<br>+ basic.append(rset)<br>+<br>+ # Menu 05 - Squelch Level<br>+ options = ["OFF"] + ["%s" % x for x in range(1, 10)]<br>+ rx = RadioSettingValueList(options, options[_settings.sqlLevel])<br>+ rset = RadioSetting("basicsettings.sqlLevel", "Squelch Level", rx)<br>+ basic.append(rset)<br>+<br>+ # Menu 06 - Dual Wait<br>+ rx = RadioSettingValueBoolean(_settings.dualWait)<br>+ rset = RadioSetting("basicsettings.dualWait", "Dual Wait/Standby", rx)<br>+ basic.append(rset)<br>+<br>+ # Menu 07 - LED Mode<br>+ options = ["Off", "On", "Auto"]<br>+ rx = RadioSettingValueList(options, options[_settings.ledMode])<br>+ rset = RadioSetting("basicsettings.ledMode", "LED Display Mode", rx)<br>+ basic.append(rset)<br>+<br>+ # Menu 08 - Light<br>+ options = ["%s" % x for x in range(1, 8)]<br>+ rx = RadioSettingValueList(options, options[_settings.light])<br>+ rset = RadioSetting("basicsettings.light",<br>+ "Background Light Color", rx)<br>+ basic.append(rset)<br>+<br>+ # Menu 09 - Beep<br>+ rx = RadioSettingValueBoolean(_settings.beep)<br>+ rset = RadioSetting("basicsettings.beep", "Keypad Beep", rx)<br>+ basic.append(rset)<br>+<br>+ # Menu 11 - TOT<br>+ options = ["Off"] + ["%s seconds" % x for x in range(30, 300, 30)]<br>+ rx = RadioSettingValueList(options, options[_settings.tot])<br>+ rset = RadioSetting("basicsettings.tot",<br>+ "Transmission Time-out Timer", rx)<br>+ basic.append(rset)<br>+<br>+ # Menu 13 - VOX Switch<br>+ rx = RadioSettingValueBoolean(_settings.voxSw)<br>+ rset = RadioSetting("basicsettings.voxSw", "Vox Switch", rx)<br>+ basic.append(rset)<br>+<br>+ # Menu 14 - Roger<br>+ rx = RadioSettingValueBoolean(_settings.roger)<br>+ rset = RadioSetting("basicsettings.roger", "Roger Beep", rx)<br>+ basic.append(rset)<br>+<br>+ # Menu 16 - Save Mode<br>+ options = ["Off", "1:1", "1:2", "1:4"]<br>+ rx = RadioSettingValueList(options, options[_settings.saveMode])<br>+ rset = RadioSetting("basicsettings.saveMode", "Battery Save Mode", rx)<br>+ basic.append(rset)<br>+<br>+ # Menu 33 - Display Mode<br>+ options = ['Frequency', 'Channel', 'Name']<br>+ rx = RadioSettingValueList(options, options[_settings.disMode])<br>+ rset = RadioSetting("basicsettings.disMode", "LED Display Mode", rx)<br>+ basic.append(rset)<br>+<br>+ advanced = RadioSettingGroup("advanced", "Advanced Settings")<br>+ group.append(advanced)<br>+<br>+ # software only<br>+ options = ['0.5S', '1.0S', '1.5S', '2.0S', '2.5S', '3.0S', '3.5S',<br>+ '4.0S', '4.5S', '5.0S']<br>+ rx = RadioSettingValueList(options, options[_settings.voxDelay])<br>+ rset = RadioSetting("basicsettings.voxDelay", "VOX Delay", rx)<br>+ advanced.append(rset)<br>+<br>+ # software only<br>+ name = ""<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("openradioname.name1", "Intro Line 1", rx)<br>+ advanced.append(rset)<br>+<br>+ # software only<br>+ name = ""<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("openradioname.name2", "Intro Line 2", rx)<br>+ advanced.append(rset)<br>+<br>+ workmode = RadioSettingGroup("workmode", "Work Mode Settings")<br>+ group.append(workmode)<br>+<br>+ # Toggle with [#] key<br>+ options = ["Frequency", "Channel"]<br>+ rx = RadioSettingValueList(options, options[_workmode.vfomrmode])<br>+ rset = RadioSetting("workmodesettings.vfomrmode", "VFO/MR Mode", rx)<br>+ workmode.append(rset)<br>+<br>+ # Toggle with [A/B] key<br>+ options = ["A", "B"]<br>+ rx = RadioSettingValueList(options, options[_workmode.ab])<br>+ rset = RadioSetting("workmodesettings.ab", "A/B Select", 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 "." in name:<br>+ bits = name.split(".")<br>+ obj = self._memobj<br>+ for bit in bits[:-1]:<br>+ if "/" in bit:<br>+ bit, index = bit.split("/", 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("Using apply callback")<br>+ element.run_apply_callback()<br>+ elif setting == "voxLevel":<br>+ setattr(obj, setting, int(element.value) + 1)<br>+ elif element.value.get_mutable():<br>+ LOG.debug("Setting %s = %s" % (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>