|
# Copyright 2016:
|
|
# * Jim Unroe KC9HI, <rock.unroe@gmail.com>
|
|
# * Pavel Milanes CO7WT <pavelmc@gmail.com>
|
|
#
|
|
# 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
|
|
|
|
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
|
|
|
|
LOG = logging.getLogger(__name__)
|
|
|
|
MEM_FORMAT = """
|
|
struct mem {
|
|
lbcd rxfreq[4];
|
|
lbcd txfreq[4];
|
|
lbcd rxtone[2];
|
|
lbcd txtone[2];
|
|
u8 unknown0:2,
|
|
txp:2,
|
|
wn:2,
|
|
unknown1:1,
|
|
bcl:1;
|
|
u8 unknown2:2,
|
|
revert:1,
|
|
dname:1,
|
|
unknown3:4;
|
|
u8 unknown4[2];
|
|
};
|
|
|
|
struct nam {
|
|
char name[6];
|
|
u8 unknown1[2];
|
|
};
|
|
|
|
#seekto 0x0000;
|
|
struct mem left_memory[500];
|
|
|
|
#seekto 0x2000;
|
|
struct mem right_memory[500];
|
|
|
|
#seekto 0x4000;
|
|
struct nam left_names[500];
|
|
|
|
#seekto 0x5000;
|
|
struct nam right_names[500];
|
|
|
|
#seekto 0x6000;
|
|
u8 left_usedflags[64];
|
|
|
|
#seekto 0x6040;
|
|
u8 left_scanflags[64];
|
|
|
|
#seekto 0x6080;
|
|
u8 right_usedflags[64];
|
|
|
|
#seekto 0x60C0;
|
|
u8 right_scanflags[64];
|
|
|
|
#seekto 0x6160;
|
|
struct {
|
|
char line32[32];
|
|
} embedded_msg;
|
|
|
|
#seekto 0x6180;
|
|
struct {
|
|
u8 sbmute:2, // sub band mute
|
|
unknown1:1,
|
|
workmodb:1, // work mode (right side)
|
|
dw:1, // dual watch
|
|
audio:1, // audio output mode (stereo/mono)
|
|
unknown2:1,
|
|
workmoda:1; // work mode (left side)
|
|
u8 scansb:1, // scan stop beep
|
|
aftone:3, // af tone control
|
|
scand:1, // scan direction
|
|
scanr:3; // scan resume
|
|
u8 rxexp:1, // rx expansion
|
|
ptt:1, // ptt mode
|
|
display:1, // display select (frequency/clock)
|
|
omode:1, // operation mode
|
|
beep:2, // beep volume
|
|
spkr:2; // speaker
|
|
u8 cpuclk:1, // operating mode(cpu clock)
|
|
fkey:3, // fkey function
|
|
mrscan:1, // memory scan type
|
|
color:3; // lcd backlight color
|
|
u8 vox:2, // vox
|
|
voxs:3, // vox sensitivity
|
|
mgain:3; // mic gain
|
|
u8 wbandb:4, // work band (right side)
|
|
wbanda:4; // work band (left side)
|
|
u8 sqlb:4, // squelch level (right side)
|
|
sqla:4; // squelch level (left side)
|
|
u8 apo:4, // auto power off
|
|
ars:1, // automatic repeater shift
|
|
tot:3; // time out timer
|
|
u8 stepb:4, // auto step (right side)
|
|
stepa:4; // auto step (left side)
|
|
u8 rxcoverm:1, // rx coverage-memory
|
|
lcdc:3, // lcd contrast
|
|
rxcoverv:1, // rx coverage-vfo
|
|
lcdb:3; // lcd brightness
|
|
u8 smode:1, // smart function mode
|
|
timefmt:1, // time format
|
|
datefmt:2, // date format
|
|
timesig:1, // time signal
|
|
keyb:3; // key/led brightness
|
|
u8 dwstop:1, // dual watch stop
|
|
unknown3:1,
|
|
sqlexp:1, // sql expansion
|
|
decbandsel:1, // decoding band select
|
|
dtmfmodenc:1, // dtmf mode encode
|
|
bell:3; // bell ringer
|
|
u8 unknown4:2,
|
|
btime:6; // lcd backlight time
|
|
u8 unknown5:2,
|
|
tz:6; // time zone
|
|
u8 unknown618E;
|
|
u8 unknown618F;
|
|
ul16 offseta; // work offset (left side)
|
|
ul16 offsetb; // work offset (right side)
|
|
ul16 mrcha; // selected memory channel (left)
|
|
ul16 mrchb; // selected memory channel (right)
|
|
ul16 wpricha; // work priority channel (left)
|
|
ul16 wprichb; // work priority channel (right)
|
|
u8 unknown6:3,
|
|
datasql:2, // data squelch
|
|
dataspd:1, // data speed
|
|
databnd:2; // data band select
|
|
u8 unknown7:1,
|
|
pfkey2:3, // mic p2 key
|
|
unknown8:1,
|
|
pfkey1:3; // mic p1 key
|
|
u8 unknown9:1,
|
|
pfkey4:3, // mic p4 key
|
|
unknowna:1,
|
|
pfkey3:3; // mic p3 key
|
|
u8 unknownb:7,
|
|
dtmfmoddec:1; // dtmf mode decode
|
|
} settings;
|
|
|
|
#seekto 0x61B0;
|
|
struct {
|
|
char line16[16];
|
|
} poweron_msg;
|
|
|
|
#seekto 0x6300;
|
|
struct {
|
|
u8 unknown1:3,
|
|
ttdgt:5; // dtmf digit time
|
|
u8 unknown2:3,
|
|
ttint:5; // dtmf interval time
|
|
u8 unknown3:3,
|
|
tt1stdgt:5; // dtmf 1st digit time
|
|
u8 unknown4:3,
|
|
tt1stdly:5; // dtmf 1st digit delay
|
|
u8 unknown5:3,
|
|
ttdlyqt:5; // dtmf delay when use qt
|
|
u8 unknown6:3,
|
|
ttdkey:5; // dtmf d key function
|
|
u8 unknown7;
|
|
u8 unknown8:4,
|
|
ttautod:4; // dtmf auto dial group
|
|
} dtmf;
|
|
|
|
#seekto 0x6330;
|
|
struct {
|
|
u8 unknown1:7,
|
|
ttsig:1; // dtmf signal
|
|
u8 unknown2:4,
|
|
ttintcode:4; // dtmf interval code
|
|
u8 unknown3:5,
|
|
ttgrpcode:3; // dtmf group code
|
|
u8 unknown4:4,
|
|
ttautorst:4; // dtmf auto reset time
|
|
u8 unknown5:5,
|
|
ttalert:3; // dtmf alert tone/transpond
|
|
} dtmf2;
|
|
|
|
#seekto 0x6360;
|
|
struct {
|
|
u8 code1[8]; // dtmf code
|
|
u8 code1_len; // dtmf code length
|
|
u8 unknown1[7];
|
|
u8 code2[8]; // dtmf code
|
|
u8 code2_len; // dtmf code length
|
|
u8 unknown2[7];
|
|
u8 code3[8]; // dtmf code
|
|
u8 code3_len; // dtmf code length
|
|
u8 unknown3[7];
|
|
u8 code4[8]; // dtmf code
|
|
u8 code4_len; // dtmf code length
|
|
u8 unknown4[7];
|
|
u8 code5[8]; // dtmf code
|
|
u8 code5_len; // dtmf code length
|
|
u8 unknown5[7];
|
|
u8 code6[8]; // dtmf code
|
|
u8 code6_len; // dtmf code length
|
|
u8 unknown6[7];
|
|
u8 code7[8]; // dtmf code
|
|
u8 code7_len; // dtmf code length
|
|
u8 unknown7[7];
|
|
u8 code8[8]; // dtmf code
|
|
u8 code8_len; // dtmf code length
|
|
u8 unknown8[7];
|
|
u8 code9[8]; // dtmf code
|
|
u8 code9_len; // dtmf code length
|
|
u8 unknown9[7];
|
|
} dtmfcode;
|
|
|
|
"""
|
|
|
|
MEM_SIZE = 0x8000
|
|
BLOCK_SIZE = 0x40
|
|
MODES = ["FM", "Auto", "NFM", "AM"]
|
|
SKIP_VALUES = ["", "S"]
|
|
TONES = chirp_common.TONES
|
|
DTCS_CODES = chirp_common.DTCS_CODES
|
|
NAME_LENGTH = 6
|
|
DTMF_CHARS = list("0123456789ABCD*#")
|
|
STIMEOUT = 1
|
|
|
|
# Basic settings lists
|
|
LIST_AFTONE = ["Low-3", "Low-2", "Low-1", "Normal", "High-1", "High-2"]
|
|
LIST_SPKR = ["Off", "Front", "Rear", "Front + Rear"]
|
|
LIST_AUDIO = ["Monaural", "Stereo"]
|
|
LIST_SBMUTE = ["Off", "TX", "RX", "Both"]
|
|
LIST_MLNHM = ["Min", "Low", "Normal", "High", "Max"]
|
|
LIST_PTT = ["Momentary", "Toggle"]
|
|
LIST_RXEXP = ["General", "Wide coverage"]
|
|
LIST_VOX = ["Off", "Internal mic", "Front hand-mic", "Rear hand-mic"]
|
|
LIST_DISPLAY = ["Frequency", "Timer/Clock"]
|
|
LIST_MINMAX = ["Min"] + ["%s" % x for x in range(2, 8)] + ["Max"]
|
|
LIST_COLOR = ["White-Blue", "Sky-Blue", "Marine-Blue", "Green",
|
|
"Yellow-Green", "Orange", "Amber", "White"]
|
|
LIST_BTIME = ["Continuous"] + ["%s" % x for x in range(1, 61)]
|
|
LIST_MRSCAN = ["All", "Selected"]
|
|
LIST_DWSTOP = ["Auto", "Hold"]
|
|
LIST_SCAND = ["Down", "Up"]
|
|
LIST_SCANR = ["Busy", "Hold", "1 sec", "3 sec", "5 sec"]
|
|
LIST_APO = ["Off", ".5", "1", "1.5"] + ["%s" % x for x in range(2, 13)]
|
|
LIST_BEEP = ["Off", "Low", "High"]
|
|
LIST_FKEY = ["MHz/AD-F", "AF Dual 1(line-in)", "AF Dual 2(AM)",
|
|
"AF Dual 3(FM)", "PA", "SQL off", "T-call", "WX"]
|
|
LIST_PFKEY = ["Off", "SQL off", "TX power", "Scan", "RPT shift", "Reverse",
|
|
"T-Call"]
|
|
LIST_AB = ["A", "B"]
|
|
LIST_COVERAGE = ["In band", "All"]
|
|
LIST_TOT = ["Off"] + ["%s" % x for x in range(5, 25, 5)] + ["30"]
|
|
LIST_DATEFMT = ["yyyy/mm/dd", "yyyy/dd/mm", "mm/dd/yyyy", "dd/mm/yyyy"]
|
|
LIST_TIMEFMT = ["24H", "12H"]
|
|
LIST_TZ = ["-12 INT DL W",
|
|
"-11 MIDWAY",
|
|
"-10 HAST",
|
|
"-9 AKST",
|
|
"-8 PST",
|
|
"-7 MST",
|
|
"-6 CST",
|
|
"-5 EST",
|
|
"-4:30 CARACAS",
|
|
"-4 AST",
|
|
"-3:30 NST",
|
|
"-3 BRASILIA",
|
|
"-2 MATLANTIC",
|
|
"-1 AZORES",
|
|
"-0 LONDON",
|
|
"+0 LONDON",
|
|
"+1 ROME",
|
|
"+2 ATHENS",
|
|
"+3 MOSCOW",
|
|
"+3:30 REHRW",
|
|
"+4 ABUDNABI",
|
|
"+4:30 KABUL",
|
|
"+5 ISLMABAD",
|
|
"+5:30 NEWDELHI",
|
|
"+6 DHAKA",
|
|
"+6:30 YANGON",
|
|
"+7 BANKOK",
|
|
"+8 BEIJING",
|
|
"+9 TOKYO",
|
|
"+10 ADELAIDE",
|
|
"+10 SYDNET",
|
|
"+11 NWCLDNIA",
|
|
"+12 FIJI",
|
|
"+13 NUKALOFA"
|
|
]
|
|
LIST_BELL = ["Off", "1 time", "3 times", "5 times", "8 times", "Continuous"]
|
|
LIST_DATABND = ["Main band", "Sub band", "Left band-fixed", "Right band-fixed"]
|
|
LIST_DATASPD = ["1200 bps", "9600 bps"]
|
|
LIST_DATASQL = ["Busy/TX", "Busy", "TX"]
|
|
|
|
# Other settings lists
|
|
LIST_CPUCLK = ["Clock frequency 1", "Clock frequency 2"]
|
|
|
|
# Work mode settings lists
|
|
LIST_WORK = ["VFO", "Memory System"]
|
|
LIST_WBANDB = ["Air", "H-V", "GR1-V", "GR1-U", "H-U", "GR2"]
|
|
LIST_WBANDA = ["Line-in", "AM", "FM"] + LIST_WBANDB
|
|
LIST_SQL = ["Open"] + ["%s" % x for x in range(1, 10)]
|
|
_STEP_LIST = [2.5, 5., 6.25, 8.33, 9., 10., 12.5, 15., 20., 25., 50., 100.,
|
|
200.]
|
|
LIST_STEP = ["Auto"] + ["{0:.2f} KHz".format(x) for x in _STEP_LIST]
|
|
LIST_SMODE = ["F-1", "F-2"]
|
|
|
|
# DTMF settings lists
|
|
LIST_TTDKEY = ["D code"] + ["Send delay %s s" % x for x in range(1, 17)]
|
|
LIST_TT200 = ["%s ms" % x for x in range(50, 210, 10)]
|
|
LIST_TT1000 = ["%s ms" % x for x in range(100, 1050, 50)]
|
|
LIST_TTSIG = ["Code squelch", "Select call"]
|
|
LIST_TTAUTORST = ["Off"] + ["%s s" % x for x in range(1, 16)]
|
|
LIST_TTGRPCODE = ["Off"] + list("ABCD*#")
|
|
LIST_TTINTCODE = DTMF_CHARS
|
|
LIST_TTALERT = ["Off", "Alert tone", "Transpond", "Transpond-ID code",
|
|
"Transpond-transpond code"]
|
|
LIST_TTAUTOD = ["%s" % x for x in range(1, 10)]
|
|
|
|
# valid chars on the LCD
|
|
VALID_CHARS = chirp_common.CHARSET_ALPHANUMERIC + \
|
|
"`{|}!\"#$%&'()*+,-./:;<=>?@[]^_"
|
|
|
|
# Power Levels
|
|
POWER_LEVELS = [chirp_common.PowerLevel("Low", watts=5),
|
|
chirp_common.PowerLevel("Mid", watts=20),
|
|
chirp_common.PowerLevel("High", watts=50)]
|
|
|
|
# B-TECH UV-50X3 id string
|
|
UV50X3_id = b"VGC6600MD"
|
|
|
|
|
|
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 _check_for_double_ack(radio):
|
|
radio.pipe.timeout = 0.005
|
|
c = radio.pipe.read(1)
|
|
radio.pipe.timeout = STIMEOUT
|
|
if c and c != b'\x06':
|
|
_exit_program_mode(radio)
|
|
raise errors.RadioError('Expected nothing or ACK, got %r' % c)
|
|
|
|
|
|
def _rawrecv(radio, amount):
|
|
"""Raw read from the radio device"""
|
|
data = b""
|
|
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 data 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(">BHB", ord(cmd), addr, length)
|
|
# add the data if set
|
|
if len(data) != 0:
|
|
frame += data
|
|
# return the data
|
|
return frame
|
|
|
|
|
|
def _recv(radio, addr, length=BLOCK_SIZE):
|
|
"""Get data from the radio """
|
|
# read 4 bytes of header
|
|
hdr = _rawrecv(radio, 4)
|
|
|
|
# check for unexpected extra command byte
|
|
c, a, l = struct.unpack(">BHB", hdr)
|
|
if hdr[0:2] == b"WW" and a != addr:
|
|
# extra command byte detected
|
|
# throw away the 1st byte and add the next byte in the buffer
|
|
hdr = hdr[1:] + _rawrecv(radio, 1)
|
|
|
|
# read 64 bytes (0x40) of data
|
|
data = _rawrecv(radio, (BLOCK_SIZE))
|
|
|
|
# DEBUG
|
|
LOG.info("Response:")
|
|
LOG.debug(util.hexprint(hdr + data))
|
|
|
|
c, a, l = struct.unpack(">BHB", hdr)
|
|
if a != addr or l != length or c != ord(b"W"):
|
|
_exit_program_mode(radio)
|
|
LOG.error("Invalid answer for block 0x%04x:" % addr)
|
|
LOG.debug("CMD: %s ADDR: %04x SIZE: %02x" % (c, a, l))
|
|
raise errors.RadioError("Unknown response from the radio")
|
|
|
|
return data
|
|
|
|
|
|
def _do_ident(radio):
|
|
"""Put the radio in PROGRAM mode & identify it"""
|
|
# set the serial discipline
|
|
radio.pipe.baudrate = 115200
|
|
radio.pipe.parity = "N"
|
|
radio.pipe.timeout = STIMEOUT
|
|
|
|
# flush input buffer
|
|
_clean_buffer(radio)
|
|
|
|
magic = b"V66LINK"
|
|
|
|
_rawsend(radio, magic)
|
|
|
|
# Ok, get the ident string
|
|
ident = _rawrecv(radio, 9)
|
|
|
|
# check if ident is OK
|
|
if ident != radio.IDENT:
|
|
# bad ident
|
|
msg = "Incorrect model ID, got this:"
|
|
msg += util.hexprint(ident)
|
|
LOG.debug(msg)
|
|
raise errors.RadioError("Radio identification failed.")
|
|
|
|
# DEBUG
|
|
LOG.info("Positive ident, got this:")
|
|
LOG.debug(util.hexprint(ident))
|
|
|
|
return True
|
|
|
|
|
|
def _exit_program_mode(radio):
|
|
endframe = b"\x45"
|
|
_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 = b""
|
|
for addr in range(0, MEM_SIZE, BLOCK_SIZE):
|
|
frame = _make_frame(b"R", addr, BLOCK_SIZE)
|
|
# DEBUG
|
|
LOG.info("Request sent:")
|
|
LOG.debug(util.hexprint(frame))
|
|
|
|
# sending the read request
|
|
_rawsend(radio, frame)
|
|
|
|
# now we read
|
|
d = _recv(radio, addr)
|
|
|
|
# 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"""
|
|
|
|
MEM_SIZE = 0x7000
|
|
|
|
# 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 start here
|
|
for addr in range(0, MEM_SIZE, BLOCK_SIZE):
|
|
# sending the data
|
|
data = radio.get_mmap()[addr:addr + BLOCK_SIZE]
|
|
|
|
frame = _make_frame(b"W", addr, BLOCK_SIZE, data)
|
|
|
|
_rawsend(radio, frame)
|
|
|
|
# receiving the response
|
|
ack = _rawrecv(radio, 1)
|
|
if ack != b"\x06":
|
|
_exit_program_mode(radio)
|
|
msg = "Bad ack writing block 0x%04x" % addr
|
|
raise errors.RadioError(msg)
|
|
|
|
_check_for_double_ack(radio)
|
|
|
|
# UI Update
|
|
status.cur = addr // BLOCK_SIZE
|
|
status.msg = "Cloning to radio..."
|
|
radio.status_fn(status)
|
|
|
|
_exit_program_mode(radio)
|
|
|
|
|
|
def model_match(cls, data):
|
|
"""Match the opened/downloaded image to the correct version"""
|
|
rid = data[0x6140:0x6148]
|
|
|
|
if rid in cls.IDENT:
|
|
return True
|
|
|
|
return False
|
|
|
|
|
|
def _split(rf, f1, f2):
|
|
"""Returns False if the two freqs are in the same band (no split)
|
|
or True otherwise"""
|
|
|
|
# determine if the two freqs are in the same band
|
|
for low, high in rf.valid_bands:
|
|
if f1 >= low and f1 <= high and \
|
|
f2 >= low and f2 <= high:
|
|
# if the two freqs are on the same Band this is not a split
|
|
return False
|
|
|
|
# if you get here is because the freq pairs are split
|
|
return True
|
|
|
|
|
|
class VGCStyleRadio(chirp_common.CloneModeRadio,
|
|
chirp_common.ExperimentalRadio):
|
|
"""BTECH's UV-50X3"""
|
|
VENDOR = "BTECH"
|
|
NEEDS_COMPAT_SERIAL = False
|
|
_air_range = (108000000, 136000000)
|
|
_vhf_range = (136000000, 174000000)
|
|
_vhf2_range = (174000000, 250000000)
|
|
_220_range = (222000000, 225000000)
|
|
_gen1_range = (300000000, 400000000)
|
|
_uhf_range = (400000000, 480000000)
|
|
_gen2_range = (480000000, 520000000)
|
|
_upper = 499
|
|
MODEL = ""
|
|
IDENT = ""
|
|
|
|
@classmethod
|
|
def get_prompts(cls):
|
|
rp = chirp_common.RadioPrompts()
|
|
rp.experimental = \
|
|
('The UV-50X3 driver is a beta version.\n'
|
|
'\n'
|
|
'Please save an unedited copy of your first successful\n'
|
|
'download to a CHIRP Radio Images(*.img) file.'
|
|
)
|
|
rp.pre_download = _(
|
|
"Follow this instructions to download your info:\n"
|
|
"1 - Turn off your radio\n"
|
|
"2 - Connect your interface cable\n"
|
|
"3 - Turn on your radio\n"
|
|
"4 - Do the download of your radio data\n")
|
|
rp.pre_upload = _(
|
|
"Follow this instructions to upload your info:\n"
|
|
"1 - Turn off your radio\n"
|
|
"2 - Connect your interface cable\n"
|
|
"3 - Turn on your radio\n"
|
|
"4 - Do the upload of your radio data\n")
|
|
return rp
|
|
|
|
def get_features(self):
|
|
rf = chirp_common.RadioFeatures()
|
|
rf.has_settings = True
|
|
rf.has_bank = False
|
|
rf.has_tuning_step = False
|
|
rf.can_odd_split = True
|
|
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 = self.VARIANT == ""
|
|
rf.valid_modes = MODES
|
|
rf.valid_characters = VALID_CHARS
|
|
rf.valid_duplexes = ["", "-", "+", "split", "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_skips = SKIP_VALUES
|
|
rf.valid_name_length = NAME_LENGTH
|
|
rf.valid_dtcs_codes = DTCS_CODES
|
|
rf.valid_tuning_steps = _STEP_LIST
|
|
rf.valid_bands = [self._air_range,
|
|
self._vhf_range,
|
|
self._vhf2_range,
|
|
self._220_range,
|
|
self._gen1_range,
|
|
self._uhf_range,
|
|
self._gen2_range]
|
|
rf.memory_bounds = (0, self._upper)
|
|
return rf
|
|
|
|
def get_sub_devices(self):
|
|
return [UV50X3Left(self._mmap), UV50X3Right(self._mmap)]
|
|
|
|
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.MemoryMapBytes(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])
|
|
|
|
def decode_tone(self, val):
|
|
"""Parse the tone data to decode from mem, it returns:
|
|
Mode (''|DTCS|Tone), Value (None|###), Polarity (None,N,R)"""
|
|
if val.get_raw() == "\xFF\xFF":
|
|
return '', None, None
|
|
|
|
val = int(val)
|
|
if val >= 12000:
|
|
a = val - 12000
|
|
return 'DTCS', a, 'R'
|
|
elif val >= 8000:
|
|
a = val - 8000
|
|
return 'DTCS', a, 'N'
|
|
else:
|
|
a = val / 10.0
|
|
return 'Tone', a, None
|
|
|
|
def encode_tone(self, memval, mode, value, pol):
|
|
"""Parse the tone data to encode from UI to mem"""
|
|
if mode == '':
|
|
memval[0].set_raw(0xFF)
|
|
memval[1].set_raw(0xFF)
|
|
elif mode == 'Tone':
|
|
memval.set_value(int(value * 10))
|
|
elif mode == 'DTCS':
|
|
flag = 0x80 if pol == 'N' else 0xC0
|
|
memval.set_value(value)
|
|
memval[1].set_bits(flag)
|
|
else:
|
|
raise Exception("Internal error: invalid mode `%s'" % mode)
|
|
|
|
def _memory_obj(self, suffix=""):
|
|
return getattr(self._memobj, "%s_memory%s" % (self._vfo, suffix))
|
|
|
|
def _name_obj(self, suffix=""):
|
|
return getattr(self._memobj, "%s_names%s" % (self._vfo, suffix))
|
|
|
|
def _scan_obj(self, suffix=""):
|
|
return getattr(self._memobj, "%s_scanflags%s" % (self._vfo, suffix))
|
|
|
|
def _used_obj(self, suffix=""):
|
|
return getattr(self._memobj, "%s_usedflags%s" % (self._vfo, suffix))
|
|
|
|
def get_memory(self, number):
|
|
"""Get the mem representation from the radio image"""
|
|
bitpos = (1 << (number % 8))
|
|
bytepos = (number / 8)
|
|
|
|
_mem = self._memory_obj()[number]
|
|
_names = self._name_obj()[number]
|
|
_scn = self._scan_obj()[bytepos]
|
|
_usd = self._used_obj()[bytepos]
|
|
|
|
isused = bitpos & int(_usd)
|
|
isscan = bitpos & int(_scn)
|
|
|
|
# Create a high-level memory object to return to the UI
|
|
mem = chirp_common.Memory()
|
|
|
|
# Memory number
|
|
mem.number = number
|
|
|
|
if not isused:
|
|
mem.empty = True
|
|
return mem
|
|
|
|
# Freq and offset
|
|
mem.freq = int(_mem.rxfreq) * 10
|
|
# tx freq can be blank
|
|
if _mem.get_raw()[4] == "\xFF":
|
|
# TX freq not set
|
|
mem.offset = 0
|
|
mem.duplex = "off"
|
|
else:
|
|
# TX feq set
|
|
offset = (int(_mem.txfreq) * 10) - mem.freq
|
|
if offset != 0:
|
|
if _split(self.get_features(), mem.freq, int(
|
|
_mem.txfreq) * 10):
|
|
mem.duplex = "split"
|
|
mem.offset = int(_mem.txfreq) * 10
|
|
elif offset < 0:
|
|
mem.offset = abs(offset)
|
|
mem.duplex = "-"
|
|
elif offset > 0:
|
|
mem.offset = offset
|
|
mem.duplex = "+"
|
|
else:
|
|
mem.offset = 0
|
|
|
|
# skip
|
|
if not isscan:
|
|
mem.skip = "S"
|
|
|
|
# name TAG of the channel
|
|
mem.name = str(_names.name).strip("\xFF")
|
|
|
|
# power
|
|
mem.power = POWER_LEVELS[int(_mem.txp)]
|
|
|
|
# wide/narrow
|
|
mem.mode = MODES[int(_mem.wn)]
|
|
|
|
# tone data
|
|
rxtone = txtone = None
|
|
txtone = self.decode_tone(_mem.txtone)
|
|
rxtone = self.decode_tone(_mem.rxtone)
|
|
chirp_common.split_tone_decode(mem, txtone, rxtone)
|
|
|
|
# Extra
|
|
mem.extra = RadioSettingGroup("extra", "Extra")
|
|
|
|
bcl = RadioSetting("bcl", "Busy channel lockout",
|
|
RadioSettingValueBoolean(bool(_mem.bcl)))
|
|
mem.extra.append(bcl)
|
|
|
|
revert = RadioSetting("revert", "Revert",
|
|
RadioSettingValueBoolean(bool(_mem.revert)))
|
|
mem.extra.append(revert)
|
|
|
|
dname = RadioSetting("dname", "Display name",
|
|
RadioSettingValueBoolean(bool(_mem.dname)))
|
|
mem.extra.append(dname)
|
|
|
|
return mem
|
|
|
|
def set_memory(self, mem):
|
|
"""Set the memory data in the eeprom img from the UI"""
|
|
bitpos = (1 << (mem.number % 8))
|
|
bytepos = (mem.number / 8)
|
|
|
|
_mem = self._memory_obj()[mem.number]
|
|
_names = self._name_obj()[mem.number]
|
|
_scn = self._scan_obj()[bytepos]
|
|
_usd = self._used_obj()[bytepos]
|
|
|
|
if mem.empty:
|
|
_usd &= ~bitpos
|
|
_scn &= ~bitpos
|
|
_mem.set_raw("\xFF" * 16)
|
|
_names.name = ("\xFF" * 6)
|
|
return
|
|
else:
|
|
_usd |= bitpos
|
|
|
|
# frequency
|
|
_mem.rxfreq = mem.freq / 10
|
|
|
|
# duplex
|
|
if mem.duplex == "+":
|
|
_mem.txfreq = (mem.freq + mem.offset) / 10
|
|
elif mem.duplex == "-":
|
|
_mem.txfreq = (mem.freq - mem.offset) / 10
|
|
elif mem.duplex == "off":
|
|
for i in _mem.txfreq:
|
|
i.set_raw("\xFF")
|
|
elif mem.duplex == "split":
|
|
_mem.txfreq = mem.offset / 10
|
|
else:
|
|
_mem.txfreq = mem.freq / 10
|
|
|
|
# tone data
|
|
((txmode, txtone, txpol), (rxmode, rxtone, rxpol)) = \
|
|
chirp_common.split_tone_encode(mem)
|
|
self.encode_tone(_mem.txtone, txmode, txtone, txpol)
|
|
self.encode_tone(_mem.rxtone, rxmode, rxtone, rxpol)
|
|
|
|
# name TAG of the channel
|
|
_names.name = mem.name.rstrip().ljust(6, "\xFF")
|
|
|
|
# power level, # default power level is low
|
|
_mem.txp = 0 if mem.power is None else POWER_LEVELS.index(mem.power)
|
|
|
|
# wide/narrow
|
|
_mem.wn = MODES.index(mem.mode)
|
|
|
|
if mem.skip == "S":
|
|
_scn &= ~bitpos
|
|
else:
|
|
_scn |= bitpos
|
|
|
|
# autoset display to display name if filled
|
|
if mem.extra:
|
|
# mem.extra only seems to be populated when called from edit panel
|
|
dname = mem.extra["dname"]
|
|
else:
|
|
dname = None
|
|
if mem.name:
|
|
_mem.dname = True
|
|
if dname and not dname.changed():
|
|
dname.value = True
|
|
else:
|
|
_mem.dname = False
|
|
if dname and not dname.changed():
|
|
dname.value = False
|
|
|
|
# resetting unknowns, this has to be set by hand
|
|
_mem.unknown0 = 0
|
|
_mem.unknown1 = 0
|
|
_mem.unknown2 = 0
|
|
_mem.unknown3 = 0
|
|
|
|
# extra settings
|
|
if len(mem.extra) > 0:
|
|
# there are setting, parse
|
|
for setting in mem.extra:
|
|
setattr(_mem, setting.get_name(), setting.value)
|
|
else:
|
|
# there are no extra settings, load defaults
|
|
_mem.bcl = 0
|
|
_mem.revert = 0
|
|
_mem.dname = 1
|
|
|
|
def _bbcd2dtmf(self, bcdarr, strlen=16):
|
|
# doing bbcd, but with support for ABCD*#
|
|
LOG.debug(bcdarr.get_value())
|
|
string = ''.join("%02X" % b for b in bcdarr)
|
|
LOG.debug("@_bbcd2dtmf, received: %s" % string)
|
|
string = string.replace('E', '*').replace('F', '#')
|
|
if strlen <= 16:
|
|
string = string[:strlen]
|
|
return string
|
|
|
|
def _dtmf2bbcd(self, value):
|
|
dtmfstr = value.get_value()
|
|
dtmfstr = dtmfstr.replace('*', 'E').replace('#', 'F')
|
|
dtmfstr = str.ljust(dtmfstr.strip(), 16, "F")
|
|
bcdarr = list(bytearray.fromhex(dtmfstr))
|
|
LOG.debug("@_dtmf2bbcd, sending: %s" % bcdarr)
|
|
return bcdarr
|
|
|
|
def get_settings(self):
|
|
"""Translate the bit in the mem_struct into settings in the UI"""
|
|
_mem = self._memobj
|
|
basic = RadioSettingGroup("basic", "Basic Settings")
|
|
other = RadioSettingGroup("other", "Other Settings")
|
|
work = RadioSettingGroup("work", "Work Mode Settings")
|
|
dtmf = RadioSettingGroup("dtmf", "DTMF Settings")
|
|
top = RadioSettings(basic, other, work, dtmf)
|
|
|
|
# Basic
|
|
|
|
# Audio: A01-A04
|
|
|
|
aftone = RadioSetting("settings.aftone", "AF tone control",
|
|
RadioSettingValueList(LIST_AFTONE, LIST_AFTONE[
|
|
_mem.settings.aftone]))
|
|
basic.append(aftone)
|
|
|
|
spkr = RadioSetting("settings.spkr", "Speaker",
|
|
RadioSettingValueList(LIST_SPKR, LIST_SPKR[
|
|
_mem.settings.spkr]))
|
|
basic.append(spkr)
|
|
|
|
audio = RadioSetting("settings.audio", "Stereo/Mono",
|
|
RadioSettingValueList(LIST_AUDIO, LIST_AUDIO[
|
|
_mem.settings.audio]))
|
|
basic.append(audio)
|
|
|
|
sbmute = RadioSetting("settings.sbmute", "Sub band mute",
|
|
RadioSettingValueList(LIST_SBMUTE, LIST_SBMUTE[
|
|
_mem.settings.sbmute]))
|
|
basic.append(sbmute)
|
|
|
|
# TX/RX: B01-B08
|
|
|
|
mgain = RadioSetting("settings.mgain", "Mic gain",
|
|
RadioSettingValueList(LIST_MLNHM, LIST_MLNHM[
|
|
_mem.settings.mgain]))
|
|
basic.append(mgain)
|
|
|
|
ptt = RadioSetting("settings.ptt", "PTT mode",
|
|
RadioSettingValueList(LIST_PTT, LIST_PTT[
|
|
_mem.settings.ptt]))
|
|
basic.append(ptt)
|
|
|
|
# B03 (per channel)
|
|
# B04 (per channel)
|
|
|
|
rxexp = RadioSetting("settings.rxexp", "RX expansion",
|
|
RadioSettingValueList(LIST_RXEXP, LIST_RXEXP[
|
|
_mem.settings.rxexp]))
|
|
basic.append(rxexp)
|
|
|
|
vox = RadioSetting("settings.vox", "Vox",
|
|
RadioSettingValueList(LIST_VOX, LIST_VOX[
|
|
_mem.settings.vox]))
|
|
basic.append(vox)
|
|
|
|
voxs = RadioSetting("settings.voxs", "Vox sensitivity",
|
|
RadioSettingValueList(LIST_MLNHM, LIST_MLNHM[
|
|
_mem.settings.voxs]))
|
|
basic.append(voxs)
|
|
|
|
# B08 (per channel)
|
|
|
|
# Display: C01-C06
|
|
|
|
display = RadioSetting("settings.display", "Display select",
|
|
RadioSettingValueList(
|
|
LIST_DISPLAY, LIST_DISPLAY[
|
|
_mem.settings.display]))
|
|
basic.append(display)
|
|
|
|
lcdb = RadioSetting("settings.lcdb", "LCD brightness",
|
|
RadioSettingValueList(LIST_MINMAX, LIST_MINMAX[
|
|
_mem.settings.lcdb]))
|
|
basic.append(lcdb)
|
|
|
|
color = RadioSetting("settings.color", "LCD color",
|
|
RadioSettingValueList(LIST_COLOR, LIST_COLOR[
|
|
_mem.settings.color]))
|
|
basic.append(color)
|
|
|
|
lcdc = RadioSetting("settings.lcdc", "LCD contrast",
|
|
RadioSettingValueList(LIST_MINMAX, LIST_MINMAX[
|
|
_mem.settings.lcdc]))
|
|
basic.append(lcdc)
|
|
|
|
btime = RadioSetting("settings.btime", "LCD backlight time",
|
|
RadioSettingValueList(LIST_BTIME, LIST_BTIME[
|
|
_mem.settings.btime]))
|
|
basic.append(btime)
|
|
|
|
keyb = RadioSetting("settings.keyb", "Key brightness",
|
|
RadioSettingValueList(LIST_MINMAX, LIST_MINMAX[
|
|
_mem.settings.keyb]))
|
|
basic.append(keyb)
|
|
|
|
# Memory: D01-D04
|
|
|
|
# D01 (per channel)
|
|
# D02 (per channel)
|
|
|
|
mrscan = RadioSetting("settings.mrscan", "Memory scan type",
|
|
RadioSettingValueList(LIST_MRSCAN, LIST_MRSCAN[
|
|
_mem.settings.mrscan]))
|
|
basic.append(mrscan)
|
|
|
|
# D04 (per channel)
|
|
|
|
# Scan: E01-E04
|
|
|
|
dwstop = RadioSetting("settings.dwstop", "Dual watch stop",
|
|
RadioSettingValueList(LIST_DWSTOP, LIST_DWSTOP[
|
|
_mem.settings.dwstop]))
|
|
basic.append(dwstop)
|
|
|
|
scand = RadioSetting("settings.scand", "Scan direction",
|
|
RadioSettingValueList(LIST_SCAND, LIST_SCAND[
|
|
_mem.settings.scand]))
|
|
basic.append(scand)
|
|
|
|
scanr = RadioSetting("settings.scanr", "Scan resume",
|
|
RadioSettingValueList(LIST_SCANR, LIST_SCANR[
|
|
_mem.settings.scanr]))
|
|
basic.append(scanr)
|
|
|
|
scansb = RadioSetting("settings.scansb", "Scan stop beep",
|
|
RadioSettingValueBoolean(_mem.settings.scansb))
|
|
basic.append(scansb)
|
|
|
|
# System: F01-F09
|
|
|
|
apo = RadioSetting("settings.apo", "Automatic power off [hours]",
|
|
RadioSettingValueList(LIST_APO, LIST_APO[
|
|
_mem.settings.apo]))
|
|
basic.append(apo)
|
|
|
|
ars = RadioSetting("settings.ars", "Automatic repeater shift",
|
|
RadioSettingValueBoolean(_mem.settings.ars))
|
|
basic.append(ars)
|
|
|
|
beep = RadioSetting("settings.beep", "Beep volume",
|
|
RadioSettingValueList(LIST_BEEP, LIST_BEEP[
|
|
_mem.settings.beep]))
|
|
basic.append(beep)
|
|
|
|
fkey = RadioSetting("settings.fkey", "F key",
|
|
RadioSettingValueList(LIST_FKEY, LIST_FKEY[
|
|
_mem.settings.fkey]))
|
|
basic.append(fkey)
|
|
|
|
pfkey1 = RadioSetting("settings.pfkey1", "Mic P1 key",
|
|
RadioSettingValueList(LIST_PFKEY, LIST_PFKEY[
|
|
_mem.settings.pfkey1]))
|
|
basic.append(pfkey1)
|
|
|
|
pfkey2 = RadioSetting("settings.pfkey2", "Mic P2 key",
|
|
RadioSettingValueList(LIST_PFKEY, LIST_PFKEY[
|
|
_mem.settings.pfkey2]))
|
|
basic.append(pfkey2)
|
|
|
|
pfkey3 = RadioSetting("settings.pfkey3", "Mic P3 key",
|
|
RadioSettingValueList(LIST_PFKEY, LIST_PFKEY[
|
|
_mem.settings.pfkey3]))
|
|
basic.append(pfkey3)
|
|
|
|
pfkey4 = RadioSetting("settings.pfkey4", "Mic P4 key",
|
|
RadioSettingValueList(LIST_PFKEY, LIST_PFKEY[
|
|
_mem.settings.pfkey4]))
|
|
basic.append(pfkey4)
|
|
|
|
omode = RadioSetting("settings.omode", "Operation mode",
|
|
RadioSettingValueList(LIST_AB, LIST_AB[
|
|
_mem.settings.omode]))
|
|
basic.append(omode)
|
|
|
|
rxcoverm = RadioSetting("settings.rxcoverm", "RX coverage - memory",
|
|
RadioSettingValueList(
|
|
LIST_COVERAGE, LIST_COVERAGE[
|
|
_mem.settings.rxcoverm]))
|
|
basic.append(rxcoverm)
|
|
|
|
rxcoverv = RadioSetting("settings.rxcoverv", "RX coverage - VFO",
|
|
RadioSettingValueList(
|
|
LIST_COVERAGE, LIST_COVERAGE[
|
|
_mem.settings.rxcoverv]))
|
|
basic.append(rxcoverv)
|
|
|
|
tot = RadioSetting("settings.tot", "Time out timer [min]",
|
|
RadioSettingValueList(LIST_TOT, LIST_TOT[
|
|
_mem.settings.tot]))
|
|
basic.append(tot)
|
|
|
|
# Timer/Clock: G01-G04
|
|
|
|
# G01
|
|
datefmt = RadioSetting("settings.datefmt", "Date format",
|
|
RadioSettingValueList(
|
|
LIST_DATEFMT, LIST_DATEFMT[
|
|
_mem.settings.datefmt]))
|
|
basic.append(datefmt)
|
|
|
|
timefmt = RadioSetting("settings.timefmt", "Time format",
|
|
RadioSettingValueList(
|
|
LIST_TIMEFMT, LIST_TIMEFMT[
|
|
_mem.settings.timefmt]))
|
|
basic.append(timefmt)
|
|
|
|
timesig = RadioSetting("settings.timesig", "Time signal",
|
|
RadioSettingValueBoolean(_mem.settings.timesig))
|
|
basic.append(timesig)
|
|
|
|
tz = RadioSetting("settings.tz", "Time zone",
|
|
RadioSettingValueList(LIST_TZ, LIST_TZ[
|
|
_mem.settings.tz]))
|
|
basic.append(tz)
|
|
|
|
# Signaling: H01-H06
|
|
|
|
bell = RadioSetting("settings.bell", "Bell ringer",
|
|
RadioSettingValueList(LIST_BELL, LIST_BELL[
|
|
_mem.settings.bell]))
|
|
basic.append(bell)
|
|
|
|
# H02 (per channel)
|
|
|
|
dtmfmodenc = RadioSetting("settings.dtmfmodenc", "DTMF mode encode",
|
|
RadioSettingValueBoolean(
|
|
_mem.settings.dtmfmodenc))
|
|
basic.append(dtmfmodenc)
|
|
|
|
dtmfmoddec = RadioSetting("settings.dtmfmoddec", "DTMF mode decode",
|
|
RadioSettingValueBoolean(
|
|
_mem.settings.dtmfmoddec))
|
|
basic.append(dtmfmoddec)
|
|
|
|
# H04 (per channel)
|
|
|
|
decbandsel = RadioSetting("settings.decbandsel", "DTMF band select",
|
|
RadioSettingValueList(LIST_AB, LIST_AB[
|
|
_mem.settings.decbandsel]))
|
|
basic.append(decbandsel)
|
|
|
|
sqlexp = RadioSetting("settings.sqlexp", "SQL expansion",
|
|
RadioSettingValueBoolean(_mem.settings.sqlexp))
|
|
basic.append(sqlexp)
|
|
|
|
# Pkt: I01-I03
|
|
|
|
databnd = RadioSetting("settings.databnd", "Packet data band",
|
|
RadioSettingValueList(
|
|
LIST_DATABND, LIST_DATABND[
|
|
_mem.settings.databnd]))
|
|
basic.append(databnd)
|
|
|
|
dataspd = RadioSetting("settings.dataspd", "Packet data speed",
|
|
RadioSettingValueList(
|
|
LIST_DATASPD, LIST_DATASPD[
|
|
_mem.settings.dataspd]))
|
|
basic.append(dataspd)
|
|
|
|
datasql = RadioSetting("settings.datasql", "Packet data squelch",
|
|
RadioSettingValueList(
|
|
LIST_DATASQL, LIST_DATASQL[
|
|
_mem.settings.datasql]))
|
|
basic.append(datasql)
|
|
|
|
# Other
|
|
|
|
dw = RadioSetting("settings.dw", "Dual watch",
|
|
RadioSettingValueBoolean(_mem.settings.dw))
|
|
other.append(dw)
|
|
|
|
cpuclk = RadioSetting("settings.cpuclk", "CPU clock frequency",
|
|
RadioSettingValueList(LIST_CPUCLK, LIST_CPUCLK[
|
|
_mem.settings.cpuclk]))
|
|
other.append(cpuclk)
|
|
|
|
def _filter(name):
|
|
filtered = ""
|
|
for char in str(name):
|
|
if char in VALID_CHARS:
|
|
filtered += char
|
|
else:
|
|
filtered += " "
|
|
return filtered
|
|
|
|
line16 = RadioSetting("poweron_msg.line16", "Power-on message",
|
|
RadioSettingValueString(0, 16, _filter(
|
|
_mem.poweron_msg.line16)))
|
|
other.append(line16)
|
|
|
|
line32 = RadioSetting("embedded_msg.line32", "Embedded message",
|
|
RadioSettingValueString(0, 32, _filter(
|
|
_mem.embedded_msg.line32)))
|
|
other.append(line32)
|
|
|
|
# Work
|
|
|
|
workmoda = RadioSetting("settings.workmoda", "Work mode A",
|
|
RadioSettingValueList(LIST_WORK, LIST_WORK[
|
|
_mem.settings.workmoda]))
|
|
work.append(workmoda)
|
|
|
|
workmodb = RadioSetting("settings.workmodb", "Work mode B",
|
|
RadioSettingValueList(LIST_WORK, LIST_WORK[
|
|
_mem.settings.workmodb]))
|
|
work.append(workmodb)
|
|
|
|
wbanda = RadioSetting("settings.wbanda", "Work band A",
|
|
RadioSettingValueList(LIST_WBANDA, LIST_WBANDA[
|
|
(_mem.settings.wbanda) - 1]))
|
|
work.append(wbanda)
|
|
|
|
wbandb = RadioSetting("settings.wbandb", "Work band B",
|
|
RadioSettingValueList(LIST_WBANDB, LIST_WBANDB[
|
|
(_mem.settings.wbandb) - 4]))
|
|
work.append(wbandb)
|
|
|
|
sqla = RadioSetting("settings.sqla", "Squelch A",
|
|
RadioSettingValueList(LIST_SQL, LIST_SQL[
|
|
_mem.settings.sqla]))
|
|
work.append(sqla)
|
|
|
|
sqlb = RadioSetting("settings.sqlb", "Squelch B",
|
|
RadioSettingValueList(LIST_SQL, LIST_SQL[
|
|
_mem.settings.sqlb]))
|
|
work.append(sqlb)
|
|
|
|
stepa = RadioSetting("settings.stepa", "Auto step A",
|
|
RadioSettingValueList(LIST_STEP, LIST_STEP[
|
|
_mem.settings.stepa]))
|
|
work.append(stepa)
|
|
|
|
stepb = RadioSetting("settings.stepb", "Auto step B",
|
|
RadioSettingValueList(LIST_STEP, LIST_STEP[
|
|
_mem.settings.stepb]))
|
|
work.append(stepb)
|
|
|
|
mrcha = RadioSetting("settings.mrcha", "Current channel A",
|
|
RadioSettingValueInteger(0, 499,
|
|
_mem.settings.mrcha))
|
|
work.append(mrcha)
|
|
|
|
mrchb = RadioSetting("settings.mrchb", "Current channel B",
|
|
RadioSettingValueInteger(0, 499,
|
|
_mem.settings.mrchb))
|
|
work.append(mrchb)
|
|
|
|
val = _mem.settings.offseta / 100.00
|
|
offseta = RadioSetting("settings.offseta", "Offset A (0-37.95)",
|
|
RadioSettingValueFloat(0, 38.00, val, 0.05, 2))
|
|
work.append(offseta)
|
|
|
|
val = _mem.settings.offsetb / 100.00
|
|
offsetb = RadioSetting("settings.offsetb", "Offset B (0-79.95)",
|
|
RadioSettingValueFloat(0, 80.00, val, 0.05, 2))
|
|
work.append(offsetb)
|
|
|
|
wpricha = RadioSetting("settings.wpricha", "Priority channel A",
|
|
RadioSettingValueInteger(0, 499,
|
|
_mem.settings.wpricha))
|
|
work.append(wpricha)
|
|
|
|
wprichb = RadioSetting("settings.wprichb", "Priority channel B",
|
|
RadioSettingValueInteger(0, 499,
|
|
_mem.settings.wprichb))
|
|
work.append(wprichb)
|
|
|
|
smode = RadioSetting("settings.smode", "Smart function mode",
|
|
RadioSettingValueList(LIST_SMODE, LIST_SMODE[
|
|
_mem.settings.smode]))
|
|
work.append(smode)
|
|
|
|
# dtmf
|
|
|
|
ttdkey = RadioSetting("dtmf.ttdkey", "D key function",
|
|
RadioSettingValueList(LIST_TTDKEY, LIST_TTDKEY[
|
|
_mem.dtmf.ttdkey]))
|
|
dtmf.append(ttdkey)
|
|
|
|
ttdgt = RadioSetting("dtmf.ttdgt", "Digit time",
|
|
RadioSettingValueList(LIST_TT200, LIST_TT200[
|
|
(_mem.dtmf.ttdgt) - 5]))
|
|
dtmf.append(ttdgt)
|
|
|
|
ttint = RadioSetting("dtmf.ttint", "Interval time",
|
|
RadioSettingValueList(LIST_TT200, LIST_TT200[
|
|
(_mem.dtmf.ttint) - 5]))
|
|
dtmf.append(ttint)
|
|
|
|
tt1stdgt = RadioSetting("dtmf.tt1stdgt", "1st digit time",
|
|
RadioSettingValueList(LIST_TT200, LIST_TT200[
|
|
(_mem.dtmf.tt1stdgt) - 5]))
|
|
dtmf.append(tt1stdgt)
|
|
|
|
tt1stdly = RadioSetting("dtmf.tt1stdly", "1st digit delay time",
|
|
RadioSettingValueList(LIST_TT1000, LIST_TT1000[
|
|
(_mem.dtmf.tt1stdly) - 2]))
|
|
dtmf.append(tt1stdly)
|
|
|
|
ttdlyqt = RadioSetting("dtmf.ttdlyqt", "Digit delay when use qt",
|
|
RadioSettingValueList(LIST_TT1000, LIST_TT1000[
|
|
(_mem.dtmf.ttdlyqt) - 2]))
|
|
dtmf.append(ttdlyqt)
|
|
|
|
ttsig = RadioSetting("dtmf2.ttsig", "Signal",
|
|
RadioSettingValueList(LIST_TTSIG, LIST_TTSIG[
|
|
_mem.dtmf2.ttsig]))
|
|
dtmf.append(ttsig)
|
|
|
|
ttautorst = RadioSetting("dtmf2.ttautorst", "Auto reset time",
|
|
RadioSettingValueList(
|
|
LIST_TTAUTORST, LIST_TTAUTORST[
|
|
_mem.dtmf2.ttautorst]))
|
|
dtmf.append(ttautorst)
|
|
|
|
if _mem.dtmf2.ttgrpcode > 0x06:
|
|
val = 0x00
|
|
else:
|
|
val = _mem.dtmf2.ttgrpcode
|
|
ttgrpcode = RadioSetting("dtmf2.ttgrpcode", "Group code",
|
|
RadioSettingValueList(LIST_TTGRPCODE,
|
|
LIST_TTGRPCODE[val]))
|
|
dtmf.append(ttgrpcode)
|
|
|
|
ttintcode = RadioSetting("dtmf2.ttintcode", "Interval code",
|
|
RadioSettingValueList(
|
|
LIST_TTINTCODE, LIST_TTINTCODE[
|
|
_mem.dtmf2.ttintcode]))
|
|
dtmf.append(ttintcode)
|
|
|
|
if _mem.dtmf2.ttalert > 0x04:
|
|
val = 0x00
|
|
else:
|
|
val = _mem.dtmf2.ttalert
|
|
ttalert = RadioSetting("dtmf2.ttalert", "Alert tone/transpond",
|
|
RadioSettingValueList(LIST_TTALERT,
|
|
LIST_TTALERT[val]))
|
|
dtmf.append(ttalert)
|
|
|
|
ttautod = RadioSetting("dtmf.ttautod", "Auto dial group",
|
|
RadioSettingValueList(
|
|
LIST_TTAUTOD, LIST_TTAUTOD[
|
|
_mem.dtmf.ttautod]))
|
|
dtmf.append(ttautod)
|
|
|
|
# setup 9 dtmf autodial entries
|
|
for i in map(str, list(range(1, 10))):
|
|
objname = "code" + i
|
|
strname = "Code " + str(i)
|
|
dtmfsetting = getattr(_mem.dtmfcode, objname)
|
|
dtmflen = getattr(_mem.dtmfcode, objname + "_len")
|
|
dtmfstr = self._bbcd2dtmf(dtmfsetting, dtmflen)
|
|
code = RadioSettingValueString(0, 16, dtmfstr)
|
|
code.set_charset(DTMF_CHARS + list(" "))
|
|
rs = RadioSetting("dtmfcode." + objname, strname, code)
|
|
dtmf.append(rs)
|
|
return top
|
|
|
|
def set_settings(self, settings):
|
|
_settings = self._memobj.settings
|
|
_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 setting == "line16":
|
|
setattr(obj, setting, str(element.value).rstrip(
|
|
" ").ljust(16, "\xFF"))
|
|
elif setting == "line32":
|
|
setattr(obj, setting, str(element.value).rstrip(
|
|
" ").ljust(32, "\xFF"))
|
|
elif setting == "wbanda":
|
|
setattr(obj, setting, int(element.value) + 1)
|
|
elif setting == "wbandb":
|
|
setattr(obj, setting, int(element.value) + 4)
|
|
elif setting in ["offseta", "offsetb"]:
|
|
val = element.value
|
|
value = int(val.get_value() * 100)
|
|
setattr(obj, setting, value)
|
|
elif setting in ["ttdgt", "ttint", "tt1stdgt"]:
|
|
setattr(obj, setting, int(element.value) + 5)
|
|
elif setting in ["tt1stdly", "ttdlyqt"]:
|
|
setattr(obj, setting, int(element.value) + 2)
|
|
elif re.match('code\d', setting):
|
|
# set dtmf length field and then get bcd dtmf
|
|
dtmfstrlen = len(str(element.value).strip())
|
|
setattr(_mem.dtmfcode, setting + "_len", dtmfstrlen)
|
|
dtmfstr = self._dtmf2bbcd(element.value)
|
|
setattr(_mem.dtmfcode, setting, dtmfstr)
|
|
elif element.value.get_mutable():
|
|
LOG.debug("Setting %s = %s" % (setting, element.value))
|
|
setattr(obj, setting, element.value)
|
|
except Exception as 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:
|
|
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
|
|
|
|
|
|
@directory.register
|
|
class UV50X3(VGCStyleRadio):
|
|
"""BTech UV-50X3"""
|
|
MODEL = "UV-50X3"
|
|
IDENT = UV50X3_id
|
|
|
|
|
|
class UV50X3Left(UV50X3):
|
|
VARIANT = "Left"
|
|
_vfo = "left"
|
|
|
|
|
|
class UV50X3Right(UV50X3):
|
|
VARIANT = "Right"
|
|
_vfo = "right"
|