[chirp_devel] Fwd: [PATCH 1 of 4] [RT21] Driver Maintenance: retevis_rt21.py

Jim Unroe
Sat Apr 3 18:02:30 PDT 2021


---------- Forwarded message ---------
From: Jim Unroe <kc9hi at comcast.net>
Date: Sat, Apr 3, 2021 at 8:59 PM
Subject: [PATCH 1 of 4] [RT21] Driver Maintenance: retevis_rt21.py
To: <Rock.Unroe at gmail.com>


# HG changeset patch
# User Jim Unroe <rock.unroe at gmail.com>
# Date 1617487505 14400
#      Sat Apr 03 18:05:05 2021 -0400
# Node ID d292f6aff5e0687bff7cf839f5de3e3164fe9f41
# Parent  b4e9bcbae55f10d09d7e385f402cad104ab56269
[RT21] Driver Maintenance: retevis_rt21.py

This patch is prepare the driver for adding additional radio models.

1: fixes some incorrect code
2: reformats some code
3: allows CHIRP to make 5 attempts at putting radio into programming mode.

Other than for number 3, no features or settings have been added or removed from
this driver.

Related to #8957, #8661 and #8959

diff -r b4e9bcbae55f -r d292f6aff5e0 chirp/drivers/retevis_rt21.py
--- a/chirp/drivers/retevis_rt21.py     Thu Apr 01 12:39:47 2021 -0400
+++ b/chirp/drivers/retevis_rt21.py     Sat Apr 03 18:05:05 2021 -0400
@@ -1,4 +1,4 @@
-# Copyright 2016 Jim Unroe <rock.unroe at gmail.com>
+# Copyright 2021 Jim Unroe <rock.unroe at 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
@@ -18,72 +18,75 @@
 import struct
 import logging

-from chirp import chirp_common, directory, memmap
-from chirp import bitwise, errors, util
-from chirp.settings import RadioSetting, RadioSettingGroup, \
-    RadioSettingValueInteger, RadioSettingValueList, \
-    RadioSettingValueBoolean, RadioSettings
+from chirp import (
+    bitwise,
+    chirp_common,
+    directory,
+    errors,
+    memmap,
+    util,
+)
+from chirp.settings import (
+    RadioSetting,
+    RadioSettingGroup,
+    RadioSettings,
+    RadioSettingValueBoolean,
+    RadioSettingValueInteger,
+    RadioSettingValueList,
+    RadioSettingValueString,
+)

 LOG = logging.getLogger(__name__)

 MEM_FORMAT = """
 #seekto 0x0010;
 struct {
-  lbcd rxfreq[4];
-  lbcd txfreq[4];
-  ul16 rx_tone;
-  ul16 tx_tone;
-  u8 unknown1:3,
-     bcl:2,       // Busy Lock
+  lbcd rxfreq[4];       // RX Frequency           0-3
+  lbcd txfreq[4];       // TX Frequency           4-7
+  ul16 rx_tone;         // PL/DPL Decode          8-9
+  ul16 tx_tone;         // PL/DPL Encode          A-B
+  u8 unknown1:3,        //                        C
+     bcl:2,             // Busy Lock
      unknown2:3;
-  u8 unknown3:2,
-     highpower:1, // Power Level
-     wide:1,      // Bandwidth
+  u8 unknown3:2,        //                        D
+     highpower:1,       // Power Level
+     wide:1,            // Bandwidth
      unknown4:4;
-  u8 scramble_type:4,
+  u8 scramble_type:4,   // Scramble Type          E
      unknown5:4;
   u8 unknown6:4,
-     scramble_type2:4;
+     scramble_type2:4;  // Scramble Type 2        F
 } memory[16];

 #seekto 0x011D;
 struct {
   u8 unused:4,
-     pf1:4;        // Programmable Function Key 1
+     pf1:4;             // Programmable Function Key 1
 } keys;

 #seekto 0x012C;
 struct {
-  u8 use_scramble; // Scramble Enable
+  u8 use_scramble;      // Scramble Enable
   u8 unknown1[2];
-  u8 voice;        // Voice Annunciation
-  u8 tot;          // Time-out Timer
-  u8 totalert;     // Time-out Timer Pre-alert
+  u8 voice;             // Voice Annunciation
+  u8 tot;               // Time-out Timer
+  u8 totalert;          // Time-out Timer Pre-alert
   u8 unknown2[2];
-  u8 squelch;      // Squelch Level
-  u8 save;         // Battery Saver
+  u8 squelch;           // Squelch Level
+  u8 save;              // Battery Saver
   u8 unknown3[3];
-  u8 use_vox;      // VOX Enable
-  u8 vox;          // VOX Gain
+  u8 use_vox;           // VOX Enable
+  u8 vox;               // VOX Gain
 } settings;

 #seekto 0x017E;
-u8 skipflags[2];  // SCAN_ADD
+u8 skipflags[2];       // SCAN_ADD
 """

 CMD_ACK = "\x06"
-BLOCK_SIZE = 0x10
-
-RT21_POWER_LEVELS = [chirp_common.PowerLevel("Low", watts=1.00),
-                     chirp_common.PowerLevel("High", watts=2.50)]
-
-
-RT21_DTCS = sorted(chirp_common.DTCS_CODES +
-                   [17, 50, 55, 135, 217, 254, 305, 645, 765])

 BCL_LIST = ["Off", "Carrier", "QT/DQT"]
-SCRAMBLE_LIST = ["Scramble 1", "Scramble 2", "Scramble 3", "Scramble 4",
-                 "Scramble 5", "Scramble 6", "Scramble 7", "Scramble 8"]
+SCRAMBLE_LIST = ["%s" % x for x in range(1, 9)]
 TIMEOUTTIMER_LIST = ["%s seconds" % x for x in range(15, 615, 15)]
 TOTALERT_LIST = ["Off"] + ["%s seconds" % x for x in range(1, 11)]
 VOICE_LIST = ["Off", "Chinese", "English"]
@@ -101,19 +104,27 @@
     }


-def _rt21_enter_programming_mode(radio):
+def _enter_programming_mode(radio):
     serial = radio.pipe

-    try:
-        serial.write("PRMZUNE")
+    exito = False
+    for i in range(0, 5):
+        serial.write(radio._magic)
         ack = serial.read(1)
-    except:
-        raise errors.RadioError("Error communicating with radio")

-    if not ack:
-        raise errors.RadioError("No response from radio")
-    elif ack != CMD_ACK:
-        raise errors.RadioError("Radio refused to enter programming mode")
+        try:
+            if ack == CMD_ACK:
+                exito = True
+                break
+        except:
+            LOG.debug("Attempt #%s, failed, trying again" % i)
+            pass
+
+    # check if we had EXITO
+    if exito is False:
+        msg = "The radio did not accept program mode after five tries.\n"
+        msg += "Check you interface cable and power cycle your radio."
+        raise errors.RadioError(msg)

     try:
         serial.write("\x02")
@@ -121,7 +132,7 @@
     except:
         raise errors.RadioError("Error communicating with radio")

-    if not ident.startswith("P3207"):
+    if not ident == radio._fingerprint:
         LOG.debug(util.hexprint(ident))
         raise errors.RadioError("Radio returned unknown identification string")

@@ -135,7 +146,7 @@
         raise errors.RadioError("Radio refused to enter programming mode")


-def _rt21_exit_programming_mode(radio):
+def _exit_programming_mode(radio):
     serial = radio.pipe
     try:
         serial.write("E")
@@ -143,16 +154,16 @@
         raise errors.RadioError("Radio refused to exit programming mode")


-def _rt21_read_block(radio, block_addr, block_size):
+def _read_block(radio, block_addr, block_size):
     serial = radio.pipe

-    cmd = struct.pack(">cHb", 'R', block_addr, BLOCK_SIZE)
+    cmd = struct.pack(">cHb", 'R', block_addr, block_size)
     expectedresponse = "W" + cmd[1:]
     LOG.debug("Reading block %04x..." % (block_addr))

     try:
         serial.write(cmd)
-        response = serial.read(4 + BLOCK_SIZE)
+        response = serial.read(4 + block_size)
         if response[:4] != expectedresponse:
             raise Exception("Error reading block %04x." % (block_addr))

@@ -169,11 +180,11 @@
     return block_data


-def _rt21_write_block(radio, block_addr, block_size):
+def _write_block(radio, block_addr, block_size):
     serial = radio.pipe

-    cmd = struct.pack(">cHb", 'W', block_addr, BLOCK_SIZE)
-    data = radio.get_mmap()[block_addr:block_addr + BLOCK_SIZE]
+    cmd = struct.pack(">cHb", '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))
@@ -189,7 +200,7 @@

 def do_download(radio):
     LOG.debug("download")
-    _rt21_enter_programming_mode(radio)
+    _enter_programming_mode(radio)

     data = ""

@@ -199,17 +210,17 @@
     status.cur = 0
     status.max = radio._memsize

-    for addr in range(0, radio._memsize, BLOCK_SIZE):
-        status.cur = addr + BLOCK_SIZE
+    for addr in range(0, radio._memsize, radio.BLOCK_SIZE):
+        status.cur = addr + radio.BLOCK_SIZE
         radio.status_fn(status)

-        block = _rt21_read_block(radio, addr, BLOCK_SIZE)
+        block = _read_block(radio, addr, radio.BLOCK_SIZE)
         data += block

         LOG.debug("Address: %04x" % addr)
         LOG.debug(util.hexprint(block))

-    _rt21_exit_programming_mode(radio)
+    _exit_programming_mode(radio)

     return memmap.MemoryMap(data)

@@ -218,18 +229,18 @@
     status = chirp_common.Status()
     status.msg = "Uploading to radio"

-    _rt21_enter_programming_mode(radio)
+    _enter_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, BLOCK_SIZE):
-            status.cur = addr + BLOCK_SIZE
+        for addr in range(start_addr, end_addr, radio.BLOCK_SIZE_UP):
+            status.cur = addr + radio.BLOCK_SIZE_UP
             radio.status_fn(status)
-            _rt21_write_block(radio, addr, BLOCK_SIZE)
+            _write_block(radio, addr, radio.BLOCK_SIZE_UP)

-    _rt21_exit_programming_mode(radio)
+    _exit_programming_mode(radio)


 def model_match(cls, data):
@@ -245,6 +256,18 @@
     VENDOR = "Retevis"
     MODEL = "RT21"
     BAUD_RATE = 9600
+    BLOCK_SIZE = 0x10
+    BLOCK_SIZE_UP = 0x10
+
+    POWER_LEVELS = [chirp_common.PowerLevel("Low", watts=1.00),
+                    chirp_common.PowerLevel("High", watts=2.50)]
+
+    _magic = "PRMZUNE"
+    _fingerprint = "P3207s\xF8\xFF"
+    _upper = 16
+    _skipflags = True
+    _reserved = False
+    _gmrs = False

     _ranges = [
                (0x0000, 0x0400),
@@ -265,10 +288,10 @@
         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 = RT21_POWER_LEVELS
+        rf.valid_power_levels = self.POWER_LEVELS
         rf.valid_duplexes = ["", "-", "+", "split", "off"]
         rf.valid_modes = ["NFM", "FM"]  # 12.5 KHz, 25 kHz.
-        rf.memory_bounds = (1, 16)
+        rf.memory_bounds = (1, self._upper)
         rf.valid_tuning_steps = [2.5, 5., 6.25, 10., 12.5, 25.]
         rf.valid_bands = [(400000000, 480000000)]

@@ -278,11 +301,31 @@
         self._memobj = bitwise.parse(MEM_FORMAT, self._mmap)

     def sync_in(self):
-        self._mmap = do_download(self)
+        """Download from radio"""
+        try:
+            data = do_download(self)
+        except errors.RadioError:
+            # Pass through any real errors we raise
+            raise
+        except:
+            # 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):
-        do_upload(self)
+        """Upload to radio"""
+        try:
+            do_upload(self)
+        except:
+            # 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 get_raw_memory(self, number):
         return repr(self._memobj.memory[number - 1])
@@ -330,13 +373,14 @@
                   (txmode, _mem.tx_tone, rxmode, _mem.rx_tone))

     def get_memory(self, number):
-        bitpos = (1 << ((number - 1) % 8))
-        bytepos = ((number - 1) / 8)
-        LOG.debug("bitpos %s" % bitpos)
-        LOG.debug("bytepos %s" % bytepos)
+        if self._skipflags:
+            bitpos = (1 << ((number - 1) % 8))
+            bytepos = ((number - 1) / 8)
+            LOG.debug("bitpos %s" % bitpos)
+            LOG.debug("bytepos %s" % bytepos)
+            _skp = self._memobj.skipflags[bytepos]

         _mem = self._memobj.memory[number - 1]
-        _skp = self._memobj.skipflags[bytepos]

         mem = chirp_common.Memory()

@@ -368,23 +412,22 @@

         self._get_tone(_mem, mem)

-        mem.power = RT21_POWER_LEVELS[_mem.highpower]
+        mem.power = self.POWER_LEVELS[_mem.highpower]

         mem.skip = "" if (_skp & bitpos) else "S"
         LOG.debug("mem.skip %s" % mem.skip)

         mem.extra = RadioSettingGroup("Extra", "extra")

-        rs = RadioSetting("bcl", "Busy Channel Lockout",
-                          RadioSettingValueList(
-                              BCL_LIST, BCL_LIST[_mem.bcl]))
-        mem.extra.append(rs)
+        if self.MODEL == "RT21":
+            rs = RadioSettingValueList(BCL_LIST, BCL_LIST[_mem.bcl])
+            rset = RadioSetting("bcl", "Busy Channel Lockout", rs)
+            mem.extra.append(rset)

-        rs = RadioSetting("scramble_type", "Scramble Type",
-                          RadioSettingValueList(SCRAMBLE_LIST,
-                                                SCRAMBLE_LIST[
-                                                    _mem.scramble_type - 8]))
-        mem.extra.append(rs)
+            rs = RadioSettingValueList(SCRAMBLE_LIST,
+                                       SCRAMBLE_LIST[_mem.scramble_type - 8])
+            rset = RadioSetting("scramble_type", "Scramble Type", rs)
+            mem.extra.append(rset)

         return mem

@@ -427,13 +470,14 @@
                   (tx_mode, _mem.tx_tone, rx_mode, _mem.rx_tone))

     def set_memory(self, mem):
-        bitpos = (1 << ((mem.number - 1) % 8))
-        bytepos = ((mem.number - 1) / 8)
-        LOG.debug("bitpos %s" % bitpos)
-        LOG.debug("bytepos %s" % bytepos)
+        if self._skipflags:
+            bitpos = (1 << ((mem.number - 1) % 8))
+            bytepos = ((mem.number - 1) / 8)
+            LOG.debug("bitpos %s" % bitpos)
+            LOG.debug("bytepos %s" % bytepos)
+            _skp = self._memobj.skipflags[bytepos]

         _mem = self._memobj.memory[mem.number - 1]
-        _skp = self._memobj.skipflags[bytepos]

         if mem.empty:
             _mem.set_raw("\xFF" * (_mem.size() / 8))
@@ -459,7 +503,7 @@

         self._set_tone(mem, _mem)

-        _mem.highpower = mem.power == RT21_POWER_LEVELS[1]
+        _mem.highpower = mem.power == self.POWER_LEVELS[1]

         if mem.skip != "S":
             _skp |= bitpos
@@ -475,65 +519,63 @@
                 setattr(_mem, setting.get_name(), setting.value)

     def get_settings(self):
-        _keys = self._memobj.keys
         _settings = self._memobj.settings
         basic = RadioSettingGroup("basic", "Basic Settings")
         top = RadioSettings(basic)

-        rs = RadioSetting("tot", "Time-out timer",
-                          RadioSettingValueList(
-                              TIMEOUTTIMER_LIST,
-                              TIMEOUTTIMER_LIST[_settings.tot - 1]))
-        basic.append(rs)
+        if self.MODEL == "RT21":
+            _keys = self._memobj.keys

-        rs = RadioSetting("totalert", "TOT Pre-alert",
-                          RadioSettingValueList(
-                              TOTALERT_LIST,
-                              TOTALERT_LIST[_settings.totalert]))
-        basic.append(rs)
+            rs = RadioSettingValueList(TIMEOUTTIMER_LIST,
+                                       TIMEOUTTIMER_LIST[_settings.tot - 1])
+            rset = RadioSetting("tot", "Time-out timer", rs)
+            basic.append(rset)

-        rs = RadioSetting("squelch", "Squelch Level",
-                          RadioSettingValueInteger(0, 9, _settings.squelch))
-        basic.append(rs)
+            rs = RadioSettingValueList(TOTALERT_LIST,
+                                       TOTALERT_LIST[_settings.totalert])
+            rset = RadioSetting("totalert", "TOT Pre-alert", rs)
+            basic.append(rset)

-        rs = RadioSetting("voice", "Voice Annumciation",
-                          RadioSettingValueList(
-                              VOICE_LIST, VOICE_LIST[_settings.voice]))
-        basic.append(rs)
+            rs = RadioSettingValueInteger(0, 9, _settings.squelch)
+            rset = RadioSetting("squelch", "Squelch Level", rs)
+            basic.append(rset)

-        rs = RadioSetting("save", "Battery Saver",
-                          RadioSettingValueBoolean(_settings.save))
-        basic.append(rs)
+            rs = RadioSettingValueList(VOICE_LIST, VOICE_LIST[_settings.voice])
+            rset = RadioSetting("voice", "Voice Annumciation", rs)
+            basic.append(rset)
+
+            rs = RadioSettingValueBoolean(_settings.save)
+            rset = RadioSetting("save", "Battery Saver", rs)
+            basic.append(rset)

-        rs = RadioSetting("use_scramble", "Scramble",
-                          RadioSettingValueBoolean(_settings.use_scramble))
-        basic.append(rs)
+            rs = RadioSettingValueBoolean(_settings.use_scramble)
+            rset = RadioSetting("use_scramble", "Scramble", rs)
+            basic.append(rset)

-        rs = RadioSetting("use_vox", "VOX",
-                          RadioSettingValueBoolean(_settings.use_vox))
-        basic.append(rs)
+            rs = RadioSettingValueBoolean(_settings.use_vox)
+            rset = RadioSetting("use_vox", "VOX", rs)
+            basic.append(rset)

-        rs = RadioSetting("vox", "VOX Gain",
-                          RadioSettingValueList(
-                              VOX_LIST, VOX_LIST[_settings.vox]))
-        basic.append(rs)
+            rs = RadioSettingValueList(VOX_LIST, VOX_LIST[_settings.vox])
+            rset = RadioSetting("vox", "VOX Gain", rs)
+            basic.append(rset)

-        def apply_pf1_listvalue(setting, obj):
-            LOG.debug("Setting value: " + str(setting.value) + " from list")
-            val = str(setting.value)
-            index = PF1_CHOICES.index(val)
-            val = PF1_VALUES[index]
-            obj.set_value(val)
+            def apply_pf1_listvalue(setting, obj):
+                LOG.debug("Setting value: " + str(
+                          setting.value) + " from list")
+                val = str(setting.value)
+                index = PF1_CHOICES.index(val)
+                val = PF1_VALUES[index]
+                obj.set_value(val)

-        if _keys.pf1 in PF1_VALUES:
-            idx = PF1_VALUES.index(_keys.pf1)
-        else:
-            idx = LIST_DTMF_SPECIAL_VALUES.index(0x04)
-        rs = RadioSetting("keys.pf1", "PF1 Key Function",
-                          RadioSettingValueList(PF1_CHOICES,
-                                                PF1_CHOICES[idx]))
-        rs.set_apply_callback(apply_pf1_listvalue, _keys.pf1)
-        basic.append(rs)
+            if _keys.pf1 in PF1_VALUES:
+                idx = PF1_VALUES.index(_keys.pf1)
+            else:
+                idx = LIST_DTMF_SPECIAL_VALUES.index(0x04)
+            rs = RadioSettingValueList(PF1_CHOICES, PF1_CHOICES[idx])
+            rset = RadioSetting("keys.pf1", "PF1 Key Function", rs)
+            rset.set_apply_callback(apply_pf1_listvalue, _keys.pf1)
+            basic.append(rset)

         return top

@@ -568,17 +610,23 @@

     @classmethod
     def match_model(cls, filedata, filename):
-        match_size = False
-        match_model = False
+        if cls.MODEL == "RT21":
+            # The RT21 is pre-metadata, so do old-school detection
+            match_size = False
+            match_model = False

-        # testing the file data size
-        if len(filedata) in [0x0400, ]:
-            match_size = True
+            # testing the file data size
+            if len(filedata) in [0x0400, ]:
+                match_size = True

-        # testing the model fingerprint
-        match_model = model_match(cls, filedata)
+            # testing the model fingerprint
+            match_model = model_match(cls, filedata)

-        if match_size and match_model:
-            return True
+            if match_size and match_model:
+                return True
+            else:
+                return False
         else:
+            # Radios that have always been post-metadata, so never do
+            # old-school detection
             return False



More information about the chirp_devel mailing list