add variant for radios with modified firmware

main
sq5bpf 2023-06-08 00:27:20 +02:00
rodzic 55fe65c3f5
commit b769749f62
1 zmienionych plików z 112 dodań i 40 usunięć

152
uvk5.py
Wyświetl plik

@ -27,16 +27,14 @@
# along with this program. If not, see <http://www.gnu.org/licenses/>.
# import struct
import struct
import logging
# import serial
from chirp import chirp_common, directory, bitwise, memmap, errors, util
from chirp.settings import RadioSetting, RadioSettingGroup, \
RadioSettingValueBoolean, RadioSettingValueList, \
RadioSettingValueInteger, RadioSettingValueString, \
RadioSettings
# from chirp.settings import RadioSettingValueFloat, RadioSettingValueMap
LOG = logging.getLogger(__name__)
@ -49,7 +47,7 @@ DEBUG_SHOW_OBFUSCATED_COMMANDS = False
# might be useful for someone debugging some obscure memory issue
DEBUG_SHOW_MEMORY_ACTIONS = False
DRIVER_VERSION = "Quansheng UV-K5 driver v20230529 (c) Jacek Lipkowski SQ5BPF"
DRIVER_VERSION = "Quansheng UV-K5 driver v20230608 (c) Jacek Lipkowski SQ5BPF"
PRINT_CONSOLE = False
MEM_FORMAT = """
@ -240,8 +238,27 @@ BANDS = {
5: [400.0, 469.9999],
6: [470.0, 600.0]
}
# for radios with modified firmware:
BANDS_NOLIMITS = {
0: [18.0, 76.0],
1: [108.0, 135.9999],
2: [136.0, 199.9990],
3: [200.0, 299.9999],
4: [350.0, 399.9999],
5: [400.0, 469.9999],
6: [470.0, 1300.0]
}
BANDMASK = 0b1111
VFO_CHANNEL_NAMES = ["F1(50M-76M)A", "F1(50M-76M)B",
"F2(108M-136M)A", "F2(108M-136M)B",
"F3(136M-174M)A", "F3(136M-174M)B",
"F4(174M-350M)A", "F4(174M-350M)B",
"F5(350M-400M)A", "F5(350M-400M)B",
"F6(400M-470M)A", "F6(400M-470M)B",
"F7(470M-600M)A", "F7(470M-600M)B"]
# the communication is obfuscated using this fine mechanism
def xorarr(data: bytes):
@ -275,9 +292,11 @@ def _send_command(serport, data: bytes):
(len(data), util.hexprint(data)))
crc = calculate_crc16_xmodem(data)
data2 = data+bytes([crc & 0xff, (crc >> 8) & 0xff])
data2 = data + struct.pack("<H", crc)
command = b"\xAB\xCD"+bytes([len(data)])+b"\x00"+xorarr(data2)+b"\xDC\xBA"
command = struct.pack(">HBB", 0xabcd, len(data), 0) + \
xorarr(data2) + \
struct.pack(">H", 0xdcba)
if DEBUG_SHOW_OBFUSCATED_COMMANDS:
LOG.debug("Sending command (obfuscated):\n%s" % util.hexprint(command))
try:
@ -335,16 +354,12 @@ def _receive_reply(serport):
def _getstring(data: bytes, begin, maxlen):
s = ""
c = 0
for i in data:
c += 1
if c < begin:
continue
if i < ord(' ') or i > ord('~'):
tmplen = min(maxlen+1, len(data))
s = [data[i] for i in range(begin, tmplen)]
for key, val in enumerate(s):
if val < ord(' ') or val > ord('~'):
break
s += chr(i)
return s
return ''.join(chr(x) for x in s[0:key])
def _sayhello(serport):
@ -362,7 +377,7 @@ def _sayhello(serport):
LOG.warning("Failed to initialise radio")
raise errors.RadioError("Failed to initialize radio")
return False
firmware = _getstring(o, 5, 16)
firmware = _getstring(o, 4, 16)
LOG.info("Found firmware: %s" % firmware)
return firmware
@ -371,7 +386,7 @@ def _readmem(serport, offset, length):
LOG.debug("Sending readmem offset=0x%4.4x len=0x%4.4x" % (offset, length))
readmem = b"\x1b\x05\x08\x00" + \
bytes([offset & 0xff, (offset >> 8) & 0xff, length, 0]) + \
struct.pack("<HBB", offset, length, 0) + \
b"\x6a\x39\x57\x64"
_send_command(serport, readmem)
o = _receive_reply(serport)
@ -390,8 +405,8 @@ def _writemem(serport, data, offset):
(offset, len(data), util.hexprint(data)))
dlen = len(data)
writemem = b"\x1d\x05"+bytes([dlen+8])+b"\x00" + \
bytes([offset & 0xff, (offset >> 8) & 0xff, dlen, 1]) + \
writemem = b"\x1d\x05" + \
struct.pack("<BBHBB", dlen+8, 0, offset, dlen, 1) + \
b"\x6a\x39\x57\x64"+data
_send_command(serport, writemem)
@ -479,16 +494,20 @@ def do_upload(radio):
return True
def _find_band(hz):
def _find_band(self, hz):
mhz = hz/1000000.0
for a in BANDS:
if mhz >= BANDS[a][0] and mhz <= BANDS[a][1]:
if self.FIRMWARE_NOLIMITS:
B = BANDS_NOLIMITS
else:
B = BANDS
for a in B:
if mhz >= B[a][0] and mhz <= B[a][1]:
return a
return False
@directory.register
class TemplateRadio(chirp_common.CloneModeRadio):
class UVK5Radio(chirp_common.CloneModeRadio):
"""Quansheng UV-K5"""
VENDOR = "Quansheng"
MODEL = "UV-K5"
@ -496,6 +515,7 @@ class TemplateRadio(chirp_common.CloneModeRadio):
NEEDS_COMPAT_SERIAL = False
FIRMWARE_VERSION = ""
FIRMWARE_NOLIMITS = False
def get_prompts(x=None):
rp = chirp_common.RadioPrompts()
@ -545,7 +565,7 @@ class TemplateRadio(chirp_common.CloneModeRadio):
"->Tone", "->DTCS", "DTCS->", "DTCS->DTCS"]
rf.valid_characters = chirp_common.CHARSET_ASCII
rf.valid_modes = ["FM", "NFM", "AM"]
rf.valid_modes = ["FM", "NFM", "AM", "NAM"]
rf.valid_tmodes = ["", "Tone", "TSQL", "DTCS", "Cross"]
rf.valid_skips = [""]
@ -675,8 +695,18 @@ class TemplateRadio(chirp_common.CloneModeRadio):
mem.number = number2
is_empty = False
# We'll consider any blank (i.e. 0MHz frequency) to be empty
if (_mem.freq == 0xffffffff) or (_mem.freq == 0):
is_empty = True
# We'll also look at the channel attributes if a memory has them
if number < 200:
_mem3 = self._memobj.channel_attributes[number]
if _mem3 & 0x08 > 0:
is_empty = True
if is_empty:
mem.empty = True
# set some sane defaults:
mem.power = UVK5_POWER_LEVELS[2]
@ -704,7 +734,7 @@ class TemplateRadio(chirp_common.CloneModeRadio):
return mem
if number > 199:
mem.name = "VFO_"+str(number-199)
mem.name = VFO_CHANNEL_NAMES[number-200]
mem.immutable = ["name"]
else:
_mem2 = self._memobj.channelname[number]
@ -733,9 +763,10 @@ class TemplateRadio(chirp_common.CloneModeRadio):
# mode
if (_mem.flags1 & FLAGS1_ISAM) > 0:
# Actually not sure if internally there aren't "Narrow AM"
# and "Wide AM" modes. To be investigated.
mem.mode = "AM"
if (_mem.flags2 & FLAGS2_BANDWIDTH) > 0:
mem.mode = "NAM"
else:
mem.mode = "AM"
else:
if (_mem.flags2 & FLAGS2_BANDWIDTH) > 0:
mem.mode = "NFM"
@ -1019,12 +1050,15 @@ class TemplateRadio(chirp_common.CloneModeRadio):
basic.append(rs)
# Battery save
tmpbatsave = _mem.battery_save
if tmpbatsave >= len(BATSAVE_LIST):
tmpbatsave = BATSAVE_LIST.index("1:4")
rs = RadioSetting(
"battery_save",
"Battery Save",
RadioSettingValueList(
BATSAVE_LIST,
BATSAVE_LIST[_mem.battery_save]))
BATSAVE_LIST[tmpbatsave]))
basic.append(rs)
# Dual watch
@ -1207,6 +1241,13 @@ class TemplateRadio(chirp_common.CloneModeRadio):
rs = RadioSetting("driver_ver", "Driver version", val)
roinfo.append(rs)
# No limits version for hacked firmware
val = RadioSettingValueBoolean(self.FIRMWARE_NOLIMITS)
val.set_mutable(False)
rs = RadioSetting("nolimits", "Limits disabled for modified firmware",
val)
roinfo.append(rs)
return top
# Store details about a high-level memory to the memory map
@ -1246,23 +1287,38 @@ class TemplateRadio(chirp_common.CloneModeRadio):
if number < 200:
_mem4.channel_attributes[number] = 0x0f
# find tx frequency
if mem.duplex == '-':
txfreq = mem.freq - mem.offset
elif mem.duplex == '+':
txfreq = mem.freq + mem.offset
else:
txfreq = mem.freq
# find band
band = _find_band(mem.freq)
band = _find_band(self, txfreq)
if band is False:
raise errors.RadioError(
"Transmit frequency %.4fMHz is not supported by this radio"
% txfreq/1000000.0)
band = _find_band(self, mem.freq)
if band is False:
# raise errors.RadioError(
# "Frequency is outside the supported bands")
return mem
# mode
if mem.mode == "AM":
_mem.flags1 = _mem.flags1 | FLAGS1_ISAM
_mem.flags2 = _mem.flags2 & ~FLAGS2_BANDWIDTH
else:
if mem.mode == "NFM":
_mem.flags2 = _mem.flags2 | FLAGS2_BANDWIDTH
_mem.flags1 = _mem.flags1 & ~FLAGS1_ISAM
if mem.mode == "NFM":
_mem.flags2 = _mem.flags2 | FLAGS2_BANDWIDTH
else:
_mem.flags2 = _mem.flags2 & ~FLAGS2_BANDWIDTH
elif mem.mode == "FM":
_mem.flags2 = _mem.flags2 & ~FLAGS2_BANDWIDTH
_mem.flags1 = _mem.flags1 & ~FLAGS1_ISAM
elif mem.mode == "NAM":
_mem.flags2 = _mem.flags2 | FLAGS2_BANDWIDTH
_mem.flags1 = _mem.flags1 | FLAGS1_ISAM
elif mem.mode == "AM":
_mem.flags2 = _mem.flags2 & ~FLAGS2_BANDWIDTH
_mem.flags1 = _mem.flags1 | FLAGS1_ISAM
# frequency/offset
_mem.freq = mem.freq/10
@ -1337,3 +1393,19 @@ class TemplateRadio(chirp_common.CloneModeRadio):
_mem.scrambler & 0xf0) | SCRAMBLER_LIST.index(svalue)
return mem
@directory.register
class UVK5Radio_nolimit(UVK5Radio):
VENDOR = "Quansheng"
MODEL = "UV-K5 (modified firmware)"
VARIANT = "nolimits"
FIRMWARE_NOLIMITS = True
def get_features(self):
rf = UVK5Radio.get_features(self)
# This is what the BK4819 chip supports
rf.valid_bands = [(18000000, 620000000),
(840000000, 1300000000)
]
return rf