[hamradio-commits] [chirp] 01/03: New upstream version 20161102
Iain R. Learmonth
irl at moszumanska.debian.org
Tue Nov 8 11:59:57 UTC 2016
This is an automated email from the git hooks/post-receive script.
irl pushed a commit to branch master
in repository chirp.
commit 1a7fa89d8f9b61411109178b7e5e5bbb1b0d9e04
Author: Iain R. Learmonth <irl at fsfe.org>
Date: Tue Nov 8 11:52:18 2016 +0000
New upstream version 20161102
---
PKG-INFO | 2 +-
chirp/__init__.py | 2 +-
chirp/chirp_common.py | 21 +-
chirp/drivers/baofeng_common.py | 623 ++++++++++
chirp/drivers/baofeng_wp970i.py | 867 +++++++++++++
chirp/drivers/btech.py | 108 +-
chirp/drivers/ft2900.py | 60 +-
chirp/drivers/ft60.py | 75 +-
chirp/drivers/ft817.py | 24 +-
chirp/drivers/gmrsuv1.py | 828 +++++++++++++
chirp/drivers/h777.py | 19 +-
chirp/drivers/icomciv.py | 102 +-
chirp/drivers/kguv8d.py | 146 +--
chirp/drivers/leixen.py | 74 +-
chirp/drivers/lt725uv.py | 763 ++++++++++++
chirp/drivers/puxing_px888k.py | 1877 +++++++++++++++++++++++++++++
chirp/drivers/retevis_rt21.py | 526 ++++++++
chirp/drivers/thd72.py | 31 +-
chirp/drivers/tk760g.py | 171 ++-
chirp/drivers/uv5r.py | 50 +-
chirp/drivers/uv5x3.py | 1173 ++++++++++++++++++
chirp/drivers/uv6r.py | 871 +++++++++++++
chirp/drivers/wouxun.py | 1 +
chirp/radioreference.py | 5 +-
chirp/settings.py | 21 +-
chirp/ui/editorset.py | 1 +
chirp/ui/mainapp.py | 22 +-
stock_configs/EU LPD and PMR Channels.csv | 24 +-
stock_configs/KDR444.csv | 9 +
stock_configs/NOAA Weather Alert.csv | 2 +-
30 files changed, 8218 insertions(+), 280 deletions(-)
diff --git a/PKG-INFO b/PKG-INFO
index a334b87..cfa0b9c 100644
--- a/PKG-INFO
+++ b/PKG-INFO
@@ -1,6 +1,6 @@
Metadata-Version: 1.0
Name: chirp
-Version: daily-20160717
+Version: daily-20161102
Summary: UNKNOWN
Home-page: UNKNOWN
Author: UNKNOWN
diff --git a/chirp/__init__.py b/chirp/__init__.py
index 54e0067..9cd6026 100644
--- a/chirp/__init__.py
+++ b/chirp/__init__.py
@@ -17,7 +17,7 @@ import os
import sys
from glob import glob
-CHIRP_VERSION="daily-20160717"
+CHIRP_VERSION="daily-20161102"
module_dir = os.path.dirname(sys.modules["chirp"].__file__)
__all__ = []
diff --git a/chirp/chirp_common.py b/chirp/chirp_common.py
index 4291dec..008222e 100644
--- a/chirp/chirp_common.py
+++ b/chirp/chirp_common.py
@@ -29,7 +29,8 @@ TONES = [67.0, 69.3, 71.9, 74.4, 77.0, 79.7, 82.5,
225.7, 229.1, 233.6, 241.8, 250.3, 254.1,
]
-TONES_EXTRA = [62.5]
+TONES_EXTRA = [56.0, 57.0, 58.0, 59.0, 60.0, 61.0, 62.0,
+ 62.5, 63.0, 64.0]
OLD_TONES = list(TONES)
[OLD_TONES.remove(x) for x in [159.8, 165.5, 171.3, 177.3, 183.5, 189.9,
@@ -47,7 +48,7 @@ DTCS_CODES = [
465, 466, 503, 506, 516, 523, 526, 532, 546, 565, 606,
612, 624, 627, 631, 632, 654, 662, 664, 703, 712, 723,
731, 732, 734, 743, 754,
- ]
+]
# 512 Possible DTCS Codes
ALL_DTCS_CODES = []
@@ -93,7 +94,7 @@ SKIP_VALUES = ["", "S", "P"]
CHARSET_UPPER_NUMERIC = "ABCDEFGHIJKLMNOPQRSTUVWXYZ 1234567890"
CHARSET_ALPHANUMERIC = \
"ABCDEFGHIJKLMNOPQRSTUVWXYZabcdefghijklmnopqrstuvwxyz 1234567890"
-CHARSET_ASCII = "".join([chr(x) for x in range(ord(" "), ord("~")+1)])
+CHARSET_ASCII = "".join([chr(x) for x in range(ord(" "), ord("~") + 1)])
# http://aprs.org/aprs11/SSIDs.txt
APRS_SSID = (
@@ -154,6 +155,7 @@ def dBm_to_watts(dBm):
class PowerLevel:
"""Represents a power level supported by a radio"""
+
def __init__(self, label, watts=0, dBm=0):
if watts:
dBm = watts_to_dBm(watts)
@@ -296,7 +298,7 @@ class Memory:
"skip": SKIP_VALUES,
"empty": [True, False],
"dv_code": [x for x in range(0, 100)],
- }
+ }
def __repr__(self):
return "Memory[%i]" % self.number
@@ -552,6 +554,7 @@ class DVMemory(Memory):
class MemoryMapping(object):
"""Base class for a memory mapping"""
+
def __init__(self, model, index, name):
self._model = model
self._index = index
@@ -618,6 +621,7 @@ class Bank(MemoryMapping):
class NamedBank(Bank):
"""A bank that can have a name"""
+
def set_name(self, name):
"""Changes the user-adjustable bank name"""
self._name = name
@@ -625,12 +629,14 @@ class NamedBank(Bank):
class BankModel(MappingModel):
"""A bank model where one memory is in zero or one banks at any point"""
+
def __init__(self, radio, name='Banks'):
super(BankModel, self).__init__(radio, name)
class MappingModelIndexInterface:
"""Interface for mappings with index capabilities"""
+
def get_index_bounds(self):
"""Returns a tuple (lo,hi) of the min and max mapping indices"""
raise NotImplementedError()
@@ -721,7 +727,7 @@ class RadioFeatures:
# D-STAR
"requires_call_lists": BOOLEAN,
"has_implicit_calls": BOOLEAN,
- }
+ }
def __setattr__(self, name, val):
if name.startswith("_"):
@@ -941,8 +947,8 @@ class RadioFeatures:
break
if not valid:
msg = ValidationError(
- ("Tx freq {freq} is out "
- "of supported range").format(freq=format_freq(freq)))
+ ("Tx freq {freq} is out "
+ "of supported range").format(freq=format_freq(freq)))
msgs.append(msg)
if mem.power and \
@@ -1190,6 +1196,7 @@ class LiveRadio(Radio):
class NetworkSourceRadio(Radio):
"""Base class for all radios based on a network source"""
+
def do_fetch(self):
"""Fetch the source data from the network"""
pass
diff --git a/chirp/drivers/baofeng_common.py b/chirp/drivers/baofeng_common.py
new file mode 100644
index 0000000..bb2d9b7
--- /dev/null
+++ b/chirp/drivers/baofeng_common.py
@@ -0,0 +1,623 @@
+# Copyright 2016:
+# * Jim Unroe KC9HI, <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
+# 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/>.
+
+"""common functions for Baofeng (or similar) handheld radios"""
+
+import time
+import struct
+import logging
+from chirp import chirp_common, directory, memmap
+from chirp import bitwise, errors, util
+from chirp.settings import RadioSettingGroup, RadioSetting, \
+ RadioSettingValueBoolean, RadioSettingValueList
+
+LOG = logging.getLogger(__name__)
+
+STIMEOUT = 1.5
+
+
+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 _rawrecv(radio, amount):
+ """Raw read from the radio device"""
+ data = ""
+ try:
+ data = radio.pipe.read(amount)
+ except:
+ msg = "Generic error reading data from radio; check your cable."
+ raise errors.RadioError(msg)
+
+ if len(data) != amount:
+ 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):
+ """Get data from the radio """
+ # read 4 bytes of header
+ hdr = _rawrecv(radio, 4)
+
+ # read data
+ data = _rawrecv(radio, length)
+
+ # 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("X"):
+ 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 _get_radio_firmware_version(radio):
+ msg = struct.pack(">BHB", ord("S"), radio._fw_ver_start,
+ radio._recv_block_size)
+ radio.pipe.write(msg)
+ block = _recv(radio, radio._fw_ver_start, radio._recv_block_size)
+ _rawsend(radio, "\x06")
+ time.sleep(0.05)
+ version = block[0:16]
+ return version
+
+
+def _image_ident_from_data(data, start, stop):
+ return data[start:stop]
+
+
+def _get_image_firmware_version(radio):
+ return _image_ident_from_data(radio.get_mmap(), radio._fw_ver_start,
+ radio._fw_ver_start + 0x10)
+
+
+def _do_ident(radio, magic):
+ """Put the radio in PROGRAM mode"""
+ # set the serial discipline
+ radio.pipe.baudrate = 9600
+ radio.pipe.parity = "N"
+ radio.pipe.timeout = STIMEOUT
+
+ # flush input buffer
+ _clean_buffer(radio)
+
+ # send request to enter program mode
+ _rawsend(radio, magic)
+
+ ack = _rawrecv(radio, 1)
+ if ack != "\x06":
+ if ack:
+ LOG.debug(repr(ack))
+ raise errors.RadioError("Radio did not respond")
+
+ _rawsend(radio, "\x02")
+
+ # Ok, get the response
+ ident = _rawrecv(radio, radio._magic_response_length)
+
+ # check if response is OK
+ if not ident.startswith("\xaa") or not ident.endswith("\xdd"):
+ # bad response
+ msg = "Unexpected response, got this:"
+ msg += util.hexprint(ident)
+ LOG.debug(msg)
+ raise errors.RadioError("Unexpected response from radio.")
+
+ # DEBUG
+ LOG.info("Valid response, got this:")
+ LOG.debug(util.hexprint(ident))
+
+ _rawsend(radio, "\x06")
+ ack = _rawrecv(radio, 1)
+ if ack != "\x06":
+ if ack:
+ LOG.debug(repr(ack))
+ raise errors.RadioError("Radio refused clone")
+
+ return ident
+
+
+def _ident_radio(radio):
+ for magic in radio._magic:
+ error = None
+ try:
+ data = _do_ident(radio, magic)
+ return data
+ except errors.RadioError, e:
+ print e
+ error = e
+ time.sleep(2)
+ if error:
+ raise error
+ raise errors.RadioError("Radio did not respond")
+
+
+def _download(radio):
+ """Get the memory map"""
+ # put radio in program mode
+ ident = _ident_radio(radio)
+
+ # identify radio
+ radio_ident = _get_radio_firmware_version(radio)
+ LOG.info("Radio firmware version:")
+ LOG.debug(util.hexprint(radio_ident))
+
+ if radio_ident == "\xFF" * 16:
+ ident += radio.MODEL.ljust(8)
+
+ # UI progress
+ status = chirp_common.Status()
+ status.cur = 0
+ status.max = radio._mem_size / radio._recv_block_size
+ status.msg = "Cloning from radio..."
+ radio.status_fn(status)
+
+ data = ""
+ for addr in range(0, radio._mem_size, radio._recv_block_size):
+ frame = _make_frame("S", addr, radio._recv_block_size)
+ # DEBUG
+ LOG.info("Request sent:")
+ LOG.debug(util.hexprint(frame))
+
+ # sending the read request
+ _rawsend(radio, frame)
+
+ if radio._ack_block:
+ ack = _rawrecv(radio, 1)
+ if ack != "\x06":
+ raise errors.RadioError(
+ "Radio refused to send block 0x%04x" % addr)
+
+ # now we read
+ d = _recv(radio, addr, radio._recv_block_size)
+
+ _rawsend(radio, "\x06")
+ time.sleep(0.05)
+
+ # aggregate the data
+ data += d
+
+ # UI Update
+ status.cur = addr / radio._recv_block_size
+ status.msg = "Cloning from radio..."
+ radio.status_fn(status)
+
+ data += ident
+
+ return data
+
+
+def _upload(radio):
+ """Upload procedure"""
+ # put radio in program mode
+ _ident_radio(radio)
+
+ # identify radio
+ radio_ident = _get_radio_firmware_version(radio)
+ LOG.info("Radio firmware version:")
+ LOG.debug(util.hexprint(radio_ident))
+ # identify image
+ image_ident = _get_image_firmware_version(radio)
+ LOG.info("Image firmware version:")
+ LOG.debug(util.hexprint(image_ident))
+
+ if radio_ident != "0xFF" * 16 and image_ident == radio_ident:
+ _ranges = radio._ranges
+ else:
+ _ranges = [(0x0000, 0x0DF0),
+ (0x0E00, 0x1800)]
+
+ # UI progress
+ status = chirp_common.Status()
+ status.cur = 0
+ status.max = radio._mem_size / radio._send_block_size
+ status.msg = "Cloning to radio..."
+ radio.status_fn(status)
+
+ # the fun start here
+ for start, end in _ranges:
+ for addr in range(start, end, radio._send_block_size):
+ # sending the data
+ data = radio.get_mmap()[addr:addr + radio._send_block_size]
+
+ frame = _make_frame("X", addr, radio._send_block_size, data)
+
+ _rawsend(radio, frame)
+ time.sleep(0.05)
+
+ # receiving the response
+ ack = _rawrecv(radio, 1)
+ if ack != "\x06":
+ msg = "Bad ack writing block 0x%04x" % addr
+ raise errors.RadioError(msg)
+
+ # UI Update
+ status.cur = addr / radio._send_block_size
+ status.msg = "Cloning to radio..."
+ radio.status_fn(status)
+
+
+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 True
+
+class BaofengCommonHT(chirp_common.CloneModeRadio,
+ chirp_common.ExperimentalRadio):
+ """Baofeng HT Sytle Radios"""
+ VENDOR = "Baofeng"
+ MODEL = ""
+ IDENT = ""
+
+ 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 get_features(self):
+ """Get the radio's features"""
+
+ rf = chirp_common.RadioFeatures()
+ rf.has_settings = True
+ 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.valid_modes = self.MODES
+ rf.valid_characters = self.VALID_CHARS
+ rf.valid_name_length = self.LENGTH_NAME
+ 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_skips = self.SKIP_VALUES
+ rf.valid_dtcs_codes = self.DTCS_CODES
+ rf.memory_bounds = (0, 127)
+ rf.valid_power_levels = self.POWER_LEVELS
+ rf.valid_bands = self.VALID_BANDS
+
+ return rf
+
+ def _is_txinh(self, _mem):
+ raw_tx = ""
+ for i in range(0, 4):
+ raw_tx += _mem.txfreq[i].get_raw()
+ return raw_tx == "\xFF\xFF\xFF\xFF"
+
+ def get_memory(self, number):
+ _mem = self._memobj.memory[number]
+ _nam = self._memobj.names[number]
+
+ mem = chirp_common.Memory()
+ mem.number = number
+
+ if _mem.get_raw()[0] == "\xff":
+ mem.empty = True
+ return mem
+
+ mem.freq = int(_mem.rxfreq) * 10
+
+ if self._is_txinh(_mem):
+ # TX freq not set
+ mem.duplex = "off"
+ mem.offset = 0
+ else:
+ # TX freq set
+ offset = (int(_mem.txfreq) * 10) - mem.freq
+ if offset != 0:
+ if _split(self.get_features(), mem.freq, int(_mem.txfreq) * 10):
+ mem.duplex = "split"
+ mem.offset = int(_mem.txfreq) * 10
+ elif offset < 0:
+ mem.offset = abs(offset)
+ mem.duplex = "-"
+ elif offset > 0:
+ mem.offset = offset
+ mem.duplex = "+"
+ else:
+ mem.offset = 0
+
+ for char in _nam.name:
+ if str(char) == "\xFF":
+ char = " " # The OEM software may have 0xFF mid-name
+ mem.name += str(char)
+ mem.name = mem.name.rstrip()
+
+ dtcs_pol = ["N", "N"]
+
+ if _mem.txtone in [0, 0xFFFF]:
+ txmode = ""
+ elif _mem.txtone >= 0x0258:
+ txmode = "Tone"
+ mem.rtone = int(_mem.txtone) / 10.0
+ elif _mem.txtone <= 0x0258:
+ txmode = "DTCS"
+ if _mem.txtone > 0x69:
+ index = _mem.txtone - 0x6A
+ dtcs_pol[0] = "R"
+ else:
+ index = _mem.txtone - 1
+ mem.dtcs = self.DTCS_CODES[index]
+ else:
+ LOG.warn("Bug: txtone is %04x" % _mem.txtone)
+
+ if _mem.rxtone in [0, 0xFFFF]:
+ rxmode = ""
+ elif _mem.rxtone >= 0x0258:
+ rxmode = "Tone"
+ mem.ctone = int(_mem.rxtone) / 10.0
+ elif _mem.rxtone <= 0x0258:
+ rxmode = "DTCS"
+ if _mem.rxtone >= 0x6A:
+ index = _mem.rxtone - 0x6A
+ dtcs_pol[1] = "R"
+ else:
+ index = _mem.rxtone - 1
+ mem.rx_dtcs = self.DTCS_CODES[index]
+ else:
+ LOG.warn("Bug: rxtone is %04x" % _mem.rxtone)
+
+ if txmode == "Tone" and not rxmode:
+ mem.tmode = "Tone"
+ elif txmode == rxmode and txmode == "Tone" and mem.rtone == mem.ctone:
+ mem.tmode = "TSQL"
+ elif txmode == rxmode and txmode == "DTCS" and mem.dtcs == mem.rx_dtcs:
+ mem.tmode = "DTCS"
+ elif rxmode or txmode:
+ mem.tmode = "Cross"
+ mem.cross_mode = "%s->%s" % (txmode, rxmode)
+
+ mem.dtcs_polarity = "".join(dtcs_pol)
+
+ if not _mem.scan:
+ mem.skip = "S"
+
+ levels = self.POWER_LEVELS
+ try:
+ mem.power = levels[_mem.lowpower]
+ except IndexError:
+ LOG.error("Radio reported invalid power level %s (in %s)" %
+ (_mem.power, levels))
+ mem.power = levels[0]
+
+ mem.mode = _mem.wide and "FM" or "NFM"
+
+ mem.extra = RadioSettingGroup("Extra", "extra")
+
+ rs = RadioSetting("bcl", "BCL",
+ RadioSettingValueBoolean(_mem.bcl))
+ mem.extra.append(rs)
+
+ rs = RadioSetting("pttid", "PTT ID",
+ RadioSettingValueList(self.PTTID_LIST,
+ self.PTTID_LIST[_mem.pttid]))
+ mem.extra.append(rs)
+
+ rs = RadioSetting("scode", "S-CODE",
+ RadioSettingValueList(self.SCODE_LIST,
+ self.SCODE_LIST[_mem.scode]))
+ mem.extra.append(rs)
+
+ return mem
+
+ def set_memory(self, mem):
+ _mem = self._memobj.memory[mem.number]
+ _nam = self._memobj.names[mem.number]
+
+ if mem.empty:
+ _mem.set_raw("\xff" * 16)
+ _nam.set_raw("\xff" * 16)
+ return
+
+ _mem.set_raw("\x00" * 16)
+
+ _mem.rxfreq = mem.freq / 10
+
+ if mem.duplex == "off":
+ for i in range(0, 4):
+ _mem.txfreq[i].set_raw("\xFF")
+ elif mem.duplex == "split":
+ _mem.txfreq = mem.offset / 10
+ elif mem.duplex == "+":
+ _mem.txfreq = (mem.freq + mem.offset) / 10
+ elif mem.duplex == "-":
+ _mem.txfreq = (mem.freq - mem.offset) / 10
+ else:
+ _mem.txfreq = mem.freq / 10
+
+ _namelength = self.get_features().valid_name_length
+ for i in range(_namelength):
+ try:
+ _nam.name[i] = mem.name[i]
+ except IndexError:
+ _nam.name[i] = "\xFF"
+
+ rxmode = txmode = ""
+ if mem.tmode == "Tone":
+ _mem.txtone = int(mem.rtone * 10)
+ _mem.rxtone = 0
+ elif mem.tmode == "TSQL":
+ _mem.txtone = int(mem.ctone * 10)
+ _mem.rxtone = int(mem.ctone * 10)
+ elif mem.tmode == "DTCS":
+ rxmode = txmode = "DTCS"
+ _mem.txtone = self.DTCS_CODES.index(mem.dtcs) + 1
+ _mem.rxtone = self.DTCS_CODES.index(mem.dtcs) + 1
+ elif mem.tmode == "Cross":
+ txmode, rxmode = mem.cross_mode.split("->", 1)
+ if txmode == "Tone":
+ _mem.txtone = int(mem.rtone * 10)
+ elif txmode == "DTCS":
+ _mem.txtone = self.DTCS_CODES.index(mem.dtcs) + 1
+ else:
+ _mem.txtone = 0
+ if rxmode == "Tone":
+ _mem.rxtone = int(mem.ctone * 10)
+ elif rxmode == "DTCS":
+ _mem.rxtone = self.DTCS_CODES.index(mem.rx_dtcs) + 1
+ else:
+ _mem.rxtone = 0
+ else:
+ _mem.rxtone = 0
+ _mem.txtone = 0
+
+ if txmode == "DTCS" and mem.dtcs_polarity[0] == "R":
+ _mem.txtone += 0x69
+ if rxmode == "DTCS" and mem.dtcs_polarity[1] == "R":
+ _mem.rxtone += 0x69
+
+ _mem.scan = mem.skip != "S"
+ _mem.wide = mem.mode == "FM"
+
+ if mem.power:
+ _mem.lowpower = self.POWER_LEVELS.index(mem.power)
+ else:
+ _mem.lowpower = 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.pttid = 0
+ _mem.scode = 0
+
+ def set_settings(self, settings):
+ _settings = self._memobj.settings
+ _mem = self._memobj
+ for element in settings:
+ if not isinstance(element, RadioSetting):
+ if element.get_name() == "fm_preset":
+ self._set_fm_preset(element)
+ else:
+ self.set_settings(element)
+ continue
+ else:
+ try:
+ name = element.get_name()
+ if "." in name:
+ bits = name.split(".")
+ obj = self._memobj
+ for bit in bits[:-1]:
+ if "/" in bit:
+ bit, index = bit.split("/", 1)
+ index = int(index)
+ obj = getattr(obj, bit)[index]
+ else:
+ obj = getattr(obj, bit)
+ setting = bits[-1]
+ else:
+ obj = _settings
+ setting = element.get_name()
+
+ if element.has_apply_callback():
+ LOG.debug("Using apply callback")
+ element.run_apply_callback()
+ elif element.value.get_mutable():
+ LOG.debug("Setting %s = %s" % (setting, element.value))
+ setattr(obj, setting, element.value)
+ except Exception, e:
+ LOG.debug(element.get_name())
+ raise
+
+ def _set_fm_preset(self, settings):
+ for element in settings:
+ try:
+ val = element.value
+ if self._memobj.fm_presets <= 108.0 * 10 - 650:
+ value = int(val.get_value() * 10 - 650)
+ else:
+ value = int(val.get_value() * 10)
+ LOG.debug("Setting fm_presets = %s" % (value))
+ self._memobj.fm_presets = value
+ except Exception, e:
+ LOG.debug(element.get_name())
+ raise
diff --git a/chirp/drivers/baofeng_wp970i.py b/chirp/drivers/baofeng_wp970i.py
new file mode 100644
index 0000000..76c8d92
--- /dev/null
+++ b/chirp/drivers/baofeng_wp970i.py
@@ -0,0 +1,867 @@
+# Copyright 2016:
+# * Jim Unroe KC9HI, <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
+# 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.drivers import baofeng_common
+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, \
+ InvalidValueError
+from textwrap import dedent
+
+##### MAGICS #########################################################
+
+# Baofeng WP970I magic string
+MSTRING_WP970I = "\x50\xBB\xFF\x20\x14\x04\x13"
+
+
+DTMF_CHARS = "0123456789 *#ABCD"
+STEPS = [2.5, 5.0, 6.25, 10.0, 12.5, 20.0, 25.0, 50.0]
+
+LIST_AB = ["A", "B"]
+LIST_ALMOD = ["Site", "Tone", "Code"]
+LIST_BANDWIDTH = ["Wide", "Narrow"]
+LIST_COLOR = ["Off", "Blue", "Orange", "Purple"]
+LIST_DTMFSPEED = ["%s ms" % x for x in range(50, 2010, 10)]
+LIST_DTMFST = ["Off", "DT-ST", "ANI-ST", "DT+ANI"]
+LIST_MODE = ["Channel", "Name", "Frequency"]
+LIST_OFF1TO9 = ["Off"] + list("123456789")
+LIST_OFF1TO10 = LIST_OFF1TO9 + ["10"]
+LIST_OFFAB = ["Off"] + LIST_AB
+LIST_RESUME = ["TO", "CO", "SE"]
+LIST_PONMSG = ["Full", "Message"]
+LIST_PTTID = ["Off", "BOT", "EOT", "Both"]
+LIST_SCODE = ["%s" % x for x in range(1, 16)]
+LIST_RPSTE = ["Off"] + ["%s" % x for x in range(1, 11)]
+LIST_SAVE = ["Off", "1:1", "1:2", "1:3", "1:4"]
+LIST_SHIFTD = ["Off", "+", "-"]
+LIST_STEDELAY = ["Off"] + ["%s ms" % x for x in range(100, 1100, 100)]
+LIST_STEP = [str(x) for x in STEPS]
+LIST_TIMEOUT = ["%s sec" % x for x in range(15, 615, 15)]
+LIST_TXPOWER = ["High", "Mid", "Low"]
+LIST_VOICE = ["Off", "English", "Chinese"]
+LIST_WORKMODE = ["Frequency", "Channel"]
+
+def model_match(cls, data):
+ """Match the opened/downloaded image to the correct version"""
+
+ if len(data) > 0x2008:
+ rid = data[0x2008:0x2010]
+ return rid.startswith(cls.MODEL)
+ elif len(data) == 0x2008:
+ rid = data[0x1EF0:0x1EF7]
+ return rid in cls._fileid
+ else:
+ return False
+
+
+class WP970I(baofeng_common.BaofengCommonHT):
+ """Baofeng WP970I"""
+ VENDOR = "Baofeng"
+ MODEL = "WP970I"
+
+ _fileid = []
+ _magic = [MSTRING_WP970I, ]
+ _magic_response_length = 8
+ _fw_ver_start = 0x1EF0
+ _recv_block_size = 0x40
+ _mem_size = 0x2000
+ _ack_block = True
+
+ _ranges = [(0x0000, 0x0DF0),
+ (0x0E00, 0x1800),
+ (0x1EE0, 0x1EF0),
+ (0x1F60, 0x1F70),
+ (0x1F80, 0x1F90),
+ (0x1FC0, 0x1FD0)]
+ _send_block_size = 0x10
+
+ MODES = ["NFM", "FM"]
+ VALID_CHARS = chirp_common.CHARSET_ALPHANUMERIC + \
+ "!@#$%^&*()+-=[]:\";'<>?,./"
+ LENGTH_NAME = 6
+ SKIP_VALUES = ["", "S"]
+ DTCS_CODES = sorted(chirp_common.DTCS_CODES + [645])
+ POWER_LEVELS = [chirp_common.PowerLevel("High", watts=5.00),
+ chirp_common.PowerLevel("Med", watts=3.00),
+ chirp_common.PowerLevel("Low", watts=1.00)]
+ VALID_BANDS = [(130000000, 180000000),
+ (400000000, 521000000)]
+ PTTID_LIST = LIST_PTTID
+ SCODE_LIST = LIST_SCODE
+
+
+ MEM_FORMAT = """
+ #seekto 0x0000;
+ struct {
+ lbcd rxfreq[4];
+ lbcd txfreq[4];
+ ul16 rxtone;
+ ul16 txtone;
+ u8 unused1:3,
+ isuhf:1,
+ scode:4;
+ u8 unknown1:7,
+ txtoneicon:1;
+ u8 mailicon:3,
+ unknown2:3,
+ lowpower:2;
+ u8 unknown3:1,
+ wide:1,
+ unknown4:2,
+ bcl:1,
+ scan:1,
+ pttid:2;
+ } memory[128];
+
+ #seekto 0x0B00;
+ struct {
+ u8 code[5];
+ u8 unused[11];
+ } pttid[15];
+
+ #seekto 0x0CAA;
+ struct {
+ u8 code[5];
+ u8 unused1:6,
+ aniid:2;
+ u8 unknown[2];
+ u8 dtmfon;
+ u8 dtmfoff;
+ } ani;
+
+ #seekto 0x0E20;
+ struct {
+ u8 squelch;
+ u8 step;
+ u8 unknown1;
+ u8 save;
+ u8 vox;
+ u8 unknown2;
+ u8 abr;
+ u8 tdr;
+ u8 beep;
+ u8 timeout;
+ u8 unknown3[4];
+ u8 voice;
+ u8 unknown4;
+ u8 dtmfst;
+ u8 unknown5;
+ u8 unknown12:6,
+ screv:2;
+ u8 pttid;
+ u8 pttlt;
+ u8 mdfa;
+ u8 mdfb;
+ u8 bcl;
+ u8 autolk;
+ u8 sftd;
+ u8 unknown6[3];
+ u8 wtled;
+ u8 rxled;
+ u8 txled;
+ u8 almod;
+ u8 band;
+ u8 tdrab;
+ u8 ste;
+ u8 rpste;
+ u8 rptrl;
+ u8 ponmsg;
+ u8 roger;
+ u8 rogerrx;
+ u8 tdrch;
+ u8 displayab:1,
+ unknown1:2,
+ fmradio:1,
+ alarm:1,
+ unknown2:1,
+ reset:1,
+ menu:1;
+ u8 unknown1:6,
+ singleptt:1,
+ vfomrlock:1;
+ u8 workmode;
+ u8 keylock;
+ } settings;
+
+ #seekto 0x0E76;
+ struct {
+ u8 unused1:1,
+ mrcha:7;
+ u8 unused2:1,
+ mrchb:7;
+ } wmchannel;
+
+ struct vfo {
+ u8 unknown0[8];
+ u8 freq[8];
+ u8 offset[6];
+ ul16 rxtone;
+ ul16 txtone;
+ u8 unused1:7,
+ band:1;
+ u8 unknown3;
+ u8 unused2:2,
+ sftd:2,
+ scode:4;
+ u8 unknown4;
+ u8 unused3:1
+ step:3,
+ unused4:4;
+ u8 unused5:1,
+ widenarr:1,
+ unused6:4,
+ txpower3:2;
+ };
+
+ #seekto 0x0F00;
+ struct {
+ struct vfo a;
+ struct vfo b;
+ } vfo;
+
+ #seekto 0x0F4E;
+ u16 fm_presets;
+
+ #seekto 0x1000;
+ struct {
+ char name[7];
+ u8 unknown1[9];
+ } names[128];
+
+ #seekto 0x1ED0;
+ struct {
+ char line1[7];
+ char line2[7];
+ } sixpoweron_msg;
+
+ #seekto 0x1EE0;
+ struct {
+ char line1[7];
+ char line2[7];
+ } poweron_msg;
+
+ #seekto 0x1EF0;
+ struct {
+ char line1[7];
+ char line2[7];
+ } firmware_msg;
+
+ struct squelch {
+ u8 sql0;
+ u8 sql1;
+ u8 sql2;
+ u8 sql3;
+ u8 sql4;
+ u8 sql5;
+ u8 sql6;
+ u8 sql7;
+ u8 sql8;
+ u8 sql9;
+ };
+
+ #seekto 0x1F60;
+ struct {
+ struct squelch vhf;
+ u8 unknown1[6];
+ u8 unknown2[16];
+ struct squelch uhf;
+ } squelch;
+
+ struct limit {
+ u8 enable;
+ bbcd lower[2];
+ bbcd upper[2];
+ };
+
+ #seekto 0x1FC0;
+ struct {
+ struct limit vhf;
+ struct limit uhf;
+ } limits;
+
+ """
+
+ @classmethod
+ def get_prompts(cls):
+ rp = chirp_common.RadioPrompts()
+ rp.experimental = \
+ ('This 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 these 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 process_mmap(self):
+ """Process the mem map into the mem object"""
+ self._memobj = bitwise.parse(self.MEM_FORMAT, self._mmap)
+
+ def get_settings(self):
+ """Translate the bit in the mem_struct into settings in the UI"""
+ _mem = self._memobj
+ basic = RadioSettingGroup("basic", "Basic Settings")
+ advanced = RadioSettingGroup("advanced", "Advanced Settings")
+ other = RadioSettingGroup("other", "Other Settings")
+ work = RadioSettingGroup("work", "Work Mode Settings")
+ fm_preset = RadioSettingGroup("fm_preset", "FM Preset")
+ dtmfe = RadioSettingGroup("dtmfe", "DTMF Encode Settings")
+ service = RadioSettingGroup("service", "Service Settings")
+ top = RadioSettings(basic, advanced, other, work, fm_preset, dtmfe,
+ service)
+
+ # Basic settings
+ if _mem.settings.squelch > 0x09:
+ val = 0x00
+ else:
+ val = _mem.settings.squelch
+ rs = RadioSetting("settings.squelch", "Squelch",
+ RadioSettingValueList(
+ LIST_OFF1TO9, LIST_OFF1TO9[val]))
+ basic.append(rs)
+
+ if _mem.settings.save > 0x04:
+ val = 0x00
+ else:
+ val = _mem.settings.save
+ rs = RadioSetting("settings.save", "Battery Saver",
+ RadioSettingValueList(
+ LIST_SAVE, LIST_SAVE[val]))
+ basic.append(rs)
+
+ if _mem.settings.vox > 0x0A:
+ val = 0x00
+ else:
+ val = _mem.settings.vox
+ rs = RadioSetting("settings.vox", "Vox",
+ RadioSettingValueList(
+ LIST_OFF1TO10, LIST_OFF1TO10[val]))
+ basic.append(rs)
+
+ if _mem.settings.abr > 0x0A:
+ val = 0x00
+ else:
+ val = _mem.settings.abr
+ rs = RadioSetting("settings.abr", "Backlight Timeout",
+ RadioSettingValueList(
+ LIST_OFF1TO10, LIST_OFF1TO10[val]))
+ basic.append(rs)
+
+ rs = RadioSetting("settings.tdr", "Dual Watch",
+ RadioSettingValueBoolean(_mem.settings.tdr))
+ basic.append(rs)
+
+ rs = RadioSetting("settings.beep", "Beep",
+ RadioSettingValueBoolean(_mem.settings.beep))
+ basic.append(rs)
+
+ if _mem.settings.timeout > 0x27:
+ val = 0x03
+ else:
+ val = _mem.settings.timeout
+ rs = RadioSetting("settings.timeout", "Timeout Timer",
+ RadioSettingValueList(
+ LIST_TIMEOUT, LIST_TIMEOUT[val]))
+ basic.append(rs)
+
+ if _mem.settings.voice > 0x02:
+ val = 0x01
+ else:
+ val = _mem.settings.voice
+ rs = RadioSetting("settings.voice", "Voice Prompt",
+ RadioSettingValueList(
+ LIST_VOICE, LIST_VOICE[val]))
+ basic.append(rs)
+
+ rs = RadioSetting("settings.dtmfst", "DTMF Sidetone",
+ RadioSettingValueList(LIST_DTMFST, LIST_DTMFST[
+ _mem.settings.dtmfst]))
+ basic.append(rs)
+
+ if _mem.settings.screv > 0x02:
+ val = 0x01
+ else:
+ val = _mem.settings.screv
+ rs = RadioSetting("settings.screv", "Scan Resume",
+ RadioSettingValueList(
+ LIST_RESUME, LIST_RESUME[val]))
+ basic.append(rs)
+
+ rs = RadioSetting("settings.pttid", "When to send PTT ID",
+ RadioSettingValueList(LIST_PTTID, LIST_PTTID[
+ _mem.settings.pttid]))
+ basic.append(rs)
+
+ if _mem.settings.pttlt > 0x1E:
+ val = 0x05
+ else:
+ val = _mem.settings.pttlt
+ rs = RadioSetting("pttlt", "PTT ID Delay",
+ RadioSettingValueInteger(0, 50, val))
+ basic.append(rs)
+
+ rs = RadioSetting("settings.mdfa", "Display Mode (A)",
+ RadioSettingValueList(LIST_MODE, LIST_MODE[
+ _mem.settings.mdfa]))
+ basic.append(rs)
+
+ rs = RadioSetting("settings.mdfb", "Display Mode (B)",
+ RadioSettingValueList(LIST_MODE, LIST_MODE[
+ _mem.settings.mdfb]))
+ basic.append(rs)
+
+ rs = RadioSetting("settings.autolk", "Automatic Key Lock",
+ RadioSettingValueBoolean(_mem.settings.autolk))
+ basic.append(rs)
+
+ rs = RadioSetting("settings.wtled", "Standby LED Color",
+ RadioSettingValueList(
+ LIST_COLOR, LIST_COLOR[_mem.settings.wtled]))
+ basic.append(rs)
+
+ rs = RadioSetting("settings.rxled", "RX LED Color",
+ RadioSettingValueList(
+ LIST_COLOR, LIST_COLOR[_mem.settings.rxled]))
+ basic.append(rs)
+
+ rs = RadioSetting("settings.txled", "TX LED Color",
+ RadioSettingValueList(
+ LIST_COLOR, LIST_COLOR[_mem.settings.txled]))
+ basic.append(rs)
+
+ val = _mem.settings.almod
+ rs = RadioSetting("settings.almod", "Alarm Mode",
+ RadioSettingValueList(
+ LIST_ALMOD, LIST_ALMOD[val]))
+ basic.append(rs)
+
+ if _mem.settings.tdrab > 0x02:
+ val = 0x00
+ else:
+ val = _mem.settings.tdrab
+ rs = RadioSetting("settings.tdrab", "Dual Watch TX Priority",
+ RadioSettingValueList(
+ LIST_OFFAB, LIST_OFFAB[val]))
+ basic.append(rs)
+
+ rs = RadioSetting("settings.ste", "Squelch Tail Eliminate (HT to HT)",
+ RadioSettingValueBoolean(_mem.settings.ste))
+ basic.append(rs)
+
+ if _mem.settings.rpste > 0x0A:
+ val = 0x00
+ else:
+ val = _mem.settings.rpste
+ rs = RadioSetting("settings.rpste",
+ "Squelch Tail Eliminate (repeater)",
+ RadioSettingValueList(
+ LIST_RPSTE, LIST_RPSTE[val]))
+ basic.append(rs)
+
+ if _mem.settings.rptrl > 0x0A:
+ val = 0x00
+ else:
+ val = _mem.settings.rptrl
+ rs = RadioSetting("settings.rptrl", "STE Repeater Delay",
+ RadioSettingValueList(
+ LIST_STEDELAY, LIST_STEDELAY[val]))
+ basic.append(rs)
+
+ rs = RadioSetting("settings.ponmsg", "Power-On Message",
+ RadioSettingValueList(LIST_PONMSG, LIST_PONMSG[
+ _mem.settings.ponmsg]))
+ basic.append(rs)
+
+ rs = RadioSetting("settings.roger", "Roger Beep",
+ RadioSettingValueBoolean(_mem.settings.roger))
+ basic.append(rs)
+
+ # Advanced settings
+ rs = RadioSetting("settings.reset", "RESET Menu",
+ RadioSettingValueBoolean(_mem.settings.reset))
+ advanced.append(rs)
+
+ rs = RadioSetting("settings.menu", "All Menus",
+ RadioSettingValueBoolean(_mem.settings.menu))
+ advanced.append(rs)
+
+ rs = RadioSetting("settings.fmradio", "Broadcast FM Radio",
+ RadioSettingValueBoolean(_mem.settings.fmradio))
+ advanced.append(rs)
+
+ rs = RadioSetting("settings.alarm", "Alarm Sound",
+ RadioSettingValueBoolean(_mem.settings.alarm))
+ advanced.append(rs)
+
+ # Other settings
+ def _filter(name):
+ filtered = ""
+ for char in str(name):
+ if char in chirp_common.CHARSET_ASCII:
+ filtered += char
+ else:
+ filtered += " "
+ return filtered
+
+ _msg = _mem.firmware_msg
+ val = RadioSettingValueString(0, 7, _filter(_msg.line1))
+ val.set_mutable(False)
+ rs = RadioSetting("firmware_msg.line1", "Firmware Message 1", val)
+ other.append(rs)
+
+ val = RadioSettingValueString(0, 7, _filter(_msg.line2))
+ val.set_mutable(False)
+ rs = RadioSetting("firmware_msg.line2", "Firmware Message 2", val)
+ other.append(rs)
+
+ _msg = _mem.sixpoweron_msg
+ val = RadioSettingValueString(0, 7, _filter(_msg.line1))
+ val.set_mutable(False)
+ rs = RadioSetting("sixpoweron_msg.line1", "6+Power-On Message 1", val)
+ other.append(rs)
+ val = RadioSettingValueString(0, 7, _filter(_msg.line2))
+ val.set_mutable(False)
+ rs = RadioSetting("sixpoweron_msg.line2", "6+Power-On Message 2", val)
+ other.append(rs)
+
+ _msg = _mem.poweron_msg
+ rs = RadioSetting("poweron_msg.line1", "Power-On Message 1",
+ RadioSettingValueString(
+ 0, 7, _filter(_msg.line1)))
+ other.append(rs)
+ rs = RadioSetting("poweron_msg.line2", "Power-On Message 2",
+ RadioSettingValueString(
+ 0, 7, _filter(_msg.line2)))
+ other.append(rs)
+
+ lower = 130
+ upper = 179
+ rs = RadioSetting("limits.vhf.lower", "VHF Lower Limit (MHz)",
+ RadioSettingValueInteger(
+ lower, upper, _mem.limits.vhf.lower))
+ other.append(rs)
+
+ rs = RadioSetting("limits.vhf.upper", "VHF Upper Limit (MHz)",
+ RadioSettingValueInteger(
+ lower, upper, _mem.limits.vhf.upper))
+ other.append(rs)
+
+ lower = 400
+ upper = 520
+ rs = RadioSetting("limits.uhf.lower", "UHF Lower Limit (MHz)",
+ RadioSettingValueInteger(
+ lower, upper, _mem.limits.uhf.lower))
+ other.append(rs)
+
+ rs = RadioSetting("limits.uhf.upper", "UHF Upper Limit (MHz)",
+ RadioSettingValueInteger(
+ lower, upper, _mem.limits.uhf.upper))
+ other.append(rs)
+
+ # Work mode settings
+ rs = RadioSetting("settings.displayab", "Display",
+ RadioSettingValueList(
+ LIST_AB, LIST_AB[_mem.settings.displayab]))
+ work.append(rs)
+
+ rs = RadioSetting("settings.workmode", "VFO/MR Mode",
+ RadioSettingValueList(
+ LIST_WORKMODE,
+ LIST_WORKMODE[_mem.settings.workmode]))
+ work.append(rs)
+
+ rs = RadioSetting("settings.keylock", "Keypad Lock",
+ RadioSettingValueBoolean(_mem.settings.keylock))
+ work.append(rs)
+
+ rs = RadioSetting("wmchannel.mrcha", "MR A Channel",
+ RadioSettingValueInteger(0, 127,
+ _mem.wmchannel.mrcha))
+ work.append(rs)
+
+ rs = RadioSetting("wmchannel.mrchb", "MR B Channel",
+ RadioSettingValueInteger(0, 127,
+ _mem.wmchannel.mrchb))
+ work.append(rs)
+
+ def convert_bytes_to_freq(bytes):
+ real_freq = 0
+ for byte in bytes:
+ real_freq = (real_freq * 10) + byte
+ return chirp_common.format_freq(real_freq * 10)
+
+ def my_validate(value):
+ value = chirp_common.parse_freq(value)
+ msg = ("Can't be less than %i.0000")
+ if value > 99000000 and value < 130 * 1000000:
+ raise InvalidValueError(msg % (130))
+ msg = ("Can't be between %i.9975-%i.0000")
+ if (179 + 1) * 1000000 <= value and value < 400 * 1000000:
+ raise InvalidValueError(msg % (179, 400))
+ msg = ("Can't be greater than %i.9975")
+ if value > 99000000 and value > (520 + 1) * 1000000:
+ raise InvalidValueError(msg % (520))
+ return chirp_common.format_freq(value)
+
+ def apply_freq(setting, obj):
+ value = chirp_common.parse_freq(str(setting.value)) / 10
+ for i in range(7, -1, -1):
+ obj.freq[i] = value % 10
+ value /= 10
+
+ val1a = RadioSettingValueString(0, 10,
+ convert_bytes_to_freq(_mem.vfo.a.freq))
+ val1a.set_validate_callback(my_validate)
+ rs = RadioSetting("vfo.a.freq", "VFO A Frequency", val1a)
+ rs.set_apply_callback(apply_freq, _mem.vfo.a)
+ work.append(rs)
+
+ val1b = RadioSettingValueString(0, 10,
+ convert_bytes_to_freq(_mem.vfo.b.freq))
+ val1b.set_validate_callback(my_validate)
+ rs = RadioSetting("vfo.b.freq", "VFO B Frequency", val1b)
+ rs.set_apply_callback(apply_freq, _mem.vfo.b)
+ work.append(rs)
+
+ rs = RadioSetting("vfo.a.sftd", "VFO A Shift",
+ RadioSettingValueList(
+ LIST_SHIFTD, LIST_SHIFTD[_mem.vfo.a.sftd]))
+ work.append(rs)
+
+ rs = RadioSetting("vfo.b.sftd", "VFO B Shift",
+ RadioSettingValueList(
+ LIST_SHIFTD, LIST_SHIFTD[_mem.vfo.b.sftd]))
+ work.append(rs)
+
+ def convert_bytes_to_offset(bytes):
+ real_offset = 0
+ for byte in bytes:
+ real_offset = (real_offset * 10) + byte
+ return chirp_common.format_freq(real_offset * 1000)
+
+ def apply_offset(setting, obj):
+ value = chirp_common.parse_freq(str(setting.value)) / 1000
+ for i in range(5, -1, -1):
+ obj.offset[i] = value % 10
+ value /= 10
+
+ val1a = RadioSettingValueString(
+ 0, 10, convert_bytes_to_offset(_mem.vfo.a.offset))
+ rs = RadioSetting("vfo.a.offset",
+ "VFO A Offset", val1a)
+ rs.set_apply_callback(apply_offset, _mem.vfo.a)
+ work.append(rs)
+
+ val1b = RadioSettingValueString(
+ 0, 10, convert_bytes_to_offset(_mem.vfo.b.offset))
+ rs = RadioSetting("vfo.b.offset",
+ "VFO B Offset", val1b)
+ rs.set_apply_callback(apply_offset, _mem.vfo.b)
+ work.append(rs)
+
+ rs = RadioSetting("vfo.a.txpower3", "VFO A Power",
+ RadioSettingValueList(
+ LIST_TXPOWER,
+ LIST_TXPOWER[_mem.vfo.a.txpower3]))
+ work.append(rs)
+
+ rs = RadioSetting("vfo.b.txpower3", "VFO B Power",
+ RadioSettingValueList(
+ LIST_TXPOWER,
+ LIST_TXPOWER[_mem.vfo.b.txpower3]))
+ work.append(rs)
+
+ rs = RadioSetting("vfo.a.widenarr", "VFO A Bandwidth",
+ RadioSettingValueList(
+ LIST_BANDWIDTH,
+ LIST_BANDWIDTH[_mem.vfo.a.widenarr]))
+ work.append(rs)
+
+ rs = RadioSetting("vfo.b.widenarr", "VFO B Bandwidth",
+ RadioSettingValueList(
+ LIST_BANDWIDTH,
+ LIST_BANDWIDTH[_mem.vfo.b.widenarr]))
+ work.append(rs)
+
+ rs = RadioSetting("vfo.a.scode", "VFO A S-CODE",
+ RadioSettingValueList(
+ LIST_SCODE,
+ LIST_SCODE[_mem.vfo.a.scode]))
+ work.append(rs)
+
+ rs = RadioSetting("vfo.b.scode", "VFO B S-CODE",
+ RadioSettingValueList(
+ LIST_SCODE,
+ LIST_SCODE[_mem.vfo.b.scode]))
+ work.append(rs)
+
+ rs = RadioSetting("vfo.a.step", "VFO A Tuning Step",
+ RadioSettingValueList(
+ LIST_STEP, LIST_STEP[_mem.vfo.a.step]))
+ work.append(rs)
+ rs = RadioSetting("vfo.b.step", "VFO B Tuning Step",
+ RadioSettingValueList(
+ LIST_STEP, LIST_STEP[_mem.vfo.b.step]))
+ work.append(rs)
+
+ # broadcast FM settings
+ _fm_presets = self._memobj.fm_presets
+ if _fm_presets <= 108.0 * 10 - 650:
+ preset = _fm_presets / 10.0 + 65
+ elif _fm_presets >= 65.0 * 10 and _fm_presets <= 108.0 * 10:
+ preset = _fm_presets / 10.0
+ else:
+ preset = 76.0
+ rs = RadioSetting("fm_presets", "FM Preset(MHz)",
+ RadioSettingValueFloat(65, 108.0, preset, 0.1, 1))
+ fm_preset.append(rs)
+
+ # DTMF settings
+ def apply_code(setting, obj, length):
+ code = []
+ for j in range(0, length):
+ try:
+ code.append(DTMF_CHARS.index(str(setting.value)[j]))
+ except IndexError:
+ code.append(0xFF)
+ obj.code = code
+
+ for i in range(0, 15):
+ _codeobj = self._memobj.pttid[i].code
+ _code = "".join([DTMF_CHARS[x] for x in _codeobj if int(x) < 0x1F])
+ val = RadioSettingValueString(0, 5, _code, False)
+ val.set_charset(DTMF_CHARS)
+ pttid = RadioSetting("pttid/%i.code" % i,
+ "Signal Code %i" % (i + 1), val)
+ pttid.set_apply_callback(apply_code, self._memobj.pttid[i], 5)
+ dtmfe.append(pttid)
+
+ if _mem.ani.dtmfon > 0xC3:
+ val = 0x03
+ else:
+ val = _mem.ani.dtmfon
+ rs = RadioSetting("ani.dtmfon", "DTMF Speed (on)",
+ RadioSettingValueList(LIST_DTMFSPEED,
+ LIST_DTMFSPEED[val]))
+ dtmfe.append(rs)
+
+ if _mem.ani.dtmfoff > 0xC3:
+ val = 0x03
+ else:
+ val = _mem.ani.dtmfoff
+ rs = RadioSetting("ani.dtmfoff", "DTMF Speed (off)",
+ RadioSettingValueList(LIST_DTMFSPEED,
+ LIST_DTMFSPEED[val]))
+ dtmfe.append(rs)
+
+ _codeobj = self._memobj.ani.code
+ _code = "".join([DTMF_CHARS[x] for x in _codeobj if int(x) < 0x1F])
+ val = RadioSettingValueString(0, 5, _code, False)
+ val.set_charset(DTMF_CHARS)
+ rs = RadioSetting("ani.code", "ANI Code", val)
+ rs.set_apply_callback(apply_code, self._memobj.ani, 5)
+ dtmfe.append(rs)
+
+ rs = RadioSetting("ani.aniid", "When to send ANI ID",
+ RadioSettingValueList(LIST_PTTID,
+ LIST_PTTID[_mem.ani.aniid]))
+ dtmfe.append(rs)
+
+ # Service settings
+ for band in ["vhf", "uhf"]:
+ for index in range(0, 10):
+ key = "squelch.%s.sql%i" % (band, index)
+ if band == "vhf":
+ _obj = self._memobj.squelch.vhf
+ elif band == "uhf":
+ _obj = self._memobj.squelch.uhf
+ val = RadioSettingValueInteger(0, 123,
+ getattr(_obj, "sql%i" % (index)))
+ if index == 0:
+ val.set_mutable(False)
+ name = "%s Squelch %i" % (band.upper(), index)
+ rs = RadioSetting(key, name, val)
+ service.append(rs)
+
+ return top
+
+ @classmethod
+ def match_model(cls, filedata, filename):
+ match_size = False
+ match_model = False
+
+ # testing the file data size
+ if len(filedata) in [0x2008, 0x2010]:
+ 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 BFA58(WP970I):
+ """Baofeng BF-A58"""
+ VENDOR = "Baofeng"
+ MODEL = "BF-A58"
+
+ _fileid = ["BFT515 ", "BFT517 "]
+
+ at directory.register
+class UV82WP(WP970I):
+ """Baofeng UV82-WP"""
+ VENDOR = "Baofeng"
+ MODEL = "UV-82WP"
+
+ at directory.register
+class GT3WP(WP970I):
+ """Baofeng GT-3WP"""
+ VENDOR = "Baofeng"
+ MODEL = "GT-3WP"
+
+ at directory.register
+class RT6(WP970I):
+ """Retevis RT6"""
+ VENDOR = "Retevis"
+ MODEL = "RT6"
diff --git a/chirp/drivers/btech.py b/chirp/drivers/btech.py
index bed353f..3d634fa 100644
--- a/chirp/drivers/btech.py
+++ b/chirp/drivers/btech.py
@@ -21,12 +21,13 @@ import logging
LOG = logging.getLogger(__name__)
+from time import sleep
from chirp import chirp_common, directory, memmap
from chirp import bitwise, errors, util
from chirp.settings import RadioSettingGroup, RadioSetting, \
RadioSettingValueBoolean, RadioSettingValueList, \
RadioSettingValueString, RadioSettingValueInteger, \
- RadioSettings, InvalidValueError
+ RadioSettingValueFloat, RadioSettings, InvalidValueError
from textwrap import dedent
MEM_FORMAT = """
@@ -154,6 +155,13 @@ struct {
u8 unknown1[10];
} names[200];
+#seekto 0x3000;
+struct {
+ u8 freq[8];
+ char broadcast_station_name[6];
+ u8 unknown[2];
+} fm_radio_preset[16];
+
#seekto 0x3C90;
struct {
u8 vhf_low[3];
@@ -304,6 +312,7 @@ KT8900_fp1 = "M2C234"
KT8900_fp2 = "M2G1F4"
KT8900_fp3 = "M2G2F4"
KT8900_fp4 = "M2G304"
+KT8900_fp5 = "M2G314"
# this radio has an extra ID
KT8900_id = " 303688"
@@ -313,6 +322,10 @@ KT8900R_fp = "M3G1F4"
KT8900R_fp1 = "M3G214"
# another model
KT8900R_fp2 = "M3C234"
+# another model G4?
+KT8900R_fp3 = "M39164"
+# another model
+KT8900R_fp4 = "M3G314"
# this radio has an extra ID
KT8900R_id = "280528"
@@ -394,6 +407,15 @@ def _send(radio, data):
try:
for byte in data:
radio.pipe.write(byte)
+ # Some OS (mainly Linux ones) are too fast on the serial and
+ # get the MCU inside the radio stuck in the early stages, this
+ # hits some models more than others.
+ #
+ # To cope with that we introduce a delay on the writes.
+ # Many option have been tested (delaying only after error occures, after short reads, only for linux, ...)
+ # Finally, a static delay was chosen as simplest of all solutions (Michael Wagner, OE4AMW)
+ # (for details, see issue 3993)
+ sleep(0.002)
# DEBUG
if debug is True:
@@ -1021,6 +1043,13 @@ class BTech(chirp_common.CloneModeRadio, chirp_common.ExperimentalRadio):
_mem = self._memobj.memory[mem.number]
_names = self._memobj.names[mem.number]
+ mem_was_empty = False
+ # same method as used in get_memory for determining if mem is empty
+ # doing this BEFORE overwriting it with new values ...
+ if _mem.get_raw()[0] == "\xFF":
+ LOG.debug("This mem was empty before")
+ mem_was_empty = True
+
# if empty memmory
if mem.empty:
# the channel itself
@@ -1078,16 +1107,24 @@ class BTech(chirp_common.CloneModeRadio, chirp_common.ExperimentalRadio):
# extra settings
if len(mem.extra) > 0:
# there are setting, parse
+ LOG.debug("Extra-Setting supplied. Setting them.")
for setting in mem.extra:
setattr(_mem, setting.get_name(), setting.value)
else:
- # there is no extra settings, load defaults
- _mem.spmute = 0
- _mem.optsig = 0
- _mem.scramble = 0
- _mem.bcl = 0
- _mem.pttid = 0
- _mem.scode = 0
+ if mem.empty:
+ LOG.debug("New mem is empty.")
+ else:
+ LOG.debug("New mem is NOT empty")
+ # set extra-settings to default ONLY when apreviously empty or
+ # deleted memory was edited to prevent errors such as #4121
+ if mem_was_empty :
+ LOG.debug("old mem was empty. Setting default for extras.")
+ _mem.spmute = 0
+ _mem.optsig = 0
+ _mem.scramble = 0
+ _mem.bcl = 0
+ _mem.pttid = 0
+ _mem.scode = 0
return mem
@@ -1098,7 +1135,8 @@ class BTech(chirp_common.CloneModeRadio, chirp_common.ExperimentalRadio):
advanced = RadioSettingGroup("advanced", "Advanced Settings")
other = RadioSettingGroup("other", "Other Settings")
work = RadioSettingGroup("work", "Work Mode Settings")
- top = RadioSettings(basic, advanced, other, work)
+ fm_presets = RadioSettingGroup("fm_presets", "FM Presets")
+ top = RadioSettings(basic, advanced, other, work, fm_presets)
# Basic
tdr = RadioSetting("settings.tdr", "Transceiver dual receive",
@@ -1282,7 +1320,7 @@ class BTech(chirp_common.CloneModeRadio, chirp_common.ExperimentalRadio):
if self.MODEL in ("UV-2501", "UV-5001"):
vfomren = RadioSetting("settings2.vfomren", "VFO/MR switching",
RadioSettingValueBoolean(
- not _mem.settings2.vfomren))
+ _mem.settings2.vfomren))
advanced.append(vfomren)
reseten = RadioSetting("settings2.reseten", "RESET",
@@ -1535,6 +1573,47 @@ class BTech(chirp_common.CloneModeRadio, chirp_common.ExperimentalRadio):
PTTID_LIST[_mem.settings.pttid]))
work.append(pttid)
+ #FM presets
+ def fm_validate(value):
+ if value == 0:
+ return chirp_common.format_freq(value)
+ if not (87.5 <= value and value <= 108.0): # 87.5-108MHz
+ msg = ("FM-Preset-Frequency: Must be between 87.5 and 108 MHz")
+ raise InvalidValueError(msg)
+ return value
+
+ def apply_fm_preset_name(setting, obj):
+ valstring = str (setting.value)
+ for i in range(0,6):
+ if valstring[i] in VALID_CHARS:
+ obj[i] = valstring[i]
+ else:
+ obj[i] = '0xff'
+
+ def apply_fm_freq(setting, obj):
+ value = chirp_common.parse_freq(str(setting.value)) / 10
+ for i in range(7, -1, -1):
+ obj.freq[i] = value % 10
+ value /= 10
+
+ _presets = self._memobj.fm_radio_preset
+ i = 1
+ for preset in _presets:
+ line = RadioSetting("fm_presets_"+ str(i), "Station name " + str(i),
+ RadioSettingValueString(0, 6, _filter(
+ preset.broadcast_station_name)))
+ line.set_apply_callback(apply_fm_preset_name,
+ preset.broadcast_station_name)
+
+ val = RadioSettingValueFloat(0, 108, convert_bytes_to_freq(preset.freq))
+ fmfreq = RadioSetting("fm_presets_"+ str(i) + "_freq", "Frequency "+ str(i), val)
+ val.set_validate_callback(fm_validate)
+ fmfreq.set_apply_callback(apply_fm_freq, preset)
+ fm_presets.append(line)
+ fm_presets.append(fmfreq)
+
+ i = i + 1
+
return top
def set_settings(self, settings):
@@ -1567,8 +1646,6 @@ class BTech(chirp_common.CloneModeRadio, chirp_common.ExperimentalRadio):
if element.has_apply_callback():
LOG.debug("Using apply callback")
element.run_apply_callback()
- elif setting == "vfomren":
- setattr(obj, setting, not int(element.value))
elif element.value.get_mutable():
LOG.debug("Setting %s = %s" % (setting, element.value))
setattr(obj, setting, element.value)
@@ -1691,7 +1768,8 @@ class KT9800(BTech):
KT8900_fp1,
KT8900_fp2,
KT8900_fp3,
- KT8900_fp4]
+ KT8900_fp4,
+ KT8900_fp5]
_id2 = KT8900_id
# Clones
ALIASES = [JT6188Mini, SSGT890, ZastoneMP300]
@@ -1708,7 +1786,9 @@ class KT9800R(BTech):
_magic = MSTRING_KT8900R
_fileid = [KT8900R_fp,
KT8900R_fp1,
- KT8900R_fp2]
+ KT8900R_fp2,
+ KT8900R_fp3,
+ KT8900R_fp4]
_id2 = KT8900R_id
diff --git a/chirp/drivers/ft2900.py b/chirp/drivers/ft2900.py
index 5712163..7175733 100644
--- a/chirp/drivers/ft2900.py
+++ b/chirp/drivers/ft2900.py
@@ -146,7 +146,7 @@ def _upload(radio):
cs += ord(byte)
while block < (radio.get_memsize() / 32):
- data = radio.get_mmap()[block*32:(block+1)*32]
+ data = radio.get_mmap()[block * 32:(block + 1) * 32]
LOG.debug("Writing block %i:\n%s" % (block, util.hexprint(data)))
@@ -381,9 +381,9 @@ def _decode_name(mem):
def _encode_name(mem):
if(mem.strip() == ""):
- return [0xff]*6
+ return [0xff] * 6
- name = [None]*6
+ name = [None] * 6
for i in range(0, 6):
try:
name[i] = CHARSET.index(mem[i])
@@ -399,6 +399,7 @@ def _wipe_memory(mem):
class FT2900Bank(chirp_common.NamedBank):
+
def get_name(self):
_bank = self._model._radio._memobj.bank_names[self.index]
name = ""
@@ -416,6 +417,7 @@ class FT2900Bank(chirp_common.NamedBank):
class FT2900BankModel(chirp_common.BankModel):
+
def get_num_mappings(self):
return 8
@@ -558,7 +560,7 @@ class FT2900Radio(YaesuCloneModeRadio):
def get_memory(self, number):
_mem = self._memobj.memory[number]
- _flag = self._memobj.flags[(number)/2]
+ _flag = self._memobj.flags[(number) / 2]
nibble = ((number) % 2) and "even" or "odd"
used = _flag["%s_masked" % nibble]
@@ -622,7 +624,7 @@ class FT2900Radio(YaesuCloneModeRadio):
def set_memory(self, mem):
_mem = self._memobj.memory[mem.number]
- _flag = self._memobj.flags[(mem.number)/2]
+ _flag = self._memobj.flags[(mem.number) / 2]
nibble = ((mem.number) % 2) and "even" or "odd"
@@ -764,19 +766,19 @@ class FT2900Radio(YaesuCloneModeRadio):
# 5 BCLO
opts = ["Off", "On"]
misc.append(RadioSetting(
- "busy_lockout", "Busy Channel Lock-Out",
- RadioSettingValueList(opts, opts[_settings.busy_lockout])))
+ "busy_lockout", "Busy Channel Lock-Out",
+ RadioSettingValueList(opts, opts[_settings.busy_lockout])))
# 6 BEEP
opts = ["Off", "Key+Scan", "Key"]
switch.append(RadioSetting(
- "beep", "Enable the Beeper",
- RadioSettingValueList(opts, opts[_settings.beep])))
+ "beep", "Enable the Beeper",
+ RadioSettingValueList(opts, opts[_settings.beep])))
# 7 BELL
opts = ["Off", "1", "3", "5", "8", "Continuous"]
ctcss.append(RadioSetting("bell", "Bell Repetitions",
- RadioSettingValueList(opts, opts[_settings.bell])))
+ RadioSettingValueList(opts, opts[_settings.bell])))
# 8 BNK.LNK
for i in range(0, 8):
@@ -800,7 +802,7 @@ class FT2900Radio(YaesuCloneModeRadio):
# 11 CW.ID
opts = ["Off", "On"]
arts.append(RadioSetting("cw_id", "CW ID Enable",
- RadioSettingValueList(opts, opts[_settings.cw_id])))
+ RadioSettingValueList(opts, opts[_settings.cw_id])))
cw_id_text = ""
for i in _settings.cw_id_string:
@@ -830,14 +832,14 @@ class FT2900Radio(YaesuCloneModeRadio):
"10WPM", "11WPM", "12WPM", "13WPM", "15WPM", "17WPM",
"20WPM", "24WPM", "30WPM", "40WPM"]
misc.append(RadioSetting("cw_trng", "CW Training",
- RadioSettingValueList(opts, opts[_settings.cw_trng])))
+ RadioSettingValueList(opts, opts[_settings.cw_trng])))
# todo: make the setting of the units here affect the display
# of the speed. Not critical, but would be slick.
opts = ["CPM", "WPM"]
misc.append(RadioSetting("cw_trng_units", "CW Training Units",
- RadioSettingValueList(opts,
- opts[_settings.cw_trng_units])))
+ RadioSettingValueList(opts,
+ opts[_settings.cw_trng_units])))
# 13 DC VLT - a read-only status, so nothing to do here
@@ -918,7 +920,7 @@ class FT2900Radio(YaesuCloneModeRadio):
# 22 INT.CD
opts = ["DTMF %X" % (x) for x in range(0, 16)]
wires.append(RadioSetting("int_cd", "Access Number for WiRES(TM)",
- RadioSettingValueList(opts, opts[_settings.int_cd])))
+ RadioSettingValueList(opts, opts[_settings.int_cd])))
# 23 ING MD
opts = ["Sister Radio Group", "Friends Radio Group"]
@@ -1053,26 +1055,26 @@ class FT2900Radio(YaesuCloneModeRadio):
# 36 PRG P1
opts = button_opts + ["DC Volts"]
switch.append(RadioSetting(
- "prog_p1", "P1 Button",
- RadioSettingValueList(opts, opts[_settings.prog_p1])))
+ "prog_p1", "P1 Button",
+ RadioSettingValueList(opts, opts[_settings.prog_p1])))
# 37 PRG P2
opts = button_opts + ["Dimmer"]
switch.append(RadioSetting(
- "prog_p2", "P2 Button",
- RadioSettingValueList(opts, opts[_settings.prog_p2])))
+ "prog_p2", "P2 Button",
+ RadioSettingValueList(opts, opts[_settings.prog_p2])))
# 38 PRG P3
opts = button_opts + ["Mic Gain"]
switch.append(RadioSetting(
- "prog_p3", "P3 Button",
- RadioSettingValueList(opts, opts[_settings.prog_p3])))
+ "prog_p3", "P3 Button",
+ RadioSettingValueList(opts, opts[_settings.prog_p3])))
# 39 PRG P4
opts = button_opts + ["Skip"]
switch.append(RadioSetting(
- "prog_p4", "P4 Button",
- RadioSettingValueList(opts, opts[_settings.prog_p4])))
+ "prog_p4", "P4 Button",
+ RadioSettingValueList(opts, opts[_settings.prog_p4])))
# 40 PSWD
password = ""
@@ -1101,24 +1103,24 @@ class FT2900Radio(YaesuCloneModeRadio):
# 41 RESUME
opts = ["3 Sec", "5 Sec", "10 Sec", "Busy", "Hold"]
scan.append(RadioSetting("resume", "Scan Resume Mode",
- RadioSettingValueList(opts, opts[_settings.resume])))
+ RadioSettingValueList(opts, opts[_settings.resume])))
# 42 RF.SQL
opts = ["Off"] + ["S-%d" % (x) for x in range(1, 10)]
misc.append(RadioSetting("rf_sql", "RF Squelch Threshold",
- RadioSettingValueList(opts, opts[_settings.rf_sql])))
+ RadioSettingValueList(opts, opts[_settings.rf_sql])))
# 43 RPT - per channel attribute, nothing to do here
# 44 RVRT
opts = ["Off", "On"]
misc.append(RadioSetting("revert", "Priority Revert",
- RadioSettingValueList(opts, opts[_settings.revert])))
+ RadioSettingValueList(opts, opts[_settings.revert])))
# 45 S.SRCH
opts = ["Single", "Continuous"]
misc.append(RadioSetting("s_search", "Smart Search Sweep Mode",
- RadioSettingValueList(opts, opts[_settings.s_search])))
+ RadioSettingValueList(opts, opts[_settings.s_search])))
# 46 SHIFT - per channel setting, nothing to do here
@@ -1164,12 +1166,12 @@ class FT2900Radio(YaesuCloneModeRadio):
# 57 WX.ALT
opts = ["Off", "On"]
misc.append(RadioSetting("wx_alert", "Weather Alert Scan",
- RadioSettingValueList(opts, opts[_settings.wx_alert])))
+ RadioSettingValueList(opts, opts[_settings.wx_alert])))
# 58 WX.VOL
opts = ["Normal", "Maximum"]
misc.append(RadioSetting("wx_vol_max", "Weather Alert Volume",
- RadioSettingValueList(opts, opts[_settings.wx_vol_max])))
+ RadioSettingValueList(opts, opts[_settings.wx_vol_max])))
# 59 W/N DV - this is a per-channel attribute, nothing to do here
diff --git a/chirp/drivers/ft60.py b/chirp/drivers/ft60.py
index 9b7020e..39c8f44 100644
--- a/chirp/drivers/ft60.py
+++ b/chirp/drivers/ft60.py
@@ -82,14 +82,14 @@ def _upload(radio):
for i in range(0, 448):
offset = 8 + (i * 64)
- _send(radio.pipe, radio.get_mmap()[offset:offset+64])
+ _send(radio.pipe, radio.get_mmap()[offset:offset + 64])
ack = radio.pipe.read(1)
if ack != ACK:
raise Exception(_("Radio did not ack block %i") % i)
if radio.status_fn:
status = chirp_common.Status()
- status.cur = offset+64
+ status.cur = offset + 64
status.max = radio.get_memsize()
status.msg = "Cloning to radio"
radio.status_fn(status)
@@ -133,7 +133,7 @@ def _decode_name(mem):
def _encode_name(mem):
- name = [None]*6
+ name = [None] * 6
for i in range(0, 6):
try:
name[i] = CHARSET.index(mem[i])
@@ -291,6 +291,7 @@ SPECIALS = ["%s%d" % (c, i + 1) for i in range(0, 50) for c in ('L', 'U')]
class FT60BankModel(chirp_common.BankModel):
+
def get_num_mappings(self):
return 10
@@ -357,7 +358,8 @@ class FT60Radio(yaesu_clone.YaesuCloneModeRadio):
radio on.
4. Rotate the DIAL job to select "F8 CLONE".
5. Press the [F/W] key momentarily.
-6. <b>After clicking OK</b>, press the [PTT] switch to send image."""))
+6. <b>After clicking OK</b>, hold the [PTT] switch
+ for one second to send image."""))
rp.pre_upload = _(dedent("""\
1. Turn radio off.
2. Connect cable to MIC/SP jack.
@@ -435,7 +437,7 @@ class FT60Radio(yaesu_clone.YaesuCloneModeRadio):
wires, eai, switch, misc, mbls)
# APO
- opts = ["OFF"] + ["%0.1f" % (x * 0.5) for x in range(1, 24+1)]
+ opts = ["OFF"] + ["%0.1f" % (x * 0.5) for x in range(1, 24 + 1)]
misc.append(
RadioSetting(
"apo", "Automatic Power Off",
@@ -465,15 +467,15 @@ class FT60Radio(yaesu_clone.YaesuCloneModeRadio):
# BCLO
opts = ["OFF", "ON"]
misc.append(RadioSetting(
- "bclo", "Busy Channel Lock-Out",
- RadioSettingValueList(opts, opts[_settings.bclo])))
+ "bclo", "Busy Channel Lock-Out",
+ RadioSettingValueList(opts, opts[_settings.bclo])))
# BEEP
opts = ["OFF", "KEY", "KEY+SC"]
rs = RadioSetting(
- "beep_key", "Enable the Beeper",
- RadioSettingValueList(
- opts, opts[_settings.beep_key + _settings.beep_sc]))
+ "beep_key", "Enable the Beeper",
+ RadioSettingValueList(
+ opts, opts[_settings.beep_key + _settings.beep_sc]))
def apply_beep(s, obj):
setattr(obj, "beep_key",
@@ -485,27 +487,27 @@ class FT60Radio(yaesu_clone.YaesuCloneModeRadio):
# BELL
opts = ["OFF", "1T", "3T", "5T", "8T", "CONT"]
ctcss.append(RadioSetting("bell", "Bell Repetitions",
- RadioSettingValueList(opts, opts[_settings.bell])))
+ RadioSettingValueList(opts, opts[_settings.bell])))
# BSY.LED
opts = ["ON", "OFF"]
misc.append(RadioSetting("bsy_led", "Busy LED",
- RadioSettingValueList(opts, opts[_settings.bsy_led])))
+ RadioSettingValueList(opts, opts[_settings.bsy_led])))
# DCS.NR
opts = ["TR/X N", "RX R", "TX R", "T/RX R"]
ctcss.append(RadioSetting("dcs_nr", "\"Inverted\" DCS Code Decoding",
- RadioSettingValueList(opts, opts[_settings.dcs_nr])))
+ RadioSettingValueList(opts, opts[_settings.dcs_nr])))
# DT.DLY
opts = ["50 MS", "100 MS", "250 MS", "450 MS", "750 MS", "1000 MS"]
ctcss.append(RadioSetting("dt_dly", "DTMF Autodialer Delay Time",
- RadioSettingValueList(opts, opts[_settings.dt_dly])))
+ RadioSettingValueList(opts, opts[_settings.dt_dly])))
# DT.SPD
opts = ["50 MS", "100 MS"]
ctcss.append(RadioSetting("dt_spd", "DTMF Autodialer Sending Speed",
- RadioSettingValueList(opts, opts[_settings.dt_spd])))
+ RadioSettingValueList(opts, opts[_settings.dt_spd])))
# DT.WRT
for i in range(0, 9):
@@ -518,8 +520,9 @@ class FT60Radio(yaesu_clone.YaesuCloneModeRadio):
str += DTMF_CHARS[c]
val = RadioSettingValueString(0, 16, str, False)
val.set_charset(DTMF_CHARS + list("abcd"))
- rs = RadioSetting("dtmf_%i" % i,
- "DTMF Autodialer Memory %i" % (i + 1), val)
+ rs = RadioSetting("dtmf_%i" % i,
+ "DTMF Autodialer Memory %i" % (i + 1), val)
+
def apply_dtmf(s, obj):
str = s.value.get_value().upper().rstrip()
val = [DTMF_CHARS.index(x) for x in str]
@@ -532,7 +535,7 @@ class FT60Radio(yaesu_clone.YaesuCloneModeRadio):
# EDG.BEP
opts = ["OFF", "ON"]
misc.append(RadioSetting("edg_bep", "Band Edge Beeper",
- RadioSettingValueList(opts, opts[_settings.edg_bep])))
+ RadioSettingValueList(opts, opts[_settings.edg_bep])))
# I.NET
opts = ["OFF", "COD", "MEM"]
@@ -550,18 +553,18 @@ class FT60Radio(yaesu_clone.YaesuCloneModeRadio):
"CODE 5", "CODE 6", "CODE 7", "CODE 8", "CODE 9",
"CODE A", "CODE B", "CODE C", "CODE D", "CODE E", "CODE F"]
wires.append(RadioSetting("int_cd", "Access Number for WiRES(TM)",
- RadioSettingValueList(opts, opts[_settings.int_cd])))
+ RadioSettingValueList(opts, opts[_settings.int_cd])))
# INT.MR
opts = ["d1", "d2", "d3", "d4", "d5", "d6", "d7", "d8", "d9"]
wires.append(RadioSetting(
- "int_mr", "Access Number (DTMF) for Non-WiRES(TM)",
+ "int_mr", "Access Number (DTMF) for Non-WiRES(TM)",
RadioSettingValueList(opts, opts[_settings.int_mr])))
# LAMP
opts = ["KEY", "5SEC", "TOGGLE"]
switch.append(RadioSetting("lamp", "Lamp Mode",
- RadioSettingValueList(opts, opts[_settings.lamp])))
+ RadioSettingValueList(opts, opts[_settings.lamp])))
# LOCK
opts = ["LK KEY", "LKDIAL", "LK K+D", "LK PTT",
@@ -578,44 +581,44 @@ class FT60Radio(yaesu_clone.YaesuCloneModeRadio):
# M/T-CL
opts = ["MONI", "T-CALL"]
switch.append(RadioSetting("mt_cl", "MONI Switch Function",
- RadioSettingValueList(opts, opts[_settings.mt_cl])))
+ RadioSettingValueList(opts, opts[_settings.mt_cl])))
# PAG.ABK
opts = ["OFF", "ON"]
eai.append(RadioSetting("pag_abk", "Paging Answer Back",
- RadioSettingValueList(opts, opts[_settings.pag_abk])))
+ RadioSettingValueList(opts, opts[_settings.pag_abk])))
# RESUME
opts = ["TIME", "HOLD", "BUSY"]
scan.append(RadioSetting("resume", "Scan Resume Mode",
- RadioSettingValueList(opts, opts[_settings.resume])))
+ RadioSettingValueList(opts, opts[_settings.resume])))
# REV/HM
opts = ["REV", "HOME"]
switch.append(RadioSetting("rev_hm", "HM/RV Key Function",
- RadioSettingValueList(opts, opts[_settings.rev_hm])))
+ RadioSettingValueList(opts, opts[_settings.rev_hm])))
# RF.SQL
opts = ["OFF", "S-1", "S-2", "S-3", "S-4", "S-5", "S-6",
"S-7", "S-8", "S-FULL"]
misc.append(RadioSetting("rf_sql", "RF Squelch Threshold",
- RadioSettingValueList(opts, opts[_settings.rf_sql])))
+ RadioSettingValueList(opts, opts[_settings.rf_sql])))
# PRI.RVT
opts = ["OFF", "ON"]
scan.append(RadioSetting("pri_rvt", "Priority Revert",
- RadioSettingValueList(opts, opts[_settings.pri_rvt])))
+ RadioSettingValueList(opts, opts[_settings.pri_rvt])))
# RXSAVE
opts = ["OFF", "200 MS", "300 MS", "500 MS", "1 S", "2 S"]
power.append(RadioSetting(
- "rxsave", "Receive Mode Batery Savery Interval",
+ "rxsave", "Receive Mode Batery Savery Interval",
RadioSettingValueList(opts, opts[_settings.rxsave])))
# S.SRCH
opts = ["SINGLE", "CONT"]
misc.append(RadioSetting("ssrch", "Smart Search Sweep Mode",
- RadioSettingValueList(opts, opts[_settings.ssrch])))
+ RadioSettingValueList(opts, opts[_settings.ssrch])))
# SCN.MD
opts = ["MEM", "ONLY"]
@@ -626,32 +629,32 @@ class FT60Radio(yaesu_clone.YaesuCloneModeRadio):
# SCN.LMP
opts = ["OFF", "ON"]
scan.append(RadioSetting("scn_lmp", "Scan Lamp",
- RadioSettingValueList(opts, opts[_settings.scn_lmp])))
+ RadioSettingValueList(opts, opts[_settings.scn_lmp])))
# TOT
- opts = ["OFF"] + ["%dMIN" % (x) for x in range(1, 30+1)]
+ opts = ["OFF"] + ["%dMIN" % (x) for x in range(1, 30 + 1)]
misc.append(RadioSetting("tot", "Timeout Timer",
- RadioSettingValueList(opts, opts[_settings.tot])))
+ RadioSettingValueList(opts, opts[_settings.tot])))
# TX.LED
opts = ["ON", "OFF"]
misc.append(RadioSetting("tx_led", "TX LED",
- RadioSettingValueList(opts, opts[_settings.tx_led])))
+ RadioSettingValueList(opts, opts[_settings.tx_led])))
# TXSAVE
opts = ["OFF", "ON"]
power.append(RadioSetting("txsave", "Transmitter Battery Saver",
- RadioSettingValueList(opts, opts[_settings.txsave])))
+ RadioSettingValueList(opts, opts[_settings.txsave])))
# VFO.BND
opts = ["BAND", "ALL"]
misc.append(RadioSetting("vfo_bnd", "VFO Band Edge Limiting",
- RadioSettingValueList(opts, opts[_settings.vfo_bnd])))
+ RadioSettingValueList(opts, opts[_settings.vfo_bnd])))
# WX.ALT
opts = ["OFF", "ON"]
scan.append(RadioSetting("wx_alt", "Weather Alert Scan",
- RadioSettingValueList(opts, opts[_settings.wx_alt])))
+ RadioSettingValueList(opts, opts[_settings.wx_alt])))
# MBS
for i in range(0, 10):
diff --git a/chirp/drivers/ft817.py b/chirp/drivers/ft817.py
index 3016538..28cd2a2 100644
--- a/chirp/drivers/ft817.py
+++ b/chirp/drivers/ft817.py
@@ -357,10 +357,10 @@ class FT817Radio(yaesu_clone.YaesuCloneModeRadio):
moredata = self.pipe.read(2)
if moredata:
raise Exception(
- _("Radio sent data after the last awaited block, "
- "this happens when the selected model is a non-US "
- "but the radio is a US one. "
- "Please choose the correct model and try again."))
+ _("Radio sent data after the last awaited block, "
+ "this happens when the selected model is a non-US "
+ "but the radio is a US one. "
+ "Please choose the correct model and try again."))
LOG.info("Clone completed in %i seconds" % (time.time() - start))
@@ -389,7 +389,7 @@ class FT817Radio(yaesu_clone.YaesuCloneModeRadio):
LOG.debug(util.hexprint(chr(blocks)))
LOG.debug(util.hexprint(self.get_mmap()[pos:pos + block]))
LOG.debug(util.hexprint(chr(checksum.get_calculated(
- self.get_mmap()))))
+ self.get_mmap()))))
self.pipe.write(chr(blocks))
self.pipe.write(self.get_mmap()[pos:pos + block])
self.pipe.write(chr(checksum.get_calculated(self.get_mmap())))
@@ -584,7 +584,7 @@ class FT817Radio(yaesu_clone.YaesuCloneModeRadio):
if key != "extd_number":
if cur_mem.__dict__[key] != mem.__dict__[key]:
raise errors.RadioError("Editing field `%s' " % key +
- "is not supported on this channel")
+ "is not supported on this channel")
self._set_memory(mem, _mem)
@@ -746,7 +746,7 @@ class FT817Radio(yaesu_clone.YaesuCloneModeRadio):
if mem.freq >= lo and mem.freq <= hi:
if mem.mode != "FM":
msgs.append(chirp_common.ValidationError(
- "Only FM is supported in this band"))
+ "Only FM is supported in this band"))
# TODO check that step is valid in current mode
return msgs
@@ -782,7 +782,7 @@ class FT817Radio(yaesu_clone.YaesuCloneModeRadio):
RadioSettingValueList(options,
options[
_settings.disable_amfm_dial
- ]))
+ ]))
panel.append(rs)
rs = RadioSetting("am_mic", "AM mic level",
RadioSettingValueInteger(0, 100, _settings.am_mic))
@@ -1007,7 +1007,7 @@ class FT817Radio(yaesu_clone.YaesuCloneModeRadio):
antenna.append(rs)
st = RadioSettingValueString(0, 7, ''.join([self._CALLSIGN_CHARSET[x]
- for x in self._memobj.callsign]))
+ for x in self._memobj.callsign]))
st.set_charset(self._CALLSIGN_CHARSET)
rs = RadioSetting("callsign", "Callsign", st)
cw.append(rs)
@@ -1042,7 +1042,7 @@ class FT817Radio(yaesu_clone.YaesuCloneModeRadio):
RadioSettingValueList(options,
options[
_settings.pwr_meter_mode
- ]))
+ ]))
panelcontr.append(rs)
rs = RadioSetting("vox", "Vox",
RadioSettingValueBoolean(_settings.vox))
@@ -1084,7 +1084,7 @@ class FT817Radio(yaesu_clone.YaesuCloneModeRadio):
setting = element.get_name()
try:
LOG.debug("Setting %s(%s) <= %s" % (setting,
- getattr(obj, setting), element.value))
+ getattr(obj, setting), element.value))
except AttributeError:
LOG.debug("Setting %s <= %s" % (setting, element.value))
if setting == "contrast":
@@ -1130,7 +1130,7 @@ class FT817NDUSRadio(FT817Radio):
"M-603": -40,
"M-604": -39,
"M-605": -38,
- }
+ }
LAST_SPECIAL60M_INDEX = -42
SPECIAL_MEMORIES = dict(FT817Radio.SPECIAL_MEMORIES)
diff --git a/chirp/drivers/gmrsuv1.py b/chirp/drivers/gmrsuv1.py
new file mode 100644
index 0000000..ea197c6
--- /dev/null
+++ b/chirp/drivers/gmrsuv1.py
@@ -0,0 +1,828 @@
+# Copyright 2016:
+# * Jim Unroe KC9HI, <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
+# 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.drivers import baofeng_common
+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, \
+ InvalidValueError
+from textwrap import dedent
+
+##### MAGICS #########################################################
+
+# BTECH GMRS-V1 magic string
+MSTRING_GMRSV1 = "\x50\x5F\x20\x15\x12\x15\x4D"
+
+##### ID strings #####################################################
+
+# BTECH GMRS-V1
+GMRSV1_fp1 = "US32411"
+
+DTMF_CHARS = "0123456789 *#ABCD"
+STEPS = [2.5, 5.0, 6.25, 10.0, 12.5, 20.0, 25.0, 50.0]
+
+LIST_AB = ["A", "B"]
+LIST_ALMOD = ["Off", "Site", "Tone", "Code"]
+LIST_BANDWIDTH = ["Wide", "Narrow"]
+LIST_COLOR = ["Off", "Blue", "Orange", "Purple"]
+LIST_DTMFSPEED = ["%s ms" % x for x in range(50, 2010, 10)]
+LIST_DTMFST = ["Off", "DT-ST", "ANI-ST", "DT+ANI"]
+LIST_MODE = ["Channel", "Name", "Frequency"]
+LIST_OFF1TO9 = ["Off"] + list("123456789")
+LIST_OFF1TO10 = LIST_OFF1TO9 + ["10"]
+LIST_OFFAB = ["Off"] + LIST_AB
+LIST_RESUME = ["TO", "CO", "SE"]
+LIST_PONMSG = ["Full", "Message"]
+LIST_PTTID = ["Off", "BOT", "EOT", "Both"]
+LIST_SCODE = ["%s" % x for x in range(1, 16)]
+LIST_RPSTE = ["Off"] + ["%s" % x for x in range(1, 11)]
+LIST_RTONE = ["1000 Hz", "1450 Hz", "1750 Hz", "2100 Hz"]
+LIST_SAVE = ["Off", "1:1", "1:2", "1:3", "1:4"]
+LIST_SHIFTD = ["Off", "+", "-"]
+LIST_STEDELAY = ["Off"] + ["%s ms" % x for x in range(100, 1100, 100)]
+LIST_STEP = [str(x) for x in STEPS]
+LIST_TIMEOUT = ["%s sec" % x for x in range(15, 615, 15)]
+LIST_TXPOWER = ["High", "Low"]
+LIST_VOICE = ["Off", "English", "Chinese"]
+LIST_WORKMODE = ["Frequency", "Channel"]
+
+def model_match(cls, data):
+ """Match the opened/downloaded image to the correct version"""
+ rid = data[0x1EF0:0x1EF7]
+
+ if rid in cls._fileid:
+ return True
+
+ return False
+
+
+class MyRadioFeatures(chirp_common.RadioFeatures):
+ def validate_memory(self, mem):
+ # Run the normal validation
+ msgs = chirp_common.RadioFeatures.validate_memory(self, mem)
+
+ # Run my validation
+ if mem.number <= 6 and mem.mode != "NFM":
+ msgs.append(chirp_common.ValidationError(
+ 'Only NFM is supported on this channel'))
+
+ return msgs
+
+
+ at directory.register
+class GMRSV1(baofeng_common.BaofengCommonHT):
+ """BTech GMRS-V1"""
+ VENDOR = "BTECH"
+ MODEL = "GMRS-V1"
+
+ _fileid = [GMRSV1_fp1, ]
+
+ _magic = [MSTRING_GMRSV1, ]
+ _magic_response_length = 8
+ _fw_ver_start = 0x1EF0
+ _recv_block_size = 0x40
+ _mem_size = 0x2000
+ _ack_block = True
+
+ _ranges = [(0x0000, 0x0DF0),
+ (0x0E00, 0x1800),
+ (0x1EE0, 0x1EF0),
+ (0x1F60, 0x1F70),
+ (0x1F80, 0x1F90),
+ (0x1FC0, 0x1FD0)]
+ _send_block_size = 0x10
+
+ MODES = ["NFM", "FM"]
+ VALID_CHARS = chirp_common.CHARSET_ALPHANUMERIC + \
+ "!@#$%^&*()+-=[]:\";'<>?,./"
+ LENGTH_NAME = 7
+ SKIP_VALUES = ["", "S"]
+ DTCS_CODES = sorted(chirp_common.DTCS_CODES + [645])
+ POWER_LEVELS = [chirp_common.PowerLevel("High", watts=5.00),
+ chirp_common.PowerLevel("Low", watts=2.00)]
+ VALID_BANDS = [(130000000, 180000000),
+ (400000000, 521000000)]
+ PTTID_LIST = LIST_PTTID
+ SCODE_LIST = LIST_SCODE
+
+
+ def get_features(self):
+ """Get the radio's features"""
+
+ rf = MyRadioFeatures()
+ rf.has_settings = True
+ rf.has_bank = False
+ rf.has_tuning_step = False
+ rf.can_odd_split = False
+ rf.has_name = True
+ rf.has_offset = False
+ 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.valid_modes = self.MODES
+ rf.valid_characters = self.VALID_CHARS
+ rf.valid_name_length = self.LENGTH_NAME
+ rf.valid_duplexes = []
+ rf.valid_tmodes = ['', 'Tone', 'TSQL', 'DTCS', 'Cross']
+ rf.valid_cross_modes = [
+ "Tone->Tone",
+ "DTCS->",
+ "->DTCS",
+ "Tone->DTCS",
+ "DTCS->Tone",
+ "->Tone",
+ "DTCS->DTCS"]
+ rf.valid_skips = self.SKIP_VALUES
+ rf.valid_dtcs_codes = self.DTCS_CODES
+ rf.memory_bounds = (0, 127)
+ rf.valid_power_levels = self.POWER_LEVELS
+ rf.valid_bands = self.VALID_BANDS
+
+ return rf
+
+
+ MEM_FORMAT = """
+ #seekto 0x0000;
+ struct {
+ lbcd rxfreq[4];
+ lbcd txfreq[4];
+ ul16 rxtone;
+ ul16 txtone;
+ u8 unknown0:4,
+ scode:4;
+ u8 unknown1;
+ u8 unknown2:7,
+ lowpower:1;
+ u8 unknown3:1,
+ wide:1,
+ unknown4:2,
+ bcl:1,
+ scan:1,
+ pttid:2;
+ } memory[128];
+
+ #seekto 0x0B00;
+ struct {
+ u8 code[5];
+ u8 unused[11];
+ } pttid[15];
+
+ #seekto 0x0CAA;
+ struct {
+ u8 code[5];
+ u8 unused1:6,
+ aniid:2;
+ u8 unknown[2];
+ u8 dtmfon;
+ u8 dtmfoff;
+ } ani;
+
+ #seekto 0x0E20;
+ struct {
+ u8 unused01:4,
+ squelch:4;
+ u8 unused02;
+ u8 unused03;
+ u8 unused04:5,
+ save:3;
+ u8 unused05:4,
+ vox:4;
+ u8 unused06;
+ u8 unused07:4,
+ abr:4;
+ u8 unused08:7,
+ tdr:1;
+ u8 unused09:7,
+ beep:1;
+ u8 unused10:2,
+ timeout:6;
+ u8 unused11[4];
+ u8 unused12:6,
+ voice:2;
+ u8 unused13;
+ u8 unused14:6,
+ dtmfst:2;
+ u8 unused15;
+ u8 unused16:6,
+ screv:2;
+ u8 unused17:6,
+ pttid:2;
+ u8 unused18:2,
+ pttlt:6;
+ u8 unused19:6,
+ mdfa:2;
+ u8 unused20:6,
+ mdfb:2;
+ u8 unused21;
+ u8 unused22:7,
+ sync:1;
+ u8 unused23[4];
+ u8 unused24:6,
+ wtled:2;
+ u8 unused25:6,
+ rxled:2;
+ u8 unused26:6,
+ txled:2;
+ u8 unused27:6,
+ almod:2;
+ u8 unused28:7,
+ dbptt:1;
+ u8 unused29:6,
+ tdrab:2;
+ u8 unused30:7,
+ ste:1;
+ u8 unused31:4,
+ rpste:4;
+ u8 unused32:4,
+ rptrl:4;
+ u8 unused33:7,
+ ponmsg:1;
+ u8 unused34:7,
+ roger:1;
+ u8 unused35:6,
+ rtone:2;
+ u8 unused36;
+ u8 unused37:6,
+ rogerrx:2;
+ u8 unused38;
+ u8 displayab:1,
+ unknown1:2,
+ fmradio:1,
+ alarm:1,
+ unknown2:1,
+ reset:1,
+ menu:1;
+ u8 unused39;
+ u8 workmode;
+ u8 keylock;
+ u8 cht;
+ } settings;
+
+ #seekto 0x0E76;
+ struct {
+ u8 unused1:1,
+ mrcha:7;
+ u8 unused2:1,
+ mrchb:7;
+ } wmchannel;
+
+ struct vfo {
+ u8 unknown0[8];
+ u8 freq[8];
+ u8 unknown1;
+ u8 offset[4];
+ u8 unknown2;
+ ul16 rxtone;
+ ul16 txtone;
+ u8 unused1:7,
+ band:1;
+ u8 unknown3;
+ u8 unused2:2,
+ sftd:2,
+ scode:4;
+ u8 unknown4;
+ u8 unused3:1
+ step:3,
+ unused4:4;
+ u8 txpower:1,
+ widenarr:1,
+ unknown5:4,
+ txpower3:2;
+ };
+
+ #seekto 0x0F00;
+ struct {
+ struct vfo a;
+ struct vfo b;
+ } vfo;
+
+ #seekto 0x0F4E;
+ u16 fm_presets;
+
+ #seekto 0x1000;
+ struct {
+ char name[7];
+ u8 unknown1[9];
+ } names[128];
+
+ #seekto 0x1ED0;
+ struct {
+ char line1[7];
+ char line2[7];
+ } sixpoweron_msg;
+
+ #seekto 0x1EE0;
+ struct {
+ char line1[7];
+ char line2[7];
+ } poweron_msg;
+
+ #seekto 0x1EF0;
+ struct {
+ char line1[7];
+ char line2[7];
+ } firmware_msg;
+
+ struct squelch {
+ u8 sql0;
+ u8 sql1;
+ u8 sql2;
+ u8 sql3;
+ u8 sql4;
+ u8 sql5;
+ u8 sql6;
+ u8 sql7;
+ u8 sql8;
+ u8 sql9;
+ };
+
+ #seekto 0x1F60;
+ struct {
+ struct squelch vhf;
+ u8 unknown1[6];
+ u8 unknown2[16];
+ struct squelch uhf;
+ } squelch;
+
+ """
+
+ @classmethod
+ def get_prompts(cls):
+ rp = chirp_common.RadioPrompts()
+ rp.experimental = \
+ ('The BTech GMRS-V1 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 these 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 process_mmap(self):
+ """Process the mem map into the mem object"""
+ self._memobj = bitwise.parse(self.MEM_FORMAT, self._mmap)
+
+ def get_settings(self):
+ """Translate the bit in the mem_struct into settings in the UI"""
+ _mem = self._memobj
+ basic = RadioSettingGroup("basic", "Basic Settings")
+ advanced = RadioSettingGroup("advanced", "Advanced Settings")
+ other = RadioSettingGroup("other", "Other Settings")
+ work = RadioSettingGroup("work", "Work Mode Settings")
+ fm_preset = RadioSettingGroup("fm_preset", "FM Preset")
+ dtmfe = RadioSettingGroup("dtmfe", "DTMF Encode Settings")
+ service = RadioSettingGroup("service", "Service Settings")
+ top = RadioSettings(basic, advanced, other, work, fm_preset, dtmfe,
+ service)
+
+ # Basic settings
+ if _mem.settings.squelch > 0x09:
+ val = 0x00
+ else:
+ val = _mem.settings.squelch
+ rs = RadioSetting("settings.squelch", "Squelch",
+ RadioSettingValueList(
+ LIST_OFF1TO9, LIST_OFF1TO9[val]))
+ basic.append(rs)
+
+ if _mem.settings.save > 0x04:
+ val = 0x00
+ else:
+ val = _mem.settings.save
+ rs = RadioSetting("settings.save", "Battery Saver",
+ RadioSettingValueList(
+ LIST_SAVE, LIST_SAVE[val]))
+ basic.append(rs)
+
+ if _mem.settings.vox > 0x0A:
+ val = 0x00
+ else:
+ val = _mem.settings.vox
+ rs = RadioSetting("settings.vox", "Vox",
+ RadioSettingValueList(
+ LIST_OFF1TO10, LIST_OFF1TO10[val]))
+ basic.append(rs)
+
+ if _mem.settings.abr > 0x0A:
+ val = 0x00
+ else:
+ val = _mem.settings.abr
+ rs = RadioSetting("settings.abr", "Backlight Timeout",
+ RadioSettingValueList(
+ LIST_OFF1TO10, LIST_OFF1TO10[val]))
+ basic.append(rs)
+
+ rs = RadioSetting("settings.tdr", "Dual Watch",
+ RadioSettingValueBoolean(_mem.settings.tdr))
+ basic.append(rs)
+
+ rs = RadioSetting("settings.beep", "Beep",
+ RadioSettingValueBoolean(_mem.settings.beep))
+ basic.append(rs)
+
+ if _mem.settings.timeout > 0x27:
+ val = 0x03
+ else:
+ val = _mem.settings.timeout
+ rs = RadioSetting("settings.timeout", "Timeout Timer",
+ RadioSettingValueList(
+ LIST_TIMEOUT, LIST_TIMEOUT[val]))
+ basic.append(rs)
+
+ if _mem.settings.voice > 0x02:
+ val = 0x01
+ else:
+ val = _mem.settings.voice
+ rs = RadioSetting("settings.voice", "Voice Prompt",
+ RadioSettingValueList(
+ LIST_VOICE, LIST_VOICE[val]))
+ basic.append(rs)
+
+ rs = RadioSetting("settings.dtmfst", "DTMF Sidetone",
+ RadioSettingValueList(LIST_DTMFST, LIST_DTMFST[
+ _mem.settings.dtmfst]))
+ basic.append(rs)
+
+ if _mem.settings.screv > 0x02:
+ val = 0x01
+ else:
+ val = _mem.settings.screv
+ rs = RadioSetting("settings.screv", "Scan Resume",
+ RadioSettingValueList(
+ LIST_RESUME, LIST_RESUME[val]))
+ basic.append(rs)
+
+ rs = RadioSetting("settings.pttid", "When to send PTT ID",
+ RadioSettingValueList(LIST_PTTID, LIST_PTTID[
+ _mem.settings.pttid]))
+ basic.append(rs)
+
+ if _mem.settings.pttlt > 0x1E:
+ val = 0x05
+ else:
+ val = _mem.settings.pttlt
+ rs = RadioSetting("pttlt", "PTT ID Delay",
+ RadioSettingValueInteger(0, 50, val))
+ basic.append(rs)
+
+ rs = RadioSetting("settings.mdfa", "Display Mode (A)",
+ RadioSettingValueList(LIST_MODE, LIST_MODE[
+ _mem.settings.mdfa]))
+ basic.append(rs)
+
+ rs = RadioSetting("settings.mdfb", "Display Mode (B)",
+ RadioSettingValueList(LIST_MODE, LIST_MODE[
+ _mem.settings.mdfb]))
+ basic.append(rs)
+
+ rs = RadioSetting("settings.sync", "Sync A & B",
+ RadioSettingValueBoolean(_mem.settings.sync))
+ basic.append(rs)
+
+ rs = RadioSetting("settings.wtled", "Standby LED Color",
+ RadioSettingValueList(
+ LIST_COLOR, LIST_COLOR[_mem.settings.wtled]))
+ basic.append(rs)
+
+ rs = RadioSetting("settings.rxled", "RX LED Color",
+ RadioSettingValueList(
+ LIST_COLOR, LIST_COLOR[_mem.settings.rxled]))
+ basic.append(rs)
+
+ rs = RadioSetting("settings.txled", "TX LED Color",
+ RadioSettingValueList(
+ LIST_COLOR, LIST_COLOR[_mem.settings.txled]))
+ basic.append(rs)
+
+ val = _mem.settings.almod
+ rs = RadioSetting("settings.almod", "Alarm Mode",
+ RadioSettingValueList(
+ LIST_ALMOD, LIST_ALMOD[val]))
+ basic.append(rs)
+
+ rs = RadioSetting("settings.dbptt", "Double PTT",
+ RadioSettingValueBoolean(_mem.settings.dbptt))
+ basic.append(rs)
+
+ if _mem.settings.tdrab > 0x02:
+ val = 0x00
+ else:
+ val = _mem.settings.tdrab
+ rs = RadioSetting("settings.tdrab", "Dual Watch TX Priority",
+ RadioSettingValueList(
+ LIST_OFFAB, LIST_OFFAB[val]))
+ basic.append(rs)
+
+ rs = RadioSetting("settings.ste", "Squelch Tail Eliminate (HT to HT)",
+ RadioSettingValueBoolean(_mem.settings.ste))
+ basic.append(rs)
+
+ if _mem.settings.rpste > 0x0A:
+ val = 0x00
+ else:
+ val = _mem.settings.rpste
+ rs = RadioSetting("settings.rpste",
+ "Squelch Tail Eliminate (repeater)",
+ RadioSettingValueList(
+ LIST_RPSTE, LIST_RPSTE[val]))
+ basic.append(rs)
+
+ if _mem.settings.rptrl > 0x0A:
+ val = 0x00
+ else:
+ val = _mem.settings.rptrl
+ rs = RadioSetting("settings.rptrl", "STE Repeater Delay",
+ RadioSettingValueList(
+ LIST_STEDELAY, LIST_STEDELAY[val]))
+ basic.append(rs)
+
+ rs = RadioSetting("settings.ponmsg", "Power-On Message",
+ RadioSettingValueList(LIST_PONMSG, LIST_PONMSG[
+ _mem.settings.ponmsg]))
+ basic.append(rs)
+
+ rs = RadioSetting("settings.roger", "Roger Beep",
+ RadioSettingValueBoolean(_mem.settings.roger))
+ basic.append(rs)
+
+ rs = RadioSetting("settings.rtone", "Tone Burst Frequency",
+ RadioSettingValueList(LIST_RTONE, LIST_RTONE[
+ _mem.settings.rtone]))
+ basic.append(rs)
+
+ rs = RadioSetting("settings.rogerrx", "Roger Beep (RX)",
+ RadioSettingValueList(
+ LIST_OFFAB, LIST_OFFAB[
+ _mem.settings.rogerrx]))
+ basic.append(rs)
+
+ # Advanced settings
+ rs = RadioSetting("settings.reset", "RESET Menu",
+ RadioSettingValueBoolean(_mem.settings.reset))
+ advanced.append(rs)
+
+ rs = RadioSetting("settings.menu", "All Menus",
+ RadioSettingValueBoolean(_mem.settings.menu))
+ advanced.append(rs)
+
+ rs = RadioSetting("settings.fmradio", "Broadcast FM Radio",
+ RadioSettingValueBoolean(_mem.settings.fmradio))
+ advanced.append(rs)
+
+ rs = RadioSetting("settings.alarm", "Alarm Sound",
+ RadioSettingValueBoolean(_mem.settings.alarm))
+ advanced.append(rs)
+
+ # Other settings
+ def _filter(name):
+ filtered = ""
+ for char in str(name):
+ if char in chirp_common.CHARSET_ASCII:
+ filtered += char
+ else:
+ filtered += " "
+ return filtered
+
+ _msg = _mem.firmware_msg
+ val = RadioSettingValueString(0, 7, _filter(_msg.line1))
+ val.set_mutable(False)
+ rs = RadioSetting("firmware_msg.line1", "Firmware Message 1", val)
+ other.append(rs)
+
+ val = RadioSettingValueString(0, 7, _filter(_msg.line2))
+ val.set_mutable(False)
+ rs = RadioSetting("firmware_msg.line2", "Firmware Message 2", val)
+ other.append(rs)
+
+ _msg = _mem.sixpoweron_msg
+ val = RadioSettingValueString(0, 7, _filter(_msg.line1))
+ val.set_mutable(False)
+ rs = RadioSetting("sixpoweron_msg.line1", "6+Power-On Message 1", val)
+ other.append(rs)
+ val = RadioSettingValueString(0, 7, _filter(_msg.line2))
+ val.set_mutable(False)
+ rs = RadioSetting("sixpoweron_msg.line2", "6+Power-On Message 2", val)
+ other.append(rs)
+
+ _msg = _mem.poweron_msg
+ rs = RadioSetting("poweron_msg.line1", "Power-On Message 1",
+ RadioSettingValueString(
+ 0, 7, _filter(_msg.line1)))
+ other.append(rs)
+ rs = RadioSetting("poweron_msg.line2", "Power-On Message 2",
+ RadioSettingValueString(
+ 0, 7, _filter(_msg.line2)))
+ other.append(rs)
+
+ # Work mode settings
+ rs = RadioSetting("settings.displayab", "Display",
+ RadioSettingValueList(
+ LIST_AB, LIST_AB[_mem.settings.displayab]))
+ work.append(rs)
+
+ rs = RadioSetting("settings.workmode", "VFO/MR Mode",
+ RadioSettingValueList(
+ LIST_WORKMODE,
+ LIST_WORKMODE[_mem.settings.workmode]))
+ work.append(rs)
+
+ rs = RadioSetting("settings.keylock", "Keypad Lock",
+ RadioSettingValueBoolean(_mem.settings.keylock))
+ work.append(rs)
+
+ rs = RadioSetting("wmchannel.mrcha", "MR A Channel",
+ RadioSettingValueInteger(0, 127,
+ _mem.wmchannel.mrcha))
+ work.append(rs)
+
+ rs = RadioSetting("wmchannel.mrchb", "MR B Channel",
+ RadioSettingValueInteger(0, 127,
+ _mem.wmchannel.mrchb))
+ work.append(rs)
+
+ def convert_bytes_to_freq(bytes):
+ real_freq = 0
+ for byte in bytes:
+ real_freq = (real_freq * 10) + byte
+ return chirp_common.format_freq(real_freq * 10)
+
+ def my_validate(value):
+ value = chirp_common.parse_freq(value)
+ msg = ("Can't be less than %i.0000")
+ if value > 99000000 and value < 130 * 1000000:
+ raise InvalidValueError(msg % (130))
+ msg = ("Can't be between %i.9975-%i.0000")
+ if (179 + 1) * 1000000 <= value and value < 400 * 1000000:
+ raise InvalidValueError(msg % (179, 400))
+ msg = ("Can't be greater than %i.9975")
+ if value > 99000000 and value > (520 + 1) * 1000000:
+ raise InvalidValueError(msg % (520))
+ return chirp_common.format_freq(value)
+
+ def apply_freq(setting, obj):
+ value = chirp_common.parse_freq(str(setting.value)) / 10
+ for i in range(7, -1, -1):
+ obj.freq[i] = value % 10
+ value /= 10
+
+ val1a = RadioSettingValueString(0, 10,
+ convert_bytes_to_freq(_mem.vfo.a.freq))
+ val1a.set_validate_callback(my_validate)
+ rs = RadioSetting("vfo.a.freq", "VFO A Frequency", val1a)
+ rs.set_apply_callback(apply_freq, _mem.vfo.a)
+ work.append(rs)
+
+ val1b = RadioSettingValueString(0, 10,
+ convert_bytes_to_freq(_mem.vfo.b.freq))
+ val1b.set_validate_callback(my_validate)
+ rs = RadioSetting("vfo.b.freq", "VFO B Frequency", val1b)
+ rs.set_apply_callback(apply_freq, _mem.vfo.b)
+ work.append(rs)
+
+ rs = RadioSetting("vfo.a.step", "VFO A Tuning Step",
+ RadioSettingValueList(
+ LIST_STEP, LIST_STEP[_mem.vfo.a.step]))
+ work.append(rs)
+ rs = RadioSetting("vfo.b.step", "VFO B Tuning Step",
+ RadioSettingValueList(
+ LIST_STEP, LIST_STEP[_mem.vfo.b.step]))
+ work.append(rs)
+
+ # broadcast FM settings
+ _fm_presets = self._memobj.fm_presets
+ if _fm_presets <= 108.0 * 10 - 650:
+ preset = _fm_presets / 10.0 + 65
+ elif _fm_presets >= 65.0 * 10 and _fm_presets <= 108.0 * 10:
+ preset = _fm_presets / 10.0
+ else:
+ preset = 76.0
+ rs = RadioSetting("fm_presets", "FM Preset(MHz)",
+ RadioSettingValueFloat(65, 108.0, preset, 0.1, 1))
+ fm_preset.append(rs)
+
+ # DTMF settings
+ def apply_code(setting, obj, length):
+ code = []
+ for j in range(0, length):
+ try:
+ code.append(DTMF_CHARS.index(str(setting.value)[j]))
+ except IndexError:
+ code.append(0xFF)
+ obj.code = code
+
+ for i in range(0, 15):
+ _codeobj = self._memobj.pttid[i].code
+ _code = "".join([DTMF_CHARS[x] for x in _codeobj if int(x) < 0x1F])
+ val = RadioSettingValueString(0, 5, _code, False)
+ val.set_charset(DTMF_CHARS)
+ pttid = RadioSetting("pttid/%i.code" % i,
+ "Signal Code %i" % (i + 1), val)
+ pttid.set_apply_callback(apply_code, self._memobj.pttid[i], 5)
+ dtmfe.append(pttid)
+
+ if _mem.ani.dtmfon > 0xC3:
+ val = 0x03
+ else:
+ val = _mem.ani.dtmfon
+ rs = RadioSetting("ani.dtmfon", "DTMF Speed (on)",
+ RadioSettingValueList(LIST_DTMFSPEED,
+ LIST_DTMFSPEED[val]))
+ dtmfe.append(rs)
+
+ if _mem.ani.dtmfoff > 0xC3:
+ val = 0x03
+ else:
+ val = _mem.ani.dtmfoff
+ rs = RadioSetting("ani.dtmfoff", "DTMF Speed (off)",
+ RadioSettingValueList(LIST_DTMFSPEED,
+ LIST_DTMFSPEED[val]))
+ dtmfe.append(rs)
+
+ _codeobj = self._memobj.ani.code
+ _code = "".join([DTMF_CHARS[x] for x in _codeobj if int(x) < 0x1F])
+ val = RadioSettingValueString(0, 5, _code, False)
+ val.set_charset(DTMF_CHARS)
+ rs = RadioSetting("ani.code", "ANI Code", val)
+ rs.set_apply_callback(apply_code, self._memobj.ani, 5)
+ dtmfe.append(rs)
+
+ rs = RadioSetting("ani.aniid", "When to send ANI ID",
+ RadioSettingValueList(LIST_PTTID,
+ LIST_PTTID[_mem.ani.aniid]))
+ dtmfe.append(rs)
+
+ # Service settings
+ for band in ["vhf", "uhf"]:
+ for index in range(0, 10):
+ key = "squelch.%s.sql%i" % (band, index)
+ if band == "vhf":
+ _obj = self._memobj.squelch.vhf
+ elif band == "uhf":
+ _obj = self._memobj.squelch.uhf
+ val = RadioSettingValueInteger(0, 123,
+ getattr(_obj, "sql%i" % (index)))
+ if index == 0:
+ val.set_mutable(False)
+ name = "%s Squelch %i" % (band.upper(), index)
+ rs = RadioSetting(key, name, val)
+ service.append(rs)
+
+ return top
+
+ @classmethod
+ def match_model(cls, filedata, filename):
+ match_size = False
+ match_model = False
+
+ # testing the file data size
+ if len(filedata) == 0x2008:
+ 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
diff --git a/chirp/drivers/h777.py b/chirp/drivers/h777.py
index d48d34c..48c657f 100644
--- a/chirp/drivers/h777.py
+++ b/chirp/drivers/h777.py
@@ -91,7 +91,7 @@ SCANMODE_LIST = ["Carrier", "Time"]
SETTING_LISTS = {
"voice": VOICE_LIST,
- }
+}
def _h777_enter_programming_mode(radio):
@@ -250,7 +250,7 @@ class H777Radio(chirp_common.CloneModeRadio):
(0x0000, 0x0110),
(0x02B0, 0x02C0),
(0x0380, 0x03E0),
- ]
+ ]
_memsize = 0x03E0
def get_features(self):
@@ -259,6 +259,8 @@ class H777Radio(chirp_common.CloneModeRadio):
rf.valid_modes = ["NFM", "FM"] # 12.5 KHz, 25 kHz.
rf.valid_skips = ["", "S"]
rf.valid_tmodes = ["", "Tone", "TSQL", "DTCS", "Cross"]
+ rf.valid_duplexes = ["", "-", "+", "split", "off"]
+ rf.can_odd_split = True
rf.has_rx_dtcs = True
rf.has_ctone = True
rf.has_cross = True
@@ -334,7 +336,10 @@ class H777Radio(chirp_common.CloneModeRadio):
mem.empty = True
return mem
- if int(_mem.rxfreq) == int(_mem.txfreq):
+ if _mem.txfreq.get_raw() == "\xFF\xFF\xFF\xFF":
+ mem.duplex = "off"
+ mem.offset = 0
+ elif int(_mem.rxfreq) == int(_mem.txfreq):
mem.duplex = ""
mem.offset = 0
else:
@@ -394,6 +399,13 @@ class H777Radio(chirp_common.CloneModeRadio):
# NOTE: Only two settings right now, both are inverted
setattr(_mem, setting.get_name(), not int(setting.value))
+ # When set to one, official programming software (BF-480) shows always
+ # "WFM", even if we choose "NFM". Therefore, for compatibility
+ # purposes, we will set these to zero.
+ _mem.unknown1 = 0
+ _mem.unknown2 = 0
+ _mem.unknown3 = 0
+
def get_settings(self):
_settings = self._memobj.settings
basic = RadioSettingGroup("basic", "Basic Settings")
@@ -515,6 +527,7 @@ class H777Radio(chirp_common.CloneModeRadio):
class H777TestCase(unittest.TestCase):
+
def setUp(self):
self.driver = H777Radio(None)
self.testdata = bitwise.parse("lbcd foo[2];",
diff --git a/chirp/drivers/icomciv.py b/chirp/drivers/icomciv.py
index 8d128ce..24fb297 100644
--- a/chirp/drivers/icomciv.py
+++ b/chirp/drivers/icomciv.py
@@ -51,6 +51,35 @@ u8 dtcs_polarity_tx;
bbcd dtcs_tx[2];
char name[9];
"""
+
+MEM_IC7100_FORMAT = """
+u8 bank; // 1 bank number
+bbcd number[2]; // 2,3
+u8 splitSelect; // 4 split and select memory settings
+lbcd freq[5]; // 5-9 operating freq
+u8 mode; // 10 operating mode
+u8 filter; // 11 filter
+u8 dataMode; // 12 data mode setting (on or off)
+u8 duplex:4, // 13 duplex on/-/+
+ tmode:4; // 13 tone
+u8 dsql:4, // 14 digital squelch
+ unknown1:4; // 14 zero
+bbcd rtone[3]; // 15-17 repeater tone freq
+bbcd ctone[3]; // 18-20 tone squelch setting
+u8 dtcsPolarity; // 21 DTCS polarity
+u8 unknown2:4, // 22 zero
+ firstDtcs:4; // 22 first digit of DTCS code
+u8 secondDtcs:4, // 23 second digit DTCS
+ thirdDtcs:4; // 23 third digit DTCS
+u8 digitalSquelch; // 24 Digital code squelch setting
+u8 duplexOffset[3]; // 25-27 duplex offset freq
+char destCall[8]; // 28-35 destination call sign
+char accessRepeaterCall[8];// 36-43 access repeater call sign
+char linkRepeaterCall[8]; // 44-51 gateway/link repeater call sign
+bbcd duplexSettings[47]; // repeat of 5-51 for duplex
+char name[16]; // 52-60 Name of station
+"""
+
mem_duptone_format = """
bbcd number[2];
u8 unknown1;
@@ -169,6 +198,7 @@ class MemFrame(Frame):
class BankMemFrame(MemFrame):
"""A memory frame for radios with multiple banks"""
+ FORMAT = MEM_IC7000_FORMAT
_bnk = 0
def set_location(self, loc, bank=1):
@@ -185,7 +215,11 @@ class BankMemFrame(MemFrame):
def get_obj(self):
self._data = MemoryMap(str(self._data)) # Make sure we're assignable
- return bitwise.parse(MEM_IC7000_FORMAT, self._data)
+ return bitwise.parse(self.FORMAT, self._data)
+
+
+class IC7100MemFrame(BankMemFrame):
+ FORMAT = MEM_IC7100_FORMAT
class DupToneMemFrame(MemFrame):
@@ -204,8 +238,12 @@ class IcomCIVRadio(icf.IcomLiveRadio):
# complete list of modes from CI-V documentation
# each radio supports a subset
# WARNING: "S-AM" and "PSK" are not valid (yet) for chirp
- _MODES = ["LSB", "USB", "AM", "CW", "RTTY",
- "FM", "WFM", "CWR", "RTTYR", "S-AM", "PSK"]
+ _MODES = [
+ "LSB", "USB", "AM", "CW", "RTTY", "FM", "WFM", "CWR"
+ "RTTYR", "S-AM", "PSK", None, None, None, None, None,
+ None, None, None, None, None, None, None, None,
+ "DV",
+ ]
def mem_to_ch_bnk(self, mem):
l, h = self._bank_index_bounds
@@ -332,7 +370,24 @@ class IcomCIVRadio(icf.IcomLiveRadio):
pass
mem.freq = int(memobj.freq)
- mem.mode = self._MODES[memobj.mode]
+ try:
+ mem.mode = self._MODES[memobj.mode]
+
+ # We do not know what a variety of the positions between
+ # PSK and DV mean, so let's behave as if those values
+ # are not set to maintain consistency between known-unknown
+ # values and unknown-unknown ones.
+ if mem.mode is None:
+ raise IndexError(memobj.mode)
+ except IndexError:
+ LOG.error(
+ "Bank %s location %s is set for mode %s, but no known "
+ "mode matches that value.",
+ int(memobj.bank),
+ int(memobj.number),
+ repr(memobj.mode),
+ )
+ raise
if self._rf.has_name:
mem.name = str(memobj.name).rstrip()
@@ -435,7 +490,8 @@ class IcomCIVRadio(icf.IcomLiveRadio):
memobj.freq = int(mem.freq)
memobj.mode = self._MODES.index(mem.mode)
if self._rf.has_name:
- memobj.name = mem.name.ljust(9)[:9]
+ name_length = len(memobj.name.get_value())
+ memobj.name = mem.name.ljust(name_length)[:name_length]
if self._rf.valid_tmodes:
memobj.tmode = self._rf.valid_tmodes.index(mem.tmode)
@@ -541,6 +597,41 @@ class Icom7000Radio(IcomCIVRadio):
@directory.register
+class Icom7100Radio(IcomCIVRadio):
+ """Icom IC-7100"""
+ MODEL = "IC-7100"
+ _model = "\x88"
+ _template = 102
+
+ _num_banks = 5
+ _bank_index_bounds = (1, 99)
+ _bank_class = icf.IcomBank
+
+ def _initialize(self):
+ self._classes["mem"] = IC7100MemFrame
+ self._rf.has_bank = True
+ self._rf.has_bank_index = False
+ self._rf.has_bank_names = False
+ self._rf.has_dtcs_polarity = False
+ self._rf.has_dtcs = False
+ self._rf.has_ctone = True
+ self._rf.has_offset = False
+ self._rf.has_name = True
+ self._rf.has_tuning_step = False
+ self._rf.valid_modes = [
+ "LSB", "USB", "AM", "CW", "RTTY", "FM", "WFM", "CWR", "RTTYR", "DV"
+ ]
+ self._rf.valid_tmodes = ["", "Tone", "TSQL", "DTCS"]
+ self._rf.valid_duplexes = ["", "-", "+"]
+ self._rf.valid_bands = [(30000, 199999999), (400000000, 470000000)]
+ self._rf.valid_tuning_steps = []
+ self._rf.valid_skips = []
+ self._rf.valid_name_length = 16
+ self._rf.valid_characters = chirp_common.CHARSET_ASCII
+ self._rf.memory_bounds = (0, 99 * self._num_banks - 1)
+
+
+ at directory.register
class Icom746Radio(IcomCIVRadio):
"""Icom IC-746"""
MODEL = "746"
@@ -571,6 +662,7 @@ class Icom746Radio(IcomCIVRadio):
CIV_MODELS = {
(0x76, 0xE0): Icom7200Radio,
+ (0x88, 0xE0): Icom7100Radio,
(0x70, 0xE0): Icom7000Radio,
(0x46, 0xE0): Icom746Radio,
}
diff --git a/chirp/drivers/kguv8d.py b/chirp/drivers/kguv8d.py
index 785d2f5..a119c86 100644
--- a/chirp/drivers/kguv8d.py
+++ b/chirp/drivers/kguv8d.py
@@ -20,9 +20,9 @@ import os
import logging
from chirp import util, chirp_common, bitwise, memmap, errors, directory
from chirp.settings import RadioSetting, RadioSettingGroup, \
- RadioSettingValueBoolean, RadioSettingValueList, \
- RadioSettingValueInteger, RadioSettingValueString, \
- RadioSettings
+ RadioSettingValueBoolean, RadioSettingValueList, \
+ RadioSettingValueInteger, RadioSettingValueString, \
+ RadioSettings
LOG = logging.getLogger(__name__)
@@ -47,7 +47,7 @@ PF1KEY_LIST = ["Call", "VFTX"]
PF3KEY_LIST = ["Scan", "Lamp", "Tele Alarm", "SOS-CH", "Radio", "Disable"]
WORKMODE_LIST = ["VFO", "Channel No.", "Ch. No.+Freq.", "Ch. No.+Name"]
BACKLIGHT_LIST = ["Always On"] + [str(x) + "s" for x in range(1, 21)] + \
- ["Always Off"]
+ ["Always Off"]
OFFSET_LIST = ["+", "-"]
PONMSG_LIST = ["Bitmap", "Battery Volts"]
SPMUTE_LIST = ["QT", "QT+DTMF", "QT*DTMF"]
@@ -464,14 +464,14 @@ class KGUV8DRadio(chirp_common.CloneModeRadio,
rf.valid_skips = ["", "S"]
rf.valid_tmodes = ["", "Tone", "TSQL", "DTCS", "Cross"]
rf.valid_cross_modes = [
- "Tone->Tone",
- "Tone->DTCS",
- "DTCS->Tone",
- "DTCS->",
- "->Tone",
- "->DTCS",
- "DTCS->DTCS",
- ]
+ "Tone->Tone",
+ "Tone->DTCS",
+ "DTCS->Tone",
+ "DTCS->",
+ "->Tone",
+ "->DTCS",
+ "DTCS->DTCS",
+ ]
rf.valid_modes = ["FM", "NFM"]
rf.valid_power_levels = self.POWER_LEVELS
rf.valid_name_length = 8
@@ -681,117 +681,117 @@ class KGUV8DRadio(chirp_common.CloneModeRadio,
RadioSettingValueBoolean(_settings.channel_menu))
cfg_grp.append(rs)
rs = RadioSetting("ponmsg", "Poweron message",
- RadioSettingValueList(
- PONMSG_LIST, PONMSG_LIST[_settings.ponmsg]))
+ RadioSettingValueList(
+ PONMSG_LIST, PONMSG_LIST[_settings.ponmsg]))
cfg_grp.append(rs)
rs = RadioSetting("voice", "Voice Guide",
- RadioSettingValueBoolean(_settings.voice))
+ RadioSettingValueBoolean(_settings.voice))
cfg_grp.append(rs)
rs = RadioSetting("language", "Language",
- RadioSettingValueList(LANGUAGE_LIST,
- LANGUAGE_LIST[_settings.language]))
+ RadioSettingValueList(LANGUAGE_LIST,
+ LANGUAGE_LIST[_settings.language]))
cfg_grp.append(rs)
rs = RadioSetting("timeout", "Timeout Timer",
- RadioSettingValueInteger(15, 900,
- _settings.timeout * 15, 15))
+ RadioSettingValueInteger(15, 900,
+ _settings.timeout * 15, 15))
cfg_grp.append(rs)
rs = RadioSetting("toalarm", "Timeout Alarm",
- RadioSettingValueInteger(0, 10, _settings.toalarm))
+ RadioSettingValueInteger(0, 10, _settings.toalarm))
cfg_grp.append(rs)
rs = RadioSetting("roger_beep", "Roger Beep",
- RadioSettingValueBoolean(_settings.roger_beep))
+ RadioSettingValueBoolean(_settings.roger_beep))
cfg_grp.append(rs)
rs = RadioSetting("power_save", "Power save",
- RadioSettingValueBoolean(_settings.power_save))
+ RadioSettingValueBoolean(_settings.power_save))
cfg_grp.append(rs)
rs = RadioSetting("autolock", "Autolock",
- RadioSettingValueBoolean(_settings.autolock))
+ RadioSettingValueBoolean(_settings.autolock))
cfg_grp.append(rs)
rs = RadioSetting("keylock", "Keypad Lock",
- RadioSettingValueBoolean(_settings.keylock))
+ RadioSettingValueBoolean(_settings.keylock))
cfg_grp.append(rs)
rs = RadioSetting("beep", "Keypad Beep",
- RadioSettingValueBoolean(_settings.beep))
+ RadioSettingValueBoolean(_settings.beep))
cfg_grp.append(rs)
rs = RadioSetting("stopwatch", "Stopwatch",
- RadioSettingValueBoolean(_settings.stopwatch))
+ RadioSettingValueBoolean(_settings.stopwatch))
cfg_grp.append(rs)
rs = RadioSetting("backlight", "Backlight",
- RadioSettingValueList(BACKLIGHT_LIST,
- BACKLIGHT_LIST[_settings.backlight]))
+ RadioSettingValueList(BACKLIGHT_LIST,
+ BACKLIGHT_LIST[_settings.backlight]))
cfg_grp.append(rs)
rs = RadioSetting("dtmf_st", "DTMF Sidetone",
- RadioSettingValueList(DTMFST_LIST,
- DTMFST_LIST[_settings.dtmf_st]))
+ RadioSettingValueList(DTMFST_LIST,
+ DTMFST_LIST[_settings.dtmf_st]))
cfg_grp.append(rs)
rs = RadioSetting("ani-id_sw", "ANI-ID Switch",
- RadioSettingValueBoolean(_settings.ani_sw))
+ RadioSettingValueBoolean(_settings.ani_sw))
cfg_grp.append(rs)
rs = RadioSetting("ptt-id_delay", "PTT-ID Delay",
- RadioSettingValueList(PTTID_LIST,
- PTTID_LIST[_settings.ptt_id]))
+ RadioSettingValueList(PTTID_LIST,
+ PTTID_LIST[_settings.ptt_id]))
cfg_grp.append(rs)
rs = RadioSetting("ring_time", "Ring Time",
- RadioSettingValueList(LIST_10,
- LIST_10[_settings.ring_time]))
+ RadioSettingValueList(LIST_10,
+ LIST_10[_settings.ring_time]))
cfg_grp.append(rs)
rs = RadioSetting("scan_rev", "Scan Mode",
- RadioSettingValueList(SCANMODE_LIST,
- SCANMODE_LIST[_settings.scan_rev]))
+ RadioSettingValueList(SCANMODE_LIST,
+ SCANMODE_LIST[_settings.scan_rev]))
cfg_grp.append(rs)
rs = RadioSetting("vox", "VOX",
- RadioSettingValueList(LIST_10,
- LIST_10[_settings.vox]))
+ RadioSettingValueList(LIST_10,
+ LIST_10[_settings.vox]))
cfg_grp.append(rs)
rs = RadioSetting("prich_sw", "Priority Channel Switch",
- RadioSettingValueBoolean(_settings.prich_sw))
+ RadioSettingValueBoolean(_settings.prich_sw))
cfg_grp.append(rs)
rs = RadioSetting("pri_ch", "Priority Channel",
- RadioSettingValueInteger(1, 999, _settings.pri_ch))
+ RadioSettingValueInteger(1, 999, _settings.pri_ch))
cfg_grp.append(rs)
rs = RadioSetting("rpt_mode", "Radio Mode",
- RadioSettingValueList(RPTMODE_LIST,
- RPTMODE_LIST[_settings.rpt_mode]))
+ RadioSettingValueList(RPTMODE_LIST,
+ RPTMODE_LIST[_settings.rpt_mode]))
cfg_grp.append(rs)
rs = RadioSetting("rpt_set", "Repeater Setting",
- RadioSettingValueList(RPTSET_LIST,
- RPTSET_LIST[_settings.rpt_set]))
+ RadioSettingValueList(RPTSET_LIST,
+ RPTSET_LIST[_settings.rpt_set]))
cfg_grp.append(rs)
rs = RadioSetting("rpt_spk", "Repeater Mode Speaker",
- RadioSettingValueBoolean(_settings.rpt_spk))
+ RadioSettingValueBoolean(_settings.rpt_spk))
cfg_grp.append(rs)
rs = RadioSetting("rpt_ptt", "Repeater PTT",
- RadioSettingValueBoolean(_settings.rpt_ptt))
+ RadioSettingValueBoolean(_settings.rpt_ptt))
cfg_grp.append(rs)
rs = RadioSetting("dtmf_tx_time", "DTMF Tx Duration",
- RadioSettingValueList(DTMF_TIMES,
- DTMF_TIMES[_settings.dtmf_tx_time]))
+ RadioSettingValueList(DTMF_TIMES,
+ DTMF_TIMES[_settings.dtmf_tx_time]))
cfg_grp.append(rs)
rs = RadioSetting("dtmf_interval", "DTMF Interval",
- RadioSettingValueList(DTMF_TIMES,
- DTMF_TIMES[_settings.dtmf_interval]))
+ RadioSettingValueList(DTMF_TIMES,
+ DTMF_TIMES[_settings.dtmf_interval]))
cfg_grp.append(rs)
rs = RadioSetting("alert", "Alert Tone",
- RadioSettingValueList(ALERTS_LIST,
- ALERTS_LIST[_settings.alert]))
+ RadioSettingValueList(ALERTS_LIST,
+ ALERTS_LIST[_settings.alert]))
cfg_grp.append(rs)
rs = RadioSetting("rpt_tone", "Repeater Tone",
- RadioSettingValueBoolean(_settings.rpt_tone))
+ RadioSettingValueBoolean(_settings.rpt_tone))
cfg_grp.append(rs)
rs = RadioSetting("rpt_hold", "Repeater Hold Time",
- RadioSettingValueList(HOLD_TIMES,
- HOLD_TIMES[_settings.rpt_hold]))
+ RadioSettingValueList(HOLD_TIMES,
+ HOLD_TIMES[_settings.rpt_hold]))
cfg_grp.append(rs)
rs = RadioSetting("scan_det", "Scan DET",
- RadioSettingValueBoolean(_settings.scan_det))
+ RadioSettingValueBoolean(_settings.scan_det))
cfg_grp.append(rs)
rs = RadioSetting("sc_qt", "SC-QT",
- RadioSettingValueList(SCQT_LIST,
- SCQT_LIST[_settings.smuteset]))
+ RadioSettingValueList(SCQT_LIST,
+ SCQT_LIST[_settings.smuteset]))
cfg_grp.append(rs)
rs = RadioSetting("smuteset", "SubFreq Mute",
- RadioSettingValueList(SMUTESET_LIST,
- SMUTESET_LIST[_settings.smuteset]))
+ RadioSettingValueList(SMUTESET_LIST,
+ SMUTESET_LIST[_settings.smuteset]))
cfg_grp.append(rs)
_pwd = "".join(map(chr, _settings.mode_sw_pwd))
val = RadioSettingValueString(0, 6, _pwd)
@@ -807,45 +807,45 @@ class KGUV8DRadio(chirp_common.CloneModeRadio,
# VFO A Settings
#
rs = RadioSetting("vfoa_mode", "VFO A Workmode",
- RadioSettingValueList(WORKMODE_LIST,
- WORKMODE_LIST[_settings.workmode_a]))
+ RadioSettingValueList(WORKMODE_LIST,
+ WORKMODE_LIST[_settings.workmode_a]))
vfoa_grp.append(rs)
rs = RadioSetting("vfoa_chan", "VFO A Channel",
- RadioSettingValueInteger(1, 999, _settings.work_cha))
+ RadioSettingValueInteger(1, 999, _settings.work_cha))
vfoa_grp.append(rs)
rs = RadioSetting("rxfreqa", "VFO A Rx Frequency",
- RadioSettingValueInteger(
+ RadioSettingValueInteger(
134000000, 520000000, _vfoa.rxfreq * 10, 5000))
vfoa_grp.append(rs)
rs = RadioSetting("txoffa", "VFO A Tx Offset",
- RadioSettingValueInteger(
+ RadioSettingValueInteger(
0, 520000000, _vfoa.txoffset * 10, 5000))
vfoa_grp.append(rs)
# u16 rxtone;
# u16 txtone;
rs = RadioSetting("vfoa_power", "VFO A Power",
- RadioSettingValueList(
+ RadioSettingValueList(
POWER_LIST, POWER_LIST[_vfoa.power]))
vfoa_grp.append(rs)
# shift_dir:2
rs = RadioSetting("vfoa_iswide", "VFO A NBFM",
- RadioSettingValueList(
+ RadioSettingValueList(
BANDWIDTH_LIST, BANDWIDTH_LIST[_vfoa.iswide]))
vfoa_grp.append(rs)
rs = RadioSetting("vfoa_mute_mode", "VFO A Mute",
- RadioSettingValueList(
+ RadioSettingValueList(
SPMUTE_LIST, SPMUTE_LIST[_vfoa.mute_mode]))
vfoa_grp.append(rs)
rs = RadioSetting("vfoa_step", "VFO A Step (kHz)",
- RadioSettingValueList(
+ RadioSettingValueList(
STEP_LIST, STEP_LIST[_vfoa.step]))
vfoa_grp.append(rs)
rs = RadioSetting("vfoa_squelch", "VFO A Squelch",
- RadioSettingValueList(
+ RadioSettingValueList(
LIST_10, LIST_10[_vfoa.squelch]))
vfoa_grp.append(rs)
rs = RadioSetting("bcl_a", "Busy Channel Lock-out A",
- RadioSettingValueBoolean(_settings.bcl_a))
+ RadioSettingValueBoolean(_settings.bcl_a))
vfoa_grp.append(rs)
#
# VFO B Settings
@@ -890,7 +890,7 @@ class KGUV8DRadio(chirp_common.CloneModeRadio,
LIST_10, LIST_10[_vfob.squelch]))
vfob_grp.append(rs)
rs = RadioSetting("bcl_b", "Busy Channel Lock-out B",
- RadioSettingValueBoolean(_settings.bcl_b))
+ RadioSettingValueBoolean(_settings.bcl_b))
vfob_grp.append(rs)
#
# Key Settings
diff --git a/chirp/drivers/leixen.py b/chirp/drivers/leixen.py
index caa34dd..c484495 100644
--- a/chirp/drivers/leixen.py
+++ b/chirp/drivers/leixen.py
@@ -135,14 +135,13 @@ struct channel {
u8 unknown5;
u8 pttidoff:1,
dtmfoff:1,
- unknown6:1,
+ %(unknownormode)s,
tailcut:1,
aliasop:1,
talkaroundoff:1,
voxoff:1,
skip:1;
- u8 power:1,
- mode:1
+ u8 %(modeorpower)s,
reverseoff:1,
blckoff:1,
unknown7:1,
@@ -223,8 +222,6 @@ PFKEYSHORT_LIST = ["OFF",
"Reverse"
]
-POWER_LEVELS = [chirp_common.PowerLevel("Low", watts=4),
- chirp_common.PowerLevel("High", watts=10)]
MODES = ["NFM", "FM"]
WTFTONES = map(float, xrange(56, 64))
TONES = WTFTONES + chirp_common.TONES
@@ -279,7 +276,7 @@ def recv(radio, readdata=True):
# util.hexprint(hdr + data).replace("\n", "\n "))
if len(data) != length:
raise errors.RadioError("Radio sent %i bytes (expected %i)" % (
- len(data), length))
+ len(data), length))
chk = radio.pipe.read(1)
else:
data = ""
@@ -326,7 +323,8 @@ def do_upload(radio):
_ranges = [(0x0d00, 0x2000)]
image_ident = _image_ident_from_image(radio)
- if image_ident.startswith(radio._file_ident) and "LX-" in image_ident:
+ if image_ident.startswith(radio._file_ident) and \
+ radio._model_ident in image_ident:
_ranges = radio._ranges
do_ident(radio)
@@ -356,14 +354,23 @@ def finish(radio):
ack = radio.pipe.read(8)
+# Declaring Aliases
+class LT898UV(chirp_common.Alias):
+ VENDOR = "LUITON"
+ MODEL = "LT-898UV"
+
+
@directory.register
class LeixenVV898Radio(chirp_common.CloneModeRadio):
"""Leixen VV-898"""
VENDOR = "Leixen"
MODEL = "VV-898"
+ ALIASES = [LT898UV, ]
BAUD_RATE = 9600
_file_ident = "Leixen"
+ _model_ident = 'LX-\x89\x85\x63'
+
_memsize = 0x2000
_ranges = [
(0x0000, 0x013f),
@@ -375,6 +382,11 @@ class LeixenVV898Radio(chirp_common.CloneModeRadio):
(0x0d00, 0x2000),
]
+ _mem_formatter = {'unknownormode': 'unknown6:1',
+ 'modeorpower': 'mode:1, power:1'}
+ _power_levels = [chirp_common.PowerLevel("Low", watts=4),
+ chirp_common.PowerLevel("High", watts=10)]
+
def get_features(self):
rf = chirp_common.RadioFeatures()
rf.has_settings = True
@@ -395,7 +407,7 @@ class LeixenVV898Radio(chirp_common.CloneModeRadio):
"DTCS->DTCS"]
rf.valid_characters = chirp_common.CHARSET_ASCII
rf.valid_name_length = 7
- rf.valid_power_levels = POWER_LEVELS
+ rf.valid_power_levels = self._power_levels
rf.valid_duplexes = ["", "-", "+", "split", "off"]
rf.valid_skips = ["", "S"]
rf.valid_bands = [(136000000, 174000000),
@@ -412,7 +424,8 @@ class LeixenVV898Radio(chirp_common.CloneModeRadio):
self.process_mmap()
def process_mmap(self):
- self._memobj = bitwise.parse(MEM_FORMAT, self._mmap)
+ self._memobj = bitwise.parse(
+ MEM_FORMAT % self._mem_formatter, self._mmap)
def sync_out(self):
try:
@@ -425,7 +438,7 @@ class LeixenVV898Radio(chirp_common.CloneModeRadio):
def get_raw_memory(self, number):
return repr(self._memobj.name[number - 1]) + \
- repr(self._memobj.memory[number - 1])
+ repr(self._memobj.memory[number - 1])
def _get_tone(self, mem, _mem):
rx_tone = tx_tone = None
@@ -447,7 +460,7 @@ class LeixenVV898Radio(chirp_common.CloneModeRadio):
rx_pol = _mem.rx_tmode == 0x03 and "R" or "N"
chirp_common.split_tone_decode(mem, (tx_tmode, tx_tone, tx_pol),
- (rx_tmode, rx_tone, rx_pol))
+ (rx_tmode, rx_tone, rx_pol))
def _is_txinh(self, _mem):
raw_tx = ""
@@ -485,12 +498,8 @@ class LeixenVV898Radio(chirp_common.CloneModeRadio):
self._get_tone(mem, _mem)
mem.mode = MODES[_mem.mode]
- mem.power = POWER_LEVELS[_mem.power]
- mem.skip = _mem.skip and "S" or ""
-
- self._get_tone(mem, _mem)
- mem.mode = MODES[_mem.mode]
- mem.power = POWER_LEVELS[_mem.power]
+ powerindex = _mem.power if _mem.power < len(self._power_levels) else -1
+ mem.power = self._power_levels[powerindex]
mem.skip = _mem.skip and "S" or ""
mem.extra = RadioSettingGroup("Extra", "extra")
@@ -505,11 +514,12 @@ class LeixenVV898Radio(chirp_common.CloneModeRadio):
RadioSettingValueList(
opts, opts[_mem.tailcut]))
mem.extra.append(rs)
+ apro = _mem.apro if _mem.apro < 0x5 else 0
opts = ["Off", "Compander", "Scrambler", "TX Scrambler",
"RX Scrambler"]
rs = RadioSetting("apro", "Audio Processing",
RadioSettingValueList(
- opts, opts[_mem.apro]))
+ opts, opts[apro]))
mem.extra.append(rs)
opts = ["On", "Off"]
rs = RadioSetting("voxoff", "VOX",
@@ -587,7 +597,7 @@ class LeixenVV898Radio(chirp_common.CloneModeRadio):
self._set_tone(mem, _mem)
- _mem.power = mem.power and POWER_LEVELS.index(mem.power) or 0
+ _mem.power = mem.power and self._power_levels.index(mem.power) or 0
_mem.mode = MODES.index(mem.mode)
_mem.skip = mem.skip == "S"
_name.name = mem.name.ljust(7)
@@ -926,9 +936,7 @@ class LeixenVV898Radio(chirp_common.CloneModeRadio):
@classmethod
def match_model(cls, filedata, filename):
if filedata[0x168:0x170].startswith(cls._file_ident) and \
- filedata[0x170:0x178].startswith("LX-\x89\x85"):
- return True
- elif filedata[0x900:0x906] == cls.MODEL:
+ filedata[0x170:0x178].startswith(cls._model_ident):
return True
else:
return False
@@ -941,3 +949,25 @@ class JetstreamJT270MRadio(LeixenVV898Radio):
MODEL = "JT270M"
_file_ident = "JET"
+ _model_ident = 'LX-\x89\x85\x53'
+
+
+class VV898E(chirp_common.Alias):
+ '''Leixen has called this radio both 898E and S historically, ident is identical'''
+ VENDOR = "Leixen"
+ MODEL = "VV-898E"
+
+
+ at directory.register
+class LeixenVV898SRadio(LeixenVV898Radio):
+ """Leixen VV-898S, also VV-898E which is identical"""
+ VENDOR = "Leixen"
+ MODEL = "VV-898S"
+ ALIASES = [VV898E, ]
+
+ _model_ident = 'LX-\x89\x85\x75'
+ _mem_formatter = {'unknownormode': 'mode:1',
+ 'modeorpower': 'power:2'}
+ _power_levels = [chirp_common.PowerLevel("Low", watts=5),
+ chirp_common.PowerLevel("Med", watts=10),
+ chirp_common.PowerLevel("High", watts=25)]
diff --git a/chirp/drivers/lt725uv.py b/chirp/drivers/lt725uv.py
new file mode 100644
index 0000000..2f3f757
--- /dev/null
+++ b/chirp/drivers/lt725uv.py
@@ -0,0 +1,763 @@
+# Copyright 2016:
+# * Jim Unroe KC9HI, <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
+# 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 = """
+#seekto 0x0200;
+struct {
+ u8 unknown1;
+ u8 volume;
+ u8 unknown2[2];
+ u8 wtled;
+ u8 rxled;
+ u8 txled;
+ u8 ledsw;
+ u8 beep;
+ u8 ring;
+ u8 bcl;
+ u8 tot;
+} settings;
+
+struct vfo {
+ u8 unknown1[2];
+ u32 rxfreq;
+ u8 unknown2[8];
+ u8 power;
+ u8 unknown3[3];
+ u24 offset;
+ u32 step;
+ u8 sql;
+};
+
+#seekto 0x0300;
+struct {
+ struct vfo vfoa;
+} upper;
+
+#seekto 0x0380;
+struct {
+ struct vfo vfob;
+} lower;
+
+struct mem {
+ u32 rxfreq;
+ u16 is_rxdigtone:1,
+ rxdtcs_pol:1,
+ rxtone:14;
+ u8 recvmode;
+ u32 txfreq;
+ u16 is_txdigtone:1,
+ txdtcs_pol:1,
+ txtone:14;
+ u8 botsignal;
+ u8 eotsignal;
+ u8 power:1,
+ wide:1,
+ compandor:1
+ scrambler:1
+ unknown:4;
+ u8 namelen;
+ u8 name[6];
+ u8 unused;
+};
+
+#seekto 0x0400;
+struct mem upper_memory[128];
+
+#seekto 0x1000;
+struct mem lower_memory[128];
+
+"""
+
+MEM_SIZE = 0x1C00
+BLOCK_SIZE = 0x40
+STIMEOUT = 2
+
+LIST_RECVMODE = ["", "QT/DQT", "QT/DQT + Signaling"]
+LIST_SIGNAL = ["Off"] + ["DTMF%s" % x for x in range(1, 9)] + \
+ ["DTMF%s + Identity" % x for x in range(1, 9)] + \
+ ["Identity code"]
+LIST_POWER = ["Low", "Mid", "High"]
+LIST_COLOR = ["Off", "Orange", "Blue", "Purple"]
+LIST_LEDSW = ["Auto", "On"]
+LIST_RING = ["Off"] + ["%s seconds" % x for x in range(1, 10)]
+LIST_TIMEOUT = ["Off"] + ["%s seconds" % x for x in range(30, 630, 30)]
+
+
+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 _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(">4sHH", cmd, addr, length)
+ # add the data if set
+ if len(data) != 0:
+ frame += data
+ # return the data
+ return frame
+
+
+def _recv(radio, addr, length):
+ """Get data from the radio """
+
+ data = _rawrecv(radio, length)
+
+ # DEBUG
+ LOG.info("Response:")
+ LOG.debug(util.hexprint(data))
+
+ return data
+
+
+def _do_ident(radio):
+ """Put the radio in PROGRAM mode & identify it"""
+ # set the serial discipline
+ radio.pipe.baudrate = 19200
+ radio.pipe.parity = "N"
+ radio.pipe.timeout = STIMEOUT
+
+ # flush input buffer
+ _clean_buffer(radio)
+
+ magic = "PROM_LIN"
+
+ _rawsend(radio, magic)
+
+ ack = _rawrecv(radio, 1)
+ if ack != "\x06":
+ _exit_program_mode(radio)
+ if ack:
+ LOG.debug(repr(ack))
+ raise errors.RadioError("Radio did not respond")
+
+ return True
+
+
+def _exit_program_mode(radio):
+ endframe = "EXIT"
+ _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("READ", 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, BLOCK_SIZE)
+
+ # 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)
+
+ data += "LT-725UV"
+
+ return data
+
+
+def _upload(radio):
+ """Upload procedure"""
+
+ # 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 starts here
+ for addr in range(0, MEM_SIZE, BLOCK_SIZE):
+ # sending the data
+ data = radio.get_mmap()[addr:addr + BLOCK_SIZE]
+
+ frame = _make_frame("WRIE", 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)
+
+ # 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[0x1C00:0x1C08]
+
+ if rid == cls.MODEL:
+ return True
+
+ return False
+
+
+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 True
+
+
+ at directory.register
+class LT725UV(chirp_common.CloneModeRadio,
+ chirp_common.ExperimentalRadio):
+ """LUITON LT-725UV Radio"""
+ VENDOR = "LUITON"
+ MODEL = "LT-725UV"
+ MODES = ["NFM", "FM"]
+ TONES = chirp_common.TONES
+ DTCS_CODES = sorted(chirp_common.DTCS_CODES + [645])
+ NAME_LENGTH = 6
+ DTMF_CHARS = list("0123456789ABCD*#")
+
+ VALID_BANDS = [(136000000, 176000000),
+ (400000000, 480000000)]
+
+ # valid chars on the LCD
+ VALID_CHARS = chirp_common.CHARSET_ALPHANUMERIC + \
+ "`{|}!\"#$%&'()*+,-./:;<=>?@[]^_"
+
+ @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 = True
+ 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 = self.MODES
+ rf.valid_characters = self.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_skips = []
+ rf.valid_name_length = self.NAME_LENGTH
+ rf.valid_dtcs_codes = self.DTCS_CODES
+ rf.valid_bands = self.VALID_BANDS
+ rf.memory_bounds = (1, 128)
+ return rf
+
+ def get_sub_devices(self):
+ return [LT725UVUpper(self._mmap), LT725UVLower(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 - 1])
+
+ def _memory_obj(self, suffix=""):
+ return getattr(self._memobj, "%s_memory%s" % (self._vfo, suffix))
+
+ def _get_dcs(self, val):
+ return int(str(val)[2:-18])
+
+ def _set_dcs(self, val):
+ return int(str(val), 16)
+
+ def get_memory(self, number):
+ _mem = self._memory_obj()[number - 1]
+
+ mem = chirp_common.Memory()
+ mem.number = number
+
+ if _mem.get_raw()[0] == "\xff":
+ mem.empty = True
+ return mem
+
+ mem.freq = int(_mem.rxfreq) * 10
+
+ if _mem.txfreq == 0xFFFFFFFF:
+ # TX freq not set
+ mem.duplex = "off"
+ mem.offset = 0
+ elif int(_mem.rxfreq) == int(_mem.txfreq):
+ mem.duplex = ""
+ mem.offset = 0
+ elif _split(self.get_features(), mem.freq, int(_mem.txfreq) * 10):
+ mem.duplex = "split"
+ mem.offset = int(_mem.txfreq) * 10
+ else:
+ mem.duplex = int(_mem.rxfreq) > int(_mem.txfreq) and "-" or "+"
+ mem.offset = abs(int(_mem.rxfreq) - int(_mem.txfreq)) * 10
+
+ for char in _mem.name[:_mem.namelen]:
+ mem.name += chr(char)
+
+ dtcs_pol = ["N", "N"]
+
+ if _mem.rxtone == 0x3FFF:
+ rxmode = ""
+ elif _mem.is_rxdigtone == 0:
+ # ctcss
+ rxmode = "Tone"
+ mem.ctone = int(_mem.rxtone) / 10.0
+ else:
+ # digital
+ rxmode = "DTCS"
+ mem.rx_dtcs = self._get_dcs(_mem.rxtone)
+ if _mem.rxdtcs_pol == 1:
+ dtcs_pol[1] = "R"
+
+ if _mem.txtone == 0x3FFF:
+ txmode = ""
+ elif _mem.is_txdigtone == 0:
+ # ctcss
+ txmode = "Tone"
+ mem.rtone = int(_mem.txtone) / 10.0
+ else:
+ # digital
+ txmode = "DTCS"
+ mem.dtcs = self._get_dcs(_mem.txtone)
+ if _mem.txdtcs_pol == 1:
+ dtcs_pol[0] = "R"
+
+ if txmode == "Tone" and not rxmode:
+ mem.tmode = "Tone"
+ elif txmode == rxmode and txmode == "Tone" and mem.rtone == mem.ctone:
+ mem.tmode = "TSQL"
+ elif txmode == rxmode and txmode == "DTCS" and mem.dtcs == mem.rx_dtcs:
+ mem.tmode = "DTCS"
+ elif rxmode or txmode:
+ mem.tmode = "Cross"
+ mem.cross_mode = "%s->%s" % (txmode, rxmode)
+
+ mem.dtcs_polarity = "".join(dtcs_pol)
+
+ mem.mode = self.MODES[_mem.wide]
+
+ # Extra
+ mem.extra = RadioSettingGroup("extra", "Extra")
+
+ if _mem.recvmode == 0xFF:
+ val = 0x00
+ else:
+ val = _mem.recvmode
+ recvmode = RadioSetting("recvmode", "Receiving mode",
+ RadioSettingValueList(LIST_RECVMODE,
+ LIST_RECVMODE[val]))
+ mem.extra.append(recvmode)
+
+ if _mem.botsignal == 0xFF:
+ val = 0x00
+ else:
+ val = _mem.botsignal
+ botsignal = RadioSetting("botsignal", "Launch signaling",
+ RadioSettingValueList(LIST_SIGNAL,
+ LIST_SIGNAL[val]))
+ mem.extra.append(botsignal)
+
+ if _mem.eotsignal == 0xFF:
+ val = 0x00
+ else:
+ val = _mem.eotsignal
+ eotsignal = RadioSetting("eotsignal", "Transmit end signaling",
+ RadioSettingValueList(LIST_SIGNAL,
+ LIST_SIGNAL[val]))
+ mem.extra.append(eotsignal)
+
+ compandor = RadioSetting("compandor", "Compandor",
+ RadioSettingValueBoolean(bool(_mem.compandor)))
+ mem.extra.append(compandor)
+
+ scrambler = RadioSetting("scrambler", "Scrambler",
+ RadioSettingValueBoolean(bool(_mem.scrambler)))
+ mem.extra.append(scrambler)
+
+ return mem
+
+ def set_memory(self, mem):
+ _mem = self._memory_obj()[mem.number - 1]
+
+ if mem.empty:
+ _mem.set_raw("\xff" * 24)
+ _mem.namelen = 0
+ return
+
+ _mem.set_raw("\xFF" * 15 + "\x00\x00" + "\xFF" * 7)
+
+ _mem.rxfreq = mem.freq / 10
+ if mem.duplex == "off":
+ _mem.txfreq = 0xFFFFFFFF
+ elif mem.duplex == "split":
+ _mem.txfreq = mem.offset / 10
+ elif mem.duplex == "+":
+ _mem.txfreq = (mem.freq + mem.offset) / 10
+ elif mem.duplex == "-":
+ _mem.txfreq = (mem.freq - mem.offset) / 10
+ else:
+ _mem.txfreq = mem.freq / 10
+
+ _mem.namelen = len(mem.name)
+ _namelength = self.get_features().valid_name_length
+ for i in range(_namelength):
+ try:
+ _mem.name[i] = ord(mem.name[i])
+ except IndexError:
+ _mem.name[i] = 0xFF
+
+ rxmode = ""
+ txmode = ""
+
+ if mem.tmode == "Tone":
+ txmode = "Tone"
+ elif mem.tmode == "TSQL":
+ rxmode = "Tone"
+ txmode = "TSQL"
+ elif mem.tmode == "DTCS":
+ rxmode = "DTCSSQL"
+ txmode = "DTCS"
+ elif mem.tmode == "Cross":
+ txmode, rxmode = mem.cross_mode.split("->", 1)
+
+ if rxmode == "":
+ _mem.rxdtcs_pol = 1
+ _mem.is_rxdigtone = 1
+ _mem.rxtone = 0x3FFF
+ elif rxmode == "Tone":
+ _mem.rxdtcs_pol = 0
+ _mem.is_rxdigtone = 0
+ _mem.rxtone = int(mem.ctone * 10)
+ elif rxmode == "DTCSSQL":
+ _mem.rxdtcs_pol = 1 if mem.dtcs_polarity[1] == "R" else 0
+ _mem.is_rxdigtone = 1
+ _mem.rxtone = self._set_dcs(mem.dtcs)
+ elif rxmode == "DTCS":
+ _mem.rxdtcs_pol = 1 if mem.dtcs_polarity[1] == "R" else 0
+ _mem.is_rxdigtone = 1
+ _mem.rxtone = self._set_dcs(mem.rx_dtcs)
+
+ if txmode == "":
+ _mem.txdtcs_pol = 1
+ _mem.is_txdigtone = 1
+ _mem.txtone = 0x3FFF
+ elif txmode == "Tone":
+ _mem.txdtcs_pol = 0
+ _mem.is_txdigtone = 0
+ _mem.txtone = int(mem.rtone * 10)
+ elif txmode == "TSQL":
+ _mem.txdtcs_pol = 0
+ _mem.is_txdigtone = 0
+ _mem.txtone = int(mem.ctone * 10)
+ elif txmode == "DTCS":
+ _mem.txdtcs_pol = 1 if mem.dtcs_polarity[0] == "R" else 0
+ _mem.is_txdigtone = 1
+ _mem.txtone = self._set_dcs(mem.dtcs)
+
+ _mem.wide = self.MODES.index(mem.mode)
+
+ # extra settings
+ for setting in mem.extra:
+ setattr(_mem, setting.get_name(), setting.value)
+
+ def get_settings(self):
+ """Translate the bit in the mem_struct into settings in the UI"""
+ _mem = self._memobj
+ basic = RadioSettingGroup("basic", "Basic Settings")
+ top = RadioSettings(basic)
+
+ # Basic
+
+ volume = RadioSetting("settings.volume", "Volume",
+ RadioSettingValueInteger(0, 20,
+ _mem.settings.volume))
+ basic.append(volume)
+
+ powera = RadioSetting("upper.vfoa.power", "Power (Upper)",
+ RadioSettingValueList(LIST_POWER, LIST_POWER[
+ _mem.upper.vfoa.power]))
+ basic.append(powera)
+
+ powerb = RadioSetting("lower.vfob.power", "Power (Lower)",
+ RadioSettingValueList(LIST_POWER, LIST_POWER[
+ _mem.lower.vfob.power]))
+ basic.append(powerb)
+
+ wtled = RadioSetting("settings.wtled", "Standby LED Color",
+ RadioSettingValueList(LIST_COLOR, LIST_COLOR[
+ _mem.settings.wtled]))
+ basic.append(wtled)
+
+ rxled = RadioSetting("settings.rxled", "RX LED Color",
+ RadioSettingValueList(LIST_COLOR, LIST_COLOR[
+ _mem.settings.rxled]))
+ basic.append(rxled)
+
+ txled = RadioSetting("settings.txled", "TX LED Color",
+ RadioSettingValueList(LIST_COLOR, LIST_COLOR[
+ _mem.settings.txled]))
+ basic.append(txled)
+
+ ledsw = RadioSetting("settings.ledsw", "Back light mode",
+ RadioSettingValueList(LIST_LEDSW, LIST_LEDSW[
+ _mem.settings.ledsw]))
+ basic.append(ledsw)
+
+ beep = RadioSetting("settings.beep", "Beep",
+ RadioSettingValueBoolean(bool(_mem.settings.beep)))
+ basic.append(beep)
+
+ ring = RadioSetting("settings.ring", "Ring",
+ RadioSettingValueList(LIST_RING, LIST_RING[
+ _mem.settings.ring]))
+ basic.append(ring)
+
+ bcl = RadioSetting("settings.bcl", "Busy channel lockout",
+ RadioSettingValueBoolean(bool(_mem.settings.bcl)))
+ basic.append(bcl)
+
+ tot = RadioSetting("settings.tot", "Timeout Timer",
+ RadioSettingValueList(LIST_TIMEOUT, LIST_TIMEOUT[
+ _mem.settings.tot]))
+ basic.append(tot)
+
+ if _mem.upper.vfoa.sql == 0xFF:
+ val = 0x04
+ else:
+ val = _mem.upper.vfoa.sql
+ sqla = RadioSetting("upper.vfoa.sql", "Squelch (Upper)",
+ RadioSettingValueInteger(0, 9, val))
+ basic.append(sqla)
+
+ if _mem.lower.vfob.sql == 0xFF:
+ val = 0x04
+ else:
+ val = _mem.lower.vfob.sql
+ sqlb = RadioSetting("lower.vfob.sql", "Squelch (Lower)",
+ RadioSettingValueInteger(0, 9, val))
+ basic.append(sqlb)
+
+ return top
+
+ def set_settings(self, settings):
+ _settings = self._memobj.settings
+ _mem = self._memobj
+ for element in settings:
+ if not isinstance(element, RadioSetting):
+ self.set_settings(element)
+ continue
+ else:
+ try:
+ name = element.get_name()
+ if "." in name:
+ bits = name.split(".")
+ obj = self._memobj
+ for bit in bits[:-1]:
+ if "/" in bit:
+ bit, index = bit.split("/", 1)
+ index = int(index)
+ obj = getattr(obj, bit)[index]
+ else:
+ obj = getattr(obj, bit)
+ setting = bits[-1]
+ else:
+ obj = _settings
+ setting = element.get_name()
+
+ if element.has_apply_callback():
+ LOG.debug("Using apply callback")
+ element.run_apply_callback()
+ elif element.value.get_mutable():
+ LOG.debug("Setting %s = %s" % (setting, element.value))
+ setattr(obj, setting, element.value)
+ except Exception, e:
+ LOG.debug(element.get_name())
+ raise
+
+
+ @classmethod
+ def match_model(cls, filedata, filename):
+ match_size = False
+ match_model = False
+
+ # testing the file data size
+ if len(filedata) == MEM_SIZE + 8:
+ 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
+
+
+class LT725UVUpper(LT725UV):
+ VARIANT = "Upper"
+ _vfo = "upper"
+
+
+class LT725UVLower(LT725UV):
+ VARIANT = "Lower"
+ _vfo = "lower"
diff --git a/chirp/drivers/puxing_px888k.py b/chirp/drivers/puxing_px888k.py
new file mode 100644
index 0000000..cc6f3c5
--- /dev/null
+++ b/chirp/drivers/puxing_px888k.py
@@ -0,0 +1,1877 @@
+# Copyright 2016 Leo Barring <leo.barring at protonmail.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/>.
+
+from chirp import chirp_common, directory, memmap, \
+ bitwise, settings, errors
+from struct import pack
+import logging
+
+LOG = logging.getLogger(__name__)
+
+SUPPORT_NONSPLIT_DUPLEX_ONLY = False
+SUPPORT_SPLIT_BUT_DEFAULT_TO_NONSPLIT_ALWAYS = True
+UNAMBIGUOUS_CROSS_MODES_ONLY = True
+
+# With this setting enabled, some CHIRP settings are stored in
+# thought-to-be junk/padding data of the channel memories.
+# Enabling this feature while using CHIRP with an actual radio
+# and any effects thereof is entirely the responsibility of the user.
+ENABLE_DANGEROUS_EXPERIMENTAL_FEATURES = False
+
+MEM_FORMAT = """
+// data fields are generally written 0xff if they are unset
+struct {
+
+#seekto 0x0000;
+ struct {
+ struct {
+ // 0-3
+ bbcd rx_freq[4];
+
+ // 4-7
+ bbcd tx_freq[4];
+
+ // 8-9 A-B
+ struct {
+ u8 digital:1,
+ invert:1,
+ high:6;
+ u8 low;
+ } tone[2];
+ // tx_squelch on 0, rx_squelch on 1
+
+ // C
+ // the duplex sign is not used for memories,
+ // but is kept for interface consistency
+ u8 duplex_sign:2,
+ compander:1,
+ txpower:1,
+ modulation_width:1,
+ txrx_reverse:1,
+ bcl:2;
+
+ // D
+ u8 scrambler_type:3,
+ use_scrambler:1,
+ opt_signal:2,
+ ptt_id_edge:2;
+
+ // E-F
+ // u8 _unknown_000E[2];
+ %s
+ } data[128];
+ struct {
+ // 0-5, alt 8-D
+ char entry[6];
+
+ // 6-8, alt E-F
+ char _unknown_0806[2];
+ } names[128];
+
+#seekto 0x0c20;
+ bit present[128];
+
+#seekto 0x0c30;
+ bit priority[128];
+ } channel_memory;
+
+#seekto 0x0c00;
+ struct {
+ // 0-3
+ bbcd rx_freq[4];
+
+ // 4-7
+ bbcd tx_freq[4]; // actually offset, but kept for name consistency
+
+ // 8
+ struct {
+ u8 digital:1,
+ invert:1,
+ high:6;
+ u8 low;
+ } tone[2]; // tx_squelch on 0, rx_squelch on 1
+
+ // C
+ u8 duplex_sign:2,
+ compander:1,
+ txpower:1,
+ modulation_width:1,
+ txrx_reverse:1,
+ bcl:2;
+
+ // D
+ u8 scrambler_type:3,
+ use_scrambler:1,
+ opt_signal:2,
+ ptt_id_edge:2;
+
+ // E-F
+ // u8 _unknown_0C0E[2];
+ %s
+
+ } vfo_data[2];
+
+#seekto 0xc40;
+ struct {
+ // 0-5
+ char model_string[6]; // typically PX888D, unknown if rw or ro
+
+ // 6-7
+ u8 _unknown_0C46[2];
+
+ // 8-9
+ struct {
+ bbcd lower_freq[2];
+ bbcd upper_freq[2];
+ } band_limits[2];
+ } model_information;
+
+#seekto 0x0c50;
+ char radio_information_string[16];
+
+#seekto 0x0c60;
+ struct {
+ // 0
+ u8 ptt_cancel_sq:1,
+ dis_ptt_id:1,
+ workmode_b:2,
+ use_roger_beep:1,
+ msk_reverse:1,
+ workmode_a:2;
+
+ // 1
+ u8 backlight_color:2,
+ backlight_mode:2,
+ dual_single_watch:1,
+ auto_keylock:1,
+ scan_mode:2;
+
+ // 2
+ u8 rx_stun:1,
+ tx_stun:1,
+ boot_message_mode:2,
+ battery_save:1,
+ key_beep:1,
+ voice_announce:2;
+
+ // 3
+ bbcd squelch_level;
+
+ // 4
+ bbcd tx_timeout;
+
+ // 5
+ u8 allow_keypad:1,
+ relay_without_disable_tail:1
+ _unknown_0C65:1,
+ call_channel_active:1,
+ vox_gain:4;
+
+ // 6
+ bbcd vox_delay;
+
+ // 7
+ bbcd vfo_step;
+
+ // 8
+ bbcd ptt_id_type;
+
+ // 9
+ u8 keypad_lock:1,
+ _unknown_0C69_1:1,
+ side_button_hold_mode:2,
+ dtmf_sidetone:1,
+ _unknown_0C69_2:1,
+ side_button_click_mode:2;
+
+ // A
+ u8 roger_beep:4,
+ main_watch:1,
+ _unknown_0C6A:3;
+
+ // B
+ u8 channel_a;
+
+ // C
+ u8 channel_b;
+
+ // D
+ u8 priority_channel;
+
+ // E
+ u8 wait_time;
+
+ // F
+ u8 _unknown_0C6F;
+
+ // 0-7 on next block
+ u8 _unknown_0C70[8];
+
+ // 8-D on next block
+ char boot_message[6];
+ } opt_settings;
+
+#seekto 0x0c80;
+ struct {
+ // these fields are used for all ptt id forms (msk/dtmf/5t)
+ // (only one can be active and stored at a time)
+ // and different constraints are applied depending
+ // on the ptt id type
+ u8 entry[7];
+ u8 length;
+ } ptt_id_data[2];
+ // 0 is BOT, 1 is EOT
+
+#seekto 0x0c90;
+ struct {
+ // 0
+ u8 _unknown_0C90;
+
+ // 1
+ u8 _unknown_0C91_1:3,
+ channel_stepping:1,
+ unknown_0C91_2:1
+ receive_range:2
+ unknown_0C91_3:1;
+
+ // 2-3
+ u8 _unknown_0C92[2];
+
+ // 4-7
+ u8 vfo_freq[4];
+
+ // 8-F and two more blocks
+ struct {
+ u8 entry[4];
+ } memory[10];
+ } fm_radio;
+
+#seekto 0x0cc0;
+ struct {
+ char id_code[4];
+ struct {
+ char entry[4];
+ } phone_book[9];
+ } msk_settings;
+
+#seekto 0x0cf0;
+ struct {
+ // 0-3
+ bbcd rx_freq[4];
+
+ // 4-7
+ bbcd tx_freq[4];
+
+ // 8
+ struct {
+ u8 digital:1,
+ invert:1,
+ high:6;
+ u8 low;
+ } tone[2];
+ // tx_squelch on 0, rx_squelch on 1
+
+
+ // C
+ // the duplex sign is not used for the CALL,
+ // channel but is kept for interface consistency
+ u8 duplex_sign:2,
+ compander:1,
+ txpower:1,
+ modulation_width:1,
+ txrx_reverse:1
+ bcl:2;
+
+ // D
+ u8 scrambler_type:3,
+ use_scrambler:1,
+ opt_signal:2,
+ ptt_id_edge:2;
+
+ // E-F
+ // u8 _unknown_0CFE[2];
+ %s
+ } call_channel;
+
+#seekto 0x0d00;
+ struct {
+
+ // DTMF codes are stored as hex half-bytes,
+ // 0-9 A-D are mapped straight
+ // DTMF '*' is HEX E, DTMF '#' is HEX F
+
+ // 0x0d00
+ struct {
+ u8 digit_length; // 0x05 to 0x14 corresponding to 50-200ms
+ u8 inter_digit_pause; // same
+ u8 first_digit_length; // same
+ u8 first_digit_delay; // 0x02 to 0x14 corresponding to 100-1000ms
+ } timing;
+
+#seekto 0x0d30;
+ u8 _unknown_0D30[2]; // 0-1
+ u8 group_code; // 2
+ u8 reset_time; // 3
+ u8 alert_transpond; // 4
+ u8 id_code[4]; // 5-8
+ u8 _unknown_0D39[4]; // 9-C
+ u8 id_code_length; // D
+ u8 _unknown_0d3e[2]; // E-F
+
+// 0x0d40
+ u8 tx_stun_code[4];
+ u8 _unknown_0D44[4];
+ u8 tx_stun_code_length;
+ u8 cancel_tx_stun_code_length;
+ u8 cancel_tx_stun_code[4];
+ u8 _unknown_0D4E[2];
+
+// 0x0d50
+ u8 rxtx_stun_code[4];
+ u8 _unknown_0D54[4];
+ u8 rxtx_stun_code_length;
+ u8 cancel_rxtx_stun_code_length;
+ u8 cancel_rxtx_stun_code[4];
+ u8 _unknown_0D4E[2];
+
+// 0x0d60
+ struct {
+ u8 entry[5];
+ u8 _unknown_0D65[3];
+ u8 length;
+ u8 _unknown_0D69[7];
+ } phone_book[9];
+ } dtmf_settings;
+
+#seekto 0x0e00;
+ struct {
+ u8 delay;
+ u8 _unknown_0E01[5];
+ u8 alert_transpond;
+ u8 reset_time;
+ u8 tone_standard;
+ u8 id_code[3];
+
+#seekto 0x0e20;
+ struct {
+ u8 period;
+ u8 group_code:4,
+ repeat_code:4;
+ } tone_settings[4];
+ // the order is ZVEI1 ZVEI2 CCIR1 CCITT
+
+#seekto 0x0e40;
+ // 5-Tone tone standard frequency table
+ // unknown use, changing the values does not seem to have
+ // any effect on the produced sound, but the values are not
+ // overwritten either.
+ il16 tone_frequency_table[16];
+
+// 0xe60
+ u8 tx_stun_code[5];
+ u8 _unknown_0E65[3];
+ u8 tx_stun_code_length;
+ u8 cancel_tx_stun_code_length;
+ u8 cancel_tx_stun_code[5];
+ u8 _unknown_0E6F;
+
+// 0xe70
+ u8 rxtx_stun_code[5];
+ u8 _unknown_0E75[3];
+ u8 rxtx_stun_code_length;
+ u8 cancel_rxtx_stun_code_length;
+ u8 cancel_rxtx_stun_code[5];
+ u8 _unknown_0E7F;
+
+// 0xe80
+ struct {
+ u8 entry[3];
+ } phone_book[9];
+ } five_tone_settings;
+} mem;"""
+
+# various magic numbers and strings, apart from the memory format
+if ENABLE_DANGEROUS_EXPERIMENTAL_FEATURES:
+ LOG.warn("ENABLE_DANGEROUS_EXPERIMENTAL_FEATURES"
+ "AND/OR DANGEROUS FEATURES ENABLED")
+ MEM_FORMAT = MEM_FORMAT % (
+ "u8 _unknown_000E_1: 6,\n"
+ " experimental_duplex_mode_indicator: 1,\n"
+ " experimental_cross_mode_indicator: 1;\n"
+ "u8 _unknown_000F;",
+ "u8 _unknown_0C0E_1: 6,\n"
+ " experimental_duplex_mode_indicator: 1,\n"
+ " experimental_cross_mode_indicator: 1;\n"
+ "u8 _unknown_0C0F;",
+ "u8 _unknown_0CFE_1: 6,\n"
+ " experimental_duplex_mode_indicator: 1,\n"
+ " experimental_cross_mode_indicator: 1;\n"
+ "u8 _unknown_0CFF;")
+ # we don't need these settings anymore, because it's exactly what the
+ # experimental features are about
+ SUPPORT_SPLIT_BUT_DEFAULT_TO_NONSPLIT_ALWAYS = False
+ SUPPORT_NONSPLIT_DUPLEX_ONLY = False
+ UNAMBIGUOUS_CROSS_MODES_ONLY = False
+
+else:
+ MEM_FORMAT = MEM_FORMAT % (
+ "u8 _unknown_000E[2];",
+ "u8 _unknown_0C0E[2];",
+ "u8 _unknown_0CFE[2];")
+
+FILE_MAGIC = [0xc40, 0xc50,
+ '\x50\x58\x38\x38\x38\x44\x00\xff'
+ '\x13\x40\x17\x60\x40\x00\x48\x00']
+HANDSHAKE_OUT = b'XONLINE'
+HANDSHAKE_IN = [b'PX888D\x00\xff']
+
+LOWER_READ_BOUND = 0
+UPPER_READ_BOUND = 0x1000
+LOWER_WRITE_BOUND = 0
+UPPER_WRITE_BOUND = 0x0fc0
+BLOCKSIZE = 64
+
+OFF_INT = ["Off"] + [str(x+1) for x in range(100)]
+OFF_ON = ["Off", "On"]
+INACTIVE_ACTIVE = ["Inactive", "Active"]
+NO_YES = ["No", "Yes"]
+YES_NO = ["Yes", "No"]
+
+BANDS = [(134000000, 176000000), # VHF
+ (400000000, 480000000)] # UHF
+
+SPECIAL_CHANNELS = {'VFO-A': -2, 'VFO-B': -1, 'CALL': 0}
+SPECIAL_NUMBERS = {-2: 'VFO-A', -1: 'VFO-B', 0: 'CALL'}
+
+DUPLEX_MODES = ['', '+', '-', 'split']
+if SUPPORT_NONSPLIT_DUPLEX_ONLY:
+ DUPLEX_MODES = ['', '+', '-']
+
+TONE_MODES = ["", "Tone", "TSQL", "DTCS", "Cross"]
+
+CROSS_MODES = ["Tone->Tone",
+ "DTCS->",
+ "->DTCS",
+ "Tone->DTCS",
+ "DTCS->Tone",
+ "->Tone",
+ "DTCS->DTCS",
+ "Tone->"]
+if UNAMBIGUOUS_CROSS_MODES_ONLY:
+ CROSS_MODES = ["Tone->Tone",
+ "DTCS->",
+ "->DTCS",
+ "Tone->DTCS",
+ "DTCS->Tone",
+ "->Tone",
+ "DTCS->DTCS"]
+
+MODES = ["NFM", "FM"]
+
+# Only the 'High' power level is quantified in the manual for
+# the radio (4W for VHF, 5W for UHF), a web search turned
+# up numbers for the 'Low' power level (0.5W for VHF and
+# 0.7W for UHF), but they are not official to my knowledge
+# and should be taken with a grain of salt or two.
+# Numbers used in code is the averages
+POWER_LEVELS = [chirp_common.PowerLevel("Low", watts=0.6),
+ chirp_common.PowerLevel("High", watts=4.5)]
+
+SKIP_MODES = ["", "S"]
+BCL_MODES = ["Off", "Carrier", "QT/DQT"]
+SCRAMBLER_MODES = OFF_INT[0:9]
+PTT_ID_EDGES = ["Off", "BOT", "EOT", "Both"]
+OPTSIGN_MODES = ["None", "DTMF", "5-Tone", "MSK"]
+
+VFO_STRIDE = ['5kHz', '6.25kHz', '10kHz', '12.5kHz', '25kHz']
+AB = ['A', 'B']
+WATCH_MODES = ['Single watch', 'Dual watch']
+AB_MODES = ['VFO', 'Memory index', 'Memory name', 'Memory frequency']
+SCAN_MODES = ["Time", "Carrier", "Seek"]
+WAIT_TIMES = [("0.3s", 6), ("0.5s", 10)] +\
+ [("%ds" % t, t*20) for t in range(1, 13)]
+
+BUTTON_MODES = ["Send call list data",
+ "Emergency alarm",
+ "Send 1750Hz signal",
+ "Open squelch"]
+BOOT_MESSAGE_TYPES = ["Off", "Battery voltage", "Custom message"]
+TALKBACK = ['Off', 'Chinese', 'English']
+BACKLIGHT_COLORS = zip(["Blue", "Orange", "Purple"], range(1, 4))
+VOX_GAIN = OFF_INT[0:10]
+VOX_DELAYS = ['1s', '2s', '3s', '4s']
+TRANSMIT_ALARMS = ['Off', '30s', '60s', '90s', '120s',
+ '150s', '180s', '210s', '240s', '270s']
+
+DATA_MODES = ['MSK', 'DTMF', '5-Tone']
+
+ASCIIPART = ''.join([chr(x) for x in range(0x20, 0x7f)])
+DTMF = "0123456789ABCD*#"
+HEXADECIMAL = "0123456789ABCDEF"
+
+ROGER_BEEP = OFF_INT[0:11]
+BACKLIGHT_MODES = ["Off", "Auto", "On"]
+
+TONE_RESET_TIME = ['Off'] + ['%ds' % x for x in range(1, 256)]
+DTMF_TONE_RESET_TIME = TONE_RESET_TIME[0:16]
+
+DTMF_GROUPS = zip(["Off", "A", "B", "C", "D", "*", "#"], [255]+range(10, 16))
+FIVE_TONE_STANDARDS = ['ZVEI1', 'ZVEI2', 'CCIR1', 'CCITT']
+
+# should mimic the defaults in the memedit MemoryEditor somewhat
+# 0 1 2 3 4 5 6 7
+SANE_MEMORY_DEFAULT = b"\x14\x61\x00\x00\x14\x61\x00\x00" + \
+ b"\xff\xff\xff\xff\xc8\x00\xff\xff"
+# 8 9 A B C D E F
+
+
+# these two option sets are listed differently like this in the stock software,
+# so I'm keeping them separate for now if they are in fact identical
+# in behaviour, that should probably be amended
+DTMF_ALERT_TRANSPOND = zip(['Off', 'Call alert',
+ 'Transpond-alert',
+ 'Transpond-ID code'],
+ [255]+range(1, 4))
+FIVE_TONE_ALERT_TRANSPOND = zip(['Off', 'Alert tone',
+ 'Transpond', 'Transpond-ID code'],
+ [255]+range(1, 4))
+
+BFM_BANDS = ['87.5-108MHz', '76.0-91.0MHz', '76.0-108.0MHz', '65.0-76.0MHz']
+BFM_STRIDE = ['100kHz', '50kHz']
+
+
+def piperead(pipe, amount):
+ """read some data, catch exceptions, validate length of data read"""
+ try:
+ d = pipe.read(amount)
+ except Exception as e:
+ raise errors.RadioError(
+ "Tried to read %d bytes, but got an exception: %s" %
+ (amount, repr(e)))
+ if d is None:
+ raise errors.RadioError(
+ "Tried to read %d bytes, but read operation returned <None>." %
+ (amount))
+ if d is None or len(d) != amount:
+ raise errors.RadioError(
+ "Tried to read %d bytes, but got %d bytes instead." %
+ (amount, len(d)))
+ return d
+
+
+def pipewrite(pipe, data):
+ """write some data, catch exceptions, validate length of data written"""
+ try:
+ n = pipe.write(data)
+ except Exception as e:
+ raise errors.RadioError(
+ "Tried to write %d bytes, but got an exception: %s." %
+ (len(data), repr(e)))
+ if n is None:
+ raise errors.RadioError(
+ "Tried to write %d bytes, but operation returned <None>." %
+ (len(data)))
+ if n != len(data):
+ raise errors.RadioError(
+ "Tried to write %d bytes, but wrote %d bytes instead." %
+ (len(data), n))
+
+
+def attempt_initial_handshake(pipe):
+ """try to do the initial handshake"""
+ pipewrite(pipe, HANDSHAKE_OUT)
+ x = piperead(pipe, len(HANDSHAKE_IN[0]))
+ if x in HANDSHAKE_IN:
+ return True
+ LOG.debug("Handshake failed: received: %s expected one of: %s" %
+ (repr(x), repr(HANDSHAKE_IN)))
+ return False
+
+
+def initial_handshake(pipe, tries):
+ """do an initial handshake attempt up to tries times"""
+ x = False
+ for i in range(tries):
+ x = attempt_initial_handshake(pipe)
+ if x:
+ break
+ if not x:
+ raise errors.RadioError("Initial handshake failed all ten tries.")
+
+
+def mk_writecommand(addr):
+ """makes a write command from an address specification"""
+ return pack('>cHc', b'W', addr, b'@')
+
+
+def mk_readcommand(addr):
+ """makes a read command from an address specification"""
+ return pack('>cHc', b'R', addr, b'@')
+
+
+def expect_ack(pipe):
+ x = piperead(pipe, 1)
+ if x != b'\x06':
+ LOG.debug(
+ "Did not get ACK. received: %s, expected: '\\x06'" %
+ repr(x))
+ raise errors.RadioError("Did not get ACK when expected.")
+
+
+def end_communications(pipe):
+ """tell the radio that we are done"""
+ pipewrite(pipe, b'E')
+ expect_ack(pipe)
+
+
+def read_block(pipe, addr):
+ """read and return a chunk of data at specified address"""
+ r = mk_readcommand(addr)
+ w = mk_writecommand(addr)
+ pipewrite(pipe, r)
+ x = piperead(pipe, len(w))
+ if x != w:
+ raise errors.RadioError("Received data not following protocol.")
+ block = piperead(pipe, BLOCKSIZE)
+ return block
+
+
+def write_block(pipe, addr, block):
+ """write a chunk of data at specified address"""
+ w = mk_writecommand(addr)
+ pipewrite(pipe, w)
+ pipewrite(pipe, block)
+ expect_ack(pipe)
+
+
+def show_progress(radio, blockaddr, upper, msg):
+ """relay read/write information to the user through the gui"""
+ if radio.status_fn:
+ status = chirp_common.Status()
+ status.cur = blockaddr
+ status.max = upper
+ status.msg = msg
+ radio.status_fn(status)
+
+
+def do_download(radio):
+ """download from the radio to the memory map"""
+ initial_handshake(radio.pipe, 10)
+ memory = memmap.MemoryMap(b'\xff'*0x1000)
+ for blockaddr in range(LOWER_READ_BOUND, UPPER_READ_BOUND, BLOCKSIZE):
+ LOG.debug("Reading block "+str(blockaddr))
+ block = read_block(radio.pipe, blockaddr)
+ memory.set(blockaddr, block)
+ show_progress(radio, blockaddr, UPPER_READ_BOUND,
+ "Reading radio memory... %04x" % blockaddr)
+ end_communications(radio.pipe)
+ return memory
+
+
+def do_upload(radio):
+ """upload from the memory map to the radio"""
+ memory = radio.get_mmap()
+ initial_handshake(radio.pipe, 10)
+ for blockaddr in range(LOWER_WRITE_BOUND, UPPER_WRITE_BOUND, BLOCKSIZE):
+ LOG.debug("Writing block "+str(blockaddr))
+ block = memory[blockaddr:blockaddr+BLOCKSIZE]
+ write_block(radio.pipe, blockaddr, block)
+ show_progress(radio, blockaddr, UPPER_WRITE_BOUND,
+ "Writing radio memory... % 04x" % blockaddr)
+ end_communications(radio.pipe)
+
+
+def parse_tone(t):
+ """
+ parse the tone (ctss, dtcs) part of the mmap
+ into more easily handled data types
+ """
+ # [ mode, value, polarity ]
+ if int(t.high) == 0x3f and int(t.low) == 0xff:
+ return [None, None, None]
+ elif bool(t.digital):
+ t = ['DTCS',
+ (int(t.high) & 0x0f)*100 +
+ ((int(t.low) & 0xf0) >> 4)*10 +
+ (int(t.low) & 0x0f),
+ ['N', 'R'][bool(t.invert)]]
+ if t[1] not in chirp_common.DTCS_CODES:
+ return [None, None, None]
+ else:
+ t = ['Tone',
+ ((int(t.high) & 0xf0) >> 4)*100 +
+ (int(t.high) & 0x0f)*10 +
+ ((int(t.low) & 0xf0) >> 4) +
+ (int(t.low) & 0x0f)/10.0,
+ None]
+ if t[1] not in chirp_common.TONES:
+ return [None, None, None]
+ return t
+
+
+def unparse_tone(t):
+ """parse tone data back into the format used by the radio"""
+ # [ mode, value, polarity ]
+ if t[0] == 'Tone':
+ tint = int(t[1]*10)
+ t0, tint = tint % 10, tint // 10
+ t1, tint = tint % 10, tint // 10
+ t2, tint = tint % 10, tint // 10
+ high = (tint << 4) | t2
+ low = (t1 << 4) | t0
+ digital = False
+ invert = False
+ return digital, invert, high, low
+ elif t[0] == 'DTCS':
+ tint = int(t[1])
+ t0, tint = tint % 10, tint // 10
+ t1, tint = tint % 10, tint // 10
+ high = tint
+ low = (t1 << 4) | t0
+ digital = True
+ invert = t[2] == 'R'
+ return digital, invert, high, low
+ return None
+
+
+def decode_halfbytes(data, mapping, length):
+ """
+ construct a string from a datatype
+ where each half-byte maps to a character
+ """
+ s = ''
+ for i in range(length):
+ if i & 1 == 0:
+ s += mapping[(int(data[i >> 1]) & 0xf0) >> 4]
+ else:
+ s += mapping[int(data[i >> 1]) & 0x0f]
+ return s
+
+
+def encode_halfbytes(data, datapad, mapping, fillvalue, fieldlen):
+ """encode data from a string where each character maps to a half-byte"""
+ if len(data) & 1:
+ # pad to an even length
+ data += datapad
+ o = [fillvalue] * fieldlen
+ for i in range(0, len(data), 2):
+ v = (mapping.index(data[i]) << 4) | mapping.index(data[i+1])
+ o[i >> 1] = v
+ return bytearray(o)
+
+
+def decode_ffstring(data):
+ """decode a string delimited by 0xff"""
+ s = ''
+ for b in data:
+ if int(b) == 0xff:
+ break
+ s += chr(int(b))
+ return s
+
+
+def encode_ffstring(data, fieldlen):
+ """right-pad to specified length with 0xff bytes"""
+ extra = fieldlen-len(data)
+ if extra > 0:
+ data += '\xff'*extra
+ return bytearray(data)
+
+
+def decode_dtmf(data, length):
+ """decode a field containing dtmf data into a string"""
+ if length == 0xff:
+ return ''
+ return decode_halfbytes(data, DTMF, length)
+
+
+def encode_dtmf(data, length, fieldlen):
+ """encode a string containing dtmf characters into a data field"""
+ return encode_halfbytes(data, '0', DTMF, b'\xff', fieldlen)
+
+
+def decode_5tone(data):
+ """decode a field containing 5-tone data into a string"""
+ if (int(data[2]) & 0x0f) != 0:
+ return ''
+ return decode_halfbytes(data, HEXADECIMAL, 5)
+
+
+def encode_5tone(data, fieldlen):
+ """encode a string containing 5-tone characters into a data field"""
+ return encode_halfbytes(data, '0', HEXADECIMAL, b'\xff', fieldlen)
+
+
+def decode_freq(data):
+ """decode frequency data for the broadcast fm radio memories"""
+ data_out = ''
+ if data[0] != 0xff:
+ data_out = chirp_common.format_freq(
+ int(decode_halfbytes(data, "0123456789", len(data)))*100000)
+ return data_out
+
+
+def encode_freq(data, fieldlen):
+ """encode frequency data for the broadcast fm radio memories"""
+ data_out = bytearray('\xff')*fieldlen
+ if data != '':
+ data_out = encode_halfbytes((('%%0%di' % (fieldlen << 1)) %
+ int(chirp_common.parse_freq(data)/10)),
+ '', '0123456789', '', fieldlen)
+ return data_out
+
+
+def sbyn(s, n):
+ """setting by name"""
+ return filter(lambda x: x.get_name() == n, s)[0]
+
+
+# These helper classes provide a direct link between the value
+# of the widget shown in the ui, and the setting in the memory
+# map of the radio, lessening the need to write large chunks
+# of code, first for populating the ui from the memory map,
+# then secondly for parsing the values back.
+# By supplying the memory map entry to the setting instance,
+# it is possible to automatically 1) initialize the value of
+# the setting, as well as 2) automatically update the memory
+# value when the user changes it in the ui, without adding
+# any code outside the class.
+class MappedIntegerSettingValue(settings.RadioSettingValueInteger):
+ """"
+ Integer setting, with the possibility to add translation
+ functions between memory map <-> integer setting
+ """
+ def __init__(self, val_mem, minval, maxval, step=1,
+ int_from_mem=lambda x: int(x),
+ mem_from_int=lambda x: x,
+ autowrite=True):
+ """
+ val_mem - memory map entry for the value
+ minval - the minimum value allowed
+ maxval - maximum value allowed
+ step - value stepping
+ int_from_mem - function to convert memory entry to integer
+ mem_from_int - function to convert integer to memory entry
+ autowrite - automatically write the memory map entry
+ when the value is changed
+ """
+ self._val_mem = val_mem
+ self._int_from_mem = int_from_mem
+ self._mem_from_int = mem_from_int
+ self._autowrite = autowrite
+ settings.RadioSettingValueInteger.__init__(
+ self,
+ minval, maxval, self._int_from_mem(val_mem), step)
+
+ def set_value(self, x):
+ settings.RadioSettingValueInteger.set_value(self, x)
+ if self._autowrite:
+ self.write_mem()
+
+ def write_mem(self):
+ if self.get_mutable() and self._mem_from_int is not None:
+ self._val_mem.set_value(self._mem_from_int(
+ settings.RadioSettingValueInteger.get_value(self)))
+
+
+class MappedListSettingValue(settings.RadioSettingValueMap):
+ """Mapped list setting"""
+ def __init__(self, val_mem, options, autowrite=True):
+ """
+ val_mem - memory map entry for the value
+ options - either a list of strings options to present,
+ mapped to integers 0...n
+ in the memory map entry, or a list of tuples
+ ("option description", memory map value)
+ int_from_mem - function to convert memory entry to integer
+ mem_from_int - function to convert integer to memory entry
+ autowrite - automatically write the memory map entry when
+ the value is changed
+ """
+ self._val_mem = val_mem
+ self._autowrite = autowrite
+ if not isinstance(options[0], tuple):
+ options = zip(options, range(len(options)))
+ settings.RadioSettingValueMap.__init__(
+ self,
+ options, mem_val=int(val_mem))
+
+ def set_value(self, value):
+ settings.RadioSettingValueMap.set_value(self, value)
+ if self._autowrite:
+ self.write_mem()
+
+ def write_mem(self):
+ if self.get_mutable():
+ self._val_mem.set_value(
+ settings.RadioSettingValueMap.get_mem_val(self))
+
+
+class MappedCodedStringSettingValue(settings.RadioSettingValueString):
+ """
+ generic base class for a number of mapped presented-as-strings
+ values which may need conversion between mem and string,
+ and may store a length value in a separate mem field
+ """
+ def __init__(self, val_mem, len_mem, min_length, max_length,
+ charset=ASCIIPART, padchar=' ', autowrite=True,
+ str_from_mem=lambda mve, lve: str(mve[0:int(lve)]),
+ mem_val_from_str=lambda s, fl: s[0:fl],
+ mem_len_from_int=lambda l: l):
+ """
+ val_mem - memory map entry for the value
+ len_mem - memory map entry for the length (or None)
+ min_length - length that the string will be right-padded to
+ max_length - maximum length of the string, set as maxlength
+ for the RadioSettingValueString
+ charset - the allowed charset
+ padchar - the character that will be used to pad short
+ strings, if not in the charset, charset[0] is used
+ autowrite - automatically call write_mem when the ui value
+ change
+ str_from_mem - function to convert from memory entry to string
+ value, form:
+ func(value_entry, length_entry or none) -> string
+ mem_val_from_str - function to convert from string value to
+ memory-fitting value, form:
+ func(string, value_entry_length) ->
+ value to store in value entry
+ mem_len_from_int - function to convert from string length to
+ memory-fitting value, form:
+ func(stringlength) -> value to store in length entry
+ """
+ self._min_length = min_length
+ self._val_mem = val_mem
+ self._len_mem = len_mem
+ self._padchar = padchar
+ if padchar not in charset:
+ self._padchar = charset[0]
+ self._autowrite = autowrite
+ self._str_from_mem = str_from_mem
+ self._mem_val_from_str = mem_val_from_str
+ self._mem_len_from_int = mem_len_from_int
+ settings.RadioSettingValueString.__init__(
+ self,
+ 0, max_length,
+ self._str_from_mem(self._val_mem, self._len_mem),
+ charset=charset, autopad=False)
+
+ def set_value(self, value):
+ """
+ Set the value of the string, pad if below minimum length,
+ unless it's '' to provide a distinction between
+ uninitialized/reset data and needs-to-be-padded data
+ """
+ while len(value) < self._min_length and len(value) != 0:
+ value += self._padchar
+ settings.RadioSettingValueString.set_value(self, value)
+ if self._autowrite:
+ self.write_mem()
+
+ def write_mem(self):
+ """update the memory"""
+ if not self.get_mutable() or self._mem_val_from_str is None:
+ return
+ v = self.get_value()
+ l = len(v)
+ self._val_mem.set_value(self._mem_val_from_str(v, len(self._val_mem)))
+ if self._len_mem is not None and self._mem_len_from_int is not None:
+ self._len_mem.set_value(self._mem_len_from_int(l))
+
+
+class MappedFFStringSettingValue(MappedCodedStringSettingValue):
+ """
+ Mapped string setting, tailored for the puxing px888k,
+ which uses 0xff terminated strings.
+ """
+ def __init__(self, val_mem, min_length, max_length,
+ charset=ASCIIPART, padchar=' ', autowrite=True):
+ MappedCodedStringSettingValue.__init__(
+ self,
+ val_mem, None, min_length, max_length,
+ charset=charset, padchar=padchar, autowrite=autowrite,
+ str_from_mem=lambda mve, lve: decode_ffstring(mve),
+ mem_val_from_str=lambda s, fl: encode_ffstring(s, fl),
+ mem_len_from_int=None)
+
+
+class MappedDTMFStringSettingValue(MappedCodedStringSettingValue):
+ """
+ Mapped string setting, tailored for the puxing px888k
+ field pairs (value and length) storing DTMF codes
+ """
+ def __init__(self, val_mem, len_mem, min_length, max_length,
+ autowrite=True):
+ MappedCodedStringSettingValue.__init__(
+ self,
+ val_mem, len_mem, min_length, max_length,
+ charset=DTMF, padchar='0', autowrite=autowrite,
+ str_from_mem=lambda mve, lve: decode_dtmf(mve, lve),
+ mem_val_from_str=lambda s, fl: encode_dtmf(s, len(s), fl))
+
+
+class MappedFiveToneStringSettingValue(MappedCodedStringSettingValue):
+ """
+ Mapped string setting, tailored for the puxing px888k
+ fields storing 5-Tone codes
+ """
+ def __init__(self, val_mem, autowrite=True):
+ MappedCodedStringSettingValue.__init__(
+ self,
+ val_mem, None, 0, 5, charset=HEXADECIMAL, padchar='0',
+ autowrite=autowrite,
+ str_from_mem=lambda mve, lve: decode_5tone(mve),
+ mem_val_from_str=lambda s, fl: encode_5tone(s, fl),
+ mem_len_from_int=None)
+
+
+class MappedFreqStringSettingValue(MappedCodedStringSettingValue):
+ """
+ Mapped string setting, tailored for the puxing px888k
+ fields for the broadcast FM radio frequencies
+ """
+ def __init__(self, val_mem, autowrite=True):
+ MappedCodedStringSettingValue.__init__(
+ self,
+ val_mem, None, 0, 128, charset=ASCIIPART, padchar=' ',
+ autowrite=autowrite,
+ str_from_mem=lambda mve, lve: decode_freq(mve),
+ mem_val_from_str=lambda s, fl: encode_freq(s, fl))
+
+
+# These functions lessen the amount of boilerplate on the form
+# x = RadioSetting("AAA", "BBB", SomeKindOfRadioSettingValue( ... ))
+# to
+# x = some_kind_of_setting("AAA", "BBB", ... )
+def integer_setting(k, n, *args, **kwargs):
+ return settings.RadioSetting(
+ k, n,
+ MappedIntegerSettingValue(*args, **kwargs))
+
+
+def list_setting(k, n, *args, **kwargs):
+ return settings.RadioSetting(
+ k, n,
+ MappedListSettingValue(*args, **kwargs))
+
+
+def ff_string_setting(k, n, *args, **kwargs):
+ return settings.RadioSetting(
+ k, n,
+ MappedFFStringSettingValue(*args, **kwargs))
+
+
+def dtmf_string_setting(k, n, *args, **kwargs):
+ return settings.RadioSetting(
+ k, n,
+ MappedDTMFStringSettingValue(*args, **kwargs))
+
+
+def five_tone_string_setting(k, n, *args, **kwargs):
+ return settings.RadioSetting(
+ k, n,
+ MappedFiveToneStringSettingValue(*args, **kwargs))
+
+
+def frequency_setting(k, n, *args, **kwargs):
+ return settings.RadioSetting(
+ k, n,
+ MappedFreqStringSettingValue(*args, **kwargs))
+
+
+ at directory.register
+class Puxing_PX888K_Radio(chirp_common.CloneModeRadio):
+ """Puxing PX-888K"""
+ VENDOR = "Puxing"
+ MODEL = "PX-888K"
+ BAUD_RATE = 9600
+
+ @classmethod
+ def match_model(cls, filedata, filename):
+ if len(filedata) == UPPER_READ_BOUND:
+ if filedata[FILE_MAGIC[0]:FILE_MAGIC[1]] == FILE_MAGIC[2]:
+ return True
+ else:
+ LOG.debug("The data at 0x0c40 does not match the PX-888K")
+ else:
+ LOG.debug("The file size does not match.")
+ return False
+
+ def get_features(self):
+ rf = chirp_common.RadioFeatures()
+ rf.has_bank_index = False
+ rf.has_dtcs = True
+ rf.has_ctone = True
+ rf.has_rx_dtcs = True
+ rf.has_dtcs_polarity = True
+ rf.has_mode = True
+ rf.has_offset = True
+ rf.has_name = True
+ rf.has_bank = False
+ rf.has_bank_names = False
+ rf.has_tuning_step = False
+ rf.has_cross = True
+ rf.has_infinite_number = False
+ rf.has_nostep_tuning = False
+ rf.has_comment = False
+ rf.has_settings = True
+ if SUPPORT_NONSPLIT_DUPLEX_ONLY:
+ rf.can_odd_split = False
+ else:
+ rf.can_odd_split = True
+
+ rf.valid_modes = MODES
+ rf.valid_tmodes = TONE_MODES
+ rf.valid_duplexes = DUPLEX_MODES
+ rf.valid_bands = BANDS
+ rf.valid_skips = SKIP_MODES
+ rf.valid_power_levels = POWER_LEVELS
+ rf.valid_characters = ASCIIPART
+ rf.valid_name_length = 6
+ rf.valid_cross_modes = CROSS_MODES
+ rf.memory_bounds = (1, 128)
+ rf.valid_special_chans = SPECIAL_CHANNELS.keys()
+ return rf
+
+ def sync_in(self):
+ self._mmap = do_download(self)
+ self.process_mmap()
+
+ def process_mmap(self):
+ self._memobj = bitwise.parse(MEM_FORMAT, self._mmap)
+
+ def sync_out(self):
+ do_upload(self)
+
+ def _set_sane_defaults(self, data):
+ # thanks thayward!
+ data.set_raw(SANE_MEMORY_DEFAULT)
+
+ def _uninitialize(self, data, n):
+ if isinstance(data, bitwise.arrayDataElement):
+ data.set_value(b"\xff"*n)
+ else:
+ data.set_raw(b"\xff"*n)
+
+ def _get_memory_structs(self, number):
+ """
+ fetch the correct data structs no matter
+ if its regular or special channels,
+ no matter if they're referred by name or channel index
+ """
+ index = 2501
+ i = -42
+ designator = 'INVALID'
+ isregular = False
+ iscall = False
+ isvfo = False
+ _data = None
+ _name = None
+ _present = None
+ _priority = None
+ if number in SPECIAL_NUMBERS.keys():
+ index = number
+ # speical by index
+ designator = SPECIAL_NUMBERS[number]
+ elif number in SPECIAL_CHANNELS.keys():
+ # special by name
+ index = SPECIAL_CHANNELS[number]
+ designator = number
+ elif number > 0:
+ # regular by number
+ index = number
+ designator = number
+
+ if index < 0:
+ isvfo = True
+ _data = self._memobj.mem.vfo_data[index+2]
+ elif index == 0:
+ iscall = True
+ _data = self._memobj.mem.call_channel
+ elif index > 0:
+ isregular = True
+ i = number - 1
+ _data = self._memobj.mem.channel_memory.data[i]
+ _name = self._memobj.mem.channel_memory.names[i].entry
+ _present = self._memobj.mem.channel_memory.present[
+ (i & 0x78) | (7-(i & 0x07))]
+ _priority = self._memobj.mem.channel_memory.priority[
+ (i & 0x78) | (7-(i & 0x07))]
+
+ if _data == bytearray(0xff)*16:
+ self._set_sane_defaults(_data)
+
+ return (index, designator,
+ _data, _name, _present, _priority,
+ isregular, isvfo, iscall)
+
+ def get_raw_memory(self, number):
+ x = self._get_memory_structs(number)
+ return repr(x[2])
+
+ def get_memory(self, number):
+ mem = chirp_common.Memory()
+ (index, designator,
+ _data, _name, _present, _priority,
+ isregular, isvfo, iscall) = self._get_memory_structs(number)
+
+ mem.number = index
+ mem.extd_number = designator
+
+ # handle empty channels
+ if isregular:
+ if bool(_present):
+ mem.empty = False
+ mem.name = str(decode_ffstring(_name))
+ mem.skip = SKIP_MODES[1-int(_priority)]
+ else:
+ mem.empty = True
+ mem.name = ''
+ return mem
+ else:
+ mem.empty = False
+ mem.name = ''
+
+ # get frequency data
+ mem.freq = int(_data.rx_freq)*10
+ mem.offset = int(_data.tx_freq)*10
+
+ # interpret frequency data
+ # only the vfo channels support duplex,
+ # memory channels operate in split mode all the time
+ if isvfo:
+ mem.duplex = DUPLEX_MODES[int(_data.duplex_sign)]
+ if mem.duplex == '-':
+ mem.offset = mem.freq - mem.offset
+ elif mem.duplex == '':
+ mem.offset = 0
+ elif mem.duplex == '+':
+ mem.offset = mem.offset - mem.freq
+ else:
+ if mem.freq == mem.offset:
+ mem.duplex = ''
+ mem.offset = 0
+ elif SUPPORT_NONSPLIT_DUPLEX_ONLY or \
+ SUPPORT_SPLIT_BUT_DEFAULT_TO_NONSPLIT_ALWAYS:
+ if mem.freq > mem.offset:
+ mem.offset = mem.freq - mem.offset
+ mem.duplex = '-'
+ elif mem.freq < mem.offset:
+ mem.offset = mem.offset - mem.freq
+ mem.duplex = '+'
+ else:
+ mem.duplex = 'split'
+
+ # get tone data
+ txtone = parse_tone(_data.tone[0])
+ rxtone = parse_tone(_data.tone[1])
+ chirp_common.split_tone_decode(mem, txtone, rxtone)
+
+######################################################################
+ if ENABLE_DANGEROUS_EXPERIMENTAL_FEATURES:
+ # override certain settings based on flags
+ # that we have set in junk areas of the memory
+ # or basically, we BELIEVE this to be junk memory,
+ # hence why it's experimental and dangerous
+ if bool(_data.experimental_cross_mode_indicator) is False:
+ if mem.tmode == 'Tone':
+ mem.cross_mode = 'Tone->'
+ elif mem.tmode == 'TSQL':
+ mem.cross_mode = 'Tone->Tone'
+ elif mem.tmode == 'DTCS':
+ mem.cross_mode = 'DTCS->DTCS'
+ mem.tmode = 'Cross'
+######################################################################
+
+ # transmit mode and power level
+ mem.mode = MODES[bool(_data.modulation_width)]
+ mem.power = POWER_LEVELS[_data.txpower]
+
+ # extra channel settings
+ mem.extra = settings.RadioSettingGroup(
+ "extra",
+ "extra",
+ list_setting("Busy channel lockout",
+ "BCL",
+ _data.bcl,
+ BCL_MODES),
+ list_setting("Swap transmit and receive frequencies",
+ "Tx Rx freq swap",
+ _data.txrx_reverse,
+ OFF_ON),
+ list_setting("Use compander",
+ "Use compander",
+ _data.compander,
+ OFF_ON),
+ list_setting("Use scrambler", "Use scrambler",
+ _data.use_scrambler,
+ NO_YES),
+ list_setting("Scrambler selection",
+ "Voice Scrambler",
+ _data.scrambler_type,
+ SCRAMBLER_MODES),
+ list_setting("Send ID code before and/or after transmitting",
+ "PTT ID",
+ _data.ptt_id_edge,
+ PTT_ID_EDGES),
+ list_setting("Optional signal before/after transmission, " +
+ "this setting overrides the PTT ID setting.",
+ "Opt Signal",
+ _data.opt_signal,
+ OPTSIGN_MODES))
+
+######################################################################
+ if ENABLE_DANGEROUS_EXPERIMENTAL_FEATURES:
+ # override certain settings based on flags
+ # that we have set in junk areas of the memory
+ # or basically, we BELIEVE this to be junk memory,
+ # hence why it's experimental and dangerous
+ if bool(_data.experimental_duplex_mode_indicator) is False:
+ # if this flag is set, this means that we in the gui
+ # have set the duplex mode to something
+ # the channel does not really support,
+ # such as split modes for vfo channels,
+ # and non-split modes for the memory channels
+ mem.duplex = DUPLEX_MODES[int(_data.duplex_sign)]
+ mem.freq = int(_data.rx_freq)*10
+ mem.offset = int(_data.tx_freq)*10
+ if isvfo:
+ # we want split, so we have to reconstruct it
+ # from -/0/+ modes
+ if mem.duplex == '-':
+ mem.offset = mem.freq - mem.offset
+ elif mem.duplex == '':
+ mem.offset = mem.freq
+ elif mem.duplex == '+':
+ mem.offset = mem.freq + mem.offset
+ mem.duplex = 'split'
+ else:
+ # we want -/0/+, so we have to reconstruct it
+ # from split modes
+ if mem.freq > mem.offset:
+ mem.offset = mem.freq - mem.offset
+ mem.duplex = '-'
+ elif mem.freq < mem.offset:
+ mem.offset = mem.offset - mem.freq
+ mem.duplex = '+'
+ else:
+ mem.offset = 0
+ mem.duplex = ''
+######################################################################
+
+ return mem
+
+ def set_memory(self, mem):
+ (index, designator,
+ _data, _name, _present, _priority,
+ isregular, isvfo, iscall) = self._get_memory_structs(mem.number)
+ mem.number = index
+ mem.extd_number = designator
+
+ # handle empty channels
+ if mem.empty:
+ if isregular:
+ _present.set_value(False)
+ _priority.set_value(False)
+ self._uninitialize(_data, 16)
+ self._uninitialize(_name, 6)
+ else:
+ raise errors.InvalidValueError(
+ "Can't remove CALL and/or VFO channels!")
+ return
+
+ # handle regular channel stuff like name and present+priority flags
+ if isregular:
+ if not bool(_present):
+ self._set_sane_defaults(_data)
+ _name.set_value(
+ encode_ffstring(self.filter_name(mem.name), len(_name)))
+ _present.set_value(True)
+ _priority.set_value(1-SKIP_MODES.index(mem.skip))
+
+ # frequency data
+ rxf = int(mem.freq/10)
+ txf = int(mem.offset/10)
+
+ _data.rx_freq.set_value(rxf)
+
+ if isvfo:
+ # fake split modes on write, for channels
+ # that do not support it, which are some
+ # (the two vfo channels)
+ if mem.duplex == 'split':
+ for band in BANDS:
+ rb = mem.freq in range(band[0], band[1])
+ tb = mem.offset in range(band[0], band[1])
+ if rb != tb:
+ raise errors.InvalidValueError(
+ "VFO frequencies should be on the same band")
+ if rxf < txf:
+ _data.duplex_sign.set_value(1)
+ _data.tx_freq.set_value(txf - rxf)
+ elif rxf > txf:
+ _data.duplex_sign.set_value(2)
+ _data.tx_freq.set_value(rxf-txf)
+ else:
+ _data.duplex_sign.set_value(0)
+ _data.tx_freq.set_value(0)
+ else:
+ _data.duplex_sign.set_value(DUPLEX_MODES.index(mem.duplex))
+ _data.tx_freq.set_value(txf)
+
+######################################################################
+ if ENABLE_DANGEROUS_EXPERIMENTAL_FEATURES:
+ if mem.duplex == 'split':
+ _data.experimental_duplex_mode_indicator.set_value(0)
+ else:
+ _data.experimental_duplex_mode_indicator.set_value(1)
+######################################################################
+
+ else:
+ # fake duplex modes on write, for channels
+ # that do not support it, which are most
+ # (all the memory channels)
+ if mem.duplex == '' or mem.duplex is None:
+ _data.tx_freq.set_value(rxf)
+ elif mem.duplex == '+':
+ _data.tx_freq.set_value(rxf + txf)
+ elif mem.duplex == '-':
+ _data.tx_freq.set_value(rxf - txf)
+ else:
+ _data.tx_freq.set_value(txf)
+
+######################################################################
+ if ENABLE_DANGEROUS_EXPERIMENTAL_FEATURES:
+ if mem.duplex != 'split':
+ _data.experimental_duplex_mode_indicator.set_value(0)
+ else:
+ _data.experimental_duplex_mode_indicator.set_value(1)
+######################################################################
+
+ # tone data
+ tonedata = chirp_common.split_tone_encode(mem)
+ for i in range(2):
+ dihl = unparse_tone(tonedata[i])
+ if dihl is not None:
+ _data.tone[i].digital.set_value(dihl[0])
+ _data.tone[i].invert.set_value(dihl[1])
+ _data.tone[i].high.set_value(dihl[2])
+ _data.tone[i].low.set_value(dihl[3])
+ else:
+ _data.tone[i].digital.set_value(1)
+ _data.tone[i].invert.set_value(1)
+ _data.tone[i].high.set_value(0x3f)
+ _data.tone[i].low.set_value(0xff)
+
+######################################################################
+ if ENABLE_DANGEROUS_EXPERIMENTAL_FEATURES:
+ if mem.tmode == 'Cross' and mem.cross_mode in ['Tone->',
+ 'Tone->Tone',
+ 'DTCS->DTCS']:
+ _data.experimental_cross_mode_indicator.set_value(0)
+ else:
+ _data.experimental_cross_mode_indicator.set_value(1)
+######################################################################
+
+ # transmit mode and power level
+ _data.modulation_width.set_value(MODES.index(mem.mode))
+ if str(mem.power) == 'High':
+ _data.txpower.set_value(1)
+ else:
+ _data.txpower = 0
+
+ def get_settings(self):
+ _model = self._memobj.mem.model_information
+ _settings = self._memobj.mem.opt_settings
+ _ptt_id_data = self._memobj.mem.ptt_id_data
+ _msk_settings = self._memobj.mem.msk_settings
+ _dtmf_settings = self._memobj.mem.dtmf_settings
+ _5tone_settings = self._memobj.mem.five_tone_settings
+ _broadcast = self._memobj.mem.fm_radio
+
+ # for safety reasons we are showing these as read-only
+ model_unit_settings = [
+ integer_setting("vhflo", "VHF lower bound",
+ _model.band_limits[0].lower_freq,
+ 134, 176,
+ int_from_mem=lambda x:int(int(x)/10),
+ mem_from_int=None),
+ integer_setting("vhfhi", "VHF upper bound",
+ _model.band_limits[0].upper_freq,
+ 134, 176,
+ int_from_mem=lambda x:int(int(x)/10),
+ mem_from_int=None),
+ integer_setting("uhflo", "UHF lower bound",
+ _model.band_limits[1].lower_freq,
+ 400, 480,
+ int_from_mem=lambda x:int(int(x)/10),
+ mem_from_int=None),
+ integer_setting("uhfhi", "UHF upper bound",
+ _model.band_limits[1].upper_freq,
+ 400, 480,
+ int_from_mem=lambda x:int(int(x)/10),
+ mem_from_int=None),
+ ff_string_setting("model", "Model string",
+ _model.model_string,
+ 0, 6)
+
+ ]
+ for s in model_unit_settings:
+ s.value.set_mutable(False)
+ model_unit_settings.append(ff_string_setting(
+ "info", "Unit Information",
+ self._memobj.mem.radio_information_string,
+ 0, 16))
+
+ # tx/rx related settings
+ radio_channel_settings = [
+ list_setting("vfostep", "VFO step size",
+ _settings.vfo_step,
+ VFO_STRIDE),
+ list_setting("abwatch", "Main watch",
+ _settings.main_watch,
+ AB),
+ list_setting("watchmade", "Watch mode",
+ _settings.main_watch,
+ WATCH_MODES),
+ list_setting("amode", "A mode",
+ _settings.workmode_a,
+ AB_MODES),
+ list_setting("bmode", "B mode",
+ _settings.workmode_b,
+ AB_MODES),
+ integer_setting("achan", "A channel index",
+ _settings.channel_a,
+ 1, 128,
+ int_from_mem=lambda i:i+1,
+ mem_from_int=lambda i:i-1),
+ integer_setting("bchan", "B channel index",
+ _settings.channel_b,
+ 1, 128,
+ int_from_mem=lambda i:i+1,
+ mem_from_int=lambda i:i-1),
+ integer_setting("pchan", "Priority channel index",
+ _settings.priority_channel,
+ 1, 128, int_from_mem=lambda i:i+1,
+ mem_from_int=lambda i:i-1),
+ list_setting("cactive", "Call channel active?",
+ _settings.call_channel_active,
+ NO_YES),
+ list_setting("scanm", "Scan mode",
+ _settings.scan_mode,
+ SCAN_MODES),
+ list_setting("swait", "Wait time",
+ _settings.wait_time,
+ WAIT_TIMES),
+ # it is unclear what this option below does,
+ # possibly squelch tail elimination?
+ list_setting("tail", "Relay without disable tail (?)",
+ _settings.relay_without_disable_tail,
+ NO_YES),
+ list_setting("batsav", "Battery saving mode",
+ _settings.battery_save,
+ OFF_ON),
+ ]
+
+ # user interface related settings
+ interface_settings = [
+ list_setting("sidehold", "Side button hold action",
+ _settings.side_button_hold_mode,
+ BUTTON_MODES),
+ list_setting("sideclick", "Side button click action",
+ _settings.side_button_click_mode,
+ BUTTON_MODES),
+ list_setting("bootmt", "Boot message type",
+ _settings.boot_message_mode,
+ BOOT_MESSAGE_TYPES),
+ ff_string_setting("bootm", "Boot message",
+ _settings.boot_message,
+ 0, 6),
+ list_setting("beep", "Key beep",
+ _settings.key_beep,
+ OFF_ON),
+ list_setting("talkback", "Menu talkback",
+ _settings.voice_announce,
+ TALKBACK),
+ list_setting("sidetone", "DTMF sidetone",
+ _settings.dtmf_sidetone,
+ OFF_ON),
+ list_setting("roger", "Roger beep",
+ _settings.use_roger_beep,
+ ROGER_BEEP),
+ list_setting("backlm", "Backlight mode",
+ _settings.backlight_mode,
+ BACKLIGHT_MODES),
+ list_setting("backlc", "Backlight color",
+ _settings.backlight_color,
+ BACKLIGHT_COLORS),
+ integer_setting("squelch", "Squelch level",
+ _settings.squelch_level,
+ 0, 9),
+ list_setting("voxg", "Vox gain",
+ _settings.vox_gain,
+ VOX_GAIN),
+ list_setting("voxd", "Vox delay",
+ _settings.vox_delay,
+ VOX_DELAYS),
+ list_setting("txal", "Trinsmit time alarm",
+ _settings.tx_timeout,
+ TRANSMIT_ALARMS),
+ ]
+
+ # settings related to tone/data sending and interpretation
+ data_general_settings = [
+ list_setting("disptt", "Display PTT ID",
+ _settings.dis_ptt_id,
+ NO_YES),
+ list_setting("pttidt", "PTT ID signal type",
+ _settings.ptt_id_type,
+ DATA_MODES)
+ ]
+
+ data_msk_settings = [
+ ff_string_setting("bot", "MSK PTT ID (BOT)",
+ _ptt_id_data[0].entry,
+ 0, 6, autowrite=False),
+ ff_string_setting("eot", "MSK PTT ID (EOT)",
+ _ptt_id_data[1].entry,
+ 0, 6, autowrite=False),
+ ff_string_setting("id", "MSK ID code",
+ _msk_settings.id_code,
+ 0, 4, charset=HEXADECIMAL),
+ list_setting("mskr", "MSK reverse",
+ _settings.msk_reverse,
+ NO_YES)
+ ]
+
+ data_dtmf_settings = [
+ dtmf_string_setting("bot", "DTMF PTT ID (BOT)",
+ _ptt_id_data[0].entry,
+ _ptt_id_data[0].length,
+ 0, 8, autowrite=False),
+ dtmf_string_setting("eot", "DTMF PTT ID (EOT)",
+ _ptt_id_data[1].entry,
+ _ptt_id_data[1].length,
+ 0, 8, autowrite=False),
+ dtmf_string_setting("id", "DTMF ID code",
+ _dtmf_settings.id_code,
+ _dtmf_settings.id_code_length,
+ 3, 8),
+
+ integer_setting("time", "Digit time (ms)",
+ _dtmf_settings.timing.digit_length,
+ 50, 200, step=10,
+ int_from_mem=lambda x:x*10,
+ mem_from_int=lambda x:int(x/10)),
+ integer_setting("pause", "Inter digit time (ms)",
+ _dtmf_settings.timing.digit_length,
+ 50, 200, step=10,
+ int_from_mem=lambda x:x*10,
+ mem_from_int=lambda x:int(x/10)),
+ integer_setting("time1", "First digit time (ms)",
+ _dtmf_settings.timing.digit_length,
+ 50, 200, step=10,
+ int_from_mem=lambda x:x*10,
+ mem_from_int=lambda x:int(x/10)),
+ integer_setting("pause1", "First digit delay (ms)",
+ _dtmf_settings.timing.digit_length,
+ 100, 1000, step=50,
+ int_from_mem=lambda x:x*50,
+ mem_from_int=lambda x:int(x/50)),
+
+ list_setting("arst", "Auto reset time",
+ _dtmf_settings.reset_time,
+ DTMF_TONE_RESET_TIME),
+ list_setting("grp", "Group code",
+ _dtmf_settings.group_code,
+ DTMF_GROUPS),
+ dtmf_string_setting("stunt", "TX Stun code",
+ _dtmf_settings.tx_stun_code,
+ _dtmf_settings.tx_stun_code_length,
+ 3, 8),
+ dtmf_string_setting("cstunt", "TX Stun cancel code",
+ _dtmf_settings.cancel_tx_stun_code,
+ _dtmf_settings.cancel_tx_stun_code_length,
+ 3, 8),
+ dtmf_string_setting("stunrt", "RX/TX Stun code",
+ _dtmf_settings.rxtx_stun_code,
+ _dtmf_settings.rxtx_stun_code_length,
+ 3, 8),
+ dtmf_string_setting("cstunrt", "RX/TX Stun cancel code",
+ _dtmf_settings.cancel_rxtx_stun_code,
+ _dtmf_settings.cancel_rxtx_stun_code_length,
+ 3, 8),
+ list_setting("altr", "Alert/Transpond",
+ _dtmf_settings.alert_transpond,
+ DTMF_ALERT_TRANSPOND),
+ ]
+
+ data_5tone_settings = [
+ five_tone_string_setting("bot",
+ "5-Tone PTT ID (BOT)",
+ _ptt_id_data[0].entry,
+ autowrite=False),
+ five_tone_string_setting("eot",
+ "5-Tone PTT ID (EOT)",
+ _ptt_id_data[1].entry,
+ autowrite=False),
+ five_tone_string_setting("id",
+ "5-tone ID code",
+ _5tone_settings.id_code),
+ list_setting("arst", "Auto reset time",
+ _5tone_settings.reset_time,
+ TONE_RESET_TIME),
+ five_tone_string_setting("stunt",
+ "TX Stun code",
+ _5tone_settings.tx_stun_code),
+ five_tone_string_setting("cstunt",
+ "TX Stun cancel code",
+ _5tone_settings.cancel_tx_stun_code),
+ five_tone_string_setting("stunrt",
+ "RX/TX Stun code",
+ _5tone_settings.rxtx_stun_code),
+ five_tone_string_setting("cstunrt",
+ "RX/TX Stun cancel code",
+ _5tone_settings.cancel_rxtx_stun_code),
+ list_setting("altr", "Alert/Transpond",
+ _5tone_settings.alert_transpond,
+ FIVE_TONE_ALERT_TRANSPOND),
+ list_setting("std", "5-Tone standard",
+ _5tone_settings.tone_standard,
+ FIVE_TONE_STANDARDS),
+ ]
+ for i in range(4):
+ s = ['z1', 'z2', 'c1', 'ct'][i]
+ l = FIVE_TONE_STANDARDS[i]
+ data_5tone_settings.append(
+ settings.RadioSettingGroup(
+ s, '%s settings' % l,
+ integer_setting("%speriod" % s, "%s Period (ms)" % l,
+ _5tone_settings.tone_settings[i].period,
+ 20, 255),
+ list_setting("%sgrp" % s, "%s Group code" % l,
+ _5tone_settings.tone_settings[i].group_code,
+ HEXADECIMAL),
+ list_setting("%srpt" % s, "%s Repeat code" % l,
+ _5tone_settings.tone_settings[i].repeat_code,
+ HEXADECIMAL)))
+
+ data_msk_call_list = []
+ data_dtmf_call_list = []
+ data_5tone_call_list = []
+ for i in range(9):
+ j = i+1
+ data_msk_call_list.append(
+ ff_string_setting("ce%d" % i,
+ "MSK call entry %d" % j,
+ _msk_settings.phone_book[i].entry,
+ 0, 4,
+ charset=HEXADECIMAL))
+
+ data_dtmf_call_list.append(
+ dtmf_string_setting("ce%d" % i,
+ "DTMF call entry %d" % j,
+ _dtmf_settings.phone_book[i].entry,
+ _dtmf_settings.phone_book[i].length,
+ 0, 10))
+
+ data_5tone_call_list.append(
+ five_tone_string_setting("ce%d" % i,
+ "5-Tone call entry %d" % j,
+ _5tone_settings.phone_book[i].entry)),
+
+ data_settings = data_general_settings
+ data_settings.extend([
+ settings.RadioSettingGroup("MSK_s", "MSK settings",
+ *data_msk_settings),
+ settings.RadioSettingGroup("MSK_c", "MSK call list",
+ *data_msk_call_list),
+ settings.RadioSettingGroup("DTMF_s", "DTMF settings",
+ *data_dtmf_settings),
+ settings.RadioSettingGroup("DTMF_c", "DTMF call list",
+ *data_dtmf_call_list),
+ settings.RadioSettingGroup("5-Tone_s", "5-tone settings",
+ *data_5tone_settings),
+ settings.RadioSettingGroup("5-Tone_c", "5-tone call list",
+ *data_5tone_call_list)
+ ])
+
+ # settings related to the various ways the radio can be locked down
+ locking_settings = [
+ list_setting("autolock", "Automatic timed keypad lock",
+ _settings.auto_keylock,
+ OFF_ON),
+ list_setting("lockon", "Current status of keypad lock",
+ _settings.keypad_lock,
+ INACTIVE_ACTIVE),
+ list_setting("nokeypad", "Disable keypad",
+ _settings.allow_keypad,
+ YES_NO),
+ list_setting("rxstun", "Disable receiver (rx stun)",
+ _settings.rx_stun,
+ NO_YES),
+ list_setting("txstun", "Disable transmitter (tx stun)",
+ _settings.tx_stun,
+ NO_YES),
+ ]
+
+ # broadcast fm radio settings
+ broadcast_settings = [
+ list_setting("band", "Frequency interval",
+ _broadcast.receive_range,
+ BFM_BANDS),
+ list_setting("stride", "VFO step",
+ _broadcast.channel_stepping,
+ BFM_STRIDE),
+ frequency_setting("vfo", "VFO frequency (MHz)",
+ _broadcast.vfo_freq)
+ ]
+
+ for i in range(10):
+ broadcast_settings.append(
+ frequency_setting("bcd%d" % i, "Memory %d frequency" % i,
+ _broadcast.memory[i].entry))
+
+ return settings.RadioSettings(
+ settings.RadioSettingGroup("model", "Model/Unit information",
+ *model_unit_settings),
+ settings.RadioSettingGroup("radio", "Radio/Channel settings",
+ *radio_channel_settings),
+ settings.RadioSettingGroup("interface", "Interface",
+ *interface_settings),
+ settings.RadioSettingGroup("data", "Data",
+ *data_settings),
+ settings.RadioSettingGroup("locking", "Locking",
+ *locking_settings),
+ settings.RadioSettingGroup("broadcast",
+ "Broadcast FM radio settings",
+ *broadcast_settings)
+ )
+
+ def set_settings(self, s, parent=''):
+ # The helper classes take care of all settings except these below,
+ # since it is a single instance of settings having interdependencies,
+ # i.e., which value that gets written to the memory depends on
+ # the value of another setting. The value of the ptt id type setting
+ # decides which of the msk/dtmf/5-tone ptt id strings are
+ # actually written to memory.
+ ds = sbyn(s, 'data')
+ idts = sbyn(ds, 'pttidt').value
+ idtv = idts.get_value()
+ cs = sbyn(ds, idtv+'_s')
+ tss = [sbyn(cs, e).value for e in ['bot', 'eot']]
+
+ for ts in tss:
+ ts.write_mem()
diff --git a/chirp/drivers/retevis_rt21.py b/chirp/drivers/retevis_rt21.py
new file mode 100644
index 0000000..e1ed491
--- /dev/null
+++ b/chirp/drivers/retevis_rt21.py
@@ -0,0 +1,526 @@
+# Copyright 2016 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
+# 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 os
+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
+
+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
+ unknown2:3;
+ u8 unknown3:2,
+ highpower:1, // Power Level
+ wide:1, // Bandwidth
+ unknown4:4;
+ u8 scramble_type:4,
+ unknown5:4;
+ u8 unknown6:4,
+ scramble_type2:4;
+} memory[16];
+
+#seekto 0x012C;
+struct {
+ 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 unknown2[2];
+ u8 squelch; // Squelch Level
+ u8 save; // Battery Saver
+ u8 unknown3[3];
+ u8 use_vox; // VOX Enable
+ u8 vox; // VOX Gain
+} settings;
+
+#seekto 0x017E;
+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"]
+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"]
+VOX_LIST = ["OFF"] + ["%s" % x for x in range(1, 17)]
+
+SETTING_LISTS = {
+ "bcl": BCL_LIST,
+ "scramble": SCRAMBLE_LIST,
+ "tot": TIMEOUTTIMER_LIST,
+ "totalert": TOTALERT_LIST,
+ "voice": VOICE_LIST,
+ "vox": VOX_LIST,
+ }
+
+
+def _rt21_enter_programming_mode(radio):
+ serial = radio.pipe
+
+ try:
+ serial.write("PRMZUNE")
+ 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:
+ serial.write("\x02")
+ ident = serial.read(8)
+ except:
+ raise errors.RadioError("Error communicating with radio")
+
+ if not ident.startswith("P3207"):
+ LOG.debug(util.hexprint(ident))
+ raise errors.RadioError("Radio returned unknown identification string")
+
+ try:
+ serial.write(CMD_ACK)
+ ack = serial.read(1)
+ except:
+ raise errors.RadioError("Error communicating with radio")
+
+ if ack != CMD_ACK:
+ raise errors.RadioError("Radio refused to enter programming mode")
+
+
+def _rt21_exit_programming_mode(radio):
+ serial = radio.pipe
+ try:
+ serial.write("E")
+ except:
+ raise errors.RadioError("Radio refused to exit programming mode")
+
+
+def _rt21_read_block(radio, block_addr, block_size):
+ serial = radio.pipe
+
+ 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)
+ if response[:4] != expectedresponse:
+ raise Exception("Error reading block %04x." % (block_addr))
+
+ block_data = response[4:]
+
+ serial.write(CMD_ACK)
+ ack = serial.read(1)
+ except:
+ raise errors.RadioError("Failed to read block at %04x" % block_addr)
+
+ if ack != CMD_ACK:
+ raise Exception("No ACK reading block %04x." % (block_addr))
+
+ return block_data
+
+
+def _rt21_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]
+
+ LOG.debug("Writing Data:")
+ LOG.debug(util.hexprint(cmd + data))
+
+ try:
+ serial.write(cmd + data)
+ if serial.read(1) != CMD_ACK:
+ raise Exception("No ACK")
+ except:
+ raise errors.RadioError("Failed to send block "
+ "to radio at %04x" % block_addr)
+
+
+def do_download(radio):
+ LOG.debug("download")
+ _rt21_enter_programming_mode(radio)
+
+ data = ""
+
+ status = chirp_common.Status()
+ status.msg = "Cloning from radio"
+
+ status.cur = 0
+ status.max = radio._memsize
+
+ for addr in range(0, radio._memsize, BLOCK_SIZE):
+ status.cur = addr + BLOCK_SIZE
+ radio.status_fn(status)
+
+ block = _rt21_read_block(radio, addr, BLOCK_SIZE)
+ data += block
+
+ LOG.debug("Address: %04x" % addr)
+ LOG.debug(util.hexprint(block))
+
+ _rt21_exit_programming_mode(radio)
+
+ return memmap.MemoryMap(data)
+
+
+def do_upload(radio):
+ status = chirp_common.Status()
+ status.msg = "Uploading to radio"
+
+ _rt21_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
+ radio.status_fn(status)
+ _rt21_write_block(radio, addr, BLOCK_SIZE)
+
+ _rt21_exit_programming_mode(radio)
+
+
+ at directory.register
+class RT21Radio(chirp_common.CloneModeRadio):
+ """RETEVIS RT21"""
+ VENDOR = "Retevis"
+ MODEL = "RT21"
+ BAUD_RATE = 9600
+
+ _ranges = [
+ (0x0000, 0x0400),
+ ]
+ _memsize = 0x0400
+
+ def get_features(self):
+ rf = chirp_common.RadioFeatures()
+ rf.has_settings = True
+ rf.has_bank = False
+ rf.has_ctone = True
+ rf.has_cross = True
+ rf.has_rx_dtcs = True
+ rf.has_tuning_step = False
+ rf.can_odd_split = True
+ rf.has_name = False
+ rf.valid_skips = ["", "S"]
+ 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_duplexes = ["", "-", "+", "split", "off"]
+ rf.valid_modes = ["NFM", "FM"] # 12.5 KHz, 25 kHz.
+ rf.memory_bounds = (1, 16)
+ rf.valid_bands = [(400000000, 480000000)]
+
+ return rf
+
+ def process_mmap(self):
+ self._memobj = bitwise.parse(MEM_FORMAT, self._mmap)
+
+ def sync_in(self):
+ self._mmap = do_download(self)
+ self.process_mmap()
+
+ def sync_out(self):
+ do_upload(self)
+
+ def get_raw_memory(self, number):
+ return repr(self._memobj.memory[number - 1])
+
+ def _get_tone(self, _mem, mem):
+ def _get_dcs(val):
+ code = int("%03o" % (val & 0x07FF))
+ pol = (val & 0x8000) and "R" or "N"
+ return code, pol
+
+ if _mem.tx_tone != 0xFFFF and _mem.tx_tone > 0x2000:
+ tcode, tpol = _get_dcs(_mem.tx_tone)
+ mem.dtcs = tcode
+ txmode = "DTCS"
+ elif _mem.tx_tone != 0xFFFF:
+ mem.rtone = _mem.tx_tone / 10.0
+ txmode = "Tone"
+ else:
+ txmode = ""
+
+ if _mem.rx_tone != 0xFFFF and _mem.rx_tone > 0x2000:
+ rcode, rpol = _get_dcs(_mem.rx_tone)
+ mem.rx_dtcs = rcode
+ rxmode = "DTCS"
+ elif _mem.rx_tone != 0xFFFF:
+ mem.ctone = _mem.rx_tone / 10.0
+ rxmode = "Tone"
+ else:
+ rxmode = ""
+
+ if txmode == "Tone" and not rxmode:
+ mem.tmode = "Tone"
+ elif txmode == rxmode and txmode == "Tone" and mem.rtone == mem.ctone:
+ mem.tmode = "TSQL"
+ elif txmode == rxmode and txmode == "DTCS" and mem.dtcs == mem.rx_dtcs:
+ mem.tmode = "DTCS"
+ elif rxmode or txmode:
+ mem.tmode = "Cross"
+ mem.cross_mode = "%s->%s" % (txmode, rxmode)
+
+ if mem.tmode == "DTCS":
+ mem.dtcs_polarity = "%s%s" % (tpol, rpol)
+
+ LOG.debug("Got TX %s (%i) RX %s (%i)" %
+ (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)
+
+ _mem = self._memobj.memory[number - 1]
+ _skp = self._memobj.skipflags[bytepos]
+
+ mem = chirp_common.Memory()
+
+ mem.number = number
+ mem.freq = int(_mem.rxfreq) * 10
+
+ # We'll consider any blank (i.e. 0MHz frequency) to be empty
+ if mem.freq == 0:
+ mem.empty = True
+ return mem
+
+ if _mem.rxfreq.get_raw() == "\xFF\xFF\xFF\xFF":
+ mem.freq = 0
+ mem.empty = True
+ return mem
+
+ if _mem.get_raw() == ("\xFF" * 16):
+ LOG.debug("Initializing empty memory")
+ _mem.set_raw("\x00" * 13 + "\x30\x8F\xF8")
+
+ if 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
+
+ mem.mode = _mem.wide and "FM" or "NFM"
+
+ self._get_tone(_mem, mem)
+
+ mem.power = RT21_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)
+
+ rs = RadioSetting("scramble_type", "Scramble Type",
+ RadioSettingValueList(SCRAMBLE_LIST,
+ SCRAMBLE_LIST[_mem.scramble_type - 8]))
+ mem.extra.append(rs)
+
+ return mem
+
+ def _set_tone(self, mem, _mem):
+ def _set_dcs(code, pol):
+ val = int("%i" % code, 8) + 0x2800
+ if pol == "R":
+ val += 0x8000
+ return val
+
+ if mem.tmode == "Cross":
+ tx_mode, rx_mode = mem.cross_mode.split("->")
+ elif mem.tmode == "Tone":
+ tx_mode = mem.tmode
+ rx_mode = None
+ else:
+ tx_mode = rx_mode = mem.tmode
+
+ if tx_mode == "DTCS":
+ _mem.tx_tone = mem.tmode != "DTCS" and \
+ _set_dcs(mem.dtcs, mem.dtcs_polarity[0]) or \
+ _set_dcs(mem.rx_dtcs, mem.dtcs_polarity[0])
+ elif tx_mode:
+ _mem.tx_tone = tx_mode == "Tone" and \
+ int(mem.rtone * 10) or int(mem.ctone * 10)
+ else:
+ _mem.tx_tone = 0xFFFF
+
+ if rx_mode == "DTCS":
+ _mem.rx_tone = _set_dcs(mem.rx_dtcs, mem.dtcs_polarity[1])
+ elif rx_mode:
+ _mem.rx_tone = int(mem.ctone * 10)
+ else:
+ _mem.rx_tone = 0xFFFF
+
+ LOG.debug("Set TX %s (%i) RX %s (%i)" %
+ (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)
+
+ _mem = self._memobj.memory[mem.number - 1]
+ _skp = self._memobj.skipflags[bytepos]
+
+ if mem.empty:
+ _mem.set_raw("\xFF" * (_mem.size() / 8))
+ return
+
+ _mem.set_raw("\x00" * 13 + "\x00\x8F\xF8")
+
+ _mem.rxfreq = mem.freq / 10
+
+ if mem.duplex == "off":
+ for i in range(0, 4):
+ _mem.txfreq[i].set_raw("\xFF")
+ elif mem.duplex == "split":
+ _mem.txfreq = mem.offset / 10
+ elif mem.duplex == "+":
+ _mem.txfreq = (mem.freq + mem.offset) / 10
+ elif mem.duplex == "-":
+ _mem.txfreq = (mem.freq - mem.offset) / 10
+ else:
+ _mem.txfreq = mem.freq / 10
+
+ _mem.wide = mem.mode == "FM"
+
+ self._set_tone(mem, _mem)
+
+ _mem.highpower = mem.power == RT21_POWER_LEVELS[1]
+
+ if mem.skip != "S":
+ _skp |= bitpos
+ else:
+ _skp &= ~bitpos
+ LOG.debug("_skp %s" % _skp)
+
+ for setting in mem.extra:
+ if setting.get_name() == "scramble_type":
+ setattr(_mem, setting.get_name(), int(setting.value) + 8)
+ setattr(_mem, "scramble_type2", int(setting.value) + 8)
+ else:
+ setattr(_mem, setting.get_name(), setting.value)
+
+ def get_settings(self):
+ _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)
+
+ rs = RadioSetting("totalert", "TOT Pre-alert",
+ RadioSettingValueList(
+ TOTALERT_LIST,
+ TOTALERT_LIST[_settings.totalert]))
+ basic.append(rs)
+
+ rs = RadioSetting("squelch", "Squelch Level",
+ RadioSettingValueInteger(0, 9, _settings.squelch))
+ basic.append(rs)
+
+ rs = RadioSetting("voice", "Voice Annumciation",
+ RadioSettingValueList(
+ VOICE_LIST, VOICE_LIST[_settings.voice]))
+ basic.append(rs)
+
+ rs = RadioSetting("save", "Battery Saver",
+ RadioSettingValueBoolean(_settings.save))
+ basic.append(rs)
+
+ rs = RadioSetting("use_scramble", "Scramble",
+ RadioSettingValueBoolean(_settings.use_scramble))
+ basic.append(rs)
+
+ rs = RadioSetting("use_vox", "VOX",
+ RadioSettingValueBoolean(_settings.use_vox))
+ basic.append(rs)
+
+ rs = RadioSetting("vox", "VOX Gain",
+ RadioSettingValueList(
+ VOX_LIST, VOX_LIST[_settings.vox]))
+ basic.append(rs)
+
+ return top
+
+ def set_settings(self, settings):
+ for element in settings:
+ if not isinstance(element, RadioSetting):
+ self.set_settings(element)
+ continue
+ else:
+ try:
+ if "." in element.get_name():
+ bits = element.get_name().split(".")
+ obj = self._memobj
+ for bit in bits[:-1]:
+ obj = getattr(obj, bit)
+ setting = bits[-1]
+ else:
+ obj = self._memobj.settings
+ setting = element.get_name()
+
+ if setting == "tot":
+ setattr(obj, setting, int(element.value) + 1)
+ elif element.value.get_mutable():
+ LOG.debug("Setting %s = %s" % (setting, element.value))
+ setattr(obj, setting, element.value)
+ except Exception, e:
+ LOG.debug(element.get_name())
+ raise
diff --git a/chirp/drivers/thd72.py b/chirp/drivers/thd72.py
index 1cae042..c0a0cc7 100644
--- a/chirp/drivers/thd72.py
+++ b/chirp/drivers/thd72.py
@@ -185,6 +185,8 @@ EXCH_W = "W\x00\x00\x00\x00"
# Uploads result in "MCP Error" and garbage data in memory
# Clone driver disabled in favor of error-checking live driver.
+
+
@directory.register
class THD72Radio(chirp_common.CloneModeRadio):
BAUD_RATE = 9600
@@ -200,10 +202,11 @@ class THD72Radio(chirp_common.CloneModeRadio):
_LCD_CONTRAST = ["Level %d" % x for x in range(1, 16)]
_LAMP_CONTROL = ["Manual", "Auto"]
_LAMP_TIMER = ["Seconds %d" % x for x in range(2, 11)]
- _BATTERY_SAVER = [ "OFF", "0.03 Seconds", "0.2 Seconds", "0.4 Seconds", "0.6 Seconds", "0.8 Seconds", "1 Seconds", "2 Seconds", "3 Seconds", "4 Seconds", "5 Seconds" ]
- _APO = [ "OFF", "15 Minutes", "30 Minutes", "60 Minutes" ]
- _AUDIO_BALANCE = [ "Center", "A +50%", "A +100%", "B +50%", "B +100%" ]
- _KEY_BEEP = [ "OFF", "Radio & GPS", "Radio Only", "GPS Only" ]
+ _BATTERY_SAVER = ["OFF", "0.03 Seconds", "0.2 Seconds", "0.4 Seconds", "0.6 Seconds",
+ "0.8 Seconds", "1 Seconds", "2 Seconds", "3 Seconds", "4 Seconds", "5 Seconds"]
+ _APO = ["OFF", "15 Minutes", "30 Minutes", "60 Minutes"]
+ _AUDIO_BALANCE = ["Center", "A +50%", "A +100%", "B +50%", "B +100%"]
+ _KEY_BEEP = ["OFF", "Radio & GPS", "Radio Only", "GPS Only"]
def get_features(self):
rf = chirp_common.RadioFeatures()
@@ -267,7 +270,7 @@ class THD72Radio(chirp_common.CloneModeRadio):
return name[:name.index('\xff')].rstrip()
def set_channel_name(self, number, name):
- name = name[:8] + '\xff'*8
+ name = name[:8] + '\xff' * 8
if number < 999:
self._memobj.channel_name[number].name = name[:8]
self.add_dirty_block(self._memobj.channel_name[number])
@@ -290,7 +293,7 @@ class THD72Radio(chirp_common.CloneModeRadio):
if number < 0 or number > (max(THD72_SPECIAL.values()) + 1):
raise errors.InvalidMemoryLocation(
- "Number must be between 0 and 999")
+ "Number must be between 0 and 999")
_mem = self._memobj.memory[number]
flag = self._memobj.flag[number]
@@ -406,7 +409,7 @@ class THD72Radio(chirp_common.CloneModeRadio):
def write_block(self, block, map):
self.pipe.write(struct.pack("<cBHB", "W", 0, block, 0))
base = block * 256
- self.pipe.write(map[base:base+256])
+ self.pipe.write(map[base:base + 256])
ack = self.pipe.read(1)
@@ -416,12 +419,12 @@ class THD72Radio(chirp_common.CloneModeRadio):
if blocks is None:
blocks = range(self._memsize / 256)
else:
- blocks = [b for b in blocks if b < self._memsize/256]
+ blocks = [b for b in blocks if b < self._memsize / 256]
if self.command("0M PROGRAM") != "0M":
raise errors.RadioError("No response from self")
- allblocks = range(self._memsize/256)
+ allblocks = range(self._memsize / 256)
self.pipe.baudrate = 57600
try:
self.pipe.setRTS()
@@ -434,7 +437,7 @@ class THD72Radio(chirp_common.CloneModeRadio):
count = 0
for i in allblocks:
if i not in blocks:
- data += 256*'\xff'
+ data += 256 * '\xff'
continue
data += self.read_block(i)
count += 1
@@ -455,7 +458,7 @@ class THD72Radio(chirp_common.CloneModeRadio):
if blocks is None:
blocks = range((self._memsize / 256) - 2)
else:
- blocks = [b for b in blocks if b < self._memsize/256]
+ blocks = [b for b in blocks if b < self._memsize / 256]
if self.command("0M PROGRAM") != "0M":
raise errors.RadioError("No response from self")
@@ -546,7 +549,7 @@ class THD72Radio(chirp_common.CloneModeRadio):
try:
old_val = getattr(obj, setting)
LOG.debug("Setting %s(%r) <= %s" % (
- element.get_name(), old_val, element.value))
+ element.get_name(), old_val, element.value))
setattr(obj, setting, element.value)
except AttributeError as e:
LOG.error("Setting %s is not in the memory map: %s" %
@@ -592,7 +595,7 @@ class THD72Radio(chirp_common.CloneModeRadio):
rs = RadioSetting("display.power_on_msg", "Power on message", val)
rs.set_apply_callback(self.apply_power_on_msg, display_settings)
menu.append(rs)
-
+
val = RadioSettingValueList(
self._LCD_CONTRAST,
self._LCD_CONTRAST[display_settings.contrast - 1])
@@ -600,7 +603,7 @@ class THD72Radio(chirp_common.CloneModeRadio):
val)
rs.set_apply_callback(self.apply_lcd_contrast, display_settings)
menu.append(rs)
-
+
val = RadioSettingValueList(
self._LAMP_CONTROL,
self._LAMP_CONTROL[display_settings.lamp_control])
diff --git a/chirp/drivers/tk760g.py b/chirp/drivers/tk760g.py
index 00cd57d..6563e06 100644
--- a/chirp/drivers/tk760g.py
+++ b/chirp/drivers/tk760g.py
@@ -1,4 +1,4 @@
-# Copyright 2016 Pavel Milanes CO7WT, <co7wt at frcuba.co.cu> <pavelmc at gmail.com>
+# Copyright 2016 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
@@ -295,6 +295,9 @@ SQL = ["off"] + ["%s" % x for x in range(1, 10)]
## BOT = 0, EOT = 1, Both = 2, NONE = 3
#PTTID = ["BOT", "EOT", "Both", "none"]
+# For debugging purposes
+debug = False
+
KEYS = {
0x33: "Display character",
0x35: "Home Channel", # Posible portable only, chek it
@@ -336,6 +339,10 @@ def _raw_recv(radio, amount):
except:
raise errors.RadioError("Error reading data from radio")
+ # DEBUG
+ if debug is True:
+ LOG.debug("<== (%d) bytes:\n\n%s" % (len(data), util.hexprint(data)))
+
return data
@@ -346,6 +353,10 @@ def _raw_send(radio, data):
except:
raise errors.RadioError("Error sending data to radio")
+ # DEBUG
+ if debug is True:
+ LOG.debug("==> (%d) bytes:\n\n%s" % (len(data), util.hexprint(data)))
+
def _close_radio(radio):
"""Get the radio out of program mode"""
@@ -441,7 +452,7 @@ def _recv(radio):
def _open_radio(radio, status):
"""Open the radio into program mode and check if it's the correct model"""
# linux min is 0.13, win min is 0.25; set to bigger to be safe
- radio.pipe.timeout = 0.25
+ radio.pipe.timeout = 0.4
radio.pipe.parity = "E"
# DEBUG
@@ -462,7 +473,7 @@ def _open_radio(radio, status):
if ack != ACK_CMD:
# DEBUG
- LOG.debug("Try %s failed, traying again...")
+ LOG.debug("Try %s failed, traying again..." % i)
time.sleep(0.25)
else:
exito = True
@@ -474,7 +485,7 @@ def _open_radio(radio, status):
if exito is False:
_close_radio(radio)
- LOG.debug("Radio did not accepted PROGRAM command in five atempts")
+ LOG.debug("Radio did not accepted PROGRAM command in %s atempts" % tries)
raise errors.RadioError("The radio doesn't accept program mode")
# DEBUG
@@ -717,6 +728,13 @@ class Kenwood_Serie_60G(chirp_common.CloneModeRadio):
'and raise and issue on the chirp site about it and maybe'
'it will be implemented in the future.'
''
+ 'The band limits on some of this radios are set here far from'
+ 'the vendor limits to cover most of the ham bands. It is a'
+ 'known fact that this radios can work safely outside the OEM'
+ 'limits, but each radio is different, you may find in trouble'
+ 'with some particular radios, but this is also possible with'
+ 'the OEM software; as usual YMMV.'
+ ''
'A detail: this driver is slow reading from the radio in'
'Windows, due to the way windows serial works.'
)
@@ -1513,23 +1531,33 @@ class Kenwood_Serie_60G(chirp_common.CloneModeRadio):
# This kenwwood family is known as "60-G Serie"
# all this radios ending in G are compatible:
#
-# Portables VHF TK-260/270/272/278
-# Portables UHF TK-360/370/372/378/388
+# Portables VHF TK-260G/270G/272G/278G
+# Portables UHF TK-360G/370G/372G/378G/388G
#
-# Mobiles VHF TK-760/762/768
-# Mobiles VHF TK-860/862/868
+# Mobiles VHF TK-760G/762G/768G
+# Mobiles VHF TK-860G/862G/868G
#
-# Just dealing with VHF models at moment,
-# this are the radios I can get in hand
-
-# WARNING !!!! Radios With Password in the data section <=###############
+# WARNING !!!! Radios With Password in the data section ###############
#
# when a radio has a data password (aka to program it) the last byte (#8)
# in the id code change from \xf1 to \xb1; so we remove this last byte
# from the identification procedures and variants.
#
-# this effectively render the data password USELESS even if set.
-# this can change if user request it with high priority
+# This effectively render the data password USELESS even if set.
+# This can change if user request it with high priority
+#
+# WARNING !!!! the band limits on some of this radios are set here far from the
+# vendor limits to cover most of the ham bands. It's a known fact that this
+# radios can work safely outside the OEM limits, but each radio is different,
+# you may find in trouble with some particular radios, but this is also
+# possible with the OEM software, as usual YMMV.
+#
+# Thanks to Stephen (GN5SWP) with his work characterizing some of the members of
+# this family about the safe band limits.
+#
+# In the radios with mod band limits you will find a comment next to it with
+# the OEM limits just for the record.
+
@directory.register
class TK868G_Radios(Kenwood_Serie_60G):
@@ -1539,8 +1567,8 @@ class TK868G_Radios(Kenwood_Serie_60G):
VARIANTS = {
"M8680\x18\xff": (8, 400, 490, "M"),
"M8680;\xff": (128, 350, 390, "C1"),
- "M86808\xff": (128, 400, 440, "C2"), # 400-430Mhz original
- "M86806\xff": (128, 450, 490, "C3"),
+ "M86808\xff": (128, 400, 450, "C2"), # 400-430Mhz original
+ "M86806\xff": (128, 430, 490, "C3"), # 450-490Mhz original
}
@@ -1552,8 +1580,8 @@ class TK862G_Radios(Kenwood_Serie_60G):
VARIANTS = {
"M8620\x06\xff": (8, 450, 490, "K"),
"M8620\x07\xff": (8, 485, 512, "K2"),
- "M8620&\xff": (8, 440, 470, "E"),
- "M8620V\xff": (8, 440, 470, "(N)E"),
+ "M8620&\xff": (8, 430, 470, "E"), # 440-470Mhz original
+ "M8620V\xff": (8, 430, 470, "(N)E"), # 440-470Mhz original
}
@@ -1563,11 +1591,11 @@ class TK860G_Radios(Kenwood_Serie_60G):
MODEL = "TK-860G"
TYPE = "M8600"
VARIANTS = {
- "M8600\x08\xff": (128, 400, 440, "K"), # 400-430Mhz original
- "M8600\x06\xff": (128, 450, 490, "K1"),
+ "M8600\x08\xff": (128, 400, 450, "K"), # 400-430Mhz original
+ "M8600\x06\xff": (128, 430, 490, "K1"), # 450-490Mhz original
"M8600\x07\xff": (128, 485, 512, "K2"),
- "M8600\x18\xff": (128, 400, 440, "M"), # 400-430Mhz original
- "M8600\x16\xff": (128, 450, 490, "M1"),
+ "M8600\x18\xff": (128, 400, 450, "M"), # 400-430Mhz original
+ "M8600\x16\xff": (128, 430, 490, "M1"), # 450-490Mhz original
"M8600\x17\xff": (128, 485, 520, "M2"),
}
@@ -1607,9 +1635,84 @@ class TK760G_Radios(Kenwood_Serie_60G):
TYPE = "M7600"
VARIANTS = {
"M7600\x05\xff": (128, 136, 162, "K2"),
- "M7600\x04\xff": (128, 144, 174, "K"), # 148-174 original
- "M7600\x14\xff": (128, 144, 174, "M"), # 148-174 original
- "M7600T\xff": (128, 144, 174, "NE") # 148-174 original
+ "M7600\x04\xff": (128, 138, 174, "K"), # 148-174 original
+ "M7600\x14\xff": (128, 138, 174, "M"), # 148-174 original
+ "M7600T\xff": (128, 138, 174, "NE") # 148-174 original
+ }
+
+
+ at directory.register
+class TK388G_Radios(Kenwood_Serie_60G):
+ """Kenwood TK-388 Radio [K/E/M/NE]"""
+ MODEL = "TK-388G"
+ TYPE = "P3880"
+ VARIANTS = {
+ "P3880\x1b\xff": (128, 350, 370, "M")
+ }
+
+
+ at directory.register
+class TK378G_Radios(Kenwood_Serie_60G):
+ """Kenwood TK-378 Radio [K/E/M/NE]"""
+ MODEL = "TK-378G"
+ TYPE = "P3780"
+ VARIANTS = {
+ "P3780\x16\xff": (16, 440, 470, "M"), # 450-470 Mhz original
+ "P3780\x17\xff": (16, 400, 430, "M1"), # 400-420 Mhz original
+ "P3780\x36\xff": (128, 490, 512, "C"),
+ "P3780\x39\xff": (128, 403, 440, "C1") # 403-430 Mhz original
+ }
+
+
+ at directory.register
+class TK372G_Radios(Kenwood_Serie_60G):
+ """Kenwood TK-372 Radio [K/E/M/NE]"""
+ MODEL = "TK-372G"
+ TYPE = "P3720"
+ VARIANTS = {
+ "P3720\x06\xff": (32, 440, 470, "K"), # 450-470 Mhz original
+ "P3720\x07\xff": (32, 470, 490, "K1"),
+ "P3720\x08\xff": (32, 490, 512, "K2"),
+ "P3720\x09\xff": (32, 403, 440, "K3") # 403-430 Mhz original
+ }
+
+
+ at directory.register
+class TK370G_Radios(Kenwood_Serie_60G):
+ """Kenwood TK-370 Radio [K/E/M/NE]"""
+ MODEL = "TK-370G"
+ TYPE = "P3700"
+ VARIANTS = {
+ "P3700\x06\xff": (128, 440, 470, "K"), # 450-470 Mhz original
+ "P3700\x07\xff": (128, 470, 490, "K1"),
+ "P3700\x08\xff": (128, 490, 512, "K2"),
+ "P3700\x09\xff": (128, 403, 440, "K3"), # 403-430 Mhz original
+ "P3700\x16\xff": (128, 440, 470, "M"), # 450-470 Mhz original
+ "P3700\x17\xff": (128, 470, 490, "M1"),
+ "P3700\x18\xff": (128, 490, 520, "M2"),
+ "P3700\x19\xff": (128, 403, 440, "M3"), # 403-430 Mhz original
+ "P3700&\xff": (128, 430, 470, "E"), # 440-470 Mhz original
+ "P3700V\xff": (128, 430, 470, "NE") # 440-470 Mhz original
+ }
+
+
+ at directory.register
+class TK360G_Radios(Kenwood_Serie_60G):
+ """Kenwood TK-360 Radio [K/E/M/NE]"""
+ MODEL = "TK-360G"
+ TYPE = "P3600"
+ VARIANTS = {
+ "P3600\x06\xff": (8, 440, 470, "K"), # 450-470 Mhz original
+ "P3600\x07\xff": (8, 470, 490, "K1"),
+ "P3600\x08\xff": (8, 490, 512, "K2"),
+ "P3600\x09\xff": (8, 403, 440, "K3"), # 403-430 Mhz original
+ "P3600&\xff": (8, 430, 470, "E"), # 440-470 Mhz original
+ "P3600)\xff": (8, 406, 440, "E1"), # 403-430 Mhz original
+ "P3600\x16\xff": (8, 440, 470, "M"), # 450-470 Mhz original
+ "P3600\x17\xff": (8, 470, 490, "M1"),
+ "P3600\x19\xff": (8, 403, 440, "M2"), # 403-430 Mhz original
+ "P3600V\xff": (8, 430, 470, "NE"), # 440-470 Mhz original
+ "P3600Y\xff": (8, 403, 440, "NE1") # 403-430 Mhz original
}
@@ -1621,9 +1724,9 @@ class TK278G_Radios(Kenwood_Serie_60G):
# Note that 16 CH don't have banks
VARIANTS = {
"P27805\xff": (128, 136, 150, "C1"),
- "P27804\xff": (128, 150, 174, "C"),
+ "P27804\xff": (128, 144, 174, "C"), # 150-174 original
"P2780\x15\xff": (16, 136, 150, "M1"),
- "P2780\x14\xff": (16, 150, 174, "M")
+ "P2780\x14\xff": (16, 144, 174, "M") # 150-174 original
}
@@ -1634,7 +1737,7 @@ class TK272G_Radios(Kenwood_Serie_60G):
TYPE = "P2720"
VARIANTS = {
"P2720\x05\xfb": (32, 136, 150, "K1"),
- "P2720\x04\xfb": (32, 150, 174, "K")
+ "P2720\x04\xfb": (32, 144, 174, "K") # 150-174 original
}
@@ -1646,9 +1749,9 @@ class TK270G_Radios(Kenwood_Serie_60G):
VARIANTS = {
"P2700T\xff": (128, 144, 174, "NE/NT"), # 146-174 original
"P2700$\xff": (128, 144, 174, "E"), # 146-174 original
- "P2700\x14\xff": (128, 150, 174, "M"),
+ "P2700\x14\xff": (128, 144, 174, "M"), # 150-174 original
"P2700\x05\xff": (128, 136, 150, "K1"),
- "P2700\x04\xff": (128, 150, 174, "K"),
+ "P2700\x04\xff": (128, 144, 174, "K") # 150-174 original
}
@@ -1660,9 +1763,9 @@ class TK260G_Radios(Kenwood_Serie_60G):
TYPE = "P2600"
VARIANTS = {
"P2600U\xff": (8, 136, 150, "N1"),
- "P2600T\xff": (8, 144, 174, "N"), # 146-174 original
- "P2600$\xff": (8, 144, 174, "E"), # 144-174 original
- "P2600\x14\xff": (8, 150, 174, "M"),
+ "P2600T\xff": (8, 144, 174, "N"), # 146-174 original
+ "P2600$\xff": (8, 144, 174, "E"), # 144-174 original
+ "P2600\x14\xff": (8, 144, 174, "M"), # 150-174 original
"P2600\x05\xff": (8, 136, 150, "K1"),
- "P2600\x04\xff": (8, 150, 174, "K")
+ "P2600\x04\xff": (8, 144, 174, "K") # 150-174 original
}
diff --git a/chirp/drivers/uv5r.py b/chirp/drivers/uv5r.py
index 9510831..e2ebc7f 100644
--- a/chirp/drivers/uv5r.py
+++ b/chirp/drivers/uv5r.py
@@ -284,7 +284,7 @@ BASETYPE_UV5R = ["BFS", "BFB", "N5R-2", "N5R2", "N5RV", "BTS", "D5R2"]
BASETYPE_F11 = ["USA"]
BASETYPE_UV82 = ["US2S", "B82S", "BF82", "N82-2", "N822"]
BASETYPE_BJ55 = ["BJ55"] # needed for for the Baojie UV-55 in bjuv55.py
-BASETYPE_UV6 = ["BF1"]
+BASETYPE_UV6 = ["BF1", "UV6"]
BASETYPE_KT980HP = ["BFP3V3 B"]
BASETYPE_F8HP = ["BFP3V3 F", "N5R-3", "N5R3", "F5R3", "BFT"]
BASETYPE_UV82HP = ["N82-3", "N823"]
@@ -411,9 +411,42 @@ def _do_ident(radio, magic):
raise errors.RadioError("Radio did not respond")
serial.write("\x02")
- ident = serial.read(8)
- LOG.info("Ident: %s" % util.hexprint(ident))
+ # Until recently, the "ident" returned by the radios supported by this
+ # driver have always been 8 bytes long. The image sturcture is the 8 byte
+ # "ident" followed by the downloaded memory data. So all of the settings
+ # structures are offset by 8 bytes. The ident returned from a UV-6 radio
+ # can be 8 bytes (original model) or now 12 bytes.
+ #
+ # To accomodate this, the "ident" is now read one byte at a time until the
+ # last byte ("\xdd") is encountered. The bytes containing the value "\x01"
+ # are discarded to shrink the "ident" length down to 8 bytes to keep the
+ # image data aligned with the existing settings structures.
+
+ # Ok, get the response
+ response = ""
+ for i in range(1, 13):
+ byte = serial.read(1)
+ response += byte
+ # stop reading once the last byte ("\xdd") is encountered
+ if byte == "\xdd":
+ break
+
+ # check if response is OK
+ if len(response) in [8, 12]:
+ # DEBUG
+ LOG.info("Valid response, got this:")
+ LOG.debug(util.hexprint(response))
+ if len(response) == 12:
+ ident = response[0] + response[3] + response[5] + response[7:]
+ else:
+ ident = response
+ else:
+ # bad response
+ msg = "Unexpected response, got this:"
+ msg += util.hexprint(response)
+ LOG.debug(msg)
+ raise errors.RadioError("Unexpected response from radio.")
serial.write("\x06")
ack = serial.read(1)
@@ -789,7 +822,7 @@ class BaofengUV5R(chirp_common.CloneModeRadio,
mem.power = levels[_mem.lowpower]
except IndexError:
LOG.error("Radio reported invalid power level %s (in %s)" %
- (_mem.power, levels))
+ (_mem.lowpower, levels))
mem.power = levels[0]
mem.mode = _mem.wide and "FM" or "NFM"
@@ -1486,7 +1519,7 @@ class BaofengUV5R(chirp_common.CloneModeRadio,
dtmf.append(rs)
rs = RadioSetting("pttlt", "PTT ID Delay",
- RadioSettingValueInteger(0, 30, _settings.pttlt))
+ RadioSettingValueInteger(0, 50, _settings.pttlt))
dtmf.append(rs)
if not self._is_orig():
@@ -1574,9 +1607,14 @@ class UV5XAlias(chirp_common.Alias):
MODEL = "UV-5X"
+class RT5RAlias(chirp_common.Alias):
+ VENDOR = "Retevis"
+ MODEL = "RT-5R"
+
+
@directory.register
class BaofengUV5RGeneric(BaofengUV5R):
- ALIASES = [UV5XAlias]
+ ALIASES = [UV5XAlias, RT5RAlias]
@directory.register
diff --git a/chirp/drivers/uv5x3.py b/chirp/drivers/uv5x3.py
new file mode 100644
index 0000000..9246121
--- /dev/null
+++ b/chirp/drivers/uv5x3.py
@@ -0,0 +1,1173 @@
+# Copyright 2016:
+# * Jim Unroe KC9HI, <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
+# 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.drivers import baofeng_common
+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, \
+ InvalidValueError
+from textwrap import dedent
+
+##### MAGICS #########################################################
+
+# BTECH UV-5X3 magic string
+MSTRING_UV5X3 = "\x50\x0D\x0C\x20\x16\x03\x28"
+
+##### ID strings #####################################################
+
+# BTECH UV-5X3
+UV5X3_fp1 = "UVVG302" # BFB300 original
+UV5X3_fp2 = "UVVG301" # UVV300 original
+UV5X3_fp3 = "UVVG306" # UVV306 original
+
+DTMF_CHARS = " 1234567890*#ABCD"
+STEPS = [2.5, 5.0, 6.25, 10.0, 12.5, 20.0, 25.0, 50.0]
+
+LIST_AB = ["A", "B"]
+LIST_ALMOD = ["Site", "Tone", "Code"]
+LIST_BANDWIDTH = ["Wide", "Narrow"]
+LIST_COLOR = ["Off", "Blue", "Orange", "Purple"]
+LIST_DELAYPROCTIME = ["%s ms" % x for x in range(100, 4100, 100)]
+LIST_DTMFSPEED = ["%s ms" % x for x in range(50, 2010, 10)]
+LIST_DTMFST = ["Off", "DT-ST", "ANI-ST", "DT+ANI"]
+LIST_MODE = ["Channel", "Name", "Frequency"]
+LIST_OFF1TO9 = ["Off"] + list("123456789")
+LIST_OFF1TO10 = LIST_OFF1TO9 + ["10"]
+LIST_OFFAB = ["Off"] + LIST_AB
+LIST_RESETTIME = ["%s ms" % x for x in range(100, 16100, 100)]
+LIST_RESUME = ["TO", "CO", "SE"]
+LIST_PONMSG = ["Full", "Message"]
+LIST_PTTID = ["Off", "BOT", "EOT", "Both"]
+LIST_SCODE = ["%s" % x for x in range(1, 16)]
+LIST_RPSTE = ["Off"] + ["%s" % x for x in range(1, 11)]
+LIST_SAVE = ["Off", "1:1", "1:2", "1:3", "1:4"]
+LIST_SHIFTD = ["Off", "+", "-"]
+LIST_STEDELAY = ["Off"] + ["%s ms" % x for x in range(100, 1100, 100)]
+LIST_STEP = [str(x) for x in STEPS]
+LIST_TIMEOUT = ["%s sec" % x for x in range(15, 615, 15)]
+LIST_TXPOWER = ["High", "Low"]
+LIST_VOICE = ["Off", "English", "Chinese"]
+LIST_WORKMODE = ["Frequency", "Channel"]
+
+def model_match(cls, data):
+ """Match the opened/downloaded image to the correct version"""
+ match_rid1 = False
+ match_rid2 = False
+
+ rid1 = data[0x1EF0:0x1EF7]
+
+ if rid1 in cls._fileid:
+ match_rid1 = True
+
+ if match_rid1:
+ return True
+ else:
+ return False
+
+
+ at directory.register
+class UV5X3(baofeng_common.BaofengCommonHT):
+ """BTech UV-5X3"""
+ VENDOR = "BTECH"
+ MODEL = "UV-5X3"
+
+ _fileid = [UV5X3_fp3,
+ UV5X3_fp2,
+ UV5X3_fp1]
+
+ _magic = [MSTRING_UV5X3, ]
+ _magic_response_length = 14
+ _fw_ver_start = 0x1EF0
+ _recv_block_size = 0x40
+ _mem_size = 0x2000
+ _ack_block = True
+
+ _ranges = [(0x0000, 0x0DF0),
+ (0x0E00, 0x1800),
+ (0x1EE0, 0x1EF0),
+ (0x1F80, 0x1F90),
+ (0x1FA0, 0x1FB0),
+ (0x1FE0, 0x2000)]
+ _send_block_size = 0x10
+
+ MODES = ["FM", "NFM"]
+ VALID_CHARS = chirp_common.CHARSET_ALPHANUMERIC + \
+ "!@#$%^&*()+-=[]:\";'<>?,./"
+ LENGTH_NAME = 7
+ SKIP_VALUES = ["", "S"]
+ DTCS_CODES = sorted(chirp_common.DTCS_CODES + [645])
+ POWER_LEVELS = [chirp_common.PowerLevel("High", watts=5.00),
+ chirp_common.PowerLevel("Low", watts=1.00)]
+ VALID_BANDS = [(130000000, 180000000),
+ (220000000, 226000000),
+ (400000000, 521000000)]
+ PTTID_LIST = LIST_PTTID
+ SCODE_LIST = LIST_SCODE
+
+
+ MEM_FORMAT = """
+ #seekto 0x0000;
+ struct {
+ lbcd rxfreq[4];
+ lbcd txfreq[4];
+ ul16 rxtone;
+ ul16 txtone;
+ u8 unknown0:4,
+ scode:4;
+ u8 unknown1;
+ u8 unknown2:7,
+ lowpower:1;
+ u8 unknown3:1,
+ wide:1,
+ unknown4:2,
+ bcl:1,
+ scan:1,
+ pttid:2;
+ } memory[128];
+
+ #seekto 0x0B00;
+ struct {
+ u8 code[16];
+ } pttid[15];
+
+ #seekto 0x0C80;
+ struct {
+ u8 inspection[8];
+ u8 monitor[8];
+ u8 alarmcode[8];
+ u8 stun[8];
+ u8 kill[8];
+ u8 revive[8];
+ u8 code[7];
+ u8 unknown06;
+ u8 dtmfon;
+ u8 dtmfoff;
+ u8 unused00:6,
+ aniid:2;
+ u8 unknown07[5];
+ u8 masterid[5];
+ u8 unknown08[3];
+ u8 viceid[5];
+ u8 unknown09[3];
+ u8 unused01:7,
+ mastervice:1;
+ u8 unused02:3,
+ mrevive:1,
+ mkill:1,
+ mstun:1,
+ mmonitor:1,
+ minspection:1;
+ u8 unused03:3,
+ vrevive:1,
+ vkill:1,
+ vstun:1,
+ vmonitor:1,
+ vinspection:1;
+ u8 unused04:6,
+ txdisable:1,
+ rxdisable:1;
+ u8 groupcode;
+ u8 spacecode;
+ u8 delayproctime;
+ u8 resettime;
+ } ani;
+
+ #seekto 0x0E20;
+ struct {
+ u8 unused00:4,
+ squelch:4;
+ u8 unused01:5,
+ step:3;
+ u8 unknown00;
+ u8 unused02:5,
+ save:3;
+ u8 unused03:4,
+ vox:4;
+ u8 unknown01;
+ u8 unused04:4,
+ abr:4;
+ u8 unused05:7,
+ tdr:1;
+ u8 unused06:7,
+ beep:1;
+ u8 unused07:2,
+ timeout:6;
+ u8 unknown02[4];
+ u8 unused09:6,
+ voice:2;
+ u8 unknown03;
+ u8 unused10:6,
+ dtmfst:2;
+ u8 unknown04;
+ u8 unused11:6,
+ screv:2;
+ u8 unused12:6,
+ pttid:2;
+ u8 unused13:2,
+ pttlt:6;
+ u8 unused14:6,
+ mdfa:2;
+ u8 unused15:6,
+ mdfb:2;
+ u8 unknown05;
+ u8 unused16:7,
+ sync:1;
+ u8 unknown06[4];
+ u8 unused17:6,
+ wtled:2;
+ u8 unused18:6,
+ rxled:2;
+ u8 unused19:6,
+ txled:2;
+ u8 unused20:6,
+ almod:2;
+ u8 unknown07;
+ u8 unused21:6,
+ tdrab:2;
+ u8 unused22:7,
+ ste:1;
+ u8 unused23:4,
+ rpste:4;
+ u8 unused24:4,
+ rptrl:4;
+ u8 unused25:7,
+ ponmsg:1;
+ u8 unused26:7,
+ roger:1;
+ u8 unused27:7,
+ dani:1;
+ u8 unused28:2,
+ dtmfg:6;
+ u8 unknown08:6,
+ reset:1,
+ unknown09:1;
+ u8 unknown10[3];
+ u8 cht;
+ u8 unknown11[13];
+ u8 displayab:1,
+ unknown12:2,
+ fmradio:1,
+ alarm:1,
+ unknown13:2,
+ menu:1;
+ u8 unknown14;
+ u8 unused29:7,
+ workmode:1;
+ u8 unused30:7,
+ keylock:1;
+ } settings;
+
+ #seekto 0x0E76;
+ struct {
+ u8 unused0:1,
+ mrcha:7;
+ u8 unused1:1,
+ mrchb:7;
+ } wmchannel;
+
+ struct vfo {
+ u8 unknown0[8];
+ u8 freq[8];
+ u8 offset[6];
+ ul16 rxtone;
+ ul16 txtone;
+ u8 unused0:7,
+ band:1;
+ u8 unknown3;
+ u8 unknown4:2,
+ sftd:2,
+ scode:4;
+ u8 unknown5;
+ u8 unknown6:1,
+ step:3,
+ unknown7:4;
+ u8 txpower:1,
+ widenarr:1,
+ unknown8:6;
+ };
+
+ #seekto 0x0F00;
+ struct {
+ struct vfo a;
+ struct vfo b;
+ } vfo;
+
+ #seekto 0x0F4E;
+ u16 fm_presets;
+
+ #seekto 0x1000;
+ struct {
+ char name[7];
+ u8 unknown[9];
+ } names[128];
+
+ #seekto 0x1ED0;
+ struct {
+ char line1[7];
+ char line2[7];
+ } sixpoweron_msg;
+
+ #seekto 0x1EF0;
+ struct {
+ char line1[7];
+ char line2[7];
+ } firmware_msg;
+
+ struct squelch {
+ u8 sql0;
+ u8 sql1;
+ u8 sql2;
+ u8 sql3;
+ u8 sql4;
+ u8 sql5;
+ u8 sql6;
+ u8 sql7;
+ u8 sql8;
+ u8 sql9;
+ };
+
+ #seekto 0x1F80;
+ struct {
+ struct squelch vhf;
+ u8 unknown0[6];
+ u8 unknown1[16];
+ struct squelch uhf;
+ } squelch;
+
+ #seekto 0x1FE0;
+ struct {
+ char line1[7];
+ char line2[7];
+ } poweron_msg;
+
+ struct limit {
+ u8 enable;
+ bbcd lower[2];
+ bbcd upper[2];
+ };
+
+ #seekto 0x1FF0;
+ struct {
+ struct limit vhf;
+ struct limit vhf2;
+ struct limit uhf;
+ } limits;
+
+ """
+
+ @classmethod
+ def get_prompts(cls):
+ rp = chirp_common.RadioPrompts()
+ rp.experimental = \
+ ('The BTech UV-5X3 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 these 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 process_mmap(self):
+ """Process the mem map into the mem object"""
+ self._memobj = bitwise.parse(self.MEM_FORMAT, self._mmap)
+
+ def get_settings(self):
+ """Translate the bit in the mem_struct into settings in the UI"""
+ _mem = self._memobj
+ basic = RadioSettingGroup("basic", "Basic Settings")
+ advanced = RadioSettingGroup("advanced", "Advanced Settings")
+ other = RadioSettingGroup("other", "Other Settings")
+ work = RadioSettingGroup("work", "Work Mode Settings")
+ fm_preset = RadioSettingGroup("fm_preset", "FM Preset")
+ dtmfe = RadioSettingGroup("dtmfe", "DTMF Encode Settings")
+ dtmfd = RadioSettingGroup("dtmfd", "DTMF Decode Settings")
+ service = RadioSettingGroup("service", "Service Settings")
+ top = RadioSettings(basic, advanced, other, work, fm_preset, dtmfe,
+ dtmfd, service)
+
+ # Basic settings
+ if _mem.settings.squelch > 0x09:
+ val = 0x00
+ else:
+ val = _mem.settings.squelch
+ rs = RadioSetting("settings.squelch", "Squelch",
+ RadioSettingValueList(
+ LIST_OFF1TO9, LIST_OFF1TO9[val]))
+ basic.append(rs)
+
+ if _mem.settings.save > 0x04:
+ val = 0x00
+ else:
+ val = _mem.settings.save
+ rs = RadioSetting("settings.save", "Battery Saver",
+ RadioSettingValueList(
+ LIST_SAVE, LIST_SAVE[val]))
+ basic.append(rs)
+
+ if _mem.settings.vox > 0x0A:
+ val = 0x00
+ else:
+ val = _mem.settings.vox
+ rs = RadioSetting("settings.vox", "Vox",
+ RadioSettingValueList(
+ LIST_OFF1TO10, LIST_OFF1TO10[val]))
+ basic.append(rs)
+
+ if _mem.settings.abr > 0x0A:
+ val = 0x00
+ else:
+ val = _mem.settings.abr
+ rs = RadioSetting("settings.abr", "Backlight Timeout",
+ RadioSettingValueList(
+ LIST_OFF1TO10, LIST_OFF1TO10[val]))
+ basic.append(rs)
+
+ rs = RadioSetting("settings.tdr", "Dual Watch",
+ RadioSettingValueBoolean(_mem.settings.tdr))
+ basic.append(rs)
+
+ rs = RadioSetting("settings.beep", "Beep",
+ RadioSettingValueBoolean(_mem.settings.beep))
+ basic.append(rs)
+
+ if _mem.settings.timeout > 0x27:
+ val = 0x03
+ else:
+ val = _mem.settings.timeout
+ rs = RadioSetting("settings.timeout", "Timeout Timer",
+ RadioSettingValueList(
+ LIST_TIMEOUT, LIST_TIMEOUT[val]))
+ basic.append(rs)
+
+ if _mem.settings.voice > 0x02:
+ val = 0x01
+ else:
+ val = _mem.settings.voice
+ rs = RadioSetting("settings.voice", "Voice Prompt",
+ RadioSettingValueList(
+ LIST_VOICE, LIST_VOICE[val]))
+ basic.append(rs)
+
+ rs = RadioSetting("settings.dtmfst", "DTMF Sidetone",
+ RadioSettingValueList(LIST_DTMFST, LIST_DTMFST[
+ _mem.settings.dtmfst]))
+ basic.append(rs)
+
+ if _mem.settings.screv > 0x02:
+ val = 0x01
+ else:
+ val = _mem.settings.screv
+ rs = RadioSetting("settings.screv", "Scan Resume",
+ RadioSettingValueList(
+ LIST_RESUME, LIST_RESUME[val]))
+ basic.append(rs)
+
+ rs = RadioSetting("settings.pttid", "When to send PTT ID",
+ RadioSettingValueList(LIST_PTTID, LIST_PTTID[
+ _mem.settings.pttid]))
+ basic.append(rs)
+
+ if _mem.settings.pttlt > 0x1E:
+ val = 0x05
+ else:
+ val = _mem.settings.pttlt
+ rs = RadioSetting("pttlt", "PTT ID Delay",
+ RadioSettingValueInteger(0, 50, val))
+ basic.append(rs)
+
+ rs = RadioSetting("settings.mdfa", "Display Mode (A)",
+ RadioSettingValueList(LIST_MODE, LIST_MODE[
+ _mem.settings.mdfa]))
+ basic.append(rs)
+
+ rs = RadioSetting("settings.mdfb", "Display Mode (B)",
+ RadioSettingValueList(LIST_MODE, LIST_MODE[
+ _mem.settings.mdfb]))
+ basic.append(rs)
+
+ rs = RadioSetting("settings.sync", "Sync A & B",
+ RadioSettingValueBoolean(_mem.settings.sync))
+ basic.append(rs)
+
+ rs = RadioSetting("settings.wtled", "Standby LED Color",
+ RadioSettingValueList(
+ LIST_COLOR, LIST_COLOR[_mem.settings.wtled]))
+ basic.append(rs)
+
+ rs = RadioSetting("settings.rxled", "RX LED Color",
+ RadioSettingValueList(
+ LIST_COLOR, LIST_COLOR[_mem.settings.rxled]))
+ basic.append(rs)
+
+ rs = RadioSetting("settings.txled", "TX LED Color",
+ RadioSettingValueList(
+ LIST_COLOR, LIST_COLOR[_mem.settings.txled]))
+ basic.append(rs)
+
+ if _mem.settings.almod > 0x02:
+ val = 0x00
+ else:
+ val = _mem.settings.almod
+ rs = RadioSetting("settings.almod", "Alarm Mode",
+ RadioSettingValueList(
+ LIST_ALMOD, LIST_ALMOD[val]))
+ basic.append(rs)
+
+ if _mem.settings.tdrab > 0x02:
+ val = 0x00
+ else:
+ val = _mem.settings.tdrab
+ rs = RadioSetting("settings.tdrab", "Dual Watch TX Priority",
+ RadioSettingValueList(
+ LIST_OFFAB, LIST_OFFAB[val]))
+ basic.append(rs)
+
+ rs = RadioSetting("settings.ste", "Squelch Tail Eliminate (HT to HT)",
+ RadioSettingValueBoolean(_mem.settings.ste))
+ basic.append(rs)
+
+ if _mem.settings.rpste > 0x0A:
+ val = 0x00
+ else:
+ val = _mem.settings.rpste
+ rs = RadioSetting("settings.rpste",
+ "Squelch Tail Eliminate (repeater)",
+ RadioSettingValueList(
+ LIST_RPSTE, LIST_RPSTE[val]))
+ basic.append(rs)
+
+ if _mem.settings.rptrl > 0x0A:
+ val = 0x00
+ else:
+ val = _mem.settings.rptrl
+ rs = RadioSetting("settings.rptrl", "STE Repeater Delay",
+ RadioSettingValueList(
+ LIST_STEDELAY, LIST_STEDELAY[val]))
+ basic.append(rs)
+
+ rs = RadioSetting("settings.ponmsg", "Power-On Message",
+ RadioSettingValueList(LIST_PONMSG, LIST_PONMSG[
+ _mem.settings.ponmsg]))
+ basic.append(rs)
+
+ rs = RadioSetting("settings.roger", "Roger Beep",
+ RadioSettingValueBoolean(_mem.settings.roger))
+ basic.append(rs)
+
+ rs = RadioSetting("settings.dani", "Decode ANI",
+ RadioSettingValueBoolean(_mem.settings.dani))
+ basic.append(rs)
+
+ if _mem.settings.dtmfg > 0x3C:
+ val = 0x14
+ else:
+ val = _mem.settings.dtmfg
+ rs = RadioSetting("settings.dtmfg", "DTMF Gain",
+ RadioSettingValueInteger(0, 60, val))
+ basic.append(rs)
+
+ # Advanced settings
+ rs = RadioSetting("settings.reset", "RESET Menu",
+ RadioSettingValueBoolean(_mem.settings.reset))
+ advanced.append(rs)
+
+ rs = RadioSetting("settings.menu", "All Menus",
+ RadioSettingValueBoolean(_mem.settings.menu))
+ advanced.append(rs)
+
+ rs = RadioSetting("settings.fmradio", "Broadcast FM Radio",
+ RadioSettingValueBoolean(_mem.settings.fmradio))
+ advanced.append(rs)
+
+ rs = RadioSetting("settings.alarm", "Alarm Sound",
+ RadioSettingValueBoolean(_mem.settings.alarm))
+ advanced.append(rs)
+
+ # Other settings
+ def _filter(name):
+ filtered = ""
+ for char in str(name):
+ if char in chirp_common.CHARSET_ASCII:
+ filtered += char
+ else:
+ filtered += " "
+ return filtered
+
+ _msg = _mem.firmware_msg
+ val = RadioSettingValueString(0, 7, _filter(_msg.line1))
+ val.set_mutable(False)
+ rs = RadioSetting("firmware_msg.line1", "Firmware Message 1", val)
+ other.append(rs)
+
+ val = RadioSettingValueString(0, 7, _filter(_msg.line2))
+ val.set_mutable(False)
+ rs = RadioSetting("firmware_msg.line2", "Firmware Message 2", val)
+ other.append(rs)
+
+ _msg = _mem.sixpoweron_msg
+ val = RadioSettingValueString(0, 7, _filter(_msg.line1))
+ val.set_mutable(False)
+ rs = RadioSetting("sixpoweron_msg.line1", "6+Power-On Message 1", val)
+ other.append(rs)
+ val = RadioSettingValueString(0, 7, _filter(_msg.line2))
+ val.set_mutable(False)
+ rs = RadioSetting("sixpoweron_msg.line2", "6+Power-On Message 2", val)
+ other.append(rs)
+
+ _msg = _mem.poweron_msg
+ rs = RadioSetting("poweron_msg.line1", "Power-On Message 1",
+ RadioSettingValueString(
+ 0, 7, _filter(_msg.line1)))
+ other.append(rs)
+ rs = RadioSetting("poweron_msg.line2", "Power-On Message 2",
+ RadioSettingValueString(
+ 0, 7, _filter(_msg.line2)))
+ other.append(rs)
+
+ if str(_mem.firmware_msg.line1) == "UVVG302":
+ lower = 136
+ upper = 174
+ else:
+ lower = 130
+ upper = 179
+ rs = RadioSetting("limits.vhf.lower", "VHF Lower Limit (MHz)",
+ RadioSettingValueInteger(
+ lower, upper, _mem.limits.vhf.lower))
+ other.append(rs)
+
+ rs = RadioSetting("limits.vhf.upper", "VHF Upper Limit (MHz)",
+ RadioSettingValueInteger(
+ lower, upper, _mem.limits.vhf.upper))
+ other.append(rs)
+
+ if str(_mem.firmware_msg.line1) == "UVVG302":
+ lower = 200
+ upper = 230
+ else:
+ lower = 220
+ upper = 225
+ rs = RadioSetting("limits.vhf2.lower", "VHF2 Lower Limit (MHz)",
+ RadioSettingValueInteger(
+ lower, upper, _mem.limits.vhf2.lower))
+ other.append(rs)
+
+ rs = RadioSetting("limits.vhf2.upper", "VHF2 Upper Limit (MHz)",
+ RadioSettingValueInteger(
+ lower, upper, _mem.limits.vhf2.upper))
+ other.append(rs)
+
+ if str(_mem.firmware_msg.line1) == "UVVG302":
+ lower = 400
+ upper = 480
+ else:
+ lower = 400
+ upper = 520
+ rs = RadioSetting("limits.uhf.lower", "UHF Lower Limit (MHz)",
+ RadioSettingValueInteger(
+ lower, upper, _mem.limits.uhf.lower))
+ other.append(rs)
+
+ rs = RadioSetting("limits.uhf.upper", "UHF Upper Limit (MHz)",
+ RadioSettingValueInteger(
+ lower, upper, _mem.limits.uhf.upper))
+ other.append(rs)
+
+ # Work mode settings
+ rs = RadioSetting("settings.displayab", "Display",
+ RadioSettingValueList(
+ LIST_AB, LIST_AB[_mem.settings.displayab]))
+ work.append(rs)
+
+ rs = RadioSetting("settings.workmode", "VFO/MR Mode",
+ RadioSettingValueList(
+ LIST_WORKMODE,
+ LIST_WORKMODE[_mem.settings.workmode]))
+ work.append(rs)
+
+ rs = RadioSetting("settings.keylock", "Keypad Lock",
+ RadioSettingValueBoolean(_mem.settings.keylock))
+ work.append(rs)
+
+ rs = RadioSetting("wmchannel.mrcha", "MR A Channel",
+ RadioSettingValueInteger(0, 127,
+ _mem.wmchannel.mrcha))
+ work.append(rs)
+
+ rs = RadioSetting("wmchannel.mrchb", "MR B Channel",
+ RadioSettingValueInteger(0, 127,
+ _mem.wmchannel.mrchb))
+ work.append(rs)
+
+ def convert_bytes_to_freq(bytes):
+ real_freq = 0
+ for byte in bytes:
+ real_freq = (real_freq * 10) + byte
+ return chirp_common.format_freq(real_freq * 10)
+
+ def my_validate(value):
+ _vhf_lower = int(_mem.limits.vhf.lower)
+ _vhf_upper = int(_mem.limits.vhf.upper)
+ _vhf2_lower = int(_mem.limits.vhf2.lower)
+ _vhf2_upper = int(_mem.limits.vhf2.upper)
+ _uhf_lower = int(_mem.limits.uhf.lower)
+ _uhf_upper = int(_mem.limits.uhf.upper)
+ value = chirp_common.parse_freq(value)
+ msg = ("Can't be less than %i.0000")
+ if value > 99000000 and value < _vhf_lower * 1000000:
+ raise InvalidValueError(msg % (_vhf_lower))
+ msg = ("Can't be between %i.9975-%i.0000")
+ if (_vhf_upper + 1) * 1000000 <= value and \
+ value < _vhf2_lower * 1000000:
+ raise InvalidValueError(msg % (_vhf_upper, _vhf2_lower))
+ if (_vhf2_upper + 1) * 1000000 <= value and \
+ value < _uhf_lower * 1000000:
+ raise InvalidValueError(msg % (_vhf2_upper, _uhf_lower))
+ msg = ("Can't be greater than %i.9975")
+ if value > 99000000 and value >= (_uhf_upper + 1) * 1000000:
+ raise InvalidValueError(msg % (_uhf_upper))
+ return chirp_common.format_freq(value)
+
+ def apply_freq(setting, obj):
+ value = chirp_common.parse_freq(str(setting.value)) / 10
+ for i in range(7, -1, -1):
+ obj.freq[i] = value % 10
+ value /= 10
+
+ val1a = RadioSettingValueString(0, 10,
+ convert_bytes_to_freq(_mem.vfo.a.freq))
+ val1a.set_validate_callback(my_validate)
+ rs = RadioSetting("vfo.a.freq", "VFO A Frequency", val1a)
+ rs.set_apply_callback(apply_freq, _mem.vfo.a)
+ work.append(rs)
+
+ val1b = RadioSettingValueString(0, 10,
+ convert_bytes_to_freq(_mem.vfo.b.freq))
+ val1b.set_validate_callback(my_validate)
+ rs = RadioSetting("vfo.b.freq", "VFO B Frequency", val1b)
+ rs.set_apply_callback(apply_freq, _mem.vfo.b)
+ work.append(rs)
+
+ rs = RadioSetting("vfo.a.sftd", "VFO A Shift",
+ RadioSettingValueList(
+ LIST_SHIFTD, LIST_SHIFTD[_mem.vfo.a.sftd]))
+ work.append(rs)
+
+ rs = RadioSetting("vfo.b.sftd", "VFO B Shift",
+ RadioSettingValueList(
+ LIST_SHIFTD, LIST_SHIFTD[_mem.vfo.b.sftd]))
+ work.append(rs)
+
+ def convert_bytes_to_offset(bytes):
+ real_offset = 0
+ for byte in bytes:
+ real_offset = (real_offset * 10) + byte
+ return chirp_common.format_freq(real_offset * 1000)
+
+ def apply_offset(setting, obj):
+ value = chirp_common.parse_freq(str(setting.value)) / 1000
+ for i in range(5, -1, -1):
+ obj.offset[i] = value % 10
+ value /= 10
+
+ val1a = RadioSettingValueString(
+ 0, 10, convert_bytes_to_offset(_mem.vfo.a.offset))
+ rs = RadioSetting("vfo.a.offset",
+ "VFO A Offset", val1a)
+ rs.set_apply_callback(apply_offset, _mem.vfo.a)
+ work.append(rs)
+
+ val1b = RadioSettingValueString(
+ 0, 10, convert_bytes_to_offset(_mem.vfo.b.offset))
+ rs = RadioSetting("vfo.b.offset",
+ "VFO B Offset", val1b)
+ rs.set_apply_callback(apply_offset, _mem.vfo.b)
+ work.append(rs)
+
+ rs = RadioSetting("vfo.a.txpower", "VFO A Power",
+ RadioSettingValueList(
+ LIST_TXPOWER,
+ LIST_TXPOWER[_mem.vfo.a.txpower]))
+ work.append(rs)
+
+ rs = RadioSetting("vfo.b.txpower", "VFO B Power",
+ RadioSettingValueList(
+ LIST_TXPOWER,
+ LIST_TXPOWER[_mem.vfo.b.txpower]))
+ work.append(rs)
+
+ rs = RadioSetting("vfo.a.widenarr", "VFO A Bandwidth",
+ RadioSettingValueList(
+ LIST_BANDWIDTH,
+ LIST_BANDWIDTH[_mem.vfo.a.widenarr]))
+ work.append(rs)
+
+ rs = RadioSetting("vfo.b.widenarr", "VFO B Bandwidth",
+ RadioSettingValueList(
+ LIST_BANDWIDTH,
+ LIST_BANDWIDTH[_mem.vfo.b.widenarr]))
+ work.append(rs)
+
+ rs = RadioSetting("vfo.a.scode", "VFO A S-CODE",
+ RadioSettingValueList(
+ LIST_SCODE,
+ LIST_SCODE[_mem.vfo.a.scode]))
+ work.append(rs)
+
+ rs = RadioSetting("vfo.b.scode", "VFO B S-CODE",
+ RadioSettingValueList(
+ LIST_SCODE,
+ LIST_SCODE[_mem.vfo.b.scode]))
+ work.append(rs)
+
+ rs = RadioSetting("vfo.a.step", "VFO A Tuning Step",
+ RadioSettingValueList(
+ LIST_STEP, LIST_STEP[_mem.vfo.a.step]))
+ work.append(rs)
+ rs = RadioSetting("vfo.b.step", "VFO B Tuning Step",
+ RadioSettingValueList(
+ LIST_STEP, LIST_STEP[_mem.vfo.b.step]))
+ work.append(rs)
+
+ # broadcast FM settings
+ _fm_presets = self._memobj.fm_presets
+ if _fm_presets <= 108.0 * 10 - 650:
+ preset = _fm_presets / 10.0 + 65
+ elif _fm_presets >= 65.0 * 10 and _fm_presets <= 108.0 * 10:
+ preset = _fm_presets / 10.0
+ else:
+ preset = 76.0
+ rs = RadioSetting("fm_presets", "FM Preset(MHz)",
+ RadioSettingValueFloat(65, 108.0, preset, 0.1, 1))
+ fm_preset.append(rs)
+
+ # DTMF encode settings
+ for i in range(0, 15):
+ _codeobj = self._memobj.pttid[i].code
+ _code = "".join([DTMF_CHARS[x] for x in _codeobj if int(x) < 0x1F])
+ val = RadioSettingValueString(0, 16, _code, False)
+ val.set_charset(DTMF_CHARS)
+ rs = RadioSetting("pttid/%i.code" % i,
+ "Signal Code %i" % (i + 1), val)
+
+ def apply_code(setting, obj):
+ code = []
+ for j in range(0, 16):
+ try:
+ code.append(DTMF_CHARS.index(str(setting.value)[j]))
+ except IndexError:
+ code.append(0xFF)
+ obj.code = code
+ rs.set_apply_callback(apply_code, self._memobj.pttid[i])
+ dtmfe.append(rs)
+
+ if _mem.ani.dtmfon > 0xC3:
+ val = 0x03
+ else:
+ val = _mem.ani.dtmfon
+ rs = RadioSetting("ani.dtmfon", "DTMF Speed (on)",
+ RadioSettingValueList(LIST_DTMFSPEED,
+ LIST_DTMFSPEED[val]))
+ dtmfe.append(rs)
+
+ if _mem.ani.dtmfoff > 0xC3:
+ val = 0x03
+ else:
+ val = _mem.ani.dtmfoff
+ rs = RadioSetting("ani.dtmfoff", "DTMF Speed (off)",
+ RadioSettingValueList(LIST_DTMFSPEED,
+ LIST_DTMFSPEED[val]))
+ dtmfe.append(rs)
+
+ _codeobj = self._memobj.ani.code
+ _code = "".join([DTMF_CHARS[x] for x in _codeobj if int(x) < 0x1F])
+ val = RadioSettingValueString(0, 7, _code, False)
+ val.set_charset(DTMF_CHARS)
+ rs = RadioSetting("ani.code", "ANI Code", val)
+
+ def apply_code(setting, obj):
+ code = []
+ for j in range(0, 7):
+ try:
+ code.append(DTMF_CHARS.index(str(setting.value)[j]))
+ except IndexError:
+ code.append(0xFF)
+ obj.code = code
+ rs.set_apply_callback(apply_code, self._memobj.ani)
+ dtmfe.append(rs)
+
+ rs = RadioSetting("ani.aniid", "When to send ANI ID",
+ RadioSettingValueList(LIST_PTTID,
+ LIST_PTTID[_mem.ani.aniid]))
+ dtmfe.append(rs)
+
+ # DTMF decode settings
+ rs = RadioSetting("ani.mastervice", "Master and Vice ID",
+ RadioSettingValueBoolean(_mem.ani.mastervice))
+ dtmfd.append(rs)
+
+ _codeobj = _mem.ani.masterid
+ _code = "".join([DTMF_CHARS[x] for x in _codeobj if int(x) < 0x1F])
+ val = RadioSettingValueString(0, 5, _code, False)
+ val.set_charset(DTMF_CHARS)
+ rs = RadioSetting("ani.masterid", "Master Control ID", val)
+
+ def apply_code(setting, obj):
+ code = []
+ for j in range(0, 5):
+ try:
+ code.append(DTMF_CHARS.index(str(setting.value)[j]))
+ except IndexError:
+ code.append(0xFF)
+ obj.masterid = code
+ rs.set_apply_callback(apply_code, self._memobj.ani)
+ dtmfd.append(rs)
+
+ rs = RadioSetting("ani.minspection", "Master Inspection",
+ RadioSettingValueBoolean(_mem.ani.minspection))
+ dtmfd.append(rs)
+
+ rs = RadioSetting("ani.mmonitor", "Master Monitor",
+ RadioSettingValueBoolean(_mem.ani.mmonitor))
+ dtmfd.append(rs)
+
+ rs = RadioSetting("ani.mstun", "Master Stun",
+ RadioSettingValueBoolean(_mem.ani.mstun))
+ dtmfd.append(rs)
+
+ rs = RadioSetting("ani.mkill", "Master Kill",
+ RadioSettingValueBoolean(_mem.ani.mkill))
+ dtmfd.append(rs)
+
+ rs = RadioSetting("ani.mrevive", "Master Revive",
+ RadioSettingValueBoolean(_mem.ani.mrevive))
+ dtmfd.append(rs)
+
+ _codeobj = _mem.ani.viceid
+ _code = "".join([DTMF_CHARS[x] for x in _codeobj if int(x) < 0x1F])
+ val = RadioSettingValueString(0, 5, _code, False)
+ val.set_charset(DTMF_CHARS)
+ rs = RadioSetting("ani.viceid", "Vice Control ID", val)
+
+ def apply_code(setting, obj):
+ code = []
+ for j in range(0, 5):
+ try:
+ code.append(DTMF_CHARS.index(str(setting.value)[j]))
+ except IndexError:
+ code.append(0xFF)
+ obj.viceid = code
+ rs.set_apply_callback(apply_code, self._memobj.ani)
+ dtmfd.append(rs)
+
+ rs = RadioSetting("ani.vinspection", "Vice Inspection",
+ RadioSettingValueBoolean(_mem.ani.vinspection))
+ dtmfd.append(rs)
+
+ rs = RadioSetting("ani.vmonitor", "Vice Monitor",
+ RadioSettingValueBoolean(_mem.ani.vmonitor))
+ dtmfd.append(rs)
+
+ rs = RadioSetting("ani.vstun", "Vice Stun",
+ RadioSettingValueBoolean(_mem.ani.vstun))
+ dtmfd.append(rs)
+
+ rs = RadioSetting("ani.vkill", "Vice Kill",
+ RadioSettingValueBoolean(_mem.ani.vkill))
+ dtmfd.append(rs)
+
+ rs = RadioSetting("ani.vrevive", "Vice Revive",
+ RadioSettingValueBoolean(_mem.ani.vrevive))
+ dtmfd.append(rs)
+
+ _codeobj = _mem.ani.inspection
+ _code = "".join([DTMF_CHARS[x] for x in _codeobj if int(x) < 0x1F])
+ val = RadioSettingValueString(0, 8, _code, False)
+ val.set_charset(DTMF_CHARS)
+ rs = RadioSetting("ani.inspection", "Inspection Code", val)
+
+ def apply_code(setting, obj):
+ code = []
+ for j in range(0, 8):
+ try:
+ code.append(DTMF_CHARS.index(str(setting.value)[j]))
+ except IndexError:
+ code.append(0xFF)
+ obj.inspection = code
+ rs.set_apply_callback(apply_code, self._memobj.ani)
+ dtmfd.append(rs)
+
+ _codeobj = _mem.ani.monitor
+ _code = "".join([DTMF_CHARS[x] for x in _codeobj if int(x) < 0x1F])
+ val = RadioSettingValueString(0, 8, _code, False)
+ val.set_charset(DTMF_CHARS)
+ rs = RadioSetting("ani.monitor", "Monitor Code", val)
+
+ def apply_code(setting, obj):
+ code = []
+ for j in range(0, 8):
+ try:
+ code.append(DTMF_CHARS.index(str(setting.value)[j]))
+ except IndexError:
+ code.append(0xFF)
+ obj.monitor = code
+ rs.set_apply_callback(apply_code, self._memobj.ani)
+ dtmfd.append(rs)
+
+ _codeobj = _mem.ani.alarmcode
+ _code = "".join([DTMF_CHARS[x] for x in _codeobj if int(x) < 0x1F])
+ val = RadioSettingValueString(0, 8, _code, False)
+ val.set_charset(DTMF_CHARS)
+ rs = RadioSetting("ani.alarm", "Alarm Code", val)
+
+ def apply_code(setting, obj):
+ code = []
+ for j in range(0, 8):
+ try:
+ code.append(DTMF_CHARS.index(str(setting.value)[j]))
+ except IndexError:
+ code.append(0xFF)
+ obj.alarmcode = code
+ rs.set_apply_callback(apply_code, self._memobj.ani)
+ dtmfd.append(rs)
+
+ _codeobj = _mem.ani.stun
+ _code = "".join([DTMF_CHARS[x] for x in _codeobj if int(x) < 0x1F])
+ val = RadioSettingValueString(0, 8, _code, False)
+ val.set_charset(DTMF_CHARS)
+ rs = RadioSetting("ani.stun", "Stun Code", val)
+
+ def apply_code(setting, obj):
+ code = []
+ for j in range(0, 8):
+ try:
+ code.append(DTMF_CHARS.index(str(setting.value)[j]))
+ except IndexError:
+ code.append(0xFF)
+ obj.stun = code
+ rs.set_apply_callback(apply_code, self._memobj.ani)
+ dtmfd.append(rs)
+
+ _codeobj = _mem.ani.kill
+ _code = "".join([DTMF_CHARS[x] for x in _codeobj if int(x) < 0x1F])
+ val = RadioSettingValueString(0, 8, _code, False)
+ val.set_charset(DTMF_CHARS)
+ rs = RadioSetting("ani.kill", "Kill Code", val)
+
+ def apply_code(setting, obj):
+ code = []
+ for j in range(0, 8):
+ try:
+ code.append(DTMF_CHARS.index(str(setting.value)[j]))
+ except IndexError:
+ code.append(0xFF)
+ obj.kill = code
+ rs.set_apply_callback(apply_code, self._memobj.ani)
+ dtmfd.append(rs)
+
+ _codeobj = _mem.ani.revive
+ _code = "".join([DTMF_CHARS[x] for x in _codeobj if int(x) < 0x1F])
+ val = RadioSettingValueString(0, 8, _code, False)
+ val.set_charset(DTMF_CHARS)
+ rs = RadioSetting("ani.revive", "Revive Code", val)
+
+ def apply_code(setting, obj):
+ code = []
+ for j in range(0, 8):
+ try:
+ code.append(DTMF_CHARS.index(str(setting.value)[j]))
+ except IndexError:
+ code.append(0xFF)
+ obj.revive = code
+ rs.set_apply_callback(apply_code, self._memobj.ani)
+ dtmfd.append(rs)
+
+ if _mem.ani.resettime > 0x9F:
+ val = 0x4F
+ else:
+ val = _mem.ani.resettime
+ rs = RadioSetting("ani.resettime", "Reset Time",
+ RadioSettingValueList(LIST_RESETTIME,
+ LIST_RESETTIME[val]))
+ dtmfd.append(rs)
+
+ if _mem.ani.delayproctime > 0x27:
+ val = 0x04
+ else:
+ val = _mem.ani.delayproctime
+ rs = RadioSetting("ani.delayproctime", "Delay Processing Time",
+ RadioSettingValueList(LIST_DELAYPROCTIME,
+ LIST_DELAYPROCTIME[val]))
+ dtmfd.append(rs)
+
+ # Service settings
+ for band in ["vhf", "uhf"]:
+ for index in range(0, 10):
+ key = "squelch.%s.sql%i" % (band, index)
+ if band == "vhf":
+ _obj = self._memobj.squelch.vhf
+ elif band == "uhf":
+ _obj = self._memobj.squelch.uhf
+ val = RadioSettingValueInteger(0, 123,
+ getattr(_obj, "sql%i" % (index)))
+ if index == 0:
+ val.set_mutable(False)
+ name = "%s Squelch %i" % (band.upper(), index)
+ rs = RadioSetting(key, name, val)
+ service.append(rs)
+
+ return top
+
+ @classmethod
+ def match_model(cls, filedata, filename):
+ match_size = False
+ match_model = False
+
+ # testing the file data size
+ if len(filedata) == 0x200E:
+ 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
diff --git a/chirp/drivers/uv6r.py b/chirp/drivers/uv6r.py
new file mode 100644
index 0000000..10fedad
--- /dev/null
+++ b/chirp/drivers/uv6r.py
@@ -0,0 +1,871 @@
+# Copyright 2016:
+# * Jim Unroe KC9HI, <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
+# 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.drivers import baofeng_common
+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, \
+ InvalidValueError
+from textwrap import dedent
+
+##### MAGICS #########################################################
+
+# Baofeng UV-6R magic string
+MSTRING_UV6R = "\x50\xBB\xFF\x20\x14\x11\x22"
+
+##### ID strings #####################################################
+
+# Baofeng UV-6R
+UV6R_fp1 = " BF230#1"
+
+DTMF_CHARS = "0123456789 *#ABCD"
+STEPS = [2.5, 5.0, 6.25, 10.0, 12.5, 20.0, 25.0, 50.0]
+
+LIST_AB = ["A", "B"]
+LIST_ALMOD = ["Site", "Tone", "Code"]
+LIST_BANDWIDTH = ["Wide", "Narrow"]
+LIST_COLOR = ["Off", "Blue", "Orange", "Purple"]
+LIST_DTMFSPEED = ["%s ms" % x for x in range(50, 2010, 10)]
+LIST_DTMFST = ["Off", "DT-ST", "ANI-ST", "DT+ANI"]
+LIST_MODE = ["Channel", "Name", "Frequency"]
+LIST_OFF1TO9 = ["Off"] + list("123456789")
+LIST_OFF1TO10 = LIST_OFF1TO9 + ["10"]
+LIST_OFFAB = ["Off"] + LIST_AB
+LIST_RESUME = ["TO", "CO", "SE"]
+LIST_PONMSG = ["Full", "Message"]
+LIST_PTTID = ["Off", "BOT", "EOT", "Both"]
+LIST_SCODE = ["%s" % x for x in range(1, 16)]
+LIST_RPSTE = ["Off"] + ["%s" % x for x in range(1, 11)]
+LIST_SAVE = ["Off", "1:1", "1:2", "1:3", "1:4"]
+LIST_SHIFTD = ["Off", "+", "-"]
+LIST_STEDELAY = ["Off"] + ["%s ms" % x for x in range(100, 1100, 100)]
+LIST_STEP = [str(x) for x in STEPS]
+LIST_TCALL = ["Off", "1000 Hz", "1450 Hz", "1750 Hz", "2100 Hz"]
+LIST_TIMEOUT = ["%s sec" % x for x in range(15, 615, 15)]
+LIST_TXPOWER = ["High", "Low"]
+LIST_VOICE = ["Off", "English", "Chinese"]
+LIST_WORKMODE = ["Frequency", "Channel"]
+
+def model_match(cls, data):
+ """Match the opened/downloaded image to the correct version"""
+ match_rid1 = False
+ match_rid2 = False
+
+ rid1 = data[0x1FF8:0x2000]
+
+ if rid1 in cls._fileid:
+ match_rid1 = True
+
+ rid2 = data[0x1FD0:0x1FD5]
+
+ if rid2 == cls.MODEL:
+ match_rid2 = True
+
+ if match_rid1 and match_rid2:
+ return True
+ else:
+ return False
+
+
+ at directory.register
+class UV6R(baofeng_common.BaofengCommonHT):
+ """Baofeng UV-6R"""
+ VENDOR = "Baofeng"
+ MODEL = "UV-6R"
+
+ _fileid = [UV6R_fp1, ]
+
+ _magic = [MSTRING_UV6R, ]
+ _magic_response_length = 8
+ _fw_ver_start = 0x1FF0
+ _recv_block_size = 0x40
+ _mem_size = 0x2000
+ _ack_block = False
+
+ _ranges = [(0x0000, 0x1800),
+ (0x1F40, 0x1F50),
+ (0x1FC0, 0x1FD0),
+ (0x1FE0, 0x1FF0)]
+ _send_block_size = 0x10
+
+ MODES = ["FM", "NFM"]
+ VALID_CHARS = chirp_common.CHARSET_ALPHANUMERIC + \
+ "!@#$%^&*()+-=[]:\";'<>?,./"
+ LENGTH_NAME = 6
+ SKIP_VALUES = ["", "S"]
+ DTCS_CODES = sorted(chirp_common.DTCS_CODES + [645])
+ POWER_LEVELS = [chirp_common.PowerLevel("High", watts=5.00),
+ chirp_common.PowerLevel("Low", watts=1.00)]
+ VALID_BANDS = [(136000000, 174000000),
+ (400000000, 520000000)]
+ PTTID_LIST = LIST_PTTID
+ SCODE_LIST = LIST_SCODE
+
+
+ MEM_FORMAT = """
+ #seekto 0x0000;
+ struct {
+ lbcd rxfreq[4];
+ lbcd txfreq[4];
+ ul16 rxtone;
+ ul16 txtone;
+ u8 unknown0:4,
+ scode:4;
+ u8 unknown1;
+ u8 unknown2:7,
+ lowpower:1;
+ u8 unknown3:1,
+ wide:1,
+ unknown4:2,
+ bcl:1,
+ scan:1,
+ pttid:2;
+ } memory[128];
+
+ #seekto 0x0B00;
+ struct {
+ u8 code[5];
+ u8 unused[11];
+ } pttid[15];
+
+ #seekto 0x0CAA;
+ struct {
+ u8 code[5];
+ u8 unused:6,
+ aniid:2;
+ u8 unknown[2];
+ u8 dtmfon;
+ u8 dtmfoff;
+ } ani;
+
+ #seekto 0x0E20;
+ struct {
+ u8 unused00:4,
+ squelch:4;
+ u8 unused01:5,
+ step:3;
+ u8 unknown00;
+ u8 unused02:5,
+ save:3;
+ u8 unused03:4,
+ vox:4;
+ u8 unknown01;
+ u8 unused04:4,
+ abr:4;
+ u8 unused05:7,
+ tdr:1;
+ u8 unused06:7,
+ beep:1;
+ u8 unused07:2,
+ timeout:6;
+ u8 unused08:6,
+ tcall:2;
+ u8 unknown02[3];
+ u8 unused09:6,
+ voice:2;
+ u8 unknown03;
+ u8 unused10:6,
+ dtmfst:2;
+ u8 unknown04;
+ u8 unused11:6,
+ screv:2;
+ u8 unused12:6,
+ pttid:2;
+ u8 unused13:2,
+ pttlt:6;
+ u8 unused14:6,
+ mdfa:2;
+ u8 unused15:6,
+ mdfb:2;
+ u8 unknown05;
+ u8 unused16:7,
+ autolk:1;
+ u8 unknown06[4];
+ u8 unused17:6,
+ wtled:2;
+ u8 unused18:6,
+ rxled:2;
+ u8 unused19:6,
+ txled:2;
+ u8 unused20:6,
+ almod:2;
+ u8 unknown07[2];
+ u8 unused22:7,
+ ste:1;
+ u8 unused23:4,
+ rpste:4;
+ u8 unused24:4,
+ rptrl:4;
+ u8 unused25:7,
+ ponmsg:1;
+ u8 unused26:7,
+ roger:1;
+ u8 unused27:7,
+ reset:1;
+ u8 unknown08;
+ u8 displayab:1,
+ unknown09:2,
+ fmradio:1,
+ alarm:1,
+ unknown10:1,
+ reset:1,
+ menu:1;
+ u8 unknown11;
+ u8 unused29:7,
+ workmode:1;
+ u8 unused30:7,
+ keylock:1;
+ u8 cht;
+ } settings;
+
+ #seekto 0x0E76;
+ struct {
+ u8 unused0:1,
+ mrcha:7;
+ u8 unused1:1,
+ mrchb:7;
+ } wmchannel;
+
+ struct vfo {
+ u8 unknown0[8];
+ u8 freq[8];
+ u8 offset[6];
+ ul16 rxtone;
+ ul16 txtone;
+ u8 unused0:7,
+ band:1;
+ u8 unknown3;
+ u8 unknown4:2,
+ sftd:2,
+ scode:4;
+ u8 unknown5;
+ u8 unknown6:1,
+ step:3,
+ unknown7:4;
+ u8 txpower:1,
+ widenarr:1,
+ unknown8:6;
+ };
+
+ #seekto 0x0F00;
+ struct {
+ struct vfo a;
+ struct vfo b;
+ } vfo;
+
+ #seekto 0x0F4E;
+ u16 fm_presets;
+
+ #seekto 0x1000;
+ struct {
+ char name[6];
+ u8 unknown[10];
+ } names[128];
+
+ #seekto 0x1F40;
+ struct {
+ u8 sql0;
+ u8 sql1;
+ u8 sql2;
+ u8 sql3;
+ u8 sql4;
+ u8 sql5;
+ u8 sql6;
+ u8 sql7;
+ u8 sql8;
+ u8 sql9;
+ } squelch;
+
+ struct limit {
+ u8 enable;
+ bbcd lower[2];
+ bbcd upper[2];
+ };
+
+ #seekto 0x1FC0;
+ struct {
+ struct limit vhf;
+ struct limit uhf;
+ } limits;
+
+ #seekto 0x1FD0;
+ struct {
+ char line1[8];
+ char line2[8];
+ } sixpoweron_msg;
+
+ #seekto 0x1FE0;
+ struct {
+ char line1[7];
+ char line2[7];
+ } poweron_msg;
+
+ #seekto 0x1FF0;
+ struct {
+ char line1[8];
+ char line2[8];
+ } firmware_msg;
+
+ """
+
+ @classmethod
+ def get_prompts(cls):
+ rp = chirp_common.RadioPrompts()
+ rp.experimental = \
+ ('The BTech UV-6R 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 these 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 process_mmap(self):
+ """Process the mem map into the mem object"""
+ self._memobj = bitwise.parse(self.MEM_FORMAT, self._mmap)
+
+ def get_settings(self):
+ """Translate the bit in the mem_struct into settings in the UI"""
+ _mem = self._memobj
+ basic = RadioSettingGroup("basic", "Basic Settings")
+ advanced = RadioSettingGroup("advanced", "Advanced Settings")
+ other = RadioSettingGroup("other", "Other Settings")
+ work = RadioSettingGroup("work", "Work Mode Settings")
+ fm_preset = RadioSettingGroup("fm_preset", "FM Preset")
+ dtmfe = RadioSettingGroup("dtmfe", "DTMF Encode Settings")
+ service = RadioSettingGroup("service", "Service Settings")
+ top = RadioSettings(basic, advanced, other, work, fm_preset, dtmfe,
+ service)
+
+ # Basic settings
+ if _mem.settings.squelch > 0x09:
+ val = 0x00
+ else:
+ val = _mem.settings.squelch
+ rs = RadioSetting("settings.squelch", "Squelch",
+ RadioSettingValueList(
+ LIST_OFF1TO9, LIST_OFF1TO9[val]))
+ basic.append(rs)
+
+ if _mem.settings.save > 0x04:
+ val = 0x00
+ else:
+ val = _mem.settings.save
+ rs = RadioSetting("settings.save", "Battery Saver",
+ RadioSettingValueList(
+ LIST_SAVE, LIST_SAVE[val]))
+ basic.append(rs)
+
+ if _mem.settings.vox > 0x0A:
+ val = 0x00
+ else:
+ val = _mem.settings.vox
+ rs = RadioSetting("settings.vox", "Vox",
+ RadioSettingValueList(
+ LIST_OFF1TO10, LIST_OFF1TO10[val]))
+ basic.append(rs)
+
+ if _mem.settings.abr > 0x0A:
+ val = 0x00
+ else:
+ val = _mem.settings.abr
+ rs = RadioSetting("settings.abr", "Backlight Timeout",
+ RadioSettingValueList(
+ LIST_OFF1TO10, LIST_OFF1TO10[val]))
+ basic.append(rs)
+
+ rs = RadioSetting("settings.tdr", "Dual Watch",
+ RadioSettingValueBoolean(_mem.settings.tdr))
+ basic.append(rs)
+
+ rs = RadioSetting("settings.beep", "Beep",
+ RadioSettingValueBoolean(_mem.settings.beep))
+ basic.append(rs)
+
+ if _mem.settings.timeout > 0x27:
+ val = 0x03
+ else:
+ val = _mem.settings.timeout
+ rs = RadioSetting("settings.timeout", "Timeout Timer",
+ RadioSettingValueList(
+ LIST_TIMEOUT, LIST_TIMEOUT[val]))
+ basic.append(rs)
+
+ if _mem.settings.voice > 0x02:
+ val = 0x01
+ else:
+ val = _mem.settings.voice
+ rs = RadioSetting("settings.voice", "Voice Prompt",
+ RadioSettingValueList(
+ LIST_VOICE, LIST_VOICE[val]))
+ basic.append(rs)
+
+ rs = RadioSetting("settings.dtmfst", "DTMF Sidetone",
+ RadioSettingValueList(LIST_DTMFST, LIST_DTMFST[
+ _mem.settings.dtmfst]))
+ basic.append(rs)
+
+ if _mem.settings.screv > 0x02:
+ val = 0x01
+ else:
+ val = _mem.settings.screv
+ rs = RadioSetting("settings.screv", "Scan Resume",
+ RadioSettingValueList(
+ LIST_RESUME, LIST_RESUME[val]))
+ basic.append(rs)
+
+ rs = RadioSetting("settings.pttid", "When to send PTT ID",
+ RadioSettingValueList(LIST_PTTID, LIST_PTTID[
+ _mem.settings.pttid]))
+ basic.append(rs)
+
+ if _mem.settings.pttlt > 0x1E:
+ val = 0x05
+ else:
+ val = _mem.settings.pttlt
+ rs = RadioSetting("pttlt", "PTT ID Delay",
+ RadioSettingValueInteger(0, 50, val))
+ basic.append(rs)
+
+ rs = RadioSetting("settings.mdfa", "Display Mode (A)",
+ RadioSettingValueList(LIST_MODE, LIST_MODE[
+ _mem.settings.mdfa]))
+ basic.append(rs)
+
+ rs = RadioSetting("settings.mdfb", "Display Mode (B)",
+ RadioSettingValueList(LIST_MODE, LIST_MODE[
+ _mem.settings.mdfb]))
+ basic.append(rs)
+
+ rs = RadioSetting("settings.autolk", "Auto Lock Keypad",
+ RadioSettingValueBoolean(_mem.settings.autolk))
+ basic.append(rs)
+
+ rs = RadioSetting("settings.wtled", "Standby LED Color",
+ RadioSettingValueList(
+ LIST_COLOR, LIST_COLOR[_mem.settings.wtled]))
+ basic.append(rs)
+
+ rs = RadioSetting("settings.rxled", "RX LED Color",
+ RadioSettingValueList(
+ LIST_COLOR, LIST_COLOR[_mem.settings.rxled]))
+ basic.append(rs)
+
+ rs = RadioSetting("settings.txled", "TX LED Color",
+ RadioSettingValueList(
+ LIST_COLOR, LIST_COLOR[_mem.settings.txled]))
+ basic.append(rs)
+
+ if _mem.settings.almod > 0x02:
+ val = 0x00
+ else:
+ val = _mem.settings.almod
+ rs = RadioSetting("settings.almod", "Alarm Mode",
+ RadioSettingValueList(
+ LIST_ALMOD, LIST_ALMOD[val]))
+ basic.append(rs)
+
+ if _mem.settings.tcall > 0x05:
+ val = 0x00
+ else:
+ val = _mem.settings.tcall
+ rs = RadioSetting("settings.tcall", "Tone Burst Frequency",
+ RadioSettingValueList(
+ LIST_TCALL, LIST_TCALL[val]))
+ basic.append(rs)
+
+ rs = RadioSetting("settings.ste", "Squelch Tail Eliminate (HT to HT)",
+ RadioSettingValueBoolean(_mem.settings.ste))
+ basic.append(rs)
+
+ if _mem.settings.rpste > 0x0A:
+ val = 0x00
+ else:
+ val = _mem.settings.rpste
+ rs = RadioSetting("settings.rpste",
+ "Squelch Tail Eliminate (repeater)",
+ RadioSettingValueList(
+ LIST_RPSTE, LIST_RPSTE[val]))
+ basic.append(rs)
+
+ if _mem.settings.rptrl > 0x0A:
+ val = 0x00
+ else:
+ val = _mem.settings.rptrl
+ rs = RadioSetting("settings.rptrl", "STE Repeater Delay",
+ RadioSettingValueList(
+ LIST_STEDELAY, LIST_STEDELAY[val]))
+ basic.append(rs)
+
+ rs = RadioSetting("settings.ponmsg", "Power-On Message",
+ RadioSettingValueList(LIST_PONMSG, LIST_PONMSG[
+ _mem.settings.ponmsg]))
+ basic.append(rs)
+
+ rs = RadioSetting("settings.roger", "Roger Beep",
+ RadioSettingValueBoolean(_mem.settings.roger))
+ basic.append(rs)
+
+ # Advanced settings
+ rs = RadioSetting("settings.reset", "RESET Menu",
+ RadioSettingValueBoolean(_mem.settings.reset))
+ advanced.append(rs)
+
+ rs = RadioSetting("settings.menu", "All Menus",
+ RadioSettingValueBoolean(_mem.settings.menu))
+ advanced.append(rs)
+
+ rs = RadioSetting("settings.fmradio", "Broadcast FM Radio",
+ RadioSettingValueBoolean(_mem.settings.fmradio))
+ advanced.append(rs)
+
+ rs = RadioSetting("settings.alarm", "Alarm Sound",
+ RadioSettingValueBoolean(_mem.settings.alarm))
+ advanced.append(rs)
+
+ # Other settings
+ def _filter(name):
+ filtered = ""
+ for char in str(name):
+ if char in chirp_common.CHARSET_ASCII:
+ filtered += char
+ else:
+ filtered += " "
+ return filtered
+
+ _msg = _mem.firmware_msg
+ val = RadioSettingValueString(0, 8, _filter(_msg.line1))
+ val.set_mutable(False)
+ rs = RadioSetting("firmware_msg.line1", "Firmware Message 1", val)
+ other.append(rs)
+
+ val = RadioSettingValueString(0, 8, _filter(_msg.line2))
+ val.set_mutable(False)
+ rs = RadioSetting("firmware_msg.line2", "Firmware Message 2", val)
+ other.append(rs)
+
+ _msg = _mem.sixpoweron_msg
+ val = RadioSettingValueString(0, 8, _filter(_msg.line1))
+ val.set_mutable(False)
+ rs = RadioSetting("sixpoweron_msg.line1", "6+Power-On Message 1", val)
+ other.append(rs)
+ val = RadioSettingValueString(0, 8, _filter(_msg.line2))
+ val.set_mutable(False)
+ rs = RadioSetting("sixpoweron_msg.line2", "6+Power-On Message 2", val)
+ other.append(rs)
+
+ _msg = _mem.poweron_msg
+ rs = RadioSetting("poweron_msg.line1", "Power-On Message 1",
+ RadioSettingValueString(
+ 0, 7, _filter(_msg.line1)))
+ other.append(rs)
+ rs = RadioSetting("poweron_msg.line2", "Power-On Message 2",
+ RadioSettingValueString(
+ 0, 7, _filter(_msg.line2)))
+ other.append(rs)
+
+ lower = 136
+ upper = 174
+ rs = RadioSetting("limits.vhf.lower", "VHF Lower Limit (MHz)",
+ RadioSettingValueInteger(
+ lower, upper, _mem.limits.vhf.lower))
+ other.append(rs)
+
+ rs = RadioSetting("limits.vhf.upper", "VHF Upper Limit (MHz)",
+ RadioSettingValueInteger(
+ lower, upper, _mem.limits.vhf.upper))
+ other.append(rs)
+
+ lower = 400
+ upper = 520
+ rs = RadioSetting("limits.uhf.lower", "UHF Lower Limit (MHz)",
+ RadioSettingValueInteger(
+ lower, upper, _mem.limits.uhf.lower))
+ other.append(rs)
+
+ rs = RadioSetting("limits.uhf.upper", "UHF Upper Limit (MHz)",
+ RadioSettingValueInteger(
+ lower, upper, _mem.limits.uhf.upper))
+ other.append(rs)
+
+ # Work mode settings
+ rs = RadioSetting("settings.displayab", "Display",
+ RadioSettingValueList(
+ LIST_AB, LIST_AB[_mem.settings.displayab]))
+ work.append(rs)
+
+ rs = RadioSetting("settings.workmode", "VFO/MR Mode",
+ RadioSettingValueList(
+ LIST_WORKMODE,
+ LIST_WORKMODE[_mem.settings.workmode]))
+ work.append(rs)
+
+ rs = RadioSetting("settings.keylock", "Keypad Lock",
+ RadioSettingValueBoolean(_mem.settings.keylock))
+ work.append(rs)
+
+ rs = RadioSetting("wmchannel.mrcha", "MR A Channel",
+ RadioSettingValueInteger(0, 127,
+ _mem.wmchannel.mrcha))
+ work.append(rs)
+
+ rs = RadioSetting("wmchannel.mrchb", "MR B Channel",
+ RadioSettingValueInteger(0, 127,
+ _mem.wmchannel.mrchb))
+ work.append(rs)
+
+ def convert_bytes_to_freq(bytes):
+ real_freq = 0
+ for byte in bytes:
+ real_freq = (real_freq * 10) + byte
+ return chirp_common.format_freq(real_freq * 10)
+
+ def my_validate(value):
+ _vhf_lower = int(_mem.limits.vhf.lower)
+ _vhf_upper = int(_mem.limits.vhf.upper)
+ _uhf_lower = int(_mem.limits.uhf.lower)
+ _uhf_upper = int(_mem.limits.uhf.upper)
+ value = chirp_common.parse_freq(value)
+ msg = ("Can't be less than %i.0000")
+ if value > 99000000 and value < _vhf_lower * 1000000:
+ raise InvalidValueError(msg % _vhf_lower)
+ msg = ("Can't be between %i.9975-%i.0000")
+ if _vhf_upper * 1000000 <= value and value < _uhf_lower * 1000000:
+ raise InvalidValueError(msg % (_vhf_upper - 1, _uhf_lower))
+ msg = ("Can't be greater than %i.9975")
+ if value > 99000000 and value >= _uhf_upper * 1000000:
+ raise InvalidValueError(msg % (_uhf_upper - 1))
+ return chirp_common.format_freq(value)
+
+ def apply_freq(setting, obj):
+ value = chirp_common.parse_freq(str(setting.value)) / 10
+ for i in range(7, -1, -1):
+ obj.freq[i] = value % 10
+ value /= 10
+
+ val1a = RadioSettingValueString(0, 10,
+ convert_bytes_to_freq(_mem.vfo.a.freq))
+ val1a.set_validate_callback(my_validate)
+ rs = RadioSetting("vfo.a.freq", "VFO A Frequency", val1a)
+ rs.set_apply_callback(apply_freq, _mem.vfo.a)
+ work.append(rs)
+
+ val1b = RadioSettingValueString(0, 10,
+ convert_bytes_to_freq(_mem.vfo.b.freq))
+ val1b.set_validate_callback(my_validate)
+ rs = RadioSetting("vfo.b.freq", "VFO B Frequency", val1b)
+ rs.set_apply_callback(apply_freq, _mem.vfo.b)
+ work.append(rs)
+
+ rs = RadioSetting("vfo.a.sftd", "VFO A Shift",
+ RadioSettingValueList(
+ LIST_SHIFTD, LIST_SHIFTD[_mem.vfo.a.sftd]))
+ work.append(rs)
+
+ rs = RadioSetting("vfo.b.sftd", "VFO B Shift",
+ RadioSettingValueList(
+ LIST_SHIFTD, LIST_SHIFTD[_mem.vfo.b.sftd]))
+ work.append(rs)
+
+ def convert_bytes_to_offset(bytes):
+ real_offset = 0
+ for byte in bytes:
+ real_offset = (real_offset * 10) + byte
+ return chirp_common.format_freq(real_offset * 1000)
+
+ def apply_offset(setting, obj):
+ value = chirp_common.parse_freq(str(setting.value)) / 1000
+ for i in range(5, -1, -1):
+ obj.offset[i] = value % 10
+ value /= 10
+
+ val1a = RadioSettingValueString(
+ 0, 10, convert_bytes_to_offset(_mem.vfo.a.offset))
+ rs = RadioSetting("vfo.a.offset",
+ "VFO A Offset", val1a)
+ rs.set_apply_callback(apply_offset, _mem.vfo.a)
+ work.append(rs)
+
+ val1b = RadioSettingValueString(
+ 0, 10, convert_bytes_to_offset(_mem.vfo.b.offset))
+ rs = RadioSetting("vfo.b.offset",
+ "VFO B Offset", val1b)
+ rs.set_apply_callback(apply_offset, _mem.vfo.b)
+ work.append(rs)
+
+ rs = RadioSetting("vfo.a.txpower", "VFO A Power",
+ RadioSettingValueList(
+ LIST_TXPOWER,
+ LIST_TXPOWER[_mem.vfo.a.txpower]))
+ work.append(rs)
+
+ rs = RadioSetting("vfo.b.txpower", "VFO B Power",
+ RadioSettingValueList(
+ LIST_TXPOWER,
+ LIST_TXPOWER[_mem.vfo.b.txpower]))
+ work.append(rs)
+
+ rs = RadioSetting("vfo.a.widenarr", "VFO A Bandwidth",
+ RadioSettingValueList(
+ LIST_BANDWIDTH,
+ LIST_BANDWIDTH[_mem.vfo.a.widenarr]))
+ work.append(rs)
+
+ rs = RadioSetting("vfo.b.widenarr", "VFO B Bandwidth",
+ RadioSettingValueList(
+ LIST_BANDWIDTH,
+ LIST_BANDWIDTH[_mem.vfo.b.widenarr]))
+ work.append(rs)
+
+ rs = RadioSetting("vfo.a.scode", "VFO A S-CODE",
+ RadioSettingValueList(
+ LIST_SCODE,
+ LIST_SCODE[_mem.vfo.a.scode]))
+ work.append(rs)
+
+ rs = RadioSetting("vfo.b.scode", "VFO B S-CODE",
+ RadioSettingValueList(
+ LIST_SCODE,
+ LIST_SCODE[_mem.vfo.b.scode]))
+ work.append(rs)
+
+ rs = RadioSetting("vfo.a.step", "VFO A Tuning Step",
+ RadioSettingValueList(
+ LIST_STEP, LIST_STEP[_mem.vfo.a.step]))
+ work.append(rs)
+ rs = RadioSetting("vfo.b.step", "VFO B Tuning Step",
+ RadioSettingValueList(
+ LIST_STEP, LIST_STEP[_mem.vfo.b.step]))
+ work.append(rs)
+
+ # broadcast FM settings
+ _fm_presets = self._memobj.fm_presets
+ if _fm_presets <= 108.0 * 10 - 650:
+ preset = _fm_presets / 10.0 + 65
+ elif _fm_presets >= 65.0 * 10 and _fm_presets <= 108.0 * 10:
+ preset = _fm_presets / 10.0
+ else:
+ preset = 76.0
+ rs = RadioSetting("fm_presets", "FM Preset(MHz)",
+ RadioSettingValueFloat(65, 108.0, preset, 0.1, 1))
+ fm_preset.append(rs)
+
+ # DTMF settings
+ def apply_code(setting, obj, length):
+ code = []
+ for j in range(0, length):
+ try:
+ code.append(DTMF_CHARS.index(str(setting.value)[j]))
+ except IndexError:
+ code.append(0xFF)
+ obj.code = code
+
+ for i in range(0, 15):
+ _codeobj = self._memobj.pttid[i].code
+ _code = "".join([DTMF_CHARS[x] for x in _codeobj if int(x) < 0x1F])
+ val = RadioSettingValueString(0, 5, _code, False)
+ val.set_charset(DTMF_CHARS)
+ pttid = RadioSetting("pttid/%i.code" % i,
+ "Signal Code %i" % (i + 1), val)
+ pttid.set_apply_callback(apply_code, self._memobj.pttid[i], 5)
+ dtmfe.append(pttid)
+
+ if _mem.ani.dtmfon > 0xC3:
+ val = 0x03
+ else:
+ val = _mem.ani.dtmfon
+ rs = RadioSetting("ani.dtmfon", "DTMF Speed (on)",
+ RadioSettingValueList(LIST_DTMFSPEED,
+ LIST_DTMFSPEED[val]))
+ dtmfe.append(rs)
+
+ if _mem.ani.dtmfoff > 0xC3:
+ val = 0x03
+ else:
+ val = _mem.ani.dtmfoff
+ rs = RadioSetting("ani.dtmfoff", "DTMF Speed (off)",
+ RadioSettingValueList(LIST_DTMFSPEED,
+ LIST_DTMFSPEED[val]))
+ dtmfe.append(rs)
+
+ _codeobj = self._memobj.ani.code
+ _code = "".join([DTMF_CHARS[x] for x in _codeobj if int(x) < 0x1F])
+ val = RadioSettingValueString(0, 5, _code, False)
+ val.set_charset(DTMF_CHARS)
+ rs = RadioSetting("ani.code", "ANI Code", val)
+ rs.set_apply_callback(apply_code, self._memobj.ani, 5)
+ dtmfe.append(rs)
+
+ rs = RadioSetting("ani.aniid", "When to send ANI ID",
+ RadioSettingValueList(LIST_PTTID,
+ LIST_PTTID[_mem.ani.aniid]))
+ dtmfe.append(rs)
+
+ # Service settings
+ for index in range(0, 10):
+ key = "squelch.sql%i" % (index)
+ _obj = self._memobj.squelch
+ val = RadioSettingValueInteger(0, 123,
+ getattr(_obj, "sql%i" % (index)))
+ if index == 0:
+ val.set_mutable(False)
+ name = "Squelch %i" % (index)
+ rs = RadioSetting(key, name, val)
+ service.append(rs)
+
+ return top
+
+ @classmethod
+ def match_model(cls, filedata, filename):
+ match_size = False
+ match_model = False
+
+ # testing the file data size
+ if len(filedata) == 0x2008:
+ 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
diff --git a/chirp/drivers/wouxun.py b/chirp/drivers/wouxun.py
index 23eb3a7..58536de 100644
--- a/chirp/drivers/wouxun.py
+++ b/chirp/drivers/wouxun.py
@@ -887,6 +887,7 @@ class KGUVD1PRadio(chirp_common.CloneModeRadio,
# New-style image (CHIRP 0.1.12)
if len(filedata) == 8192 and \
filedata[0x60:0x64] != "2009" and \
+ filedata[0x170:0x173] != "LX-" and \
filedata[0x1f77:0x1f7d] == "\xff\xff\xff\xff\xff\xff" and \
filedata[0x0d70:0x0d80] == "\xff\xff\xff\xff\xff\xff\xff\xff" \
"\xff\xff\xff\xff\xff\xff\xff\xff":
diff --git a/chirp/radioreference.py b/chirp/radioreference.py
index d52284e..515e96f 100644
--- a/chirp/radioreference.py
+++ b/chirp/radioreference.py
@@ -98,7 +98,10 @@ class RadioReferenceRadio(chirp_common.NetworkSourceRadio):
for cat in agency.cats:
LOG.debug("Fetching category:", cat.cName)
for subcat in cat.subcats:
- LOG.debug("\t", subcat.scName)
+ try:
+ LOG.debug("\t", subcat.scName)
+ except AttributeError:
+ pass
result = self._client.service.getSubcatFreqs(subcat.scid,
self._auth)
self._freqs += result
diff --git a/chirp/settings.py b/chirp/settings.py
index b03cd4f..dd690ad 100644
--- a/chirp/settings.py
+++ b/chirp/settings.py
@@ -28,6 +28,7 @@ class InternalError(Exception):
class RadioSettingValue:
"""Base class for a single radio setting"""
+
def __init__(self):
self._current = None
self._has_changed = False
@@ -69,6 +70,7 @@ class RadioSettingValue:
class RadioSettingValueInteger(RadioSettingValue):
"""An integer setting"""
+
def __init__(self, minval, maxval, current, step=1):
RadioSettingValue.__init__(self)
self._min = minval
@@ -101,6 +103,7 @@ class RadioSettingValueInteger(RadioSettingValue):
class RadioSettingValueFloat(RadioSettingValue):
"""A floating-point setting"""
+
def __init__(self, minval, maxval, current, resolution=0.001, precision=4):
RadioSettingValue.__init__(self)
self._min = minval
@@ -123,8 +126,8 @@ class RadioSettingValueFloat(RadioSettingValue):
raise InvalidValueError("A floating point value is required")
if value > self._max or value < self._min:
raise InvalidValueError("Value %s not in range %s-%s" % (
- self.format(value),
- self.format(self._min), self.format(self._max)))
+ self.format(value),
+ self.format(self._min), self.format(self._max)))
# FIXME: honor resolution
@@ -140,6 +143,7 @@ class RadioSettingValueFloat(RadioSettingValue):
class RadioSettingValueBoolean(RadioSettingValue):
"""A boolean setting"""
+
def __init__(self, current):
RadioSettingValue.__init__(self)
self.set_value(current)
@@ -157,6 +161,7 @@ class RadioSettingValueBoolean(RadioSettingValue):
class RadioSettingValueList(RadioSettingValue):
"""A list-of-strings setting"""
+
def __init__(self, options, current):
RadioSettingValue.__init__(self)
self._options = options
@@ -177,6 +182,7 @@ class RadioSettingValueList(RadioSettingValue):
class RadioSettingValueString(RadioSettingValue):
"""A string setting"""
+
def __init__(self, minlength, maxlength, current,
autopad=True, charset=chirp_common.CHARSET_ASCII):
RadioSettingValue.__init__(self)
@@ -214,6 +220,7 @@ class RadioSettingValueMap(RadioSettingValueList):
conversions not needed.
"""
+
def __init__(self, map_entries, mem_val=None, user_option=None):
"""Create new map
@@ -242,7 +249,12 @@ class RadioSettingValueMap(RadioSettingValueList):
index = self._mem_vals.index(mem_val)
self.set_value(self._options[index])
else:
- raise InvalidValueError("%s is not valid for this setting" % value)
+ raise InvalidValueError(
+ "%s is not valid for this setting" % mem_val)
+
+ def get_mem_val(self):
+ """Get the mem val corresponding to the currently selected user option"""
+ return self._mem_vals[self._options.index(self.get_value())]
def __trunc__(self):
"""Return memory value that matches current user option"""
@@ -264,6 +276,7 @@ def zero_indexed_seq_map(user_options):
class RadioSettings(list):
+
def __init__(self, *groups):
list.__init__(self, groups)
@@ -274,6 +287,7 @@ class RadioSettings(list):
class RadioSettingGroup(object):
"""A group of settings"""
+
def _validate(self, element):
# RadioSettingGroup can only contain RadioSettingGroup objects
if not isinstance(element, RadioSettingGroup):
@@ -364,6 +378,7 @@ class RadioSettingGroup(object):
class RadioSetting(RadioSettingGroup):
"""A single setting, which could be an array of items like a group"""
+
def __init__(self, *args):
super(RadioSetting, self).__init__(*args)
self._apply_callback = None
diff --git a/chirp/ui/editorset.py b/chirp/ui/editorset.py
index 5c5d62e..2866dc9 100644
--- a/chirp/ui/editorset.py
+++ b/chirp/ui/editorset.py
@@ -70,6 +70,7 @@ class EditorSet(gtk.VBox):
members.connect("changed", lambda x: names.mappings_changed())
names.connect("changed", lambda x: members.mappings_changed())
names.connect("changed", self.editor_changed)
+ sub_index += 1
def _make_device_editors(self, device, devrthread, index):
if isinstance(device, chirp_common.IcomDstarSupport):
diff --git a/chirp/ui/mainapp.py b/chirp/ui/mainapp.py
index b3ca4ea..1d0156c 100644
--- a/chirp/ui/mainapp.py
+++ b/chirp/ui/mainapp.py
@@ -81,6 +81,7 @@ class ModifiedError(Exception):
class ChirpMain(gtk.Window):
+
def get_current_editorset(self):
page = self.tabs.get_current_page()
if page is not None:
@@ -137,7 +138,7 @@ class ChirpMain(gtk.Window):
def ev_editor_selected(self, editorset, editortype):
mappings = {
"memedit": ["view", "edit"],
- }
+ }
for _editortype, actions in mappings.items():
for _action in actions:
@@ -443,7 +444,7 @@ of file.
).replace('/', '_')
types = [(label + " (*.%s)" % eset.radio.FILE_EXTENSION,
- eset.radio.FILE_EXTENSION)]
+ eset.radio.FILE_EXTENSION)]
if isinstance(eset.radio, vx7.VX7Radio):
types += [(_("VX7 Commander") + " (*.vx7)", "vx7")]
@@ -517,7 +518,7 @@ of file.
file_basename = os.path.basename(fname).replace("_", "__")
action = gtk.Action(
- action_name, "_%i. %s" % (i+1, file_basename),
+ action_name, "_%i. %s" % (i + 1, file_basename),
_("Open recent file {name}").format(name=fname), "")
action.connect("activate", lambda a, f: self.do_open(f), fname)
mid = self.menu_uim.new_merge_id()
@@ -644,7 +645,7 @@ of file.
_("Don't show instructions for any radio again"))
again.show()
again.connect("toggled", lambda action:
- self.clonemenu.set_active(not action.get_active()))
+ self.clonemenu.set_active(not action.get_active()))
d.vbox.pack_start(again, 0, 0, 0)
h_button_box = d.vbox.get_children()[2]
try:
@@ -657,7 +658,6 @@ of file.
d.run()
d.destroy()
-
def do_download(self, port=None, rtype=None):
d = clone.CloneSettingsDialog(parent=self)
settings = d.run()
@@ -989,7 +989,7 @@ of file.
"Latitude": (gtk.Entry(), lambda x: float(x.get_text())),
"Longitude": (gtk.Entry(), lambda x: float(x.get_text())),
"Range": (gtk.Entry(), lambda x: int(x.get_text())),
- }
+ }
for name in sorted(fields.keys()):
value, fn = fields[name]
d.add_field(name, value)
@@ -1371,7 +1371,8 @@ of file.
devaction.set_visible(action.get_active())
def do_toggle_clone_instructions(self, action):
- CONF.set_bool("clone_instructions", not action.get_active(), "noconfirm")
+ CONF.set_bool("clone_instructions",
+ not action.get_active(), "noconfirm")
def do_change_language(self):
langs = ["Auto", "English", "Polish", "Italian", "Dutch", "German",
@@ -1629,7 +1630,7 @@ of file.
('help', None, _('Help'), None, None, self.mh),
('about', gtk.STOCK_ABOUT, None, None, None, self.mh),
('gethelp', None, _("Get Help Online..."), None, None, self.mh),
- ]
+ ]
conf = config.get()
re = not conf.get_bool("no_report")
@@ -1660,7 +1661,8 @@ of file.
self.add_accel_group(self.menu_uim.get_accel_group())
- self.clonemenu = self.menu_uim.get_widget("/MenuBar/help/clone_instructions")
+ self.clonemenu = self.menu_uim.get_widget(
+ "/MenuBar/help/clone_instructions")
# Initialize
self.do_toggle_developer(self.menu_ag.get_action("developer"))
@@ -1719,7 +1721,7 @@ of file.
actions = [
# ("action_name", "key", function)
- ]
+ ]
for name, key, fn in actions:
a = gtk.Action(name, name, name, "")
diff --git a/stock_configs/EU LPD and PMR Channels.csv b/stock_configs/EU LPD and PMR Channels.csv
index 57eab2e..19d9e14 100644
--- a/stock_configs/EU LPD and PMR Channels.csv
+++ b/stock_configs/EU LPD and PMR Channels.csv
@@ -68,11 +68,19 @@ Location,Name,Frequency,Duplex,Offset,Tone,rToneFreq,cToneFreq,DtcsCode,DtcsPola
67,LPD 67,434.725000,,0.600000,,88.5,88.5,023,NN,FM,5.00,,,,,
68,LPD 68,434.750000,,0.600000,,88.5,88.5,023,NN,FM,5.00,,,,,
69,LPD 69,434.775000,,0.600000,,88.5,88.5,023,NN,FM,5.00,,,,,
-71,PMR 1,446.006250,,0.600000,,88.5,88.5,023,NN,NFM,6.25,,,,,
-72,PMR 2,446.018750,,0.600000,,88.5,88.5,023,NN,NFM,6.25,,,,,
-73,PMR 3,446.031250,,0.600000,,88.5,88.5,023,NN,NFM,6.25,,,,,
-74,PMR 4,446.043750,,0.600000,,88.5,88.5,023,NN,NFM,6.25,,,,,
-75,PMR 5,446.056250,,0.600000,,88.5,88.5,023,NN,NFM,6.25,,,,,
-76,PMR 6,446.068750,,0.600000,,88.5,88.5,023,NN,NFM,6.25,,,,,
-77,PMR 7,446.081250,,0.600000,,88.5,88.5,023,NN,NFM,6.25,,,,,
-78,PMR 8,446.093750,,0.600000,,88.5,88.5,023,NN,NFM,6.25,,,,,
+71,PMR 01,446.006250,,0.600000,,88.5,88.5,023,NN,NFM,6.25,,,,,
+72,PMR 02,446.018750,,0.600000,,88.5,88.5,023,NN,NFM,6.25,,,,,
+73,PMR 03,446.031250,,0.600000,,88.5,88.5,023,NN,NFM,6.25,,,,,
+74,PMR 04,446.043750,,0.600000,,88.5,88.5,023,NN,NFM,6.25,,,,,
+75,PMR 05,446.056250,,0.600000,,88.5,88.5,023,NN,NFM,6.25,,,,,
+76,PMR 06,446.068750,,0.600000,,88.5,88.5,023,NN,NFM,6.25,,,,,
+77,PMR 07,446.081250,,0.600000,,88.5,88.5,023,NN,NFM,6.25,,,,,
+78,PMR 08,446.093750,,0.600000,,88.5,88.5,023,NN,NFM,6.25,,,,,
+81,PMR 09,446.106250,,0.600000,,88.5,88.5,023,NN,NFM,6.25,,,,,
+82,PMR 10,446.118750,,0.600000,,88.5,88.5,023,NN,NFM,6.25,,,,,
+83,PMR 11,446.131250,,0.600000,,88.5,88.5,023,NN,NFM,6.25,,,,,
+84,PMR 12,446.143750,,0.600000,,88.5,88.5,023,NN,NFM,6.25,,,,,
+85,PMR 13,446.156250,,0.600000,,88.5,88.5,023,NN,NFM,6.25,,,,,
+86,PMR 14,446.168750,,0.600000,,88.5,88.5,023,NN,NFM,6.25,,,,,
+87,PMR 15,446.181250,,0.600000,,88.5,88.5,023,NN,NFM,6.25,,,,,
+88,PMR 16,446.193750,,0.600000,,88.5,88.5,023,NN,NFM,6.25,,,,,
diff --git a/stock_configs/KDR444.csv b/stock_configs/KDR444.csv
new file mode 100644
index 0000000..7ab0e42
--- /dev/null
+++ b/stock_configs/KDR444.csv
@@ -0,0 +1,9 @@
+Location,Name,Frequency,Duplex,Offset,Tone,rToneFreq,cToneFreq,DtcsCode,DtcsPolarity,Mode,TStep,Skip,Comment,URCALL,RPT1CALL,RPT2CALL
+1,KDR444 1,444.600,,0.600000,,88.5,88.5,023,NN,NFM,6.25,,,,,
+2,KDR444 2,444.650,,0.600000,,88.5,88.5,023,NN,NFM,6.25,,,,,
+3,KDR444 3,444.800,,0.600000,,88.5,88.5,023,NN,NFM,6.25,,,,,
+4,KDR444 4,444.825,,0.600000,,88.5,88.5,023,NN,NFM,6.25,,,,,
+5,KDR444 5,444.850,,0.600000,,88.5,88.5,023,NN,NFM,6.25,,,,,
+6,KDR444 6,444.875,,0.600000,,88.5,88.5,023,NN,NFM,6.25,,,,,
+7,KDR444 7,444.925,,0.600000,,88.5,88.5,023,NN,NFM,6.25,,,,,
+8,KDR444 8,444.975,,0.600000,,88.5,88.5,023,NN,NFM,6.25,,,,,
diff --git a/stock_configs/NOAA Weather Alert.csv b/stock_configs/NOAA Weather Alert.csv
index 41cc764..9e355b5 100644
--- a/stock_configs/NOAA Weather Alert.csv
+++ b/stock_configs/NOAA Weather Alert.csv
@@ -1,4 +1,4 @@
-Location,Name,Frequency,Duplex,Offset,Tone,rToneFreq,cToneFreq,DtcsCode,DtcsPolarity,Mode,TStep,Skip,Comment,URCALL,RPT1CALL,RPT2CALL
+Location,Name,Frequency,Duplex,Offset,Tone,rToneFreq,cToneFreq,DtcsCode,DtcsPolarity,Mode,TStep,Skip,Comment,URCALL,RPT1CALL,RPT2CALL
1,WX1PA7,162.550000,,0.000000,,88.5,88.5,023,NN,FM,5.00,,,,,
2,WX2PA1,162.400000,,0.000000,,88.5,88.5,023,NN,FM,5.00,,,,,
3,WX3PA4,162.475000,,0.000000,,88.5,88.5,023,NN,FM,5.00,,,,,
--
Alioth's /usr/local/bin/git-commit-notice on /srv/git.debian.org/git/pkg-hamradio/chirp.git
More information about the pkg-hamradio-commits
mailing list