From 55f75a6b16136600f9e60a6093914f9a7754e784 Mon Sep 17 00:00:00 2001 From: Kai Lauterbach Date: Fri, 12 Apr 2024 12:15:35 +0200 Subject: [PATCH] Initial commit of the driver - copy if the IJL firmware https://www.universirius.com/preppers/quansheng-uv-k5-manuale-del-firmware-ijv_2-9r5/#Firmware-IJV --- uvk5_CEC_05_HF_DO4KLA.py | 2355 ++++++++++++++++++++++++++++++++++++++ 1 file changed, 2355 insertions(+) create mode 100755 uvk5_CEC_05_HF_DO4KLA.py diff --git a/uvk5_CEC_05_HF_DO4KLA.py b/uvk5_CEC_05_HF_DO4KLA.py new file mode 100755 index 0000000..627bf4a --- /dev/null +++ b/uvk5_CEC_05_HF_DO4KLA.py @@ -0,0 +1,2355 @@ +# 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 . +# +# Adapted partially to IJV firmware v2.9 by Julian Lilov (LZ1JDL) +# http://www.hamskey.com/2024/04/introduction-to-uv-k5-hf-05-fullband.html#more +# +# Adapted full to IJV by Francesco IK8JHL +# FIX: QRA , Beacon/CQ CAll Message, Selettive , TX Enable, PTTID, Squelch A/B, Band TX, Band A/B TX , Singol Band enable, Satcom , Upconverter etc etc +# eliminatte funzioni non attive nel FW IJV +# +# Adapted partially to CEC firmware v0.5 by KD8CEC +# https://www.universirius.com/en_gb/preppers/quansheng-uv-k5-manuale-del-firmware-ijv/#Firmware-IJV +# +#JH11i + + + +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 unknown1:2, + txcodeflag:2, + unknown2:2, + rxcodeflag:2; + + //u8 flags1; + u8 flags1_unknown7:1, + flags1_unknown6:1, + enable_extramodes:1, + enable_am:1, + flags1_unknown3:1, + is_in_scanlist:1, + shift:2; + + //u8 flags2; + u8 flags2_unknown7:1, + flags2_unknown6:1, + bclo:1, + txpower:2, + bandwidth:2, + freq_reverse:1; + + //u8 dtmf_flags; + u8 dtmf_flags_unknown7:1, + dtmf_flags_unknown6:1, + dtmf_pttid:5, + dtmf_decode:1; + + + u8 step; + u8 scrambler; +} channel[200]; + +#seekto 0xd60; +struct { +u8 is_scanlist1:1, +is_scanlist2:1, +unknown1:1, +unknown2:1, +is_free:1, +band:3; +} channel_attributes[200]; + +#seekto 0xd7e; +u8 compander; + +#seekto 0xe40; +ul16 fmfreq[20]; + +#seekto 0xe70; +u8 call_channel; +u8 squelch_a; +u8 max_talk_time; +u8 squelch_b; +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 0xea0; +char qrz_label[8]; + +#seekto 0xea8; +u8 alarm_mode; +u8 reminding_of_end_talk; +u8 repeater_tail_elimination; + +#seekto 0xeab; +u8 bands_tx; + +#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[8]; +char kill_code[5]; +char unused1[3]; +char revive_code[5]; +char unused2[3]; +char dtmf_up_code[8]; +char unused3[8]; +char dtmf_down_code[8]; +} 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; +} lock; + +#seekto 0xf46; +u8 beacon; + +#seekto 0xf47; +u8 micbar; + +#seekto 0xf47; +u8 bl_mode; + +#seekto 0xf47; +u8 tx_enable; + +#seekto 0xf4f; +u8 single_vfo; + +#seekto 0xf4f; +u8 signal_meter; + +#seekto 0xf4f; +u8 satcom; + +#seekto 0xf4f; +u8 upconv; + + + + + +#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 + +# flags2 + +POWER_HIGH = 0b10 +POWER_MEDIUM = 0b01 +POWER_LOW = 0b00 + +BANDWIDTH_WIDE = 0b00 +BANDWIDTH_WIDE_PLUS = 0b11 +BANDWIDTH_NARROW = 0b01 +BANDWIDTH_NARROW_MINUS = 0b10 +# bandwidth +BANDWIDTH_LIST = ["W", "N", "N-", "W+"] + +# dtmf_flags +PTTID_LIST = ["off", "DTMF CALL ID", "DTMF BEGIN", "DTMF END", "DTMF BEG+END", "ZVEI1 BEGIN", "ZVEI1 END", "ZVEI1 BEG+END", "ZVEI2 BEGIN", "ZVEI2 END", "ZVEI2 BEG+END", "CCIR-1F BEGIN", "CCIR-1F END", "CCIR-1F BEG+END", "CCIR-1 BEGIN", "CCIR-1 END", "CCIR-1 BEG+END" ] + +# power +UVK5_POWER_LEVELS = [chirp_common.PowerLevel("Low", watts=1.00), + chirp_common.PowerLevel("Med", watts=2.50), + chirp_common.PowerLevel("High", watts=5.00) + ] + +# scrambler +SCRAMBLER_LIST = ["Off", "2600Hz", "2700Hz", "2800Hz", "2900Hz", "3000Hz", "3100Hz", "3200Hz", "3300Hz", "3400Hz", "3500Hz"] + +# channel display mode +CHANNELDISP_LIST = ["Frequency", "Channel No", "Channel Name", "Name_S Freq_L", "Name_L Freq_S"] + +# Beacon +BEACON_LIST = ["Off","5 Sec","10 Sec","30 Sec","5 min","10 min","20 min"] + + +# battery save +BATSAVE_LIST = ["Off", "50%", "67%", "75%", "80%"] + +# compander +COMPANDER_LIST = ["Off", "TX", "RX", "RX/TX"] + +# mic gain +MICGAIN_LIST = ["+1.1dB","+4.0dB","+8.0dB","+12.0dB","+15.1dB"] + + +# Talk Time +TALKTIME_LIST = ["Off","30s","1min","3min","5min"] + +# Backlight auto mode +BACKLIGHT_LIST = ["Off", "5s", "10s", "20s", "1min", "3min", "RX/TX", "ON", "NIGHT"] + +# Crossband receiving/transmitting +CROSSBAND_LIST = ["Same VFO", "VFO A", "VFO B"] +DUALWATCH_LIST = ["Off", "On"] +BANDS_TX_LIST = ["A","B"] + +# steps +STEPS = [0.02, 0.05, 0.10, 0.50, 1.00, 2.50, 5.00, 6.25, 8.33, 9.00, 10.00, 12.50, 25.00, 100.00, 1000.00] + +# 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 = ["TIME: Resume after 5 seconds", + "CARRIER: Resume after signal disappears", + "SEARCH: Stop scanning after receiving a signal", + "LOG"] + +WELCOME_LIST = ["None", "FW Mod", "Message"] +KEYPADTONE_LIST = ["Off", "Chinese", "English"] +LANGUAGE_LIST = ["Chinese", "English"] +ALARMMODE_LIST = ["SITE", "TONE"] +REMENDOFTALK_LIST = ["Off", "Single", "Roger", "MDC 1200", "Apollo Quindar", "Digital Code ID"] +RTE_LIST = ["Off", + "1*100ms", "2*100ms", "3*100ms", "4*100ms", "5*100ms", + "6*100ms", "7*100ms", "8*100ms", "9*100ms", "10*100ms", + "11*100ms", "12*100ms", "13*100ms", "14*100ms", "15*100ms", + "16*100ms", "17*100ms", "18*100ms", "19*100ms", "20*100ms"] + +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: [15.0, 107.9999], + 1: [108.0, 135.9999], + 2: [136.0, 173.9990], + 3: [174.0, 349.9999], + 4: [350.0, 399.9999], + 5: [400.0, 469.9999], + 6: [470.0, 1299.9999] + } + +# for radios with modified firmware: +BANDS_NOLIMITS = { + 0: [15.0, 107.9999], + 1: [108.0, 135.9999], + 2: [136.0, 173.9990], + 3: [174.0, 349.9999], + 4: [350.0, 399.9999], + 5: [400.0, 469.9999], + 6: [470.0, 1299.9999] + } + +SPECIALS = { + # "VFO A1(15-108)": 200, + # "VFO B1(15-108)": 201, + # "VFO A2(108-136)": 202, + # "VFO B2(108-136)": 203, + # "VFO A3(136-174)": 204, + # "VFO B3(136-174)": 205, + # "VFO A4(174-350)": 206, + # "VFO B4(174-350)": 207, + # "VFO A5(350-400)": 208, + # "VFO B5(350-400)": 209, + # "VFO A6(400-470)": 210, + # "VFO B6(400-470)": 211, + # "VFO A7(470-1300)": 212, + # "VFO B7(470-1300)": 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", "TX Power", + "Monitor", "Scan on/off", "VOX on/off", + "FM radio on/off", "VFO Change", "VFO Swap", + "SQL +", "SQL -", "REGA Test", "REGA Alarm", "CW Call CQ"] + +UPCONV_LIST = ["Off","50", "125"] + +# the communication is obfuscated using this fine mechanism +def xorarr(data: bytes): + tbl = [22, 108, 20, 230, 46, 145, 13, 64, 33, 53, 213, 64, 19, 3, 233, 128] + x = b"" + r = 0 + for byte in data: + x += bytes([byte ^ tbl[r]]) + r = (r+1) % len(tbl) + return x + + +# 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 +def calculate_crc16_xmodem(data: bytes): + poly = 0x1021 + crc = 0x0 + for byte in data: + crc = crc ^ (byte << 8) + for i 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: + raise errors.RadioError("Error writing data to radio") + return result + + +def _receive_reply(serport): + header = serport.read(4) + if len(header) != 4: + LOG.warning("Header short read: [%s] len=%i" % + (util.hexprint(header), len(header))) + raise errors.RadioError("Header short read") + if 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)) + s = [data[i] for i in range(begin, tmplen)] + for key, val in enumerate(s): + if val < ord(' ') or val > ord('~'): + break + return ''.join(chr(x) for x in s[0:key]) + + +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) + o = _receive_reply(serport) + if (o): + break + tries -= 1 + if tries == 0: + LOG.warning("Failed to initialise radio") + raise errors.RadioError("Failed to initialize radio") + firmware = _getstring(o, 4, 16) + 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 + else: + 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): + 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 f: + radio.FIRMWARE_VERSION = f + else: + raise errors.RadioError('Unable to determine firmware version') + + addr = 0 + while addr < MEM_SIZE: + o = _readmem(serport, addr, MEM_BLOCK) + status.cur = addr + radio.status_fn(status) + + if o and len(o) == MEM_BLOCK: + eeprom += o + addr += MEM_BLOCK + else: + raise errors.RadioError("Memory download incomplete") + + return memmap.MemoryMapBytes(eeprom) + + +def do_upload(radio): + serport = radio.pipe + serport.timeout = 0.5 + status = chirp_common.Status() + status.cur = 0 + status.max = PROG_SIZE + status.msg = "Uploading to radio" + radio.status_fn(status) + + f = _sayhello(serport) + if f: + radio.FIRMWARE_VERSION = f + else: + return False + + addr = 0 + while addr < PROG_SIZE: + o = radio.get_mmap()[addr:addr+MEM_BLOCK] + _writemem(serport, o, addr) + status.cur = addr + radio.status_fn(status) + if o: + 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 + + +@directory.register +class UVK5Radio(chirp_common.CloneModeRadio): + """Quansheng UV-K5""" + VENDOR = "Quansheng" + MODEL = "UV-K5-CEC" + BAUD_RATE = 38400 + NEEDS_COMPAT_SERIAL = False + FIRMWARE_VERSION = "" + _expanded_limits = True + + 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 = [0.01, 0.1, 1.0] + STEPS + rf.valid_tuning_steps = 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", "AM", "USB", "CW"] + + 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) + + # Convert the raw byte array into a memory object structure + def process_mmap(self): + 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 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 = _find_band(self._expanded_limits, 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 = _find_band(self._expanded_limits, 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.unknown1 = 0 + _mem.unknown2 = 0 + _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)) + + # 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 = SPECIALS[number2] + mem.extd_number = number2 + else: + number = number2 - 1 + + mem.number = number + 1 + + _mem = self._memobj.channel[number] + + tmpcomment = "" + + is_empty = False + # We'll consider any blank (i.e. 0 MHz frequency) to be empty + if (_mem.freq == 0xffffffff) or (_mem.freq == 0): + is_empty = True + + tmpscn = SCANLIST_LIST[0] + + # We'll also look at the channel attributes if a memory has them + if number < 200: + _mem3 = self._memobj.channel_attributes[number] + # free memory bit + if _mem3.is_free > 0: + is_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" + + if is_empty: + mem.empty = True + # set some sane defaults: + mem.power = UVK5_POWER_LEVELS[2] + mem.extra = RadioSettingGroup("Extra", "extra") + + rs = RadioSetting( + "bandwidth", "Bandwidth", + RadioSettingValueList(BANDWIDTH_LIST, BANDWIDTH_LIST[0])) + mem.extra.append(rs) + + rs = RadioSetting( + "bclo", "BCLO", + RadioSettingValueBoolean(False)) + mem.extra.append(rs) + rs = RadioSetting( + "frev", "FreqRev", + RadioSettingValueBoolean(False)) + mem.extra.append(rs) + rs = RadioSetting( + "pttid", "PTTID", + RadioSettingValueList(PTTID_LIST, PTTID_LIST[0])) + mem.extra.append(rs) + rs = RadioSetting( + "dtmfdecode", _("DTMF decode"), + RadioSettingValueBoolean(False)) + mem.extra.append(rs) + rs = RadioSetting( + "scrambler", _("Scrambler"), + RadioSettingValueList(SCRAMBLER_LIST, SCRAMBLER_LIST[0])) + mem.extra.append(rs) + + rs = RadioSetting( + "scanlists", _("Scanlists"), + RadioSettingValueList(SCANLIST_LIST, SCANLIST_LIST[0])) + mem.extra.append(rs) + + # actually the step and duplex are overwritten by chirp based on + # bandplan. they are here to document sane defaults for IARU r1 + # mem.tuning_step = 25.0 + # mem.duplex = "" + + 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) + + # mode + if _mem.enable_extramodes > 0: + if _mem.enable_am > 0: + mem.mode = "CW" + else: + mem.mode = "USB" + else: + if _mem.enable_am > 0: + mem.mode = "AM" + else: + mem.mode = "FM" + + + # tuning step + tstep = _mem.step & 0xF + if tstep < len(STEPS): + mem.tuning_step = STEPS[tstep] + else: + mem.tuning_step = 0.02 + + # 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 + + mem.extra = RadioSettingGroup("Extra", "extra") + + # bandwidth + bwidth = _mem.bandwidth + rs = RadioSetting("bandwidth", "Bandwidth", RadioSettingValueList( + BANDWIDTH_LIST, BANDWIDTH_LIST[bwidth])) + mem.extra.append(rs) + tmpcomment += "bandwidth:"+BANDWIDTH_LIST[bwidth]+" " + + # BCLO + is_bclo = bool(_mem.bclo > 0) + rs = RadioSetting("bclo", "BCLO", RadioSettingValueBoolean(is_bclo)) + mem.extra.append(rs) + tmpcomment += "BCLO:"+(is_bclo and "ON" or "off")+" " + + # Frequency reverse - whatever that means, don't see it in the manual + is_frev = bool(_mem.freq_reverse > 0) + rs = RadioSetting("frev", "FreqRev", RadioSettingValueBoolean(is_frev)) + mem.extra.append(rs) + tmpcomment += "FreqReverse:"+(is_frev and "ON" or "off")+" " + + # PTTID + pttid = _mem.dtmf_pttid + rs = RadioSetting("pttid", "PTTID", RadioSettingValueList( + PTTID_LIST, PTTID_LIST[pttid])) + mem.extra.append(rs) + tmpcomment += "PTTid:"+PTTID_LIST[pttid]+" " + + # DTMF DECODE + is_dtmf = bool(_mem.dtmf_decode > 0) + rs = RadioSetting("dtmfdecode", _("DTMF decode"), + RadioSettingValueBoolean(is_dtmf)) + mem.extra.append(rs) + tmpcomment += "DTMFdecode:"+(is_dtmf and "ON" or "off")+" " + + # 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) + tmpcomment += "Scrambler:"+SCRAMBLER_LIST[enc]+" " + + rs = RadioSetting("scanlists", _("Scanlists"), RadioSettingValueList( + SCANLIST_LIST, tmpscn)) + mem.extra.append(rs) + + return mem + + def set_settings(self, settings): + _mem = self._memobj + + s1 = False + s2 = False + s3 = False + + sv1 = False + sv2 = False + sv3 = False + sv4 = False + + 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_a": + _mem.squelch_a = int(element.value) + + if element.get_name() == "squelch_b": + _mem.squelch_b = int(element.value) + + # TOT + if element.get_name() == "max_talk_time": + _mem.max_talk_time = TALKTIME_LIST.index(str(element.value)) + + # Beacon + if element.get_name() == "beacon": + _mem.beacon = BEACON_LIST.index(str(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 = MICGAIN_LIST.index(str(element.value)) +############################################### 0xF47 ##################################################################### + # MicBar + if element.get_name() == "micbar": + tmp_micbar = element.value and 0x10 or 0x00 #tmp_micbar = element.value and 0x10 or 0x00 + s1 = True + + if element.get_name() == "bl_mode": + tmp_bl_mode = element.value and 0x20 or 0x00 #tmp_bl_mode = element.value and 0x20 or 0x00 + s2 = True + + if element.get_name() == "tx_enable": + tmp_tx_enable = element.value and 0x01 or 0x00 + s3 = True + #tmp_tx_enable = 0x01 + + if (s1 and s2)and s3: + _mem.micbar = tmp_micbar | tmp_bl_mode | tmp_tx_enable + _mem.bl_mode = tmp_micbar | tmp_bl_mode | tmp_tx_enable + _mem.tx_enable = tmp_micbar |tmp_bl_mode | tmp_tx_enable + +######################################### 0xF4F ########################################################################### + # Single vfo BIT 1 + if element.get_name() == "single_vfo": + tmp_single_vfo = element.value and 0x02 or 0x00 + sv1 = True + + # Signal Meter BIT 2 + if element.get_name() == "signal_meter": + tmp_signal_meter = element.value and 0x04 or 0x00 #_mem.signal_meter = element.value and 0x04 or 0x00 + sv2 = True + + # Satcom BIT 3 + if element.get_name() == "satcom": + tmp_satcom = element.value and 0x08 or 0x00 + sv3 = True + + # Upconv BIT 4 e 5 + if element.get_name() == "upconv": + tmp_upconv = (UPCONV_LIST.index(str(element.value)) << 4) #il risultato viene spostat di 4 posizioni a sinistra + sv4 = True + + + ### Sommo in OR tutti BIT + if ((sv1 and sv2) and sv3 ) and sv4: + _mem.single_vfo = tmp_single_vfo | tmp_signal_meter | tmp_satcom | tmp_upconv + _mem.signal_meter = tmp_single_vfo | tmp_signal_meter | tmp_satcom | tmp_upconv + _mem.satcom = tmp_single_vfo | tmp_signal_meter | tmp_satcom | tmp_upconv + _mem.upconv = tmp_single_vfo | tmp_signal_meter | tmp_satcom | tmp_upconv + +#################################################################################################################### + # 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)) + + # Compander + if element.get_name() == "compander": + _mem.compander = (COMPANDER_LIST.index(str(element.value)) << 4) | 0x03 + + # Dual Watch + if element.get_name() == "dualwatch": + _mem.dual_watch = DUALWATCH_LIST.index(str(element.value)) + + # Band TX + if element.get_name() == "bands_tx": + _mem.bands_tx = BANDS_TX_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"*15 + _mem.logo_line1 = b[0:15]+"\x00" + + # Logo string 2 + if element.get_name() == "logo2": + b = str(element.value).rstrip("\x20\xff\x00")+"\x00"*15 + _mem.logo_line2 = b[0:15]+"\x00" + + # QRZ label + if element.get_name() == "qrz_label": + b = str(element.value).rstrip("\x20\xff\x00")+"\x00"*8 + _mem.qrz_label = b[0:8] + + # 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"*8 + _mem.dtmf_settings_numbers.dtmf_local_code = k[0:8] + + if element.get_name() == "dtmf_dtmf_up_code": + k = str(element.value).strip("\x20\xff\x00") + "\x00"*8 + _mem.dtmf_settings_numbers.dtmf_up_code = k[0:8] + + if element.get_name() == "dtmf_dtmf_down_code": + k = str(element.value).rstrip("\x20\xff\x00") + "\x00"*8 + _mem.dtmf_settings_numbers.dtmf_down_code = k[0:8] + + # 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/Selcall 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/SELCALL 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(1, 8, tmpval) + val.set_charset(DTMF_CHARS_ID) + rs = RadioSetting("dtmf_dtmf_local_code", + "Own ID (1-8 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, 8, tmpval) + val.set_charset(DTMF_CHARS_UPDOWN) + rs = RadioSetting("dtmf_dtmf_up_code", + "Up code (1-8 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, 8, tmpval) + val.set_charset(DTMF_CHARS_UPDOWN) + rs = RadioSetting("dtmf_dtmf_down_code", + "Down code (1-8 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) + + val = RadioSettingValueString(0, 80, + "All DTMF Contacts are 3 codes " + "(valid: 0-9 * # ABCD), " + "or an empty string") + val.set_mutable(False) + rs = RadioSetting("dtmf_descr1", "DTMF Contacts", val) + dtmfc.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) + + # 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 + + # squelch + tmpsq = _mem.squelch_a + if tmpsq > 9: + tmpsq = 1 + rs = RadioSetting("squelch_a", "Squelch A", + RadioSettingValueInteger(0, 9, tmpsq)) + basic.append(rs) + + tmpsq = _mem.squelch_b + if tmpsq > 9: + tmpsq = 1 + rs = RadioSetting("squelch_b", "Squelch B", + RadioSettingValueInteger(0, 9, tmpsq)) + basic.append(rs) + + # TOT + tmptot = _mem.max_talk_time + if tmptot >= len(TALKTIME_LIST): + tmptot = TALKTIME_LIST.index("2min") + rs = RadioSetting( + "max_talk_time", + "Max talk time (Tx TOT)", + RadioSettingValueList( + TALKTIME_LIST, + TALKTIME_LIST[tmptot])) + 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) + + # Backlight auto mode + tmpback = _mem.backlight_auto_mode + if tmpback >= len(BACKLIGHT_LIST): + tmpback = 0 + rs = RadioSetting("backlight_auto_mode", + "BackLightTtime", + RadioSettingValueList( + BACKLIGHT_LIST, + BACKLIGHT_LIST[tmpback])) + basic.append(rs) + + # BLMode + rs = RadioSetting( + "bl_mode", + "BLmode (TX/RX)", RadioSettingValueBoolean(bool((_mem.bl_mode & 0x20) > 0))) + basic.append(rs) + + # TX Enable + rs = RadioSetting( + "tx_enable", + "TX Enable", RadioSettingValueBoolean(bool((_mem.tx_enable & 0x01) > 0))) + unlock.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 (Sc REV)", + 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) + + # Tail tone elimination + rs = RadioSetting( + "tail_note_elimination", + "Tail tone elimination", + RadioSettingValueBoolean( + bool(_mem.tail_note_elimination > 0))) + 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) + + # Mic gain + tmpmicgain = _mem.mic_gain + if tmpmicgain >= len(MICGAIN_LIST): + tmpmicgain = MICGAIN_LIST.index("+12.0dB") + rs = RadioSetting( + "mic_gain", + "Mic Gain", + RadioSettingValueList( + MICGAIN_LIST, + MICGAIN_LIST[tmpmicgain])) + basic.append(rs) +####################################### 0xF47 ############################################################ + # MicBar + rs = RadioSetting( + "micbar", + "MicBar", RadioSettingValueBoolean(bool((_mem.micbar & 0x10) > 0))) + basic.append(rs) + + # Compander + tmpcompander = _mem.compander >> 4 + if tmpcompander >= len(COMPANDER_LIST): + tmpcompander = COMPANDER_LIST.index("Off") + rs = RadioSetting( + "compander", + "Compander", + RadioSettingValueList( + COMPANDER_LIST, + COMPANDER_LIST[tmpcompander])) + 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) + + # 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) + + # 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 (DigSRV)", + RadioSettingValueList( + REMENDOFTALK_LIST, + REMENDOFTALK_LIST[tmpalarmmode])) + basic.append(rs) + + # Beacon + tmpbeacon = _mem.beacon + if tmpbeacon >= len(BEACON_LIST): + tmpbeacon = BEACON_LIST.index("Off") + rs = RadioSetting( + "beacon", + "Beacon/CQ Call", + RadioSettingValueList( + BEACON_LIST, + BEACON_LIST[tmpbeacon])) + basic.append(rs) + + + # Battery save + tmpbatsave = _mem.battery_save + if tmpbatsave >= len(BATSAVE_LIST): + tmpbatsave = BATSAVE_LIST.index("80%") + rs = RadioSetting( + "battery_save", + "Battery Save", + RadioSettingValueList( + BATSAVE_LIST, + BATSAVE_LIST[tmpbatsave])) + basic.append(rs) + +################################################# 0xF4F ########################################################################## + # Single VFO BIT 1 + rs = RadioSetting( + "single_vfo", + "Single VFO", RadioSettingValueBoolean(bool((_mem.single_vfo & 0x02) > 0))) + basic.append(rs) + + # RSSI / S Meter BIT 2 + rs = RadioSetting( + "signal_meter", + "SMeter (instead of RSSI)", RadioSettingValueBoolean(bool((_mem.signal_meter & 0x04) > 0))) + basic.append(rs) + + # Satcom BIT 3 + rs = RadioSetting( + "satcom", + "SATCOM", RadioSettingValueBoolean(bool((_mem.satcom & 0x08) > 0))) + basic.append(rs) + + # Upconv BIT 4 e 5 + tmpupconv = _mem.upconv >> 4 # sposta il bit di 4 posizioni a destra + if tmpupconv >= len(UPCONV_LIST): + tmpupconv = UPCONV_LIST.index("Off") + rs = RadioSetting( + "upconv", + "UP Converter", RadioSettingValueList( + UPCONV_LIST, + UPCONV_LIST[tmpupconv])) + basic.append(rs) + +#################################################################################################################### + + # Crossband receiving/transmitting + tmpcross = _mem.crossband + if tmpcross >= len(CROSSBAND_LIST): + tmpcross = 0 + rs = RadioSetting( + "crossband", + "Cross-band receiving/transmitting (Tx VFO)", + RadioSettingValueList( + CROSSBAND_LIST, + CROSSBAND_LIST[tmpcross])) + basic.append(rs) + + # Dual watch + tmpdual = _mem.dual_watch + if tmpdual >= len(DUALWATCH_LIST): + tmpdual = 0 + rs = RadioSetting("dualwatch", "Dual Watch (DualRX)", RadioSettingValueList( + DUALWATCH_LIST, DUALWATCH_LIST[tmpdual])) + basic.append(rs) + + # Band TX + tmpbandstx = _mem.bands_tx + if tmpbandstx >= len(BANDS_TX_LIST): + tmpbandstx = 0 + rs = RadioSetting("bands_tx", "Bands TX", RadioSettingValueList( + BANDS_TX_LIST, BANDS_TX_LIST[tmpbandstx])) + 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 MSG", + RadioSettingValueList( + WELCOME_LIST, + WELCOME_LIST[tmpdispmode])) + basic.append(rs) + + # QRZ label + qrz_label = str(_mem.qrz_label).rstrip("\x20\x00\xff") + "\x00" + qrz_label = _getstring(qrz_label.encode('ascii', errors='ignore'), 0, 8) + rs = RadioSetting("qrz_label", _("QRA (8 characters)"), + RadioSettingValueString(0, 8, qrz_label)) + basic.append(rs) + + # Logo string 1 + logo1 = str(_mem.logo_line1).strip("\x20\x00\xff") + "\x00" + logo1 = _getstring(logo1.encode('ascii', errors='ignore'), 0, 15) + rs = RadioSetting("logo1", _("Logo string 1 (15 characters)"), + RadioSettingValueString(0, 15, 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, 15) + rs = RadioSetting("logo2", _("Logo string 2 (15 characters)"), + RadioSettingValueString(0, 15, logo2)) + basic.append(rs) + + # NOAA autoscan +# rs = RadioSetting( +# "noaa_autoscan", +# "NOAA Autoscan (not implemented)", RadioSettingValueBoolean( +# bool(_mem.noaa_autoscan > 0))) +# basic.append(rs) + + + # VFO open +# rs = RadioSetting("vfo_open", "VFO open (???)", +# RadioSettingValueBoolean(bool(_mem.vfo_open > 0))) +# basic.append(rs) + + # Keypad Tone +# tmpkeypadtone = _mem.keypad_tone +# if tmpkeypadtone >= len(KEYPADTONE_LIST): +# tmpkeypadtone = 0 +# rs = RadioSetting("keypad_tone", "Voice prompts (not implemented)", RadioSettingValueList( +# KEYPADTONE_LIST, KEYPADTONE_LIST[tmpkeypadtone])) +# basic.append(rs) + + # Language +# tmplanguage = _mem.language +# if tmplanguage >= len(LANGUAGE_LIST): +# tmplanguage = 0 +# rs = RadioSetting("language", "Language (not implemented)", 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 (not implemented)", RadioSettingValueList( +# ALARMMODE_LIST, ALARMMODE_LIST[tmpalarmmode])) +# 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 + if self.FIRMWARE_VERSION == "": + firmware = "To get the firmware version please download" + "the image from the radio first" + else: + firmware = self.FIRMWARE_VERSION + + 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 + + # 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("\xFF" * 16) + if number < 200: + _mem2 = self._memobj.channelname[number] + _mem2.set_raw("\xFF" * 16) + _mem4.channel_attributes[number].is_scanlist1 = 0 + _mem4.channel_attributes[number].is_scanlist2 = 0 + _mem4.channel_attributes[number].unknown1 = 0 + _mem4.channel_attributes[number].unknown2 = 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("\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("\x00" * 10 + + chr(prev_0a) + chr(prev_0b) + chr(prev_0c) + + chr(prev_0d) + chr(prev_0e) + chr(prev_0f)) + + if number < 200: + _mem4.channel_attributes[number].is_scanlist1 = 0 + _mem4.channel_attributes[number].is_scanlist2 = 0 + _mem4.channel_attributes[number].unknown1 = 0 + _mem4.channel_attributes[number].unknown2 = 0 + _mem4.channel_attributes[number].is_free = 1 + _mem4.channel_attributes[number].band = 0x7 + + # find band + band = _find_band(self, mem.freq) + + # mode + if mem.mode == "FM": + _mem.enable_am = 0 + _mem.enable_extramodes = 0 + elif mem.mode == "AM": + _mem.enable_am = 1 + _mem.enable_extramodes = 0 + elif mem.mode == "USB": + _mem.enable_am = 0 + _mem.enable_extramodes = 1 + elif mem.mode == "CW": + _mem.enable_am = 1 + _mem.enable_extramodes = 1 + + + # 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 = 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 == "bandwidth": + _mem.bandwidth = BANDWIDTH_LIST.index(svalue) + + + if sname == "bclo": + _mem.bclo = svalue and 1 or 0 + + if sname == "pttid": + _mem.dtmf_pttid = 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 RA79Radio(UVK5Radio): + """Retevis RA79""" + VENDOR = "Retevis" + MODEL = "RA79"