[chirp_devel] Patch for #8339
Rick (AA0RD) DeWitt
Mon Nov 16 14:27:16 PST 2020
Patch to TYT TH-UV8000D/E issue # 8339. Allows setting priority scan
channel to 0 : Off.
--
Rick DeWitt
AA0RD
Sequim, Washington, USA 98382
(360) 681-3494
-------------- next part --------------
# HG changeset patch
# User Rick DeWitt <aa0rd at yahoo.com>
# Date 1605564872 28800
# Mon Nov 16 14:14:32 2020 -0800
# Node ID 25ea0536d1f6676a7c622ced7c74581fdc5621d7
# Parent 322a135bdf2bf42ad8b6f4026a488adad003e4c0
[th_uv8000] Issue # 8339, Priority channel: OFF now enabled
Allowed priority channel number to be 0
diff -r 322a135bdf2b -r 25ea0536d1f6 chirp/drivers/th_uv8000.py
--- a/chirp/drivers/th_uv8000.py Mon Nov 16 14:08:27 2020 -0800
+++ b/chirp/drivers/th_uv8000.py Mon Nov 16 14:14:32 2020 -0800
@@ -1,1491 +1,1480 @@
-# Copyright 2019: Rick DeWitt (RJD), <aa0rd at yahoo.com>
-# Version 1.0 for TYT-UV8000D/E
-# Thanks to Damon Schaefer (K9CQB) and the Loudoun County, VA ARES
-# club for the donated radio.
-# And thanks to Ian Harris (VA3IHX) for decoding the memory map.
-#
-# 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 <http://www.gnu.org/licenses/>.
-
-import time
-import struct
-import logging
-import re
-import math
-from chirp import chirp_common, directory, memmap
-from chirp import bitwise, errors, util
-from chirp.settings import RadioSettingGroup, RadioSetting, \
- RadioSettingValueBoolean, RadioSettingValueList, \
- RadioSettingValueString, RadioSettingValueInteger, \
- RadioSettingValueFloat, RadioSettings, InvalidValueError
-from textwrap import dedent
-
-LOG = logging.getLogger(__name__)
-
-MEM_FORMAT = """
-struct chns {
- ul32 rxfreq;
- ul32 txfreq;
- u8 rxtone[2];
- u8 txtone[2];
- u8 wide:1 // 0x0c
- vox_on:1
- chunk01:1
- bcl:1 // inv bool
- epilogue:1
- power:1
- chunk02:1
- chunk03:1;
- u8 ani:1 // 0x0d inv
- chunk08:1
- ptt:2
- chpad04:4;
- u8 chunk05; // 0x0e
- u16 id_code; // 0x0f, 10
- u8 chunk06;
- u8 name[7];
- ul32 chpad06; // Need 56 byte pad
- ul16 chpad07;
- u8 chpad08;
-};
-
-struct fm_chn {
- ul16 rxfreq;
-};
-
-struct frqx {
- ul32 rxfreq;
- ul24 ofst;
- u8 fqunk01:4 // 0x07
- funk10:2
- duplx:2;
- u8 rxtone[2]; // 0x08, 9
- u8 txtone[2]; // 0x0a, b
- u8 wide:1 // 0x0c
- vox_on:1
- funk11:1
- bcl:1 // inv bool
- epilogue:1
- power:1
- fqunk02:2;
- u8 ani:1 // 0x0d inv bool
- fqunk03:1
- ptt:2
- fqunk12:1
- fqunk04:3;
- u8 fqunk07; // 0x0e
- u16 id_code; // 0x0f, 0x10
- u8 name[7]; // dummy
- u8 fqunk09[8]; // empty bytes after 1st entry
-};
-
-struct bitmap {
- u8 map[16];
-};
-
-#seekto 0x0010;
-struct chns chan_mem[128];
-
-#seekto 0x1010;
-struct frqx frq[2];
-
-#seekto 0x1050;
-struct fm_chn fm_stations[25];
-
-#seekto 0x1080;
-struct {
- u8 fmunk01[14];
- ul16 fmcur;
-} fmfrqs;
-
-#seekto 0x1190;
-struct bitmap chnmap;
-
-#seekto 0x11a0;
-struct bitmap skpchns;
-
-#seekto 0x011b0;
-struct {
- u8 fmset[4];
-} fmmap;
-
-#seekto 0x011b4;
-struct {
- u8 setunk01[4];
- u8 setunk02[3];
- u8 chs_name:1 // 0x11bb
- txsel:1
- dbw:1
- setunk05:1
- ponfmchs:2
- ponchs:2;
- u8 voltx:2 // 0x11bc
- setunk04:1
- keylok:1
- setunk07:1
- batsav:3;
- u8 setunk09:1 // 0x11bd
- rxinhib:1
- rgrbeep:1 // inv bool
- lampon:2
- voice:2
- beepon:1;
- u8 setunk11:1 // 0x11be
- manualset:1
- xbandon:1 // inv
- xbandenable:1
- openmsg:2
- ledclr:2;
- u8 tot:4 // 0x11bf
- sql:4;
- u8 setunk27:1 // 0x11c0
- voxdelay:2
- setunk28:1
- voxgain:4;
- u8 fmstep:4 // 0x11c1
- freqstep:4;
- u8 scanspeed:4 // 0x11c2
- scanmode:4;
- u8 scantmo; // 0x11c3
- u8 prichan; // 0x11c4
- u8 setunk12:4 // 0x11c5
- supersave:4;
- u8 setunk13;
- u8 fmsclo; // 0x11c7 ??? placeholder
- u8 radioname[7]; // hex char codes, not true ASCII
- u8 fmschi; // ??? placeholder
- u8 setunk14[3]; // 0x11d0
- u8 setunk17[2]; // 0x011d3, 4
- u8 setunk18:4
- dtmfspd:4;
- u8 dtmfdig1dly:4 // 0x11d6
- dtmfdig1time:4;
- u8 stuntype:1
- setunk19:1
- dtmfspms:2
- grpcode:4;
- u8 setunk20:1 // 0x11d8
- txdecode:1
- codeabcd:1
- idedit:1
- pttidon:2
- setunk40:1,
- dtmfside:1;
- u8 setunk50:4,
- autoresettmo:4;
- u8 codespctim:4, // 0x11da
- decodetmo:4;
- u8 pttecnt:4 // 0x11db
- pttbcnt:4;
- lbcd dtmfdecode[3];
- u8 setunk22;
- u8 stuncnt; // 0x11e0
- u8 stuncode[5];
- u8 setunk60;
- u8 setunk61;
- u8 pttbot[8]; // 0x11e8-f
- u8 ptteot[8]; // 0x11f0-7
- u8 setunk62; // 0x11f8
- u8 setunk63;
- u8 setunk64; // 0x11fa
- u8 setunk65;
- u8 setunk66;
- u8 manfrqyn; // 0x11fd
- u8 setunk27:3
- frqr3:1
- setunk28:1
- frqr2:1
- setunk29:1
- frqr1:1;
- u8 setunk25;
- ul32 frqr1lo; // 0x1200
- ul32 frqr1hi;
- ul32 frqr2lo;
- ul32 frqr2hi;
- ul32 frqr3lo; // 0x1210
- ul32 frqr3hi;
- u8 setunk26[8];
-} setstuf;
-
-#seekto 0x1260;
-struct {
- u8 modnum[7];
-} modcode;
-
-#seekto 0x1300;
-struct {
- char mod_num[9];
-} mod_id;
-"""
-
-MEM_SIZE = 0x1300
-BLOCK_SIZE = 0x10 # can read 0x20, but must write 0x10
-STIMEOUT = 2
-BAUDRATE = 4800
-# Channel power: 2 levels
-POWER_LEVELS = [chirp_common.PowerLevel("Low", watts=5.00),
- chirp_common.PowerLevel("High", watts=10.00)]
-
-LIST_RECVMODE = ["QT/DQT", "QT/DQT + Signaling"]
-LIST_COLOR = ["Off", "Orange", "Blue", "Purple"]
-LIST_LEDSW = ["Auto", "On"]
-LIST_TIMEOUT = ["Off"] + ["%s" % x for x in range(30, 390, 30)]
-LIST_VFOMODE = ["Frequency Mode", "Channel Mode"]
-# Tones are numeric, Defined in \chirp\chirp_common.py
-TONES_CTCSS = sorted(chirp_common.TONES)
-# Converted to strings
-LIST_CTCSS = ["Off"] + [str(x) for x in TONES_CTCSS]
-# Now append the DxxxN and DxxxI DTCS codes from chirp_common
-for x in chirp_common.DTCS_CODES:
- LIST_CTCSS.append("D{:03d}N".format(x))
-for x in chirp_common.DTCS_CODES:
- LIST_CTCSS.append("D{:03d}R".format(x))
-LIST_BW = ["Narrow", "Wide"]
-LIST_SHIFT = ["off", "+", "-"]
-STEPS = [0.5, 2.5, 5.0, 6.25, 10.0, 12.5, 25.0, 37.5, 50.0, 100.0]
-LIST_STEPS = [str(x) for x in STEPS]
-LIST_VOXDLY = ["0.5", "1.0", "2.0", "3.0"] # LISTS must be strings
-LIST_PTT = ["Both", "EoT", "BoT", "Off"]
-
-SETTING_LISTS = {"tot": LIST_TIMEOUT, "wtled": LIST_COLOR,
- "rxled": LIST_COLOR, "txled": LIST_COLOR,
- "ledsw": LIST_LEDSW, "frq_chn_mode": LIST_VFOMODE,
- "rx_tone": LIST_CTCSS, "tx_tone": LIST_CTCSS,
- "rx_mode": LIST_RECVMODE, "fm_bw": LIST_BW,
- "shift": LIST_SHIFT, "step": LIST_STEPS,
- "vox_dly": LIST_VOXDLY, "ptt": LIST_PTT}
-
-
-def _clean_buffer(radio):
- radio.pipe.timeout = 0.005
- junk = radio.pipe.read(256)
- radio.pipe.timeout = STIMEOUT
- if junk:
- LOG.debug("Got %i bytes of junk before starting" % len(junk))
-
-
-def _rawrecv(radio, amount):
- """Raw read from the radio device"""
- data = ""
- try:
- data = radio.pipe.read(amount)
- except Exception:
- _exit_program_mode(radio)
- msg = "Generic error reading data from radio; check your cable."
- raise errors.RadioError(msg)
-
- if len(data) != amount:
- _exit_program_mode(radio)
- msg = "Error reading from radio: not the amount of data we want."
- raise errors.RadioError(msg)
-
- return data
-
-
-def _rawsend(radio, data):
- """Raw send to the radio device"""
- try:
- radio.pipe.write(data)
- except Exception:
- raise errors.RadioError("Error sending data to radio")
-
-
-def _make_frame(cmd, addr, length, data=""):
- """Pack the info in the headder format"""
- frame = struct.pack(">shB", cmd, addr, length)
- # Add the data if set
- if len(data) != 0:
- frame += data
- # Return the data
- return frame
-
-
-def _recv(radio, addr, length):
- """Get data from the radio """
-
- data = _rawrecv(radio, length)
-
- # DEBUG
- LOG.info("Response:")
- LOG.debug(util.hexprint(data))
-
- return data
-
-
-def _do_ident(radio):
- """Put the radio in PROGRAM mode & identify it"""
- radio.pipe.baudrate = BAUDRATE
- radio.pipe.parity = "N"
- radio.pipe.timeout = STIMEOUT
-
- # Flush input buffer
- _clean_buffer(radio)
-
- magic = "PROGRAMa"
- _rawsend(radio, magic)
- ack = _rawrecv(radio, 1)
- # LOG.warning("PROGa Ack:" + util.hexprint(ack))
- if ack != "\x06":
- _exit_program_mode(radio)
- if ack:
- LOG.debug(repr(ack))
- raise errors.RadioError("Radio did not respond")
- magic = "PROGRAMb"
- _rawsend(radio, magic)
- ack = _rawrecv(radio, 1)
- if ack != "\x06":
- _exit_program_mode(radio)
- if ack:
- LOG.debug(repr(ack))
- raise errors.RadioError("Radio did not respond to B")
- magic = chr(0x02)
- _rawsend(radio, magic)
- ack = _rawrecv(radio, 1) # s/b: 0x50
- magic = _rawrecv(radio, 7) # s/b TC88...
- magic = "MTC88CUMHS3E7BN-"
- _rawsend(radio, magic)
- ack = _rawrecv(radio, 1) # s/b 0x80
- magic = chr(0x06)
- _rawsend(radio, magic)
- ack = _rawrecv(radio, 1)
-
- return True
-
-
-def _exit_program_mode(radio):
- endframe = "E"
- _rawsend(radio, endframe)
-
-
-def _download(radio):
- """Get the memory map"""
-
- # Put radio in program mode and identify it
- _do_ident(radio)
-
- # UI progress
- status = chirp_common.Status()
- status.cur = 0
- status.max = MEM_SIZE / BLOCK_SIZE
- status.msg = "Cloning from radio..."
- radio.status_fn(status)
-
- data = ""
- for addr in range(0, MEM_SIZE, BLOCK_SIZE):
- frame = _make_frame("R", addr, BLOCK_SIZE)
- # DEBUG
- LOG.info("Request sent:")
- LOG.debug("Frame=" + util.hexprint(frame))
-
- # Sending the read request
- _rawsend(radio, frame)
- dx = _rawrecv(radio, 4)
-
- # Now we read data
- d = _recv(radio, addr, BLOCK_SIZE)
- # LOG.warning("Data= " + util.hexprint(d))
-
- # Aggregate the data
- data += d
-
- # UI Update
- status.cur = addr / BLOCK_SIZE
- status.msg = "Cloning from radio..."
- radio.status_fn(status)
-
- _exit_program_mode(radio)
-
- return data
-
-
-def _upload(radio):
- """Upload procedure"""
- # Put radio in program mode and identify it
- _do_ident(radio)
-
- # UI progress
- status = chirp_common.Status()
- status.cur = 0
- status.max = MEM_SIZE / BLOCK_SIZE
- status.msg = "Cloning to radio..."
- radio.status_fn(status)
-
- # The fun starts here
- for addr in range(0, MEM_SIZE, BLOCK_SIZE):
- # Sending the data
- data = radio.get_mmap()[addr:addr + BLOCK_SIZE]
-
- frame = _make_frame("W", addr, BLOCK_SIZE, data)
- # LOG.warning("Frame:%s:" % util.hexprint(frame))
- _rawsend(radio, frame)
-
- # Receiving the response
- ack = _rawrecv(radio, 1)
- if ack != "\x06":
- _exit_program_mode(radio)
- msg = "Bad ack writing block 0x%04x" % addr
- raise errors.RadioError(msg)
-
- # UI Update
- status.cur = addr / BLOCK_SIZE
- status.msg = "Cloning to radio..."
- radio.status_fn(status)
-
- _exit_program_mode(radio)
-
-
-def set_tone(_mem, txrx, ctdt, tval, pol):
- """Set rxtone[] or txtone[] word values as decimal bytes"""
- # txrx: Boolean T= set Rx tones, F= set Tx tones
- # ctdt: Boolean T = CTCSS, F= DTCS
- # tval = integer tone freq (*10) or DTCS code
- # pol = string for DTCS polarity "R" or "N"
- xv = int(str(tval), 16)
- if txrx: # True = set rxtones
- _mem.rxtone[0] = xv & 0xFF # Low byte
- _mem.rxtone[1] = (xv >> 8) # Hi byte
- if not ctdt: # dtcs,
- if pol == "R":
- _mem.rxtone[1] = _mem.rxtone[1] | 0xC0
- else:
- _mem.rxtone[1] = _mem.rxtone[1] | 0x80
- else: # txtones
- _mem.txtone[0] = xv & 0xFF # Low byte
- _mem.txtone[1] = (xv >> 8)
- if not ctdt: # dtcs
- if pol == "R":
- _mem.txtone[1] = _mem.txtone[1] | 0xC0
- else:
- _mem.txtone[1] = _mem.txtone[1] | 0x80
-
- return 0
-
-
-def _do_map(chn, sclr, mary):
- """Set or Clear the chn (1-128) bit in mary[] word array map"""
- # chn is 1-based channel, sclr:1 = set, 0= = clear, 2= return state
- # mary[] is u8 array, but the map is by nibbles
- ndx = int(math.floor((chn - 1) / 8))
- bv = (chn - 1) % 8
- msk = 1 << bv
- mapbit = sclr
- if sclr == 1: # Set the bit
- mary[ndx] = mary[ndx] | msk
- elif sclr == 0: # clear
- mary[ndx] = mary[ndx] & (~ msk) # ~ is complement
- else: # return current bit state
- mapbit = 0
- if (mary[ndx] & msk) > 0:
- mapbit = 1
- return mapbit
-
-
- at directory.register
-class THUV8000Radio(chirp_common.CloneModeRadio):
- """TYT UV8000D Radio"""
- VENDOR = "TYT"
- MODEL = "TH-UV8000"
- MODES = ["NFM", "FM"]
- TONES = chirp_common.TONES
- DTCS_CODES = sorted(chirp_common.DTCS_CODES + [645])
- NAME_LENGTH = 7
- DTMF_CHARS = list("0123456789ABCD*#")
- # NOTE: SE Model supports 220-260 MHz
- # The following bands are the the range the radio is capable of,
- # not the legal FCC amateur bands
- VALID_BANDS = [(87500000, 107900000), (136000000, 174000000),
- (220000000, 260000000), (400000000, 520000000)]
-
- # Valid chars on the LCD
- VALID_CHARS = chirp_common.CHARSET_ALPHANUMERIC + \
- "`!\"#$%&'()*+,-./:;<=>?@[]^_"
-
- # Special Channels Declaration
- # WARNING Indecis are hard wired in get/set_memory code !!!
- # Channels print in + increasing index order (most negative first)
- SPECIAL_MEMORIES = {
- "UpVFO": -2,
- "LoVFO": -1
- }
- FIRST_FREQ_INDEX = -1
- LAST_FREQ_INDEX = -2
-
- SPECIAL_MEMORIES_REV = dict(zip(SPECIAL_MEMORIES.values(),
- SPECIAL_MEMORIES.keys()))
-
- @classmethod
- def get_prompts(cls):
- rp = chirp_common.RadioPrompts()
- rp.info = \
- ('Click on the "Special Channels" toggle-button of the memory '
- 'editor to see/set the upper and lower frequency-mode values.\n')
-
- rp.pre_download = _(dedent("""\
- Follow these instructions to download the radio memory:
-
- 1 - Turn off your radio
- 2 - Connect your interface cable
- 3 - Turn on your radio, volume @ 50%
- 4 - Radio > Download from radio
- """))
- rp.pre_upload = _(dedent("""\
- Follow these instructions to upload the radio memory:
-
- 1 - Turn off your radio
- 2 - Connect your interface cable
- 3 - Turn on your radio, volume @ 50%
- 4 - Radio > Upload to radio
- """))
- return rp
-
- def get_features(self):
- rf = chirp_common.RadioFeatures()
- # .has. attributes are boolean, .valid. are lists
- rf.has_settings = True
- rf.has_bank = False
- rf.has_comment = False
- rf.has_nostep_tuning = True # Radio accepts any entered freq
- rf.has_tuning_step = False # Not as chan feature
- rf.can_odd_split = False
- rf.has_name = True
- rf.has_offset = True
- rf.has_mode = True
- rf.has_dtcs = True
- rf.has_rx_dtcs = True
- rf.has_dtcs_polarity = True
- rf.has_ctone = True
- rf.has_cross = True
- rf.has_sub_devices = False
- rf.valid_name_length = self.NAME_LENGTH
- rf.valid_modes = self.MODES
- rf.valid_characters = self.VALID_CHARS
- rf.valid_duplexes = ["-", "+", "off", ""]
- rf.valid_tmodes = ['', 'Tone', 'TSQL', 'DTCS', 'Cross']
- rf.valid_cross_modes = ["Tone->Tone", "DTCS->", "->DTCS",
- "Tone->DTCS", "DTCS->Tone", "->Tone",
- "DTCS->DTCS"]
- rf.valid_skips = []
- rf.valid_power_levels = POWER_LEVELS
- rf.valid_dtcs_codes = self.DTCS_CODES
- rf.valid_bands = self.VALID_BANDS
- rf.memory_bounds = (1, 128)
- rf.valid_skips = ["", "S"]
- rf.valid_special_chans = sorted(self.SPECIAL_MEMORIES.keys())
- return rf
-
- def sync_in(self):
- """Download from radio"""
- try:
- data = _download(self)
- except errors.RadioError:
- # Pass through any real errors we raise
- raise
- except Exception:
- # If anything unexpected happens, make sure we raise
- # a RadioError and log the problem
- LOG.exception('Unexpected error during download')
- raise errors.RadioError('Unexpected error communicating '
- 'with the radio')
- self._mmap = memmap.MemoryMap(data)
- self.process_mmap()
-
- def sync_out(self):
- """Upload to radio"""
-
- try:
- _upload(self)
- except Exception:
- # If anything unexpected happens, make sure we raise
- # a RadioError and log the problem
- LOG.exception('Unexpected error during upload')
- raise errors.RadioError('Unexpected error communicating '
- 'with the radio')
-
- def process_mmap(self):
- """Process the mem map into the mem object"""
- self._memobj = bitwise.parse(MEM_FORMAT, self._mmap)
-
- def get_raw_memory(self, number):
- return repr(self._memobj.memory[number - 1])
-
- def get_memory(self, number):
- if isinstance(number, str):
- return self._get_special(number)
- elif number < 0:
- # I can't stop delete operation from loosing extd_number but
- # I know how to get it back
- return self._get_special(self.SPECIAL_MEMORIES_REV[number])
- else:
- return self._get_normal(number)
-
- def set_memory(self, memory):
- """A value in a UI column for chan 'number' has been modified."""
- # update all raw channel memory values (_mem) from UI (mem)
- if memory.number < 0:
- return self._set_special(memory)
- else:
- return self._set_normal(memory)
-
- def _get_normal(self, number):
- # radio first channel is 1, mem map is base 0
- _mem = self._memobj.chan_mem[number - 1]
- mem = chirp_common.Memory()
- mem.number = number
-
- return self._get_memory(mem, _mem)
-
- def _get_memory(self, mem, _mem):
- """Convert raw channel memory data into UI columns"""
- mem.extra = RadioSettingGroup("extra", "Extra")
-
- if _mem.get_raw()[0] == "\xff":
- mem.empty = True
- return mem
-
- mem.empty = False
- # This function process both 'normal' and Freq up/down' entries
- mem.freq = int(_mem.rxfreq) * 10
- mem.power = POWER_LEVELS[_mem.power]
- mem.mode = self.MODES[_mem.wide]
- dtcs_pol = ["N", "N"]
-
- if _mem.rxtone[0] == 0xFF:
- rxmode = ""
- elif _mem.rxtone[1] < 0x26:
- # CTCSS
- rxmode = "Tone"
- tonehi = int(str(_mem.rxtone[1])[2:])
- tonelo = int(str(_mem.rxtone[0])[2:])
- mem.ctone = int(tonehi * 100 + tonelo) / 10.0
- else:
- # Digital
- rxmode = "DTCS"
- tonehi = int(str(_mem.rxtone[1] & 0x3f))
- tonelo = int(str(_mem.rxtone[0])[2:])
- mem.rx_dtcs = int(tonehi * 100 + tonelo)
- if (_mem.rxtone[1] & 0x40) != 0:
- dtcs_pol[1] = "R"
-
- if _mem.txtone[0] == 0xFF:
- txmode = ""
- elif _mem.txtone[1] < 0x26:
- # CTCSS
- txmode = "Tone"
- tonehi = int(str(_mem.txtone[1])[2:])
- tonelo = int(str(_mem.txtone[0])[2:])
- mem.rtone = int(tonehi * 100 + tonelo) / 10.0
- else:
- # Digital
- txmode = "DTCS"
- tonehi = int(str(_mem.txtone[1] & 0x3f))
- tonelo = int(str(_mem.txtone[0])[2:])
- mem.dtcs = int(tonehi * 100 + tonelo)
- if (_mem.txtone[1] & 0x40) != 0:
- dtcs_pol[0] = "R"
-
- mem.tmode = ""
- if txmode == "Tone" and not rxmode:
- mem.tmode = "Tone"
- elif txmode == rxmode and txmode == "Tone" and mem.rtone == mem.ctone:
- mem.tmode = "TSQL"
- elif txmode == rxmode and txmode == "DTCS" and mem.dtcs == mem.rx_dtcs:
- mem.tmode = "DTCS"
- elif rxmode or txmode:
- mem.tmode = "Cross"
- mem.cross_mode = "%s->%s" % (txmode, rxmode)
-
- mem.dtcs_polarity = "".join(dtcs_pol)
-
- # Now test the mem.number to process special vs normal
- if mem.number >= 0: # Normal
- mem.name = ""
- for i in range(self.NAME_LENGTH): # 0 - 6
- mem.name += chr(_mem.name[i] + 32)
- mem.name = mem.name.rstrip() # remove trailing spaces
-
- if _mem.txfreq == 0xFFFFFFFF:
- # TX freq not set
- mem.duplex = "off"
- mem.offset = 0
- elif int(_mem.rxfreq) == int(_mem.txfreq):
- mem.duplex = ""
- mem.offset = 0
- else:
- mem.duplex = int(_mem.rxfreq) > int(_mem.txfreq) \
- and "-" or "+"
- mem.offset = abs(int(_mem.rxfreq) - int(_mem.txfreq)) * 10
-
- if _do_map(mem.number, 2, self._memobj.skpchns.map) > 0:
- mem.skip = "S"
- else:
- mem.skip = ""
-
- else: # specials VFO
- mem.name = "----"
- mem.duplex = LIST_SHIFT[_mem.duplx]
- mem.offset = int(_mem.ofst) * 10
- mem.skip = ""
- # End if specials
-
- # Channel Extra settings: Only Boolean & List methods, no call-backs
- rx = RadioSettingValueBoolean(bool(not _mem.bcl)) # Inverted bool
- # NOTE: first param of RadioSetting is the object attribute name
- rset = RadioSetting("bcl", "Busy Channel Lockout", rx)
- mem.extra.append(rset)
-
- rx = RadioSettingValueBoolean(bool(not _mem.vox_on))
- rset = RadioSetting("vox_on", "Vox", rx)
- mem.extra.append(rset)
-
- rx = RadioSettingValueBoolean(bool(not _mem.ani))
- rset = RadioSetting("ani", "Auto Number ID (ANI)", rx)
- mem.extra.append(rset)
-
- # ID code can't be done in extra - no Integer method or call-back
-
- rx = RadioSettingValueList(LIST_PTT, LIST_PTT[_mem.ptt])
- rset = RadioSetting("ptt", "Xmit PTT ID", rx)
- mem.extra.append(rset)
-
- rx = RadioSettingValueBoolean(bool(_mem.epilogue))
- rset = RadioSetting("epilogue", "Epilogue/Tail", rx)
- mem.extra.append(rset)
-
- return mem
-
- def _get_special(self, number):
- mem = chirp_common.Memory()
- mem.number = self.SPECIAL_MEMORIES[number]
- mem.extd_number = number
- # Unused attributes are ignored in Set_memory
- if (mem.number == -1) or (mem.number == -2):
- # Print Upper[1] first, and Lower[0] next
- rx = 0
- if mem.number == -2:
- rx = 1
- _mem = self._memobj.frq[rx]
- # immutable = ["number", "extd_number", "name"]
- mem = self._get_memory(mem, _mem)
- else:
- raise Exception("Sorry, you can't edit that special"
- " memory channel %i." % mem.number)
-
- # mem.immutable = immutable
-
- return mem
-
- def _set_memory(self, mem, _mem):
- """Convert UI column data (mem) into MEM_FORMAT memory (_mem)."""
- # At this point mem points to either normal or Freq chans
- # These first attributes are common to all types
- if mem.empty:
- if mem.number > 0:
- _mem.rxfreq = 0xffffffff
- # Set 'empty' and 'skip' bits
- _do_map(mem.number, 1, self._memobj.chnmap.map)
- _do_map(mem.number, 1, self._memobj.skpchns.map)
- elif mem.number == -2: # upper VFO Freq
- _mem.rxfreq = 14652000 # VHF National Calling freq
- elif mem.number == -1: # lower VFO
- _mem.rxfreq = 44600000 # UHF National Calling freq
- return
-
- _mem.rxfreq = mem.freq / 10
-
- if str(mem.power) == "Low":
- _mem.power = 0
- else:
- _mem.power = 1
-
- _mem.wide = self.MODES.index(mem.mode)
-
- rxmode = ""
- txmode = ""
-
- if mem.tmode == "Tone":
- txmode = "Tone"
- elif mem.tmode == "TSQL":
- rxmode = "Tone"
- txmode = "TSQL"
- elif mem.tmode == "DTCS":
- rxmode = "DTCSSQL"
- txmode = "DTCS"
- elif mem.tmode == "Cross":
- txmode, rxmode = mem.cross_mode.split("->", 1)
-
- sx = mem.dtcs_polarity[1]
- if rxmode == "":
- _mem.rxtone[0] = 0xFF
- _mem.rxtone[1] = 0xFF
- elif rxmode == "Tone":
- val = int(mem.ctone * 10)
- i = set_tone(_mem, True, True, val, sx)
- elif rxmode == "DTCSSQL":
- i = set_tone(_mem, True, False, mem.dtcs, sx)
- elif rxmode == "DTCS":
- i = set_tone(_mem, True, False, mem.rx_dtcs, sx)
-
- sx = mem.dtcs_polarity[0]
- if txmode == "":
- _mem.txtone[0] = 0xFF
- _mem.txtone[1] = 0xFF
- elif txmode == "Tone":
- val = int(mem.rtone * 10)
- i = set_tone(_mem, False, True, val, sx)
- elif txmode == "TSQL":
- val = int(mem.ctone * 10)
- i = set_tone(_mem, False, True, val, sx)
- elif txmode == "DTCS":
- i = set_tone(_mem, False, False, mem.dtcs, sx)
-
- if mem.number > 0: # Normal chans
- for i in range(self.NAME_LENGTH):
- pq = ord(mem.name.ljust(self.NAME_LENGTH)[i]) - 32
- if pq < 0:
- pq = 0
- _mem.name[i] = pq
-
- if mem.duplex == "off":
- _mem.txfreq = 0xFFFFFFFF
- elif mem.duplex == "+":
- _mem.txfreq = (mem.freq + mem.offset) / 10
- elif mem.duplex == "-":
- _mem.txfreq = (mem.freq - mem.offset) / 10
- else:
- _mem.txfreq = mem.freq / 10
-
- # Set the channel map bit FALSE = Enabled
- _do_map(mem.number, 0, self._memobj.chnmap.map)
- # Skip
- if mem.skip == "S":
- _do_map(mem.number, 1, self._memobj.skpchns.map)
- else:
- _do_map(mem.number, 0, self._memobj.skpchns.map)
-
- else: # Freq (VFO) chans
- _mem.duplx = 0
- _mem.ofst = 0
- if mem.duplex == "+":
- _mem.duplx = 1
- _mem.ofst = mem.offset / 10
- elif mem.duplex == "-":
- _mem.duplx = 2
- _mem.ofst = mem.offset / 10
- for i in range(self.NAME_LENGTH):
- _mem.name[i] = 0xff
-
- # All mem.extra << Once the channel is defined
- for setting in mem.extra:
- # Overide list strings with signed value
- if setting.get_name() == "ptt":
- sx = str(setting.value)
- for i in range(0, 4):
- if sx == LIST_PTT[i]:
- val = i
- setattr(_mem, "ptt", val)
- elif setting.get_name() == "epilogue": # not inverted bool
- setattr(_mem, setting.get_name(), setting.value)
- else: # inverted booleans
- setattr(_mem, setting.get_name(), not setting.value)
-
- def _set_special(self, mem):
-
- cur_mem = self._get_special(self.SPECIAL_MEMORIES_REV[mem.number])
-
- if mem.number == -2: # upper frq[1]
- _mem = self._memobj.frq[1]
- elif mem.number == -1: # lower frq[0]
- _mem = self._memobj.frq[0]
- else:
- raise Exception("Sorry, you can't edit that special memory.")
-
- self._set_memory(mem, _mem) # Now update the _mem
-
- def _set_normal(self, mem):
- _mem = self._memobj.chan_mem[mem.number - 1]
-
- self._set_memory(mem, _mem)
-
- def get_settings(self):
- """Translate the MEM_FORMAT structs into setstuf in the UI"""
- # Define mem struct write-back shortcuts
- _sets = self._memobj.setstuf
- _fmx = self._memobj.fmfrqs
-
- basic = RadioSettingGroup("basic", "Basic Settings")
- adv = RadioSettingGroup("adv", "Other Settings")
- fmb = RadioSettingGroup("fmb", "FM Broadcast")
- scn = RadioSettingGroup("scn", "Scan Settings")
- dtmf = RadioSettingGroup("dtmf", "DTMF Settings")
- frng = RadioSettingGroup("frng", "Frequency Ranges")
- group = RadioSettings(basic, adv, scn, fmb, dtmf, frng)
-
- def my_val_list(setting, obj, atrb):
- """Callback:from ValueList with non-sequential, actual values."""
- # This call back also used in get_settings
- value = int(str(setting.value)) # Get the integer value
- setattr(obj, atrb, value)
- return
-
- def my_adjraw(setting, obj, atrb, fix):
- """Callback from Integer add or subtract fix from value."""
- vx = int(str(setting.value))
- value = vx + int(fix)
- if value < 0:
- value = 0
- setattr(obj, atrb, value)
- return
-
- def my_strnam(setting, obj, atrb, mln):
- """Callback from String to build u8 array with -32 offset."""
- # mln is max string length
- ary = []
- knt = mln
- for j in range(mln - 1, -1, -1): # Strip trailing spaces or nulls
- pq = str(setting.value)[j]
- if pq == "" or pq == " ":
- knt = knt - 1
- else:
- break
- for j in range(mln): # 0 to mln-1
- pq = str(setting.value).ljust(mln)[j]
- if j < knt:
- ary.append(ord(pq) - 32)
- else:
- ary.append(0)
- setattr(obj, atrb, ary)
- return
-
- def unpack_str(cary, cknt, mxw):
- """Convert u8 nibble array to a string: NOT a callback."""
- # cknt is char count, 2/word; mxw is max WORDS
- stx = ""
- mty = True
- for i in range(mxw): # unpack entire array
- nib = (cary[i] & 0xf0) >> 4 # LE, Hi nib first
- if nib != 0xf:
- mty = False
- stx += format(nib, '0X')
- nib = cary[i] & 0xf
- if nib != 0xf:
- mty = False
- stx += format(nib, '0X')
- stx = stx[:cknt]
- if mty: # all ff, empty string
- sty = ""
- else:
- # Convert E to #, F to *
- sty = ""
- for i in range(cknt):
- if stx[i] == "E":
- sty += "#"
- elif stx[i] == "F":
- sty += "*"
- else:
- sty += stx[i]
-
- return sty
-
- def pack_chars(setting, obj, atrstr, atrcnt, mxl):
- """Callback to build 0-9,A-D,*# nibble array from string"""
- # cknt is generated char count, 2 chars per word
- # String will be f padded to mxl
- # Chars are stored as hex values
- # store cknt-1 in atrcnt, 0xf if empty
- cknt = 0
- ary = []
- stx = str(setting.value).upper()
- stx = stx.strip() # trim spaces
- # Remove illegal characters first
- sty = ""
- for j in range(0, len(stx)):
- if stx[j] in self.DTMF_CHARS:
- sty += stx[j]
- for j in range(mxl):
- if j < len(sty):
- if sty[j] == "#":
- chrv = 0xE
- elif sty[j] == "*":
- chrv = 0xF
- else:
- chrv = int(sty[j], 16)
- cknt += 1 # char count
- else: # pad to mxl, cknt does not increment
- chrv = 0xF
- if (j % 2) == 0: # odd count (0-based), high nibble
- hi_nib = chrv
- else: # even count, lower nibble
- lo_nib = chrv
- nibs = lo_nib | (hi_nib << 4)
- ary.append(nibs) # append word
- setattr(obj, atrstr, ary)
- if setting.get_name() != "setstuf.stuncode": # cknt is actual
- if cknt > 0:
- cknt = cknt - 1
- else:
- cknt = 0xf
- setattr(obj, atrcnt, cknt)
- return
-
- def myset_freq(setting, obj, atrb, mult):
- """ Callback to set frequency by applying multiplier"""
- value = int(float(str(setting.value)) * mult)
- setattr(obj, atrb, value)
- return
-
- def my_invbool(setting, obj, atrb):
- """Callback to invert the boolean """
- bval = not setting.value
- setattr(obj, atrb, bval)
- return
-
- def my_batsav(setting, obj, atrb):
- """Callback to set batsav attribute """
- stx = str(setting.value) # Off, 1:1...
- if stx == "Off":
- value = 0x1 # bit value 4 clear, ratio 1 = 1:2
- elif stx == "1:1":
- value = 0x4 # On, ratio 0 = 1:1
- elif stx == "1:2":
- value = 0x5 # On, ratio 1 = 1:2
- elif stx == "1:3":
- value = 0x6 # On, ratio 2 = 1:3
- else:
- value = 0x7 # On, ratio 3 = 1:4
- # LOG.warning("Batsav stx:%s:, value= %x" % (stx, value))
- setattr(obj, atrb, value)
- return
-
- def my_manfrq(setting, obj, atrb):
- """Callback to set 2-byte manfrqyn yes/no """
- # LOG.warning("Manfrq value = %d" % setting.value)
- if (str(setting.value)) == "No":
- value = 0xff
- else:
- value = 0xaa
- setattr(obj, atrb, value)
- return
-
- def myset_mask(setting, obj, atrb, nx):
- if bool(setting.value): # Enabled = 0
- vx = 0
- else:
- vx = 1
- _do_map(nx + 1, vx, self._memobj.fmmap.fmset)
- return
-
- def myset_fmfrq(setting, obj, atrb, nx):
- """ Callback to set xx.x FM freq in memory as xx.x * 40"""
- # in-valid even KHz freqs are allowed; to satisfy run_tests
- vx = float(str(setting.value))
- vx = int(vx * 40)
- setattr(obj[nx], atrb, vx)
- return
-
- rx = RadioSettingValueInteger(1, 9, _sets.voxgain + 1)
- rset = RadioSetting("setstuf.voxgain", "Vox Level", rx)
- rset.set_apply_callback(my_adjraw, _sets, "voxgain", -1)
- basic.append(rset)
-
- rx = RadioSettingValueList(LIST_VOXDLY, LIST_VOXDLY[_sets.voxdelay])
- rset = RadioSetting("setstuf.voxdelay", "Vox Delay (secs)", rx)
- basic.append(rset)
-
- rx = RadioSettingValueInteger(0, 9, _sets.sql)
- rset = RadioSetting("setstuf.sql", "Squelch", rx)
- basic.append(rset)
-
- rx = RadioSettingValueList(LIST_STEPS, LIST_STEPS[_sets.freqstep])
- rset = RadioSetting("setstuf.freqstep", "VFO Tune Step (KHz))", rx)
- basic.append(rset)
-
- rx = RadioSettingValueBoolean(bool(_sets.dbw)) # true logic
- rset = RadioSetting("setstuf.dbw", "Dual Band Watch (D.WAIT)", rx)
- basic.append(rset)
-
- options = ["Off", "On", "Auto"]
- rx = RadioSettingValueList(options, options[_sets.lampon])
- rset = RadioSetting("setstuf.lampon", "Backlight (LED)", rx)
- basic.append(rset)
-
- options = ["Orange", "Purple", "Blue"]
- rx = RadioSettingValueList(options, options[_sets.ledclr])
- rset = RadioSetting("setstuf.ledclr", "Backlight Color (LIGHT)", rx)
- basic.append(rset)
-
- rx = RadioSettingValueBoolean(bool(_sets.beepon))
- rset = RadioSetting("setstuf.beepon", "Keypad Beep", rx)
- basic.append(rset)
-
- rx = RadioSettingValueBoolean(bool(_sets.xbandenable))
- rset = RadioSetting("setstuf.xbandenable", "Cross Band Allowed", rx)
- basic.append(rset)
-
- rx = RadioSettingValueBoolean(bool(not _sets.xbandon))
- rset = RadioSetting("setstuf.xbandon", "Cross Band On", rx)
- rset.set_apply_callback(my_invbool, _sets, "xbandon")
- basic.append(rset)
-
- rx = RadioSettingValueList(LIST_TIMEOUT, LIST_TIMEOUT[_sets.tot])
- rset = RadioSetting("setstuf.tot", "TX Timeout (Secs)", rx)
- basic.append(rset)
-
- rx = RadioSettingValueBoolean(bool(not _sets.rgrbeep)) # Invert
- rset = RadioSetting("setstuf.rgrbeep", "Beep at Eot (Roger)", rx)
- rset.set_apply_callback(my_invbool, _sets, "rgrbeep")
- basic.append(rset)
-
- rx = RadioSettingValueBoolean(bool(not _sets.keylok))
- rset = RadioSetting("setstuf.keylok", "Keypad AutoLock", rx)
- rset.set_apply_callback(my_invbool, _sets, "keylok")
- basic.append(rset)
-
- options = ["None", "Message", "DC Volts"]
- rx = RadioSettingValueList(options, options[_sets.openmsg])
- rset = RadioSetting("setstuf.openmsg", "Power-On Display", rx)
- basic.append(rset)
-
- options = ["Channel Name", "Frequency"]
- rx = RadioSettingValueList(options, options[_sets.chs_name])
- rset = RadioSetting("setstuf.chs_name", "Display Name/Frq", rx)
- basic.append(rset)
-
- sx = ""
- for i in range(7):
- if _sets.radioname[i] != 0:
- sx += chr(_sets.radioname[i] + 32)
- rx = RadioSettingValueString(0, 7, sx)
- rset = RadioSetting("setstuf.radioname", "Power-On Message", rx)
- rset.set_apply_callback(my_strnam, _sets, "radioname", 7)
- basic.append(rset)
-
- # Advanced (Strange) Settings
- options = ["Busy: Last Tx Band", "Edit: Current Band"]
- rx = RadioSettingValueList(options, options[_sets.txsel])
- rset = RadioSetting("setstuf.txsel", "Transmit Priority", rx)
- rset.set_doc("'Busy' transmits on last band used, not current one.")
- adv.append(rset)
-
- options = ["Off", "English", "Unk", "Chinese"]
- val = _sets.voice
- rx = RadioSettingValueList(options, options[val])
- rset = RadioSetting("setstuf.voice", "Voice", rx)
- adv.append(rset)
-
- options = ["Off", "1:1", "1:2", "1:3", "1:4"]
- val = (_sets.batsav & 0x3) + 1 # ratio
- if (_sets.batsav & 0x4) == 0: # Off
- val = 0
- rx = RadioSettingValueList(options, options[val])
- rset = RadioSetting("setstuf.batsav", "Battery Saver", rx)
- rset.set_apply_callback(my_batsav, _sets, "batsav")
- adv.append(rset)
-
- # Find out what & where SuperSave is
- options = ["Off", "1", "2", "3", "4", "5", "6", "7", "8", "9"]
- rx = RadioSettingValueList(options, options[_sets.supersave])
- rset = RadioSetting("setstuf.supersave", "Super Save (Secs)", rx)
- rset.set_doc("Unknown radio attribute??")
- adv.append(rset)
-
- sx = unpack_str(_sets.pttbot, _sets.pttbcnt + 1, 8)
- rx = RadioSettingValueString(0, 16, sx)
- rset = RadioSetting("setstuf.pttbot", "PTT BoT Code", rx)
- rset.set_apply_callback(pack_chars, _sets, "pttbot", "pttbcnt", 16)
- adv.append(rset)
-
- sx = unpack_str(_sets.ptteot, _sets.pttecnt + 1, 8)
- rx = RadioSettingValueString(0, 16, sx)
- rset = RadioSetting("setstuf.ptteot", "PTT EoT Code", rx)
- rset.set_apply_callback(pack_chars, _sets, "ptteot", "pttecnt", 16)
- adv.append(rset)
-
- options = ["None", "Low", "High", "Both"]
- rx = RadioSettingValueList(options, options[_sets.voltx])
- rset = RadioSetting("setstuf.voltx", "Transmit Inhibit Voltage", rx)
- rset.set_doc("Block Transmit if battery volts are too high or low,")
- adv.append(rset)
-
- val = 0 # No = 0xff
- if _sets.manfrqyn == 0xaa:
- val = 1
- options = ["No", "Yes"]
- rx = RadioSettingValueList(options, options[val])
- rset = RadioSetting("setstuf.manfrqyn", "Manual Frequency", rx)
- rset.set_apply_callback(my_manfrq, _sets, "manfrqyn")
- adv.append(rset)
-
- rx = RadioSettingValueBoolean(bool(_sets.manualset))
- rset = RadioSetting("setstuf.manualset", "Manual Setting", rx)
- adv.append(rset)
-
- # Scan Settings
- options = ["CO: During Rx", "TO: Timed", "SE: Halt"]
- rx = RadioSettingValueList(options, options[_sets.scanmode])
- rset = RadioSetting("setstuf.scanmode",
- "Scan Mode (Scan Pauses When)", rx)
- scn.append(rset)
-
- options = ["100", "150", "200", "250",
- "300", "350", "400", "450"]
- rx = RadioSettingValueList(options, options[_sets.scanspeed])
- rset = RadioSetting("setstuf.scanspeed", "Scan Speed (ms)", rx)
- scn.append(rset)
-
- val = _sets.scantmo + 3
- rx = RadioSettingValueInteger(3, 30, val)
- rset = RadioSetting("setstuf.scantmo",
- "TO Mode Timeout (secs)", rx)
- rset.set_apply_callback(my_adjraw, _sets, "scantmo", -3)
- scn.append(rset)
-
- val = _sets.prichan
- if val <= 0:
- val = 1
- rx = RadioSettingValueInteger(1, 128, val)
- rset = RadioSetting("setstuf.prichan", "Priority Channel", rx)
- scn.append(rset)
-
- # FM Broadcast Settings
- val = _fmx.fmcur
- val = val / 40.0
- if val < 87.5 or val > 107.9:
- val = 88.0
- rx = RadioSettingValueFloat(87.5, 107.9, val, 0.1, 1)
- rset = RadioSetting("fmfrqs.fmcur", "Manual FM Freq (MHz)", rx)
- rset.set_apply_callback(myset_freq, _fmx, "fmcur", 40)
- fmb.append(rset)
-
- options = ["5", "50", "100", "200(USA)"] # 5 is not used
- rx = RadioSettingValueList(options, options[_sets.fmstep])
- rset = RadioSetting("setstuf.fmstep", "FM Freq Step (KHz)", rx)
- fmb.append(rset)
-
- # FM Scan Range fmsclo and fmschi are unknown memory locations,
- # Not supported at this time
-
- rx = RadioSettingValueBoolean(bool(_sets.rxinhib))
- rset = RadioSetting("setstuf.rxinhib",
- "Rcvr Will Interupt FM (DW)", rx)
- fmb.append(rset)
-
- _fmfrq = self._memobj.fm_stations
- _fmap = self._memobj.fmmap
- for j in range(0, 25):
- val = _fmfrq[j].rxfreq
- if val == 0xFFFF:
- val = 88.0
- fmset = False
- else:
- val = (float(int(val)) / 40)
- # get fmmap bit value: 0 = enabled
- ndx = int(math.floor((j) / 8))
- bv = j % 8
- msk = 1 << bv
- vx = _fmap.fmset[ndx]
- fmset = not bool(vx & msk)
- rx = RadioSettingValueBoolean(fmset)
- rset = RadioSetting("fmmap.fmset/%d" % j,
- "FM Preset %02d" % (j + 1), rx)
- rset.set_apply_callback(myset_mask, _fmap, "fmset", j)
- fmb.append(rset)
-
- rx = RadioSettingValueFloat(87.5, 107.9, val, 0.1, 1)
- rset = RadioSetting("fm_stations/%d.rxfreq" % j,
- " Preset %02d Freq" % (j + 1), rx)
- # This callback uses the array index
- rset.set_apply_callback(myset_fmfrq, _fmfrq, "rxfreq", j)
- fmb.append(rset)
-
- # DTMF Settings
- options = [str(x) for x in range(4, 16)]
- rx = RadioSettingValueList(options, options[_sets.dtmfspd])
- rset = RadioSetting("setstuf.dtmfspd",
- "Tx Speed (digits/sec)", rx)
- dtmf.append(rset)
-
- options = [str(x) for x in range(0, 1100, 100)]
- rx = RadioSettingValueList(options, options[_sets.dtmfdig1time])
- rset = RadioSetting("setstuf.dtmfdig1time",
- "Tx 1st Digit Time (ms)", rx)
- dtmf.append(rset)
-
- options = [str(x) for x in range(100, 1100, 100)]
- rx = RadioSettingValueList(options, options[_sets.dtmfdig1dly])
- rset = RadioSetting("setstuf.dtmfdig1dly",
- "Tx 1st Digit Delay (ms)", rx)
- dtmf.append(rset)
-
- options = ["0", "100", "500", "1000"]
- rx = RadioSettingValueList(options, options[_sets.dtmfspms])
- rset = RadioSetting("setstuf.dtmfspms",
- "Tx Star & Pound Time (ms)", rx)
- dtmf.append(rset)
-
- options = ["None"] + [str(x) for x in range(600, 2100, 100)]
- rx = RadioSettingValueList(options, options[_sets.codespctim])
- rset = RadioSetting("setstuf.codespctim",
- "Tx Code Space Time (ms)", rx)
- dtmf.append(rset)
-
- rx = RadioSettingValueBoolean(bool(_sets.codeabcd))
- rset = RadioSetting("setstuf.codeabcd", "Tx Codes A,B,C,D", rx)
- dtmf.append(rset)
-
- rx = RadioSettingValueBoolean(bool(_sets.dtmfside))
- rset = RadioSetting("setstuf.dtmfside", "DTMF Side Tone", rx)
- dtmf.append(rset)
-
- options = ["Off", "A", "B", "C", "D"]
- rx = RadioSettingValueList(options, options[_sets.grpcode])
- rset = RadioSetting("setstuf.grpcode", "Rx Group Code", rx)
- dtmf.append(rset)
-
- options = ["Off"] + [str(x) for x in range(1, 16)]
- rx = RadioSettingValueList(options, options[_sets.autoresettmo])
- rset = RadioSetting("setstuf.autoresettmo",
- "Rx Auto Reset Timeout (secs)", rx)
- dtmf.append(rset)
-
- rx = RadioSettingValueBoolean(bool(_sets.txdecode))
- rset = RadioSetting("setstuf.txdecode", "Tx Decode", rx)
- dtmf.append(rset)
-
- rx = RadioSettingValueBoolean(bool(_sets.idedit))
- rset = RadioSetting("setstuf.idedit", "Allow ANI Code Edit", rx)
- dtmf.append(rset)
-
- options = [str(x) for x in range(500, 1600, 100)]
- rx = RadioSettingValueList(options, options[_sets.decodetmo])
- rset = RadioSetting("setstuf.decodetmo",
- "Rx Decode Timeout (ms)", rx)
- dtmf.append(rset)
-
- options = ["Tx & Rx Inhibit", "Tx Inhibit"]
- rx = RadioSettingValueList(options, options[_sets.stuntype])
- rset = RadioSetting("setstuf.stuntype", "Stun Type", rx)
- dtmf.append(rset)
-
- sx = unpack_str(_sets.stuncode, _sets.stuncnt, 5)
- rx = RadioSettingValueString(0, 10, sx)
- rset = RadioSetting("setstuf.stuncode", "Stun Code", rx)
- rset.set_apply_callback(pack_chars, _sets,
- "stuncode", "stuncnt", 10)
- dtmf.append(rset)
-
- # Frequency ranges
- rx = RadioSettingValueBoolean(bool(_sets.frqr1))
- rset = RadioSetting("setstuf.frqr1", "Freq Range 1 (UHF)", rx)
- rset.set_doc("Enable the UHF frequency bank.")
- frng.append(rset)
-
- rx = RadioSettingValueBoolean(bool(_sets.frqr2))
- rset = RadioSetting("setstuf.frqr2", "Freq Range 2 (VHF)", rx)
- rset.set_doc("Enable the VHF frequency bank.")
- frng.append(rset)
-
- mod_se = True # UV8000SE has 3rd freq bank
- if mod_se:
- rx = RadioSettingValueBoolean(bool(_sets.frqr3))
- rset = RadioSetting("setstuf.frqr3", "Freq Range 3 (220Mhz)", rx)
- rset.set_doc("Enable the 220MHz frequency bank.")
- frng.append(rset)
-
- frqm = 100000
- val = _sets.frqr1lo / frqm
- rx = RadioSettingValueFloat(400.0, 520.0, val, 0.005, 3)
- rset = RadioSetting("setstuf.frqr1lo",
- "UHF Range Low Limit (MHz)", rx)
- rset.set_apply_callback(myset_freq, _sets, "frqr1lo", frqm)
- rset.set_doc("Low limit of the UHF frequency bank.")
- frng.append(rset)
-
- val = _sets.frqr1hi / frqm
- rx = RadioSettingValueFloat(400.0, 520.0, val, 0.005, 3)
- rset = RadioSetting("setstuf.frqr1hi",
- "UHF Range High Limit (MHz)", rx)
- rset.set_apply_callback(myset_freq, _sets, "frqr1hi", frqm)
- rset.set_doc("High limit of the UHF frequency bank.")
- frng.append(rset)
-
- val = _sets.frqr2lo / frqm
- rx = RadioSettingValueFloat(136.0, 174.0, val, 0.005, 3)
- rset = RadioSetting("setstuf.frqr2lo",
- "VHF Range Low Limit (MHz)", rx)
- rset.set_apply_callback(myset_freq, _sets, "frqr2lo", frqm)
- rset.set_doc("Low limit of the VHF frequency bank.")
- frng.append(rset)
-
- val = _sets.frqr2hi / frqm
- rx = RadioSettingValueFloat(136.0, 174.0, val, 0.005, 3)
- rset = RadioSetting("setstuf.frqr2hi",
- "VHF Range High Limit (MHz)", rx)
- rset.set_apply_callback(myset_freq, _sets, "frqr2hi", frqm)
- rset.set_doc("High limit of the VHF frequency bank.")
- frng.append(rset)
-
- if mod_se:
- val = _sets.frqr3lo / frqm
- if val < 220.0:
- val = 220.0
- rx = RadioSettingValueFloat(220.0, 260.0, val, 0.005, 3)
- rset = RadioSetting("setstuf.frqr3lo",
- "1.25m Range Low Limit (MHz)", rx)
- rset.set_apply_callback(myset_freq, _sets, "frqr3lo", frqm)
- frng.append(rset)
-
- val = _sets.frqr3hi / frqm
- if val < 220.0:
- val = 260.0
- rx = RadioSettingValueFloat(220.0, 260.0, val, 0.005, 3)
- rset = RadioSetting("setstuf.frqr3hi",
- "1.25m Range High Limit (MHz)", rx)
- rset.set_apply_callback(myset_freq, _sets, "frqr3hi", 1000)
- frng.append(rset)
-
- return group # END get_settings()
-
- def set_settings(self, settings):
- _settings = self._memobj.setstuf
- _mem = self._memobj
- for element in settings:
- if not isinstance(element, RadioSetting):
- self.set_settings(element)
- continue
- else:
- try:
- name = element.get_name()
- if "." in name:
- bits = name.split(".")
- obj = self._memobj
- for bit in bits[:-1]:
- if "/" in bit:
- bit, index = bit.split("/", 1)
- index = int(index)
- obj = getattr(obj, bit)[index]
- else:
- obj = getattr(obj, bit)
- setting = bits[-1]
- else:
- obj = _settings
- setting = element.get_name()
-
- if element.has_apply_callback():
- LOG.debug("Using apply callback")
- element.run_apply_callback()
- elif element.value.get_mutable():
- LOG.debug("Setting %s = %s" % (setting, element.value))
- setattr(obj, setting, element.value)
- except Exception, e:
- LOG.debug(element.get_name())
- raise
+# Copyright 2020: Rick DeWitt (RJD), <aa0rd at yahoo.com>
+# Version 1.1 for TYT-UV8000D/E
+# V1.1 fixes issue #8339 Priority scan channel: OFF
+# Thanks to Damon Schaefer (K9CQB) and the Loudoun County, VA ARES
+# club for the donated radio.
+# And thanks to Ian Harris (VA3IHX) for decoding the memory map.
+#
+# 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 <http://www.gnu.org/licenses/>.
+
+import time
+import struct
+import logging
+import re
+import math
+from chirp import chirp_common, directory, memmap
+from chirp import bitwise, errors, util
+from chirp.settings import RadioSettingGroup, RadioSetting, \
+ RadioSettingValueBoolean, RadioSettingValueList, \
+ RadioSettingValueString, RadioSettingValueInteger, \
+ RadioSettingValueFloat, RadioSettings, InvalidValueError
+from textwrap import dedent
+
+LOG = logging.getLogger(__name__)
+
+MEM_FORMAT = """
+struct chns {
+ ul32 rxfreq;
+ ul32 txfreq;
+ u8 rxtone[2];
+ u8 txtone[2];
+ u8 wide:1 // 0x0c
+ vox_on:1
+ chunk01:1
+ bcl:1 // inv bool
+ epilogue:1
+ power:1
+ chunk02:1
+ chunk03:1;
+ u8 ani:1 // 0x0d inv
+ chunk08:1
+ ptt:2
+ chpad04:4;
+ u8 chunk05; // 0x0e
+ u16 id_code; // 0x0f, 10
+ u8 chunk06;
+ u8 name[7];
+ ul32 chpad06; // Need 56 byte pad
+ ul16 chpad07;
+ u8 chpad08;
+};
+
+struct fm_chn {
+ ul16 rxfreq;
+};
+
+struct frqx {
+ ul32 rxfreq;
+ ul24 ofst;
+ u8 fqunk01:4 // 0x07
+ funk10:2
+ duplx:2;
+ u8 rxtone[2]; // 0x08, 9
+ u8 txtone[2]; // 0x0a, b
+ u8 wide:1 // 0x0c
+ vox_on:1
+ funk11:1
+ bcl:1 // inv bool
+ epilogue:1
+ power:1
+ fqunk02:2;
+ u8 ani:1 // 0x0d inv bool
+ fqunk03:1
+ ptt:2
+ fqunk12:1
+ fqunk04:3;
+ u8 fqunk07; // 0x0e
+ u16 id_code; // 0x0f, 0x10
+ u8 name[7]; // dummy
+ u8 fqunk09[8]; // empty bytes after 1st entry
+};
+
+struct bitmap {
+ u8 map[16];
+};
+
+#seekto 0x0010;
+struct chns chan_mem[128];
+
+#seekto 0x1010;
+struct frqx frq[2];
+
+#seekto 0x1050;
+struct fm_chn fm_stations[25];
+
+#seekto 0x1080;
+struct {
+ u8 fmunk01[14];
+ ul16 fmcur;
+} fmfrqs;
+
+#seekto 0x1190;
+struct bitmap chnmap;
+
+#seekto 0x11a0;
+struct bitmap skpchns;
+
+#seekto 0x011b0;
+struct {
+ u8 fmset[4];
+} fmmap;
+
+#seekto 0x011b4;
+struct {
+ u8 setunk01[4];
+ u8 setunk02[3];
+ u8 chs_name:1 // 0x11bb
+ txsel:1
+ dbw:1
+ setunk05:1
+ ponfmchs:2
+ ponchs:2;
+ u8 voltx:2 // 0x11bc
+ setunk04:1
+ keylok:1
+ setunk07:1
+ batsav:3;
+ u8 setunk09:1 // 0x11bd
+ rxinhib:1
+ rgrbeep:1 // inv bool
+ lampon:2
+ voice:2
+ beepon:1;
+ u8 setunk11:1 // 0x11be
+ manualset:1
+ xbandon:1 // inv
+ xbandenable:1
+ openmsg:2
+ ledclr:2;
+ u8 tot:4 // 0x11bf
+ sql:4;
+ u8 setunk27:1 // 0x11c0
+ voxdelay:2
+ setunk28:1
+ voxgain:4;
+ u8 fmstep:4 // 0x11c1
+ freqstep:4;
+ u8 scanspeed:4 // 0x11c2
+ scanmode:4;
+ u8 scantmo; // 0x11c3
+ u8 prichan; // 0x11c4
+ u8 setunk12:4 // 0x11c5
+ supersave:4;
+ u8 setunk13;
+ u8 fmsclo; // 0x11c7 ??? placeholder
+ u8 radioname[7]; // hex char codes, not true ASCII
+ u8 fmschi; // ??? placeholder
+ u8 setunk14[3]; // 0x11d0
+ u8 setunk17[2]; // 0x011d3, 4
+ u8 setunk18:4
+ dtmfspd:4;
+ u8 dtmfdig1dly:4 // 0x11d6
+ dtmfdig1time:4;
+ u8 stuntype:1
+ setunk19:1
+ dtmfspms:2
+ grpcode:4;
+ u8 setunk20:1 // 0x11d8
+ txdecode:1
+ codeabcd:1
+ idedit:1
+ pttidon:2
+ setunk40:1,
+ dtmfside:1;
+ u8 setunk50:4,
+ autoresettmo:4;
+ u8 codespctim:4, // 0x11da
+ decodetmo:4;
+ u8 pttecnt:4 // 0x11db
+ pttbcnt:4;
+ lbcd dtmfdecode[3];
+ u8 setunk22;
+ u8 stuncnt; // 0x11e0
+ u8 stuncode[5];
+ u8 setunk60;
+ u8 setunk61;
+ u8 pttbot[8]; // 0x11e8-f
+ u8 ptteot[8]; // 0x11f0-7
+ u8 setunk62; // 0x11f8
+ u8 setunk63;
+ u8 setunk64; // 0x11fa
+ u8 setunk65;
+ u8 setunk66;
+ u8 manfrqyn; // 0x11fd
+ u8 setunk27:3
+ frqr3:1
+ setunk28:1
+ frqr2:1
+ setunk29:1
+ frqr1:1;
+ u8 setunk25;
+ ul32 frqr1lo; // 0x1200
+ ul32 frqr1hi;
+ ul32 frqr2lo;
+ ul32 frqr2hi;
+ ul32 frqr3lo; // 0x1210
+ ul32 frqr3hi;
+ u8 setunk26[8];
+} setstuf;
+
+#seekto 0x1260;
+struct {
+ u8 modnum[7];
+} modcode;
+
+#seekto 0x1300;
+struct {
+ char mod_num[9];
+} mod_id;
+"""
+
+MEM_SIZE = 0x1300
+BLOCK_SIZE = 0x10 # can read 0x20, but must write 0x10
+STIMEOUT = 2
+BAUDRATE = 4800
+# Channel power: 2 levels
+POWER_LEVELS = [chirp_common.PowerLevel("Low", watts=5.00),
+ chirp_common.PowerLevel("High", watts=10.00)]
+
+LIST_RECVMODE = ["QT/DQT", "QT/DQT + Signaling"]
+LIST_COLOR = ["Off", "Orange", "Blue", "Purple"]
+LIST_LEDSW = ["Auto", "On"]
+LIST_TIMEOUT = ["Off"] + ["%s" % x for x in range(30, 390, 30)]
+LIST_VFOMODE = ["Frequency Mode", "Channel Mode"]
+# Tones are numeric, Defined in \chirp\chirp_common.py
+TONES_CTCSS = sorted(chirp_common.TONES)
+# Converted to strings
+LIST_CTCSS = ["Off"] + [str(x) for x in TONES_CTCSS]
+# Now append the DxxxN and DxxxI DTCS codes from chirp_common
+for x in chirp_common.DTCS_CODES:
+ LIST_CTCSS.append("D{:03d}N".format(x))
+for x in chirp_common.DTCS_CODES:
+ LIST_CTCSS.append("D{:03d}R".format(x))
+LIST_BW = ["Narrow", "Wide"]
+LIST_SHIFT = ["off", "+", "-"]
+STEPS = [0.5, 2.5, 5.0, 6.25, 10.0, 12.5, 25.0, 37.5, 50.0, 100.0]
+LIST_STEPS = [str(x) for x in STEPS]
+LIST_VOXDLY = ["0.5", "1.0", "2.0", "3.0"] # LISTS must be strings
+LIST_PTT = ["Both", "EoT", "BoT", "Off"]
+
+SETTING_LISTS = {"tot": LIST_TIMEOUT, "wtled": LIST_COLOR,
+ "rxled": LIST_COLOR, "txled": LIST_COLOR,
+ "ledsw": LIST_LEDSW, "frq_chn_mode": LIST_VFOMODE,
+ "rx_tone": LIST_CTCSS, "tx_tone": LIST_CTCSS,
+ "rx_mode": LIST_RECVMODE, "fm_bw": LIST_BW,
+ "shift": LIST_SHIFT, "step": LIST_STEPS,
+ "vox_dly": LIST_VOXDLY, "ptt": LIST_PTT}
+
+
+def _clean_buffer(radio):
+ radio.pipe.timeout = 0.005
+ junk = radio.pipe.read(256)
+ radio.pipe.timeout = STIMEOUT
+ if junk:
+ LOG.debug("Got %i bytes of junk before starting" % len(junk))
+
+
+def _rawrecv(radio, amount):
+ """Raw read from the radio device"""
+ data = ""
+ try:
+ data = radio.pipe.read(amount)
+ except Exception:
+ _exit_program_mode(radio)
+ msg = "Generic error reading data from radio; check your cable."
+ raise errors.RadioError(msg)
+
+ if len(data) != amount:
+ _exit_program_mode(radio)
+ msg = "Error reading from radio: not the amount of data we want."
+ raise errors.RadioError(msg)
+
+ return data
+
+
+def _rawsend(radio, data):
+ """Raw send to the radio device"""
+ try:
+ radio.pipe.write(data)
+ except Exception:
+ raise errors.RadioError("Error sending data to radio")
+
+
+def _make_frame(cmd, addr, length, data=""):
+ """Pack the info in the headder format"""
+ frame = struct.pack(">shB", cmd, addr, length)
+ # Add the data if set
+ if len(data) != 0:
+ frame += data
+ # Return the data
+ return frame
+
+
+def _recv(radio, addr, length):
+ """Get data from the radio """
+
+ data = _rawrecv(radio, length)
+
+ # DEBUG
+ LOG.info("Response:")
+ LOG.debug(util.hexprint(data))
+
+ return data
+
+
+def _do_ident(radio):
+ """Put the radio in PROGRAM mode & identify it"""
+ radio.pipe.baudrate = BAUDRATE
+ radio.pipe.parity = "N"
+ radio.pipe.timeout = STIMEOUT
+
+ # Flush input buffer
+ _clean_buffer(radio)
+
+ magic = "PROGRAMa"
+ _rawsend(radio, magic)
+ ack = _rawrecv(radio, 1)
+ # LOG.warning("PROGa Ack:" + util.hexprint(ack))
+ if ack != "\x06":
+ _exit_program_mode(radio)
+ if ack:
+ LOG.debug(repr(ack))
+ raise errors.RadioError("Radio did not respond")
+ magic = "PROGRAMb"
+ _rawsend(radio, magic)
+ ack = _rawrecv(radio, 1)
+ if ack != "\x06":
+ _exit_program_mode(radio)
+ if ack:
+ LOG.debug(repr(ack))
+ raise errors.RadioError("Radio did not respond to B")
+ magic = chr(0x02)
+ _rawsend(radio, magic)
+ ack = _rawrecv(radio, 1) # s/b: 0x50
+ magic = _rawrecv(radio, 7) # s/b TC88...
+ magic = "MTC88CUMHS3E7BN-"
+ _rawsend(radio, magic)
+ ack = _rawrecv(radio, 1) # s/b 0x80
+ magic = chr(0x06)
+ _rawsend(radio, magic)
+ ack = _rawrecv(radio, 1)
+
+ return True
+
+
+def _exit_program_mode(radio):
+ endframe = "E"
+ _rawsend(radio, endframe)
+
+
+def _download(radio):
+ """Get the memory map"""
+
+ # Put radio in program mode and identify it
+ _do_ident(radio)
+
+ # UI progress
+ status = chirp_common.Status()
+ status.cur = 0
+ status.max = MEM_SIZE / BLOCK_SIZE
+ status.msg = "Cloning from radio..."
+ radio.status_fn(status)
+
+ data = ""
+ for addr in range(0, MEM_SIZE, BLOCK_SIZE):
+ frame = _make_frame("R", addr, BLOCK_SIZE)
+ # DEBUG
+ LOG.info("Request sent:")
+ LOG.debug("Frame=" + util.hexprint(frame))
+
+ # Sending the read request
+ _rawsend(radio, frame)
+ dx = _rawrecv(radio, 4)
+
+ # Now we read data
+ d = _recv(radio, addr, BLOCK_SIZE)
+ # LOG.warning("Data= " + util.hexprint(d))
+
+ # Aggregate the data
+ data += d
+
+ # UI Update
+ status.cur = addr / BLOCK_SIZE
+ status.msg = "Cloning from radio..."
+ radio.status_fn(status)
+
+ _exit_program_mode(radio)
+
+ return data
+
+
+def _upload(radio):
+ """Upload procedure"""
+ # Put radio in program mode and identify it
+ _do_ident(radio)
+
+ # UI progress
+ status = chirp_common.Status()
+ status.cur = 0
+ status.max = MEM_SIZE / BLOCK_SIZE
+ status.msg = "Cloning to radio..."
+ radio.status_fn(status)
+
+ # The fun starts here
+ for addr in range(0, MEM_SIZE, BLOCK_SIZE):
+ # Sending the data
+ data = radio.get_mmap()[addr:addr + BLOCK_SIZE]
+
+ frame = _make_frame("W", addr, BLOCK_SIZE, data)
+ # LOG.warning("Frame:%s:" % util.hexprint(frame))
+ _rawsend(radio, frame)
+
+ # Receiving the response
+ ack = _rawrecv(radio, 1)
+ if ack != "\x06":
+ _exit_program_mode(radio)
+ msg = "Bad ack writing block 0x%04x" % addr
+ raise errors.RadioError(msg)
+
+ # UI Update
+ status.cur = addr / BLOCK_SIZE
+ status.msg = "Cloning to radio..."
+ radio.status_fn(status)
+
+ _exit_program_mode(radio)
+
+
+def set_tone(_mem, txrx, ctdt, tval, pol):
+ """Set rxtone[] or txtone[] word values as decimal bytes"""
+ # txrx: Boolean T= set Rx tones, F= set Tx tones
+ # ctdt: Boolean T = CTCSS, F= DTCS
+ # tval = integer tone freq (*10) or DTCS code
+ # pol = string for DTCS polarity "R" or "N"
+ xv = int(str(tval), 16)
+ if txrx: # True = set rxtones
+ _mem.rxtone[0] = xv & 0xFF # Low byte
+ _mem.rxtone[1] = (xv >> 8) # Hi byte
+ if not ctdt: # dtcs,
+ if pol == "R":
+ _mem.rxtone[1] = _mem.rxtone[1] | 0xC0
+ else:
+ _mem.rxtone[1] = _mem.rxtone[1] | 0x80
+ else: # txtones
+ _mem.txtone[0] = xv & 0xFF # Low byte
+ _mem.txtone[1] = (xv >> 8)
+ if not ctdt: # dtcs
+ if pol == "R":
+ _mem.txtone[1] = _mem.txtone[1] | 0xC0
+ else:
+ _mem.txtone[1] = _mem.txtone[1] | 0x80
+
+
+
+def _do_map(chn, sclr, mary):
+ """Set or Clear the chn (1-128) bit in mary[] word array map"""
+ # chn is 1-based channel, sclr:1 = set, 0= = clear, 2= return state
+ # mary[] is u8 array, but the map is by nibbles
+ ndx = int(math.floor((chn - 1) / 8))
+ bv = (chn - 1) % 8
+ msk = 1 << bv
+ mapbit = sclr
+ if sclr == 1: # Set the bit
+ mary[ndx] = mary[ndx] | msk
+ elif sclr == 0: # clear
+ mary[ndx] = mary[ndx] & (~ msk) # ~ is complement
+ else: # return current bit state
+ mapbit = 0
+ if (mary[ndx] & msk) > 0:
+ mapbit = 1
+ return mapbit
+
+
+ at directory.register
+class THUV8000Radio(chirp_common.CloneModeRadio):
+ """TYT UV8000D Radio"""
+ VENDOR = "TYT"
+ MODEL = "TH-UV8000"
+ MODES = ["NFM", "FM"]
+ TONES = chirp_common.TONES
+ DTCS_CODES = sorted(chirp_common.DTCS_CODES + [645])
+ NAME_LENGTH = 7
+ DTMF_CHARS = list("0123456789ABCD*#")
+ # NOTE: SE Model supports 220-260 MHz
+ # The following bands are the the range the radio is capable of,
+ # not the legal FCC amateur bands
+ VALID_BANDS = [(87500000, 107900000), (136000000, 174000000),
+ (220000000, 260000000), (400000000, 520000000)]
+
+ # Valid chars on the LCD
+ VALID_CHARS = chirp_common.CHARSET_ALPHANUMERIC + \
+ "`!\"#$%&'()*+,-./:;<=>?@[]^_"
+
+ # Special Channels Declaration
+ # WARNING Indecis are hard wired in get/set_memory code !!!
+ # Channels print in + increasing index order (most negative first)
+ SPECIAL_MEMORIES = {
+ "UpVFO": -2,
+ "LoVFO": -1
+ }
+ FIRST_FREQ_INDEX = -1
+ LAST_FREQ_INDEX = -2
+
+ SPECIAL_MEMORIES_REV = dict(zip(SPECIAL_MEMORIES.values(),
+ SPECIAL_MEMORIES.keys()))
+
+ @classmethod
+ def get_prompts(cls):
+ rp = chirp_common.RadioPrompts()
+ rp.info = \
+ ('Click on the "Special Channels" toggle-button of the memory '
+ 'editor to see/set the upper and lower frequency-mode values.\n')
+
+ rp.pre_download = _(dedent("""\
+ Follow these instructions to download the radio memory:
+
+ 1 - Turn off your radio
+ 2 - Connect your interface cable
+ 3 - Turn on your radio, volume @ 50%
+ 4 - Radio > Download from radio
+ """))
+ rp.pre_upload = _(dedent("""\
+ Follow these instructions to upload the radio memory:
+
+ 1 - Turn off your radio
+ 2 - Connect your interface cable
+ 3 - Turn on your radio, volume @ 50%
+ 4 - Radio > Upload to radio
+ """))
+ return rp
+
+ def get_features(self):
+ rf = chirp_common.RadioFeatures()
+ # .has. attributes are boolean, .valid. are lists
+ rf.has_settings = True
+ rf.has_bank = False
+ rf.has_comment = False
+ rf.has_nostep_tuning = True # Radio accepts any entered freq
+ rf.has_tuning_step = False # Not as chan feature
+ rf.can_odd_split = False
+ rf.has_name = True
+ rf.has_offset = True
+ rf.has_mode = True
+ rf.has_dtcs = True
+ rf.has_rx_dtcs = True
+ rf.has_dtcs_polarity = True
+ rf.has_ctone = True
+ rf.has_cross = True
+ rf.has_sub_devices = False
+ rf.valid_name_length = self.NAME_LENGTH
+ rf.valid_modes = self.MODES
+ rf.valid_characters = self.VALID_CHARS
+ rf.valid_duplexes = ["-", "+", "off", ""]
+ rf.valid_tmodes = ['', 'Tone', 'TSQL', 'DTCS', 'Cross']
+ rf.valid_cross_modes = ["Tone->Tone", "DTCS->", "->DTCS",
+ "Tone->DTCS", "DTCS->Tone", "->Tone",
+ "DTCS->DTCS"]
+ rf.valid_power_levels = POWER_LEVELS
+ rf.valid_dtcs_codes = self.DTCS_CODES
+ rf.valid_bands = self.VALID_BANDS
+ rf.memory_bounds = (1, 128)
+ rf.valid_skips = ["", "S"]
+ rf.valid_special_chans = sorted(self.SPECIAL_MEMORIES.keys())
+ return rf
+
+ def sync_in(self):
+ """Download from radio"""
+ try:
+ data = _download(self)
+ except errors.RadioError:
+ # Pass through any real errors we raise
+ raise
+ except Exception:
+ # If anything unexpected happens, make sure we raise
+ # a RadioError and log the problem
+ LOG.exception('Unexpected error during download')
+ raise errors.RadioError('Unexpected error communicating '
+ 'with the radio')
+ self._mmap = memmap.MemoryMap(data)
+ self.process_mmap()
+
+ def sync_out(self):
+ """Upload to radio"""
+
+ try:
+ _upload(self)
+ except Exception:
+ # If anything unexpected happens, make sure we raise
+ # a RadioError and log the problem
+ LOG.exception('Unexpected error during upload')
+ raise errors.RadioError('Unexpected error communicating '
+ 'with the radio')
+
+ def process_mmap(self):
+ """Process the mem map into the mem object"""
+ self._memobj = bitwise.parse(MEM_FORMAT, self._mmap)
+
+ def get_raw_memory(self, number):
+ return repr(self._memobj.memory[number - 1])
+
+ def get_memory(self, number):
+ if isinstance(number, str):
+ return self._get_special(number)
+ elif number < 0:
+ # I can't stop delete operation from loosing extd_number but
+ # I know how to get it back
+ return self._get_special(self.SPECIAL_MEMORIES_REV[number])
+ else:
+ return self._get_normal(number)
+
+ def set_memory(self, memory):
+ """A value in a UI column for chan 'number' has been modified."""
+ # update all raw channel memory values (_mem) from UI (mem)
+ if memory.number < 0:
+ return self._set_special(memory)
+ else:
+ return self._set_normal(memory)
+
+ def _get_normal(self, number):
+ # radio first channel is 1, mem map is base 0
+ _mem = self._memobj.chan_mem[number - 1]
+ mem = chirp_common.Memory()
+ mem.number = number
+
+ return self._get_memory(mem, _mem)
+
+ def _get_memory(self, mem, _mem):
+ """Convert raw channel memory data into UI columns"""
+ mem.extra = RadioSettingGroup("extra", "Extra")
+
+ if _mem.get_raw()[0] == "\xff":
+ mem.empty = True
+ return mem
+
+ mem.empty = False
+ # This function process both 'normal' and Freq up/down' entries
+ mem.freq = int(_mem.rxfreq) * 10
+ mem.power = POWER_LEVELS[_mem.power]
+ mem.mode = self.MODES[_mem.wide]
+ dtcs_pol = ["N", "N"]
+
+ if _mem.rxtone[0] == 0xFF:
+ rxmode = ""
+ elif _mem.rxtone[1] < 0x26:
+ # CTCSS
+ rxmode = "Tone"
+ tonehi = int(str(_mem.rxtone[1])[2:])
+ tonelo = int(str(_mem.rxtone[0])[2:])
+ mem.ctone = int(tonehi * 100 + tonelo) / 10.0
+ else:
+ # Digital
+ rxmode = "DTCS"
+ tonehi = int(str(_mem.rxtone[1] & 0x3f))
+ tonelo = int(str(_mem.rxtone[0])[2:])
+ mem.rx_dtcs = int(tonehi * 100 + tonelo)
+ if (_mem.rxtone[1] & 0x40) != 0:
+ dtcs_pol[1] = "R"
+
+ if _mem.txtone[0] == 0xFF:
+ txmode = ""
+ elif _mem.txtone[1] < 0x26:
+ # CTCSS
+ txmode = "Tone"
+ tonehi = int(str(_mem.txtone[1])[2:])
+ tonelo = int(str(_mem.txtone[0])[2:])
+ mem.rtone = int(tonehi * 100 + tonelo) / 10.0
+ else:
+ # Digital
+ txmode = "DTCS"
+ tonehi = int(str(_mem.txtone[1] & 0x3f))
+ tonelo = int(str(_mem.txtone[0])[2:])
+ mem.dtcs = int(tonehi * 100 + tonelo)
+ if (_mem.txtone[1] & 0x40) != 0:
+ dtcs_pol[0] = "R"
+
+ mem.tmode = ""
+ if txmode == "Tone" and not rxmode:
+ mem.tmode = "Tone"
+ elif txmode == rxmode and txmode == "Tone" and mem.rtone == mem.ctone:
+ mem.tmode = "TSQL"
+ elif txmode == rxmode and txmode == "DTCS" and mem.dtcs == mem.rx_dtcs:
+ mem.tmode = "DTCS"
+ elif rxmode or txmode:
+ mem.tmode = "Cross"
+ mem.cross_mode = "%s->%s" % (txmode, rxmode)
+
+ mem.dtcs_polarity = "".join(dtcs_pol)
+
+ # Now test the mem.number to process special vs normal
+ if mem.number >= 0: # Normal
+ mem.name = ""
+ for i in range(self.NAME_LENGTH): # 0 - 6
+ mem.name += chr(_mem.name[i] + 32)
+ mem.name = mem.name.rstrip() # remove trailing spaces
+
+ if _mem.txfreq == 0xFFFFFFFF:
+ # TX freq not set
+ mem.duplex = "off"
+ mem.offset = 0
+ elif int(_mem.rxfreq) == int(_mem.txfreq):
+ mem.duplex = ""
+ mem.offset = 0
+ else:
+ mem.duplex = int(_mem.rxfreq) > int(_mem.txfreq) \
+ and "-" or "+"
+ mem.offset = abs(int(_mem.rxfreq) - int(_mem.txfreq)) * 10
+
+ if _do_map(mem.number, 2, self._memobj.skpchns.map) > 0:
+ mem.skip = "S"
+ else:
+ mem.skip = ""
+
+ else: # specials VFO
+ mem.name = "----"
+ mem.duplex = LIST_SHIFT[_mem.duplx]
+ mem.offset = int(_mem.ofst) * 10
+ mem.skip = ""
+ # End if specials
+
+ # Channel Extra settings: Only Boolean & List methods, no call-backs
+ rx = RadioSettingValueBoolean(bool(not _mem.bcl)) # Inverted bool
+ # NOTE: first param of RadioSetting is the object attribute name
+ rset = RadioSetting("bcl", "Busy Channel Lockout", rx)
+ mem.extra.append(rset)
+
+ rx = RadioSettingValueBoolean(bool(not _mem.vox_on))
+ rset = RadioSetting("vox_on", "Vox", rx)
+ mem.extra.append(rset)
+
+ rx = RadioSettingValueBoolean(bool(not _mem.ani))
+ rset = RadioSetting("ani", "Auto Number ID (ANI)", rx)
+ mem.extra.append(rset)
+
+ # ID code can't be done in extra - no Integer method or call-back
+
+ rx = RadioSettingValueList(LIST_PTT, LIST_PTT[_mem.ptt])
+ rset = RadioSetting("ptt", "Xmit PTT ID", rx)
+ mem.extra.append(rset)
+
+ rx = RadioSettingValueBoolean(bool(_mem.epilogue))
+ rset = RadioSetting("epilogue", "Epilogue/Tail", rx)
+ mem.extra.append(rset)
+
+ return mem
+
+ def _get_special(self, number):
+ mem = chirp_common.Memory()
+ mem.number = self.SPECIAL_MEMORIES[number]
+ mem.extd_number = number
+ # Unused attributes are ignored in Set_memory
+ if (mem.number == -1) or (mem.number == -2):
+ # Print Upper[1] first, and Lower[0] next
+ rx = 0
+ if mem.number == -2:
+ rx = 1
+ _mem = self._memobj.frq[rx]
+ # immutable = ["number", "extd_number", "name"]
+ mem = self._get_memory(mem, _mem)
+ else:
+ raise Exception("Sorry, you can't edit that special"
+ " memory channel %i." % mem.number)
+
+ # mem.immutable = immutable
+
+ return mem
+
+ def _set_memory(self, mem, _mem):
+ """Convert UI column data (mem) into MEM_FORMAT memory (_mem)."""
+ # At this point mem points to either normal or Freq chans
+ # These first attributes are common to all types
+ if mem.empty:
+ if mem.number > 0:
+ _mem.rxfreq = 0xffffffff
+ # Set 'empty' and 'skip' bits
+ _do_map(mem.number, 1, self._memobj.chnmap.map)
+ _do_map(mem.number, 1, self._memobj.skpchns.map)
+ elif mem.number == -2: # upper VFO Freq
+ _mem.rxfreq = 14652000 # VHF National Calling freq
+ elif mem.number == -1: # lower VFO
+ _mem.rxfreq = 44600000 # UHF National Calling freq
+ return
+
+ _mem.rxfreq = mem.freq / 10
+
+ if str(mem.power) == "Low":
+ _mem.power = 0
+ else:
+ _mem.power = 1
+
+ _mem.wide = self.MODES.index(mem.mode)
+
+ rxmode = ""
+ txmode = ""
+
+ if mem.tmode == "Tone":
+ txmode = "Tone"
+ elif mem.tmode == "TSQL":
+ rxmode = "Tone"
+ txmode = "TSQL"
+ elif mem.tmode == "DTCS":
+ rxmode = "DTCSSQL"
+ txmode = "DTCS"
+ elif mem.tmode == "Cross":
+ txmode, rxmode = mem.cross_mode.split("->", 1)
+
+ sx = mem.dtcs_polarity[1]
+ if rxmode == "":
+ _mem.rxtone[0] = 0xFF
+ _mem.rxtone[1] = 0xFF
+ elif rxmode == "Tone":
+ val = int(mem.ctone * 10)
+ i = set_tone(_mem, True, True, val, sx)
+ elif rxmode == "DTCSSQL":
+ i = set_tone(_mem, True, False, mem.dtcs, sx)
+ elif rxmode == "DTCS":
+ i = set_tone(_mem, True, False, mem.rx_dtcs, sx)
+
+ sx = mem.dtcs_polarity[0]
+ if txmode == "":
+ _mem.txtone[0] = 0xFF
+ _mem.txtone[1] = 0xFF
+ elif txmode == "Tone":
+ val = int(mem.rtone * 10)
+ i = set_tone(_mem, False, True, val, sx)
+ elif txmode == "TSQL":
+ val = int(mem.ctone * 10)
+ i = set_tone(_mem, False, True, val, sx)
+ elif txmode == "DTCS":
+ i = set_tone(_mem, False, False, mem.dtcs, sx)
+
+ if mem.number > 0: # Normal chans
+ for i in range(self.NAME_LENGTH):
+ pq = ord(mem.name.ljust(self.NAME_LENGTH)[i]) - 32
+ if pq < 0:
+ pq = 0
+ _mem.name[i] = pq
+
+ if mem.duplex == "off":
+ _mem.txfreq = 0xFFFFFFFF
+ elif mem.duplex == "+":
+ _mem.txfreq = (mem.freq + mem.offset) / 10
+ elif mem.duplex == "-":
+ _mem.txfreq = (mem.freq - mem.offset) / 10
+ else:
+ _mem.txfreq = mem.freq / 10
+
+ # Set the channel map bit FALSE = Enabled
+ _do_map(mem.number, 0, self._memobj.chnmap.map)
+ # Skip
+ if mem.skip == "S":
+ _do_map(mem.number, 1, self._memobj.skpchns.map)
+ else:
+ _do_map(mem.number, 0, self._memobj.skpchns.map)
+
+ else: # Freq (VFO) chans
+ _mem.duplx = 0
+ _mem.ofst = 0
+ if mem.duplex == "+":
+ _mem.duplx = 1
+ _mem.ofst = mem.offset / 10
+ elif mem.duplex == "-":
+ _mem.duplx = 2
+ _mem.ofst = mem.offset / 10
+ for i in range(self.NAME_LENGTH):
+ _mem.name[i] = 0xff
+
+ # All mem.extra << Once the channel is defined
+ for setting in mem.extra:
+ # Overide list strings with signed value
+ if setting.get_name() == "ptt":
+ sx = str(setting.value)
+ for i in range(0, 4):
+ if sx == LIST_PTT[i]:
+ val = i
+ setattr(_mem, "ptt", val)
+ elif setting.get_name() == "epilogue": # not inverted bool
+ setattr(_mem, setting.get_name(), setting.value)
+ else: # inverted booleans
+ setattr(_mem, setting.get_name(), not setting.value)
+
+ def _set_special(self, mem):
+
+ cur_mem = self._get_special(self.SPECIAL_MEMORIES_REV[mem.number])
+
+ if mem.number == -2: # upper frq[1]
+ _mem = self._memobj.frq[1]
+ elif mem.number == -1: # lower frq[0]
+ _mem = self._memobj.frq[0]
+ else:
+ raise Exception("Sorry, you can't edit that special memory.")
+
+ self._set_memory(mem, _mem) # Now update the _mem
+
+ def _set_normal(self, mem):
+ _mem = self._memobj.chan_mem[mem.number - 1]
+
+ self._set_memory(mem, _mem)
+
+ def get_settings(self):
+ """Translate the MEM_FORMAT structs into setstuf in the UI"""
+ # Define mem struct write-back shortcuts
+ _sets = self._memobj.setstuf
+ _fmx = self._memobj.fmfrqs
+
+ basic = RadioSettingGroup("basic", "Basic Settings")
+ adv = RadioSettingGroup("adv", "Other Settings")
+ fmb = RadioSettingGroup("fmb", "FM Broadcast")
+ scn = RadioSettingGroup("scn", "Scan Settings")
+ dtmf = RadioSettingGroup("dtmf", "DTMF Settings")
+ frng = RadioSettingGroup("frng", "Frequency Ranges")
+ group = RadioSettings(basic, adv, scn, fmb, dtmf, frng)
+
+ def my_val_list(setting, obj, atrb):
+ """Callback:from ValueList with non-sequential, actual values."""
+ # This call back also used in get_settings
+ value = int(str(setting.value)) # Get the integer value
+ setattr(obj, atrb, value)
+
+ def my_adjraw(setting, obj, atrb, fix):
+ """Callback from Integer add or subtract fix from value."""
+ vx = int(str(setting.value))
+ value = vx + int(fix)
+ if value < 0:
+ value = 0
+ setattr(obj, atrb, value)
+
+ def my_strnam(setting, obj, atrb, mln):
+ """Callback from String to build u8 array with -32 offset."""
+ # mln is max string length
+ ary = []
+ knt = mln
+ for j in range(mln - 1, -1, -1): # Strip trailing spaces or nulls
+ pq = str(setting.value)[j]
+ if pq == "" or pq == " ":
+ knt = knt - 1
+ else:
+ break
+ for j in range(mln): # 0 to mln-1
+ pq = str(setting.value).ljust(mln)[j]
+ if j < knt:
+ ary.append(ord(pq) - 32)
+ else:
+ ary.append(0)
+ setattr(obj, atrb, ary)
+
+ def unpack_str(cary, cknt, mxw):
+ """Convert u8 nibble array to a string: NOT a callback."""
+ # cknt is char count, 2/word; mxw is max WORDS
+ stx = ""
+ mty = True
+ for i in range(mxw): # unpack entire array
+ nib = (cary[i] & 0xf0) >> 4 # LE, Hi nib first
+ if nib != 0xf:
+ mty = False
+ stx += format(nib, '0X')
+ nib = cary[i] & 0xf
+ if nib != 0xf:
+ mty = False
+ stx += format(nib, '0X')
+ stx = stx[:cknt]
+ if mty: # all ff, empty string
+ sty = ""
+ else:
+ # Convert E to #, F to *
+ sty = ""
+ for i in range(cknt):
+ if stx[i] == "E":
+ sty += "#"
+ elif stx[i] == "F":
+ sty += "*"
+ else:
+ sty += stx[i]
+
+ return sty
+
+ def pack_chars(setting, obj, atrstr, atrcnt, mxl):
+ """Callback to build 0-9,A-D,*# nibble array from string"""
+ # cknt is generated char count, 2 chars per word
+ # String will be f padded to mxl
+ # Chars are stored as hex values
+ # store cknt-1 in atrcnt, 0xf if empty
+ cknt = 0
+ ary = []
+ stx = str(setting.value).upper()
+ stx = stx.strip() # trim spaces
+ # Remove illegal characters first
+ sty = ""
+ for j in range(0, len(stx)):
+ if stx[j] in self.DTMF_CHARS:
+ sty += stx[j]
+ for j in range(mxl):
+ if j < len(sty):
+ if sty[j] == "#":
+ chrv = 0xE
+ elif sty[j] == "*":
+ chrv = 0xF
+ else:
+ chrv = int(sty[j], 16)
+ cknt += 1 # char count
+ else: # pad to mxl, cknt does not increment
+ chrv = 0xF
+ if (j % 2) == 0: # odd count (0-based), high nibble
+ hi_nib = chrv
+ else: # even count, lower nibble
+ lo_nib = chrv
+ nibs = lo_nib | (hi_nib << 4)
+ ary.append(nibs) # append word
+ setattr(obj, atrstr, ary)
+ if setting.get_name() != "setstuf.stuncode": # cknt is actual
+ if cknt > 0:
+ cknt = cknt - 1
+ else:
+ cknt = 0xf
+ setattr(obj, atrcnt, cknt)
+
+ def myset_freq(setting, obj, atrb, mult):
+ """ Callback to set frequency by applying multiplier"""
+ value = int(float(str(setting.value)) * mult)
+ setattr(obj, atrb, value)
+
+ def my_invbool(setting, obj, atrb):
+ """Callback to invert the boolean """
+ bval = not setting.value
+ setattr(obj, atrb, bval)
+
+ def my_batsav(setting, obj, atrb):
+ """Callback to set batsav attribute """
+ stx = str(setting.value) # Off, 1:1...
+ if stx == "Off":
+ value = 0x1 # bit value 4 clear, ratio 1 = 1:2
+ elif stx == "1:1":
+ value = 0x4 # On, ratio 0 = 1:1
+ elif stx == "1:2":
+ value = 0x5 # On, ratio 1 = 1:2
+ elif stx == "1:3":
+ value = 0x6 # On, ratio 2 = 1:3
+ else:
+ value = 0x7 # On, ratio 3 = 1:4
+ # LOG.warning("Batsav stx:%s:, value= %x" % (stx, value))
+ setattr(obj, atrb, value)
+
+ def my_manfrq(setting, obj, atrb):
+ """Callback to set 2-byte manfrqyn yes/no """
+ # LOG.warning("Manfrq value = %d" % setting.value)
+ if (str(setting.value)) == "No":
+ value = 0xff
+ else:
+ value = 0xaa
+ setattr(obj, atrb, value)
+
+ def myset_mask(setting, obj, atrb, nx):
+ if bool(setting.value): # Enabled = 0
+ vx = 0
+ else:
+ vx = 1
+ _do_map(nx + 1, vx, self._memobj.fmmap.fmset)
+
+ def myset_fmfrq(setting, obj, atrb, nx):
+ """ Callback to set xx.x FM freq in memory as xx.x * 40"""
+ # in-valid even KHz freqs are allowed; to satisfy run_tests
+ vx = float(str(setting.value))
+ vx = int(vx * 40)
+ setattr(obj[nx], atrb, vx)
+
+ rx = RadioSettingValueInteger(1, 9, _sets.voxgain + 1)
+ rset = RadioSetting("setstuf.voxgain", "Vox Level", rx)
+ rset.set_apply_callback(my_adjraw, _sets, "voxgain", -1)
+ basic.append(rset)
+
+ rx = RadioSettingValueList(LIST_VOXDLY, LIST_VOXDLY[_sets.voxdelay])
+ rset = RadioSetting("setstuf.voxdelay", "Vox Delay (secs)", rx)
+ basic.append(rset)
+
+ rx = RadioSettingValueInteger(0, 9, _sets.sql)
+ rset = RadioSetting("setstuf.sql", "Squelch", rx)
+ basic.append(rset)
+
+ rx = RadioSettingValueList(LIST_STEPS, LIST_STEPS[_sets.freqstep])
+ rset = RadioSetting("setstuf.freqstep", "VFO Tune Step (KHz))", rx)
+ basic.append(rset)
+
+ rx = RadioSettingValueBoolean(bool(_sets.dbw)) # true logic
+ rset = RadioSetting("setstuf.dbw", "Dual Band Watch (D.WAIT)", rx)
+ basic.append(rset)
+
+ options = ["Off", "On", "Auto"]
+ rx = RadioSettingValueList(options, options[_sets.lampon])
+ rset = RadioSetting("setstuf.lampon", "Backlight (LED)", rx)
+ basic.append(rset)
+
+ options = ["Orange", "Purple", "Blue"]
+ rx = RadioSettingValueList(options, options[_sets.ledclr])
+ rset = RadioSetting("setstuf.ledclr", "Backlight Color (LIGHT)", rx)
+ basic.append(rset)
+
+ rx = RadioSettingValueBoolean(bool(_sets.beepon))
+ rset = RadioSetting("setstuf.beepon", "Keypad Beep", rx)
+ basic.append(rset)
+
+ rx = RadioSettingValueBoolean(bool(_sets.xbandenable))
+ rset = RadioSetting("setstuf.xbandenable", "Cross Band Allowed", rx)
+ basic.append(rset)
+
+ rx = RadioSettingValueBoolean(bool(not _sets.xbandon))
+ rset = RadioSetting("setstuf.xbandon", "Cross Band On", rx)
+ rset.set_apply_callback(my_invbool, _sets, "xbandon")
+ basic.append(rset)
+
+ rx = RadioSettingValueList(LIST_TIMEOUT, LIST_TIMEOUT[_sets.tot])
+ rset = RadioSetting("setstuf.tot", "TX Timeout (Secs)", rx)
+ basic.append(rset)
+
+ rx = RadioSettingValueBoolean(bool(not _sets.rgrbeep)) # Invert
+ rset = RadioSetting("setstuf.rgrbeep", "Beep at Eot (Roger)", rx)
+ rset.set_apply_callback(my_invbool, _sets, "rgrbeep")
+ basic.append(rset)
+
+ rx = RadioSettingValueBoolean(bool(not _sets.keylok))
+ rset = RadioSetting("setstuf.keylok", "Keypad AutoLock", rx)
+ rset.set_apply_callback(my_invbool, _sets, "keylok")
+ basic.append(rset)
+
+ options = ["None", "Message", "DC Volts"]
+ rx = RadioSettingValueList(options, options[_sets.openmsg])
+ rset = RadioSetting("setstuf.openmsg", "Power-On Display", rx)
+ basic.append(rset)
+
+ options = ["Channel Name", "Frequency"]
+ rx = RadioSettingValueList(options, options[_sets.chs_name])
+ rset = RadioSetting("setstuf.chs_name", "Display Name/Frq", rx)
+ basic.append(rset)
+
+ sx = ""
+ for i in range(7):
+ if _sets.radioname[i] != 0:
+ sx += chr(_sets.radioname[i] + 32)
+ rx = RadioSettingValueString(0, 7, sx)
+ rset = RadioSetting("setstuf.radioname", "Power-On Message", rx)
+ rset.set_apply_callback(my_strnam, _sets, "radioname", 7)
+ basic.append(rset)
+
+ # Advanced (Strange) Settings
+ options = ["Busy: Last Tx Band", "Edit: Current Band"]
+ rx = RadioSettingValueList(options, options[_sets.txsel])
+ rset = RadioSetting("setstuf.txsel", "Transmit Priority", rx)
+ rset.set_doc("'Busy' transmits on last band used, not current one.")
+ adv.append(rset)
+
+ options = ["Off", "English", "Unk", "Chinese"]
+ val = _sets.voice
+ rx = RadioSettingValueList(options, options[val])
+ rset = RadioSetting("setstuf.voice", "Voice", rx)
+ adv.append(rset)
+
+ options = ["Off", "1:1", "1:2", "1:3", "1:4"]
+ val = (_sets.batsav & 0x3) + 1 # ratio
+ if (_sets.batsav & 0x4) == 0: # Off
+ val = 0
+ rx = RadioSettingValueList(options, options[val])
+ rset = RadioSetting("setstuf.batsav", "Battery Saver", rx)
+ rset.set_apply_callback(my_batsav, _sets, "batsav")
+ adv.append(rset)
+
+ # Find out what & where SuperSave is
+ options = ["Off", "1", "2", "3", "4", "5", "6", "7", "8", "9"]
+ rx = RadioSettingValueList(options, options[_sets.supersave])
+ rset = RadioSetting("setstuf.supersave", "Super Save (Secs)", rx)
+ rset.set_doc("Unknown radio attribute??")
+ adv.append(rset)
+
+ sx = unpack_str(_sets.pttbot, _sets.pttbcnt + 1, 8)
+ rx = RadioSettingValueString(0, 16, sx)
+ rset = RadioSetting("setstuf.pttbot", "PTT BoT Code", rx)
+ rset.set_apply_callback(pack_chars, _sets, "pttbot", "pttbcnt", 16)
+ adv.append(rset)
+
+ sx = unpack_str(_sets.ptteot, _sets.pttecnt + 1, 8)
+ rx = RadioSettingValueString(0, 16, sx)
+ rset = RadioSetting("setstuf.ptteot", "PTT EoT Code", rx)
+ rset.set_apply_callback(pack_chars, _sets, "ptteot", "pttecnt", 16)
+ adv.append(rset)
+
+ options = ["None", "Low", "High", "Both"]
+ rx = RadioSettingValueList(options, options[_sets.voltx])
+ rset = RadioSetting("setstuf.voltx", "Transmit Inhibit Voltage", rx)
+ rset.set_doc("Block Transmit if battery volts are too high or low,")
+ adv.append(rset)
+
+ val = 0 # No = 0xff
+ if _sets.manfrqyn == 0xaa:
+ val = 1
+ options = ["No", "Yes"]
+ rx = RadioSettingValueList(options, options[val])
+ rset = RadioSetting("setstuf.manfrqyn", "Manual Frequency", rx)
+ rset.set_apply_callback(my_manfrq, _sets, "manfrqyn")
+ adv.append(rset)
+
+ rx = RadioSettingValueBoolean(bool(_sets.manualset))
+ rset = RadioSetting("setstuf.manualset", "Manual Setting", rx)
+ adv.append(rset)
+
+ # Scan Settings
+ options = ["CO: During Rx", "TO: Timed", "SE: Halt"]
+ rx = RadioSettingValueList(options, options[_sets.scanmode])
+ rset = RadioSetting("setstuf.scanmode",
+ "Scan Mode (Scan Pauses When)", rx)
+ scn.append(rset)
+
+ options = ["100", "150", "200", "250",
+ "300", "350", "400", "450"]
+ rx = RadioSettingValueList(options, options[_sets.scanspeed])
+ rset = RadioSetting("setstuf.scanspeed", "Scan Speed (ms)", rx)
+ scn.append(rset)
+
+ val = _sets.scantmo + 3
+ rx = RadioSettingValueInteger(3, 30, val)
+ rset = RadioSetting("setstuf.scantmo",
+ "TO Mode Timeout (secs)", rx)
+ rset.set_apply_callback(my_adjraw, _sets, "scantmo", -3)
+ scn.append(rset)
+
+ val = _sets.prichan
+ if val <= 0:
+ val = 0
+ rx = RadioSettingValueInteger(0, 128, val)
+ rset = RadioSetting("setstuf.prichan", "Priority Channel (0=OFF)", rx)
+ scn.append(rset)
+
+ # FM Broadcast Settings
+ val = _fmx.fmcur
+ val = val / 40.0
+ if val < 87.5 or val > 107.9:
+ val = 88.0
+ rx = RadioSettingValueFloat(87.5, 107.9, val, 0.1, 1)
+ rset = RadioSetting("fmfrqs.fmcur", "Manual FM Freq (MHz)", rx)
+ rset.set_apply_callback(myset_freq, _fmx, "fmcur", 40)
+ fmb.append(rset)
+
+ options = ["5", "50", "100", "200(USA)"] # 5 is not used
+ rx = RadioSettingValueList(options, options[_sets.fmstep])
+ rset = RadioSetting("setstuf.fmstep", "FM Freq Step (KHz)", rx)
+ fmb.append(rset)
+
+ # FM Scan Range fmsclo and fmschi are unknown memory locations,
+ # Not supported at this time
+
+ rx = RadioSettingValueBoolean(bool(_sets.rxinhib))
+ rset = RadioSetting("setstuf.rxinhib",
+ "Rcvr Will Interupt FM (DW)", rx)
+ fmb.append(rset)
+
+ _fmfrq = self._memobj.fm_stations
+ _fmap = self._memobj.fmmap
+ for j in range(0, 25):
+ val = _fmfrq[j].rxfreq
+ if val == 0xFFFF:
+ val = 88.0
+ fmset = False
+ else:
+ val = (float(int(val)) / 40)
+ # get fmmap bit value: 0 = enabled
+ ndx = int(math.floor((j) / 8))
+ bv = j % 8
+ msk = 1 << bv
+ vx = _fmap.fmset[ndx]
+ fmset = not bool(vx & msk)
+ rx = RadioSettingValueBoolean(fmset)
+ rset = RadioSetting("fmmap.fmset/%d" % j,
+ "FM Preset %02d" % (j + 1), rx)
+ rset.set_apply_callback(myset_mask, _fmap, "fmset", j)
+ fmb.append(rset)
+
+ rx = RadioSettingValueFloat(87.5, 107.9, val, 0.1, 1)
+ rset = RadioSetting("fm_stations/%d.rxfreq" % j,
+ " Preset %02d Freq" % (j + 1), rx)
+ # This callback uses the array index
+ rset.set_apply_callback(myset_fmfrq, _fmfrq, "rxfreq", j)
+ fmb.append(rset)
+
+ # DTMF Settings
+ options = [str(x) for x in range(4, 16)]
+ rx = RadioSettingValueList(options, options[_sets.dtmfspd])
+ rset = RadioSetting("setstuf.dtmfspd",
+ "Tx Speed (digits/sec)", rx)
+ dtmf.append(rset)
+
+ options = [str(x) for x in range(0, 1100, 100)]
+ rx = RadioSettingValueList(options, options[_sets.dtmfdig1time])
+ rset = RadioSetting("setstuf.dtmfdig1time",
+ "Tx 1st Digit Time (ms)", rx)
+ dtmf.append(rset)
+
+ options = [str(x) for x in range(100, 1100, 100)]
+ rx = RadioSettingValueList(options, options[_sets.dtmfdig1dly])
+ rset = RadioSetting("setstuf.dtmfdig1dly",
+ "Tx 1st Digit Delay (ms)", rx)
+ dtmf.append(rset)
+
+ options = ["0", "100", "500", "1000"]
+ rx = RadioSettingValueList(options, options[_sets.dtmfspms])
+ rset = RadioSetting("setstuf.dtmfspms",
+ "Tx Star & Pound Time (ms)", rx)
+ dtmf.append(rset)
+
+ options = ["None"] + [str(x) for x in range(600, 2100, 100)]
+ rx = RadioSettingValueList(options, options[_sets.codespctim])
+ rset = RadioSetting("setstuf.codespctim",
+ "Tx Code Space Time (ms)", rx)
+ dtmf.append(rset)
+
+ rx = RadioSettingValueBoolean(bool(_sets.codeabcd))
+ rset = RadioSetting("setstuf.codeabcd", "Tx Codes A,B,C,D", rx)
+ dtmf.append(rset)
+
+ rx = RadioSettingValueBoolean(bool(_sets.dtmfside))
+ rset = RadioSetting("setstuf.dtmfside", "DTMF Side Tone", rx)
+ dtmf.append(rset)
+
+ options = ["Off", "A", "B", "C", "D"]
+ rx = RadioSettingValueList(options, options[_sets.grpcode])
+ rset = RadioSetting("setstuf.grpcode", "Rx Group Code", rx)
+ dtmf.append(rset)
+
+ options = ["Off"] + [str(x) for x in range(1, 16)]
+ rx = RadioSettingValueList(options, options[_sets.autoresettmo])
+ rset = RadioSetting("setstuf.autoresettmo",
+ "Rx Auto Reset Timeout (secs)", rx)
+ dtmf.append(rset)
+
+ rx = RadioSettingValueBoolean(bool(_sets.txdecode))
+ rset = RadioSetting("setstuf.txdecode", "Tx Decode", rx)
+ dtmf.append(rset)
+
+ rx = RadioSettingValueBoolean(bool(_sets.idedit))
+ rset = RadioSetting("setstuf.idedit", "Allow ANI Code Edit", rx)
+ dtmf.append(rset)
+
+ options = [str(x) for x in range(500, 1600, 100)]
+ rx = RadioSettingValueList(options, options[_sets.decodetmo])
+ rset = RadioSetting("setstuf.decodetmo",
+ "Rx Decode Timeout (ms)", rx)
+ dtmf.append(rset)
+
+ options = ["Tx & Rx Inhibit", "Tx Inhibit"]
+ rx = RadioSettingValueList(options, options[_sets.stuntype])
+ rset = RadioSetting("setstuf.stuntype", "Stun Type", rx)
+ dtmf.append(rset)
+
+ sx = unpack_str(_sets.stuncode, _sets.stuncnt, 5)
+ rx = RadioSettingValueString(0, 10, sx)
+ rset = RadioSetting("setstuf.stuncode", "Stun Code", rx)
+ rset.set_apply_callback(pack_chars, _sets,
+ "stuncode", "stuncnt", 10)
+ dtmf.append(rset)
+
+ # Frequency ranges
+ rx = RadioSettingValueBoolean(bool(_sets.frqr1))
+ rset = RadioSetting("setstuf.frqr1", "Freq Range 1 (UHF)", rx)
+ rset.set_doc("Enable the UHF frequency bank.")
+ frng.append(rset)
+
+ rx = RadioSettingValueBoolean(bool(_sets.frqr2))
+ rset = RadioSetting("setstuf.frqr2", "Freq Range 2 (VHF)", rx)
+ rset.set_doc("Enable the VHF frequency bank.")
+ frng.append(rset)
+
+ mod_se = True # UV8000SE has 3rd freq bank
+ if mod_se:
+ rx = RadioSettingValueBoolean(bool(_sets.frqr3))
+ rset = RadioSetting("setstuf.frqr3", "Freq Range 3 (220Mhz)", rx)
+ rset.set_doc("Enable the 220MHz frequency bank.")
+ frng.append(rset)
+
+ frqm = 100000
+ val = _sets.frqr1lo / frqm
+ rx = RadioSettingValueFloat(400.0, 520.0, val, 0.005, 3)
+ rset = RadioSetting("setstuf.frqr1lo",
+ "UHF Range Low Limit (MHz)", rx)
+ rset.set_apply_callback(myset_freq, _sets, "frqr1lo", frqm)
+ rset.set_doc("Low limit of the UHF frequency bank.")
+ frng.append(rset)
+
+ val = _sets.frqr1hi / frqm
+ rx = RadioSettingValueFloat(400.0, 520.0, val, 0.005, 3)
+ rset = RadioSetting("setstuf.frqr1hi",
+ "UHF Range High Limit (MHz)", rx)
+ rset.set_apply_callback(myset_freq, _sets, "frqr1hi", frqm)
+ rset.set_doc("High limit of the UHF frequency bank.")
+ frng.append(rset)
+
+ val = _sets.frqr2lo / frqm
+ rx = RadioSettingValueFloat(136.0, 174.0, val, 0.005, 3)
+ rset = RadioSetting("setstuf.frqr2lo",
+ "VHF Range Low Limit (MHz)", rx)
+ rset.set_apply_callback(myset_freq, _sets, "frqr2lo", frqm)
+ rset.set_doc("Low limit of the VHF frequency bank.")
+ frng.append(rset)
+
+ val = _sets.frqr2hi / frqm
+ rx = RadioSettingValueFloat(136.0, 174.0, val, 0.005, 3)
+ rset = RadioSetting("setstuf.frqr2hi",
+ "VHF Range High Limit (MHz)", rx)
+ rset.set_apply_callback(myset_freq, _sets, "frqr2hi", frqm)
+ rset.set_doc("High limit of the VHF frequency bank.")
+ frng.append(rset)
+
+ if mod_se:
+ val = _sets.frqr3lo / frqm
+ if val < 220.0:
+ val = 220.0
+ rx = RadioSettingValueFloat(220.0, 260.0, val, 0.005, 3)
+ rset = RadioSetting("setstuf.frqr3lo",
+ "1.25m Range Low Limit (MHz)", rx)
+ rset.set_apply_callback(myset_freq, _sets, "frqr3lo", frqm)
+ frng.append(rset)
+
+ val = _sets.frqr3hi / frqm
+ if val < 220.0:
+ val = 260.0
+ rx = RadioSettingValueFloat(220.0, 260.0, val, 0.005, 3)
+ rset = RadioSetting("setstuf.frqr3hi",
+ "1.25m Range High Limit (MHz)", rx)
+ rset.set_apply_callback(myset_freq, _sets, "frqr3hi", 1000)
+ frng.append(rset)
+
+ return group # END get_settings()
+
+ def set_settings(self, settings):
+ _settings = self._memobj.setstuf
+ _mem = self._memobj
+ for element in settings:
+ if not isinstance(element, RadioSetting):
+ self.set_settings(element)
+ continue
+ else:
+ try:
+ name = element.get_name()
+ if "." in name:
+ bits = name.split(".")
+ obj = self._memobj
+ for bit in bits[:-1]:
+ if "/" in bit:
+ bit, index = bit.split("/", 1)
+ index = int(index)
+ obj = getattr(obj, bit)[index]
+ else:
+ obj = getattr(obj, bit)
+ setting = bits[-1]
+ else:
+ obj = _settings
+ setting = element.get_name()
+
+ if element.has_apply_callback():
+ LOG.debug("Using apply callback")
+ element.run_apply_callback()
+ elif element.value.get_mutable():
+ LOG.debug("Setting %s = %s" % (setting, element.value))
+ setattr(obj, setting, element.value)
+ except Exception, e:
+ LOG.debug(element.get_name())
+ raise
More information about the chirp_devel
mailing list