diff --git a/chirp/drivers/uvk5_99.py b/chirp/drivers/uvk5_99.py deleted file mode 100644 index c9869f39..00000000 --- a/chirp/drivers/uvk5_99.py +++ /dev/null @@ -1,2090 +0,0 @@ -""" Quansheng UV-K5(99) undriver (c) 2024 dara/ei2081 -# -# based on Quansheng UV-K5 driver (c) 2023 Jacek Lipkowski -# -# based on template.py Copyright 2012 Dan Smith -# -# -# This is a preliminary version of a driver for the UV-K5 -# It is based on my reverse engineering effort described here: -# https://github.com/sq5bpf/uvk5-reverse-engineering -# -# Warning: this driver is experimental, it may brick your radio, -# eat your lunch and mess up your configuration. -# -# -# 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 . -# -# """ - - -import struct -import logging - -from chirp import chirp_common, directory, bitwise, memmap, errors, util -from chirp.settings import RadioSetting, RadioSettingGroup, \ - RadioSettingValueBoolean, RadioSettingValueList, \ - RadioSettingValueInteger, RadioSettingValueString, \ - RadioSettings - -LOG = logging.getLogger(__name__) - -# Show the obfuscated version of commands. Not needed normally, but -# might be useful for someone who is debugging a similar radio -DEBUG_SHOW_OBFUSCATED_COMMANDS = False - -# Show the memory being written/received. Not needed normally, because -# this is the same information as in the packet hexdumps, but -# might be useful for someone debugging some obscure memory issue -DEBUG_SHOW_MEMORY_ACTIONS = False - -MEM_FORMAT = """ -#seekto 0x0000; -struct { - ul32 freq; - ul32 offset; - u8 rxcode; - u8 txcode; - - u8 txcodeflag:4, - rxcodeflag:4; - - //u8 flags1; - u8 flags1_unknown7:1, - flags1_unknown6:1, - flags1_unknown5:1, - enable_am:1, - flags1_unknown3:1, - is_in_scanlist:1, - shift:2; - - //u8 flags2; - u8 flags2_unknown7:1, - flags2_unknown6:1, - flags2_unknown5:1, - bclo:1, - txpower:2, - bandwidth:1, - freq_reverse:1; - - //u8 dtmf_flags; - u8 dtmf_flags_unknown7:1, - dtmf_flags_unknown6:1, - dtmf_flags_unknown5:1, - dtmf_flags_unknown4:1, - dtmf_flags_unknown3:1, - dtmf_pttid:2, - dtmf_decode:1; - - - u8 step; - u8 scrambler; -} channel[214]; - -#seekto 0xd60; -struct { -u8 is_scanlist1:1, -is_scanlist2:1, -compander:2, -is_free:1, -band:3; -} channel_attributes[200]; - -#seekto 0xe40; -ul16 fmfreq[20]; - -#seekto 0xe70; -u8 call_channel; -u8 squelch; -u8 max_talk_time; -u8 noaa_autoscan; -u8 key_lock; -u8 vox_switch; -u8 vox_level; -u8 mic_gain; -u8 unknown3; -u8 channel_display_mode; -u8 crossband; -u8 battery_save; -u8 dual_watch; -u8 backlight_auto_mode; -u8 tail_note_elimination; -u8 vfo_open; - -#seekto 0xe90; -u8 beep_control; -u8 key1_shortpress_action; -u8 key1_longpress_action; -u8 key2_shortpress_action; -u8 key2_longpress_action; -u8 scan_resume_mode; -u8 auto_keypad_lock; -u8 power_on_dispmode; -u8 password[4]; - -#seekto 0xea0; -u8 keypad_tone; -u8 language; - -#seekto 0xea8; -u8 alarm_mode; -u8 reminding_of_end_talk; -u8 repeater_tail_elimination; - -#seekto 0xeb0; -char logo_line1[16]; -char logo_line2[16]; - -#seekto 0xed0; -struct { -u8 side_tone; -char separate_code; -char group_call_code; -u8 decode_response; -u8 auto_reset_time; -u8 preload_time; -u8 first_code_persist_time; -u8 hash_persist_time; -u8 code_persist_time; -u8 code_interval_time; -u8 permit_remote_kill; -} dtmf_settings; - -#seekto 0xee0; -struct { -char dtmf_local_code[3]; -char unused1[5]; -char kill_code[5]; -char unused2[3]; -char revive_code[5]; -char unused3[3]; -char dtmf_up_code[16]; -char dtmf_down_code[16]; -} dtmf_settings_numbers; - -#seekto 0xf18; -u8 scanlist_default; -u8 scanlist1_priority_scan; -u8 scanlist1_priority_ch1; -u8 scanlist1_priority_ch2; -u8 scanlist2_priority_scan; -u8 scanlist2_priority_ch1; -u8 scanlist2_priority_ch2; -u8 scanlist_unknown_0xff; - - -#seekto 0xf40; -struct { -u8 flock; -u8 tx350; -u8 killed; -u8 tx200; -u8 tx500; -u8 en350; -u8 enscramble; -} lock; - -#seekto 0xf50; -struct { -char name[16]; -} channelname[200]; - -#seekto 0x1c00; -struct { -char name[8]; -char number[3]; -char unused_00[5]; -} dtmfcontact[16]; - -#seekto 0x1ed0; -struct { -struct { - u8 start; - u8 mid; - u8 end; -} low; -struct { - u8 start; - u8 mid; - u8 end; -} medium; -struct { - u8 start; - u8 mid; - u8 end; -} high; -u8 unused_00[7]; -} perbandpowersettings[7]; - -#seekto 0x1f40; -ul16 battery_level[6]; -""" - -# bits that we will save from the channel structure (mostly unknown) -SAVE_MASK_0A = 0b11001100 -SAVE_MASK_0B = 0b11101100 -SAVE_MASK_0C = 0b11100000 -SAVE_MASK_0D = 0b11111000 -SAVE_MASK_0E = 0b11110001 -SAVE_MASK_0F = 0b11110000 - -# flags1 -FLAGS1_OFFSET_NONE = 0b00 -FLAGS1_OFFSET_MINUS = 0b10 -FLAGS1_OFFSET_PLUS = 0b01 - -POWER_HIGH = 0b10 -POWER_MEDIUM = 0b01 -POWER_LOW = 0b00 - -# power -UVK5_POWER_LEVELS = [chirp_common.PowerLevel("Low", watts=1.50), - chirp_common.PowerLevel("Med", watts=3.00), - chirp_common.PowerLevel("High", watts=5.00), - ] - -# scrambler -SCRAMBLER_LIST = ["off", "1", "2", "3", "4", "5", "6", "7", "8", "9", "10"] - -# channel display mode -CHANNELDISP_LIST = ["Frequency", "Channel No", "Channel Name"] -# battery save -BATSAVE_LIST = ["OFF", "1:1", "1:2", "1:3", "1:4"] - -# Backlight auto mode -BACKLIGHT_LIST = ["Off", "1s", "2s", "3s", "4s", "5s"] - -# Crossband receiving/transmitting -CROSSBAND_LIST = ["Off", "Band A", "Band B"] -DUALWATCH_LIST = CROSSBAND_LIST - -# ctcss/dcs codes -TMODES = ["", "Tone", "DTCS", "DTCS"] -TONE_NONE = 0 -TONE_CTCSS = 1 -TONE_DCS = 2 -TONE_RDCS = 3 - - -CTCSS_TONES = [ - 67.0, 69.3, 71.9, 74.4, 77.0, 79.7, 82.5, 85.4, - 88.5, 91.5, 94.8, 97.4, 100.0, 103.5, 107.2, 110.9, - 114.8, 118.8, 123.0, 127.3, 131.8, 136.5, 141.3, 146.2, - 151.4, 156.7, 159.8, 162.2, 165.5, 167.9, 171.3, 173.8, - 177.3, 179.9, 183.5, 186.2, 189.9, 192.8, 196.6, 199.5, - 203.5, 206.5, 210.7, 218.1, 225.7, 229.1, 233.6, 241.8, - 250.3, 254.1 -] - -# lifted from ft4.py -DTCS_CODES = [ - 23, 25, 26, 31, 32, 36, 43, 47, 51, 53, 54, - 65, 71, 72, 73, 74, 114, 115, 116, 122, 125, 131, - 132, 134, 143, 145, 152, 155, 156, 162, 165, 172, 174, - 205, 212, 223, 225, 226, 243, 244, 245, 246, 251, 252, - 255, 261, 263, 265, 266, 271, 274, 306, 311, 315, 325, - 331, 332, 343, 346, 351, 356, 364, 365, 371, 411, 412, - 413, 423, 431, 432, 445, 446, 452, 454, 455, 462, 464, - 465, 466, 503, 506, 516, 523, 526, 532, 546, 565, 606, - 612, 624, 627, 631, 632, 654, 662, 664, 703, 712, 723, - 731, 732, 734, 743, 754 -] - -FLOCK_LIST = ["Off", "FCC", "CE", "GB", "430", "438"] - -SCANRESUME_LIST = ["TO: Resume after 5 seconds", - "CO: Resume after signal disappears", - "SE: Stop scanning after receiving a signal"] - -WELCOME_LIST = ["Full Screen", "Welcome Info", "Voltage"] -KEYPADTONE_LIST = ["Off", "Chinese", "English"] -LANGUAGE_LIST = ["Chinese", "English"] -ALARMMODE_LIST = ["SITE", "TONE"] -REMENDOFTALK_LIST = ["Off", "ROGER", "MDC"] -RTE_LIST = ["Off", "100ms", "200ms", "300ms", "400ms", - "500ms", "600ms", "700ms", "800ms", "900ms"] - -MEM_SIZE = 0x2000 # size of all memory -PROG_SIZE = 0x1d00 # size of the memory that we will write -MEM_BLOCK = 0x80 # largest block of memory that we can reliably write - -# fm radio supported frequencies -FMMIN = 76.0 -FMMAX = 108.0 - -# bands supported by the UV-K5 -BANDS = { - 0: [50.0, 76.0], - 1: [108.0, 135.9999], - 2: [136.0, 199.9990], - 3: [200.0, 299.9999], - 4: [350.0, 399.9999], - 5: [400.0, 469.9999], - 6: [470.0, 600.0] - } - -# for radios with modified firmware: -BANDS_NOLIMITS = { - 0: [18.0, 76.0], - 1: [108.0, 135.9999], - 2: [136.0, 199.9990], - 3: [200.0, 299.9999], - 4: [350.0, 399.9999], - 5: [400.0, 469.9999], - 6: [470.0, 1300.0] - } - -SPECIALS = { - "F1(50M-76M)A": 200, - "F1(50M-76M)B": 201, - "F2(108M-136M)A": 202, - "F2(108M-136M)B": 203, - "F3(136M-174M)A": 204, - "F3(136M-174M)B": 205, - "F4(174M-350M)A": 206, - "F4(174M-350M)B": 207, - "F5(350M-400M)A": 208, - "F5(350M-400M)B": 209, - "F6(400M-470M)A": 210, - "F6(400M-470M)B": 211, - "F7(470M-600M)A": 212, - "F7(470M-600M)B": 213 - } - -VFO_CHANNEL_NAMES = ["F1(50M-76M)A", "F1(50M-76M)B", - "F2(108M-136M)A", "F2(108M-136M)B", - "F3(136M-174M)A", "F3(136M-174M)B", - "F4(174M-350M)A", "F4(174M-350M)B", - "F5(350M-400M)A", "F5(350M-400M)B", - "F6(400M-470M)A", "F6(400M-470M)B", - "F7(470M-600M)A", "F7(470M-600M)B"] - -SCANLIST_LIST = ["None", "1", "2", "1+2"] - -DTMF_CHARS = "0123456789ABCD*# " -DTMF_CHARS_ID = "0123456789ABCDabcd" -DTMF_CHARS_KILL = "0123456789ABCDabcd" -DTMF_CHARS_UPDOWN = "0123456789ABCDabcd#* " -DTMF_CODE_CHARS = "ABCD*# " -DTMF_DECODE_RESPONSE_LIST = ["None", "Ring", "Reply", "Both"] - -KEYACTIONS_LIST = ["None", "Flashlight on/off", "Power select", - "Monitor", "Scan on/off", "VOX on/off", - "Alarm on/off", "FM radio on/off", "Transmit 1750 Hz"] - - -def xorarr(data: bytes): - """the communication is obfuscated using this fine mechanism""" - tbl = [22, 108, 20, 230, 46, 145, 13, 64, 33, 53, 213, 64, 19, 3, 233, 128] - ret = b"" - idx = 0 - for byte in data: - ret += bytes([byte ^ tbl[idx]]) - idx = (idx+1) % len(tbl) - return ret - - -def calculate_crc16_xmodem(data: bytes): - """ - if this crc was used for communication to AND from the radio, then it - would be a measure to increase reliability. - but it's only used towards the radio, so it's for further obfuscation - """ - poly = 0x1021 - crc = 0x0 - for byte in data: - crc = crc ^ (byte << 8) - for _ in range(8): - crc = crc << 1 - if crc & 0x10000: - crc = (crc ^ poly) & 0xFFFF - return crc & 0xFFFF - - -def _send_command(serport, data: bytes): - """Send a command to UV-K5 radio""" - LOG.debug("Sending command (unobfuscated) len=0x%4.4x:\n%s", - len(data), util.hexprint(data)) - - crc = calculate_crc16_xmodem(data) - data2 = data + struct.pack("HBB", 0xabcd, len(data), 0) + \ - xorarr(data2) + \ - struct.pack(">H", 0xdcba) - if DEBUG_SHOW_OBFUSCATED_COMMANDS: - LOG.debug("Sending command (obfuscated):\n%s", util.hexprint(command)) - try: - result = serport.write(command) - except Exception as e: - raise errors.RadioError("Error writing data to radio") from e - return result - - -def _receive_reply(serport): - header = serport.read(4) - if not header: - raise errors.RadioError("No response from radio") - elif len(header) != 4: - LOG.warning("Header short read: [%s] len=%i", - util.hexprint(header), len(header)) - raise errors.RadioError("Header short read") - elif header[0] != 0xAB or header[1] != 0xCD or header[3] != 0x00: - LOG.warning("Bad response header: %s len=%i", - util.hexprint(header), len(header)) - raise errors.RadioError("Bad response header") - - cmd = serport.read(int(header[2])) - if len(cmd) != int(header[2]): - LOG.warning("Body short read: [%s] len=%i", - util.hexprint(cmd), len(cmd)) - raise errors.RadioError("Command body short read") - - footer = serport.read(4) - - if len(footer) != 4: - LOG.warning("Footer short read: [%s] len=%i", - util.hexprint(footer), len(footer)) - raise errors.RadioError("Footer short read") - - if footer[2] != 0xDC or footer[3] != 0xBA: - LOG.debug("Reply before bad response footer (obfuscated)" - "len=0x%4.4x:\n%s", len(cmd), util.hexprint(cmd)) - LOG.warning("Bad response footer: %s len=%i", - util.hexprint(footer), len(footer)) - raise errors.RadioError("Bad response footer") - - if DEBUG_SHOW_OBFUSCATED_COMMANDS: - LOG.debug("Received reply (obfuscated) len=0x%4.4x:\n%s", - len(cmd), util.hexprint(cmd)) - - cmd2 = xorarr(cmd) - - LOG.debug("Received reply (unobfuscated) len=0x%4.4x:\n%s", - len(cmd2), util.hexprint(cmd2)) - - return cmd2 - - -def _getstring(data: bytes, begin, maxlen): - tmplen = min(maxlen+1, len(data)) - ss = [data[i] for i in range(begin, tmplen)] - key = 0 - for key, val in enumerate(ss): - if val < ord(' ') or val > ord('~'): - return ''.join(chr(x) for x in ss[0:key]) - return '' - - -def _sayhello(serport): - hellopacket = b"\x14\x05\x04\x00\x6a\x39\x57\x64" - - tries = 5 - while True: - LOG.debug("Sending hello packet") - _send_command(serport, hellopacket) - rep = _receive_reply(serport) - if rep: - break - tries -= 1 - if tries == 0: - LOG.warning("Failed to initialise radio") - raise errors.RadioError("Failed to initialize radio") - if rep.startswith(b'\x18\x05'): - raise errors.RadioError("Radio is in programming mode, " - "restart radio into normal mode") - firmware = _getstring(rep, 4, 24) - - LOG.info("Found firmware: %s", firmware) - return firmware - - -def _readmem(serport, offset, length): - LOG.debug("Sending readmem offset=0x%4.4x len=0x%4.4x", offset, length) - - readmem = b"\x1b\x05\x08\x00" + \ - struct.pack("> 8) & 0xff): - return True - - LOG.warning("Bad data from writemem") - raise errors.RadioError("Bad response to writemem") - - -def _resetradio(serport): - resetpacket = b"\xdd\x05\x00\x00" - _send_command(serport, resetpacket) - - -def do_download(radio): - """download eeprom from radio""" - serport = radio.pipe - serport.timeout = 0.5 - status = chirp_common.Status() - status.cur = 0 - status.max = MEM_SIZE - status.msg = "Downloading from radio" - radio.status_fn(status) - - eeprom = b"" - f = _sayhello(serport) - if not f: - raise errors.RadioError('Unable to determine firmware version') - - if not radio.k5_approve_firmware(f): - raise errors.RadioError( - 'Firmware version is not supported by this driver') - - radio.metadata = {'uvk5_firmware': f} - - addr = 0 - while addr < MEM_SIZE: - data = _readmem(serport, addr, MEM_BLOCK) - status.cur = addr - radio.status_fn(status) - - if data and len(data) == MEM_BLOCK: - eeprom += data - addr += MEM_BLOCK - else: - raise errors.RadioError("Memory download incomplete") - - return memmap.MemoryMapBytes(eeprom) - - -def do_upload(radio): - """upload configuration to radio eeprom""" - serport = radio.pipe - serport.timeout = 0.5 - status = chirp_common.Status() - status.cur = 0 - status.msg = "Uploading to radio" - - if radio._upload_calibration: - status.max = MEM_SIZE - radio._cal_start - start_addr = radio._cal_start - stop_addr = MEM_SIZE - else: - status.max = PROG_SIZE - start_addr = 0 - stop_addr = PROG_SIZE - - radio.status_fn(status) - - f = _sayhello(serport) - if not f: - raise errors.RadioError('Unable to determine firmware version') - - if not radio.k5_approve_firmware(f): - raise errors.RadioError( - 'Firmware version is not supported by this driver') - LOG.info('Uploading image from firmware %r to radio with %r', - radio.metadata.get('uvk5_firmware', 'unknown'), f) - addr = start_addr - while addr < stop_addr: - dat = radio.get_mmap()[addr:addr+MEM_BLOCK] - _writemem(serport, dat, addr) - status.cur = addr - start_addr - radio.status_fn(status) - if dat: - addr += MEM_BLOCK - else: - raise errors.RadioError("Memory upload incomplete") - status.msg = "Uploaded OK" - - _resetradio(serport) - - return True - - -def _find_band(nolimits, hz): - mhz = hz/1000000.0 - if nolimits: - B = BANDS_NOLIMITS - else: - B = BANDS - - # currently the hacked firmware sets band=1 below 50 MHz - if nolimits and mhz < 50.0: - return 1 - - for a in B: - if mhz >= B[a][0] and mhz <= B[a][1]: - return a - return False - - -class UVK5RadioBase(chirp_common.CloneModeRadio): - """Quansheng UV-K5""" - VENDOR = "Quansheng" - MODEL = "UV-K5" - BAUD_RATE = 38400 - _cal_start = 0 - _expanded_limits = False - _upload_calibration = False - _pttid_list = ["off", "BOT", "EOT", "BOTH"] - _steps = [1.0, 2.5, 5.0, 6.25, 10.0, 12.5, 25.0, 8.33] - - @classmethod - def k5_approve_firmware(cls, firmware): - # All subclasses must implement this - raise NotImplementedError() - - def get_prompts(x=None): - rp = chirp_common.RadioPrompts() - rp.experimental = _( - 'This is an experimental driver for the Quansheng UV-K5. ' - 'It may harm your radio, or worse. Use at your own risk.\n\n' - 'Before attempting to do any changes please download ' - 'the memory image from the radio with chirp ' - 'and keep it. This can be later used to recover the ' - 'original settings. \n\n' - 'some details are not yet implemented') - rp.pre_download = _( - "1. Turn radio on.\n" - "2. Connect cable to mic/spkr connector.\n" - "3. Make sure connector is firmly connected.\n" - "4. Click OK to download image from device.\n\n" - "It will may not work if you turn on the radio " - "with the cable already attached\n") - rp.pre_upload = _( - "1. Turn radio on.\n" - "2. Connect cable to mic/spkr connector.\n" - "3. Make sure connector is firmly connected.\n" - "4. Click OK to upload the image to device.\n\n" - "It will may not work if you turn on the radio " - "with the cable already attached") - return rp - - # Return information about this radio's features, including - # how many memories it has, what bands it supports, etc - def get_features(self): - rf = chirp_common.RadioFeatures() - rf.has_bank = False - rf.valid_dtcs_codes = DTCS_CODES - rf.has_rx_dtcs = True - rf.has_ctone = True - rf.has_settings = True - rf.has_comment = False - rf.valid_name_length = 10 - rf.valid_power_levels = UVK5_POWER_LEVELS - rf.valid_special_chans = list(SPECIALS.keys()) - rf.valid_duplexes = ["", "-", "+", "off"] - - # hack so we can input any frequency, - # the 0.1 and 0.01 steps don't work unfortunately - rf.valid_tuning_steps = list(self._steps) - - rf.valid_tmodes = ["", "Tone", "TSQL", "DTCS", "Cross"] - rf.valid_cross_modes = ["Tone->Tone", "Tone->DTCS", "DTCS->Tone", - "->Tone", "->DTCS", "DTCS->", "DTCS->DTCS"] - - rf.valid_characters = chirp_common.CHARSET_ASCII - rf.valid_modes = ["FM", "NFM", "AM", "NAM"] - rf.valid_tmodes = ["", "Tone", "TSQL", "DTCS", "Cross"] - - rf.valid_skips = [""] - - # This radio supports memories 1-200, 201-214 are the VFO memories - rf.memory_bounds = (1, 200) - - rf.valid_bands = [] - for a in BANDS_NOLIMITS: - rf.valid_bands.append( - (int(BANDS_NOLIMITS[a][0]*1000000), - int(BANDS_NOLIMITS[a][1]*1000000))) - return rf - - # Do a download of the radio from the serial port - def sync_in(self): - self._mmap = do_download(self) - self.process_mmap() - - # Do an upload of the radio to the serial port - def sync_out(self): - do_upload(self) - - def _check_firmware_at_load(self): - firmware = self.metadata.get('uvk5_firmware') - if not firmware: - LOG.warning(_('This image is missing firmware information. ' - 'It may have been generated with an old or ' - 'modified version of CHIRP. It is advised that ' - 'you download a fresh image from your radio and ' - 'use that going forward for the best safety and ' - 'compatibility.')) - elif not self.k5_approve_firmware(self.metadata['uvk5_firmware']): - raise errors.RadioError( - 'Image firmware is %r but is not supported by ' - 'this driver' % firmware) - - # Convert the raw byte array into a memory object structure - def process_mmap(self): - self._check_firmware_at_load() - self._memobj = bitwise.parse(MEM_FORMAT, self._mmap) - - # Return a raw representation of the memory object, which - # is very helpful for development - def get_raw_memory(self, number): - return repr(self._memobj.channel[number-1]) - - def _find_band(self, hz): - return _find_band(self._expanded_limits, hz) - - def validate_memory(self, mem): - msgs = super().validate_memory(mem) - - if mem.duplex == 'off': - return msgs - - # find tx frequency - if mem.duplex == '-': - txfreq = mem.freq - mem.offset - elif mem.duplex == '+': - txfreq = mem.freq + mem.offset - else: - txfreq = mem.freq - - # find band - band = self._find_band(txfreq) - if band is False: - msg = "Transmit frequency %.4f MHz is not supported by this radio"\ - % (txfreq/1000000.0) - msgs.append(chirp_common.ValidationError(msg)) - - band = self._find_band(mem.freq) - if band is False: - msg = "The frequency %.4f MHz is not supported by this radio" \ - % (mem.freq/1000000.0) - msgs.append(chirp_common.ValidationError(msg)) - - return msgs - - def _set_tone(self, mem, _mem): - ((txmode, txtone, txpol), - (rxmode, rxtone, rxpol)) = chirp_common.split_tone_encode(mem) - - if txmode == "Tone": - txtoval = CTCSS_TONES.index(txtone) - txmoval = 0b01 - elif txmode == "DTCS": - txmoval = txpol == "R" and 0b11 or 0b10 - txtoval = DTCS_CODES.index(txtone) - else: - txmoval = 0 - txtoval = 0 - - if rxmode == "Tone": - rxtoval = CTCSS_TONES.index(rxtone) - rxmoval = 0b01 - elif rxmode == "DTCS": - rxmoval = rxpol == "R" and 0b11 or 0b10 - rxtoval = DTCS_CODES.index(rxtone) - else: - rxmoval = 0 - rxtoval = 0 - - _mem.rxcodeflag = rxmoval - _mem.txcodeflag = txmoval - _mem.rxcode = rxtoval - _mem.txcode = txtoval - - def _get_tone(self, mem, _mem): - rxtype = _mem.rxcodeflag - txtype = _mem.txcodeflag - rx_tmode = TMODES[rxtype] - tx_tmode = TMODES[txtype] - - rx_tone = tx_tone = None - - if tx_tmode == "Tone": - if _mem.txcode < len(CTCSS_TONES): - tx_tone = CTCSS_TONES[_mem.txcode] - else: - tx_tone = 0 - tx_tmode = "" - elif tx_tmode == "DTCS": - if _mem.txcode < len(DTCS_CODES): - tx_tone = DTCS_CODES[_mem.txcode] - else: - tx_tone = 0 - tx_tmode = "" - - if rx_tmode == "Tone": - if _mem.rxcode < len(CTCSS_TONES): - rx_tone = CTCSS_TONES[_mem.rxcode] - else: - rx_tone = 0 - rx_tmode = "" - elif rx_tmode == "DTCS": - if _mem.rxcode < len(DTCS_CODES): - rx_tone = DTCS_CODES[_mem.rxcode] - else: - rx_tone = 0 - rx_tmode = "" - - tx_pol = txtype == 0x03 and "R" or "N" - rx_pol = rxtype == 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_mem_extra(self, mem, _mem): - tmpscn = SCANLIST_LIST[0] - - # We'll also look at the channel attributes if a memory has them - if mem.number <= 200: - _mem3 = self._memobj.channel_attributes[mem.number - 1] - # free memory bit - if _mem3.is_free > 0: - mem.empty = True - # scanlists - if _mem3.is_scanlist1 > 0 and _mem3.is_scanlist2 > 0: - tmpscn = SCANLIST_LIST[3] # "1+2" - elif _mem3.is_scanlist1 > 0: - tmpscn = SCANLIST_LIST[1] # "1" - elif _mem3.is_scanlist2 > 0: - tmpscn = SCANLIST_LIST[2] # "2" - - mem.extra = RadioSettingGroup("Extra", "extra") - - # BCLO - is_bclo = not mem.empty and bool(_mem.bclo > 0) - rs = RadioSetting("bclo", "BCLO", RadioSettingValueBoolean(is_bclo)) - mem.extra.append(rs) - - # Frequency reverse - reverse tx/rx frequency - is_frev = not mem.empty and bool(_mem.freq_reverse > 0) - rs = RadioSetting("frev", "FreqRev", RadioSettingValueBoolean(is_frev)) - mem.extra.append(rs) - - # PTTID - try: - pttid = self._pttid_list[_mem.dtmf_pttid] - except IndexError: - pttid = 0 - rs = RadioSetting("pttid", "PTTID", RadioSettingValueList( - self._pttid_list, pttid)) - mem.extra.append(rs) - - # DTMF DECODE - is_dtmf = not mem.empty and bool(_mem.dtmf_decode > 0) - rs = RadioSetting("dtmfdecode", _("DTMF decode"), - RadioSettingValueBoolean(is_dtmf)) - mem.extra.append(rs) - - # Scrambler - if _mem.scrambler & 0x0f < len(SCRAMBLER_LIST): - enc = _mem.scrambler & 0x0f - else: - enc = 0 - - rs = RadioSetting("scrambler", _("Scrambler"), RadioSettingValueList( - SCRAMBLER_LIST, SCRAMBLER_LIST[enc])) - mem.extra.append(rs) - - rs = RadioSetting("scanlists", _("Scanlists"), RadioSettingValueList( - SCANLIST_LIST, tmpscn)) - mem.extra.append(rs) - - def _get_mem_mode(self, _mem): - if _mem.enable_am > 0: - if _mem.bandwidth > 0: - return "NAM" - else: - return "AM" - else: - if _mem.bandwidth > 0: - return "NFM" - else: - return "FM" - - def _get_specials(self): - return dict(SPECIALS) - - # Extract a high-level memory object from the low-level memory map - # This is called to populate a memory in the UI - def get_memory(self, number2): - - mem = chirp_common.Memory() - - if isinstance(number2, str): - number = self._get_specials()[number2] - mem.extd_number = number2 - else: - number = number2 - 1 - - mem.number = number + 1 - - _mem = self._memobj.channel[number] - - # We'll consider any blank (i.e. 0 MHz frequency) to be empty - if (_mem.freq == 0xffffffff) or (_mem.freq == 0): - mem.empty = True - - self._get_mem_extra(mem, _mem) - - if mem.empty: - return mem - - if number > 199: - mem.immutable = ["name", "scanlists"] - else: - _mem2 = self._memobj.channelname[number] - for char in _mem2.name: - if str(char) == "\xFF" or str(char) == "\x00": - break - mem.name += str(char) - mem.name = mem.name.rstrip() - - # Convert your low-level frequency to Hertz - mem.freq = int(_mem.freq)*10 - mem.offset = int(_mem.offset)*10 - - if (mem.offset == 0): - mem.duplex = '' - else: - if _mem.shift == FLAGS1_OFFSET_MINUS: - if _mem.freq == _mem.offset: - # fake tx disable by setting tx to 0 MHz - mem.duplex = 'off' - mem.offset = 0 - else: - mem.duplex = '-' - elif _mem.shift == FLAGS1_OFFSET_PLUS: - mem.duplex = '+' - else: - mem.duplex = '' - - # tone data - self._get_tone(mem, _mem) - - mem.mode = self._get_mem_mode(_mem) - - # tuning step - try: - mem.tuning_step = self._steps[_mem.step] - except IndexError: - mem.tuning_step = 2.5 - - # power - if _mem.txpower == POWER_HIGH: - mem.power = UVK5_POWER_LEVELS[2] - elif _mem.txpower == POWER_MEDIUM: - mem.power = UVK5_POWER_LEVELS[1] - else: - mem.power = UVK5_POWER_LEVELS[0] - - # We'll consider any blank (i.e. 0 MHz frequency) to be empty - if (_mem.freq == 0xffffffff) or (_mem.freq == 0): - mem.empty = True - else: - mem.empty = False - - return mem - - def set_settings(self, settings): - _mem = self._memobj - for element in settings: - if not isinstance(element, RadioSetting): - self.set_settings(element) - continue - - # basic settings - - # call channel - if element.get_name() == "call_channel": - _mem.call_channel = int(element.value)-1 - - # squelch - if element.get_name() == "squelch": - _mem.squelch = int(element.value) - # TOT - if element.get_name() == "tot": - _mem.max_talk_time = int(element.value) - - # NOAA autoscan - if element.get_name() == "noaa_autoscan": - _mem.noaa_autoscan = element.value and 1 or 0 - - # VOX switch - if element.get_name() == "vox_switch": - _mem.vox_switch = element.value and 1 or 0 - - # vox level - if element.get_name() == "vox_level": - _mem.vox_level = int(element.value)-1 - - # mic gain - if element.get_name() == "mic_gain": - _mem.mic_gain = int(element.value) - - # Channel display mode - if element.get_name() == "channel_display_mode": - _mem.channel_display_mode = CHANNELDISP_LIST.index( - str(element.value)) - - # Crossband receiving/transmitting - if element.get_name() == "crossband": - _mem.crossband = CROSSBAND_LIST.index(str(element.value)) - - # Battery Save - if element.get_name() == "battery_save": - _mem.battery_save = BATSAVE_LIST.index(str(element.value)) - # Dual Watch - if element.get_name() == "dualwatch": - _mem.dual_watch = DUALWATCH_LIST.index(str(element.value)) - - # Backlight auto mode - if element.get_name() == "backlight_auto_mode": - _mem.backlight_auto_mode = \ - BACKLIGHT_LIST.index(str(element.value)) - - # Tail tone elimination - if element.get_name() == "tail_note_elimination": - _mem.tail_note_elimination = element.value and 1 or 0 - - # VFO Open - if element.get_name() == "vfo_open": - _mem.vfo_open = element.value and 1 or 0 - - # Beep control - if element.get_name() == "beep_control": - _mem.beep_control = element.value and 1 or 0 - - # Scan resume mode - if element.get_name() == "scan_resume_mode": - _mem.scan_resume_mode = SCANRESUME_LIST.index( - str(element.value)) - - # Keypad lock - if element.get_name() == "key_lock": - _mem.key_lock = element.value and 1 or 0 - - # Auto keypad lock - if element.get_name() == "auto_keypad_lock": - _mem.auto_keypad_lock = element.value and 1 or 0 - - # Power on display mode - if element.get_name() == "welcome_mode": - _mem.power_on_dispmode = WELCOME_LIST.index(str(element.value)) - - # Keypad Tone - if element.get_name() == "keypad_tone": - _mem.keypad_tone = KEYPADTONE_LIST.index(str(element.value)) - - # Language - if element.get_name() == "language": - _mem.language = LANGUAGE_LIST.index(str(element.value)) - - # Alarm mode - if element.get_name() == "alarm_mode": - _mem.alarm_mode = ALARMMODE_LIST.index(str(element.value)) - - # Reminding of end of talk - if element.get_name() == "reminding_of_end_talk": - _mem.reminding_of_end_talk = REMENDOFTALK_LIST.index( - str(element.value)) - - # Repeater tail tone elimination - if element.get_name() == "repeater_tail_elimination": - _mem.repeater_tail_elimination = RTE_LIST.index( - str(element.value)) - - # Logo string 1 - if element.get_name() == "logo1": - b = str(element.value).rstrip("\x20\xff\x00")+"\x00"*12 - _mem.logo_line1 = b[0:12]+"\x00\xff\xff\xff" - - # Logo string 2 - if element.get_name() == "logo2": - b = str(element.value).rstrip("\x20\xff\x00")+"\x00"*12 - _mem.logo_line2 = b[0:12]+"\x00\xff\xff\xff" - - # unlock settings - - # FLOCK - if element.get_name() == "flock": - _mem.lock.flock = FLOCK_LIST.index(str(element.value)) - - # 350TX - if element.get_name() == "tx350": - _mem.lock.tx350 = element.value and 1 or 0 - - # 200TX - if element.get_name() == "tx200": - _mem.lock.tx200 = element.value and 1 or 0 - - # 500TX - if element.get_name() == "tx500": - _mem.lock.tx500 = element.value and 1 or 0 - - # 350EN - if element.get_name() == "en350": - _mem.lock.en350 = element.value and 1 or 0 - - # SCREN - if element.get_name() == "enscramble": - _mem.lock.enscramble = element.value and 1 or 0 - - # KILLED - if element.get_name() == "killed": - _mem.lock.killed = element.value and 1 or 0 - - # fm radio - for i in range(1, 21): - freqname = "FM_" + str(i) - if element.get_name() == freqname: - val = str(element.value).strip() - try: - val2 = int(float(val)*10) - except Exception: - val2 = 0xffff - - if val2 < FMMIN*10 or val2 > FMMAX*10: - val2 = 0xffff -# raise errors.InvalidValueError( -# "FM radio frequency should be a value " -# "in the range %.1f - %.1f" % (FMMIN , FMMAX)) - _mem.fmfreq[i-1] = val2 - - # dtmf settings - if element.get_name() == "dtmf_side_tone": - _mem.dtmf_settings.side_tone = \ - element.value and 1 or 0 - - if element.get_name() == "dtmf_separate_code": - _mem.dtmf_settings.separate_code = str(element.value) - - if element.get_name() == "dtmf_group_call_code": - _mem.dtmf_settings.group_call_code = element.value - - if element.get_name() == "dtmf_decode_response": - _mem.dtmf_settings.decode_response = \ - DTMF_DECODE_RESPONSE_LIST.index(str(element.value)) - - if element.get_name() == "dtmf_auto_reset_time": - _mem.dtmf_settings.auto_reset_time = \ - int(int(element.value)/10) - - if element.get_name() == "dtmf_preload_time": - _mem.dtmf_settings.preload_time = \ - int(int(element.value)/10) - - if element.get_name() == "dtmf_first_code_persist_time": - _mem.dtmf_settings.first_code_persist_time = \ - int(int(element.value)/10) - - if element.get_name() == "dtmf_hash_persist_time": - _mem.dtmf_settings.hash_persist_time = \ - int(int(element.value)/10) - - if element.get_name() == "dtmf_code_persist_time": - _mem.dtmf_settings.code_persist_time = \ - int(int(element.value)/10) - - if element.get_name() == "dtmf_code_interval_time": - _mem.dtmf_settings.code_interval_time = \ - int(int(element.value)/10) - - if element.get_name() == "dtmf_permit_remote_kill": - _mem.dtmf_settings.permit_remote_kill = \ - element.value and 1 or 0 - - if element.get_name() == "dtmf_dtmf_local_code": - k = str(element.value).rstrip("\x20\xff\x00") + "\x00"*3 - _mem.dtmf_settings_numbers.dtmf_local_code = k[0:3] - - if element.get_name() == "dtmf_dtmf_up_code": - k = str(element.value).strip("\x20\xff\x00") + "\x00"*16 - _mem.dtmf_settings_numbers.dtmf_up_code = k[0:16] - - if element.get_name() == "dtmf_dtmf_down_code": - k = str(element.value).rstrip("\x20\xff\x00") + "\x00"*16 - _mem.dtmf_settings_numbers.dtmf_down_code = k[0:16] - - if element.get_name() == "dtmf_kill_code": - k = str(element.value).strip("\x20\xff\x00") + "\x00"*5 - _mem.dtmf_settings_numbers.kill_code = k[0:5] - - if element.get_name() == "dtmf_revive_code": - k = str(element.value).strip("\x20\xff\x00") + "\x00"*5 - _mem.dtmf_settings_numbers.revive_code = k[0:5] - - # dtmf contacts - for i in range(1, 17): - varname = "DTMF_" + str(i) - if element.get_name() == varname: - k = str(element.value).rstrip("\x20\xff\x00") + "\x00"*8 - _mem.dtmfcontact[i-1].name = k[0:8] - - varnumname = "DTMFNUM_" + str(i) - if element.get_name() == varnumname: - k = str(element.value).rstrip("\x20\xff\x00") + "\xff"*3 - _mem.dtmfcontact[i-1].number = k[0:3] - - # scanlist stuff - if element.get_name() == "scanlist_default": - val = (int(element.value) == 2) and 1 or 0 - _mem.scanlist_default = val - - if element.get_name() == "scanlist1_priority_scan": - _mem.scanlist1_priority_scan = \ - element.value and 1 or 0 - - if element.get_name() == "scanlist2_priority_scan": - _mem.scanlist2_priority_scan = \ - element.value and 1 or 0 - - if element.get_name() == "scanlist1_priority_ch1" or \ - element.get_name() == "scanlist1_priority_ch2" or \ - element.get_name() == "scanlist2_priority_ch1" or \ - element.get_name() == "scanlist2_priority_ch2": - - val = int(element.value) - - if val > 200 or val < 1: - val = 0xff - else: - val -= 1 - - if element.get_name() == "scanlist1_priority_ch1": - _mem.scanlist1_priority_ch1 = val - if element.get_name() == "scanlist1_priority_ch2": - _mem.scanlist1_priority_ch2 = val - if element.get_name() == "scanlist2_priority_ch1": - _mem.scanlist2_priority_ch1 = val - if element.get_name() == "scanlist2_priority_ch2": - _mem.scanlist2_priority_ch2 = val - - if element.get_name() == "key1_shortpress_action": - _mem.key1_shortpress_action = KEYACTIONS_LIST.index( - str(element.value)) - - if element.get_name() == "key1_longpress_action": - _mem.key1_longpress_action = KEYACTIONS_LIST.index( - str(element.value)) - - if element.get_name() == "key2_shortpress_action": - _mem.key2_shortpress_action = KEYACTIONS_LIST.index( - str(element.value)) - - if element.get_name() == "key2_longpress_action": - _mem.key2_longpress_action = KEYACTIONS_LIST.index( - str(element.value)) - - if element.get_name() == "nolimits": - LOG.warning("User expanded band limits") - self._expanded_limits = bool(element.value) - - def get_settings(self): - _mem = self._memobj - basic = RadioSettingGroup("basic", "Basic Settings") - keya = RadioSettingGroup("keya", "Programmable keys") - dtmf = RadioSettingGroup("dtmf", "DTMF Settings") - dtmfc = RadioSettingGroup("dtmfc", "DTMF Contacts") - scanl = RadioSettingGroup("scn", "Scan Lists") - unlock = RadioSettingGroup("unlock", "Unlock Settings") - fmradio = RadioSettingGroup("fmradio", _("FM Radio")) - - roinfo = RadioSettingGroup("roinfo", _("Driver information")) - - top = RadioSettings( - basic, keya, dtmf, dtmfc, scanl, unlock, fmradio, roinfo) - - # Programmable keys - tmpval = int(_mem.key1_shortpress_action) - if tmpval >= len(KEYACTIONS_LIST): - tmpval = 0 - rs = RadioSetting("key1_shortpress_action", "Side key 1 short press", - RadioSettingValueList( - KEYACTIONS_LIST, KEYACTIONS_LIST[tmpval])) - keya.append(rs) - - tmpval = int(_mem.key1_longpress_action) - if tmpval >= len(KEYACTIONS_LIST): - tmpval = 0 - rs = RadioSetting("key1_longpress_action", "Side key 1 long press", - RadioSettingValueList( - KEYACTIONS_LIST, KEYACTIONS_LIST[tmpval])) - keya.append(rs) - - tmpval = int(_mem.key2_shortpress_action) - if tmpval >= len(KEYACTIONS_LIST): - tmpval = 0 - rs = RadioSetting("key2_shortpress_action", "Side key 2 short press", - RadioSettingValueList( - KEYACTIONS_LIST, KEYACTIONS_LIST[tmpval])) - keya.append(rs) - - tmpval = int(_mem.key2_longpress_action) - if tmpval >= len(KEYACTIONS_LIST): - tmpval = 0 - rs = RadioSetting("key2_longpress_action", "Side key 2 long press", - RadioSettingValueList( - KEYACTIONS_LIST, KEYACTIONS_LIST[tmpval])) - keya.append(rs) - - # DTMF settings - tmppr = bool(_mem.dtmf_settings.side_tone > 0) - rs = RadioSetting( - "dtmf_side_tone", - "DTMF Sidetone", - RadioSettingValueBoolean(tmppr)) - dtmf.append(rs) - - tmpval = str(_mem.dtmf_settings.separate_code) - if tmpval not in DTMF_CODE_CHARS: - tmpval = '*' - val = RadioSettingValueString(1, 1, tmpval) - val.set_charset(DTMF_CODE_CHARS) - rs = RadioSetting("dtmf_separate_code", "Separate Code", val) - dtmf.append(rs) - - tmpval = str(_mem.dtmf_settings.group_call_code) - if tmpval not in DTMF_CODE_CHARS: - tmpval = '#' - val = RadioSettingValueString(1, 1, tmpval) - val.set_charset(DTMF_CODE_CHARS) - rs = RadioSetting("dtmf_group_call_code", "Group Call Code", val) - dtmf.append(rs) - - tmpval = _mem.dtmf_settings.decode_response - if tmpval >= len(DTMF_DECODE_RESPONSE_LIST): - tmpval = 0 - rs = RadioSetting("dtmf_decode_response", "Decode Response", - RadioSettingValueList( - DTMF_DECODE_RESPONSE_LIST, - DTMF_DECODE_RESPONSE_LIST[tmpval])) - dtmf.append(rs) - - tmpval = _mem.dtmf_settings.auto_reset_time - if tmpval > 60 or tmpval < 5: - tmpval = 5 - rs = RadioSetting("dtmf_auto_reset_time", - "Auto reset time (s)", - RadioSettingValueInteger(5, 60, tmpval)) - dtmf.append(rs) - - tmpval = int(_mem.dtmf_settings.preload_time) - if tmpval > 100 or tmpval < 3: - tmpval = 30 - tmpval *= 10 - rs = RadioSetting("dtmf_preload_time", - "Pre-load time (ms)", - RadioSettingValueInteger(30, 1000, tmpval, 10)) - dtmf.append(rs) - - tmpval = int(_mem.dtmf_settings.first_code_persist_time) - if tmpval > 100 or tmpval < 3: - tmpval = 30 - tmpval *= 10 - rs = RadioSetting("dtmf_first_code_persist_time", - "First code persist time (ms)", - RadioSettingValueInteger(30, 1000, tmpval, 10)) - dtmf.append(rs) - - tmpval = int(_mem.dtmf_settings.hash_persist_time) - if tmpval > 100 or tmpval < 3: - tmpval = 30 - tmpval *= 10 - rs = RadioSetting("dtmf_hash_persist_time", - "#/* persist time (ms)", - RadioSettingValueInteger(30, 1000, tmpval, 10)) - dtmf.append(rs) - - tmpval = int(_mem.dtmf_settings.code_persist_time) - if tmpval > 100 or tmpval < 3: - tmpval = 30 - tmpval *= 10 - rs = RadioSetting("dtmf_code_persist_time", - "Code persist time (ms)", - RadioSettingValueInteger(30, 1000, tmpval, 10)) - dtmf.append(rs) - - tmpval = int(_mem.dtmf_settings.code_interval_time) - if tmpval > 100 or tmpval < 3: - tmpval = 30 - tmpval *= 10 - rs = RadioSetting("dtmf_code_interval_time", - "Code interval time (ms)", - RadioSettingValueInteger(30, 1000, tmpval, 10)) - dtmf.append(rs) - - tmpval = bool(_mem.dtmf_settings.permit_remote_kill > 0) - rs = RadioSetting( - "dtmf_permit_remote_kill", - "Permit remote kill", - RadioSettingValueBoolean(tmpval)) - dtmf.append(rs) - - tmpval = str(_mem.dtmf_settings_numbers.dtmf_local_code).upper().strip( - "\x00\xff\x20") - for i in tmpval: - if i in DTMF_CHARS_ID: - continue - else: - tmpval = "103" - break - val = RadioSettingValueString(3, 3, tmpval) - val.set_charset(DTMF_CHARS_ID) - rs = RadioSetting("dtmf_dtmf_local_code", - "Local code (3 chars 0-9 ABCD)", val) - dtmf.append(rs) - - tmpval = str(_mem.dtmf_settings_numbers.dtmf_up_code).upper().strip( - "\x00\xff\x20") - for i in tmpval: - if i in DTMF_CHARS_UPDOWN or i == "": - continue - else: - tmpval = "123" - break - val = RadioSettingValueString(1, 16, tmpval) - val.set_charset(DTMF_CHARS_UPDOWN) - rs = RadioSetting("dtmf_dtmf_up_code", - "Up code (1-16 chars 0-9 ABCD*#)", val) - dtmf.append(rs) - - tmpval = str(_mem.dtmf_settings_numbers.dtmf_down_code).upper().strip( - "\x00\xff\x20") - for i in tmpval: - if i in DTMF_CHARS_UPDOWN: - continue - else: - tmpval = "456" - break - val = RadioSettingValueString(1, 16, tmpval) - val.set_charset(DTMF_CHARS_UPDOWN) - rs = RadioSetting("dtmf_dtmf_down_code", - "Down code (1-16 chars 0-9 ABCD*#)", val) - dtmf.append(rs) - - tmpval = str(_mem.dtmf_settings_numbers.kill_code).upper().strip( - "\x00\xff\x20") - for i in tmpval: - if i in DTMF_CHARS_KILL: - continue - else: - tmpval = "77777" - break - if not len(tmpval) == 5: - tmpval = "77777" - val = RadioSettingValueString(5, 5, tmpval) - val.set_charset(DTMF_CHARS_KILL) - rs = RadioSetting("dtmf_kill_code", - "Kill code (5 chars 0-9 ABCD)", val) - dtmf.append(rs) - - tmpval = str(_mem.dtmf_settings_numbers.revive_code).upper().strip( - "\x00\xff\x20") - for i in tmpval: - if i in DTMF_CHARS_KILL: - continue - else: - tmpval = "88888" - break - if not len(tmpval) == 5: - tmpval = "88888" - val = RadioSettingValueString(5, 5, tmpval) - val.set_charset(DTMF_CHARS_KILL) - rs = RadioSetting("dtmf_revive_code", - "Revive code (5 chars 0-9 ABCD)", val) - dtmf.append(rs) - - for i in range(1, 17): - varname = "DTMF_"+str(i) - varnumname = "DTMFNUM_"+str(i) - vardescr = "DTMF Contact "+str(i)+" name" - varinumdescr = "DTMF Contact "+str(i)+" number" - - cntn = str(_mem.dtmfcontact[i-1].name).strip("\x20\x00\xff") - cntnum = str(_mem.dtmfcontact[i-1].number).strip("\x20\x00\xff") - - val = RadioSettingValueString(0, 8, cntn) - rs = RadioSetting(varname, vardescr, val) - dtmfc.append(rs) - - val = RadioSettingValueString(0, 3, cntnum) - val.set_charset(DTMF_CHARS) - rs = RadioSetting(varnumname, varinumdescr, val) - dtmfc.append(rs) - rs.set_doc("DTMF Contacts are 3 codes (valid: 0-9 * # ABCD), " - "or an empty string") - - # scanlists - if _mem.scanlist_default == 1: - tmpsc = 2 - else: - tmpsc = 1 - rs = RadioSetting("scanlist_default", - "Default scanlist", - RadioSettingValueInteger(1, 2, tmpsc)) - scanl.append(rs) - - tmppr = bool((_mem.scanlist1_priority_scan & 1) > 0) - rs = RadioSetting( - "scanlist1_priority_scan", - "Scanlist 1 priority channel scan", - RadioSettingValueBoolean(tmppr)) - scanl.append(rs) - - tmpch = _mem.scanlist1_priority_ch1 + 1 - if tmpch > 200: - tmpch = 0 - rs = RadioSetting("scanlist1_priority_ch1", - "Scanlist 1 priority channel 1 (0 - off)", - RadioSettingValueInteger(0, 200, tmpch)) - scanl.append(rs) - - tmpch = _mem.scanlist1_priority_ch2 + 1 - if tmpch > 200: - tmpch = 0 - rs = RadioSetting("scanlist1_priority_ch2", - "Scanlist 1 priority channel 2 (0 - off)", - RadioSettingValueInteger(0, 200, tmpch)) - scanl.append(rs) - - tmppr = bool((_mem.scanlist2_priority_scan & 1) > 0) - rs = RadioSetting( - "scanlist2_priority_scan", - "Scanlist 2 priority channel scan", - RadioSettingValueBoolean(tmppr)) - scanl.append(rs) - - tmpch = _mem.scanlist2_priority_ch1 + 1 - if tmpch > 200: - tmpch = 0 - rs = RadioSetting("scanlist2_priority_ch1", - "Scanlist 2 priority channel 1 (0 - off)", - RadioSettingValueInteger(0, 200, tmpch)) - scanl.append(rs) - - tmpch = _mem.scanlist2_priority_ch2 + 1 - if tmpch > 200: - tmpch = 0 - rs = RadioSetting("scanlist2_priority_ch2", - "Scanlist 2 priority channel 2 (0 - off)", - RadioSettingValueInteger(0, 200, tmpch)) - scanl.append(rs) - - # basic settings - - # call channel - tmpc = _mem.call_channel+1 - if tmpc > 200: - tmpc = 1 - rs = RadioSetting("call_channel", "One key call channel", - RadioSettingValueInteger(1, 200, tmpc)) - basic.append(rs) - - # squelch - tmpsq = _mem.squelch - if tmpsq > 9: - tmpsq = 1 - rs = RadioSetting("squelch", "Squelch", - RadioSettingValueInteger(0, 9, tmpsq)) - basic.append(rs) - - # TOT - tmptot = _mem.max_talk_time - if tmptot > 10: - tmptot = 10 - rs = RadioSetting( - "tot", - "Max talk time [min]", - RadioSettingValueInteger(0, 10, tmptot)) - basic.append(rs) - - # NOAA autoscan - rs = RadioSetting( - "noaa_autoscan", - "NOAA Autoscan", RadioSettingValueBoolean( - bool(_mem.noaa_autoscan > 0))) - basic.append(rs) - - # VOX switch - rs = RadioSetting( - "vox_switch", - "VOX enabled", RadioSettingValueBoolean( - bool(_mem.vox_switch > 0))) - basic.append(rs) - - # VOX Level - tmpvox = _mem.vox_level+1 - if tmpvox > 10: - tmpvox = 10 - rs = RadioSetting("vox_level", "VOX Level", - RadioSettingValueInteger(1, 10, tmpvox)) - basic.append(rs) - - # Mic gain - tmpmicgain = _mem.mic_gain - if tmpmicgain > 4: - tmpmicgain = 4 - rs = RadioSetting("mic_gain", "Mic Gain", - RadioSettingValueInteger(0, 4, tmpmicgain)) - basic.append(rs) - - # Channel display mode - tmpchdispmode = _mem.channel_display_mode - if tmpchdispmode >= len(CHANNELDISP_LIST): - tmpchdispmode = 0 - rs = RadioSetting( - "channel_display_mode", - "Channel display mode", - RadioSettingValueList( - CHANNELDISP_LIST, - CHANNELDISP_LIST[tmpchdispmode])) - basic.append(rs) - - # Crossband receiving/transmitting - tmpcross = _mem.crossband - if tmpcross >= len(CROSSBAND_LIST): - tmpcross = 0 - rs = RadioSetting( - "crossband", - "Cross-band receiving/transmitting", - RadioSettingValueList( - CROSSBAND_LIST, - CROSSBAND_LIST[tmpcross])) - basic.append(rs) - - # Battery save - tmpbatsave = _mem.battery_save - if tmpbatsave >= len(BATSAVE_LIST): - tmpbatsave = BATSAVE_LIST.index("1:4") - rs = RadioSetting( - "battery_save", - "Battery Save", - RadioSettingValueList( - BATSAVE_LIST, - BATSAVE_LIST[tmpbatsave])) - basic.append(rs) - - # Dual watch - tmpdual = _mem.dual_watch - if tmpdual >= len(DUALWATCH_LIST): - tmpdual = 0 - rs = RadioSetting("dualwatch", "Dual Watch", RadioSettingValueList( - DUALWATCH_LIST, DUALWATCH_LIST[tmpdual])) - basic.append(rs) - - # Backlight auto mode - tmpback = _mem.backlight_auto_mode - if tmpback >= len(BACKLIGHT_LIST): - tmpback = 0 - rs = RadioSetting("backlight_auto_mode", - "Backlight auto mode", - RadioSettingValueList( - BACKLIGHT_LIST, - BACKLIGHT_LIST[tmpback])) - basic.append(rs) - - # Tail tone elimination - rs = RadioSetting( - "tail_note_elimination", - "Tail tone elimination", - RadioSettingValueBoolean( - bool(_mem.tail_note_elimination > 0))) - basic.append(rs) - - # VFO open - rs = RadioSetting("vfo_open", "VFO open", - RadioSettingValueBoolean(bool(_mem.vfo_open > 0))) - basic.append(rs) - - # Beep control - rs = RadioSetting( - "beep_control", - "Beep control", - RadioSettingValueBoolean(bool(_mem.beep_control > 0))) - basic.append(rs) - - # Scan resume mode - tmpscanres = _mem.scan_resume_mode - if tmpscanres >= len(SCANRESUME_LIST): - tmpscanres = 0 - rs = RadioSetting( - "scan_resume_mode", - "Scan resume mode", - RadioSettingValueList( - SCANRESUME_LIST, - SCANRESUME_LIST[tmpscanres])) - basic.append(rs) - - # Keypad locked - rs = RadioSetting( - "key_lock", - "Keypad lock", - RadioSettingValueBoolean(bool(_mem.key_lock > 0))) - basic.append(rs) - - # Auto keypad lock - rs = RadioSetting( - "auto_keypad_lock", - "Auto keypad lock", - RadioSettingValueBoolean(bool(_mem.auto_keypad_lock > 0))) - basic.append(rs) - - # Power on display mode - tmpdispmode = _mem.power_on_dispmode - if tmpdispmode >= len(WELCOME_LIST): - tmpdispmode = 0 - rs = RadioSetting( - "welcome_mode", - "Power on display mode", - RadioSettingValueList( - WELCOME_LIST, - WELCOME_LIST[tmpdispmode])) - basic.append(rs) - - # Keypad Tone - tmpkeypadtone = _mem.keypad_tone - if tmpkeypadtone >= len(KEYPADTONE_LIST): - tmpkeypadtone = 0 - rs = RadioSetting("keypad_tone", "Keypad tone", RadioSettingValueList( - KEYPADTONE_LIST, KEYPADTONE_LIST[tmpkeypadtone])) - basic.append(rs) - - # Language - tmplanguage = _mem.language - if tmplanguage >= len(LANGUAGE_LIST): - tmplanguage = 0 - rs = RadioSetting("language", "Language", RadioSettingValueList( - LANGUAGE_LIST, LANGUAGE_LIST[tmplanguage])) - basic.append(rs) - - # Alarm mode - tmpalarmmode = _mem.alarm_mode - if tmpalarmmode >= len(ALARMMODE_LIST): - tmpalarmmode = 0 - rs = RadioSetting("alarm_mode", "Alarm mode", RadioSettingValueList( - ALARMMODE_LIST, ALARMMODE_LIST[tmpalarmmode])) - basic.append(rs) - - # Reminding of end of talk - tmpalarmmode = _mem.reminding_of_end_talk - if tmpalarmmode >= len(REMENDOFTALK_LIST): - tmpalarmmode = 0 - rs = RadioSetting( - "reminding_of_end_talk", - "Reminding of end of talk", - RadioSettingValueList( - REMENDOFTALK_LIST, - REMENDOFTALK_LIST[tmpalarmmode])) - basic.append(rs) - - # Repeater tail tone elimination - tmprte = _mem.repeater_tail_elimination - if tmprte >= len(RTE_LIST): - tmprte = 0 - rs = RadioSetting( - "repeater_tail_elimination", - "Repeater tail tone elimination", - RadioSettingValueList(RTE_LIST, RTE_LIST[tmprte])) - basic.append(rs) - - # Logo string 1 - logo1 = str(_mem.logo_line1).strip("\x20\x00\xff") + "\x00" - logo1 = _getstring(logo1.encode('ascii', errors='ignore'), 0, 12) - rs = RadioSetting("logo1", _("Logo string 1 (12 characters)"), - RadioSettingValueString(0, 12, logo1)) - basic.append(rs) - - # Logo string 2 - logo2 = str(_mem.logo_line2).strip("\x20\x00\xff") + "\x00" - logo2 = _getstring(logo2.encode('ascii', errors='ignore'), 0, 12) - rs = RadioSetting("logo2", _("Logo string 2 (12 characters)"), - RadioSettingValueString(0, 12, logo2)) - basic.append(rs) - - # FM radio - for i in range(1, 21): - freqname = "FM_"+str(i) - fmfreq = _mem.fmfreq[i-1]/10.0 - if fmfreq < FMMIN or fmfreq > FMMAX: - rs = RadioSetting(freqname, freqname, - RadioSettingValueString(0, 5, "")) - else: - rs = RadioSetting(freqname, freqname, - RadioSettingValueString(0, 5, str(fmfreq))) - - fmradio.append(rs) - - # unlock settings - - # F-LOCK - tmpflock = _mem.lock.flock - if tmpflock >= len(FLOCK_LIST): - tmpflock = 0 - rs = RadioSetting( - "flock", "F-LOCK", - RadioSettingValueList(FLOCK_LIST, FLOCK_LIST[tmpflock])) - unlock.append(rs) - - # 350TX - rs = RadioSetting("tx350", "350TX - unlock 350-400 MHz TX", - RadioSettingValueBoolean( - bool(_mem.lock.tx350 > 0))) - unlock.append(rs) - - # Killed - rs = RadioSetting("Killed", "KILLED Device was disabled (via DTMF)", - RadioSettingValueBoolean( - bool(_mem.lock.killed > 0))) - unlock.append(rs) - - # 200TX - rs = RadioSetting("tx200", "200TX - unlock 174-350 MHz TX", - RadioSettingValueBoolean( - bool(_mem.lock.tx200 > 0))) - unlock.append(rs) - - # 500TX - rs = RadioSetting("tx500", "500TX - unlock 500-600 MHz TX", - RadioSettingValueBoolean( - bool(_mem.lock.tx500 > 0))) - unlock.append(rs) - - # 350EN - rs = RadioSetting("en350", "350EN - unlock 350-400 MHz RX", - RadioSettingValueBoolean( - bool(_mem.lock.en350 > 0))) - unlock.append(rs) - - # SCREEN - rs = RadioSetting("scrambler", "SCREN - scrambler enable", - RadioSettingValueBoolean( - bool(_mem.lock.enscramble > 0))) - unlock.append(rs) - - # readonly info - # Firmware - firmware = self.metadata.get('uvk5_firmware', 'UNKNOWN') - - val = RadioSettingValueString(0, 128, firmware) - val.set_mutable(False) - rs = RadioSetting("fw_ver", "Firmware Version", val) - roinfo.append(rs) - - # No limits version for hacked firmware - val = RadioSettingValueBoolean(self._expanded_limits) - rs = RadioSetting("nolimits", "Limits disabled for modified firmware", - val) - rs.set_warning(_( - 'This should only be enabled if you are using modified firmware ' - 'that supports wider frequency coverage. Enabling this will cause ' - 'CHIRP not to enforce OEM restrictions and may lead to undefined ' - 'or unregulated behavior. Use at your own risk!'), - safe_value=False) - roinfo.append(rs) - - return top - - def _set_mem_mode(self, _mem, mode): - if mode == "NFM": - _mem.bandwidth = 1 - _mem.enable_am = 0 - elif mode == "FM": - _mem.bandwidth = 0 - _mem.enable_am = 0 - elif mode == "NAM": - _mem.bandwidth = 1 - _mem.enable_am = 1 - elif mode == "AM": - _mem.bandwidth = 0 - _mem.enable_am = 1 - - # Store details about a high-level memory to the memory map - # This is called when a user edits a memory in the UI - def set_memory(self, mem): - number = mem.number-1 - - # Get a low-level memory object mapped to the image - _mem = self._memobj.channel[number] - _mem4 = self._memobj - # empty memory - if mem.empty: - _mem.set_raw(b"\xFF" * 16) - if number < 200: - _mem2 = self._memobj.channelname[number] - _mem2.set_raw(b"\xFF" * 16) - _mem4.channel_attributes[number].is_scanlist1 = 0 - _mem4.channel_attributes[number].is_scanlist2 = 0 - # Compander in other models, not supported here - _mem4.channel_attributes[number].compander = 0 - _mem4.channel_attributes[number].is_free = 1 - _mem4.channel_attributes[number].band = 0x7 - return mem - - # clean the channel memory, restore some bits if it was used before - if _mem.get_raw(asbytes=False)[0] == "\xff": - # this was an empty memory - _mem.set_raw(b"\x00" * 16) - else: - # this memory wasn't empty, save some bits that we don't know the - # meaning of, or that we don't support yet - prev_0a = _mem.get_raw()[0x0a] & SAVE_MASK_0A - prev_0b = _mem.get_raw()[0x0b] & SAVE_MASK_0B - prev_0c = _mem.get_raw()[0x0c] & SAVE_MASK_0C - prev_0d = _mem.get_raw()[0x0d] & SAVE_MASK_0D - prev_0e = _mem.get_raw()[0x0e] & SAVE_MASK_0E - prev_0f = _mem.get_raw()[0x0f] & SAVE_MASK_0F - _mem.set_raw(b"\x00" * 10 + - bytes([prev_0a, prev_0b, prev_0c, - prev_0d, prev_0e, prev_0f])) - - if number < 200: - _mem4.channel_attributes[number].is_scanlist1 = 0 - _mem4.channel_attributes[number].is_scanlist2 = 0 - _mem4.channel_attributes[number].compander = 0 - _mem4.channel_attributes[number].is_free = 1 - _mem4.channel_attributes[number].band = 0x7 - - # find band - band = _find_band(self, mem.freq) - - self._set_mem_mode(_mem, mem.mode) - - # frequency/offset - _mem.freq = mem.freq/10 - _mem.offset = mem.offset/10 - - if mem.duplex == "": - _mem.offset = 0 - _mem.shift = 0 - elif mem.duplex == '-': - _mem.shift = FLAGS1_OFFSET_MINUS - elif mem.duplex == '+': - _mem.shift = FLAGS1_OFFSET_PLUS - elif mem.duplex == 'off': - # we fake tx disable by setting the tx freq to 0 MHz - _mem.shift = FLAGS1_OFFSET_MINUS - _mem.offset = _mem.freq - - # set band - if number < 200: - _mem4.channel_attributes[number].is_free = 0 - _mem4.channel_attributes[number].band = band - - # channels >200 are the 14 VFO chanells and don't have names - if number < 200: - _mem2 = self._memobj.channelname[number] - tag = mem.name.ljust(10) + "\x00"*6 - _mem2.name = tag # Store the alpha tag - - # tone data - self._set_tone(mem, _mem) - - # step - _mem.step = self._steps.index(mem.tuning_step) - - # tx power - if str(mem.power) == str(UVK5_POWER_LEVELS[2]): - _mem.txpower = POWER_HIGH - elif str(mem.power) == str(UVK5_POWER_LEVELS[1]): - _mem.txpower = POWER_MEDIUM - else: - _mem.txpower = POWER_LOW - - for setting in mem.extra: - sname = setting.get_name() - svalue = setting.value.get_value() - - if sname == "bclo": - _mem.bclo = svalue and 1 or 0 - - if sname == "pttid": - _mem.dtmf_pttid = self._pttid_list.index(svalue) - - if sname == "frev": - _mem.freq_reverse = svalue and 1 or 0 - - if sname == "dtmfdecode": - _mem.dtmf_decode = svalue and 1 or 0 - - if sname == "scrambler": - _mem.scrambler = ( - _mem.scrambler & 0xf0) | SCRAMBLER_LIST.index(svalue) - - if number < 200 and sname == "scanlists": - if svalue == "1": - _mem4.channel_attributes[number].is_scanlist1 = 1 - _mem4.channel_attributes[number].is_scanlist2 = 0 - elif svalue == "2": - _mem4.channel_attributes[number].is_scanlist1 = 0 - _mem4.channel_attributes[number].is_scanlist2 = 1 - elif svalue == "1+2": - _mem4.channel_attributes[number].is_scanlist1 = 1 - _mem4.channel_attributes[number].is_scanlist2 = 1 - else: - _mem4.channel_attributes[number].is_scanlist1 = 0 - _mem4.channel_attributes[number].is_scanlist2 = 0 - - return mem - - -@directory.register -class UVK5Radio(UVK5RadioBase): - @classmethod - def k5_approve_firmware(cls, firmware): - approved_prefixes = ('k5_2.01.', 'app_2.01.', '2.01.', - '1o11', '4.00.', 'k5_4.00.') - return any(firmware.startswith(x) for x in approved_prefixes) - - @classmethod - def detect_from_serial(cls, pipe): - firmware = _sayhello(pipe) - for rclass in cls.detected_models(): - if rclass.k5_approve_firmware(firmware): - return rclass - raise errors.RadioError('Firmware %r not supported' % firmware) - - -@directory.register -class RA79Radio(UVK5Radio): - """Retevis RA79""" - VENDOR = "Retevis" - MODEL = "RA79"