[chirp_devel] [PATCH] [New Model] Add Support for Retevis RT23 Radio
# HG changeset patch # User Jim Unroe rock.unroe@gmail.com # Date 1497404221 14400 # Node ID 5af61447fc034ef20d7bf2e1799bd092b641c51e # Parent fa2ef4dedc56b786c2ff94d70a5a9b2c94785973 [New Model] Add Support for Retevis RT23 Radio
This patch adds basic support for the Retevis RT23 radio.
#4619
diff -r fa2ef4dedc56 -r 5af61447fc03 chirp/drivers/retevis_rt23.py --- /dev/null Thu Jan 01 00:00:00 1970 +0000 +++ b/chirp/drivers/retevis_rt23.py Tue Jun 13 21:37:01 2017 -0400 @@ -0,0 +1,872 @@ +# Copyright 2017 Jim Unroe rock.unroe@gmail.com +# +# This program is free software: you can redistribute it and/or modify +# it under the terms of the GNU General Public License as published by +# the Free Software Foundation, either version 2 of the License, or +# (at your option) any later version. +# +# This program is distributed in the hope that it will be useful, +# but WITHOUT ANY WARRANTY; without even the implied warranty of +# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the +# GNU General Public License for more details. +# +# You should have received a copy of the GNU General Public License +# along with this program. If not, see http://www.gnu.org/licenses/. + +import time +import os +import struct +import re +import logging + +from chirp import chirp_common, directory, memmap +from chirp import bitwise, errors, util +from chirp.settings import RadioSetting, RadioSettingGroup, \ + RadioSettingValueInteger, RadioSettingValueList, \ + RadioSettingValueBoolean, RadioSettingValueString, \ + RadioSettings + +LOG = logging.getLogger(__name__) + +MEM_FORMAT = """ +struct memory { + lbcd rxfreq[4]; + lbcd txfreq[4]; + lbcd rxtone[2]; + lbcd txtone[2]; + u8 unknown1; + u8 pttid:2, // PTT-ID + unknown2:1, + signaling:1, // Signaling(ANI) + unknown3:1, + bcl:1, // Busy Channel Lockout + unknown4:2; + u8 unknown5:3, + highpower:1, // Power Level + isnarrow:1, // Bandwidth + scan:1, // Scan Add + unknown6:2; + u8 unknown7; +}; + +#seekto 0x0010; +struct memory channels[128]; + +#seekto 0x0810; +struct memory vfo_a; +struct memory vfo_b; + +#seekto 0x0830; +struct { + u8 unknown_0830_1:4, + color:2, // Background Color + dst:1, // DTMF Side Tone + txsel:1; // Priority TX Channel Select + u8 scans:2, // Scan Mode + unknown_0831:1, + autolk:1, // Auto Key Lock + save:1, // Battery Save + beep:1, // Key Beep + voice:2; // Voice Prompt + u8 vfomr_fm:1, // FM Radio Display Mode + led:2, // Background Light + unknown_0832_2:1, + dw:1, // FM Radio Dual Watch + name:1, // Display Names + vfomr_a:2; // Display Mode A + u8 opnset:2, // Power On Message + unknown_0833_1:3, + dwait:1, // Dual Standby + vfomr_b:2; // Display Mode B + u8 mrcha; // mr a ch num + u8 mrchb; // mr b ch num + u8 fmch; // fm radio ch num + u8 unknown_0837_1:1, + ste:1, // Squelch Tail Eliminate + roger:1, // Roger Beep + unknown_0837_2:1, + vox:4; // VOX + u8 step:4, // Step + unknown_0838_1:4; + u8 squelch; // Squelch + u8 tot; // Time Out Timer + u8 rptmod:1, // Repeater Mode + volmod:2, // Volume Mode + rptptt:1, // Repeater PTT Switch + rptspk:1, // Repeater Speaker + relay:3; // Cross Band Repeater Enable + u8 unknown_083C:4, // 0x083C + rptrl:4; // Repeater TX Delay + u8 pf1:4, // Function Key 1 + pf2:4; // Function Key 2 + u8 vot; // VOX Delay Time +} settings; + +#seekto 0x0848; +struct { + char line1[7]; +} poweron_msg; + +struct limit { + bbcd lower[2]; + bbcd upper[2]; +}; + +#seekto 0x0850; +struct { + struct limit vhf; + struct limit uhf; +} limits; + +#seekto 0x08D0; +struct { + char name[7]; + u8 unknown2[1]; +} names[128]; + +#seekto 0x0D20; +u8 usedflags[16]; +u8 scanflags[16]; + +#seekto 0x0FA0; +struct { + u8 unknown_0FA0_1:4, + dispab:1, // select a/b + unknown_0FA0_2:3; +} settings2; +""" + +CMD_ACK = "\x06" +BLOCK_SIZE = 0x10 + +RT23_POWER_LEVELS = [chirp_common.PowerLevel("Low", watts=1.00), + chirp_common.PowerLevel("High", watts=2.50)] + + +RT23_DTCS = sorted(chirp_common.DTCS_CODES + + [17, 50, 55, 135, 217, 254, 305, 645, 765]) + +RT23_CHARSET = chirp_common.CHARSET_UPPER_NUMERIC + \ + ":;<=>?@ !"#$%&'()*+,-./" + +LIST_COLOR = ["Blue", "Orange", "Purple"] +LIST_LED = ["Off", "On", "Auto"] +LIST_OPNSET = ["Full", "Voltage", "Message"] +LIST_PFKEY = [ + "Radio", + "Sub-channel Sent", + "Scan", + "Alarm", + "DTMF", + "Squelch Off Momentarily", + "Battery Power Indicator", + "Tone 1750", + "Tone 2100", + "Tone 1000", + "Tone 1450"] +LIST_PTTID = ["Off", "BOT", "EOT", "Both"] +LIST_RPTMOD = ["Single", "Double"] +LIST_RPTRL = ["0.5S", "1.0S", "1.5S", "2.0S", "2.5S", "3.0S", "3.5S", "4.0S", + "4.5S"] +LIST_SCANS = ["Time Operated", "Carrier Operated", "Search"] +LIST_SIGNALING = ["No", "DTMF"] +LIST_TOT = ["OFF"] + ["%s seconds" % x for x in range(30, 300, 30)] +LIST_TXSEL = ["Edit", "Busy"] +LIST_STEP = ["2.50K", "5.00K", "6.25K", "10.00K", "12,50K", "20.00K", "25.00K", + "50.00K"] +LIST_VFOMR = ["VFO", "MR(Frequency)", "MR(Channel #/Name)"] +LIST_VFOMRFM = ["VFO", "Channel"] +LIST_VOICE = ["Off", "Chinese", "English"] +LIST_VOLMOD = ["Off", "Sub", "Main"] +LIST_VOT = ["0.5S", "1.0S", "1.5S", "2.0S", "3.0S"] +LIST_VOX = ["OFF"] + ["%s" % x for x in range(1, 6)] + + +def _rt23_enter_programming_mode(radio): + serial = radio.pipe + + magic = "PROIUAM" + exito = False + for i in range(0, 5): + for j in range(0, len(magic)): + time.sleep(0.005) + serial.write(magic[j]) + ack = serial.read(1) + + try: + if ack == CMD_ACK: + exito = True + break + except: + LOG.debug("Attempt #%s, failed, trying again" % i) + pass + + # check if we had EXITO + if exito is False: + msg = "The radio did not accept program mode after five tries.\n" + msg += "Check you interface cable and power cycle your radio." + raise errors.RadioError(msg) + + try: + serial.write("\x02") + ident = serial.read(8) + except: + raise errors.RadioError("Error communicating with radio") + + if not ident.startswith("P31183"): + LOG.debug(util.hexprint(ident)) + raise errors.RadioError("Radio returned unknown identification string") + + try: + serial.write(CMD_ACK) + ack = serial.read(1) + except: + raise errors.RadioError("Error communicating with radio") + + if ack != CMD_ACK: + raise errors.RadioError("Radio refused to enter programming mode") + + +def _rt23_exit_programming_mode(radio): + serial = radio.pipe + try: + serial.write("E") + except: + raise errors.RadioError("Radio refused to exit programming mode") + + +def _rt23_read_block(radio, block_addr, block_size): + serial = radio.pipe + + cmd = struct.pack(">cHb", 'R', block_addr, BLOCK_SIZE) + expectedresponse = "W" + cmd[1:] + LOG.debug("Reading block %04x..." % (block_addr)) + + try: + serial.write(cmd) + response = serial.read(4 + BLOCK_SIZE + 1) + if response[:4] != expectedresponse: + raise Exception("Error reading block %04x." % (block_addr)) + + chunk = response[4:] + + cs = 0 + for byte in chunk[:-1]: + cs += ord(byte) + if ord(chunk[-1]) != (cs & 0xFF): + raise Exception("Block failed checksum!") + + block_data = chunk[:-1] + except: + raise errors.RadioError("Failed to read block at %04x" % block_addr) + + return block_data + + +def _rt23_write_block(radio, block_addr, block_size): + serial = radio.pipe + + cmd = struct.pack(">cHb", 'W', block_addr, BLOCK_SIZE) + data = radio.get_mmap()[block_addr:block_addr + BLOCK_SIZE] + cs = 0 + for byte in data: + cs += ord(byte) + data += chr(cs & 0xFF) + + LOG.debug("Writing Data:") + LOG.debug(util.hexprint(cmd + data)) + + try: + for j in range(0, len(cmd)): + time.sleep(0.002) + serial.write(cmd[j]) + for j in range(0, len(data)): + time.sleep(0.002) + serial.write(data[j]) + if serial.read(1) != CMD_ACK: + raise Exception("No ACK") + except: + raise errors.RadioError("Failed to send block " + "to radio at %04x" % block_addr) + + +def do_download(radio): + LOG.debug("download") + _rt23_enter_programming_mode(radio) + + data = "" + + status = chirp_common.Status() + status.msg = "Cloning from radio" + + status.cur = 0 + status.max = radio._memsize + + for addr in range(0, radio._memsize, BLOCK_SIZE): + status.cur = addr + BLOCK_SIZE + radio.status_fn(status) + + block = _rt23_read_block(radio, addr, BLOCK_SIZE) + if addr == 0 and block.startswith("\xFF" * 6): + block = "P31183" + "\xFF" * 10 + data += block + + LOG.debug("Address: %04x" % addr) + LOG.debug(util.hexprint(block)) + + _rt23_exit_programming_mode(radio) + + return memmap.MemoryMap(data) + + +def do_upload(radio): + status = chirp_common.Status() + status.msg = "Uploading to radio" + + _rt23_enter_programming_mode(radio) + + status.cur = 0 + status.max = radio._memsize + + for start_addr, end_addr in radio._ranges: + for addr in range(start_addr, end_addr, BLOCK_SIZE): + status.cur = addr + BLOCK_SIZE + radio.status_fn(status) + _rt23_write_block(radio, addr, BLOCK_SIZE) + + +def model_match(cls, data): + """Match the opened/downloaded image to the correct version""" + + if len(data) == 0x1000: + rid = data[0x0000:0x0006] + return rid == "P31183" + else: + return False + + +def _split(rf, f1, f2): + """Returns False if the two freqs are in the same band (no split) + or True otherwise""" + + # determine if the two freqs are in the same band + for low, high in rf.valid_bands: + if f1 >= low and f1 <= high and \ + f2 >= low and f2 <= high: + # if the two freqs are on the same Band this is not a split + return False + + # if you get here is because the freq pairs are split + return True + + +@directory.register +class RT23Radio(chirp_common.CloneModeRadio): + """RETEVIS RT23""" + VENDOR = "Retevis" + MODEL = "RT23" + BAUD_RATE = 9600 + + _ranges = [ + (0x0000, 0x0EC0), + ] + _memsize = 0x1000 + + def get_features(self): + rf = chirp_common.RadioFeatures() + rf.has_settings = True + rf.has_bank = False + rf.has_ctone = True + rf.has_cross = True + rf.has_rx_dtcs = True + rf.has_tuning_step = False + rf.can_odd_split = True + rf.valid_name_length = 7 + rf.valid_characters = RT23_CHARSET + rf.has_name = True + rf.valid_skips = ["", "S"] + rf.valid_tmodes = ["", "Tone", "TSQL", "DTCS", "Cross"] + rf.valid_cross_modes = ["Tone->Tone", "Tone->DTCS", "DTCS->Tone", + "->Tone", "->DTCS", "DTCS->", "DTCS->DTCS"] + rf.valid_power_levels = RT23_POWER_LEVELS + rf.valid_duplexes = ["", "-", "+", "split", "off"] + rf.valid_modes = ["FM", "NFM"] # 25 KHz, 12.5 KHz. + rf.memory_bounds = (1, 128) + rf.valid_bands = [ + (136000000, 174000000), + (400000000, 480000000)] + + return rf + + def process_mmap(self): + self._memobj = bitwise.parse(MEM_FORMAT, self._mmap) + + def sync_in(self): + """Download from radio""" + try: + data = do_download(self) + except errors.RadioError: + # Pass through any real errors we raise + raise + except: + # If anything unexpected happens, make sure we raise + # a RadioError and log the problem + LOG.exception('Unexpected error during download') + raise errors.RadioError('Unexpected error communicating ' + 'with the radio') + self._mmap = data + self.process_mmap() + + def sync_out(self): + """Upload to radio""" + try: + do_upload(self) + except: + # If anything unexpected happens, make sure we raise + # a RadioError and log the problem + LOG.exception('Unexpected error during upload') + raise errors.RadioError('Unexpected error communicating ' + 'with the radio') + + def get_raw_memory(self, number): + return repr(self._memobj.memory[number - 1]) + + def decode_tone(self, val): + """Parse the tone data to decode from mem, it returns: + Mode (''|DTCS|Tone), Value (None|###), Polarity (None,N,R)""" + if val.get_raw() == "\xFF\xFF": + return '', None, None + + val = int(val) + if val >= 12000: + a = val - 12000 + return 'DTCS', a, 'R' + elif val >= 8000: + a = val - 8000 + return 'DTCS', a, 'N' + else: + a = val / 10.0 + return 'Tone', a, None + + def encode_tone(self, memval, mode, value, pol): + """Parse the tone data to encode from UI to mem""" + if mode == '': + memval[0].set_raw(0xFF) + memval[1].set_raw(0xFF) + elif mode == 'Tone': + memval.set_value(int(value * 10)) + elif mode == 'DTCS': + flag = 0x80 if pol == 'N' else 0xC0 + memval.set_value(value) + memval[1].set_bits(flag) + else: + raise Exception("Internal error: invalid mode `%s'" % mode) + + def get_memory(self, number): + mem = chirp_common.Memory() + _mem = self._memobj.channels[number-1] + _nam = self._memobj.names[number - 1] + mem.number = number + bitpos = (1 << ((number - 1) % 8)) + bytepos = ((number - 1) / 8) + _scn = self._memobj.scanflags[bytepos] + _usd = self._memobj.usedflags[bytepos] + isused = bitpos & int(_usd) + isscan = bitpos & int(_scn) + + if not isused: + mem.empty = True + return mem + + mem.freq = int(_mem.rxfreq) * 10 + + # We'll consider any blank (i.e. 0MHz frequency) to be empty + if mem.freq == 0: + mem.empty = True + return mem + + if _mem.rxfreq.get_raw() == "\xFF\xFF\xFF\xFF": + mem.empty = True + return mem + + if _mem.get_raw() == ("\xFF" * 16): + LOG.debug("Initializing empty memory") + _mem.set_raw("\x00" * 16) + + # Freq and offset + mem.freq = int(_mem.rxfreq) * 10 + # tx freq can be blank + if _mem.get_raw()[4] == "\xFF": + # TX freq not set + mem.offset = 0 + mem.duplex = "off" + else: + # TX freq set + offset = (int(_mem.txfreq) * 10) - mem.freq + if offset != 0: + if _split(self.get_features(), mem.freq, int(_mem.txfreq) * 10): + mem.duplex = "split" + mem.offset = int(_mem.txfreq) * 10 + elif offset < 0: + mem.offset = abs(offset) + mem.duplex = "-" + elif offset > 0: + mem.offset = offset + mem.duplex = "+" + else: + mem.offset = 0 + + for char in _nam.name: + if str(char) == "\xFF": + char = " " + mem.name += str(char) + mem.name = mem.name.rstrip() + + mem.mode = _mem.isnarrow and "NFM" or "FM" + + rxtone = txtone = None + txtone = self.decode_tone(_mem.txtone) + rxtone = self.decode_tone(_mem.rxtone) + chirp_common.split_tone_decode(mem, txtone, rxtone) + + mem.power = RT23_POWER_LEVELS[_mem.highpower] + + if not isscan: + mem.skip = "S" + + mem.extra = RadioSettingGroup("Extra", "extra") + + rs = RadioSetting("bcl", "BCL", + RadioSettingValueBoolean(_mem.bcl)) + mem.extra.append(rs) + + rs = RadioSetting("pttid", "PTT ID", + RadioSettingValueList( + LIST_PTTID, LIST_PTTID[_mem.pttid])) + mem.extra.append(rs) + + rs = RadioSetting("signaling", "Optional Signaling", + RadioSettingValueList(LIST_SIGNALING, + LIST_SIGNALING[_mem.signaling])) + mem.extra.append(rs) + + return mem + + def set_memory(self, mem): + LOG.debug("Setting %i(%s)" % (mem.number, mem.extd_number)) + _mem = self._memobj.channels[mem.number - 1] + _nam = self._memobj.names[mem.number - 1] + bitpos = (1 << ((mem.number - 1) % 8)) + bytepos = ((mem.number - 1) / 8) + _scn = self._memobj.scanflags[bytepos] + _usd = self._memobj.usedflags[bytepos] + + if mem.empty: + _mem.set_raw("\xFF" * 16) + _nam.name = ("\xFF" * 7) + _usd &= ~bitpos + _scn &= ~bitpos + return + else: + _usd |= bitpos + + if _mem.get_raw() == ("\xFF" * 16): + LOG.debug("Initializing empty memory") + _mem.set_raw("\x00" * 16) + _scn |= bitpos + + _mem.rxfreq = mem.freq / 10 + + if mem.duplex == "off": + for i in range(0, 4): + _mem.txfreq[i].set_raw("\xFF") + elif mem.duplex == "split": + _mem.txfreq = mem.offset / 10 + elif mem.duplex == "+": + _mem.txfreq = (mem.freq + mem.offset) / 10 + elif mem.duplex == "-": + _mem.txfreq = (mem.freq - mem.offset) / 10 + else: + _mem.txfreq = mem.freq / 10 + + _namelength = self.get_features().valid_name_length + for i in range(_namelength): + try: + _nam.name[i] = mem.name[i] + except IndexError: + _nam.name[i] = "\xFF" + + _mem.scan = mem.skip != "S" + if mem.skip == "S": + _scn &= ~bitpos + else: + _scn |= bitpos + _mem.isnarrow = mem.mode == "NFM" + + ((txmode, txtone, txpol), (rxmode, rxtone, rxpol)) = \ + chirp_common.split_tone_encode(mem) + self.encode_tone(_mem.txtone, txmode, txtone, txpol) + self.encode_tone(_mem.rxtone, rxmode, rxtone, rxpol) + + _mem.highpower = mem.power == RT23_POWER_LEVELS[1] + + for setting in mem.extra: + setattr(_mem, setting.get_name(), setting.value) + + def get_settings(self): + _settings = self._memobj.settings + _mem = self._memobj + basic = RadioSettingGroup("basic", "Basic Settings") + advanced = RadioSettingGroup("advanced", "Advanced Settings") + other = RadioSettingGroup("other", "Other Settings") + workmode = RadioSettingGroup("workmode", "Workmode Settings") + fmradio = RadioSettingGroup("fmradio", "FM Radio Settings") + top = RadioSettings(basic, advanced, other, workmode, fmradio) + + save = RadioSetting("save", "Battery Saver", + RadioSettingValueBoolean(_settings.save)) + basic.append(save) + + vox = RadioSetting("vox", "VOX Gain", + RadioSettingValueList( + LIST_VOX, LIST_VOX[_settings.vox])) + basic.append(vox) + + squelch = RadioSetting("squelch", "Squelch Level", + RadioSettingValueInteger( + 0, 9, _settings.squelch)) + basic.append(squelch) + + relay = RadioSetting("relay", "Repeater", + RadioSettingValueBoolean(_settings.relay)) + basic.append(relay) + + tot = RadioSetting("tot", "Time-out timer", RadioSettingValueList( + LIST_TOT, LIST_TOT[_settings.tot])) + basic.append(tot) + + beep = RadioSetting("beep", "Key Beep", + RadioSettingValueBoolean(_settings.beep)) + basic.append(beep) + + color = RadioSetting("color", "Background Color", RadioSettingValueList( + LIST_COLOR, LIST_COLOR[_settings.color - 1])) + basic.append(color) + + vot = RadioSetting("vot", "VOX Delay Time", RadioSettingValueList( + LIST_VOT, LIST_VOT[_settings.vot])) + basic.append(vot) + + dwait = RadioSetting("dwait", "Dual Standby", + RadioSettingValueBoolean(_settings.dwait)) + basic.append(dwait) + + led = RadioSetting("led", "Background Light", RadioSettingValueList( + LIST_LED, LIST_LED[_settings.led])) + basic.append(led) + + voice = RadioSetting("voice", "Voice Prompt", RadioSettingValueList( + LIST_VOICE, LIST_VOICE[_settings.voice])) + basic.append(voice) + + roger = RadioSetting("roger", "Roger Beep", + RadioSettingValueBoolean(_settings.roger)) + basic.append(roger) + + autolk = RadioSetting("autolk", "Auto Key Lock", + RadioSettingValueBoolean(_settings.autolk)) + basic.append(autolk) + + opnset = RadioSetting("opnset", "Open Mode Set", + RadioSettingValueList( + LIST_OPNSET, LIST_OPNSET[_settings.opnset])) + basic.append(opnset) + + def _filter(name): + filtered = "" + for char in str(name): + if char in chirp_common.CHARSET_ASCII: + filtered += char + else: + filtered += " " + return filtered + + _msg = self._memobj.poweron_msg + ponmsg = RadioSetting("poweron_msg.line1", "Power-On Message", + RadioSettingValueString( + 0, 7, _filter(_msg.line1))) + basic.append(ponmsg) + + + scans = RadioSetting("scans", "Scan Mode", RadioSettingValueList( + LIST_SCANS, LIST_SCANS[_settings.scans])) + basic.append(scans) + + dw = RadioSetting("dw", "FM Radio Dual Watch", + RadioSettingValueBoolean(_settings.dw)) + basic.append(dw) + + name = RadioSetting("name", "Display Names", + RadioSettingValueBoolean(_settings.name)) + basic.append(name) + + rptrl = RadioSetting("rptrl", "Repeater TX Delay", + RadioSettingValueList(LIST_RPTRL, LIST_RPTRL[ + _settings.rptrl])) + basic.append(rptrl) + + rptspk = RadioSetting("rptspk", "Repeater Speaker", + RadioSettingValueBoolean(_settings.rptspk)) + basic.append(rptspk) + + rptptt = RadioSetting("rptptt", "Repeater PTT Switch", + RadioSettingValueBoolean(_settings.rptptt)) + basic.append(rptptt) + + rptmod = RadioSetting("rptmod", "Repeater Mode", + RadioSettingValueList( + LIST_RPTMOD, LIST_RPTMOD[_settings.rptmod])) + basic.append(rptmod) + + volmod = RadioSetting("volmod", "Volume Mode", + RadioSettingValueList( + LIST_VOLMOD, LIST_VOLMOD[_settings.volmod])) + basic.append(volmod) + + dst = RadioSetting("dst", "DTMF Side Tone", + RadioSettingValueBoolean(_settings.dst)) + basic.append(dst) + + txsel = RadioSetting("txsel", "Priority TX Channel", + RadioSettingValueList( + LIST_TXSEL, LIST_TXSEL[_settings.txsel])) + basic.append(txsel) + + ste = RadioSetting("ste", "Squelch Tail Eliminate", + RadioSettingValueBoolean(_settings.ste)) + basic.append(ste) + + #advanced + if _settings.pf1 > 0x0A: + val = 0x00 + else: + val = _settings.pf1 + pf1 = RadioSetting("pf1", "PF1 Key", + RadioSettingValueList( + LIST_PFKEY, LIST_PFKEY[val])) + advanced.append(pf1) + + if _settings.pf2 > 0x0A: + val = 0x00 + else: + val = _settings.pf2 + pf2 = RadioSetting("pf2", "PF2 Key", + RadioSettingValueList( + LIST_PFKEY, LIST_PFKEY[val])) + advanced.append(pf2) + + # other + _limit = str(int(_mem.limits.vhf.lower) / 10) + val = RadioSettingValueString(0, 3, _limit) + val.set_mutable(False) + rs = RadioSetting("limits.vhf.lower", "VHF low", val) + other.append(rs) + + _limit = str(int(_mem.limits.vhf.upper) / 10) + val = RadioSettingValueString(0, 3, _limit) + val.set_mutable(False) + rs = RadioSetting("limits.vhf.upper", "VHF high", val) + other.append(rs) + + _limit = str(int(_mem.limits.uhf.lower) / 10) + val = RadioSettingValueString(0, 3, _limit) + val.set_mutable(False) + rs = RadioSetting("limits.uhf.lower", "UHF low", val) + other.append(rs) + + _limit = str(int(_mem.limits.uhf.upper) / 10) + val = RadioSettingValueString(0, 3, _limit) + val.set_mutable(False) + rs = RadioSetting("limits.uhf.upper", "UHF high", val) + other.append(rs) + + #work mode + vfomr_a = RadioSetting("vfomr_a", "Display Mode A", + RadioSettingValueList( + LIST_VFOMR, LIST_VFOMR[_settings.vfomr_a])) + workmode.append(vfomr_a) + + vfomr_b = RadioSetting("vfomr_b", "Display Mode B", + RadioSettingValueList( + LIST_VFOMR, LIST_VFOMR[_settings.vfomr_b])) + workmode.append(vfomr_b) + + mrcha = RadioSetting("mrcha", "Channel # A", + RadioSettingValueInteger( + 1, 128, _settings.mrcha)) + workmode.append(mrcha) + + mrchb = RadioSetting("mrchb", "Channel # B", + RadioSettingValueInteger( + 1, 128, _settings.mrchb)) + workmode.append(mrchb) + + #fm radio + vfomr_fm = RadioSetting("vfomr_fm", "FM Radio Display Mode", + RadioSettingValueList( + LIST_VFOMRFM, LIST_VFOMRFM[ + _settings.vfomr_fm])) + fmradio.append(vfomr_fm) + + fmch = RadioSetting("fmch", "FM Radio Channel #", + RadioSettingValueInteger( + 1, 25, _settings.fmch)) + fmradio.append(fmch) + + return top + + 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() + elif setting == "color": + setattr(obj, setting, int(element.value) + 1) + elif element.value.get_mutable(): + LOG.debug("Setting %s = %s" % (setting, element.value)) + setattr(obj, setting, element.value) + except Exception, e: + LOG.debug(element.get_name()) + raise + + @classmethod + def match_model(cls, filedata, filename): + match_size = False + match_model = False + + # testing the file data size + if len(filedata) in [0x1000, ]: + match_size = True + + # testing the model fingerprint + match_model = model_match(cls, filedata) + + if match_size and match_model: + return True + else: + return False
Attached is a test image to go along with this driver.
Jim KC9HI
On Tue, Jun 13, 2017 at 9:38 PM, Jim Unroe rock.unroe@gmail.com wrote:
# HG changeset patch # User Jim Unroe rock.unroe@gmail.com # Date 1497404221 14400 # Node ID 5af61447fc034ef20d7bf2e1799bd092b641c51e # Parent fa2ef4dedc56b786c2ff94d70a5a9b2c94785973 [New Model] Add Support for Retevis RT23 Radio
This patch adds basic support for the Retevis RT23 radio.
#4619
diff -r fa2ef4dedc56 -r 5af61447fc03 chirp/drivers/retevis_rt23.py --- /dev/null Thu Jan 01 00:00:00 1970 +0000 +++ b/chirp/drivers/retevis_rt23.py Tue Jun 13 21:37:01 2017 -0400 @@ -0,0 +1,872 @@ +# Copyright 2017 Jim Unroe rock.unroe@gmail.com +# +# This program is free software: you can redistribute it and/or modify +# it under the terms of the GNU General Public License as published by +# the Free Software Foundation, either version 2 of the License, or +# (at your option) any later version. +# +# This program is distributed in the hope that it will be useful, +# but WITHOUT ANY WARRANTY; without even the implied warranty of +# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the +# GNU General Public License for more details. +# +# You should have received a copy of the GNU General Public License +# along with this program. If not, see http://www.gnu.org/licenses/.
+import time +import os +import struct +import re +import logging
+from chirp import chirp_common, directory, memmap +from chirp import bitwise, errors, util +from chirp.settings import RadioSetting, RadioSettingGroup, \
- RadioSettingValueInteger, RadioSettingValueList, \
- RadioSettingValueBoolean, RadioSettingValueString, \
- RadioSettings
+LOG = logging.getLogger(__name__)
+MEM_FORMAT = """ +struct memory {
- lbcd rxfreq[4];
- lbcd txfreq[4];
- lbcd rxtone[2];
- lbcd txtone[2];
- u8 unknown1;
- u8 pttid:2, // PTT-ID
unknown2:1,
signaling:1, // Signaling(ANI)
unknown3:1,
bcl:1, // Busy Channel Lockout
unknown4:2;
- u8 unknown5:3,
highpower:1, // Power Level
isnarrow:1, // Bandwidth
scan:1, // Scan Add
unknown6:2;
- u8 unknown7;
+};
+#seekto 0x0010; +struct memory channels[128];
+#seekto 0x0810; +struct memory vfo_a; +struct memory vfo_b;
+#seekto 0x0830; +struct {
- u8 unknown_0830_1:4,
color:2, // Background Color
dst:1, // DTMF Side Tone
txsel:1; // Priority TX Channel Select
- u8 scans:2, // Scan Mode
unknown_0831:1,
autolk:1, // Auto Key Lock
save:1, // Battery Save
beep:1, // Key Beep
voice:2; // Voice Prompt
- u8 vfomr_fm:1, // FM Radio Display Mode
led:2, // Background Light
unknown_0832_2:1,
dw:1, // FM Radio Dual Watch
name:1, // Display Names
vfomr_a:2; // Display Mode A
- u8 opnset:2, // Power On Message
unknown_0833_1:3,
dwait:1, // Dual Standby
vfomr_b:2; // Display Mode B
- u8 mrcha; // mr a ch num
- u8 mrchb; // mr b ch num
- u8 fmch; // fm radio ch num
- u8 unknown_0837_1:1,
ste:1, // Squelch Tail Eliminate
roger:1, // Roger Beep
unknown_0837_2:1,
vox:4; // VOX
- u8 step:4, // Step
unknown_0838_1:4;
- u8 squelch; // Squelch
- u8 tot; // Time Out Timer
- u8 rptmod:1, // Repeater Mode
volmod:2, // Volume Mode
rptptt:1, // Repeater PTT Switch
rptspk:1, // Repeater Speaker
relay:3; // Cross Band Repeater Enable
- u8 unknown_083C:4, // 0x083C
rptrl:4; // Repeater TX Delay
- u8 pf1:4, // Function Key 1
pf2:4; // Function Key 2
- u8 vot; // VOX Delay Time
+} settings;
+#seekto 0x0848; +struct {
- char line1[7];
+} poweron_msg;
+struct limit {
- bbcd lower[2];
- bbcd upper[2];
+};
+#seekto 0x0850; +struct {
- struct limit vhf;
- struct limit uhf;
+} limits;
+#seekto 0x08D0; +struct {
- char name[7];
- u8 unknown2[1];
+} names[128];
+#seekto 0x0D20; +u8 usedflags[16]; +u8 scanflags[16];
+#seekto 0x0FA0; +struct {
- u8 unknown_0FA0_1:4,
dispab:1, // select a/b
unknown_0FA0_2:3;
+} settings2; +"""
+CMD_ACK = "\x06" +BLOCK_SIZE = 0x10
+RT23_POWER_LEVELS = [chirp_common.PowerLevel("Low", watts=1.00),
chirp_common.PowerLevel("High", watts=2.50)]
+RT23_DTCS = sorted(chirp_common.DTCS_CODES +
[17, 50, 55, 135, 217, 254, 305, 645, 765])
+RT23_CHARSET = chirp_common.CHARSET_UPPER_NUMERIC + \
- ":;<=>?@ !"#$%&'()*+,-./"
+LIST_COLOR = ["Blue", "Orange", "Purple"] +LIST_LED = ["Off", "On", "Auto"] +LIST_OPNSET = ["Full", "Voltage", "Message"] +LIST_PFKEY = [
- "Radio",
- "Sub-channel Sent",
- "Scan",
- "Alarm",
- "DTMF",
- "Squelch Off Momentarily",
- "Battery Power Indicator",
- "Tone 1750",
- "Tone 2100",
- "Tone 1000",
- "Tone 1450"]
+LIST_PTTID = ["Off", "BOT", "EOT", "Both"] +LIST_RPTMOD = ["Single", "Double"] +LIST_RPTRL = ["0.5S", "1.0S", "1.5S", "2.0S", "2.5S", "3.0S", "3.5S", "4.0S",
"4.5S"]
+LIST_SCANS = ["Time Operated", "Carrier Operated", "Search"] +LIST_SIGNALING = ["No", "DTMF"] +LIST_TOT = ["OFF"] + ["%s seconds" % x for x in range(30, 300, 30)] +LIST_TXSEL = ["Edit", "Busy"] +LIST_STEP = ["2.50K", "5.00K", "6.25K", "10.00K", "12,50K", "20.00K", "25.00K",
"50.00K"]
+LIST_VFOMR = ["VFO", "MR(Frequency)", "MR(Channel #/Name)"] +LIST_VFOMRFM = ["VFO", "Channel"] +LIST_VOICE = ["Off", "Chinese", "English"] +LIST_VOLMOD = ["Off", "Sub", "Main"] +LIST_VOT = ["0.5S", "1.0S", "1.5S", "2.0S", "3.0S"] +LIST_VOX = ["OFF"] + ["%s" % x for x in range(1, 6)]
+def _rt23_enter_programming_mode(radio):
- serial = radio.pipe
- magic = "PROIUAM"
- exito = False
- for i in range(0, 5):
for j in range(0, len(magic)):
time.sleep(0.005)
serial.write(magic[j])
ack = serial.read(1)
try:
if ack == CMD_ACK:
exito = True
break
except:
LOG.debug("Attempt #%s, failed, trying again" % i)
pass
- # check if we had EXITO
- if exito is False:
msg = "The radio did not accept program mode after five tries.\n"
msg += "Check you interface cable and power cycle your radio."
raise errors.RadioError(msg)
- try:
serial.write("\x02")
ident = serial.read(8)
- except:
raise errors.RadioError("Error communicating with radio")
- if not ident.startswith("P31183"):
LOG.debug(util.hexprint(ident))
raise errors.RadioError("Radio returned unknown identification string")
- try:
serial.write(CMD_ACK)
ack = serial.read(1)
- except:
raise errors.RadioError("Error communicating with radio")
- if ack != CMD_ACK:
raise errors.RadioError("Radio refused to enter programming mode")
+def _rt23_exit_programming_mode(radio):
- serial = radio.pipe
- try:
serial.write("E")
- except:
raise errors.RadioError("Radio refused to exit programming mode")
+def _rt23_read_block(radio, block_addr, block_size):
- serial = radio.pipe
- cmd = struct.pack(">cHb", 'R', block_addr, BLOCK_SIZE)
- expectedresponse = "W" + cmd[1:]
- LOG.debug("Reading block %04x..." % (block_addr))
- try:
serial.write(cmd)
response = serial.read(4 + BLOCK_SIZE + 1)
if response[:4] != expectedresponse:
raise Exception("Error reading block %04x." % (block_addr))
chunk = response[4:]
cs = 0
for byte in chunk[:-1]:
cs += ord(byte)
if ord(chunk[-1]) != (cs & 0xFF):
raise Exception("Block failed checksum!")
block_data = chunk[:-1]
- except:
raise errors.RadioError("Failed to read block at %04x" % block_addr)
- return block_data
+def _rt23_write_block(radio, block_addr, block_size):
- serial = radio.pipe
- cmd = struct.pack(">cHb", 'W', block_addr, BLOCK_SIZE)
- data = radio.get_mmap()[block_addr:block_addr + BLOCK_SIZE]
- cs = 0
- for byte in data:
cs += ord(byte)
- data += chr(cs & 0xFF)
- LOG.debug("Writing Data:")
- LOG.debug(util.hexprint(cmd + data))
- try:
for j in range(0, len(cmd)):
time.sleep(0.002)
serial.write(cmd[j])
for j in range(0, len(data)):
time.sleep(0.002)
serial.write(data[j])
if serial.read(1) != CMD_ACK:
raise Exception("No ACK")
- except:
raise errors.RadioError("Failed to send block "
"to radio at %04x" % block_addr)
+def do_download(radio):
- LOG.debug("download")
- _rt23_enter_programming_mode(radio)
- data = ""
- status = chirp_common.Status()
- status.msg = "Cloning from radio"
- status.cur = 0
- status.max = radio._memsize
- for addr in range(0, radio._memsize, BLOCK_SIZE):
status.cur = addr + BLOCK_SIZE
radio.status_fn(status)
block = _rt23_read_block(radio, addr, BLOCK_SIZE)
if addr == 0 and block.startswith("\xFF" * 6):
block = "P31183" + "\xFF" * 10
data += block
LOG.debug("Address: %04x" % addr)
LOG.debug(util.hexprint(block))
- _rt23_exit_programming_mode(radio)
- return memmap.MemoryMap(data)
+def do_upload(radio):
- status = chirp_common.Status()
- status.msg = "Uploading to radio"
- _rt23_enter_programming_mode(radio)
- status.cur = 0
- status.max = radio._memsize
- for start_addr, end_addr in radio._ranges:
for addr in range(start_addr, end_addr, BLOCK_SIZE):
status.cur = addr + BLOCK_SIZE
radio.status_fn(status)
_rt23_write_block(radio, addr, BLOCK_SIZE)
+def model_match(cls, data):
- """Match the opened/downloaded image to the correct version"""
- if len(data) == 0x1000:
rid = data[0x0000:0x0006]
return rid == "P31183"
- else:
return False
+def _split(rf, f1, f2):
- """Returns False if the two freqs are in the same band (no split)
- or True otherwise"""
- # determine if the two freqs are in the same band
- for low, high in rf.valid_bands:
if f1 >= low and f1 <= high and \
f2 >= low and f2 <= high:
# if the two freqs are on the same Band this is not a split
return False
- # if you get here is because the freq pairs are split
- return True
+@directory.register +class RT23Radio(chirp_common.CloneModeRadio):
- """RETEVIS RT23"""
- VENDOR = "Retevis"
- MODEL = "RT23"
- BAUD_RATE = 9600
- _ranges = [
(0x0000, 0x0EC0),
]
- _memsize = 0x1000
- def get_features(self):
rf = chirp_common.RadioFeatures()
rf.has_settings = True
rf.has_bank = False
rf.has_ctone = True
rf.has_cross = True
rf.has_rx_dtcs = True
rf.has_tuning_step = False
rf.can_odd_split = True
rf.valid_name_length = 7
rf.valid_characters = RT23_CHARSET
rf.has_name = True
rf.valid_skips = ["", "S"]
rf.valid_tmodes = ["", "Tone", "TSQL", "DTCS", "Cross"]
rf.valid_cross_modes = ["Tone->Tone", "Tone->DTCS", "DTCS->Tone",
"->Tone", "->DTCS", "DTCS->", "DTCS->DTCS"]
rf.valid_power_levels = RT23_POWER_LEVELS
rf.valid_duplexes = ["", "-", "+", "split", "off"]
rf.valid_modes = ["FM", "NFM"] # 25 KHz, 12.5 KHz.
rf.memory_bounds = (1, 128)
rf.valid_bands = [
(136000000, 174000000),
(400000000, 480000000)]
return rf
- def process_mmap(self):
self._memobj = bitwise.parse(MEM_FORMAT, self._mmap)
- def sync_in(self):
"""Download from radio"""
try:
data = do_download(self)
except errors.RadioError:
# Pass through any real errors we raise
raise
except:
# If anything unexpected happens, make sure we raise
# a RadioError and log the problem
LOG.exception('Unexpected error during download')
raise errors.RadioError('Unexpected error communicating '
'with the radio')
self._mmap = data
self.process_mmap()
- def sync_out(self):
"""Upload to radio"""
try:
do_upload(self)
except:
# If anything unexpected happens, make sure we raise
# a RadioError and log the problem
LOG.exception('Unexpected error during upload')
raise errors.RadioError('Unexpected error communicating '
'with the radio')
- def get_raw_memory(self, number):
return repr(self._memobj.memory[number - 1])
- def decode_tone(self, val):
"""Parse the tone data to decode from mem, it returns:
Mode (''|DTCS|Tone), Value (None|###), Polarity (None,N,R)"""
if val.get_raw() == "\xFF\xFF":
return '', None, None
val = int(val)
if val >= 12000:
a = val - 12000
return 'DTCS', a, 'R'
elif val >= 8000:
a = val - 8000
return 'DTCS', a, 'N'
else:
a = val / 10.0
return 'Tone', a, None
- def encode_tone(self, memval, mode, value, pol):
"""Parse the tone data to encode from UI to mem"""
if mode == '':
memval[0].set_raw(0xFF)
memval[1].set_raw(0xFF)
elif mode == 'Tone':
memval.set_value(int(value * 10))
elif mode == 'DTCS':
flag = 0x80 if pol == 'N' else 0xC0
memval.set_value(value)
memval[1].set_bits(flag)
else:
raise Exception("Internal error: invalid mode `%s'" % mode)
- def get_memory(self, number):
mem = chirp_common.Memory()
_mem = self._memobj.channels[number-1]
_nam = self._memobj.names[number - 1]
mem.number = number
bitpos = (1 << ((number - 1) % 8))
bytepos = ((number - 1) / 8)
_scn = self._memobj.scanflags[bytepos]
_usd = self._memobj.usedflags[bytepos]
isused = bitpos & int(_usd)
isscan = bitpos & int(_scn)
if not isused:
mem.empty = True
return mem
mem.freq = int(_mem.rxfreq) * 10
# We'll consider any blank (i.e. 0MHz frequency) to be empty
if mem.freq == 0:
mem.empty = True
return mem
if _mem.rxfreq.get_raw() == "\xFF\xFF\xFF\xFF":
mem.empty = True
return mem
if _mem.get_raw() == ("\xFF" * 16):
LOG.debug("Initializing empty memory")
_mem.set_raw("\x00" * 16)
# Freq and offset
mem.freq = int(_mem.rxfreq) * 10
# tx freq can be blank
if _mem.get_raw()[4] == "\xFF":
# TX freq not set
mem.offset = 0
mem.duplex = "off"
else:
# TX freq set
offset = (int(_mem.txfreq) * 10) - mem.freq
if offset != 0:
if _split(self.get_features(), mem.freq, int(_mem.txfreq) * 10):
mem.duplex = "split"
mem.offset = int(_mem.txfreq) * 10
elif offset < 0:
mem.offset = abs(offset)
mem.duplex = "-"
elif offset > 0:
mem.offset = offset
mem.duplex = "+"
else:
mem.offset = 0
for char in _nam.name:
if str(char) == "\xFF":
char = " "
mem.name += str(char)
mem.name = mem.name.rstrip()
mem.mode = _mem.isnarrow and "NFM" or "FM"
rxtone = txtone = None
txtone = self.decode_tone(_mem.txtone)
rxtone = self.decode_tone(_mem.rxtone)
chirp_common.split_tone_decode(mem, txtone, rxtone)
mem.power = RT23_POWER_LEVELS[_mem.highpower]
if not isscan:
mem.skip = "S"
mem.extra = RadioSettingGroup("Extra", "extra")
rs = RadioSetting("bcl", "BCL",
RadioSettingValueBoolean(_mem.bcl))
mem.extra.append(rs)
rs = RadioSetting("pttid", "PTT ID",
RadioSettingValueList(
LIST_PTTID, LIST_PTTID[_mem.pttid]))
mem.extra.append(rs)
rs = RadioSetting("signaling", "Optional Signaling",
RadioSettingValueList(LIST_SIGNALING,
LIST_SIGNALING[_mem.signaling]))
mem.extra.append(rs)
return mem
- def set_memory(self, mem):
LOG.debug("Setting %i(%s)" % (mem.number, mem.extd_number))
_mem = self._memobj.channels[mem.number - 1]
_nam = self._memobj.names[mem.number - 1]
bitpos = (1 << ((mem.number - 1) % 8))
bytepos = ((mem.number - 1) / 8)
_scn = self._memobj.scanflags[bytepos]
_usd = self._memobj.usedflags[bytepos]
if mem.empty:
_mem.set_raw("\xFF" * 16)
_nam.name = ("\xFF" * 7)
_usd &= ~bitpos
_scn &= ~bitpos
return
else:
_usd |= bitpos
if _mem.get_raw() == ("\xFF" * 16):
LOG.debug("Initializing empty memory")
_mem.set_raw("\x00" * 16)
_scn |= bitpos
_mem.rxfreq = mem.freq / 10
if mem.duplex == "off":
for i in range(0, 4):
_mem.txfreq[i].set_raw("\xFF")
elif mem.duplex == "split":
_mem.txfreq = mem.offset / 10
elif mem.duplex == "+":
_mem.txfreq = (mem.freq + mem.offset) / 10
elif mem.duplex == "-":
_mem.txfreq = (mem.freq - mem.offset) / 10
else:
_mem.txfreq = mem.freq / 10
_namelength = self.get_features().valid_name_length
for i in range(_namelength):
try:
_nam.name[i] = mem.name[i]
except IndexError:
_nam.name[i] = "\xFF"
_mem.scan = mem.skip != "S"
if mem.skip == "S":
_scn &= ~bitpos
else:
_scn |= bitpos
_mem.isnarrow = mem.mode == "NFM"
((txmode, txtone, txpol), (rxmode, rxtone, rxpol)) = \
chirp_common.split_tone_encode(mem)
self.encode_tone(_mem.txtone, txmode, txtone, txpol)
self.encode_tone(_mem.rxtone, rxmode, rxtone, rxpol)
_mem.highpower = mem.power == RT23_POWER_LEVELS[1]
for setting in mem.extra:
setattr(_mem, setting.get_name(), setting.value)
- def get_settings(self):
_settings = self._memobj.settings
_mem = self._memobj
basic = RadioSettingGroup("basic", "Basic Settings")
advanced = RadioSettingGroup("advanced", "Advanced Settings")
other = RadioSettingGroup("other", "Other Settings")
workmode = RadioSettingGroup("workmode", "Workmode Settings")
fmradio = RadioSettingGroup("fmradio", "FM Radio Settings")
top = RadioSettings(basic, advanced, other, workmode, fmradio)
save = RadioSetting("save", "Battery Saver",
RadioSettingValueBoolean(_settings.save))
basic.append(save)
vox = RadioSetting("vox", "VOX Gain",
RadioSettingValueList(
LIST_VOX, LIST_VOX[_settings.vox]))
basic.append(vox)
squelch = RadioSetting("squelch", "Squelch Level",
RadioSettingValueInteger(
0, 9, _settings.squelch))
basic.append(squelch)
relay = RadioSetting("relay", "Repeater",
RadioSettingValueBoolean(_settings.relay))
basic.append(relay)
tot = RadioSetting("tot", "Time-out timer", RadioSettingValueList(
LIST_TOT, LIST_TOT[_settings.tot]))
basic.append(tot)
beep = RadioSetting("beep", "Key Beep",
RadioSettingValueBoolean(_settings.beep))
basic.append(beep)
color = RadioSetting("color", "Background Color", RadioSettingValueList(
LIST_COLOR, LIST_COLOR[_settings.color - 1]))
basic.append(color)
vot = RadioSetting("vot", "VOX Delay Time", RadioSettingValueList(
LIST_VOT, LIST_VOT[_settings.vot]))
basic.append(vot)
dwait = RadioSetting("dwait", "Dual Standby",
RadioSettingValueBoolean(_settings.dwait))
basic.append(dwait)
led = RadioSetting("led", "Background Light", RadioSettingValueList(
LIST_LED, LIST_LED[_settings.led]))
basic.append(led)
voice = RadioSetting("voice", "Voice Prompt", RadioSettingValueList(
LIST_VOICE, LIST_VOICE[_settings.voice]))
basic.append(voice)
roger = RadioSetting("roger", "Roger Beep",
RadioSettingValueBoolean(_settings.roger))
basic.append(roger)
autolk = RadioSetting("autolk", "Auto Key Lock",
RadioSettingValueBoolean(_settings.autolk))
basic.append(autolk)
opnset = RadioSetting("opnset", "Open Mode Set",
RadioSettingValueList(
LIST_OPNSET, LIST_OPNSET[_settings.opnset]))
basic.append(opnset)
def _filter(name):
filtered = ""
for char in str(name):
if char in chirp_common.CHARSET_ASCII:
filtered += char
else:
filtered += " "
return filtered
_msg = self._memobj.poweron_msg
ponmsg = RadioSetting("poweron_msg.line1", "Power-On Message",
RadioSettingValueString(
0, 7, _filter(_msg.line1)))
basic.append(ponmsg)
scans = RadioSetting("scans", "Scan Mode", RadioSettingValueList(
LIST_SCANS, LIST_SCANS[_settings.scans]))
basic.append(scans)
dw = RadioSetting("dw", "FM Radio Dual Watch",
RadioSettingValueBoolean(_settings.dw))
basic.append(dw)
name = RadioSetting("name", "Display Names",
RadioSettingValueBoolean(_settings.name))
basic.append(name)
rptrl = RadioSetting("rptrl", "Repeater TX Delay",
RadioSettingValueList(LIST_RPTRL, LIST_RPTRL[
_settings.rptrl]))
basic.append(rptrl)
rptspk = RadioSetting("rptspk", "Repeater Speaker",
RadioSettingValueBoolean(_settings.rptspk))
basic.append(rptspk)
rptptt = RadioSetting("rptptt", "Repeater PTT Switch",
RadioSettingValueBoolean(_settings.rptptt))
basic.append(rptptt)
rptmod = RadioSetting("rptmod", "Repeater Mode",
RadioSettingValueList(
LIST_RPTMOD, LIST_RPTMOD[_settings.rptmod]))
basic.append(rptmod)
volmod = RadioSetting("volmod", "Volume Mode",
RadioSettingValueList(
LIST_VOLMOD, LIST_VOLMOD[_settings.volmod]))
basic.append(volmod)
dst = RadioSetting("dst", "DTMF Side Tone",
RadioSettingValueBoolean(_settings.dst))
basic.append(dst)
txsel = RadioSetting("txsel", "Priority TX Channel",
RadioSettingValueList(
LIST_TXSEL, LIST_TXSEL[_settings.txsel]))
basic.append(txsel)
ste = RadioSetting("ste", "Squelch Tail Eliminate",
RadioSettingValueBoolean(_settings.ste))
basic.append(ste)
#advanced
if _settings.pf1 > 0x0A:
val = 0x00
else:
val = _settings.pf1
pf1 = RadioSetting("pf1", "PF1 Key",
RadioSettingValueList(
LIST_PFKEY, LIST_PFKEY[val]))
advanced.append(pf1)
if _settings.pf2 > 0x0A:
val = 0x00
else:
val = _settings.pf2
pf2 = RadioSetting("pf2", "PF2 Key",
RadioSettingValueList(
LIST_PFKEY, LIST_PFKEY[val]))
advanced.append(pf2)
# other
_limit = str(int(_mem.limits.vhf.lower) / 10)
val = RadioSettingValueString(0, 3, _limit)
val.set_mutable(False)
rs = RadioSetting("limits.vhf.lower", "VHF low", val)
other.append(rs)
_limit = str(int(_mem.limits.vhf.upper) / 10)
val = RadioSettingValueString(0, 3, _limit)
val.set_mutable(False)
rs = RadioSetting("limits.vhf.upper", "VHF high", val)
other.append(rs)
_limit = str(int(_mem.limits.uhf.lower) / 10)
val = RadioSettingValueString(0, 3, _limit)
val.set_mutable(False)
rs = RadioSetting("limits.uhf.lower", "UHF low", val)
other.append(rs)
_limit = str(int(_mem.limits.uhf.upper) / 10)
val = RadioSettingValueString(0, 3, _limit)
val.set_mutable(False)
rs = RadioSetting("limits.uhf.upper", "UHF high", val)
other.append(rs)
#work mode
vfomr_a = RadioSetting("vfomr_a", "Display Mode A",
RadioSettingValueList(
LIST_VFOMR, LIST_VFOMR[_settings.vfomr_a]))
workmode.append(vfomr_a)
vfomr_b = RadioSetting("vfomr_b", "Display Mode B",
RadioSettingValueList(
LIST_VFOMR, LIST_VFOMR[_settings.vfomr_b]))
workmode.append(vfomr_b)
mrcha = RadioSetting("mrcha", "Channel # A",
RadioSettingValueInteger(
1, 128, _settings.mrcha))
workmode.append(mrcha)
mrchb = RadioSetting("mrchb", "Channel # B",
RadioSettingValueInteger(
1, 128, _settings.mrchb))
workmode.append(mrchb)
#fm radio
vfomr_fm = RadioSetting("vfomr_fm", "FM Radio Display Mode",
RadioSettingValueList(
LIST_VFOMRFM, LIST_VFOMRFM[
_settings.vfomr_fm]))
fmradio.append(vfomr_fm)
fmch = RadioSetting("fmch", "FM Radio Channel #",
RadioSettingValueInteger(
1, 25, _settings.fmch))
fmradio.append(fmch)
return top
- 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()
elif setting == "color":
setattr(obj, setting, int(element.value) + 1)
elif element.value.get_mutable():
LOG.debug("Setting %s = %s" % (setting, element.value))
setattr(obj, setting, element.value)
except Exception, e:
LOG.debug(element.get_name())
raise
- @classmethod
- def match_model(cls, filedata, filename):
match_size = False
match_model = False
# testing the file data size
if len(filedata) in [0x1000, ]:
match_size = True
# testing the model fingerprint
match_model = model_match(cls, filedata)
if match_size and match_model:
return True
else:
return False
+def _rt23_write_block(radio, block_addr, block_size):
- serial = radio.pipe
- cmd = struct.pack(">cHb", 'W', block_addr, BLOCK_SIZE)
- data = radio.get_mmap()[block_addr:block_addr + BLOCK_SIZE]
- cs = 0
- for byte in data:
cs += ord(byte)
- data += chr(cs & 0xFF)
- LOG.debug("Writing Data:")
- LOG.debug(util.hexprint(cmd + data))
- try:
for j in range(0, len(cmd)):
time.sleep(0.002)
serial.write(cmd[j])
for j in range(0, len(data)):
time.sleep(0.002)
serial.write(data[j])
if serial.read(1) != CMD_ACK:
raise Exception("No ACK")
This inter-byte delay timing is rather unfortunate. I assume your testing shows that this is the only way the radio will accept the image?
It takes a hair over 1ms to send a single byte at 9600 baud. Sleeping for 2ms in between means you're sending one byte every 3ms, which is crazy slow. Do you see the factory software actually writing one byte at a time in the trace? As we've covered before, doing sleeps at this granularity requires the operating system to schedule you pretty regularly to keep up the stream, which is both wasteful and error-prone of the system is loaded. It also increases the likelihood that the driver will behave differently on Linux, Windows, and with differing serial hardware.
Have you tried doing something like writing 8 bytes at a time, with a sleep in between or something like that?
--Dan
Dan,
This inter-byte delay timing is rather unfortunate. I assume your testing shows that this is the only way the radio will accept the image?
For testing here I have used a Windows 7 64-bit PC, a Linux Mint 64-bit PC and a Windows 10 32-bit virtual machine. On every one of these machines, I can send the upload to the radio at full speed. smb://tower/disk2/Drivers,%20Manuals%20&%20Firmware/RETEVIS/RT23/RT23_Write_Factory.txt
I have had 2 testers tell me that they cannot upload to their respective radios without this hack. Both tell me that the factory software uploads to their radio just fine.
I don't like doing it this way either. Unfortunately, I'm not sure I have what it take to figure it out. But I sure would like to figure it out. Because I would like to get rid of it on the btech.py driver as well.
I have attached the serial port capture that I made using Windows 7 and my RT23. Does it provide a clue as to how I can resolve this. I know about Retevis, but if this can be resolved in a reasonable amount of time, I would rather delay adding this go CHIRP. I just need to assistance/advice on how to proceed.
Have you tried doing something like writing 8 bytes at a time, with a sleep in between or something like that?
I will give this a try.
Thanks, Jim
For testing here I have used a Windows 7 64-bit PC, a Linux Mint 64-bit PC and a Windows 10 32-bit virtual machine. On every one of these machines, I can send the upload to the radio at full speed.
You mean without these delays in the code, right?
I assume that the upload takes at least double the time with these in place, correct?
I have attached the serial port capture that I made using Windows 7 and my RT23. Does it provide a clue as to how I can resolve this.
I'm not sure what program that came from, but it doesn't have the extra information that portmon gives us about how the serial port was opened. However, from the look of that, I would assume that the software is writing the whole block in one go, not doing byte-at-a-time.
I know about Retevis, but if this can be resolved in a reasonable amount of time, I would rather delay adding this go CHIRP. I just need to assistance/advice on how to proceed.
Given our relationship with Retevis, maybe we can get some information from them about any restrictions the radios may have in terms of digesting the data stream?
Have you tried doing something like writing 8 bytes at a time, with a sleep in between or something like that?
I will give this a try.
I think that's a good first step. If we can knock it down to a delay every N bytes that'll be a huge improvement.
--Dan
You mean without these delays in the code, right?
Yes. No delays. Before I shared the driver to anyone for testing, I had none of the delays in the driver because there was no need to have it.
I assume that the upload takes at least double the time with these in place, correct?
It takes longer with .002 as the sleep delay, but probably not double. With a delay of .003 it is quite a bit longer. Unbearably long.
I'm not sure what program that came from, but it doesn't have the extra information that portmon gives us about how the serial port was opened. However, from the look of that, I would assume that the software is writing the whole block in one go, not doing byte-at-a-time.
Correct. It is not from Portmon. I bought a program that will run on a 64-bit Windows computer. My 32-bit laptop that I can use Portmon on is currently unavailable.
Given our relationship with Retevis, maybe we can get some information from them about any restrictions the radios may have in terms of digesting the data stream?
I can try.
Have you tried doing something like writing 8 bytes at a time, with a sleep in between or something like that?
I will give this a try.
I think that's a good first step. If we can knock it down to a delay every N bytes that'll be a huge improvement.
--Dan
Jim
You mean without these delays in the code, right?
Yes. No delays. Before I shared the driver to anyone for testing, I had none of the delays in the driver because there was no need to have it.
Hmm, I'm _highly_ skeptical of why those are required then. If you have a modern and fast machine and the same radio, you should be stressing them plenty if there's really a hardware problem.
Anyway, I'd definitely like to see if writing larger blocks between the sleeps accomplishes the same goal. I'd also be more amenable to merging the driver without the sleeps (since we know it works on controlled environments) and then pursue workarounds after the fact. Potentially getting data from a wider audience could be useful too.
--Dan
I'd also be more amenable to merging the driver without the sleeps (since we know it works on controlled environments) and then pursue workarounds after the fact. Potentially getting data from a wider audience could be useful too.
I will remove the sleeps and resubmit the patch soon.
Then I will see if I can accomplish the 8 bytes at at time upload and send it to my tester to see what happens.
Jim
Hmm, I'm _highly_ skeptical of why those are required then. If you have a modern and fast machine and the same radio, you should be stressing them plenty if there's really a hardware problem.
I discovered something interesting while testing the driver with the sleeps removed. While using my main programming cable with (counterfeit) Prolific chip, the driver uploads just fine with no delays in the driver. However, when using my FTDI chip based programming cable, it fails immediately after the 1st block is sent.
That at least means I will be able to test my changes in real time.
Jim
El 15/06/17 a las 19:53, Jim Unroe via chirp_devel escribió:
I discovered something interesting while testing the driver with the sleeps removed. While using my main programming cable with (counterfeit) Prolific chip, the driver uploads just fine with no delays in the driver. However, when using my FTDI chip based programming cable, it fails immediately after the 1st block is sent.
Hi developers, I found a behavior like that at home, I have this options as interfaces at home:
* Real Motherboard Serial via a DB9 back port. * Rather OLD all-in-one USB dongle (USRobotics) that has LPT/ethernet/Audio/Serial adapters (DB9) this one needs a extra feed of +9 to +12 V dc * Cheap chinese CH340 USB-Serial adapter (DB9) * Cheap chinese ARK316? USB-Serial adapter (DB9) * Arduino USB-TLL serial adapter (0.1" headers)
Followed by a homebrewed RS232 to TTL levels using a MAX232 like Chip that can go as high as 2 Mbps (External power feed, not powered from the Serial line)
The real Serial at the MoBo and the old all-in-one adapters work fine no matter what combination I shoot to them.
But all the rest of the adapters have it's problems keeping up with the speed... some simply don't work with some radios, other trough errors due mainly to out of sync problems at some point after the download has started, etc.
I found this behaviour programming mainly the Serie/Family 60 and 60G from the Kenwoods...
So different serial adapters (drivers) has different performance and some of them fails... I has Ubuntu Linux & Windows here, and this affects both OS.
Just my 6 cents...
Dan,
The build process must not have completed fully. I didn't see a mailing list notification of a new release. The new Linux build was available in Linux Mint's Software Manager.
Something interesting. Even though my Linux machine is running the 20170617 build, I am prompted that the previous 20170608 build is newer.
Jim
The build process must not have completed fully. I didn't see a mailing list notification of a new release. The new Linux build was available in Linux Mint's Software Manager.
Something interesting. Even though my Linux machine is running the 20170617 build, I am prompted that the previous 20170608 build is newer.
Yeah, expected based on how that works.
The build system got hung up on the windows build for some reason. It happens rarely.
I reconfigured it this morning to be a little more convenient for triggering rebuilds. Previously, it would build and upload the ubuntu packages early, which always succeeds. If the windows build fails (which happens much more often), I couldn't re-trigger it (without some monkeywork) because the PPA won't let you re-upload the packages. Anyway, I changed the ordering so the PPA upload happens last if everything else succeeds, which will make it easier for me to re-trigger them for a given day if we need it.
I just did that and it went fine.
--Dan
The other thing to try is setting the latency of your USB serial adapter in the driver advanced tab. I have car tuning gear that requires this.
By default the latency setting is a bit high for a lot of USB serial dongles. It increases efficiency at the cost of reduced speed. Akin to the Nagle algorithm in networking maybe?
-Nathan
On Jun 15, 2017 3:26 PM, "Dan Smith via chirp_devel" < chirp_devel@intrepid.danplanet.com> wrote:
+def _rt23_write_block(radio, block_addr, block_size):
- serial = radio.pipe
- cmd = struct.pack(">cHb", 'W', block_addr, BLOCK_SIZE)
- data = radio.get_mmap()[block_addr:block_addr + BLOCK_SIZE]
- cs = 0
- for byte in data:
cs += ord(byte)
- data += chr(cs & 0xFF)
- LOG.debug("Writing Data:")
- LOG.debug(util.hexprint(cmd + data))
- try:
for j in range(0, len(cmd)):
time.sleep(0.002)
serial.write(cmd[j])
for j in range(0, len(data)):
time.sleep(0.002)
serial.write(data[j])
if serial.read(1) != CMD_ACK:
raise Exception("No ACK")
This inter-byte delay timing is rather unfortunate. I assume your testing shows that this is the only way the radio will accept the image?
It takes a hair over 1ms to send a single byte at 9600 baud. Sleeping for 2ms in between means you're sending one byte every 3ms, which is crazy slow. Do you see the factory software actually writing one byte at a time in the trace? As we've covered before, doing sleeps at this granularity requires the operating system to schedule you pretty regularly to keep up the stream, which is both wasteful and error-prone of the system is loaded. It also increases the likelihood that the driver will behave differently on Linux, Windows, and with differing serial hardware.
Have you tried doing something like writing 8 bytes at a time, with a sleep in between or something like that?
--Dan _______________________________________________ chirp_devel mailing list chirp_devel@intrepid.danplanet.com http://intrepid.danplanet.com/mailman/listinfo/chirp_devel Developer docs: http://chirp.danplanet.com/projects/chirp/wiki/Developers
The other thing to try is setting the latency of your USB serial adapter in the driver advanced tab. I have car tuning gear that requires this.
By default the latency setting is a bit high for a lot of USB serial dongles. It increases efficiency at the cost of reduced speed. Akin to the Nagle algorithm in networking maybe?
You mean turn _up_ the latency to slow things down a bit? If so, then that's worth a try.
IMHO, this really smells like a flow control problem. Either the radios are expecting XON/XOFF and we're not doing it, or some of the chips/drivers are not signaling to the line discipline that their UART buffer is full and thus they're getting overwritten. The latter is more likely given that 9600 baud is really slow and most of the radios should be able to ingest that stream without needing us to slow down.
If this is really a pervasive problem with drivers or USB devices, perhaps we should just write a generic throttled serial class, and let people opt-in to using that with a given flow rate. It'd be interesting to know if writing 8/16 bytes at a time, and timing it for 10% higher than the time it should take to flush that at the current baudrate helps all of these problems universally.
--Dan
My mistake. I didn't read the thread well enough. High latency is the problem I've experienced most with USB serial devices. This is different.
You're right Dan. One could try /increasing/ the latency to increase buffering, but I think that will just send larger chunks. It will likely increase the overall throughput, if anything, not decrease it. If the radio just isn't keeping up with the throughput in general this won't help.
I do like your idea of creating a throttled serial driver so long as users can still opt out of it. Maybe they could even tune the throttling with a parameter? This might help some HW without strewing sleep() calls all over the place. I want the max speed I can use, though. It takes too long to read/write some radios as it is. Don't throttle everyone.
Throttling baudrate completely might be a bad idea in some cases. What if a radio needs a relatively high amount of time to "digest" a packet/block/line of information? Maybe it reads an entire block, and then commits it to EEPROM, for example. Or maybe it decrypts the block (slowly). If the time needed to process a block is high compared to the time between symbols, you wouldn't want to impose the block timeout on each symbol, or the throughput would be much lower than needed; maybe painfully so.
My experience is most embedded devices don't use XON/XOFF. Even the simplest microcontrollers can usually keep up with 9600 baud until they try to do something like decryption or writing to EEPROM. Maybe I'm wrong here - I've never written firmware for a radio.
-Nathan
On Fri, Jun 16, 2017 at 2:30 PM, Dan Smith via chirp_devel < chirp_devel@intrepid.danplanet.com> wrote:
The other thing to try is setting the latency of your USB serial adapter in the driver advanced tab. I have car tuning gear that requires this.
By default the latency setting is a bit high for a lot of USB serial dongles. It increases efficiency at the cost of reduced speed. Akin to the Nagle algorithm in networking maybe?
You mean turn _up_ the latency to slow things down a bit? If so, then that's worth a try.
IMHO, this really smells like a flow control problem. Either the radios are expecting XON/XOFF and we're not doing it, or some of the chips/drivers are not signaling to the line discipline that their UART buffer is full and thus they're getting overwritten. The latter is more likely given that 9600 baud is really slow and most of the radios should be able to ingest that stream without needing us to slow down.
If this is really a pervasive problem with drivers or USB devices, perhaps we should just write a generic throttled serial class, and let people opt-in to using that with a given flow rate. It'd be interesting to know if writing 8/16 bytes at a time, and timing it for 10% higher than the time it should take to flush that at the current baudrate helps all of these problems universally.
--Dan _______________________________________________ chirp_devel mailing list chirp_devel@intrepid.danplanet.com http://intrepid.danplanet.com/mailman/listinfo/chirp_devel Developer docs: http://chirp.danplanet.com/projects/chirp/wiki/Developers
I do like your idea of creating a throttled serial driver so long as users can still opt out of it. Maybe they could even tune the throttling with a parameter? This might help some HW without strewing sleep() calls all over the place. I want the max speed I can use, though. It takes too long to read/write some radios as it is. Don't throttle everyone.
Yeah, although then you create a whole cottage industry of people on the mailing list arguing about which value they feel is best for completely ignorant reasons. Seems better to have the driver expose a value that should be used in compatibility mode, and if one isn't exposed, calculate a default based on the baudrate.
Throttling baudrate completely might be a bad idea in some cases. What if a radio needs a relatively high amount of time to "digest" a packet/block/line of information? Maybe it reads an entire block, and then commits it to EEPROM, for example. Or maybe it decrypts the block (slowly). If the time needed to process a block is high compared to the time between symbols, you wouldn't want to impose the block timeout on each symbol, or the throughput would be much lower than needed; maybe painfully so.
Right, but those are things we _should_ bake into the driver as they're characteristics of the radio itself. Indeed several drivers have this sort of thing and it's fine.
What we're talking about here is (apparently) some buggy serial hardware or drivers that bring their own characteristics.
My experience is most embedded devices don't use XON/XOFF. Even the simplest microcontrollers can usually keep up with 9600 baud until they try to do something like decryption or writing to EEPROM. Maybe I'm wrong here - I've never written firmware for a radio.
Yeah, I don't think I've seen any that do software flow either, but with only two or three serial lines, it's the only way to provide back pressure. Clearly anything should be able to keep up with 9600 baud, but we do have some that can't although it's mostly at the record level and not just straight line speed. The thing I want us to avoid is forcing all users of the driver to send one byte at a time with a lengthy delay just because some people have broken serial hardware.
--Dan
Yeah, although then you create a whole cottage industry of people on the mailing list arguing about which value they feel is best for completely ignorant reasons.
LOL. You're probably right. Sounds like the voice of experience.
What we're talking about here is (apparently) some buggy serial hardware
or
drivers that bring their own characteristics.
A buggy dongle that sends characters too fast? Hmm. Maybe the serial dongle isn't preserving application timing because of buffering? Now we're back to lowering the latency timer. For example: the application sends a record, waits for processing, and then sends another record. If the dongle/driver buffers across two records, and then blasts out the entire block it could nullify the record processing timeout. Lowering the latency timer might help with this as it reduces buffering.
My experience was FTDI chipsets were most reliable in many ways. They responded well to a change in the latency parameter I mentioned earlier. Prolific USB dongles come in second to FTDI in my experience.
I'm fairly sure some of the USB dongle experience is its OS driver as much as anything. The Linux Prolific driver has a quirk or two. I've had a lot of problems with the generic Windows usbserial driver. It seemed to do a lot of internal buffering that I couldn't tune out the usual way.
The thing I want us to avoid is forcing all users of the driver to send
one
byte at a time with a lengthy delay just because some people have broken serial hardware.
I agree completely. That's one of the things I was trying to stress.
-Nathan
On Fri, Jun 16, 2017 at 3:26 PM, Dan Smith dsmith@danplanet.com wrote:
I do like your idea of creating a throttled serial driver so long as users can still opt out of it. Maybe they could even tune the throttling with a parameter? This might help some HW without strewing sleep() calls all over the place. I want the max speed I can use, though. It takes too long to read/write some radios as it is. Don't throttle everyone.
Yeah, although then you create a whole cottage industry of people on the mailing list arguing about which value they feel is best for completely ignorant reasons. Seems better to have the driver expose a value that should be used in compatibility mode, and if one isn't exposed, calculate a default based on the baudrate.
Throttling baudrate completely might be a bad idea in some cases. What if a radio needs a relatively high amount of time to "digest" a packet/block/line of information? Maybe it reads an entire block, and then commits it to EEPROM, for example. Or maybe it decrypts the block (slowly). If the time needed to process a block is high compared to the time between symbols, you wouldn't want to impose the block timeout on each symbol, or the throughput would be much lower than needed; maybe painfully so.
Right, but those are things we _should_ bake into the driver as they're characteristics of the radio itself. Indeed several drivers have this sort of thing and it's fine.
What we're talking about here is (apparently) some buggy serial hardware or drivers that bring their own characteristics.
My experience is most embedded devices don't use XON/XOFF. Even the simplest microcontrollers can usually keep up with 9600 baud until they try to do something like decryption or writing to EEPROM. Maybe I'm wrong here - I've never written firmware for a radio.
Yeah, I don't think I've seen any that do software flow either, but with only two or three serial lines, it's the only way to provide back pressure. Clearly anything should be able to keep up with 9600 baud, but we do have some that can't although it's mostly at the record level and not just straight line speed. The thing I want us to avoid is forcing all users of the driver to send one byte at a time with a lengthy delay just because some people have broken serial hardware.
--Dan
A buggy dongle that sends characters too fast? Hmm. Maybe the serial dongle isn't preserving application timing because of buffering?
No, a buggy dongle that doesn't signal to the driver that its buffer is full, or a driver that doesn't respect a signal and/or manage the buffer properly. A UART is a tiny 16-byte buffer and whatever is managing that has to ensure not to overrun it. It's a super simple piece of hardware, and if something isn't managing it properly then you just end up with garbage going out the serial line.
By writing to it slowly from the application you're working around that issue. If we know the radio can take the data at a full 9600 baud (which we do) then there's really not any other explanation for the application's speed of writing the data affecting the result, IMHO.
Now we're back to lowering the latency timer. For example: the application sends a record, waits for processing, and then sends another record. If the dongle/driver buffers across two records, and then blasts out the entire block it could nullify the record processing timeout. Lowering the latency timer might help with this as it reduces buffering.
Have you looked at the code? That's not what is happening. We're waiting for an ack after every block we write to the device. The inter-character delays being introduced basically mean we never fill the tiny buffer in the UART.
My experience was FTDI chipsets were most reliable in many ways. They responded well to a change in the latency parameter I mentioned earlier. Prolific USB dongles come in second to FTDI in my experience.
Yep, me as well, although Jim's testing just now seems to indicate that they are the least reliable, which surprises me. I'm not really sure what the "latency" setting is affecting, and I'm not aware of any such equivalent on non-Windows platforms. I also think that the non-Windows platforms seem to be unproblematic, IIRC.
--Dan
No, a buggy dongle that doesn't signal to the driver that its buffer is
full,
or a driver that doesn't respect a signal and/or manage the buffer
properly. A
UART is a tiny 16-byte buffer and whatever is managing that has to ensure
not
to overrun it. It's a super simple piece of hardware, and if something
isn't
managing it properly then you just end up with garbage going out the
serial
line.
I don't see concrete evidence of a buffer overflow or dropped char in this thread. Not ruling it out, but I wouldn't assume that until you see proof.
FWIW, I've never seen a USB dongle drop chars. I have seen buffering issues a lot. USB devices have much more overhead for device driver <-> HW communication. Buffering more increase CPU and bus efficiency to counter the overhead.
Have you looked at the code? That's not what is happening. We're waiting
for
an ack after every block we write to the device. The inter-character
delays
being introduced basically mean we never fill the tiny buffer in the UART.
Just tossing out potentially helpful ideas. Are you sure the failure is when CHIRP is waiting for the "ack" from the radio? I didn't see anyone report that specifically. Does the radio actually send the ack?
Here's a situation I experienced: I used a USB serial dongle to communicate between a laptop and a car ECU. A Windows application would send a request character to the ECU, then wait for a response. It did this as fast as possible over and over to constantly receive telemetry data.
A lot of USB serial dongles would break communication because they wouldn't send the ECU response quick enough and the application would give up. The application thought the connection was broke, when in fact the ECU did respond, the dongle received the char and buffered it. But then the dongle didn't send the response to the host in time. It was simply waiting for more characters because it wanted a fuller buffer before sending something to the host.
There are two ways to fix this problem: wait longer in the application for the response, or allow the USB dongle to send its RX data sooner (latency timer).
HW UARTs do not suffer this problem as they don't have the same greedy buffering. Like you said, they're simpler. The driver <-> HW communication is much more efficient, so higher buffering is not needed.
I'm not really sure what the "latency" setting is affecting, and I'm not
aware
of any such equivalent on non-Windows platforms.
The latency setting controls how long the driver waits for a full buffer before sending it to the dongle. The latency timer starts when the first character arrives at the driver from an app. The buffer is sent if the timer expires, no matter how full it is. If the buffer fills before the timer expires, it is sent immediately. This scheme is used for dongle -> host buffering as well.
Looks like you can set the latency_timer via sysfs in Linux for an FTDI chipset. I doubt this is very consistent for different dongles. See:
https://askubuntu.com/questions/696593/reduce-request-latency-on-an-ftdi-ubs...
I also think that the non-Windows platforms seem to be unproblematic,
IIRC.
Pavel Milanes said this issue happens in both Linux and Windows in his post at 2:00PM MDT.
Maybe the best solution is to increase the serial port timeout value in CHIRP. I can't think of a down-side other than sluggishness when trying to open invalid ports.
-Nathan
Have you looked at the code? That's not what is happening. We're waiting for an ack after every block we write to the device. The inter-character delays being introduced basically mean we never fill the tiny buffer in the UART.
Just tossing out potentially helpful ideas. Are you sure the failure is when CHIRP is waiting for the "ack" from the radio? I didn't see anyone report that specifically. Does the radio actually send the ack?
There is never an ack when it fails.
Maybe the best solution is to increase the serial port timeout value in CHIRP.
I tried this earlier today and it made no difference.
Jim KC9HI
Jim, I'd try playing with the latency timer. I think we've shot my previous two theories out of the water. However, buffering between the PC and dongle can alter timing and sometimes accounts for behavioral difference between dongles.
Separately, I like Dan's idea to try sending larger amounts of data at once. It may provide more data if nothing else. Like Dan said, there's very little guarantee small sleep values are accurate or efficient. There are even fewer guarantees that the timing you use to submit each byte in the application is preserved by the USB dongle. Data takes a much more complex path than if you were to use a HW UART. It should be a lot closer in that case.
-Nathan
On Fri, Jun 16, 2017 at 7:00 PM, Jim Unroe rock.unroe@gmail.com wrote:
Have you looked at the code? That's not what is happening. We're waiting for an ack after every block we write to the device. The inter-character delays being introduced basically mean we never fill the tiny buffer in the
UART.
Just tossing out potentially helpful ideas. Are you sure the failure is when CHIRP is waiting for the "ack" from the radio? I didn't see anyone
report
that specifically. Does the radio actually send the ack?
There is never an ack when it fails.
Maybe the best solution is to increase the serial port timeout value in CHIRP.
I tried this earlier today and it made no difference.
Jim KC9HI
Just tossing out potentially helpful ideas. Are you sure the failure is when CHIRP is waiting for the "ack" from the radio? I didn't see anyone report that specifically.
No, it's not that's the point. The sending speed either causes something to be dropped or corrupted such that the radio doesn't send the ACK. Increasing the time to wait for the ack doesn't solve anything because the radio doesn't ack it.
The latency setting controls how long the driver waits for a full buffer before sending it to the dongle. The latency timer starts when the first character arrives at the driver from an app. The buffer is sent if the timer expires, no matter how full it is. If the buffer fills before the timer expires, it is sent immediately. This scheme is used for dongle -> host buffering as well.
Okay, based on what I've seen I don't see how this would affect anything, but it's certainly worth poking at it to see if anything useful comes out.
Maybe the best solution is to increase the serial port timeout value in CHIRP. I can't think of a down-side other than sluggishness when trying to open invalid ports.
It affects the time to recover when the radio never sends the ACK. However I believe that we've sufficiently proven that this is _not_ a problem with aggressive timeout while waiting for the ack.
You might be missing some context on this issue, which stretches back to the btech driver development many months ago. That should be in the archives if you're interested. I wrote most of the drivers in the tree and had never encountered a problem like this until Pavel brought it up with the btech driver, and now Jim with this.
--Dan
participants (4)
-
Dan Smith
-
Jim Unroe
-
Nathan Crapo
-
Pavel Milanes