[chirp_devel] New Driver submission
Rick DeWitt AA0RD
Fri May 31 06:46:38 PDT 2019
Attached is the new TYT TH-UV8000D/E (and probably SE) driver.
--
Rick DeWitt
AA0RD
Sequim, Washington, USA 98382
(360) 681-3494
-------------- next part --------------
# HG changeset patch
# User Rick DeWitt <aa0rd at yahoo.com>
# Date 1559226735 25200
# Thu May 30 07:32:15 2019 -0700
# Node ID 120e390016146e92f490619e4f7029ae60418d72
# Parent 76c88493bd3f873d355d08e66c624c6acf201b4d
[th-uv8000] New driver for TYT TH-UV8000 family, fixes issue #2837
Submitting new driver in support of 11 issues 2837 ... 6183
diff -r 76c88493bd3f -r 120e39001614 chirp/drivers/th_uv8000.py
--- /dev/null Thu Jan 01 00:00:00 1970 +0000
+++ b/chirp/drivers/th_uv8000.py Thu May 30 07:32:15 2019 -0700
@@ -0,0 +1,1598 @@
+# 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 (W2OOT) for decoding the mamory 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 datetime import datetime
+
+LOG = logging.getLogger(__name__)
+
+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
+
+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 {
+ ul16 year;
+ u8 month;
+ u8 day;
+ u8 hour;
+ u8 min;
+ u8 sec;
+} moddate;
+
+#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:
+ _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:
+ 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)
+ # Append the model number to the downloaded memory
+ data += radio.MODEL.ljust(9)
+
+ return data
+
+def _upload(radio):
+ """Upload procedure"""
+ # Put radio in program mode and identify it
+ _do_ident(radio)
+
+ # Generate upload time stamp; not used by Chirp
+ dnow = datetime.now()
+ _mem = radio._memobj.moddate
+ _mem.year = dnow.year
+ _mem.month = dnow.month
+ _mem.day = dnow.day
+ _mem.hour = dnow.hour
+ _mem.min = dnow.minute
+ _mem.sec = dnow.second
+
+ # 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 force_odd(fmfrq):
+ """Force FM Broadcast Frequency to have odd KHz """
+ oddfrq = int(fmfrq / 100000) # remove 10Khz and lower
+ if (oddfrq % 2) == 0: # increment to odd
+ oddfrq += 1
+ return (oddfrq * 100000)
+
+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 model_match(cls, data):
+ """Match the opened/downloaded image to the correct version"""
+ if len(data) == 0x1307:
+ rid = data[0x1300:0x1309]
+ return rid.startswith(cls.MODEL)
+ else:
+ return False
+
+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
+ # LOG.warning("- Do_Map chn %d, sclr= %d, ndx= %d" % (chn, sclr, ndx))
+ # LOG.warning("- bv= %d, msk = %02x, mapbit= %d" % (bv, msk, mapbit))
+ # LOG.warning("- mary[%d] = %02x" % (ndx, mary[ndx]))
+ return mapbit
+
+ at directory.register
+class tyt_uv8000d(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 = {
+ "FM 25": -3,
+ "FM 24": -4,
+ "FM 23": -5,
+ "FM 22": -6,
+ "FM 21": -7,
+ "FM 20": -8,
+ "FM 19": -9,
+ "FM 18": -10,
+ "FM 17": -11,
+ "FM 16": -12,
+ "FM 15": -13,
+ "FM 14": -14,
+ "FM 13": -15,
+ "FM 12": -16,
+ "FM 11": -17,
+ "FM 10": -18,
+ "FM 09": -19,
+ "FM 08": -20,
+ "FM 07": -21,
+ "FM 06": -22,
+ "FM 05": -23,
+ "FM 04": -24,
+ "FM 03": -25,
+ "FM 02": -26,
+ "FM 01": -27
+ }
+ FIRST_FM_INDEX = -3
+ LAST_FM_INDEX = -27
+
+ SPECIAL_FREQ = {
+ "UpVFO": -2,
+ "LoVFO": -1
+ }
+ FIRST_FREQ_INDEX = -1
+ LAST_FREQ_INDEX = -2
+ SPECIAL_MEMORIES.update(SPECIAL_FREQ)
+
+ SPECIAL_MEMORIES_REV = dict(zip(SPECIAL_MEMORIES.values(),
+ SPECIAL_MEMORIES.keys()))
+
+ @classmethod
+ def get_prompts(cls):
+ rp = chirp_common.RadioPrompts()
+ rp.info = \
+ ('NOTE 1: Some of these radios come shipped with the frequency '
+ 'ranges locked to comply with FCC band limitations.\n'
+ 'This driver allows the user to program frequencies outside '
+ 'the legal amateur bands, so that they can receive on marine, '
+ 'aviation, FRS/GMRS and NOAA weather bands.\n'
+ 'If these frequencies are locked out on your radio, '
+ 'you will receive an error beep when attempting to operate.\n'
+ 'It is the users responsibility to only transmit on '
+ 'authorized freqencies. \n\n'
+ 'NOTE 2: Click on the "Special Channels" tab of the layout '
+ 'screen to see/set the upper and lower frequency-mode values, '
+ 'as well as the 25 FM broadcast radio station channels.\n\n'
+ 'NOTE 3: The PTT ID codes are 1-16 characters of DTMF (0-9, '
+ 'A-D,*,#). The per-channel ANI Id Codes are not implemented at '
+ 'this time. However they may be set manually by using the '
+ 'Browser developer tool. Contact Rick at AA0RD at Yahoo.com.\n\n'
+ 'NOTE 4: To access the stored FM broadcast stations: activate '
+ 'FM mode (F, MONI), then # and up/down.'
+ )
+
+ rp.pre_download = _(dedent("""\
+ Follow these instructions to download your config:
+
+ 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 this instructions to upload your config:
+
+ 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_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:
+ # 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:
+ # 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_fm_memory(self, mem, _mem):
+ mem.name = "----"
+ rx = self._memobj.fm_stations[mem.number + 2].rxfreq
+ if rx == 0xFFFF:
+ mem.empty = True
+ return mem
+ else:
+ mem.freq = (float(int(rx)) / 40) * 1000000
+ mem.empty = False
+
+ mem.offset = 0.0
+ mem.power = 0
+ mem.duplex = "off" # inhibits tx
+ mem.mode = self.MODES[1]
+ dtcs_pol = ["N", "N"]
+ mem.ctone = 88.5
+ mem.rtone = 88.5
+ mem.tmode = ""
+ mem.cross_mode = "Tone->Tone"
+ mem.skip = ""
+ mem.comment = ""
+
+ return mem
+
+ 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)
+ elif mem.number in range(self.FIRST_FM_INDEX,
+ self.LAST_FM_INDEX - 1, -1):
+ _mem = self._memobj.fm_stations[-self.LAST_FM_INDEX + mem.number]
+ # immutable = ["number", "extd_number", "name", "power", "ctone",
+ # "rtone", "skip", "duplex", "offset", "mode"]
+ mem = self._get_fm_memory(mem, _mem) # special fnctn
+ 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, fm 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
+ else: # FM stations, mem.number -3 to -27
+ _mem.rxfreq = 0xffff
+ _do_map(mem.number + 28, 1, self._memobj.fmmap.fmset)
+ return
+
+ _mem.rxfreq = mem.freq / 10
+
+ if mem.number < -2: # FM stations, only rxfreq
+ _mem.rxfreq = (force_odd(mem.freq) * 40) / 1000000
+ _do_map(mem.number + 28, 0, self._memobj.fmmap.fmset)
+ return
+
+ 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]
+ elif mem.number in range(self.FIRST_FM_INDEX,
+ self.LAST_FM_INDEX - 1, -1):
+ _mem = self._memobj.fm_stations[-self.LAST_FM_INDEX + mem.number]
+ 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
+
+ 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)
+
+ if False: # Skip these unknowns
+ # FM Scan Range lo and Hi ??? MEM
+ val = 87.5
+ rx = RadioSettingValueFloat(87.5, 107.9, val, 0.1, 1)
+ rset = RadioSetting("setstuf.fmsclo",
+ "Low FM Scan Bound (MHz)", rx)
+ rset.set_apply_callback(myset_freq, _sets, "fmsclo", 40)
+ fmb.append(rset)
+
+ val = 107.9 # ??? @@@ where
+ rx = RadioSettingValueFloat(87.5, 107.9, val, 0.1, 1)
+ rset = RadioSetting("setstuf.fmschi",
+ "High FM Scan Freq (MHz)", rx)
+ rset.set_apply_callback(myset_freq, _sets, "fmschi", 40)
+ fmb.append(rset)
+
+ rx = RadioSettingValueBoolean(bool(_sets.rxinhib))
+ rset = RadioSetting("setstuf.rxinhib",
+ "Rcvr Will Interupt FM (DW)", rx)
+ fmb.append(rset)
+
+ 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 == 0: val = 222.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 == 0: val = 225.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
+
+ @classmethod
+ def match_model(cls, filedata, filename):
+ match_size = False
+ match_model = False
+
+ # Testing the file data size
+ if len(filedata) == MEM_SIZE + 10:
+ match_size = True
+
+ # Testing the firmware model fingerprint
+ match_model = model_match(cls, filedata)
+
+ if match_size and match_model:
+ return True
+ else:
+ return False
More information about the chirp_devel
mailing list