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-25 20:37:18 ++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++ Comparing /work/SRC/openSUSE:Factory/chirp (Old) and /work/SRC/openSUSE:Factory/.chirp.new.30751 (New) ++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++
Package is "chirp" Mon Aug 25 20:37:18 2025 rev:35 rq:1301001 version:20250822 Changes: -------- --- /work/SRC/openSUSE:Factory/chirp/chirp.changes 2025-08-15 21:54:39.814614608 +0200 +++ /work/SRC/openSUSE:Factory/.chirp.new.30751/chirp.changes 2025-08-25 20:37:47.751331213 +0200 @@ -1,0 +2,9 @@ +Fri Aug 22 15:56:52 UTC 2025 - Andreas Stieger <andreas.stie...@gmx.de> + +- Update to version 20250822: + * Various models: Remove verbose debug logging and use trace + instead + * Add generic serial tracing for debug + * Allow hexprint with variable line length + +------------------------------------------------------------------- Old: ---- chirp-20250815.obscpio New: ---- chirp-20250822.obscpio ++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++ Other differences: ------------------ ++++++ chirp.spec ++++++ --- /var/tmp/diff_new_pack.vCR4ur/_old 2025-08-25 20:37:48.363356854 +0200 +++ /var/tmp/diff_new_pack.vCR4ur/_new 2025-08-25 20:37:48.367357021 +0200 @@ -19,7 +19,7 @@ %define pythons python3 Name: chirp -Version: 20250815 +Version: 20250822 Release: 0 Summary: Tool for programming amateur radio sets License: GPL-3.0-only ++++++ _service ++++++ --- /var/tmp/diff_new_pack.vCR4ur/_old 2025-08-25 20:37:48.399358362 +0200 +++ /var/tmp/diff_new_pack.vCR4ur/_new 2025-08-25 20:37:48.403358529 +0200 @@ -4,8 +4,8 @@ <param name="scm">git</param> <param name="changesgenerate">enable</param> <param name="filename">chirp</param> - <param name="versionformat">20250815</param> - <param name="revision">c686bcfdeae9d8633503ac9f18e5d9f15908a34f</param> + <param name="versionformat">20250822</param> + <param name="revision">0705a4c61e11f952ef1bcdb282f22a74dc72782f</param> </service> <service mode="manual" name="set_version"/> <service name="tar" mode="buildtime"/> ++++++ _servicedata ++++++ --- /var/tmp/diff_new_pack.vCR4ur/_old 2025-08-25 20:37:48.427359535 +0200 +++ /var/tmp/diff_new_pack.vCR4ur/_new 2025-08-25 20:37:48.431359702 +0200 @@ -1,7 +1,7 @@ <servicedata> <service name="tar_scm"> <param name="url">https://github.com/kk7ds/chirp.git</param> - <param name="changesrevision">c686bcfdeae9d8633503ac9f18e5d9f15908a34f</param> + <param name="changesrevision">0705a4c61e11f952ef1bcdb282f22a74dc72782f</param> </service> </servicedata> (No newline at EOF) ++++++ chirp-20250815.obscpio -> chirp-20250822.obscpio ++++++ diff -urN '--exclude=CVS' '--exclude=.cvsignore' '--exclude=.svn' '--exclude=.svnignore' old/chirp-20250815/.github/pull_request_template.md new/chirp-20250822/.github/pull_request_template.md --- old/chirp-20250815/.github/pull_request_template.md 2025-08-14 23:59:27.000000000 +0200 +++ new/chirp-20250822/.github/pull_request_template.md 2025-08-20 01:19:02.000000000 +0200 @@ -6,7 +6,7 @@ 1. Commits should be rebased (or simply rebase-able in the web UI) on current master. Do not put merge commits in a PR. 1. Commits in a single PR should be related. Squash intermediate commits into logical units (i.e. "fix tests" commits need not survive on their own). Keep cleanup commits separate from functional changes. 1. Major new features or bug fixes should reference a [CHIRP issue](https://chirpmyradio.com/projects/chirp/issues) _in the commit message_. Do this with the pattern `Fixes #1234` or `Related to #1234` so that the ticket system links the commit to the issue. -1. Please write a reasonable commit message, especially if making some change that isn't totally obvious (such as adding a new model, adding a feature, etc). The first line of every commit is emailed to the users' list after each build. It should be short, but meaningful for regular users (examples: "thd74: Fixed tone decoding" or "uv5r: Added settings support"). +1. Please write a reasonable commit message, especially if making some change that isn't totally obvious (such as adding a new model, adding a feature, etc). The first line of every commit is emailed to the users' list after each build. It should be short, but meaningful for regular users (examples: "thd74: Fixed tone decoding" or "uv5r: Added settings support"). There should be a blank line after the first, followed by additional text so that it gets formatted properly for the mailing list. 1. New drivers should be accompanied by a test image in `tests/images` (except for thin aliases where the driver is sufficiently tested already). All new drivers must use `MemoryMapBytes`. 1. All files must be GPLv3 licensed or contain no license verbiage. No additional restrictions can be placed on the usage (i.e. such as noncommercial). 1. Do not add new py2-compatibility code (No new uses of `six`, `future`, etc). diff -urN '--exclude=CVS' '--exclude=.cvsignore' '--exclude=.svn' '--exclude=.svnignore' old/chirp-20250815/chirp/drivers/baofeng_uv17.py new/chirp-20250822/chirp/drivers/baofeng_uv17.py --- old/chirp-20250815/chirp/drivers/baofeng_uv17.py 2025-08-14 23:59:27.000000000 +0200 +++ new/chirp-20250822/chirp/drivers/baofeng_uv17.py 2025-08-20 01:19:02.000000000 +0200 @@ -22,7 +22,7 @@ RadioSettings, RadioSettingValueString import struct from chirp.drivers import baofeng_common, baofeng_uv17Pro -from chirp import errors, util +from chirp import errors LOG = logging.getLogger(__name__) LIST_DTMFST = ["Off", "DT-ST", "ANI-ST", "DT+ANI"] @@ -88,15 +88,11 @@ for addr in range(start_addr, start_addr + 0x1000, radio.BLOCK_SIZE): frame = radio._make_read_frame(addr, radio.BLOCK_SIZE) - # DEBUG - LOG.debug("Frame=" + util.hexprint(frame)) - + radio.pipe.log('Reading addr %04x' % addr) baofeng_common._rawsend(radio, frame) d = baofeng_common._rawrecv(radio, radio.BLOCK_SIZE + 5) - LOG.debug("Response Data= " + util.hexprint(d)) - data += d[5:] status.cur = len(data) // radio.BLOCK_SIZE @@ -133,6 +129,7 @@ data_addr = data_start_addr + addr - start_addr data = radio.get_mmap()[data_addr:data_addr + radio.BLOCK_SIZE] frame = radio._make_frame(b"W", addr, radio.BLOCK_SIZE, data) + radio.pipe.log('Sending addr %04x' % addr) baofeng_common._rawsend(radio, frame) # receiving the response diff -urN '--exclude=CVS' '--exclude=.cvsignore' '--exclude=.svn' '--exclude=.svnignore' old/chirp-20250815/chirp/drivers/baofeng_uv17Pro.py new/chirp-20250822/chirp/drivers/baofeng_uv17Pro.py --- old/chirp-20250815/chirp/drivers/baofeng_uv17Pro.py 2025-08-14 23:59:27.000000000 +0200 +++ new/chirp-20250822/chirp/drivers/baofeng_uv17Pro.py 2025-08-20 01:19:02.000000000 +0200 @@ -24,7 +24,7 @@ RadioSettingValueString, \ RadioSettings, RadioSettingGroup import struct -from chirp import errors, util +from chirp import errors LOG = logging.getLogger(__name__) @@ -145,16 +145,12 @@ for addr in range(MEM_START, MEM_START + MEM_SIZE, radio.BLOCK_SIZE): frame = radio._make_read_frame(addr, radio.BLOCK_SIZE) - # DEBUG - LOG.debug("Frame=" + util.hexprint(frame)) - # Sending the read request + radio.pipe.log('Sending request for %04x' % addr) bfc._rawsend(radio, frame) - # Now we read data d = bfc._rawrecv(radio, radio.BLOCK_SIZE + 4) - LOG.debug("Response Data= " + util.hexprint(d)) if radio._uses_encr: d = _crypt(radio._encrsym, d[4:]) else: @@ -195,13 +191,9 @@ data_addr += radio.BLOCK_SIZE frame = radio._make_frame(b"W", addr, radio.BLOCK_SIZE, data) - # DEBUG - LOG.debug("Frame=" + util.hexprint(frame)) - - # Sending the read request + radio.pipe.log('Sending address %04x' % addr) bfc._rawsend(radio, frame) - # receiving the response ack = bfc._rawrecv(radio, 1) if ack != b"\x06": msg = "Bad ack writing block 0x%04x" % addr @@ -1351,7 +1343,6 @@ mem.mode = _mem.wide and self.MODES[0] or self.MODES[1] if chirp_common.in_range(mem.freq, [self._airband]): - print('freq %i means am' % mem.freq) mem.mode = "AM" mem.extra = RadioSettingGroup("Extra", "extra") diff -urN '--exclude=CVS' '--exclude=.cvsignore' '--exclude=.svn' '--exclude=.svnignore' old/chirp-20250815/chirp/drivers/ga510.py new/chirp-20250822/chirp/drivers/ga510.py --- old/chirp-20250815/chirp/drivers/ga510.py 2025-08-14 23:59:27.000000000 +0200 +++ new/chirp-20250822/chirp/drivers/ga510.py 2025-08-20 01:19:02.000000000 +0200 @@ -72,7 +72,7 @@ data = bytes() for addr in range(0, 0x1C40, 0x40): cmd = struct.pack('>cHB', b'R', addr, 0x40) - LOG.debug('Reading block at %04x: %r' % (addr, cmd)) + radio.pipe.log('Reading block at %04x' % addr) radio.pipe.write(cmd) block = radio.pipe.read(0x44) @@ -106,7 +106,7 @@ # here. for addr in range(0, 0x1C20, 0x20): cmd = struct.pack('>cHB', b'W', addr, 0x20) - LOG.debug('Writing block at %04x: %r' % (addr, cmd)) + radio.pipe.log('Writing block at %04x' % addr) block = radio._mmap[addr:addr + 0x20] radio.pipe.write(cmd) radio.pipe.write(block) diff -urN '--exclude=CVS' '--exclude=.cvsignore' '--exclude=.svn' '--exclude=.svnignore' old/chirp-20250815/chirp/drivers/h777.py new/chirp-20250822/chirp/drivers/h777.py --- old/chirp-20250815/chirp/drivers/h777.py 2025-08-14 23:59:27.000000000 +0200 +++ new/chirp-20250822/chirp/drivers/h777.py 2025-08-20 01:19:02.000000000 +0200 @@ -189,8 +189,7 @@ cmd = struct.pack(">cHb", b'W', block_addr, BLOCK_SIZE) data = radio.get_mmap().get_byte_compatible()[block_addr:block_addr + 8] - LOG.debug("Writing Data:") - LOG.debug(util.hexprint(cmd + data)) + radio.pipe.log('Writing %i block at %04x' % (BLOCK_SIZE, block_addr)) try: serial.write(cmd + data) @@ -237,12 +236,10 @@ status.cur = addr + BLOCK_SIZE radio.status_fn(status) + radio.pipe.log('Reading %i block at %04x' % (BLOCK_SIZE, addr)) block = _h777_read_block(radio, addr, BLOCK_SIZE) data += block - LOG.debug("Address: %04x" % addr) - LOG.debug(util.hexprint(block)) - _h777_exit_programming_mode(radio.pipe) return memmap.MemoryMapBytes(data) diff -urN '--exclude=CVS' '--exclude=.cvsignore' '--exclude=.svn' '--exclude=.svnignore' old/chirp-20250815/chirp/drivers/thd74.py new/chirp-20250822/chirp/drivers/thd74.py --- old/chirp-20250815/chirp/drivers/thd74.py 2025-08-14 23:59:27.000000000 +0200 +++ new/chirp-20250822/chirp/drivers/thd74.py 2025-08-20 01:19:02.000000000 +0200 @@ -189,6 +189,7 @@ _memsize = 0x7A300 def read_block(self, block, count=256): + self.pipe.log('Reading block %i' % block) hdr = struct.pack(">cHH", b"R", block, 0) self.pipe.write(hdr) r = self.pipe.read(5) @@ -210,6 +211,7 @@ return data def write_block(self, block, map, size=256): + self.pipe.log('Writing block %i' % block) hdr = struct.pack(">cHH", b"W", block, size < 256 and size or 0) base = block * size data = map[base:base + size] diff -urN '--exclude=CVS' '--exclude=.cvsignore' '--exclude=.svn' '--exclude=.svnignore' old/chirp-20250815/chirp/drivers/tk3140.py new/chirp-20250822/chirp/drivers/tk3140.py --- old/chirp-20250815/chirp/drivers/tk3140.py 2025-08-14 23:59:27.000000000 +0200 +++ new/chirp-20250822/chirp/drivers/tk3140.py 2025-08-20 01:19:02.000000000 +0200 @@ -36,13 +36,11 @@ def send(radio, frame): - LOG.debug("%04i P>R:\n%s" % (len(frame), util.hexprint(frame))) radio.pipe.write(frame) def recv(radio, count): buf = radio.pipe.read(count) - LOG.debug('%04i: R>P:\n%s' % (len(buf), util.hexprint(buf))) return buf @@ -144,7 +142,6 @@ data = radio._mmap.get_packed() for block in range(0x80): if 0x54 <= block < 0x60: - LOG.debug('Skipping block 0x%02x' % block) continue addr = block * 256 chunk = data[addr:addr + 256] diff -urN '--exclude=CVS' '--exclude=.cvsignore' '--exclude=.svn' '--exclude=.svnignore' old/chirp-20250815/chirp/drivers/tk8180.py new/chirp-20250822/chirp/drivers/tk8180.py --- old/chirp-20250815/chirp/drivers/tk8180.py 2025-08-14 23:59:27.000000000 +0200 +++ new/chirp-20250822/chirp/drivers/tk8180.py 2025-08-20 01:19:02.000000000 +0200 @@ -445,16 +445,15 @@ status.max = radio._memsize status.msg = "Cloning from radio" radio.status_fn(status) - LOG.debug('Radio address 0x%04x' % len(data)) # Addresses 0x0000-0xBF00 pulled by block number (divide by 0x100) for block in range(0, 0xBF + 1): + radio.pipe.log('Read block index %02x' % block) send(radio, make_frame('R', block)) cmd = radio.pipe.read(1) chunk = b'' if cmd == b'Z': data += bytes(b'\xff' * 256) - LOG.debug('Radio reports empty block %02x' % block) elif cmd == b'W': chunk = bytes(radio.pipe.read(256)) if len(chunk) != 256: @@ -466,7 +465,6 @@ chr(cmd))) raise errors.RadioError('Radio sent unexpected response') - LOG.debug('Read block index %02x' % block) status() chksum = radio.pipe.read(1) @@ -488,6 +486,7 @@ # Addresses 0xC000 - 0xD1F0 pulled by address for block in range(0x0100, 0x1200, 0x40): + radio.pipe.log('Read memory address %04x' % block) send(radio, make_frame('S', block, b'\x40')) x = radio.pipe.read(1) if x != b'X': @@ -495,7 +494,6 @@ chunk = radio.pipe.read(0x40) data += chunk - LOG.debug('Read memory address %04x' % block) status() radio.pipe.write(b'\x06') @@ -524,9 +522,9 @@ addr = block * 0x100 chunk = bytes(radio._mmap[addr:addr + 0x100]) if all(byte == b'\xff' for byte in chunk): - LOG.debug('Sending zero block %i, range 0x%04x' % (block, addr)) send(radio, make_frame('Z', block, b'\xFF')) else: + radio.pipe.log('Sending block %i' % block) checksum = checksum_data(chunk) send(radio, make_frame('W', block, chunk + bytes([checksum]))) diff -urN '--exclude=CVS' '--exclude=.cvsignore' '--exclude=.svn' '--exclude=.svnignore' old/chirp-20250815/chirp/drivers/uvk5.py new/chirp-20250822/chirp/drivers/uvk5.py --- old/chirp-20250815/chirp/drivers/uvk5.py 2025-08-14 23:59:27.000000000 +0200 +++ new/chirp-20250822/chirp/drivers/uvk5.py 2025-08-20 01:19:02.000000000 +0200 @@ -383,8 +383,8 @@ def _send_command(serport, data: bytes): """Send a command to UV-K5 radio""" - LOG.debug("Sending command (unobfuscated) len=0x%4.4x:\n%s", - len(data), util.hexprint(data)) + serport.log("Sending command (unobfuscated) len=0x%4.4x:\n%s" % ( + len(data), util.hexprint(data))) crc = calculate_crc16_xmodem(data) data2 = data + struct.pack("<H", crc) @@ -392,8 +392,6 @@ command = struct.pack(">HBB", 0xabcd, len(data), 0) + \ xorarr(data2) + \ struct.pack(">H", 0xdcba) - if DEBUG_SHOW_OBFUSCATED_COMMANDS: - LOG.debug("Sending command (obfuscated):\n%s", util.hexprint(command)) try: result = serport.write(command) except Exception as e: @@ -434,15 +432,8 @@ util.hexprint(footer), len(footer)) raise errors.RadioError("Bad response footer") - if DEBUG_SHOW_OBFUSCATED_COMMANDS: - LOG.debug("Received reply (obfuscated) len=0x%4.4x:\n%s", - len(cmd), util.hexprint(cmd)) - cmd2 = xorarr(cmd) - LOG.debug("Received reply (unobfuscated) len=0x%4.4x:\n%s", - len(cmd2), util.hexprint(cmd2)) - return cmd2 @@ -480,38 +471,27 @@ def _readmem(serport, offset, length): - LOG.debug("Sending readmem offset=0x%4.4x len=0x%4.4x", offset, length) + serport.log("Sending readmem offset=0x%4.4x len=0x%4.4x" % ( + offset, length)) readmem = b"\x1b\x05\x08\x00" + \ struct.pack("<HBB", offset, length, 0) + \ b"\x6a\x39\x57\x64" _send_command(serport, readmem) rep = _receive_reply(serport) - if DEBUG_SHOW_MEMORY_ACTIONS: - LOG.debug("readmem Received data len=0x%4.4x:\n%s", - len(rep), util.hexprint(rep)) return rep[8:] def _writemem(serport, data, offset): - LOG.debug("Sending writemem offset=0x%4.4x len=0x%4.4x", - offset, len(data)) - - if DEBUG_SHOW_MEMORY_ACTIONS: - LOG.debug("writemem sent data offset=0x%4.4x len=0x%4.4x:\n%s", - offset, len(data), util.hexprint(data)) - dlen = len(data) writemem = b"\x1d\x05" + \ struct.pack("<BBHBB", dlen+8, 0, offset, dlen, 1) + \ b"\x6a\x39\x57\x64"+data + serport.log('Writemem at offset %04x len %04x' % (offset, dlen)) _send_command(serport, writemem) rep = _receive_reply(serport) - LOG.debug("writemem Received data: %s len=%i", - util.hexprint(rep), len(rep)) - if (rep[0] == 0x1e and rep[4] == (offset & 0xff) and rep[5] == (offset >> 8) & 0xff): diff -urN '--exclude=CVS' '--exclude=.cvsignore' '--exclude=.svn' '--exclude=.svnignore' old/chirp-20250815/chirp/util.py new/chirp-20250822/chirp/util.py --- old/chirp-20250815/chirp/util.py 2025-08-14 23:59:27.000000000 +0200 +++ new/chirp-20250822/chirp/util.py 2025-08-20 01:19:02.000000000 +0200 @@ -37,12 +37,11 @@ return i -def hexprint(data, addrfmt=None): +def hexprint(data, addrfmt=None, block_size=8): """Return a hexdump-like encoding of @data""" if addrfmt is None: addrfmt = '%(addr)03i' - block_size = 8 out = "" blocks = len(data) // block_size diff -urN '--exclude=CVS' '--exclude=.cvsignore' '--exclude=.svn' '--exclude=.svnignore' old/chirp-20250815/chirp/wxui/bugreport.py new/chirp-20250822/chirp/wxui/bugreport.py --- old/chirp-20250815/chirp/wxui/bugreport.py 2025-08-14 23:59:27.000000000 +0200 +++ new/chirp-20250822/chirp/wxui/bugreport.py 2025-08-20 01:19:02.000000000 +0200 @@ -14,6 +14,7 @@ # along with this program. If not, see <http://www.gnu.org/licenses/>. import datetime +import gzip import logging import os import platform @@ -32,6 +33,7 @@ from chirp import platform as chirp_platform from chirp.wxui import common from chirp.wxui import config +from chirp.wxui import serialtrace _ = wx.GetTranslation CONF = config.get() @@ -126,6 +128,15 @@ manifest['files']['debug_log.txt'] = f.read() tmpf = tempfile.mktemp('.config', 'chirp') + # Grab any trace files + for tracefile in serialtrace.TRACEFILES: + if os.path.exists(tracefile): + LOG.debug('Capturing serial trace file %s', tracefile) + with open(tracefile, 'rb') as f: + manifest['files'][os.path.basename(tracefile)] = f.read() + else: + LOG.debug('Serial trace file %s does not exist', tracefile) + return manifest @@ -623,7 +634,7 @@ elif r.status_code != 201: LOG.error('Failed to upload %s: %s %s', fn, r.status_code, r.reason) - raise Exception('Failed to upload file') + raise Exception('Failed to upload file: %s' % r.reason) return r.json()['upload']['token'] raise Exception('Failed to upload %s after multiple attempts', fn) @@ -631,19 +642,36 @@ if 'issue' not in manifest: self._create_bug(manifest) + for fn in list(manifest['files'].keys()): + fdata = manifest['files'][fn] + if len(fdata) > 1024 * 1024: + LOG.warning('File %s is larger than 1MB, compressing', fn) + fdata = gzip.compress(fdata) + manifest['files'].pop(fn) + fn += '.gz' + manifest['files'][fn] = fdata + + notes = '[Uploaded from CHIRP %s]\n\n' % CHIRP_VERSION tokens = [] for fn in manifest['files']: - token = self._upload_file(manifest, fn) - if fn.lower().endswith('.img'): - ct = 'application/octet-stream' - else: + try: + token = self._upload_file(manifest, fn) + except Exception as e: + LOG.error('Failed to upload file %s: %s', fn, e) + notes += '[Failed to upload file %s: %s]\n\n' % (fn, e) + continue + ext = os.path.splitext(fn)[1].lower() + if ext in ('.log', '.txt'): ct = 'text/plain' - tokens.append({'token': token, - 'filename': fn, - 'content_type': ct}) + else: + ct = 'application/octet-stream' + token_info = {'token': token, + 'filename': fn, + 'content_type': ct} + tokens.append(token_info) + LOG.debug('File tokens: %s', tokens) - notes = '[Uploaded from CHIRP %s]\n\n' % CHIRP_VERSION if not self.context.is_new: notes += manifest['desc'] r = self.context.session.put( diff -urN '--exclude=CVS' '--exclude=.cvsignore' '--exclude=.svn' '--exclude=.svnignore' old/chirp-20250815/chirp/wxui/clone.py new/chirp-20250822/chirp/wxui/clone.py --- old/chirp-20250815/chirp/wxui/clone.py 2025-08-14 23:59:27.000000000 +0200 +++ new/chirp-20250822/chirp/wxui/clone.py 2025-08-20 01:19:02.000000000 +0200 @@ -33,6 +33,7 @@ from chirp.wxui import config from chirp.wxui import common from chirp.wxui import developer +from chirp.wxui import serialtrace _ = wx.GetTranslation LOG = logging.getLogger(__name__) @@ -163,8 +164,9 @@ pipe.open() pipe.baudrate = rclass.BAUD_RATE else: - pipe = serial.Serial(baudrate=rclass.BAUD_RATE, - rtscts=rclass.HARDWARE_FLOW, timeout=0.25) + pipe = serialtrace.SerialTrace( + baudrate=rclass.BAUD_RATE, + rtscts=rclass.HARDWARE_FLOW, timeout=0.25) pipe.rts = rclass.WANTS_RTS pipe.dtr = rclass.WANTS_DTR pipe.port = port diff -urN '--exclude=CVS' '--exclude=.cvsignore' '--exclude=.svn' '--exclude=.svnignore' old/chirp-20250815/chirp/wxui/main.py new/chirp-20250822/chirp/wxui/main.py --- old/chirp-20250815/chirp/wxui/main.py 2025-08-14 23:59:27.000000000 +0200 +++ new/chirp-20250822/chirp/wxui/main.py 2025-08-20 01:19:02.000000000 +0200 @@ -51,6 +51,7 @@ from chirp.wxui import radioinfo from chirp.wxui import radiothread from chirp.wxui import report +from chirp.wxui import serialtrace from chirp.wxui import settingsedit from chirp import CHIRP_VERSION @@ -1016,6 +1017,12 @@ help_menu.Append(self.bug_report_item) self.bug_report_item.Enable(False) + if developer.developer_mode(): + trace_item = wx.MenuItem(help_menu, wx.NewId(), + 'Open last serial trace') + self.Bind(wx.EVT_MENU, self._menu_last_trace, trace_item) + help_menu.Append(trace_item) + menu_bar = wx.MenuBar() menu_bar.Append(file_menu, wx.GetStockLabel(wx.ID_FILE)) menu_bar.Append(edit_menu, wx.GetStockLabel(wx.ID_EDIT)) @@ -1322,6 +1329,9 @@ 'state') config._CONFIG.save() + # Clean up any trace files we left + serialtrace.purge_trace_files(0) + ALL_MAIN_WINDOWS.remove(self) self.Destroy() @@ -2017,6 +2027,13 @@ _('Success'), wx.ICON_INFORMATION) + def _menu_last_trace(self, event): + try: + fn = serialtrace.TRACEFILES[-1] + wx.LaunchDefaultApplication(fn) + except IndexError: + common.error_proof.show_error('No traces stored', parent=self) + def _menu_auto_edits(self, event): CONF.set_bool('auto_edits', event.IsChecked(), 'state') LOG.debug('Set auto_edits=%s' % event.IsChecked()) diff -urN '--exclude=CVS' '--exclude=.cvsignore' '--exclude=.svn' '--exclude=.svnignore' old/chirp-20250815/chirp/wxui/serialtrace.py new/chirp-20250822/chirp/wxui/serialtrace.py --- old/chirp-20250815/chirp/wxui/serialtrace.py 1970-01-01 01:00:00.000000000 +0100 +++ new/chirp-20250822/chirp/wxui/serialtrace.py 2025-08-20 01:19:02.000000000 +0200 @@ -0,0 +1,129 @@ +# Copyright 2025 Dan Smith <ch...@f.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 datetime +import logging +import os +import serial +import tempfile +import time + +from chirp import util +from chirp.wxui import config + +CONF = config.get() +LOG = logging.getLogger(__name__) +TRACEFILES = [] + + +def get_trace_entry(direction, start_ts, data): + loglines = util.hexprint(data, block_size=16).split('\n') + ts = time.monotonic() - start_ts + loglines = ['%7.3f %s %s%s' % (ts, direction, line, os.linesep) + for line in loglines if line.strip()] + if not loglines and direction == 'R' and not data: + # No data read means timeout, so denote that for clarity + loglines = ['%7.3f %s # timeout%s' % (ts, direction, os.linesep)] + return loglines + + +def purge_trace_files(keep=10): + global TRACEFILES + if keep == 0: + purge = TRACEFILES + TRACEFILES = [] + else: + purge = TRACEFILES[:-keep] + TRACEFILES = TRACEFILES[-10:] + for fn in purge: + try: + os.remove(fn) + LOG.debug('Removed old trace file %s', fn) + except FileNotFoundError: + pass + except Exception as e: + LOG.error('Failed to remove old trace file %s: %s', fn, e) + + +class SerialTrace(serial.Serial): + def __init__(self, *a, **k): + self.__tracef = None + super().__init__(*a, **k) + + def open(self): + super().open() + try: + self.__trace_start = time.monotonic() + self.__tracef = tempfile.NamedTemporaryFile(mode='w', + delete=False, + prefix='chirp-trace-', + suffix='.txt') + TRACEFILES.append(self.__tracef.name) + purge_trace_files(10) + now = datetime.datetime.now() + self.log('Serial trace %s started at %s' % (self, now.isoformat())) + LOG.info('Serial trace file created: %s' % self.__tracef.name) + except Exception as e: + LOG.error('Failed to create serial trace file: %s' % e) + self.__tracef = None + + def write(self, data): + super().write(data) + if self.__tracef: + try: + self.__tracef.writelines(get_trace_entry('W', + self.__trace_start, + data)) + except Exception as e: + LOG.error('Failed to write to serial trace file: %s' % e) + self.__tracef = None + + def read(self, size=1): + data = super().read(size) + if self.__tracef: + try: + self.__tracef.writelines(get_trace_entry('R', + self.__trace_start, + data)) + except Exception as e: + LOG.error('Failed to write to serial trace file: %s' % e) + self.__tracef = None + return data + + def close(self): + super().close() + if self.__tracef: + try: + now = datetime.datetime.now() + self.log('Trace ended at %s' % now.isoformat()) + self.__tracef.close() + LOG.info('Serial trace file closed: %s' % self.__tracef.name) + except Exception as e: + LOG.error('Failed to close serial trace file: %s' % e) + finally: + self.__tracef = None + + def log(self, message): + """Log a message to the trace file. + + Use this to annotate important events in the trace file, such as + reading a new block, etc. + """ + if self.__tracef: + try: + self.__tracef.write('# %s\n' % message) + except Exception as e: + LOG.error('Failed to write log message to trace file: %s' % e) + self.__tracef = None diff -urN '--exclude=CVS' '--exclude=.cvsignore' '--exclude=.svn' '--exclude=.svnignore' old/chirp-20250815/tests/test_clone.py new/chirp-20250822/tests/test_clone.py --- old/chirp-20250815/tests/test_clone.py 2025-08-14 23:59:27.000000000 +0200 +++ new/chirp-20250822/tests/test_clone.py 2025-08-20 01:19:02.000000000 +0200 @@ -5,6 +5,7 @@ from chirp import chirp_common from chirp import errors +from chirp.wxui import serialtrace from tests import base LOG = logging.getLogger(__name__) @@ -14,7 +15,7 @@ pass -class SerialNone: +class SerialNone(serialtrace.SerialTrace): def flush(self): pass diff -urN '--exclude=CVS' '--exclude=.cvsignore' '--exclude=.svn' '--exclude=.svnignore' old/chirp-20250815/tests/unit/test_serialtrace.py new/chirp-20250822/tests/unit/test_serialtrace.py --- old/chirp-20250815/tests/unit/test_serialtrace.py 1970-01-01 01:00:00.000000000 +0100 +++ new/chirp-20250822/tests/unit/test_serialtrace.py 2025-08-20 01:19:02.000000000 +0200 @@ -0,0 +1,79 @@ +import unittest +from unittest import mock + +from chirp.wxui import serialtrace + + +class TestSerialTrace(unittest.TestCase): + @mock.patch('serial.Serial.open') + def test_open(self, mock_open): + trace = serialtrace.SerialTrace() + self.assertIsNone(trace._SerialTrace__tracef) + trace.open() + self.assertIsNotNone(trace._SerialTrace__tracef) + self.assertTrue(trace._SerialTrace__tracef.name.endswith('.txt')) + mock_open.assert_called_once() + + @mock.patch('os.remove') + def test_purge_trace_files(self, mock_remove): + from chirp.wxui import serialtrace + + for i in range(15): + serialtrace.TRACEFILES.append('test_trace_%i.txt' % i) + files = serialtrace.TRACEFILES[:] + + # Purge to 10 files keeps the last 10 + serialtrace.purge_trace_files(10) + self.assertEqual(len(serialtrace.TRACEFILES), 10) + self.assertEqual(['test_trace_%i.txt' % (i + 5) for i in range(10)], + serialtrace.TRACEFILES) + + # Purge to 20 does not change anything since only 10 stored + serialtrace.purge_trace_files(20) + self.assertEqual(10, len(serialtrace.TRACEFILES)) + + # Purge to zero removes all files + serialtrace.purge_trace_files(0) + self.assertEqual(0, len(serialtrace.TRACEFILES)) + + # Make sure we ended up removing all the files + mock_remove.assert_has_calls([mock.call(fn) for fn in files], + any_order=True) + + @mock.patch('serial.Serial.open') + @mock.patch('serial.Serial.write') + @mock.patch('serial.Serial.read') + def test_log_write(self, mock_read, mock_write, mock_open): + mock_read.side_effect = [b'123', b''] + trace = serialtrace.SerialTrace() + trace.open() + fn = serialtrace.TRACEFILES[-1] + trace.write(b'foo') + trace.read(3) + trace.read(5) + trace.close() + with open(fn, 'r') as f: + content = f.read() + self.assertIn('# Serial trace', content) + self.assertIn('foo...', content) + self.assertIn('R # timeout', content) + + @mock.patch('tempfile.NamedTemporaryFile') + @mock.patch('serial.Serial.open') + @mock.patch('serial.Serial.write') + @mock.patch('serial.Serial.read') + def test_log_write_fail(self, mock_read, mock_write, mock_open, mock_tf): + mock_tf.return_value.writelines.side_effect = [ + None, Exception("Write error")] + trace = serialtrace.SerialTrace() + trace.open() + # This should generate a write failure + trace.read(3) + + # Make sure we don't interrupt further communication + trace.write(b'foo') + trace.read() + trace.write(b'bar') + + # Before we are closed, the trace file should have been abandoned + self.assertIsNone(trace._SerialTrace__tracef) diff -urN '--exclude=CVS' '--exclude=.cvsignore' '--exclude=.svn' '--exclude=.svnignore' old/chirp-20250815/tools/check_commit.sh new/chirp-20250822/tools/check_commit.sh --- old/chirp-20250815/tools/check_commit.sh 2025-08-14 23:59:27.000000000 +0200 +++ new/chirp-20250822/tools/check_commit.sh 2025-08-20 01:19:02.000000000 +0200 @@ -115,4 +115,18 @@ rm -f added_lines license_lines +commits=$(git log --pretty=format:%h ${BASE}..) +for commit in $commits; do + git log -n1 $commit --pretty=format:%B > commit_msg + if [[ `sed -n '1p' commit_msg | wc -c` > 99 ]]; then + fail "First line of commit message of $commit must be <99 chars" + fi + if [[ `wc -l < commit_msg` > 1 ]]; then + if ! sed -n '2p' commit_msg | grep '^$'; then + fail "Second line of commit message of $commit must be blank for proper formatting in the notification emails" + fi + fi + rm -f commit_msg +done + exit $RETCODE ++++++ chirp.obsinfo ++++++ --- /var/tmp/diff_new_pack.vCR4ur/_old 2025-08-25 20:37:49.579407798 +0200 +++ /var/tmp/diff_new_pack.vCR4ur/_new 2025-08-25 20:37:49.583407965 +0200 @@ -1,5 +1,5 @@ name: chirp -version: 20250815 -mtime: 1755208767 -commit: c686bcfdeae9d8633503ac9f18e5d9f15908a34f +version: 20250822 +mtime: 1755645542 +commit: 0705a4c61e11f952ef1bcdb282f22a74dc72782f