|
# Copyright 2011 Dan Smith <dsmith@danplanet.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 3 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/>.
|
|
|
|
"""Wouxun radios management module"""
|
|
|
|
import time
|
|
import logging
|
|
from chirp import util, chirp_common, bitwise, memmap, errors, directory
|
|
from chirp.settings import RadioSetting, RadioSettingGroup, \
|
|
RadioSettingValueBoolean, RadioSettingValueList, \
|
|
RadioSettingValueInteger, RadioSettingValueString, \
|
|
RadioSettingValueFloat, RadioSettings
|
|
from chirp.drivers.wouxun_common import wipe_memory, do_download, do_upload
|
|
|
|
LOG = logging.getLogger(__name__)
|
|
|
|
FREQ_ENCODE_TABLE = [0x7, 0xa, 0x0, 0x9, 0xb, 0x2, 0xe, 0x1, 0x3, 0xf]
|
|
|
|
|
|
def encode_freq(freq):
|
|
"""Convert frequency (4 decimal digits) to Wouxun format (2 bytes)"""
|
|
enc = 0
|
|
div = 1000
|
|
for i in range(0, 4):
|
|
enc <<= 4
|
|
enc |= FREQ_ENCODE_TABLE[int((freq/div) % 10)]
|
|
div /= 10
|
|
return enc
|
|
|
|
|
|
def decode_freq(data):
|
|
"""Convert from Wouxun format (2 bytes) to frequency (4 decimal digits)"""
|
|
freq = 0
|
|
shift = 12
|
|
for i in range(0, 4):
|
|
freq *= 10
|
|
freq += FREQ_ENCODE_TABLE.index((data >> shift) & 0xf)
|
|
shift -= 4
|
|
# LOG.debug("data %04x freq %d shift %d" % (data, freq, shift))
|
|
return freq
|
|
|
|
|
|
@directory.register
|
|
class KGUVD1PRadio(chirp_common.CloneModeRadio,
|
|
chirp_common.ExperimentalRadio):
|
|
"""Wouxun KG-UVD1P,UV2,UV3"""
|
|
VENDOR = "Wouxun"
|
|
MODEL = "KG-UVD1P"
|
|
_model = b"KG669V"
|
|
|
|
_querymodel = (b"HiWOUXUN\x02", b"PROGUV6X\x02")
|
|
|
|
CHARSET = list("0123456789") + \
|
|
[chr(x + ord("A")) for x in range(0, 26)] + list("?+-")
|
|
|
|
POWER_LEVELS = [chirp_common.PowerLevel("High", watts=5.00),
|
|
chirp_common.PowerLevel("Low", watts=1.00)]
|
|
|
|
valid_freq = [(136000000, 175000000), (216000000, 520000000)]
|
|
|
|
_MEM_FORMAT = """
|
|
#seekto 0x0010;
|
|
struct {
|
|
lbcd rx_freq[4];
|
|
lbcd tx_freq[4];
|
|
ul16 rx_tone;
|
|
ul16 tx_tone;
|
|
u8 _3_unknown_1:4,
|
|
bcl:1,
|
|
_3_unknown_2:3;
|
|
u8 splitdup:1,
|
|
skip:1,
|
|
power_high:1,
|
|
iswide:1,
|
|
_2_unknown_2:4;
|
|
u8 unknown;
|
|
u8 _0_unknown_1:3,
|
|
iswidex:1,
|
|
_0_unknown_2:4;
|
|
} memory[199];
|
|
|
|
#seekto 0x0842;
|
|
u16 fm_presets_0[9];
|
|
|
|
#seekto 0x0882;
|
|
u16 fm_presets_1[9];
|
|
|
|
#seekto 0x0970;
|
|
struct {
|
|
u16 vhf_rx_start;
|
|
u16 vhf_rx_stop;
|
|
u16 uhf_rx_start;
|
|
u16 uhf_rx_stop;
|
|
u16 vhf_tx_start;
|
|
u16 vhf_tx_stop;
|
|
u16 uhf_tx_start;
|
|
u16 uhf_tx_stop;
|
|
} freq_ranges;
|
|
|
|
#seekto 0x0E00;
|
|
struct {
|
|
char welcome1[6];
|
|
char welcome2[6];
|
|
char single_band[6];
|
|
} strings;
|
|
|
|
#seekto 0x0E20;
|
|
struct {
|
|
u8 unknown_flag_01:6,
|
|
vfo_b_ch_disp:2;
|
|
u8 unknown_flag_02:5,
|
|
vfo_a_fr_step:3;
|
|
u8 unknown_flag_03:4,
|
|
vfo_a_squelch:4;
|
|
u8 unknown_flag_04:7,
|
|
power_save:1;
|
|
u8 unknown_flag_05:8;
|
|
u8 unknown_flag_06:6,
|
|
roger_beep:2;
|
|
u8 unknown_flag_07:2,
|
|
transmit_time_out:6;
|
|
u8 unknown_flag_08:4,
|
|
vox:4;
|
|
u8 unknown_1[4];
|
|
u8 unknown_flag_09:6,
|
|
voice:2;
|
|
u8 unknown_flag_10:7,
|
|
beep:1;
|
|
u8 unknown_flag_11:7,
|
|
ani_id_enable:1;
|
|
u8 unknown_2[2];
|
|
u8 unknown_flag_12:5,
|
|
vfo_b_fr_step:3;
|
|
u8 unknown_3[1];
|
|
u8 unknown_flag_13:3,
|
|
ani_id_tx_delay:5;
|
|
u8 unknown_4[1];
|
|
u8 unknown_flag_14:6,
|
|
ani_id_sidetone:2;
|
|
u8 unknown_flag_15:4,
|
|
tx_time_out_alert:4;
|
|
u8 unknown_flag_16:6,
|
|
vfo_a_ch_disp:2;
|
|
u8 unknown_flag_27:6,
|
|
scan_mode:2;
|
|
u8 unknown_flag_28:7,
|
|
kbd_lock:1;
|
|
u8 unknown_flag_17:6,
|
|
ponmsg:2;
|
|
u8 unknown_flag_18:5,
|
|
pf1_function:3;
|
|
u8 unknown_5[1];
|
|
u8 unknown_flag_19:7,
|
|
auto_backlight:1;
|
|
u8 unknown_flag_20:7,
|
|
sos_ch:1;
|
|
u8 unknown_6;
|
|
u8 sd_available;
|
|
u8 unknown_flag_21:7,
|
|
auto_lock_kbd:1;
|
|
u8 unknown_flag_22:4,
|
|
vfo_b_squelch:4;
|
|
u8 unknown_7[1];
|
|
u8 unknown_flag_23:7,
|
|
stopwatch:1;
|
|
u8 vfo_a_cur_chan;
|
|
u8 unknown_flag_24:7,
|
|
dual_band_receive:1;
|
|
u8 current_vfo:1,
|
|
unknown_flag_26:7;
|
|
u8 unknown_8[2];
|
|
u8 mode_password[6];
|
|
u8 reset_password[6];
|
|
u8 ani_id_content[6];
|
|
u8 unknown_flag_25:7,
|
|
menu_available:1;
|
|
u8 unknown_9[1];
|
|
u8 priority_chan;
|
|
u8 vfo_b_cur_chan;
|
|
} settings;
|
|
|
|
#seekto 0x1008;
|
|
struct {
|
|
u8 unknown[8];
|
|
u8 name[6];
|
|
u8 pad[2];
|
|
} names[199];
|
|
"""
|
|
|
|
@classmethod
|
|
def get_prompts(cls):
|
|
rp = chirp_common.RadioPrompts()
|
|
rp.experimental = \
|
|
('This version of the Wouxun driver allows you to modify the '
|
|
'frequency range settings of your radio. This has been tested '
|
|
'and reports from other users indicate that it is a safe '
|
|
'thing to do. However, modifications to this value may have '
|
|
'unintended consequences, including damage to your device. '
|
|
'You have been warned. Proceed at your own risk!')
|
|
rp.pre_download = _(
|
|
"1. Turn radio off.\n"
|
|
"2. Connect cable to mic/spkr connector.\n"
|
|
"3. Make sure connector is firmly connected.\n"
|
|
"4. Turn radio on.\n"
|
|
"5. Ensure that the radio is tuned to channel with no"
|
|
" activity.\n"
|
|
"6. Click OK to download image from device.\n")
|
|
rp.pre_upload = _(
|
|
"1. Turn radio off.\n"
|
|
"2. Connect cable to mic/spkr connector.\n"
|
|
"3. Make sure connector is firmly connected.\n"
|
|
"4. Turn radio on.\n"
|
|
"5. Ensure that the radio is tuned to channel with no"
|
|
" activity.\n"
|
|
"6. Click OK to upload image to device.\n")
|
|
return rp
|
|
|
|
@classmethod
|
|
def _get_querymodel(cls):
|
|
if isinstance(cls._querymodel, str):
|
|
while True:
|
|
yield cls._querymodel
|
|
else:
|
|
i = 0
|
|
while True:
|
|
yield cls._querymodel[i % len(cls._querymodel)]
|
|
i += 1
|
|
|
|
def _identify(self):
|
|
"""Do the original Wouxun identification dance"""
|
|
query = self._get_querymodel()
|
|
for _i in range(0, 10):
|
|
self.pipe.write(next(query))
|
|
resp = self.pipe.read(9)
|
|
if len(resp) != 9:
|
|
LOG.debug("Got:\n%s" % util.hexprint(resp))
|
|
LOG.info("Retrying identification...")
|
|
time.sleep(1)
|
|
continue
|
|
if resp[2:8] != self._model:
|
|
raise Exception("I can't talk to this model (%s)" %
|
|
util.hexprint(resp))
|
|
return
|
|
if len(resp) == 0:
|
|
raise Exception("Radio not responding")
|
|
else:
|
|
raise Exception("Unable to identify radio")
|
|
|
|
def _start_transfer(self):
|
|
"""Tell the radio to go into transfer mode"""
|
|
self.pipe.write(b"\x02\x06")
|
|
time.sleep(0.05)
|
|
ack = self.pipe.read(1)
|
|
if ack != b"\x06":
|
|
raise Exception("Radio refused transfer mode")
|
|
|
|
def _download(self):
|
|
"""Talk to an original Wouxun and do a download"""
|
|
try:
|
|
self._identify()
|
|
self._start_transfer()
|
|
return do_download(self, 0x0000, 0x2000, 0x0040)
|
|
except errors.RadioError:
|
|
raise
|
|
except Exception as e:
|
|
raise errors.RadioError("Failed to communicate with radio: %s" % e)
|
|
|
|
def _upload(self):
|
|
"""Talk to an original Wouxun and do an upload"""
|
|
try:
|
|
self._identify()
|
|
self._start_transfer()
|
|
return do_upload(self, 0x0000, 0x2000, 0x0010)
|
|
except errors.RadioError:
|
|
raise
|
|
except Exception as e:
|
|
raise errors.RadioError("Failed to communicate with radio: %s" % e)
|
|
|
|
def sync_in(self):
|
|
self._mmap = self._download()
|
|
self.process_mmap()
|
|
|
|
def sync_out(self):
|
|
self._upload()
|
|
|
|
def process_mmap(self):
|
|
if len(self._mmap.get_packed()) != 8192:
|
|
LOG.info("Fixing old-style Wouxun image")
|
|
# Originally, CHIRP's Wouxun image had eight bytes of
|
|
# static data, followed by the first memory at offset
|
|
# 0x0008. Between 0.1.11 and 0.1.12, this was fixed to 16
|
|
# bytes of (whatever) followed by the first memory at
|
|
# offset 0x0010, like the radio actually stores it. So,
|
|
# if we find one of those old ones, convert it to the new
|
|
# format, padding 16 bytes of 0xFF in front.
|
|
self._mmap = memmap.MemoryMap(
|
|
("\xFF" * 16) + self._mmap.get_packed()[8:8184])
|
|
self._memobj = bitwise.parse(self._MEM_FORMAT, self._mmap)
|
|
# This sets our frequency ranges, so run it once after we process the
|
|
# mmap to make sure they're set for later
|
|
self.get_settings()
|
|
|
|
def get_features(self):
|
|
rf = chirp_common.RadioFeatures()
|
|
rf.valid_tmodes = ["", "Tone", "TSQL", "DTCS", "Cross"]
|
|
rf.valid_cross_modes = [
|
|
"Tone->Tone",
|
|
"Tone->DTCS",
|
|
"DTCS->Tone",
|
|
"DTCS->",
|
|
"->Tone",
|
|
"->DTCS",
|
|
"DTCS->DTCS",
|
|
]
|
|
rf.valid_modes = ["FM", "NFM"]
|
|
rf.valid_power_levels = self.POWER_LEVELS
|
|
rf.valid_bands = self.valid_freq
|
|
rf.valid_characters = "".join(self.CHARSET)
|
|
rf.valid_name_length = 6
|
|
rf.valid_duplexes = ["", "+", "-", "split", "off"]
|
|
rf.valid_tuning_steps = [5.0, 6.25, 10.0, 12.5, 25.0, 50.0, 100.0]
|
|
rf.has_ctone = True
|
|
rf.has_rx_dtcs = True
|
|
rf.has_cross = True
|
|
rf.has_tuning_step = False
|
|
rf.has_bank = False
|
|
rf.has_settings = True
|
|
rf.memory_bounds = (1, 128)
|
|
rf.can_odd_split = True
|
|
return rf
|
|
|
|
def get_settings(self):
|
|
freq_ranges = RadioSettingGroup("freq_ranges", "Freq Ranges")
|
|
fm_preset = RadioSettingGroup("fm_preset", "FM Presets")
|
|
cfg_s = RadioSettingGroup("cfg_settings", "Configuration Settings")
|
|
group = RadioSettings(cfg_s, freq_ranges, fm_preset)
|
|
|
|
rs = RadioSetting("menu_available", "Menu Available",
|
|
RadioSettingValueBoolean(
|
|
self._memobj.settings.menu_available))
|
|
cfg_s.append(rs)
|
|
|
|
rs = RadioSetting("vhf_rx_start", "1st band RX Lower Limit (MHz)",
|
|
RadioSettingValueInteger(
|
|
50, 174, decode_freq(
|
|
self._memobj.freq_ranges.vhf_rx_start)))
|
|
freq_ranges.append(rs)
|
|
rs = RadioSetting("vhf_rx_stop", "1st band RX Upper Limit (MHz)",
|
|
RadioSettingValueInteger(
|
|
50, 174, decode_freq(
|
|
self._memobj.freq_ranges.vhf_rx_stop)))
|
|
freq_ranges.append(rs)
|
|
rs = RadioSetting("uhf_rx_start", "2nd band RX Lower Limit (MHz)",
|
|
RadioSettingValueInteger(
|
|
136, 520, decode_freq(
|
|
self._memobj.freq_ranges.uhf_rx_start)))
|
|
freq_ranges.append(rs)
|
|
rs = RadioSetting("uhf_rx_stop", "2nd band RX Upper Limit (MHz)",
|
|
RadioSettingValueInteger(
|
|
136, 520, decode_freq(
|
|
self._memobj.freq_ranges.uhf_rx_stop)))
|
|
freq_ranges.append(rs)
|
|
rs = RadioSetting("vhf_tx_start", "1st band TX Lower Limit (MHz)",
|
|
RadioSettingValueInteger(
|
|
50, 174, decode_freq(
|
|
self._memobj.freq_ranges.vhf_tx_start)))
|
|
freq_ranges.append(rs)
|
|
rs = RadioSetting("vhf_tx_stop", "1st TX Upper Limit (MHz)",
|
|
RadioSettingValueInteger(
|
|
50, 174, decode_freq(
|
|
self._memobj.freq_ranges.vhf_tx_stop)))
|
|
freq_ranges.append(rs)
|
|
rs = RadioSetting("uhf_tx_start", "2nd band TX Lower Limit (MHz)",
|
|
RadioSettingValueInteger(
|
|
136, 520, decode_freq(
|
|
self._memobj.freq_ranges.uhf_tx_start)))
|
|
freq_ranges.append(rs)
|
|
rs = RadioSetting("uhf_tx_stop", "2nd band TX Upper Limit (MHz)",
|
|
RadioSettingValueInteger(
|
|
136, 520, decode_freq(
|
|
self._memobj.freq_ranges.uhf_tx_stop)))
|
|
freq_ranges.append(rs)
|
|
|
|
# tell the decoded ranges to UI
|
|
freq_ranges = self._memobj.freq_ranges
|
|
self.valid_freq = \
|
|
[(decode_freq(freq_ranges.vhf_rx_start) * 1000000,
|
|
(decode_freq(freq_ranges.vhf_rx_stop) + 1) * 1000000),
|
|
(decode_freq(freq_ranges.uhf_rx_start) * 1000000,
|
|
(decode_freq(freq_ranges.uhf_rx_stop) + 1) * 1000000)]
|
|
|
|
def _filter(name):
|
|
filtered = ""
|
|
for char in str(name):
|
|
if char in chirp_common.CHARSET_ASCII:
|
|
filtered += char
|
|
else:
|
|
filtered += " "
|
|
return filtered
|
|
|
|
# add some radio specific settings
|
|
options = ["Off", "Welcome", "V bat"]
|
|
rs = RadioSetting("ponmsg", "Poweron message", RadioSettingValueList(
|
|
options, current_index=self._memobj.settings.ponmsg))
|
|
cfg_s.append(rs)
|
|
rs = RadioSetting("strings.welcome1", "Power-On Message 1",
|
|
RadioSettingValueString(
|
|
0, 6, _filter(self._memobj.strings.welcome1)))
|
|
cfg_s.append(rs)
|
|
rs = RadioSetting("strings.welcome2", "Power-On Message 2",
|
|
RadioSettingValueString(
|
|
0, 6, _filter(self._memobj.strings.welcome2)))
|
|
cfg_s.append(rs)
|
|
rs = RadioSetting("strings.single_band", "Single Band Message",
|
|
RadioSettingValueString(
|
|
0, 6, _filter(self._memobj.strings.single_band)))
|
|
cfg_s.append(rs)
|
|
options = ["Channel", "ch/freq", "Name", "VFO"]
|
|
rs = RadioSetting(
|
|
"vfo_a_ch_disp", "VFO A Channel disp mode",
|
|
RadioSettingValueList(
|
|
options, current_index=self._memobj.settings.vfo_a_ch_disp))
|
|
cfg_s.append(rs)
|
|
rs = RadioSetting(
|
|
"vfo_b_ch_disp", "VFO B Channel disp mode",
|
|
RadioSettingValueList(
|
|
options, current_index=self._memobj.settings.vfo_b_ch_disp))
|
|
cfg_s.append(rs)
|
|
options = ["5.0", "6.25", "10.0", "12.5", "25.0", "50.0", "100.0"]
|
|
rs = RadioSetting(
|
|
"vfo_a_fr_step", "VFO A Frequency Step",
|
|
RadioSettingValueList(
|
|
options, current_index=self._memobj.settings.vfo_a_fr_step))
|
|
cfg_s.append(rs)
|
|
rs = RadioSetting(
|
|
"vfo_b_fr_step", "VFO B Frequency Step",
|
|
RadioSettingValueList(
|
|
options, current_index=self._memobj.settings.vfo_b_fr_step))
|
|
cfg_s.append(rs)
|
|
rs = RadioSetting("vfo_a_squelch", "VFO A Squelch",
|
|
RadioSettingValueInteger(
|
|
0, 9, self._memobj.settings.vfo_a_squelch))
|
|
cfg_s.append(rs)
|
|
rs = RadioSetting("vfo_b_squelch", "VFO B Squelch",
|
|
RadioSettingValueInteger(
|
|
0, 9, self._memobj.settings.vfo_b_squelch))
|
|
cfg_s.append(rs)
|
|
rs = RadioSetting("vfo_a_cur_chan", "VFO A current channel",
|
|
RadioSettingValueInteger(
|
|
1, 128, self._memobj.settings.vfo_a_cur_chan))
|
|
cfg_s.append(rs)
|
|
rs = RadioSetting("vfo_b_cur_chan", "VFO B current channel",
|
|
RadioSettingValueInteger(
|
|
1, 128, self._memobj.settings.vfo_b_cur_chan))
|
|
cfg_s.append(rs)
|
|
rs = RadioSetting("priority_chan", "Priority channel",
|
|
RadioSettingValueInteger(
|
|
0, 199, self._memobj.settings.priority_chan))
|
|
cfg_s.append(rs)
|
|
rs = RadioSetting("power_save", "Power save",
|
|
RadioSettingValueBoolean(
|
|
self._memobj.settings.power_save))
|
|
cfg_s.append(rs)
|
|
options = ["Off", "Scan", "Lamp", "SOS", "Radio"]
|
|
rs = RadioSetting(
|
|
"pf1_function", "PF1 Function select",
|
|
RadioSettingValueList(
|
|
options,
|
|
current_index=self._memobj.settings.pf1_function))
|
|
cfg_s.append(rs)
|
|
options = ["Off", "Begin", "End", "Both"]
|
|
rs = RadioSetting("roger_beep", "Roger beep select",
|
|
RadioSettingValueList(
|
|
options,
|
|
current_index=self._memobj.settings.roger_beep))
|
|
cfg_s.append(rs)
|
|
options = ["%s" % x for x in range(15, 615, 15)]
|
|
transmit_time_out = options[self._memobj.settings.transmit_time_out]
|
|
rs = RadioSetting("transmit_time_out", "TX Time-out Timer",
|
|
RadioSettingValueList(
|
|
options, transmit_time_out))
|
|
cfg_s.append(rs)
|
|
rs = RadioSetting("tx_time_out_alert", "TX Time-out Alert",
|
|
RadioSettingValueInteger(
|
|
0, 10, self._memobj.settings.tx_time_out_alert))
|
|
cfg_s.append(rs)
|
|
rs = RadioSetting("vox", "Vox",
|
|
RadioSettingValueInteger(
|
|
0, 10, self._memobj.settings.vox))
|
|
cfg_s.append(rs)
|
|
options = ["Off", "Chinese", "English"]
|
|
rs = RadioSetting("voice", "Voice", RadioSettingValueList(
|
|
options, current_index=self._memobj.settings.voice))
|
|
cfg_s.append(rs)
|
|
rs = RadioSetting("beep", "Beep",
|
|
RadioSettingValueBoolean(
|
|
self._memobj.settings.beep))
|
|
cfg_s.append(rs)
|
|
rs = RadioSetting("ani_id_enable", "ANI id enable",
|
|
RadioSettingValueBoolean(
|
|
self._memobj.settings.ani_id_enable))
|
|
cfg_s.append(rs)
|
|
rs = RadioSetting("ani_id_tx_delay", "ANI id tx delay",
|
|
RadioSettingValueInteger(
|
|
0, 30, self._memobj.settings.ani_id_tx_delay))
|
|
cfg_s.append(rs)
|
|
options = ["Off", "Key", "ANI", "Key+ANI"]
|
|
rs = RadioSetting(
|
|
"ani_id_sidetone", "ANI id sidetone",
|
|
RadioSettingValueList(
|
|
options,
|
|
current_index=self._memobj.settings.ani_id_sidetone))
|
|
cfg_s.append(rs)
|
|
options = ["Time", "Carrier", "Search"]
|
|
rs = RadioSetting("scan_mode", "Scan mode",
|
|
RadioSettingValueList(
|
|
options,
|
|
current_index=self._memobj.settings.scan_mode))
|
|
cfg_s.append(rs)
|
|
rs = RadioSetting("kbd_lock", "Keyboard lock",
|
|
RadioSettingValueBoolean(
|
|
self._memobj.settings.kbd_lock))
|
|
cfg_s.append(rs)
|
|
rs = RadioSetting("auto_lock_kbd", "Auto lock keyboard",
|
|
RadioSettingValueBoolean(
|
|
self._memobj.settings.auto_lock_kbd))
|
|
cfg_s.append(rs)
|
|
rs = RadioSetting("auto_backlight", "Auto backlight",
|
|
RadioSettingValueBoolean(
|
|
self._memobj.settings.auto_backlight))
|
|
cfg_s.append(rs)
|
|
options = ["CH A", "CH B"]
|
|
rs = RadioSetting("sos_ch", "SOS CH",
|
|
RadioSettingValueList(
|
|
options,
|
|
current_index=self._memobj.settings.sos_ch))
|
|
cfg_s.append(rs)
|
|
rs = RadioSetting("stopwatch", "Stopwatch",
|
|
RadioSettingValueBoolean(
|
|
self._memobj.settings.stopwatch))
|
|
cfg_s.append(rs)
|
|
rs = RadioSetting("dual_band_receive", "Dual band receive",
|
|
RadioSettingValueBoolean(
|
|
self._memobj.settings.dual_band_receive))
|
|
cfg_s.append(rs)
|
|
options = ["VFO A", "VFO B"]
|
|
rs = RadioSetting("current_vfo", "Current VFO",
|
|
RadioSettingValueList(
|
|
options,
|
|
current_index=self._memobj.settings.current_vfo))
|
|
cfg_s.append(rs)
|
|
|
|
options = ["Dual", "Single"]
|
|
rs = RadioSetting(
|
|
"sd_available", "Single/Dual Band",
|
|
RadioSettingValueList(
|
|
options, current_index=self._memobj.settings.sd_available))
|
|
cfg_s.append(rs)
|
|
|
|
_pwd = self._memobj.settings.mode_password
|
|
rs = RadioSetting("mode_password", "Mode password (000000 disabled)",
|
|
RadioSettingValueInteger(0, 9, _pwd[0]),
|
|
RadioSettingValueInteger(0, 9, _pwd[1]),
|
|
RadioSettingValueInteger(0, 9, _pwd[2]),
|
|
RadioSettingValueInteger(0, 9, _pwd[3]),
|
|
RadioSettingValueInteger(0, 9, _pwd[4]),
|
|
RadioSettingValueInteger(0, 9, _pwd[5]))
|
|
cfg_s.append(rs)
|
|
_pwd = self._memobj.settings.reset_password
|
|
rs = RadioSetting("reset_password", "Reset password (000000 disabled)",
|
|
RadioSettingValueInteger(0, 9, _pwd[0]),
|
|
RadioSettingValueInteger(0, 9, _pwd[1]),
|
|
RadioSettingValueInteger(0, 9, _pwd[2]),
|
|
RadioSettingValueInteger(0, 9, _pwd[3]),
|
|
RadioSettingValueInteger(0, 9, _pwd[4]),
|
|
RadioSettingValueInteger(0, 9, _pwd[5]))
|
|
cfg_s.append(rs)
|
|
|
|
dtmfchars = "0123456789 *#ABCD"
|
|
_codeobj = self._memobj.settings.ani_id_content
|
|
_code = "".join([dtmfchars[x] for x in _codeobj if int(x) < 0x1F])
|
|
val = RadioSettingValueString(0, 6, _code, False)
|
|
val.set_charset(dtmfchars)
|
|
rs = RadioSetting("settings.ani_id_content", "PTT-ID Code", val)
|
|
|
|
def apply_ani_id(setting, obj):
|
|
value = []
|
|
for j in range(0, 6):
|
|
try:
|
|
value.append(dtmfchars.index(str(setting.value)[j]))
|
|
except IndexError:
|
|
value.append(0xFF)
|
|
obj.ani_id_content = value
|
|
rs.set_apply_callback(apply_ani_id, self._memobj.settings)
|
|
cfg_s.append(rs)
|
|
|
|
for i in range(0, 9):
|
|
if self._memobj.fm_presets_0[i] != 0xFFFF:
|
|
used = True
|
|
preset = self._memobj.fm_presets_0[i] / 10.0 + 76
|
|
else:
|
|
used = False
|
|
preset = 76
|
|
rs = RadioSetting("fm_presets_0_%1i" % i,
|
|
"Team 1 Location %i" % (i + 1),
|
|
RadioSettingValueBoolean(used),
|
|
RadioSettingValueFloat(76, 108, preset, 0.1, 1))
|
|
fm_preset.append(rs)
|
|
for i in range(0, 9):
|
|
if self._memobj.fm_presets_1[i] != 0xFFFF:
|
|
used = True
|
|
preset = self._memobj.fm_presets_1[i] / 10.0 + 76
|
|
else:
|
|
used = False
|
|
preset = 76
|
|
rs = RadioSetting("fm_presets_1_%1i" % i,
|
|
"Team 2 Location %i" % (i + 1),
|
|
RadioSettingValueBoolean(used),
|
|
RadioSettingValueFloat(76, 108, preset, 0.1, 1))
|
|
fm_preset.append(rs)
|
|
|
|
return group
|
|
|
|
def set_settings(self, settings):
|
|
for element in settings:
|
|
if not isinstance(element, RadioSetting):
|
|
if element.get_name() == "freq_ranges":
|
|
self._set_freq_settings(element)
|
|
elif element.get_name() == "fm_preset":
|
|
self._set_fm_preset(element)
|
|
else:
|
|
self.set_settings(element)
|
|
continue
|
|
else:
|
|
try:
|
|
if "." in element.get_name():
|
|
bits = element.get_name().split(".")
|
|
obj = self._memobj
|
|
for bit in bits[:-1]:
|
|
obj = getattr(obj, bit)
|
|
setting = bits[-1]
|
|
else:
|
|
obj = self._memobj.settings
|
|
setting = element.get_name()
|
|
|
|
if element.has_apply_callback():
|
|
LOG.debug("Using apply callback")
|
|
element.run_apply_callback()
|
|
else:
|
|
LOG.debug("Setting %s = %s" % (setting, element.value))
|
|
setattr(obj, setting, element.value)
|
|
except Exception:
|
|
LOG.debug(element.get_name())
|
|
raise
|
|
|
|
def _set_fm_preset(self, settings):
|
|
obj = self._memobj
|
|
for element in settings:
|
|
try:
|
|
(bank, index) = \
|
|
(int(a) for a in element.get_name().split("_")[-2:])
|
|
val = element.value
|
|
if val[0].get_value():
|
|
value = int(val[1].get_value()*10-760)
|
|
else:
|
|
value = 0xffff
|
|
LOG.debug("Setting fm_presets_%1i[%1i] = %s" %
|
|
(bank, index, value))
|
|
if bank == 0:
|
|
setting = self._memobj.fm_presets_0
|
|
else:
|
|
setting = self._memobj.fm_presets_1
|
|
setting[index] = value
|
|
except Exception:
|
|
LOG.debug(element.get_name())
|
|
raise
|
|
|
|
def _set_freq_settings(self, settings):
|
|
for element in settings:
|
|
try:
|
|
setattr(self._memobj.freq_ranges,
|
|
element.get_name(),
|
|
encode_freq(int(element.value)))
|
|
except Exception:
|
|
LOG.debug(element.get_name())
|
|
raise
|
|
|
|
def get_raw_memory(self, number):
|
|
return repr(self._memobj.memory[number - 1])
|
|
|
|
def _get_tone(self, _mem, mem):
|
|
def _get_dcs(val):
|
|
code = int("%03o" % (val & 0x07FF))
|
|
pol = (val & 0x8000) and "R" or "N"
|
|
return code, pol
|
|
|
|
tpol = False
|
|
if _mem.tx_tone != 0xFFFF and _mem.tx_tone > 0x2800:
|
|
tcode, tpol = _get_dcs(_mem.tx_tone)
|
|
mem.dtcs = tcode
|
|
txmode = "DTCS"
|
|
elif _mem.tx_tone != 0xFFFF:
|
|
mem.rtone = _mem.tx_tone / 10.0
|
|
txmode = "Tone"
|
|
else:
|
|
txmode = ""
|
|
|
|
rpol = False
|
|
if _mem.rx_tone != 0xFFFF and _mem.rx_tone > 0x2800:
|
|
rcode, rpol = _get_dcs(_mem.rx_tone)
|
|
mem.rx_dtcs = rcode
|
|
rxmode = "DTCS"
|
|
elif _mem.rx_tone != 0xFFFF:
|
|
mem.ctone = _mem.rx_tone / 10.0
|
|
rxmode = "Tone"
|
|
else:
|
|
rxmode = ""
|
|
|
|
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)
|
|
|
|
# always set it even if no dtcs is used
|
|
mem.dtcs_polarity = "%s%s" % (tpol or "N", rpol or "N")
|
|
|
|
LOG.debug("Got TX %s (%i) RX %s (%i)" %
|
|
(txmode, _mem.tx_tone, rxmode, _mem.rx_tone))
|
|
|
|
def _is_txinh(self, _mem):
|
|
return _mem.tx_freq.get_raw() == b"\xFF\xFF\xFF\xFF"
|
|
|
|
def get_memory(self, number):
|
|
_mem = self._memobj.memory[number - 1]
|
|
_nam = self._memobj.names[number - 1]
|
|
|
|
mem = chirp_common.Memory()
|
|
mem.number = number
|
|
|
|
if _mem.get_raw() == (b"\xff" * 16):
|
|
mem.empty = True
|
|
return mem
|
|
|
|
mem.freq = int(_mem.rx_freq) * 10
|
|
if _mem.splitdup:
|
|
mem.duplex = "split"
|
|
elif self._is_txinh(_mem):
|
|
mem.duplex = "off"
|
|
elif int(_mem.rx_freq) < int(_mem.tx_freq):
|
|
mem.duplex = "+"
|
|
elif int(_mem.rx_freq) > int(_mem.tx_freq):
|
|
mem.duplex = "-"
|
|
|
|
if mem.duplex == "" or mem.duplex == "off":
|
|
mem.offset = 0
|
|
elif mem.duplex == "split":
|
|
mem.offset = int(_mem.tx_freq) * 10
|
|
else:
|
|
mem.offset = abs(int(_mem.tx_freq) - int(_mem.rx_freq)) * 10
|
|
|
|
if not _mem.skip:
|
|
mem.skip = "S"
|
|
if not _mem.iswide:
|
|
mem.mode = "NFM"
|
|
|
|
self._get_tone(_mem, mem)
|
|
|
|
mem.power = self.POWER_LEVELS[not _mem.power_high]
|
|
|
|
for i in _nam.name:
|
|
if i == 0xFF:
|
|
break
|
|
mem.name += self.CHARSET[i]
|
|
|
|
mem.extra = RadioSettingGroup("extra", "Extra")
|
|
bcl = RadioSetting("bcl", "BCL",
|
|
RadioSettingValueBoolean(bool(_mem.bcl)))
|
|
bcl.set_doc("Busy Channel Lockout")
|
|
mem.extra.append(bcl)
|
|
|
|
options = ["NFM", "FM"]
|
|
iswidex = RadioSetting("iswidex", "Mode TX(KG-UV6X)",
|
|
RadioSettingValueList(
|
|
options, current_index=_mem.iswidex))
|
|
iswidex.set_doc("Mode TX")
|
|
mem.extra.append(iswidex)
|
|
|
|
return mem
|
|
|
|
def _set_tone(self, mem, _mem):
|
|
def _set_dcs(code, pol):
|
|
val = int("%i" % code, 8) + 0x2800
|
|
if pol == "R":
|
|
val += 0x8000
|
|
return val
|
|
|
|
rx_mode = tx_mode = None
|
|
rx_tone = tx_tone = 0xFFFF
|
|
|
|
if mem.tmode == "Tone":
|
|
tx_mode = "Tone"
|
|
rx_mode = None
|
|
tx_tone = int(mem.rtone * 10)
|
|
elif mem.tmode == "TSQL":
|
|
rx_mode = tx_mode = "Tone"
|
|
rx_tone = tx_tone = int(mem.ctone * 10)
|
|
elif mem.tmode == "DTCS":
|
|
tx_mode = rx_mode = "DTCS"
|
|
tx_tone = _set_dcs(mem.dtcs, mem.dtcs_polarity[0])
|
|
rx_tone = _set_dcs(mem.dtcs, mem.dtcs_polarity[1])
|
|
elif mem.tmode == "Cross":
|
|
tx_mode, rx_mode = mem.cross_mode.split("->")
|
|
if tx_mode == "DTCS":
|
|
tx_tone = _set_dcs(mem.dtcs, mem.dtcs_polarity[0])
|
|
elif tx_mode == "Tone":
|
|
tx_tone = int(mem.rtone * 10)
|
|
if rx_mode == "DTCS":
|
|
rx_tone = _set_dcs(mem.rx_dtcs, mem.dtcs_polarity[1])
|
|
elif rx_mode == "Tone":
|
|
rx_tone = int(mem.ctone * 10)
|
|
|
|
_mem.rx_tone = rx_tone
|
|
_mem.tx_tone = tx_tone
|
|
|
|
LOG.debug("Set TX %s (%i) RX %s (%i)" %
|
|
(tx_mode, _mem.tx_tone, rx_mode, _mem.rx_tone))
|
|
|
|
def set_memory(self, mem):
|
|
_mem = self._memobj.memory[mem.number - 1]
|
|
_nam = self._memobj.names[mem.number - 1]
|
|
|
|
if mem.empty:
|
|
wipe_memory(_mem, "\xFF")
|
|
return
|
|
|
|
if _mem.get_raw() == (b"\xFF" * 16):
|
|
wipe_memory(_mem, "\x00")
|
|
|
|
_mem.rx_freq = int(mem.freq / 10)
|
|
if mem.duplex == "split":
|
|
_mem.tx_freq = int(mem.offset / 10)
|
|
elif mem.duplex == "off":
|
|
_mem.tx_freq.fill_raw(b"\xFF")
|
|
elif mem.duplex == "+":
|
|
_mem.tx_freq = int(mem.freq / 10) + int(mem.offset / 10)
|
|
elif mem.duplex == "-":
|
|
_mem.tx_freq = int(mem.freq / 10) - int(mem.offset / 10)
|
|
else:
|
|
_mem.tx_freq = int(mem.freq / 10)
|
|
_mem.splitdup = mem.duplex == "split"
|
|
_mem.skip = mem.skip != "S"
|
|
_mem.iswide = mem.mode != "NFM"
|
|
|
|
self._set_tone(mem, _mem)
|
|
|
|
if mem.power:
|
|
_mem.power_high = not self.POWER_LEVELS.index(mem.power)
|
|
else:
|
|
_mem.power_high = True
|
|
|
|
_nam.name = [0xFF] * 6
|
|
for i in range(0, len(mem.name)):
|
|
try:
|
|
_nam.name[i] = self.CHARSET.index(mem.name[i])
|
|
except IndexError:
|
|
raise Exception("Character `%s' not supported")
|
|
|
|
for setting in mem.extra:
|
|
setattr(_mem, setting.get_name(), setting.value)
|
|
|
|
@classmethod
|
|
def match_model(cls, filedata, filename):
|
|
# New-style image (CHIRP 0.1.12)
|
|
if len(filedata) == 8192 and \
|
|
filedata[0x60:0x64] != b"2009" and \
|
|
filedata[0x170:0x173] != b"LX-" and \
|
|
filedata[0x1f77:0x1f7d] == b"\xff\xff\xff\xff\xff\xff" and \
|
|
filedata[0x0d70:0x0d80] == \
|
|
b"\xff\xff\xff\xff\xff\xff\xff\xff" \
|
|
b"\xff\xff\xff\xff\xff\xff\xff\xff":
|
|
# those areas are (seems to be) unused
|
|
return True
|
|
# Old-style image (CHIRP 0.1.11)
|
|
if len(filedata) == 8200 and \
|
|
filedata[0:4] == b"\x01\x00\x00\x00":
|
|
return True
|
|
return False
|
|
|
|
|
|
@directory.register
|
|
class KGUV6DRadio(KGUVD1PRadio):
|
|
"""Wouxun KG-UV6 (D and X variants)"""
|
|
MODEL = "KG-UV6"
|
|
|
|
_querymodel = (b"HiWXUVD1\x02", b"HiKGUVD1\x02")
|
|
|
|
_MEM_FORMAT = """
|
|
#seekto 0x0010;
|
|
struct {
|
|
lbcd rx_freq[4];
|
|
lbcd tx_freq[4];
|
|
ul16 rx_tone;
|
|
ul16 tx_tone;
|
|
u8 _3_unknown_1:4,
|
|
bcl:1,
|
|
_3_unknown_2:3;
|
|
u8 splitdup:1,
|
|
skip:1,
|
|
power_high:1,
|
|
iswide:1,
|
|
_2_unknown_2:4;
|
|
u8 pad;
|
|
u8 _0_unknown_1:3,
|
|
iswidex:1,
|
|
_0_unknown_2:4;
|
|
} memory[199];
|
|
|
|
#seekto 0x0F00;
|
|
struct {
|
|
char welcome1[6];
|
|
char welcome2[6];
|
|
char single_band[6];
|
|
} strings;
|
|
|
|
#seekto 0x0F20;
|
|
struct {
|
|
u8 unknown_flag_01:6,
|
|
vfo_b_ch_disp:2;
|
|
u8 unknown_flag_02:5,
|
|
vfo_a_fr_step:3;
|
|
u8 unknown_flag_03:4,
|
|
vfo_a_squelch:4;
|
|
u8 unknown_flag_04:7,
|
|
power_save:1;
|
|
u8 unknown_flag_05:5,
|
|
pf2_function:3;
|
|
u8 unknown_flag_06:6,
|
|
roger_beep:2;
|
|
u8 unknown_flag_07:2,
|
|
transmit_time_out:6;
|
|
u8 unknown_flag_08:4,
|
|
vox:4;
|
|
u8 unknown_1[4];
|
|
u8 unknown_flag_09:6,
|
|
voice:2;
|
|
u8 unknown_flag_10:7,
|
|
beep:1;
|
|
u8 unknown_flag_11:7,
|
|
ani_id_enable:1;
|
|
u8 unknown_2[2];
|
|
u8 unknown_flag_12:5,
|
|
vfo_b_fr_step:3;
|
|
u8 unknown_3[1];
|
|
u8 unknown_flag_13:3,
|
|
ani_id_tx_delay:5;
|
|
u8 unknown_4[1];
|
|
u8 unknown_flag_14:6,
|
|
ani_id_sidetone:2;
|
|
u8 unknown_flag_15:4,
|
|
tx_time_out_alert:4;
|
|
u8 unknown_flag_16:6,
|
|
vfo_a_ch_disp:2;
|
|
u8 unknown_flag_20:6,
|
|
scan_mode:2;
|
|
u8 unknown_flag_31:7,
|
|
kbd_lock:1;
|
|
u8 unknown_flag_17:6,
|
|
ponmsg:2;
|
|
u8 unknown_flag_18:5,
|
|
pf1_function:3;
|
|
u8 unknown_5[1];
|
|
u8 unknown_flag_19:7,
|
|
auto_backlight:1;
|
|
u8 unknown_flag_26:7,
|
|
sos_ch:1;
|
|
u8 unknown_6;
|
|
u8 sd_available;
|
|
u8 unknown_flag_32:7,
|
|
auto_lock_kbd:1;
|
|
u8 unknown_flag_22:4,
|
|
vfo_b_squelch:4;
|
|
u8 unknown_7[1];
|
|
u8 unknown_flag_23:7,
|
|
stopwatch:1;
|
|
u8 vfo_a_cur_chan;
|
|
u8 unknown_flag_24:7,
|
|
dual_band_receive:1;
|
|
u8 current_vfo:1,
|
|
unknown_flag_33:7;
|
|
u8 unknown_8[2];
|
|
u8 mode_password[6];
|
|
u8 reset_password[6];
|
|
u8 ani_id_content[6];
|
|
u8 unknown_flag_25:7,
|
|
menu_available:1;
|
|
u8 unknown_9[1];
|
|
u8 priority_chan;
|
|
u8 vfo_b_cur_chan;
|
|
} settings;
|
|
|
|
//#seekto 0x0f60;
|
|
struct {
|
|
lbcd rx_freq[4];
|
|
lbcd tx_freq[4];
|
|
ul16 rx_tone;
|
|
ul16 tx_tone;
|
|
u8 _3_unknown_3:4,
|
|
bcl:1,
|
|
_3_unknown_4:3;
|
|
u8 splitdup:1,
|
|
_2_unknown_3:1,
|
|
power_high:1,
|
|
iswide:1,
|
|
_2_unknown_4:4;
|
|
u8 pad[2];
|
|
} vfo_settings[2];
|
|
|
|
#seekto 0x0f82;
|
|
u16 fm_presets_0[9];
|
|
|
|
#seekto 0x0ff0;
|
|
struct {
|
|
u16 vhf_rx_start;
|
|
u16 vhf_rx_stop;
|
|
u16 uhf_rx_start;
|
|
u16 uhf_rx_stop;
|
|
u16 vhf_tx_start;
|
|
u16 vhf_tx_stop;
|
|
u16 uhf_tx_start;
|
|
u16 uhf_tx_stop;
|
|
} freq_ranges;
|
|
|
|
#seekto 0x1010;
|
|
struct {
|
|
u8 name[6];
|
|
u8 pad[10];
|
|
} names[199];
|
|
|
|
#seekto 0x1f60;
|
|
struct {
|
|
u8 unknown_flag_26:6,
|
|
tx_offset_dir:2;
|
|
u8 tx_offset[6];
|
|
u8 pad[9];
|
|
} vfo_offset[2];
|
|
|
|
#seekto 0x1f82;
|
|
u16 fm_presets_1[9];
|
|
"""
|
|
|
|
def get_features(self):
|
|
rf = KGUVD1PRadio.get_features(self)
|
|
rf.memory_bounds = (1, 199)
|
|
rf.valid_tuning_steps = [2.5, 5.0, 6.25, 10.0, 12.5, 25.0, 50.0,
|
|
100.0]
|
|
return rf
|
|
|
|
def get_settings(self):
|
|
freq_ranges = RadioSettingGroup("freq_ranges", "Freq Ranges")
|
|
fm_preset = RadioSettingGroup("fm_preset", "FM Presets")
|
|
cfg_s = RadioSettingGroup("cfg_settings", "Configuration Settings")
|
|
group = RadioSettings(cfg_s, freq_ranges, fm_preset)
|
|
|
|
rs = RadioSetting("menu_available", "Menu Available",
|
|
RadioSettingValueBoolean(
|
|
self._memobj.settings.menu_available))
|
|
cfg_s.append(rs)
|
|
|
|
rs = RadioSetting("vhf_rx_start", "VHF RX Lower Limit (MHz)",
|
|
RadioSettingValueInteger(
|
|
1, 1000, decode_freq(
|
|
self._memobj.freq_ranges.vhf_rx_start)))
|
|
freq_ranges.append(rs)
|
|
rs = RadioSetting("vhf_rx_stop", "VHF RX Upper Limit (MHz)",
|
|
RadioSettingValueInteger(
|
|
1, 1000, decode_freq(
|
|
self._memobj.freq_ranges.vhf_rx_stop)))
|
|
freq_ranges.append(rs)
|
|
rs = RadioSetting("uhf_rx_start", "UHF RX Lower Limit (MHz)",
|
|
RadioSettingValueInteger(
|
|
1, 1000, decode_freq(
|
|
self._memobj.freq_ranges.uhf_rx_start)))
|
|
freq_ranges.append(rs)
|
|
rs = RadioSetting("uhf_rx_stop", "UHF RX Upper Limit (MHz)",
|
|
RadioSettingValueInteger(
|
|
1, 1000, decode_freq(
|
|
self._memobj.freq_ranges.uhf_rx_stop)))
|
|
freq_ranges.append(rs)
|
|
rs = RadioSetting("vhf_tx_start", "VHF TX Lower Limit (MHz)",
|
|
RadioSettingValueInteger(
|
|
1, 1000, decode_freq(
|
|
self._memobj.freq_ranges.vhf_tx_start)))
|
|
freq_ranges.append(rs)
|
|
rs = RadioSetting("vhf_tx_stop", "VHF TX Upper Limit (MHz)",
|
|
RadioSettingValueInteger(
|
|
1, 1000, decode_freq(
|
|
self._memobj.freq_ranges.vhf_tx_stop)))
|
|
freq_ranges.append(rs)
|
|
rs = RadioSetting("uhf_tx_start", "UHF TX Lower Limit (MHz)",
|
|
RadioSettingValueInteger(
|
|
1, 1000, decode_freq(
|
|
self._memobj.freq_ranges.uhf_tx_start)))
|
|
freq_ranges.append(rs)
|
|
rs = RadioSetting("uhf_tx_stop", "UHF TX Upper Limit (MHz)",
|
|
RadioSettingValueInteger(
|
|
1, 1000, decode_freq(
|
|
self._memobj.freq_ranges.uhf_tx_stop)))
|
|
freq_ranges.append(rs)
|
|
|
|
# tell the decoded ranges to UI
|
|
freq_ranges = self._memobj.freq_ranges
|
|
self.valid_freq = \
|
|
[(decode_freq(freq_ranges.vhf_rx_start) * 1000000,
|
|
(decode_freq(freq_ranges.vhf_rx_stop) + 1) * 1000000),
|
|
(decode_freq(freq_ranges.uhf_rx_start) * 1000000,
|
|
(decode_freq(freq_ranges.uhf_rx_stop) + 1) * 1000000)]
|
|
|
|
def _filter(name):
|
|
filtered = ""
|
|
for char in str(name):
|
|
if char in chirp_common.CHARSET_ASCII:
|
|
filtered += char
|
|
else:
|
|
filtered += " "
|
|
return filtered
|
|
|
|
# add some radio specific settings
|
|
options = ["Off", "Welcome", "V bat", "N/A(KG-UV6X)"]
|
|
rs = RadioSetting("ponmsg", "Poweron message", RadioSettingValueList(
|
|
options, current_index=self._memobj.settings.ponmsg))
|
|
cfg_s.append(rs)
|
|
rs = RadioSetting("strings.welcome1", "Power-On Message 1",
|
|
RadioSettingValueString(
|
|
0, 6, _filter(self._memobj.strings.welcome1)))
|
|
cfg_s.append(rs)
|
|
rs = RadioSetting("strings.welcome2", "Power-On Message 2",
|
|
RadioSettingValueString(
|
|
0, 6, _filter(self._memobj.strings.welcome2)))
|
|
cfg_s.append(rs)
|
|
rs = RadioSetting("strings.single_band", "Single Band Message",
|
|
RadioSettingValueString(
|
|
0, 6, _filter(self._memobj.strings.single_band)))
|
|
cfg_s.append(rs)
|
|
options = ["Channel", "ch/freq", "Name", "VFO"]
|
|
rs = RadioSetting(
|
|
"vfo_a_ch_disp", "VFO A Channel disp mode",
|
|
RadioSettingValueList(
|
|
options, current_index=self._memobj.settings.vfo_a_ch_disp))
|
|
cfg_s.append(rs)
|
|
rs = RadioSetting(
|
|
"vfo_b_ch_disp", "VFO B Channel disp mode",
|
|
RadioSettingValueList(
|
|
options, current_index=self._memobj.settings.vfo_b_ch_disp))
|
|
cfg_s.append(rs)
|
|
options = \
|
|
["2.5", "5.0", "6.25", "10.0", "12.5", "25.0", "50.0", "100.0"]
|
|
rs = RadioSetting(
|
|
"vfo_a_fr_step", "VFO A Frequency Step",
|
|
RadioSettingValueList(
|
|
options, current_index=self._memobj.settings.vfo_a_fr_step))
|
|
cfg_s.append(rs)
|
|
rs = RadioSetting(
|
|
"vfo_b_fr_step", "VFO B Frequency Step",
|
|
RadioSettingValueList(
|
|
options, current_index=self._memobj.settings.vfo_b_fr_step))
|
|
cfg_s.append(rs)
|
|
rs = RadioSetting("vfo_a_squelch", "VFO A Squelch",
|
|
RadioSettingValueInteger(
|
|
0, 9, self._memobj.settings.vfo_a_squelch))
|
|
cfg_s.append(rs)
|
|
rs = RadioSetting("vfo_b_squelch", "VFO B Squelch",
|
|
RadioSettingValueInteger(
|
|
0, 9, self._memobj.settings.vfo_b_squelch))
|
|
cfg_s.append(rs)
|
|
rs = RadioSetting("vfo_a_cur_chan", "VFO A current channel",
|
|
RadioSettingValueInteger(
|
|
1, 199, self._memobj.settings.vfo_a_cur_chan))
|
|
cfg_s.append(rs)
|
|
rs = RadioSetting("vfo_b_cur_chan", "VFO B current channel",
|
|
RadioSettingValueInteger(
|
|
1, 199, self._memobj.settings.vfo_b_cur_chan))
|
|
cfg_s.append(rs)
|
|
rs = RadioSetting("priority_chan", "Priority channel",
|
|
RadioSettingValueInteger(
|
|
0, 199, self._memobj.settings.priority_chan))
|
|
cfg_s.append(rs)
|
|
rs = RadioSetting("power_save", "Power save",
|
|
RadioSettingValueBoolean(
|
|
self._memobj.settings.power_save))
|
|
cfg_s.append(rs)
|
|
options = ["Off", "Scan", "Lamp", "SOS", "Radio"]
|
|
rs = RadioSetting(
|
|
"pf1_function", "PF1 Function select",
|
|
RadioSettingValueList(
|
|
options,
|
|
current_index=self._memobj.settings.pf1_function))
|
|
cfg_s.append(rs)
|
|
options = ["Off", "Radio", "fr/ch", "Rpt", "Stopwatch", "Lamp", "SOS"]
|
|
rs = RadioSetting(
|
|
"pf2_function", "PF2 Function select",
|
|
RadioSettingValueList(
|
|
options,
|
|
current_index=self._memobj.settings.pf2_function))
|
|
cfg_s.append(rs)
|
|
options = ["Off", "Begin", "End", "Both"]
|
|
rs = RadioSetting("roger_beep", "Roger beep select",
|
|
RadioSettingValueList(
|
|
options,
|
|
current_index=self._memobj.settings.roger_beep))
|
|
cfg_s.append(rs)
|
|
options = ["%s" % x for x in range(15, 615, 15)]
|
|
transmit_time_out = options[self._memobj.settings.transmit_time_out]
|
|
rs = RadioSetting("transmit_time_out", "TX Time-out Timer",
|
|
RadioSettingValueList(
|
|
options, transmit_time_out))
|
|
cfg_s.append(rs)
|
|
rs = RadioSetting("tx_time_out_alert", "TX Time-out Alert",
|
|
RadioSettingValueInteger(
|
|
0, 10, self._memobj.settings.tx_time_out_alert))
|
|
cfg_s.append(rs)
|
|
rs = RadioSetting("vox", "Vox",
|
|
RadioSettingValueInteger(
|
|
0, 10, self._memobj.settings.vox))
|
|
cfg_s.append(rs)
|
|
options = ["Off", "Chinese", "English"]
|
|
rs = RadioSetting("voice", "Voice", RadioSettingValueList(
|
|
options, current_index=self._memobj.settings.voice))
|
|
cfg_s.append(rs)
|
|
rs = RadioSetting("beep", "Beep",
|
|
RadioSettingValueBoolean(
|
|
self._memobj.settings.beep))
|
|
cfg_s.append(rs)
|
|
rs = RadioSetting("ani_id_enable", "ANI id enable",
|
|
RadioSettingValueBoolean(
|
|
self._memobj.settings.ani_id_enable))
|
|
cfg_s.append(rs)
|
|
rs = RadioSetting("ani_id_tx_delay", "ANI id tx delay",
|
|
RadioSettingValueInteger(
|
|
0, 30, self._memobj.settings.ani_id_tx_delay))
|
|
cfg_s.append(rs)
|
|
options = ["Off", "Key", "ANI", "Key+ANI"]
|
|
rs = RadioSetting(
|
|
"ani_id_sidetone", "ANI id sidetone",
|
|
RadioSettingValueList(
|
|
options,
|
|
current_index=self._memobj.settings.ani_id_sidetone))
|
|
cfg_s.append(rs)
|
|
options = ["Time", "Carrier", "Search"]
|
|
rs = RadioSetting("scan_mode", "Scan mode",
|
|
RadioSettingValueList(
|
|
options,
|
|
current_index=self._memobj.settings.scan_mode))
|
|
cfg_s.append(rs)
|
|
rs = RadioSetting("kbd_lock", "Keyboard lock",
|
|
RadioSettingValueBoolean(
|
|
self._memobj.settings.kbd_lock))
|
|
cfg_s.append(rs)
|
|
rs = RadioSetting("auto_lock_kbd", "Auto lock keyboard",
|
|
RadioSettingValueBoolean(
|
|
self._memobj.settings.auto_lock_kbd))
|
|
cfg_s.append(rs)
|
|
rs = RadioSetting("auto_backlight", "Auto backlight",
|
|
RadioSettingValueBoolean(
|
|
self._memobj.settings.auto_backlight))
|
|
cfg_s.append(rs)
|
|
options = ["CH A", "CH B"]
|
|
rs = RadioSetting("sos_ch", "SOS CH", RadioSettingValueList(
|
|
options, current_index=self._memobj.settings.sos_ch))
|
|
cfg_s.append(rs)
|
|
rs = RadioSetting("stopwatch", "Stopwatch",
|
|
RadioSettingValueBoolean(
|
|
self._memobj.settings.stopwatch))
|
|
cfg_s.append(rs)
|
|
rs = RadioSetting("dual_band_receive", "Dual band receive",
|
|
RadioSettingValueBoolean(
|
|
self._memobj.settings.dual_band_receive))
|
|
cfg_s.append(rs)
|
|
options = ["VFO A", "VFO B"]
|
|
rs = RadioSetting("current_vfo", "Current VFO",
|
|
RadioSettingValueList(
|
|
options,
|
|
current_index=self._memobj.settings.current_vfo))
|
|
cfg_s.append(rs)
|
|
|
|
options = ["Dual", "Single"]
|
|
rs = RadioSetting(
|
|
"sd_available", "Single/Dual Band",
|
|
RadioSettingValueList(
|
|
options, current_index=self._memobj.settings.sd_available))
|
|
cfg_s.append(rs)
|
|
|
|
_pwd = self._memobj.settings.mode_password
|
|
rs = RadioSetting("mode_password", "Mode password (000000 disabled)",
|
|
RadioSettingValueInteger(0, 9, _pwd[0]),
|
|
RadioSettingValueInteger(0, 9, _pwd[1]),
|
|
RadioSettingValueInteger(0, 9, _pwd[2]),
|
|
RadioSettingValueInteger(0, 9, _pwd[3]),
|
|
RadioSettingValueInteger(0, 9, _pwd[4]),
|
|
RadioSettingValueInteger(0, 9, _pwd[5]))
|
|
cfg_s.append(rs)
|
|
_pwd = self._memobj.settings.reset_password
|
|
rs = RadioSetting("reset_password", "Reset password (000000 disabled)",
|
|
RadioSettingValueInteger(0, 9, _pwd[0]),
|
|
RadioSettingValueInteger(0, 9, _pwd[1]),
|
|
RadioSettingValueInteger(0, 9, _pwd[2]),
|
|
RadioSettingValueInteger(0, 9, _pwd[3]),
|
|
RadioSettingValueInteger(0, 9, _pwd[4]),
|
|
RadioSettingValueInteger(0, 9, _pwd[5]))
|
|
cfg_s.append(rs)
|
|
|
|
dtmfchars = "0123456789 *#ABCD"
|
|
_codeobj = self._memobj.settings.ani_id_content
|
|
_code = "".join([dtmfchars[x] for x in _codeobj if int(x) < 0x1F])
|
|
val = RadioSettingValueString(0, 6, _code, False)
|
|
val.set_charset(dtmfchars)
|
|
rs = RadioSetting("settings.ani_id_content", "ANI Code", val)
|
|
|
|
def apply_ani_id(setting, obj):
|
|
value = []
|
|
for j in range(0, 6):
|
|
try:
|
|
value.append(dtmfchars.index(str(setting.value)[j]))
|
|
except IndexError:
|
|
value.append(0xFF)
|
|
obj.ani_id_content = value
|
|
rs.set_apply_callback(apply_ani_id, self._memobj.settings)
|
|
cfg_s.append(rs)
|
|
|
|
for i in range(0, 9):
|
|
if self._memobj.fm_presets_0[i] != 0xFFFF:
|
|
used = True
|
|
preset = self._memobj.fm_presets_0[i]/10.0+76
|
|
else:
|
|
used = False
|
|
preset = 76
|
|
rs = RadioSetting("fm_presets_0_%1i" % i,
|
|
"Team 1 Location %i" % (i+1),
|
|
RadioSettingValueBoolean(used),
|
|
RadioSettingValueFloat(76, 108, preset, 0.1, 1))
|
|
fm_preset.append(rs)
|
|
for i in range(0, 9):
|
|
if self._memobj.fm_presets_1[i] != 0xFFFF:
|
|
used = True
|
|
preset = self._memobj.fm_presets_1[i]/10.0+76
|
|
else:
|
|
used = False
|
|
preset = 76
|
|
rs = RadioSetting("fm_presets_1_%1i" % i,
|
|
"Team 2 Location %i" % (i+1),
|
|
RadioSettingValueBoolean(used),
|
|
RadioSettingValueFloat(76, 108, preset, 0.1, 1))
|
|
fm_preset.append(rs)
|
|
|
|
return group
|
|
|
|
def set_settings(self, settings):
|
|
for element in settings:
|
|
if not isinstance(element, RadioSetting):
|
|
if element.get_name() == "freq_ranges":
|
|
self._set_freq_settings(element)
|
|
elif element.get_name() == "fm_preset":
|
|
self._set_fm_preset(element)
|
|
else:
|
|
self.set_settings(element)
|
|
else:
|
|
try:
|
|
if "." in element.get_name():
|
|
bits = element.get_name().split(".")
|
|
obj = self._memobj
|
|
for bit in bits[:-1]:
|
|
obj = getattr(obj, bit)
|
|
setting = bits[-1]
|
|
else:
|
|
obj = self._memobj.settings
|
|
setting = element.get_name()
|
|
|
|
if element.has_apply_callback():
|
|
LOG.debug("Using apply callback")
|
|
element.run_apply_callback()
|
|
else:
|
|
LOG.debug("Setting %s = %s" % (setting, element.value))
|
|
setattr(obj, setting, element.value)
|
|
except Exception:
|
|
LOG.debug(element.get_name())
|
|
raise
|
|
|
|
def _set_fm_preset(self, settings):
|
|
obj = self._memobj
|
|
for element in settings:
|
|
try:
|
|
(bank, index) = \
|
|
(int(a) for a in element.get_name().split("_")[-2:])
|
|
val = element.value
|
|
if val[0].get_value():
|
|
value = int(val[1].get_value()*10-760)
|
|
else:
|
|
value = 0xffff
|
|
LOG.debug("Setting fm_presets_%1i[%1i] = %s" %
|
|
(bank, index, value))
|
|
if bank == 0:
|
|
setting = self._memobj.fm_presets_0
|
|
else:
|
|
setting = self._memobj.fm_presets_1
|
|
setting[index] = value
|
|
except Exception:
|
|
LOG.debug(element.get_name())
|
|
raise
|
|
|
|
@classmethod
|
|
def match_model(cls, filedata, filename):
|
|
if len(filedata) == 8192 and \
|
|
filedata[0x1f77:0x1f7d] == b"WELCOM":
|
|
return True
|
|
return False
|
|
|
|
|
|
@directory.register
|
|
class KG816Radio(KGUVD1PRadio, chirp_common.ExperimentalRadio):
|
|
"""Wouxun KG-816"""
|
|
MODEL = "KG-816"
|
|
|
|
_querymodel = b"HiWOUXUN\x02"
|
|
|
|
_MEM_FORMAT = """
|
|
#seekto 0x0010;
|
|
struct {
|
|
lbcd rx_freq[4];
|
|
lbcd tx_freq[4];
|
|
ul16 rx_tone;
|
|
ul16 tx_tone;
|
|
u8 _3_unknown_1:4,
|
|
bcl:1,
|
|
_3_unknown_2:3;
|
|
u8 splitdup:1,
|
|
skip:1,
|
|
power_high:1,
|
|
iswide:1,
|
|
_2_unknown_2:4;
|
|
u8 unknown;
|
|
u8 _0_unknown_1:3,
|
|
iswidex:1,
|
|
_0_unknown_2:4;
|
|
} memory[199];
|
|
|
|
#seekto 0x0d70;
|
|
struct {
|
|
u16 vhf_rx_start;
|
|
u16 vhf_rx_stop;
|
|
u16 uhf_rx_start;
|
|
u16 uhf_rx_stop;
|
|
u16 vhf_tx_start;
|
|
u16 vhf_tx_stop;
|
|
u16 uhf_tx_start;
|
|
u16 uhf_tx_stop;
|
|
} freq_ranges;
|
|
|
|
#seekto 0x1010;
|
|
struct {
|
|
u8 name[6];
|
|
u8 pad[10];
|
|
} names[199];
|
|
"""
|
|
|
|
@classmethod
|
|
def get_experimental_warning(cls):
|
|
return ('We have not that much information on this model '
|
|
'up to now we only know it has the same memory '
|
|
'organization of KGUVD1 but uses 199 memories. '
|
|
'it has been reported to work but '
|
|
'proceed at your own risk!')
|
|
|
|
def get_features(self):
|
|
rf = KGUVD1PRadio.get_features(self)
|
|
rf.memory_bounds = (1, 199) # this is the only known difference
|
|
return rf
|
|
|
|
def get_settings(self):
|
|
freq_ranges = RadioSettingGroup("freq_ranges",
|
|
"Freq Ranges (read only)")
|
|
group = RadioSettings(freq_ranges)
|
|
|
|
rs = RadioSetting("vhf_rx_start", "vhf rx start",
|
|
RadioSettingValueInteger(
|
|
66, 520, decode_freq(
|
|
self._memobj.freq_ranges.vhf_rx_start)))
|
|
freq_ranges.append(rs)
|
|
rs = RadioSetting("vhf_rx_stop", "vhf rx stop",
|
|
RadioSettingValueInteger(
|
|
66, 520, decode_freq(
|
|
self._memobj.freq_ranges.vhf_rx_stop)))
|
|
freq_ranges.append(rs)
|
|
rs = RadioSetting("uhf_rx_start", "uhf rx start",
|
|
RadioSettingValueInteger(
|
|
66, 520, decode_freq(
|
|
self._memobj.freq_ranges.uhf_rx_start)))
|
|
freq_ranges.append(rs)
|
|
rs = RadioSetting("uhf_rx_stop", "uhf rx stop",
|
|
RadioSettingValueInteger(
|
|
66, 520, decode_freq(
|
|
self._memobj.freq_ranges.uhf_rx_stop)))
|
|
freq_ranges.append(rs)
|
|
rs = RadioSetting("vhf_tx_start", "vhf tx start",
|
|
RadioSettingValueInteger(
|
|
66, 520, decode_freq(
|
|
self._memobj.freq_ranges.vhf_tx_start)))
|
|
freq_ranges.append(rs)
|
|
rs = RadioSetting("vhf_tx_stop", "vhf tx stop",
|
|
RadioSettingValueInteger(
|
|
66, 520, decode_freq(
|
|
self._memobj.freq_ranges.vhf_tx_stop)))
|
|
freq_ranges.append(rs)
|
|
rs = RadioSetting("uhf_tx_start", "uhf tx start",
|
|
RadioSettingValueInteger(
|
|
66, 520, decode_freq(
|
|
self._memobj.freq_ranges.uhf_tx_start)))
|
|
freq_ranges.append(rs)
|
|
rs = RadioSetting("uhf_tx_stop", "uhf tx stop",
|
|
RadioSettingValueInteger(
|
|
66, 520, decode_freq(
|
|
self._memobj.freq_ranges.uhf_tx_stop)))
|
|
freq_ranges.append(rs)
|
|
|
|
# tell the decoded ranges to UI
|
|
self.valid_freq = \
|
|
[(decode_freq(self._memobj.freq_ranges.vhf_rx_start) * 1000000,
|
|
(decode_freq(self._memobj.freq_ranges.vhf_rx_stop)+1) * 1000000)]
|
|
|
|
return group
|
|
|
|
@classmethod
|
|
def match_model(cls, filedata, filename):
|
|
if len(filedata) == 8192 and \
|
|
filedata[0x60:0x64] != b"2009" and \
|
|
filedata[0x170:0x173] != b"LX-" and \
|
|
filedata[0xF7E:0xF80] != b"\x01\xE2" and \
|
|
filedata[0x1f77:0x1f7d] == b"\xff\xff\xff\xff\xff\xff" and \
|
|
filedata[0x0d70:0x0d80] != (
|
|
b"\xff\xff\xff\xff\xff\xff\xff\xff"
|
|
b"\xff\xff\xff\xff\xff\xff\xff\xff"):
|
|
return True
|
|
return False
|
|
|
|
|
|
@directory.register
|
|
class KG818Radio(KG816Radio):
|
|
"""Wouxun KG-818"""
|
|
MODEL = "KG-818"
|
|
|
|
@classmethod
|
|
def match_model(cls, filedata, filename):
|
|
return False
|