[chirp_devel] [PATCH] [UV-50X3] Add Support for BTech UV-50X3

Jim Unroe
Tue Jul 12 16:46:54 PDT 2016


# HG changeset patch
# User Jim Unroe <rock.unroe at gmail.com>
# Date 1468366999 14400
# Node ID b717d6b593bcb8e9f170e571173c51129a6a62da
# Parent  971c5f5430d90e7bfe37b8d9235790ae5cae7d9f
[UV-50X3] Add Support for BTech UV-50X3

This patch adds basic support (500 left memories/500 right memories) plus
all per-channel settings. It also and exposes the structures that will be used
to add settings in future patches.

related to #3815

diff -r 971c5f5430d9 -r b717d6b593bc chirp/drivers/vgc.py
--- /dev/null	Thu Jan 01 00:00:00 1970 +0000
+++ b/chirp/drivers/vgc.py	Tue Jul 12 19:43:19 2016 -0400
@@ -0,0 +1,828 @@
+# Copyright 2016:
+# * Jim Unroe KC9HI, <rock.unroe at gmail.com>
+# * Pavel Milanes CO7WT <pavelmc 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
+# the Free Software Foundation, either version 2 of the License, or
+# (at your option) any later version.
+#
+# This program is distributed in the hope that it will be useful,
+# but WITHOUT ANY WARRANTY; without even the implied warranty of
+# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
+# GNU General Public License for more details.
+#
+# You should have received a copy of the GNU General Public License
+# along with this program.  If not, see <http://www.gnu.org/licenses/>.
+
+import time
+import struct
+import logging
+import re
+
+LOG = logging.getLogger(__name__)
+
+from chirp import chirp_common, directory, memmap
+from chirp import bitwise, errors, util
+from chirp.settings import RadioSettingGroup, RadioSetting, \
+    RadioSettingValueBoolean, RadioSettingValueList, \
+    RadioSettingValueString, RadioSettingValueInteger, \
+    RadioSettingValueFloat, RadioSettings
+from textwrap import dedent
+
+MEM_FORMAT = """
+struct mem {
+  lbcd rxfreq[4];
+  lbcd txfreq[4];
+  lbcd rxtone[2];
+  lbcd txtone[2];
+  u8 unknown0:2,
+     txp:2,
+     wn:2,
+     unknown1:1,
+     bcl:1;
+  u8 unknown2:2,
+     revert:1,
+     dname:1,
+     unknown3:4;
+  u8 unknown4[2];
+};
+
+struct nam {
+  char name[6];
+  u8 unknown1[2];
+};
+
+#seekto 0x0000;
+struct mem left_memory[500];
+
+#seekto 0x2000;
+struct mem right_memory[500];
+
+#seekto 0x4000;
+struct nam left_names[500];
+
+#seekto 0x5000;
+struct nam right_names[500];
+
+#seekto 0x6000;
+u8 left_usedflags[64];
+
+#seekto 0x6040;
+u8 left_scanflags[64];
+
+#seekto 0x6080;
+u8 right_usedflags[64];
+
+#seekto 0x60C0;
+u8 right_scanflags[64];
+
+#seekto 0x6160;
+struct {
+  char line32[32];
+} embedded_msg;
+
+#seekto 0x6180;
+struct {
+  u8  sbmute:2,        // sub band mute
+      unknown1:1,
+      workmodb:1,      // work mode (right side)
+      dw:1,            // dual watch
+      audio:1,         // audio output mode (stereo/mono)
+      unknown2:1,
+      workmoda:1;      // work mode (left side)
+  u8  scansb:1,        // scan stop beep
+      aftone:3,        // af tone control
+      scand:1,         // scan directon
+      scanr:3;         // scan resume
+  u8  rxexp:1,         // rx expansion
+      ptt:1,           // ptt mode
+      display:1,       // display select (frequency/clock)
+      omode:1,         // operaton mode
+      beep:2,          // beep volume
+      spkr:2;          // speaker
+  u8  cpuclk:1,        // operating mode(cpu clock)
+      fkey:3,          // fkey function
+      mrscan:1,        // memory scan type
+      color:3;         // lcd backlight color
+  u8  vox:2,           // vox
+      voxs:3,          // vox sensitivity
+      mgain:3;         // mic gain
+  u8  wbandb:4,        // work band (right side)
+      wbanda:4;        // work band (left side)
+  u8  sqlb:4,          // squelch level (right side)
+      sqla:4;          // squelch level (left side)
+  u8  apo:4,           // auto power off
+      ars:1,           // automatic repeater shift
+      tot:3;           // time out timer
+  u8  stepb:4,         // auto step (right side)
+      stepa:4;         // auto step (left side)
+  u8  rxcoverm:1,      // rx coverage-memory
+      lcdc:3,          // lcd contrast
+      rxcoverv:1,      // rx coverage-vfo
+      lcdb:3;          // lcd brightness
+  u8  smode:1,         // smart function mode
+      timefmt:1,       // time format
+      datefmt:2,       // date format
+      timesig:1,       // time signal
+      keyb:3;          // key/led brightness
+  u8  dwstop:1,        // dual watch stop
+      unknown3:1,
+      sqlexp:1,        // sql expansion
+      decbandsel:1,    // decoding band select
+      dtmfmodenc:1,    // dtmf mode encode
+      bell:3;          // bell ringer
+  u8  unknown4:2,
+      btime:6;         // lcd backlight time
+  u8  unknown5:2,
+      tz:6;            // time zone
+  u8  unknown618E;
+  u8  unknown618F;
+  ul16  offseta;       // work offset (left side)
+  ul16  offsetb;       // work offset (right side)
+  ul16  mrcha;         // selected memory channel (left)
+  ul16  mrchb;         // selected memory channel (right)
+  ul16  wpricha;       // work priority channel (left)
+  ul16  wprichb;       // work priority channel (right)
+  u8  unknown6:3,
+      datasql:2,       // data squelch
+      dataspd:1,       // data speed
+      databnd:2;       // data band select
+  u8  unknown7:1,
+      pfkey2:3,        // mic p2 key
+      unknown8:1,
+      pfkey1:3;        // mic p1 key
+  u8  unknown9:1,
+      pfkey4:3,        // mic p4 key
+      unknowna:1,
+      pfkey3:3;        // mic p3 key
+  u8  unknownb:7,
+      dtmfmoddec:1;    // dtmf mode decode
+} settings;
+
+#seekto 0x61B0;
+struct {
+  char line16[16];
+} poweron_msg;
+
+#seekto 0x6300;
+struct {
+  u8  unknown1:3,
+      ttdgt:5;         // dtmf digit time
+  u8  unknown2:3,
+      ttint:5;         // dtmf interval time
+  u8  unknown3:3,
+      tt1stdgt:5;      // dtmf 1st digit time
+  u8  unknown4:3,
+      tt1stdly:5;      // dtmf 1st digit delay
+  u8  unknown5:3,
+      ttdlyqt:5;       // dtmf delay when use qt
+  u8  unknown6:3,
+      ttdkey:5;        // dtmf d key function
+  u8  unknown7;
+  u8  unknown8:4,
+      ttautod:4;       // dtmf auto dial group
+} dtmf;
+
+#seekto 0x6330;
+struct {
+  u8  unknown1:7,
+      ttsig:1;         // dtmf signal
+  u8  unknown2:4,
+      ttintcode:4;     // dtmf interval code
+  u8  unknown3:5,
+      ttgrpcode:3;     // dtmf group code
+  u8  unknown4:4,
+      ttautorst:4;     // dtmf auto reset time
+  u8  unknown5:5,
+      ttalert:3;       // dtmf alert tone/transpond
+} dtmf2;
+
+#seekto 0x6360;
+struct {
+  u8 code1[8];         // dtmf code
+  u8 code1_len;        // dtmf code length
+  u8 unknown1[7];
+  u8 code2[8];         // dtmf code
+  u8 code2_len;        // dtmf code length
+  u8 unknown2[7];
+  u8 code3[8];         // dtmf code
+  u8 code3_len;        // dtmf code length
+  u8 unknown3[7];
+  u8 code4[8];         // dtmf code
+  u8 code4_len;        // dtmf code length
+  u8 unknown4[7];
+  u8 code5[8];         // dtmf code
+  u8 code5_len;        // dtmf code length
+  u8 unknown5[7];
+  u8 code6[8];         // dtmf code
+  u8 code6_len;        // dtmf code length
+  u8 unknown6[7];
+  u8 code7[8];         // dtmf code
+  u8 code7_len;        // dtmf code length
+  u8 unknown7[7];
+  u8 code8[8];         // dtmf code
+  u8 code8_len;        // dtmf code length
+  u8 unknown8[7];
+  u8 code9[8];         // dtmf code
+  u8 code9_len;        // dtmf code length
+  u8 unknown9[7];
+} dtmfcode;
+
+"""
+
+MEM_SIZE = 0x8000
+BLOCK_SIZE = 0x40
+MODES = ["FM", "Auto", "NFM", "AM"]
+SKIP_VALUES = ["", "S"]
+TONES = chirp_common.TONES
+DTCS_CODES = chirp_common.DTCS_CODES
+NAME_LENGTH = 6
+DTMF_CHARS = list("0123456789ABCD*#")
+STIMEOUT = 1
+
+# valid chars on the LCD
+VALID_CHARS = chirp_common.CHARSET_ALPHANUMERIC + \
+    "`{|}!\"#$%&'()*+,-./:;<=>?@[]^_"
+
+# Power Levels
+POWER_LEVELS = [chirp_common.PowerLevel("Low", watts=5),
+                chirp_common.PowerLevel("Mid", watts=20),
+                chirp_common.PowerLevel("High", watts=50)]
+
+# B-TECH UV-50X3 id string
+UV50X3_id  = "VGC6600MD"
+
+
+def _clean_buffer(radio):
+    radio.pipe.timeout = 0.005
+    junk = radio.pipe.read(256)
+    radio.pipe.timeout = STIMEOUT
+    if junk:
+        Log.debug("Got %i bytes of junk before starting" % len(junk))
+
+
+def _check_for_double_ack(radio):
+    radio.pipe.timeout = 0.005
+    c = radio.pipe.read(1)
+    radio.pipe.timeout = STIMEOUT
+    if c and c != '\x06':
+        _exit_program_mode(radio)
+        raise errors.RadioError('Expected nothing or ACK, got %r' % c)
+
+
+def _rawrecv(radio, amount):
+    """Raw read from the radio device"""
+    data = ""
+    try:
+        data = radio.pipe.read(amount)
+    except:
+        _exit_program_mode(radio)
+        msg = "Generic error reading data from radio; check your cable."
+        raise errors.RadioError(msg)
+
+    if len(data) != amount:
+        _exit_program_mode(radio)
+        msg = "Error reading data from radio: not the amount of data we want."
+        raise errors.RadioError(msg)
+
+    return data
+
+
+def _rawsend(radio, data):
+    """Raw send to the radio device"""
+    try:
+        radio.pipe.write(data)
+    except:
+        raise errors.RadioError("Error sending data to radio")
+
+
+def _make_frame(cmd, addr, length, data=""):
+    """Pack the info in the headder format"""
+    frame = struct.pack(">BHB", ord(cmd), addr, length)
+    # add the data if set
+    if len(data) != 0:
+        frame += data
+    # return the data
+    return frame
+
+
+def _recv(radio, addr, length=BLOCK_SIZE):
+    """Get data from the radio """
+    # read 4 bytes of header
+    hdr = _rawrecv(radio, 4)
+
+    # check for unexpected extra command byte
+    c, a, l = struct.unpack(">BHB", hdr)
+    if hdr[0:2] == "WW" and a != addr:
+        # extra command byte detected
+        # throw away the 1st byte and add the next byte in the buffer
+        hdr = hdr[1:] + _rawrecv(radio, 1)
+
+    # read 64 bytes (0x40) of data
+    data = _rawrecv(radio, (BLOCK_SIZE))
+
+    # DEBUG
+    LOG.info("Response:")
+    LOG.debug(util.hexprint(hdr + data))
+
+    c, a, l = struct.unpack(">BHB", hdr)
+    if a != addr or l != length or c != ord("W"):
+        _exit_program_mode(radio)
+        LOG.error("Invalid answer for block 0x%04x:" % addr)
+        LOG.debug("CMD: %s  ADDR: %04x  SIZE: %02x" % (c, a, l))
+        raise errors.RadioError("Unknown response from the radio")
+
+    return data
+
+
+def _do_ident(radio):
+    """Put the radio in PROGRAM mode & identify it"""
+    #  set the serial discipline
+    radio.pipe.baudrate = 115200
+    radio.pipe.parity = "N"
+    radio.pipe.timeout = STIMEOUT
+
+    # flush input buffer
+    _clean_buffer(radio)
+
+    magic = "V66LINK"
+
+    _rawsend(radio, magic)
+
+    # Ok, get the ident string
+    ident = _rawrecv(radio, 9)
+
+    # check if ident is OK
+    if ident != radio.IDENT:
+        # bad ident
+        msg = "Incorrect model ID, got this:"
+        msg +=  util.hexprint(ident)
+        LOG.debug(msg)
+        raise errors.RadioError("Radio identification failed.")
+
+    # DEBUG
+    LOG.info("Positive ident, got this:")
+    LOG.debug(util.hexprint(ident))
+
+    return True
+
+
+def _exit_program_mode(radio):
+    endframe = "\x45"
+    _rawsend(radio, endframe)
+
+
+def _download(radio):
+    """Get the memory map"""
+
+    # put radio in program mode and identify it
+    _do_ident(radio)
+
+    # UI progress
+    status = chirp_common.Status()
+    status.cur = 0
+    status.max = MEM_SIZE / BLOCK_SIZE
+    status.msg = "Cloning from radio..."
+    radio.status_fn(status)
+
+    data = ""
+    for addr in range(0, MEM_SIZE, BLOCK_SIZE):
+        frame = _make_frame("R", addr, BLOCK_SIZE)
+        # DEBUG
+        LOG.info("Request sent:")
+        LOG.debug(util.hexprint(frame))
+
+        # sending the read request
+        _rawsend(radio, frame)
+
+        # now we read
+        d = _recv(radio, addr)
+
+        # aggregate the data
+        data += d
+
+        # UI Update
+        status.cur = addr / BLOCK_SIZE
+        status.msg = "Cloning from radio..."
+        radio.status_fn(status)
+
+    _exit_program_mode(radio)
+
+    return data
+
+
+def _upload(radio):
+    """Upload procedure"""
+
+    MEM_SIZE = 0x7000
+
+    # put radio in program mode and identify it
+    _do_ident(radio)
+
+    # UI progress
+    status = chirp_common.Status()
+    status.cur = 0
+    status.max = MEM_SIZE / BLOCK_SIZE
+    status.msg = "Cloning to radio..."
+    radio.status_fn(status)
+
+    # the fun start here
+    for addr in range(0, MEM_SIZE, BLOCK_SIZE):
+        # sending the data
+        data = radio.get_mmap()[addr:addr + BLOCK_SIZE]
+
+        frame = _make_frame("W", addr, BLOCK_SIZE, data)
+
+        _rawsend(radio, frame)
+
+        # receiving the response
+        ack = _rawrecv(radio, 1)
+        if ack != "\x06":
+            _exit_program_mode(radio)
+            msg = "Bad ack writing block 0x%04x" % addr
+            raise errors.RadioError(msg)
+
+        _check_for_double_ack(radio)
+
+        # UI Update
+        status.cur = addr / BLOCK_SIZE
+        status.msg = "Cloning to radio..."
+        radio.status_fn(status)
+
+    _exit_program_mode(radio)
+
+
+def model_match(cls, data):
+    """Match the opened/downloaded image to the correct version"""
+    rid = data[0x6140:0x6148]
+
+    #if rid in cls._fileid:
+    if rid in cls.IDENT:
+        return True
+
+    return False
+
+
+class VGCStyleRadio(chirp_common.CloneModeRadio,
+                    chirp_common.ExperimentalRadio):
+    """BTECH's UV-50X3"""
+    VENDOR = "BTECH"
+    _air_range = (108000000, 136000000)
+    _vhf_range = (136000000, 174000000)
+    _vhf2_range = (174000000, 250000000)
+    _220_range = (222000000, 225000000)
+    _gen1_range = (300000000, 400000000)
+    _uhf_range = (400000000, 480000000)
+    _gen2_range = (480000000, 520000000)
+    _upper = 499
+    MODEL = ""
+    IDENT = ""
+
+    @classmethod
+    def get_prompts(cls):
+        rp = chirp_common.RadioPrompts()
+        rp.experimental = \
+            ('The UV-50X3 driver is a beta version.\n'
+             '\n'
+             'Please save an unedited copy of your first successful\n'
+             'download to a CHIRP Radio Images(*.img) file.'
+             )
+        rp.pre_download = _(dedent("""\
+            Follow this instructions to download your info:
+
+            1 - Turn off your radio
+            2 - Connect your interface cable
+            3 - Turn on your radio
+            4 - Do the download of your radio data
+            """))
+        rp.pre_upload = _(dedent("""\
+            Follow this instructions to upload your info:
+
+            1 - Turn off your radio
+            2 - Connect your interface cable
+            3 - Turn on your radio
+            4 - Do the upload of your radio data
+            """))
+        return rp
+
+    def get_features(self):
+        rf = chirp_common.RadioFeatures()
+        rf.has_settings = False
+        rf.has_bank = False
+        rf.has_tuning_step = False
+        rf.can_odd_split = True
+        rf.has_name = True
+        rf.has_offset = True
+        rf.has_mode = True
+        rf.has_dtcs = True
+        rf.has_rx_dtcs = True
+        rf.has_dtcs_polarity = True
+        rf.has_ctone = True
+        rf.has_cross = True
+        rf.has_sub_devices = self.VARIANT == ""
+        rf.valid_modes = MODES
+        rf.valid_characters = VALID_CHARS
+        rf.valid_duplexes = ["", "-", "+", "split", "off"]
+        rf.valid_tmodes = ['', 'Tone', 'TSQL', 'DTCS', 'Cross']
+        rf.valid_cross_modes = [
+            "Tone->Tone",
+            "DTCS->",
+            "->DTCS",
+            "Tone->DTCS",
+            "DTCS->Tone",
+            "->Tone",
+            "DTCS->DTCS"]
+        rf.valid_power_levels = POWER_LEVELS
+        rf.valid_skips = SKIP_VALUES
+        rf.valid_name_length = NAME_LENGTH
+        rf.valid_dtcs_codes = DTCS_CODES
+        rf.valid_bands = [self._air_range,
+                          self._vhf_range,
+                          self._vhf2_range,
+                          self._220_range,
+                          self._gen1_range,
+                          self._uhf_range,
+                          self._gen2_range]
+        rf.memory_bounds = (0, self._upper)
+        return rf
+
+    def get_sub_devices(self):
+        return [UV50X3Left(self._mmap), UV50X3Right(self._mmap)]
+
+    def sync_in(self):
+        """Download from radio"""
+        try:
+            data = _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 = memmap.MemoryMap(data)
+        self.process_mmap()
+
+    def sync_out(self):
+        """Upload to radio"""
+        try:
+            _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 process_mmap(self):
+        """Process the mem map into the mem object"""
+        self._memobj = bitwise.parse(MEM_FORMAT, self._mmap)
+
+    def get_raw_memory(self, number):
+        return repr(self._memobj.memory[number])
+
+    def decode_tone(self, val):
+        """Parse the tone data to decode from mem, it returns:
+        Mode (''|DTCS|Tone), Value (None|###), Polarity (None,N,R)"""
+        if val.get_raw() == "\xFF\xFF":
+            return '', None, None
+
+        val = int(val)
+        if val >= 12000:
+            a = val - 12000
+            return 'DTCS', a, 'R'
+        elif val >= 8000:
+            a = val - 8000
+            return 'DTCS', a, 'N'
+        else:
+            a = val / 10.0
+            return 'Tone', a, None
+
+    def encode_tone(self, memval, mode, value, pol):
+        """Parse the tone data to encode from UI to mem"""
+        if mode == '':
+            memval[0].set_raw(0xFF)
+            memval[1].set_raw(0xFF)
+        elif mode == 'Tone':
+            memval.set_value(int(value * 10))
+        elif mode == 'DTCS':
+            flag = 0x80 if pol == 'N' else 0xC0
+            memval.set_value(value)
+            memval[1].set_bits(flag)
+        else:
+            raise Exception("Internal error: invalid mode `%s'" % mode)
+
+    def _memory_obj(self, suffix=""):
+        return getattr(self._memobj, "%s_memory%s" % (self._vfo, suffix))
+
+    def _name_obj(self, suffix=""):
+        return getattr(self._memobj, "%s_names%s" % (self._vfo, suffix))
+
+    def _scan_obj(self, suffix=""):
+        return getattr(self._memobj, "%s_scanflags%s" % (self._vfo, suffix))
+
+    def _used_obj(self, suffix=""):
+        return getattr(self._memobj, "%s_usedflags%s" % (self._vfo, suffix))
+
+    def get_memory(self, number):
+        """Get the mem representation from the radio image"""
+        bitpos = (1 << (number % 8))
+        bytepos = (number / 8)
+
+        _mem = self._memory_obj()[number]
+        _names = self._name_obj()[number]
+        _scn = self._scan_obj()[bytepos]
+        _usd = self._used_obj()[bytepos]
+
+        isused = bitpos & int(_usd)
+        isscan = bitpos & int(_scn)
+
+        # Create a high-level memory object to return to the UI
+        mem = chirp_common.Memory()
+
+        # Memory number
+        mem.number = number
+
+        if not isused:
+            mem.empty = True
+            return mem
+
+        # Freq and offset
+        mem.freq = int(_mem.rxfreq) * 10
+        # tx freq can be blank
+        if _mem.get_raw()[4] == "\xFF":
+            # TX freq not set
+            mem.offset = 0
+            mem.duplex = "off"
+        else:
+            # TX feq set
+            offset = (int(_mem.txfreq) * 10) - mem.freq
+            if offset < 0:
+                mem.offset = abs(offset)
+                mem.duplex = "-"
+            elif offset > 0:
+                mem.offset = offset
+                mem.duplex = "+"
+            else:
+                mem.offset = 0
+
+        # skip
+        if not isscan:
+            mem.skip = "S"
+
+        # name TAG of the channel
+        mem.name = str(_names.name).strip("\xFF")
+
+        # power
+        mem.power = POWER_LEVELS[int(_mem.txp)]
+
+        # wide/narrow
+        mem.mode = MODES[int(_mem.wn)]
+
+        # tone data
+        rxtone = txtone = None
+        txtone = self.decode_tone(_mem.txtone)
+        rxtone = self.decode_tone(_mem.rxtone)
+        chirp_common.split_tone_decode(mem, txtone, rxtone)
+
+        # Extra
+        mem.extra = RadioSettingGroup("extra", "Extra")
+
+        bcl = RadioSetting("bcl", "Busy channel lockout",
+                              RadioSettingValueBoolean(bool(_mem.bcl)))
+        mem.extra.append(bcl)
+
+        revert = RadioSetting("revert", "Revert",
+                              RadioSettingValueBoolean(bool(_mem.revert)))
+        mem.extra.append(revert)
+
+        dname = RadioSetting("dname", "Display name",
+                             RadioSettingValueBoolean(bool(_mem.dname)))
+        mem.extra.append(dname)
+
+        return mem
+
+    def set_memory(self, mem):
+        """Set the memory data in the eeprom img from the UI"""
+        bitpos = (1 << (mem.number % 8))
+        bytepos = (mem.number / 8)
+
+        _mem = self._memory_obj()[mem.number]
+        _names = self._name_obj()[mem.number]
+        _scn = self._scan_obj()[bytepos]
+        _usd = self._used_obj()[bytepos]
+
+        if mem.empty:
+            _usd &= ~bitpos
+            _scn &= ~bitpos
+            _mem.set_raw("\xFF" * 16)
+            _names.name = ("\xFF" * 6)
+            return
+        else:
+            _usd |= bitpos
+
+        # frequency
+        _mem.rxfreq = mem.freq / 10
+
+        # duplex
+        if mem.duplex == "+":
+            _mem.txfreq = (mem.freq + mem.offset) / 10
+        elif mem.duplex == "-":
+            _mem.txfreq = (mem.freq - mem.offset) / 10
+        elif mem.duplex == "off":
+            for i in _mem.txfreq:
+                i.set_raw("\xFF")
+        elif mem.duplex == "split":
+            _mem.txfreq = mem.offset / 10
+        else:
+            _mem.txfreq = mem.freq / 10
+
+        # tone data
+        ((txmode, txtone, txpol), (rxmode, rxtone, rxpol)) = \
+            chirp_common.split_tone_encode(mem)
+        self.encode_tone(_mem.txtone, txmode, txtone, txpol)
+        self.encode_tone(_mem.rxtone, rxmode, rxtone, rxpol)
+
+        # name TAG of the channel
+        _names.name = mem.name.ljust(6, "\xFF")
+
+        # power level, # default power level is low
+        _mem.txp = 0 if mem.power is None else POWER_LEVELS.index(mem.power)
+
+        # wide/narrow
+        _mem.wn = MODES.index(mem.mode)
+
+        if mem.skip == "S":
+            _scn &= ~bitpos
+        else:
+            _scn |= bitpos
+
+        # autoset display to display name if filled
+        if mem.extra:
+            # mem.extra only seems to be populated when called from edit panel
+            dname = mem.extra["dname"]
+        else:
+            dname = None
+        if mem.name:
+            _mem.dname = True
+            if dname and not dname.changed():
+                dname.value = True
+        else:
+            _mem.dname = False
+            if dname and not dname.changed():
+                dname.value = False
+
+        # reseting unknowns, this has to be set by hand
+        _mem.unknown0 = 0
+        _mem.unknown1 = 0
+        _mem.unknown2 = 0
+        _mem.unknown3 = 0
+
+        # extra settings
+        if len(mem.extra) > 0:
+            # there are setting, parse
+            for setting in mem.extra:
+                setattr(_mem, setting.get_name(), setting.value)
+        else:
+            # there are no extra settings, load defaults
+            _mem.bcl = 0
+            _mem.revert = 0
+            _mem.dname = 1
+
+
+    @classmethod
+    def match_model(cls, filedata, filename):
+        match_size = False
+        match_model = False
+
+        # testing the file data size
+        if len(filedata) == MEM_SIZE:
+            match_size = True
+
+        # testing the firmware model fingerprint
+        match_model = model_match(cls, filedata)
+
+        if match_size and match_model:
+            return True
+        else:
+            return False
+
+
+ at directory.register
+class UV50X3(VGCStyleRadio):
+    """BTech UV-50X3"""
+    MODEL = "UV-50X3"
+    IDENT = UV50X3_id
+
+
+class UV50X3Left(UV50X3):
+    VARIANT = "Left"
+    _vfo = "left"
+
+
+class UV50X3Right(UV50X3):
+    VARIANT = "Right"
+    _vfo = "right"



More information about the chirp_devel mailing list