[chirp_devel] [PATCH] Figured out encryption algorithm and implement functions for Wouxun KG-UV8D Plus #5123
# HG changeset patch # User Krystian SP6IT toner_82@tlen.pl # Date 1511561875 -3600 # Fri Nov 24 23:17:55 2017 +0100 # Node ID e870a7f807f5966aa315ab49b344222b7c9cd4a2 # Parent 0e0470d3e892bfd4818bfb972b8b85d58f2b00d4 Figured out encryption algorithm and implement functions for Wouxun KG-UV8D Plus #5123
diff -r 0e0470d3e892 -r e870a7f807f5 chirp/drivers/kguv8dplus.py --- /dev/null Thu Jan 01 00:00:00 1970 +0000 +++ b/chirp/drivers/kguv8dplus.py Fri Nov 24 23:17:55 2017 +0100 @@ -0,0 +1,1110 @@ +# Copyright 2017 Krystian Struzik toner_82@tlen.pl +# Based on Ron Wellsted driver for Wouxun KG-UV8D. +# KG-UV8D Plus model has all serial data encrypted. +# Figured out how the data is encrypted and implement +# serial data encryption and decryption functions. +# The algorithm of decryption works like this: +# - the first byte of data stream is XOR by const 57h +# - each next byte is encoded by previous byte using the XOR +# including the checksum (e.g data[i - 1] xor data[i]) +# I also changed the data structure to fit radio memory +# and implement set_settings function. +# +# 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 KG-UV8D Plus radio management module""" + +import time +import os +import logging +from chirp import util, chirp_common, bitwise, memmap, errors, directory +from chirp.settings import RadioSetting, RadioSettingGroup, \ + RadioSettingValueBoolean, RadioSettingValueList, \ + RadioSettingValueInteger, RadioSettingValueString, \ + RadioSettings + +LOG = logging.getLogger(__name__) + +CMD_ID = 128 +CMD_END = 129 +CMD_RD = 130 +CMD_WR = 131 + +MEM_VALID = 158 + +AB_LIST = ["A", "B"] +STEPS = [2.5, 5.0, 6.25, 10.0, 12.5, 25.0, 50.0, 100.0] +STEP_LIST = [str(x) for x in STEPS] +ROGER_LIST = ["Off", "Begin", "End", "Both"] +TIMEOUT_LIST = ["Off"] + [str(x) + "s" for x in range(15, 901, 15)] +VOX_LIST = ["Off"] + ["%s" % x for x in range(1, 10)] +BANDWIDTH_LIST = ["Narrow", "Wide"] +VOICE_LIST = ["Off", "On"] +LANGUAGE_LIST = ["Chinese", "English"] +SCANMODE_LIST = ["TO", "CO", "SE"] +PF1KEY_LIST = ["Call", "VFTX"] +PF3KEY_LIST = ["Disable", "Scan", "Lamp", "Tele Alarm", "SOS-CH", "Radio"] +WORKMODE_LIST = ["VFO", "Channel No.", "Ch. No.+Freq.", "Ch. No.+Name"] +BACKLIGHT_LIST = ["Always On"] + [str(x) + "s" for x in range(1, 21)] + \ + ["Always Off"] +OFFSET_LIST = ["+", "-"] +PONMSG_LIST = ["Bitmap", "Battery Volts"] +SPMUTE_LIST = ["QT", "QT+DTMF", "QT*DTMF"] +DTMFST_LIST = ["DT-ST", "ANI-ST", "DT-ANI", "Off"] +DTMF_TIMES = ["%s" % x for x in range(50, 501, 10)] +RPTSET_LIST = ["X-TWRPT", "X-DIRRPT"] +ALERTS = [1750, 2100, 1000, 1450] +ALERTS_LIST = [str(x) for x in ALERTS] +PTTID_LIST = ["Begin", "End", "Both"] +LIST_10 = ["Off"] + ["%s" % x for x in range(1, 11)] +SCANGRP_LIST = ["All"] + ["%s" % x for x in range(1, 11)] +SCQT_LIST = ["Decoder", "Encoder", "All"] +SMUTESET_LIST = ["Off", "Tx", "Rx", "Tx/Rx"] +POWER_LIST = ["Lo", "Hi"] +HOLD_TIMES = ["Off"] + ["%s" % x for x in range(100, 5001, 100)] +RPTMODE_LIST = ["Radio", "Repeater"] + +# memory slot 0 is not used, start at 1 (so need 1000 slots, not 999) +# structure elements whose name starts with x are currently unidentified + +_MEM_FORMAT = """ + #seekto 0x0044; + struct { + u32 rx_start; + u32 rx_stop; + u32 tx_start; + u32 tx_stop; + } uhf_limits; + + #seekto 0x0054; + struct { + u32 rx_start; + u32 rx_stop; + u32 tx_start; + u32 tx_stop; + } vhf_limits; + + #seekto 0x0400; + struct { + u8 oem1[8]; + u8 unknown[2]; + u8 unknown2[10]; + u8 unknown3[10]; + u8 unknown4[8]; + u8 model[10]; + u8 version[6]; + u8 date[8]; + u8 unknown5[1]; + u8 oem2[8]; + } oem_info; + + #seekto 0x0480; + struct { + u16 lower; + u16 upper; + } scan_groups[10]; + + #seekto 0x0500; + struct { + u8 call_code[6]; + } call_groups[20]; + + #seekto 0x0580; + struct { + char call_name[6]; + } call_group_name[20]; + + #seekto 0x0800; + struct { + u8 ponmsg; + char dispstr[15]; + u8 x0810; + u8 x0811; + u8 x0812; + u8 x0813; + u8 x0814; + u8 voice; + u8 timeout; + u8 toalarm; + u8 channel_menu; + u8 power_save; + u8 autolock; + u8 keylock; + u8 beep; + u8 stopwatch; + u8 vox; + u8 scan_rev; + u8 backlight; + u8 roger_beep; + u8 x0822[6]; + u8 x0823[6]; + u16 pri_ch; + u8 ani_sw; + u8 ptt_delay; + u8 ani_code[6]; + u8 dtmf_st; + u8 bcl_a; + u8 bcl_b; + u8 ptt_id; + u8 prich_sw; + u8 rpt_set; + u8 rpt_spk; + u8 rpt_ptt; + u8 alert; + u8 pf1_func; + u8 pf3_func; + u8 x0843; + u8 workmode_a; + u8 workmode_b; + u8 dtmf_tx_time; + u8 dtmf_interval; + u8 main_ab; + u16 work_cha; + u16 work_chb; + u8 x084d; + u8 x084e; + u8 x084f; + u8 x0850; + u8 x0851; + u8 x0852; + u8 x0853; + u8 x0854; + u8 rpt_mode; + u8 language; + u8 x0857; + u8 x0858; + u8 x0859; + u8 x085a; + u8 x085b; + u8 x085c; + u8 x085d; + u8 x085e; + u8 single_display; + u8 ring_time; + u8 scg_a; + u8 scg_b; + u8 x0863; + u8 rpt_tone; + u8 rpt_hold; + u8 scan_det; + u8 sc_qt; + u8 x0868; + u8 smuteset; + u8 callcode; + } settings; + + #seekto 0x0880; + struct { + u32 rxfreq; + u32 txoffset; + u16 rxtone; + u16 txtone; + u8 scrambler:4, + unknown1:2, + power:1, + unknown2:1; + u8 unknown3:1, + shift_dir:2 + unknown4:1, + compander:1, + mute_mode:2, + iswide:1; + u8 step; + u8 squelch; + } vfoa; + + #seekto 0x08c0; + struct { + u32 rxfreq; + u32 txoffset; + u16 rxtone; + u16 txtone; + u8 scrambler:4, + unknown1:2, + power:1, + unknown2:1; + u8 unknown3:1, + shift_dir:2 + unknown4:1, + compander:1, + mute_mode:2, + iswide:1; + u8 step; + u8 squelch; + } vfob; + + #seekto 0x0900; + struct { + u32 rxfreq; + u32 txfreq; + u16 rxtone; + u16 txtone; + u8 scrambler:4, + unknown1:2, + power:1, + unknown2:1; + u8 unknown3:2, + scan_add:1, + unknown4:1, + compander:1, + mute_mode:2, + iswide:1; + u16 padding; + } memory[1000]; + + #seekto 0x4780; + struct { + u8 name[8]; + u8 unknown[4]; + } names[1000]; + + #seekto 0x7670; + u8 valid[1000]; + """ + +# Support for the Wouxun KG-UV8D Plus radio +# Serial coms are at 19200 baud +# The data is passed in variable length records +# Record structure: +# Offset Usage +# 0 start of record (\x7a) +# 1 Command (\x80 Identify \x81 End/Reboot \x82 Read \x83 Write) +# 2 direction (\xff PC-> Radio, \x00 Radio -> PC) +# 3 length of payload (excluding header/checksum) (n) +# 4 payload (n bytes) +# 4+n+1 checksum - byte sum (% 256) of bytes 1 -> 4+n +# +# Memory Read Records: +# the payload is 3 bytes, first 2 are offset (big endian), +# 3rd is number of bytes to read +# Memory Write Records: +# the maximum payload size (from the Wouxun software) seems to be 66 bytes +# (2 bytes location + 64 bytes data). + +@directory.register +class KGUV8DPlusRadio(chirp_common.CloneModeRadio, + chirp_common.ExperimentalRadio): + + """Wouxun KG-UV8D Plus""" + VENDOR = "Wouxun" + MODEL = "KG-UV8D Plus" + _model = "KG-UV8D" + _file_ident = "kguv8dplus" + BAUD_RATE = 19200 + POWER_LEVELS = [chirp_common.PowerLevel("L", watts=1), + chirp_common.PowerLevel("H", watts=5)] + _mmap = "" + + def _checksum(self, data): + cs = 0 + for byte in data: + cs += ord(byte) + return chr(cs % 256) + + def _write_record(self, cmd, payload = None): + # build the packet + _header = '\x7a' + chr(cmd) + '\xff' + + _length = 0 + if payload: + _length = len(payload) + + # update the length field + _header += chr(_length) + if payload: + # calculate checksum then add it with the payload to the packet and encrypt + crc = self._checksum(_header[1:] + payload) + payload += crc + _header += self.encrypt(payload) + else: + # calculate and add encrypted checksum to the packet + crc = self._checksum(_header[1:]) + _header += self.strxor(crc, '\x57') + LOG.debug("Sent:\n%s" % util.hexprint(_header)) + self.pipe.write(_header) + + def _read_record(self): + # read 4 chars for the header + _header = self.pipe.read(4) + if len(_header) != 4: + raise errors.RadioError('Radio did not respond') + _length = ord(_header[3]) + _packet = self.pipe.read(_length) + _rcs_xor = _packet[-1] + _packet = self.decrypt(_packet) + _cs = ord(self._checksum(_header[1:] + _packet)) + # read the checksum and decrypt it + _rcs = ord(self.strxor(self.pipe.read(1), _rcs_xor)) + LOG.debug("_cs =%x", _cs) + LOG.debug("_rcs=%x", _rcs) + return (_rcs != _cs, _packet) + + def decrypt(self, data): + result = '' + for i in range(len(data)-1, 0, -1): + result += self.strxor(data[i], data[i - 1]) + result += self.strxor(data[0], '\x57') + return result[::-1] + + def encrypt(self, data): + result = self.strxor('\x57', data[0]) + for i in range(1, len(data), 1): + result += self.strxor(result[i - 1], data[i]) + return result + + def strxor (self, xora, xorb): + return chr(ord(xora) ^ ord(xorb)) + +# Identify the radio +# +# A Gotcha: the first identify packet returns a bad checksum, subsequent +# attempts return the correct checksum... (well it does on my radio!) +# +# The ID record returned by the radio also includes the current frequency range +# as 4 bytes big-endian in 10Hz increments +# +# Offset +# 0:10 Model, zero padded (Use first 7 chars for 'KG-UV8D') +# 11:14 UHF rx lower limit (in units of 10Hz) +# 15:18 UHF rx upper limit +# 19:22 UHF tx lower limit +# 23:26 UHF tx upper limit +# 27:30 VHF rx lower limit +# 31:34 VHF rx upper limit +# 35:38 VHF tx lower limit +# 39:42 VHF tx upper limit +# + @classmethod + def match_model(cls, filedata, filename): + return cls._file_ident in 'kg' + filedata[0x426:0x430].replace('(', '').replace(')', '').lower() + + def _identify(self): + """Do the identification dance""" + for _i in range(0, 10): + self._write_record(CMD_ID) + _chksum_err, _resp = self._read_record() + LOG.debug("Got:\n%s" % util.hexprint(_resp)) + if _chksum_err: + LOG.error("Checksum error: retrying ident...") + time.sleep(0.100) + continue + LOG.debug("Model %s" % util.hexprint(_resp[0:7])) + if _resp[0:7] == self._model: + return + if len(_resp) == 0: + raise Exception("Radio not responding") + else: + raise Exception("Unable to identify radio") + + def _finish(self): + self._write_record(CMD_END) + + def process_mmap(self): + self._memobj = bitwise.parse(_MEM_FORMAT, self._mmap) + + def sync_in(self): + try: + self._mmap = self._download() + except errors.RadioError: + raise + except Exception, e: + raise errors.RadioError("Failed to communicate with radio: %s" % e) + self.process_mmap() + + def sync_out(self): + self._upload() + + # TODO: Load all memory. + # It would be smarter to only load the active areas and none of + # the padding/unused areas. Padding still need to be investigated. + def _download(self): + """Talk to a wouxun KG-UV8D Plus and do a download""" + try: + self._identify() + return self._do_download(0, 32768, 64) + except errors.RadioError: + raise + except Exception, e: + LOG.exception('Unknown error during download process') + raise errors.RadioError("Failed to communicate with radio: %s" % e) + + def _do_download(self, start, end, blocksize): + # allocate & fill memory + image = "" + for i in range(start, end, blocksize): + req = chr(i / 256) + chr(i % 256) + chr(blocksize) + self._write_record(CMD_RD, req) + cs_error, resp = self._read_record() + if cs_error: + LOG.debug(util.hexprint(resp)) + raise Exception("Checksum error on read") + LOG.debug("Got:\n%s" % util.hexprint(resp)) + image += resp[2:] + if self.status_fn: + status = chirp_common.Status() + status.cur = i + status.max = end + status.msg = "Cloning from radio" + self.status_fn(status) + self._finish() + return memmap.MemoryMap(''.join(image)) + + def _upload(self): + """Talk to a wouxun KG-UV8D Plus and do a upload""" + try: + self._identify() + self._do_upload(0, 32768, 64) + except errors.RadioError: + raise + except Exception, e: + raise errors.RadioError("Failed to communicate with radio: %s" % e) + return + + def _do_upload(self, start, end, blocksize): + ptr = start + for i in range(start, end, blocksize): + req = chr(i / 256) + chr(i % 256) + chunk = self.get_mmap()[ptr:ptr + blocksize] + self._write_record(CMD_WR, req + chunk) + LOG.debug(util.hexprint(req + chunk)) + cserr, ack = self._read_record() + LOG.debug(util.hexprint(ack)) + j = ord(ack[0]) * 256 + ord(ack[1]) + if cserr or j != ptr: + raise Exception("Radio did not ack block %i" % ptr) + ptr += blocksize + if self.status_fn: + status = chirp_common.Status() + status.cur = i + status.max = end + status.msg = "Cloning to radio" + self.status_fn(status) + self._finish() + + def get_features(self): + rf = chirp_common.RadioFeatures() + rf.has_settings = True + rf.has_ctone = True + rf.has_rx_dtcs = True + rf.has_cross = True + rf.has_tuning_step = False + rf.has_bank = False + rf.can_odd_split = True + rf.valid_skips = ["", "S"] + 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_name_length = 8 + rf.valid_duplexes = ["", "-", "+", "split", "off"] + rf.valid_bands = [(134000000, 175000000), # supports 2m + (400000000, 520000000)] # supports 70cm + rf.valid_characters = chirp_common.CHARSET_ASCII + rf.memory_bounds = (1, 999) # 999 memories + return rf + + @classmethod + def get_prompts(cls): + rp = chirp_common.RadioPrompts() + rp.experimental = ("This radio driver is currently under development. " + "There are no known issues with it, but you should " + "proceed with caution.") + return rp + + def get_raw_memory(self, number): + return repr(self._memobj.memory[number]) + + 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.txtone != 0xFFFF and (_mem.txtone & 0x2800) == 0x2800: + tcode, tpol = _get_dcs(_mem.txtone) + mem.dtcs = tcode + txmode = "DTCS" + elif _mem.txtone != 0xFFFF and _mem.txtone != 0x0: + mem.rtone = (_mem.txtone & 0x7fff) / 10.0 + txmode = "Tone" + else: + txmode = "" + + rpol = False + if _mem.rxtone != 0xFFFF and (_mem.rxtone & 0x2800) == 0x2800: + rcode, rpol = _get_dcs(_mem.rxtone) + mem.rx_dtcs = rcode + rxmode = "DTCS" + elif _mem.rxtone != 0xFFFF and _mem.rxtone != 0x0: + mem.ctone = (_mem.rxtone & 0x7fff) / 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.txtone, rxmode, _mem.rxtone)) + + def get_memory(self, number): + _mem = self._memobj.memory[number] + _nam = self._memobj.names[number] + + mem = chirp_common.Memory() + mem.number = number + _valid = self._memobj.valid[mem.number] + LOG.debug("%d %s", number, _valid == MEM_VALID) + if _valid != MEM_VALID: + mem.empty = True + return mem + else: + mem.empty = False + + mem.freq = int(_mem.rxfreq) * 10 + + if _mem.txfreq == 0xFFFFFFFF: + # TX freq not set + mem.duplex = "off" + mem.offset = 0 + elif int(_mem.rxfreq) == int(_mem.txfreq): + mem.duplex = "" + mem.offset = 0 + elif abs(int(_mem.rxfreq) * 10 - int(_mem.txfreq) * 10) > 70000000: + mem.duplex = "split" + mem.offset = int(_mem.txfreq) * 10 + else: + mem.duplex = int(_mem.rxfreq) > int(_mem.txfreq) and "-" or "+" + mem.offset = abs(int(_mem.rxfreq) - int(_mem.txfreq)) * 10 + + for char in _nam.name: + if char != 0: + mem.name += chr(char) + mem.name = mem.name.rstrip() + + self._get_tone(_mem, mem) + + mem.skip = "" if bool(_mem.scan_add) else "S" + + mem.power = self.POWER_LEVELS[_mem.power] + mem.mode = _mem.iswide and "FM" or "NFM" + 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 + rxtone = txtone = 0x0000 + + if mem.tmode == "Tone": + tx_mode = "Tone" + rx_mode = None + txtone = int(mem.rtone * 10) + 0x8000 + elif mem.tmode == "TSQL": + rx_mode = tx_mode = "Tone" + rxtone = txtone = int(mem.ctone * 10) + 0x8000 + elif mem.tmode == "DTCS": + tx_mode = rx_mode = "DTCS" + txtone = _set_dcs(mem.dtcs, mem.dtcs_polarity[0]) + rxtone = _set_dcs(mem.dtcs, mem.dtcs_polarity[1]) + elif mem.tmode == "Cross": + tx_mode, rx_mode = mem.cross_mode.split("->") + if tx_mode == "DTCS": + txtone = _set_dcs(mem.dtcs, mem.dtcs_polarity[0]) + elif tx_mode == "Tone": + txtone = int(mem.rtone * 10) + 0x8000 + if rx_mode == "DTCS": + rxtone = _set_dcs(mem.rx_dtcs, mem.dtcs_polarity[1]) + elif rx_mode == "Tone": + rxtone = int(mem.ctone * 10) + 0x8000 + + _mem.rxtone = rxtone + _mem.txtone = txtone + + LOG.debug("Set TX %s (%i) RX %s (%i)" % + (tx_mode, _mem.txtone, rx_mode, _mem.rxtone)) + + def set_memory(self, mem): + number = mem.number + + _mem = self._memobj.memory[number] + _nam = self._memobj.names[number] + + if mem.empty: + _mem.set_raw("\x00" * (_mem.size() / 8)) + self._memobj.valid[number] = 0 + self._memobj.names[number].set_raw("\x00" * (_nam.size() / 8)) + return + + _mem.rxfreq = int(mem.freq / 10) + if mem.duplex == "off": + _mem.txfreq = 0xFFFFFFFF + elif mem.duplex == "split": + _mem.txfreq = int(mem.offset / 10) + elif mem.duplex == "off": + for i in range(0, 4): + _mem.txfreq[i].set_raw("\xFF") + elif mem.duplex == "+": + _mem.txfreq = int(mem.freq / 10) + int(mem.offset / 10) + elif mem.duplex == "-": + _mem.txfreq = int(mem.freq / 10) - int(mem.offset / 10) + else: + _mem.txfreq = int(mem.freq / 10) + _mem.scan_add = int(mem.skip != "S") + _mem.iswide = int(mem.mode == "FM") + # set the tone + self._set_tone(mem, _mem) + # set the scrambler and compander to off by default + _mem.scrambler = 0 + _mem.compander = 0 + # set the power + if mem.power: + _mem.power = self.POWER_LEVELS.index(mem.power) + else: + _mem.power = True + # set to mute mode to QT (not QT+DTMF or QT*DTMF) by default + _mem.mute_mode = 0 + + for i in range(0, len(_nam.name)): + if i < len(mem.name) and mem.name[i]: + _nam.name[i] = ord(mem.name[i]) + else: + _nam.name[i] = 0x0 + self._memobj.valid[mem.number] = MEM_VALID + + def _get_settings(self): + _settings = self._memobj.settings + _vfoa = self._memobj.vfoa + _vfob = self._memobj.vfob + cfg_grp = RadioSettingGroup("cfg_grp", "Configuration") + vfoa_grp = RadioSettingGroup("vfoa_grp", "VFO A Settings") + vfob_grp = RadioSettingGroup("vfob_grp", "VFO B Settings") + key_grp = RadioSettingGroup("key_grp", "Key Settings") + lmt_grp = RadioSettingGroup("lmt_grp", "Frequency Limits") + uhf_lmt_grp = RadioSettingGroup("uhf_lmt_grp", "UHF") + vhf_lmt_grp = RadioSettingGroup("vhf_lmt_grp", "VHF") + oem_grp = RadioSettingGroup("oem_grp", "OEM Info") + + lmt_grp.append(uhf_lmt_grp); + lmt_grp.append(vhf_lmt_grp); + group = RadioSettings(cfg_grp, vfoa_grp, vfob_grp, + key_grp, lmt_grp, oem_grp) + + # + # Configuration Settings + # + rs = RadioSetting("channel_menu", "Menu available in channel mode", + RadioSettingValueBoolean(_settings.channel_menu)) + cfg_grp.append(rs) + rs = RadioSetting("ponmsg", "Poweron message", + RadioSettingValueList( + PONMSG_LIST, PONMSG_LIST[_settings.ponmsg])) + cfg_grp.append(rs) + rs = RadioSetting("voice", "Voice Guide", + RadioSettingValueBoolean(_settings.voice)) + cfg_grp.append(rs) + rs = RadioSetting("language", "Language", + RadioSettingValueList(LANGUAGE_LIST, + LANGUAGE_LIST[_settings. + language])) + cfg_grp.append(rs) + rs = RadioSetting("timeout", "Timeout Timer", + RadioSettingValueList( + TIMEOUT_LIST, TIMEOUT_LIST[_settings.timeout])) + cfg_grp.append(rs) + rs = RadioSetting("toalarm", "Timeout Alarm", + RadioSettingValueInteger(0, 10, _settings.toalarm)) + cfg_grp.append(rs) + rs = RadioSetting("roger_beep", "Roger Beep", + RadioSettingValueList(ROGER_LIST, + ROGER_LIST[_settings.roger_beep])) + cfg_grp.append(rs) + rs = RadioSetting("power_save", "Power save", + RadioSettingValueBoolean(_settings.power_save)) + cfg_grp.append(rs) + rs = RadioSetting("autolock", "Autolock", + RadioSettingValueBoolean(_settings.autolock)) + cfg_grp.append(rs) + rs = RadioSetting("keylock", "Keypad Lock", + RadioSettingValueBoolean(_settings.keylock)) + cfg_grp.append(rs) + rs = RadioSetting("beep", "Keypad Beep", + RadioSettingValueBoolean(_settings.beep)) + cfg_grp.append(rs) + rs = RadioSetting("stopwatch", "Stopwatch", + RadioSettingValueBoolean(_settings.stopwatch)) + cfg_grp.append(rs) + rs = RadioSetting("backlight", "Backlight", + RadioSettingValueList(BACKLIGHT_LIST, + BACKLIGHT_LIST[_settings. + backlight])) + cfg_grp.append(rs) + rs = RadioSetting("dtmf_st", "DTMF Sidetone", + RadioSettingValueList(DTMFST_LIST, + DTMFST_LIST[_settings. + dtmf_st])) + cfg_grp.append(rs) + rs = RadioSetting("ani_sw", "ANI-ID Switch", + RadioSettingValueBoolean(_settings.ani_sw)) + cfg_grp.append(rs) + rs = RadioSetting("ptt_id", "PTT-ID Delay", + RadioSettingValueList(PTTID_LIST, + PTTID_LIST[_settings.ptt_id])) + cfg_grp.append(rs) + rs = RadioSetting("ring_time", "Ring Time", + RadioSettingValueList(LIST_10, + LIST_10[_settings.ring_time])) + cfg_grp.append(rs) + rs = RadioSetting("scan_rev", "Scan Mode", + RadioSettingValueList(SCANMODE_LIST, + SCANMODE_LIST[_settings. + scan_rev])) + cfg_grp.append(rs) + rs = RadioSetting("vox", "VOX", + RadioSettingValueList(LIST_10, + LIST_10[_settings.vox])) + cfg_grp.append(rs) + rs = RadioSetting("prich_sw", "Priority Channel Switch", + RadioSettingValueBoolean(_settings.prich_sw)) + cfg_grp.append(rs) + rs = RadioSetting("pri_ch", "Priority Channel", + RadioSettingValueInteger(1, 999, _settings.pri_ch)) + cfg_grp.append(rs) + rs = RadioSetting("rpt_mode", "Radio Mode", + RadioSettingValueList(RPTMODE_LIST, + RPTMODE_LIST[_settings. + rpt_mode])) + cfg_grp.append(rs) + rs = RadioSetting("rpt_set", "Repeater Setting", + RadioSettingValueList(RPTSET_LIST, + RPTSET_LIST[_settings. + rpt_set])) + cfg_grp.append(rs) + rs = RadioSetting("rpt_spk", "Repeater Mode Speaker", + RadioSettingValueBoolean(_settings.rpt_spk)) + cfg_grp.append(rs) + rs = RadioSetting("rpt_ptt", "Repeater PTT", + RadioSettingValueBoolean(_settings.rpt_ptt)) + cfg_grp.append(rs) + rs = RadioSetting("dtmf_tx_time", "DTMF Tx Duration", + RadioSettingValueList(DTMF_TIMES, + DTMF_TIMES[_settings. + dtmf_tx_time])) + cfg_grp.append(rs) + rs = RadioSetting("dtmf_interval", "DTMF Interval", + RadioSettingValueList(DTMF_TIMES, + DTMF_TIMES[_settings. + dtmf_interval])) + cfg_grp.append(rs) + rs = RadioSetting("alert", "Alert Tone", + RadioSettingValueList(ALERTS_LIST, + ALERTS_LIST[_settings.alert])) + cfg_grp.append(rs) + rs = RadioSetting("rpt_tone", "Repeater Tone", + RadioSettingValueBoolean(_settings.rpt_tone)) + cfg_grp.append(rs) + rs = RadioSetting("rpt_hold", "Repeater Hold Time", + RadioSettingValueList(HOLD_TIMES, + HOLD_TIMES[_settings. + rpt_hold])) + cfg_grp.append(rs) + rs = RadioSetting("scan_det", "Scan DET", + RadioSettingValueBoolean(_settings.scan_det)) + cfg_grp.append(rs) + rs = RadioSetting("sc_qt", "SC-QT", + RadioSettingValueList(SCQT_LIST, + SCQT_LIST[_settings.sc_qt])) + cfg_grp.append(rs) + rs = RadioSetting("smuteset", "SubFreq Mute", + RadioSettingValueList(SMUTESET_LIST, + SMUTESET_LIST[_settings. + smuteset])) + cfg_grp.append(rs) + + # + # VFO A Settings + # + rs = RadioSetting("workmode_a", "VFO A Workmode", + RadioSettingValueList(WORKMODE_LIST, WORKMODE_LIST[_settings.workmode_a])) + vfoa_grp.append(rs) + rs = RadioSetting("work_cha", "VFO A Channel", + RadioSettingValueInteger(1, 999, _settings.work_cha)) + vfoa_grp.append(rs) + rs = RadioSetting("vfoa.rxfreq", "VFO A Rx Frequency", + RadioSettingValueInteger( + 134000000, 520000000, _vfoa.rxfreq * 10, 5000)) + vfoa_grp.append(rs) + rs = RadioSetting("vfoa.txoffset", "VFO A Tx Offset", + RadioSettingValueInteger( + 0, 520000000, _vfoa.txoffset * 10, 5000)) + vfoa_grp.append(rs) + # u16 rxtone; + # u16 txtone; + rs = RadioSetting("vfoa.power", "VFO A Power", + RadioSettingValueList( + POWER_LIST, POWER_LIST[_vfoa.power])) + vfoa_grp.append(rs) + # shift_dir:2 + rs = RadioSetting("vfoa.iswide", "VFO A NBFM", + RadioSettingValueList( + BANDWIDTH_LIST, BANDWIDTH_LIST[_vfoa.iswide])) + vfoa_grp.append(rs) + rs = RadioSetting("vfoa.mute_mode", "VFO A Mute", + RadioSettingValueList( + SPMUTE_LIST, SPMUTE_LIST[_vfoa.mute_mode])) + vfoa_grp.append(rs) + rs = RadioSetting("vfoa.step", "VFO A Step (kHz)", + RadioSettingValueList( + STEP_LIST, STEP_LIST[_vfoa.step])) + vfoa_grp.append(rs) + rs = RadioSetting("vfoa.squelch", "VFO A Squelch", + RadioSettingValueList( + LIST_10, LIST_10[_vfoa.squelch])) + vfoa_grp.append(rs) + rs = RadioSetting("bcl_a", "Busy Channel Lock-out A", + RadioSettingValueBoolean(_settings.bcl_a)) + vfoa_grp.append(rs) + + # + # VFO B Settings + # + rs = RadioSetting("workmode_b", "VFO B Workmode", + RadioSettingValueList(WORKMODE_LIST, WORKMODE_LIST[_settings.workmode_b])) + vfob_grp.append(rs) + rs = RadioSetting("work_chb", "VFO B Channel", + RadioSettingValueInteger(1, 999, _settings.work_chb)) + vfob_grp.append(rs) + rs = RadioSetting("vfob.rxfreq", "VFO B Rx Frequency", + RadioSettingValueInteger( + 134000000, 520000000, _vfob.rxfreq * 10, 5000)) + vfob_grp.append(rs) + rs = RadioSetting("vfob.txoffset", "VFO B Tx Offset", + RadioSettingValueInteger( + 0, 520000000, _vfob.txoffset * 10, 5000)) + vfob_grp.append(rs) + # u16 rxtone; + # u16 txtone; + rs = RadioSetting("vfob.power", "VFO B Power", + RadioSettingValueList( + POWER_LIST, POWER_LIST[_vfob.power])) + vfob_grp.append(rs) + # shift_dir:2 + rs = RadioSetting("vfob.iswide", "VFO B NBFM", + RadioSettingValueList( + BANDWIDTH_LIST, BANDWIDTH_LIST[_vfob.iswide])) + vfob_grp.append(rs) + rs = RadioSetting("vfob.mute_mode", "VFO B Mute", + RadioSettingValueList( + SPMUTE_LIST, SPMUTE_LIST[_vfob.mute_mode])) + vfob_grp.append(rs) + rs = RadioSetting("vfob.step", "VFO B Step (kHz)", + RadioSettingValueList( + STEP_LIST, STEP_LIST[_vfob.step])) + vfob_grp.append(rs) + rs = RadioSetting("vfob.squelch", "VFO B Squelch", + RadioSettingValueList( + LIST_10, LIST_10[_vfob.squelch])) + vfob_grp.append(rs) + rs = RadioSetting("bcl_b", "Busy Channel Lock-out B", + RadioSettingValueBoolean(_settings.bcl_b)) + vfob_grp.append(rs) + + # + # Key Settings + # + _msg = str(_settings.dispstr).split("\0")[0] + val = RadioSettingValueString(0, 15, _msg) + val.set_mutable(True) + rs = RadioSetting("dispstr", "Display Message", val) + key_grp.append(rs) + + dtmfchars = "0123456789" + _codeobj = _settings.ani_code + _code = "".join([dtmfchars[x] for x in _codeobj if int(x) < 0x0A]) + val = RadioSettingValueString(3, 6, _code, False) + val.set_charset(dtmfchars) + rs = RadioSetting("ani_code", "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_code = value + rs.set_apply_callback(apply_ani_id, _settings) + key_grp.append(rs) + + rs = RadioSetting("pf1_func", "PF1 Key function", + RadioSettingValueList( + PF1KEY_LIST, + PF1KEY_LIST[_settings.pf1_func])) + key_grp.append(rs) + rs = RadioSetting("pf3_func", "PF3 Key function", + RadioSettingValueList( + PF3KEY_LIST, + PF3KEY_LIST[_settings.pf3_func])) + key_grp.append(rs) + + # + # Limits settings + # + rs = RadioSetting("uhf_limits.rx_start", "UHF RX Lower Limit", + RadioSettingValueInteger( + 400000000, 520000000, + self._memobj.uhf_limits.rx_start * 10, 5000)) + uhf_lmt_grp.append(rs) + rs = RadioSetting("uhf_limits.rx_stop", "UHF RX Upper Limit", + RadioSettingValueInteger( + 400000000, 520000000, + self._memobj.uhf_limits.rx_stop * 10, 5000)) + uhf_lmt_grp.append(rs) + rs = RadioSetting("uhf_limits.tx_start", "UHF TX Lower Limit", + RadioSettingValueInteger( + 400000000, 520000000, + self._memobj.uhf_limits.tx_start * 10, 5000)) + uhf_lmt_grp.append(rs) + rs = RadioSetting("uhf_limits.tx_stop", "UHF TX Upper Limit", + RadioSettingValueInteger( + 400000000, 520000000, + self._memobj.uhf_limits.tx_stop * 10, 5000)) + uhf_lmt_grp.append(rs) + rs = RadioSetting("vhf_limits.rx_start", "VHF RX Lower Limit", + RadioSettingValueInteger( + 134000000, 174997500, + self._memobj.vhf_limits.rx_start * 10, 5000)) + vhf_lmt_grp.append(rs) + rs = RadioSetting("vhf_limits.rx_stop", "VHF RX Upper Limit", + RadioSettingValueInteger( + 134000000, 174997500, + self._memobj.vhf_limits.rx_stop * 10, 5000)) + vhf_lmt_grp.append(rs) + rs = RadioSetting("vhf_limits.tx_start", "VHF TX Lower Limit", + RadioSettingValueInteger( + 134000000, 174997500, + self._memobj.vhf_limits.tx_start * 10, 5000)) + vhf_lmt_grp.append(rs) + rs = RadioSetting("vhf_limits.tx_stop", "VHF TX Upper Limit", + RadioSettingValueInteger( + 134000000, 174997500, + self._memobj.vhf_limits.tx_stop * 10, 5000)) + vhf_lmt_grp.append(rs) + + # + # OEM info + # + def _decode(lst): + _str = ''.join([chr(c) for c in lst + if chr(c) in chirp_common.CHARSET_ASCII]) + return _str + + def do_nothing(setting, obj): + return + + _str = _decode(self._memobj.oem_info.model) + val = RadioSettingValueString(0, 15, _str) + val.set_mutable(False) + rs = RadioSetting("oem_info.model", "Model", val) + rs.set_apply_callback(do_nothing, _settings) + oem_grp.append(rs) + _str = _decode(self._memobj.oem_info.oem1) + val = RadioSettingValueString(0, 15, _str) + val.set_mutable(False) + rs = RadioSetting("oem_info.oem1", "OEM String 1", val) + rs.set_apply_callback(do_nothing, _settings) + oem_grp.append(rs) + _str = _decode(self._memobj.oem_info.oem2) + val = RadioSettingValueString(0, 15, _str) + val.set_mutable(False) + rs = RadioSetting("oem_info.oem2", "OEM String 2", val) + rs.set_apply_callback(do_nothing, _settings) + oem_grp.append(rs) + _str = _decode(self._memobj.oem_info.version) + val = RadioSettingValueString(0, 15, _str) + val.set_mutable(False) + rs = RadioSetting("oem_info.version", "Software Version", val) + rs.set_apply_callback(do_nothing, _settings) + oem_grp.append(rs) + _str = _decode(self._memobj.oem_info.date) + val = RadioSettingValueString(0, 15, _str) + val.set_mutable(False) + rs = RadioSetting("oem_info.date", "OEM Date", val) + rs.set_apply_callback(do_nothing, _settings) + oem_grp.append(rs) + + return group + + def get_settings(self): + try: + return self._get_settings() + except: + import traceback + LOG.error("Failed to parse settings: %s", traceback.format_exc()) + return None + + def set_settings(self, settings): + for element in settings: + if not isinstance(element, RadioSetting): + 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)) + if self._is_freq(element): + setattr(obj, setting, int(element.value)/10) + else: + setattr(obj, setting, element.value) + except Exception, e: + LOG.debug(element.get_name()) + raise + + def _is_freq(self, element): + return "rxfreq" in element.get_name() or "txoffset" in element.get_name() or "rx_start" in element.get_name() or "rx_stop" in element.get_name() or "tx_start" in element.get_name() or "tx_stop" in element.get_name() \ No newline at end of file diff -r 0e0470d3e892 -r e870a7f807f5 tests/images/Wouxun_KG-UV8D_Plus.img Binary file tests/images/Wouxun_KG-UV8D_Plus.img has changed
Fixed all problems reported by Dan after review. Checked with radio to address some of the issues. One of the issue is still to do (reading memory), but this might take a lot of time to figure out how it can work more efficient. This issue is not causing any problems and works fine.
Kind regards Krystian Struzik
Hi Guys
Can you please let me know whether I can commit my changes to chirp devel ? Because for now I just generate a patch only, so the changes are currently uncommited. So when I commit my changes two new files will be added: image and driver.
Kind regards, Krystian Struzik
Can you please let me know whether I can commit my changes to chirp devel ? Because for now I just generate a patch only, so the changes are currently uncommited. So when I commit my changes two new files will be added: image and driver.
Mercurial is a distributed system which means committing locally doesn’t affect anything other than your tree. I applied your patch last night and pushed to the main repo, so it went out in today’s build for everyone. You can now delete your local patch (hg qdel). I forgot to do the image though, so I’ll get that in later today.
Thanks for helping!
—Dan
Can you please let me know whether I can commit my changes to chirp devel ? Because for now I just generate a patch only, so the changes are currently uncommited. So when I commit my changes two new files will be added: image and driver.
Mercurial is a distributed system which means committing locally doesn’t affect anything other than your tree. I applied your patch last night and pushed to the main repo, so it went out in today’s build for everyone. You can now delete your local patch (hg qdel). I forgot to do the image though, so I’ll get that in later today.
Thanks for helping!
—Dan
Yes, I thought so :), but wanted to have sure. Anyway yes image wasn't attached, so that's why asked.
Many thanks for your help Dan.
Kind regards, Krystian Struzik
participants (3)
-
Dan Smith
-
Krystian Struzik
-
KS