diff --git a/chirp/bitwise.py b/chirp/bitwise.py index 98720a145..b5654b697 100644 --- a/chirp/bitwise.py +++ b/chirp/bitwise.py @@ -322,6 +322,19 @@ def __set_value_char(self, value): if len(value) != len(self.__items): raise ValueError("String expects exactly %i characters, not %i" % ( len(self.__items), len(value))) + if not isinstance(value, bytes) and hasattr(value, '__str__'): + # Special case to allow ASCII strings straight through because + # almost anything will support them. If the string is not fully + # supported by ASCII, then the driver should have provided a + # pre-encoded bytestring. + warnings.warn( + 'String compatibility used - should pass bytes instead', + category=DeprecationWarning, stacklevel=4) + try: + value = str(value).encode('ascii') + except UnicodeEncodeError: + raise ValueError('Non-ASCII string set on char type: use ' + 'pre-encoded bytestring instead') for i in range(0, len(self.__items)): self.__items[i].set_value(value[i]) diff --git a/chirp/drivers/boblov_x3plus.py b/chirp/drivers/boblov_x3plus.py index 5482a4bc7..62c310590 100644 --- a/chirp/drivers/boblov_x3plus.py +++ b/chirp/drivers/boblov_x3plus.py @@ -51,6 +51,7 @@ class BoblovX3Plus(chirp_common.CloneModeRadio, MODEL = 'X3Plus' BAUD_RATE = 9600 CHANNELS = 16 + NEEDS_COMPAT_SERIAL = False MEM_FORMAT = """ #seekto 0x0010; @@ -86,12 +87,12 @@ class BoblovX3Plus(chirp_common.CloneModeRadio, """ # Radio command data - CMD_ACK = '\x06' - CMD_IDENTIFY = '\x02' - CMD_PROGRAM_ENTER = '.VKOGRAM' - CMD_PROGRAM_EXIT = '\x62' # 'b' - CMD_READ = 'R' - CMD_WRITE = 'W' + CMD_ACK = b'\x06' + CMD_IDENTIFY = b'\x02' + CMD_PROGRAM_ENTER = b'.VKOGRAM' + CMD_PROGRAM_EXIT = b'\x62' # 'b' + CMD_READ = b'R' + CMD_WRITE = b'W' BLOCK_SIZE = 0x08 @@ -177,7 +178,7 @@ def sync_in(self): self._enter_programming_mode() - data = '' + data = b'' for addr in range(0, self._memsize, self.BLOCK_SIZE): status.cur = addr + self.BLOCK_SIZE self.status_fn(status) @@ -260,14 +261,13 @@ def get_memory(self, number): mem.freq = int(rmem.rxfreq) * 10 # A blank (0 MHz) or 0xFFFFFFFF frequency is considered empty - if mem.freq == 0 or ( - rmem.rxfreq.get_raw(asbytes=False) == '\xFF\xFF\xFF\xFF'): + if mem.freq == 0 or rmem.rxfreq.get_raw() == b'\xFF\xFF\xFF\xFF': LOG.debug('empty channel %d', number) mem.freq = 0 mem.empty = True return mem - if rmem.txfreq.get_raw(asbytes=False) == '\xFF\xFF\xFF\xFF': + if rmem.txfreq.get_raw() == b'\xFF\xFF\xFF\xFF': mem.duplex = 'off' mem.offset = 0 elif int(rmem.rxfreq) == int(rmem.txfreq): @@ -303,7 +303,7 @@ def set_memory(self, memory): rmem = self._memobj.memory[memory.number - 1] if memory.empty: - rmem.set_raw('\xFF' * (rmem.size() // 8)) + rmem.set_raw(b'\xFF' * (rmem.size() // 8)) return rmem.rxfreq = memory.freq / 10 @@ -311,7 +311,7 @@ def set_memory(self, memory): set_txtone = True if memory.duplex == 'off': for i in range(0, 4): - rmem.txfreq[i].set_raw('\xFF') + rmem.txfreq[i].set_raw(b'\xFF') # If receive only then txtone value should be none self._encode_tone(rmem.txtone, mode='', value=None, pol=None) set_txtone = False @@ -534,7 +534,7 @@ def _enter_programming_mode(self): self._write(self.CMD_IDENTIFY) ident = self._read(8) - if not ident.startswith('SMP558'): + if not ident.startswith(b'SMP558'): LOG.debug(util.hexprint(ident)) raise errors.RadioError('Radio returned unknown ID string') diff --git a/chirp/drivers/leixen.py b/chirp/drivers/leixen.py index 777b2be01..6cf15e71d 100644 --- a/chirp/drivers/leixen.py +++ b/chirp/drivers/leixen.py @@ -472,9 +472,9 @@ def _get_tone(self, mem, _mem): (rx_tmode, rx_tone, rx_pol)) def _is_txinh(self, _mem): - raw_tx = "" + raw_tx = b"" for i in range(0, 4): - raw_tx += _mem.tx_freq[i].get_raw(asbytes=False) + raw_tx += _mem.tx_freq[i].get_raw() return raw_tx == b"\xFF\xFF\xFF\xFF" def _get_memobjs(self, number): @@ -488,7 +488,7 @@ def get_memory(self, number): mem = chirp_common.Memory() mem.number = number - if _mem.get_raw(asbytes=False)[:4] == "\xFF\xFF\xFF\xFF": + if _mem.get_raw()[0:4] == b"\xFF\xFF\xFF\xFF": mem.empty = True return mem @@ -590,7 +590,7 @@ def set_memory(self, mem): if mem.empty: _mem.set_raw(b"\xFF" * 16) return - elif _mem.get_raw(asbytes=False) == (b"\xFF" * 16): + elif _mem.get_raw() == (b"\xFF" * 16): _mem.set_raw(b"\xFF" * 8 + b"\xFF\x00\xFF\x00\xFF\xFE\xF0\xFC") _mem.rx_freq = mem.freq / 10 diff --git a/chirp/drivers/puxing.py b/chirp/drivers/puxing.py index 3645ad688..4a626f3ee 100644 --- a/chirp/drivers/puxing.py +++ b/chirp/drivers/puxing.py @@ -218,12 +218,12 @@ def get_memory(self, number): def _is_empty(): for i in range(0, 4): - if _mem.rx_freq[i].get_raw(asbytes=False) != "\xFF": + if _mem.rx_freq[i].get_raw() != b"\xFF": return False return True def _is_no_tone(field): - return field.get_raw(asbytes=False) in ["\x00\x00", "\xFF\xFF"] + return field.get_raw() in [b"\x00\x00", b"\xFF\xFF"] def _get_dtcs(value): # Upper nibble 0x80 -> DCS, 0xC0 -> Inv. DCS @@ -238,12 +238,12 @@ def _do_dtcs(mem, txfield, rxfield): if int(txfield) < 8000 or int(rxfield) < 8000: raise Exception("Split tone not supported") - if txfield[0].get_raw(asbytes=False) == "\xFF": + if txfield[0].get_raw() == b"\xFF": tp, tx = "N", None else: tp, tx = _get_dtcs(int(txfield)) - if rxfield[0].get_raw(asbytes=False) == "\xFF": + if rxfield[0].get_raw() == b"\xFF": rp, rx = "N", None else: rp, rx = _get_dtcs(int(rxfield)) @@ -303,7 +303,7 @@ def set_memory(self, mem): _nam = self._memobj.names[mem.number - 1] if mem.empty: - wipe_memory(_mem, "\xFF") + wipe_memory(_mem, b"\xFF") return _mem.rx_freq = mem.freq / 10 @@ -316,10 +316,10 @@ def set_memory(self, mem): _mem.skip = mem.skip != "S" _mem.iswide = mem.mode != "NFM" - _mem.rx_tone[0].set_raw("\xFF") - _mem.rx_tone[1].set_raw("\xFF") - _mem.tx_tone[0].set_raw("\xFF") - _mem.tx_tone[1].set_raw("\xFF") + _mem.rx_tone[0].set_raw(b"\xFF") + _mem.rx_tone[1].set_raw(b"\xFF") + _mem.tx_tone[0].set_raw(b"\xFF") + _mem.tx_tone[1].set_raw(b"\xFF") if mem.tmode == "DTCS": _mem.tx_tone = int("%x" % int("%i" % (mem.dtcs), 16)) @@ -329,9 +329,9 @@ def set_memory(self, mem): txm = mem.dtcs_polarity[0] == "N" and 0x80 or 0xC0 rxm = mem.dtcs_polarity[1] == "N" and 0x80 or 0xC0 _mem.tx_tone[1].set_raw( - chr(ord(_mem.tx_tone[1].get_raw(asbytes=False)) | txm)) + bytes([_mem.tx_tone[1].get_raw()[0] | txm])) _mem.rx_tone[1].set_raw( - chr(ord(_mem.rx_tone[1].get_raw(asbytes=False)) | rxm)) + bytes([_mem.rx_tone[1].get_raw()[0] | rxm])) elif mem.tmode: _mem.tx_tone = int(mem.rtone * 10) @@ -461,7 +461,7 @@ def get_memory(self, number): mem = chirp_common.Memory() mem.number = number - if _mem.get_raw(asbytes=False)[0:4] == "\xff\xff\xff\xff": + if _mem.get_raw()[0:4] == b"\xff\xff\xff\xff": mem.empty = True return mem @@ -500,7 +500,7 @@ def set_memory(self, mem): _mem = self._memobj.memory[mem.number-1] if mem.empty: - _mem.set_raw("\xff" * 16) + _mem.set_raw(b"\xff" * 16) return _mem.freq = mem.freq / 10 diff --git a/chirp/drivers/tk8102.py b/chirp/drivers/tk8102.py index 888f015ca..b15836838 100644 --- a/chirp/drivers/tk8102.py +++ b/chirp/drivers/tk8102.py @@ -297,7 +297,7 @@ def get_memory(self, number): mem = chirp_common.Memory() mem.number = number - if _mem.get_raw(asbytes=False)[:4] == "\xFF\xFF\xFF\xFF": + if _mem.get_raw()[0:4] == b"\xFF\xFF\xFF\xFF": mem.empty = True return mem @@ -382,7 +382,7 @@ def set_memory(self, mem): _mem = self._memobj.memory[mem.number - 1] if mem.empty: - _mem.set_raw("\xFF" * 16) + _mem.set_raw(b"\xFF" * 16) return _mem.unknown3[0] = 0x07 @@ -490,7 +490,8 @@ def set_settings(self, settings): setting = element.get_name() if "line" in setting: - value = str(element.value).ljust(32, "\xFF") + value = bytes(str(element.value).encode('ascii')) + value = value.ljust(32, b"\xFF") elif 'key' in setting: value = int(element.value) + 4 else: diff --git a/chirp/drivers/vx2.py b/chirp/drivers/vx2.py index 41ee3f4a9..62f765054 100644 --- a/chirp/drivers/vx2.py +++ b/chirp/drivers/vx2.py @@ -265,7 +265,7 @@ def get_memory_mappings(self, memory): def _wipe_memory(mem): - mem.set_raw("\x00" * (mem.size() // 8)) + mem.set_raw(b"\x00" * (mem.size() // 8)) @directory.register diff --git a/chirp/drivers/vx3.py b/chirp/drivers/vx3.py index 6cff08381..1951163db 100644 --- a/chirp/drivers/vx3.py +++ b/chirp/drivers/vx3.py @@ -342,7 +342,7 @@ def get_memory_mappings(self, memory): def _wipe_memory(mem): - mem.set_raw("\x00" * (mem.size() // 8)) + mem.set_raw(b"\x00" * (mem.size() // 8)) # the following settings are set to match the defaults # on the radio, some of these fields are unknown mem.name = [0xFF for _i in range(0, 6)] diff --git a/chirp/drivers/vx7.py b/chirp/drivers/vx7.py index 01b854343..ebe827354 100644 --- a/chirp/drivers/vx7.py +++ b/chirp/drivers/vx7.py @@ -168,7 +168,7 @@ def get_memory_mappings(self, memory): def _wipe_memory(mem): - mem.set_raw("\x00" * (mem.size() // 8)) + mem.set_raw(b"\x00" * (mem.size() // 8)) mem.unknown1 = 0x05 mem.ones = 0x03 @@ -286,7 +286,7 @@ def get_memory(self, number): mem.power = levels[0] for i in _mem.name: - if i == "\xFF": + if i == 0xFF: break mem.name += CHARSET[i] mem.name = mem.name.rstrip() diff --git a/chirp/drivers/vx8.py b/chirp/drivers/vx8.py index 91608472c..ee93fbffe 100644 --- a/chirp/drivers/vx8.py +++ b/chirp/drivers/vx8.py @@ -496,7 +496,7 @@ def get_memory_mappings(self, memory): def _wipe_memory(mem): - mem.set_raw("\x00" * (mem.size() // 8)) + mem.set_raw(b"\x00" * (mem.size() // 8)) @directory.register @@ -629,7 +629,7 @@ def _checksums(self): @staticmethod def _add_ff_pad(val, length): - return val.ljust(length, "\xFF")[:length] + return val.ljust(length, b"\xFF")[:length] @classmethod def _strip_ff_pads(cls, messages): @@ -720,7 +720,7 @@ def set_memory(self, mem): _mem.power = 0 label = "".join([chr(CHARSET.index(x)) for x in mem.name.rstrip()]) - _mem.label = self._add_ff_pad(label, 16) + _mem.label = self._add_ff_pad(label.encode('ascii'), 16) # We only speak English here in chirpville _mem.charsetbits[0] = 0x00 _mem.charsetbits[1] = 0x00 @@ -1338,7 +1338,7 @@ def _apply_callsign(cls, callsign, obj, default_ssid=None): ssid = int(ssid) % 16 except ValueError: ssid = default_ssid - setattr(obj, "callsign", cls._add_ff_pad(callsign, 6)) + setattr(obj, "callsign", cls._add_ff_pad(callsign.encode('ascii'), 6)) if ssid is not None: setattr(obj, "ssid", ssid) @@ -1380,8 +1380,8 @@ def apply_digi_path(self, setting, obj): def apply_ff_padded_string(cls, setting, obj): # FF pad. val = setting.value.get_value() - max_len = getattr(obj, "padded_string").size() / 8 - val = str(val).rstrip() + max_len = getattr(obj, "padded_string").size() // 8 + val = str(val).rstrip().encode('ascii') setattr(obj, "padded_string", cls._add_ff_pad(val, max_len)) @classmethod @@ -1440,12 +1440,9 @@ def set_settings(self, settings): def apply_ff_padded_yaesu(cls, setting, obj): # FF pad yaesus custom string format. rawval = setting.value.get_value() - max_len = getattr(obj, "padded_yaesu").size() / 8 - rawval = str(rawval).rstrip() - val = [CHARSET.index(x) for x in rawval] - for x in range(len(val), max_len): - val.append(0xFF) - obj.padded_yaesu = val + max_len = getattr(obj, "padded_yaesu").size() // 8 + rawval = str(rawval).rstrip().translate(CHARSET).encode('ascii') + obj.padded_yaesu = list(rawval.ljust(max_len, b'\xFF')[0:max_len]) def apply_volume(cls, setting, vfo): val = setting.value.get_value() diff --git a/tests/Python3_Driver_Testing.md b/tests/Python3_Driver_Testing.md index 6e79c32d5..d7562d08f 100644 --- a/tests/Python3_Driver_Testing.md +++ b/tests/Python3_Driver_Testing.md @@ -73,7 +73,7 @@ | Baojie_BJ-318 | [@KC9HI](https://github.com/KC9HI) | 2-Jan-2023 | Yes | 0.10% | | Baojie_BJ-9900 | | | | 0.02% | | Baojie_BJ-UV55 | | | Yes | 0.03% | -| Boblov_X3Plus | | | | 0.02% | +| Boblov_X3Plus | | | Yes | 0.02% | | CRT_Micron_UV | [Implied by Retevis_RT95](#user-content-Retevis_RT95) | 13-Nov-2022 | Yes | 0.08% | | CRT_Micron_UV_V2 | [Implied by Retevis_RT95](#user-content-Retevis_RT95) | 13-Nov-2022 | Yes | 0.10% | | Cignus_XTR-5 | [Implied by Radioddity_GS-5B](#user-content-Radioddity_GS-5B) | 17-Mar-2023 | Yes | | @@ -429,7 +429,7 @@ **Tested:** 87% (369/53) (93% of usage stats) -**Byte clean:** 90% (382/40) +**Byte clean:** 90% (383/39) ## Meaning of this testing