[hamradio-commits] [chirp] 01/04: New upstream version 20170124
Dave Hibberd
hibby-guest at moszumanska.debian.org
Tue Jan 24 20:44:43 UTC 2017
This is an automated email from the git hooks/post-receive script.
hibby-guest pushed a commit to branch master
in repository chirp.
commit 84922147dd2ac531b95e7a52f1e9bcb2e2e397dc
Author: Hibby <d at vehibberd.com>
Date: Tue Jan 24 20:09:59 2017 +0000
New upstream version 20170124
---
PKG-INFO | 2 +-
chirp/__init__.py | 2 +-
chirp/chirp_common.py | 2 +-
chirp/dmrmarc.py | 139 +++++
chirp/drivers/alinco.py | 39 +-
chirp/drivers/btech.py | 5 +-
chirp/drivers/fd268.py | 12 +-
chirp/drivers/kyd.py | 3 +-
chirp/{__init__.py => drivers/repeaterbook.py} | 27 +-
chirp/drivers/retevis_rt1.py | 747 +++++++++++++++++++++++++
chirp/drivers/retevis_rt21.py | 31 +-
chirp/drivers/retevis_rt22.py | 55 +-
chirp/drivers/uv5r.py | 18 +-
chirp/drivers/vx8.py | 2 +-
chirp/platform.py | 1 +
chirp/settings.py | 1 +
chirp/ui/mainapp.py | 221 +++++++-
17 files changed, 1234 insertions(+), 73 deletions(-)
diff --git a/PKG-INFO b/PKG-INFO
index f336a43..95f827c 100644
--- a/PKG-INFO
+++ b/PKG-INFO
@@ -1,6 +1,6 @@
Metadata-Version: 1.0
Name: chirp
-Version: daily-20161123
+Version: daily-20170124
Summary: UNKNOWN
Home-page: UNKNOWN
Author: UNKNOWN
diff --git a/chirp/__init__.py b/chirp/__init__.py
index 33863f9..6f4f7bd 100644
--- a/chirp/__init__.py
+++ b/chirp/__init__.py
@@ -17,7 +17,7 @@ import os
import sys
from glob import glob
-CHIRP_VERSION="daily-20161123"
+CHIRP_VERSION="daily-20170124"
module_dir = os.path.dirname(sys.modules["chirp"].__file__)
__all__ = []
diff --git a/chirp/chirp_common.py b/chirp/chirp_common.py
index 008222e..8ec3917 100644
--- a/chirp/chirp_common.py
+++ b/chirp/chirp_common.py
@@ -70,7 +70,7 @@ CROSS_MODES = [
MODES = ["WFM", "FM", "NFM", "AM", "NAM", "DV", "USB", "LSB", "CW", "RTTY",
"DIG", "PKT", "NCW", "NCWR", "CWR", "P25", "Auto", "RTTYR",
- "FSK", "FSKR"]
+ "FSK", "FSKR", "DMR"]
TONE_MODES = [
"",
diff --git a/chirp/dmrmarc.py b/chirp/dmrmarc.py
new file mode 100644
index 0000000..d66328f
--- /dev/null
+++ b/chirp/dmrmarc.py
@@ -0,0 +1,139 @@
+# Copyright 2016 Tom Hayward <tom at tomh.us>
+#
+# 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 3 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 json
+import logging
+import tempfile
+import urllib
+from chirp import chirp_common, errors
+from chirp.settings import RadioSetting, RadioSettingGroup, \
+ RadioSettingValueList
+
+LOG = logging.getLogger(__name__)
+
+
+def list_filter(haystack, attr, needles):
+ if not needles or not needles[0]:
+ return haystack
+ return [x for x in haystack if x[attr] in needles]
+
+
+class DMRMARCRadio(chirp_common.NetworkSourceRadio):
+ """DMR-MARC data source"""
+ VENDOR = "DMR-MARC"
+ MODEL = "Repeater database"
+
+ URL = "http://www.dmr-marc.net/cgi-bin/trbo-database/datadump.cgi?" \
+ "table=repeaters&format=json"
+
+ def __init__(self, *args, **kwargs):
+ chirp_common.NetworkSourceRadio.__init__(self, *args, **kwargs)
+ self._repeaters = None
+
+ def set_params(self, city, state, country):
+ """Set the parameters to be used for a query"""
+ self._city = city and [x.strip() for x in city.split(",")] or ['']
+ self._state = state and [x.strip() for x in state.split(",")] or ['']
+ self._country = country and [x.strip() for x in country.split(",")] \
+ or ['']
+
+ def do_fetch(self):
+ fn = tempfile.mktemp(".json")
+ filename, headers = urllib.urlretrieve(self.URL, fn)
+ with open(fn, 'r') as f:
+ try:
+ self._repeaters = json.load(f)['repeaters']
+ except AttributeError:
+ raise errors.RadioError(
+ "Unexpected response from %s" % self.URL)
+ except ValueError as e:
+ raise errors.RadioError(
+ "Invalid JSON from %s. %s" % (self.URL, str(e)))
+
+ self._repeaters = list_filter(self._repeaters, "city", self._city)
+ self._repeaters = list_filter(self._repeaters, "state", self._state)
+ self._repeaters = list_filter(self._repeaters, "country",
+ self._country)
+
+ def get_features(self):
+ if not self._repeaters:
+ self.do_fetch()
+
+ rf = chirp_common.RadioFeatures()
+ rf.memory_bounds = (0, len(self._repeaters)-1)
+ rf.has_bank = False
+ rf.has_comment = True
+ rf.has_ctone = False
+ rf.valid_tmodes = [""]
+ return rf
+
+ def get_raw_memory(self, number):
+ return repr(self._repeaters[number])
+
+ def get_memory(self, number):
+ if not self._repeaters:
+ self.do_fetch()
+
+ repeater = self._repeaters[number]
+
+ mem = chirp_common.Memory()
+ mem.number = number
+
+ mem.name = repeater.get('city')
+ mem.freq = chirp_common.parse_freq(repeater.get('frequency'))
+ offset = chirp_common.parse_freq(repeater.get('offset', '0'))
+ if offset > 0:
+ mem.duplex = "+"
+ elif offset < 0:
+ mem.duplex = "-"
+ else:
+ mem.duplex = ""
+ mem.offset = abs(offset)
+ mem.mode = 'DMR'
+ mem.comment = repeater.get('map_info')
+
+ mem.extra = RadioSettingGroup("Extra", "extra")
+
+ rs = RadioSetting(
+ "color_code", "Color Code", RadioSettingValueList(
+ range(16), int(repeater.get('color_code', 0))))
+ mem.extra.append(rs)
+
+ return mem
+
+
+def main():
+ import argparse
+ from pprint import PrettyPrinter
+
+ parser = argparse.ArgumentParser(description="Fetch DMR-MARC repeater "
+ "database and filter by city, state, and/or country. Multiple items "
+ "combined with a , will be filtered with logical OR.")
+ parser.add_argument("-c", "--city",
+ help="Comma-separated list of cities to include in output.")
+ parser.add_argument("-s", "--state",
+ help="Comma-separated list of states to include in output.")
+ parser.add_argument("--country",
+ help="Comma-separated list of countries to include in output.")
+ args = parser.parse_args()
+
+ dmrmarc = DMRMARCRadio(None)
+ dmrmarc.set_params(**vars(args))
+ dmrmarc.do_fetch()
+ pp = PrettyPrinter(indent=2)
+ pp.pprint(dmrmarc._repeaters)
+
+if __name__ == "__main__":
+ main()
diff --git a/chirp/drivers/alinco.py b/chirp/drivers/alinco.py
index 65ff70f..f1b4225 100644
--- a/chirp/drivers/alinco.py
+++ b/chirp/drivers/alinco.py
@@ -592,6 +592,7 @@ struct {
} memory[1000];
"""
+
@directory.register
class AlincoDJG7EG(AlincoStyleRadio):
"""Alinco DJ-G7EG"""
@@ -600,12 +601,14 @@ class AlincoDJG7EG(AlincoStyleRadio):
BAUD_RATE = 57600
# Those are different from the other Alinco radios.
- STEPS = [5.0, 6.25, 8.33, 10.0, 12.5, 15.0, 20.0, 25.0, 30.0, 50.0, 100.0, 125.0, 150.0, 200.0, 500.0, 1000.0]
+ STEPS = [5.0, 6.25, 8.33, 10.0, 12.5, 15.0, 20.0, 25.0, 30.0, 50.0,
+ 100.0, 125.0, 150.0, 200.0, 500.0, 1000.0]
DUPLEX = ["", "+", "-"]
MODES = ["NFM", "FM", "AM", "WFM"]
TMODES = ["", "??1", "Tone", "TSQL", "TSQL-R", "DTCS"]
- _model = "AL~DJ-G7EG" # This is a bit of a hack to avoid overwriting _identify()
+ # This is a bit of a hack to avoid overwriting _identify()
+ _model = "AL~DJ-G7EG"
_memsize = 0x1a7c0
_range = [(500000, 1300000000)]
@@ -613,7 +616,7 @@ class AlincoDJG7EG(AlincoStyleRadio):
rf = chirp_common.RadioFeatures()
rf.has_dtcs_polarity = False
rf.has_bank = False
- rf.has_settings = False
+ rf.has_settings = False
rf.valid_modes = self.MODES
rf.valid_tmodes = ["", "Tone", "TSQL", "Cross", "TSQL-R", "DTCS"]
@@ -715,7 +718,8 @@ class AlincoDJG7EG(AlincoStyleRadio):
mem.tuning_step = self.STEPS[_mem.step]
mem.offset = int(_mem.offset)
mem.duplex = self.DUPLEX[_mem.duplex]
- if self.TMODES[_mem.squelch_type] == "TSQL" and _mem.tx_tone != _mem.rx_tone:
+ if self.TMODES[_mem.squelch_type] == "TSQL" and \
+ _mem.tx_tone != _mem.rx_tone:
mem.tmode = "Cross"
mem.cross_mode = "Tone->Tone"
else:
@@ -733,7 +737,7 @@ class AlincoDJG7EG(AlincoStyleRadio):
# Get a low-level memory object mapped to the image
_mem = self._memobj.memory[mem.number]
if mem.empty:
- _mem.unknown = 0x00 # Maybe 0 is empty, 2 is used?
+ _mem.unknown = 0x00 # Maybe 0 is empty, 2 is used?
else:
_mem.unknown = 0x02
_mem.freq = mem.freq
@@ -746,34 +750,35 @@ class AlincoDJG7EG(AlincoStyleRadio):
try:
_mem.tx_tone = ALINCO_TONES.index(mem.rtone)+1
except ValueError:
- raise errors.UnsupportedToneError("This radio does not support " +
- "tone %.1fHz" % mem.rtone)
+ raise errors.UnsupportedToneError(
+ "This radio does not support tone %.1fHz" % mem.rtone)
try:
_mem.rx_tone = ALINCO_TONES.index(mem.ctone)+1
except ValueError:
- raise errors.UnsupportedToneError("This radio does not support " +
- "tone %.1fHz" % mem.ctone)
+ raise errors.UnsupportedToneError(
+ "This radio does not support tone %.1fHz" % mem.ctone)
elif mem.tmode == "TSQL":
_mem.squelch_type = self.TMODES.index("TSQL")
- # Note how the same TSQL tone is copied to both memory locaations
+ # Note how the same TSQL tone is copied to both memory
+ # locaations
try:
_mem.tx_tone = ALINCO_TONES.index(mem.ctone)+1
_mem.rx_tone = ALINCO_TONES.index(mem.ctone)+1
except ValueError:
- raise errors.UnsupportedToneError("This radio does not support " +
- "tone %.1fHz" % mem.ctone)
+ raise errors.UnsupportedToneError(
+ "This radio does not support tone %.1fHz" % mem.ctone)
else:
_mem.squelch_type = self.TMODES.index(mem.tmode)
try:
_mem.tx_tone = ALINCO_TONES.index(mem.rtone)+1
except ValueError:
- raise errors.UnsupportedToneError("This radio does not support " +
- "tone %.1fHz" % mem.rtone)
+ raise errors.UnsupportedToneError(
+ "This radio does not support tone %.1fHz" % mem.rtone)
try:
_mem.rx_tone = ALINCO_TONES.index(mem.ctone)+1
except ValueError:
- raise errors.UnsupportedToneError("This radio does not support " +
- "tone %.1fHz" % mem.ctone)
+ raise errors.UnsupportedToneError(
+ "This radio does not support tone %.1fHz" % mem.ctone)
_mem.dcs = DCS_CODES[self.VENDOR].index(mem.dtcs)
_mem.skip = (mem.skip == "S")
- _mem.name = "\x00".join(mem.name).ljust(32,"\x00")
+ _mem.name = "\x00".join(mem.name).ljust(32, "\x00")
diff --git a/chirp/drivers/btech.py b/chirp/drivers/btech.py
index 249f29c..0d62ebd 100644
--- a/chirp/drivers/btech.py
+++ b/chirp/drivers/btech.py
@@ -483,6 +483,8 @@ KT8900R_id = "280528"
# LUITON LT-588UV
LT588UV_fp = "V2G1F4"
+# Added by rstrickoff gen 2 id
+LT588UV_fp1 = "V2G214"
#### MAGICS
@@ -2716,4 +2718,5 @@ class LT588UV(BTech):
_vhf_range = (136000000, 175000000)
_uhf_range = (400000000, 481000000)
_magic = MSTRING_KT8900
- _fileid = [LT588UV_fp, ]
+ _fileid = [LT588UV_fp,
+ LT588UV_fp1]
diff --git a/chirp/drivers/fd268.py b/chirp/drivers/fd268.py
index b524c4f..44e5c66 100644
--- a/chirp/drivers/fd268.py
+++ b/chirp/drivers/fd268.py
@@ -1,4 +1,4 @@
-# Copyright 2015 Pavel Milanes CO7WT, <co7wt at frcuba.co.cu> <pavelmc at gmail.com>
+# Copyright 2015 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
@@ -889,7 +889,7 @@ class FD288ARadio(FeidaxinFD2x8yRadio):
@directory.register
class FD288BRadio(FeidaxinFD2x8yRadio):
- """Feidaxin FD-288 Radio"""
+ """Feidaxin FD-288B Radio"""
MODEL = "FD-288B"
_range = (400000000, 470000000)
_VFO_DEFAULT = 439000000
@@ -934,3 +934,11 @@ class FD460ARadio(FeidaxinFD2x8yRadio):
_VFO_DEFAULT = 439000000
_IDENT = "\xFF\xEE\x4A\xFF"
+
+ at directory.register
+class FD460UHRadio(FeidaxinFD2x8yRadio):
+ """Feidaxin FD-460UH Radio"""
+ MODEL = "FD-460UH"
+ _range = (400000000, 480000000)
+ _VFO_DEFAULT = 439000000
+ _IDENT = "\xFF\xF4\x44\xFF"
diff --git a/chirp/drivers/kyd.py b/chirp/drivers/kyd.py
index 01ec7b3..c14af0b 100644
--- a/chirp/drivers/kyd.py
+++ b/chirp/drivers/kyd.py
@@ -209,6 +209,7 @@ class MT700Alias(chirp_common.Alias):
VENDOR = "Plant-Tours"
MODEL = "MT-700"
+
@directory.register
class NC630aRadio(chirp_common.CloneModeRadio):
"""KYD NC-630A"""
@@ -509,7 +510,7 @@ class NC630aRadio(chirp_common.CloneModeRadio):
# testing model fingerprint
if filedata[0x01B8:0x01BE] == cls._fileid:
- match_model = True
+ match_model = True
if match_size and match_model:
return True
diff --git a/chirp/__init__.py b/chirp/drivers/repeaterbook.py
similarity index 51%
copy from chirp/__init__.py
copy to chirp/drivers/repeaterbook.py
index 33863f9..26a34fd 100644
--- a/chirp/__init__.py
+++ b/chirp/drivers/repeaterbook.py
@@ -1,4 +1,4 @@
-# Copyright 2008 Dan Smith <dsmith at danplanet.com>
+# Copyright 2016 Tom Hayward <tom at tomh.us>
#
# 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
@@ -13,15 +13,20 @@
# 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 os
-import sys
-from glob import glob
+from chirp import chirp_common
+from chirp.drivers import generic_csv
-CHIRP_VERSION="daily-20161123"
-module_dir = os.path.dirname(sys.modules["chirp"].__file__)
-__all__ = []
-for i in glob(os.path.join(module_dir, "*.py")):
- name = os.path.basename(i)[:-3]
- if not name.startswith("__"):
- __all__.append(name)
+class RBRadio(generic_csv.CSVRadio, chirp_common.NetworkSourceRadio):
+ VENDOR = "RepeaterBook"
+ MODEL = ""
+
+ def _clean_comment(self, headers, line, mem):
+ "Converts iso-8859-1 encoded comments to unicode for pyGTK."
+ mem.comment = unicode(mem.comment, 'iso-8859-1')
+ return mem
+
+ def _clean_name(self, headers, line, mem):
+ "Converts iso-8859-1 encoded names to unicode for pyGTK."
+ mem.name = unicode(mem.name, 'iso-8859-1')
+ return mem
diff --git a/chirp/drivers/retevis_rt1.py b/chirp/drivers/retevis_rt1.py
new file mode 100644
index 0000000..e91300a
--- /dev/null
+++ b/chirp/drivers/retevis_rt1.py
@@ -0,0 +1,747 @@
+# 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, RadioSettingValueString, \
+ InvalidValueError, RadioSettings
+
+LOG = logging.getLogger(__name__)
+
+MEM_FORMAT = """
+#seekto 0x0010;
+struct {
+ lbcd rxfreq[4];
+ lbcd txfreq[4];
+ lbcd rxtone[2];
+ lbcd txtone[2];
+ u8 bcl:1, // Busy Lock
+ epilogue:1, // Epilogue (STE)
+ scramble:1, // Scramble
+ compander:1, // Compander
+ skip:1, // Scan Add
+ wide:1, // Bandwidth
+ unknown1:1,
+ highpower:1; // Power Level
+ u8 unknown2[3];
+} memory[16];
+
+#seekto 0x0120;
+struct {
+ u8 hivoltnotx:1, // TX Inhibit when voltage too high
+ lovoltnotx:1, // TX Inhibit when voltage too low
+ unknown1:1,
+ alarm:1, // Incept Alarm
+ scan:1, // Scan
+ tone:1, // Tone
+ voice:2; // Voice
+ u8 unknown2:1,
+ ssave:3, // Super Battery Save
+ unknown3:1,
+ save:3; // Battery Save
+ u8 squelch; // Squelch
+ u8 tot; // Time Out Timer
+ u8 voxi:1, // VOX Inhibit on Receive
+ voxd:2, // VOX Delay
+ voxc:1, // VOX Control
+ voxg:4; // VOX Gain
+ u8 unknown4:4,
+ scanspeed:4; // Scan Speed
+ u8 unknown5:3,
+ scandelay:5; // Scan Delay
+ u8 unknown6:3,
+ prioritych:5; // Priority Channel
+ u8 k1shortp; // Key 1 Short Press
+ u8 k2shortp; // Key 2 Short Press
+ u8 k1longp; // Key 1 Long Press
+ u8 k2longp; // Key 2 Long Press
+ u8 lpt; // Long Press Time
+} settings;
+
+#seekto 0x0170;
+struct {
+ char fp[8];
+} fingerprint;
+"""
+
+CMD_ACK = "\x06"
+
+RT1_POWER_LEVELS = [chirp_common.PowerLevel("Low", watts=5.00),
+ chirp_common.PowerLevel("High", watts=9.00)]
+
+RT1_DTCS = sorted(chirp_common.DTCS_CODES + [645])
+
+LIST_LPT = ["0.5", "1.0", "1.5", "2.0", "2.5"]
+LIST_SHORT_PRESS = ["Off", "Monitor On/Off", "Power High/Low", "Alarm", "Volt"]
+LIST_LONG_PRESS = ["Off", "Monitor On/Off", "Monitor(momentary)",
+ "Power High/Low", "Alarm", "Volt", "TX 1750 Hz"]
+LIST_VOXDELAY = ["0.5", "1.0", "2.0", "3.0"]
+LIST_VOICE = ["Off", "English", "Chinese"]
+LIST_TIMEOUTTIMER = ["Off"] + ["%s" % x for x in range(30, 330, 30)]
+LIST_SAVE = ["Off", "1:1", "1:2", "1:3", "1:4"]
+LIST_SSAVE = ["Off"] + ["%s" % x for x in range(1, 7)]
+LIST_PRIORITYCH = ["Off"] + ["%s" % x for x in range(1, 17)]
+LIST_SCANSPEED = ["%s" % x for x in range(100, 550, 50)]
+LIST_SCANDELAY = ["%s" % x for x in range(3, 31)]
+
+SETTING_LISTS = {
+ "lpt": LIST_LPT,
+ "k1shortp": LIST_SHORT_PRESS,
+ "k1longp": LIST_LONG_PRESS,
+ "k2shortp": LIST_SHORT_PRESS,
+ "k2longp": LIST_LONG_PRESS,
+ "voxd": LIST_VOXDELAY,
+ "voice": LIST_VOICE,
+ "tot": LIST_TIMEOUTTIMER,
+ "save": LIST_SAVE,
+ "ssave": LIST_SSAVE,
+ "prioritych": LIST_PRIORITYCH,
+ "scanspeed": LIST_SCANSPEED,
+ "scandelay": LIST_SCANDELAY,
+ }
+
+# Retevis RT1 fingerprints
+RT1_VHF_fp = "PXT8K" + "\xF0\x00\x00" # RT1 VHF model
+RT1_UHF_fp = "PXT8K" + "\xF3\x00\x00" # RT1 UHF model
+
+MODELS = [RT1_VHF_fp, RT1_UHF_fp]
+
+
+def _model_from_data(data):
+ return data[0x0170:0x0178]
+
+
+def _model_from_image(radio):
+ return _model_from_data(radio.get_mmap())
+
+
+def _get_radio_model(radio):
+ block = _rt1_read_block(radio, 0x0170, 0x10)
+ version = block[0:8]
+ return version
+
+
+def _rt1_enter_programming_mode(radio):
+ serial = radio.pipe
+
+ magic = ["PROGRAMa", "PROGRAMb"]
+ for i in range(0, 2):
+
+ try:
+ LOG.debug("sending " + magic[i])
+ serial.write(magic[i])
+ ack = serial.read(1)
+ except:
+ _rt1_exit_programming_mode(radio)
+ raise errors.RadioError("Error communicating with radio")
+
+ if not ack:
+ _rt1_exit_programming_mode(radio)
+ raise errors.RadioError("No response from radio")
+ elif ack != CMD_ACK:
+ LOG.debug("Incorrect response, got this:\n\n" + util.hexprint(ack))
+ _rt1_exit_programming_mode(radio)
+ raise errors.RadioError("Radio refused to enter programming mode")
+
+ try:
+ LOG.debug("sending " + util.hexprint("\x02"))
+ serial.write("\x02")
+ ident = serial.read(16)
+ except:
+ _rt1_exit_programming_mode(radio)
+ raise errors.RadioError("Error communicating with radio")
+
+ if not ident.startswith("PXT8K"):
+ LOG.debug("Incorrect response, got this:\n\n" + util.hexprint(ident))
+ _rt1_exit_programming_mode(radio)
+ LOG.debug(util.hexprint(ident))
+ raise errors.RadioError("Radio returned unknown identification string")
+
+ try:
+ LOG.debug("sending " + util.hexprint("MXT8KCUMHS1X7BN/"))
+ serial.write("MXT8KCUMHS1X7BN/")
+ ack = serial.read(1)
+ except:
+ _rt1_exit_programming_mode(radio)
+ raise errors.RadioError("Error communicating with radio")
+
+ if ack != "\xB2":
+ LOG.debug("Incorrect response, got this:\n\n" + util.hexprint(ack))
+ _rt1_exit_programming_mode(radio)
+ raise errors.RadioError("Radio refused to enter programming mode")
+
+ try:
+ LOG.debug("sending " + util.hexprint(CMD_ACK))
+ serial.write(CMD_ACK)
+ ack = serial.read(1)
+ except:
+ _rt1_exit_programming_mode(radio)
+ raise errors.RadioError("Error communicating with radio")
+
+ if ack != CMD_ACK:
+ LOG.debug("Incorrect response, got this:\n\n" + util.hexprint(ack))
+ _rt1_exit_programming_mode(radio)
+ raise errors.RadioError("Radio refused to enter programming mode")
+
+ # DEBUG
+ LOG.info("Positive ident, this is a %s %s" % (radio.VENDOR, radio.MODEL))
+
+
+def _rt1_exit_programming_mode(radio):
+ serial = radio.pipe
+ try:
+ serial.write("E")
+ except:
+ raise errors.RadioError("Radio refused to exit programming mode")
+
+
+def _rt1_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:
+ _rt1_exit_programming_mode(radio)
+ raise Exception("Error reading block %04x." % (block_addr))
+
+ block_data = response[4:]
+
+ serial.write(CMD_ACK)
+ ack = serial.read(1)
+ except:
+ _rt1_exit_programming_mode(radio)
+ raise errors.RadioError("Failed to read block at %04x" % block_addr)
+
+ if ack != CMD_ACK:
+ _rt1_exit_programming_mode(radio)
+ raise Exception("No ACK reading block %04x." % (block_addr))
+
+ return block_data
+
+
+def _rt1_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:
+ _rt1_exit_programming_mode(radio)
+ raise errors.RadioError("Failed to send block "
+ "to radio at %04x" % block_addr)
+
+
+def do_download(radio):
+ LOG.debug("download")
+ _rt1_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, radio._block_size):
+ status.cur = addr + radio._block_size
+ radio.status_fn(status)
+
+ block = _rt1_read_block(radio, addr, radio._block_size)
+ data += block
+
+ LOG.debug("Address: %04x" % addr)
+ LOG.debug(util.hexprint(block))
+
+ _rt1_exit_programming_mode(radio)
+
+ return memmap.MemoryMap(data)
+
+
+def do_upload(radio):
+ status = chirp_common.Status()
+ status.msg = "Uploading to radio"
+
+ _rt1_enter_programming_mode(radio)
+
+ image_model = _model_from_image(radio)
+ LOG.info("Image Version is %s" % repr(image_model))
+
+ radio_model = _get_radio_model(radio)
+ LOG.info("Radio Version is %s" % repr(radio_model))
+
+ bands = ["VHF", "UHF"]
+ image_band = radio_band = "unknown"
+ for i in range(0,2):
+ if image_model == MODELS[i]:
+ image_band = bands[i]
+ if radio_model == MODELS[i]:
+ radio_band = bands[i]
+
+ if image_model != radio_model:
+ _rt1_exit_programming_mode(radio)
+ msg = ("The upload was stopped because the band supported by "
+ "the image (%s) does not match the band supported by "
+ "the radio (%s).")
+ raise errors.RadioError(msg % (image_band, radio_band))
+
+ status.cur = 0
+ status.max = 0x0190
+
+ for start_addr, end_addr, block_size in radio._ranges:
+ for addr in range(start_addr, end_addr, block_size):
+ status.cur = addr + block_size
+ radio.status_fn(status)
+ _rt1_write_block(radio, addr, block_size)
+
+ _rt1_exit_programming_mode(radio)
+
+
+def model_match(cls, data):
+ """Match the opened/downloaded image to the correct version"""
+ rid = data[0x0170:0x0176]
+
+ return rid.startswith("PXT8K")
+
+
+ at directory.register
+class RT1Radio(chirp_common.CloneModeRadio):
+ """Retevis RT1"""
+ VENDOR = "Retevis"
+ MODEL = "RT1"
+ BAUD_RATE = 2400
+
+ _ranges = [
+ (0x0000, 0x0190, 0x10),
+ ]
+ _memsize = 0x0400
+ _block_size = 0x10
+ _vhf_range = (134000000, 175000000)
+ _uhf_range = (400000000, 521000000)
+
+ 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 = RT1_POWER_LEVELS
+ rf.valid_duplexes = ["", "-", "+", "split", "off"]
+ rf.valid_modes = ["NFM", "FM"] # 12.5 KHz, 25 kHz.
+ rf.memory_bounds = (1, 16)
+ if self._mmap is None:
+ rf.valid_bands = [self._vhf_range, self._uhf_range]
+ elif self._my_band() == RT1_VHF_fp:
+ rf.valid_bands = [self._vhf_range]
+ elif self._my_band() == RT1_UHF_fp:
+ rf.valid_bands = [self._uhf_range]
+
+ 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 decode_tone(self, val):
+ """Parse the tone data to decode from mem, it returns:
+ Mode (''|DTCS|Tone), Value (None|###), Polarity (None,N,R)"""
+ if val.get_raw() == "\xFF\xFF":
+ return '', None, None
+
+ val = int(val)
+ if val >= 12000:
+ a = val - 12000
+ return 'DTCS', a, 'R'
+ elif val >= 8000:
+ a = val - 8000
+ return 'DTCS', a, 'N'
+ else:
+ a = val / 10.0
+ return 'Tone', a, None
+
+ def encode_tone(self, memval, mode, value, pol):
+ """Parse the tone data to encode from UI to mem"""
+ if mode == '':
+ memval[0].set_raw(0xFF)
+ memval[1].set_raw(0xFF)
+ elif mode == 'Tone':
+ memval.set_value(int(value * 10))
+ elif mode == 'DTCS':
+ flag = 0x80 if pol == 'N' else 0xC0
+ memval.set_value(value)
+ memval[1].set_bits(flag)
+ else:
+ raise Exception("Internal error: invalid mode `%s'" % mode)
+
+ def _my_band(self):
+ model_tag = _model_from_image(self)
+ return model_tag
+
+ def get_memory(self, number):
+ _mem = self._memobj.memory[number - 1]
+
+ 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 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"
+
+ rxtone = txtone = None
+ txtone = self.decode_tone(_mem.txtone)
+ rxtone = self.decode_tone(_mem.rxtone)
+ chirp_common.split_tone_decode(mem, txtone, rxtone)
+
+ mem.power = RT1_POWER_LEVELS[_mem.highpower]
+
+ if _mem.skip:
+ mem.skip = "S"
+
+ mem.extra = RadioSettingGroup("Extra", "extra")
+
+ rs = RadioSetting("bcl", "BCL",
+ RadioSettingValueBoolean(not _mem.bcl))
+ mem.extra.append(rs)
+
+ rs = RadioSetting("epilogue", "Epilogue(STE)",
+ RadioSettingValueBoolean(not _mem.epilogue))
+ mem.extra.append(rs)
+
+ rs = RadioSetting("compander", "Compander",
+ RadioSettingValueBoolean(not _mem.compander))
+ mem.extra.append(rs)
+
+ rs = RadioSetting("scramble", "Scramble",
+ RadioSettingValueBoolean(not _mem.scramble))
+ mem.extra.append(rs)
+
+ return mem
+
+ def set_memory(self, mem):
+ _mem = self._memobj.memory[mem.number - 1]
+
+ if mem.empty:
+ _mem.set_raw("\xFF" * (_mem.size() / 8))
+ return
+
+ _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"
+
+ ((txmode, txtone, txpol), (rxmode, rxtone, rxpol)) = \
+ chirp_common.split_tone_encode(mem)
+ self.encode_tone(_mem.txtone, txmode, txtone, txpol)
+ self.encode_tone(_mem.rxtone, rxmode, rxtone, rxpol)
+
+ _mem.highpower = mem.power == RT1_POWER_LEVELS[1]
+
+ _mem.skip = mem.skip == "S"
+
+ for setting in mem.extra:
+ setattr(_mem, setting.get_name(), not int(setting.value))
+
+ def get_settings(self):
+ _settings = self._memobj.settings
+ basic = RadioSettingGroup("basic", "Basic Settings")
+ top = RadioSettings(basic)
+
+ rs = RadioSetting("lpt", "Long Press Time[s]",
+ RadioSettingValueList(
+ LIST_LPT,
+ LIST_LPT[_settings.lpt]))
+ basic.append(rs)
+
+ if _settings.k1shortp > 4:
+ val = 1
+ else:
+ val = _settings.k1shortp
+ rs = RadioSetting("k1shortp", "Key 1 Short Press",
+ RadioSettingValueList(
+ LIST_SHORT_PRESS,
+ LIST_SHORT_PRESS[val]))
+ basic.append(rs)
+
+ if _settings.k1longp > 6:
+ val = 3
+ else:
+ val = _settings.k1longp
+ rs = RadioSetting("k1longp", "Key 1 Long Press",
+ RadioSettingValueList(
+ LIST_LONG_PRESS,
+ LIST_LONG_PRESS[val]))
+ basic.append(rs)
+
+ if _settings.k2shortp > 4:
+ val = 4
+ else:
+ val = _settings.k2shortp
+ rs = RadioSetting("k2shortp", "Key 2 Short Press",
+ RadioSettingValueList(
+ LIST_SHORT_PRESS,
+ LIST_SHORT_PRESS[val]))
+ basic.append(rs)
+
+ if _settings.k2longp > 6:
+ val = 4
+ else:
+ val = _settings.k2longp
+ rs = RadioSetting("k2longp", "Key 2 Long Press",
+ RadioSettingValueList(
+ LIST_LONG_PRESS,
+ LIST_LONG_PRESS[val]))
+ basic.append(rs)
+
+ rs = RadioSetting("voxc", "VOX Control",
+ RadioSettingValueBoolean(not _settings.voxc))
+ basic.append(rs)
+
+ if _settings.voxg > 8:
+ val = 4
+ else:
+ val = _settings.voxg + 1
+ rs = RadioSetting("voxg", "VOX Gain",
+ RadioSettingValueInteger(1, 9, val))
+ basic.append(rs)
+
+ rs = RadioSetting("voxd", "VOX Delay Time",
+ RadioSettingValueList(
+ LIST_VOXDELAY,
+ LIST_VOXDELAY[_settings.voxd]))
+ basic.append(rs)
+
+ rs = RadioSetting("voxi", "VOX Inhibit on Receive",
+ RadioSettingValueBoolean(not _settings.voxi))
+ basic.append(rs)
+
+ if _settings.squelch > 8:
+ val = 4
+ else:
+ val = _settings.squelch
+ rs = RadioSetting("squelch", "Squelch Level",
+ RadioSettingValueInteger(0, 9, val))
+ basic.append(rs)
+
+ if _settings.voice == 3:
+ val = 1
+ else:
+ val = _settings.voice
+ rs = RadioSetting("voice", "Voice Prompts",
+ RadioSettingValueList(
+ LIST_VOICE,
+ LIST_VOICE[val]))
+ basic.append(rs)
+
+ rs = RadioSetting("tone", "Tone",
+ RadioSettingValueBoolean(_settings.tone))
+ basic.append(rs)
+
+ rs = RadioSetting("lovoltnotx", "TX Inhibit (when battery < 6 volts)",
+ RadioSettingValueBoolean(_settings.lovoltnotx))
+ basic.append(rs)
+
+ rs = RadioSetting("hivoltnotx", "TX Inhibit (when battery > 9 volts)",
+ RadioSettingValueBoolean(_settings.hivoltnotx))
+ basic.append(rs)
+
+ if _settings.tot > 10:
+ val = 6
+ else:
+ val = _settings.tot
+ rs = RadioSetting("tot", "Time-out Timer[s]",
+ RadioSettingValueList(
+ LIST_TIMEOUTTIMER,
+ LIST_TIMEOUTTIMER[val]))
+ basic.append(rs)
+
+ if _settings.save < 3:
+ val = 0
+ else:
+ val = _settings.save - 3
+ rs = RadioSetting("save", "Battery Saver",
+ RadioSettingValueList(
+ LIST_SAVE,
+ LIST_SAVE[val]))
+ basic.append(rs)
+
+ rs = RadioSetting("ssave", "Super Battery Saver[s]",
+ RadioSettingValueList(
+ LIST_SSAVE,
+ LIST_SSAVE[_settings.ssave]))
+ basic.append(rs)
+
+ rs = RadioSetting("alarm", "Incept Alarm",
+ RadioSettingValueBoolean(_settings.alarm))
+ basic.append(rs)
+
+ rs = RadioSetting("scan", "Scan Function",
+ RadioSettingValueBoolean(_settings.scan))
+ basic.append(rs)
+
+ if _settings.prioritych > 15:
+ val = 0
+ else:
+ val = _settings.prioritych + 1
+ rs = RadioSetting("prioritych", "Priority Channel",
+ RadioSettingValueList(
+ LIST_PRIORITYCH,
+ LIST_PRIORITYCH[val]))
+ basic.append(rs)
+
+ if _settings.scanspeed > 8:
+ val = 4
+ else:
+ val = _settings.scanspeed
+ rs = RadioSetting("scanspeed", "Scan Speed[ms]",
+ RadioSettingValueList(
+ LIST_SCANSPEED,
+ LIST_SCANSPEED[val]))
+ basic.append(rs)
+
+ if _settings.scandelay > 27:
+ val = 12
+ else:
+ val = _settings.scandelay
+ rs = RadioSetting("scandelay", "Scan Droupout Delay Time[s]",
+ RadioSettingValueList(
+ LIST_SCANDELAY,
+ LIST_SCANDELAY[val]))
+ 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 == "voxc":
+ setattr(obj, setting, not int(element.value))
+ elif setting == "voxg":
+ setattr(obj, setting, int(element.value) - 1)
+ elif setting == "voxi":
+ setattr(obj, setting, not int(element.value))
+ elif setting == "voice":
+ if int(element.value) == 2:
+ setattr(obj, setting, int(element.value) + 1)
+ else:
+ setattr(obj, setting, int(element.value))
+ elif setting == "save":
+ setattr(obj, setting, int(element.value) + 3)
+ elif setting == "prioritych":
+ if int(element.value) == 0:
+ setattr(obj, setting, int(element.value) + 31)
+ else:
+ 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
+
+ @classmethod
+ def match_model(cls, filedata, filename):
+ match_size = False
+ match_model = False
+
+ # testing the file data size
+ if len(filedata) in [0x0400, ]:
+ match_size = True
+
+ # testing the model fingerprint
+ match_model = model_match(cls, filedata)
+
+ if match_size and match_model:
+ return True
+ else:
+ return False
diff --git a/chirp/drivers/retevis_rt21.py b/chirp/drivers/retevis_rt21.py
index e1ed491..29f83c7 100644
--- a/chirp/drivers/retevis_rt21.py
+++ b/chirp/drivers/retevis_rt21.py
@@ -46,6 +46,12 @@ struct {
scramble_type2:4;
} memory[16];
+#seekto 0x011D;
+struct {
+ u8 unused:4,
+ pf1:4; // Programmable Function Key 1
+} keys;
+
#seekto 0x012C;
struct {
u8 use_scramble; // Scramble Enable
@@ -82,6 +88,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)]
+PF1_CHOICES = ["None", "Monitor", "Scan", "Scramble", "Alarm"]
+PF1_VALUES = [0x0F, 0x04, 0x06, 0x08, 0x0C]
SETTING_LISTS = {
"bcl": BCL_LIST,
@@ -455,6 +463,7 @@ class RT21Radio(chirp_common.CloneModeRadio):
setattr(_mem, setting.get_name(), setting.value)
def get_settings(self):
+ _keys = self._memobj.keys
_settings = self._memobj.settings
basic = RadioSettingGroup("basic", "Basic Settings")
top = RadioSettings(basic)
@@ -497,6 +506,23 @@ class RT21Radio(chirp_common.CloneModeRadio):
VOX_LIST, VOX_LIST[_settings.vox]))
basic.append(rs)
+ def apply_pf1_listvalue(setting, obj):
+ LOG.debug("Setting value: "+ str(setting.value) + " from list")
+ val = str(setting.value)
+ index = PF1_CHOICES.index(val)
+ val = PF1_VALUES[index]
+ obj.set_value(val)
+
+ if _keys.pf1 in PF1_VALUES:
+ idx = PF1_VALUES.index(_keys.pf1)
+ else:
+ idx = LIST_DTMF_SPECIAL_VALUES.index(0x04)
+ rs = RadioSetting("keys.pf1", "PF1 Key Function",
+ RadioSettingValueList(PF1_CHOICES,
+ PF1_CHOICES[idx]))
+ rs.set_apply_callback(apply_pf1_listvalue, _keys.pf1)
+ basic.append(rs)
+
return top
def set_settings(self, settings):
@@ -516,7 +542,10 @@ class RT21Radio(chirp_common.CloneModeRadio):
obj = self._memobj.settings
setting = element.get_name()
- if setting == "tot":
+ if element.has_apply_callback():
+ LOG.debug("Using apply callback")
+ element.run_apply_callback()
+ elif setting == "tot":
setattr(obj, setting, int(element.value) + 1)
elif element.value.get_mutable():
LOG.debug("Setting %s = %s" % (setting, element.value))
diff --git a/chirp/drivers/retevis_rt22.py b/chirp/drivers/retevis_rt22.py
index a7098c9..9c0e2aa 100644
--- a/chirp/drivers/retevis_rt22.py
+++ b/chirp/drivers/retevis_rt22.py
@@ -97,21 +97,26 @@ def _rt22_enter_programming_mode(radio):
serial = radio.pipe
magic = "PROGRGS"
- try:
+ exito = False
+ for i in range(0, 5):
for j in range(0, len(magic)):
time.sleep(0.005)
serial.write(magic[j])
ack = serial.read(1)
- except:
- _rt22_exit_programming_mode(radio)
- raise errors.RadioError("Error communicating with radio")
- if not ack:
- _rt22_exit_programming_mode(radio)
- raise errors.RadioError("No response from radio")
- elif ack != CMD_ACK:
- _rt22_exit_programming_mode(radio)
- raise errors.RadioError("Radio refused to enter programming mode")
+ try:
+ if ack == CMD_ACK:
+ exito = True
+ break
+ except:
+ LOG.debug("Attempt #%s, failed, trying again" % i)
+ pass
+
+ # check if we had EXITO
+ if exito is False:
+ msg = "The radio did not accept program mode after five tries.\n"
+ msg += "Check you interface cable and power cycle your radio."
+ raise errors.RadioError(msg)
try:
serial.write("\x02")
@@ -311,11 +316,31 @@ class RT22Radio(chirp_common.CloneModeRadio):
self._memobj = bitwise.parse(MEM_FORMAT, self._mmap)
def sync_in(self):
- self._mmap = do_download(self)
+ """Download from radio"""
+ try:
+ data = do_download(self)
+ except errors.RadioError:
+ # Pass through any real errors we raise
+ raise
+ except:
+ # If anything unexpected happens, make sure we raise
+ # a RadioError and log the problem
+ LOG.exception('Unexpected error during download')
+ raise errors.RadioError('Unexpected error communicating '
+ 'with the radio')
+ self._mmap = data
self.process_mmap()
def sync_out(self):
- do_upload(self)
+ """Upload to radio"""
+ try:
+ do_upload(self)
+ except:
+ # If anything unexpected happens, make sure we raise
+ # a RadioError and log the problem
+ LOG.exception('Unexpected error during upload')
+ raise errors.RadioError('Unexpected error communicating '
+ 'with the radio')
def get_raw_memory(self, number):
return repr(self._memobj.memory[number - 1])
@@ -599,3 +624,9 @@ class ZTX6(RT22Radio):
"""Zastone ZT-X6"""
VENDOR = "Zastone"
MODEL = "ZT-X6"
+
+ at directory.register
+class LT316(RT22Radio):
+ """Luiton LT-316"""
+ VENDOR = "LUITON"
+ MODEL = "LT-316"
diff --git a/chirp/drivers/uv5r.py b/chirp/drivers/uv5r.py
index 8a2f9ee..83ce148 100644
--- a/chirp/drivers/uv5r.py
+++ b/chirp/drivers/uv5r.py
@@ -1633,9 +1633,24 @@ class RT5RAlias(chirp_common.Alias):
MODEL = "RT-5R"
+class RT5RVAlias(chirp_common.Alias):
+ VENDOR = "Retevis"
+ MODEL = "RT-5RV"
+
+
+class RT5Alias(chirp_common.Alias):
+ VENDOR = "Retevis"
+ MODEL = "RT5"
+
+
+class RT5_TPAlias(chirp_common.Alias):
+ VENDOR = "Retevis"
+ MODEL = "RT5(tri-power)"
+
+
@directory.register
class BaofengUV5RGeneric(BaofengUV5R):
- ALIASES = [UV5XAlias, RT5RAlias]
+ ALIASES = [UV5XAlias, RT5RAlias, RT5RVAlias, RT5Alias]
@directory.register
@@ -1719,6 +1734,7 @@ class IntekKT980Radio(BaofengUV5R):
class BaofengBFF8HPRadio(BaofengUV5R):
VENDOR = "Baofeng"
MODEL = "BF-F8HP"
+ ALIASES = [RT5_TPAlias, ]
_basetype = BASETYPE_F8HP
_idents = [UV5R_MODEL_291,
UV5R_MODEL_A58
diff --git a/chirp/drivers/vx8.py b/chirp/drivers/vx8.py
index d65e77c..6e8dd5f 100644
--- a/chirp/drivers/vx8.py
+++ b/chirp/drivers/vx8.py
@@ -550,7 +550,7 @@ class VX8Radio(yaesu_clone.YaesuCloneModeRadio):
return rf
def get_raw_memory(self, number):
- return repr(self._memobj.memory[number])
+ return repr(self._memobj.memory[number-1])
def _checksums(self):
return [yaesu_clone.YaesuChecksum(0x064A, 0x06C8),
diff --git a/chirp/platform.py b/chirp/platform.py
index 0b9696f..8250af1 100644
--- a/chirp/platform.py
+++ b/chirp/platform.py
@@ -302,6 +302,7 @@ class UnixPlatform(Platform):
ports = ["/dev/ttyS*",
"/dev/ttyUSB*",
"/dev/ttyAMA*",
+ "/dev/ttyACM*",
"/dev/cu.*",
"/dev/cuaU*",
"/dev/cua0*",
diff --git a/chirp/settings.py b/chirp/settings.py
index 210e046..3133903 100644
--- a/chirp/settings.py
+++ b/chirp/settings.py
@@ -251,6 +251,7 @@ class RadioSettingValueMap(RadioSettingValueList):
self.set_mem_val(mem_val)
elif user_option is not None:
self.set_value(user_option)
+ self._has_changed = False
def set_mem_val(self, mem_val):
"""Change setting to User Option that corresponds to 'mem_val'"""
diff --git a/chirp/ui/mainapp.py b/chirp/ui/mainapp.py
index 23ac948..a6835c9 100644
--- a/chirp/ui/mainapp.py
+++ b/chirp/ui/mainapp.py
@@ -29,7 +29,7 @@ import sys
from chirp.ui import inputdialog, common
from chirp import platform, directory, util
-from chirp.drivers import generic_xml, generic_csv
+from chirp.drivers import generic_xml, generic_csv, repeaterbook
from chirp.drivers import ic9x, kenwood_live, idrp, vx7, vx5, vx6
from chirp.drivers import icf, ic9x_icf
from chirp import CHIRP_VERSION, chirp_common, detect, errors
@@ -316,7 +316,8 @@ of file.
def do_open(self, fname=None, tempname=None):
if not fname:
- types = [(_("CHIRP Radio Images") + " (*.img)", "*.img"),
+ types = [(_("All files") + " (*.*)", "*"),
+ (_("CHIRP Radio Images") + " (*.img)", "*.img"),
(_("CHIRP Files") + " (*.chirp)", "*.chirp"),
(_("CSV Files") + " (*.csv)", "*.csv"),
(_("DAT Files") + " (*.dat)", "*.dat"),
@@ -786,7 +787,8 @@ of file.
return True
def do_import(self):
- types = [(_("CHIRP Files") + " (*.chirp)", "*.chirp"),
+ types = [(_("All files") + " (*.*)", "*"),
+ (_("CHIRP Files") + " (*.chirp)", "*.chirp"),
(_("CHIRP Radio Images") + " (*.img)", "*.img"),
(_("CSV Files") + " (*.csv)", "*.csv"),
(_("DAT Files") + " (*.dat)", "*.dat"),
@@ -806,7 +808,66 @@ of file.
count = eset.do_import(filen)
reporting.report_model_usage(eset.rthread.radio, "import", count > 0)
- def do_repeaterbook_prompt(self):
+ def do_dmrmarc_prompt(self):
+ fields = {"1City": (gtk.Entry(), lambda x: x),
+ "2State": (gtk.Entry(), lambda x: x),
+ "3Country": (gtk.Entry(), lambda x: x),
+ }
+
+ d = inputdialog.FieldDialog(title=_("DMR-MARC Repeater Database Dump"),
+ parent=self)
+ for k in sorted(fields.keys()):
+ d.add_field(k[1:], fields[k][0])
+ fields[k][0].set_text(CONF.get(k[1:], "dmrmarc") or "")
+
+ while d.run() == gtk.RESPONSE_OK:
+ for k in sorted(fields.keys()):
+ widget, validator = fields[k]
+ try:
+ if validator(widget.get_text()):
+ CONF.set(k[1:], widget.get_text(), "dmrmarc")
+ continue
+ except Exception:
+ pass
+
+ d.destroy()
+ return True
+
+ d.destroy()
+ return False
+
+ def do_dmrmarc(self, do_import):
+ self.window.set_cursor(gtk.gdk.Cursor(gtk.gdk.WATCH))
+ if not self.do_dmrmarc_prompt():
+ self.window.set_cursor(None)
+ return
+
+ city = CONF.get("city", "dmrmarc")
+ state = CONF.get("state", "dmrmarc")
+ country = CONF.get("country", "dmrmarc")
+
+ # Do this in case the import process is going to take a while
+ # to make sure we process events leading up to this
+ gtk.gdk.window_process_all_updates()
+ while gtk.events_pending():
+ gtk.main_iteration(False)
+
+ if do_import:
+ eset = self.get_current_editorset()
+ dmrmarcstr = "dmrmarc://%s/%s/%s" % (city, state, country)
+ eset.do_import(dmrmarcstr)
+ else:
+ try:
+ from chirp import dmrmarc
+ radio = dmrmarc.DMRMARCRadio(None)
+ radio.set_params(city, state, country)
+ self.do_open_live(radio, read_only=True)
+ except errors.RadioError, e:
+ common.show_error(e)
+
+ self.window.set_cursor(None)
+
+ def do_repeaterbook_political_prompt(self):
if not CONF.get_bool("has_seen_credit", "repeaterbook"):
d = gtk.MessageDialog(parent=self, buttons=gtk.BUTTONS_OK)
d.set_markup("<big><big><b>RepeaterBook</b></big>\r\n" +
@@ -882,9 +943,9 @@ of file.
return True
- def do_repeaterbook(self, do_import):
+ def do_repeaterbook_political(self, do_import):
self.window.set_cursor(gtk.gdk.Cursor(gtk.gdk.WATCH))
- if not self.do_repeaterbook_prompt():
+ if not self.do_repeaterbook_political_prompt():
self.window.set_cursor(None)
return
@@ -912,6 +973,7 @@ of file.
query = query % (code,
band and band or "%%",
county and county or "%%")
+ print query
# Do this in case the import process is going to take a while
# to make sure we process events leading up to this
@@ -927,24 +989,115 @@ of file.
self.window.set_cursor(None)
return
- class RBRadio(generic_csv.CSVRadio,
- chirp_common.NetworkSourceRadio):
- VENDOR = "RepeaterBook"
- MODEL = ""
+ try:
+ # Validate CSV
+ radio = repeaterbook.RBRadio(filename)
+ if radio.errors:
+ reporting.report_misc_error("repeaterbook",
+ ("query=%s\n" % query) +
+ ("\n") +
+ ("\n".join(radio.errors)))
+ except errors.InvalidDataError, e:
+ common.show_error(str(e))
+ self.window.set_cursor(None)
+ return
+ except Exception, e:
+ common.log_exception()
+
+ reporting.report_model_usage(radio, "import", True)
+
+ self.window.set_cursor(None)
+ if do_import:
+ eset = self.get_current_editorset()
+ count = eset.do_import(filename)
+ else:
+ self.do_open_live(radio, read_only=True)
+
+ def do_repeaterbook_proximity_prompt(self):
+ default_band = "--All--"
+ try:
+ code = int(CONF.get("band", "repeaterbook"))
+ for k, v in RB_BANDS.items():
+ if code == v:
+ default_band = k
+ break
+ except:
+ pass
+ fields = {"1Location": (gtk.Entry(), lambda x: x.get_text()),
+ "2Distance": (gtk.Entry(), lambda x: x.get_text()),
+ "3Band": (miscwidgets.make_choice(
+ sorted(RB_BANDS.keys(), key=key_bands),
+ False, default_band),
+ lambda x: RB_BANDS[x.get_active_text()]),
+ }
+
+ d = inputdialog.FieldDialog(title=_("RepeaterBook Query"),
+ parent=self)
+ for k in sorted(fields.keys()):
+ d.add_field(k[1:], fields[k][0])
+ if isinstance(fields[k][0], gtk.Entry):
+ fields[k][0].set_text(
+ CONF.get(k[1:].lower(), "repeaterbook") or "")
+
+ while d.run() == gtk.RESPONSE_OK:
+ valid = True
+ for k, (widget, fn) in fields.items():
+ try:
+ CONF.set(k[1:].lower(), str(fn(widget)), "repeaterbook")
+ continue
+ except:
+ pass
+ common.show_error("Invalid value for %s" % k[1:])
+ valid = False
+ break
+
+ if valid:
+ d.destroy()
+ return True
+
+ d.destroy()
+ return False
+
+ def do_repeaterbook_proximity(self, do_import):
+ self.window.set_cursor(gtk.gdk.Cursor(gtk.gdk.WATCH))
+ if not self.do_repeaterbook_proximity_prompt():
+ self.window.set_cursor(None)
+ return
+
+ loc = CONF.get("location", "repeaterbook")
+
+ try:
+ dist = int(CONF.get("distance", "repeaterbook"))
+ except:
+ dist = 20
+
+ try:
+ band = int(CONF.get("band", "repeaterbook")) or '%'
+ band = str(band)
+ except:
+ band = '%'
- def _clean_comment(self, headers, line, mem):
- "Converts iso-8859-1 encoded comments to unicode for pyGTK."
- mem.comment = unicode(mem.comment, 'iso-8859-1')
- return mem
+ query = "https://www.repeaterbook.com/repeaters/downloads/CHIRP/" \
+ "app_direct.php?loc=%s&band=%s&dist=%s" % (loc, band, dist)
+ print query
+
+ # Do this in case the import process is going to take a while
+ # to make sure we process events leading up to this
+ gtk.gdk.window_process_all_updates()
+ while gtk.events_pending():
+ gtk.main_iteration(False)
- def _clean_name(self, headers, line, mem):
- "Converts iso-8859-1 encoded names to unicode for pyGTK."
- mem.name = unicode(mem.name, 'iso-8859-1')
- return mem
+ fn = tempfile.mktemp(".csv")
+ filename, headers = urllib.urlretrieve(query, fn)
+ if not os.path.exists(filename):
+ LOG.error("Failed, headers were: %s", headers)
+ common.show_error(_("RepeaterBook query failed"))
+ self.window.set_cursor(None)
+ return
try:
# Validate CSV
- radio = RBRadio(filename)
+ radio = repeaterbook.RBRadio(filename)
if radio.errors:
reporting.report_misc_error("repeaterbook",
("query=%s\n" % query) +
@@ -1436,14 +1589,18 @@ of file.
self.do_close()
elif action == "import":
self.do_import()
+ elif action in ["qdmrmarc", "idmrmarc"]:
+ self.do_dmrmarc(action[0] == "i")
elif action in ["qrfinder", "irfinder"]:
self.do_rfinder(action[0] == "i")
elif action in ["qradioreference", "iradioreference"]:
self.do_radioreference(action[0] == "i")
elif action == "export":
self.do_export()
- elif action in ["qrbook", "irbook"]:
- self.do_repeaterbook(action[0] == "i")
+ elif action in ["qrbookpolitical", "irbookpolitical"]:
+ self.do_repeaterbook_political(action[0] == "i")
+ elif action in ["qrbookproximity", "irbookproximity"]:
+ self.do_repeaterbook_proximity(action[0] == "i")
elif action in ["qpr", "ipr"]:
self.do_przemienniki(action[0] == "i")
elif action == "about":
@@ -1531,14 +1688,22 @@ of file.
<menuitem action="download"/>
<menuitem action="upload"/>
<menu action="importsrc" name="importsrc">
+ <menuitem action="idmrmarc"/>
<menuitem action="iradioreference"/>
- <menuitem action="irbook"/>
+ <menu action="irbook" name="irbook">
+ <menuitem action="irbookpolitical"/>
+ <menuitem action="irbookproximity"/>
+ </menu>
<menuitem action="ipr"/>
<menuitem action="irfinder"/>
</menu>
<menu action="querysrc" name="querysrc">
+ <menuitem action="qdmrmarc"/>
<menuitem action="qradioreference"/>
- <menuitem action="qrbook"/>
+ <menu action="qrbook" name="qrbook">
+ <menuitem action="qrbookpolitical"/>
+ <menuitem action="qrbookproximity"/>
+ </menu>
<menuitem action="qpr"/>
<menuitem action="qrfinder"/>
</menu>
@@ -1609,17 +1774,27 @@ of file.
('export', None, _("Export"), "%se" % ALT_KEY, None, self.mh),
('importsrc', None, _("Import from data source"),
None, None, self.mh),
+ ('idmrmarc', None, _("DMR-MARC Repeaters"), None, None, self.mh),
('iradioreference', None, _("RadioReference.com"),
None, None, self.mh),
('irfinder', None, _("RFinder"), None, None, self.mh),
('irbook', None, _("RepeaterBook"), None, None, self.mh),
+ ('irbookpolitical', None, _("RepeaterBook political query"), None,
+ None, self.mh),
+ ('irbookproximity', None, _("RepeaterBook proximity query"), None,
+ None, self.mh),
('ipr', None, _("przemienniki.net"), None, None, self.mh),
('querysrc', None, _("Query data source"), None, None, self.mh),
+ ('qdmrmarc', None, _("DMR-MARC Repeaters"), None, None, self.mh),
('qradioreference', None, _("RadioReference.com"),
None, None, self.mh),
('qrfinder', None, _("RFinder"), None, None, self.mh),
('qpr', None, _("przemienniki.net"), None, None, self.mh),
('qrbook', None, _("RepeaterBook"), None, None, self.mh),
+ ('qrbookpolitical', None, _("RepeaterBook political query"), None,
+ None, self.mh),
+ ('qrbookproximity', None, _("RepeaterBook proximity query"), None,
+ None, self.mh),
('export_chirp', None, _("CHIRP Native File"),
None, None, self.mh),
('export_csv', None, _("CSV File"), None, None, self.mh),
--
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