[chirp_devel] [PATCH] [New Model] Support for the BTECH Mobile Radios, update 1 for #3015
Jim Unroe
Sat Mar 26 17:11:16 PDT 2016
# HG changeset patch
# User Jim Unroe <rock.unroe at gmail.com>
# Date 1459036642 14400
# Node ID be962502a6c19e3a09de92ff7bdcded96e2c90a2
# Parent 1dea27fb59f7adefe00398d8e5648120cddc8e12
[New Model] Support for the BTECH Mobile Radios, update 1 for #3015
This patch includes the following changes:
Changes related to developer mailing list discussions
- Less intensive strategy to get big chunks of data instead the former more
fine grained but resource intensive one
- Removed our software serial timeout in favor of the system serial timeout
- Removed the reference of the second magic for the alpha radio
- Renamed the _do_magic() def
- Restructured the "super nested" code to a less nested one
- Removed the "not needed" time.sleep() before the read of just one ACK, in
favor of a serial system timeout
- Removed/changed some of the LOG.xxx()
- Removed the exception that goes to the UI about maxtime
- Renamed the base class as BTech (from btech)
Bug fixes
- Fixed the bug found by minuteman1120 about the duplex not working in some
cases
- New "split" algorithm for the duplex bug above
- Fixed a bug with the scode about 0x*F reseted to 0x*0
New
- Added support for the QYT KT-UV980 Radio
also related to #2673
diff -r 1dea27fb59f7 -r be962502a6c1 chirp/drivers/btech.py
--- a/chirp/drivers/btech.py Tue Mar 22 18:50:50 2016 -0700
+++ b/chirp/drivers/btech.py Sat Mar 26 19:57:22 2016 -0400
@@ -18,6 +18,7 @@
import time
import struct
import logging
+import sys
LOG = logging.getLogger(__name__)
@@ -160,6 +161,9 @@
MINI8900_fp = "M28854"
+# QYT KT UV-890
+KTUV890_fp = "H28854"
+
#### MAGICS
# for the Waccom Mini-8900
MSTRING_MINI8900 = "\x55\xA5\xB5\x45\x55\x45\x4d\x02"
@@ -169,37 +173,41 @@
MSTRING = "\x55\x20\x15\x09\x20\x45\x4d\x02"
+def _clean_buffer(radio):
+ """Cleaning the read serial buffer"""
+ radio.pipe.setTimeout(0.1)
+
+ try:
+ dump = radio.pipe.read(100)
+
+ except Exception:
+ raise errors.RadioError("Unknown error cleaning the serial buffer")
+
+
def _rawrecv(radio, amount):
- """Raw read from the radio device, new approach, this time a byte at
- a time as the original driver, the receive data has to be atomic"""
+ """Raw read from the radio device, less intensive way"""
+
data = ""
try:
- tdiff = 0
- start = time.time()
- maxtime = amount * 0.020
-
- while len(data) < amount and tdiff < maxtime:
- d = radio.pipe.read(1)
- if len(d) == 1:
- data += d
-
- # Delta time
- tdiff = time.time() - start
-
- # DEBUG
- if debug is True:
- LOG.debug("time diff %.04f maxtime %.04f, data: %d" %
- (tdiff, maxtime, len(data)))
+ # Getting the data with a dynamic timeout in the serial deppending on
+ # the amount of data we need
+ timeout = amount * 0.06
+ radio.pipe.setTimeout(timeout)
+ data = radio.pipe.read(amount)
# DEBUG
if debug is True:
LOG.debug("<== (%d) bytes:\n\n%s" %
(len(data), util.hexprint(data)))
+ # fail if no data is received
+ if len(data) == 0:
+ raise errors.RadioError("No data received from radio")
+
if len(data) < amount:
- LOG.error("Short reading %d bytes from the %d requested." %
- (len(data), amount))
+ LOG.warn("Short reading %d bytes from the %d requested." %
+ (len(data), amount))
except:
raise errors.RadioError("Error reading data from radio")
@@ -212,7 +220,6 @@
try:
for byte in data:
radio.pipe.write(byte)
- time.sleep(0.003)
# DEBUG
if debug is True:
@@ -242,123 +249,102 @@
def _recv(radio, addr):
- """Get data from the radio """
+ """Get data from the radio all at once to lower syscalls load"""
+ # Get the full 69 bytes at a time to reduce load
+ #
# 1 byte ACK +
# 4 bytes header +
- # data of length of data (as I see always 0x40 = 64 bytes)
+ # 64 bytes of data (BLOCK_SIZE)
- # catching ack
- ack = _rawrecv(radio, 1)
+ # get the whole block
+ block = _rawrecv(radio, BLOCK_SIZE + 5)
- # checking for a response
- if len(ack) != 1:
- msg = "No response in the read of the block #0x%04x" % addr
- LOG.error(msg)
- raise errors.RadioError(msg)
+ # basic check
+ if len(block) < (BLOCK_SIZE + 5):
+ raise errors.RadioError("Short read of the block 0x%04x" % addr)
- # valid data
+ # checking for the ack
+ ack = block[0]
if ack != ACK_CMD:
- msg = "Bad ack received from radio in block 0x%04x" % addr
- LOG.error(msg)
- LOG.debug("Bad ACK was 0x%02x" % ord(ack))
- raise errors.RadioError(msg)
+ raise errors.RadioError("Bad ack from radio in block 0x%04x" % addr)
- # Get the header + basic sanitize
- hdr = _rawrecv(radio, 4)
- if len(hdr) != 4:
- msg = "Short header for block: 0x%04x" % addr
- LOG.error(msg)
- raise errors.RadioError(msg)
-
- # receive and validate the header
+ # header validation
+ hdr = block[1:5]
c, a, l = struct.unpack(">BHB", hdr)
if a != addr or l != BLOCK_SIZE or c != ord("X"):
- msg = "Invalid answer for block 0x%04x:" % addr
- LOG.error(msg)
+ LOG.debug("Invalid header for block 0x%04x" % addr)
LOG.debug("CMD: %s ADDR: %04x SIZE: %02x" % (c, a, l))
- raise errors.RadioError(msg)
+ raise errors.RadioError("Invalid header for block 0x%04x:" % addr)
# Get the data
- data = _rawrecv(radio, l)
-
- # basic validation
- if len(data) != l:
- msg = "Short block of data in block #0x%04x" % addr
- LOG.error(msg)
- raise errors.RadioError(msg)
-
+ data = block[5:]
return data
-def _do_magic(radio, status):
- """Try to put the radio in program mode and get the ident string
- it will make multiple tries"""
+def _start_clone_mode(radio, status):
+ """Put the radio in clone mode and get the ident string
+ using multiple tries"""
# how many tries
- tries = 5
+ tries = 3
# prep the data to show in the UI
status.cur = 0
status.msg = "Identifying the radio..."
- status.max = len(radio._magic) * tries
+ status.max = tries * len(radio._magic)
radio.status_fn(status)
mc = 0
+ radio.pipe.setTimeout(0.6)
+
+ def send_magic_word(m, magic):
+ """Send the magic word to the radio, catching the answer"""
+
+ # cleaning the buffer and set timeout
+ _clean_buffer(radio)
+
+ for a in range(tries):
+ # Update the UI
+ status.cur = m * a + a
+ radio.status_fn(status)
+
+ # send the magic word
+ _send(radio, magic)
+
+ # Now you get a x06 of ACK if all goes well
+ ack = radio.pipe.read(1)
+
+ if ack == "\x06":
+ # DEBUG
+ LOG.info("Magic ACK received")
+ status.msg = "Positive Ident!"
+ status.cur = status.max
+ radio.status_fn(status)
+
+ return True
+
+ # if the magic word don't open the gate we must inform it
+ status.cur = status.max
+ radio.status_fn(status)
+ return False
try:
- # do the magic
+ # try to put the radio in clone mode
for magic in radio._magic:
- # we try a few times
- for a in range(0, tries):
- # Update the UI
- status.cur = (mc * tries) + a
- radio.status_fn(status)
-
- # cleaning the serial buffer, try wrapped
- try:
- radio.pipe.flushInput()
- except:
- msg = "Error with a serial rx buffer flush at _do_magic"
- LOG.error(msg)
- raise errors.RadioError(msg)
-
- # send the magic a byte at a time
- for byte in magic:
- ack = _rawrecv(radio, 1)
- _send(radio, byte)
-
- # A explicit time delay, with a longer one for the UV-5001
- if "5001" in radio.MODEL:
- time.sleep(0.5)
- else:
- time.sleep(0.1)
-
- # Now you get a x06 of ACK if all goes well
- ack = _rawrecv(radio, 1)
-
- if ack == "\x06":
- # DEBUG
- LOG.info("Magic ACK received")
- status.msg = "Positive Ident!"
- status.cur = status.max
- radio.status_fn(status)
-
- return True
+ # send the magic word and check for a valid answer
+ r = send_magic_word(mc, magic)
+ if r is True:
+ return True
# increment the count of magics to send, this is for the UI status
mc += 1
- # wait between tries for different MAGICs to allow the radio to
- # timeout, this is an experimental fature for the 5001 alpha that
- # has the same ident as the MINI8900, raise it if it don't work
- time.sleep(5)
+ # if you get here if that it don't worked
+ return False
except errors.RadioError:
raise
except Exception, e:
- msg = "Unknown error sending Magic to radio:\n%s" % e
- raise errors.RadioError(msg)
-
- return False
+ raise errors.RadioError("Error sending Magic to radio:\n%s" % e)
def _do_ident(radio, status):
@@ -366,28 +352,21 @@
# set the serial discipline
radio.pipe.setBaudrate(9600)
radio.pipe.setParity("N")
- radio.pipe.setTimeout(0.005)
- # cleaning the serial buffer, try wrapped
- try:
- radio.pipe.flushInput()
- except:
- msg = "Error with a serial rx buffer flush at _do_ident"
- LOG.error(msg)
- raise errors.RadioError(msg)
+
+ # cleaning the buffers
+ _clean_buffer(radio)
# do the magic trick
- if _do_magic(radio, status) is False:
+ if _start_clone_mode(radio, status) is False:
msg = "Radio did not respond to magic string, check your cable."
- LOG.error(msg)
raise errors.RadioError(msg)
# Ok, get the ident string
ident = _rawrecv(radio, 49)
# basic check for the ident
- if len(ident) != 49:
- msg = "Radio send a sort ident block, you need to increase maxtime."
- LOG.error(msg)
+ if len(ident) < 49:
+ msg = "Radio sent a sort ident block, aborting"
raise errors.RadioError(msg)
# check if ident is OK
@@ -402,7 +381,9 @@
msg = "Incorrect model ID, got this:\n\n"
msg += util.hexprint(ident)
LOG.debug(msg)
- raise errors.RadioError("Radio identification failed.")
+ error = "Radio identification failed, this is not the correct model, "
+ error += "or it's a not supported variant yet."
+ raise errors.RadioError()
# DEBUG
LOG.info("Positive ident, this is a %s" % radio.MODEL)
@@ -424,11 +405,9 @@
# we just care about the first 16, our magic string is in there
if len(id2) < 16:
msg = "The extra UV-2501+220 ID is short, aborting."
- # DEBUG
- LOG.error(msg)
raise errors.RadioError(msg)
- # ok, check for it, any of the correct ID must be in the received data
+ # ok, check for it, any of the correct IDs must be in the received data
itis = False
for eid in radio._id2:
if eid in id2:
@@ -442,7 +421,6 @@
if itis is False:
msg = "The extra UV-2501+220 ID is wrong, aborting."
# DEBUG
- LOG.error(msg)
LOG.debug("Full extra ID on the 2501+220 is: \n%s" %
util.hexprint(id2))
raise errors.RadioError(msg)
@@ -476,16 +454,11 @@
status.cur = 0
radio.status_fn(status)
+ # clean serial buffers
+ _clean_buffer(radio)
+
data = ""
for addr in range(0, MEM_SIZE, BLOCK_SIZE):
- # flush input, as per the original driver behavior, try wrapped
- try:
- radio.pipe.flushInput()
- except:
- msg = "Error with a serial rx buffer flush at _download"
- LOG.error(msg)
- raise errors.RadioError(msg)
-
# sending the read request
_send(radio, _make_frame("S", addr, BLOCK_SIZE), 0.1)
@@ -525,19 +498,22 @@
status.msg = "Cloning to radio..."
radio.status_fn(status)
+ # clean buffers
+ _clean_buffer(radio)
+
+ # set a delay based on the OS
+ if sys.platform in ["win32", "cygwin"]:
+ # we are in windows
+ delay = 0.015
+ else:
+ # we are in linux/mac
+ delay = 0.033
+
# the fun start here
for addr in range(0, MEM_SIZE, TX_BLOCK_SIZE):
- # flush input, as per the original driver behavior, try wrapped
- try:
- radio.pipe.flushInput()
- except:
- msg = "Error with a serial rx buffer flush at _upload"
- LOG.error(msg)
- raise errors.RadioError(msg)
-
# sending the data
d = data[addr:addr + TX_BLOCK_SIZE]
- _send(radio, _make_frame("X", addr, TX_BLOCK_SIZE, d), 0.015)
+ _send(radio, _make_frame("X", addr, TX_BLOCK_SIZE, d), delay)
# receiving the response
ack = _rawrecv(radio, 1)
@@ -580,7 +556,22 @@
return (ilow, ihigh)
-class btech(chirp_common.CloneModeRadio, chirp_common.ExperimentalRadio):
+def _split(rf, f1, f2):
+ """Returns False if the two freqs are in the same band (no split)
+ or True otherwise"""
+
+ # determine if the two freqs are in the same band
+ for low, high in rf.valid_bands:
+ if f1 >= low and f1 <= high and \
+ f2 >= low and f2 <= high:
+ # if the two freqs are on the same Band this is not a split
+ return False
+
+ # if you get here is because the freq pairs are split
+ return False
+
+
+class BTech(chirp_common.CloneModeRadio, chirp_common.ExperimentalRadio):
"""BTECH's UV-5001 and alike radios"""
VENDOR = "BTECH"
MODEL = ""
@@ -697,7 +688,7 @@
ranges"""
# setting the correct ranges for each radio type
- if self.MODEL == "UV-2501+220":
+ if "+220" in self.MODEL:
# the model 2501+220 has a segment in 220
# and a different position in the memmap
ranges = self._memobj.ranges220
@@ -713,7 +704,7 @@
LOG.info("Radio ranges: UHF %d to %d" % uhf)
# 220Mhz case
- if self.MODEL == "UV-2501+220":
+ if "+220" in self.MODEL:
vhf2 = _decode_ranges(ranges.vhf2_low, ranges.vhf2_high)
LOG.info("Radio ranges: VHF(220) %d to %d" % vhf2)
self._220_range = vhf2
@@ -805,7 +796,7 @@
# TX freq set
offset = (int(_mem.txfreq) * 10) - mem.freq
if offset != 0:
- if offset > 70000000: # 70 Mhz
+ if _split(self.get_features(), mem.freq, int(_mem.txfreq) * 10):
mem.duplex = "split"
mem.offset = int(_mem.txfreq) * 10
elif offset < 0:
@@ -855,10 +846,12 @@
PTTID_LIST[_mem.pttid]))
mem.extra.append(pttid)
+ # validating scode
+ scode = _mem.scode if _mem.scode != 15 else 0
pttidcode = RadioSetting("scode", "PTT ID signal code",
RadioSettingValueList(
PTTIDCODE_LIST,
- PTTIDCODE_LIST[_mem.scode]))
+ PTTIDCODE_LIST[scode]))
mem.extra.append(pttidcode)
optsig = RadioSetting("optsig", "Optional signaling",
@@ -963,16 +956,18 @@
return False
-# Note:
-# the order in the lists in the _magic, IDENT and _fileid is important
-# we put the most common units first, the policy is as follows:
-
-# - First latest (newer) units, as they will be the most common
-# - Second the former latest version, and recursively...
-# - At the end the pre-production units (pp) as this will be unique
+# ===========================================================================
+# The order in the _magic list, is important if it's not bigger than two.
+# If we found a situation where the count of magics for a single model is
+# more than two we need to make a new radio model definition as "Gen 2 v2"
+# or something like that.
+#
+# This has a time impact on the _start_clone_mode() with 5 secs of pause for
+# each magic to sent, so if this list get bigger than two the user has to wait
+# for at least 15 seconds to the start of the real clone mode.
@directory.register
-class UV2501(btech):
+class UV2501(BTech):
"""Baofeng Tech UV2501"""
MODEL = "UV-2501"
_magic = [MSTRING, ]
@@ -980,7 +975,7 @@
@directory.register
-class UV2501_220(btech):
+class UV2501_220(BTech):
"""Baofeng Tech UV2501+220"""
MODEL = "UV-2501+220"
_magic = [MSTRING_220, ]
@@ -989,17 +984,28 @@
@directory.register
-class UV5001(btech):
+class UV5001(BTech):
"""Baofeng Tech UV5001"""
MODEL = "UV-5001"
- _magic = [MSTRING, MSTRING_MINI8900]
+ _magic = [MSTRING, ]
_fileid = [UV5001G22_fp, UV5001G2_fp, UV5001alpha_fp, UV5001pp_fp]
@directory.register
-class MINI8900(btech):
+class MINI8900(BTech):
"""WACCOM MINI-8900"""
VENDOR = "WACCOM"
MODEL = "MINI-8900"
_magic = [MSTRING_MINI8900, ]
_fileid = [MINI8900_fp, ]
+
+
+ at directory.register
+class KTUV980(BTech):
+ """QYT KT-UV980"""
+ VENDOR = "QYT"
+ MODEL = "KT-UV980"
+ _vhf_range = (136000000, 175000000)
+ _uhf_range = (400000000, 481000000)
+ _magic = [MSTRING_MINI8900, ]
+ _fileid = [KTUV890_fp, ]
More information about the chirp_devel
mailing list