Script 'mail_helper' called by obssrc
Hello community,

here is the log from the commit of package chirp for openSUSE:Factory checked 
in at 2026-03-01 22:15:02
++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++
Comparing /work/SRC/openSUSE:Factory/chirp (Old)
 and      /work/SRC/openSUSE:Factory/.chirp.new.29461 (New)
++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++

Package is "chirp"

Sun Mar  1 22:15:02 2026 rev:59 rq:1335604 version:20260227

Changes:
--------
--- /work/SRC/openSUSE:Factory/chirp/chirp.changes      2026-02-21 
21:05:27.377498692 +0100
+++ /work/SRC/openSUSE:Factory/.chirp.new.29461/chirp.changes   2026-03-01 
22:15:52.148538530 +0100
@@ -1,0 +2,7 @@
+Sun Mar  1 07:55:06 UTC 2026 - Andreas Stieger <[email protected]>
+
+- Update to version 20260227:
+  * add Retevis RT21H
+  * d7: Avoid breaking settings load for individual failures
+
+-------------------------------------------------------------------

Old:
----
  chirp-20260220.obscpio

New:
----
  chirp-20260227.obscpio

++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++

Other differences:
------------------
++++++ chirp.spec ++++++
--- /var/tmp/diff_new_pack.Rm3bQu/_old  2026-03-01 22:15:52.968572232 +0100
+++ /var/tmp/diff_new_pack.Rm3bQu/_new  2026-03-01 22:15:52.972572396 +0100
@@ -20,7 +20,7 @@
 
 %define pythons python3
 Name:           chirp
-Version:        20260220
+Version:        20260227
 Release:        0
 Summary:        Tool for programming amateur radio sets
 License:        GPL-3.0-only

++++++ _service ++++++
--- /var/tmp/diff_new_pack.Rm3bQu/_old  2026-03-01 22:15:53.016574205 +0100
+++ /var/tmp/diff_new_pack.Rm3bQu/_new  2026-03-01 22:15:53.020574369 +0100
@@ -4,8 +4,8 @@
     <param name="scm">git</param>
     <param name="changesgenerate">enable</param>
     <param name="filename">chirp</param>
-    <param name="versionformat">20260220</param>
-    <param name="revision">1467519e792e8ebcc9a33dc40df0b2e273ce9a53</param>
+    <param name="versionformat">20260227</param>
+    <param name="revision">bc56ae93ab3cd1d3d7d0f3e96b86e9b757155372</param>
   </service>
   <service mode="manual" name="set_version"/>
   <service name="tar" mode="buildtime"/>

++++++ _servicedata ++++++
--- /var/tmp/diff_new_pack.Rm3bQu/_old  2026-03-01 22:15:53.040575191 +0100
+++ /var/tmp/diff_new_pack.Rm3bQu/_new  2026-03-01 22:15:53.044575355 +0100
@@ -1,7 +1,7 @@
 <servicedata>
   <service name="tar_scm">
     <param name="url">https://github.com/kk7ds/chirp.git</param>
-    <param 
name="changesrevision">1467519e792e8ebcc9a33dc40df0b2e273ce9a53</param>
+    <param 
name="changesrevision">bc56ae93ab3cd1d3d7d0f3e96b86e9b757155372</param>
   </service>
 </servicedata>
 (No newline at EOF)

++++++ chirp-20260220.obscpio -> chirp-20260227.obscpio ++++++
diff -urN '--exclude=CVS' '--exclude=.cvsignore' '--exclude=.svn' 
'--exclude=.svnignore' old/chirp-20260220/chirp/chirp_common.py 
new/chirp-20260227/chirp/chirp_common.py
--- old/chirp-20260220/chirp/chirp_common.py    2026-02-20 00:42:21.000000000 
+0100
+++ new/chirp-20260227/chirp/chirp_common.py    2026-02-26 03:26:40.000000000 
+0100
@@ -1954,17 +1954,17 @@
 
 def from_GHz(val):
     """Convert @val in Hz to GHz"""
-    return val // 100000000
+    return val // 1000000000
 
 
 def from_MHz(val):
     """Convert @val in Hz to MHz"""
-    return val // 100000
+    return val // 1000000
 
 
 def from_kHz(val):
     """Convert @val in Hz to kHz"""
-    return val // 100
+    return val // 1000
 
 
 def split_to_offset(mem, rxfreq, txfreq):
diff -urN '--exclude=CVS' '--exclude=.cvsignore' '--exclude=.svn' 
'--exclude=.svnignore' old/chirp-20260220/chirp/drivers/icq7.py 
new/chirp-20260227/chirp/drivers/icq7.py
--- old/chirp-20260220/chirp/drivers/icq7.py    2026-02-20 00:42:21.000000000 
+0100
+++ new/chirp-20260227/chirp/drivers/icq7.py    2026-02-26 03:26:40.000000000 
+0100
@@ -17,7 +17,7 @@
 
 from chirp.drivers import icf
 from chirp import chirp_common, directory, bitwise
-from chirp.chirp_common import to_GHz, from_GHz
+from chirp.chirp_common import to_GHz
 from chirp.settings import RadioSetting, RadioSettingGroup, \
                 RadioSettingValueBoolean, RadioSettingValueList, \
                 RadioSettingValueInteger, RadioSettings
@@ -189,7 +189,8 @@
 
         if mem.freq > to_GHz(1):
             _mem.freq = (mem.freq // 1000) - to_GHz(1)
-            upper = from_GHz(mem.freq) << 4
+            # For 1300MHz, but 13 in these bits
+            upper = (mem.freq // 100000000) << 4
             _mem.freq[0].clr_bits(0xF0)
             _mem.freq[0].set_bits(upper)
         else:
diff -urN '--exclude=CVS' '--exclude=.cvsignore' '--exclude=.svn' 
'--exclude=.svnignore' old/chirp-20260220/chirp/drivers/kenwood_d7.py 
new/chirp-20260227/chirp/drivers/kenwood_d7.py
--- old/chirp-20260220/chirp/drivers/kenwood_d7.py      2026-02-20 
00:42:21.000000000 +0100
+++ new/chirp-20260227/chirp/drivers/kenwood_d7.py      2026-02-26 
03:26:40.000000000 +0100
@@ -920,7 +920,13 @@
     def _refresh_settings(self):
         for entry in self._setcache.values():
             if not entry.kenwood_d7_loaded:
-                self._refresh_setting(entry)
+                try:
+                    self._refresh_setting(entry)
+                except errors.RadioError as e:
+                    # Don't fail if we can't read a setting. Some radios
+                    # refuse to return MCL (perhaps v1 radios?)
+                    LOG.error("Error refreshing setting %s: %s"
+                              % (entry.get_name(), str(e)))
 
     def _validate_memid(self, memid_or_index):
         if isinstance(memid_or_index, int):
diff -urN '--exclude=CVS' '--exclude=.cvsignore' '--exclude=.svn' 
'--exclude=.svnignore' old/chirp-20260220/chirp/drivers/retevis_h777v4.py 
new/chirp-20260227/chirp/drivers/retevis_h777v4.py
--- old/chirp-20260220/chirp/drivers/retevis_h777v4.py  2026-02-20 
00:42:21.000000000 +0100
+++ new/chirp-20260227/chirp/drivers/retevis_h777v4.py  2026-02-26 
03:26:40.000000000 +0100
@@ -27,6 +27,7 @@
 from chirp.drivers import h777
 from chirp.settings import (
     MemSetting,
+    RadioSetting,
     RadioSettingGroup,
     RadioSettings,
     RadioSettingValueBoolean,
@@ -49,7 +50,7 @@
      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
+     bcl:1;           //     Busy Channel Lockout  0 = On, 1 = Off
 } memory[16];
 
 struct {
@@ -73,17 +74,74 @@
 } settings;
 """
 
+MEM_FORMAT_RT21H = """
+struct {
+  ul24 rxfreq;        // 0-2
+  ul24 txfreq;        // 3-5
+  lbcd rx_tone[2];    // 6-7
+  lbcd tx_tone[2];    // 8-9
+  u8 speccode:1,      // A   Spec Code  0 = On, 1 = Off
+     compand:1,       //     Compander  0 = On, 1 = Off
+     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 Lockout  0 = On, 1 = Off
+  u8 scrambopt;       // B   Scramble Options
+} memory[16];
+
+struct {
+  u8 unknown_4:1,                  // 00C0
+     scanm:1,                      //       Scan Mode
+     roger:1,                      //       Roger
+     lowbattwarn:1,                //       Low Battery Warning
+     unknown_0:1,
+     voice:1,                      //       Voice Annunciation
+     save:1,                       //       Battery Save
+     voxs:1;                       //       VOX Switch
+  u8 squelch;                      // 00C1  Squelch Level
+  u8 tot;                          // 00C2  Time-out Timer
+  u8 voxd;                         // 00C3  Vox Delay
+  u8 vox_level;                    // 00C4  VOX Level
+  u8 unknown_1;
+  u8 unknown_2;
+  u8 skey1L:4,                     // 00C7  Side Key 1 (long)
+     skey1S:4;                     //       Side Key 1 (short)
+  u8 unknown_3:5,                  // 00C8
+     removectdcs:1,                //       Remove CT/DCS
+     beep:1,                       //       Beep Tone
+     specmode:1;                   //       Spec Mode
+} settings;
+"""
+
 
 CMD_ACK = b"\x06"
 
 DTCS = tuple(sorted(chirp_common.DTCS_CODES + (645,)))
 
 OFF1TO9_LIST = ["Off"] + ["%s" % x for x in range(1, 10)]
+ONE_TO_NINE_LIST = OFF1TO9_LIST[1:]  # ["1", "2", ..., "9"]
 SCANM_LIST = ["Carrier", "Time"]
+SCRAMBOPT_LIST = ["1", "2", "3", "4", "5", "6", "7", "8"]
+SPECMODE_LIST = ["Spec Code 1", "Spec Code 2"]
+SKEY1_LIST = ["Off", "Monitor", "Scan", "Channel Lock", "VOX", "Power",
+              "Alarm", "Roger"]
 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"]
+
+MODEL_CONFIG = {
+    "RT21H": {
+        "base_freq_offset": 0x2000000,
+        "freq_storage_bits": 24,
+    },
+}
+
+DEFAULT_CONFIG = {
+    "base_freq_offset": 0,
+    "freq_storage_bits": 32,
+}
 
 
 def _read_block(radio, block_addr, block_size):
@@ -127,6 +185,8 @@
 def do_download(radio):
     LOG.debug("download")
 
+    h777._h777_enter_single_programming_mode(radio)
+
     data = b""
 
     status = chirp_common.Status()
@@ -172,9 +232,25 @@
     BLOCK_SIZE = 0x0D
     BLOCK_SIZE_UP = 0x0D
 
+    VOXD_LIST = ["0.5", "1.0", "1.5", "2.0", "2.5", "3.0"]
+    VOXD_DOC = "VOX Delay: 0.5, 1.0, 1.5, 2.0, 2.5, 3.0 seconds"
+    VOXL_DOC = "VOX Level: Off, 1, 2, 3, 4, 5, 6, 7, 8, 9"
+
+    _has_codeswitch = True
+    _has_compander = False
+    _has_low_battery_warning = False
+    _has_remove_ctdcs = False
+    _has_scramble_options = False
+    _has_spec_code = False
+    _has_sidekey1 = False
+    _has_sidekey2 = True
+    _has_spec_mode = False
+    _has_vox_level_off = True
+
     POWER_LEVELS = [chirp_common.PowerLevel("Low", watts=0.50),
                     chirp_common.PowerLevel("High", watts=2.00)
                     ]
+    VALID_BANDS = [(400000000, 520000000)]
 
     PROGRAM_CMD = b"C777HAM"
     _ranges = [
@@ -202,7 +278,7 @@
         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)]
+        rf.valid_bands = self.VALID_BANDS
         return rf
 
     def process_mmap(self):
@@ -278,7 +354,19 @@
     def _encode_tone(self, _toneval, mode, val, pol):
         toneval = 0
         if mode == "Tone":
-            toneval = int("%i" % (val * 10), 16)
+            v = int(round(val * 10))
+
+            thousands = (v // 1000) % 10
+            hundreds = (v // 100) % 10
+            tens = (v // 10) % 10
+            ones = v % 10
+
+            toneval = (
+                (thousands << 12)
+                | (hundreds << 8)
+                | (tens << 4)
+                | ones
+            )
         elif mode == "DTCS":
             toneval = int('%i' % val, 8)
             toneval |= 0x8000
@@ -290,6 +378,15 @@
         _toneval[0].set_raw(toneval & 0xFF)
         _toneval[1].set_raw((toneval >> 8) & 0xFF)
 
+    def _model_cfg(self):
+        cfg = MODEL_CONFIG.get(self.MODEL, DEFAULT_CONFIG).copy()
+        cfg["tx_unused_marker"] = (1 << cfg["freq_storage_bits"]) - 1
+        return cfg
+
+    def _decode_freq(self, raw):
+        cfg = self._model_cfg()
+        return (int(raw) + cfg["base_freq_offset"]) * 10
+
     def get_raw_memory(self, number):
         return repr(self._memobj.memory[number - 1])
 
@@ -303,22 +400,27 @@
             mem.empty = True
             return mem
 
-        mem.freq = int(_mem.rxfreq) * 10
+        cfg = self._model_cfg()
 
-        if _mem.txfreq == 0xFFFFFFFF:
-            # TX freq not set
+        rx_hz = self._decode_freq(_mem.rxfreq)
+        mem.freq = rx_hz
+
+        if _mem.txfreq == cfg["tx_unused_marker"]:
             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
+            tx_hz = self._decode_freq(_mem.txfreq)
+            diff = rx_hz - tx_hz
+
+            if abs(diff) > 25_000_000:
+                mem.duplex = "split"
+                mem.offset = tx_hz
+            elif diff == 0:
+                mem.duplex = ""
+                mem.offset = 0
+            else:
+                mem.duplex = "-" if diff > 0 else "+"
+                mem.offset = abs(diff)
 
         txmode, txval, txpol = self._decode_tone(_mem.tx_tone, 'TX', number)
         rxmode, rxval, rxpol = self._decode_tone(_mem.rx_tone, 'RX', number)
@@ -348,26 +450,64 @@
         rset.set_doc("Frequency inversion Scramble")
         mem.extra.append(rset)
 
+        # Compander
+        if self._has_compander:
+            rs = RadioSettingValueInvertedBoolean(not bool(_mem.compand))
+            rset = MemSetting("compand", "Compander", rs)
+            rset.set_doc("Voice Compander")
+            mem.extra.append(rset)
+
+        # Spec Code
+        if self._has_spec_code:
+            rs = RadioSettingValueInvertedBoolean(not bool(_mem.speccode))
+            rset = MemSetting("speccode", "Spec Code", rs)
+            rset.set_doc("Spec Code")
+            mem.extra.append(rset)
+
+        # Scramble Options
+        if self._has_scramble_options:
+            if _mem.scrambopt > 0x07:
+                val = 0x00
+            else:
+                val = _mem.scrambopt
+            rs = RadioSettingValueList(SCRAMBOPT_LIST, current_index=val)
+            rset = MemSetting("scrambopt", "Scramble Options", rs)
+            rset.set_doc("Scramble Options: 1, 2, 3, 4, 5, 6, 7, 8")
+            mem.extra.append(rset)
+
         return mem
 
+    def _encode_freq(self, hz):
+        cfg = self._model_cfg()
+        raw = int(hz / 10) - cfg["base_freq_offset"]
+        if not (0 <= raw <= cfg["tx_unused_marker"]):
+            raise ValueError(f"Frequency {hz} out of 24-bit range")
+        return raw
+
     def set_memory(self, mem):
         _mem = self._memobj.memory[mem.number - 1]
 
-        _mem.set_raw(b"\xff" * 13)
+        cfg = self._model_cfg()
+
         if mem.empty:
+            _mem.set_raw(b"\xFF" * (_mem.size() // 8))
             return
 
-        _mem.rxfreq = mem.freq / 10
+        rx = self._encode_freq(mem.freq)
+        _mem.rxfreq = rx
+
         if mem.duplex == "off":
-            _mem.txfreq = 0xFFFFFFFF
+            tx = cfg["tx_unused_marker"]
         elif mem.duplex == "split":
-            _mem.txfreq = mem.offset / 10
+            tx = self._encode_freq(mem.offset)
         elif mem.duplex == "+":
-            _mem.txfreq = (mem.freq + mem.offset) / 10
+            tx = self._encode_freq(mem.freq + mem.offset)
         elif mem.duplex == "-":
-            _mem.txfreq = (mem.freq - mem.offset) / 10
+            tx = self._encode_freq(mem.freq - mem.offset)
         else:
-            _mem.txfreq = _mem.rxfreq
+            tx = rx
+
+        _mem.txfreq = tx
 
         (txmode, txval, txpol), (rxmode, rxval, rxpol) = \
             chirp_common.split_tone_encode(mem)
@@ -380,10 +520,6 @@
 
         _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)
 
@@ -407,18 +543,42 @@
         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")
+        if self._has_vox_level_off:
+            # H777 behavior: 0-9 maps directly
+            rs = RadioSettingValueList(
+                OFF1TO9_LIST,
+                current_index=_settings.vox
+            )
+            rset = MemSetting("vox", "VOX Level", rs)
+        else:
+            # RT21H behavior: hide "Off"
+            # Firmware: 1-9
+            # UI index: 0-8
+            index = min(8, max(0, _settings.vox_level - 1))
+            rs = RadioSettingValueList(
+                ONE_TO_NINE_LIST,
+                current_index=index
+            )
+            rset = MemSetting("vox_level", "VOX Level", rs)
+
+        rset.set_doc(self.VOXL_DOC)
         basic.append(rset)
 
         # Vox Delay
-        rs = RadioSettingValueList(VOXD_LIST,
+        rs = RadioSettingValueList(self.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")
+        rset.set_doc(self.VOXD_DOC)
         basic.append(rset)
 
+        # Spec Mode
+        if self._has_spec_mode:
+            rs = RadioSettingValueList(SPECMODE_LIST,
+                                       current_index=_settings.specmode)
+            rset = MemSetting("specmode", "Spec Mode", rs)
+            rset.set_doc("Spec Mode: Spec Code 1, Spec Code 2")
+            basic.append(rset)
+
         # Scan Mode
         rs = RadioSettingValueList(SCANM_LIST, current_index=_settings.scanm)
         rset = MemSetting("scanm", "Scan Mode", rs)
@@ -432,18 +592,40 @@
         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)
+        # Side Key 1
+        if self._has_sidekey1:
+            SKEY1_DOC = ("Off, Monitor, Scan, Channel Lock, VOX, Power," +
+                         " Alarm, Roger")
+
+            # Side Key 1 (short)
+            rs = RadioSettingValueList(SKEY1_LIST,
+                                       current_index=_settings.skey1S)
+            rset = MemSetting("skey1S", "Side Key 1 (short)", rs)
+            rset.set_doc("Side Key 1 (short press): " + SKEY1_DOC)
+            basic.append(rset)
+
+            # Side Key 1 (long)
+            rs = RadioSettingValueList(SKEY1_LIST,
+                                       current_index=_settings.skey1L)
+            rset = MemSetting("skey1L", "Side Key 1 (long)", rs)
+            rset.set_doc("Side Key 1 (long press): " + SKEY1_DOC)
+            basic.append(rset)
+
+        # Side key 2
+        if self._has_sidekey2:
+            # 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)
+        if self._has_codeswitch:
+            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)
@@ -463,12 +645,26 @@
         rset.set_doc("VOX Switch: Off, Enabled")
         basic.append(rset)
 
+        # Low Battery Warning
+        if self._has_low_battery_warning:
+            rs = RadioSettingValueBoolean(_settings.lowbattwarn)
+            rset = MemSetting("lowbattwarn", "Low Battery Warning", rs)
+            rset.set_doc("Low Battery Warning: Off, Enabled")
+            basic.append(rset)
+
         # Roger
         rs = RadioSettingValueBoolean(_settings.roger)
         rset = MemSetting("roger", "Roger", rs)
         rset.set_doc("Roger: Off, Enabled")
         basic.append(rset)
 
+        # Remove CT/DCS
+        if self._has_remove_ctdcs:
+            rs = RadioSettingValueBoolean(_settings.removectdcs)
+            rset = MemSetting("removectdcs", "Remove CT/DCS", rs)
+            rset.set_doc("Remove CT/DCS: Off, Enabled")
+            basic.append(rset)
+
         return group
 
     def set_settings(self, settings):
@@ -503,3 +699,69 @@
 
     # SKU #: A9294D (same as SKU #: A9294B (2 pack))
     # Serial #: 24XXR777XXXXXXX
+
+
[email protected]
+class RT21HRadio(H777V4BaseRadio):
+    """RETEVIS RT21H"""
+    VENDOR = "Retevis"
+    MODEL = "RT21H"
+
+    # SKU #: A9118R (sold as FRS radio but supports full band TX/RX)
+    # SKU #: A9118S (sold as PMR radio but supports full band TX/RX)
+
+    IDENT = [b'SMP558\x02\xFF', b'SMP558\x02\x00']
+    BLOCK_SIZE = 0x0C
+    BLOCK_SIZE_UP = 0x0C
+
+    VOXD_LIST = ["0.5", "1.0", "1.5", "2.0", "2.5", "3.0", "3.5"]
+    VOXD_DOC = "VOX Delay: 0.5, 1.0, 1.5, 2.0, 2.5, 3.0, 3.5 seconds"
+    VOXL_DOC = "VOX Level: 1, 2, 3, 4, 5, 6, 7, 8, 9"
+
+    _has_codeswitch = False
+    _has_compander = True
+    _has_low_battery_warning = True
+    _has_remove_ctdcs = True
+    _has_scramble_options = True
+    _has_sidekey1 = True
+    _has_sidekey2 = False
+    _has_spec_code = True
+    _has_spec_mode = True
+    _has_vox_level_off = False
+
+    POWER_LEVELS = [chirp_common.PowerLevel("Low", watts=0.50),
+                    chirp_common.PowerLevel("High", watts=2.00)
+                    ]
+    VALID_BANDS = [(400000000, 502000000)]
+
+    PROGRAM_CMD = b"T210GAM"
+    _ranges = [
+               (0x0000, 0x00D8),
+              ]
+    _memsize = 0x00D8
+
+    def process_mmap(self):
+        self._memobj = bitwise.parse(MEM_FORMAT_RT21H, self._mmap)
+
+    def set_settings(self, settings):
+        for element in settings:
+            if not isinstance(element, RadioSetting):
+                self.set_settings(element)
+                continue
+
+            obj = self._memobj.settings
+            setting = element.get_name()
+
+            try:
+                if setting == "vox_level":
+                    # UI index 0-8 ? firmware value 1-9
+                    value = min(9, max(1, int(element.value) + 1))
+                else:
+                    value = element.value
+
+                LOG.debug("Setting %s = %s", setting, value)
+                setattr(obj, setting, value)
+
+            except Exception:
+                LOG.debug("Error applying setting %s", setting)
+                raise
Binary files old/chirp-20260220/tests/images/Retevis_RT21H.img and 
new/chirp-20260227/tests/images/Retevis_RT21H.img differ
diff -urN '--exclude=CVS' '--exclude=.cvsignore' '--exclude=.svn' 
'--exclude=.svnignore' old/chirp-20260220/tests/unit/test_chirp_common.py 
new/chirp-20260227/tests/unit/test_chirp_common.py
--- old/chirp-20260220/tests/unit/test_chirp_common.py  2026-02-20 
00:42:21.000000000 +0100
+++ new/chirp-20260227/tests/unit/test_chirp_common.py  2026-02-26 
03:26:40.000000000 +0100
@@ -72,11 +72,9 @@
         self.assertTrue(chirp_common.is_version_newer('daily-20180101'))
 
     def test_from_Hz(self):
-        # FIXME: These are wrong! Adding them here purely to test the
-        # python3 conversion, but they should be fixed.
-        self.assertEqual(140, chirp_common.from_GHz(14000000001))
-        self.assertEqual(140, chirp_common.from_MHz(14000001))
-        self.assertEqual(140, chirp_common.from_kHz(14001))
+        self.assertEqual(140, chirp_common.from_GHz(140000000001))
+        self.assertEqual(140, chirp_common.from_MHz(140000001))
+        self.assertEqual(140, chirp_common.from_kHz(140001))
 
     def test_mem_to_from_csv(self):
         mem1 = chirp_common.Memory()

++++++ chirp.obsinfo ++++++
--- /var/tmp/diff_new_pack.Rm3bQu/_old  2026-03-01 22:15:54.356629279 +0100
+++ /var/tmp/diff_new_pack.Rm3bQu/_new  2026-03-01 22:15:54.356629279 +0100
@@ -1,5 +1,5 @@
 name: chirp
-version: 20260220
-mtime: 1771544541
-commit: 1467519e792e8ebcc9a33dc40df0b2e273ce9a53
+version: 20260227
+mtime: 1772072800
+commit: bc56ae93ab3cd1d3d7d0f3e96b86e9b757155372
 

Reply via email to