kopia lustrzana https://github.com/sq5bpf/uvk5-reverse-engineering
add scanlist support
rodzic
224cf9eb9c
commit
8ccecfb4cc
176
uvk5.py
176
uvk5.py
|
@ -47,7 +47,7 @@ DEBUG_SHOW_OBFUSCATED_COMMANDS = False
|
||||||
# might be useful for someone debugging some obscure memory issue
|
# might be useful for someone debugging some obscure memory issue
|
||||||
DEBUG_SHOW_MEMORY_ACTIONS = False
|
DEBUG_SHOW_MEMORY_ACTIONS = False
|
||||||
|
|
||||||
DRIVER_VERSION = "Quansheng UV-K5 driver v20230610 (c) Jacek Lipkowski SQ5BPF"
|
DRIVER_VERSION = "Quansheng UV-K5 driver v20230613 (c) Jacek Lipkowski SQ5BPF"
|
||||||
PRINT_CONSOLE = False
|
PRINT_CONSOLE = False
|
||||||
|
|
||||||
MEM_FORMAT = """
|
MEM_FORMAT = """
|
||||||
|
@ -66,7 +66,14 @@ struct {
|
||||||
} channel[214];
|
} channel[214];
|
||||||
|
|
||||||
#seekto 0xd60;
|
#seekto 0xd60;
|
||||||
u8 channel_attributes[200];
|
struct {
|
||||||
|
u8 is_scanlist1:1,
|
||||||
|
is_scanlist2:1,
|
||||||
|
unknown1:1,
|
||||||
|
unknown2:1,
|
||||||
|
is_free:1,
|
||||||
|
band:3;
|
||||||
|
} channel_attributes[200];
|
||||||
|
|
||||||
#seekto 0xe40;
|
#seekto 0xe40;
|
||||||
ul16 fmfreq[20];
|
ul16 fmfreq[20];
|
||||||
|
@ -109,6 +116,17 @@ u8 repeater_tail_elimination;
|
||||||
char logo_line1[16];
|
char logo_line1[16];
|
||||||
char logo_line2[16];
|
char logo_line2[16];
|
||||||
|
|
||||||
|
#seekto 0xf18;
|
||||||
|
u8 scanlist_default;
|
||||||
|
u8 scanlist1_priority_scan;
|
||||||
|
u8 scanlist1_priority_ch1;
|
||||||
|
u8 scanlist1_priority_ch2;
|
||||||
|
u8 scanlist2_priority_scan;
|
||||||
|
u8 scanlist2_priority_ch1;
|
||||||
|
u8 scanlist2_priority_ch2;
|
||||||
|
u8 scanlist_unknown_0xff;
|
||||||
|
|
||||||
|
|
||||||
#seekto 0xf40;
|
#seekto 0xf40;
|
||||||
u8 int_flock;
|
u8 int_flock;
|
||||||
u8 int_350tx;
|
u8 int_350tx;
|
||||||
|
@ -263,6 +281,8 @@ VFO_CHANNEL_NAMES = ["F1(50M-76M)A", "F1(50M-76M)B",
|
||||||
"F6(400M-470M)A", "F6(400M-470M)B",
|
"F6(400M-470M)A", "F6(400M-470M)B",
|
||||||
"F7(470M-600M)A", "F7(470M-600M)B"]
|
"F7(470M-600M)A", "F7(470M-600M)B"]
|
||||||
|
|
||||||
|
SCANLIST_LIST = ["None", "1", "2", "1+2"]
|
||||||
|
|
||||||
|
|
||||||
# the communication is obfuscated using this fine mechanism
|
# the communication is obfuscated using this fine mechanism
|
||||||
def xorarr(data: bytes):
|
def xorarr(data: bytes):
|
||||||
|
@ -516,7 +536,6 @@ class UVK5Radio(chirp_common.CloneModeRadio):
|
||||||
VENDOR = "Quansheng"
|
VENDOR = "Quansheng"
|
||||||
MODEL = "UV-K5"
|
MODEL = "UV-K5"
|
||||||
BAUD_RATE = 38400
|
BAUD_RATE = 38400
|
||||||
|
|
||||||
NEEDS_COMPAT_SERIAL = False
|
NEEDS_COMPAT_SERIAL = False
|
||||||
FIRMWARE_VERSION = ""
|
FIRMWARE_VERSION = ""
|
||||||
FIRMWARE_NOLIMITS = False
|
FIRMWARE_NOLIMITS = False
|
||||||
|
@ -530,7 +549,7 @@ class UVK5Radio(chirp_common.CloneModeRadio):
|
||||||
'the memory image from the radio with chirp or k5prog '
|
'the memory image from the radio with chirp or k5prog '
|
||||||
'and keep it. This can be later used to recover the '
|
'and keep it. This can be later used to recover the '
|
||||||
'original settings. \n\n'
|
'original settings. \n\n'
|
||||||
'FM radio, DTMF settings and scanlists are not yet implemented')
|
'DTMF settings and other details are not yet implemented')
|
||||||
rp.pre_download = _(
|
rp.pre_download = _(
|
||||||
"1. Turn radio on.\n"
|
"1. Turn radio on.\n"
|
||||||
"2. Connect cable to mic/spkr connector.\n"
|
"2. Connect cable to mic/spkr connector.\n"
|
||||||
|
@ -681,7 +700,6 @@ class UVK5Radio(chirp_common.CloneModeRadio):
|
||||||
|
|
||||||
# Extract a high-level memory object from the low-level memory map
|
# Extract a high-level memory object from the low-level memory map
|
||||||
# This is called to populate a memory in the UI
|
# This is called to populate a memory in the UI
|
||||||
|
|
||||||
def get_memory(self, number2):
|
def get_memory(self, number2):
|
||||||
number = number2-1 # in the radio memories start with 0
|
number = number2-1 # in the radio memories start with 0
|
||||||
|
|
||||||
|
@ -704,11 +722,21 @@ class UVK5Radio(chirp_common.CloneModeRadio):
|
||||||
if (_mem.freq == 0xffffffff) or (_mem.freq == 0):
|
if (_mem.freq == 0xffffffff) or (_mem.freq == 0):
|
||||||
is_empty = True
|
is_empty = True
|
||||||
|
|
||||||
|
tmpscn = SCANLIST_LIST[0]
|
||||||
|
|
||||||
# We'll also look at the channel attributes if a memory has them
|
# We'll also look at the channel attributes if a memory has them
|
||||||
if number < 200:
|
if number < 200:
|
||||||
_mem3 = self._memobj.channel_attributes[number]
|
_mem3 = self._memobj.channel_attributes[number]
|
||||||
if _mem3 & 0x08 > 0:
|
# free memory bit
|
||||||
|
if _mem3.is_free > 0:
|
||||||
is_empty = True
|
is_empty = True
|
||||||
|
# scanlists
|
||||||
|
if _mem3.is_scanlist1 > 0 and _mem3.is_scanlist2 > 0:
|
||||||
|
tmpscn = SCANLIST_LIST[3] # "1+2"
|
||||||
|
elif _mem3.is_scanlist1 > 0:
|
||||||
|
tmpscn = SCANLIST_LIST[1] # "1"
|
||||||
|
elif _mem3.is_scanlist2 > 0:
|
||||||
|
tmpscn = SCANLIST_LIST[2] # "2"
|
||||||
|
|
||||||
if is_empty:
|
if is_empty:
|
||||||
mem.empty = True
|
mem.empty = True
|
||||||
|
@ -730,6 +758,10 @@ class UVK5Radio(chirp_common.CloneModeRadio):
|
||||||
SCRAMBLER_LIST, SCRAMBLER_LIST[0]))
|
SCRAMBLER_LIST, SCRAMBLER_LIST[0]))
|
||||||
mem.extra.append(rs)
|
mem.extra.append(rs)
|
||||||
|
|
||||||
|
rs = RadioSetting("scanlists", "Scanlists", RadioSettingValueList(
|
||||||
|
SCANLIST_LIST, SCANLIST_LIST[0]))
|
||||||
|
mem.extra.append(rs)
|
||||||
|
|
||||||
# actually the step and duplex are overwritten by chirp based on
|
# actually the step and duplex are overwritten by chirp based on
|
||||||
# bandplan. they are here to document sane defaults for IARU r1
|
# bandplan. they are here to document sane defaults for IARU r1
|
||||||
# mem.tuning_step = 25.0
|
# mem.tuning_step = 25.0
|
||||||
|
@ -739,7 +771,7 @@ class UVK5Radio(chirp_common.CloneModeRadio):
|
||||||
|
|
||||||
if number > 199:
|
if number > 199:
|
||||||
mem.name = VFO_CHANNEL_NAMES[number-200]
|
mem.name = VFO_CHANNEL_NAMES[number-200]
|
||||||
mem.immutable = ["name"]
|
mem.immutable = ["name", "scanlists"]
|
||||||
else:
|
else:
|
||||||
_mem2 = self._memobj.channelname[number]
|
_mem2 = self._memobj.channelname[number]
|
||||||
for char in _mem2.name:
|
for char in _mem2.name:
|
||||||
|
@ -837,6 +869,12 @@ class UVK5Radio(chirp_common.CloneModeRadio):
|
||||||
mem.extra.append(rs)
|
mem.extra.append(rs)
|
||||||
tmpcomment += "Scrambler:"+SCRAMBLER_LIST[enc]+" "
|
tmpcomment += "Scrambler:"+SCRAMBLER_LIST[enc]+" "
|
||||||
|
|
||||||
|
# scanlists
|
||||||
|
pttid = (_mem.dtmf_flags & FLAGS_DTMF_PTTID_MASK) >> 1
|
||||||
|
rs = RadioSetting("scanlists", "Scanlists", RadioSettingValueList(
|
||||||
|
SCANLIST_LIST, tmpscn))
|
||||||
|
mem.extra.append(rs)
|
||||||
|
|
||||||
return mem
|
return mem
|
||||||
|
|
||||||
def set_settings(self, settings):
|
def set_settings(self, settings):
|
||||||
|
@ -986,15 +1024,105 @@ class UVK5Radio(chirp_common.CloneModeRadio):
|
||||||
# "in the range %.1f - %.1f" % (FMMIN , FMMAX))
|
# "in the range %.1f - %.1f" % (FMMIN , FMMAX))
|
||||||
_mem.fmfreq[i-1] = val2
|
_mem.fmfreq[i-1] = val2
|
||||||
|
|
||||||
|
# scanlist stuff
|
||||||
|
if element.get_name() == "scanlist_default":
|
||||||
|
val = (int(element.value) == 2) and 1 or 0
|
||||||
|
_mem.scanlist_default = val
|
||||||
|
|
||||||
|
if element.get_name() == "scanlist1_priority_scan":
|
||||||
|
_mem.scanlist1_priority_scan = \
|
||||||
|
element.value and 1 or 0
|
||||||
|
|
||||||
|
if element.get_name() == "scanlist2_priority_scan":
|
||||||
|
_mem.scanlist2_priority_scan = \
|
||||||
|
element.value and 1 or 0
|
||||||
|
|
||||||
|
if element.get_name() == "scanlist1_priority_ch1" or \
|
||||||
|
element.get_name() == "scanlist1_priority_ch2" or \
|
||||||
|
element.get_name() == "scanlist2_priority_ch1" or \
|
||||||
|
element.get_name() == "scanlist2_priority_ch2":
|
||||||
|
|
||||||
|
val = int(element.value)
|
||||||
|
|
||||||
|
if val > 200 or val < 1:
|
||||||
|
val = 0xff
|
||||||
|
else:
|
||||||
|
val -= 1
|
||||||
|
|
||||||
|
if element.get_name() == "scanlist1_priority_ch1":
|
||||||
|
_mem.scanlist1_priority_ch1 = val
|
||||||
|
if element.get_name() == "scanlist1_priority_ch2":
|
||||||
|
_mem.scanlist1_priority_ch2 = val
|
||||||
|
if element.get_name() == "scanlist2_priority_ch1":
|
||||||
|
_mem.scanlist2_priority_ch1 = val
|
||||||
|
if element.get_name() == "scanlist2_priority_ch2":
|
||||||
|
_mem.scanlist2_priority_ch2 = val
|
||||||
|
|
||||||
def get_settings(self):
|
def get_settings(self):
|
||||||
_mem = self._memobj
|
_mem = self._memobj
|
||||||
basic = RadioSettingGroup("basic", "Basic Settings")
|
basic = RadioSettingGroup("basic", "Basic Settings")
|
||||||
|
scanl = RadioSettingGroup("scn", "Scan Lists")
|
||||||
unlock = RadioSettingGroup("unlock", "Unlock Settings")
|
unlock = RadioSettingGroup("unlock", "Unlock Settings")
|
||||||
fmradio = RadioSettingGroup("fmradio", "FM Radio")
|
fmradio = RadioSettingGroup("fmradio", "FM Radio")
|
||||||
|
|
||||||
roinfo = RadioSettingGroup("roinfo", "Driver information")
|
roinfo = RadioSettingGroup("roinfo", "Driver information")
|
||||||
|
|
||||||
top = RadioSettings(basic, unlock, fmradio, roinfo)
|
top = RadioSettings(basic, scanl, unlock, fmradio, roinfo)
|
||||||
|
|
||||||
|
if _mem.scanlist_default == 1:
|
||||||
|
tmpsc = 2
|
||||||
|
else:
|
||||||
|
tmpsc = 1
|
||||||
|
rs = RadioSetting("scanlist_default",
|
||||||
|
"Default scanlist",
|
||||||
|
RadioSettingValueInteger(1, 2, tmpsc))
|
||||||
|
scanl.append(rs)
|
||||||
|
|
||||||
|
tmppr = bool((_mem.scanlist1_priority_scan & 1) > 0)
|
||||||
|
rs = RadioSetting(
|
||||||
|
"scanlist1_priority_scan",
|
||||||
|
"Scanlist 1 priority channel scan",
|
||||||
|
RadioSettingValueBoolean(tmppr))
|
||||||
|
scanl.append(rs)
|
||||||
|
|
||||||
|
tmpch = _mem.scanlist1_priority_ch1 + 1
|
||||||
|
if tmpch > 200:
|
||||||
|
tmpch = 0
|
||||||
|
rs = RadioSetting("scanlist1_priority_ch1",
|
||||||
|
"Scanlist 1 priority channel 1 (0 - off)",
|
||||||
|
RadioSettingValueInteger(0, 200, tmpch))
|
||||||
|
scanl.append(rs)
|
||||||
|
|
||||||
|
tmpch = _mem.scanlist1_priority_ch2 + 1
|
||||||
|
if tmpch > 200:
|
||||||
|
tmpch = 0
|
||||||
|
rs = RadioSetting("scanlist1_priority_ch2",
|
||||||
|
"Scanlist 1 priority channel 2 (0 - off)",
|
||||||
|
RadioSettingValueInteger(0, 200, tmpch))
|
||||||
|
scanl.append(rs)
|
||||||
|
|
||||||
|
tmppr = bool((_mem.scanlist2_priority_scan & 1) > 0)
|
||||||
|
rs = RadioSetting(
|
||||||
|
"scanlist2_priority_scan",
|
||||||
|
"Scanlist 2 priority channel scan",
|
||||||
|
RadioSettingValueBoolean(tmppr))
|
||||||
|
scanl.append(rs)
|
||||||
|
|
||||||
|
tmpch = _mem.scanlist2_priority_ch1 + 1
|
||||||
|
if tmpch > 200:
|
||||||
|
tmpch = 0
|
||||||
|
rs = RadioSetting("scanlist2_priority_ch1",
|
||||||
|
"Scanlist 2 priority channel 1 (0 - off)",
|
||||||
|
RadioSettingValueInteger(0, 200, tmpch))
|
||||||
|
scanl.append(rs)
|
||||||
|
|
||||||
|
tmpch = _mem.scanlist2_priority_ch2 + 1
|
||||||
|
if tmpch > 200:
|
||||||
|
tmpch = 0
|
||||||
|
rs = RadioSetting("scanlist2_priority_ch2",
|
||||||
|
"Scanlist 2 priority channel 2 (0 - off)",
|
||||||
|
RadioSettingValueInteger(0, 200, tmpch))
|
||||||
|
scanl.append(rs)
|
||||||
|
|
||||||
# basic settings
|
# basic settings
|
||||||
|
|
||||||
|
@ -1298,7 +1426,12 @@ class UVK5Radio(chirp_common.CloneModeRadio):
|
||||||
if number < 200:
|
if number < 200:
|
||||||
_mem2 = self._memobj.channelname[number]
|
_mem2 = self._memobj.channelname[number]
|
||||||
_mem2.set_raw("\xFF" * 16)
|
_mem2.set_raw("\xFF" * 16)
|
||||||
_mem4.channel_attributes[number] = 0x0f
|
_mem4.channel_attributes[number].is_scanlist1 = 0
|
||||||
|
_mem4.channel_attributes[number].is_scanlist2 = 0
|
||||||
|
_mem4.channel_attributes[number].unknown1 = 0
|
||||||
|
_mem4.channel_attributes[number].unknown2 = 0
|
||||||
|
_mem4.channel_attributes[number].is_free = 1
|
||||||
|
_mem4.channel_attributes[number].band = 0x7
|
||||||
return mem
|
return mem
|
||||||
|
|
||||||
# clean the channel memory, restore some bits if it was used before
|
# clean the channel memory, restore some bits if it was used before
|
||||||
|
@ -1319,7 +1452,12 @@ class UVK5Radio(chirp_common.CloneModeRadio):
|
||||||
chr(prev_0d) + chr(prev_0e) + chr(prev_0f))
|
chr(prev_0d) + chr(prev_0e) + chr(prev_0f))
|
||||||
|
|
||||||
if number < 200:
|
if number < 200:
|
||||||
_mem4.channel_attributes[number] = 0x0f
|
_mem4.channel_attributes[number].is_scanlist1 = 0
|
||||||
|
_mem4.channel_attributes[number].is_scanlist2 = 0
|
||||||
|
_mem4.channel_attributes[number].unknown1 = 0
|
||||||
|
_mem4.channel_attributes[number].unknown2 = 0
|
||||||
|
_mem4.channel_attributes[number].is_free = 1
|
||||||
|
_mem4.channel_attributes[number].band = 0x7
|
||||||
|
|
||||||
# find tx frequency
|
# find tx frequency
|
||||||
if mem.duplex == '-':
|
if mem.duplex == '-':
|
||||||
|
@ -1370,8 +1508,8 @@ class UVK5Radio(chirp_common.CloneModeRadio):
|
||||||
|
|
||||||
# set band
|
# set band
|
||||||
if number < 200:
|
if number < 200:
|
||||||
_mem4.channel_attributes[number] = (
|
_mem4.channel_attributes[number].is_free = 0
|
||||||
_mem4.channel_attributes[number] & ~BANDMASK) | band
|
_mem4.channel_attributes[number].band = band
|
||||||
|
|
||||||
# channels >200 are the 14 VFO chanells and don't have names
|
# channels >200 are the 14 VFO chanells and don't have names
|
||||||
if number < 200:
|
if number < 200:
|
||||||
|
@ -1426,6 +1564,20 @@ class UVK5Radio(chirp_common.CloneModeRadio):
|
||||||
_mem.scrambler = (
|
_mem.scrambler = (
|
||||||
_mem.scrambler & 0xf0) | SCRAMBLER_LIST.index(svalue)
|
_mem.scrambler & 0xf0) | SCRAMBLER_LIST.index(svalue)
|
||||||
|
|
||||||
|
if number < 200 and sname == "scanlists":
|
||||||
|
if svalue == "1":
|
||||||
|
_mem4.channel_attributes[number].is_scanlist1 = 1
|
||||||
|
_mem4.channel_attributes[number].is_scanlist2 = 0
|
||||||
|
elif svalue == "2":
|
||||||
|
_mem4.channel_attributes[number].is_scanlist1 = 0
|
||||||
|
_mem4.channel_attributes[number].is_scanlist2 = 1
|
||||||
|
elif svalue == "1+2":
|
||||||
|
_mem4.channel_attributes[number].is_scanlist1 = 1
|
||||||
|
_mem4.channel_attributes[number].is_scanlist2 = 1
|
||||||
|
else:
|
||||||
|
_mem4.channel_attributes[number].is_scanlist1 = 0
|
||||||
|
_mem4.channel_attributes[number].is_scanlist2 = 0
|
||||||
|
|
||||||
return mem
|
return mem
|
||||||
|
|
||||||
|
|
||||||
|
|
Ładowanie…
Reference in New Issue