Script 'mail_helper' called by obssrc Hello community, here is the log from the commit of package chirp for openSUSE:Factory checked in at 2025-08-09 19:59:35 ++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++ Comparing /work/SRC/openSUSE:Factory/chirp (Old) and /work/SRC/openSUSE:Factory/.chirp.new.1085 (New) ++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++
Package is "chirp" Sat Aug 9 19:59:35 2025 rev:33 rq:1298456 version:20250808 Changes: -------- --- /work/SRC/openSUSE:Factory/chirp/chirp.changes 2025-08-03 13:37:59.800909433 +0200 +++ /work/SRC/openSUSE:Factory/.chirp.new.1085/chirp.changes 2025-08-09 20:06:00.205739463 +0200 @@ -1,0 +2,8 @@ +Fri Aug 8 20:59:20 UTC 2025 - Andreas Stieger <andreas.stie...@gmx.de> + +- Update to version 20250808: + * Add Support for Retevis H777 V4.0 + * uvk5: Fix band limits + * Remove legacy radio detection module + +------------------------------------------------------------------- Old: ---- chirp-20250801.obscpio New: ---- chirp-20250808.obscpio ++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++ Other differences: ------------------ ++++++ chirp.spec ++++++ --- /var/tmp/diff_new_pack.dX0eLa/_old 2025-08-09 20:06:01.309785348 +0200 +++ /var/tmp/diff_new_pack.dX0eLa/_new 2025-08-09 20:06:01.313785514 +0200 @@ -19,7 +19,7 @@ %define pythons python3 Name: chirp -Version: 20250801 +Version: 20250808 Release: 0 Summary: Tool for programming amateur radio sets License: GPL-3.0-only ++++++ _service ++++++ --- /var/tmp/diff_new_pack.dX0eLa/_old 2025-08-09 20:06:01.373788008 +0200 +++ /var/tmp/diff_new_pack.dX0eLa/_new 2025-08-09 20:06:01.381788340 +0200 @@ -4,8 +4,8 @@ <param name="scm">git</param> <param name="changesgenerate">enable</param> <param name="filename">chirp</param> - <param name="versionformat">20250801</param> - <param name="revision">477a28489d173e582f859e6f7242bbbeb840fd60</param> + <param name="versionformat">20250808</param> + <param name="revision">600a65c103b6c44c1d0d28f5a98d3025aebacd36</param> </service> <service mode="manual" name="set_version"/> <service name="tar" mode="buildtime"/> ++++++ _servicedata ++++++ --- /var/tmp/diff_new_pack.dX0eLa/_old 2025-08-09 20:06:01.421790003 +0200 +++ /var/tmp/diff_new_pack.dX0eLa/_new 2025-08-09 20:06:01.425790169 +0200 @@ -1,7 +1,7 @@ <servicedata> <service name="tar_scm"> <param name="url">https://github.com/kk7ds/chirp.git</param> - <param name="changesrevision">477a28489d173e582f859e6f7242bbbeb840fd60</param> + <param name="changesrevision">600a65c103b6c44c1d0d28f5a98d3025aebacd36</param> </service> </servicedata> (No newline at EOF) ++++++ chirp-20250801.obscpio -> chirp-20250808.obscpio ++++++ diff -urN '--exclude=CVS' '--exclude=.cvsignore' '--exclude=.svn' '--exclude=.svnignore' old/chirp-20250801/chirp/detect.py new/chirp-20250808/chirp/detect.py --- old/chirp-20250801/chirp/detect.py 2025-08-01 00:30:55.000000000 +0200 +++ new/chirp-20250808/chirp/detect.py 1970-01-01 01:00:00.000000000 +0100 @@ -1,127 +0,0 @@ -# Copyright 2010 Dan Smith <dsm...@danplanet.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 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/>. - -import serial -import logging - -from chirp import chirp_common, errors, directory -from chirp.drivers import ic9x_ll, icf, kenwood_live, icomciv - -LOG = logging.getLogger(__name__) - - -class DetectorRadio(chirp_common.Radio): - """Minimal radio for model detection""" - - def get_payload(self, data, raw, checksum): - return data - - -def _icom_model_data_to_rclass(md): - for _rtype, rclass in list(directory.DRV_TO_RADIO.items()): - if rclass.VENDOR != "Icom": - continue - if not hasattr(rclass, 'get_model') or not rclass.get_model(): - continue - if rclass.get_model()[:4] == md[:4]: - return rclass - - raise errors.RadioError("Unknown radio type %02x%02x%02x%02x" % - (ord(md[0]), ord(md[1]), ord(md[2]), ord(md[3]))) - - -def _detect_icom_radio(ser): - # ICOM VHF/UHF Clone-type radios @ 9600 baud - - try: - ser.baudrate = 9600 - md = icf.get_model_data(DetectorRadio(ser)) - return _icom_model_data_to_rclass(md) - except errors.RadioError as e: - LOG.error("_detect_icom_radio: %s", e) - - # ICOM IC-91/92 Live-mode radios @ 4800/38400 baud - - ser.baudrate = 4800 - try: - ic9x_ll.send_magic(ser) - return _icom_model_data_to_rclass("ic9x") - except errors.RadioError: - pass - - # ICOM CI/V Radios @ various bauds - - for rate in [9600, 4800, 19200]: - try: - ser.baudrate = rate - return icomciv.probe_model(ser) - except errors.RadioError: - pass - - ser.close() - - raise errors.RadioError("Unable to get radio model") - - -def detect_icom_radio(port): - """Detect which Icom model is connected to @port""" - if '://' in port: - ser = serial.serial_for_url(port, do_not_open=True) - ser.timeout = 0.5 - ser.open() - else: - ser = serial.Serial(port=port, timeout=0.5) - - try: - result = _detect_icom_radio(ser) - except Exception: - ser.close() - raise - - ser.close() - - LOG.info("Auto-detected %s %s on %s" % - (result.VENDOR, result.MODEL, port)) - - return result - - -def detect_kenwoodlive_radio(port): - """Detect which Kenwood model is connected to @port""" - if '://' in port: - ser = serial.serial_for_url(port, do_not_open=True) - ser.timeout = 0.5 - ser.open() - else: - ser = serial.Serial(port=port, baudrate=9600, timeout=0.5) - - r_id = kenwood_live.get_id(ser) - ser.close() - - models = {} - for rclass in list(directory.DRV_TO_RADIO.values()): - if rclass.VENDOR == "Kenwood": - models[rclass.MODEL] = rclass - - if r_id in list(models.keys()): - return models[r_id] - else: - raise errors.RadioError("Unsupported model `%s'" % r_id) - - -DETECT_FUNCTIONS = { - "Icom": detect_icom_radio, - "Kenwood": detect_kenwoodlive_radio, -} diff -urN '--exclude=CVS' '--exclude=.cvsignore' '--exclude=.svn' '--exclude=.svnignore' old/chirp-20250801/chirp/drivers/h777.py new/chirp-20250808/chirp/drivers/h777.py --- old/chirp-20250801/chirp/drivers/h777.py 2025-08-01 00:30:55.000000000 +0200 +++ new/chirp-20250808/chirp/drivers/h777.py 2025-08-06 23:43:59.000000000 +0200 @@ -102,22 +102,24 @@ DTCS_REV_FLAG = 0x40 -def _h777_enter_programming_mode(radio): - serial = radio.pipe +def _h777_enter_programming_mode(serial, radio_cls): # increase default timeout from .25 to .5 for all serial communications serial.timeout = 0.5 try: serial.write(b"\x02") time.sleep(0.1) - serial.write(radio.PROGRAM_CMD) + serial.write(radio_cls.PROGRAM_CMD) ack = serial.read(1) - except: + except Exception as e: + LOG.warning('Failed to send program command: %s', e) raise errors.RadioError("Error communicating with radio") if not ack: - raise errors.RadioError("No response from radio") + raise errors.RadioError("No response from radio to program command") elif ack != CMD_ACK: + LOG.warning('Ack from program command was %r, expected %r', + ack, CMD_ACK) raise errors.RadioError("Radio refused to enter programming mode") try: @@ -127,37 +129,29 @@ # version data and the last three bytes. We need to raise the # timeout so that the read doesn't finish early. ident = serial.read(8) - except: + except Exception as e: + LOG.warning('Failed to read ident: %s', e) raise errors.RadioError("Error communicating with radio") - # check if ident is OK - itis = False - for fp in radio.IDENT: - if fp in ident: - # got it! - itis = True - - break - - if itis is False: - LOG.debug("Incorrect model ID, got this:\n\n" + util.hexprint(ident)) - raise errors.RadioError("Radio identification failed.") + if ident: + LOG.info('Radio identified with:\n%s', util.hexprint(ident)) + try: + serial.write(CMD_ACK) + ack = serial.read(1) + if ack != CMD_ACK: + raise errors.RadioError("Bad ACK after reading ident") + except: + raise errors.RadioError('No ACK after reading ident') + return ident - 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") + raise errors.RadioError('No identification received from radio') -def _h777_exit_programming_mode(radio): - serial = radio.pipe +def _h777_exit_programming_mode(serial): try: serial.write(b"E") - except: + except Exception as e: + LOG.warning('Failed to send exit command: %s', e) raise errors.RadioError("Radio refused to exit programming mode") @@ -172,6 +166,8 @@ serial.write(cmd) response = serial.read(4 + BLOCK_SIZE) if response[:4] != expectedresponse: + LOG.warning('Got %r expected %r' % (response[:4], + expectedresponse)) raise Exception("Error reading block %04x." % (block_addr)) block_data = response[4:] @@ -208,9 +204,26 @@ "to radio at %04x" % block_addr) +def _h777_enter_single_programming_mode(radio): + ident = _h777_enter_programming_mode(radio.pipe, radio.__class__) + if not ident: + raise errors.RadioError('Radio did not identify') + if not any(rc_ident in ident for rc_ident in radio.IDENT): + LOG.warning('Expected %s for %s but got:\n%s', + radio.IDENT, radio.__class__.__name__, + util.hexprint(ident)) + raise errors.RadioError('Incorrect model') + + def do_download(radio): LOG.debug("download") - _h777_enter_programming_mode(radio) + + if len(radio.detected_models()) <= 1: + LOG.debug('Entering programming mode for %s', radio.__class__.__name__) + _h777_enter_single_programming_mode(radio) + else: + LOG.debug('Already in programming mode for %s', + radio.__class__.__name__) data = b"" @@ -230,7 +243,7 @@ LOG.debug("Address: %04x" % addr) LOG.debug(util.hexprint(block)) - _h777_exit_programming_mode(radio) + _h777_exit_programming_mode(radio.pipe) return memmap.MemoryMapBytes(data) @@ -239,7 +252,7 @@ status = chirp_common.Status() status.msg = "Uploading to radio" - _h777_enter_programming_mode(radio) + _h777_enter_single_programming_mode(radio) status.cur = 0 status.max = radio._memsize @@ -250,7 +263,7 @@ radio.status_fn(status) _h777_write_block(radio, addr, BLOCK_SIZE) - _h777_exit_programming_mode(radio) + _h777_exit_programming_mode(radio.pipe) class ArcshellAR5(chirp_common.Alias): @@ -283,11 +296,6 @@ MODEL = 'TW-325' -class RetevisH777Alias(chirp_common.Alias): - VENDOR = 'Retevis' - MODEL = 'H777' - - @directory.register class H777Radio(chirp_common.CloneModeRadio): """HST H-777""" @@ -305,7 +313,7 @@ VALID_BANDS = (400000000, 490000000) MAX_VOXLEVEL = 5 ALIASES = [ArcshellAR5, ArcshellAR6, GV8SAlias, GV9SAlias, A8SAlias, - TenwayTW325Alias, RetevisH777Alias] + TenwayTW325Alias] SIDEKEYFUNCTION_LIST = ["Off", "Monitor", "Transmit Power", "Alarm"] SCANMODE_LIST = ["Carrier", "Time"] @@ -620,6 +628,37 @@ raise +@directory.register +class RetevisH777(H777Radio): + VENDOR = 'Retevis' + MODEL = 'H777' + ALIASES = [] + + @classmethod + def detect_from_serial(cls, pipe): + cls_to_ident = {rc: rc.IDENT for rc in cls.detected_models()} + for ident_rclass in reversed(cls.detected_models()): + ident = _h777_enter_programming_mode(pipe, ident_rclass) + for rclass, idents in cls_to_ident.items(): + if any(rc_ident in ident for rc_ident in idents): + LOG.debug('Detected %s', rclass.__name__) + return rclass + LOG.debug('Ident was %r, idents were %r', ident, idents) + + LOG.info('Did not identify radio as %s', rclass.__name__) + time.sleep(0.5) + _h777_exit_programming_mode(pipe) + time.sleep(1) + + raise errors.RadioError('Failed to identify with radio.') + + @classmethod + def match_model(cls, filedata, filename): + # This radio has always been post-metadata, so never do + # old-school detection + return False + + class H777TestCase(unittest.TestCase): def setUp(self): diff -urN '--exclude=CVS' '--exclude=.cvsignore' '--exclude=.svn' '--exclude=.svnignore' old/chirp-20250801/chirp/drivers/retevis_h777v4.py new/chirp-20250808/chirp/drivers/retevis_h777v4.py --- old/chirp-20250801/chirp/drivers/retevis_h777v4.py 1970-01-01 01:00:00.000000000 +0100 +++ new/chirp-20250808/chirp/drivers/retevis_h777v4.py 2025-08-06 23:43:59.000000000 +0200 @@ -0,0 +1,505 @@ +# Copyright 2025 Jim Unroe <rock.un...@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 logging +import struct + +from chirp import ( + bitwise, + chirp_common, + directory, + errors, + memmap, + util, +) +from chirp.drivers import h777 +from chirp.settings import ( + MemSetting, + RadioSettingGroup, + RadioSettings, + RadioSettingValueBoolean, + RadioSettingValueInvertedBoolean, + RadioSettingValueInteger, + RadioSettingValueList, +) + +LOG = logging.getLogger(__name__) + +MEM_FORMAT = """ +struct { + ul32 rxfreq; // 0-3 + ul32 txfreq; // 4-7 + lbcd rx_tone[2]; // 8-9 + lbcd tx_tone[2]; // A-B + u8 unknown0:2, // C + scramb:1, // Scramble 0 = Off, 1 = On + scanadd:1, // Scan Add 0 = Scan, 1 = Skip + ishighpower:1, // Power Level 0 = Low, 1 = High + narrow:1, // Bandwidth 0 = Wide, 1 = Narrow + unknown1:1, // + bcl:1; // Busy Channel Locklut 0 = On, 1 = Off +} memory[16]; + +struct { + u8 codesw:1, // 00D0 Code Switch + scanm:1, // Scan Mode + voxs:1, // VOX Switch + roger:1, // Roger + voice:1, // Voice Annunciation + unknown_0:1, + save:1, // Battery Save + beep:1; // Beep Tone + u8 squelch; // 00D1 Squelch Level + u8 tot; // 00D2 Time-out Timer + u8 vox; // 00D3 VOX Level + u8 voxd; // 00D4 Vox Delay + u8 unknown_1; + u8 unknown_2; + u8 skey2; // 00D7 Side Key 2 (long) + u8 unknown_3[5]; // 00D8 - 00DC + u8 password[6]; // 00DD - 00E2 Password +} settings; +""" + + +CMD_ACK = b"\x06" + +DTCS = tuple(sorted(chirp_common.DTCS_CODES + (645,))) + +OFF1TO9_LIST = ["Off"] + ["%s" % x for x in range(1, 10)] +SCANM_LIST = ["Carrier", "Time"] +SKEY2_LIST = ["Off", "VOX", "Power", "Scan"] +TIMEOUTTIMER_LIST = ["Off"] + ["%s seconds" % x for x in range(30, 210, 30)] +VOICE_LIST = ["Off", "English"] +VOXD_LIST = ["0.5", "1.0", "1.5", "2.0", "2.5", "3.0"] + + +def _read_block(radio, block_addr, block_size): + serial = radio.pipe + + cmd = struct.pack(">cHb", b'R', block_addr, block_size) + expectedresponse = b"W" + cmd[1:] + LOG.debug("Reading block %04x..." % (block_addr)) + + try: + serial.write(cmd) + response = serial.read(4 + block_size) + if response[:4] != expectedresponse: + raise Exception("Error reading block %04x." % (block_addr)) + + block_data = response[4:] + except Exception: + raise errors.RadioError("Failed to read block at %04x" % block_addr) + + return block_data + + +def _write_block(radio, block_addr, block_size): + serial = radio.pipe + + cmd = struct.pack(">cHb", b'W', block_addr, block_size) + data = radio.get_mmap()[block_addr:block_addr + block_size] + + LOG.debug("Writing Data:") + LOG.debug(util.hexprint(cmd + data)) + + try: + serial.write(cmd + data) + if serial.read(1) != CMD_ACK: + raise Exception("No ACK") + except Exception: + raise errors.RadioError("Failed to send block " + "to radio at %04x" % block_addr) + + +def do_download(radio): + LOG.debug("download") + + data = b"" + + status = chirp_common.Status() + status.msg = "Cloning from radio" + + status.cur = 0 + status.max = radio._memsize + + for addr in range(0, radio._memsize, radio.BLOCK_SIZE): + status.cur = addr + radio.BLOCK_SIZE + radio.status_fn(status) + + block = _read_block(radio, addr, radio.BLOCK_SIZE) + data += block + + LOG.debug("Address: %04x" % addr) + LOG.debug(util.hexprint(block)) + + return memmap.MemoryMapBytes(data) + + +def do_upload(radio): + status = chirp_common.Status() + status.msg = "Uploading to radio" + + h777._h777_enter_single_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, radio.BLOCK_SIZE_UP): + status.cur = addr + radio.BLOCK_SIZE_UP + radio.status_fn(status) + _write_block(radio, addr, radio.BLOCK_SIZE_UP) + + +class H777V4BaseRadio(chirp_common.CloneModeRadio): + """RETEVIS H777 V4 Base""" + VENDOR = "Retevis" + MODEL = "H777 V4 Base" + BAUD_RATE = 9600 + BLOCK_SIZE = 0x0D + BLOCK_SIZE_UP = 0x0D + + POWER_LEVELS = [chirp_common.PowerLevel("Low", watts=0.50), + chirp_common.PowerLevel("High", watts=2.00) + ] + + PROGRAM_CMD = b"C777HAM" + _ranges = [ + (0x0000, 0x00EA), + ] + _memsize = 0x00EA + + 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.has_name = False + 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 = self.POWER_LEVELS + rf.valid_duplexes = ["", "-", "+", "split", "off"] + rf.valid_modes = ["FM", "NFM"] # 25 kHz, 12.5 kHz. + rf.valid_dtcs_codes = DTCS + rf.memory_bounds = (1, 16) + rf.valid_tuning_steps = [2.5, 5., 6.25, 10., 12.5, 20., 25., 50.] + rf.valid_bands = [(400000000, 520000000)] + 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 Exception: + # 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 errors.RadioError: + raise + except Exception: + # 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 _decode_tone(self, toneval, txrx, chnum): + pol = "N" + rawval = (toneval[1].get_bits(0xFF) << 8) | toneval[0].get_bits(0xFF) + + if toneval[0].get_bits(0xFF) == 0xFF: + mode = "" + val = 0 + elif toneval[1].get_bits(0xC0) == 0xC0: + val = int(f'{rawval & 0x1FF:o}') + if val in DTCS: + mode = "DTCS" + pol = "R" + else: + LOG.error('unknown value: CH# %i DTCS %fR %s' % ( + chnum, val, txrx)) + mode = "" + val = 0 + elif toneval[1].get_bits(0x80): + val = int(f'{rawval & 0x1FF:o}') + if val in DTCS: + mode = "DTCS" + else: + LOG.error('unknown value: CH# %i DTCS %fN %s' % ( + chnum, val, txrx)) + mode = "" + val = 0 + else: + val = int(toneval) / 10.0 + if val in chirp_common.TONES: + mode = "Tone" + else: + LOG.error('unknown value: CH# %i CTCSS %fHZ %s' % ( + chnum, val, txrx)) + mode = "" + val = 0 + + return mode, val, pol + + def _encode_tone(self, _toneval, mode, val, pol): + toneval = 0 + if mode == "Tone": + toneval = int("%i" % (val * 10), 16) + elif mode == "DTCS": + toneval = int('%i' % val, 8) + toneval |= 0x8000 + if pol == "R": + toneval |= 0x4000 + else: + toneval = 0xFFFF + + _toneval[0].set_raw(toneval & 0xFF) + _toneval[1].set_raw((toneval >> 8) & 0xFF) + + def get_raw_memory(self, number): + return repr(self._memobj.memory[number - 1]) + + def get_memory(self, number): + _mem = self._memobj.memory[number - 1] + + mem = chirp_common.Memory() + mem.number = number + + if _mem.get_raw()[:1] == b"\xFF": + mem.empty = True + return mem + + mem.freq = int(_mem.rxfreq) * 10 + + if _mem.txfreq == 0xFFFFFFFF: + # TX freq not set + mem.duplex = "off" + mem.offset = 0 + elif abs(int(_mem.rxfreq) * 10 - int(_mem.txfreq) * 10) > 25000000: + mem.duplex = "split" + mem.offset = int(_mem.txfreq) * 10 + elif int(_mem.rxfreq) == int(_mem.txfreq): + mem.duplex = "" + mem.offset = 0 + else: + mem.duplex = int(_mem.rxfreq) > int(_mem.txfreq) \ + and "-" or "+" + mem.offset = abs(int(_mem.rxfreq) - int(_mem.txfreq)) * 10 + + txmode, txval, txpol = self._decode_tone(_mem.tx_tone, 'TX', number) + rxmode, rxval, rxpol = self._decode_tone(_mem.rx_tone, 'RX', number) + + chirp_common.split_tone_decode(mem, + (txmode, txval, txpol), + (rxmode, rxval, rxpol)) + + if _mem.scanadd: + mem.skip = "S" + + mem.power = self.POWER_LEVELS[_mem.ishighpower] + + mem.mode = _mem.narrow and "NFM" or "FM" + + mem.extra = RadioSettingGroup("Extra", "extra") + + # BCL (Busy Channel Lockout) + rs = RadioSettingValueInvertedBoolean(not bool(_mem.bcl)) + rset = MemSetting("bcl", "BCL", rs) + rset.set_doc("Busy Channel Lockout") + mem.extra.append(rset) + + # Scramble + rs = RadioSettingValueInvertedBoolean(not bool(_mem.scramb)) + rset = MemSetting("scramb", "Scramble", rs) + rset.set_doc("Frequency inversion Scramble") + mem.extra.append(rset) + + return mem + + def set_memory(self, mem): + _mem = self._memobj.memory[mem.number - 1] + + _mem.set_raw(b"\xff" * 13) + if mem.empty: + return + + _mem.rxfreq = mem.freq / 10 + if mem.duplex == "off": + _mem.txfreq = 0xFFFFFFFF + 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.rxfreq + + (txmode, txval, txpol), (rxmode, rxval, rxpol) = \ + chirp_common.split_tone_encode(mem) + + self._encode_tone(_mem.tx_tone, txmode, txval, txpol) + self._encode_tone(_mem.rx_tone, rxmode, rxval, rxpol) + + _mem.scanadd = mem.skip == "S" + _mem.narrow = mem.mode == "NFM" + + _mem.ishighpower = mem.power == self.POWER_LEVELS[1] + + # resetting unknowns, this have to be set by hand + _mem.unknown0 = 3 + _mem.unknown1 = 1 + + for setting in mem.extra: + setting.apply_to_memobj(_mem) + + def get_settings(self): + _settings = self._memobj.settings + basic = RadioSettingGroup("basic", "Basic Settings") + group = RadioSettings(basic) + + # Squelch + rs = RadioSettingValueInteger(0, 9, _settings.squelch) + rset = MemSetting("squelch", "Squelch", rs) + rset.set_doc("Squelch Level: 0, 1, 2, 3, 4, 5, 6, 7, 8, 9") + basic.append(rset) + + # Time Out Timer + rs = RadioSettingValueList(TIMEOUTTIMER_LIST, + current_index=_settings.tot) + rset = MemSetting("tot", "Time-out Timer", rs) + rset.set_doc("TX Time-out Timer: Off, 30, 60, 90, 120, 150," + + " 180 seconds") + basic.append(rset) + + # Vox Level + rs = RadioSettingValueList(OFF1TO9_LIST, current_index=_settings.vox) + rset = MemSetting("vox", "VOX Level", rs) + rset.set_doc("VOX Level: Off, 1, 2, 3, 4, 5, 6, 7, 8, 9") + basic.append(rset) + + # Vox Delay + rs = RadioSettingValueList(VOXD_LIST, + current_index=_settings.voxd) + rset = MemSetting("voxd", "Vox Delay", rs) + rset.set_doc("VOX Delay: 0.5, 1.0, 1.5, 2.0, 2.5, 3.0 seconds") + basic.append(rset) + + # Scan Mode + rs = RadioSettingValueList(SCANM_LIST, current_index=_settings.scanm) + rset = MemSetting("scanm", "Scan Mode", rs) + rset.set_doc("Scan Mode: Carrier, Time") + basic.append(rset) + + # Voice Annunciation + rs = RadioSettingValueList(VOICE_LIST, + current_index=_settings.voice) + rset = MemSetting("voice", "Voice", rs) + rset.set_doc("Voice Prompts: Off, English") + basic.append(rset) + + # Side Key 2 (long) + rs = RadioSettingValueList(SKEY2_LIST, + current_index=_settings.skey2) + rset = MemSetting("skey2", "Side Key 2", rs) + rset.set_doc("Side Key 2 (long press): Off, VOX, Power, Scan") + basic.append(rset) + + # Code Switch + rs = RadioSettingValueBoolean(_settings.codesw) + rset = MemSetting("codesw", "Code Switch", rs) + rset.set_doc("Code Switch: Off, Enabled") + basic.append(rset) + + # Battery Save + rs = RadioSettingValueBoolean(_settings.save) + rset = MemSetting("save", "Battery Save", rs) + rset.set_doc("Battery Save: Off, Enabled") + basic.append(rset) + + # Beep Tone + rs = RadioSettingValueBoolean(_settings.beep) + rset = MemSetting("beep", "Beep", rs) + rset.set_doc("Beep Prompt: Off, Enabled") + basic.append(rset) + + # VOX Switch + rs = RadioSettingValueBoolean(_settings.voxs) + rset = MemSetting("voxs", "VOX Switch", rs) + rset.set_doc("VOX Switch: Off, Enabled") + basic.append(rset) + + # Roger + rs = RadioSettingValueBoolean(_settings.roger) + rset = MemSetting("roger", "Roger", rs) + rset.set_doc("Roger: Off, Enabled") + basic.append(rset) + + return group + + def set_settings(self, settings): + others = settings.apply_to(self._memobj.settings) + if others: + LOG.error('Did not apply %s' % others) + + @classmethod + def match_model(cls, filedata, filename): + # This radio has always been post-metadata, so never do + # old-school detection + return False + + +@directory.register +@directory.detected_by(h777.RetevisH777) +class H777V4(H777V4BaseRadio): + """RETEVIS H777V4""" + VENDOR = "Retevis" + MODEL = "H777" + VARIANT = 'V4' + IDENT = [b'\x00' * 6, b'\xFF' * 6] + + # SKU #: A9294A (sold as FRS radio but supports full band TX/RX) + # Serial #: 2412R777XXXXXXX + + # SKU #: A9294C (same as SKU #: A9294A (2 pack)) + # Serial #: 2406R777XXXXXXX + + # SKU #: A9294B (sold as PMR radio but supports full band TX/RX) + # Serial #: 2412R777XXXXXXX + + # SKU #: A9294D (same as SKU #: A9294B (2 pack)) + # Serial #: 24XXR777XXXXXXX diff -urN '--exclude=CVS' '--exclude=.cvsignore' '--exclude=.svn' '--exclude=.svnignore' old/chirp-20250801/chirp/drivers/uvk5.py new/chirp-20250808/chirp/drivers/uvk5.py --- old/chirp-20250801/chirp/drivers/uvk5.py 2025-08-01 00:30:55.000000000 +0200 +++ new/chirp-20250808/chirp/drivers/uvk5.py 2025-08-06 23:43:59.000000000 +0200 @@ -295,9 +295,9 @@ # bands supported by the UV-K5 BANDS = { 0: [50.0, 76.0], - 1: [108.0, 135.9999], - 2: [136.0, 199.9990], - 3: [200.0, 299.9999], + 1: [108.0, 136.9999], + 2: [137.0, 173.9999], + 3: [174.0, 349.9999], 4: [350.0, 399.9999], 5: [400.0, 469.9999], 6: [470.0, 600.0] @@ -306,9 +306,9 @@ # for radios with modified firmware: BANDS_NOLIMITS = { 0: [18.0, 76.0], - 1: [108.0, 135.9999], - 2: [136.0, 199.9990], - 3: [200.0, 299.9999], + 1: [108.0, 136.9999], + 2: [137.0, 173.9999], + 3: [174.0, 349.9999], 4: [350.0, 399.9999], 5: [400.0, 469.9999], 6: [470.0, 1300.0] @@ -737,7 +737,8 @@ # Return a raw representation of the memory object, which # is very helpful for development def get_raw_memory(self, number): - return repr(self._memobj.channel[number-1]) + return '\n'.join([repr(self._memobj.channel[number-1]), + repr(self._memobj.channel_attributes[number-1])]) def _find_band(self, hz): return _find_band(self._expanded_limits, hz) diff -urN '--exclude=CVS' '--exclude=.cvsignore' '--exclude=.svn' '--exclude=.svnignore' old/chirp-20250801/chirp/locale/pl.po new/chirp-20250808/chirp/locale/pl.po --- old/chirp-20250801/chirp/locale/pl.po 2025-08-01 00:30:55.000000000 +0200 +++ new/chirp-20250808/chirp/locale/pl.po 2025-08-06 23:43:59.000000000 +0200 @@ -1518,16 +1518,16 @@ #: ../drivers/ga510.py:1072 ../drivers/baofeng_uv17Pro.py:1244 #: ../drivers/tdh8.py:2609 ../drivers/mml_jc8810.py:1393 msgid "Frequency in this range must not be AM mode" -msgstr "" +msgstr "Częstotliwość w tym zakresie nie może używać trybu AM" #: ../drivers/ga510.py:1065 ../drivers/baofeng_uv17Pro.py:1240 #: ../drivers/tdh8.py:2606 ../drivers/mml_jc8810.py:1390 msgid "Frequency in this range requires AM mode" -msgstr "" +msgstr "Częstotliwość w tym zakresie musi używać trybu AM" #: ../drivers/tdh8.py:2613 msgid "Frequency outside TX bands must be duplex=off" -msgstr "" +msgstr "Częstotliwość poza pasmami TX musi używać duplex=off" #: ../wxui/query_sources.py:354 ../wxui/query_sources.py:434 #: ../wxui/query_sources.py:534 @@ -1595,7 +1595,7 @@ #: ../wxui/main.py:1482 msgid "Import messages" -msgstr "" +msgstr "Importuj wiadomości" #: ../wxui/main.py:1476 msgid "Import not recommended" @@ -1749,15 +1749,15 @@ #: ../wxui/bugreport.py:326 msgid "Login failed: Check your username and password" -msgstr "" +msgstr "Błąd logowania: Sprawdź nazwę użytkownika i hasło" #: ../drivers/uvk5.py:1799 msgid "Logo string 1 (12 characters)" -msgstr "" +msgstr "Napis powitalny 1 (12 znaków)" #: ../drivers/uvk5.py:1806 msgid "Logo string 2 (12 characters)" -msgstr "" +msgstr "Napis powitalny 2 (12 znaków)" #: ../wxui/query_sources.py:97 ../wxui/query_sources.py:374 #: ../wxui/query_sources.py:673 ../wxui/query_sources.py:825 @@ -1767,7 +1767,7 @@ #: ../wxui/memedit.py:1955 #, python-format msgid "Manual edit of memory %i" -msgstr "" +msgstr "Ręczna edycja pamięci %i" #: ../wxui/main.py:202 msgid "Memories" @@ -1775,7 +1775,7 @@ #: ../drivers/uvk5.py:2118 msgid "Memories are read-only due to unsupported firmware version" -msgstr "" +msgstr "Pamięć tylko do odczytu z powodu nie wspieranej wersji firmware" #: ../wxui/memedit.py:1984 #, python-format @@ -1960,7 +1960,7 @@ #: ../wxui/query_sources.py:401 msgid "Open repeaters only" -msgstr "" +msgstr "Otórz tylko przemienniki" #: ../wxui/main.py:648 msgid "Open stock config directory" @@ -2021,7 +2021,7 @@ #: ../wxui/bugreport.py:259 msgid "Password" -msgstr "" +msgstr "Hasło" #: ../wxui/memedit.py:2120 msgid "Paste" @@ -2140,12 +2140,12 @@ #: ../wxui/memquery.py:139 #, fuzzy msgid "Property Name" -msgstr "Właściwości" +msgstr "Nazwa" #: ../wxui/memquery.py:147 #, fuzzy msgid "Property Value" -msgstr "Właściwości" +msgstr "Wartość" #: ../wxui/query_sources.py:831 msgid "QTH Locator" @@ -2162,7 +2162,7 @@ #: ../wxui/memquery.py:330 msgid "Query string is invalid" -msgstr "" +msgstr "Błędne zapytanie" #: ../wxui/memquery.py:186 #, fuzzy @@ -2261,11 +2261,11 @@ #: ../wxui/clone.py:616 msgid "Remove" -msgstr "" +msgstr "Usuń" #: ../wxui/clone.py:617 msgid "Remove selected model from list" -msgstr "" +msgstr "Usuń wybrany model z listy" #: ../wxui/bankedit.py:205 msgid "Rename bank" @@ -2340,7 +2340,7 @@ #: ../drivers/uvk5.py:893 msgid "Scanlists" -msgstr "" +msgstr "Lista skanowania" #: ../drivers/uvk5.py:889 ../drivers/ksun_m6.py:418 msgid "Scrambler" @@ -2548,7 +2548,7 @@ #: ../wxui/memedit.py:2146 msgid "This Memory" -msgstr "Tą pamięć" +msgstr "Tę pamięć" #: ../drivers/id5100.py:332 msgid "This driver has been tested with v3 of the ID-5100. If your radio is not fully updated please help by opening a bug report with a debug log so we can add support for the other revisions." @@ -2602,11 +2602,11 @@ #: ../wxui/memedit.py:2150 msgid "This memory and shift all up" -msgstr "Tą pamięć i przeusń wszystkie wyżej" +msgstr "Tę pamięć i przeusń wszystkie wyżej" #: ../wxui/memedit.py:2148 msgid "This memory and shift block up" -msgstr "Tą pamięć i przesuń blok wyżej" +msgstr "Tę pamięć i przesuń blok wyżej" #: ../drivers/uvk5_egzumer.py:1468 msgid "This option may break your radio! Each radio has a unique set of calibration data and uploading the data from the image will cause physical harm to the radio if it is from a different piece of hardware. Do not use this unless you know what you are doing and accept the risk of destroying your radio!" @@ -3068,7 +3068,7 @@ #~ msgstr "Plik ICF nie może być edytowany, może być wyświetlany oraz importowany do innego pliku.Otworzyć w trybie do odczytu?" #~ msgid "If you wish to disable this feature you may do so in the <u>Help</u> menu" -#~ msgstr "Jeśli chcesz wyłączyć tą funkcję możesz to zrobić w menu <u>Pomoc</>" +#~ msgstr "Jeśli chcesz wyłączyć tę funkcję możesz to zrobić w menu <u>Pomoc</>" #~ msgid "Import from RepeaterBook" #~ msgstr "Importuj z RepeaterBook" Binary files old/chirp-20250801/tests/images/Retevis_H777_V4.img and new/chirp-20250808/tests/images/Retevis_H777_V4.img differ ++++++ chirp.obsinfo ++++++ --- /var/tmp/diff_new_pack.dX0eLa/_old 2025-08-09 20:06:02.437832230 +0200 +++ /var/tmp/diff_new_pack.dX0eLa/_new 2025-08-09 20:06:02.437832230 +0200 @@ -1,5 +1,5 @@ name: chirp -version: 20250801 -mtime: 1754001055 -commit: 477a28489d173e582f859e6f7242bbbeb840fd60 +version: 20250808 +mtime: 1754516639 +commit: 600a65c103b6c44c1d0d28f5a98d3025aebacd36