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
 

Reply via email to