[hamradio-commits] [chirp] 08/19: Imported Upstream version 0.4.0
Iain R. Learmonth
irl at moszumanska.debian.org
Sun Nov 1 16:02:43 UTC 2015
This is an automated email from the git hooks/post-receive script.
irl pushed a commit to branch master
in repository chirp.
commit c86187dcdf158df59977baee6c8d6385f964b69b
Author: Iain R. Learmonth <irl at fsfe.org>
Date: Sun May 3 18:15:26 2015 +0100
Imported Upstream version 0.4.0
---
PKG-INFO | 2 +-
chirp/__init__.py | 2 +-
chirp/anytone.py | 548 +++++++++++++++
chirp/bandplan.py | 84 +++
chirp/bandplan_au.py | 114 ++++
chirp/bandplan_iaru_r1.py | 144 ++++
chirp/bandplan_iaru_r2.py | 145 ++++
chirp/bandplan_iaru_r3.py | 139 ++++
chirp/bandplan_na.py | 269 ++++++++
chirp/baofeng_uv3r.py | 343 +++++++++-
chirp/bitwise.py | 161 ++++-
chirp/bitwise_grammar.py | 36 +-
chirp/bjuv55.py | 647 ++++++++++++++++++
chirp/chirp_common.py | 309 +++++----
chirp/elib_intl.py | 495 ++++++++++++++
chirp/ft1802.py | 16 +
chirp/ft60.py | 39 +-
chirp/ft7800.py | 382 ++++++++++-
chirp/ft817.py | 112 ++-
chirp/ft857.py | 776 ++++++++++++++++++++-
chirp/ft90.py | 652 ++++++++++++++++++
chirp/ftm350.py | 420 ++++++++++++
chirp/generic_csv.py | 195 +++++-
chirp/generic_tpe.py | 50 +-
chirp/h777.py | 542 +++++++++++++++
chirp/ic208.py | 34 +-
chirp/ic2820.py | 2 +-
chirp/icf.py | 21 +-
chirp/icq7.py | 184 +++++
chirp/ict70.py | 2 +-
chirp/ict7h.py | 10 +-
chirp/id31.py | 24 +-
chirp/id51.py | 109 +++
chirp/import_logic.py | 30 +-
chirp/kenwood_hmk.py | 5 +
chirp/{kenwood_hmk.py => kenwood_itm.py} | 94 +--
chirp/kenwood_live.py | 178 ++++-
chirp/platform.py | 90 +--
chirp/puxing.py | 39 +-
chirp/pyPEG.py | 181 +++--
chirp/radioreference.py | 8 +-
chirp/settings.py | 71 +-
chirp/th_uv3r.py | 2 +-
chirp/th_uvf8d.py | 623 +++++++++++++++++
chirp/thuv1f.py | 22 +-
chirp/tk8102.py | 422 ++++++++++++
chirp/uv5r.py | 1083 ++++++++++++++++++++++--------
chirp/uvb5.py | 775 +++++++++++++++++++++
chirp/vx2.py | 708 +++++++++++++++++++
chirp/vx3.py | 686 ++++++++++++++++++-
chirp/vx5.py | 29 +-
chirp/vx510.py | 201 ++++++
chirp/vx6.py | 159 ++++-
chirp/vx7.py | 36 +-
chirp/vx8.py | 1075 +++++++++++++++++++++++++++--
chirp/wouxun.py | 604 +++++++++++++++--
chirp/yaesu_clone.py | 20 +-
chirpui/bandplans.py | 107 +++
chirpui/bankedit.py | 229 ++++---
chirpui/clone.py | 2 +
chirpui/common.py | 43 +-
chirpui/config.py | 9 +
chirpui/dstaredit.py | 3 +-
chirpui/editorset.py | 216 +++---
chirpui/mainapp.py | 316 ++++++---
chirpui/memdetail.py | 60 +-
chirpui/memedit.py | 212 ++++--
chirpui/radiobrowser.py | 320 +++++++++
chirpui/settingsedit.py | 43 +-
chirpui/shiftdialog.py | 24 +-
chirpw | 34 +-
locale/de/LC_MESSAGES/CHIRP.mo | Bin 0 -> 17225 bytes
locale/en_US/LC_MESSAGES/CHIRP.mo | Bin 0 -> 4802 bytes
locale/hu/LC_MESSAGES/CHIRP.mo | Bin 0 -> 15824 bytes
locale/it/LC_MESSAGES/CHIRP.mo | Bin 0 -> 15186 bytes
locale/nl/LC_MESSAGES/CHIRP.mo | Bin 0 -> 15633 bytes
locale/pl/LC_MESSAGES/CHIRP.mo | Bin 0 -> 11137 bytes
locale/pt_BR/LC_MESSAGES/CHIRP.mo | Bin 0 -> 15187 bytes
locale/ru/LC_MESSAGES/CHIRP.mo | Bin 0 -> 16690 bytes
79 files changed, 14377 insertions(+), 1390 deletions(-)
diff --git a/PKG-INFO b/PKG-INFO
index be04fdb..1d5e8a6 100644
--- a/PKG-INFO
+++ b/PKG-INFO
@@ -1,6 +1,6 @@
Metadata-Version: 1.0
Name: chirp
-Version: 0.3.1
+Version: 0.4.0
Summary: UNKNOWN
Home-page: UNKNOWN
Author: UNKNOWN
diff --git a/chirp/__init__.py b/chirp/__init__.py
index 645552e..e676a98 100644
--- a/chirp/__init__.py
+++ b/chirp/__init__.py
@@ -13,7 +13,7 @@
# You should have received a copy of the GNU General Public License
# along with this program. If not, see <http://www.gnu.org/licenses/>.
-CHIRP_VERSION="0.3.1"
+CHIRP_VERSION="0.4.0"
import os
import sys
diff --git a/chirp/anytone.py b/chirp/anytone.py
new file mode 100644
index 0000000..b9058ec
--- /dev/null
+++ b/chirp/anytone.py
@@ -0,0 +1,548 @@
+# Copyright 2013 Dan Smith <dsmith at danplanet.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 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 os
+import struct
+import time
+
+from chirp import bitwise
+from chirp import chirp_common
+from chirp import directory
+from chirp import errors
+from chirp import memmap
+from chirp import util
+from chirp.settings import RadioSettingGroup, RadioSetting, \
+ RadioSettingValueList, RadioSettingValueString, RadioSettingValueBoolean
+
+_mem_format = """
+#seekto 0x0100;
+struct {
+ u8 even_unknown:2,
+ even_pskip:1,
+ even_skip:1,
+ odd_unknown:2,
+ odd_pskip:1,
+ odd_skip:1;
+} flags[379];
+"""
+
+mem_format = _mem_format + """
+struct memory {
+ bbcd freq[4];
+ bbcd offset[4];
+ u8 unknownA:4,
+ tune_step:4;
+ u8 rxdcsextra:1,
+ txdcsextra:1,
+ rxinv:1,
+ txinv:1,
+ channel_width:2,
+ unknownB:2;
+ u8 unknown8:3,
+ is_am:1,
+ power:2,
+ duplex:2;
+ u8 unknown4:4,
+ rxtmode:2,
+ txtmode:2;
+ u8 unknown5:2,
+ txtone:6;
+ u8 unknown6:2,
+ rxtone:6;
+ u8 txcode;
+ u8 rxcode;
+ u8 unknown7[2];
+ u8 unknown2[5];
+ char name[7];
+ u8 unknownZ[2];
+};
+
+#seekto 0x0030;
+struct {
+ char serial[16];
+} serial_no;
+
+#seekto 0x0050;
+struct {
+ char date[16];
+} version;
+
+#seekto 0x0280;
+struct {
+ u8 unknown1:6,
+ display:2;
+ u8 unknown2[11];
+ u8 unknown3:3,
+ apo:5;
+ u8 unknown4a[2];
+ u8 unknown4b:6,
+ mute:2;
+ u8 unknown4;
+ u8 unknown5:5,
+ beep:1,
+ unknown6:2;
+ u8 unknown[334];
+ char welcome[8];
+} settings;
+
+#seekto 0x0540;
+struct memory memblk1[12];
+
+#seekto 0x2000;
+struct memory memory[758];
+
+#seekto 0x7ec0;
+struct memory memblk2[10];
+"""
+
+class FlagObj(object):
+ def __init__(self, flagobj, which):
+ self._flagobj = flagobj
+ self._which = which
+
+ def _get(self, flag):
+ return getattr(self._flagobj, "%s_%s" % (self._which, flag))
+
+ def _set(self, flag, value):
+ return setattr(self._flagobj, "%s_%s" % (self._which, flag), value)
+
+ def get_skip(self):
+ return self._get("skip")
+
+ def set_skip(self, value):
+ self._set("skip", value)
+
+ skip = property(get_skip, set_skip)
+
+ def get_pskip(self):
+ return self._get("pskip")
+
+ def set_pskip(self, value):
+ self._set("pskip", value)
+
+ pskip = property(get_pskip, set_pskip)
+
+ def set(self):
+ self._set("unknown", 3)
+ self._set("skip", 1)
+ self._set("pskip", 1)
+
+ def clear(self):
+ self._set("unknown", 0)
+ self._set("skip", 0)
+ self._set("pskip", 0)
+
+ def get(self):
+ return (self._get("unknown") << 2 |
+ self._get("skip") << 1 |
+ self._get("pskip"))
+
+ def __repr__(self):
+ return repr(self._flagobj)
+
+def _is_loc_used(memobj, loc):
+ return memobj.flags[loc / 2].get_raw() != "\xFF"
+
+def _addr_to_loc(addr):
+ return (addr - 0x2000) / 32
+
+def _should_send_addr(memobj, addr):
+ if addr < 0x2000 or addr >= 0x7EC0:
+ return True
+ else:
+ return _is_loc_used(memobj, _addr_to_loc(addr))
+
+def _debug(string):
+ if "CHIRP_DEBUG" in os.environ or True:
+ print string
+
+def _echo_write(radio, data):
+ try:
+ radio.pipe.write(data)
+ radio.pipe.read(len(data))
+ except Exception, e:
+ print "Error writing to radio: %s" % e
+ raise errors.RadioError("Unable to write to radio")
+
+def _read(radio, length):
+ try:
+ data = radio.pipe.read(length)
+ except Exception, e:
+ print "Error reading from radio: %s" % e
+ raise errors.RadioError("Unable to read from radio")
+
+ if len(data) != length:
+ print "Short read from radio (%i, expected %i)" % (len(data),
+ length)
+ print util.hexprint(data)
+ raise errors.RadioError("Short read from radio")
+ return data
+
+valid_model = ['QX588UV', 'HR-2040', 'DB-50M\x00', 'DB-750X']
+
+def _ident(radio):
+ radio.pipe.setTimeout(1)
+ _echo_write(radio, "PROGRAM")
+ response = radio.pipe.read(3)
+ if response != "QX\x06":
+ print "Response was:\n%s" % util.hexprint(response)
+ raise errors.RadioError("Unsupported model")
+ _echo_write(radio, "\x02")
+ response = radio.pipe.read(16)
+ _debug(util.hexprint(response))
+ if response[1:8] not in valid_model:
+ print "Response was:\n%s" % util.hexprint(response)
+ raise errors.RadioError("Unsupported model")
+
+def _finish(radio):
+ endframe = "\x45\x4E\x44"
+ _echo_write(radio, endframe)
+ result = radio.pipe.read(1)
+ if result != "\x06":
+ print "Got:\n%s" % util.hexprint(result)
+ raise errors.RadioError("Radio did not finish cleanly")
+
+def _checksum(data):
+ cs = 0
+ for byte in data:
+ cs += ord(byte)
+ return cs % 256
+
+def _send(radio, cmd, addr, length, data=None):
+ frame = struct.pack(">cHb", cmd, addr, length)
+ if data:
+ frame += data
+ frame += chr(_checksum(frame[1:]))
+ frame += "\x06"
+ _echo_write(radio, frame)
+ _debug("Sent:\n%s" % util.hexprint(frame))
+ if data:
+ result = radio.pipe.read(1)
+ if result != "\x06":
+ print "Ack was: %s" % repr(result)
+ raise errors.RadioError("Radio did not accept block at %04x" % addr)
+ return
+ result = _read(radio, length + 6)
+ _debug("Got:\n%s" % util.hexprint(result))
+ header = result[0:4]
+ data = result[4:-2]
+ ack = result[-1]
+ if ack != "\x06":
+ print "Ack was: %s" % repr(ack)
+ raise errors.RadioError("Radio NAK'd block at %04x" % addr)
+ _cmd, _addr, _length = struct.unpack(">cHb", header)
+ if _addr != addr or _length != _length:
+ print "Expected/Received:"
+ print " Length: %02x/%02x" % (length, _length)
+ print " Addr: %04x/%04x" % (addr, _addr)
+ raise errors.RadioError("Radio send unexpected block")
+ cs = _checksum(result[1:-2])
+ if cs != ord(result[-2]):
+ print "Calculated: %02x" % cs
+ print "Actual: %02x" % ord(result[-2])
+ raise errors.RadioError("Block at 0x%04x failed checksum" % addr)
+ return data
+
+def _download(radio):
+ _ident(radio)
+
+ memobj = None
+
+ data = ""
+ for start, end in radio._ranges:
+ for addr in range(start, end, 0x10):
+ if memobj is not None and not _should_send_addr(memobj, addr):
+ block = "\xFF" * 0x10
+ else:
+ block = _send(radio, 'R', addr, 0x10)
+ data += block
+
+ status = chirp_common.Status()
+ status.cur = len(data)
+ status.max = end
+ status.msg = "Cloning from radio"
+ radio.status_fn(status)
+
+ if addr == 0x19F0:
+ memobj = bitwise.parse(_mem_format, data)
+
+ _finish(radio)
+
+ return memmap.MemoryMap(data)
+
+def _upload(radio):
+ _ident(radio)
+
+ for start, end in radio._ranges:
+ for addr in range(start, end, 0x10):
+ if addr < 0x0100:
+ continue
+ if not _should_send_addr(radio._memobj, addr):
+ continue
+ block = radio._mmap[addr:addr + 0x10]
+ _send(radio, 'W', addr, len(block), block)
+
+ status = chirp_common.Status()
+ status.cur = addr
+ status.max = end
+ status.msg = "Cloning to radio"
+ radio.status_fn(status)
+
+ _finish(radio)
+
+TONES = [62.5] + list(chirp_common.TONES)
+TMODES = ['', 'Tone', 'DTCS', '']
+DUPLEXES = ['', '-', '+', '']
+MODES = ["FM", "FM", "NFM"]
+POWER_LEVELS = [chirp_common.PowerLevel("High", watts=50),
+ chirp_common.PowerLevel("Mid1", watts=25),
+ chirp_common.PowerLevel("Mid2", watts=10),
+ chirp_common.PowerLevel("Low", watts=5)]
+
+
+ at directory.register
+class AnyTone5888UVRadio(chirp_common.CloneModeRadio,
+ chirp_common.ExperimentalRadio):
+ """AnyTone 5888UV"""
+ VENDOR = "AnyTone"
+ MODEL = "5888UV"
+ BAUD_RATE = 9600
+ _file_ident = "QX588UV"
+
+ # May try to mirror the OEM behavior later
+ _ranges = [
+ (0x0000, 0x8000),
+ ]
+
+ @classmethod
+ def get_prompts(cls):
+ rp = chirp_common.RadioPrompts()
+ rp.experimental = ("The Anytone driver is currently experimental. "
+ "There are no known issues with it, but you should "
+ "proceed with caution.")
+ return rp
+
+ def get_features(self):
+ rf = chirp_common.RadioFeatures()
+ rf.has_settings = True
+ rf.has_bank = False
+ rf.has_cross = True
+ rf.has_tuning_step = False
+ rf.has_rx_dtcs = True
+ rf.valid_skips = ["", "S", "P"]
+ rf.valid_modes = ["FM", "NFM", "AM"]
+ rf.valid_tmodes = ['', 'Tone', 'TSQL', 'DTCS', 'Cross']
+ rf.valid_cross_modes = ['Tone->DTCS', 'DTCS->Tone',
+ '->Tone', '->DTCS', 'Tone->Tone']
+ rf.valid_dtcs_codes = chirp_common.ALL_DTCS_CODES
+ rf.valid_bands = [(108000000, 500000000)]
+ rf.valid_characters = chirp_common.CHARSET_UPPER_NUMERIC + "-"
+ rf.valid_name_length = 7
+ rf.valid_power_levels = POWER_LEVELS
+ rf.memory_bounds = (1, 758)
+ return rf
+
+ def sync_in(self):
+ self._mmap = _download(self)
+ self.process_mmap()
+
+ def sync_out(self):
+ _upload(self)
+
+ def process_mmap(self):
+ self._memobj = bitwise.parse(mem_format, self._mmap)
+
+ def _get_memobjs(self, number):
+ number -= 1
+ _mem = self._memobj.memory[number]
+ _flg = FlagObj(self._memobj.flags[number / 2],
+ number % 2 and "even" or "odd")
+ return _mem, _flg
+
+ def _get_dcs_index(self, _mem, which):
+ base = getattr(_mem, '%scode' % which)
+ extra = getattr(_mem, '%sdcsextra' % which)
+ return (int(extra) << 8) | int(base)
+
+ def _set_dcs_index(self, _mem, which, index):
+ base = getattr(_mem, '%scode' % which)
+ extra = getattr(_mem, '%sdcsextra' % which)
+ base.set_value(index & 0xFF)
+ extra.set_value(index >> 8)
+
+ def get_raw_memory(self, number):
+ _mem, _flg = self._get_memobjs(number)
+ return repr(_mem) + repr(_flg)
+
+ def get_memory(self, number):
+ _mem, _flg = self._get_memobjs(number)
+ mem = chirp_common.Memory()
+ mem.number = number
+
+ if _flg.get() == 0x0F:
+ mem.empty = True
+ return mem
+
+ mem.freq = int(_mem.freq) * 100
+ mem.offset = int(_mem.offset) * 100
+ mem.name = str(_mem.name).rstrip()
+ mem.duplex = DUPLEXES[_mem.duplex]
+ mem.mode = _mem.is_am and "AM" or MODES[_mem.channel_width]
+
+ rxtone = txtone = None
+ rxmode = TMODES[_mem.rxtmode]
+ txmode = TMODES[_mem.txtmode]
+ if txmode == "Tone":
+ txtone = TONES[_mem.txtone]
+ elif txmode == "DTCS":
+ txtone = chirp_common.ALL_DTCS_CODES[self._get_dcs_index(_mem,
+ 'tx')]
+ if rxmode == "Tone":
+ rxtone = TONES[_mem.rxtone]
+ elif rxmode == "DTCS":
+ rxtone = chirp_common.ALL_DTCS_CODES[self._get_dcs_index(_mem,
+ 'rx')]
+
+ rxpol = _mem.rxinv and "R" or "N"
+ txpol = _mem.txinv and "R" or "N"
+
+ chirp_common.split_tone_decode(mem,
+ (txmode, txtone, txpol),
+ (rxmode, rxtone, rxpol))
+
+ mem.skip = _flg.get_skip() and "S" or _flg.get_pskip() and "P" or ""
+ mem.power = POWER_LEVELS[_mem.power]
+
+ return mem
+
+ def set_memory(self, mem):
+ _mem, _flg = self._get_memobjs(mem.number)
+ if mem.empty:
+ _flg.set()
+ return
+ _flg.clear()
+ _mem.set_raw("\x00" * 32)
+
+ _mem.freq = mem.freq / 100
+ _mem.offset = mem.offset / 100
+ _mem.name = mem.name.ljust(7)
+ _mem.is_am = mem.mode == "AM"
+ _mem.duplex = DUPLEXES.index(mem.duplex)
+
+ try:
+ _mem.channel_width = MODES.index(mem.mode)
+ except ValueError:
+ _mem.channel_width = 0
+
+ ((txmode, txtone, txpol),
+ (rxmode, rxtone, rxpol)) = chirp_common.split_tone_encode(mem)
+
+ _mem.txtmode = TMODES.index(txmode)
+ _mem.rxtmode = TMODES.index(rxmode)
+ if txmode == "Tone":
+ _mem.txtone = TONES.index(txtone)
+ elif txmode == "DTCS":
+ self._set_dcs_index(_mem, 'tx',
+ chirp_common.ALL_DTCS_CODES.index(txtone))
+ if rxmode == "Tone":
+ _mem.rxtone = TONES.index(rxtone)
+ elif rxmode == "DTCS":
+ self._set_dcs_index(_mem, 'rx',
+ chirp_common.ALL_DTCS_CODES.index(rxtone))
+
+ _mem.txinv = txpol == "R"
+ _mem.rxinv = rxpol == "R"
+
+ _flg.set_skip(mem.skip == "S")
+ _flg.set_pskip(mem.skip == "P")
+
+ if mem.power:
+ _mem.power = POWER_LEVELS.index(mem.power)
+ else:
+ _mem.power = 0
+
+ def get_settings(self):
+ _settings = self._memobj.settings
+ settings = RadioSettingGroup('all', 'All Settings')
+
+ display = ["Frequency", "Channel", "Name"]
+ rs = RadioSetting("display", "Display",
+ RadioSettingValueList(display,
+ display[_settings.display]))
+ settings.append(rs)
+
+ apo = ["Off"] + ['%.1f hour(s)' % (0.5 * x) for x in range(1, 25)]
+ rs = RadioSetting("apo", "Automatic Power Off",
+ RadioSettingValueList(apo,
+ apo[_settings.apo]))
+ settings.append(rs)
+
+ def filter(s):
+ s_ = ""
+ for i in range(0, 8):
+ c = str(s[i])
+ s_ += (c if c in chirp_common.CHARSET_ASCII else "")
+ return s_
+
+ rs = RadioSetting("welcome", "Welcome Message",
+ RadioSettingValueString(0, 8,
+ filter(_settings.welcome)))
+ settings.append(rs)
+
+ rs = RadioSetting("beep", "Beep Enabled",
+ RadioSettingValueBoolean(_settings.beep))
+ settings.append(rs)
+
+ mute = ["Off", "TX", "RX", "TX/RX"]
+ rs = RadioSetting("mute", "Sub Band Mute",
+ RadioSettingValueList(mute,
+ mute[_settings.mute]))
+ settings.append(rs)
+
+ return settings
+
+ def set_settings(self, settings):
+ _settings = self._memobj.settings
+ for element in settings:
+ name = element.get_name()
+ setattr(_settings, name, element.value)
+
+ @classmethod
+ def match_model(cls, filedata, filename):
+ return cls._file_ident in filedata[0x20:0x40]
+
+ at directory.register
+class IntekHR2040Radio(AnyTone5888UVRadio):
+ """Intek HR-2040"""
+ VENDOR = "Intek"
+ MODEL = "HR-2040"
+ _file_ident = "HR-2040"
+
+ at directory.register
+class PolmarDB50MRadio(AnyTone5888UVRadio):
+ """Polmar DB-50M"""
+ VENDOR = "Polmar"
+ MODEL = "DB-50M"
+ _file_ident = "DB-50M"
+
+ at directory.register
+class PowerwerxDB750XRadio(AnyTone5888UVRadio):
+ """Powerwerx DB-750X"""
+ VENDOR = "Powerwerx"
+ MODEL = "DB-750X"
+ _file_ident = "DB-750X"
+
+ def get_settings(self):
+ return {}
diff --git a/chirp/bandplan.py b/chirp/bandplan.py
new file mode 100644
index 0000000..f2b8541
--- /dev/null
+++ b/chirp/bandplan.py
@@ -0,0 +1,84 @@
+# Copyright 2013 Sean Burford <sburford at google.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 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/>.
+
+from chirp import chirp_common
+
+class Band(object):
+ def __init__(self, limits, name, mode=None, step_khz=None,
+ input_offset=None, output_offset=None, tones=None):
+ # Apply semantic and chirp limitations to settings.
+ # memedit applies radio limitations when settings are applied.
+ try:
+ assert limits[0] <= limits[1], "Lower freq > upper freq"
+ if mode is not None:
+ assert mode in chirp_common.MODES, "Mode %s not one of %s" % (
+ mode, chirp_common.MODES)
+ if step_khz is not None:
+ assert step_khz in chirp_common.TUNING_STEPS, (
+ "step_khz %s not one of %s" %
+ (step_khz, chirp_common.TUNING_STEPS))
+ if tones:
+ for tone in tones:
+ assert tone in chirp_common.TONES, (
+ "tone %s not one of %s" % (tone, chirp_common.TONES))
+ except AssertionError, e:
+ raise ValueError("%s %s: %s" % (name, limits, e))
+
+ self.name = name
+ self.mode = mode
+ self.step_khz = step_khz
+ self.tones = tones
+ self.limits = limits
+ self.offset = None
+ self.duplex = "simplex"
+ if input_offset is not None:
+ self.offset = input_offset
+ self.duplex = "rpt TX"
+ elif output_offset is not None:
+ self.offset = output_offset
+ self.duplex = "rpt RX"
+
+ def __eq__(self, other):
+ return (other.limits[0] == self.limits[0] and
+ other.limits[1] == self.limits[1])
+
+ def contains(self, other):
+ return (other.limits[0] >= self.limits[0] and
+ other.limits[1] <= self.limits[1])
+
+ def width(self):
+ return self.limits[1] - self.limits[0]
+
+ def inverse(self):
+ """Create an RX/TX shadow of this band using the offset."""
+ if not self.offset:
+ return self
+ limits = (self.limits[0] + self.offset, self.limits[1] + self.offset)
+ offset = -1 * self.offset
+ if self.duplex == "rpt RX":
+ return Band(limits, self.name, self.mode, self.step_khz,
+ input_offset=offset, tones=self.tones)
+ return Band(limits, self.name, self.mode, self.step_khz,
+ output_offset=offset, tones=self.tones)
+
+ def __repr__(self):
+ desc = '%s%s%s%s' % (
+ self.mode and 'mode: %s ' % (self.mode,) or '',
+ self.step_khz and 'step_khz: %s ' % (self.step_khz,) or '',
+ self.offset and 'offset: %s ' % (self.offset,) or '',
+ self.tones and 'tones: %s ' % (self.tones,) or '')
+
+ return "%s-%s %s %s %s" % (
+ self.limits[0], self.limits[1], self.name, self.duplex, desc)
diff --git a/chirp/bandplan_au.py b/chirp/bandplan_au.py
new file mode 100644
index 0000000..136ef93
--- /dev/null
+++ b/chirp/bandplan_au.py
@@ -0,0 +1,114 @@
+# Copyright 2013 Sean Burford <sburford at google.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 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/>.
+
+
+from chirp import bandplan, bandplan_iaru_r3
+
+
+SHORTNAME = "australia"
+
+DESC = {
+ "name": "Australian Amateur Band Plan",
+ "updated": "April 2010",
+ "url": "http://www.wia.org.au/members/bandplans/data/documents/Australian%20Band%20Plans%20100404.pdf",
+}
+
+BANDS_10M = (
+ # bandplan.Band((28000000, 29700000), "10 Meter Band"),
+ bandplan.Band((29520000, 29680000), "FM Simplex and Repeaters",
+ mode="FM", step_khz=20),
+ bandplan.Band((29620000, 29680000), "FM Repeaters", input_offset=-100000),
+)
+
+BANDS_6M = (
+ # bandplan.Band((50000000, 54000000), "6 Meter Band"),
+ bandplan.Band((52525000, 53975000), "FM Simplex and Repeaters",
+ mode="FM", step_khz=25),
+ bandplan.Band((53550000, 53975000), "FM Repeaters", input_offset=-1000000),
+)
+
+BANDS_2M = (
+ bandplan.Band((144000000, 148000000), "2 Meter Band",
+ tones=(91.5, 123.0, 141.3, 146.2, 85.4)),
+ bandplan.Band((144400000, 144600000), "Beacons", step_khz=1),
+ bandplan.Band((146025000, 147975000), "FM Simplex and Repeaters",
+ mode="FM", step_khz=25),
+ bandplan.Band((146625000, 147000000), "FM Repeaters Group A",
+ input_offset=-600000),
+ bandplan.Band((147025000, 147375000), "FM Repeaters Group B",
+ input_offset=600000),
+)
+
+BANDS_70CM = (
+ bandplan.Band((420000000, 450000000), "70cm Band",
+ tones=(91.5, 123.0, 141.3, 146.2, 85.4)),
+ bandplan.Band((432400000, 432600000), "Beacons", step_khz=1),
+ bandplan.Band((438025000, 439975000), "FM Simplex and Repeaters",
+ mode="FM", step_khz=25),
+ bandplan.Band((438025000, 438725000), "FM Repeaters Group A",
+ input_offset=-5000000),
+ bandplan.Band((439275000, 439975000), "FM Repeaters Group B",
+ input_offset=-5000000),
+)
+
+BANDS_23CM = (
+ # bandplan.Band((1240000000, 1300000000), "23cm Band"),
+ bandplan.Band((1273025000, 1273975000), "FM Repeaters",
+ mode="FM", step_khz=25, input_offset=20000000),
+ bandplan.Band((1296400000, 1296600000), "Beacons", step_khz=1),
+ bandplan.Band((1297025000, 1300400000), "General FM Simplex Data",
+ mode="FM", step_khz=25),
+)
+
+BANDS_13CM = (
+ bandplan.Band((2300000000, 2450000000), "13cm Band"),
+ bandplan.Band((2403400000, 2403600000), "Beacons", step_khz=1),
+ bandplan.Band((2425000000, 2428000000), "FM Simplex",
+ mode="FM", step_khz=25),
+ bandplan.Band((2428025000, 2429000000), "FM Duplex (Voice)",
+ mode="FM", step_khz=25, input_offset=20000000),
+ bandplan.Band((2429000000, 2429975000), "FM Duplex (Data)",
+ mode="FM", step_khz=100, input_offset=20000000),
+)
+
+BANDS_9CM = (
+ bandplan.Band((3300000000, 3600000000), "9cm Band"),
+ bandplan.Band((3320000000, 3340000000), "WB Channel 2: Voice/Data",
+ step_khz=100),
+ bandplan.Band((3400400000, 3400600000), "Beacons", step_khz=1),
+ bandplan.Band((3402000000, 3403000000), "FM Simplex (Voice)",
+ mode="FM", step_khz=100),
+ bandplan.Band((3403000000, 3405000000), "FM Simplex (Data)",
+ mode="FM", step_khz=100),
+)
+
+BANDS_6CM = (
+ bandplan.Band((5650000000, 5850000000), "6cm Band"),
+ bandplan.Band((5760400000, 5760600000), "Beacons", step_khz=1),
+ bandplan.Band((5700000000, 5720000000), "WB Channel 2: Data",
+ step_khz=100, input_offset=70000000),
+ bandplan.Band((5720000000, 5740000000), "WB Channel 3: Voice",
+ step_khz=100, input_offset=70000000),
+ bandplan.Band((5762000000, 5763000000), "FM Simplex (Voice)",
+ mode="FM", step_khz=100),
+ bandplan.Band((5763000000, 5765000000), "FM Simplex (Data)",
+ mode="FM", step_khz=100),
+)
+
+BANDS = bandplan_iaru_r3.BANDS_20M + bandplan_iaru_r3.BANDS_17M
+BANDS += bandplan_iaru_r3.BANDS_15M + bandplan_iaru_r3.BANDS_12M
+BANDS += bandplan_iaru_r3.BANDS_10M + bandplan_iaru_r3.BANDS_6M
+BANDS += BANDS_10M + BANDS_6M + BANDS_2M + BANDS_70CM + BANDS_23CM
+BANDS += BANDS_13CM + BANDS_9CM + BANDS_6CM
diff --git a/chirp/bandplan_iaru_r1.py b/chirp/bandplan_iaru_r1.py
new file mode 100644
index 0000000..f71be4a
--- /dev/null
+++ b/chirp/bandplan_iaru_r1.py
@@ -0,0 +1,144 @@
+# Copyright 2013 Sean Burford <sburford at google.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 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/>.
+
+from chirp import bandplan
+
+SHORTNAME = "iaru_r1"
+
+DESC = {
+ "name": "IARU Region 1 (Europe, Africa, Middle East and Northern Asia)",
+ "url": "http://iaru-r1.org/index.php?option=com_content&view=article&id=175&Itemid=127",
+ "updated": "General Conference Sun City 2011",
+}
+
+# Bands are broken up like this so that other plans can import bits.
+
+BANDS_2100M = (
+ bandplan.Band((135700, 137800), "137khz Band", mode="CW"),
+)
+
+BANDS_160M = (
+ bandplan.Band((1810000, 2000000), "160 Meter Band"),
+ bandplan.Band((1810000, 1838000), "CW", mode="CW"),
+ bandplan.Band((1838000, 1840000), "All narrow band modes"),
+ bandplan.Band((1840000, 1843000), "All modes, digimodes", mode="RTTY"),
+)
+
+BANDS_80M = (
+ bandplan.Band((3500000, 3800000), "80 Meter Band"),
+ bandplan.Band((3500000, 3510000), "CW, priority for DX", mode="CW"),
+ bandplan.Band((3510000, 3560000), "CW, contest preferred", mode="CW"),
+ bandplan.Band((3560000, 3580000), "CW", mode="CW"),
+ bandplan.Band((3580000, 3600000), "All narrow band modes, digimodes"),
+ bandplan.Band((3590000, 3600000), "All narrow band, digimodes, unattended"),
+ bandplan.Band((3600000, 3650000), "All modes, SSB contest preferred",
+ mode="LSB"),
+ bandplan.Band((3600000, 3700000), "All modes, SSB QRP", mode="LSB"),
+ bandplan.Band((3700000, 3800000), "All modes, SSB contest preferred",
+ mode="LSB"),
+ bandplan.Band((3775000, 3800000), "All modes, SSB DX preferred", mode="LSB"),
+)
+
+BANDS_40M = (
+ bandplan.Band((7000000, 7200000), "40 Meter Band"),
+ bandplan.Band((7000000, 7040000), "CW", mode="CW"),
+ bandplan.Band((7040000, 7047000), "All narrow band modes, digimodes"),
+ bandplan.Band((7047000, 7050000), "All narrow band, digimodes, unattended"),
+ bandplan.Band((7050000, 7053000), "All modes, digimodes, unattended"),
+ bandplan.Band((7053000, 7060000), "All modes, digimodes"),
+ bandplan.Band((7060000, 7100000), "All modes, SSB contest preferred",
+ mode="LSB"),
+ bandplan.Band((7100000, 7130000), "All modes, R1 Emergency Center Of Activity",
+ mode="LSB"),
+ bandplan.Band((7130000, 7200000), "All modes, SSB contest preferred",
+ mode="LSB"),
+ bandplan.Band((7175000, 7200000), "All modes, SSB DX preferred", mode="LSB"),
+)
+
+BANDS_30M = (
+ bandplan.Band((10100000, 10150000), "30 Meter Band"),
+ bandplan.Band((10100000, 10140000), "CW", mode="CW"),
+ bandplan.Band((10140000, 10150000), "All narrow band digimodes"),
+)
+
+BANDS_20M = (
+ bandplan.Band((14000000, 14350000), "20 Meter Band"),
+ bandplan.Band((14000000, 14070000), "CW", mode="CW"),
+ bandplan.Band((14070000, 14099000), "All narrow band modes, digimodes"),
+ bandplan.Band((14099000, 14101000), "IBP, exclusively for beacons",
+ mode="CW"),
+ bandplan.Band((14101000, 14112000), "All narrow band modes, digimodes"),
+ bandplan.Band((14125000, 14350000), "All modes, SSB contest preferred",
+ mode="USB"),
+ bandplan.Band((14300000, 14350000), "All modes, Global Emergency center of activity",
+ mode="USB"),
+)
+
+BANDS_17M = (
+ bandplan.Band((18068000, 18168000), "17 Meter Band"),
+ bandplan.Band((18068000, 18095000), "CW", mode="CW"),
+ bandplan.Band((18095000, 18109000), "All narrow band modes, digimodes"),
+ bandplan.Band((18109000, 18111000), "IBP, exclusively for beacons",
+ mode="CW"),
+ bandplan.Band((18111000, 18168000), "All modes, digimodes"),
+)
+
+BANDS_15M = (
+ bandplan.Band((21000000, 21450000), "15 Meter Band"),
+ bandplan.Band((21000000, 21070000), "CW", mode="CW"),
+ bandplan.Band((21070000, 21090000), "All narrow band modes, digimodes"),
+ bandplan.Band((21090000, 21110000), "All narrow band, digimodes, unattended"),
+ bandplan.Band((21110000, 21120000), "All modes, digimodes, unattended"),
+ bandplan.Band((21120000, 21149000), "All narrow band modes"),
+ bandplan.Band((21149000, 21151000), "IBP, exclusively for beacons",
+ mode="CW"),
+ bandplan.Band((21151000, 21450000), "All modes", mode="USB"),
+)
+
+BANDS_12M = (
+ bandplan.Band((24890000, 24990000), "12 Meter Band"),
+ bandplan.Band((24890000, 24915000), "CW", mode="CW"),
+ bandplan.Band((24915000, 24929000), "All narrow band modes, digimodes"),
+ bandplan.Band((24929000, 24931000), "IBP, exclusively for beacons",
+ mode="CW"),
+ bandplan.Band((24931000, 24990000), "All modes, digimodes", mode="USB"),
+)
+
+BANDS_10M = (
+ bandplan.Band((28000000, 29700000), "10 Meter Band"),
+ bandplan.Band((28000000, 28070000), "CW", mode="CW"),
+ bandplan.Band((28070000, 28120000), "All narrow band modes, digimodes"),
+ bandplan.Band((28120000, 28150000), "All narrow band, digimodes, unattended"),
+ bandplan.Band((28150000, 28190000), "All narrow band modes"),
+ bandplan.Band((28190000, 28199000), "Beacons", mode="CW"),
+ bandplan.Band((28199000, 28201000), "IBP, exclusively for beacons",
+ mode="CW"),
+ bandplan.Band((28201000, 28300000), "Beacons", mode="CW"),
+ bandplan.Band((28300000, 28320000), "All modes, digimodes, unattended"),
+ bandplan.Band((28320000, 29100000), "All modes"),
+ bandplan.Band((29100000, 29200000), "FM simplex", mode="NFM", step_khz=10),
+ bandplan.Band((29200000, 29300000), "All modes, digimodes, unattended"),
+ bandplan.Band((29300000, 29510000), "Satellite downlink"),
+ bandplan.Band((29510000, 29520000), "Guard band, no transmission allowed"),
+ bandplan.Band((29520000, 29590000), "All modes, FM repeater inputs",
+ step_khz=10, mode="NFM"),
+ bandplan.Band((29600000, 29610000), "FM simplex", step_khz=10, mode="NFM"),
+ bandplan.Band((29620000, 29700000), "All modes, FM repeater outputs",
+ step_khz=10, mode="NFM", input_offset=-100000),
+ bandplan.Band((29520000, 29700000), "Wide band", step_khz=10, mode="NFM"),
+)
+
+BANDS = BANDS_2100M + BANDS_160M + BANDS_80M + BANDS_40M + BANDS_30M
+BANDS = BANDS + BANDS_20M + BANDS_17M + BANDS_15M + BANDS_12M + BANDS_10M
diff --git a/chirp/bandplan_iaru_r2.py b/chirp/bandplan_iaru_r2.py
new file mode 100644
index 0000000..5886187
--- /dev/null
+++ b/chirp/bandplan_iaru_r2.py
@@ -0,0 +1,145 @@
+# Copyright 2013 Sean Burford <sburford at google.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 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/>.
+
+from chirp import bandplan
+
+SHORTNAME = "iaru_r2"
+
+DESC = {
+ "name": "IARU Region 2 (The Americas)",
+ "updated": "October 8, 2010",
+ "url": "http://www.iaru.org/uploads/1/3/0/7/13073366/r2_band_plan.pdf"
+}
+
+# Bands are broken up like this so that other plans can import bits.
+
+BANDS_160M = (
+ bandplan.Band((1800000, 2000000), "160 Meter Band"),
+ bandplan.Band((1800000, 1810000), "Digimodes"),
+ bandplan.Band((1810000, 1830000), "CW", mode="CW"),
+ bandplan.Band((1830000, 1840000), "CW, priority for DX", mode="CW"),
+ bandplan.Band((1840000, 1850000), "SSB, priority for DX", mode="LSB"),
+ bandplan.Band((1850000, 1999000), "All modes", mode="LSB"),
+ bandplan.Band((1999000, 2000000), "Beacons", mode="CW"),
+)
+
+BANDS_80M = (
+ bandplan.Band((3500000, 4000000), "80 Meter Band"),
+ bandplan.Band((3500000, 3510000), "CW, priority for DX", mode="CW"),
+ bandplan.Band((3510000, 3560000), "CW, contest preferred", mode="CW"),
+ bandplan.Band((3560000, 3580000), "CW", mode="CW"),
+ bandplan.Band((3580000, 3590000), "All narrow band modes, digimodes"),
+ bandplan.Band((3590000, 3600000), "All modes"),
+ bandplan.Band((3600000, 3650000), "All modes, SSB contest preferred",
+ mode="LSB"),
+ bandplan.Band((3650000, 3700000), "All modes", mode="LSB"),
+ bandplan.Band((3700000, 3775000), "All modes, SSB contest preferred",
+ mode="LSB"),
+ bandplan.Band((3775000, 3800000), "All modes, SSB DX preferred", mode="LSB"),
+ bandplan.Band((3800000, 4000000), "All modes"),
+)
+
+BANDS_40M = (
+ bandplan.Band((7000000, 7300000), "40 Meter Band"),
+ bandplan.Band((7000000, 7025000), "CW, priority for DX", mode="CW"),
+ bandplan.Band((7025000, 7035000), "CW", mode="CW"),
+ bandplan.Band((7035000, 7038000), "All narrow band modes, digimodes"),
+ bandplan.Band((7038000, 7040000), "All narrow band modes, digimodes"),
+ bandplan.Band((7040000, 7043000), "All modes, digimodes"),
+ bandplan.Band((7043000, 7300000), "All modes"),
+)
+
+BANDS_30M = (
+ bandplan.Band((10100000, 10150000), "30 Meter Band"),
+ bandplan.Band((10100000, 10130000), "CW", mode="CW"),
+ bandplan.Band((10130000, 10140000), "All narrow band digimodes"),
+ bandplan.Band((10140000, 10150000), "All modes, digimodes, no phone"),
+)
+
+BANDS_20M = (
+ bandplan.Band((14000000, 14350000), "20 Meter Band"),
+ bandplan.Band((14000000, 14025000), "CW, priority for DX", mode="CW"),
+ bandplan.Band((14025000, 14060000), "CW, contest preferred", mode="CW"),
+ bandplan.Band((14060000, 14070000), "CW", mode="CW"),
+ bandplan.Band((14070000, 14089000), "All narrow band modes, digimodes"),
+ bandplan.Band((14089000, 14099000), "All modes, digimodes"),
+ bandplan.Band((14099000, 14101000), "IBP, exclusively for beacons",
+ mode="CW"),
+ bandplan.Band((14101000, 14112000), "All modes, digimodes"),
+ bandplan.Band((14112000, 14285000), "All modes, SSB contest preferred",
+ mode="USB"),
+ bandplan.Band((14285000, 14300000), "All modes", mode="AM"),
+ bandplan.Band((14300000, 14350000), "All modes"),
+)
+
+BANDS_17M = (
+ bandplan.Band((18068000, 18168000), "17 Meter Band"),
+ bandplan.Band((18068000, 18095000), "CW", mode="CW"),
+ bandplan.Band((18095000, 18105000), "All narrow band modes, digimodes"),
+ bandplan.Band((18105000, 18109000), "All narrow band modes, digimodes"),
+ bandplan.Band((18109000, 18111000), "IBP, exclusively for beacons",
+ mode="CW"),
+ bandplan.Band((18111000, 18120000), "All modes, digimodes"),
+ bandplan.Band((18120000, 18168000), "All modes"),
+)
+
+BANDS_15M = (
+ bandplan.Band((21000000, 21450000), "15 Meter Band"),
+ bandplan.Band((21000000, 21070000), "CW", mode="CW"),
+ bandplan.Band((21070000, 21090000), "All narrow band modes, digimodes"),
+ bandplan.Band((21090000, 21110000), "All narrow band modes, digimodes"),
+ bandplan.Band((21110000, 21120000), "All modes (exc SSB), digimodes"),
+ bandplan.Band((21120000, 21149000), "All narrow band modes"),
+ bandplan.Band((21149000, 21151000), "IBP, exclusively for beacons",
+ mode="CW"),
+ bandplan.Band((21151000, 21450000), "All modes", mode="USB"),
+)
+
+BANDS_12M = (
+ bandplan.Band((24890000, 24990000), "12 Meter Band"),
+ bandplan.Band((24890000, 24915000), "CW", mode="CW"),
+ bandplan.Band((24915000, 24925000), "All narrow band modes, digimodes"),
+ bandplan.Band((24925000, 24929000), "All narrow band modes, digimodes"),
+ bandplan.Band((24929000, 24931000), "IBP, exclusively for beacons",
+ mode="CW"),
+ bandplan.Band((24931000, 24940000), "All modes, digimodes"),
+ bandplan.Band((24940000, 24990000), "All modes", mode="USB"),
+)
+
+BANDS_10M = (
+ bandplan.Band((28000000, 29520000), "10 Meter Band"),
+ bandplan.Band((28000000, 28070000), "CW", mode="CW"),
+ bandplan.Band((28070000, 28120000), "All narrow band modes, digimodes"),
+ bandplan.Band((28120000, 28150000), "All narrow band modes, digimodes"),
+ bandplan.Band((28150000, 28190000), "All narrow band modes, digimodes"),
+ bandplan.Band((28190000, 28199000), "Regional time shared beacons",
+ mode="CW"),
+ bandplan.Band((28199000, 28201000), "IBP, exclusively for beacons",
+ mode="CW"),
+ bandplan.Band((28201000, 28225000), "Continuous duty beacons",
+ mode="CW"),
+ bandplan.Band((28225000, 28300000), "All modes, beacons"),
+ bandplan.Band((28300000, 28320000), "All modes, digimodes"),
+ bandplan.Band((28320000, 29000000), "All modes"),
+ bandplan.Band((29000000, 29200000), "All modes, AM preferred", mode="AM"),
+ bandplan.Band((29200000, 29300000), "All modes including FM, digimodes"),
+ bandplan.Band((29300000, 29510000), "Satellite downlink"),
+ bandplan.Band((29510000, 29520000), "Guard band, no transmission allowed"),
+ bandplan.Band((29520000, 29700000), "FM", step_khz=10, mode="NFM"),
+ bandplan.Band((29620000, 29690000), "FM Repeaters", input_offset=-100000),
+)
+
+BANDS = BANDS_160M + BANDS_80M + BANDS_40M + BANDS_30M + BANDS_20M
+BANDS = BANDS + BANDS_17M + BANDS_15M + BANDS_12M + BANDS_10M
diff --git a/chirp/bandplan_iaru_r3.py b/chirp/bandplan_iaru_r3.py
new file mode 100644
index 0000000..08bb56e
--- /dev/null
+++ b/chirp/bandplan_iaru_r3.py
@@ -0,0 +1,139 @@
+# Copyright 2013 Sean Burford <sburford at google.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 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/>.
+
+from chirp import bandplan
+
+SHORTNAME = "iaru_r3"
+
+DESC = {
+ "name": "IARU Region 3 (Asia Pacific)",
+ "updated": "16 October 2009",
+ "url": "http://www.iaru.org/uploads/1/3/0/7/13073366/r3_band_plan.pdf"
+}
+
+# Bands are broken up like this so that other plans can import bits.
+
+BANDS_2100M = (
+ bandplan.Band((135700, 137800), "137khz Band", mode="CW"),
+)
+
+BANDS_160M = (
+ bandplan.Band((1800000, 2000000), "160 Meter Band"),
+ bandplan.Band((1830000, 1840000), "Digimodes", mode="RTTY"),
+ bandplan.Band((1840000, 2000000), "Phone"),
+)
+
+BANDS_80M = (
+ bandplan.Band((3500000, 3900000), "80 Meter Band"),
+ bandplan.Band((3500000, 3510000), "CW, priority for DX", mode="CW"),
+ bandplan.Band((3535000, 3900000), "Phone"),
+ bandplan.Band((3775000, 3800000), "All modes, SSB DX preferred", mode="LSB"),
+)
+
+BANDS_40M = (
+ bandplan.Band((7000000, 7300000), "40 Meter Band"),
+ bandplan.Band((7000000, 7025000), "CW, priority for DX", mode="CW"),
+ bandplan.Band((7025000, 7035000), "All narrow band modes, cw", mode="CW"),
+ bandplan.Band((7035000, 7040000), "All narrow band modes, phone"),
+ bandplan.Band((7040000, 7300000), "All modes, digimodes"),
+)
+
+BANDS_30M = (
+ bandplan.Band((10100000, 10150000), "30 Meter Band"),
+ bandplan.Band((10100000, 10130000), "CW", mode="CW"),
+ bandplan.Band((10130000, 10150000), "All narrow band digimodes"),
+)
+
+BANDS_20M = (
+ bandplan.Band((14000000, 14350000), "20 Meter Band"),
+ bandplan.Band((14000000, 14070000), "CW", mode="CW"),
+ bandplan.Band((14070000, 14099000), "All narrow band modes, digimodes"),
+ bandplan.Band((14099000, 14101000), "IBP, exclusively for beacons",
+ mode="CW"),
+ bandplan.Band((14101000, 14112000), "All narrow band modes, digimodes"),
+ bandplan.Band((14101000, 14350000), "All modes, digimodes"),
+)
+
+BANDS_17M = (
+ bandplan.Band((18068000, 18168000), "17 Meter Band"),
+ bandplan.Band((18068000, 18100000), "CW", mode="CW"),
+ bandplan.Band((18100000, 18109000), "All narrow band modes, digimodes"),
+ bandplan.Band((18109000, 18111000), "IBP, exclusively for beacons",
+ mode="CW"),
+ bandplan.Band((18111000, 18168000), "All modes, digimodes"),
+)
+
+BANDS_15M = (
+ bandplan.Band((21000000, 21450000), "15 Meter Band"),
+ bandplan.Band((21000000, 21070000), "CW", mode="CW"),
+ bandplan.Band((21070000, 21125000), "All narrow band modes, digimodes"),
+ bandplan.Band((21125000, 21149000), "All narrow band modes, digimodes"),
+ bandplan.Band((21149000, 21151000), "IBP, exclusively for beacons",
+ mode="CW"),
+ bandplan.Band((21151000, 21450000), "All modes", mode="USB"),
+)
+
+BANDS_12M = (
+ bandplan.Band((24890000, 24990000), "12 Meter Band"),
+ bandplan.Band((24890000, 24920000), "CW", mode="CW"),
+ bandplan.Band((24920000, 24929000), "All narrow band modes, digimodes"),
+ bandplan.Band((24929000, 24931000), "IBP, exclusively for beacons",
+ mode="CW"),
+ bandplan.Band((24931000, 24990000), "All modes, digimodes", mode="USB"),
+)
+
+BANDS_10M = (
+ bandplan.Band((28000000, 29700000), "10 Meter Band"),
+ bandplan.Band((28000000, 28050000), "CW", mode="CW"),
+ bandplan.Band((28050000, 28150000), "All narrow band modes, digimodes"),
+ bandplan.Band((28150000, 28190000), "All narrow band modes, digimodes"),
+ bandplan.Band((28190000, 28199000), "Beacons", mode="CW"),
+ bandplan.Band((28199000, 28201000), "IBP, exclusively for beacons",
+ mode="CW"),
+ bandplan.Band((28201000, 28300000), "Beacons", mode="CW"),
+ bandplan.Band((28300000, 29300000), "Phone"),
+ bandplan.Band((29300000, 29510000), "Satellite downlink"),
+ bandplan.Band((29510000, 29520000), "Guard band, no transmission allowed"),
+ bandplan.Band((29520000, 29700000), "Wide band", step_khz=10, mode="NFM"),
+)
+
+BANDS_6M = (
+ bandplan.Band((50000000, 54000000), "6 Meter Band"),
+ bandplan.Band((50000000, 50100000), "Beacons", mode="CW"),
+ bandplan.Band((50100000, 50500000), "Phone and narrow band"),
+ bandplan.Band((50500000, 54000000), "Wide band"),
+)
+
+BANDS_2M = (
+ bandplan.Band((144000000, 148000000), "2 Meter Band"),
+ bandplan.Band((144000000, 144035000), "Earth Moon Earth"),
+ bandplan.Band((145800000, 146000000), "Satellite"),
+)
+
+BANDS_70CM = (
+ bandplan.Band((430000000, 450000000), "70cm Band"),
+ bandplan.Band((431900000, 432240000), "Earth Moon Earth"),
+ bandplan.Band((435000000, 438000000), "Satellite"),
+)
+
+BANDS_23CM = (
+ bandplan.Band((1240000000, 1300000000), "23cm Band"),
+ bandplan.Band((1260000000, 1270000000), "Satellite"),
+ bandplan.Band((1296000000, 1297000000), "Earth Moon Earth"),
+)
+
+BANDS = BANDS_2100M + BANDS_160M + BANDS_80M + BANDS_40M + BANDS_30M
+BANDS = BANDS + BANDS_20M + BANDS_17M + BANDS_15M + BANDS_12M + BANDS_10M
+BANDS = BANDS + BANDS_6M + BANDS_2M + BANDS_70CM + BANDS_23CM
diff --git a/chirp/bandplan_na.py b/chirp/bandplan_na.py
new file mode 100644
index 0000000..ba46c8c
--- /dev/null
+++ b/chirp/bandplan_na.py
@@ -0,0 +1,269 @@
+# Copyright 2013 Dan Smith <dsmith at danplanet.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 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/>.
+
+from chirp import bandplan, bandplan_iaru_r2
+
+
+SHORTNAME = "north_america"
+
+DESC = {
+ "name": "North American Band Plan",
+ "url": "http://www.arrl.org/band-plan"
+}
+
+BANDS_160M = (
+ bandplan.Band((1800000, 2000000), "160 Meter Band", mode="CW"),
+ bandplan.Band((1800000, 1810000), "Digital Modes"),
+ bandplan.Band((1843000, 2000000), "SSB, SSTV and other wideband modes"),
+ bandplan.Band((1995000, 2000000), "Experimental"),
+ bandplan.Band((1999000, 2000000), "Beacons"),
+)
+
+BANDS_80M = (
+ bandplan.Band((3500000, 4000000), "80 Meter Band"),
+ bandplan.Band((3570000, 3600000), "RTTY/Data", mode="RTTY"),
+ bandplan.Band((3790000, 3800000), "DX window"),
+)
+
+BANDS_40M = (
+ bandplan.Band((7000000, 7300000), "40 Meter Band"),
+ bandplan.Band((7080000, 7125000), "RTTY/Data", mode="RTTY"),
+)
+
+BANDS_30M = (
+ bandplan.Band((10100000, 10150000), "30 Meter Band"),
+ bandplan.Band((10130000, 10140000), "RTTY", mode="RTTY"),
+ bandplan.Band((10140000, 10150000), "Packet"),
+)
+
+BANDS_20M = (
+ bandplan.Band((14000000, 14350000), "20 Meter Band"),
+ bandplan.Band((14070000, 14095000), "RTTY", mode="RTTY"),
+ bandplan.Band((14095000, 14099500), "Packet"),
+ bandplan.Band((14100500, 14112000), "Packet"),
+)
+
+BANDS_17M = (
+ bandplan.Band((18068000, 18168000), "17 Meter Band"),
+ bandplan.Band((18100000, 18105000), "RTTY", mode="RTTY"),
+ bandplan.Band((18105000, 18110000), "Packet"),
+)
+
+BANDS_15M = (
+ bandplan.Band((21000000, 21450000), "15 Meter Band"),
+ bandplan.Band((21070000, 21110000), "RTTY/Data", mode="RTTY"),
+)
+
+BANDS_12M = (
+ bandplan.Band((24890000, 24990000), "12 Meter Band"),
+ bandplan.Band((24920000, 24925000), "RTTY", mode="RTTY"),
+ bandplan.Band((24925000, 24930000), "Packet"),
+)
+
+BANDS_10M = (
+ bandplan.Band((28000000, 29700000), "10 Meter Band"),
+ bandplan.Band((28000000, 28070000), "CW", mode="CW"),
+ bandplan.Band((28070000, 28150000), "RTTY", mode="RTTY"),
+ bandplan.Band((28150000, 28190000), "CW", mode="CW"),
+ bandplan.Band((28201000, 28300000), "Beacons", mode="CW"),
+ bandplan.Band((28300000, 29300000), "Phone"),
+ bandplan.Band((29000000, 29200000), "AM", mode="AM"),
+ bandplan.Band((29300000, 29510000), "Satellite Downlinks"),
+ bandplan.Band((29520000, 29590000), "Repeater Inputs",
+ step_khz=10, mode="NFM"),
+ bandplan.Band((29610000, 29700000), "Repeater Outputs",
+ step_khz=10, mode="NFM", input_offset=-890000),
+)
+
+BANDS_6M = (
+ bandplan.Band((50000000, 54000000), "6 Meter Band"),
+ bandplan.Band((50000000, 50100000), "CW, beacons", mode="CW"),
+ bandplan.Band((50060000, 50080000), "beacon subband"),
+ bandplan.Band((50100000, 50300000), "SSB, CW", mode="USB"),
+ bandplan.Band((50100000, 50125000), "DX window", mode="USB"),
+ bandplan.Band((50300000, 50600000), "All modes"),
+ bandplan.Band((50600000, 50800000), "Nonvoice communications"),
+ bandplan.Band((50800000, 51000000), "Radio remote control", step_khz=20),
+ bandplan.Band((51000000, 51100000), "Pacific DX window"),
+ bandplan.Band((51120000, 51180000), "Digital repeater inputs", step_khz=10),
+ bandplan.Band((51500000, 51600000), "Simplex"),
+ bandplan.Band((51620000, 51980000), "Repeater outputs A",
+ input_offset=-500000),
+ bandplan.Band((51620000, 51680000), "Digital repeater outputs",
+ input_offset=-500000),
+ bandplan.Band((52020000, 52040000), "FM simplex", mode="NFM"),
+ bandplan.Band((52500000, 52980000), "Repeater outputs B",
+ input_offset=-500000, step_khz=20, mode="NFM"),
+ bandplan.Band((53000000, 53100000), "FM simplex", mode="NFM"),
+ bandplan.Band((53100000, 53400000), "Radio remote control", step_khz=100),
+ bandplan.Band((53500000, 53980000), "Repeater outputs C",
+ input_offset=-500000),
+ bandplan.Band((53500000, 53800000), "Radio remote control", step_khz=100),
+ bandplan.Band((53520000, 53900000), "Simplex"),
+)
+
+BANDS_2M = (
+ bandplan.Band((144000000, 148000000), "2 Meter Band"),
+ bandplan.Band((144000000, 144050000), "EME (CW)", mode="CW"),
+ bandplan.Band((144050000, 144100000), "General CW and weak signals",
+ mode="CW"),
+ bandplan.Band((144100000, 144200000), "EME and weak-signal SSB",
+ mode="USB"),
+ bandplan.Band((144200000, 144275000), "General SSB operation",
+ mode="USB"),
+ bandplan.Band((144275000, 144300000), "Propagation beacons", mode="CW"),
+ bandplan.Band((144300000, 144500000), "OSCAR subband"),
+ bandplan.Band((144600000, 144900000), "FM repeater inputs", mode="NFM"),
+ bandplan.Band((144900000, 145100000), "Weak signal and FM simplex",
+ mode="NFM", step_khz=10),
+ bandplan.Band((145100000, 145200000), "Linear translator outputs",
+ input_offset=-600000),
+ bandplan.Band((145200000, 145500000), "FM repeater outputs",
+ input_offset=-600000, mode="NFM",),
+ bandplan.Band((145500000, 145800000), "Misc and experimental modes"),
+ bandplan.Band((145800000, 146000000), "OSCAR subband"),
+ bandplan.Band((146400000, 146580000), "Simplex"),
+ bandplan.Band((146610000, 146970000), "Repeater outputs",
+ input_offset=-600000),
+ bandplan.Band((147000000, 147390000), "Repeater outputs",
+ input_offset=600000),
+ bandplan.Band((147420000, 147570000), "Simplex"),
+)
+
+BANDS_1_25M = (
+ bandplan.Band((222000000, 225000000), "1.25 Meters"),
+ bandplan.Band((222000000, 222150000), "Weak-signal modes"),
+ bandplan.Band((222000000, 222025000), "EME"),
+ bandplan.Band((222050000, 222060000), "Propagation beacons"),
+ bandplan.Band((222100000, 222150000), "Weak-signal CW & SSB"),
+ bandplan.Band((222150000, 222250000), "Local coordinator's option"),
+ bandplan.Band((223400000, 223520000), "FM simplex", mode="NFM"),
+ bandplan.Band((223520000, 223640000), "Digital, packet"),
+ bandplan.Band((223640000, 223700000), "Links, control"),
+ bandplan.Band((223710000, 223850000), "Local coordinator's option"),
+ bandplan.Band((223850000, 224980000), "Repeater outputs only",
+ mode="NFM", input_offset=-1600000),
+)
+
+BANDS_70CM = (
+ bandplan.Band((420000000, 450000000), "70cm Band"),
+ bandplan.Band((420000000, 426000000), "ATV repeater or simplex"),
+ bandplan.Band((426000000, 432000000), "ATV simplex"),
+ bandplan.Band((432000000, 432070000), "EME (Earth-Moon-Earth)"),
+ bandplan.Band((432070000, 432100000), "Weak-signal CW", mode="CW"),
+ bandplan.Band((432100000, 432300000), "Mixed-mode and weak-signal work"),
+ bandplan.Band((432300000, 432400000), "Propagation beacons"),
+ bandplan.Band((432400000, 433000000), "Mixed-mode and weak-signal work"),
+ bandplan.Band((433000000, 435000000), "Auxiliary/repeater links"),
+ bandplan.Band((435000000, 438000000), "Satellite only (internationally)"),
+ bandplan.Band((438000000, 444000000), "ATV repeater input/repeater links",
+ input_offset=5000000),
+ bandplan.Band((442000000, 445000000), "Repeater input/output (local option)",
+ input_offset=5000000),
+ bandplan.Band((445000000, 447000000), "Shared by aux and control links, "
+ "repeaters, simplex (local option)"),
+ bandplan.Band((447000000, 450000000), "Repeater inputs and outputs "
+ "(local option)", input_offset=-5000000),
+)
+
+BANDS_33CM = (
+ bandplan.Band((902000000, 928000000), "33 Centimeter Band"),
+ bandplan.Band((902075000, 902100000), "CW/SSB, Weak signal"),
+ bandplan.Band((902100000, 902125000), "CW/SSB, Weak signal"),
+ bandplan.Band((903000000, 903100000), "CW/SSB, Beacons and weak signal"),
+ bandplan.Band((903100000, 903400000), "CW/SSB, Weak signal"),
+ bandplan.Band((903400000, 909000000), "Mixed modes, Mixed operations "
+ "including control links"),
+ bandplan.Band((909000000, 915000000), "Analog/digital Broadband multimedia "
+ "including ATV, DATV and SS"),
+ bandplan.Band((915000000, 921000000), "Analog/digital Broadband multimedia "
+ "including ATV, DATV and SS"),
+ bandplan.Band((921000000, 927000000), "Analog/digital Broadband multimedia "
+ "including ATV, DATV and SS"),
+ bandplan.Band((927000000, 927075000), "FM / other including DV or CW/SSB",
+ input_offset=-25000000, step_khz=12.5),
+ bandplan.Band((927075000, 927125000), "FM / other including DV. Simplex"),
+ bandplan.Band((927125000, 928000000), "FM / other including DV",
+ input_offset=-25000000, step_khz=12.5),
+)
+
+BANDS_23CM = (
+ bandplan.Band((1240000000, 1300000000), "23 Centimeter Band"),
+ bandplan.Band((1240000000, 1246000000), "ATV Channel #1"),
+ bandplan.Band((1246000000, 1248000000), "Point-to-point links paired "
+ "with 1258.000-1260.000", mode="FM"),
+ bandplan.Band((1248000000, 1252000000), "Digital"),
+ bandplan.Band((1252000000, 1258000000), "ATV Channel #2"),
+ bandplan.Band((1258000000, 1260000000), "Point-to-point links paired "
+ "with 1246.000-1248.000", mode="FM"),
+ bandplan.Band((1240000000, 1260000000), "Regional option, FM ATV"),
+ bandplan.Band((1260000000, 1270000000), "Satellite uplinks, Experimental, "
+ "Simplex ATV"),
+ bandplan.Band((1270000000, 1276000000), "FM, digital Repeater inputs "
+ "(Regional option)", step_khz=25),
+ bandplan.Band((1276000000, 1282000000), "ATV Channel #3"),
+ bandplan.Band((1282000000, 1288000000), "FM, digital repeater outputs",
+ step_khz=25, input_offset=-12000000),
+ bandplan.Band((1288000000, 1294000000), "Various Broadband Experimental, "
+ "Simplex ATV"),
+ bandplan.Band((1290000000, 1294000000), "FM, digital Repeater outputs "
+ "(Regional option)", step_khz=25, input_offset=-20000000),
+ bandplan.Band((1294000000, 1295000000), "FM simplex", mode="FM"),
+ bandplan.Band((1295000000, 1297000000), "Narrow Band Segment"),
+ bandplan.Band((1295000000, 1295800000), "Narrow Band Image, Experimental"),
+ bandplan.Band((1295800000, 1296080000), "CW, SSB, digital EME"),
+ bandplan.Band((1296080000, 1296200000), "CW, SSB Weak Signal"),
+ bandplan.Band((1296200000, 1296400000), "CW, digital Beacons"),
+ bandplan.Band((1296400000, 1297000000), "General Narrow Band"),
+ bandplan.Band((1297000000, 1300000000), "Digital"),
+)
+
+BANDS_13CM = (
+ bandplan.Band((2300000000, 2450000000), "13 Centimeter Band"),
+ bandplan.Band((2300000000, 2303000000), "Analog & Digital 0.05-1.0 MHz, "
+ "including full duplex; paired with 2390-2393"),
+ bandplan.Band((2303000000, 2303750000), "Analog & Digital <50kHz; "
+ "paired with 2393 - 2393.750"),
+ bandplan.Band((2303750000, 2304000000), "SSB, CW, digital weak-signal"),
+ bandplan.Band((2304000000, 2304100000), "Weak Signal EME Band, <3kHz"),
+ bandplan.Band((2304100000, 2304300000), "SSB, CW, digital weak-signal, <3kHz"
+ ),
+ bandplan.Band((2304300000, 2304400000), "Beacons, <3kHz"),
+ bandplan.Band((2304400000, 2304750000), "SSB, CW, digital weak-signal and "
+ "NBFM, <6kHz"),
+ bandplan.Band((2304750000, 2305000000), "Analog & Digital; paired with "
+ "2394.750-2395, <50kHz"),
+ bandplan.Band((2305000000, 2310000000), "Analog & Digital, paired with "
+ "2395-2400, 0.05 - 1.0 MHz"),
+ bandplan.Band((2310000000, 2390000000), "NON-AMATEUR"),
+ bandplan.Band((2390000000, 2393000000), "Analog & Digital, including full "
+ "duplex; paired with 2300-2303, 0.05 - 1.0 MHz"),
+ bandplan.Band((2393000000, 2393750000), "Analog & Digital; paired with "
+ "2303-2303.750, < 50 kHz"),
+ bandplan.Band((2393750000, 2394750000), "Experimental"),
+ bandplan.Band((2394750000, 2395000000), "Analog & Digital; paired with "
+ "2304.750-2305, < 50 kHz"),
+ bandplan.Band((2395000000, 2400000000), "Analog & Digital, including full "
+ "duplex; paired with 2305-2310, 0.05-1.0 MHz"),
+ bandplan.Band((2400000000, 2410000000), "Amateur Satellite Communications, "
+ "<6kHz"),
+ bandplan.Band((2410000000, 2450000000), "Broadband Modes, 22MHz max."),
+)
+
+BANDS = bandplan_iaru_r2.BANDS
+BANDS += BANDS_160M + BANDS_80M + BANDS_40M + BANDS_30M + BANDS_20M
+BANDS += BANDS_17M + BANDS_15M + BANDS_12M + BANDS_10M + BANDS_6M
+BANDS += BANDS_2M + BANDS_1_25M + BANDS_70CM + BANDS_33CM + BANDS_23CM
+BANDS += BANDS_13CM
diff --git a/chirp/baofeng_uv3r.py b/chirp/baofeng_uv3r.py
index 19d6921..c465edc 100644
--- a/chirp/baofeng_uv3r.py
+++ b/chirp/baofeng_uv3r.py
@@ -19,6 +19,10 @@ import time
import os
from chirp import util, chirp_common, bitwise, errors, directory
from chirp.wouxun_common import do_download, do_upload
+from chirp.settings import RadioSetting, RadioSettingGroup, \
+ RadioSettingValueBoolean, RadioSettingValueList, \
+ RadioSettingValueInteger, RadioSettingValueString, \
+ RadioSettingValueFloat
if os.getenv("CHIRP_DEBUG"):
DEBUG = True
@@ -88,6 +92,60 @@ struct {
u8 unknown;
lbcd tx_freq[4];
} tx_memory[99];
+
+#seekto 0x0780;
+struct {
+ lbcd lower_vhf[2];
+ lbcd upper_vhf[2];
+ lbcd lower_uhf[2];
+ lbcd upper_uhf[2];
+} limits;
+
+struct vfosettings {
+ lbcd freq[4];
+ u8 rxtone;
+ u8 unknown1;
+ lbcd offset[3];
+ u8 txtone;
+ u8 power:1,
+ bandwidth:1,
+ unknown2:4,
+ duplex:2;
+ u8 step;
+ u8 unknown3[4];
+};
+
+#seekto 0x0790;
+struct {
+ struct vfosettings uhf;
+ struct vfosettings vhf;
+} vfo;
+
+#seekto 0x07C2;
+struct {
+ u8 squelch;
+ u8 vox;
+ u8 timeout;
+ u8 save:1,
+ unknown_1:1,
+ dw:1,
+ ste:1,
+ beep:1,
+ unknown_2:1,
+ bclo:1,
+ ch_flag:1;
+ u8 backlight:2,
+ relaym:1,
+ scanm:1,
+ pri:1,
+ unknown_3:3;
+ u8 unknown_4[3];
+ u8 pri_ch;
+} settings;
+
+#seekto 0x07E0;
+u16 fm_presets[16];
+
#seekto 0x0810;
struct {
lbcd rx_freq[4];
@@ -113,6 +171,18 @@ struct {
} names[128];
"""
+STEPS = [5.0, 6.25, 10.0, 12.5, 20.0, 25.0]
+STEP_LIST = [str(x) for x in STEPS]
+BACKLIGHT_LIST = ["Off", "Key", "Continuous"]
+TIMEOUT_LIST = ["Off"] + ["%s sec" % x for x in range(30, 210, 30)]
+SCANM_LIST = ["TO", "CO"]
+PRI_CH_LIST = ["Off"] + ["%s" % x for x in range(1, 100)]
+CH_FLAG_LIST = ["Freq Mode", "Channel Mode"]
+POWER_LIST = ["Low", "High"]
+BANDWIDTH_LIST = ["Narrow", "Wide"]
+DUPLEX_LIST = ["Off", "-", "+"]
+STE_LIST = ["On", "Off"]
+
UV3R_DUPLEX = ["", "-", "+", ""]
UV3R_POWER_LEVELS = [chirp_common.PowerLevel("High", watts=2.00),
chirp_common.PowerLevel("Low", watts=0.50)]
@@ -126,10 +196,11 @@ class UV3RRadio(chirp_common.CloneModeRadio):
def get_features(self):
rf = chirp_common.RadioFeatures()
+ rf.has_settings = True
rf.valid_tmodes = ["", "Tone", "TSQL", "DTCS", "Cross"]
rf.valid_modes = ["FM", "NFM"]
rf.valid_power_levels = UV3R_POWER_LEVELS
- rf.valid_bands = [(136000000, 174000000), (400000000, 470000000)]
+ rf.valid_bands = [(136000000, 235000000), (400000000, 529000000)]
rf.valid_skips = []
rf.valid_duplexes = ["", "-", "+", "split"]
rf.valid_cross_modes = ["Tone->Tone", "Tone->DTCS", "DTCS->Tone",
@@ -282,6 +353,276 @@ class UV3RRadio(chirp_common.CloneModeRadio):
self._set_memory(mem, _tmem)
self._set_memory(mem, _rmem)
+ def get_settings(self):
+ _settings = self._memobj.settings
+ _vfo = self._memobj.vfo
+ basic = RadioSettingGroup("basic", "Basic Settings")
+ group = RadioSettingGroup("top", "All Settings", basic)
+
+ rs = RadioSetting("squelch", "Squelch Level",
+ RadioSettingValueInteger(0, 9, _settings.squelch))
+ basic.append(rs)
+
+ rs = RadioSetting("backlight", "LCD Back Light",
+ RadioSettingValueList(BACKLIGHT_LIST,
+ BACKLIGHT_LIST[_settings.backlight]))
+ basic.append(rs)
+
+ rs = RadioSetting("beep", "Keypad Beep",
+ RadioSettingValueBoolean(_settings.beep))
+ basic.append(rs)
+
+ rs = RadioSetting("vox", "VOX Level (0=OFF)",
+ RadioSettingValueInteger(0, 9, _settings.vox))
+ basic.append(rs)
+
+ rs = RadioSetting("dw", "Dual Watch",
+ RadioSettingValueBoolean(_settings.dw))
+ basic.append(rs)
+
+ rs = RadioSetting("ste", "Squelch Tail Eliminate",
+ RadioSettingValueList(STE_LIST,
+ STE_LIST[_settings.ste]))
+ basic.append(rs)
+
+ rs = RadioSetting("save", "Battery Saver",
+ RadioSettingValueBoolean(_settings.save))
+ basic.append(rs)
+
+ rs = RadioSetting("timeout", "Time Out Timer",
+ RadioSettingValueList(TIMEOUT_LIST,
+ TIMEOUT_LIST[_settings.timeout]))
+ basic.append(rs)
+
+ rs = RadioSetting("scanm", "Scan Mode",
+ RadioSettingValueList(SCANM_LIST,
+ SCANM_LIST[_settings.scanm]))
+ basic.append(rs)
+
+ rs = RadioSetting("relaym", "Repeater Sound Response",
+ RadioSettingValueBoolean(_settings.relaym))
+ basic.append(rs)
+
+ rs = RadioSetting("bclo", "Busy Channel Lock Out",
+ RadioSettingValueBoolean(_settings.bclo))
+ basic.append(rs)
+
+ rs = RadioSetting("pri", "Priority Channel Scanning",
+ RadioSettingValueBoolean(_settings.pri))
+ basic.append(rs)
+
+ rs = RadioSetting("pri_ch", "Priority Channel",
+ RadioSettingValueList(PRI_CH_LIST,
+ PRI_CH_LIST[_settings.pri_ch]))
+ basic.append(rs)
+
+ rs = RadioSetting("ch_flag", "Display Mode",
+ RadioSettingValueList(CH_FLAG_LIST,
+ CH_FLAG_LIST[_settings.ch_flag]))
+ basic.append(rs)
+
+ _limit = int(self._memobj.limits.lower_vhf) / 10
+ rs = RadioSetting("limits.lower_vhf", "VHF Lower Limit (115-239 MHz)",
+ RadioSettingValueInteger(115, 235, _limit))
+ def apply_limit(setting, obj):
+ value = int(setting.value) * 10
+ obj.lower_vhf = value
+ rs.set_apply_callback(apply_limit, self._memobj.limits)
+ basic.append(rs)
+
+ _limit = int(self._memobj.limits.upper_vhf) / 10
+ rs = RadioSetting("limits.upper_vhf", "VHF Upper Limit (115-239 MHz)",
+ RadioSettingValueInteger(115, 235, _limit))
+ def apply_limit(setting, obj):
+ value = int(setting.value) * 10
+ obj.upper_vhf = value
+ rs.set_apply_callback(apply_limit, self._memobj.limits)
+ basic.append(rs)
+
+ _limit = int(self._memobj.limits.lower_uhf) / 10
+ rs = RadioSetting("limits.lower_uhf", "UHF Lower Limit (200-529 MHz)",
+ RadioSettingValueInteger(200, 529, _limit))
+ def apply_limit(setting, obj):
+ value = int(setting.value) * 10
+ obj.lower_uhf = value
+ rs.set_apply_callback(apply_limit, self._memobj.limits)
+ basic.append(rs)
+
+ _limit = int(self._memobj.limits.upper_uhf) / 10
+ rs = RadioSetting("limits.upper_uhf", "UHF Upper Limit (200-529 MHz)",
+ RadioSettingValueInteger(200, 529, _limit))
+ def apply_limit(setting, obj):
+ value = int(setting.value) * 10
+ obj.upper_uhf = value
+ rs.set_apply_callback(apply_limit, self._memobj.limits)
+ basic.append(rs)
+
+ vfo_preset = RadioSettingGroup("vfo_preset", "VFO Presets")
+ group.append(vfo_preset)
+
+ def convert_bytes_to_freq(bytes):
+ real_freq = 0
+ real_freq = bytes
+ return chirp_common.format_freq(real_freq * 10)
+
+ def apply_vhf_freq(setting, obj):
+ value = chirp_common.parse_freq(str(setting.value)) / 10
+ obj.vhf.freq = value
+
+ val = RadioSettingValueString(0, 10,
+ convert_bytes_to_freq(int(_vfo.vhf.freq)))
+ rs = RadioSetting("vfo.vhf.freq",
+ "VHF RX Frequency (115.00000-236.00000)", val)
+ rs.set_apply_callback(apply_vhf_freq, _vfo)
+ vfo_preset.append(rs)
+
+ rs = RadioSetting("vfo.vhf.duplex", "Shift Direction",
+ RadioSettingValueList(DUPLEX_LIST,
+ DUPLEX_LIST[_vfo.vhf.duplex]))
+ vfo_preset.append(rs)
+
+ def convert_bytes_to_offset(bytes):
+ real_offset = 0
+ real_offset = bytes
+ return chirp_common.format_freq(real_offset * 10000)
+
+ def apply_vhf_offset(setting, obj):
+ value = chirp_common.parse_freq(str(setting.value)) / 10000
+ obj.vhf.offset = value
+
+ val = RadioSettingValueString(0, 10,
+ convert_bytes_to_offset(int(_vfo.vhf.offset)))
+ rs = RadioSetting("vfo.vhf.offset", "Offset (0.00-37.995)", val)
+ rs.set_apply_callback(apply_vhf_offset, _vfo)
+ vfo_preset.append(rs)
+
+ rs = RadioSetting("vfo.vhf.power", "Power Level",
+ RadioSettingValueList(POWER_LIST,
+ POWER_LIST[_vfo.vhf.power]))
+ vfo_preset.append(rs)
+
+ rs = RadioSetting("vfo.vhf.bandwidth", "Bandwidth",
+ RadioSettingValueList(BANDWIDTH_LIST,
+ BANDWIDTH_LIST[_vfo.vhf.bandwidth]))
+ vfo_preset.append(rs)
+
+ rs = RadioSetting("vfo.vhf.step", "Step",
+ RadioSettingValueList(STEP_LIST,
+ STEP_LIST[_vfo.vhf.step]))
+ vfo_preset.append(rs)
+
+ def apply_uhf_freq(setting, obj):
+ value = chirp_common.parse_freq(str(setting.value)) / 10
+ obj.uhf.freq = value
+
+ val = RadioSettingValueString(0, 10,
+ convert_bytes_to_freq(int(_vfo.uhf.freq)))
+ rs = RadioSetting("vfo.uhf.freq",
+ "UHF RX Frequency (200.00000-529.00000)", val)
+ rs.set_apply_callback(apply_uhf_freq, _vfo)
+ vfo_preset.append(rs)
+
+ rs = RadioSetting("vfo.uhf.duplex", "Shift Direction",
+ RadioSettingValueList(DUPLEX_LIST,
+ DUPLEX_LIST[_vfo.uhf.duplex]))
+ vfo_preset.append(rs)
+
+ def apply_uhf_offset(setting, obj):
+ value = chirp_common.parse_freq(str(setting.value)) / 10000
+ obj.uhf.offset = value
+
+ val = RadioSettingValueString(0, 10,
+ convert_bytes_to_offset(int(_vfo.uhf.offset)))
+ rs = RadioSetting("vfo.uhf.offset", "Offset (0.00-69.995)", val)
+ rs.set_apply_callback(apply_uhf_offset, _vfo)
+ vfo_preset.append(rs)
+
+ rs = RadioSetting("vfo.uhf.power", "Power Level",
+ RadioSettingValueList(POWER_LIST,
+ POWER_LIST[_vfo.uhf.power]))
+ vfo_preset.append(rs)
+
+ rs = RadioSetting("vfo.uhf.bandwidth", "Bandwidth",
+ RadioSettingValueList(BANDWIDTH_LIST,
+ BANDWIDTH_LIST[_vfo.uhf.bandwidth]))
+ vfo_preset.append(rs)
+
+ rs = RadioSetting("vfo.uhf.step", "Step",
+ RadioSettingValueList(STEP_LIST,
+ STEP_LIST[_vfo.uhf.step]))
+ vfo_preset.append(rs)
+
+ fm_preset = RadioSettingGroup("fm_preset", "FM Radio Presets")
+ group.append(fm_preset)
+
+ for i in range(0, 16):
+ if self._memobj.fm_presets[i] < 0x01AF:
+ used = True
+ preset = self._memobj.fm_presets[i] / 10.0 + 65
+ else:
+ used = False
+ preset = 65
+ rs = RadioSetting("fm_presets_%1i" % i, "FM Preset %i" % (i + 1),
+ RadioSettingValueBoolean(used),
+ RadioSettingValueFloat(65, 108, preset, 0.1, 1))
+ fm_preset.append(rs)
+
+ return group
+
+ def set_settings(self, settings):
+ _settings = self._memobj.settings
+ 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():
+ print "Using apply callback"
+ element.run_apply_callback()
+ else:
+ print "Setting %s = %s" % (setting, element.value)
+ setattr(obj, setting, element.value)
+ except Exception, e:
+ print element.get_name()
+ raise
+
+ def _set_fm_preset(self, settings):
+ for element in settings:
+ try:
+ index = (int(element.get_name().split("_")[-1]))
+ val = element.value
+ if val[0].get_value():
+ value = int(val[1].get_value() * 10 - 650)
+ else:
+ value = 0x01AF
+ print "Setting fm_presets[%1i] = %s" % (index, value)
+ setting = self._memobj.fm_presets
+ setting[index] = value
+ except Exception, e:
+ print element.get_name()
+ raise
+
+
@classmethod
def match_model(cls, filedata, filename):
return len(filedata) == 3648
diff --git a/chirp/bitwise.py b/chirp/bitwise.py
index 3f91c8a..149c588 100644
--- a/chirp/bitwise.py
+++ b/chirp/bitwise.py
@@ -17,6 +17,7 @@
#
# Example definitions:
#
+# bit foo[8]; /* Eight single bit values */
# u8 foo; /* Unsigned 8-bit value */
# u16 foo; /* Unsigned 16-bit value */
# ul16 foo; /* Unsigned 16-bit value (LE) */
@@ -24,6 +25,13 @@
# ul24 foo; /* Unsigned 24-bit value (LE) */
# u32 foo; /* Unsigned 32-bit value */
# ul32 foo; /* Unsigned 32-bit value (LE) */
+# i8 foo; /* Signed 8-bit value */
+# i16 foo; /* Signed 16-bit value */
+# il16 foo; /* Signed 16-bit value (LE) */
+# i24 foo; /* Signed 24-bit value */
+# il24 foo; /* Signed 24-bit value (LE) */
+# i32 foo; /* Signed 32-bit value */
+# il32 foo; /* Signed 32-bit value (LE) */
# char foo; /* Character (single-byte */
# lbcd foo; /* BCD-encoded byte (LE) */
# bbcd foo; /* BCD-encoded byte (BE) */
@@ -157,7 +165,7 @@ class arrayDataElement(DataElement):
return "%i:[(%i)]" % (len(self.__items), int(self))
if isinstance(self.__items[0], charDataElement):
- return "%i:[(%s)]" % (len(self.__items), str(self))
+ return "%i:[(%s)]" % (len(self.__items), repr(str(self))[1:-1])
s = "%i:[" % len(self.__items)
s += ",".join([repr(item) for item in self.__items])
@@ -193,18 +201,16 @@ class arrayDataElement(DataElement):
return str(self.__items)
def __int__(self):
- if isinstance(self.__items[0], bbcdDataElement):
+ if isinstance(self.__items[0], bcdDataElement):
val = 0
- for i in self.__items:
+ if isinstance(self.__items[0], bbcdDataElement):
+ items = self.__items
+ else:
+ items = reversed(self.__items)
+ for i in items:
tens, ones = i.get_value()
val = (val * 100) + (tens * 10) + ones
return val
- elif isinstance(self.__items[0], lbcdDataElement):
- val = 0
- for i in reversed(self.__items):
- ones, tens = i.get_value()
- val = (val * 100) + (tens * 10) + ones
- return val
else:
raise ValueError("Cannot coerce this to int")
@@ -221,6 +227,9 @@ class arrayDataElement(DataElement):
i.set_value(twodigits)
def __set_value_char(self, value):
+ if len(value) != len(self.__items):
+ raise ValueError("String expects exactly %i characters" %
+ len(self.__items))
for i in range(0, len(self.__items)):
self.__items[i].set_value(value[i])
@@ -248,6 +257,12 @@ class arrayDataElement(DataElement):
def __iter__(self):
return iter(self.__items)
+ def items(self):
+ index = 0
+ for item in self.__items:
+ yield (str(index), item)
+ index += 1
+
def size(self):
size = 0
for i in self.__items:
@@ -393,7 +408,7 @@ class u16DataElement(intDataElement):
class ul16DataElement(u16DataElement):
_endianess = "<"
-
+
class u24DataElement(intDataElement):
_size = 3
_endianess = ">"
@@ -430,6 +445,65 @@ class u32DataElement(intDataElement):
class ul32DataElement(u32DataElement):
_endianess = "<"
+class i8DataElement(u8DataElement):
+ _size = 1
+
+ def _get_value(self, data):
+ return struct.unpack("b", data)[0]
+
+ def set_value(self, value):
+ self._data[self._offset] = struct.pack("b", int(value) )
+
+class i16DataElement(intDataElement):
+ _size = 2
+ _endianess = ">"
+
+ def _get_value(self, data):
+ return struct.unpack(self._endianess + "h", data)[0]
+
+ def set_value(self, value):
+ self._data[self._offset] = struct.pack(self._endianess + "h",
+ int(value) )
+
+class il16DataElement(i16DataElement):
+ _endianess = "<"
+
+class i24DataElement(intDataElement):
+ _size = 3
+ _endianess = ">"
+
+ def _get_value(self, data):
+ pre = self._endianess == ">" and "\x00" or ""
+ post = self._endianess == "<" and "\x00" or ""
+ return struct.unpack(self._endianess + "i", pre+data+post)[0]
+
+ def set_value(self, value):
+ if self._endianess == "<":
+ start = 0
+ end = 3
+ else:
+ start = 1
+ end = 4
+ self._data[self._offset] = struct.pack(self._endianess + "i",
+ int(value) )[start:end]
+
+class il24DataElement(i24DataElement):
+ _endianess = "<"
+
+class i32DataElement(intDataElement):
+ _size = 4
+ _endianess = ">"
+
+ def _get_value(self, data):
+ return struct.unpack(self._endianess + "i", data)[0]
+
+ def set_value(self, value):
+ self._data[self._offset] = struct.pack(self._endianess + "i",
+ int(value) )
+
+class il32DataElement(i32DataElement):
+ _endianess = "<"
+
class charDataElement(DataElement):
_size = 1
@@ -468,29 +542,20 @@ class bcdDataElement(DataElement):
raise TypeError("Unable to set bcdDataElement from type %s" %
type(data))
-class lbcdDataElement(bcdDataElement):
- _size = 1
+ def set_value(self, value):
+ self._data[self._offset] = int("%02i" % value, 16)
def _get_value(self, data):
a = (ord(data) & 0xF0) >> 4
b = ord(data) & 0x0F
- return (b, a)
+ return (a, b)
- def set_value(self, value):
- value = int("%02i" % value, 16)
- self._data[self._offset] = value
+class lbcdDataElement(bcdDataElement):
+ _size = 1
class bbcdDataElement(bcdDataElement):
_size = 1
- def _get_value(self, data):
- a = (ord(data) & 0xF0) >> 4
- b = ord(data) & 0x0F
- return (a, b)
-
- def set_value(self, value):
- self._data[self._offset] = int("%02i" % value, 16)
-
class bitDataElement(intDataElement):
_nbits = 0
_shift = 0
@@ -612,6 +677,14 @@ class structDataElement(DataElement):
raise ValueError("Struct size mismatch during set_raw()")
self._data[self._offset] = buffer
+ def __iter__(self):
+ for item in self._generators.values():
+ yield item
+
+ def items(self):
+ for key in self._keys:
+ yield key, self._generators[key]
+
class Processor:
_types = {
@@ -622,6 +695,12 @@ class Processor:
"ul24" : ul24DataElement,
"u32" : u32DataElement,
"ul32" : ul32DataElement,
+ "i8" : i8DataElement,
+ "i16" : i16DataElement,
+ "il16" : il16DataElement,
+ "i24" : i24DataElement,
+ "il24" : il24DataElement,
+ "i32" : i32DataElement,
"char" : charDataElement,
"lbcd" : lbcdDataElement,
"bbcd" : bbcdDataElement,
@@ -661,6 +740,16 @@ class Processor:
return bytes
+ def do_bitarray(self, i, count):
+ if count % 8 != 0:
+ raise ValueError("bit array must be divisible by 8.")
+
+ class bitDE(bitDataElement):
+ _nbits = 1
+ _shift = 8 - i % 8
+
+ return bitDE(self._data, self._offset)
+
def parse_defn(self, defn):
dtype = defn[0]
@@ -680,9 +769,13 @@ class Processor:
res = arrayDataElement(self._offset)
size = 0
for i in range(0, count):
- gen = self._types[dtype](self._data, self._offset)
+ if dtype == "bit":
+ gen = self.do_bitarray(i, count)
+ self._offset += int((i+1) % 8 == 0)
+ else:
+ gen = self._types[dtype](self._data, self._offset)
+ self._offset += (gen.size() / 8)
res.append(gen)
- self._offset += (gen.size() / 8)
if count == 1:
self._generators[name] = res[0]
@@ -732,20 +825,14 @@ class Processor:
def parse_directive(self, directive):
name = directive[0][0]
+ value = directive[0][1][0][1]
if name == "seekto":
- offset = directive[0][1][0][1]
- if offset.startswith("0x"):
- offset = int(offset[2:], 16)
- else:
- offset = int(offset)
#print "NOTICE: Setting offset to %i (0x%X)" % (offset, offset)
- self._offset = offset
+ self._offset = int(value, 0)
elif name == "seek":
- offset = int(directive[0][1][0][1])
- self._offset += offset
+ self._offset += int(value, 0)
elif name == "printoffset":
- string = directive[0][1][0][1]
- print "%s: %i (0x%08X)" % (string[1:-1], self._offset, self._offset)
+ print "%s: %i (0x%08X)" % (value[1:-1], self._offset, self._offset)
def parse_block(self, lang):
for t, d in lang:
@@ -760,7 +847,7 @@ class Processor:
def parse(self, lang):
self._generators = structDataElement(self._data, self._offset)
- self.parse_block(lang[0])
+ self.parse_block(lang)
return self._generators
diff --git a/chirp/bitwise_grammar.py b/chirp/bitwise_grammar.py
index f311410..fe81b42 100644
--- a/chirp/bitwise_grammar.py
+++ b/chirp/bitwise_grammar.py
@@ -14,9 +14,10 @@
# along with this program. If not, see <http://www.gnu.org/licenses/>.
import re
-from chirp.pyPEG import keyword, parseLine
+from chirp.pyPEG import keyword, parse as pypeg_parse
-TYPES = ["u8", "u16", "ul16", "u24", "ul24", "u32", "ul32", "char",
+TYPES = ["bit", "u8", "u16", "ul16", "u24", "ul24", "u32", "ul32",
+ "i8", "i16", "il16", "i24", "il24", "i32", "il32", "char",
"lbcd", "bbcd"]
DIRECTIVES = ["seekto", "seek", "printoffset"]
@@ -78,4 +79,33 @@ def _language():
return _block_inner
def parse(data):
- return parseLine(data, _language, resultSoFar=[])
+ lines = data.split("\n")
+ for index, line in enumerate(lines):
+ if '//' in line:
+ lines[index] = line[:line.index('//')]
+
+ class FakeFileInput:
+ """Simulate line-by-line file reading from @data"""
+ line = -1
+
+ def isfirstline(self):
+ return self.line == 0
+
+ def filename(self):
+ return "input"
+
+ def lineno(self):
+ return self.line
+
+ def __iter__(self):
+ return self
+
+ def next(self):
+ self.line += 1
+ try:
+ # Note, FileInput objects keep the newlines
+ return lines[self.line] + "\n"
+ except IndexError:
+ raise StopIteration
+
+ return pypeg_parse(_language, FakeFileInput())
diff --git a/chirp/bjuv55.py b/chirp/bjuv55.py
new file mode 100644
index 0000000..4f7f5ad
--- /dev/null
+++ b/chirp/bjuv55.py
@@ -0,0 +1,647 @@
+# Copyright 2013 Jens Jensen AF5MI <kd4tjx at yahoo.com>
+# Based on work by Jim Unroe, Dan Smith, et al.
+# Special thanks to Mats SM0BTP for equipment donation.
+#
+# 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 struct
+import time
+import os
+
+from chirp import chirp_common, errors, util, directory, memmap
+from chirp import bitwise
+from chirp.settings import RadioSetting, RadioSettingGroup, \
+ RadioSettingValueInteger, RadioSettingValueList, \
+ RadioSettingValueBoolean, RadioSettingValueString, \
+ RadioSettingValueFloat, InvalidValueError
+from textwrap import dedent
+from chirp import uv5r
+
+if os.getenv("CHIRP_DEBUG"):
+ CHIRP_DEBUG = True
+else:
+ CHIRP_DEBUG = False
+
+BJUV55_MODEL = "\x50\xBB\xDD\x55\x63\x98\x4D"
+
+COLOR_LIST = ["Off", "Blue", "Red", "Pink"]
+
+STEPS = uv5r.STEPS
+STEPS.remove(2.5)
+STEP_LIST = [str(x) for x in STEPS]
+
+MEM_FORMAT = """
+#seekto 0x0008;
+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:4,
+ lowpower:1;
+ u8 unknown3:1,
+ wide:1,
+ unknown4:2,
+ bcl:1,
+ scan:1,
+ pttid:2;
+} memory[128];
+
+#seekto 0x0B08;
+struct {
+ u8 code[5];
+ u8 unused[11];
+} pttid[15];
+
+#seekto 0x0C88;
+struct {
+ u8 inspection[5];
+ u8 monitor[5];
+ u8 alarmcode[5];
+ u8 unknown1;
+ u8 stun[5];
+ u8 kill[5];
+ u8 revive[5];
+ u8 unknown2;
+ u8 master_control_id[5];
+ u8 vice_control_id[5];
+ u8 code[5];
+ u8 unused1:6,
+ aniid:2;
+ u8 unknown[2];
+ u8 dtmfon;
+ u8 dtmfoff;
+} ani;
+
+#seekto 0x0E28;
+struct {
+ u8 squelch;
+ u8 step;
+ u8 tdrab;
+ u8 tdr;
+ u8 vox;
+ u8 timeout;
+ u8 unk2[6];
+ u8 abr;
+ u8 beep;
+ u8 ani;
+ u8 unknown3[2];
+ u8 voice;
+ u8 ring_time;
+ 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 unknown7[5];
+ u8 save;
+ u8 unknown8;
+ u8 displayab:1,
+ unknown1:2,
+ fmradio:1,
+ alarm:1,
+ unknown2:1,
+ reset:1,
+ menu:1;
+ u8 vfomrlock;
+ u8 workmode;
+ u8 keylock;
+ u8 workmode_channel;
+ u8 password[6];
+ u8 unknown10[11];
+} settings;
+
+#seekto 0x0E7E;
+struct {
+ u8 mrcha;
+ u8 mrchb;
+} wmchannel;
+
+#seekto 0x0F10;
+struct {
+ 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:6;
+} vfoa;
+
+#seekto 0x0F30;
+struct {
+ 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:6;
+} vfob;
+
+#seekto 0x0F57;
+u8 fm_preset;
+
+#seekto 0x1008;
+struct {
+ char name[6];
+ u8 unknown2[10];
+} names[128];
+
+#seekto 0x%04X;
+struct {
+ char line1[7];
+ char line2[7];
+} poweron_msg;
+
+#seekto 0x1838;
+struct {
+ char line1[7];
+ char line2[7];
+} firmware_msg;
+
+#seekto 0x1849;
+u8 power_vhf_hi[14]; // 136-174 MHz, 3 MHz divisions
+u8 power_uhf_hi[14]; // 400-470 MHz, 5 MHz divisions
+#seekto 0x1889;
+u8 power_vhf_lo[14];
+u8 power_uhf_lo[14];
+
+struct limit {
+ u8 enable;
+ bbcd lower[2];
+ bbcd upper[2];
+};
+
+#seekto 0x1908;
+struct {
+ struct limit vhf;
+ u8 unk11[11];
+ struct limit uhf;
+} limits;
+
+"""
+
+ at directory.register
+class BaojieBJUV55Radio(uv5r.BaofengUV5R):
+ VENDOR = "Baojie"
+ MODEL = "BJ-UV55"
+ #_basetype = "BJ55"
+ _basetype = ["BJ55"]
+ _idents = [ BJUV55_MODEL ]
+ _mem_params = ( 0x1928 # poweron_msg offset
+ )
+ _fw_ver_file_start = 0x1938
+ _fw_ver_file_stop = 0x193E
+
+ def get_features(self):
+ rf = super(BaojieBJUV55Radio, self).get_features()
+ rf.valid_name_length = 6
+ return rf
+
+ def process_mmap(self):
+ self._memobj = bitwise.parse(MEM_FORMAT % self._mem_params, self._mmap)
+
+ def set_memory(self, mem):
+ super(BaojieBJUV55Radio, self).set_memory(mem)
+ _mem = self._memobj.memory[mem.number]
+ if (mem.freq - mem.offset) > (400 * 1000000):
+ _mem.isuhf = True
+ else:
+ _mem.isuhf = False
+ if mem.tmode in ["Tone", "TSQL"]:
+ _mem.txtoneicon = True
+ else:
+ _mem.txtoneicon = False
+
+
+ def _get_settings(self):
+ _settings = self._memobj.settings
+ basic = RadioSettingGroup("basic", "Basic Settings")
+ advanced = RadioSettingGroup("advanced", "Advanced Settings")
+ group = RadioSettingGroup("top", "All Settings", basic, advanced)
+
+ rs = RadioSetting("squelch", "Carrier Squelch Level",
+ RadioSettingValueInteger(0, 9, _settings.squelch))
+ basic.append(rs)
+
+ rs = RadioSetting("save", "Battery Saver",
+ RadioSettingValueInteger(0, 4, _settings.save))
+ basic.append(rs)
+
+ rs = RadioSetting("abr", "Backlight",
+ RadioSettingValueBoolean(_settings.abr))
+ basic.append(rs)
+
+ rs = RadioSetting("tdr", "Dual Watch (BDR)",
+ RadioSettingValueBoolean(_settings.tdr))
+ advanced.append(rs)
+
+ rs = RadioSetting("tdrab", "Dual Watch TX Priority",
+ RadioSettingValueList(uv5r.TDRAB_LIST,
+ uv5r.TDRAB_LIST[_settings.tdrab]))
+ advanced.append(rs)
+
+ rs = RadioSetting("alarm", "Alarm",
+ RadioSettingValueBoolean(_settings.alarm))
+ advanced.append(rs)
+
+ rs = RadioSetting("beep", "Beep",
+ RadioSettingValueBoolean(_settings.beep))
+ basic.append(rs)
+
+ rs = RadioSetting("timeout", "Timeout Timer",
+ RadioSettingValueList(uv5r.TIMEOUT_LIST,
+ uv5r.TIMEOUT_LIST[_settings.timeout]))
+ basic.append(rs)
+
+ rs = RadioSetting("screv", "Scan Resume",
+ RadioSettingValueList(uv5r.RESUME_LIST,
+ uv5r.RESUME_LIST[_settings.screv]))
+ advanced.append(rs)
+
+ rs = RadioSetting("mdfa", "Display Mode (A)",
+ RadioSettingValueList(uv5r.MODE_LIST,
+ uv5r.MODE_LIST[_settings.mdfa]))
+ basic.append(rs)
+
+ rs = RadioSetting("mdfb", "Display Mode (B)",
+ RadioSettingValueList(uv5r.MODE_LIST,
+ uv5r.MODE_LIST[_settings.mdfb]))
+ basic.append(rs)
+
+ rs = RadioSetting("bcl", "Busy Channel Lockout",
+ RadioSettingValueBoolean(_settings.bcl))
+ advanced.append(rs)
+
+ rs = RadioSetting("autolk", "Automatic Key Lock",
+ RadioSettingValueBoolean(_settings.autolk))
+ advanced.append(rs)
+
+ rs = RadioSetting("fmradio", "Broadcast FM Radio",
+ RadioSettingValueBoolean(_settings.fmradio))
+ advanced.append(rs)
+
+ rs = RadioSetting("wtled", "Standby LED Color",
+ RadioSettingValueList(COLOR_LIST,
+ COLOR_LIST[_settings.wtled]))
+ basic.append(rs)
+
+ rs = RadioSetting("rxled", "RX LED Color",
+ RadioSettingValueList(COLOR_LIST,
+ COLOR_LIST[_settings.rxled]))
+ basic.append(rs)
+
+ rs = RadioSetting("txled", "TX LED Color",
+ RadioSettingValueList(COLOR_LIST,
+ COLOR_LIST[_settings.txled]))
+ basic.append(rs)
+
+ rs = RadioSetting("reset", "RESET Menu",
+ RadioSettingValueBoolean(_settings.reset))
+ advanced.append(rs)
+
+ rs = RadioSetting("menu", "All Menus",
+ RadioSettingValueBoolean(_settings.menu))
+ advanced.append(rs)
+
+
+ if len(self._mmap.get_packed()) == 0x1808:
+ # Old image, without aux block
+ return group
+
+ other = RadioSettingGroup("other", "Other Settings")
+ group.append(other)
+
+ def _filter(name):
+ filtered = ""
+ for char in str(name):
+ if char in chirp_common.CHARSET_ASCII:
+ filtered += char
+ else:
+ filtered += " "
+ return filtered
+
+ _msg = self._memobj.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)
+
+ limit = "limits"
+ vhf_limit = getattr(self._memobj, limit).vhf
+ rs = RadioSetting("%s.vhf.lower" % limit, "VHF Lower Limit (MHz)",
+ RadioSettingValueInteger(1, 1000,
+ vhf_limit.lower))
+ other.append(rs)
+
+ rs = RadioSetting("%s.vhf.upper" % limit, "VHF Upper Limit (MHz)",
+ RadioSettingValueInteger(1, 1000,
+ vhf_limit.upper))
+ other.append(rs)
+
+ rs = RadioSetting("%s.vhf.enable" % limit, "VHF TX Enabled",
+ RadioSettingValueBoolean(vhf_limit.enable))
+ other.append(rs)
+
+ uhf_limit = getattr(self._memobj, limit).uhf
+ rs = RadioSetting("%s.uhf.lower" % limit, "UHF Lower Limit (MHz)",
+ RadioSettingValueInteger(1, 1000,
+ uhf_limit.lower))
+ other.append(rs)
+ rs = RadioSetting("%s.uhf.upper" % limit, "UHF Upper Limit (MHz)",
+ RadioSettingValueInteger(1, 1000,
+ uhf_limit.upper))
+ other.append(rs)
+ rs = RadioSetting("%s.uhf.enable" % limit, "UHF TX Enabled",
+ RadioSettingValueBoolean(uhf_limit.enable))
+ other.append(rs)
+
+ workmode = RadioSettingGroup("workmode", "Work Mode Settings")
+ group.append(workmode)
+
+ options = ["A", "B"]
+ rs = RadioSetting("displayab", "Display Selected",
+ RadioSettingValueList(options,
+ options[_settings.displayab]))
+ workmode.append(rs)
+
+ options = ["Frequency", "Channel"]
+ rs = RadioSetting("workmode", "VFO/MR Mode",
+ RadioSettingValueList(options,
+ options[_settings.workmode]))
+ workmode.append(rs)
+
+ rs = RadioSetting("keylock", "Keypad Lock",
+ RadioSettingValueBoolean(_settings.keylock))
+ workmode.append(rs)
+
+ _mrcna = self._memobj.wmchannel.mrcha
+ rs = RadioSetting("wmchannel.mrcha", "MR A Channel",
+ RadioSettingValueInteger(0, 127, _mrcna))
+ workmode.append(rs)
+
+ _mrcnb = self._memobj.wmchannel.mrchb
+ rs = RadioSetting("wmchannel.mrchb", "MR B Channel",
+ RadioSettingValueInteger(0, 127, _mrcnb))
+ workmode.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)
+ if 17400000 <= value and value < 40000000:
+ raise InvalidValueError("Can't be between 174.00000-400.00000")
+ return chirp_common.format_freq(value)
+
+ def apply_freq(setting, obj):
+ value = chirp_common.parse_freq(str(setting.value)) / 10
+ obj.band = value >= 40000000
+ for i in range(7, -1, -1):
+ obj.freq[i] = value % 10
+ value /= 10
+
+ val1a = RadioSettingValueString(0, 10,
+ convert_bytes_to_freq(self._memobj.vfoa.freq))
+ val1a.set_validate_callback(my_validate)
+ rs = RadioSetting("vfoa.freq", "VFO A Frequency", val1a)
+ rs.set_apply_callback(apply_freq, self._memobj.vfoa)
+ workmode.append(rs)
+
+ val1b = RadioSettingValueString(0, 10,
+ convert_bytes_to_freq(self._memobj.vfob.freq))
+ val1b.set_validate_callback(my_validate)
+ rs = RadioSetting("vfob.freq", "VFO B Frequency", val1b)
+ rs.set_apply_callback(apply_freq, self._memobj.vfob)
+ workmode.append(rs)
+
+ options = ["Off", "+", "-"]
+ rs = RadioSetting("vfoa.sftd", "VFO A Shift",
+ RadioSettingValueList(options,
+ options[self._memobj.vfoa.sftd]))
+ workmode.append(rs)
+
+ rs = RadioSetting("vfob.sftd", "VFO B Shift",
+ RadioSettingValueList(options,
+ options[self._memobj.vfob.sftd]))
+ workmode.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 * 10000)
+
+ def apply_offset(setting, obj):
+ value = chirp_common.parse_freq(str(setting.value)) / 10000
+ for i in range(3, -1, -1):
+ obj.offset[i] = value % 10
+ value /= 10
+
+ val1a = RadioSettingValueString(0, 10,
+ convert_bytes_to_offset(self._memobj.vfoa.offset))
+ rs = RadioSetting("vfoa.offset", "VFO A Offset (0.00-69.95)", val1a)
+ rs.set_apply_callback(apply_offset, self._memobj.vfoa)
+ workmode.append(rs)
+
+ val1b = RadioSettingValueString(0, 10,
+ convert_bytes_to_offset(self._memobj.vfob.offset))
+ rs = RadioSetting("vfob.offset", "VFO B Offset (0.00-69.95)", val1b)
+ rs.set_apply_callback(apply_offset, self._memobj.vfob)
+ workmode.append(rs)
+
+ options = ["High", "Low"]
+ rs = RadioSetting("vfoa.txpower", "VFO A Power",
+ RadioSettingValueList(options,
+ options[self._memobj.vfoa.txpower]))
+ workmode.append(rs)
+
+ rs = RadioSetting("vfob.txpower", "VFO B Power",
+ RadioSettingValueList(options,
+ options[self._memobj.vfob.txpower]))
+ workmode.append(rs)
+
+ options = ["Wide", "Narrow"]
+ rs = RadioSetting("vfoa.widenarr", "VFO A Bandwidth",
+ RadioSettingValueList(options,
+ options[self._memobj.vfoa.widenarr]))
+ workmode.append(rs)
+
+ rs = RadioSetting("vfob.widenarr", "VFO B Bandwidth",
+ RadioSettingValueList(options,
+ options[self._memobj.vfob.widenarr]))
+ workmode.append(rs)
+
+ options = ["%s" % x for x in range(1, 16)]
+ rs = RadioSetting("vfoa.scode", "VFO A PTT-ID",
+ RadioSettingValueList(options,
+ options[self._memobj.vfoa.scode]))
+ workmode.append(rs)
+
+ rs = RadioSetting("vfob.scode", "VFO B PTT-ID",
+ RadioSettingValueList(options,
+ options[self._memobj.vfob.scode]))
+ workmode.append(rs)
+
+
+ rs = RadioSetting("vfoa.step", "VFO A Tuning Step",
+ RadioSettingValueList(STEP_LIST,
+ STEP_LIST[self._memobj.vfoa.step]))
+ workmode.append(rs)
+ rs = RadioSetting("vfob.step", "VFO B Tuning Step",
+ RadioSettingValueList(STEP_LIST,
+ STEP_LIST[self._memobj.vfob.step]))
+ workmode.append(rs)
+
+
+ fm_preset = RadioSettingGroup("fm_preset", "FM Radio Preset")
+ group.append(fm_preset)
+
+ preset = self._memobj.fm_preset / 10.0 + 87
+ rs = RadioSetting("fm_preset", "FM Preset(MHz)",
+ RadioSettingValueFloat(87, 107.5, preset, 0.1, 1))
+ fm_preset.append(rs)
+
+ dtmf = RadioSettingGroup("dtmf", "DTMF Settings")
+ group.append(dtmf)
+ dtmfchars = "0123456789 *#ABCD"
+
+ for i in range(0, 15):
+ _codeobj = self._memobj.pttid[i].code
+ _code = "".join([dtmfchars[x] for x in _codeobj if int(x) < 0x1F])
+ val = RadioSettingValueString(0, 5, _code, False)
+ val.set_charset(dtmfchars)
+ rs = RadioSetting("pttid/%i.code" % i, "PTT ID Code %i" % (i + 1), val)
+ def apply_code(setting, obj):
+ code = []
+ for j in range(0, 5):
+ try:
+ code.append(dtmfchars.index(str(setting.value)[j]))
+ except IndexError:
+ code.append(0xFF)
+ obj.code = code
+ rs.set_apply_callback(apply_code, self._memobj.pttid[i])
+ dtmf.append(rs)
+
+ _codeobj = self._memobj.ani.code
+ _code = "".join([dtmfchars[x] for x in _codeobj if int(x) < 0x1F])
+ val = RadioSettingValueString(0, 5, _code, False)
+ val.set_charset(dtmfchars)
+ rs = RadioSetting("ani.code", "ANI Code", val)
+ def apply_code(setting, obj):
+ code = []
+ for j in range(0, 5):
+ try:
+ code.append(dtmfchars.index(str(setting.value)[j]))
+ except IndexError:
+ code.append(0xFF)
+ obj.code = code
+ rs.set_apply_callback(apply_code, self._memobj.ani)
+ dtmf.append(rs)
+
+ options = ["Off", "BOT", "EOT", "Both"]
+ rs = RadioSetting("ani.aniid", "ANI ID",
+ RadioSettingValueList(options,
+ options[self._memobj.ani.aniid]))
+ dtmf.append(rs)
+
+ _codeobj = self._memobj.ani.alarmcode
+ _code = "".join([dtmfchars[x] for x in _codeobj if int(x) < 0x1F])
+ val = RadioSettingValueString(0, 5, _code, False)
+ val.set_charset(dtmfchars)
+ rs = RadioSetting("ani.alarmcode", "Alarm Code", val)
+ def apply_code(setting, obj):
+ alarmcode = []
+ for j in range(5):
+ try:
+ alarmcode.append(dtmfchars.index(str(setting.value)[j]))
+ except IndexError:
+ alarmcode.append(0xFF)
+ obj.alarmcode = alarmcode
+ rs.set_apply_callback(apply_code, self._memobj.ani)
+ dtmf.append(rs)
+
+ rs = RadioSetting("dtmfst", "DTMF Sidetone",
+ RadioSettingValueList(uv5r.DTMFST_LIST,
+ uv5r.DTMFST_LIST[_settings.dtmfst]))
+ dtmf.append(rs)
+
+ rs = RadioSetting("ani.dtmfon", "DTMF Speed (on)",
+ RadioSettingValueList(uv5r.DTMFSPEED_LIST,
+ uv5r.DTMFSPEED_LIST[self._memobj.ani.dtmfon]))
+ dtmf.append(rs)
+
+ rs = RadioSetting("ani.dtmfoff", "DTMF Speed (off)",
+ RadioSettingValueList(uv5r.DTMFSPEED_LIST,
+ uv5r.DTMFSPEED_LIST[self._memobj.ani.dtmfoff]))
+ dtmf.append(rs)
+
+ return group
+
+ def _set_fm_preset(self, settings):
+ for element in settings:
+ try:
+ val = element.value
+ value = int(val.get_value() * 10 - 870)
+ print "Setting fm_preset = %s" % (value)
+ self._memobj.fm_preset = value
+ except Exception, e:
+ print element.get_name()
+ raise
diff --git a/chirp/chirp_common.py b/chirp/chirp_common.py
index f2472a0..600f4eb 100644
--- a/chirp/chirp_common.py
+++ b/chirp/chirp_common.py
@@ -32,6 +32,12 @@ 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]
+
+OLD_TONES = list(TONES)
+[OLD_TONES.remove(x) for x in [159.8, 165.5, 171.3, 177.3, 183.5, 189.9,
+ 196.6, 199.5, 206.5, 229.1, 254.1]]
+
# 104 DTCS Codes
DTCS_CODES = [
23, 25, 26, 31, 32, 36, 43, 47, 51, 53, 54,
@@ -46,74 +52,26 @@ DTCS_CODES = [
731, 732, 734, 743, 754,
]
-# Some radios have some strange codes
-DTCS_EXTRA_CODES = [ 17, 645 ]
+# 512 Possible DTCS Codes
+ALL_DTCS_CODES = []
+for a in range(0, 8):
+ for b in range(0, 8):
+ for c in range(0, 8):
+ ALL_DTCS_CODES.append((a * 100) + (b * 10) + c)
CROSS_MODES = [
"Tone->Tone",
+ "DTCS->",
+ "->DTCS",
"Tone->DTCS",
"DTCS->Tone",
- "DTCS->",
"->Tone",
- "->DTCS",
"DTCS->DTCS",
]
MODES = ["WFM", "FM", "NFM", "AM", "NAM", "DV", "USB", "LSB", "CW", "RTTY",
"DIG", "PKT", "NCW", "NCWR", "CWR", "P25", "Auto"]
-STD_6M_OFFSETS = [
- (51620000, 51980000, -500000),
- (52500000, 52980000, -500000),
- (53500000, 53980000, -500000),
- ]
-
-STD_2M_OFFSETS = [
- (145100000, 145500000, -600000),
- (146000000, 146400000, 600000),
- (146600000, 147000000, -600000),
- (147000000, 147400000, 600000),
- (147600000, 148000000, -600000),
- ]
-
-STD_220_OFFSETS = [
- (223850000, 224980000, -1600000),
- ]
-
-STD_70CM_OFFSETS = [
- (440000000, 445000000, 5000000),
- (447000000, 450000000, -5000000),
- ]
-
-STD_23CM_OFFSETS = [
- (1282000000, 1288000000, -12000000),
- ]
-
-# Standard offsets, indexed by band (wavelength in cm)
-STD_OFFSETS = {
- 600 : STD_6M_OFFSETS,
- 200 : STD_2M_OFFSETS,
- 125 : STD_220_OFFSETS,
- 70 : STD_70CM_OFFSETS,
- 23 : STD_23CM_OFFSETS,
- }
-
-BAND_TO_MHZ = {
- 600 : ( 50000000, 54000000 ),
- 200 : ( 144000000, 148000000 ),
- 125 : ( 219000000, 225000000 ),
- 70 : ( 420000000, 450000000 ),
- 23 : ( 1240000000, 1300000000 ),
-}
-
-# NB: This only works for some bands, throws an Exception otherwise
-def freq_to_band(freq):
- """Returns the band (in cm) for a given frequency"""
- for band, (lo, hi) in BAND_TO_MHZ.items():
- if int(freq) > lo and int(freq) < hi:
- return band
- raise Exception("No conversion for frequency %i" % freq)
-
TONE_MODES = [
"",
"Tone",
@@ -138,6 +96,52 @@ CHARSET_ALPHANUMERIC = \
"ABCDEFGHIJKLMNOPQRSTUVWXYZabcdefghijklmnopqrstuvwxyz 1234567890"
CHARSET_ASCII = "".join([chr(x) for x in range(ord(" "), ord("~")+1)])
+# http://aprs.org/aprs11/SSIDs.txt
+APRS_SSID = (
+ "0 Your primary station usually fixed and message capable",
+ "1 generic additional station, digi, mobile, wx, etc",
+ "2 generic additional station, digi, mobile, wx, etc",
+ "3 generic additional station, digi, mobile, wx, etc",
+ "4 generic additional station, digi, mobile, wx, etc",
+ "5 Other networks (Dstar, Iphones, Androids, Blackberry's etc)",
+ "6 Special activity, Satellite ops, camping or 6 meters, etc",
+ "7 walkie talkies, HT's or other human portable",
+ "8 boats, sailboats, RV's or second main mobile",
+ "9 Primary Mobile (usually message capable)",
+ "10 internet, Igates, echolink, winlink, AVRS, APRN, etc",
+ "11 balloons, aircraft, spacecraft, etc",
+ "12 APRStt, DTMF, RFID, devices, one-way trackers*, etc",
+ "13 Weather stations",
+ "14 Truckers or generally full time drivers",
+ "15 generic additional station, digi, mobile, wx, etc")
+APRS_POSITION_COMMENT = (
+ "off duty", "en route", "in service", "returning", "committed",
+ "special", "priority", "custom 0", "custom 1", "custom 2", "custom 3",
+ "custom 4", "custom 5", "custom 6", "EMERGENCY")
+# http://aprs.org/symbols/symbolsX.txt
+APRS_SYMBOLS = (
+ "Police/Sheriff", "[reserved]", "Digi", "Phone", "DX Cluster",
+ "HF Gateway", "Small Aircraft", "Mobile Satellite Groundstation",
+ "Wheelchair", "Snowmobile", "Red Cross", "Boy Scouts", "House QTH (VHF)",
+ "X", "Red Dot", "0 in Circle", "1 in Circle", "2 in Circle",
+ "3 in Circle", "4 in Circle", "5 in Circle", "6 in Circle", "7 in Circle",
+ "8 in Circle", "9 in Circle", "Fire", "Campground", "Motorcycle",
+ "Railroad Engine", "Car", "File Server", "Hurricane Future Prediction",
+ "Aid Station", "BBS or PBBS", "Canoe", "[reserved]", "Eyeball",
+ "Tractor/Farm Vehicle", "Grid Square", "Hotel", "TCP/IP", "[reserved]",
+ "School", "PC User", "MacAPRS", "NTS Station", "Balloon", "Police", "TBD",
+ "Recreational Vehicle", "Space Shuttle", "SSTV", "Bus", "ATV",
+ "National WX Service Site", "Helicopter", "Yacht/Sail Boat", "WinAPRS",
+ "Human/Person", "Triangle", "Mail/Postoffice", "Large Aircraft",
+ "WX Station", "Dish Antenna", "Ambulance", "Bicycle",
+ "Incident Command Post", "Dual Garage/Fire Dept", "Horse/Equestrian",
+ "Fire Truck", "Glider", "Hospital", "IOTA", "Jeep", "Truck", "Laptop",
+ "Mic-Repeater", "Node", "Emergency Operations Center", "Rover (dog)",
+ "Grid Square above 128m", "Repeater", "Ship/Power Boat", "Truck Stop",
+ "Truck (18 wheeler)", "Van", "Water Station", "X-APRS", "Yagi at QTH",
+ "TDB", "[reserved]"
+)
+
def watts_to_dBm(watts):
"""Converts @watts in watts to dBm"""
return int(10 * math.log10(int(watts * 1000)))
@@ -185,16 +189,28 @@ class PowerLevel:
def parse_freq(freqstr):
"""Parse a frequency string and return the value in integral Hz"""
+ freqstr = freqstr.strip()
+ if freqstr == "":
+ return 0
+ elif freqstr.endswith(" MHz"):
+ return parse_freq(freqstr.split(" ")[0])
+ elif freqstr.endswith(" kHz"):
+ return int(freqstr.split(" ")[0]) * 1000
+
if "." in freqstr:
mhz, khz = freqstr.split(".")
+ if mhz == "":
+ mhz = 0
+ khz = khz.ljust(6, "0")
+ if len(khz) > 6:
+ raise ValueError("Invalid kHz value: %s", khz)
+ mhz = int(mhz) * 1000000
+ khz = int(khz)
else:
- mhz = freqstr
- khz = "0"
- if not mhz.isdigit() and khz.isdigit():
- raise ValueError("Invalid value")
+ mhz = int(freqstr) * 1000000
+ khz = 0
- # Make kHz exactly six decimal places
- return int(("%s%-6s" % (mhz, khz)).replace(" ", "0"))
+ return mhz + khz
def format_freq(freq):
"""Format a frequency given in Hz as a string"""
@@ -262,10 +278,10 @@ class Memory:
self.immutable = []
_valid_map = {
- "rtone" : TONES,
- "ctone" : TONES,
- "dtcs" : DTCS_CODES + DTCS_EXTRA_CODES,
- "rx_dtcs" : DTCS_CODES + DTCS_EXTRA_CODES,
+ "rtone" : TONES + TONES_EXTRA,
+ "ctone" : TONES + TONES_EXTRA,
+ "dtcs" : ALL_DTCS_CODES,
+ "rx_dtcs" : ALL_DTCS_CODES,
"tmode" : TONE_MODES,
"dtcs_polarity" : ["NN", "NR", "RN", "RR"],
"cross_mode" : CROSS_MODES,
@@ -525,8 +541,8 @@ class DVMemory(Memory):
except Exception:
self.dv_code = 0
-class Bank:
- """Base class for a radio's Bank"""
+class MemoryMapping(object):
+ """Base class for a memory mapping"""
def __init__(self, model, index, name):
self._model = model
self._index = index
@@ -536,75 +552,87 @@ class Bank:
return self.get_name()
def __repr__(self):
- return "Bank-%s" % self._index
+ return "%s-%s" % (self.__class__.__name__, self._index)
def get_name(self):
- """Returns the static or user-adjustable bank name"""
+ """Returns the mapping name"""
return self._name
def get_index(self):
- """Returns the immutable bank index (string or int)"""
+ """Returns the immutable index (string or int)"""
return self._index
def __eq__(self, other):
return self.get_index() == other.get_index()
-class NamedBank(Bank):
- """A bank that can have a name"""
- def set_name(self, name):
- """Changes the user-adjustable bank name"""
- self._name = name
+class MappingModel(object):
+ """Base class for a memory mapping model"""
-class BankModel:
- """A bank model where one memory is in zero or one banks at any point"""
- def __init__(self, radio):
+ def __init__(self, radio, name):
self._radio = radio
+ self._name = name
- def get_num_banks(self):
- """Returns the number of banks (should be callable without
- consulting the radio"""
- raise Exception("Not implemented")
+ def get_name(self):
+ return self._name
- def get_banks(self):
- """Return a list of banks"""
- raise Exception("Not implemented")
+ def get_num_mappings(self):
+ """Returns the number of mappings in the model (should be
+ callable without consulting the radio"""
+ raise NotImplementedError()
- def add_memory_to_bank(self, memory, bank):
- """Add @memory to @bank."""
- raise Exception("Not implemented")
+ def get_mappings(self):
+ """Return a list of mappings"""
+ raise NotImplementedError()
- def remove_memory_from_bank(self, memory, bank):
- """Remove @memory from @bank.
- Shall raise exception if @memory is not in @bank."""
- raise Exception("Not implemented")
+ def add_memory_to_mapping(self, memory, mapping):
+ """Add @memory to @mapping."""
+ raise NotImplementedError()
- def get_bank_memories(self, bank):
- """Return a list of memories in @bank"""
- raise Exception("Not implemented")
+ def remove_memory_from_mapping(self, memory, mapping):
+ """Remove @memory from @mapping.
+ Shall raise exception if @memory is not in @bank"""
+ raise NotImplementedError()
- def get_memory_banks(self, memory):
- """Returns a list of the banks that @memory is in"""
- raise Exception("Not implemented")
+ def get_mapping_memories(self, mapping):
+ """Return a list of memories in @mapping"""
+ raise NotImplementedError()
-class BankIndexInterface:
- """Interface for banks with index capabilities"""
+ def get_memory_mappings(self, memory):
+ """Return a list of mappings that @memory is in"""
+ raise NotImplementedError()
+
+class Bank(MemoryMapping):
+ """Base class for a radio's Bank"""
+
+class NamedBank(Bank):
+ """A bank that can have a name"""
+ def set_name(self, name):
+ """Changes the user-adjustable bank name"""
+ self._name = name
+
+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 minimum and maximum bank indices"""
- raise Exception("Not implemented")
+ """Returns a tuple (lo,hi) of the min and max mapping indices"""
+ raise NotImplementedError()
- def get_memory_index(self, memory, bank):
- """Returns the index of @memory in @bank"""
- raise Exception("Not implemented")
+ def get_memory_index(self, memory, mapping):
+ """Returns the index of @memory in @mapping"""
+ raise NotImplementedError()
- def set_memory_index(self, memory, bank, index):
- """Sets the index of @memory in @bank to @index"""
- raise Exception("Not implemented")
+ def set_memory_index(self, memory, mapping, index):
+ """Sets the index of @memory in @mapping to @index"""
+ raise NotImplementedError()
- def get_next_bank_index(self, bank):
- """Returns the next available bank index in @bank, or raises
+ def get_next_mapping_index(self, mapping):
+ """Returns the next available mapping index in @mapping, or raises
Exception if full"""
- raise Exception("Not implemented")
-
+ raise NotImplementedError()
class MTOBankModel(BankModel):
"""A bank model where one memory can be in multiple banks at once """
@@ -615,7 +643,15 @@ def console_status(status):
import sys
sys.stderr.write("\r%s" % status)
-
+
+
+class RadioPrompts:
+ """Radio prompt strings"""
+ experimental = None
+ pre_download = None
+ pre_upload = None
+ display_pre_upload_prompt_before_opening_port = True
+
BOOLEAN = [True, False]
@@ -652,6 +688,7 @@ class RadioFeatures:
"valid_name_length" : 0,
"valid_cross_modes" : [],
"valid_dtcs_pols" : [],
+ "valid_dtcs_codes" : [],
"valid_special_chans" : [],
"has_sub_devices" : BOOLEAN,
@@ -771,6 +808,8 @@ class RadioFeatures:
"Supported tone cross modes")
self.init("valid_dtcs_pols", ["NN", "RN", "NR", "RR"],
"Supported DTCS polarities")
+ self.init("valid_dtcs_codes", list(DTCS_CODES),
+ "Supported DTCS codes")
self.init("valid_special_chans", [],
"Supported special channel names")
@@ -833,6 +872,13 @@ class RadioFeatures:
mem.dtcs_polarity)
msgs.append(msg)
+ if self.valid_dtcs_codes and \
+ mem.dtcs not in self.valid_dtcs_codes:
+ msg = ValidationError("DTCS Code %03i not supported" % mem.dtcs)
+ if self.valid_dtcs_codes and \
+ mem.rx_dtcs not in self.valid_dtcs_codes:
+ msg = ValidationError("DTCS Code %03i not supported" % mem.rx_dtcs)
+
if self.valid_duplexes and mem.duplex not in self.valid_duplexes:
msg = ValidationError("Duplex %s not supported" % mem.duplex)
msgs.append(msg)
@@ -893,7 +939,7 @@ class ValidationError(ValidationMessage):
"""A fatal error during memory validation"""
pass
-class Radio:
+class Radio(object):
"""Base class for all Radio drivers"""
BAUD_RATE = 9600
HARDWARE_FLOW = False
@@ -918,6 +964,11 @@ class Radio:
"""Return a printable name for this radio"""
return "%s %s" % (cls.VENDOR, cls.MODEL)
+ @classmethod
+ def get_prompts(cls):
+ """Return a set of strings for use in prompts"""
+ return RadioPrompts()
+
def set_pipe(self, pipe):
"""Set the serial object to be used for communications"""
self.pipe = pipe
@@ -941,9 +992,14 @@ class Radio:
"""Set the memory object @memory"""
pass
- def get_bank_model(self):
- """Returns either a BankModel or None if not supported"""
- return None
+ def get_mapping_models(self):
+ """Returns a list of MappingModel objects (or an empty list)"""
+ if hasattr(self, "get_bank_model"):
+ # FIXME: Backwards compatibility for old bank models
+ bank_model = self.get_bank_model()
+ if bank_model:
+ return [bank_model]
+ return []
def get_raw_memory(self, number):
"""Return a raw string describing the memory at @number"""
@@ -1300,10 +1356,10 @@ def split_tone_encode(mem):
"DTCS", 23, "N"
"""
- txmode = txval = None
- txpol = mem.dtcs_polarity[0]
- rxmode = rxval = None
- rxpol = mem.dtcs_polarity[1]
+ txmode = ''
+ rxmode = ''
+ txval = None
+ rxval = None
if mem.tmode == "Tone":
txmode = "Tone"
@@ -1325,5 +1381,14 @@ def split_tone_encode(mem):
elif rxmode == "DTCS":
rxval = mem.rx_dtcs
+ if txmode == "DTCS":
+ txpol = mem.dtcs_polarity[0]
+ else:
+ txpol = None
+ if rxmode == "DTCS":
+ rxpol = mem.dtcs_polarity[1]
+ else:
+ rxpol = None
+
return ((txmode, txval, txpol),
(rxmode, rxval, rxpol))
diff --git a/chirp/elib_intl.py b/chirp/elib_intl.py
new file mode 100644
index 0000000..6e7cafa
--- /dev/null
+++ b/chirp/elib_intl.py
@@ -0,0 +1,495 @@
+# -*- coding: utf-8 -*-
+#
+# Copyright © 2007-2010 Dieter Verfaillie <dieterv at optionexplicit.be>
+#
+# This file is part of elib.intl.
+#
+# elib.intl is free software: you can redistribute it and/or modify
+# it under the terms of the GNU Lesser General Public License as published by
+# the Free Software Foundation, either version 3 of the License, or
+# (at your option) any later version.
+#
+# elib.intl 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 Lesser General Public License for more details.
+#
+# You should have received a copy of the GNU Lesser General Public License
+# along with elib.intl. If not, see <http://www.gnu.org/licenses/>.
+
+
+'''
+The elib.intl module provides enhanced internationalization (I18N) services for
+your Python modules and applications.
+
+elib.intl wraps Python's :func:`gettext` functionality and adds the following on
+Microsoft Windows systems:
+
+ - automatic detection of the current screen language (not necessarily the same
+ as the installation language) provided by MUI packs,
+ - makes sure internationalized C libraries which internally invoke gettext() or
+ dcgettext() can properly locate their message catalogs. This fixes a known
+ limitation in gettext's Windows support when using eg. gtk.builder or gtk.glade.
+
+See http://www.gnu.org/software/gettext/FAQ.html#windows_setenv for more
+information.
+
+The elib.intl module defines the following functions:
+'''
+
+
+__all__ = ['install', 'install_module']
+__version__ = '0.0.3'
+__docformat__ = 'restructuredtext'
+
+
+import os
+import sys
+import locale
+import gettext
+
+from logging import getLogger
+
+
+logger = getLogger('elib.intl')
+
+
+def _isofromlcid(lcid):
+ '''
+ :param lcid: Microsoft Windows LCID
+ :returns: the ISO 639-1 language code for a given lcid. If there is no
+ ISO 639-1 language code assigned to the language specified by lcid,
+ the ISO 639-2 language code is returned. If the language specified
+ by lcid is unknown in the ISO 639-x database, None is returned.
+
+ More information can be found on the following websites:
+ - List of ISO 639-1 and ISO 639-2 language codes: http://www.loc.gov/standards/iso639-2/
+ - List of known lcid's: http://www.microsoft.com/globaldev/reference/lcid-all.mspx
+ - List of known MUI packs: http://www.microsoft.com/globaldev/reference/win2k/setup/Langid.mspx
+ '''
+ mapping = {1078: 'af', #Afrikaans - South Africa
+ 1052: 'sq', #Albanian - Albania
+ 1118: 'am', #Amharic - Ethiopia
+ 1025: 'ar', #Arabic - Saudi Arabia
+ 5121: 'ar', #Arabic - Algeria
+ 15361: 'ar', #Arabic - Bahrain
+ 3073: 'ar', #Arabic - Egypt
+ 2049: 'ar', #Arabic - Iraq
+ 11265: 'ar', #Arabic - Jordan
+ 13313: 'ar', #Arabic - Kuwait
+ 12289: 'ar', #Arabic - Lebanon
+ 4097: 'ar', #Arabic - Libya
+ 6145: 'ar', #Arabic - Morocco
+ 8193: 'ar', #Arabic - Oman
+ 16385: 'ar', #Arabic - Qatar
+ 10241: 'ar', #Arabic - Syria
+ 7169: 'ar', #Arabic - Tunisia
+ 14337: 'ar', #Arabic - U.A.E.
+ 9217: 'ar', #Arabic - Yemen
+ 1067: 'hy', #Armenian - Armenia
+ 1101: 'as', #Assamese
+ 2092: 'az', #Azeri (Cyrillic)
+ 1068: 'az', #Azeri (Latin)
+ 1069: 'eu', #Basque
+ 1059: 'be', #Belarusian
+ 1093: 'bn', #Bengali (India)
+ 2117: 'bn', #Bengali (Bangladesh)
+ 5146: 'bs', #Bosnian (Bosnia/Herzegovina)
+ 1026: 'bg', #Bulgarian
+ 1109: 'my', #Burmese
+ 1027: 'ca', #Catalan
+ 1116: 'chr', #Cherokee - United States
+ 2052: 'zh', #Chinese - People's Republic of China
+ 4100: 'zh', #Chinese - Singapore
+ 1028: 'zh', #Chinese - Taiwan
+ 3076: 'zh', #Chinese - Hong Kong SAR
+ 5124: 'zh', #Chinese - Macao SAR
+ 1050: 'hr', #Croatian
+ 4122: 'hr', #Croatian (Bosnia/Herzegovina)
+ 1029: 'cs', #Czech
+ 1030: 'da', #Danish
+ 1125: 'dv', #Divehi
+ 1043: 'nl', #Dutch - Netherlands
+ 2067: 'nl', #Dutch - Belgium
+ 1126: 'bin', #Edo
+ 1033: 'en', #English - United States
+ 2057: 'en', #English - United Kingdom
+ 3081: 'en', #English - Australia
+ 10249: 'en', #English - Belize
+ 4105: 'en', #English - Canada
+ 9225: 'en', #English - Caribbean
+ 15369: 'en', #English - Hong Kong SAR
+ 16393: 'en', #English - India
+ 14345: 'en', #English - Indonesia
+ 6153: 'en', #English - Ireland
+ 8201: 'en', #English - Jamaica
+ 17417: 'en', #English - Malaysia
+ 5129: 'en', #English - New Zealand
+ 13321: 'en', #English - Philippines
+ 18441: 'en', #English - Singapore
+ 7177: 'en', #English - South Africa
+ 11273: 'en', #English - Trinidad
+ 12297: 'en', #English - Zimbabwe
+ 1061: 'et', #Estonian
+ 1080: 'fo', #Faroese
+ 1065: None, #TODO: Farsi
+ 1124: 'fil', #Filipino
+ 1035: 'fi', #Finnish
+ 1036: 'fr', #French - France
+ 2060: 'fr', #French - Belgium
+ 11276: 'fr', #French - Cameroon
+ 3084: 'fr', #French - Canada
+ 9228: 'fr', #French - Democratic Rep. of Congo
+ 12300: 'fr', #French - Cote d'Ivoire
+ 15372: 'fr', #French - Haiti
+ 5132: 'fr', #French - Luxembourg
+ 13324: 'fr', #French - Mali
+ 6156: 'fr', #French - Monaco
+ 14348: 'fr', #French - Morocco
+ 58380: 'fr', #French - North Africa
+ 8204: 'fr', #French - Reunion
+ 10252: 'fr', #French - Senegal
+ 4108: 'fr', #French - Switzerland
+ 7180: 'fr', #French - West Indies
+ 1122: 'fy', #Frisian - Netherlands
+ 1127: None, #TODO: Fulfulde - Nigeria
+ 1071: 'mk', #FYRO Macedonian
+ 2108: 'ga', #Gaelic (Ireland)
+ 1084: 'gd', #Gaelic (Scotland)
+ 1110: 'gl', #Galician
+ 1079: 'ka', #Georgian
+ 1031: 'de', #German - Germany
+ 3079: 'de', #German - Austria
+ 5127: 'de', #German - Liechtenstein
+ 4103: 'de', #German - Luxembourg
+ 2055: 'de', #German - Switzerland
+ 1032: 'el', #Greek
+ 1140: 'gn', #Guarani - Paraguay
+ 1095: 'gu', #Gujarati
+ 1128: 'ha', #Hausa - Nigeria
+ 1141: 'haw', #Hawaiian - United States
+ 1037: 'he', #Hebrew
+ 1081: 'hi', #Hindi
+ 1038: 'hu', #Hungarian
+ 1129: None, #TODO: Ibibio - Nigeria
+ 1039: 'is', #Icelandic
+ 1136: 'ig', #Igbo - Nigeria
+ 1057: 'id', #Indonesian
+ 1117: 'iu', #Inuktitut
+ 1040: 'it', #Italian - Italy
+ 2064: 'it', #Italian - Switzerland
+ 1041: 'ja', #Japanese
+ 1099: 'kn', #Kannada
+ 1137: 'kr', #Kanuri - Nigeria
+ 2144: 'ks', #Kashmiri
+ 1120: 'ks', #Kashmiri (Arabic)
+ 1087: 'kk', #Kazakh
+ 1107: 'km', #Khmer
+ 1111: 'kok', #Konkani
+ 1042: 'ko', #Korean
+ 1088: 'ky', #Kyrgyz (Cyrillic)
+ 1108: 'lo', #Lao
+ 1142: 'la', #Latin
+ 1062: 'lv', #Latvian
+ 1063: 'lt', #Lithuanian
+ 1086: 'ms', #Malay - Malaysia
+ 2110: 'ms', #Malay - Brunei Darussalam
+ 1100: 'ml', #Malayalam
+ 1082: 'mt', #Maltese
+ 1112: 'mni', #Manipuri
+ 1153: 'mi', #Maori - New Zealand
+ 1102: 'mr', #Marathi
+ 1104: 'mn', #Mongolian (Cyrillic)
+ 2128: 'mn', #Mongolian (Mongolian)
+ 1121: 'ne', #Nepali
+ 2145: 'ne', #Nepali - India
+ 1044: 'no', #Norwegian (Bokmᅢᆬl)
+ 2068: 'no', #Norwegian (Nynorsk)
+ 1096: 'or', #Oriya
+ 1138: 'om', #Oromo
+ 1145: 'pap', #Papiamentu
+ 1123: 'ps', #Pashto
+ 1045: 'pl', #Polish
+ 1046: 'pt', #Portuguese - Brazil
+ 2070: 'pt', #Portuguese - Portugal
+ 1094: 'pa', #Punjabi
+ 2118: 'pa', #Punjabi (Pakistan)
+ 1131: 'qu', #Quecha - Bolivia
+ 2155: 'qu', #Quecha - Ecuador
+ 3179: 'qu', #Quecha - Peru
+ 1047: 'rm', #Rhaeto-Romanic
+ 1048: 'ro', #Romanian
+ 2072: 'ro', #Romanian - Moldava
+ 1049: 'ru', #Russian
+ 2073: 'ru', #Russian - Moldava
+ 1083: 'se', #Sami (Lappish)
+ 1103: 'sa', #Sanskrit
+ 1132: 'nso', #Sepedi
+ 3098: 'sr', #Serbian (Cyrillic)
+ 2074: 'sr', #Serbian (Latin)
+ 1113: 'sd', #Sindhi - India
+ 2137: 'sd', #Sindhi - Pakistan
+ 1115: 'si', #Sinhalese - Sri Lanka
+ 1051: 'sk', #Slovak
+ 1060: 'sl', #Slovenian
+ 1143: 'so', #Somali
+ 1070: 'wen', #Sorbian
+ 3082: 'es', #Spanish - Spain (Modern Sort)
+ 1034: 'es', #Spanish - Spain (Traditional Sort)
+ 11274: 'es', #Spanish - Argentina
+ 16394: 'es', #Spanish - Bolivia
+ 13322: 'es', #Spanish - Chile
+ 9226: 'es', #Spanish - Colombia
+ 5130: 'es', #Spanish - Costa Rica
+ 7178: 'es', #Spanish - Dominican Republic
+ 12298: 'es', #Spanish - Ecuador
+ 17418: 'es', #Spanish - El Salvador
+ 4106: 'es', #Spanish - Guatemala
+ 18442: 'es', #Spanish - Honduras
+ 58378: 'es', #Spanish - Latin America
+ 2058: 'es', #Spanish - Mexico
+ 19466: 'es', #Spanish - Nicaragua
+ 6154: 'es', #Spanish - Panama
+ 15370: 'es', #Spanish - Paraguay
+ 10250: 'es', #Spanish - Peru
+ 20490: 'es', #Spanish - Puerto Rico
+ 21514: 'es', #Spanish - United States
+ 14346: 'es', #Spanish - Uruguay
+ 8202: 'es', #Spanish - Venezuela
+ 1072: None, #TODO: Sutu
+ 1089: 'sw', #Swahili
+ 1053: 'sv', #Swedish
+ 2077: 'sv', #Swedish - Finland
+ 1114: 'syr', #Syriac
+ 1064: 'tg', #Tajik
+ 1119: None, #TODO: Tamazight (Arabic)
+ 2143: None, #TODO: Tamazight (Latin)
+ 1097: 'ta', #Tamil
+ 1092: 'tt', #Tatar
+ 1098: 'te', #Telugu
+ 1054: 'th', #Thai
+ 2129: 'bo', #Tibetan - Bhutan
+ 1105: 'bo', #Tibetan - People's Republic of China
+ 2163: 'ti', #Tigrigna - Eritrea
+ 1139: 'ti', #Tigrigna - Ethiopia
+ 1073: 'ts', #Tsonga
+ 1074: 'tn', #Tswana
+ 1055: 'tr', #Turkish
+ 1090: 'tk', #Turkmen
+ 1152: 'ug', #Uighur - China
+ 1058: 'uk', #Ukrainian
+ 1056: 'ur', #Urdu
+ 2080: 'ur', #Urdu - India
+ 2115: 'uz', #Uzbek (Cyrillic)
+ 1091: 'uz', #Uzbek (Latin)
+ 1075: 've', #Venda
+ 1066: 'vi', #Vietnamese
+ 1106: 'cy', #Welsh
+ 1076: 'xh', #Xhosa
+ 1144: 'ii', #Yi
+ 1085: 'yi', #Yiddish
+ 1130: 'yo', #Yoruba
+ 1077: 'zu'} #Zulu
+
+ return mapping[lcid]
+
+def _getscreenlanguage():
+ '''
+ :returns: the ISO 639-x language code for this session.
+
+ If the LANGUAGE environment variable is set, it's value overrides the
+ screen language detection. Otherwise the screen language is determined by
+ the currently selected Microsoft Windows MUI language pack or the Microsoft
+ Windows installation language.
+
+ Works on Microsoft Windows 2000 and up.
+ '''
+ if sys.platform == 'win32' or sys.platform == 'nt':
+ # Start with nothing
+ lang = None
+
+ # Check the LANGUAGE environment variable
+ lang = os.getenv('LANGUAGE')
+
+ if lang is None:
+ # Start with nothing
+ lcid = None
+
+ try:
+ from ctypes import windll
+ lcid = windll.kernel32.GetUserDefaultUILanguage()
+ except:
+ logger.debug('Failed to get current screen language with \'GetUserDefaultUILanguage\'')
+ finally:
+ if lcid is None:
+ lang = 'C'
+ else:
+ lang = _isofromlcid(lcid)
+
+ logger.debug('Windows screen language is \'%s\' (lcid %s)' % (lang, lcid))
+
+ return lang
+
+def _putenv(name, value):
+ '''
+ :param name: environment variable name
+ :param value: environment variable value
+
+ This function ensures that changes to an environment variable are applied
+ to each copy of the environment variables used by a process. Starting from
+ Python 2.4, os.environ changes only apply to the copy Python keeps (os.environ)
+ and are no longer automatically applied to the other copies for the process.
+
+ On Microsoft Windows, each process has multiple copies of the environment
+ variables, one managed by the OS and one managed by the C library. We also
+ need to take care of the fact that the C library used by Python is not
+ necessarily the same as the C library used by pygtk and friends. This because
+ the latest releases of pygtk and friends are built with mingw32 and are thus
+ linked against msvcrt.dll. The official gtk+ binaries have always been built
+ in this way.
+ '''
+
+ if sys.platform == 'win32' or sys.platform == 'nt':
+ from ctypes import windll
+ from ctypes import cdll
+ from ctypes.util import find_msvcrt
+
+ # Update Python's copy of the environment variables
+ os.environ[name] = value
+
+ # Update the copy maintained by Windows (so SysInternals Process Explorer sees it)
+ try:
+ result = windll.kernel32.SetEnvironmentVariableW(name, value)
+ if result == 0: raise Warning
+ except Exception:
+ logger.debug('Failed to set environment variable \'%s\' (\'kernel32.SetEnvironmentVariableW\')' % name)
+ else:
+ logger.debug('Set environment variable \'%s\' to \'%s\' (\'kernel32.SetEnvironmentVariableW\')' % (name, value))
+
+ # Update the copy maintained by msvcrt (used by gtk+ runtime)
+ try:
+ result = cdll.msvcrt._putenv('%s=%s' % (name, value))
+ if result !=0: raise Warning
+ except Exception:
+ logger.debug('Failed to set environment variable \'%s\' (\'msvcrt._putenv\')' % name)
+ else:
+ logger.debug('Set environment variable \'%s\' to \'%s\' (\'msvcrt._putenv\')' % (name, value))
+
+ # Update the copy maintained by whatever c runtime is used by Python
+ try:
+ msvcrt = find_msvcrt()
+ msvcrtname = str(msvcrt).split('.')[0] if '.' in msvcrt else str(msvcrt)
+ result = cdll.LoadLibrary(msvcrt)._putenv('%s=%s' % (name, value))
+ if result != 0: raise Warning
+ except Exception:
+ logger.debug('Failed to set environment variable \'%s\' (\'%s._putenv\')' % (name, msvcrtname))
+ else:
+ logger.debug('Set environment variable \'%s\' to \'%s\' (\'%s._putenv\')' % (name, value, msvcrtname))
+
+def _dugettext(domain, message):
+ '''
+ :param domain: translation domain
+ :param message: message to translate
+ :returns: the translated message
+
+ Unicode version of :func:`gettext.dgettext`.
+ '''
+ try:
+ t = gettext.translation(domain, gettext._localedirs.get(domain, None),
+ codeset=gettext._localecodesets.get(domain))
+ except IOError:
+ return message
+ else:
+ return t.ugettext(message)
+
+def _install(domain, localedir, asglobal=False):
+ '''
+ :param domain: translation domain
+ :param localedir: locale directory
+ :param asglobal: if True, installs the function _() in Python’s builtin namespace. Default is False
+
+ Private function doing all the work for the :func:`elib.intl.install` and
+ :func:`elib.intl.install_module` functions.
+ '''
+ # prep locale system
+ if asglobal:
+ locale.setlocale(locale.LC_ALL, '')
+
+ # on windows systems, set the LANGUAGE environment variable
+ if sys.platform == 'win32' or sys.platform == 'nt':
+ _putenv('LANGUAGE', _getscreenlanguage())
+
+ # The locale module on Max OS X lacks bindtextdomain so we specifically
+ # test on linux2 here. See commit 4ae8b26fd569382ab66a9e844daa0e01de409ceb
+ if sys.platform == 'linux2':
+ locale.bindtextdomain(domain, localedir)
+ locale.bind_textdomain_codeset(domain, 'UTF-8')
+ locale.textdomain(domain)
+
+ # initialize Python's gettext interface
+ gettext.bindtextdomain(domain, localedir)
+ gettext.bind_textdomain_codeset(domain, 'UTF-8')
+
+ if asglobal:
+ gettext.textdomain(domain)
+
+ # on windows systems, initialize libintl
+ if sys.platform == 'win32' or sys.platform == 'nt':
+ from ctypes import cdll
+ libintl = cdll.intl
+ libintl.bindtextdomain(domain, localedir)
+ libintl.bind_textdomain_codeset(domain, 'UTF-8')
+
+ if asglobal:
+ libintl.textdomain(domain)
+
+ del libintl
+
+def install(domain, localedir):
+ '''
+ :param domain: translation domain
+ :param localedir: locale directory
+
+ Installs the function _() in Python’s builtin namespace, based on
+ domain and localedir. Codeset is always UTF-8.
+
+ As seen below, you usually mark the strings in your application that are
+ candidates for translation, by wrapping them in a call to the _() function,
+ like this:
+
+ .. sourcecode:: python
+
+ import elib.intl
+ elib.intl.install('myapplication', '/path/to/usr/share/locale')
+ print _('This string will be translated.')
+
+ Note that this is only one way, albeit the most convenient way,
+ to make the _() function available to your application. Because it affects
+ the entire application globally, and specifically Python’s built-in
+ namespace, localized modules should never install _(). Instead, you should
+ use :func:`elib.intl.install_module` to make _() available to your module.
+ '''
+ _install(domain, localedir, True)
+ gettext.install(domain, localedir, unicode=True)
+
+def install_module(domain, localedir):
+ '''
+ :param domain: translation domain
+ :param localedir: locale directory
+ :returns: an anonymous function object, based on domain and localedir.
+ Codeset is always UTF-8.
+
+ You may find this function usefull when writing localized modules.
+ Use this code to make _() available to your module:
+
+ .. sourcecode:: python
+
+ import elib.intl
+ _ = elib.intl.install_module('mymodule', '/path/to/usr/share/locale')
+ print _('This string will be translated.')
+
+ When writing a package, you can usually do this in the package's __init__.py
+ file and import the _() function from the package namespace as needed.
+ '''
+ _install(domain, localedir, False)
+ return lambda message: _dugettext(domain, message)
diff --git a/chirp/ft1802.py b/chirp/ft1802.py
index 9a806c0..af83a51 100644
--- a/chirp/ft1802.py
+++ b/chirp/ft1802.py
@@ -26,6 +26,7 @@
from chirp import chirp_common, bitwise, directory, yaesu_clone
from chirp.settings import RadioSetting, RadioSettingGroup, \
RadioSettingValueBoolean
+from textwrap import dedent
MEM_FORMAT = """
#seekto 0x06ea;
@@ -88,6 +89,21 @@ class FT1802Radio(yaesu_clone.YaesuCloneModeRadio):
_block_lengths = [10, 8001]
_memsize = 8011
+ @classmethod
+ def get_prompts(cls):
+ rp = chirp_common.RadioPrompts()
+ rp.pre_download = _(dedent("""\
+ 1. Turn radio off.
+ 2. Connect cable to mic jack.
+ 3. Press and hold in the [LOW(A/N)] key while turning the radio on.
+ 4. <b>After clicking OK</b>, press the [MHz(SET)] key to send image."""))
+ rp.pre_upload = _(dedent("""\
+ 1. Turn radio off.
+ 2. Connect cable to mic jack.
+ 3. Press and hold in the [LOW(A/N)] key while turning the radio on.
+ 4. Press the [D/MR(MW)] key ("--WAIT--" will appear on the LCD)."""))
+ return rp
+
def get_features(self):
rf = chirp_common.RadioFeatures()
diff --git a/chirp/ft60.py b/chirp/ft60.py
index d51d83b..9a882ab 100644
--- a/chirp/ft60.py
+++ b/chirp/ft60.py
@@ -16,6 +16,7 @@
import time
from chirp import chirp_common, yaesu_clone, memmap, bitwise, directory
from chirp import errors
+from textwrap import dedent
ACK = "\x06"
@@ -71,7 +72,7 @@ def _upload(radio):
_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)
+ raise Exception(_("Radio did not ack block %i") % i)
if radio.status_fn:
status = chirp_common.Status()
@@ -96,11 +97,12 @@ def _decode_freq(freqraw):
def _encode_freq(freq):
freqraw = freq / 10000
- if ((freq / 1000) % 10) == 5:
- freqraw += 800000
+ flags = 0x00
+ if ((freq / 1000) % 10) >= 5:
+ flags += 0x80
if chirp_common.is_fractional_step(freq):
- freqraw += 400000
- return freqraw
+ flags += 0x40
+ return freqraw, flags
MEM_FORMAT = """
@@ -166,6 +168,27 @@ class FT60Radio(yaesu_clone.YaesuCloneModeRadio):
_memsize = 28617
+ @classmethod
+ def get_prompts(cls):
+ rp = chirp_common.RadioPrompts()
+ rp.pre_download = _(dedent("""\
+ 1. Turn radio off.
+ 2. Connect cable to MIC/SP jack.
+ 3. Press and hold in the [MONI] switch while turning the
+ 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."""))
+ rp.pre_upload = _(dedent("""\
+ 1. Turn radio off.
+ 2. Connect cable to MIC/SP jack.
+ 3. Press and hold in the [MONI] switch while turning the
+ radio on.
+ 4. Rotate the DIAL job to select "F8 CLONE".
+ 5. Press the [F/W] key momentarily.
+ 6. Press the [MONI] switch ("--RX--" will appear on the LCD)."""))
+ return rp
+
def get_features(self):
rf = chirp_common.RadioFeatures()
rf.memory_bounds = (1, 999)
@@ -268,9 +291,11 @@ class FT60Radio(yaesu_clone.YaesuCloneModeRadio):
_mem.used = 1
print "Wiped"
- _mem.freq = _encode_freq(mem.freq)
+ _mem.freq, flags = _encode_freq(mem.freq)
+ _mem.freq[0].set_bits(flags)
if mem.duplex == "split":
- _mem.tx_freq = _encode_freq(mem.offset)
+ _mem.tx_freq, flags = _encode_freq(mem.offset)
+ _mem.tx_freq[0].set_bits(flags)
_mem.offset = 0
else:
_mem.tx_freq = 0
diff --git a/chirp/ft7800.py b/chirp/ft7800.py
index f12b95c..6fd5601 100644
--- a/chirp/ft7800.py
+++ b/chirp/ft7800.py
@@ -16,23 +16,88 @@
import time
from chirp import chirp_common, yaesu_clone, memmap, directory
from chirp import bitwise, errors
+from textwrap import dedent
+from chirp.settings import RadioSetting, RadioSettingGroup, \
+ RadioSettingValueInteger, RadioSettingValueList, \
+ RadioSettingValueBoolean, RadioSettingValueString
+import os, re
from collections import defaultdict
+if os.getenv("CHIRP_DEBUG"):
+ CHIRP_DEBUG = True
+else:
+ CHIRP_DEBUG = False
+
ACK = chr(0x06)
MEM_FORMAT = """
-#seekto 0x04C8;
+#seekto 0x002A;
+u8 banks_unk2;
+u8 current_channel;
+u8 unk3;
+u8 unk4;
+u8 current_menu;
+
+#seekto 0x0035;
+u8 banks_unk1;
+
+#seekto 0x00C8;
struct {
+ u8 memory[16];
+} dtmf[16];
+
+#seekto 0x003A;
+struct {
+ u8 apo;
+ u8 tot;
+ u8 lock:3,
+ arts_interval:1,
+ unk1a:1,
+ prog_panel_acc:3;
+ u8 prog_p1;
+ u8 prog_p2;
+ u8 prog_p3;
+ u8 prog_p4;
+ u8 rf_sql;
+ u8 inet_dtmf_mem:4,
+ inet_dtmf_digit:4;
+ u8 arts_cwid_enable:1,
+ prog_tone_vm:1,
+ unk2a:1,
+ hyper_write:2,
+ memory_only:1,
+ dimmer:2;
+ u8 beep_scan:1,
+ beep_edge:1,
+ beep_key:1,
+ unk3a:1,
+ inet_mode:1,
+ unk3b:1,
+ dtmf_speed:2;
+ u8 dcs_polarity:2,
+ smart_search:1,
+ priority_revert:1,
+ unk4a:1,
+ dtmf_delay:3;
+ u8 unk5a:3,
+ microphone_type:1,
+ scan_resume:1,
+ unk5b:1,
+ arts_mode:2;
+ u8 unk6;
+} settings;
+
+struct mem_struct {
u8 used:1,
unknown1:1,
mode:2,
unknown2:1,
duplex:3;
bbcd freq[3];
- u8 unknown3:1,
+ u8 clockshift:1,
tune_step:3,
- unknown5:2,
+ unknown5:2, // TODO: tmode has extended settings, at least 4 bits
tmode:2;
bbcd split[3];
u8 power:2,
@@ -42,7 +107,19 @@ struct {
u8 unknown7[2];
u8 offset;
u8 unknown9[3];
-} memory[1000];
+};
+
+#seekto 0x0048;
+struct mem_struct vfos[5];
+
+#seekto 0x01C8;
+struct mem_struct homes[5];
+
+#seekto 0x0218;
+u8 arts_cwid[6];
+
+#seekto 0x04C8;
+struct mem_struct memory[1000];
#seekto 0x4988;
struct {
@@ -82,6 +159,8 @@ CHARSET = ["%i" % int(x) for x in range(0, 10)] + \
list("*+,- /| [ ] _") + \
list("\x00" * 100)
+DTMFCHARSET = list("0123456789ABCD*#")
+
POWER_LEVELS_VHF = [chirp_common.PowerLevel("Hi", watts=50),
chirp_common.PowerLevel("Mid1", watts=20),
chirp_common.PowerLevel("Mid2", watts=10),
@@ -181,7 +260,33 @@ class FTx800Radio(yaesu_clone.YaesuCloneModeRadio):
"""Base class for FT-7800,7900,8800,8900 radios"""
BAUD_RATE = 9600
VENDOR = "Yaesu"
-
+ MODES = list(MODES)
+
+ @classmethod
+ def get_prompts(cls):
+ rp = chirp_common.RadioPrompts()
+ rp.pre_download = _(dedent("""\
+ 1. Turn radio off.
+ 2. Connect cable to DATA jack.
+ 3. Press and hold in the [MHz(PRI)] key while turning the
+ radio on.
+ 4. Rotate the DIAL job to select "F-7 CLONE".
+ 5. Press and hold in the [BAND(SET)] key. The display
+ will disappear for a moment, then the "CLONE" notation
+ will appear.
+ 6. <b>After clicking OK</b>, press the [V/M(MW)] key to send image."""))
+ rp.pre_upload = _(dedent("""\
+ 1. Turn radio off.
+ 2. Connect cable to DATA jack.
+ 3. Press and hold in the [MHz(PRI)] key while turning the
+ radio on.
+ 4. Rotate the DIAL job to select "F-7 CLONE".
+ 5. Press and hold in the [BAND(SET)] key. The display
+ will disappear for a moment, then the "CLONE" notation
+ will appear.
+ 6. Press the [LOW(ACC)] key ("--RX--" will appear on the display)."""))
+ return rp
+
def get_features(self):
rf = chirp_common.RadioFeatures()
rf.memory_bounds = (1, 999)
@@ -288,7 +393,7 @@ class FTx800Radio(yaesu_clone.YaesuCloneModeRadio):
mem.freq = get_freq(int(_mem.freq) * 10000)
mem.rtone = chirp_common.TONES[_mem.tone]
mem.tmode = TMODES[_mem.tmode]
- mem.mode = MODES[_mem.mode]
+ mem.mode = self.MODES[_mem.mode]
mem.dtcs = chirp_common.DTCS_CODES[_mem.dtcs]
if self.get_features().has_tuning_step:
mem.tuning_step = STEPS[_mem.tune_step]
@@ -315,7 +420,7 @@ class FTx800Radio(yaesu_clone.YaesuCloneModeRadio):
set_freq(mem.freq, _mem, "freq")
_mem.tone = chirp_common.TONES.index(mem.rtone)
_mem.tmode = TMODES.index(mem.tmode)
- _mem.mode = MODES.index(mem.mode)
+ _mem.mode = self.MODES.index(mem.mode)
_mem.dtcs = chirp_common.DTCS_CODES.index(mem.dtcs)
if self.get_features().has_tuning_step:
_mem.tune_step = STEPS.index(mem.tuning_step)
@@ -336,7 +441,7 @@ class FTx800Radio(yaesu_clone.YaesuCloneModeRadio):
class FT7800BankModel(chirp_common.BankModel):
"""Yaesu FT-7800/7900 bank model"""
def __init__(self, radio):
- chirp_common.BankModel.__init__(self, radio)
+ super(FT7800BankModel, self).__init__(radio)
self.__b2m_cache = defaultdict(list)
self.__m2b_cache = defaultdict(list)
@@ -344,24 +449,24 @@ class FT7800BankModel(chirp_common.BankModel):
if self.__b2m_cache:
return
- for bank in self.get_banks():
+ for bank in self.get_mappings():
self.__b2m_cache[bank.index] = self._get_bank_memories(bank)
for memnum in self.__b2m_cache[bank.index]:
self.__m2b_cache[memnum].append(bank.index)
- def get_num_banks(self):
+ def get_num_mappings(self):
return 20
- def get_banks(self):
+ def get_mappings(self):
banks = []
- for i in range(0, self.get_num_banks()):
+ for i in range(0, self.get_num_mappings()):
bank = chirp_common.Bank(self, "%i" % i, "BANK-%i" % (i + 1))
bank.index = i
banks.append(bank)
return banks
- def add_memory_to_bank(self, memory, bank):
+ def add_memory_to_mapping(self, memory, bank):
self.__precache()
index = memory.number - 1
@@ -371,7 +476,7 @@ class FT7800BankModel(chirp_common.BankModel):
self.__m2b_cache[memory.number].append(bank.index)
self.__b2m_cache[bank.index].append(memory.number)
- def remove_memory_from_bank(self, memory, bank):
+ def remove_memory_from_mapping(self, memory, bank):
self.__precache()
index = memory.number - 1
@@ -395,32 +500,34 @@ class FT7800BankModel(chirp_common.BankModel):
memories.append(i + 1)
return memories
- def get_bank_memories(self, bank):
+ def get_mapping_memories(self, bank):
self.__precache()
return [self._radio.get_memory(n)
for n in self.__b2m_cache[bank.index]]
- def get_memory_banks(self, memory):
+ def get_memory_mappings(self, memory):
self.__precache()
- _banks = self.get_banks()
+ _banks = self.get_mappings()
return [_banks[b] for b in self.__m2b_cache[memory.number]]
@directory.register
class FT7800Radio(FTx800Radio):
"""Yaesu FT-7800"""
- MODEL = "FT-7800"
+ MODEL = "FT-7800/7900"
_model = "AH016"
_memsize = 31561
-
+ _block_lengths = [8, 31552, 1]
+
def get_bank_model(self):
return FT7800BankModel(self)
def get_features(self):
rf = FTx800Radio.get_features(self)
rf.has_bank = True
+ rf.has_settings = True
return rf
def set_memory(self, memory):
@@ -428,9 +535,209 @@ class FT7800Radio(FTx800Radio):
self._wipe_memory_banks(memory)
FTx800Radio.set_memory(self, memory)
-class FT7900Radio(FT7800Radio):
- """Yaesu FT-7900"""
- MODEL = "FT-7900"
+ def _decode_chars(self, inarr):
+ if CHIRP_DEBUG:
+ print "@_decode_chars, type: %s" % type(inarr)
+ print inarr
+ outstr = ""
+ for i in inarr:
+ if i == 0xFF:
+ break
+ outstr += CHARSET[i & 0x7F]
+ return outstr.rstrip()
+
+ def _encode_chars(self, instr, length = 16):
+ if CHIRP_DEBUG:
+ print "@_encode_chars, type: %s" % type(instr)
+ print instr
+ outarr = []
+ instr = str(instr)
+ for i in range(length):
+ if i < len(instr):
+ outarr.append(CHARSET.index(instr[i]))
+ else:
+ outarr.append(0xFF)
+ return outarr
+
+ def get_settings(self):
+ _settings = self._memobj.settings
+ basic = RadioSettingGroup("basic", "Basic")
+ dtmf = RadioSettingGroup("dtmf", "DTMF")
+ arts = RadioSettingGroup("arts", "ARTS")
+ prog = RadioSettingGroup("prog", "Programmable Buttons")
+ top = RadioSettingGroup("top", "All Settings",
+ basic, dtmf, arts, prog)
+
+ basic.append( RadioSetting("priority_revert", "Priority Revert",
+ RadioSettingValueBoolean(_settings.priority_revert)))
+
+ basic.append( RadioSetting("memory_only", "Memory Only mode",
+ RadioSettingValueBoolean(_settings.memory_only)))
+
+ opts = ["off"] + [ "%0.1f" % (t / 60.0) for t in range(30, 750, 30) ]
+ basic.append( RadioSetting("apo", "APO time (hrs)",
+ RadioSettingValueList(opts, opts[_settings.apo])))
+
+ basic.append( RadioSetting("beep_scan", "Beep: Scan",
+ RadioSettingValueBoolean(_settings.beep_scan)))
+
+ basic.append( RadioSetting("beep_edge", "Beep: Edge",
+ RadioSettingValueBoolean(_settings.beep_edge)))
+
+ basic.append( RadioSetting("beep_key", "Beep: Key",
+ RadioSettingValueBoolean(_settings.beep_key)))
+
+ opts = ["T/RX Normal", "RX Reverse", "TX Reverse", "T/RX Reverse"]
+ basic.append( RadioSetting("dcs_polarity", "DCS polarity",
+ RadioSettingValueList(opts, opts[_settings.dcs_polarity])))
+
+ opts = ["off", "dim 1", "dim 2", "dim 3"]
+ basic.append( RadioSetting("dimmer", "Dimmer",
+ RadioSettingValueList(opts, opts[_settings.dimmer])))
+
+ opts = ["manual", "auto", "1-auto"]
+ basic.append( RadioSetting("hyper_write", "Hyper Write",
+ RadioSettingValueList(opts, opts[_settings.hyper_write])))
+
+ opts = ["", "key", "dial", "key+dial", "ptt",
+ "ptt+key", "ptt+dial", "all"]
+ basic.append( RadioSetting("lock", "Lock mode",
+ RadioSettingValueList(opts, opts[_settings.lock])))
+
+ opts = ["MH-42", "MH-48"]
+ basic.append( RadioSetting("microphone_type", "Microphone Type",
+ RadioSettingValueList(opts, opts[_settings.microphone_type])))
+
+ opts = ["off"] + ["S-%d" % n for n in range(2, 10) ] + ["S-Full"]
+ basic.append( RadioSetting("rf_sql", "RF Squelch",
+ RadioSettingValueList(opts, opts[_settings.rf_sql])))
+
+ opts = ["time", "hold", "busy"]
+ basic.append( RadioSetting("scan_resume", "Scan Resume",
+ RadioSettingValueList(opts, opts[_settings.scan_resume])))
+
+ opts = ["single", "continuous"]
+ basic.append( RadioSetting("smart_search", "Smart Search",
+ RadioSettingValueList(opts, opts[_settings.smart_search])))
+
+ opts = ["off"] + [ "%d" % t for t in range(1, 31) ]
+ basic.append( RadioSetting("tot", "Time-out timer (mins)",
+ RadioSettingValueList(opts, opts[_settings.tot])))
+
+ # dtmf tab
+
+ opts = ["50", "100", "250", "450", "750", "1000"]
+ dtmf.append( RadioSetting("dtmf_delay", "DTMF delay (ms)",
+ RadioSettingValueList(opts, opts[_settings.dtmf_delay])))
+
+ opts = ["50", "75", "100"]
+ dtmf.append( RadioSetting("dtmf_speed", "DTMF speed (ms)",
+ RadioSettingValueList(opts, opts[_settings.dtmf_speed])))
+
+ for i in range(16):
+ name = "dtmf%02d" % i
+ dtmfsetting = self._memobj.dtmf[i]
+ dtmfstr = ""
+ for c in dtmfsetting.memory:
+ if c == 0xFF:
+ break
+ if c < len(DTMFCHARSET):
+ dtmfstr += DTMFCHARSET[c]
+ if CHIRP_DEBUG:
+ print dtmfstr
+ dtmfentry = RadioSettingValueString(0, 16, dtmfstr)
+ dtmfentry.set_charset(DTMFCHARSET + list(" "))
+ rs = RadioSetting(name, name.upper(), dtmfentry)
+ dtmf.append(rs)
+
+ # arts tab
+
+ opts = ["off", "in range", "always"]
+ arts.append( RadioSetting("arts_mode", "ARTS beep",
+ RadioSettingValueList(opts, opts[_settings.arts_mode])))
+
+ opts = ["15", "25"]
+ arts.append( RadioSetting("arts_interval", "ARTS interval",
+ RadioSettingValueList(opts, opts[_settings.arts_interval])))
+
+ arts.append( RadioSetting("arts_cwid_enable", "CW ID",
+ RadioSettingValueBoolean(_settings.arts_cwid_enable)))
+
+ _arts_cwid = self._memobj.arts_cwid
+ cwid = RadioSettingValueString(0, 16,
+ self._decode_chars(_arts_cwid.get_value()))
+ cwid.set_charset(CHARSET)
+ arts.append( RadioSetting("arts_cwid", "CW ID", cwid ))
+
+ # prog buttons
+
+ opts = ["WX", "Reverse", "Repeater", "SQL Off", "Lock", "Dimmer"]
+ prog.append( RadioSetting("prog_panel_acc", "Prog Panel - Low(ACC)",
+ RadioSettingValueList(opts, opts[_settings.prog_panel_acc])))
+
+ opts = ["Reverse", "Home"]
+ prog.append( RadioSetting("prog_tone_vm", "TONE | V/M",
+ RadioSettingValueList(opts, opts[_settings.prog_tone_vm])))
+
+ opts = ["" for n in range(26)] + \
+ ["Priority", "Low", "Tone", "MHz", "Reverse", "Home", "Band",
+ "VFO/MR", "Scan", "Sql Off", "TCall", "SSCH", "ARTS", "Tone Freq",
+ "DCSC", "WX", "Repeater" ]
+
+ prog.append( RadioSetting("prog_p1", "P1",
+ RadioSettingValueList(opts, opts[_settings.prog_p1])))
+
+ prog.append( RadioSetting("prog_p2", "P2",
+ RadioSettingValueList(opts, opts[_settings.prog_p2])))
+
+ prog.append( RadioSetting("prog_p3", "P3",
+ RadioSettingValueList(opts, opts[_settings.prog_p3])))
+
+ prog.append( RadioSetting("prog_p4", "P4",
+ RadioSettingValueList(opts, opts[_settings.prog_p4])))
+
+ return top
+
+ def set_settings(self, uisettings):
+ for element in uisettings:
+ if not isinstance(element, RadioSetting):
+ self.set_settings(element)
+ continue
+ if not element.changed():
+ continue
+ try:
+ _settings = self._memobj.settings
+ setting = element.get_name()
+ if re.match('dtmf\d', setting):
+ # set dtmf fields
+ dtmfstr = str(element.value).strip()
+ newval = []
+ for i in range(0,16):
+ if i < len(dtmfstr):
+ newval.append(DTMFCHARSET.index(dtmfstr[i]))
+ else:
+ newval.append(0xFF)
+ if CHIRP_DEBUG:
+ print newval
+ idx = int(setting[-2:])
+ _settings = self._memobj.dtmf[idx]
+ _settings.memory = newval
+ continue
+ if setting == "arts_cwid":
+ oldval = self._memobj.arts_cwid
+ newval = self._encode_chars(newval.get_value(), 6)
+ self._memobj.arts_cwid = newval
+ continue
+ # normal settings
+ newval = element.value
+ oldval = getattr(_settings, setting)
+ if CHIRP_DEBUG:
+ print "Setting %s(%s) <= %s" % (setting,
+ oldval, newval)
+ setattr(_settings, setting, newval)
+ except Exception, e:
+ print element.get_name()
+ raise
MEM_FORMAT_8800 = """
#seekto 0x%X;
@@ -473,7 +780,7 @@ u8 checksum;
"""
class FT8800BankModel(FT7800BankModel):
- def get_num_banks(self):
+ def get_num_mappings(self):
return 10
@directory.register
@@ -489,6 +796,31 @@ class FT8800Radio(FTx800Radio):
_memstart = 0x0000
+ @classmethod
+ def get_prompts(cls):
+ rp = chirp_common.RadioPrompts()
+ rp.pre_download = _(dedent("""\
+ 1. Turn radio off.
+ 2. Connect cable to DATA jack.
+ 3. Press and hold in the "left" [V/M] key while turning the
+ radio on.
+ 4. Rotate the "right" DIAL knob to select "CLONE START".
+ 5. Press the [SET] key. The display will disappear
+ for a moment, then the "CLONE" notation will appear.
+ 6. <b>After clicking OK</b>, press the "left" [V/M] key to
+ send image."""))
+ rp.pre_upload = _(dedent("""\
+ 1. Turn radio off.
+ 2. Connect cable to DATA jack.
+ 3. Press and hold in the "left" [V/M] key while turning the
+ radio on.
+ 4. Rotate the "right" DIAL knob to select "CLONE START".
+ 5. Press the [SET] key. The display will disappear
+ for a moment, then the "CLONE" notation will appear.
+ 6. Press the "left" [LOW] key ("CLONE -RX-" will appear on
+ the display)."""))
+ return rp
+
def get_features(self):
rf = FTx800Radio.get_features(self)
rf.has_sub_devices = self.VARIANT == ""
@@ -607,6 +939,8 @@ class FT8900Radio(FT8800Radio):
_memsize = 14793
_block_lengths = [8, 14784, 1]
+ MODES = ["FM", "NFM", "AM"]
+
def process_mmap(self):
self._memobj = bitwise.parse(MEM_FORMAT_8900, self._mmap)
@@ -614,7 +948,7 @@ class FT8900Radio(FT8800Radio):
rf = FT8800Radio.get_features(self)
rf.has_sub_devices = False
rf.has_bank = False
- rf.valid_modes = MODES
+ rf.valid_modes = self.MODES
rf.valid_bands = [( 28000000, 29700000),
( 50000000, 54000000),
(108000000, 180000000),
diff --git a/chirp/ft817.py b/chirp/ft817.py
index 7f877e9..de73ff5 100644
--- a/chirp/ft817.py
+++ b/chirp/ft817.py
@@ -22,6 +22,7 @@ from chirp.settings import RadioSetting, RadioSettingGroup, \
RadioSettingValueInteger, RadioSettingValueList, \
RadioSettingValueBoolean, RadioSettingValueString
import time, os
+from textwrap import dedent
CMD_ACK = 0x06
@@ -31,6 +32,7 @@ class FT817Radio(yaesu_clone.YaesuCloneModeRadio):
BAUD_RATE = 9600
MODEL = "FT-817"
_model = ""
+ _US_model = False
DUPLEX = ["", "-", "+", "split"]
# narrow modes has to be at end
@@ -46,7 +48,8 @@ class FT817Radio(yaesu_clone.YaesuCloneModeRadio):
(76000000, 108000000), (108000000, 137000000),
(137000000, 154000000), (420000000, 470000000)]
- CHARSET = [chr(x) for x in range(0, 256)]
+ CHARSET = list(chirp_common.CHARSET_ASCII)
+ CHARSET.remove("\\")
# Hi not used in memory
POWER_LEVELS = [chirp_common.PowerLevel("Hi", watts=5.00),
@@ -159,12 +162,12 @@ class FT817Radio(yaesu_clone.YaesuCloneModeRadio):
pkt_mic:7;
u8 unknown14:1,
pkt9600_mic:7;
- ul16 dig_shift;
- ul16 dig_disp;
- u8 r_lsb_car;
- u8 r_usb_car;
- u8 t_lsb_car;
- u8 t_usb_car;
+ il16 dig_shift;
+ il16 dig_disp;
+ i8 r_lsb_car;
+ i8 r_usb_car;
+ i8 t_lsb_car;
+ i8 t_usb_car;
u8 unknown15:2,
menu_item:6;
u8 unknown16:4,
@@ -271,9 +274,33 @@ class FT817Radio(yaesu_clone.YaesuCloneModeRadio):
SPECIAL_MEMORIES_REV = dict(zip(SPECIAL_MEMORIES.values(),
SPECIAL_MEMORIES.keys()))
-
- def _read(self, block, blocknum):
- for _i in range(0, 60):
+
+ @classmethod
+ def get_prompts(cls):
+ rp = chirp_common.RadioPrompts()
+ rp.pre_download = _(dedent("""\
+ 1. Turn radio off.
+ 2. Connect cable to ACC jack.
+ 3. Press and hold in the [MODE <] and [MODE >] keys while
+ turning the radio on ("CLONE MODE" will appear on the
+ display).
+ 4. <b>After clicking OK</b>, press the [A] key to send image."""))
+ rp.pre_upload = _(dedent("""\
+ 1. Turn radio off.
+ 2. Connect cable to ACC jack.
+ 3. Press and hold in the [MODE <] and [MODE >] keys while
+ turning the radio on ("CLONE MODE" will appear on the
+ display).
+ 4. Press the [C] key ("RX" will appear on the LCD)."""))
+ return rp
+
+ def _read(self, block, blocknum, lastblock):
+ # be very patient at first block
+ if blocknum == 0:
+ attempts = 60
+ else:
+ attempts = 5
+ for _i in range(0, attempts):
data = self.pipe.read(block+2)
if data:
break
@@ -287,7 +314,13 @@ class FT817Radio(yaesu_clone.YaesuCloneModeRadio):
checksum.get_calculated(data), blocknum))
data = data[1:block+1] # Chew away the block number and the checksum
else:
- raise Exception("Unable to read block %02X expected %i got %i" %
+ if lastblock and self._US_model:
+ raise Exception(_("Unable to read last block. "
+ "This often happens when the selected model is US "
+ "but the radio is a non-US one (or widebanded). "
+ "Please choose the correct model and try again."))
+ else:
+ raise Exception("Unable to read block %02X expected %i got %i" %
(blocknum, block+2, len(data)))
if os.getenv("CHIRP_DEBUG"):
@@ -303,8 +336,9 @@ class FT817Radio(yaesu_clone.YaesuCloneModeRadio):
data = ""
blocks = 0
status = chirp_common.Status()
- status.msg = "Cloning from radio"
- status.max = len(self._block_lengths) + 39
+ status.msg = _("Cloning from radio")
+ nblocks = len(self._block_lengths) + 39
+ status.max = nblocks
for block in self._block_lengths:
if blocks == 8:
# repeated read of 40 block same size (memory area)
@@ -312,11 +346,22 @@ class FT817Radio(yaesu_clone.YaesuCloneModeRadio):
else:
repeat = 1
for _i in range(0, repeat):
- data += self._read(block, blocks)
+ data += self._read(block, blocks, blocks == nblocks-1)
self.pipe.write(chr(CMD_ACK))
blocks += 1
status.cur = blocks
self.status_fn(status)
+
+ if not self._US_model:
+ status.msg = _("Clone completed, checking for spurious bytes")
+ self.status_fn(status)
+ 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."))
+
print "Clone completed in %i seconds" % (time.time() - start)
@@ -329,7 +374,7 @@ class FT817Radio(yaesu_clone.YaesuCloneModeRadio):
blocks = 0
pos = 0
status = chirp_common.Status()
- status.msg = "Cloning to radio"
+ status.msg = _("Cloning to radio")
status.max = len(self._block_lengths) + 39
for block in self._block_lengths:
if blocks == 8:
@@ -359,7 +404,7 @@ class FT817Radio(yaesu_clone.YaesuCloneModeRadio):
if not buf or buf[0] != chr(CMD_ACK):
if os.getenv("CHIRP_DEBUG"):
print util.hexprint(buf)
- raise Exception("Radio did not ack block %i" % blocks)
+ raise Exception(_("Radio did not ack block %i") % blocks)
pos += block
blocks += 1
status.cur = blocks
@@ -459,21 +504,21 @@ class FT817Radio(yaesu_clone.YaesuCloneModeRadio):
self.LAST_VFOA_INDEX - 1,
-1):
_mem = self._memobj.vfoa[-self.LAST_VFOA_INDEX + mem.number]
- immutable = ["number", "skip", "rtone", "ctone", "extd_number",
+ immutable = ["number", "skip", "extd_number",
"name", "dtcs_polarity", "power", "comment"]
elif mem.number in range(self.FIRST_VFOB_INDEX,
self.LAST_VFOB_INDEX - 1,
-1):
_mem = self._memobj.vfob[-self.LAST_VFOB_INDEX + mem.number]
- immutable = ["number", "skip", "rtone", "ctone", "extd_number",
+ immutable = ["number", "skip", "extd_number",
"name", "dtcs_polarity", "power", "comment"]
elif mem.number in range(-2, -6, -1):
_mem = self._memobj.home[5 + mem.number]
- immutable = ["number", "skip", "rtone", "ctone", "extd_number",
- "dtcs_polarity", "power", "comment"]
+ immutable = ["number", "skip", "extd_number",
+ "name", "dtcs_polarity", "power", "comment"]
elif mem.number == -1:
_mem = self._memobj.qmb
- immutable = ["number", "skip", "rtone", "ctone", "extd_number",
+ immutable = ["number", "skip", "extd_number",
"name", "dtcs_polarity", "power", "comment"]
elif mem.number in self.SPECIAL_PMS.values():
bitindex = -self.LAST_PMS_INDEX + mem.number
@@ -556,9 +601,8 @@ class FT817Radio(yaesu_clone.YaesuCloneModeRadio):
mem.number = number
if not used:
mem.empty = True
- if not valid:
- mem.empty = True
- return mem
+ if not valid:
+ return mem
return self._get_memory(mem, _mem)
@@ -613,9 +657,15 @@ class FT817Radio(yaesu_clone.YaesuCloneModeRadio):
if _mem.tag_on_off == 1:
for i in _mem.name:
- if i == "\xFF":
+ if i == 0xFF:
break
- mem.name += self.CHARSET[i]
+ if chr(i) in self.CHARSET:
+ mem.name += chr(i)
+ else:
+ # radio have some graphical chars that are not supported
+ # we replace those with a *
+ print "Replacing char %x with *" % i
+ mem.name += "*"
mem.name = mem.name.rstrip()
else:
mem.name = ""
@@ -681,8 +731,11 @@ class FT817Radio(yaesu_clone.YaesuCloneModeRadio):
_mem.rit = 0 # not supported in chirp
_mem.freq = mem.freq / 10
_mem.offset = mem.offset / 10
+ # there are ft857D that have problems with short labels, see bug #937
+ # some of the radio fill with 0xff and some with blanks
+ # the latter is safe for all ft8x7 radio so why should i do it only for some?
for i in range(0, 8):
- _mem.name[i] = self.CHARSET.index(mem.name.ljust(8)[i])
+ _mem.name[i] = ord(mem.name.ljust(8)[i])
for setting in mem.extra:
setattr(_mem, setting.get_name(), setting.value)
@@ -718,10 +771,10 @@ class FT817Radio(yaesu_clone.YaesuCloneModeRadio):
RadioSettingValueBoolean(_settings.ars_144))
basic.append(rs)
rs = RadioSetting("ars_430", "430 ARS",
- RadioSettingValueBoolean(_settings.ars_144))
+ RadioSettingValueBoolean(_settings.ars_430))
basic.append(rs)
rs = RadioSetting("pkt9600_mic", "Paket 9600 mic level",
- RadioSettingValueInteger(0, 100, _settings.am_mic))
+ RadioSettingValueInteger(0, 100, _settings.pkt9600_mic))
packet.append(rs)
options = ["enable", "disable"]
rs = RadioSetting("disable_amfm_dial", "AM&FM Dial",
@@ -1041,6 +1094,7 @@ class FT817NDUSRadio(FT817Radio):
MODEL = "FT-817ND (US)"
_model = ""
+ _US_model = True
_memsize = 6651
# block 9 (130 Bytes long) is to be repeted 40 times
_block_lengths = [ 2, 40, 208, 182, 208, 182, 198, 53, 130, 118, 130, 130]
diff --git a/chirp/ft857.py b/chirp/ft857.py
index 57db2d0..f86273a 100644
--- a/chirp/ft857.py
+++ b/chirp/ft857.py
@@ -17,6 +17,11 @@
"""FT857 - FT857/US management module"""
from chirp import ft817, chirp_common, errors, directory
+from chirp.settings import RadioSetting, RadioSettingGroup, \
+ RadioSettingValueInteger, RadioSettingValueList, \
+ RadioSettingValueBoolean, RadioSettingValueString
+import os
+from textwrap import dedent
@directory.register
class FT857Radio(ft817.FT817Radio):
@@ -56,6 +61,10 @@ class FT857Radio(ft817.FT817Radio):
(76000000, 108000000), (108000000, 137000000),
(137000000, 164000000), (420000000, 470000000)]
+ CHARSET = list(chirp_common.CHARSET_ASCII)
+ for i in "\\{|}":
+ CHARSET.remove(i)
+
MEM_FORMAT = """
struct mem_struct{
u8 tag_on_off:1,
@@ -97,6 +106,172 @@ class FT857Radio(ft817.FT817Radio):
u8 name[8];
};
+ #seekto 0x00;
+ struct {
+ u16 radioconfig;
+ u8 mem_vfo:2,
+ m_tune:1,
+ home:1,
+ pms_tune:1,
+ qmb:1,
+ mt_qmb:1,
+ vfo_ab:1;
+ u8 unknown;
+ u8 fst:1,
+ lock:1,
+ nb:1,
+ unknown1:2,
+ disp:1,
+ agc:2;
+ u8 vox:1,
+ unknown2:1,
+ bk:1,
+ kyr:1,
+ cw_speed_unit:1,
+ cw_key_rev:1,
+ pwr_meter_mode:2;
+ u8 vfo_b_freq_range:4,
+ vfo_a_freq_range:4;
+ u8 unknown3;
+ u8 disp_mode:2,
+ unknown4:2,
+ disp_contrast:4;
+ u8 unknown5:4,
+ clar_dial_sel:2,
+ beep_tone:2;
+ u8 arts_beep:2,
+ dial_step:1,
+ arts_id:1,
+ unknown6:1,
+ pkt_rate:1,
+ unknown7:2;
+ u8 unknown8:2,
+ lock_mode:2,
+ unknown9:1,
+ cw_pitch:3;
+ u8 sql_rf_gain:1,
+ ars_144:1,
+ ars_430:1,
+ cw_weight:5;
+ u8 cw_delay;
+ u8 cw_delay_hi:1
+ cw_sidetone:7;
+ u8 unknown10:2,
+ cw_speed:6;
+ u8 disable_amfm_dial:1,
+ vox_gain:7;
+ u8 cat_rate:2,
+ emergency:1,
+ vox_delay:5;
+ u8 dig_mode:3,
+ mem_group:1,
+ unknown11:1,
+ apo_time:3;
+ u8 dcs_inv:2,
+ unknown12:1,
+ tot_time:5;
+ u8 mic_scan:1,
+ ssb_mic:7;
+ u8 cw_paddle:1,
+ am_mic:7;
+ u8 unknown13:1,
+ fm_mic:7;
+ u8 unknown14:1,
+ dig_mic:7;
+ u8 extended_menu:1,
+ pkt1200:7;
+ u8 unknown15:1,
+ pkt9600:7;
+ il16 dig_shift;
+ il16 dig_disp;
+ i8 r_lsb_car;
+ i8 r_usb_car;
+ i8 t_lsb_car;
+ i8 t_usb_car;
+ u8 unknown16:1,
+ menu_item:7;
+ u8 unknown17[5];
+ u8 unknown18:1,
+ mtr_peak_hold:1,
+ mic_sel:2,
+ cat_lin_tun:2,
+ unknown19:1,
+ split_tone:1;
+ u8 unknown20:1,
+ beep_vol:7;
+ u8 unknown21:1,
+ dig_vox:7;
+ u8 ext_menu:1,
+ home_vfo:1,
+ scan_mode:2,
+ scan_resume:4;
+ u8 cw_auto_mode:1,
+ cw_training:2,
+ cw_qsk:3,
+ cw_bfo:2;
+ u8 dsp_nr:4,
+ dsp_bpf:2,
+ dsp_mic_eq:2;
+ u8 unknown22:3,
+ dsp_lpf:5;
+ u8 mtr_atx_sel:3,
+ unknown23:1,
+ dsp_hpf:4;
+ u8 unknown24:2,
+ disp_intensity:2,
+ unknown25:1,
+ disp_color:3;
+ u8 unknown26:1,
+ disp_color_vfo:1,
+ disp_color_mtr:1,
+ disp_color_mode:1,
+ disp_color_memgrp:1,
+ unknown27:1,
+ disp_color_band:1,
+ disp_color_arts:1;
+ u8 unknown28:3,
+ disp_color_fix:5;
+ u8 unknown29:1,
+ nb_level:7;
+ u8 unknown30:1,
+ proc_level:7;
+ u8 unknown31:1,
+ rf_power_hf:7;
+ u8 unknown32:2,
+ tuner_atas:3,
+ mem_vfo_dial_mode:3;
+ u8 pg_a;
+ u8 pg_b;
+ u8 pg_c;
+ u8 pg_acc;
+ u8 pg_p1;
+ u8 pg_p2;
+ u8 unknown33:3,
+ xvtr_sel:2,
+ unknown33_1:2,
+ op_filter1:1;
+ u8 unknown34:6,
+ tx_if_filter:2;
+ u8 unknown35:3,
+ xvtr_a_negative:1,
+ xvtr_b_negative:1,
+ mtr_arx_sel:3;
+ u8 beacon_time;
+ u8 unknown36[2];
+ u8 dig_vox_enable:1,
+ unknown37:2,
+ scope_peakhold:1,
+ scope_width:2,
+ proc:1,
+ unknown38:1;
+ u8 unknown39:1,
+ rf_power_6m:7;
+ u8 unknown40:1,
+ rf_power_vhf:7;
+ u8 unknown41:1,
+ rf_power_uhf:7;
+ } settings;
+
#seekto 0x54;
struct mem_struct vfoa[16];
struct mem_struct vfob[16];
@@ -117,11 +292,29 @@ class FT857Radio(ft817.FT817Radio):
struct mem_struct memory[200];
struct mem_struct pms[10];
+ #seekto 0x1bf3;
+ u8 arts_idw[10];
+ u8 beacon_text1[40];
+ u8 beacon_text2[40];
+ u8 beacon_text3[40];
+ u32 xvtr_a_offset;
+ u32 xvtr_b_offset;
+ u8 op_filter1_name[4];
+ u8 op_filter2_name[4];
+
#seekto 0x1CAD;
struct mem_struct sixtymeterchannels[5];
"""
+ _CALLSIGN_CHARSET = [chr(x) for x in range(ord("0"), ord("9")+1) +
+ range(ord("A"), ord("Z")+1)] + [" ", "/"]
+ _CALLSIGN_CHARSET_REV = dict(zip(_CALLSIGN_CHARSET,
+ range(0,len(_CALLSIGN_CHARSET))))
+ _BEACON_CHARSET = _CALLSIGN_CHARSET + ["+", "."]
+ _BEACON_CHARSET_REV = dict(zip(_BEACON_CHARSET,
+ range(0,len(_BEACON_CHARSET))))
+
# WARNING Index are hard wired in memory management code !!!
SPECIAL_MEMORIES = {
"VFOa-1.8M" : -37,
@@ -186,6 +379,74 @@ class FT857Radio(ft817.FT817Radio):
SPECIAL_MEMORIES_REV = dict(zip(SPECIAL_MEMORIES.values(),
SPECIAL_MEMORIES.keys()))
+ FILTERS = [ "CFIL", "FIL1", "FIL2" ]
+ PROGRAMMABLEOPTIONS = [
+ "MFa:A/B", "MFa:A=B", "MFa:SPL",
+ "MFb:MW", "MFb:SKIP/MCLR", "MFb:TAG",
+ "MFc:STO", "MFc:RCL", "MFc:PROC",
+ "MFd:RPT", "MFd:REV", "MFd:VOX",
+ "MFe:TON/ENC", "MFe:TON/DEC", "MFe:TDCH",
+ "MFf:ARTS", "MFf:SRCH", "MFf:PMS",
+ "MFg:SCN", "MFg:PRI", "MFg:DW",
+ "MFh:SCOP", "MFh:WID", "MFh:STEP",
+ "MFi:MTR", "MFi:SWR", "MFi:DISP",
+ "MFj:SPOT", "MFj:BK", "MFj:KYR",
+ "MFk:TUNE", "MFk:DOWN", "MFk:UP",
+ "MFl:NB", "MFl:AGC", "MFl:AGC SEL",
+ "MFm:IPO", "MFm:ATT", "MFm:NAR",
+ "MFn:CFIL", "MFn:FIL1", "MFn:FIL2",
+ "MFo:PLY1", "MFo:PLY2", "MFo:PLY3",
+ "MFp:DNR", "MFp:DNF", "MFp:DBF",
+ "01:EXT MENU", "02:144MHz ARS", "03:430MHz ARS",
+ "04:AM&FM DIAL", "05:AM MIC GAIN", "06:AM STEP",
+ "07:APO TIME", "08:ARTS BEEP", "09:ARTS ID",
+ "10:ARTS IDW", "11:BEACON TEXT", "12:BEACON TIME",
+ "13:BEEP TONE", "14:BEEP VOL", "15:CAR LSB R",
+ "16:CAR LSB T", "17:CAR USB R", "18:CAR USB T",
+ "19:CAT RATE", "20:CAT/LIN/TUN", "21:CLAR DIAL SEL",
+ "22:CW AUTO MODE", "23:CW BFO", "24:CW DELAY",
+ "25:CW KEY REV", "26:CW PADDLE", "27:CW PITCH",
+ "28:CW QSK", "29:CW SIDE TONE", "30:CW SPEED",
+ "31:CW TRAINING", "32:CW WEIGHT", "33:DCS CODE",
+ "34:DCS INV", "35:DIAL STEP", "36:DIG DISP",
+ "37:DIG GAIN", "38:DIG MODE", "39:DIG SHIFT",
+ "40:DIG VOX", "41:DISP COLOR", "42:DISP CONTRAST",
+ "43:DISP INTENSITY", "44:DISP MODE", "45:DSP BPF WIDTH",
+ "46:DSP HPF CUTOFF", "47:DSP LPF CUTOFF","48:DSP MIC EQ",
+ "49:DSP NR LEVEL", "50:EMERGENCY", "51:FM MIC GAIN",
+ "52:FM STEP", "53:HOME->VFO", "54:LOCK MODE",
+ "55:MEM GROUP", "56:MEM TAG", "57:MEM/VFO DIAL MODE",
+ "58:MIC SCAN", "59:MIC SEL", "60:MTR ARX",
+ "61:MTR ATX", "62:MTR PEAK HOLD", "63:NB LEVEL",
+ "64:OP FILTER", "71:PKT 1200", "72:PKT 9600",
+ "73:PKT RATE", "74:PROC LEVEL", "75:RF POWER SET",
+ "76:RPT SHIFT", "77:SCAN MODE", "78:SCAN RESUME",
+ "79:SPLIT TONE", "80:SQL/RF GAIN", "81:SSB MIC GAIN",
+ "82:SSB STEP", "83:TONE FREQ", "84:TX TIME",
+ "85:TUNER/ATAS", "86:TX IF FILTER", "87:VOX DELAY",
+ "88:VOX GAIN", "89:XVTR A FREQ", "90:XVTR B FREQ",
+ "91:XVTR SEL",
+ "MONI", "Q.SPL", "TCALL", "ATC", "USER"]
+
+ @classmethod
+ def get_prompts(cls):
+ rp = chirp_common.RadioPrompts()
+ rp.pre_download = _(dedent("""\
+ 1. Turn radio off.
+ 2. Connect cable to CAT/LINEAR jack.
+ 3. Press and hold in the [MODE <] and [MODE >] keys while
+ turning the radio on ("CLONE MODE" will appear on the
+ display).
+ 4. <b>After clicking OK</b>, press the [C](SEND) key to send image."""))
+ rp.pre_upload = _(dedent("""\
+ 1. Turn radio off.
+ 2. Connect cable to ACC jack.
+ 3. Press and hold in the [MODE <] and [MODE >] keys while
+ turning the radio on ("CLONE MODE" will appear on the
+ display).
+ 4. Press the [A](RCV) key ("receiving" will appear on the LCD)."""))
+ return rp
+
def get_features(self):
rf = ft817.FT817Radio.get_features(self)
rf.has_cross = True
@@ -193,7 +454,6 @@ class FT857Radio(ft817.FT817Radio):
rf.has_rx_dtcs = True
rf.valid_tmodes = self.TMODES_REV.keys()
rf.valid_cross_modes = self.CROSS_MODES_REV.keys()
- rf.has_settings = False # not implemented yet
return rf
def _get_duplex(self, mem, _mem):
@@ -256,6 +516,510 @@ class FT857Radio(ft817.FT817Radio):
# dunno if there's the same problem here but to be safe ...
_mem.unknown_rxtoneflag = 0
+ def get_settings(self):
+ _settings = self._memobj.settings
+ basic = RadioSettingGroup("basic", "Basic")
+ cw = RadioSettingGroup("cw", "CW")
+ packet = RadioSettingGroup("packet", "Digital & packet")
+ panel = RadioSettingGroup("panel", "Panel settings")
+ extended = RadioSettingGroup("extended", "Extended")
+ panelcontr = RadioSettingGroup("panelcontr", "Panel controls")
+ top = RadioSettingGroup("top", "All Settings", basic, cw, packet,
+ panelcontr, panel, extended)
+
+ rs = RadioSetting("extended_menu", "Extended menu",
+ RadioSettingValueBoolean(_settings.extended_menu))
+ extended.append(rs)
+ rs = RadioSetting("ars_144", "144MHz ARS",
+ RadioSettingValueBoolean(_settings.ars_144))
+ basic.append(rs)
+ rs = RadioSetting("ars_430", "430MHz ARS",
+ RadioSettingValueBoolean(_settings.ars_430))
+ basic.append(rs)
+ options = ["enable", "disable"]
+ rs = RadioSetting("disable_amfm_dial", "AM&FM Dial",
+ RadioSettingValueList(options,
+ options[_settings.disable_amfm_dial]))
+ panel.append(rs)
+ rs = RadioSetting("am_mic", "AM mic gain",
+ RadioSettingValueInteger(0, 100, _settings.am_mic))
+ basic.append(rs)
+ options = ["OFF", "1h", "2h", "3h", "4h", "5h", "6h"]
+ rs = RadioSetting("apo_time", "APO time",
+ RadioSettingValueList(options,
+ options[_settings.apo_time]))
+ basic.append(rs)
+ options = ["OFF", "Range", "All"]
+ rs = RadioSetting("arts_beep", "ARTS beep",
+ RadioSettingValueList(options,
+ options[_settings.arts_beep]))
+ basic.append(rs)
+ rs = RadioSetting("arts_id", "ARTS ID",
+ RadioSettingValueBoolean(_settings.arts_id))
+ extended.append(rs)
+ s = RadioSettingValueString(0, 10,
+ ''.join([self._CALLSIGN_CHARSET[x] for x in
+ self._memobj.arts_idw]))
+ s.set_charset(self._CALLSIGN_CHARSET)
+ rs = RadioSetting("arts_idw", "ARTS IDW", s)
+ extended.append(rs)
+ s = RadioSettingValueString(0, 40,
+ ''.join([self._BEACON_CHARSET[x] for x in
+ self._memobj.beacon_text1]))
+ s.set_charset(self._BEACON_CHARSET)
+ rs = RadioSetting("beacon_text1", "Beacon text1", s)
+ extended.append(rs)
+ s = RadioSettingValueString(0, 40,
+ ''.join([self._BEACON_CHARSET[x] for x in
+ self._memobj.beacon_text2]))
+ s.set_charset(self._BEACON_CHARSET)
+ rs = RadioSetting("beacon_text2", "Beacon text2", s)
+ extended.append(rs)
+ s = RadioSettingValueString(0, 40,
+ ''.join([self._BEACON_CHARSET[x] for x in
+ self._memobj.beacon_text3]))
+ s.set_charset(self._BEACON_CHARSET)
+ rs = RadioSetting("beacon_text3", "Beacon text3", s)
+ extended.append(rs)
+ options = ["OFF"]+["%i sec" % i for i in range(1,256)]
+ rs = RadioSetting("beacon_time", "Beacon time",
+ RadioSettingValueList(options,
+ options[_settings.beacon_time]))
+ extended.append(rs)
+ options = ["440Hz", "880Hz", "1760Hz"]
+ rs = RadioSetting("beep_tone", "Beep tone",
+ RadioSettingValueList(options,
+ options[_settings.beep_tone]))
+ panel.append(rs)
+ rs = RadioSetting("beep_vol", "Beep volume",
+ RadioSettingValueInteger(0, 100, _settings.beep_vol))
+ panel.append(rs)
+ rs = RadioSetting("r_lsb_car", "LSB Rx carrier point (*10 Hz)",
+ RadioSettingValueInteger(-30, 30, _settings.r_lsb_car))
+ extended.append(rs)
+ rs = RadioSetting("r_usb_car", "USB Rx carrier point (*10 Hz)",
+ RadioSettingValueInteger(-30, 30, _settings.r_usb_car))
+ extended.append(rs)
+ rs = RadioSetting("t_lsb_car", "LSB Tx carrier point (*10 Hz)",
+ RadioSettingValueInteger(-30, 30, _settings.t_lsb_car))
+ extended.append(rs)
+ rs = RadioSetting("t_usb_car", "USB Tx carrier point (*10 Hz)",
+ RadioSettingValueInteger(-30, 30, _settings.t_usb_car))
+ extended.append(rs)
+ options = ["4800", "9600", "38400"]
+ rs = RadioSetting("cat_rate", "CAT rate",
+ RadioSettingValueList(options,
+ options[_settings.cat_rate]))
+ basic.append(rs)
+ options = ["CAT", "Linear", "Tuner"]
+ rs = RadioSetting("cat_lin_tun", "CAT/LIN/TUN selection",
+ RadioSettingValueList(options,
+ options[_settings.cat_lin_tun]))
+ extended.append(rs)
+ options = ["MAIN", "VFO/MEM", "CLAR"] # TODO test the 3 options on non D radio
+ # which have only SEL and MAIN
+ rs = RadioSetting("clar_dial_sel", "Clarifier dial selection",
+ RadioSettingValueList(options,
+ options[_settings.clar_dial_sel]))
+ panel.append(rs)
+ rs = RadioSetting("cw_auto_mode", "CW Automatic mode",
+ RadioSettingValueBoolean(_settings.cw_auto_mode))
+ cw.append(rs)
+ options = ["USB", "LSB", "AUTO"]
+ rs = RadioSetting("cw_bfo", "CW BFO",
+ RadioSettingValueList(options,
+ options[_settings.cw_bfo]))
+ cw.append(rs)
+ options = ["FULL"]+["%i ms" % (i*10) for i in range(3,301)]
+ val = (_settings.cw_delay + _settings.cw_delay_hi * 256) - 2
+ rs = RadioSetting("cw_delay", "CW delay",
+ RadioSettingValueList(options,
+ options[val]))
+ cw.append(rs)
+ options = ["Normal", "Reverse"]
+ rs = RadioSetting("cw_key_rev", "CW key reverse",
+ RadioSettingValueList(options,
+ options[_settings.cw_key_rev]))
+ cw.append(rs)
+ rs = RadioSetting("cw_paddle", "CW paddle",
+ RadioSettingValueBoolean(_settings.cw_paddle))
+ cw.append(rs)
+ options = ["%i Hz" % i for i in range(400,801,100)]
+ rs = RadioSetting("cw_pitch", "CW pitch",
+ RadioSettingValueList(options,
+ options[_settings.cw_pitch]))
+ cw.append(rs)
+ options = ["%i ms" % i for i in range(5,31,5)]
+ rs = RadioSetting("cw_qsk", "CW QSK",
+ RadioSettingValueList(options,
+ options[_settings.cw_qsk]))
+ cw.append(rs)
+ rs = RadioSetting("cw_sidetone", "CW sidetone volume",
+ RadioSettingValueInteger(0, 100, _settings.cw_sidetone))
+ cw.append(rs)
+ options = ["%i wpm" % i for i in range(4,61)]
+ rs = RadioSetting("cw_speed", "CW speed",
+ RadioSettingValueList(options,
+ options[_settings.cw_speed]))
+ cw.append(rs)
+ options = ["Numeric", "Alphabet", "AlphaNumeric"]
+ rs = RadioSetting("cw_training", "CW trainig",
+ RadioSettingValueList(options,
+ options[_settings.cw_training]))
+ cw.append(rs)
+ options = ["1:%1.1f" % (i/10) for i in range(25,46,1)]
+ rs = RadioSetting("cw_weight", "CW weight",
+ RadioSettingValueList(options,
+ options[_settings.cw_weight]))
+ cw.append(rs)
+ options = ["Tn-Rn", "Tn-Riv", "Tiv-Rn", "Tiv-Riv"]
+ rs = RadioSetting("dcs_inv", "DCS inv",
+ RadioSettingValueList(options,
+ options[_settings.dcs_inv]))
+ extended.append(rs)
+ options = ["Fine", "Coarse"]
+ rs = RadioSetting("dial_step", "Dial step",
+ RadioSettingValueList(options,
+ options[_settings.dial_step]))
+ panel.append(rs)
+ rs = RadioSetting("dig_disp", "Dig disp (*10 Hz)",
+ RadioSettingValueInteger(-300, 300, _settings.dig_disp))
+ packet.append(rs)
+ rs = RadioSetting("dig_mic", "Dig gain",
+ RadioSettingValueInteger(0, 100, _settings.dig_mic))
+ packet.append(rs)
+ options = ["RTTYL", "RTTYU", "PSK31-L", "PSK31-U", "USER-L", "USER-U"]
+ rs = RadioSetting("dig_mode", "Dig mode",
+ RadioSettingValueList(options,
+ options[_settings.dig_mode]))
+ packet.append(rs)
+ rs = RadioSetting("dig_shift", "Dig shift (*10 Hz)",
+ RadioSettingValueInteger(-300, 300, _settings.dig_shift))
+ packet.append(rs)
+ rs = RadioSetting("dig_vox", "Dig vox",
+ RadioSettingValueInteger(0, 100, _settings.dig_vox))
+ packet.append(rs)
+ options = ["ARTS", "BAND", "FIX", "MEMGRP", "MODE", "MTR", "VFO"]
+ rs = RadioSetting("disp_color", "Display color mode",
+ RadioSettingValueList(options,
+ options[_settings.disp_color]))
+ panel.append(rs)
+ rs = RadioSetting("disp_color_arts", "Display color ARTS set",
+ RadioSettingValueInteger(0, 1,_settings.disp_color_arts))
+ panel.append(rs)
+ rs = RadioSetting("disp_color_band", "Display color band set",
+ RadioSettingValueInteger(0, 1,_settings.disp_color_band))
+ panel.append(rs)
+ rs = RadioSetting("disp_color_memgrp", "Display color memory group set",
+ RadioSettingValueInteger(0, 1,_settings.disp_color_memgrp))
+ panel.append(rs)
+ rs = RadioSetting("disp_color_mode", "Display color mode set",
+ RadioSettingValueInteger(0, 1,_settings.disp_color_mode))
+ panel.append(rs)
+ rs = RadioSetting("disp_color_mtr", "Display color meter set",
+ RadioSettingValueInteger(0, 1,_settings.disp_color_mtr))
+ panel.append(rs)
+ rs = RadioSetting("disp_color_vfo", "Display color VFO set",
+ RadioSettingValueInteger(0, 1,_settings.disp_color_vfo))
+ panel.append(rs)
+ rs = RadioSetting("disp_color_fix", "Display color fix set",
+ RadioSettingValueInteger(1, 32,_settings.disp_color_fix+1))
+ panel.append(rs)
+ rs = RadioSetting("disp_contrast", "Contrast",
+ RadioSettingValueInteger(3, 15,_settings.disp_contrast+2))
+ panel.append(rs)
+ rs = RadioSetting("disp_intensity", "Intensity",
+ RadioSettingValueInteger(1, 3,_settings.disp_intensity))
+ panel.append(rs)
+ options = ["OFF", "Auto1", "Auto2", "ON"]
+ rs = RadioSetting("disp_mode", "Display backlight mode",
+ RadioSettingValueList(options,
+ options[_settings.disp_mode]))
+ panel.append(rs)
+ options = ["60Hz", "120Hz", "240Hz"]
+ rs = RadioSetting("dsp_bpf", "Dsp band pass filter",
+ RadioSettingValueList(options,
+ options[_settings.dsp_bpf]))
+ cw.append(rs)
+ options = ["100Hz", "160Hz", "220Hz", "280Hz", "340Hz", "400Hz", "460Hz", "520Hz",
+ "580Hz", "640Hz", "720Hz", "760Hz", "820Hz", "880Hz", "940Hz", "1000Hz"]
+ rs = RadioSetting("dsp_hpf", "Dsp hi pass filter cut off",
+ RadioSettingValueList(options,
+ options[_settings.dsp_hpf]))
+ basic.append(rs)
+ options = ["1000Hz", "1160Hz", "1320Hz", "1480Hz", "1650Hz", "1800Hz", "1970Hz", "2130Hz",
+ "2290Hz", "2450Hz", "2610Hz", "2770Hz", "2940Hz", "3100Hz", "3260Hz", "3420Hz",
+ "3580Hz", "3740Hz", "3900Hz", "4060Hz", "4230Hz", "4390Hz", "4550Hz", "4710Hz",
+ "4870Hz", "5030Hz", "5190Hz", "5390Hz", "5520Hz", "5680Hz", "5840Hz", "6000Hz"]
+ rs = RadioSetting("dsp_lpf", "Dsp low pass filter cut off",
+ RadioSettingValueList(options,
+ options[_settings.dsp_lpf]))
+ basic.append(rs)
+ options = ["OFF", "LPF", "HPF", "BOTH"]
+ rs = RadioSetting("dsp_mic_eq", "Dsp mic equalization",
+ RadioSettingValueList(options,
+ options[_settings.dsp_mic_eq]))
+ basic.append(rs)
+ rs = RadioSetting("dsp_nr", "DSP noise reduction level",
+ RadioSettingValueInteger(1, 16, _settings.dsp_nr+1))
+ basic.append(rs)
+ # emergency only for US model
+ rs = RadioSetting("fm_mic", "FM mic gain",
+ RadioSettingValueInteger(0, 100, _settings.fm_mic))
+ basic.append(rs)
+ rs = RadioSetting("home_vfo", "Enable HOME to VFO moving",
+ RadioSettingValueBoolean(_settings.home_vfo))
+ panel.append(rs)
+ options = ["Dial", "Freq", "Panel", "All"]
+ rs = RadioSetting("lock_mode", "Lock mode",
+ RadioSettingValueList(options,
+ options[_settings.lock_mode]))
+ panel.append(rs)
+ rs = RadioSetting("mem_group", "Mem group",
+ RadioSettingValueBoolean(_settings.mem_group))
+ basic.append(rs)
+ options = ["CW SIDETONE", "CW SPEED", "MHz/MEM GRP", "MIC GAIN",
+ "NB LEVEL", "RF POWER", "STEP"]
+ rs = RadioSetting("mem_vfo_dial_mode", "Mem/VFO dial mode",
+ RadioSettingValueList(options,
+ options[_settings.mem_vfo_dial_mode]))
+ panel.append(rs)
+ rs = RadioSetting("mic_scan", "Mic scan",
+ RadioSettingValueBoolean(_settings.mic_scan))
+ basic.append(rs)
+ options = ["NOR", "RMT", "CAT"]
+ rs = RadioSetting("mic_sel", "Mic selection",
+ RadioSettingValueList(options,
+ options[_settings.mic_sel]))
+ extended.append(rs)
+ options = ["SIG", "CTR", "VLT", "N/A", "FS", "OFF"]
+ rs = RadioSetting("mtr_arx_sel", "Meter receive selection",
+ RadioSettingValueList(options,
+ options[_settings.mtr_arx_sel]))
+ extended.append(rs)
+ options = ["PWR", "ALC", "MOD", "SWR", "VLT", "N/A", "OFF"]
+ rs = RadioSetting("mtr_atx_sel", "Meter transmit selection",
+ RadioSettingValueList(options,
+ options[_settings.mtr_atx_sel]))
+ extended.append(rs)
+ rs = RadioSetting("mtr_peak_hold", "Meter peak hold",
+ RadioSettingValueBoolean(_settings.mtr_peak_hold))
+ extended.append(rs)
+ rs = RadioSetting("nb_level", "Noise blanking level",
+ RadioSettingValueInteger(0, 100, _settings.nb_level))
+ basic.append(rs)
+ s = RadioSettingValueString(0, 4,
+ ''.join([self._CALLSIGN_CHARSET[x] for x in
+ self._memobj.op_filter1_name]))
+ s.set_charset(self._CALLSIGN_CHARSET)
+ rs = RadioSetting("op_filter1_name", "Optional filter1 name", s)
+ extended.append(rs)
+ s = RadioSettingValueString(0, 4,
+ ''.join([self._CALLSIGN_CHARSET[x] for x in
+ self._memobj.op_filter2_name]))
+ s.set_charset(self._CALLSIGN_CHARSET)
+ rs = RadioSetting("op_filter2_name", "Optional filter2 name", s)
+ extended.append(rs)
+ rs = RadioSetting("pg_a", "Programmable key MFq:A",
+ RadioSettingValueList(self.PROGRAMMABLEOPTIONS,
+ self.PROGRAMMABLEOPTIONS[_settings.pg_a]))
+ extended.append(rs)
+ rs = RadioSetting("pg_b", "Programmable key MFq:B",
+ RadioSettingValueList(self.PROGRAMMABLEOPTIONS,
+ self.PROGRAMMABLEOPTIONS[_settings.pg_b]))
+ extended.append(rs)
+ rs = RadioSetting("pg_c", "Programmable key MFq:C",
+ RadioSettingValueList(self.PROGRAMMABLEOPTIONS,
+ self.PROGRAMMABLEOPTIONS[_settings.pg_c]))
+ extended.append(rs)
+ rs = RadioSetting("pg_acc", "Programmable mic key ACC",
+ RadioSettingValueList(self.PROGRAMMABLEOPTIONS,
+ self.PROGRAMMABLEOPTIONS[_settings.pg_acc]))
+ extended.append(rs)
+ rs = RadioSetting("pg_p1", "Programmable mic key P1",
+ RadioSettingValueList(self.PROGRAMMABLEOPTIONS,
+ self.PROGRAMMABLEOPTIONS[_settings.pg_p1]))
+ extended.append(rs)
+ rs = RadioSetting("pg_p2", "Programmable mic key P2",
+ RadioSettingValueList(self.PROGRAMMABLEOPTIONS,
+ self.PROGRAMMABLEOPTIONS[_settings.pg_p2]))
+ extended.append(rs)
+ rs = RadioSetting("pkt1200", "Packet 1200 gain level",
+ RadioSettingValueInteger(0, 100, _settings.pkt1200))
+ packet.append(rs)
+ rs = RadioSetting("pkt9600", "Packet 9600 gain level",
+ RadioSettingValueInteger(0, 100, _settings.pkt9600))
+ packet.append(rs)
+ options = ["1200", "9600"]
+ rs = RadioSetting("pkt_rate", "Packet rate",
+ RadioSettingValueList(options,
+ options[_settings.pkt_rate]))
+ packet.append(rs)
+ rs = RadioSetting("proc_level", "Proc level",
+ RadioSettingValueInteger(0, 100, _settings.proc_level))
+ basic.append(rs)
+ rs = RadioSetting("rf_power_hf", "Rf power set HF",
+ RadioSettingValueInteger(5, 100, _settings.rf_power_hf))
+ basic.append(rs)
+ rs = RadioSetting("rf_power_6m", "Rf power set 6m",
+ RadioSettingValueInteger(5, 100, _settings.rf_power_6m))
+ basic.append(rs)
+ rs = RadioSetting("rf_power_vhf", "Rf power set VHF",
+ RadioSettingValueInteger(5, 50, _settings.rf_power_vhf))
+ basic.append(rs)
+ rs = RadioSetting("rf_power_uhf", "Rf power set UHF",
+ RadioSettingValueInteger(2, 20, _settings.rf_power_uhf))
+ basic.append(rs)
+ options = ["TIME", "BUSY", "STOP"]
+ rs = RadioSetting("scan_mode", "Scan mode",
+ RadioSettingValueList(options,
+ options[_settings.scan_mode]))
+ basic.append(rs)
+ rs = RadioSetting("scan_resume", "Scan resume",
+ RadioSettingValueInteger(1, 10, _settings.scan_resume))
+ basic.append(rs)
+ rs = RadioSetting("split_tone", "Split tone enable",
+ RadioSettingValueBoolean(_settings.split_tone))
+ extended.append(rs)
+ options = ["RF-Gain", "Squelch"]
+ rs = RadioSetting("sql_rf_gain", "Squelch/RF-Gain",
+ RadioSettingValueList(options,
+ options[_settings.sql_rf_gain]))
+ panel.append(rs)
+ rs = RadioSetting("ssb_mic", "SSB Mic gain",
+ RadioSettingValueInteger(0, 100, _settings.ssb_mic))
+ basic.append(rs)
+ options = ["Off"]+["%i" % i for i in range(1, 21)]
+ rs = RadioSetting("tot_time", "Time-out timer",
+ RadioSettingValueList(options,
+ options[_settings.tot_time]))
+ basic.append(rs)
+ options = ["OFF", "ATAS(HF)", "ATAS(HF&50)", "ATAS(ALL)", "TUNER"]
+ rs = RadioSetting("tuner_atas", "Tuner/ATAS device",
+ RadioSettingValueList(options,
+ options[_settings.tuner_atas]))
+ extended.append(rs)
+ rs = RadioSetting("tx_if_filter", "Transmit IF filter",
+ RadioSettingValueList(self.FILTERS,
+ self.FILTERS[_settings.tx_if_filter]))
+ basic.append(rs)
+ rs = RadioSetting("vox_delay", "VOX delay (*100 ms)",
+ RadioSettingValueInteger(1, 30, _settings.vox_delay))
+ basic.append(rs)
+ rs = RadioSetting("vox_gain", "VOX Gain",
+ RadioSettingValueInteger(1, 100, _settings.vox_gain))
+ basic.append(rs)
+ rs = RadioSetting("xvtr_a", "Xvtr A displacement",
+ RadioSettingValueInteger(-4294967295, 4294967295, self._memobj.xvtr_a_offset * (-1 if _settings.xvtr_a_negative else 1)))
+ extended.append(rs)
+ rs = RadioSetting("xvtr_b", "Xvtr B displacement",
+ RadioSettingValueInteger(-4294967295, 4294967295, self._memobj.xvtr_b_offset * (-1 if _settings.xvtr_b_negative else 1)))
+ extended.append(rs)
+ options = ["OFF", "XVTR A", "XVTR B"]
+ rs = RadioSetting("xvtr_sel", "Transverter function selection",
+ RadioSettingValueList(options,
+ options[_settings.xvtr_sel]))
+ extended.append(rs)
+
+ rs = RadioSetting("disp", "Display large",
+ RadioSettingValueBoolean(_settings.disp))
+ panel.append(rs)
+ rs = RadioSetting("nb", "Noise blanker",
+ RadioSettingValueBoolean(_settings.nb))
+ panelcontr.append(rs)
+ options = ["Auto", "Fast", "Slow", "Off"]
+ rs = RadioSetting("agc", "AGC",
+ RadioSettingValueList(options,
+ options[_settings.agc]))
+ panelcontr.append(rs)
+ options = ["PWR", "ALC", "SWR", "MOD"]
+ rs = RadioSetting("pwr_meter_mode", "Power meter mode",
+ RadioSettingValueList(options,
+ options[_settings.pwr_meter_mode]))
+ panelcontr.append(rs)
+ rs = RadioSetting("vox", "Vox",
+ RadioSettingValueBoolean(_settings.vox))
+ panelcontr.append(rs)
+ rs = RadioSetting("bk", "Semi break-in",
+ RadioSettingValueBoolean(_settings.bk))
+ cw.append(rs)
+ rs = RadioSetting("kyr", "Keyer",
+ RadioSettingValueBoolean(_settings.kyr))
+ cw.append(rs)
+ options = ["enabled", "disabled"]
+ rs = RadioSetting("fst", "Fast",
+ RadioSettingValueList(options,
+ options[_settings.fst]))
+ panelcontr.append(rs)
+ options = ["enabled", "disabled"]
+ rs = RadioSetting("lock", "Lock",
+ RadioSettingValueList(options,
+ options[_settings.lock]))
+ panelcontr.append(rs)
+ rs = RadioSetting("scope_peakhold", "Scope max hold",
+ RadioSettingValueBoolean(_settings.scope_peakhold))
+ panelcontr.append(rs)
+ options = ["21", "31", "127"]
+ rs = RadioSetting("scope_width", "Scope width (channels)",
+ RadioSettingValueList(options,
+ options[_settings.scope_width]))
+ panelcontr.append(rs)
+ rs = RadioSetting("proc", "Speech processor",
+ RadioSettingValueBoolean(_settings.proc))
+ panelcontr.append(rs)
+
+ return top
+
+ def set_settings(self, settings):
+ _settings = self._memobj.settings
+ for element in settings:
+ if not isinstance(element, RadioSetting):
+ self.set_settings(element)
+ continue
+ 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 = _settings
+ setting = element.get_name()
+ if os.getenv("CHIRP_DEBUG"):
+ print "Setting %s(%s) <= %s" % (setting,
+ getattr(obj, setting), element.value)
+ if setting == "arts_idw":
+ self._memobj.arts_idw = \
+ [self._CALLSIGN_CHARSET_REV[x] for x in
+ str(element.value)]
+ elif setting in ["beacon_text1", "beacon_text2",
+ "beacon_text3", "op_filter1_name", "op_filter2_name"]:
+ setattr(self._memobj, setting,
+ [self._BEACON_CHARSET_REV[x] for x in
+ str(element.value)])
+ elif setting == "cw_delay":
+ val = int(element.value)+2
+ setattr(obj, "cw_delay_hi", val/256)
+ setattr(obj, setting, val&0xff)
+ elif setting == "dig_vox":
+ val = int(element.value)
+ setattr(obj, "dig_vox_enable", int(val>0))
+ setattr(obj, setting, val)
+ elif setting in ["disp_color_fix", "dsp_nr"]:
+ setattr(obj, setting, int(element.value)-1)
+ elif setting == "disp_contrast":
+ setattr(obj, setting, int(element.value)-2)
+ elif setting in ["xvtr_a", "xvtr_b"]:
+ val = int(element.value)
+ setattr(obj, setting + "_negative", int(val<0))
+ setattr(self._memobj, setting + "_offset", abs(val))
+ else:
+ setattr(obj, setting, element.value)
+ except Exception, e:
+ print element.get_name()
+ raise
+
@directory.register
class FT857USRadio(FT857Radio):
"""Yaesu FT857/897 (US version)"""
@@ -264,6 +1028,7 @@ class FT857USRadio(FT857Radio):
MODEL = "FT-857/897 (US)"
_model = ""
+ _US_model = True
_memsize = 7481
# block 9 (140 Bytes long) is to be repeted 40 times
# should be 42 times but this way I can use original 817 functions
@@ -339,3 +1104,12 @@ class FT857USRadio(FT857Radio):
return self._set_special_60m(memory)
else:
return FT857Radio.set_memory(self, memory)
+
+ def get_settings(self):
+ top = FT857Radio.get_settings(self)
+ basic = top["basic"]
+ rs = RadioSetting("emergency", "Emergency",
+ RadioSettingValueBoolean(self._memobj.settings.emergency))
+ basic.append(rs)
+ return top
+
diff --git a/chirp/ft90.py b/chirp/ft90.py
new file mode 100644
index 0000000..bd5c1d4
--- /dev/null
+++ b/chirp/ft90.py
@@ -0,0 +1,652 @@
+# Copyright 2011 Dan Smith <dsmith at danplanet.com>
+# Copyright 2013 Jens Jensen AF5MI <kd4tjx at yahoo.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 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/>.
+
+from chirp import chirp_common, bitwise, memmap, directory, errors, util, yaesu_clone
+from chirp.settings import RadioSetting, RadioSettingGroup, \
+ RadioSettingValueInteger, RadioSettingValueList, \
+ RadioSettingValueBoolean, RadioSettingValueString
+import time, os, traceback, string, re
+from textwrap import dedent
+
+if os.getenv("CHIRP_DEBUG"):
+ CHIRP_DEBUG = True
+else:
+ CHIRP_DEBUG=False
+
+CMD_ACK = chr(0x06)
+
+FT90_STEPS = [5.0, 10.0, 12.5, 15.0, 20.0, 25.0, 50.0]
+FT90_MODES = ["AM", "FM", "Auto"]
+FT90_TMODES = ["", "Tone", "TSQL", "", "DTCS"] # idx 3 (Bell) not supported yet
+FT90_TONES = list(chirp_common.TONES)
+for tone in [ 165.5, 171.3, 177.3 ]:
+ FT90_TONES.remove(tone)
+FT90_POWER_LEVELS_VHF = [chirp_common.PowerLevel("Hi", watts=50),
+ chirp_common.PowerLevel("Mid1", watts=20),
+ chirp_common.PowerLevel("Mid2", watts=10),
+ chirp_common.PowerLevel("Low", watts=5)]
+
+FT90_POWER_LEVELS_UHF = [chirp_common.PowerLevel("Hi", watts=35),
+ chirp_common.PowerLevel("Mid1", watts=20),
+ chirp_common.PowerLevel("Mid2", watts=10),
+ chirp_common.PowerLevel("Low", watts=5)]
+
+FT90_DUPLEX = ["", "-", "+", "split"]
+FT90_CWID_CHARS = list(string.digits) + list(string.uppercase) + list(" ")
+FT90_DTMF_CHARS = list("0123456789ABCD*#")
+FT90_SPECIAL = [ "vfo_vhf", "home_vhf", "vfo_uhf", "home_uhf", \
+ "pms_1L", "pms_1U", "pms_2L", "pms_2U"]
+
+ at directory.register
+class FT90Radio(yaesu_clone.YaesuCloneModeRadio):
+ VENDOR = "Yaesu"
+ MODEL = "FT-90"
+ ID = "\x8E\xF6"
+
+ _memsize = 4063
+ # block 03 (200 Bytes long) repeats 18 times; channel memories
+ _block_lengths = [ 2, 232, 24 ] + ([200] * 18 ) + [205]
+
+ mem_format = """
+ u16 id;
+ #seekto 0x22;
+ struct {
+ u8 dtmf_active;
+ u8 dtmf1_len;
+ u8 dtmf2_len;
+ u8 dtmf3_len;
+ u8 dtmf4_len;
+ u8 dtmf5_len;
+ u8 dtmf6_len;
+ u8 dtmf7_len;
+ u8 dtmf8_len;
+ u8 dtmf1[8];
+ u8 dtmf2[8];
+ u8 dtmf3[8];
+ u8 dtmf4[8];
+ u8 dtmf5[8];
+ u8 dtmf6[8];
+ u8 dtmf7[8];
+ u8 dtmf8[8];
+ char cwid[7];
+ u8 unk1;
+ u8 scan1:2,
+ beep:1,
+ unk3:3,
+ rfsqlvl:2;
+ u8 unk4:2,
+ scan2:1,
+ cwid_en:1,
+ txnarrow:1,
+ dtmfspeed:1,
+ pttlock:2;
+ u8 dtmftxdelay:3,
+ fancontrol:2,
+ unk5:3;
+ u8 dimmer:3,
+ unk6:1,
+ lcdcontrast:4;
+ u8 dcsmode:2,
+ unk16:2,
+ tot:4;
+ u8 unk14;
+ u8 unk8:1,
+ ars:1,
+ lock:1,
+ txpwrsave:1,
+ apo:4;
+ u8 unk15;
+ u8 unk9:4,
+ key_lt:4;
+ u8 unk10:4,
+ key_rt:4;
+ u8 unk11:4,
+ key_p1:4;
+ u8 unk12:4,
+ key_p2:4;
+ u8 unk13:4,
+ key_acc:4;
+
+ } settings;
+
+ struct mem_struct {
+ u8 mode:2,
+ isUhf1:1,
+ unknown1:2,
+ step:3;
+ u8 artsmode:2,
+ unknown2:1,
+ isUhf2:1
+ power:2,
+ shift:2;
+ u8 skip:1,
+ showname:1,
+ unknown3:1,
+ isUhfHi:1,
+ unknown4:1,
+ tmode:3;
+ u32 rxfreq;
+ u32 txfreqoffset;
+ u8 UseDefaultName:1,
+ ars:1,
+ tone:6;
+ u8 packetmode:1,
+ unknown5:1,
+ dcstone:6;
+ char name[7];
+ };
+
+ #seekto 0x86;
+ struct mem_struct vfo_vhf;
+ struct mem_struct home_vhf;
+ struct mem_struct vfo_uhf;
+ struct mem_struct home_uhf;
+
+ #seekto 0xEB;
+ u8 chan_enable[23];
+
+ #seekto 0x101;
+ struct {
+ u8 pms_2U_enable:1,
+ pms_2L_enable:1,
+ pms_1U_enable:1,
+ pms_1L_enable:1,
+ unknown6:4;
+ } special_enables;
+
+ #seekto 0x102;
+ struct mem_struct memory[180];
+
+ #seekto 0xf12;
+ struct mem_struct pms_1L;
+ struct mem_struct pms_1U;
+ struct mem_struct pms_2L;
+ struct mem_struct pms_2U;
+
+ #seekto 0x0F7B;
+ struct {
+ char demomsg1[50];
+ char demomsg2[50];
+ } demomsg;
+
+ """
+ @classmethod
+ def get_prompts(cls):
+ rp = chirp_common.RadioPrompts()
+ rp.pre_download = _(dedent("""\
+ 1. Turn radio off.
+ 2. Connect mic and hold [ACC] on mic while powering on.
+ ("CLONE" will appear on the display)
+ 3. Replace mic with PC programming cable.
+ 4. <b>After clicking OK</b>, press the [SET] key to send image."""))
+ rp.pre_upload = _(dedent("""\
+ 1. Turn radio off.
+ 2. Connect mic and hold [ACC] on mic while powering on.
+ ("CLONE" will appear on the display)
+ 3. Replace mic with PC programming cable.
+ 4. Press the [DISP/SS] key
+ ("R" will appear on the lower left of LCD)."""))
+ rp.display_pre_upload_prompt_before_opening_port = False
+ return rp
+
+ @classmethod
+ def match_model(cls, filedata, filename):
+ return len(filedata) == cls._memsize
+
+ def get_features(self):
+ rf = chirp_common.RadioFeatures()
+ rf.has_settings = True
+ rf.has_ctone = False
+ rf.has_bank = False
+ rf.has_dtcs_polarity = False
+ rf.has_dtcs = True
+ rf.valid_modes = FT90_MODES
+ rf.valid_tmodes = FT90_TMODES
+ rf.valid_duplexes = FT90_DUPLEX
+ rf.valid_tuning_steps = FT90_STEPS
+ rf.valid_power_levels = FT90_POWER_LEVELS_VHF
+ rf.valid_name_length = 7
+ rf.valid_characters = chirp_common.CHARSET_ASCII
+ rf.valid_skips = ["", "S"]
+ rf.valid_special_chans = FT90_SPECIAL
+ rf.memory_bounds = (1, 180)
+ rf.valid_bands = [(100000000, 230000000),
+ (300000000, 530000000), (810000000, 999975000)]
+
+ return rf
+
+ def _read(self, blocksize, blocknum):
+ data = self.pipe.read(blocksize+2)
+
+ # chew echo'd ack
+ self.pipe.write(CMD_ACK)
+ time.sleep(0.02)
+ self.pipe.read(1) # chew echoed ACK from 1-wire serial
+
+ if len(data) == blocksize+2 and data[0] == chr(blocknum):
+ checksum = yaesu_clone.YaesuChecksum(1, blocksize)
+ if checksum.get_existing(data) != checksum.get_calculated(data):
+ raise Exception("Checksum Failed [%02X<>%02X] block %02X, data len: %i" %
+ (checksum.get_existing(data),
+ checksum.get_calculated(data), blocknum, len(data) ))
+ data = data[1:blocksize+1] # Chew blocknum and checksum
+
+ else:
+ raise Exception("Unable to read blocknum %02X expected blocksize %i got %i." %
+ (blocknum, blocksize+2, len(data)))
+
+ return data
+
+ def _clone_in(self):
+ # Be very patient with the radio
+ self.pipe.setTimeout(4)
+ start = time.time()
+
+ data = ""
+ blocknum = 0
+ status = chirp_common.Status()
+ status.msg = "Cloning..."
+ self.status_fn(status)
+ status.max = len(self._block_lengths)
+ for blocksize in self._block_lengths:
+ data += self._read(blocksize, blocknum)
+ blocknum += 1
+ status.cur = blocknum
+ self.status_fn(status)
+
+ print "Clone completed in %i seconds, blocks read: %i" % (time.time() - start, blocknum)
+
+ return memmap.MemoryMap(data)
+
+ def _clone_out(self):
+ looppredelay = 0.4
+ looppostdelay = 1.9
+ start = time.time()
+
+ blocknum = 0
+ pos = 0
+ status = chirp_common.Status()
+ status.msg = "Cloning to radio..."
+ self.status_fn(status)
+ status.max = len(self._block_lengths)
+
+ for blocksize in self._block_lengths:
+ checksum = yaesu_clone.YaesuChecksum(pos, pos+blocksize-1)
+ blocknumbyte = chr(blocknum)
+ payloadbytes = self.get_mmap()[pos:pos+blocksize]
+ checksumbyte = chr(checksum.get_calculated(self.get_mmap()))
+ if CHIRP_DEBUG:
+ print "Block %i - will send from %i to %i byte " % \
+ (blocknum, pos, pos + blocksize)
+ print util.hexprint(blocknumbyte)
+ print util.hexprint(payloadbytes)
+ print util.hexprint(checksumbyte)
+ # send wrapped bytes
+ time.sleep(looppredelay)
+ self.pipe.write(blocknumbyte)
+ self.pipe.write(payloadbytes)
+ self.pipe.write(checksumbyte)
+ tmp = self.pipe.read(blocksize+2) #chew echo
+ if CHIRP_DEBUG:
+ print "bytes echoed: "
+ print util.hexprint(tmp)
+ # radio is slow to write/ack:
+ time.sleep(looppostdelay)
+ buf = self.pipe.read(1)
+ if CHIRP_DEBUG:
+ print "ack recd:"
+ print util.hexprint(buf)
+ if buf != CMD_ACK:
+ raise Exception("Radio did not ack block %i" % blocknum)
+ pos += blocksize
+ blocknum += 1
+ status.cur = blocknum
+ self.status_fn(status)
+
+ print "Clone completed in %i seconds" % (time.time() - start)
+
+ def sync_in(self):
+ try:
+ self._mmap = self._clone_in()
+ except errors.RadioError:
+ raise
+ except Exception, e:
+ trace = traceback.format_exc()
+ raise errors.RadioError("Failed to communicate with radio: %s" % trace)
+ self.process_mmap()
+
+ def sync_out(self):
+ try:
+ self._clone_out()
+ except errors.RadioError:
+ raise
+ except Exception, e:
+ trace = traceback.format_exc()
+ raise errors.RadioError("Failed to communicate with radio: %s" % trace)
+
+ def process_mmap(self):
+ self._memobj = bitwise.parse(self.mem_format, self._mmap)
+
+ def _get_chan_enable(self, number):
+ number = number - 1
+ bytepos = number // 8
+ bitpos = number % 8
+ chan_enable = self._memobj.chan_enable[bytepos]
+ if chan_enable & ( 1 << bitpos ):
+ return True
+ else:
+ return False
+
+ def _set_chan_enable(self, number, enable):
+ number = number - 1
+ bytepos = number // 8
+ bitpos = number % 8
+ chan_enable = self._memobj.chan_enable[bytepos]
+ if enable:
+ chan_enable = chan_enable | ( 1 << bitpos ) # enable
+ else:
+ chan_enable = chan_enable & ~ ( 1 << bitpos ) # disable
+ self._memobj.chan_enable[bytepos] = chan_enable
+
+ def get_memory(self, number):
+ mem = chirp_common.Memory()
+ if isinstance(number, str):
+ # special channel
+ _mem = getattr(self._memobj, number)
+ mem.number = - len(FT90_SPECIAL) + FT90_SPECIAL.index(number)
+ mem.extd_number = number
+ if re.match('^pms', mem.extd_number):
+ # enable pms_XY channel flag
+ _special_enables = self._memobj.special_enables
+ mem.empty = not getattr(_special_enables, mem.extd_number + "_enable")
+ else:
+ # regular memory
+ _mem = self._memobj.memory[number-1]
+ mem.number = number
+ mem.empty = not self._get_chan_enable(number)
+ if mem.empty:
+ return mem # bail out, do not parse junk
+ mem.freq = _mem.rxfreq * 10
+ mem.offset = _mem.txfreqoffset * 10
+ if not _mem.tmode < len(FT90_TMODES):
+ _mem.tmode = 0
+ mem.tmode = FT90_TMODES[_mem.tmode]
+ mem.rtone = FT90_TONES[_mem.tone]
+ mem.dtcs = chirp_common.DTCS_CODES[_mem.dcstone]
+ mem.mode = FT90_MODES[_mem.mode]
+ mem.duplex = FT90_DUPLEX[_mem.shift]
+ if mem.freq / 1000000 > 300:
+ mem.power = FT90_POWER_LEVELS_UHF[_mem.power]
+ else:
+ mem.power = FT90_POWER_LEVELS_VHF[_mem.power]
+
+ # radio has a known bug with 5khz step and squelch
+ if _mem.step == 0 or _mem.step > len(FT90_STEPS)-1:
+ _mem.step = 2
+ mem.tuning_step = FT90_STEPS[_mem.step]
+ mem.skip = _mem.skip and "S" or ""
+ if not all(char in chirp_common.CHARSET_ASCII for char in str(_mem.name)):
+ # dont display blank/junk name
+ mem.name = ""
+ else:
+ mem.name = str(_mem.name)
+ return mem
+
+ def get_raw_memory(self, number):
+ return repr(self._memobj.memory[number-1])
+
+ def set_memory(self, mem):
+ if mem.number < 0: # special channels
+ _mem = getattr(self._memobj, mem.extd_number)
+ if re.match('^pms', mem.extd_number):
+ # enable pms_XY channel flag
+ _special_enables = self._memobj.special_enables
+ setattr(_special_enables, mem.extd_number + "_enable", True)
+ else:
+ _mem = self._memobj.memory[mem.number - 1]
+ self._set_chan_enable( mem.number, not mem.empty )
+ _mem.skip = mem.skip == "S"
+ # radio has a known bug with 5khz step and dead squelch
+ if not mem.tuning_step or mem.tuning_step == FT90_STEPS[0]:
+ _mem.step = 2
+ else:
+ _mem.step = FT90_STEPS.index(mem.tuning_step)
+ _mem.rxfreq = mem.freq / 10
+ # vfo will unlock if not in right band?
+ if mem.freq > 300000000:
+ # uhf
+ _mem.isUhf1 = 1
+ _mem.isUhf2 = 1
+ if mem.freq > 810000000:
+ # uhf hiband
+ _mem.isUhfHi = 1
+ else:
+ _mem.isUhfHi = 0
+ else:
+ # vhf
+ _mem.isUhf1 = 0
+ _mem.isUhf2 = 0
+ _mem.isUhfHi = 0
+ _mem.txfreqoffset = mem.offset / 10
+ _mem.tone = FT90_TONES.index(mem.rtone)
+ _mem.tmode = FT90_TMODES.index(mem.tmode)
+ _mem.mode = FT90_MODES.index(mem.mode)
+ _mem.shift = FT90_DUPLEX.index(mem.duplex)
+ _mem.dcstone = chirp_common.DTCS_CODES.index(mem.dtcs)
+ _mem.step = FT90_STEPS.index(mem.tuning_step)
+ _mem.shift = FT90_DUPLEX.index(mem.duplex)
+ if mem.power:
+ _mem.power = FT90_POWER_LEVELS_VHF.index(mem.power)
+ else:
+ _mem.power = 3 # default to low power
+ if (len(mem.name) == 0):
+ _mem.name = bytearray.fromhex("80ffffffffffff")
+ _mem.showname = 0
+ else:
+ _mem.name = str(mem.name).ljust(7)
+ _mem.showname = 1
+ _mem.UseDefaultName = 0
+
+ def _decode_cwid(self, cwidarr):
+ cwid = ""
+ if CHIRP_DEBUG:
+ print "@ +_decode_cwid:"
+ for byte in cwidarr.get_value():
+ char = int(byte)
+ if CHIRP_DEBUG:
+ print char
+ # bitwise wraps in quotes! get rid of those
+ if char < len(FT90_CWID_CHARS):
+ cwid += FT90_CWID_CHARS[char]
+ return cwid
+
+ def _encode_cwid(self, cwidarr):
+ cwid = ""
+ if CHIRP_DEBUG:
+ print "@ _encode_cwid:"
+ for char in cwidarr.get_value():
+ cwid += chr(FT90_CWID_CHARS.index(char))
+ if CHIRP_DEBUG:
+ print cwid
+ return cwid
+
+ def _bbcd2dtmf(self, bcdarr, strlen = 16):
+ # doing bbcd, but with support for ABCD*#
+ if CHIRP_DEBUG:
+ print bcdarr.get_value()
+ string = ''.join("%02X" % b for b in bcdarr)
+ if CHIRP_DEBUG:
+ print "@_bbcd2dtmf, received: %s" % string
+ string = string.replace('E','*').replace('F','#')
+ if strlen <= 16:
+ string = string[:strlen]
+ return string
+
+ def _dtmf2bbcd(self, dtmf):
+ dtmfstr = dtmf.get_value()
+ dtmfstr = dtmfstr.replace('*', 'E').replace('#', 'F')
+ dtmfstr = str.ljust(dtmfstr.strip(), 16, "0" )
+ bcdarr = list(bytearray.fromhex(dtmfstr))
+ if CHIRP_DEBUG:
+ print "@_dtmf2bbcd, sending: %s" % bcdarr
+ return bcdarr
+
+ def get_settings(self):
+ _settings = self._memobj.settings
+ basic = RadioSettingGroup("basic", "Basic")
+ autodial = RadioSettingGroup("autodial", "AutoDial")
+ keymaps = RadioSettingGroup("keymaps", "KeyMaps")
+ top = RadioSettingGroup("top", "All Settings", basic, keymaps, autodial)
+
+ rs = RadioSetting("beep", "Beep",
+ RadioSettingValueBoolean(_settings.beep))
+ basic.append(rs)
+ rs = RadioSetting("lock", "Lock",
+ RadioSettingValueBoolean(_settings.lock))
+ basic.append(rs)
+ rs = RadioSetting("ars", "Auto Repeater Shift",
+ RadioSettingValueBoolean(_settings.ars))
+ basic.append(rs)
+ rs = RadioSetting("txpwrsave", "TX Power Save",
+ RadioSettingValueBoolean(_settings.txpwrsave))
+ basic.append(rs)
+ rs = RadioSetting("txnarrow", "TX Narrow",
+ RadioSettingValueBoolean(_settings.txnarrow))
+ basic.append(rs)
+ options = ["Off", "S-3", "S-5", "S-Full"]
+ rs = RadioSetting("rfsqlvl", "RF Squelch Level",
+ RadioSettingValueList(options,
+ options[_settings.rfsqlvl]))
+ basic.append(rs)
+ options = ["Off", "Band A", "Band B", "Both"]
+ rs = RadioSetting("pttlock", "PTT Lock",
+ RadioSettingValueList(options,
+ options[_settings.pttlock]))
+ basic.append(rs)
+
+ rs = RadioSetting("cwid_en", "CWID Enable",
+ RadioSettingValueBoolean(_settings.cwid_en))
+ basic.append(rs)
+
+ cwid = RadioSettingValueString(0, 7, self._decode_cwid(_settings.cwid))
+ cwid.set_charset(FT90_CWID_CHARS)
+ rs = RadioSetting("cwid", "CWID", cwid)
+ basic.append(rs)
+
+ options = ["OFF"] + map(str, range(1, 12+1))
+ rs = RadioSetting("apo", "APO time (hrs)",
+ RadioSettingValueList(options,
+ options[_settings.apo]))
+ basic.append(rs)
+
+ options = ["Off"] + map(str, range(1, 60+1))
+ rs = RadioSetting("tot", "Time Out Timer (mins)",
+ RadioSettingValueList(options,options[_settings.tot]))
+ basic.append(rs)
+
+ options = ["off", "Auto/TX", "Auto", "TX"]
+ rs = RadioSetting("fancontrol", "Fan Control",
+ RadioSettingValueList(options,options[_settings.fancontrol]))
+ basic.append(rs)
+
+ keyopts = ["Scan Up", "Scan Down", "Repeater", "Reverse", "Tone Burst",
+ "Tx Power", "Home Ch", "VFO/MR", "Tone", "Priority"]
+ rs = RadioSetting("key_lt", "Left Key",
+ RadioSettingValueList(keyopts,keyopts[_settings.key_lt]))
+ keymaps.append(rs)
+ rs = RadioSetting("key_rt", "Right Key",
+ RadioSettingValueList(keyopts,keyopts[_settings.key_rt]))
+ keymaps.append(rs)
+ rs = RadioSetting("key_p1", "P1 Key",
+ RadioSettingValueList(keyopts,keyopts[_settings.key_p1]))
+ keymaps.append(rs)
+ rs = RadioSetting("key_p2", "P2 Key",
+ RadioSettingValueList(keyopts,keyopts[_settings.key_p2]))
+ keymaps.append(rs)
+ rs = RadioSetting("key_acc", "ACC Key",
+ RadioSettingValueList(keyopts,keyopts[_settings.key_acc]))
+ keymaps.append(rs)
+
+ options = map(str, range(0,12+1))
+ rs = RadioSetting("lcdcontrast", "LCD Contrast",
+ RadioSettingValueList(options,options[_settings.lcdcontrast]))
+ basic.append(rs)
+
+ options = ["off", "d4", "d3", "d2", "d1"]
+ rs = RadioSetting("dimmer", "Dimmer",
+ RadioSettingValueList(options,options[_settings.dimmer]))
+ basic.append(rs)
+
+ options = ["TRX Normal", "RX Reverse", "TX Reverse", "TRX Reverse"]
+ rs = RadioSetting("dcsmode", "DCS Mode",
+ RadioSettingValueList(options,options[_settings.dcsmode]))
+ basic.append(rs)
+
+ options = ["50 ms", "100 ms"]
+ rs = RadioSetting("dtmfspeed", "DTMF Speed",
+ RadioSettingValueList(options,options[_settings.dtmfspeed]))
+ autodial.append(rs)
+
+ options = ["50 ms", "250 ms", "450 ms", "750 ms", "1 sec"]
+ rs = RadioSetting("dtmftxdelay", "DTMF TX Delay",
+ RadioSettingValueList(options,options[_settings.dtmftxdelay]))
+ autodial.append(rs)
+
+ options = map(str,range(1,8+1))
+ rs = RadioSetting("dtmf_active", "DTMF Active",
+ RadioSettingValueList(options,options[_settings.dtmf_active]))
+ autodial.append(rs)
+
+ # setup 8 dtmf autodial entries
+ for i in map(str, range(1,9)):
+ objname = "dtmf" + i
+ dtmfsetting = getattr(_settings, objname)
+ dtmflen = getattr(_settings, objname + "_len")
+ dtmfstr = self._bbcd2dtmf(dtmfsetting, dtmflen)
+ dtmf = RadioSettingValueString(0, 16, dtmfstr)
+ dtmf.set_charset(FT90_DTMF_CHARS + list(" "))
+ rs = RadioSetting(objname, objname.upper(), dtmf)
+ autodial.append(rs)
+
+ return top
+
+ def set_settings(self, uisettings):
+ _settings = self._memobj.settings
+ for element in uisettings:
+ if not isinstance(element, RadioSetting):
+ self.set_settings(element)
+ continue
+ if not element.changed():
+ continue
+ try:
+ setting = element.get_name()
+ oldval = getattr(_settings, setting)
+ newval = element.value
+ if setting == "cwid":
+ newval = self._encode_cwid(newval)
+ if re.match('dtmf\d', setting):
+ # set dtmf length field and then get bcd dtmf
+ dtmfstrlen = len(str(newval).strip())
+ setattr(_settings, setting + "_len", dtmfstrlen)
+ newval = self._dtmf2bbcd(newval)
+ if CHIRP_DEBUG:
+ print "Setting %s(%s) <= %s" % (setting,
+ oldval, newval)
+ setattr(_settings, setting, newval)
+ except Exception, e:
+ print element.get_name()
+ raise
+
diff --git a/chirp/ftm350.py b/chirp/ftm350.py
new file mode 100644
index 0000000..784bdff
--- /dev/null
+++ b/chirp/ftm350.py
@@ -0,0 +1,420 @@
+# Copyright 2013 Dan Smith <dsmith at danplanet.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 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 time
+import struct
+import os
+
+from chirp import chirp_common, yaesu_clone, directory, errors, util
+from chirp import bitwise, memmap
+from chirp.settings import RadioSettingGroup, RadioSetting
+from chirp.settings import RadioSettingValueInteger, RadioSettingValueString
+
+mem_format = """
+struct mem {
+ u8 used:1,
+ skip:2,
+ unknown1:5;
+ u8 unknown2:1,
+ mode:3,
+ unknown8:1,
+ oddsplit:1,
+ duplex:2;
+ bbcd freq[3];
+ u8 unknownA:1,
+ tmode:3,
+ unknownB:4;
+ bbcd split[3];
+ u8 power:2,
+ tone:6;
+ u8 unknownC:1,
+ dtcs:7;
+ u8 showalpha:1,
+ unknown5:7;
+ u8 unknown6;
+ u8 offset;
+ u8 unknown7[2];
+};
+
+struct lab {
+ u8 string[8];
+};
+
+#seekto 0x0508;
+struct {
+ char call[6];
+ u8 ssid;
+} aprs_my_callsign;
+
+#seekto 0x0480;
+struct mem left_memory_zero;
+#seekto 0x04A0;
+struct lab left_label_zero;
+#seekto 0x04C0;
+struct mem right_memory_zero;
+#seekto 0x04E0;
+struct lab right_label_zero;
+
+#seekto 0x0800;
+struct mem left_memory[500];
+
+#seekto 0x2860;
+struct mem right_memory[500];
+
+#seekto 0x48C0;
+struct lab left_label[518];
+struct lab right_label[518];
+"""
+
+_TMODES = ["", "Tone", "TSQL", "-RVT", "DTCS", "-PR", "-PAG"]
+TMODES = ["", "Tone", "TSQL", "", "DTCS", "", ""]
+MODES = ["FM", "AM", "NFM", "", "WFM"]
+DUPLEXES = ["", "", "-", "+", "split"]
+#TODO: add japaneese characters (viewable in special menu, scroll backwards)
+CHARSET = ('0123456789ABCDEFGHIJKLMNOPQRSTUVWXYZabcdefghijklmnopqrstuvwxyz!"' +
+ '#$%&`()*+,-./:;<=>?@[\\]^_`{|}~?????? ' + '?' * 91)
+
+POWER_LEVELS = [chirp_common.PowerLevel("Hi", watts=50),
+ chirp_common.PowerLevel("Mid", watts=20),
+ chirp_common.PowerLevel("Low", watts=5)]
+
+SKIPS = ["", "S", "P"]
+
+def aprs_call_to_str(_call):
+ call = ""
+ for i in str(_call):
+ if i == "\xca":
+ break
+ call += i
+ return call
+
+def _safe_read(radio, length):
+ data = ""
+ while len(data) < length:
+ data += radio.pipe.read(length - len(data))
+ return data
+
+def _clone_in(radio):
+ data = ""
+
+ radio.pipe.setTimeout(1)
+ attempts = 30
+
+ data = memmap.MemoryMap("\x00" * (radio._memsize + 128))
+ length = 0
+ last_addr = 0
+ while length < radio._memsize:
+ frame = radio.pipe.read(131)
+ if length and not frame:
+ raise errors.RadioError("Radio not responding")
+
+ if not frame:
+ attempts -= 1
+ if attempts <= 0:
+ raise errors.RadioError("Radio not responding")
+
+ if frame:
+ addr, = struct.unpack(">H", frame[0:2])
+ checksum = ord(frame[130])
+ block = frame[2:130]
+
+ cs = 0
+ for i in frame[:-1]:
+ cs = (cs + ord(i)) % 256
+ if cs != checksum:
+ print "Calc: %02x Real: %02x Len: %i" % (cs, checksum,
+ len(block))
+ raise errors.RadioError("Block failed checksum")
+
+ radio.pipe.write("\x06")
+ time.sleep(0.05)
+
+ if os.getenv("CHIRP_DEBUG") and (last_addr + 128) != addr:
+ print "Gap, expecting %04x, got %04x" % (last_addr+128, addr)
+ last_addr = addr
+ data[addr] = block
+ length += len(block)
+
+ status = chirp_common.Status()
+ status.cur = length
+ status.max = radio._memsize
+ status.msg = "Cloning from radio"
+ radio.status_fn(status)
+
+ return data
+
+def _clone_out(radio):
+ radio.pipe.setTimeout(1)
+
+ # Seriously, WTF Yaesu?
+ ranges = [
+ (0x0000, 0x0000),
+ (0x0100, 0x0380),
+ (0x0480, 0xFF80),
+ (0x0080, 0x0080),
+ (0xFFFE, 0xFFFE),
+ ]
+
+ for start, end in ranges:
+ for i in range(start, end+1, 128):
+ block = radio._mmap[i:i + 128]
+ frame = struct.pack(">H", i) + block
+ cs = 0
+ for byte in frame:
+ cs += ord(byte)
+ frame += chr(cs % 256)
+ radio.pipe.write(frame)
+ ack = radio.pipe.read(1)
+ if ack != "\x06":
+ raise errors.RadioError("Radio refused block %i" % (i / 128))
+ time.sleep(0.05)
+
+ status = chirp_common.Status()
+ status.cur = i + 128
+ status.max = radio._memsize
+ status.msg = "Cloning to radio"
+ radio.status_fn(status)
+
+def get_freq(rawfreq):
+ """Decode a frequency that may include a fractional step flag"""
+ # Ugh. The 0x80 and 0x40 indicate values to add to get the
+ # real frequency. Gross.
+ if rawfreq > 8000000000:
+ rawfreq = (rawfreq - 8000000000) + 5000
+
+ if rawfreq > 4000000000:
+ rawfreq = (rawfreq - 4000000000) + 2500
+
+ if rawfreq > 2000000000:
+ rawfreq = (rawfreq - 2000000000) + 1250
+
+ return rawfreq
+
+def set_freq(freq, obj, field):
+ """Encode a frequency with any necessary fractional step flags"""
+ obj[field] = freq / 10000
+ frac = freq % 10000
+
+ if frac >= 5000:
+ frac -= 5000
+ obj[field][0].set_bits(0x80)
+
+ if frac >= 2500:
+ frac -= 2500
+ obj[field][0].set_bits(0x40)
+
+ if frac >= 1250:
+ frac -= 1250
+ obj[field][0].set_bits(0x20)
+
+ return freq
+
+ at directory.register
+class FTM350Radio(yaesu_clone.YaesuCloneModeRadio):
+ """Yaesu FTM-350"""
+ BAUD_RATE = 48000
+ VENDOR = "Yaesu"
+ MODEL = "FTM-350"
+
+ _model = ""
+ _memsize = 65536
+ _vfo = ""
+
+ def get_features(self):
+ rf = chirp_common.RadioFeatures()
+ rf.has_bank = False
+ rf.has_ctone = False
+ rf.has_settings = self._vfo == "left"
+ rf.has_tuning_step = False
+ rf.has_dtcs_polarity = False
+ rf.has_sub_devices = self.VARIANT == ""
+ rf.valid_skips = [] # FIXME: Finish this
+ rf.valid_tmodes = [""] + [x for x in TMODES if x]
+ rf.valid_modes = [x for x in MODES if x]
+ rf.valid_duplexes = DUPLEXES
+ rf.valid_skips = SKIPS
+ rf.valid_name_length = 8
+ rf.valid_characters = CHARSET
+ rf.memory_bounds = (0, 500)
+ rf.valid_power_levels = POWER_LEVELS
+ rf.valid_bands = [( 500000, 1800000),
+ (76000000, 250000000),
+ (30000000, 1000000000)]
+ rf.can_odd_split = True
+ return rf
+
+ def get_sub_devices(self):
+ return [FTM350RadioLeft(self._mmap), FTM350RadioRight(self._mmap)]
+
+ def sync_in(self):
+ try:
+ self._mmap = _clone_in(self)
+ except errors.RadioError:
+ raise
+ except Exception, e:
+ raise errors.RadioError("Failed to download from radio (%s)" % e)
+ self.process_mmap()
+
+ def sync_out(self):
+ try:
+ _clone_out(self)
+ except errors.RadioError:
+ raise
+ except Exception, e:
+ raise errors.RadioError("Failed to upload to radio (%s)" % e)
+
+ def process_mmap(self):
+ self._memobj = bitwise.parse(mem_format, self._mmap)
+
+ def get_raw_memory(self, number):
+ if number == 0:
+ suffix = "_zero"
+ fn = lambda o: o
+ else:
+ suffix = ""
+ fn = lambda o: o[number - 1]
+ return (repr(fn(self._memory_obj(suffix))) +
+ repr(fn(self._label_obj(suffix))))
+
+ def _memory_obj(self, suffix=""):
+ return getattr(self._memobj, "%s_memory%s" % (self._vfo, suffix))
+
+ def _label_obj(self, suffix=""):
+ return getattr(self._memobj, "%s_label%s" % (self._vfo, suffix))
+
+ def get_memory(self, number):
+ if number == 0:
+ _mem = self._memory_obj("_zero")
+ _lab = self._label_obj("_zero")
+ else:
+ _mem = self._memory_obj()[number - 1]
+ _lab = self._label_obj()[number - 1]
+ mem = chirp_common.Memory()
+ mem.number = number
+
+ if not _mem.used:
+ mem.empty = True
+ return mem
+
+ mem.freq = get_freq(int(_mem.freq) * 10000)
+ mem.rtone = chirp_common.TONES[_mem.tone]
+ mem.tmode = TMODES[_mem.tmode]
+
+ if _mem.oddsplit:
+ mem.duplex = "split"
+ mem.offset = get_freq(int(_mem.split) * 10000)
+ else:
+ mem.duplex = DUPLEXES[_mem.duplex]
+ mem.offset = int(_mem.offset) * 50000
+
+ mem.dtcs = chirp_common.DTCS_CODES[_mem.dtcs]
+ mem.mode = MODES[_mem.mode]
+ mem.skip = SKIPS[_mem.skip]
+ mem.power = POWER_LEVELS[_mem.power]
+
+ for char in _lab.string:
+ if char == 0xCA:
+ break
+ try:
+ mem.name += CHARSET[char]
+ except IndexError:
+ mem.name += "?"
+ mem.name = mem.name.rstrip()
+
+ return mem
+
+ def set_memory(self, mem):
+ if mem.number == 0:
+ _mem = self._memory_obj("_zero")
+ _lab = self._label_obj("_zero")
+ else:
+ _mem = self._memory_obj()[mem.number - 1]
+ _lab = self._label_obj()[mem.number - 1]
+ _mem.used = not mem.empty
+ if mem.empty:
+ return
+
+ set_freq(mem.freq, _mem, 'freq')
+ _mem.tone = chirp_common.TONES.index(mem.rtone)
+ _mem.dtcs = chirp_common.DTCS_CODES.index(mem.dtcs)
+ _mem.tmode = TMODES.index(mem.tmode)
+ _mem.mode = MODES.index(mem.mode)
+ _mem.skip = SKIPS.index(mem.skip)
+
+ _mem.oddsplit = 0
+ _mem.duplex = 0
+ if mem.duplex == "split":
+ set_freq(mem.offset, _mem, 'split')
+ _mem.oddsplit = 1
+ else:
+ _mem.offset = mem.offset / 50000
+ _mem.duplex = DUPLEXES.index(mem.duplex)
+
+ if mem.power:
+ _mem.power = POWER_LEVELS.index(mem.power)
+ else:
+ _mem.power = 0
+
+ for i in range(0, 8):
+ try:
+ char = CHARSET.index(mem.name[i])
+ except IndexError:
+ char = 0xCA
+ _lab.string[i] = char
+ _mem.showalpha = mem.name.strip() != ""
+
+ @classmethod
+ def match_model(self, filedata, filename):
+ return filedata.startswith("AH033$")
+
+ def get_settings(self):
+ top = RadioSettingGroup("all", "All Settings")
+
+ aprs = RadioSettingGroup("aprs", "APRS")
+ top.append(aprs)
+
+ myc = self._memobj.aprs_my_callsign
+ rs = RadioSetting("aprs_my_callsign.call", "APRS My Callsign",
+ RadioSettingValueString(0, 6,
+ aprs_call_to_str(myc.call)))
+ aprs.append(rs)
+
+ rs = RadioSetting("aprs_my_callsign.ssid", "APRS My SSID",
+ RadioSettingValueInteger(0, 15, myc.ssid))
+ aprs.append(rs)
+
+ return top
+
+ def set_settings(self, settings):
+ for setting in settings:
+ if not isinstance(setting, RadioSetting):
+ self.set_settings(setting)
+ continue
+
+ # Quick hack to make these work
+ if setting.get_name() == "aprs_my_callsign.call":
+ self._memobj.aprs_my_callsign.call = \
+ setting.value.get_value().upper().replace(" ", "\xCA")
+ elif setting.get_name() == "aprs_my_callsign.ssid":
+ self._memobj.aprs_my_callsign.ssid = setting.value
+
+
+class FTM350RadioLeft(FTM350Radio):
+ VARIANT = "Left"
+ _vfo = "left"
+
+class FTM350RadioRight(FTM350Radio):
+ VARIANT = "Right"
+ _vfo = "right"
diff --git a/chirp/generic_csv.py b/chirp/generic_csv.py
index 4ca06d9..5777e73 100644
--- a/chirp/generic_csv.py
+++ b/chirp/generic_csv.py
@@ -106,6 +106,19 @@ class CSVRadio(chirp_common.FileBackedRadio, chirp_common.IcomDstarSupport):
return rf
+ def _clean(self, headers, line, mem):
+ """Runs post-processing functions on new mem objects.
+
+ This is useful for parsing other CSV dialects when multiple columns
+ convert to a single Chirp column."""
+
+ for attr in dir(mem):
+ fname = "_clean_%s" % attr
+ if hasattr(self, fname):
+ mem = getattr(self, fname)(headers, line, mem)
+
+ return mem
+
def _parse_csv_data_line(self, headers, line):
mem = chirp_common.Memory()
try:
@@ -114,7 +127,11 @@ class CSVRadio(chirp_common.FileBackedRadio, chirp_common.IcomDstarSupport):
except OmittedHeaderError:
pass
- for header, (typ, attr) in self.ATTR_MAP.items():
+ for header in headers:
+ try:
+ typ, attr = self.ATTR_MAP[header]
+ except KeyError:
+ continue
try:
val = get_datum_by_header(headers, line, header)
if not val and typ == int:
@@ -128,7 +145,7 @@ class CSVRadio(chirp_common.FileBackedRadio, chirp_common.IcomDstarSupport):
except Exception, e:
raise Exception("[%s] %s" % (attr, e))
- return mem
+ return self._clean(headers, line, mem)
def load(self, filename=None):
if filename is None and self._filename is None:
@@ -238,6 +255,176 @@ class CSVRadio(chirp_common.FileBackedRadio, chirp_common.IcomDstarSupport):
",".join(self.memories[number].to_csv())
@classmethod
- def match_model(cls, _filedata, filename):
+ def match_model(cls, filedata, filename):
"""Match files ending in .CSV"""
- return filename.lower().endswith("." + cls.FILE_EXTENSION)
+ return filename.lower().endswith("." + cls.FILE_EXTENSION) and \
+ (filedata.startswith("Location,") or filedata == "")
+
+
+ at directory.register
+class CommanderCSVRadio(CSVRadio):
+ """A driver for reading CSV files generated by KG-UV Commander software"""
+ VENDOR = "Commander"
+ MODEL = "KG-UV"
+ FILE_EXTENSION = "csv"
+
+ MODE_MAP = {
+ "NARR": "NFM",
+ "WIDE": "FM",
+ }
+
+ SCAN_MAP = {
+ "ON": "",
+ "OFF": "S"
+ }
+
+ ATTR_MAP = {
+ "#": (int, "number"),
+ "Name": (str, "name"),
+ "RX Freq": (chirp_common.parse_freq, "freq"),
+ "Scan": (lambda v: CommanderCSVRadio.SCAN_MAP.get(v), "skip"),
+ "TX Dev": (lambda v: CommanderCSVRadio.MODE_MAP.get(v), "mode"),
+ "Group/Notes": (str, "comment"),
+ }
+
+ def _clean_number(self, headers, line, mem):
+ if mem.number == 0:
+ for memory in self.memories:
+ if memory.empty:
+ mem.number = memory.number
+ break
+ return mem
+
+ def _clean_duplex(self, headers, line, mem):
+ try:
+ txfreq = chirp_common.parse_freq(
+ get_datum_by_header(headers, line, "TX Freq"))
+ except ValueError:
+ mem.duplex = "off"
+ return mem
+
+ if mem.freq == txfreq:
+ mem.duplex = ""
+ elif txfreq:
+ mem.duplex = "split"
+ mem.offset = txfreq
+
+ return mem
+
+ def _clean_tmode(self, headers, line, mem):
+ rtone = get_datum_by_header(headers, line, "Encode")
+ ctone = get_datum_by_header(headers, line, "Decode")
+ if rtone == "OFF":
+ rtone = None
+ else:
+ rtone = float(rtone)
+
+ if ctone == "OFF":
+ ctone = None
+ else:
+ ctone = float(ctone)
+
+ if rtone:
+ mem.tmode = "Tone"
+ if ctone:
+ mem.tmode = "TSQL"
+
+ mem.rtone = rtone or 88.5
+ mem.ctone = ctone or mem.rtone
+
+ return mem
+
+ @classmethod
+ def match_model(cls, filedata, filename):
+ """Match files ending in .csv and using Commander column names."""
+ return filename.lower().endswith("." + cls.FILE_EXTENSION) and \
+ filedata.startswith("Name,RX Freq,TX Freq,Decode,Encode,TX Pwr,"
+ "Scan,TX Dev,Busy Lck,Group/Notes") or \
+ filedata.startswith('"#","Name","RX Freq","TX Freq","Decode",'
+ '"Encode","TX Pwr","Scan","TX Dev","Busy Lck","Group/Notes"')
+
+
+ at directory.register
+class RTCSVRadio(CSVRadio):
+ """A driver for reading CSV files generated by RT Systems software"""
+ VENDOR = "RT Systems"
+ MODEL = "CSV"
+ FILE_EXTENSION = "csv"
+
+ DUPLEX_MAP = {
+ "Minus": "-",
+ "Plus": "+",
+ "Simplex": "",
+ "Split": "split",
+ }
+
+ SKIP_MAP = {
+ "Off": "",
+ "On": "S",
+ "P Scan": "P",
+ "Skip": "S",
+ }
+
+ TMODE_MAP = {
+ "None": "",
+ "T Sql": "TSQL",
+ }
+
+ BOOL_MAP = {
+ "Off": False,
+ "On": True,
+ }
+
+ ATTR_MAP = {
+ "Channel Number": (int, "number"),
+ "Receive Frequency":(chirp_common.parse_freq, "freq"),
+ "Offset Frequency": (chirp_common.parse_freq, "offset"),
+ "Offset Direction": (lambda v: RTCSVRadio.DUPLEX_MAP.get(v, v), "duplex"),
+ "Operating Mode": (str, "mode"),
+ "Name": (str, "name"),
+ "Tone Mode": (lambda v: RTCSVRadio.TMODE_MAP.get(v, v), "tmode"),
+ "CTCSS": (lambda v: float(v.split(" ")[0]), "rtone"),
+ "DCS": (int, "dtcs"),
+ "Skip": (lambda v: RTCSVRadio.SKIP_MAP.get(v, v), "skip"),
+ "Step": (lambda v: float(v.split(" ")[0]), "tuning_step"),
+ "Mask": (lambda v: RTCSVRadio.BOOL_MAP.get(v, v), "empty",),
+ "Comment": (str, "comment"),
+ }
+
+ def _clean_duplex(self, headers, line, mem):
+ if mem.duplex == "split":
+ try:
+ val = get_datum_by_header(headers, line, "Transmit Frequency")
+ val = chirp_common.parse_freq(val)
+ mem.offset = val
+ except OmittedHeaderError:
+ pass
+
+ return mem
+
+ def _clean_mode(self, headers, line, mem):
+ if mem.mode == "FM":
+ try:
+ val = get_datum_by_header(headers, line, "Half Dev")
+ if self.BOOL_MAP[val]:
+ mem.mode = "FMN"
+ except OmittedHeaderError:
+ pass
+
+ return mem
+
+ def _clean_ctone(self, headers, line, mem):
+ # RT Systems only stores a single tone value
+ mem.ctone = mem.rtone
+ return mem
+
+ @classmethod
+ def match_model(cls, filedata, filename):
+ """Match files ending in .csv and using RT Systems column names."""
+ # RT Systems provides a different set of columns for each radio.
+ # We attempt to match only the first few columns, hoping they are
+ # consistent across radio models.
+ return filename.lower().endswith("." + cls.FILE_EXTENSION) and \
+ filedata.startswith("Channel Number,Receive Frequency,"
+ "Transmit Frequency,Offset Frequency,Offset Direction,"
+ "Operating Mode,Name,Tone Mode,CTCSS,DCS")
diff --git a/chirp/generic_tpe.py b/chirp/generic_tpe.py
index 8fa949c..f720c0f 100644
--- a/chirp/generic_tpe.py
+++ b/chirp/generic_tpe.py
@@ -16,25 +16,6 @@
import UserDict
from chirp import chirp_common, directory, generic_csv
-class TpeMap(UserDict.UserDict):
- """Pretend we're a dict"""
- def items(self):
- return [
- ("Sequence Number" , (int, "number")),
- ("Location" , (str, "comment")),
- ("Call Sign" , (str, "name")),
- ("Output Frequency", (chirp_common.parse_freq, "freq")),
- ("Input Frequency" , (str, "duplex")),
- ("CTCSS Tones" , (lambda v: "Tone"
- if float(v) in chirp_common.TONES
- else "", "tmode")),
- ("CTCSS Tones" , (lambda v: float(v)
- if float(v) in chirp_common.TONES
- else 88.5, "rtone")),
- ("CTCSS Tones" , (lambda v: float(v)
- if float(v) in chirp_common.TONES
- else 88.5, "ctone")),
- ]
@directory.register
class TpeRadio(generic_csv.CSVRadio):
@@ -43,4 +24,33 @@ class TpeRadio(generic_csv.CSVRadio):
MODEL = "Travel Plus"
FILE_EXTENSION = "tpe"
- ATTR_MAP = TpeMap()
+ ATTR_MAP = {
+ "Sequence Number" : (int, "number"),
+ "Location" : (str, "comment"),
+ "Call Sign" : (str, "name"),
+ "Output Frequency": (chirp_common.parse_freq, "freq"),
+ "Input Frequency" : (str, "duplex"),
+ "CTCSS Tones" : (lambda v: float(v)
+ if v and float(v) in chirp_common.TONES
+ else 88.5, "rtone"),
+ "Repeater Notes": (str, "comment"),
+ }
+
+ def _clean_tmode(self, headers, line, mem):
+ try:
+ val = generic_csv.get_datum_by_header(headers, line, "CTCSS Tones")
+ if val and float(val) in chirp_common.TONES:
+ mem.tmode = "Tone"
+ except generic_csv.OmittedHeaderError:
+ pass
+
+ return mem
+
+ def _clean_ctone(self, headers, line, mem):
+ # TPE only stores a single tone value
+ mem.ctone = mem.rtone
+ return mem
+
+ @classmethod
+ def match_model(cls, filedata, filename):
+ return filename.lower().endswith("." + cls.FILE_EXTENSION)
diff --git a/chirp/h777.py b/chirp/h777.py
new file mode 100644
index 0000000..b9db514
--- /dev/null
+++ b/chirp/h777.py
@@ -0,0 +1,542 @@
+# -*- coding: utf-8 -*-
+# Copyright 2013 Andrew Morgan <ziltro at ziltro.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 unittest
+
+from chirp import chirp_common, directory, memmap
+from chirp import bitwise, errors, util
+from chirp.settings import RadioSetting, RadioSettingGroup, \
+ RadioSettingValueInteger, RadioSettingValueList, \
+ RadioSettingValueBoolean
+
+DEBUG = os.getenv("CHIRP_DEBUG") and True or False
+
+MEM_FORMAT = """
+#seekto 0x0010;
+struct {
+ lbcd rxfreq[4];
+ lbcd txfreq[4];
+ lbcd rxtone[2];
+ lbcd txtone[2];
+ u8 unknown3:1,
+ unknown2:1,
+ unknown1:1,
+ skip:1,
+ highpower:1,
+ narrow:1,
+ beatshift:1,
+ bcl:1;
+ u8 unknown4[3];
+} memory[16];
+#seekto 0x02B0;
+struct {
+ u8 voiceprompt;
+ u8 voicelanguage;
+ u8 scan;
+ u8 vox;
+ u8 voxlevel;
+ u8 voxinhibitonrx;
+ u8 lowvolinhibittx;
+ u8 highvolinhibittx;
+ u8 alarm;
+ u8 fmradio;
+} settings;
+#seekto 0x03C0;
+struct {
+ u8 unused:6,
+ batterysaver:1,
+ beep:1;
+ u8 squelchlevel;
+ u8 sidekeyfunction;
+ u8 timeouttimer;
+ u8 unused2[3];
+ u8 unused3:7,
+ scanmode:1;
+} settings2;
+"""
+
+CMD_ACK = "\x06"
+BLOCK_SIZE = 0x08
+UPLOAD_BLOCKS = [range(0x0000, 0x0110, 8),
+ range(0x02b0, 0x02c0, 8),
+ range(0x0380, 0x03e0, 8)]
+
+# TODO: Is it 1 watt?
+H777_POWER_LEVELS = [chirp_common.PowerLevel("Low", watts=1.00),
+ chirp_common.PowerLevel("High", watts=5.00)]
+VOICE_LIST = ["English", "Chinese"]
+SIDEKEYFUNCTION_LIST = ["Off", "Monitor", "Transmit Power", "Alarm"]
+TIMEOUTTIMER_LIST = ["Off", "30 seconds", "60 seconds", "90 seconds",
+ "120 seconds", "150 seconds", "180 seconds",
+ "210 seconds", "240 seconds", "270 seconds",
+ "300 seconds"]
+SCANMODE_LIST = ["Carrier", "Time"]
+
+SETTING_LISTS = {
+ "voice" : VOICE_LIST,
+ }
+
+def _h777_enter_programming_mode(radio):
+ serial = radio.pipe
+
+ try:
+ serial.write("\x02")
+ time.sleep(0.1)
+ serial.write("PROGRAM")
+ 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("P3107"):
+ print 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 _h777_exit_programming_mode(radio):
+ serial = radio.pipe
+ try:
+ serial.write("E")
+ except:
+ raise errors.RadioError("Radio refused to exit programming mode")
+
+def _h777_read_block(radio, block_addr, block_size):
+ serial = radio.pipe
+
+ cmd = struct.pack(">cHb", 'R', block_addr, BLOCK_SIZE)
+ expectedresponse = "W" + cmd[1:]
+ if DEBUG:
+ print("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 _h777_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 + 8]
+
+ if DEBUG:
+ print("Writing Data:")
+ print 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):
+ print "download"
+ _h777_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 = _h777_read_block(radio, addr, BLOCK_SIZE)
+ data += block
+
+ if DEBUG:
+ print "Address: %04x" % addr
+ print util.hexprint(block)
+
+ _h777_exit_programming_mode(radio)
+
+ return memmap.MemoryMap(data)
+
+def do_upload(radio):
+ status = chirp_common.Status()
+ status.msg = "Uploading to radio"
+
+ _h777_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)
+ _h777_write_block(radio, addr, BLOCK_SIZE)
+
+ _h777_exit_programming_mode(radio)
+
+ at directory.register
+class H777Radio(chirp_common.CloneModeRadio):
+ """HST H-777"""
+ # VENDOR = "Heng Shun Tong (恒顺通)"
+ # MODEL = "H-777"
+ VENDOR = "Baofeng"
+ MODEL = "BF-888"
+ BAUD_RATE = 9600
+
+ # This code currently requires that ranges start at 0x0000
+ # and are continious. In the original program 0x0388 and 0x03C8
+ # are only written (all bytes 0xFF), not read.
+ #_ranges = [
+ # (0x0000, 0x0110),
+ # (0x02B0, 0x02C0),
+ # (0x0380, 0x03E0)
+ # ]
+ # Memory starts looping at 0x1000... But not every 0x1000.
+
+ _ranges = [
+ (0x0000, 0x0110),
+ (0x02B0, 0x02C0),
+ (0x0380, 0x03E0),
+ ]
+ _memsize = 0x03E0
+
+ def get_features(self):
+ rf = chirp_common.RadioFeatures()
+ rf.has_settings = True
+ rf.valid_modes = ["NFM", "FM"] # 12.5 KHz, 25 kHz.
+ rf.valid_skips = ["", "S"]
+ rf.valid_tmodes = ["", "Tone", "TSQL", "DTCS", "Cross"]
+ rf.has_rx_dtcs = True
+ rf.has_ctone = True
+ rf.has_cross = True
+ rf.has_tuning_step = False
+ rf.has_bank = False
+ rf.has_name = False
+ rf.memory_bounds = (1, 16)
+ rf.valid_bands = [(400000000, 470000000)]
+ rf.valid_power_levels = H777_POWER_LEVELS
+
+ 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):
+ val = int(val)
+ if val == 16665:
+ return '', None, None
+ elif val >= 12000:
+ return 'DTCS', val - 12000, 'R'
+ elif val >= 8000:
+ return 'DTCS', val - 8000, 'N'
+ else:
+ return 'Tone', val / 10.0, None
+
+ def _encode_tone(self, memval, mode, value, pol):
+ 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 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 = not _mem.narrow and "FM" or "NFM"
+ mem.power = H777_POWER_LEVELS[_mem.highpower]
+
+ mem.skip = _mem.skip and "S" or ""
+
+ txtone = self._decode_tone(_mem.txtone)
+ rxtone = self._decode_tone(_mem.rxtone)
+ chirp_common.split_tone_decode(mem, txtone, rxtone)
+
+ mem.extra = RadioSettingGroup("Extra", "extra")
+ rs = RadioSetting("bcl", "Busy Channel Lockout",
+ RadioSettingValueBoolean(not _mem.bcl))
+ mem.extra.append(rs)
+ rs = RadioSetting("beatshift", "Beat Shift(scramble)",
+ RadioSettingValueBoolean(not _mem.beatshift))
+ mem.extra.append(rs)
+
+ return mem
+
+ def set_memory(self, mem):
+ # Get a low-level memory object mapped to the image
+ _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
+
+ txtone, rxtone = chirp_common.split_tone_encode(mem)
+ self._encode_tone(_mem.txtone, *txtone)
+ self._encode_tone(_mem.rxtone, *rxtone)
+
+ _mem.narrow = 'N' in mem.mode
+ _mem.highpower = mem.power == H777_POWER_LEVELS[1]
+ _mem.skip = mem.skip == "S"
+
+ for setting in mem.extra:
+ # NOTE: Only two settings right now, both are inverted
+ setattr(_mem, setting.get_name(), not setting.value)
+
+ def get_settings(self):
+ _settings = self._memobj.settings
+ basic = RadioSettingGroup("basic", "Basic Settings")
+
+ # TODO: Check that all these settings actually do what they
+ # say they do.
+
+ rs = RadioSetting("voiceprompt", "Voice prompt",
+ RadioSettingValueBoolean(_settings.voiceprompt))
+ basic.append(rs)
+
+ rs = RadioSetting("voicelanguage", "Voice language",
+ RadioSettingValueList(VOICE_LIST,
+ VOICE_LIST[_settings.voicelanguage]))
+ basic.append(rs)
+
+ rs = RadioSetting("scan", "Scan",
+ RadioSettingValueBoolean(_settings.scan))
+ basic.append(rs)
+
+ rs = RadioSetting("settings2.scanmode", "Scan mode",
+ RadioSettingValueList(SCANMODE_LIST,
+ SCANMODE_LIST[self._memobj.settings2.scanmode]))
+ basic.append(rs)
+
+ rs = RadioSetting("vox", "VOX",
+ RadioSettingValueBoolean(_settings.vox))
+ basic.append(rs)
+
+ rs = RadioSetting("voxlevel", "VOX level",
+ RadioSettingValueInteger(
+ 1, 5, _settings.voxlevel + 1))
+ basic.append(rs)
+
+ rs = RadioSetting("voxinhibitonrx", "Inhibit VOX on receive",
+ RadioSettingValueBoolean(_settings.voxinhibitonrx))
+ basic.append(rs)
+
+ rs = RadioSetting("lowvolinhibittx", "Low voltage inhibit transmit",
+ RadioSettingValueBoolean(_settings.lowvolinhibittx))
+ basic.append(rs)
+
+ rs = RadioSetting("highvolinhibittx", "High voltage inhibit transmit",
+ RadioSettingValueBoolean(_settings.highvolinhibittx))
+ basic.append(rs)
+
+ rs = RadioSetting("alarm", "Alarm",
+ RadioSettingValueBoolean(_settings.alarm))
+ basic.append(rs)
+
+ # TODO: This should probably be called “FM Broadcast Band Radio”
+ # or something. I'm not sure if the model actually has one though.
+ rs = RadioSetting("fmradio", "FM function",
+ RadioSettingValueBoolean(_settings.fmradio))
+ basic.append(rs)
+
+ rs = RadioSetting("settings2.beep", "Beep",
+ RadioSettingValueBoolean(
+ self._memobj.settings2.beep))
+ basic.append(rs)
+
+ rs = RadioSetting("settings2.batterysaver", "Battery saver",
+ RadioSettingValueBoolean(
+ self._memobj.settings2.batterysaver))
+ basic.append(rs)
+
+ rs = RadioSetting("settings2.squelchlevel", "Squelch level",
+ RadioSettingValueInteger(0, 9,
+ self._memobj.settings2.squelchlevel))
+ basic.append(rs)
+
+ rs = RadioSetting("settings2.sidekeyfunction", "Side key function",
+ RadioSettingValueList(SIDEKEYFUNCTION_LIST,
+ SIDEKEYFUNCTION_LIST[
+ self._memobj.settings2.sidekeyfunction]))
+ basic.append(rs)
+
+ rs = RadioSetting("settings2.timeouttimer", "Timeout timer",
+ RadioSettingValueList(TIMEOUTTIMER_LIST,
+ TIMEOUTTIMER_LIST[
+ self._memobj.settings2.timeouttimer]))
+ basic.append(rs)
+
+ return basic
+
+ 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 element.has_apply_callback():
+ print "Using apply callback"
+ element.run_apply_callback()
+ elif setting == "voxlevel":
+ setattr(obj, setting, int(element.value) - 1)
+ else:
+ print "Setting %s = %s" % (setting, element.value)
+ setattr(obj, setting, element.value)
+ except Exception, e:
+ print element.get_name()
+ raise
+
+class H777TestCase(unittest.TestCase):
+ def setUp(self):
+ self.driver = H777Radio(None)
+ self.testdata = bitwise.parse("lbcd foo[2];",
+ memmap.MemoryMap("\x00\x00"))
+
+ def test_decode_tone_dtcs_normal(self):
+ mode, value, pol = self.driver._decode_tone(8023)
+ self.assertEqual('DTCS', mode)
+ self.assertEqual(23, value)
+ self.assertEqual('N', pol)
+
+ def test_decode_tone_dtcs_rev(self):
+ mode, value, pol = self.driver._decode_tone(12023)
+ self.assertEqual('DTCS', mode)
+ self.assertEqual(23, value)
+ self.assertEqual('R', pol)
+
+ def test_decode_tone_tone(self):
+ mode, value, pol = self.driver._decode_tone(885)
+ self.assertEqual('Tone', mode)
+ self.assertEqual(88.5, value)
+ self.assertEqual(None, pol)
+
+ def test_decode_tone_none(self):
+ mode, value, pol = self.driver._decode_tone(16665)
+ self.assertEqual('', mode)
+ self.assertEqual(None, value)
+ self.assertEqual(None, pol)
+
+ def test_encode_tone_dtcs_normal(self):
+ self.driver._encode_tone(self.testdata.foo, 'DTCS', 23, 'N')
+ self.assertEqual(8023, int(self.testdata.foo))
+
+ def test_encode_tone_dtcs_rev(self):
+ self.driver._encode_tone(self.testdata.foo, 'DTCS', 23, 'R')
+ self.assertEqual(12023, int(self.testdata.foo))
+
+ def test_encode_tone(self):
+ self.driver._encode_tone(self.testdata.foo, 'Tone', 88.5, 'N')
+ self.assertEqual(885, int(self.testdata.foo))
+
+ def test_encode_tone_none(self):
+ self.driver._encode_tone(self.testdata.foo, '', 67.0, 'N')
+ self.assertEqual(16665, int(self.testdata.foo))
diff --git a/chirp/ic208.py b/chirp/ic208.py
index 627607c..f93300c 100644
--- a/chirp/ic208.py
+++ b/chirp/ic208.py
@@ -75,18 +75,19 @@ for i in range(1, 6):
IC208_SPECIAL.append("%iA" % i)
IC208_SPECIAL.append("%iB" % i)
-ALPHA_CHARSET = " ABCDEFGHIJKLMNOPQRSTUVWXYZ"
-NUMERIC_CHARSET = "0123456789+-=*/()|"
+CHARSET = dict(zip([0x00, 0x08, 0x09, 0x0a, 0x0b, 0x0d, 0x0f], " ()*+-/") +
+ zip(range(0x10, 0x1a), "0123456789") +
+ [(0x1c,'|'), (0x1d,'=')] +
+ zip(range(0x21, 0x3b), "ABCDEFGHIJKLMNOPQRSTUVWXYZ"))
+CHARSET_REV = dict(zip(CHARSET.values(), CHARSET.keys()))
def get_name(_mem):
"""Decode the name from @_mem"""
def _get_char(val):
- if val == 0:
- return " "
- elif val & 0x20:
- return ALPHA_CHARSET[val & 0x1F]
- else:
- return NUMERIC_CHARSET[val & 0x0F]
+ try:
+ return CHARSET[int(val)]
+ except KeyError:
+ return "*"
name_bytes = [_mem.name1, _mem.name2, _mem.name3,
_mem.name4, _mem.name5, _mem.name6]
@@ -99,12 +100,10 @@ def get_name(_mem):
def set_name(_mem, name):
"""Encode @name in @_mem"""
def _get_index(char):
- if char == " ":
- return 0
- elif char.isalpha():
- return ALPHA_CHARSET.index(char) | 0x20
- else:
- return NUMERIC_CHARSET.index(char) | 0x10
+ try:
+ return CHARSET_REV[char]
+ except KeyError:
+ return CHARSET_REV["*"]
name = name.ljust(6)[:6]
@@ -144,10 +143,11 @@ class IC208Radio(icf.IcomCloneModeRadio):
rf.valid_duplexes = list(DUPLEX)
rf.valid_power_levels = list(POWER)
rf.valid_skips = ["", "S", "P"]
- rf.valid_bands = [(118000000, 173995000),
- (230000000, 549995000),
- (810000000, 999990000)]
+ rf.valid_bands = [(118000000, 174000000),
+ (230000000, 550000000),
+ (810000000, 999995000)]
rf.valid_special_chans = ["C1", "C2"] + sorted(IC208_SPECIAL)
+ rf.valid_characters = "".join(CHARSET.values())
return rf
def get_raw_memory(self, number):
diff --git a/chirp/ic2820.py b/chirp/ic2820.py
index 6475713..5f5827c 100644
--- a/chirp/ic2820.py
+++ b/chirp/ic2820.py
@@ -176,7 +176,7 @@ class IC2820Radio(icf.IcomCloneModeRadio, chirp_common.IcomDstarSupport):
rf.valid_tuning_steps = list(chirp_common.TUNING_STEPS)
rf.valid_bands = [(118000000, 999990000)]
rf.valid_skips = ["", "S", "P"]
- rf.valid_characters = chirp_common.CHARSET_ALPHANUMERIC
+ rf.valid_characters = chirp_common.CHARSET_ASCII
rf.valid_name_length = 8
rf.valid_special_chans = sorted(_get_special().keys())
diff --git a/chirp/icf.py b/chirp/icf.py
index 8c00bab..fb5950e 100644
--- a/chirp/icf.py
+++ b/chirp/icf.py
@@ -485,10 +485,10 @@ class IcomBankModel(chirp_common.BankModel):
central implementation can, with a few icom-specific radio interfaces
serve most/all of them"""
- def get_num_banks(self):
+ def get_num_mappings(self):
return self._radio._num_banks
- def get_banks(self):
+ def get_mappings(self):
banks = []
for i in range(0, self._radio._num_banks):
@@ -498,31 +498,32 @@ class IcomBankModel(chirp_common.BankModel):
banks.append(bank)
return banks
- def add_memory_to_bank(self, memory, bank):
+ def add_memory_to_mapping(self, memory, bank):
self._radio._set_bank(memory.number, bank.index)
- def remove_memory_from_bank(self, memory, bank):
+ def remove_memory_from_mapping(self, memory, bank):
if self._radio._get_bank(memory.number) != bank.index:
raise Exception("Memory %i not in bank %s. Cannot remove." % \
(memory.number, bank))
self._radio._set_bank(memory.number, None)
- def get_bank_memories(self, bank):
+ def get_mapping_memories(self, bank):
memories = []
for i in range(*self._radio.get_features().memory_bounds):
if self._radio._get_bank(i) == bank.index:
memories.append(self._radio.get_memory(i))
return memories
- def get_memory_banks(self, memory):
+ def get_memory_mappings(self, memory):
index = self._radio._get_bank(memory.number)
if index is None:
return []
else:
- return [self.get_banks()[index]]
+ return [self.get_mappings()[index]]
-class IcomIndexedBankModel(IcomBankModel, chirp_common.BankIndexInterface):
+class IcomIndexedBankModel(IcomBankModel,
+ chirp_common.MappingModelIndexInterface):
"""Generic bank model for Icom radios with indexed banks"""
def get_index_bounds(self):
return self._radio._bank_index_bounds
@@ -531,7 +532,7 @@ class IcomIndexedBankModel(IcomBankModel, chirp_common.BankIndexInterface):
return self._radio._get_bank_index(memory.number)
def set_memory_index(self, memory, bank, index):
- if bank not in self.get_memory_banks(memory):
+ if bank not in self.get_memory_mappings(memory):
raise Exception("Memory %i is not in bank %s" % (memory.number,
bank))
@@ -539,7 +540,7 @@ class IcomIndexedBankModel(IcomBankModel, chirp_common.BankIndexInterface):
raise Exception("Invalid index")
self._radio._set_bank_index(memory.number, index)
- def get_next_bank_index(self, bank):
+ def get_next_mapping_index(self, bank):
indexes = []
for i in range(*self._radio.get_features().memory_bounds):
if self._radio._get_bank(i) == bank.index:
diff --git a/chirp/icq7.py b/chirp/icq7.py
index a6e2e95..6a95ef9 100644
--- a/chirp/icq7.py
+++ b/chirp/icq7.py
@@ -13,9 +13,14 @@
# 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 struct
from chirp import chirp_common, icf, directory
from chirp import bitwise
from chirp.chirp_common import to_GHz, from_GHz
+from chirp.settings import RadioSetting, RadioSettingGroup, \
+ RadioSettingValueBoolean, RadioSettingValueList, \
+ RadioSettingValueInteger, RadioSettingValueString, \
+ RadioSettingValueFloat
MEM_FORMAT = """
struct {
@@ -39,12 +44,52 @@ struct {
#seekto 0x0690;
u8 flags_whole[200];
+
+#seekto 0x0767;
+struct {
+i8 rit;
+u8 squelch;
+u8 lock:1,
+ ritfunct:1,
+ unknown:6;
+u8 unknown1[6];
+u8 d_sel;
+u8 autorp;
+u8 priority;
+u8 resume;
+u8 pause;
+u8 p_scan;
+u8 bnk_scan;
+u8 expand;
+u8 ch;
+u8 beep;
+u8 light;
+u8 ap_off;
+u8 p_save;
+u8 monitor;
+u8 speed;
+u8 edge;
+u8 lockgroup;
+} settings;
+
"""
TMODES = ["", "", "Tone", "TSQL", "TSQL"] # last one is pocket beep
DUPLEX = ["", "", "-", "+"]
MODES = ["FM", "WFM", "AM", "Auto"]
STEPS = [5.0, 6.25, 10.0, 12.5, 15.0, 20.0, 25.0, 30.0, 50.0, 100.0]
+AUTORP_LIST = ["Off", "Duplex Only", "Duplex and Tone"]
+LOCKGROUP_LIST = ["Normal", "No Squelch", "No Volume", "All"]
+SQUELCH_LIST = ["Open", "Auto"] + ["L%s" % x for x in range(1, 10)]
+MONITOR_LIST = ["Push", "Hold"]
+LIGHT_LIST = ["Off", "On", "Auto"]
+PRIORITY_LIST = ["Off", "On", "Bell"]
+BANKSCAN_LIST = ["Off", "Bank 0", "Bank 1"]
+EDGE_LIST = ["%sP" % x for x in range(0, 20)] + ["Band", "All"]
+PAUSE_LIST = ["%s sec" % x for x in range(2, 22, 2)] + ["Hold"]
+RESUME_LIST = ["%s sec" % x for x in range(0, 6)]
+APOFF_LIST = ["Off"] + ["%s min" % x for x in range(30, 150, 30)]
+D_SEL_LIST = ["100 KHz", "1 MHz", "10 MHz"]
@directory.register
class ICQ7Radio(icf.IcomCloneModeRadio):
@@ -60,6 +105,7 @@ class ICQ7Radio(icf.IcomCloneModeRadio):
def get_features(self):
rf = chirp_common.RadioFeatures()
+ rf.has_settings = True
rf.memory_bounds = (0, 199)
rf.valid_modes = list(MODES)
rf.valid_tmodes = list(TMODES)
@@ -148,3 +194,141 @@ class ICQ7Radio(icf.IcomCloneModeRadio):
_flag.mode = MODES.index(mem.mode)
_flag.skip = mem.skip == "S" and 1 or 0
_flag.pskip = mem.skip == "P" and 1 or 0
+
+ def get_settings(self):
+ _settings = self._memobj.settings
+ basic = RadioSettingGroup("basic", "Basic Settings")
+ group = RadioSettingGroup("top", "All Settings", basic)
+
+ rs = RadioSetting("ch", "Channel Indication Mode",
+ RadioSettingValueBoolean(_settings.ch))
+ basic.append(rs)
+
+ rs = RadioSetting("expand", "Expanded Settings Mode",
+ RadioSettingValueBoolean(_settings.expand))
+ basic.append(rs)
+
+ rs = RadioSetting("beep", "Beep Tones",
+ RadioSettingValueBoolean(_settings.beep))
+ basic.append(rs)
+
+ rs = RadioSetting("autorp", "Auto Repeater Function",
+ RadioSettingValueList(AUTORP_LIST,
+ AUTORP_LIST[_settings.autorp]))
+ basic.append(rs)
+
+ rs = RadioSetting("ritfunct", "RIT Runction",
+ RadioSettingValueBoolean(_settings.ritfunct))
+ basic.append(rs)
+
+ rs = RadioSetting("rit", "RIT Shift (KHz)",
+ RadioSettingValueInteger(-7, 7, _settings.rit))
+ basic.append(rs)
+
+ rs = RadioSetting("lock", "Lock",
+ RadioSettingValueBoolean(_settings.lock))
+ basic.append(rs)
+
+ rs = RadioSetting("lockgroup", "Lock Group",
+ RadioSettingValueList(LOCKGROUP_LIST,
+ LOCKGROUP_LIST[_settings.lockgroup]))
+ basic.append(rs)
+
+ rs = RadioSetting("squelch", "Squelch",
+ RadioSettingValueList(SQUELCH_LIST,
+ SQUELCH_LIST[_settings.squelch]))
+ basic.append(rs)
+
+ rs = RadioSetting("monitor", "Monitor Switch Function",
+ RadioSettingValueList(MONITOR_LIST,
+ MONITOR_LIST[_settings.monitor]))
+ basic.append(rs)
+
+ rs = RadioSetting("light", "Display Backlighting",
+ RadioSettingValueList(LIGHT_LIST,
+ LIGHT_LIST[_settings.light]))
+ basic.append(rs)
+
+ rs = RadioSetting("priority", "Priority Watch Operation",
+ RadioSettingValueList(PRIORITY_LIST,
+ PRIORITY_LIST[_settings.priority]))
+ basic.append(rs)
+
+ rs = RadioSetting("p_scan", "Frequency Skip Function",
+ RadioSettingValueBoolean(_settings.p_scan))
+ basic.append(rs)
+
+ rs = RadioSetting("bnk_scan", "Memory Bank Scan Selection",
+ RadioSettingValueList(BANKSCAN_LIST,
+ BANKSCAN_LIST[_settings.bnk_scan]))
+ basic.append(rs)
+
+ rs = RadioSetting("edge", "Band Edge Scan Selection",
+ RadioSettingValueList(EDGE_LIST,
+ EDGE_LIST[_settings.edge]))
+ basic.append(rs)
+
+ rs = RadioSetting("pause", "Scan Pause Time",
+ RadioSettingValueList(PAUSE_LIST,
+ PAUSE_LIST[_settings.pause]))
+ basic.append(rs)
+
+ rs = RadioSetting("resume", "Scan Resume Time",
+ RadioSettingValueList(RESUME_LIST,
+ RESUME_LIST[_settings.resume]))
+ basic.append(rs)
+
+ rs = RadioSetting("p_save", "Power Saver",
+ RadioSettingValueBoolean(_settings.p_save))
+ basic.append(rs)
+
+ rs = RadioSetting("ap_off", "Auto Power-off Function",
+ RadioSettingValueList(APOFF_LIST,
+ APOFF_LIST[_settings.ap_off]))
+ basic.append(rs)
+
+ rs = RadioSetting("speed", "Dial Speed Acceleration",
+ RadioSettingValueBoolean(_settings.speed))
+ basic.append(rs)
+
+ rs = RadioSetting("d_sel", "Dial Select Step",
+ RadioSettingValueList(D_SEL_LIST,
+ D_SEL_LIST[_settings.d_sel]))
+ basic.append(rs)
+
+ return group
+
+ def set_settings(self, settings):
+ _settings = self._memobj.settings
+ 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():
+ print "Using apply callback"
+ element.run_apply_callback()
+ else:
+ print "Setting %s = %s" % (setting, element.value)
+ setattr(obj, setting, element.value)
+ except Exception, e:
+ print element.get_name()
+ raise
+
diff --git a/chirp/ict70.py b/chirp/ict70.py
index 391b129..d035e14 100644
--- a/chirp/ict70.py
+++ b/chirp/ict70.py
@@ -77,7 +77,7 @@ class ICT70Bank(icf.IcomBank):
def set_name(self, name):
_bank = self._model._radio._memobj.bank_names[self.index]
- _bank.name = name.ljust(8)[:8]
+ _bank.name = name.ljust(6)[:6]
@directory.register
class ICT70Radio(icf.IcomCloneModeRadio):
diff --git a/chirp/ict7h.py b/chirp/ict7h.py
index 4781652..768fb34 100644
--- a/chirp/ict7h.py
+++ b/chirp/ict7h.py
@@ -57,7 +57,7 @@ class ICT7HRadio(icf.IcomCloneModeRadio):
def get_features(self):
rf = chirp_common.RadioFeatures()
- rf.memory_bounds = (0, 59)
+ rf.memory_bounds = (1, 60)
rf.valid_modes = list(MODES)
rf.valid_tmodes = list(TMODES)
rf.valid_duplexes = list(DUPLEX)
@@ -79,8 +79,8 @@ class ICT7HRadio(icf.IcomCloneModeRadio):
return repr(self._memobj.memory[number])
def get_memory(self, number):
- _mem = self._memobj.memory[number]
- _flag = self._memobj.flags[number]
+ _mem = self._memobj.memory[number - 1]
+ _flag = self._memobj.flags[number - 1]
mem = chirp_common.Memory()
mem.number = number
@@ -103,8 +103,8 @@ class ICT7HRadio(icf.IcomCloneModeRadio):
return mem
def set_memory(self, mem):
- _mem = self._memobj.memory[mem.number]
- _flag = self._memobj.flags[mem.number]
+ _mem = self._memobj.memory[mem.number - 1]
+ _flag = self._memobj.flags[mem.number - 1]
_mem.freq = int(mem.freq / 100000)
topfreq = int(mem.freq / 100000) * 100000
diff --git a/chirp/id31.py b/chirp/id31.py
index b381d07..128d79d 100644
--- a/chirp/id31.py
+++ b/chirp/id31.py
@@ -22,9 +22,7 @@ struct {
u16 rtone:6,
ctone:6,
unknown2:1,
- is_dv:1,
- unknown2_0:1,
- is_narrow:1;
+ mode:3;
u8 dtcs;
u8 tune_step:4,
unknown5:4;
@@ -79,6 +77,7 @@ struct {
#seekto 0x10F20;
struct {
char call[8];
+ char tag[4];
} mycall[6];
#seekto 0x10F68;
@@ -166,6 +165,8 @@ class ID31Radio(icf.IcomCloneModeRadio, chirp_common.IcomDstarSupport):
_ranges = [(0x00000, 0x15500, 32)]
+ MODES = {0: "FM", 1: "NFM", 5: "DV"}
+
def _get_bank(self, loc):
_bank = self._memobj.banks[loc]
if _bank.bank == 0xFF:
@@ -198,7 +199,7 @@ class ID31Radio(icf.IcomCloneModeRadio, chirp_common.IcomDstarSupport):
rf.has_bank_names = True
rf.valid_tmodes = list(TMODES)
rf.valid_tuning_steps = sorted(list(TUNING_STEPS))
- rf.valid_modes = ["FM", "NFM", "DV"]
+ rf.valid_modes = self.MODES.values()
rf.valid_skips = ["", "S", "P"]
rf.valid_characters = chirp_common.CHARSET_ASCII
rf.valid_name_length = 16
@@ -218,7 +219,7 @@ class ID31Radio(icf.IcomCloneModeRadio, chirp_common.IcomDstarSupport):
bit = (1 << (number % 8))
- if _mem.is_dv:
+ if self.MODES[int(_mem.mode)] == "DV":
mem = chirp_common.DVMemory()
else:
mem = chirp_common.Memory()
@@ -237,16 +238,12 @@ class ID31Radio(icf.IcomCloneModeRadio, chirp_common.IcomDstarSupport):
mem.dtcs = chirp_common.DTCS_CODES[_mem.dtcs]
mem.dtcs_polarity = DTCS_POLARITY[_mem.dtcs_polarity]
mem.tuning_step = TUNING_STEPS[_mem.tune_step]
+ mem.mode = self.MODES[int(_mem.mode)]
- if _mem.is_dv:
- mem.mode = "DV"
+ if mem.mode == "DV":
mem.dv_urcall = _decode_call(_mem.urcall).rstrip()
mem.dv_rpt1call = _decode_call(_mem.rpt1call).rstrip()
mem.dv_rpt2call = _decode_call(_mem.rpt2call).rstrip()
- elif _mem.is_narrow:
- mem.mode = "NFM"
- else:
- mem.mode = "FM"
if _psk & bit:
mem.skip = "P"
@@ -279,9 +276,8 @@ class ID31Radio(icf.IcomCloneModeRadio, chirp_common.IcomDstarSupport):
_mem.dtcs = chirp_common.DTCS_CODES.index(memory.dtcs)
_mem.dtcs_polarity = DTCS_POLARITY.index(memory.dtcs_polarity)
_mem.tune_step = TUNING_STEPS.index(memory.tuning_step)
-
- _mem.is_narrow = memory.mode in ["NFM", "DV"]
- _mem.is_dv = memory.mode == "DV"
+ _mem.mode = next(i for i, mode in self.MODES.items() \
+ if mode == memory.mode)
if isinstance(memory, chirp_common.DVMemory):
_mem.urcall = _encode_call(memory.dv_urcall.ljust(8))
diff --git a/chirp/id51.py b/chirp/id51.py
new file mode 100644
index 0000000..4b72c44
--- /dev/null
+++ b/chirp/id51.py
@@ -0,0 +1,109 @@
+# Copyright 2012 Dan Smith <dsmith at danplanet.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 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/>.
+
+from chirp import directory, bitwise, id31
+
+MEM_FORMAT = """
+struct {
+ u24 freq;
+ u16 offset;
+ u16 rtone:6,
+ ctone:6,
+ unknown2:1,
+ mode:3;
+ u8 dtcs;
+ u8 tune_step:4,
+ unknown5:4;
+ u8 unknown4;
+ u8 tmode:4,
+ duplex:2,
+ dtcs_polarity:2;
+ char name[16];
+ u8 unknown13;
+ u8 urcall[7];
+ u8 rpt1call[7];
+ u8 rpt2call[7];
+} memory[500];
+
+#seekto 0x6A40;
+u8 used_flags[70];
+
+#seekto 0x6A86;
+u8 skip_flags[69];
+
+#seekto 0x6ACB;
+u8 pskp_flags[69];
+
+#seekto 0x6B40;
+struct {
+ u8 bank;
+ u8 index;
+} banks[500];
+
+#seekto 0x6FD0;
+struct {
+ char name[16];
+} bank_names[26];
+
+#seekto 0xA8C0;
+struct {
+ u24 freq;
+ u16 offset;
+ u8 unknown1[3];
+ u8 call[7];
+ char name[16];
+ char subname[8];
+ u8 unknown3[10];
+} repeaters[750];
+
+#seekto 0x1384E;
+struct {
+ u8 call[7];
+} rptcall[750];
+
+#seekto 0x14E60;
+struct {
+ char call[8];
+ char tag[4];
+} mycall[6];
+
+#seekto 0x14EA8;
+struct {
+ char call[8];
+} urcall[200];
+
+"""
+
+
+ at directory.register
+class ID51Radio(id31.ID31Radio):
+ """Icom ID-51"""
+ MODEL = "ID-51A"
+
+ _memsize = 0x1FB40
+ _model = "\x33\x90\x00\x01"
+ _endframe = "Icom Inc\x2E\x44\x41"
+
+ _ranges = [(0x00000, 0x1FB40, 32)]
+
+ MODES = {0: "FM", 1: "NFM", 3: "AM", 5: "DV"}
+
+ def get_features(self):
+ rf = super(ID51Radio, self).get_features()
+ rf.valid_bands = [(108000000, 174000000), (400000000, 479000000)]
+ return rf
+
+ def process_mmap(self):
+ self._memobj = bitwise.parse(MEM_FORMAT, self._mmap)
diff --git a/chirp/import_logic.py b/chirp/import_logic.py
index e52283a..b787378 100644
--- a/chirp/import_logic.py
+++ b/chirp/import_logic.py
@@ -210,38 +210,42 @@ def import_mem(dst_radio, src_features, src_mem, overrides={}):
", ".join(errs))
return dst_mem
-
+
+def _get_bank_model(radio):
+ for model in radio.get_mapping_models():
+ if isinstance(model, chirp_common.BankModel):
+ return model
+ return None
+
def import_bank(dst_radio, src_radio, dst_mem, src_mem):
"""Attempt to set the same banks for @mem(by index) in @dst_radio that
it has in @src_radio"""
- dst_bm = dst_radio.get_bank_model()
+ dst_bm = _get_bank_model(dst_radio)
if not dst_bm:
return
- dst_banks = dst_bm.get_banks()
+ dst_banks = dst_bm.get_mappings()
- src_bm = src_radio.get_bank_model()
+ src_bm = _get_bank_model(src_radio)
if not src_bm:
return
- src_banks = src_bm.get_banks()
- src_mem_banks = src_bm.get_memory_banks(src_mem)
+ src_banks = src_bm.get_mappings()
+ src_mem_banks = src_bm.get_memory_mappings(src_mem)
src_indexes = [src_banks.index(b) for b in src_mem_banks]
- for bank in dst_bm.get_memory_banks(dst_mem):
- dst_bm.remove_memory_from_bank(dst_mem, bank)
+ for bank in dst_bm.get_memory_mappings(dst_mem):
+ dst_bm.remove_memory_from_mapping(dst_mem, bank)
for index in src_indexes:
try:
bank = dst_banks[index]
print "Adding memory to bank %s" % bank
- dst_bm.add_memory_to_bank(dst_mem, bank)
- if isinstance(dst_bm, chirp_common.BankIndexInterface):
+ dst_bm.add_memory_to_mapping(dst_mem, bank)
+ if isinstance(dst_bm, chirp_common.MappingModelIndexInterface):
dst_bm.set_memory_index(dst_mem, bank,
- dst_bm.get_next_bank_index(bank))
+ dst_bm.get_next_mapping_index(bank))
except IndexError:
pass
-
-
diff --git a/chirp/kenwood_hmk.py b/chirp/kenwood_hmk.py
index de52f68..59260ac 100644
--- a/chirp/kenwood_hmk.py
+++ b/chirp/kenwood_hmk.py
@@ -120,3 +120,8 @@ class HMKRadio(generic_csv.CSVRadio):
if not good:
print self.errors
raise errors.InvalidDataError("No channels found")
+
+ @classmethod
+ def match_model(cls, filedata, filename):
+ """Match files ending in .hmk"""
+ return filename.lower().endswith("." + cls.FILE_EXTENSION)
diff --git a/chirp/kenwood_hmk.py b/chirp/kenwood_itm.py
similarity index 62%
copy from chirp/kenwood_hmk.py
copy to chirp/kenwood_itm.py
index de52f68..86414eb 100644
--- a/chirp/kenwood_hmk.py
+++ b/chirp/kenwood_itm.py
@@ -23,47 +23,53 @@ class OmittedHeaderError(Exception):
pass
@directory.register
-class HMKRadio(generic_csv.CSVRadio):
- """Kenwood HMK format"""
+class ITMRadio(generic_csv.CSVRadio):
+ """Kenwood ITM format"""
VENDOR = "Kenwood"
- MODEL = "HMK"
- FILE_EXTENSION = "hmk"
-
- DUPLEX_MAP = {
- " ": "",
- "S": "split",
- "+": "+",
- "-": "-",
- }
-
- SKIP_MAP = {
- "Off": "",
- "On": "S",
- }
-
- TMODE_MAP = {
- "Off": "",
- "T": "Tone",
- "CT": "TSQL",
- "DCS": "DTCS",
- "": "Cross",
- }
+ MODEL = "ITM"
+ FILE_EXTENSION = "itm"
ATTR_MAP = {
- "!!Ch" : (int, "number"),
- "M.Name" : (str, "name"),
- "Rx Freq." : (chirp_common.parse_freq, "freq"),
- "Shift/Split" : (lambda v: HMKRadio.DUPLEX_MAP[v], "duplex"),
- "Offset" : (chirp_common.parse_freq, "offset"),
- "T/CT/DCS" : (lambda v: HMKRadio.TMODE_MAP[v], "tmode"),
- "TO Freq." : (float, "rtone"),
- "CT Freq." : (float, "ctone"),
- "DCS Code" : (int, "dtcs"),
- "Mode" : (str, "mode"),
- "Rx Step" : (float, "tuning_step"),
- "L.Out" : (lambda v: HMKRadio.SKIP_MAP[v], "skip"),
+ "CH" : (int, "number"),
+ "RXF" : (chirp_common.parse_freq, "freq"),
+ "NAME" : (str, "name"),
}
+ def _clean_duplex(self, headers, line, mem):
+ try:
+ txfreq = chirp_common.parse_freq(
+ generic_csv.get_datum_by_header(headers, line, "TXF"))
+ except ValueError:
+ mem.duplex = "off"
+ return mem
+
+ if mem.freq == txfreq:
+ mem.duplex = ""
+ elif txfreq:
+ mem.duplex = "split"
+ mem.offset = txfreq
+
+ return mem
+
+ def _clean_number(self, headers, line, mem):
+ zone = int(generic_csv.get_datum_by_header(headers, line, "ZN"))
+ mem.number = zone * 100 + mem.number
+ return mem
+
+ def _clean_tmode(self, headers, line, mem):
+ rtone = eval(generic_csv.get_datum_by_header(headers, line, "TXSIG"))
+ ctone = eval(generic_csv.get_datum_by_header(headers, line, "RXSIG"))
+
+ if rtone:
+ mem.tmode = "Tone"
+ if ctone:
+ mem.tmode = "TSQL"
+
+ mem.rtone = rtone or 88.5
+ mem.ctone = ctone or mem.rtone
+
+ return mem
+
def load(self, filename=None):
if filename is None and self._filename is None:
raise errors.RadioError("Need a location to load from")
@@ -75,7 +81,7 @@ class HMKRadio(generic_csv.CSVRadio):
f = file(self._filename, "r")
for line in f:
- if line.strip() == "// Memory Channels":
+ if line.strip() == "// Conventional Data":
break
reader = csv.reader(f, delimiter=chirp_common.SEPCHAR, quotechar='"')
@@ -88,6 +94,10 @@ class HMKRadio(generic_csv.CSVRadio):
header = line
continue
+ if len(line) == 0:
+ # End of channel data
+ break
+
if len(header) > len(line):
print "Line %i has %i columns, expected %i" % (lineno,
len(line),
@@ -95,12 +105,6 @@ class HMKRadio(generic_csv.CSVRadio):
self.errors.append("Column number mismatch on line %i" % lineno)
continue
- # hmk stores Tx Freq. in its own field, but Chirp expects the Tx
- # Freq. for odd-split channels to be in the Offset field.
- # If channel is odd-split, copy Tx Freq. field to Offset field.
- if line[header.index('Shift/Split')] == "S":
- line[header.index('Offset')] = line[header.index('Tx Freq.')]
-
# fix EU decimal
line = [i.replace(',','.') for i in line]
@@ -120,3 +124,7 @@ class HMKRadio(generic_csv.CSVRadio):
if not good:
print self.errors
raise errors.InvalidDataError("No channels found")
+
+ @classmethod
+ def match_model(cls, filedata, filename):
+ return filename.lower().endswith("." + cls.FILE_EXTENSION)
diff --git a/chirp/kenwood_live.py b/chirp/kenwood_live.py
index d66a73f..bfa87dd 100644
--- a/chirp/kenwood_live.py
+++ b/chirp/kenwood_live.py
@@ -158,7 +158,7 @@ class KenwoodLiveRadio(chirp_common.LiveRadio):
return self.__memcache[number]
result = command(self.pipe, *self._cmd_get_memory(number))
- if result == "N":
+ if result == "N" or result == "E":
mem = chirp_common.Memory()
mem.number = number
mem.empty = True
@@ -268,8 +268,23 @@ TH_D7_SETTINGS = {
"9 digit NMEA", "6 digit Magellan", "DGPS"],
}
+class KenwoodOldLiveRadio(KenwoodLiveRadio):
+ _kenwood_valid_tones = list(chirp_common.OLD_TONES)
+
+ def set_memory(self, memory):
+ supported_tones = list(chirp_common.OLD_TONES)
+ supported_tones.remove(69.3)
+ if memory.rtone not in supported_tones:
+ raise errors.UnsupportedToneError("This radio does not support " +
+ "tone %.1fHz" % memory.rtone)
+ if memory.ctone not in supported_tones:
+ raise errors.UnsupportedToneError("This radio does not support " +
+ "tone %.1fHz" % memory.ctone)
+
+ return KenwoodLiveRadio.set_memory(self, memory)
+
@directory.register
-class THD7Radio(KenwoodLiveRadio):
+class THD7Radio(KenwoodOldLiveRadio):
"""Kenwood TH-D7"""
MODEL = "TH-D7"
@@ -475,7 +490,7 @@ class THD7GRadio(THD7Radio):
MODEL = "TH-D7G"
@directory.register
-class TMD700Radio(KenwoodLiveRadio):
+class TMD700Radio(KenwoodOldLiveRadio):
"""Kenwood TH-D700"""
MODEL = "TM-D700"
@@ -542,40 +557,13 @@ class TMD700Radio(KenwoodLiveRadio):
return mem
-OLD_TONES = list(chirp_common.TONES)
-OLD_TONES.remove(159.8)
-OLD_TONES.remove(165.5)
-OLD_TONES.remove(171.3)
-OLD_TONES.remove(177.3)
-OLD_TONES.remove(183.5)
-OLD_TONES.remove(189.9)
-OLD_TONES.remove(196.6)
-OLD_TONES.remove(199.5)
-OLD_TONES.remove(206.5)
-OLD_TONES.remove(229.1)
-OLD_TONES.remove(254.1)
-
@directory.register
-class TMV7Radio(KenwoodLiveRadio):
+class TMV7Radio(KenwoodOldLiveRadio):
"""Kenwood TM-V7"""
MODEL = "TM-V7"
mem_upper_limit = 200 # Will be updated
- _kenwood_valid_tones = list(OLD_TONES)
-
- def set_memory(self, memory):
- supported_tones = list(OLD_TONES)
- supported_tones.remove(69.3)
- if memory.rtone not in supported_tones:
- raise errors.UnsupportedToneError("This radio does not support " +
- "tone %.1fHz" % memory.rtone)
- if memory.ctone not in supported_tones:
- raise errors.UnsupportedToneError("This radio does not support " +
- "tone %.1fHz" % memory.ctone)
-
- return KenwoodLiveRadio.set_memory(self, memory)
-
def get_features(self):
rf = chirp_common.RadioFeatures()
rf.has_dtcs = False
@@ -680,10 +668,63 @@ class TMG707Radio(TMV7Radio):
rf = TMV7Radio.get_features(self)
rf.has_sub_devices = False
rf.memory_bounds = (1, 180)
- rf.valid_bands = [(144000000, 148000000),
- (430000000, 450000000)]
+ rf.valid_bands = [(118000000, 174000000),
+ (300000000, 520000000),
+ (800000000, 999000000)]
return rf
+THG71_STEPS = [ 5, 6.25, 10, 12.5, 15, 20, 25, 30, 50, 100 ]
+ at directory.register
+class THG71Radio(TMV7Radio):
+ """Kenwood TH-G71"""
+ MODEL = "TH-G71"
+
+ def get_features(self):
+ rf = TMV7Radio.get_features(self)
+ rf.has_tuning_step = True
+ rf.valid_tuning_steps = list(THG71_STEPS)
+ rf.valid_name_length = 6
+ rf.has_sub_devices = False
+ rf.valid_bands = [(118000000, 174000000),
+ (320000000, 470000000),
+ (800000000, 945000000)]
+ return rf
+
+ def _make_mem_spec(self, mem):
+ spec = ( \
+ "%011i" % mem.freq,
+ "%X" % THG71_STEPS.index(mem.tuning_step),
+ "%i" % util.get_dict_rev(DUPLEX, mem.duplex),
+ "0",
+ "%i" % (mem.tmode == "Tone"),
+ "%i" % (mem.tmode == "TSQL"),
+ "0",
+ "%02i" % (self._kenwood_valid_tones.index(mem.rtone) + 1),
+ "000",
+ "%02i" % (self._kenwood_valid_tones.index(mem.ctone) + 1),
+ "%09i" % mem.offset,
+ "%i" % ((mem.skip == "S") and 1 or 0))
+ return spec
+
+ def _parse_mem_spec(self, spec):
+ mem = chirp_common.Memory()
+ mem.number = int(spec[2])
+ mem.freq = int(spec[3])
+ mem.tuning_step = THG71_STEPS[int(spec[4], 16)]
+ mem.duplex = DUPLEX[int(spec[5])]
+ if int(spec[7]):
+ mem.tmode = "Tone"
+ elif int(spec[8]):
+ mem.tmode = "TSQL"
+ mem.rtone = self._kenwood_valid_tones[int(spec[10]) - 1]
+ mem.ctone = self._kenwood_valid_tones[int(spec[12]) - 1]
+ if spec[13]:
+ mem.offset = int(spec[13])
+ else:
+ mem.offset = 0
+ return mem
+
+
THF6A_STEPS = [5.0, 6.25, 8.33, 9.0, 10.0, 12.5, 15.0, 20.0, 25.0, 30.0, 50.0,
100.0]
@@ -1084,6 +1125,12 @@ class TM271Radio(THK2Radio):
def _cmd_set_memory_name(self, number, name):
return "MN", "%03i,%s" % (number, name)
+ at directory.register
+class TM281Radio(TM271Radio):
+ """Kenwood TM-281"""
+ MODEL = "TM-281"
+ # seems that this is a perfect clone of TM271 with just a different model
+
def do_test():
"""Dev test"""
mem = chirp_common.Memory()
@@ -1114,5 +1161,70 @@ def do_test():
radio = tc(FakeSerial())
radio.set_memory(mem)
+ at directory.register
+class TM471Radio(THK2Radio):
+ """Kenwood TM-471"""
+ MODEL = "TM-471"
+
+ def get_features(self):
+ rf = chirp_common.RadioFeatures()
+ rf.can_odd_split = False
+ rf.has_dtcs_polarity = False
+ rf.has_bank = False
+ rf.has_tuning_step = False
+ rf.valid_tmodes = ["", "Tone", "TSQL", "DTCS"]
+ rf.valid_modes = THK2_MODES
+ rf.valid_duplexes = THK2_DUPLEX
+ rf.valid_characters = THK2_CHARS
+ rf.valid_name_length = 6
+ rf.valid_bands = [(444000000, 479990000)]
+ rf.valid_skips = ["", "S"]
+ rf.valid_tuning_steps = [5.0]
+ rf.memory_bounds = (0, 99)
+ return rf
+
+ def _cmd_get_memory(self, number):
+ return "ME", "%03i" % number
+
+ def _cmd_get_memory_name(self, number):
+ return "MN", "%03i" % number
+
+ def _cmd_set_memory(self, number, spec):
+ return "ME", "%03i,%s" % (number, spec)
+
+ def _cmd_set_memory_name(self, number, name):
+ return "MN", "%03i,%s" % (number, name)
+
+def do_test():
+ """Dev test"""
+ mem = chirp_common.Memory()
+ mem.number = 1
+ mem.freq = 440000000
+ mem.duplex = "split"
+ mem.offset = 442000000
+
+ tc = THF6ARadio
+ class FakeSerial:
+ """Faked serial line"""
+ buf = ""
+ def write(self, buf):
+ """Write"""
+ self.buf = buf
+ def read(self, count):
+ """Read"""
+ if self.buf[:2] == "ID":
+ return "ID %s\r" % tc.MODEL
+ return self.buf
+ def setTimeout(self, foo):
+ """Set Timeout"""
+ pass
+ def setBaudrate(self, foo):
+ """Set Baudrate"""
+ pass
+
+ radio = tc(FakeSerial())
+ radio.set_memory(mem)
+
if __name__ == "__main__":
do_test()
+
diff --git a/chirp/platform.py b/chirp/platform.py
index da346a4..313dfbd 100644
--- a/chirp/platform.py
+++ b/chirp/platform.py
@@ -16,41 +16,47 @@
import os
import sys
import glob
+import re
from subprocess import Popen
+def win32_comports_bruteforce():
+ import win32file
+ import win32con
+
+ ports = []
+ for i in range(1, 257):
+ portname = "COM%i" % i
+ try:
+ mode = win32con.GENERIC_READ | win32con.GENERIC_WRITE
+ port = \
+ win32file.CreateFile(portname,
+ mode,
+ win32con.FILE_SHARE_READ,
+ None,
+ win32con.OPEN_EXISTING,
+ 0,
+ None)
+ ports.append((portname,"Unknown","Serial"))
+ win32file.CloseHandle(port)
+ port = None
+ except Exception, e:
+ pass
+
+ return ports
+
try:
from serial.tools.list_ports import comports
except:
- def comports():
- import win32file
- import win32con
-
- ports = []
- for i in range(1, 257):
- portname = "COM%i" % i
- print "Trying %s" % portname
- try:
- mode = win32con.GENERIC_READ | win32con.GENERIC_WRITE
- port = \
- win32file.CreateFile(portname,
- mode,
- win32con.FILE_SHARE_READ,
- None,
- win32con.OPEN_EXISTING,
- 0,
- None)
- ports.append((portname,"Unknown","Serial"))
- win32file.CloseHandle(port)
- port = None
- except Exception, e:
- print "Failed: %s" % e
- pass
-
- return ports
-
+ comports = win32_comports_bruteforce
+
def _find_me():
return sys.modules["chirp.platform"].__file__
+def natural_sorted(l):
+ convert = lambda text: int(text) if text.isdigit() else text.lower()
+ natural_key = lambda key: [convert(c) for c in re.split('([0-9]+)', key)]
+ return sorted(l, key=natural_key)
+
class Platform:
"""Base class for platform-specific functions"""
@@ -277,11 +283,13 @@ class UnixPlatform(Platform):
os.system("firefox '%s'" % path)
def list_serial_ports(self):
- return sorted(glob.glob("/dev/ttyS*") +
- glob.glob("/dev/ttyUSB*") +
- glob.glob("/dev/cu.*") +
- glob.glob("/dev/term/*") +
- glob.glob("/dev/tty.KeySerial*"))
+ ports = ["/dev/ttyS*",
+ "/dev/ttyUSB*",
+ "/dev/ttyAMA*",
+ "/dev/cu.*",
+ "/dev/term/*",
+ "/dev/tty.KeySerial*"]
+ return natural_sorted(sum([glob.glob(x) for x in ports], []))
def os_version_string(self):
try:
@@ -324,17 +332,15 @@ class Win32Platform(Platform):
def open_html_file(self, path):
os.system("explorer %s" % path)
-
+
def list_serial_ports(self):
- def cmp(a, b):
- try:
- return int(a[3:]) - int(b[3:])
- except:
- return 0
-
- ports = [port for port, name, url in comports()]
- ports.sort(cmp=cmp)
- return ports
+ try:
+ ports = list(comports())
+ except Exception, e:
+ if comports != win32_comports_bruteforce:
+ print "Failed to detect win32 serial ports: %s" % e
+ ports = win32_comports_bruteforce()
+ return natural_sorted([port for port, name, url in ports])
def gui_open_file(self, start_dir=None, types=[]):
import win32gui
diff --git a/chirp/puxing.py b/chirp/puxing.py
index 05602ef..88cbe79 100644
--- a/chirp/puxing.py
+++ b/chirp/puxing.py
@@ -159,7 +159,7 @@ class Puxing777Radio(chirp_common.CloneModeRadio):
rf.valid_tmodes = ["", "Tone", "TSQL", "DTCS"]
rf.valid_modes = ["FM", "NFM"]
rf.valid_power_levels = POWER_LEVELS
- rf.valid_characters = "0123456789ABCDEFGHIJKLMNOPQRSTUVWXYZ"
+ rf.valid_characters = ''.join(set(PUXING_CHARSET))
rf.valid_name_length = 6
rf.has_ctone = False
rf.has_tuning_step = False
@@ -167,15 +167,24 @@ class Puxing777Radio(chirp_common.CloneModeRadio):
rf.memory_bounds = (1, 128)
if not hasattr(self, "_memobj") or self._memobj is None:
- limit_idx = 1
- else:
- limit_idx = self._memobj.model.limits - 0xEE
- try:
- rf.valid_bands = [PUXING_777_BANDS[limit_idx]]
- except IndexError:
- print "Invalid band index %i (0x%02x)" % \
- (limit_idx, self._memobj.model.limits)
rf.valid_bands = [PUXING_777_BANDS[1]]
+ elif self._memobj.model.model == PUXING_MODELS[777]:
+ limit_idx = self._memobj.model.limits - 0xEE
+ try:
+ rf.valid_bands = [PUXING_777_BANDS[limit_idx]]
+ except IndexError:
+ print "Invalid band index %i (0x%02x)" % \
+ (limit_idx, self._memobj.model.limits)
+ rf.valid_bands = [PUXING_777_BANDS[1]]
+ elif self._memobj.model.model == PUXING_MODELS[328]:
+ # There are PX-777 that says to be model 328 ...
+ # for them we only know this freq limits till now
+ if self._memobj.model.limits == 0xEE:
+ rf.valid_bands = [PUXING_777_BANDS[1]]
+ else:
+ raise Exception("Unsupported band limits 0x%02x for PX-777" % \
+ (self._memobj.model.limits) +
+ " submodel 328 - PLEASE REPORT THIS ERROR TO DEVELOPERS!!")
return rf
@@ -188,10 +197,13 @@ class Puxing777Radio(chirp_common.CloneModeRadio):
@classmethod
def match_model(cls, filedata, filename):
- if len(filedata) > 0x080B and \
- ord(filedata[0x080B]) != PUXING_MODELS[777]:
- return False
- return len(filedata) == 3168
+ # There are PX-777 that says to be model 328 ...
+ return len(filedata) == 3168 and (
+ ord(filedata[0x080B]) == PUXING_MODELS[777] or (
+ ord(filedata[0x080B]) == PUXING_MODELS[328] and
+ ord(filedata[0x080A]) == 0xEE
+ )
+ )
def get_memory(self, number):
_mem = self._memobj.memory[number - 1]
@@ -275,6 +287,7 @@ class Puxing777Radio(chirp_common.CloneModeRadio):
if i == 0xFF:
break
mem.name += PUXING_CHARSET[i]
+ mem.name = mem.name.rstrip()
return mem
diff --git a/chirp/pyPEG.py b/chirp/pyPEG.py
index 381a403..5faa637 100644
--- a/chirp/pyPEG.py
+++ b/chirp/pyPEG.py
@@ -1,14 +1,16 @@
-# YPL parser 0.45
+# YPL parser 1.5
# written by VB.
import re
+import sys, codecs
+import exceptions
-class keyword(str): pass
-class code(str): pass
+class keyword(unicode): pass
+class code(unicode): pass
class ignore(object):
- def __init__(self, regex_text):
- self.regex = re.compile(regex_text)
+ def __init__(self, regex_text, *args):
+ self.regex = re.compile(regex_text, *args)
class _and(object):
def __init__(self, something):
@@ -16,18 +18,44 @@ class _and(object):
class _not(_and): pass
-class Name(str):
+class Name(unicode):
def __init__(self, *args):
self.line = 0
- self.file = ""
-
-word_regex = re.compile(r"\w+")
-rest_regex = re.compile(r".*")
-ignoring = ignore("")
+ self.file = u""
+
+class Symbol(list):
+ def __init__(self, name, what):
+ self.__name__ = name
+ self.append(name)
+ self.what = what
+ self.append(what)
+ def __call__(self):
+ return self.what
+ def __unicode__(self):
+ return u'Symbol(' + repr(self.__name__) + ', ' + repr(self.what) + u')'
+ def __repr__(self):
+ return unicode(self)
+
+word_regex = re.compile(ur"\w+")
+rest_regex = re.compile(ur".*")
+
+print_trace = False
+
+def u(text):
+ if isinstance(text, exceptions.BaseException):
+ text = text.args[0]
+ if type(text) is unicode:
+ return text
+ if isinstance(text, str):
+ if sys.stdin.encoding:
+ return codecs.decode(text, sys.stdin.encoding)
+ else:
+ return codecs.decode(text, "utf-8")
+ return unicode(text)
-def skip(skipper, text, pattern, skipWS, skipComments):
+def skip(skipper, text, skipWS, skipComments):
if skipWS:
- t = text.strip()
+ t = text.lstrip()
else:
t = text
if skipComments:
@@ -35,21 +63,22 @@ def skip(skipper, text, pattern, skipWS, skipComments):
while True:
skip, t = skipper.parseLine(t, skipComments, [], skipWS, None)
if skipWS:
- t = t.strip()
+ t = t.lstrip()
except: pass
return t
class parser(object):
- def __init__(self, another = False):
+ def __init__(self, another = False, p = False):
self.restlen = -1
if not(another):
- self.skipper = parser(True)
+ self.skipper = parser(True, p)
+ self.skipper.packrat = p
else:
self.skipper = self
self.lines = None
self.textlen = 0
self.memory = {}
- self.packrat = False
+ self.packrat = p
# parseLine():
# textline: text to parse
@@ -69,100 +98,111 @@ class parser(object):
name = None
_textline = textline
_pattern = pattern
- _packrat = self.packrat
- _memory = self.memory
def R(result, text):
+ if __debug__:
+ if print_trace:
+ try:
+ if _pattern.__name__ != "comment":
+ sys.stderr.write(u"match: " + _pattern.__name__ + u"\n")
+ except: pass
+
if self.restlen == -1:
self.restlen = len(text)
else:
self.restlen = min(self.restlen, len(text))
res = resultSoFar
if name and result:
- res.append((name, result))
+ name.line = self.lineNo()
+ res.append(Symbol(name, result))
elif name:
- res.append((name, []))
+ name.line = self.lineNo()
+ res.append(Symbol(name, []))
elif result:
if type(result) is type([]):
res.extend(result)
else:
res.extend([result])
- if _packrat:
- if name:
- _memory[(len(_textline), id(_pattern))] = (res, text)
+ if self.packrat:
+ self.memory[(len(_textline), id(_pattern))] = (res, text)
return res, text
def syntaxError():
- if _packrat:
- if name:
- _memory[(len(_textline), id(_pattern))] = False
+ if self.packrat:
+ self.memory[(len(_textline), id(_pattern))] = False
raise SyntaxError()
- if type(pattern) is type(lambda x: 0):
- if _packrat:
- try:
- result = _memory[(len(_textline), id(_pattern))]
- if result:
- return result
- else:
- raise SyntaxError()
- except: pass
+ if self.packrat:
+ try:
+ result = self.memory[(len(textline), id(pattern))]
+ if result:
+ return result
+ else:
+ raise SyntaxError()
+ except: pass
+
+ if callable(pattern):
+ if __debug__:
+ if print_trace:
+ try:
+ if pattern.__name__ != "comment":
+ sys.stderr.write(u"testing with " + pattern.__name__ + u": " + textline[:40] + u"\n")
+ except: pass
if pattern.__name__[0] != "_":
name = Name(pattern.__name__)
- name.line = self.lineNo()
pattern = pattern()
- if type(pattern) is type(lambda x: 0):
+ if callable(pattern):
pattern = (pattern,)
- text = skip(self.skipper, textline, pattern, skipWS, skipComments)
+ text = skip(self.skipper, textline, skipWS, skipComments)
pattern_type = type(pattern)
- if pattern_type is type(""):
+ if pattern_type is str or pattern_type is unicode:
if text[:len(pattern)] == pattern:
- text = skip(self.skipper, text[len(pattern):], pattern, skipWS, skipComments)
+ text = skip(self.skipper, text[len(pattern):], skipWS, skipComments)
return R(None, text)
else:
syntaxError()
- elif pattern_type is type(keyword("")):
+ elif pattern_type is keyword:
m = word_regex.match(text)
if m:
if m.group(0) == pattern:
- text = skip(self.skipper, text[len(pattern):], pattern, skipWS, skipComments)
+ text = skip(self.skipper, text[len(pattern):], skipWS, skipComments)
return R(None, text)
else:
syntaxError()
else:
syntaxError()
- elif pattern_type is type(_not("")):
+ elif pattern_type is _not:
try:
r, t = self.parseLine(text, pattern.obj, [], skipWS, skipComments)
except:
return resultSoFar, textline
syntaxError()
- elif pattern_type is type(_and("")):
+ elif pattern_type is _and:
r, t = self.parseLine(text, pattern.obj, [], skipWS, skipComments)
return resultSoFar, textline
- elif pattern_type is type(word_regex) or pattern_type is type(ignoring):
- if pattern_type is type(ignoring):
+ elif pattern_type is type(word_regex) or pattern_type is ignore:
+ if pattern_type is ignore:
pattern = pattern.regex
m = pattern.match(text)
if m:
- text = skip(self.skipper, text[len(m.group(0)):], pattern, skipWS, skipComments)
- if pattern_type is type(ignoring):
+ text = skip(self.skipper, text[len(m.group(0)):], skipWS, skipComments)
+ if pattern_type is ignore:
return R(None, text)
else:
return R(m.group(0), text)
else:
syntaxError()
- elif pattern_type is type((None,)):
+ elif pattern_type is tuple:
result = []
n = 1
for p in pattern:
@@ -194,7 +234,7 @@ class parser(object):
n = 1
return R(result, text)
- elif pattern_type is type([]):
+ elif pattern_type is list:
result = []
found = False
for p in pattern:
@@ -211,42 +251,41 @@ class parser(object):
syntaxError()
else:
- raise SyntaxError("illegal type in grammar: " + str(pattern_type))
+ raise SyntaxError(u"illegal type in grammar: " + u(pattern_type))
def lineNo(self):
- if not(self.lines): return ""
- if self.restlen == -1: return ""
+ if not(self.lines): return u""
+ if self.restlen == -1: return u""
parsed = self.textlen - self.restlen
left, right = 0, len(self.lines)
while True:
- mid = (right + left) / 2
+ mid = int((right + left) / 2)
if self.lines[mid][0] <= parsed:
try:
if self.lines[mid + 1][0] >= parsed:
try:
- return self.lines[mid + 1][1] + ":" + str(self.lines[mid + 1][2])
+ return u(self.lines[mid + 1][1]) + u":" + u(self.lines[mid + 1][2])
except:
- return ""
+ return u""
else:
left = mid + 1
except:
try:
- return self.lines[mid + 1][1] + ":" + str(self.lines[mid + 1][2])
+ return u(self.lines[mid + 1][1]) + u":" + u(self.lines[mid + 1][2])
except:
- return ""
+ return u""
else:
right = mid - 1
if left > right:
- return ""
+ return u""
# plain module API
def parseLine(textline, pattern, resultSoFar = [], skipWS = True, skipComments = None, packrat = False):
- p = parser()
- p.packrat = packrat
- text = skip(p.skipper, textline, pattern, skipWS, skipComments)
+ p = parser(p=packrat)
+ text = skip(p.skipper, textline, skipWS, skipComments)
ast, text = p.parseLine(text, pattern, resultSoFar, skipWS, skipComments)
return ast, text
@@ -266,28 +305,28 @@ def parseLine(textline, pattern, resultSoFar = [], skipWS = True, skipComments =
def parse(language, lineSource, skipWS = True, skipComments = None, packrat = False, lineCount = True):
lines, lineNo = [], 0
- while type(language) is type(lambda x: 0):
+ while callable(language):
language = language()
- orig, ld = "", 0
+ orig, ld = u"", 0
for line in lineSource:
if lineSource.isfirstline():
ld = 1
else:
ld += 1
lines.append((len(orig), lineSource.filename(), lineSource.lineno() - 1))
- orig += line
+ orig += u(line)
+
textlen = len(orig)
try:
- p = parser()
- p.packrat = packrat
+ p = parser(p=packrat)
p.textlen = len(orig)
if lineCount:
p.lines = lines
else:
p.line = None
- text = skip(p.skipper, orig, language, skipWS, skipComments)
+ text = skip(p.skipper, orig, skipWS, skipComments)
result, text = p.parseLine(text, language, [], skipWS, skipComments)
if text:
raise SyntaxError()
@@ -295,7 +334,7 @@ def parse(language, lineSource, skipWS = True, skipComments = None, packrat = Fa
except SyntaxError, msg:
parsed = textlen - p.restlen
textlen = 0
- nn, lineNo, file = 0, 0, ""
+ nn, lineNo, file = 0, 0, u""
for n, ld, l in lines:
if n >= parsed:
break
@@ -307,6 +346,6 @@ def parse(language, lineSource, skipWS = True, skipComments = None, packrat = Fa
lineNo += 1
nn -= 1
lineCont = orig.splitlines()[nn]
- raise SyntaxError("syntax error in " + file + ":" + str(l) + ": " + lineCont)
+ raise SyntaxError(u"syntax error in " + u(file) + u":" + u(lineNo) + u": " + lineCont)
return result
diff --git a/chirp/radioreference.py b/chirp/radioreference.py
index 6db0ceb..9ca54aa 100644
--- a/chirp/radioreference.py
+++ b/chirp/radioreference.py
@@ -16,6 +16,7 @@
from chirp import chirp_common, errors
try:
from suds.client import Client
+ from suds import WebFault
HAVE_SUDS = True
except ImportError:
HAVE_SUDS = False
@@ -62,8 +63,11 @@ class RadioReferenceRadio(chirp_common.NetworkSourceRadio):
"""Fetches frequencies for all subcategories in a county."""
self._freqs = []
- zipcode = self._client.service.getZipcodeInfo(self._zip, self._auth)
- county = self._client.service.getCountyInfo(zipcode.ctid, self._auth)
+ try:
+ zipcode = self._client.service.getZipcodeInfo(self._zip, self._auth)
+ county = self._client.service.getCountyInfo(zipcode.ctid, self._auth)
+ except WebFault, err:
+ raise errors.RadioError(err)
status = chirp_common.Status()
status.max = 0
diff --git a/chirp/settings.py b/chirp/settings.py
index 111c424..ad1e15c 100644
--- a/chirp/settings.py
+++ b/chirp/settings.py
@@ -28,16 +28,30 @@ class RadioSettingValue:
def __init__(self):
self._current = None
self._has_changed = False
+ self._validate_callback = lambda x: x
+ self._mutable = True
+
+ def set_mutable(self, mutable):
+ self._mutable = mutable
+
+ def get_mutable(self):
+ return self._mutable
def changed(self):
"""Returns True if the setting has been changed since init"""
return self._has_changed
+ def set_validate_callback(self, callback):
+ self._validate_callback = callback
+
def set_value(self, value):
"""Sets the current value, triggers changed"""
+ if not self.get_mutable():
+ raise InvalidValueError("This value is not mutable")
+
if self._current != None and value != self._current:
self._has_changed = True
- self._current = value
+ self._current = self._validate_callback(value)
def get_value(self):
"""Gets the current value"""
@@ -81,6 +95,45 @@ class RadioSettingValueInteger(RadioSettingValue):
"""Returns the step increment"""
return self._step
+class RadioSettingValueFloat(RadioSettingValue):
+ """A floating-point setting"""
+ def __init__(self, minval, maxval, current, resolution=0.001, precision=4):
+ RadioSettingValue.__init__(self)
+ self._min = minval
+ self._max = maxval
+ self._res = resolution
+ self._pre = precision
+ self.set_value(current)
+
+ def format(self, value=None):
+ """Formats the value into a string"""
+ if value is None:
+ value = self._current
+ fmt_string = "%%.%if" % self._pre
+ print fmt_string
+ return fmt_string % value
+
+ def set_value(self, value):
+ try:
+ value = float(value)
+ except:
+ 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)))
+
+ # FIXME: honor resolution
+
+ RadioSettingValue.set_value(self, value)
+
+ def get_min(self):
+ """Returns the minimum allowed value"""
+ return self._min
+
+ def get_max(self):
+ """Returns the maximum allowed value"""
+
class RadioSettingValueBoolean(RadioSettingValue):
"""A boolean setting"""
def __init__(self, current):
@@ -147,7 +200,7 @@ class RadioSettingGroup(object):
def _validate(self, element):
# RadioSettingGroup can only contain RadioSettingGroup objects
if not isinstance(element, RadioSettingGroup):
- raise InternalError("Incorrect type")
+ raise InternalError("Incorrect type %s" % type(element))
def __init__(self, name, shortname, *elements):
self._name = name # Setting identifier
@@ -158,7 +211,6 @@ class RadioSettingGroup(object):
for element in elements:
self._validate(element)
- print "Appending element to %s" % self._name
self.append(element)
def get_name(self):
@@ -231,6 +283,19 @@ 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
+
+ def set_apply_callback(self, callback, *args):
+ self._apply_callback = lambda: callback(self, *args)
+
+ def has_apply_callback(self):
+ return self._apply_callback is not None
+
+ def run_apply_callback(self):
+ return self._apply_callback()
+
def _validate(self, value):
# RadioSetting can only contain RadioSettingValue objects
if not isinstance(value, RadioSettingValue):
diff --git a/chirp/th_uv3r.py b/chirp/th_uv3r.py
index f06ba45..5e3e2c0 100644
--- a/chirp/th_uv3r.py
+++ b/chirp/th_uv3r.py
@@ -90,7 +90,7 @@ class TYTUV3RRadio(chirp_common.CloneModeRadio):
rf.valid_modes = ["FM", "NFM"]
rf.valid_name_length = 6
rf.valid_characters = THUV3R_CHARSET
- rf.valid_bands = [(136000000, 470000000)]
+ rf.valid_bands = [(136000000, 520000000)]
rf.valid_tuning_steps = [5.0, 6.25, 10.0, 12.5, 25.0, 37.50,
50.0, 100.0]
rf.valid_skips = ["", "S"]
diff --git a/chirp/th_uvf8d.py b/chirp/th_uvf8d.py
new file mode 100644
index 0000000..74e73ce
--- /dev/null
+++ b/chirp/th_uvf8d.py
@@ -0,0 +1,623 @@
+# Copyright 2013 Dan Smith <dsmith at danplanet.com>, Eric Allen <eric at hackerengineer.net>
+#
+# 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/>.
+
+"""TYT TH-UVF8D radio management module"""
+
+# TODO: support FM Radio memories
+# TODO: support bank B (another 128 memories)
+# TODO: [setting] Battery Save
+# TODO: [setting] Tail Eliminate
+# TODO: [setting] Tail Mode
+
+
+import struct
+
+from chirp import chirp_common, bitwise, errors, directory, memmap, util
+from chirp.settings import RadioSetting, RadioSettingGroup, \
+ RadioSettingValueInteger, RadioSettingValueList, \
+ RadioSettingValueBoolean, RadioSettingValueString
+
+
+def uvf8d_identify(radio):
+ """Do identify handshake with TYT TH-UVF8D"""
+ try:
+ radio.pipe.write("\x02PROGRAM")
+ ack = radio.pipe.read(2)
+ if ack != "PG":
+ raise errors.RadioError("Radio did not ACK first command: %x" % ord(ack))
+ except:
+ raise errors.RadioError("Unable to communicate with the radio")
+
+ radio.pipe.write("\x02")
+ ident = radio.pipe.read(32)
+ radio.pipe.write("A")
+ r = radio.pipe.read(1)
+ if r != "A":
+ raise errors.RadioError("Ack failed")
+ return ident
+
+
+def tyt_uvf8d_download(radio):
+ data = uvf8d_identify(radio)
+ for i in range(0, 0x4000, 0x20):
+ msg = struct.pack(">cHb", "R", i, 0x20)
+ radio.pipe.write(msg)
+ block = radio.pipe.read(0x20 + 4)
+ if len(block) != (0x20 + 4):
+ raise errors.RadioError("Radio sent a short block")
+ radio.pipe.write("A")
+ ack = radio.pipe.read(1)
+ if ack != "A":
+ raise errors.RadioError("Radio NAKed block")
+ data += block[4:]
+
+ if radio.status_fn:
+ status = chirp_common.Status()
+ status.cur = i
+ status.max = 0x4000
+ status.msg = "Cloning from radio"
+ radio.status_fn(status)
+
+ radio.pipe.write("ENDR")
+
+ return memmap.MemoryMap(data)
+
+
+def tyt_uvf8d_upload(radio):
+ """Upload to TYT TH-UVF8D"""
+ data = uvf8d_identify(radio)
+
+ radio.pipe.setTimeout(1)
+
+ if data != radio._mmap[:32]:
+ raise errors.RadioError("Model mis-match: \n%s\n%s" % (util.hexprint(data),
+ util.hexprint(radio._mmap[:32])))
+
+ for i in range(0, 0x4000, 0x20):
+ addr = i + 0x20
+ msg = struct.pack(">cHb", "W", i, 0x20)
+ msg += radio._mmap[addr:(addr + 0x20)]
+
+ radio.pipe.write(msg)
+ ack = radio.pipe.read(1)
+ if ack != "A":
+ raise errors.RadioError("Radio did not ack block %i" % i)
+
+ if radio.status_fn:
+ status = chirp_common.Status()
+ status.cur = i
+ status.max = 0x4000
+ status.msg = "Cloning to radio"
+ radio.status_fn(status)
+
+ # End of clone?
+ radio.pipe.write("ENDW")
+
+ # Checksum?
+ final_data = radio.pipe.read(3)
+
+# these require working desktop software
+# TODO: DTMF features (ID, delay, speed, kill, etc.)
+
+# TODO: Display Name
+
+
+UVF8D_MEM_FORMAT = """
+struct memory {
+ lbcd rx_freq[4];
+ lbcd tx_freq[4];
+ lbcd rx_tone[2];
+ lbcd tx_tone[2];
+
+ u8 apro:4,
+ rpt_md:2,
+ unknown1:2;
+ u8 bclo:2,
+ wideband:1,
+ ishighpower:1,
+ unknown21:1,
+ vox:1,
+ pttid:2;
+ u8 unknown3:8;
+
+ u8 unknown4:6,
+ duplex:2;
+
+ lbcd offset[4];
+
+ char unknown5[4];
+
+ char name[7];
+
+ char unknown6[1];
+};
+
+struct fm_broadcast_memory {
+ lbcd freq[3];
+ u8 unknown;
+};
+
+struct enable_flags {
+ bit flags[8];
+};
+
+#seekto 0x0020;
+struct memory channels[128];
+
+#seekto 0x2020;
+struct memory vfo1;
+struct memory vfo2;
+
+#seekto 0x2060;
+struct {
+ u8 unknown2060:4,
+ tot:4;
+ u8 unknown2061;
+ u8 squelch;
+ u8 unknown2063:4,
+ vox_level:4;
+ u8 tuning_step;
+ char unknown12;
+ u8 lamp_t;
+ char unknown11;
+ u8 unknown2068;
+ u8 ani:1,
+ scan_mode:2,
+ unknown2069:2,
+ beep:1,
+ tx_sel:1,
+ roger:1;
+ u8 light:2,
+ led:2,
+ unknown206a:1,
+ autolk:1,
+ unknown206ax:2;
+ u8 unknown206b:1,
+ b_display:2,
+ a_display:2,
+ ab_switch:1,
+ dwait:1,
+ mode:1;
+ u8 dw:1,
+ unknown206c:6,
+ voice:1;
+ u8 unknown206d:2,
+ rxsave:2,
+ opnmsg:2,
+ lock_mode:2;
+ u8 a_work_area:1,
+ b_work_area:1,
+ unknown206ex:6;
+ u8 a_channel;
+ u8 b_channel;
+ u8 pad3[15];
+ char ponmsg[7];
+} settings;
+
+#seekto 0x2E60;
+struct enable_flags enable[16];
+struct enable_flags skip[16];
+
+#seekto 0x2FA0;
+struct fm_broadcast_memory fm_current;
+
+#seekto 0x2FA8;
+struct fm_broadcast_memory fm_memories[20];
+"""
+
+THUVF8D_DUPLEX = ["", "-", "+"]
+THUVF8D_CHARSET = "".join([chr(ord("0") + x) for x in range(0, 10)] +
+ [" -*+"] +
+ [chr(ord("A") + x) for x in range(0, 26)] +
+ ["_/"])
+TXSEL_LIST = ["EDIT", "BUSY"]
+LED_LIST = ["Off", "Auto", "On"]
+MODE_LIST = ["Memory", "VFO"]
+AB_LIST = ["A", "B"]
+DISPLAY_LIST = ["Channel", "Frequency", "Name"]
+LIGHT_LIST = ["Purple", "Orange", "Blue"]
+RPTMD_LIST = ["Off", "Reverse", "Talkaround"]
+VOX_LIST = ["1", "2", "3", "4", "5", "6", "7", "8"]
+WIDEBAND_LIST = ["Narrow", "Wide"]
+TOT_LIST = ["Off", "30s", "60s", "90s", "120s", "150s", "180s", "210s",
+ "240s", "270s"]
+SCAN_MODE_LIST = ["Time", "Carry", "Seek"]
+OPNMSG_LIST = ["Off", "DC (Battery)", "Message"]
+
+POWER_LEVELS = [chirp_common.PowerLevel("High", watts=5),
+ chirp_common.PowerLevel("Low", watts=0.5),
+ ]
+
+PTTID_LIST = ["Off", "BOT", "EOT", "Both"]
+BCLO_LIST = ["Off", "Wave", "Call"]
+APRO_LIST = ["Off", "Compander", "Scramble 1", "Scramble 2", "Scramble 3",
+ "Scramble 4", "Scramble 5", "Scramble 6", "Scramble 7",
+ "Scramble 8"]
+LOCK_MODE_LIST = ["PTT", "Key", "Key+S", "All"]
+
+TUNING_STEPS_LIST = ["2.5", "5.0", "6.25", "10.0", "12.5", "25.0", "50.0", "100.0"]
+BACKLIGHT_TIMEOUT_LIST = ["1s", "2s", "3s", "4s", "5s", "6s", "7s", "8s", "9s", "10s"]
+
+SPECIALS = {
+ "VFO1": -2,
+ "VFO2": -1}
+
+
+ at directory.register
+class TYTUVF8DRadio(chirp_common.CloneModeRadio):
+ VENDOR = "TYT"
+ MODEL = "TH-UVF8D"
+ BAUD_RATE = 9600
+
+ def get_features(self):
+ rf = chirp_common.RadioFeatures()
+ rf.memory_bounds = (1, 128)
+ rf.has_bank = False
+ rf.has_ctone = True
+ rf.has_tuning_step = False
+ rf.has_cross = False
+ rf.has_rx_dtcs = True
+ rf.has_settings = True
+ rf.can_odd_split = False # it may actually be supported, but I haven't tested
+ rf.valid_duplexes = THUVF8D_DUPLEX
+ rf.valid_tmodes = ["", "Tone", "TSQL", "DTCS", "Cross"]
+ rf.valid_characters = chirp_common.CHARSET_UPPER_NUMERIC + "-"
+ rf.valid_bands = [(136000000, 174000000),
+ (400000000, 520000000)]
+ rf.valid_skips = ["", "S"]
+ rf.valid_power_levels = POWER_LEVELS
+ rf.valid_modes = ["FM", "NFM"]
+ rf.valid_special_chans = SPECIALS.keys()
+ rf.valid_name_length = 7
+ return rf
+
+ def sync_in(self):
+ self._mmap = tyt_uvf8d_download(self)
+ self.process_mmap()
+
+ def sync_out(self):
+ tyt_uvf8d_upload(self)
+
+ @classmethod
+ def match_model(cls, filedata, filename):
+ return filedata.startswith("TYT-F10\x00")
+
+ def process_mmap(self):
+ self._memobj = bitwise.parse(UVF8D_MEM_FORMAT, self._mmap)
+
+ def _decode_tone(self, toneval):
+ pol = "N"
+ rawval = (toneval[1].get_bits(0xFF) << 8) | toneval[0].get_bits(0xFF)
+
+ if toneval[0].get_bits(0xFF) == 0xFF:
+ mode = ""
+ val = 0
+ elif toneval[1].get_bits(0xC0) == 0xC0:
+ mode = "DTCS"
+ val = int("%x" % (rawval & 0x3FFF))
+ pol = "R"
+ elif toneval[1].get_bits(0x80):
+ mode = "DTCS"
+ val = int("%x" % (rawval & 0x3FFF))
+ else:
+ mode = "Tone"
+ val = int(toneval) / 10.0
+
+ return mode, val, pol
+
+ def _encode_tone(self, _toneval, mode, val, pol):
+ toneval = 0
+ if mode == "Tone":
+ toneval = int("%i" % (val * 10), 16)
+ elif mode == "DTCS":
+ toneval = int("%i" % val, 16)
+ toneval |= 0x8000
+ if pol == "R":
+ toneval |= 0x4000
+ else:
+ toneval = 0xFFFF
+
+ _toneval[0].set_raw(toneval & 0xFF)
+ _toneval[1].set_raw((toneval >> 8) & 0xFF)
+
+ def get_raw_memory(self, number):
+ return repr(self._memobj.channels[number - 1])
+
+ def _get_memobjs(self, number):
+ if isinstance(number, str):
+ return (getattr(self._memobj, number.lower()), None)
+ elif number < 0:
+ for k, v in SPECIALS.items():
+ if number == v:
+ return (getattr(self._memobj, k.lower()), None)
+ else:
+ return (self._memobj.channels[number - 1],
+ None)
+
+ def get_memory(self, number):
+ _mem, _name = self._get_memobjs(number)
+
+ mem = chirp_common.Memory()
+
+ if isinstance(number, str):
+ mem.number = SPECIALS[number]
+ mem.extd_number = number
+ else:
+ mem.number = number
+
+ if _mem.get_raw().startswith("\xFF\xFF\xFF\xFF"):
+ mem.empty = True
+ return mem
+
+ if isinstance(number, int):
+ enabled = self._memobj.enable[(number - 1) / 8].flags[7 - ((number - 1) % 8)]
+ dont_skip = self._memobj.skip[(number - 1) / 8].flags[7 - ((number - 1) % 8)]
+ else:
+ enabled = True
+ dont_skip = True
+
+ if not enabled:
+ mem.empty = True
+ return mem
+
+ mem.freq = int(_mem.rx_freq) * 10
+
+ mem.duplex = THUVF8D_DUPLEX[_mem.duplex]
+ mem.offset = int(_mem.offset) * 10
+
+ txmode, txval, txpol = self._decode_tone(_mem.tx_tone)
+ rxmode, rxval, rxpol = self._decode_tone(_mem.rx_tone)
+
+ chirp_common.split_tone_decode(mem,
+ (txmode, txval, txpol),
+ (rxmode, rxval, rxpol))
+
+ mem.name = str(_mem.name).rstrip('\xFF ')
+
+ mem.skip = dont_skip and "" or "S"
+
+ mem.mode = _mem.wideband and "FM" or "NFM"
+ mem.power = POWER_LEVELS[1 - _mem.ishighpower]
+
+ mem.extra = RadioSettingGroup("extra", "Extra Settings")
+
+ rs = RadioSetting("pttid", "PTT ID",
+ RadioSettingValueList(PTTID_LIST,
+ PTTID_LIST[_mem.pttid]))
+ mem.extra.append(rs)
+
+ rs = RadioSetting("vox", "VOX",
+ RadioSettingValueBoolean(_mem.vox))
+ mem.extra.append(rs)
+
+ rs = RadioSetting("bclo", "Busy Channel Lockout",
+ RadioSettingValueList(BCLO_LIST,
+ BCLO_LIST[_mem.bclo]))
+ mem.extra.append(rs)
+
+ rs = RadioSetting("apro", "APRO",
+ RadioSettingValueList(APRO_LIST,
+ APRO_LIST[_mem.apro]))
+ mem.extra.append(rs)
+
+ rs = RadioSetting("rpt_md", "Repeater Mode",
+ RadioSettingValueList(RPTMD_LIST,
+ RPTMD_LIST[_mem.rpt_md]))
+ mem.extra.append(rs)
+
+ return mem
+
+ def set_memory(self, mem):
+ _mem, _name = self._get_memobjs(mem.number)
+
+ if mem.empty:
+ _mem.set_raw("\xFF" * 32)
+ self._memobj.enable[(mem.number - 1) / 8].flags[7 - ((mem.number - 1) % 8)] = False
+ self._memobj.skip[(mem.number - 1) / 8].flags[7 - ((mem.number - 1) % 8)] = False
+ return
+ else:
+ self._memobj.enable[(mem.number - 1) / 8].flags[7 - ((mem.number - 1) % 8)] = True
+
+ if _mem.get_raw() == ("\xFF" * 32):
+ print "Initializing empty memory"
+ _mem.set_raw("\x00" * 32)
+
+ _mem.rx_freq = mem.freq / 10
+ if mem.duplex == "-":
+ _mem.tx_freq = (mem.freq - mem.offset) / 10
+ elif mem.duplex == "+":
+ _mem.tx_freq = (mem.freq + mem.offset) / 10
+ else:
+ _mem.tx_freq = mem.freq / 10
+
+ _mem.duplex = THUVF8D_DUPLEX.index(mem.duplex)
+ _mem.offset = mem.offset / 10
+
+ (txmode, txval, txpol), (rxmode, rxval, rxpol) = \
+ chirp_common.split_tone_encode(mem)
+
+ self._encode_tone(_mem.tx_tone, txmode, txval, txpol)
+ self._encode_tone(_mem.rx_tone, rxmode, rxval, rxpol)
+
+ _mem.name = mem.name.rstrip(' ').ljust(7, "\xFF")
+
+ flag_index = 7 - ((mem.number - 1) % 8)
+ self._memobj.skip[(mem.number - 1) / 8].flags[flag_index] = (mem.skip == "")
+ _mem.wideband = mem.mode == "FM"
+ _mem.ishighpower = mem.power == POWER_LEVELS[0]
+
+ for element in mem.extra:
+ setattr(_mem, element.get_name(), element.value)
+
+ def get_settings(self):
+ _settings = self._memobj.settings
+
+ group = RadioSettingGroup("top", "All Settings")
+
+ group.append(
+ RadioSetting("mode", "Mode",
+ RadioSettingValueList(MODE_LIST,
+ MODE_LIST[_settings.mode])))
+
+ group.append(
+ RadioSetting("ab_switch", "A/B",
+ RadioSettingValueList(AB_LIST,
+ AB_LIST[_settings.ab_switch])))
+
+ group.append(
+ RadioSetting("a_channel", "A Selected Memory",
+ RadioSettingValueInteger(1, 128, _settings.a_channel + 1)))
+
+ group.append(
+ RadioSetting("b_channel", "B Selected Memory",
+ RadioSettingValueInteger(1, 128, _settings.b_channel + 1)))
+
+ group.append(
+ RadioSetting("a_display", "A Channel Display",
+ RadioSettingValueList(DISPLAY_LIST,
+ DISPLAY_LIST[_settings.a_display])))
+ group.append(
+ RadioSetting("b_display", "B Channel Display",
+ RadioSettingValueList(DISPLAY_LIST,
+ DISPLAY_LIST[_settings.b_display])))
+ group.append(
+ RadioSetting("tx_sel", "Priority Transmit",
+ RadioSettingValueList(TXSEL_LIST,
+ TXSEL_LIST[_settings.tx_sel])))
+ group.append(
+ RadioSetting("vox_level", "VOX Level",
+ RadioSettingValueList(VOX_LIST,
+ VOX_LIST[_settings.vox_level])))
+
+ group.append(
+ RadioSetting("squelch", "Squelch Level",
+ RadioSettingValueInteger(0, 9, _settings.squelch)))
+
+ group.append(
+ RadioSetting("dwait", "Dual Wait",
+ RadioSettingValueBoolean(_settings.dwait)))
+
+ group.append(
+ RadioSetting("led", "LED Mode",
+ RadioSettingValueList(LED_LIST,
+ LED_LIST[_settings.led])))
+
+ group.append(
+ RadioSetting("light", "Light Color",
+ RadioSettingValueList(LIGHT_LIST,
+ LIGHT_LIST[_settings.light])))
+
+ group.append(
+ RadioSetting("beep", "Beep",
+ RadioSettingValueBoolean(_settings.beep)))
+
+ group.append(
+ RadioSetting("ani", "ANI",
+ RadioSettingValueBoolean(_settings.ani)))
+
+ group.append(
+ RadioSetting("tot", "Timeout Timer",
+ RadioSettingValueList(TOT_LIST,
+ TOT_LIST[_settings.tot])))
+
+ group.append(
+ RadioSetting("roger", "Roger Beep",
+ RadioSettingValueBoolean(_settings.roger)))
+
+ group.append(
+ RadioSetting("dw", "Dual Watch",
+ RadioSettingValueBoolean(_settings.dw)))
+
+ group.append(
+ RadioSetting("rxsave", "RX Save",
+ RadioSettingValueBoolean(_settings.rxsave)))
+
+ def _filter(name):
+ return str(name).rstrip("\xFF").rstrip()
+
+ group.append(
+ RadioSetting("ponmsg", "Power-On Message",
+ RadioSettingValueString(0, 7,
+ _filter(_settings.ponmsg))))
+
+ group.append(
+ RadioSetting("scan_mode", "Scan Mode",
+ RadioSettingValueList(SCAN_MODE_LIST,
+ SCAN_MODE_LIST[_settings.scan_mode])))
+
+ group.append(
+ RadioSetting("autolk", "Auto Lock",
+ RadioSettingValueBoolean(_settings.autolk)))
+
+ group.append(
+ RadioSetting("lock_mode", "Keypad Lock Mode",
+ RadioSettingValueList(LOCK_MODE_LIST,
+ LOCK_MODE_LIST[_settings.lock_mode])))
+
+ group.append(
+ RadioSetting("voice", "Voice Prompt",
+ RadioSettingValueBoolean(_settings.voice)))
+
+ group.append(
+ RadioSetting("opnmsg", "Opening Message",
+ RadioSettingValueList(OPNMSG_LIST,
+ OPNMSG_LIST[_settings.opnmsg])))
+
+ group.append(
+ RadioSetting("tuning_step", "Tuning Step",
+ RadioSettingValueList(TUNING_STEPS_LIST,
+ TUNING_STEPS_LIST[_settings.tuning_step])))
+
+ group.append(
+ RadioSetting("lamp_t", "Backlight Timeout",
+ RadioSettingValueList(BACKLIGHT_TIMEOUT_LIST,
+ BACKLIGHT_TIMEOUT_LIST[_settings.lamp_t])))
+
+ group.append(
+ RadioSetting("a_work_area", "A Work Area",
+ RadioSettingValueList(AB_LIST,
+ AB_LIST[_settings.a_work_area])))
+
+ group.append(
+ RadioSetting("b_work_area", "B Work Area",
+ RadioSettingValueList(AB_LIST,
+ AB_LIST[_settings.b_work_area])))
+
+ return group
+
+ group.append(
+ RadioSetting("disnm", "Display Name",
+ RadioSettingValueBoolean(_settings.disnm)))
+
+ return group
+
+ def set_settings(self, settings):
+ _settings = self._memobj.settings
+
+ for element in settings:
+ if element.get_name() == 'rxsave':
+ if bool(element.value.get_value()):
+ _settings.rxsave = 3
+ else:
+ _settings.rxsave = 0
+ continue
+ if element.get_name().endswith('_channel'):
+ print element.value, type(element.value)
+ setattr(_settings, element.get_name(), int(element.value) - 1)
+ continue
+ if not isinstance(element, RadioSetting):
+ self.set_settings(element)
+ continue
+ setattr(_settings, element.get_name(), element.value)
diff --git a/chirp/thuv1f.py b/chirp/thuv1f.py
index fb3d428..5f99784 100644
--- a/chirp/thuv1f.py
+++ b/chirp/thuv1f.py
@@ -99,7 +99,7 @@ struct mem {
lbcd rx_tone[2];
lbcd tx_tone[2];
u8 unknown1:1,
- pttid:2,
+ pttid:2,
unknown2:2,
ishighpower:1,
unknown3:2;
@@ -199,7 +199,7 @@ class TYTTHUVF1Radio(chirp_common.CloneModeRadio):
rf.valid_cross_modes = ["Tone->Tone", "DTCS->DTCS",
"Tone->DTCS", "DTCS->Tone",
"->Tone", "->DTCS", "DTCS->"]
-
+
return rf
def sync_in(self):
@@ -221,8 +221,16 @@ class TYTTHUVF1Radio(chirp_common.CloneModeRadio):
@classmethod
def match_model(cls, filedata, filename):
- return filedata.startswith("\x13\x60\x17\x40\x40\x00\x48\x00" +
- "\x35\x00\x39\x00\x47\x00\x52\x00")
+ # TYT TH-UVF1 original
+ if filedata.startswith("\x13\x60\x17\x40\x40\x00\x48\x00" +
+ "\x35\x00\x39\x00\x47\x00\x52\x00"):
+ return True
+ # TYT TH-UVF1 V2
+ elif filedata.startswith("\x14\x40\x14\x80\x43\x00\x45\x00" +
+ "\x13\x60\x17\x40\x40\x00\x47\x00"):
+ return True
+ else:
+ return False
def process_mmap(self):
self._memobj = bitwise.parse(THUV1F_MEM_FORMAT, self._mmap)
@@ -230,7 +238,7 @@ class TYTTHUVF1Radio(chirp_common.CloneModeRadio):
def _decode_tone(self, toneval):
pol = "N"
rawval = (toneval[1].get_bits(0xFF) << 8) | toneval[0].get_bits(0xFF)
-
+
if toneval[0].get_bits(0xFF) == 0xFF:
mode = ""
val = 0
@@ -452,9 +460,9 @@ class TYTTHUVF1Radio(chirp_common.CloneModeRadio):
group.append(
RadioSetting("ponmsg", "Power-On Message",
- RadioSettingValueString(0, 7,
+ RadioSettingValueString(0, 6,
_filter(_settings.ponmsg))))
-
+
return group
def set_settings(self, settings):
diff --git a/chirp/tk8102.py b/chirp/tk8102.py
new file mode 100644
index 0000000..85d1dcd
--- /dev/null
+++ b/chirp/tk8102.py
@@ -0,0 +1,422 @@
+# Copyright 2013 Dan Smith <dsmith at danplanet.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 struct
+import os
+
+from chirp import chirp_common, directory, memmap, errors, util
+from chirp import bitwise
+from chirp.settings import RadioSettingGroup, RadioSetting
+from chirp.settings import RadioSettingValueBoolean, RadioSettingValueList
+from chirp.settings import RadioSettingValueString
+
+MEM_FORMAT = """
+#seekto 0x0030;
+struct {
+ lbcd rx_freq[4];
+ lbcd tx_freq[4];
+ ul16 rx_tone;
+ ul16 tx_tone;
+ u8 signaling:2,
+ unknown1:3,
+ bcl:1,
+ wide:1,
+ beatshift:1;
+ u8 pttid:2,
+ highpower:1,
+ scan:1
+ unknown2:4;
+ u8 unknown3[2];
+} memory[8];
+
+#seekto 0x0310;
+struct {
+ char line1[32];
+ char line2[32];
+} messages;
+
+"""
+
+POWER_LEVELS = [chirp_common.PowerLevel("Low", watts=5),
+ chirp_common.PowerLevel("High", watts=50)]
+MODES = ["NFM", "FM"]
+PTTID = ["", "BOT", "EOT", "Both"]
+SIGNAL = ["", "DTMF"]
+
+def make_frame(cmd, addr, length, data=""):
+ return struct.pack(">BHB", ord(cmd), addr, length) + data
+
+def send(radio, frame):
+ #print "%04i P>R: %s" % (len(frame), util.hexprint(frame))
+ radio.pipe.write(frame)
+
+def recv(radio, readdata=True):
+ hdr = radio.pipe.read(4)
+ cmd, addr, length = struct.unpack(">BHB", hdr)
+ if readdata:
+ data = radio.pipe.read(length)
+ #print " P<R: %s" % util.hexprint(hdr + data)
+ if len(data) != length:
+ raise errors.RadioError("Radio sent %i bytes (expected %i)" % (
+ len(data), length))
+ else:
+ data = ""
+ radio.pipe.write("\x06")
+ return addr, data
+
+def do_ident(radio):
+ send(radio, "PROGRAM")
+ ack = radio.pipe.read(1)
+ if ack != "\x06":
+ raise errors.RadioError("Radio refused program mode")
+ radio.pipe.write("\x02")
+ ident = radio.pipe.read(8)
+ if ident[1:5] != radio.MODEL.split("-")[1]:
+ raise errors.RadioError("Incorrect model: TK-%s, expected %s" % (
+ ident[1:5], radio.MODEL))
+ print "Model: %s" % util.hexprint(ident)
+ radio.pipe.write("\x06")
+ ack = radio.pipe.read(1)
+
+def do_download(radio):
+ radio.pipe.setParity("E")
+ radio.pipe.setTimeout(1)
+ do_ident(radio)
+
+ data = ""
+ for addr in range(0, 0x0400, 8):
+ send(radio, make_frame("R", addr, 8))
+ _addr, _data = recv(radio)
+ if _addr != addr:
+ raise errors.RadioError("Radio sent unexpected address")
+ data += _data
+ radio.pipe.write("\x06")
+ ack = radio.pipe.read(1)
+ if ack != "\x06":
+ raise errors.RadioError("Radio refused block at %04x" % addr)
+
+ status = chirp_common.Status()
+ status.cur = addr
+ status.max = 0x0400
+ status.msg = "Cloning to radio"
+ radio.status_fn(status)
+
+ radio.pipe.write("\x45")
+
+ data = ("\x45\x58\x33\x34\x30\x32\xff\xff" + ("\xff" * 8) +
+ data)
+ return memmap.MemoryMap(data)
+
+def do_upload(radio):
+ radio.pipe.setParity("E")
+ radio.pipe.setTimeout(1)
+ do_ident(radio)
+
+ for addr in range(0, 0x0400, 8):
+ eaddr = addr + 16
+ send(radio, make_frame("W", addr, 8, radio._mmap[eaddr:eaddr + 8]))
+ ack = radio.pipe.read(1)
+ if ack != "\x06":
+ raise errors.RadioError("Radio refused block at %04x" % addr)
+ radio.pipe.write("\x06")
+
+ status = chirp_common.Status()
+ status.cur = addr
+ status.max = 0x0400
+ status.msg = "Cloning to radio"
+ radio.status_fn(status)
+
+ radio.pipe.write("\x45")
+
+class KenwoodTKx102Radio(chirp_common.CloneModeRadio):
+ """Kenwood TK-x102"""
+ VENDOR = "Kenwood"
+ MODEL = "TK-x102"
+ BAUD_RATE = 9600
+
+ _memsize = 0x410
+
+ def get_features(self):
+ rf = chirp_common.RadioFeatures()
+ rf.has_settings = True
+ rf.has_cross = True
+ rf.has_bank = False
+ rf.has_tuning_step = False
+ rf.has_name = False
+ rf.has_rx_dtcs = True
+ rf.valid_tmodes = ['', 'Tone', 'TSQL', 'DTCS', 'Cross']
+ rf.valid_modes = MODES
+ rf.valid_power_levels = POWER_LEVELS
+ rf.valid_skips = ["", "S"]
+ rf.valid_bands = [self._range]
+ rf.memory_bounds = (1, self._upper)
+ return rf
+
+ def sync_in(self):
+ try:
+ self._mmap = do_download(self)
+ except errors.RadioError:
+ self.pipe.write("\x45")
+ raise
+ except Exception, e:
+ raise errors.RadioError("Failed to download from radio: %s" % e)
+ self.process_mmap()
+
+ def process_mmap(self):
+ self._memobj = bitwise.parse(MEM_FORMAT, self._mmap)
+
+ def sync_out(self):
+ try:
+ do_upload(self)
+ except errors.RadioError:
+ self.pipe.write("\x45")
+ raise
+ except Exception, e:
+ raise errors.RadioError("Failed to upload to radio: %s" % e)
+
+ 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 > 0x2800:
+ 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 > 0x2800:
+ 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)
+
+ def get_memory(self, number):
+ _mem = self._memobj.memory[number - 1]
+
+ mem = chirp_common.Memory()
+ mem.number = number
+
+ if _mem.get_raw()[:4] == "\xFF\xFF\xFF\xFF":
+ mem.empty = True
+ return mem
+
+ mem.freq = int(_mem.rx_freq) * 10
+ offset = (int(_mem.tx_freq) * 10) - mem.freq
+ if offset < 0:
+ mem.offset = abs(offset)
+ mem.duplex = "-"
+ elif offset > 0:
+ mem.offset = offset
+ mem.duplex = "+"
+ else:
+ mem.offset = 0
+
+ self._get_tone(_mem, mem)
+ mem.power = POWER_LEVELS[_mem.highpower]
+ mem.mode = MODES[_mem.wide]
+ mem.skip = not _mem.scan and "S" or ""
+
+ mem.extra = RadioSettingGroup("all", "All Settings")
+
+ bcl = RadioSetting("bcl", "Busy Channel Lockout",
+ RadioSettingValueBoolean(bool(_mem.bcl)))
+ mem.extra.append(bcl)
+
+ beat = RadioSetting("beatshift", "Beat Shift",
+ RadioSettingValueBoolean(bool(_mem.beatshift)))
+ mem.extra.append(beat)
+
+ pttid = RadioSetting("pttid", "PTT ID",
+ RadioSettingValueList(PTTID,
+ PTTID[_mem.pttid]))
+ mem.extra.append(pttid)
+
+ signal = RadioSetting("signaling", "Signaling",
+ RadioSettingValueList(SIGNAL,
+ SIGNAL[
+ _mem.signaling & 0x01]))
+ mem.extra.append(signal)
+
+ return mem
+
+ def _set_tone(self, mem, _mem):
+ def _set_dcs(code, pol):
+ val = int("%i" % code, 8) + 0x2800
+ if pol == "R":
+ val += 0xA000
+ 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
+
+ if os.getenv("CHIRP_DEBUG"):
+ print "Set TX %s (%i) RX %s (%i)" % (tx_mode, _mem.tx_tone,
+ rx_mode, _mem.rx_tone)
+ def set_memory(self, mem):
+ _mem = self._memobj.memory[mem.number - 1]
+
+ if mem.empty:
+ _mem.set_raw("\xFF" * 16)
+ return
+
+ _mem.unknown3[0] = 0x07
+ _mem.unknown3[1] = 0x22
+ _mem.rx_freq = mem.freq / 10
+ if mem.duplex == "+":
+ _mem.tx_freq = (mem.freq + mem.offset) / 10
+ elif mem.duplex == "-":
+ _mem.tx_freq = (mem.freq - mem.offset) / 10
+ else:
+ _mem.tx_freq = mem.freq / 10
+
+ self._set_tone(mem, _mem)
+
+
+ _mem.highpower = mem.power == POWER_LEVELS[1]
+ _mem.wide = mem.mode == "FM"
+ _mem.scan = mem.skip != "S"
+
+ for setting in mem.extra:
+ if setting.get_name == "signaling":
+ if setting.value == "DTMF":
+ _mem.signaling = 0x03
+ else:
+ _mem.signaling = 0x00
+ else:
+ setattr(_mem, setting.get_name(), setting.value)
+
+ def get_settings(self):
+ _mem = self._memobj
+ top = RadioSettingGroup("all", "All Settings")
+
+ def _f(val):
+ string = ""
+ for char in str(val):
+ if char == "\xFF":
+ break
+ string += char
+ return string
+
+ line1 = RadioSetting("messages.line1", "Message Line 1",
+ RadioSettingValueString(0, 32,
+ _f(_mem.messages.line1),
+ autopad=False))
+ top.append(line1)
+
+ line2 = RadioSetting("messages.line2", "Message Line 2",
+ RadioSettingValueString(0, 32,
+ _f(_mem.messages.line2),
+ autopad=False))
+ top.append(line2)
+
+ return top
+
+ def set_settings(self, settings):
+ for element in settings:
+ 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 = _settings
+ setting = element.get_name()
+
+ if "line" in setting:
+ value = str(element.value).ljust(32, "\xFF")
+ else:
+ value = element.value
+ setattr(obj, setting, value)
+
+ @classmethod
+ def match_model(cls, filedata, filename):
+ model = filedata[0x03D1:0x03D5]
+ print model
+ return model == cls.MODEL.split("-")[1]
+
+
+ at directory.register
+class KenwoodTK7102Radio(KenwoodTKx102Radio):
+ MODEL = "TK-7102"
+ _range = (136000000, 174000000)
+ _upper = 4
+
+ at directory.register
+class KenwoodTK8102Radio(KenwoodTKx102Radio):
+ MODEL = "TK-8102"
+ _range = (400000000, 500000000)
+ _upper = 4
+
+ at directory.register
+class KenwoodTK7108Radio(KenwoodTKx102Radio):
+ MODEL = "TK-7108"
+ _range = (136000000, 174000000)
+ _upper = 8
+
+ at directory.register
+class KenwoodTK8108Radio(KenwoodTKx102Radio):
+ MODEL = "TK-8108"
+ _range = (400000000, 500000000)
+ _upper = 8
+
diff --git a/chirp/uv5r.py b/chirp/uv5r.py
index e7457d8..dcd3afc 100644
--- a/chirp/uv5r.py
+++ b/chirp/uv5r.py
@@ -15,13 +15,20 @@
import struct
import time
+import os
from chirp import chirp_common, errors, util, directory, memmap
from chirp import bitwise
from chirp.settings import RadioSetting, RadioSettingGroup, \
RadioSettingValueInteger, RadioSettingValueList, \
- RadioSettingValueList, RadioSettingValueBoolean, \
- RadioSettingValueString
+ RadioSettingValueBoolean, RadioSettingValueString, \
+ RadioSettingValueFloat, InvalidValueError
+from textwrap import dedent
+
+if os.getenv("CHIRP_DEBUG"):
+ CHIRP_DEBUG = True
+else:
+ CHIRP_DEBUG = False
MEM_FORMAT = """
#seekto 0x0008;
@@ -30,23 +37,52 @@ struct {
lbcd txfreq[4];
ul16 rxtone;
ul16 txtone;
- u8 unused1:4,
+ u8 unused1:3,
+ isuhf:1,
scode:4;
- u8 unknown1[1];
- u8 unknown2:7,
+ u8 unknown1:7,
+ txtoneicon:1;
+ u8 mailicon:3,
+ unknown2:4,
lowpower:1;
u8 unknown3:1,
wide:1,
unknown4:2,
bcl:1,
scan:1,
- pttideot:1,
- pttidbot:1;
+ pttid:2;
} memory[128];
-#seekto 0x0CB2;
+#seekto 0x0B08;
+struct {
+ u8 code[5];
+ u8 unused[11];
+} pttid[15];
+
+#seekto 0x0C88;
struct {
+ u8 code222[3];
+ u8 unused222[2];
+ u8 code333[3];
+ u8 unused333[2];
+ u8 alarmcode[3];
+ u8 unused119[2];
+ u8 unknown1;
+ u8 code555[3];
+ u8 unused555[2];
+ u8 code666[3];
+ u8 unused666[2];
+ u8 code777[3];
+ u8 unused777[2];
+ u8 unknown2;
+ u8 code60606[5];
+ u8 code70707[5];
u8 code[5];
+ u8 unused1:6,
+ aniid:2;
+ u8 unknown[2];
+ u8 dtmfon;
+ u8 dtmfoff;
} ani;
#seekto 0x0E28;
@@ -66,13 +102,16 @@ struct {
u8 unknown4;
u8 dtmfst;
u8 unknown5;
- u8 screv;
+ u8 unknown12:6,
+ screv:2;
u8 pttid;
u8 pttlt;
u8 mdfa;
u8 mdfb;
u8 bcl;
- u8 autolk;
+ u8 autolk; // NOTE: The UV-6 calls this byte voxenable, but the UV-5R calls
+ // it autolk. Since this is a minor difference, it will be referred
+ // to by the wrong name for the UV-6.
u8 sftd;
u8 unknown6[3];
u8 wtled;
@@ -86,10 +125,8 @@ struct {
u8 rptrl;
u8 ponmsg;
u8 roger;
-} settings[2];
-
-#seekto 0x0E52;
-struct {
+ u8 rogerrx;
+ u8 tdrch;
u8 displayab:1,
unknown1:2,
fmradio:1,
@@ -97,10 +134,10 @@ struct {
unknown2:1,
reset:1,
menu:1;
- u8 unknown3;
+ u8 vfomrlock;
u8 workmode;
u8 keylock;
-} extra;
+} settings;
#seekto 0x0E7E;
struct {
@@ -121,7 +158,8 @@ struct {
u8 unused1:7,
band:1;
u8 unknown3;
- u8 unused2:4,
+ u8 unused2:2,
+ sftd:2,
scode:4;
u8 unknown4;
u8 unused3:1
@@ -143,7 +181,8 @@ struct {
u8 unused1:7,
band:1;
u8 unknown3;
- u8 unused2:4,
+ u8 unused2:2,
+ sftd:2,
scode:4;
u8 unknown4;
u8 unused3:1
@@ -154,11 +193,13 @@ struct {
unknown5:6;
} vfob;
-#seekto 0x1000;
+#seekto 0x0F56;
+u16 fm_presets;
+
+#seekto 0x1008;
struct {
- u8 unknown1[8];
char name[7];
- u8 unknown2;
+ u8 unknown2[9];
} names[128];
#seekto 0x1818;
@@ -167,12 +208,18 @@ struct {
char line2[7];
} sixpoweron_msg;
-#seekto 0x1828;
+#seekto 0x%04X;
struct {
char line1[7];
char line2[7];
} poweron_msg;
+#seekto 0x1838;
+struct {
+ char line1[7];
+ char line2[7];
+} firmware_msg;
+
struct limit {
u8 enable;
bbcd lower[2];
@@ -199,43 +246,75 @@ struct {
# 0x1EC0 - 0x2000
+vhf_220_radio = "\x02"
+
+BASETYPE_UV5R = ["BFS", "BFB"]
+BASETYPE_F11 = ["USA"]
+BASETYPE_UV82 = ["B82S", "BF82"]
+BASETYPE_BJ55 = ["BJ55"] # needed for for the Baojie UV-55 in bjuv55.py
+BASETYPE_UV6 = ["BF1"]
+BASETYPE_LIST = BASETYPE_UV5R + BASETYPE_F11 + BASETYPE_UV82 + \
+ BASETYPE_BJ55 + BASETYPE_UV6
+
+AB_LIST = ["A", "B"]
+ALMOD_LIST = ["Site", "Tone", "Code", "_unknown_"]
+BANDWIDTH_LIST = ["Wide", "Narrow"]
+COLOR_LIST = ["Off", "Blue", "Orange", "Purple"]
+DTMFSPEED_LIST = ["%s ms" % x for x in range(50, 2010, 10)]
+DTMFST_LIST = ["OFF", "DT-ST", "ANI-ST", "DT+ANI"]
+MODE_LIST = ["Channel", "Name", "Frequency"]
+PONMSG_LIST = ["Full", "Message"]
+PTTID_LIST = ["Off", "BOT", "EOT", "Both"]
+PTTIDCODE_LIST = ["%s" % x for x in range(1, 16)]
+RESUME_LIST = ["TO", "CO", "SE"]
+ROGERRX_LIST = ["Off"] + AB_LIST
+RPSTE_LIST = ["OFF"] + ["%s" % x for x in range(1, 11)]
+SAVE_LIST = ["Off", "1:1", "1:2", "1:3", "1:4"]
+SCODE_LIST = ["%s" % x for x in range(1, 16)]
+SHIFTD_LIST = ["Off", "+", "-"]
+STEDELAY_LIST = ["OFF"] + ["%s ms" % x for x in range(100, 1100, 100)]
STEPS = [2.5, 5.0, 6.25, 10.0, 12.5, 25.0]
STEP_LIST = [str(x) for x in STEPS]
STEPS = [2.5, 5.0, 6.25, 10.0, 12.5, 20.0, 25.0, 50.0]
STEP291_LIST = [str(x) for x in STEPS]
+TDRAB_LIST = ["Off"] + AB_LIST
+TDRCH_LIST = ["CH%s" % x for x in range(1, 129)]
TIMEOUT_LIST = ["%s sec" % x for x in range(15, 615, 15)]
+TXPOWER_LIST = ["High", "Low"]
VOICE_LIST = ["Off", "English", "Chinese"]
-DTMFST_LIST = ["OFF", "DT-ST", "ANI-ST", "DT+ANI"]
-RESUME_LIST = ["TO", "CO", "SE"]
-MODE_LIST = ["Channel", "Name", "Frequency"]
-COLOR_LIST = ["Off", "Blue", "Orange", "Purple"]
-ALMOD_LIST = ["Site", "Tone", "Code"]
-TDRAB_LIST = ["Off", "A", "B"]
-PONMSG_LIST = ["Full", "Message"]
-RPSTE_LIST = ["%s" % x for x in range(1, 11, 1)]
-RPSTE_LIST.insert(0, "OFF")
-STEDELAY_LIST = ["%s ms" % x for x in range(100, 1100, 100)]
-STEDELAY_LIST.insert(0, "OFF")
-SCODE_LIST = ["%s" % x for x in range(1, 16)]
+VOX_LIST = ["OFF"] + ["%s" % x for x in range(1, 11)]
+WORKMODE_LIST = ["Frequency", "Channel"]
SETTING_LISTS = {
- "step" : STEP_LIST,
- "step291" : STEP291_LIST,
- "timeout" : TIMEOUT_LIST,
- "voice" : VOICE_LIST,
+ "almod" : ALMOD_LIST,
+ "aniid" : PTTID_LIST,
+ "displayab" : AB_LIST,
"dtmfst" : DTMFST_LIST,
- "screv" : RESUME_LIST,
+ "dtmfspeed" : DTMFSPEED_LIST,
"mdfa" : MODE_LIST,
"mdfb" : MODE_LIST,
- "wtled" : COLOR_LIST,
- "rxled" : COLOR_LIST,
- "txled" : COLOR_LIST,
- "almod" : ALMOD_LIST,
- "tdrab" : TDRAB_LIST,
"ponmsg" : PONMSG_LIST,
+ "pttid" : PTTID_LIST,
+ "rogerrx" : ROGERRX_LIST,
"rpste" : RPSTE_LIST,
+ "rxled" : COLOR_LIST,
+ "save" : SAVE_LIST,
+ "scode" : PTTIDCODE_LIST,
+ "screv" : RESUME_LIST,
+ "sftd" : SHIFTD_LIST,
"stedelay" : STEDELAY_LIST,
- "scode" : SCODE_LIST,
+ "step" : STEP_LIST,
+ "step291" : STEP291_LIST,
+ "tdrab" : TDRAB_LIST,
+ "tdrch" : TDRCH_LIST,
+ "timeout" : TIMEOUT_LIST,
+ "txled" : COLOR_LIST,
+ "txpower" : TXPOWER_LIST,
+ "voice" : VOICE_LIST,
+ "vox" : VOX_LIST,
+ "widenarr" : BANDWIDTH_LIST,
+ "workmode" : WORKMODE_LIST,
+ "wtled" : COLOR_LIST
}
def _do_status(radio, block):
@@ -258,23 +337,39 @@ def validate_291(ident):
raise errors.RadioError("Radio version not supported")
UV5R_MODEL_ORIG = "\x50\xBB\xFF\x01\x25\x98\x4D"
-UV5R_MODEL_291 = "\x50\xBB\xFF\x20\x12\x07\x25"
+UV5R_MODEL_291 = "\x50\xBB\xFF\x20\x12\x07\x25"
+UV5R_MODEL_F11 = "\x50\xBB\xFF\x13\xA1\x11\xDD"
+UV5R_MODEL_UV82 = "\x50\xBB\xFF\x20\x13\x01\x05"
+UV5R_MODEL_UV6 = "\x50\xBB\xFF\x20\x12\x08\x23"
+
+def _upper_band_from_data(data):
+ return data[0x03:0x04]
-IDENTS = [UV5R_MODEL_ORIG,
- UV5R_MODEL_291,
- ]
+def _upper_band_from_image(radio):
+ return _upper_band_from_data(radio.get_mmap())
+
+def _firmware_version_from_data(data, version_start, version_stop):
+ version_tag = data[version_start:version_stop]
+ return version_tag
def _firmware_version_from_image(radio):
- return radio.get_mmap()[0x1838:0x1848]
+ version = _firmware_version_from_data(radio.get_mmap(),
+ radio._fw_ver_file_start,
+ radio._fw_ver_file_stop)
+ if CHIRP_DEBUG:
+ print "_firmware_version_from_image: " + util.hexprint(version)
+ return version
def _do_ident(radio, magic):
serial = radio.pipe
serial.setTimeout(1)
print "Sending Magic: %s" % util.hexprint(magic)
- serial.write(magic)
+ for byte in magic:
+ serial.write(byte)
+ time.sleep(0.01)
ack = serial.read(1)
-
+
if ack != "\x06":
if ack:
print repr(ack)
@@ -312,23 +407,28 @@ def _read_block(radio, start, size):
elif len(chunk) != size:
print "Chunk length was 0x%04i" % len(chunk)
raise errors.RadioError("Radio sent incomplete block 0x%04x" % start)
-
+
radio.pipe.write("\x06")
ack = radio.pipe.read(1)
if ack != "\x06":
raise errors.RadioError("Radio refused to send block 0x%04x" % start)
-
+
return chunk
def _get_radio_firmware_version(radio):
- block1 = _read_block(radio, 0x1EC0, 0x40)
- block2 = _read_block(radio, 0x1F00, 0x40)
- block = block1 + block2
- return block[48:64]
+ if radio.MODEL == "BJ-UV55":
+ block = _read_block(radio, 0x1FF0, 0x40)
+ version = block[0:6]
+ else:
+ block1 = _read_block(radio, 0x1EC0, 0x40)
+ block2 = _read_block(radio, 0x1F00, 0x40)
+ block = block1 + block2
+ version = block[48:64]
+ return version
def _ident_radio(radio):
- for magic in IDENTS:
+ for magic in radio._idents:
error = None
try:
data = _do_ident(radio, magic)
@@ -345,14 +445,19 @@ def _do_download(radio):
data = _ident_radio(radio)
# Main block
+ if CHIRP_DEBUG:
+ print "downloading main block..."
for i in range(0, 0x1800, 0x40):
data += _read_block(radio, i, 0x40)
_do_status(radio, i)
-
+ if CHIRP_DEBUG:
+ print "done."
+ print "downloading aux block..."
# Auxiliary block starts at 0x1ECO (?)
for i in range(0x1EC0, 0x2000, 0x40):
data += _read_block(radio, i, 0x40)
-
+ if CHIRP_DEBUG:
+ print "done."
return memmap.MemoryMap(data)
def _send_block(radio, addr, data):
@@ -362,22 +467,28 @@ def _send_block(radio, addr, data):
ack = radio.pipe.read(1)
if ack != "\x06":
raise errors.RadioError("Radio refused to accept block 0x%04x" % addr)
-
+
def _do_upload(radio):
- _ident_radio(radio)
+ ident = _ident_radio(radio)
+ radio_upper_band = ident[3:4]
+ image_upper_band = _upper_band_from_image(radio)
+
+ if image_upper_band == vhf_220_radio or radio_upper_band == vhf_220_radio:
+ if image_upper_band != radio_upper_band:
+ raise errors.RadioError("Image not supported by radio")
image_version = _firmware_version_from_image(radio)
radio_version = _get_radio_firmware_version(radio)
print "Image is %s" % repr(image_version)
print "Radio is %s" % repr(radio_version)
- if "BFB" not in radio_version:
+ if not any(type in radio_version for type in BASETYPE_LIST):
raise errors.RadioError("Unsupported firmware version: `%s'" %
radio_version)
# Main block
for i in range(0x08, 0x1808, 0x10):
- _send_block(radio, i - 0x08, radio.get_mmap()[i:i+0x10])
+ _send_block(radio, i - 0x08, radio.get_mmap()[i:i + 0x10])
_do_status(radio, i)
if len(radio.get_mmap().get_packed()) == 0x1808:
@@ -385,15 +496,16 @@ def _do_upload(radio):
return # Old image, no aux block
if image_version != radio_version:
- raise errors.RadioError("Upload finished, but the 'Other Settings' "
- "could not be sent because the firmware "
- "version of the image does not match that "
- "of the radio")
+ msg = ("Upload finished, but the 'Other Settings' "
+ "could not be sent because the firmware "
+ "version of the image (%s) does not match "
+ "that of the radio (%s).")
+ raise errors.RadioError(msg % (image_version, radio_version))
# Auxiliary block at radio address 0x1EC0, our offset 0x1808
for i in range(0x1EC0, 0x2000, 0x10):
addr = 0x1808 + (i - 0x1EC0)
- _send_block(radio, i, radio.get_mmap()[addr:addr+0x10])
+ _send_block(radio, i, radio.get_mmap()[addr:addr + 0x10])
UV5R_POWER_LEVELS = [chirp_common.PowerLevel("High", watts=4.00),
chirp_common.PowerLevel("Low", watts=1.00)]
@@ -413,15 +525,40 @@ class BaofengUV5R(chirp_common.CloneModeRadio,
BAUD_RATE = 9600
_memsize = 0x1808
+ _basetype = BASETYPE_UV5R
+ _idents = [UV5R_MODEL_291,
+ UV5R_MODEL_ORIG
+ ]
+ _mem_params = ( 0x1828 # poweron_msg offset
+ )
+ # offset of fw version in image file
+ _fw_ver_file_start = 0x1838
+ _fw_ver_file_stop = 0x1848
@classmethod
- def get_experimental_warning(cls):
- return ('Due to the fact that the manufacturer continues to '
+ def get_prompts(cls):
+ rp = chirp_common.RadioPrompts()
+ rp.experimental = ('Due to the fact that the manufacturer continues to '
'release new versions of the firmware with obscure and '
'hard-to-track changes, this driver may not work with '
'your device. Thus far and to the best knowledge of the '
'author, no UV-5R radios have been harmed by using CHIRP. '
'However, proceed at your own risk!')
+ rp.pre_download = _(dedent("""\
+ 1. Turn radio off.
+ 2. Connect cable to mic/spkr connector.
+ 3. Make sure connector is firmly connected.
+ 4. Turn radio on.
+ 5. Ensure that the radio is tuned to channel with no activity.
+ 6. Click OK to download image from device."""))
+ rp.pre_upload = _(dedent("""\
+ 1. Turn radio off.
+ 2. Connect cable to mic/spkr connector.
+ 3. Make sure connector is firmly connected.
+ 4. Turn radio on.
+ 5. Ensure that the radio is tuned to channel with no activity.
+ 6. Click OK to upload image to device."""))
+ return rp
def get_features(self):
rf = chirp_common.RadioFeatures()
@@ -440,16 +577,37 @@ class BaofengUV5R(chirp_common.CloneModeRadio,
rf.valid_power_levels = UV5R_POWER_LEVELS
rf.valid_duplexes = ["", "-", "+", "split", "off"]
rf.valid_modes = ["FM", "NFM"]
- rf.valid_bands = [(136000000, 174000000), (400000000, 520000000)]
+
+ normal_bands = [(136000000, 174000000), (400000000, 520000000)]
+ rax_bands = [(136000000, 174000000), (220000000, 260000000)]
+
+ if self._mmap is None:
+ rf.valid_bands = [normal_bands[0], rax_bands[1], normal_bands[1]]
+ elif not self._is_orig() and self._my_upper_band() == vhf_220_radio:
+ rf.valid_bands = rax_bands
+ else:
+ rf.valid_bands = normal_bands
rf.memory_bounds = (0, 127)
return rf
@classmethod
def match_model(cls, filedata, filename):
- return len(filedata) in [0x1808, 0x1948]
+ match_size = False
+ match_model = False
+ if len(filedata) in [0x1808, 0x1948]:
+ match_size = True
+ fwdata = _firmware_version_from_data(filedata,
+ cls._fw_ver_file_start,
+ cls._fw_ver_file_stop)
+ if any(type in fwdata for type in cls._basetype):
+ match_model = True
+ if match_size and match_model:
+ return True
+ else:
+ return False
def process_mmap(self):
- self._memobj = bitwise.parse(MEM_FORMAT, self._mmap)
+ self._memobj = bitwise.parse(MEM_FORMAT % self._mem_params, self._mmap)
def sync_in(self):
try:
@@ -477,9 +635,15 @@ class BaofengUV5R(chirp_common.CloneModeRadio,
raw_tx += _mem.txfreq[i].get_raw()
return raw_tx == "\xFF\xFF\xFF\xFF"
+ def _get_mem(self, number):
+ return self._memobj.memory[number]
+
+ def _get_nam(self, number):
+ return self._memobj.names[number]
+
def get_memory(self, number):
- _mem = self._memobj.memory[number]
- _nam = self._memobj.names[number]
+ _mem = self._get_mem(number)
+ _nam = self._get_nam(number)
mem = chirp_common.Memory()
mem.number = number
@@ -567,11 +731,27 @@ class BaofengUV5R(chirp_common.CloneModeRadio,
RadioSettingValueBoolean(_mem.bcl))
mem.extra.append(rs)
+ rs = RadioSetting("pttid", "PTT ID",
+ RadioSettingValueList(PTTID_LIST,
+ PTTID_LIST[_mem.pttid]))
+ mem.extra.append(rs)
+
+ rs = RadioSetting("scode", "PTT ID Code",
+ RadioSettingValueList(PTTIDCODE_LIST,
+ PTTIDCODE_LIST[_mem.scode]))
+ mem.extra.append(rs)
+
return mem
+ def _set_mem(self, number):
+ return self._memobj.memory[number]
+
+ def _set_nam(self, number):
+ return self._memobj.names[number]
+
def set_memory(self, mem):
- _mem = self._memobj.memory[mem.number]
- _nam = self._memobj.names[mem.number]
+ _mem = self._get_mem(mem.number)
+ _nam = self._get_nam(mem.number)
if mem.empty:
_mem.set_raw("\xff" * 16)
@@ -593,7 +773,8 @@ class BaofengUV5R(chirp_common.CloneModeRadio,
else:
_mem.txfreq = mem.freq / 10
- for i in range(0, 7):
+ _namelength = self.get_features().valid_name_length
+ for i in range(_namelength):
try:
_nam.name[i] = mem.name[i]
except IndexError:
@@ -642,11 +823,14 @@ class BaofengUV5R(chirp_common.CloneModeRadio,
def _is_orig(self):
version_tag = _firmware_version_from_image(self)
+ if CHIRP_DEBUG:
+ print "@_is_orig, version_tag:", util.hexprint(version_tag)
try:
if 'BFB' in version_tag:
idx = version_tag.index("BFB") + 3
- version = int(version_tag[idx:idx+3])
+ version = int(version_tag[idx:idx + 3])
return version < 291
+ return False
except:
pass
raise errors.RadioError("Unable to parse version string %s" %
@@ -656,11 +840,38 @@ class BaofengUV5R(chirp_common.CloneModeRadio,
version_tag = _firmware_version_from_image(self)
if 'BFB' in version_tag:
idx = version_tag.index("BFB") + 3
- return int(version_tag[idx:idx+3])
+ return int(version_tag[idx:idx + 3])
+ elif 'BFS' in version_tag:
+ idx = version_tag.index("BFS") + 3
+ return int(version_tag[idx:idx + 3])
+ elif 'BF82' in version_tag:
+ idx = version_tag.index("BF82") + 2
+ return int(version_tag[idx:idx + 4])
+ elif 'B82S' in version_tag:
+ idx = version_tag.index("B82S") + 4
+ return int(version_tag[idx:idx + 2]) + 8200
+ elif 'USA' in version_tag:
+ idx = version_tag.index("USA") + 3
+ return int(version_tag[idx:idx + 3]) + 11000
+ elif 'BJ55' in version_tag:
+ idx = version_tag.index("BJ55") + 2
+ return int(version_tag[idx:idx + 4])
+ elif 'BF1' in version_tag:
+ idx = version_tag.index("BF1") + 2
+ return int(version_tag[idx:idx + 4])
+
raise Exception("Unrecognized firmware version string")
+ def _my_upper_band(self):
+ band_tag = _upper_band_from_image(self)
+ return band_tag
+
def _get_settings(self):
- _settings = self._memobj.settings[0]
+ _ani = self._memobj.ani
+ _settings = self._memobj.settings
+ _vfoa = self._memobj.vfoa
+ _vfob = self._memobj.vfob
+ _wmchannel = self._memobj.wmchannel
basic = RadioSettingGroup("basic", "Basic Settings")
advanced = RadioSettingGroup("advanced", "Advanced Settings")
group = RadioSettingGroup("top", "All Settings", basic, advanced)
@@ -669,31 +880,54 @@ class BaofengUV5R(chirp_common.CloneModeRadio,
RadioSettingValueInteger(0, 9, _settings.squelch))
basic.append(rs)
- rs = RadioSetting("dtmfst", "DTMF Sidetone",
- RadioSettingValueList(DTMFST_LIST,
- DTMFST_LIST[_settings.dtmfst]))
- advanced.append(rs)
-
rs = RadioSetting("save", "Battery Saver",
- RadioSettingValueInteger(0, 4, _settings.save))
+ RadioSettingValueList(SAVE_LIST,
+ SAVE_LIST[_settings.save]))
basic.append(rs)
rs = RadioSetting("vox", "VOX Sensitivity",
- RadioSettingValueInteger(0, 10, _settings.vox))
+ RadioSettingValueList(VOX_LIST,
+ VOX_LIST[_settings.vox]))
advanced.append(rs)
- rs = RadioSetting("abr", "Backlight Timeout",
- RadioSettingValueInteger(0, 24, _settings.abr))
- basic.append(rs)
+ if self.MODEL == "UV-6":
+ # NOTE: The UV-6 calls this byte voxenable, but the UV-5R calls it
+ # autolk. Since this is a minor difference, it will be referred to
+ # by the wrong name for the UV-6.
+ rs = RadioSetting("autolk", "Vox",
+ RadioSettingValueBoolean(_settings.autolk))
+ advanced.append(rs)
+
+ if self.MODEL != "UV-6":
+ rs = RadioSetting("abr", "Backlight Timeout",
+ RadioSettingValueInteger(0, 24, _settings.abr))
+ basic.append(rs)
rs = RadioSetting("tdr", "Dual Watch",
RadioSettingValueBoolean(_settings.tdr))
advanced.append(rs)
- rs = RadioSetting("tdrab", "Dual Watch Priority",
- RadioSettingValueList(TDRAB_LIST,
- TDRAB_LIST[_settings.tdrab]))
- advanced.append(rs)
+ if self.MODEL == "UV-6":
+ rs = RadioSetting("tdrch", "Dual Watch Channel",
+ RadioSettingValueList(TDRCH_LIST,
+ TDRCH_LIST[
+ _settings.tdrch]))
+ advanced.append(rs)
+
+ rs = RadioSetting("tdrab", "Dual Watch TX Priority",
+ RadioSettingValueBoolean(_settings.tdrab))
+ advanced.append(rs)
+ else:
+ rs = RadioSetting("tdrab", "Dual Watch TX Priority",
+ RadioSettingValueList(TDRAB_LIST,
+ TDRAB_LIST[
+ _settings.tdrab]))
+ advanced.append(rs)
+
+ if self.MODEL == "UV-6":
+ rs = RadioSetting("alarm", "Alarm Sound",
+ RadioSettingValueBoolean(_settings.alarm))
+ advanced.append(rs)
rs = RadioSetting("almod", "Alarm Mode",
RadioSettingValueList(ALMOD_LIST,
@@ -706,17 +940,19 @@ class BaofengUV5R(chirp_common.CloneModeRadio,
rs = RadioSetting("timeout", "Timeout Timer",
RadioSettingValueList(TIMEOUT_LIST,
- TIMEOUT_LIST[_settings.timeout]))
+ TIMEOUT_LIST[
+ _settings.timeout]))
basic.append(rs)
- if self._my_version() >= 251:
+ if self._is_orig() and self._my_version() < 251:
rs = RadioSetting("voice", "Voice",
- RadioSettingValueList(VOICE_LIST,
- VOICE_LIST[_settings.voice]))
+ RadioSettingValueBoolean(_settings.voice))
advanced.append(rs)
else:
rs = RadioSetting("voice", "Voice",
- RadioSettingValueBoolean(_settings.voice))
+ RadioSettingValueList(VOICE_LIST,
+ VOICE_LIST[
+ _settings.voice]))
advanced.append(rs)
rs = RadioSetting("screv", "Scan Resume",
@@ -724,59 +960,62 @@ class BaofengUV5R(chirp_common.CloneModeRadio,
RESUME_LIST[_settings.screv]))
advanced.append(rs)
- rs = RadioSetting("mdfa", "Display Mode (A)",
- RadioSettingValueList(MODE_LIST,
- MODE_LIST[_settings.mdfa]))
- basic.append(rs)
+ if self.MODEL != "UV-6":
+ rs = RadioSetting("mdfa", "Display Mode (A)",
+ RadioSettingValueList(MODE_LIST,
+ MODE_LIST[_settings.mdfa]))
+ basic.append(rs)
- rs = RadioSetting("mdfb", "Display Mode (B)",
- RadioSettingValueList(MODE_LIST,
- MODE_LIST[_settings.mdfb]))
- basic.append(rs)
+ rs = RadioSetting("mdfb", "Display Mode (B)",
+ RadioSettingValueList(MODE_LIST,
+ MODE_LIST[_settings.mdfb]))
+ basic.append(rs)
rs = RadioSetting("bcl", "Busy Channel Lockout",
RadioSettingValueBoolean(_settings.bcl))
advanced.append(rs)
- rs = RadioSetting("autolk", "Automatic Key Lock",
- RadioSettingValueBoolean(_settings.autolk))
- advanced.append(rs)
+ if self.MODEL != "UV-6":
+ rs = RadioSetting("autolk", "Automatic Key Lock",
+ RadioSettingValueBoolean(_settings.autolk))
+ advanced.append(rs)
- rs = RadioSetting("extra.fmradio", "Broadcast FM Radio",
- RadioSettingValueBoolean(self._memobj.extra.fmradio))
+ rs = RadioSetting("fmradio", "Broadcast FM Radio",
+ RadioSettingValueBoolean(_settings.fmradio))
advanced.append(rs)
- rs = RadioSetting("wtled", "Standby LED Color",
- RadioSettingValueList(COLOR_LIST,
- COLOR_LIST[_settings.wtled]))
- basic.append(rs)
-
- rs = RadioSetting("rxled", "RX LED Color",
- RadioSettingValueList(COLOR_LIST,
- COLOR_LIST[_settings.rxled]))
- basic.append(rs)
-
- rs = RadioSetting("txled", "TX LED Color",
- RadioSettingValueList(COLOR_LIST,
- COLOR_LIST[_settings.txled]))
- basic.append(rs)
-
- rs = RadioSetting("roger", "Roger Beep",
- RadioSettingValueBoolean(_settings.roger))
- basic.append(rs)
-
- try:
- _ani = self._memobj.ani.code
- rs = RadioSetting("ani.code", "ANI Code",
- RadioSettingValueInteger(0, 9, _ani[0]),
- RadioSettingValueInteger(0, 9, _ani[1]),
- RadioSettingValueInteger(0, 9, _ani[2]),
- RadioSettingValueInteger(0, 9, _ani[3]),
- RadioSettingValueInteger(0, 9, _ani[4]))
- advanced.append(rs)
- except Exception:
- print ("Your ANI code is not five digits, which is not currently"
- " supported in CHIRP.")
+ if self.MODEL != "UV-6":
+ rs = RadioSetting("wtled", "Standby LED Color",
+ RadioSettingValueList(COLOR_LIST,
+ COLOR_LIST[
+ _settings.wtled]))
+ basic.append(rs)
+
+ rs = RadioSetting("rxled", "RX LED Color",
+ RadioSettingValueList(COLOR_LIST,
+ COLOR_LIST[
+ _settings.rxled]))
+ basic.append(rs)
+
+ rs = RadioSetting("txled", "TX LED Color",
+ RadioSettingValueList(COLOR_LIST,
+ COLOR_LIST[
+ _settings.txled]))
+ basic.append(rs)
+
+ if self.MODEL == "UV-82":
+ rs = RadioSetting("roger", "Roger Beep (TX)",
+ RadioSettingValueBoolean(_settings.roger))
+ basic.append(rs)
+ rs = RadioSetting("rogerrx", "Roger Beep (RX)",
+ RadioSettingValueList(ROGERRX_LIST,
+ ROGERRX_LIST[
+ _settings.rogerrx]))
+ basic.append(rs)
+ else:
+ rs = RadioSetting("roger", "Roger Beep",
+ RadioSettingValueBoolean(_settings.roger))
+ basic.append(rs)
rs = RadioSetting("ste", "Squelch Tail Eliminate (HT to HT)",
RadioSettingValueBoolean(_settings.ste))
@@ -792,13 +1031,20 @@ class BaofengUV5R(chirp_common.CloneModeRadio,
STEDELAY_LIST[_settings.rptrl]))
advanced.append(rs)
- rs = RadioSetting("extra.reset", "RESET Menu",
- RadioSettingValueBoolean(self._memobj.extra.reset))
- advanced.append(rs)
+ if self.MODEL != "UV-6":
+ rs = RadioSetting("reset", "RESET Menu",
+ RadioSettingValueBoolean(_settings.reset))
+ advanced.append(rs)
- rs = RadioSetting("extra.menu", "All Menus",
- RadioSettingValueBoolean(self._memobj.extra.menu))
- advanced.append(rs)
+ rs = RadioSetting("menu", "All Menus",
+ RadioSettingValueBoolean(_settings.menu))
+ advanced.append(rs)
+
+ if self.MODEL == "F-11":
+ # this is an F-11 only feature
+ rs = RadioSetting("vfomrlock", "VFO/MR Button",
+ RadioSettingValueBoolean(_settings.vfomrlock))
+ advanced.append(rs)
if len(self._mmap.get_packed()) == 0x1808:
# Old image, without aux block
@@ -816,152 +1062,319 @@ class BaofengUV5R(chirp_common.CloneModeRadio,
filtered += " "
return filtered
- _msg = self._memobj.sixpoweron_msg
- rs = RadioSetting("sixpoweron_msg.line1", "6+Power-On Message 1",
- RadioSettingValueString(0, 7, _filter(_msg.line1)))
- other.append(rs)
- rs = RadioSetting("sixpoweron_msg.line2", "6+Power-On Message 2",
- RadioSettingValueString(0, 7, _filter(_msg.line2)))
+ _msg = self._memobj.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)
- _msg = self._memobj.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)))
+ val = RadioSettingValueString(0, 7, _filter(_msg.line2))
+ val.set_mutable(False)
+ rs = RadioSetting("firmware_msg.line2", "Firmware Message 2", val)
other.append(rs)
- rs = RadioSetting("ponmsg", "Power-On Message",
- RadioSettingValueList(PONMSG_LIST,
- PONMSG_LIST[_settings.ponmsg]))
- other.append(rs)
+ if self.MODEL != "UV-6":
+ _msg = self._memobj.sixpoweron_msg
+ rs = RadioSetting("sixpoweron_msg.line1", "6+Power-On Message 1",
+ RadioSettingValueString(0, 7, _filter(
+ _msg.line1)))
+ other.append(rs)
+ rs = RadioSetting("sixpoweron_msg.line2", "6+Power-On Message 2",
+ RadioSettingValueString(0, 7, _filter(
+ _msg.line2)))
+ other.append(rs)
+
+ _msg = self._memobj.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)
+
+ rs = RadioSetting("ponmsg", "Power-On Message",
+ RadioSettingValueList(PONMSG_LIST,
+ PONMSG_LIST[
+ _settings.ponmsg]))
+ other.append(rs)
+
+ if self._is_orig():
+ limit = "limits_old"
+ else:
+ limit = "limits_new"
+
+ vhf_limit = getattr(self._memobj, limit).vhf
+ rs = RadioSetting("%s.vhf.lower" % limit, "VHF Lower Limit (MHz)",
+ RadioSettingValueInteger(1, 1000,
+ vhf_limit.lower))
+ other.append(rs)
+
+ rs = RadioSetting("%s.vhf.upper" % limit, "VHF Upper Limit (MHz)",
+ RadioSettingValueInteger(1, 1000,
+ vhf_limit.upper))
+ other.append(rs)
+
+ rs = RadioSetting("%s.vhf.enable" % limit, "VHF TX Enabled",
+ RadioSettingValueBoolean(vhf_limit.enable))
+ other.append(rs)
+
+ uhf_limit = getattr(self._memobj, limit).uhf
+ rs = RadioSetting("%s.uhf.lower" % limit, "UHF Lower Limit (MHz)",
+ RadioSettingValueInteger(1, 1000,
+ uhf_limit.lower))
+ other.append(rs)
+ rs = RadioSetting("%s.uhf.upper" % limit, "UHF Upper Limit (MHz)",
+ RadioSettingValueInteger(1, 1000,
+ uhf_limit.upper))
+ other.append(rs)
+ rs = RadioSetting("%s.uhf.enable" % limit, "UHF TX Enabled",
+ RadioSettingValueBoolean(uhf_limit.enable))
+ other.append(rs)
+
+ if self.MODEL != "UV-6":
+ workmode = RadioSettingGroup("workmode", "Work Mode Settings")
+ group.append(workmode)
+
+ rs = RadioSetting("displayab", "Display",
+ RadioSettingValueList(AB_LIST,
+ AB_LIST[
+ _settings.displayab]))
+ workmode.append(rs)
- if self._is_orig():
- limit = "limits_old"
- else:
- limit = "limits_new"
+ rs = RadioSetting("workmode", "VFO/MR Mode",
+ RadioSettingValueList(WORKMODE_LIST,
+ WORKMODE_LIST[
+ _settings.workmode]))
+ workmode.append(rs)
- vhf_limit = getattr(self._memobj, limit).vhf
- rs = RadioSetting("%s.vhf.lower" % limit, "VHF Lower Limit (MHz)",
- RadioSettingValueInteger(1, 1000,
- vhf_limit.lower))
- other.append(rs)
+ rs = RadioSetting("keylock", "Keypad Lock",
+ RadioSettingValueBoolean(_settings.keylock))
+ workmode.append(rs)
- rs = RadioSetting("%s.vhf.upper" % limit, "VHF Upper Limit (MHz)",
- RadioSettingValueInteger(1, 1000,
- vhf_limit.upper))
- other.append(rs)
+ rs = RadioSetting("wmchannel.mrcha", "MR A Channel",
+ RadioSettingValueInteger(0, 127,
+ _wmchannel.mrcha))
+ workmode.append(rs)
- rs = RadioSetting("%s.vhf.enable" % limit, "VHF TX Enabled",
- RadioSettingValueBoolean(vhf_limit.enable))
- other.append(rs)
+ rs = RadioSetting("wmchannel.mrchb", "MR B Channel",
+ RadioSettingValueInteger(0, 127,
+ _wmchannel.mrchb))
+ workmode.append(rs)
- uhf_limit = getattr(self._memobj, limit).uhf
- rs = RadioSetting("%s.uhf.lower" % limit, "UHF Lower Limit (MHz)",
- RadioSettingValueInteger(1, 1000,
- uhf_limit.lower))
- other.append(rs)
- rs = RadioSetting("%s.uhf.upper" % limit, "UHF Upper Limit (MHz)",
- RadioSettingValueInteger(1, 1000,
- uhf_limit.upper))
- other.append(rs)
- rs = RadioSetting("%s.uhf.enable" % limit, "UHF TX Enabled",
- RadioSettingValueBoolean(uhf_limit.enable))
- other.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)
+ if 17400000 <= value and value < 40000000:
+ msg = ("Can't be between 174.00000-400.00000")
+ raise InvalidValueError(msg)
+ return chirp_common.format_freq(value)
+
+ def apply_freq(setting, obj):
+ value = chirp_common.parse_freq(str(setting.value)) / 10
+ obj.band = value >= 40000000
+ for i in range(7, -1, -1):
+ obj.freq[i] = value % 10
+ value /= 10
+
+ val1a = RadioSettingValueString(0, 10,
+ convert_bytes_to_freq(_vfoa.freq))
+ val1a.set_validate_callback(my_validate)
+ rs = RadioSetting("vfoa.freq", "VFO A Frequency", val1a)
+ rs.set_apply_callback(apply_freq, _vfoa)
+ workmode.append(rs)
- workmode = RadioSettingGroup("workmode", "Work Mode Settings")
- group.append(workmode)
-
- options = ["A", "B"]
- rs = RadioSetting("extra.displayab", "Display",
- RadioSettingValueList(options,
- options[self._memobj.extra.displayab]))
- workmode.append(rs)
-
- options = ["Frequency", "Channel"]
- rs = RadioSetting("extra.workmode", "VFO/MR Mode",
- RadioSettingValueList(options,
- options[self._memobj.extra.workmode]))
- workmode.append(rs)
-
- rs = RadioSetting("extra.keylock", "Keypad Lock",
- RadioSettingValueBoolean(self._memobj.extra.keylock))
- workmode.append(rs)
-
- _mrcna = self._memobj.wmchannel.mrcha
- rs = RadioSetting("wmchannel.mrcha", "MR A Channel",
- RadioSettingValueInteger(0, 127, _mrcna))
- workmode.append(rs)
-
- _mrcnb = self._memobj.wmchannel.mrchb
- rs = RadioSetting("wmchannel.mrchb", "MR B Channel",
- RadioSettingValueInteger(0, 127, _mrcnb))
- workmode.append(rs)
-
- options = ["VHF", "UHF"]
- rs = RadioSetting("vfoa.band", "VFO A Band",
- RadioSettingValueList(options,
- options[self._memobj.vfoa.band]))
- workmode.append(rs)
-
- rs = RadioSetting("vfob.band", "VFO B Band",
- RadioSettingValueList(options,
- options[self._memobj.vfob.band]))
- workmode.append(rs)
-
- options = ["High", "Low"]
- rs = RadioSetting("vfoa.txpower", "VFO A Power",
- RadioSettingValueList(options,
- options[self._memobj.vfoa.txpower]))
- workmode.append(rs)
-
- rs = RadioSetting("vfob.txpower", "VFO B Power",
- RadioSettingValueList(options,
- options[self._memobj.vfob.txpower]))
- workmode.append(rs)
-
- options = ["Wide", "Narrow"]
- rs = RadioSetting("vfoa.widenarr", "VFO A Bandwidth",
- RadioSettingValueList(options,
- options[self._memobj.vfoa.widenarr]))
- workmode.append(rs)
-
- rs = RadioSetting("vfob.widenarr", "VFO B Bandwidth",
- RadioSettingValueList(options,
- options[self._memobj.vfob.widenarr]))
- workmode.append(rs)
-
- options = ["%s" % x for x in range(1, 16)]
- rs = RadioSetting("vfoa.scode", "VFO A PTT-ID",
- RadioSettingValueList(options,
- options[self._memobj.vfoa.scode]))
- workmode.append(rs)
-
- rs = RadioSetting("vfob.scode", "VFO B PTT-ID",
- RadioSettingValueList(options,
- options[self._memobj.vfob.scode]))
- workmode.append(rs)
-
- if self._my_version() >= 291:
- rs = RadioSetting("vfoa.step", "VFO A Tuning Step",
- RadioSettingValueList(STEP291_LIST,
- STEP291_LIST[self._memobj.vfoa.step]))
+ val1b = RadioSettingValueString(0, 10,
+ convert_bytes_to_freq(_vfob.freq))
+ val1b.set_validate_callback(my_validate)
+ rs = RadioSetting("vfob.freq", "VFO B Frequency", val1b)
+ rs.set_apply_callback(apply_freq, _vfob)
workmode.append(rs)
- rs = RadioSetting("vfob.step", "VFO B Tuning Step",
- RadioSettingValueList(STEP291_LIST,
- STEP291_LIST[self._memobj.vfob.step]))
+
+ rs = RadioSetting("vfoa.sftd", "VFO A Shift",
+ RadioSettingValueList(SHIFTD_LIST,
+ SHIFTD_LIST[_vfoa.sftd]))
workmode.append(rs)
- else:
- rs = RadioSetting("vfoa.step", "VFO A Tuning Step",
- RadioSettingValueList(STEP_LIST,
- STEP_LIST[self._memobj.vfoa.step]))
+
+ rs = RadioSetting("vfob.sftd", "VFO B Shift",
+ RadioSettingValueList(SHIFTD_LIST,
+ SHIFTD_LIST[_vfob.sftd]))
+ workmode.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 * 10000)
+
+ def apply_offset(setting, obj):
+ value = chirp_common.parse_freq(str(setting.value)) / 10000
+ for i in range(3, -1, -1):
+ obj.offset[i] = value % 10
+ value /= 10
+
+ val1a = RadioSettingValueString(0, 10,
+ convert_bytes_to_offset(
+ _vfoa.offset))
+ rs = RadioSetting("vfoa.offset", "VFO A Offset (0.00-69.95)", val1a)
+ rs.set_apply_callback(apply_offset, _vfoa)
+ workmode.append(rs)
+
+ val1b = RadioSettingValueString(0, 10,
+ convert_bytes_to_offset(
+ _vfob.offset))
+ rs = RadioSetting("vfob.offset", "VFO B Offset (0.00-69.95)", val1b)
+ rs.set_apply_callback(apply_offset, _vfob)
workmode.append(rs)
- rs = RadioSetting("vfob.step", "VFO B Tuning Step",
- RadioSettingValueList(STEP_LIST,
- STEP_LIST[self._memobj.vfob.step]))
+
+ rs = RadioSetting("vfoa.txpower", "VFO A Power",
+ RadioSettingValueList(TXPOWER_LIST,
+ TXPOWER_LIST[
+ _vfoa.txpower]))
+ workmode.append(rs)
+
+ rs = RadioSetting("vfob.txpower", "VFO B Power",
+ RadioSettingValueList(TXPOWER_LIST,
+ TXPOWER_LIST[
+ _vfob.txpower]))
+ workmode.append(rs)
+
+ rs = RadioSetting("vfoa.widenarr", "VFO A Bandwidth",
+ RadioSettingValueList(BANDWIDTH_LIST,
+ BANDWIDTH_LIST[
+ _vfoa.widenarr]))
+ workmode.append(rs)
+
+ rs = RadioSetting("vfob.widenarr", "VFO B Bandwidth",
+ RadioSettingValueList(BANDWIDTH_LIST,
+ BANDWIDTH_LIST[
+ _vfob.widenarr]))
+ workmode.append(rs)
+
+ rs = RadioSetting("vfoa.scode", "VFO A PTT-ID",
+ RadioSettingValueList(PTTIDCODE_LIST,
+ PTTIDCODE_LIST[
+ _vfoa.scode]))
workmode.append(rs)
+ rs = RadioSetting("vfob.scode", "VFO B PTT-ID",
+ RadioSettingValueList(PTTIDCODE_LIST,
+ PTTIDCODE_LIST[
+ _vfob.scode]))
+ workmode.append(rs)
+
+ if not self._is_orig():
+ rs = RadioSetting("vfoa.step", "VFO A Tuning Step",
+ RadioSettingValueList(STEP291_LIST,
+ STEP291_LIST[
+ _vfoa.step]))
+ workmode.append(rs)
+ rs = RadioSetting("vfob.step", "VFO B Tuning Step",
+ RadioSettingValueList(STEP291_LIST,
+ STEP291_LIST[
+ _vfob.step]))
+ workmode.append(rs)
+ else:
+ rs = RadioSetting("vfoa.step", "VFO A Tuning Step",
+ RadioSettingValueList(STEP_LIST,
+ STEP_LIST[_vfoa.step]))
+ workmode.append(rs)
+ rs = RadioSetting("vfob.step", "VFO B Tuning Step",
+ RadioSettingValueList(STEP_LIST,
+ STEP_LIST[_vfob.step]))
+ workmode.append(rs)
+
+ fm_preset = RadioSettingGroup("fm_preset", "FM Radio Preset")
+ group.append(fm_preset)
+
+ if self._memobj.fm_presets <= 116.1 * 10 - 650:
+ preset = self._memobj.fm_presets / 10.0 + 65
+ else:
+ preset = 76.0
+ rs = RadioSetting("fm_presets", "FM Preset(MHz)",
+ RadioSettingValueFloat(65, 116.1, preset, 0.1, 1))
+ fm_preset.append(rs)
+
+ dtmf = RadioSettingGroup("dtmf", "DTMF Settings")
+ group.append(dtmf)
+ dtmfchars = "0123456789 *#ABCD"
+
+ for i in range(0, 15):
+ _codeobj = self._memobj.pttid[i].code
+ _code = "".join([dtmfchars[x] for x in _codeobj if int(x) < 0x1F])
+ val = RadioSettingValueString(0, 5, _code, False)
+ val.set_charset(dtmfchars)
+ rs = RadioSetting("pttid/%i.code" % i,
+ "PTT ID Code %i" % (i + 1), val)
+ def apply_code(setting, obj):
+ code = []
+ for j in range(0, 5):
+ try:
+ code.append(dtmfchars.index(str(setting.value)[j]))
+ except IndexError:
+ code.append(0xFF)
+ obj.code = code
+ rs.set_apply_callback(apply_code, self._memobj.pttid[i])
+ dtmf.append(rs)
+
+ _codeobj = self._memobj.ani.code
+ _code = "".join([dtmfchars[x] for x in _codeobj if int(x) < 0x1F])
+ val = RadioSettingValueString(0, 5, _code, False)
+ val.set_charset(dtmfchars)
+ rs = RadioSetting("ani.code", "ANI Code", val)
+ def apply_code(setting, obj):
+ code = []
+ for j in range(0, 5):
+ try:
+ code.append(dtmfchars.index(str(setting.value)[j]))
+ except IndexError:
+ code.append(0xFF)
+ obj.code = code
+ rs.set_apply_callback(apply_code, _ani)
+ dtmf.append(rs)
+
+ rs = RadioSetting("ani.aniid", "ANI ID",
+ RadioSettingValueList(PTTID_LIST,
+ PTTID_LIST[_ani.aniid]))
+ dtmf.append(rs)
+
+ _codeobj = self._memobj.ani.alarmcode
+ _code = "".join([dtmfchars[x] for x in _codeobj if int(x) < 0x1F])
+ val = RadioSettingValueString(0, 3, _code, False)
+ val.set_charset(dtmfchars)
+ rs = RadioSetting("ani.alarmcode", "Alarm Code", val)
+ def apply_code(setting, obj):
+ alarmcode = []
+ for j in range(0, 3):
+ try:
+ alarmcode.append(dtmfchars.index(str(setting.value)[j]))
+ except IndexError:
+ alarmcode.append(0xFF)
+ obj.alarmcode = alarmcode
+ rs.set_apply_callback(apply_code, _ani)
+ dtmf.append(rs)
+
+ rs = RadioSetting("dtmfst", "DTMF Sidetone",
+ RadioSettingValueList(DTMFST_LIST,
+ DTMFST_LIST[_settings.dtmfst]))
+ dtmf.append(rs)
+
+ rs = RadioSetting("ani.dtmfon", "DTMF Speed (on)",
+ RadioSettingValueList(DTMFSPEED_LIST,
+ DTMFSPEED_LIST[_ani.dtmfon]))
+ dtmf.append(rs)
+
+ rs = RadioSetting("ani.dtmfoff", "DTMF Speed (off)",
+ RadioSettingValueList(DTMFSPEED_LIST,
+ DTMFSPEED_LIST[_ani.dtmfoff]))
+ dtmf.append(rs)
+
return group
def get_settings(self):
@@ -974,23 +1387,99 @@ class BaofengUV5R(chirp_common.CloneModeRadio,
return None
def set_settings(self, settings):
- _settings = self._memobj.settings[0]
+ _settings = self._memobj.settings
for element in settings:
if not isinstance(element, RadioSetting):
- self.set_settings(element)
- continue
- 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]
+ if element.get_name() == "fm_preset" :
+ self._set_fm_preset(element)
else:
- obj = _settings
- setting = element.get_name()
- print "Setting %s = %s" % (setting, element.value)
- setattr(obj, setting, element.value)
+ 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():
+ print "Using apply callback"
+ element.run_apply_callback()
+ else:
+ print "Setting %s = %s" % (setting, element.value)
+ setattr(obj, setting, element.value)
+ except Exception, e:
+ print element.get_name()
+ raise
+
+ def _set_fm_preset(self, settings):
+ for element in settings:
+ try:
+ val = element.value
+ value = int(val.get_value() * 10 - 650)
+ print "Setting fm_presets = %s" % (value)
+ self._memobj.fm_presets = value
except Exception, e:
print element.get_name()
raise
+
+ at directory.register
+class BaofengF11Radio(BaofengUV5R):
+ VENDOR = "Baofeng"
+ MODEL = "F-11"
+ _basetype = BASETYPE_F11
+ _idents = [UV5R_MODEL_F11]
+
+ def _is_orig(self):
+ # Override this for F11 to always return False
+ return False
+
+ at directory.register
+class BaofengUV82Radio(BaofengUV5R):
+ MODEL = "UV-82"
+ _basetype = BASETYPE_UV82
+ _idents = [UV5R_MODEL_UV82]
+
+ def _is_orig(self):
+ # Override this for UV82 to always return False
+ return False
+
+ at directory.register
+class BaofengUV6Radio(BaofengUV5R):
+ """Baofeng UV-6/UV-7"""
+ VENDOR = "Baofeng"
+ MODEL = "UV-6"
+ _basetype = BASETYPE_UV6
+ _idents = [UV5R_MODEL_UV6]
+
+ def get_features(self):
+ rf = BaofengUV5R.get_features(self)
+ rf.memory_bounds = (1, 128)
+ return rf
+
+ def _get_mem(self, number):
+ return self._memobj.memory[number - 1]
+
+ def _get_nam(self, number):
+ return self._memobj.names[number - 1]
+
+ def _set_mem(self, number):
+ return self._memobj.memory[number - 1]
+
+ def _set_nam(self, number):
+ return self._memobj.names[number - 1]
+
+ def _is_orig(self):
+ # Override this for UV6 to always return False
+ return False
diff --git a/chirp/uvb5.py b/chirp/uvb5.py
new file mode 100644
index 0000000..a989372
--- /dev/null
+++ b/chirp/uvb5.py
@@ -0,0 +1,775 @@
+# Copyright 2013 Dan Smith <dsmith at danplanet.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 struct
+from chirp import chirp_common, directory, bitwise, memmap, errors, util
+from chirp.settings import RadioSetting, RadioSettingGroup, \
+ RadioSettingValueBoolean, RadioSettingValueList, \
+ RadioSettingValueInteger, RadioSettingValueString, \
+ RadioSettingValueFloat
+from textwrap import dedent
+
+mem_format = """
+struct memory {
+ lbcd freq[4];
+ lbcd offset[4];
+ u8 unknown1:2,
+ txpol:1,
+ rxpol:1,
+ compander:1,
+ unknown2:3;
+ u8 rxtone;
+ u8 txtone;
+ u8 pttid:1,
+ scanadd:1,
+ isnarrow:1,
+ bcl:1,
+ highpower:1,
+ revfreq:1,
+ duplex:2;
+ u8 step;
+ u8 unknown[3];
+};
+
+#seekto 0x0000;
+char ident[32];
+u8 blank[16];
+struct memory vfo1;
+struct memory channels[99];
+#seekto 0x0850;
+struct memory vfo2;
+
+#seekto 0x09D0;
+u16 fm_presets[16];
+
+#seekto 0x0A30;
+struct {
+ u8 name[5];
+} names[99];
+
+#seekto 0x0D30;
+struct {
+ u8 squelch;
+ u8 freqmode_ab:1,
+ save_funct:1,
+ backlight:1,
+ beep_tone_disabled:1,
+ roger:1,
+ tdr:1,
+ scantype:2;
+ u8 language:1,
+ workmode_b:1,
+ workmode_a:1,
+ workmode_fm:1,
+ voice_prompt:1,
+ fm:1,
+ pttid:2;
+ u8 unknown_0:5,
+ timeout:3;
+ u8 mdf_b:2,
+ mdf_a:2,
+ unknown_1:2,
+ txtdr:2;
+ u8 unknown_2:4,
+ ste_disabled:1,
+ unknown_3:2,
+ sidetone:1;
+ u8 vox;
+ u8 unk1;
+ u8 mem_chan_a;
+ u16 fm_vfo;
+ u8 unk4;
+ u8 unk5;
+ u8 mem_chan_b;
+ u8 unk6;
+ u8 last_menu; // number of last menu item accessed
+} settings;
+
+#seekto 0x0D50;
+struct {
+ u8 code[6];
+} pttid;
+
+#seekto 0x0F30;
+struct {
+ lbcd lower_vhf[2];
+ lbcd upper_vhf[2];
+ lbcd lower_uhf[2];
+ lbcd upper_uhf[2];
+} limits;
+
+#seekto 0x0FF0;
+struct {
+ u8 vhfsquelch0;
+ u8 vhfsquelch1;
+ u8 vhfsquelch2;
+ u8 vhfsquelch3;
+ u8 vhfsquelch4;
+ u8 vhfsquelch5;
+ u8 vhfsquelch6;
+ u8 vhfsquelch7;
+ u8 vhfsquelch8;
+ u8 vhfsquelch9;
+ u8 unknown1[6];
+ u8 uhfsquelch0;
+ u8 uhfsquelch1;
+ u8 uhfsquelch2;
+ u8 uhfsquelch3;
+ u8 uhfsquelch4;
+ u8 uhfsquelch5;
+ u8 uhfsquelch6;
+ u8 uhfsquelch7;
+ u8 uhfsquelch8;
+ u8 uhfsquelch9;
+ u8 unknown2[6];
+ u8 vhfhipwr0;
+ u8 vhfhipwr1;
+ u8 vhfhipwr2;
+ u8 vhfhipwr3;
+ u8 vhfhipwr4;
+ u8 vhfhipwr5;
+ u8 vhfhipwr6;
+ u8 vhfhipwr7;
+ u8 vhflopwr0;
+ u8 vhflopwr1;
+ u8 vhflopwr2;
+ u8 vhflopwr3;
+ u8 vhflopwr4;
+ u8 vhflopwr5;
+ u8 vhflopwr6;
+ u8 vhflopwr7;
+ u8 uhfhipwr0;
+ u8 uhfhipwr1;
+ u8 uhfhipwr2;
+ u8 uhfhipwr3;
+ u8 uhfhipwr4;
+ u8 uhfhipwr5;
+ u8 uhfhipwr6;
+ u8 uhfhipwr7;
+ u8 uhflopwr0;
+ u8 uhflopwr1;
+ u8 uhflopwr2;
+ u8 uhflopwr3;
+ u8 uhflopwr4;
+ u8 uhflopwr5;
+ u8 uhflopwr6;
+ u8 uhflopwr7;
+} test;
+"""
+
+def do_ident(radio):
+ radio.pipe.setTimeout(3)
+ radio.pipe.write("PROGRAM")
+ ack = radio.pipe.read(1)
+ if ack != '\x06':
+ raise errors.RadioError("Radio did not ack programming mode")
+ radio.pipe.write("\x02")
+ ident = radio.pipe.read(8)
+ print util.hexprint(ident)
+ if not ident.startswith('HKT511'):
+ raise errors.RadioError("Unsupported model")
+ radio.pipe.write("\x06")
+ ack = radio.pipe.read(1)
+ if ack != "\x06":
+ raise errors.RadioError("Radio did not ack ident")
+
+def do_status(radio, direction, addr):
+ status = chirp_common.Status()
+ status.msg = "Cloning %s radio" % direction
+ status.cur = addr
+ status.max = 0x1000
+ radio.status_fn(status)
+
+def do_download(radio):
+ do_ident(radio)
+ data = "KT511 Radio Program data v1.08\x00\x00"
+ data += ("\x00" * 16)
+ firstack = None
+ for i in range(0, 0x1000, 16):
+ frame = struct.pack(">cHB", "R", i, 16)
+ radio.pipe.write(frame)
+ result = radio.pipe.read(20)
+ if frame[1:4] != result[1:4]:
+ print util.hexprint(result)
+ raise errors.RadioError("Invalid response for address 0x%04x" % i)
+ radio.pipe.write("\x06")
+ ack = radio.pipe.read(1)
+ if not firstack:
+ firstack = ack
+ else:
+ if not ack == firstack:
+ print "first ack:", util.hexprint(firstack), \
+ "ack received:", util.hexprint(ack)
+ raise errors.RadioError("Unexpected response")
+ data += result[4:]
+ do_status(radio, "from", i)
+
+ return memmap.MemoryMap(data)
+
+def do_upload(radio):
+ do_ident(radio)
+ data = radio._mmap[0x0030:]
+
+ for i in range(0, 0x1000, 16):
+ frame = struct.pack(">cHB", "W", i, 16)
+ frame += data[i:i + 16]
+ radio.pipe.write(frame)
+ ack = radio.pipe.read(1)
+ if ack != "\x06":
+ raise errors.RadioError("Radio NAK'd block at address 0x%04x" % i)
+ do_status(radio, "to", i)
+
+DUPLEX = ["", "-", "+", 'off', "split"]
+UVB5_STEPS = [5.00, 6.25, 10.0, 12.5, 20.0, 25.0]
+CHARSET = "0123456789- ABCDEFGHIJKLMNOPQRSTUVWXYZ/_+*"
+SPECIALS = {
+ "VFO1": -2,
+ "VFO2": -1,
+ }
+POWER_LEVELS = [chirp_common.PowerLevel("Low", watts=1),
+ chirp_common.PowerLevel("High", watts=5)]
+
+ at directory.register
+class BaofengUVB5(chirp_common.CloneModeRadio,
+ chirp_common.ExperimentalRadio):
+ """Baofeng UV-B5"""
+ VENDOR = "Baofeng"
+ MODEL = "UV-B5"
+ BAUD_RATE = 9600
+
+ _memsize = 0x1000
+
+ @classmethod
+ def get_prompts(cls):
+ rp = chirp_common.RadioPrompts()
+ rp.experimental = ('This version of the UV-B5 driver allows you to '
+ 'modify the Test Mode settings of your radio. This has been '
+ 'tested and reports from other users indicate that it is a '
+ 'safe thing to do. However, modifications to these values may '
+ 'have unintended consequences, including damage to your '
+ 'device. You have been warned. Proceed at your own risk!')
+ rp.pre_download = _(dedent("""\
+ 1. Turn radio off.
+ 2. Connect cable to mic/spkr connector.
+ 3. Make sure connector is firmly connected.
+ 4. Turn radio on.
+ 5. Ensure that the radio is tuned to channel with no activity.
+ 6. Click OK to download image from device."""))
+ rp.pre_upload = _(dedent("""\
+ 1. Turn radio off.
+ 2. Connect cable to mic/spkr connector.
+ 3. Make sure connector is firmly connected.
+ 4. Turn radio on.
+ 5. Ensure that the radio is tuned to channel with no activity.
+ 6. Click OK to upload image to device."""))
+ return rp
+
+ def get_features(self):
+ rf = chirp_common.RadioFeatures()
+ rf.has_settings = True
+ rf.has_cross = True
+ rf.has_rx_dtcs = True
+ rf.valid_tmodes = ["", "Tone", "TSQL", "DTCS", "Cross"]
+ rf.valid_cross_modes = ["Tone->Tone", "Tone->DTCS", "DTCS->Tone",
+ "->Tone", "->DTCS", "DTCS->", "DTCS->DTCS"]
+ rf.valid_duplexes = DUPLEX
+ rf.can_odd_split = True
+ rf.valid_skips = ["", "S"]
+ rf.valid_characters = CHARSET
+ rf.valid_name_length = 5
+ rf.valid_bands = [(136000000, 174000000),
+ (400000000, 520000000)]
+ rf.valid_modes = ["FM", "NFM"]
+ rf.valid_special_chans = SPECIALS.keys()
+ rf.valid_power_levels = POWER_LEVELS
+ rf.has_ctone = True
+ rf.has_bank = False
+ rf.has_tuning_step = False
+ rf.memory_bounds = (1, 99)
+ return rf
+
+ def sync_in(self):
+ try:
+ self._mmap = do_download(self)
+ except errors.RadioError:
+ raise
+ except Exception, e:
+ raise errors.RadioError("Failed to communicate with radio: %s" % e)
+ self.process_mmap()
+
+ def sync_out(self):
+ try:
+ do_upload(self)
+ except errors.RadioError:
+ raise
+ except Exception, e:
+ raise errors.RadioError("Failed to communicate with radio: %s" % e)
+
+ def process_mmap(self):
+ self._memobj = bitwise.parse(mem_format, self._mmap)
+
+ def get_raw_memory(self, number):
+ return repr(self._memobj.channels[number - 1])
+
+ def _decode_tone(self, value, flag):
+ if value > 155:
+ mode = val = pol = None
+ elif value > 50:
+ mode = 'DTCS'
+ val = chirp_common.DTCS_CODES[value - 51]
+ pol = flag and 'R' or 'N'
+ elif value:
+ mode = 'Tone'
+ val = chirp_common.TONES[value - 1]
+ pol = None
+ else:
+ mode = val = pol = None
+
+ return mode, val, pol
+
+ def _encode_tone(self, _mem, which, mode, val, pol):
+ def _set(field, value):
+ setattr(_mem, "%s%s" % (which, field), value)
+
+ _set("pol", 0)
+ if mode == "Tone":
+ _set("tone", chirp_common.TONES.index(val) + 1)
+ elif mode == "DTCS":
+ _set("tone", chirp_common.DTCS_CODES.index(val) + 51)
+ _set("pol", pol == "R")
+ else:
+ _set("tone", 0)
+
+ def _get_memobjs(self, number):
+ if isinstance(number, str):
+ return (getattr(self._memobj, number.lower()), None)
+ elif number < 0:
+ for k, v in SPECIALS.items():
+ if number == v:
+ return (getattr(self._memobj, k.lower()), None)
+ else:
+ return (self._memobj.channels[number - 1],
+ self._memobj.names[number - 1].name)
+
+ def get_memory(self, number):
+ _mem, _nam = self._get_memobjs(number)
+ mem = chirp_common.Memory()
+ if isinstance(number, str):
+ mem.number = SPECIALS[number]
+ mem.extd_number = number
+ else:
+ mem.number = number
+
+ if _mem.freq.get_raw()[0] == "\xFF":
+ mem.empty = True
+ return mem
+
+ mem.freq = int(_mem.freq) * 10
+ mem.offset = int(_mem.offset) * 10
+
+ chirp_common.split_tone_decode(
+ mem,
+ self._decode_tone(_mem.txtone, _mem.txpol),
+ self._decode_tone(_mem.rxtone, _mem.rxpol))
+
+ if _mem.step > 0x05:
+ _mem.step = 0x00
+ mem.duplex = DUPLEX[_mem.duplex]
+ mem.mode = _mem.isnarrow and "NFM" or "FM"
+ mem.skip = "" if _mem.scanadd else "S"
+ mem.power = POWER_LEVELS[_mem.highpower]
+
+ if mem.freq == mem.offset and mem.duplex == "-":
+ mem.duplex = "off"
+ mem.offset = 0
+
+ if _nam:
+ for char in _nam:
+ try:
+ mem.name += CHARSET[char]
+ except IndexError:
+ break
+ mem.name = mem.name.rstrip()
+
+ mem.extra = RadioSettingGroup("Extra", "extra")
+
+ rs = RadioSetting("bcl", "BCL",
+ RadioSettingValueBoolean(_mem.bcl))
+ mem.extra.append(rs)
+
+ rs = RadioSetting("revfreq", "Reverse Duplex",
+ RadioSettingValueBoolean(_mem.revfreq))
+ mem.extra.append(rs)
+
+ rs = RadioSetting("pttid", "PTT ID",
+ RadioSettingValueBoolean(_mem.pttid))
+ mem.extra.append(rs)
+
+ rs = RadioSetting("compander", "Compander",
+ RadioSettingValueBoolean(_mem.compander))
+ mem.extra.append(rs)
+
+ return mem
+
+ def set_memory(self, mem):
+ _mem, _nam = self._get_memobjs(mem.number)
+
+ if mem.empty:
+ if _nam is None:
+ raise errors.InvalidValueError("VFO channels can not be empty")
+ _mem.set_raw("\xFF" * 16)
+ return
+
+ if _mem.get_raw() == ("\xFF" * 16):
+ _mem.set_raw("\x00" * 13 + "\xFF" * 3)
+
+ _mem.freq = mem.freq / 10
+
+ if mem.duplex == "off":
+ _mem.duplex = DUPLEX.index("-")
+ _mem.offset = _mem.freq
+ elif mem.duplex == "split":
+ diff = mem.offset - mem.freq
+ _mem.duplex = DUPLEX.index("-") if diff < 0 else DUPLEX.index("+")
+ _mem.offset = abs(diff) / 10
+ else:
+ _mem.offset = mem.offset / 10
+ _mem.duplex = DUPLEX.index(mem.duplex)
+
+ tx, rx = chirp_common.split_tone_encode(mem)
+ self._encode_tone(_mem, 'tx', *tx)
+ self._encode_tone(_mem, 'rx', *rx)
+
+ _mem.isnarrow = mem.mode == "NFM"
+ _mem.scanadd = mem.skip == ""
+ _mem.highpower = mem.power == POWER_LEVELS[1]
+
+ if _nam:
+ for i in range(0, 5):
+ try:
+ _nam[i] = CHARSET.index(mem.name[i])
+ except IndexError:
+ _nam[i] = 0xFF
+
+ for setting in mem.extra:
+ setattr(_mem, setting.get_name(), setting.value)
+
+ def validate_memory(self, mem):
+ msgs = chirp_common.CloneModeRadio.validate_memory(self, mem)
+
+ if (mem.duplex == "split" and abs(mem.freq - mem.offset) > 69995000) \
+ or (mem.duplex in ["+", "-"] and mem.offset > 69995000) :
+ msgs.append(chirp_common.ValidationError(
+ "Max split is 69.995MHz"))
+ return msgs
+
+
+ def get_settings(self):
+ _settings = self._memobj.settings
+ basic = RadioSettingGroup("basic", "Basic Settings")
+ group = RadioSettingGroup("top", "All Settings", basic)
+
+ options = ["Time", "Carrier", "Search"]
+ rs = RadioSetting("scantype", "Scan Type",
+ RadioSettingValueList(options,
+ options[_settings.scantype]))
+ basic.append(rs)
+
+ options = ["Off"] + ["%s min" % x for x in range(1, 8)]
+ rs = RadioSetting("timeout", "Time Out Timer",
+ RadioSettingValueList(options,
+ options[_settings.timeout]))
+ basic.append(rs)
+
+ options = ["A", "B"]
+ rs = RadioSetting("freqmode_ab", "Frequency Mode",
+ RadioSettingValueList(options,
+ options[_settings.freqmode_ab]))
+ basic.append(rs)
+
+ options = ["Frequency Mode", "Channel Mode"]
+ rs = RadioSetting("workmode_a", "Radio Work Mode(A)",
+ RadioSettingValueList(options,
+ options[_settings.workmode_a]))
+ basic.append(rs)
+
+ rs = RadioSetting("workmode_b", "Radio Work Mode(B)",
+ RadioSettingValueList(options,
+ options[_settings.workmode_b]))
+ basic.append(rs)
+
+ options = ["Frequency", "Name", "Channel"]
+ rs = RadioSetting("mdf_a", "Display Format(F1)",
+ RadioSettingValueList(options,
+ options[_settings.mdf_a]))
+ basic.append(rs)
+
+ rs = RadioSetting("mdf_b", "Display Format(F2)",
+ RadioSettingValueList(options,
+ options[_settings.mdf_b]))
+ basic.append(rs)
+
+ rs = RadioSetting("mem_chan_a", "Mem Channel (A)",
+ RadioSettingValueInteger(1, 99, _settings.mem_chan_a))
+ basic.append(rs)
+
+ rs = RadioSetting("mem_chan_b", "Mem Channel (B)",
+ RadioSettingValueInteger(1, 99, _settings.mem_chan_b))
+ basic.append(rs)
+
+ options = ["Off", "BOT", "EOT", "Both"]
+ rs = RadioSetting("pttid", "PTT-ID",
+ RadioSettingValueList(options,
+ options[_settings.pttid]))
+ basic.append(rs)
+
+ dtmfchars = "0123456789ABCD*#"
+ _codeobj = self._memobj.pttid.code
+ _code = "".join([dtmfchars[x] for x in _codeobj if int(x) < 0x1F])
+ val = RadioSettingValueString(0, 6, _code, False)
+ val.set_charset(dtmfchars)
+ rs = RadioSetting("pttid.code", "PTT-ID Code", val)
+ def apply_code(setting, obj):
+ code = []
+ for j in range(0, 6):
+ try:
+ code.append(dtmfchars.index(str(setting.value)[j]))
+ except IndexError:
+ code.append(0xFF)
+ obj.code = code
+ rs.set_apply_callback(apply_code, self._memobj.pttid)
+ basic.append(rs)
+
+ rs = RadioSetting("squelch", "Squelch Level",
+ RadioSettingValueInteger(0, 9, _settings.squelch))
+ basic.append(rs)
+
+ rs = RadioSetting("vox", "VOX Level",
+ RadioSettingValueInteger(0, 9, _settings.vox))
+ basic.append(rs)
+
+ options = ["Frequency Mode", "Channel Mode"]
+ rs = RadioSetting("workmode_fm", "FM Work Mode",
+ RadioSettingValueList(options,
+ options[_settings.workmode_fm]))
+ basic.append(rs)
+
+ options = ["Current Frequency", "F1 Frequency", "F2 Frequency"]
+ rs = RadioSetting("txtdr", "Dual Standby TX Priority",
+ RadioSettingValueList(options,
+ options[_settings.txtdr]))
+ basic.append(rs)
+
+ options = ["English", "Chinese"]
+ rs = RadioSetting("language", "Language",
+ RadioSettingValueList(options,
+ options[_settings.language]))
+ basic.append(rs)
+
+ rs = RadioSetting("tdr", "Dual Standby",
+ RadioSettingValueBoolean(_settings.tdr))
+ basic.append(rs)
+
+ rs = RadioSetting("roger", "Roger Beep",
+ RadioSettingValueBoolean(_settings.roger))
+ basic.append(rs)
+
+ rs = RadioSetting("backlight", "Backlight",
+ RadioSettingValueBoolean(_settings.backlight))
+ basic.append(rs)
+
+ rs = RadioSetting("save_funct", "Save Mode",
+ RadioSettingValueBoolean( _settings.save_funct))
+ basic.append(rs)
+
+ rs = RadioSetting("fm", "FM Function",
+ RadioSettingValueBoolean(_settings.fm))
+ basic.append(rs)
+
+ rs = RadioSetting("beep_tone_disabled", "Beep Prompt",
+ RadioSettingValueBoolean(
+ not _settings.beep_tone_disabled))
+ basic.append(rs)
+
+ rs = RadioSetting("voice_prompt", "Voice Prompt",
+ RadioSettingValueBoolean(_settings.voice_prompt))
+ basic.append(rs)
+
+ rs = RadioSetting("sidetone", "DTMF Side Tone",
+ RadioSettingValueBoolean(_settings.sidetone))
+ basic.append(rs)
+
+ rs = RadioSetting("ste_disabled", "Squelch Tail Eliminate",
+ RadioSettingValueBoolean(not _settings.ste_disabled))
+ basic.append(rs)
+
+ _limit = int(self._memobj.limits.lower_vhf) / 10
+ rs = RadioSetting("limits.lower_vhf", "VHF Lower Limit (MHz)",
+ RadioSettingValueInteger(136, 174, _limit))
+ def apply_limit(setting, obj):
+ value = int(setting.value) * 10
+ obj.lower_vhf = value
+ rs.set_apply_callback(apply_limit, self._memobj.limits)
+ basic.append(rs)
+
+ _limit = int(self._memobj.limits.upper_vhf) / 10
+ rs = RadioSetting("limits.upper_vhf", "VHF Upper Limit (MHz)",
+ RadioSettingValueInteger(136, 174, _limit))
+ def apply_limit(setting, obj):
+ value = int(setting.value) * 10
+ obj.upper_vhf = value
+ rs.set_apply_callback(apply_limit, self._memobj.limits)
+ basic.append(rs)
+
+ _limit = int(self._memobj.limits.lower_uhf) / 10
+ rs = RadioSetting("limits.lower_uhf", "UHF Lower Limit (MHz)",
+ RadioSettingValueInteger(400, 520, _limit))
+ def apply_limit(setting, obj):
+ value = int(setting.value) * 10
+ obj.lower_uhf = value
+ rs.set_apply_callback(apply_limit, self._memobj.limits)
+ basic.append(rs)
+
+ _limit = int(self._memobj.limits.upper_uhf) / 10
+ rs = RadioSetting("limits.upper_uhf", "UHF Upper Limit (MHz)",
+ RadioSettingValueInteger(400, 520, _limit))
+ def apply_limit(setting, obj):
+ value = int(setting.value) * 10
+ obj.upper_uhf = value
+ rs.set_apply_callback(apply_limit, self._memobj.limits)
+ basic.append(rs)
+
+ fm_preset = RadioSettingGroup("fm_preset", "FM Radio Presets")
+ group.append(fm_preset)
+
+ for i in range(0, 16):
+ if self._memobj.fm_presets[i] < 0x01AF:
+ used = True
+ preset = self._memobj.fm_presets[i] / 10.0 + 65
+ else:
+ used = False
+ preset = 65
+ rs = RadioSetting("fm_presets_%1i" % i, "FM Preset %i" % (i + 1),
+ RadioSettingValueBoolean(used),
+ RadioSettingValueFloat(65, 108, preset, 0.1, 1))
+ fm_preset.append(rs)
+
+ testmode = RadioSettingGroup("testmode", "Test Mode Settings")
+ group.append(testmode)
+
+ vhfdata = ["136-139", "140-144", "145-149", "150-154",
+ "155-159", "160-164", "165-169", "170-174"]
+ uhfdata = ["400-409", "410-419", "420-429", "430-439",
+ "440-449", "450-459", "460-469", "470-479"]
+ powernamedata = ["Hi", "Lo"]
+ powerkeydata = ["hipwr", "lopwr"]
+
+ for power in range (0, 2):
+ for index in range(0, 8):
+ key = "test.vhf%s%i" % (powerkeydata[power], index)
+ name = "%s Mhz %s Power" % (vhfdata[index],
+ powernamedata[power])
+ rs = RadioSetting(key, name, RadioSettingValueInteger(0, 255,
+ getattr(self._memobj.test, "vhf%s%i"
+ % (powerkeydata[power], index))))
+ testmode.append(rs)
+
+ for power in range (0, 2):
+ for index in range(0, 8):
+ key = "test.uhf%s%i" % (powerkeydata[power], index)
+ name = "%s Mhz %s Power" % (uhfdata[index],
+ powernamedata[power])
+ rs = RadioSetting(key, name, RadioSettingValueInteger(0, 255,
+ getattr(self._memobj.test, "uhf%s%i"
+ % (powerkeydata[power], index))))
+ testmode.append(rs)
+
+ for band in ["vhf", "uhf"]:
+ for index in range(0, 10):
+ key = "test.%ssquelch%i" % (band, index)
+ name = "%s Squelch %i" % (band.upper(), index)
+ rs = RadioSetting(key, name, RadioSettingValueInteger(0, 255,
+ getattr(self._memobj.test, "%ssquelch%i"
+ % (band, index))))
+ testmode.append(rs)
+
+ return group
+
+ def set_settings(self, settings):
+ _settings = self._memobj.settings
+ 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():
+ print "Using apply callback"
+ element.run_apply_callback()
+ elif setting == "beep_tone_disabled":
+ val = not _settings.beep_tone_disabled
+ print "Setting %s = %s" % (setting, val)
+ setattr(obj, setting, val)
+ elif setting == "ste_disabled":
+ val = not _settings.ste_disabled
+ print "Setting %s = %s" % (setting, val)
+ setattr(obj, setting, val)
+ else:
+ print "Setting %s = %s" % (setting, element.value)
+ setattr(obj, setting, element.value)
+ except Exception, e:
+ print element.get_name()
+ raise
+
+ def _set_fm_preset(self, settings):
+ for element in settings:
+ try:
+ index = (int(element.get_name().split("_")[-1]))
+ val = element.value
+ if val[0].get_value():
+ value = int(val[1].get_value() * 10 - 650)
+ else:
+ value = 0x01AF
+ print "Setting fm_presets[%1i] = %s" % (index, value)
+ setting = self._memobj.fm_presets
+ setting[index] = value
+ except Exception, e:
+ print element.get_name()
+ raise
+
+
+ @classmethod
+ def match_model(cls, filedata, filename):
+ return (filedata.startswith("KT511 Radio Program data") and
+ len(filedata) == (cls._memsize + 0x30))
diff --git a/chirp/vx2.py b/chirp/vx2.py
new file mode 100644
index 0000000..d68f098
--- /dev/null
+++ b/chirp/vx2.py
@@ -0,0 +1,708 @@
+# Copyright 2013 Jens Jensen <kd4tjx at yahoo.com>
+# based on modification of Dan Smith's and Rick Farina's original work
+#
+# 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, yaesu_clone, directory, bitwise
+from chirp.settings import RadioSetting, RadioSettingGroup, \
+ RadioSettingValueInteger, RadioSettingValueList, \
+ RadioSettingValueBoolean, RadioSettingValueString
+import os, traceback, re
+
+if os.getenv("CHIRP_DEBUG"):
+ CHIRP_DEBUG = True
+else:
+ CHIRP_DEBUG = False
+
+MEM_FORMAT = """
+#seekto 0x7F52;
+u8 checksum;
+
+#seekto 0x005A;
+u8 banksoff1;
+#seekto 0x00DA;
+u8 banksoff2;
+
+#seekto 0x0068;
+u16 prioritychan1;
+
+#seekto 0x00E8;
+u16 prioritychan2;
+
+#seekto 0x110;
+struct {
+ u8 unk1;
+ u8 unk2;
+ u8 nfm_sql;
+ u8 wfm_sql;
+ u8 rfsql;
+ u8 vfomode:1,
+ cwid_en:1,
+ scan_lamp:1,
+ unk3:1,
+ ars:1,
+ beep:1,
+ split:1,
+ dtmfmode:1;
+ u8 busyled:1,
+ unk4:2,
+ bclo:1,
+ edgebeep:1,
+ unk5:2,
+ txsave:1;
+ u8 unk6:2,
+ smartsearch:1,
+ unk7:1,
+ artsinterval:1,
+ unk8:1,
+ hmrv:1,
+ moni_tcall:1;
+ u8 unk9:5,
+ dcsrev:1,
+ unk10:1,
+ mwmode:1;
+ u8 internet_mode:1,
+ internet_key:1,
+ wx_alert:1,
+ unk11:2,
+ att:1,
+ unk12:2;
+ u8 lamp;
+ u8 dimmer;
+ u8 rxsave;
+ u8 resume;
+ u8 chcounter;
+ u8 openmsgmode;
+ u8 openmsg[6];
+ u8 cwid[16];
+ u8 unk13[16];
+ u8 artsbeep;
+ u8 bell;
+ u8 apo;
+ u8 tot;
+ u8 lock;
+ u8 mymenu;
+ u8 unk14[4];
+ u8 emergmode;
+
+} settings;
+
+#seekto 0x0192;
+struct {
+ u8 digits[16];
+} dtmf[9];
+
+#seekto 0x016A;
+struct {
+ u16 in_use;
+} bank_used[20];
+
+#seekto 0x0396;
+struct {
+ u8 name[6];
+} wxchannels[10];
+
+#seekto 0x05C2;
+struct {
+ u16 channels[100];
+} banks[20];
+
+#seekto 0x1562;
+struct {
+ u8 even_pskip:1,
+ even_skip:1,
+ even_valid:1,
+ even_masked:1,
+ odd_pskip:1,
+ odd_skip:1,
+ odd_valid:1,
+ odd_masked:1;
+} flags[500];
+
+struct mem_struct {
+ u8 unknown1:2,
+ txnarrow:1,
+ clk:1,
+ unknown2:4;
+ u8 mode:2,
+ duplex:2,
+ tune_step:4;
+ bbcd freq[3];
+ u8 power:2,
+ unknown3:4,
+ tmode:2;
+ u8 name[6];
+ bbcd offset[3];
+ u8 unknown4:2,
+ tone:6;
+ u8 unknown5:1,
+ dcs:7;
+ u8 unknown6;
+};
+
+#seekto 0x17C2;
+struct mem_struct memory[1000];
+struct {
+ struct mem_struct lower;
+ struct mem_struct upper;
+} pms[50];
+
+
+#seekto 0x03D2;
+struct mem_struct home[12];
+
+#seekto 0x04E2;
+struct mem_struct vfo[12];
+
+
+"""
+
+VX2_DUPLEX = ["", "-", "+", "split"]
+VX2_MODES = ["FM", "AM", "WFM", "Auto", "NFM"] # NFM handled specially in radio
+VX2_TMODES = ["", "Tone", "TSQL", "DTCS"]
+
+VX2_STEPS = [ 5.0, 10.0, 12.5, 15.0, 20.0, 25.0, 50.0, 100.0, 9.0 ]
+
+CHARSET = list("0123456789" + \
+ "ABCDEFGHIJKLMNOPQRSTUVWXYZ " + \
+ "+-/\x00[](){}\x00\x00_" + \
+ ("\x00" * 13) + "*" + "\x00\x00,'|\x00\x00\x00\x00" + \
+ ("\x00" * 64))
+
+DTMFCHARSET = list("0123456789ABCD*#")
+
+POWER_LEVELS = [chirp_common.PowerLevel("High", watts=1.50),
+ chirp_common.PowerLevel("Low", watts=0.10)]
+
+class VX2BankModel(chirp_common.BankModel):
+ """A VX-2 bank model"""
+
+ def get_num_mappings(self):
+ return len(self.get_mappings())
+
+ def get_mappings(self):
+ banks = self._radio._memobj.banks
+ bank_mappings = []
+ for index, _bank in enumerate(banks):
+ bank = chirp_common.Bank(self, "%i" % index, "b%i" % (index + 1))
+ bank.index = index
+ bank_mappings.append(bank)
+
+ return bank_mappings
+
+ def _get_channel_numbers_in_bank(self, bank):
+ _bank_used = self._radio._memobj.bank_used[bank.index]
+ if _bank_used.in_use == 0xFFFF:
+ return set()
+
+ _members = self._radio._memobj.banks[bank.index]
+ return set([int(ch) + 1 for ch in _members.channels if ch != 0xFFFF])
+
+ def _update_bank_with_channel_numbers(self, bank, channels_in_bank):
+ _members = self._radio._memobj.banks[bank.index]
+ if len(channels_in_bank) > len(_members.channels):
+ raise Exception("Too many entries in bank %d" % bank.index)
+
+ empty = 0
+ for index, channel_number in enumerate(sorted(channels_in_bank)):
+ _members.channels[index] = channel_number - 1
+ empty = index + 1
+ for index in range(empty, len(_members.channels)):
+ _members.channels[index] = 0xFFFF
+
+ def add_memory_to_mapping(self, memory, bank):
+ channels_in_bank = self._get_channel_numbers_in_bank(bank)
+ channels_in_bank.add(memory.number)
+ self._update_bank_with_channel_numbers(bank, channels_in_bank)
+
+ _bank_used = self._radio._memobj.bank_used[bank.index]
+ _bank_used.in_use = 0x0000
+
+ # also needed for unit to recognize banks?
+ self._radio._memobj.banksoff1 = 0x00
+ self._radio._memobj.banksoff2 = 0x00
+ # todo: turn back off (0xFF) when all banks are empty?
+
+ def remove_memory_from_mapping(self, memory, bank):
+ channels_in_bank = self._get_channel_numbers_in_bank(bank)
+ try:
+ channels_in_bank.remove(memory.number)
+ except KeyError:
+ raise Exception("Memory %i is not in bank %s. Cannot remove" % \
+ (memory.number, bank))
+ self._update_bank_with_channel_numbers(bank, channels_in_bank)
+
+ if not channels_in_bank:
+ _bank_used = self._radio._memobj.bank_used[bank.index]
+ _bank_used.in_use = 0xFFFF
+
+ def get_mapping_memories(self, bank):
+ memories = []
+ for channel in self._get_channel_numbers_in_bank(bank):
+ memories.append(self._radio.get_memory(channel))
+
+ return memories
+
+ def get_memory_mappings(self, memory):
+ banks = []
+ for bank in self.get_mappings():
+ if memory.number in self._get_channel_numbers_in_bank(bank):
+ banks.append(bank)
+
+ return banks
+
+
+def _wipe_memory(mem):
+ mem.set_raw("\x00" * (mem.size() / 8))
+
+ at directory.register
+class VX2Radio(yaesu_clone.YaesuCloneModeRadio):
+ """Yaesu VX-2"""
+ MODEL = "VX-2"
+ _model = "AH015"
+ BAUD_RATE = 19200
+ _block_lengths = [ 10, 8, 32577 ]
+ _memsize = 32595
+
+ def get_features(self):
+ rf = chirp_common.RadioFeatures()
+ rf.has_bank = True
+ rf.has_settings = True
+ rf.has_dtcs_polarity = False
+ rf.valid_modes = list(set(VX2_MODES))
+ rf.valid_tmodes = list(VX2_TMODES)
+ rf.valid_duplexes = list(VX2_DUPLEX)
+ rf.valid_tuning_steps = list(VX2_STEPS)
+ rf.valid_bands = [(500000, 999000000)]
+ rf.valid_skips = ["", "S", "P"]
+ rf.valid_power_levels = POWER_LEVELS
+ rf.valid_characters = "".join(CHARSET)
+ rf.valid_name_length = 6
+ rf.memory_bounds = (1, 1000)
+ rf.can_odd_split = True
+ rf.has_ctone = False
+ return rf
+
+ def _checksums(self):
+ return [ yaesu_clone.YaesuChecksum(0x0000, 0x7F51) ]
+
+ def process_mmap(self):
+ self._memobj = bitwise.parse(MEM_FORMAT, self._mmap)
+
+ def get_raw_memory(self, number):
+ return repr(self._memobj.memory[number])
+
+ def get_memory(self, number):
+ _mem = self._memobj.memory[number-1]
+ _flag = self._memobj.flags[(number-1)/2]
+
+ nibble = ((number-1) % 2) and "even" or "odd"
+ used = _flag["%s_masked" % nibble]
+ valid = _flag["%s_valid" % nibble]
+ pskip = _flag["%s_pskip" % nibble]
+ skip = _flag["%s_skip" % nibble]
+
+ mem = chirp_common.Memory()
+ mem.number = number
+
+ if not used:
+ mem.empty = True
+ if not valid:
+ mem.empty = True
+ mem.power = POWER_LEVELS[0]
+ return mem
+
+ mem.freq = chirp_common.fix_rounded_step(int(_mem.freq) * 1000)
+ mem.offset = int(_mem.offset) * 1000
+ mem.rtone = mem.ctone = chirp_common.TONES[_mem.tone]
+ mem.tmode = VX2_TMODES[_mem.tmode]
+ mem.duplex = VX2_DUPLEX[_mem.duplex]
+ if mem.duplex == "split":
+ mem.offset = chirp_common.fix_rounded_step(mem.offset)
+ if _mem.txnarrow and _mem.mode == VX2_MODES.index("FM"):
+ # narrow + FM
+ mem.mode = "NFM"
+ else:
+ mem.mode = VX2_MODES[_mem.mode]
+ mem.dtcs = chirp_common.DTCS_CODES[_mem.dcs]
+ mem.tuning_step = VX2_STEPS[_mem.tune_step]
+ mem.skip = pskip and "P" or skip and "S" or ""
+ mem.power = POWER_LEVELS[~_mem.power & 0x01]
+
+ for i in _mem.name:
+ if i == 0xFF:
+ break
+ mem.name += CHARSET[i & 0x7F]
+ mem.name = mem.name.rstrip()
+ return mem
+
+ def set_memory(self, mem):
+ _mem = self._memobj.memory[mem.number-1]
+ _flag = self._memobj.flags[(mem.number-1)/2]
+
+ nibble = ((mem.number-1) % 2) and "even" or "odd"
+
+ used = _flag["%s_masked" % nibble]
+ valid = _flag["%s_valid" % nibble]
+
+ if not mem.empty and not valid:
+ _wipe_memory(_mem)
+
+ if mem.empty and valid and not used:
+ _flag["%s_valid" % nibble] = False
+ return
+ _flag["%s_masked" % nibble] = not mem.empty
+
+ if mem.empty:
+ return
+
+ _flag["%s_valid" % nibble] = True
+
+ _mem.freq = mem.freq / 1000
+ _mem.offset = mem.offset / 1000
+ _mem.tone = chirp_common.TONES.index(mem.rtone)
+ _mem.tmode = VX2_TMODES.index(mem.tmode)
+ _mem.duplex = VX2_DUPLEX.index(mem.duplex)
+ if mem.mode == "NFM":
+ _mem.mode = VX2_MODES.index("FM")
+ _mem.txnarrow = True
+ else:
+ _mem.mode = VX2_MODES.index(mem.mode)
+ _mem.txnarrow = False
+ _mem.dcs = chirp_common.DTCS_CODES.index(mem.dtcs)
+ _mem.tune_step = VX2_STEPS.index(mem.tuning_step)
+ if mem.power == POWER_LEVELS[1]: # Low
+ _mem.power = 0x00
+ else: # Default to High
+ _mem.power = 0x03
+
+ _flag["%s_pskip" % nibble] = mem.skip == "P"
+ _flag["%s_skip" % nibble] = mem.skip == "S"
+
+ for i in range(0, 6):
+ _mem.name[i] = CHARSET.index(mem.name.ljust(6)[i])
+ if mem.name.strip():
+ # empty name field, disable name display
+ # leftmost bit of name chararr is: 1 = display freq, 0 = display name
+ _mem.name[0] |= 0x80
+
+ # for now, clear unknown fields
+ for i in range(1,7):
+ setattr(_mem, "unknown%i" % i, 0)
+
+ def validate_memory(self, mem):
+ msgs = yaesu_clone.YaesuCloneModeRadio.validate_memory(self, mem)
+ return msgs
+
+ def get_bank_model(self):
+ return VX2BankModel(self)
+
+ def _decode_chars(self, inarr):
+ if CHIRP_DEBUG:
+ print "@_decode_chars, type: %s" % type(inarr)
+ print inarr
+ outstr = ""
+ for i in inarr:
+ if i == 0xFF:
+ break
+ outstr += CHARSET[i & 0x7F]
+ return outstr.rstrip()
+
+ def _encode_chars(self, instr, length = 16):
+ if CHIRP_DEBUG:
+ print "@_encode_chars, type: %s" % type(instr)
+ print instr
+ outarr = []
+ instr = str(instr)
+ for i in range(0, length):
+ if i < len(instr):
+ outarr.append(CHARSET.index(instr[i]))
+ else:
+ outarr.append(0xFF)
+ return outarr
+
+ def get_settings(self):
+ _settings = self._memobj.settings
+ basic = RadioSettingGroup("basic", "Basic")
+ dtmf = RadioSettingGroup("dtmf", "DTMF")
+ arts = RadioSettingGroup("arts", "ARTS")
+ top = RadioSettingGroup("top", "All Settings", basic, arts, dtmf)
+
+ options = [ "off", "30m", "1h", "3h", "5h", "8h" ]
+ rs = RadioSetting("apo", "APO time (hrs)",
+ RadioSettingValueList(options,
+ options[_settings.apo]))
+ basic.append(rs)
+
+ rs = RadioSetting("ars", "Auto Repeater Shift",
+ RadioSettingValueBoolean(_settings.ars))
+ basic.append(rs)
+
+ rs = RadioSetting("att", "Attenuation",
+ RadioSettingValueBoolean(_settings.att))
+ basic.append(rs)
+
+ rs = RadioSetting("bclo", "Busy Channel Lockout",
+ RadioSettingValueBoolean(_settings.bclo))
+ basic.append(rs)
+
+ rs = RadioSetting("beep", "Beep",
+ RadioSettingValueBoolean(_settings.beep))
+ basic.append(rs)
+
+ options = [ "off", "1", "3", "5", "8", "cont"]
+ rs = RadioSetting("bell", "Bell",
+ RadioSettingValueList(options,
+ options[_settings.bell]))
+ basic.append(rs)
+
+ rs = RadioSetting("busyled", "Busy LED",
+ RadioSettingValueBoolean(_settings.busyled))
+ basic.append(rs)
+
+ options = [ "5", "10", "50", "100" ]
+ rs = RadioSetting("chcounter", "Channel Counter (MHz)",
+ RadioSettingValueList(options,
+ options[_settings.chcounter]))
+ basic.append(rs)
+
+ rs = RadioSetting("dcsrev", "DCS Reverse",
+ RadioSettingValueBoolean(_settings.dcsrev))
+ basic.append(rs)
+
+ options = map(str, range(0,12+1))
+ rs = RadioSetting("dimmer", "Dimmer",
+ RadioSettingValueList(options, options[_settings.dimmer]))
+ basic.append(rs)
+
+ rs = RadioSetting("edgebeep", "Edge Beep",
+ RadioSettingValueBoolean(_settings.edgebeep))
+ basic.append(rs)
+
+ options = [ "beep", "strobe", "bp+str", "beam", "bp+beam", "cw", "bp+cw" ]
+ rs = RadioSetting("emergmode", "Emergency Mode",
+ RadioSettingValueList(options,
+ options[_settings.emergmode]))
+ basic.append(rs)
+
+ options = ["Home", "Reverse"]
+ rs = RadioSetting("hmrv", "HM/RV key",
+ RadioSettingValueList(options, options[_settings.hmrv]))
+ basic.append(rs)
+
+ options = ["My Menu", "Internet"]
+ rs = RadioSetting("internet_key", "Internet key",
+ RadioSettingValueList(options, options[_settings.internet_key]))
+ basic.append(rs)
+
+ options = ["1 APO","2 AR BEP","3 AR INT","4 ARS","5 ATT","6 BCLO","7 BEEP","8 BELL",
+ "9 BSYLED","10 CH CNT","11 CK SFT","12 CW ID","13 DC VLT","14 DCS CD","15 DCS RV",
+ "16 DIMMER","17 DTMF","18 DTMF S","19 EDG BP","20 EMG S","21 HLFDEV","22 HM/RV",
+ "23 INT MD","24 LAMP","25 LOCK","26 M/T-CL","27 MW MD","28 NAME","29 NM SET",
+ "30 OPNMSG","31 RESUME","32 RF SQL","33 RPT","34 RX MD","35 RXSAVE","36 S SCH",
+ "37 SCNLMP","38 SHIFT","39 SKIP","40 SPLIT","41 SQL","42 SQL TYP","43 STEP",
+ "44 TN FRQ","45 TOT","46 TXSAVE","47 VFO MD", "48 TR SQL (JAPAN)", "48 WX ALT"]
+
+ rs = RadioSetting("mymenu", "My Menu function",
+ RadioSettingValueList(options, options[_settings.mymenu - 9]))
+ basic.append(rs)
+
+ options = ["wires", "link"]
+ rs = RadioSetting("internet_mode", "Internet mode",
+ RadioSettingValueList(options, options[_settings.internet_mode]))
+ basic.append(rs)
+
+ options = ["key", "cont", "off"]
+ rs = RadioSetting("lamp", "Lamp mode",
+ RadioSettingValueList(options, options[_settings.lamp]))
+ basic.append(rs)
+
+ options = ["key", "dial", "key+dial", "ptt", "key+ptt", "dial+ptt", "all"]
+ rs = RadioSetting("lock", "Lock mode",
+ RadioSettingValueList(options, options[_settings.lock]))
+ basic.append(rs)
+
+ options = ["monitor", "tone call"]
+ rs = RadioSetting("moni_tcall", "MONI key",
+ RadioSettingValueList(options, options[_settings.moni_tcall]))
+ basic.append(rs)
+
+ options = ["lower", "next"]
+ rs = RadioSetting("mwmode", "Memory write mode",
+ RadioSettingValueList(options, options[_settings.mwmode]))
+ basic.append(rs)
+
+ options = map(str, range(0,15+1))
+ rs = RadioSetting("nfm_sql", "NFM Sql",
+ RadioSettingValueList(options, options[_settings.nfm_sql]))
+ basic.append(rs)
+
+ options = map(str, range(0,8+1))
+ rs = RadioSetting("wfm_sql", "WFM Sql",
+ RadioSettingValueList(options, options[_settings.wfm_sql]))
+ basic.append(rs)
+
+ options = ["off", "dc", "msg"]
+ rs = RadioSetting("openmsgmode", "Opening message",
+ RadioSettingValueList(options, options[_settings.openmsgmode]))
+ basic.append(rs)
+
+ openmsg = RadioSettingValueString(0, 6, self._decode_chars(_settings.openmsg.get_value()))
+ openmsg.set_charset(CHARSET)
+ rs = RadioSetting("openmsg", "Opening Message", openmsg)
+ basic.append(rs)
+
+ options = ["3s","5s","10s","busy","hold"]
+ rs = RadioSetting("resume", "Resume",
+ RadioSettingValueList(options, options[_settings.resume]))
+ basic.append(rs)
+
+ options = ["off"] + map(str,range(1,9+1))
+ rs = RadioSetting("rfsql", "RF Sql",
+ RadioSettingValueList(options,options[_settings.rfsql]))
+ basic.append(rs)
+
+ options = ["off","200ms","300ms","500ms","1s","2s"]
+ rs = RadioSetting("rxsave", "RX pwr save",
+ RadioSettingValueList(options, options[_settings.rxsave]))
+ basic.append(rs)
+
+ options = ["single","cont"]
+ rs = RadioSetting("smartsearch", "Smart search",
+ RadioSettingValueList(options, options[_settings.smartsearch]))
+ basic.append(rs)
+
+ rs = RadioSetting("scan_lamp", "Scan lamp",
+ RadioSettingValueBoolean(_settings.scan_lamp))
+ basic.append(rs)
+
+ rs = RadioSetting("split", "Split",
+ RadioSettingValueBoolean(_settings.split))
+ basic.append(rs)
+
+ options = ["off","1","3","5","10"]
+ rs = RadioSetting("tot", "TOT (mins)",
+ RadioSettingValueList(options, options[_settings.tot]))
+ basic.append(rs)
+
+ rs = RadioSetting("txsave", "TX pwr save",
+ RadioSettingValueBoolean(_settings.txsave))
+ basic.append(rs)
+
+ options = ["all", "band"]
+ rs = RadioSetting("vfomode", "VFO mode",
+ RadioSettingValueList(options, options[_settings.vfomode]))
+ basic.append(rs)
+
+ rs = RadioSetting("wx_alert", "WX Alert",
+ RadioSettingValueBoolean(_settings.wx_alert))
+ basic.append(rs)
+
+ #todo: priority channel
+
+ #todo: handle WX ch labels
+
+ # arts settings (ar beep, ar int, cwid en, cwid field)
+ options = ["15s", "25s"]
+ rs = RadioSetting("artsinterval", "ARTS Interval",
+ RadioSettingValueList(options, options[_settings.artsinterval]))
+ arts.append(rs)
+
+ options = ["off", "in range", "always"]
+ rs = RadioSetting("artsbeep", "ARTS Beep",
+ RadioSettingValueList(options, options[_settings.artsbeep]))
+ arts.append(rs)
+
+ rs = RadioSetting("cwid_en", "CWID Enable",
+ RadioSettingValueBoolean(_settings.cwid_en))
+ arts.append(rs)
+
+ cwid = RadioSettingValueString(0, 16, self._decode_chars(_settings.cwid.get_value()))
+ cwid.set_charset(CHARSET)
+ rs = RadioSetting("cwid", "CWID", cwid)
+ arts.append(rs)
+
+ # setup dtmf
+ options = ["manual", "auto"]
+ rs = RadioSetting("dtmfmode", "DTMF mode",
+ RadioSettingValueList(options, options[_settings.dtmfmode]))
+ dtmf.append(rs)
+
+ for i in range(0,8+1):
+ name = "dtmf" + str(i+1)
+ dtmfsetting = self._memobj.dtmf[i]
+ #dtmflen = getattr(_settings, objname + "_len")
+ dtmfstr = ""
+ for c in dtmfsetting.digits:
+ if c < len(DTMFCHARSET):
+ dtmfstr += DTMFCHARSET[c]
+ if CHIRP_DEBUG:
+ print dtmfstr
+ dtmfentry = RadioSettingValueString(0, 16, dtmfstr)
+ dtmfentry.set_charset(DTMFCHARSET + list(" "))
+ rs = RadioSetting(name, name.upper(), dtmfentry)
+ dtmf.append(rs)
+
+ return top
+
+ def set_settings(self, uisettings):
+ for element in uisettings:
+ if not isinstance(element, RadioSetting):
+ self.set_settings(element)
+ continue
+ if not element.changed():
+ continue
+ try:
+ setting = element.get_name()
+ _settings = self._memobj.settings
+ if re.match('dtmf\d', setting):
+ # set dtmf fields
+ dtmfstr = str(element.value).strip()
+ newval = []
+ for i in range(0,16):
+ if i < len(dtmfstr):
+ newval.append(DTMFCHARSET.index(dtmfstr[i]))
+ else:
+ newval.append(0xFF)
+ if CHIRP_DEBUG:
+ print newval
+ idx = int(setting[-1:]) - 1
+ _settings = self._memobj.dtmf[idx]
+ _settings.digits = newval
+ continue
+ if setting == "prioritychan":
+ # prioritychan is top-level member, fix 0 index
+ element.value -= 1
+ _settings = self._memobj
+ if setting == "mymenu":
+ opts = element.value.get_options()
+ optsidx = opts.index(element.value.get_value())
+ idx = optsidx + 9
+ setattr(_settings, "mymenu", idx)
+ continue
+ oldval = getattr(_settings, setting)
+ newval = element.value
+ if setting == "cwid":
+ newval = self._encode_chars(newval)
+ if setting == "openmsg":
+ newval = self._encode_chars(newval, 6)
+ if CHIRP_DEBUG:
+ print "Setting %s(%s) <= %s" % (setting,
+ oldval, newval)
+ setattr(_settings, setting, newval)
+ except Exception, e:
+ print element.get_name()
+ raise
+
+
\ No newline at end of file
diff --git a/chirp/vx3.py b/chirp/vx3.py
index a06c910..fddbce0 100644
--- a/chirp/vx3.py
+++ b/chirp/vx3.py
@@ -16,6 +16,11 @@
from chirp import chirp_common, yaesu_clone, directory
from chirp import bitwise
+from chirp.settings import RadioSetting, RadioSettingGroup, \
+ RadioSettingValueInteger, RadioSettingValueList, \
+ RadioSettingValueBoolean, RadioSettingValueString
+from textwrap import dedent
+import os, re
#interesting offsets which may be checksums needed later
#0x0393 checksum1?
@@ -23,37 +28,175 @@ from chirp import bitwise
#0x0409 checksum2?
#0x04C9 checksum2a?
+if os.getenv("CHIRP_DEBUG"):
+ CHIRP_DEBUG = True
+else:
+ CHIRP_DEBUG = False
+
MEM_FORMAT = """
#seekto 0x7F4A;
u8 checksum;
+#seekto 0x024A;
+struct {
+ u8 unk01_1:3,
+ att_broadcast:1,
+ att_marine:1,
+ unk01_2:2
+ att_wx:1;
+ u8 unk02;
+ u8 apo;
+ u8 arts_beep;
+ u8 unk04_1;
+ u8 beep_level;
+ u8 beep_mode;
+ u8 unk04_2;
+ u8 arts_cwid[16];
+ u8 unk05[10];
+ u8 channel_counter;
+ u8 unk06_1[2];
+ u8 dtmf_delay;
+ u8 dtmf_chan_active;
+ u8 unk06_2[5];
+ u8 emergency_eai_time;
+ u8 emergency_signal;
+ u8 unk07[30];
+ u8 fw_key_timer;
+ u8 internet_key;
+ u8 lamp;
+ u8 lock_mode;
+ u8 my_key;
+ u8 mic_gain;
+ u8 mem_ch_step;
+ u8 unk08[3];
+ u8 sql_fm;
+ u8 sql_wfm;
+ u8 radio_am_sql;
+ u8 radio_fm_sql;
+ u8 on_timer;
+ u8 openmsg_mode;
+ u8 openmsg[6];
+ u8 pager_rxtone1;
+ u8 pager_rxtone2;
+ u8 pager_txtone1;
+ u8 pager_txtone2;
+ u8 password[4];
+ u8 unk10;
+ u8 priority_time;
+ u8 ptt_delay;
+ u8 rx_save;
+ u8 scan_resume;
+ u8 scan_restart;
+ u8 sub_rx_timer;
+ u8 unk11[7];
+ u8 tot;
+ u8 wake_up;
+ u8 unk12[2];
+ u8 vfo_mode:1,
+ arts_cwid_enable:1,
+ scan_lamp:1,
+ fast_tone_search:1,
+ ars:1,
+ dtmf_speed:1,
+ split_tone:1,
+ dtmf_autodialer:1;
+ u8 busy_led:1,
+ tone_search_mute:1,
+ unk14_1:1,
+ bclo:1,
+ band_edge_beep:1,
+ unk14_2:2,
+ txsave:1;
+ u8 unk15_1:2,
+ smart_search:1,
+ emergency_eai:1,
+ unk15_2:2,
+ hm_rv:1,
+ moni_tcall:1;
+ u8 lock:1,
+ unk16_1:1,
+ arts:1,
+ arts_interval:1,
+ unk16_2:1,
+ protect_memory:1,
+ unk16_3:1,
+ mem_storage:1;
+ u8 vol_key_mode:1,
+ unk17_1:2,
+ wx_alert:1,
+ temp_unit:1,
+ unk17_2:2,
+ password_active:1;
+ u8 fm_broadcast_mode:1,
+ fm_antenna:1,
+ am_antenna:1,
+ fm_speaker_out:1,
+ home_vfo:1,
+ unk18_1:2,
+ priority_revert:1;
+} settings;
+
+// banks?
+#seekto 0x034D;
+u8 banks_unk1;
+
+#seekto 0x0356;
+u32 banks_unk2;
+
+#seekto 0x0409;
+u8 banks_unk3;
+
+#seekto 0x04CA;
+struct {
+ u8 memory[16];
+} dtmf[10];
+
#seekto 0x0B7A;
struct {
u8 name[6];
} bank_names[24];
+#seekto 0x0E0A;
+struct {
+ u16 channels[100];
+} banks[24];
+
+#seekto 0x02EE;
+struct {
+ u16 in_use;
+} bank_used[24];
+
+#seekto 0x03FE;
+struct {
+ u8 speaker;
+ u8 earphone;
+} volumes;
+
#seekto 0x20CA;
struct {
u8 even_pskip:1,
even_skip:1,
- even_valid:1,
+ even_valid:1, // TODO: should be "showname", i.e., show alpha name
even_masked:1,
odd_pskip:1,
odd_skip:1,
odd_valid:1,
odd_masked:1;
-} flags[900];
+} flags[999];
#seekto 0x244A;
struct {
- u8 unknown1;
+ u8 unknown1a:2,
+ txnarrow:1,
+ clockshift:1,
+ unknown1b:4;
u8 mode:2,
duplex:2,
tune_step:4;
bbcd freq[3];
u8 power:2,
unknown2:4,
- tmode:2;
+ tmode:2; // TODO: tmode should be 6 bits (extended tone modes)
u8 name[6];
bbcd offset[3];
u8 unknown3:2,
@@ -61,18 +204,23 @@ struct {
u8 unknown4:1,
dcs:7;
u8 unknown5;
- u8 unknown6;
- u8 unknown7:4,
+ u8 smetersquelch;
+ u8 unknown7a:2,
+ attenuate:1,
+ unknown7b:1,
automode:1,
- unknown8:3;
-} memory[900];
+ unknown8:1,
+ bell:2;
+} memory[999];
"""
#fix auto mode setting and auto step setting
DUPLEX = ["", "-", "+", "split"]
-MODES = ["FM", "AM", "WFM", "FM"] # last is auto
+MODES = ["FM", "AM", "WFM", "Auto", "NFM"] # NFM handled specially in radio
TMODES = ["", "Tone", "TSQL", "DTCS"]
+## TODO: TMODES = ["", "Tone, "TSQL", "DTCS", "Rev Tone", "User Tone", "Pager",
+## "Message", "D Code", "Tone/DTCS", "DTCS/Tone"]
#still need to verify 9 is correct, and add auto: look at byte 1 and 20
STEPS = [ 5.0, 9, 10.0, 12.5, 15.0, 20.0, 25.0, 50.0, 100.0 ]
@@ -89,6 +237,7 @@ CHARSET = list("0123456789" + \
("\x00" * 13) + "*" + "\x00\x00,'|\x00\x00\x00\x00" + \
("\x00" * 64))
+DTMFCHARSET = list("0123456789ABCD*#")
POWER_LEVELS = [chirp_common.PowerLevel("High", watts=1.50),
chirp_common.PowerLevel("Low", watts=0.10)]
@@ -101,7 +250,7 @@ class VX3Bank(chirp_common.NamedBank):
if i == 0xFF:
break
name += CHARSET[i & 0x7F]
- return name
+ return name.rstrip()
def set_name(self, name):
name = name.upper()
@@ -110,17 +259,73 @@ class VX3Bank(chirp_common.NamedBank):
class VX3BankModel(chirp_common.BankModel):
"""A VX-3 bank model"""
- def get_num_banks(self):
- return 24
-
- def get_banks(self):
- _banks = self._radio._memobj.bank_names
+ def get_num_mappings(self):
+ return len(self.get_mappings())
+
+ def get_mappings(self):
+ banks = self._radio._memobj.banks
+ bank_mappings = []
+ for index, _bank in enumerate(banks):
+ bank = VX3Bank(self, "%i" % index, "b%i" % (index + 1))
+ bank.index = index
+ bank_mappings.append(bank)
+
+ return bank_mappings
+
+ def _get_channel_numbers_in_bank(self, bank):
+ _bank_used = self._radio._memobj.bank_used[bank.index]
+ if _bank_used.in_use == 0xFFFF:
+ return set()
+
+ _members = self._radio._memobj.banks[bank.index]
+ return set([int(ch) + 1 for ch in _members.channels if ch != 0xFFFF])
+
+ def _update_bank_with_channel_numbers(self, bank, channels_in_bank):
+ _members = self._radio._memobj.banks[bank.index]
+ if len(channels_in_bank) > len(_members.channels):
+ raise Exception("Too many entries in bank %d" % bank.index)
+
+ empty = 0
+ for index, channel_number in enumerate(sorted(channels_in_bank)):
+ _members.channels[index] = channel_number - 1
+ empty = index + 1
+ for index in range(empty, len(_members.channels)):
+ _members.channels[index] = 0xFFFF
+
+ def add_memory_to_mapping(self, memory, bank):
+ channels_in_bank = self._get_channel_numbers_in_bank(bank)
+ channels_in_bank.add(memory.number)
+ self._update_bank_with_channel_numbers(bank, channels_in_bank)
+ _bank_used = self._radio._memobj.bank_used[bank.index]
+ _bank_used.in_use = 0x0000
+
+ def remove_memory_from_mapping(self, memory, bank):
+ channels_in_bank = self._get_channel_numbers_in_bank(bank)
+ try:
+ channels_in_bank.remove(memory.number)
+ except KeyError:
+ raise Exception("Memory %i is not in bank %s. Cannot remove" % \
+ (memory.number, bank))
+ self._update_bank_with_channel_numbers(bank, channels_in_bank)
+
+ if not channels_in_bank:
+ _bank_used = self._radio._memobj.bank_used[bank.index]
+ _bank_used.in_use = 0xFFFF
+
+ def get_mapping_memories(self, bank):
+ memories = []
+ for channel in self._get_channel_numbers_in_bank(bank):
+ memories.append(self._radio.get_memory(channel))
+
+ return memories
+
+ def get_memory_mappings(self, memory):
banks = []
- for i in range(0, self.get_num_banks()):
- bank = VX3Bank(self, "%i" % i, "Bank-%i" % i)
- bank.index = i
- banks.append(bank)
+ for bank in self.get_mappings():
+ if memory.number in self._get_channel_numbers_in_bank(bank):
+ banks.append(bank)
+
return banks
def _wipe_memory(mem):
@@ -129,7 +334,8 @@ def _wipe_memory(mem):
#on the radio, some of these fields are unknown
mem.name = [0xFF for _i in range(0, 6)]
mem.unknown5 = 0x0D #not sure what this is
- mem.unknown7 = 0x01 #this likely is part of autostep
+ mem.unknown7a = 0b0
+ mem.unknown7b = 0b1
mem.automode = 0x01 #autoselect mode
@directory.register
@@ -143,10 +349,26 @@ class VX3Radio(yaesu_clone.YaesuCloneModeRadio):
_model = "AH028"
_memsize = 32587
_block_lengths = [ 10, 32577 ]
- #right now this reads in 45 seconds and writes in 123 seconds
- #attempts to speed it up appear unstable, more testing required
- _block_size = 8
-
+ #right now this reads in 45 seconds and writes in 41 seconds
+ _block_size = 32
+
+ @classmethod
+ def get_prompts(cls):
+ rp = chirp_common.RadioPrompts()
+ rp.pre_download = _(dedent("""\
+ 1. Turn radio off.
+ 2. Connect cable to MIC/SP jack.
+ 3. Press and hold in the [F/W] key while turning the radio on
+ ("CLONE" will appear on the display).
+ 4. <b>After clicking OK</b>, press the [BAND] key to send image."""))
+ rp.pre_upload = _(dedent("""\
+ 1. Turn radio off.
+ 2. Connect cable to MIC/SP jack.
+ 3. Press and hold in the [F/W] key while turning the radio on
+ ("CLONE" will appear on the display).
+ 4. Press the [V/M] key ("-WAIT-" will appear on the LCD)."""))
+ return rp
+
def _checksums(self):
return [ yaesu_clone.YaesuChecksum(0x0000, 0x7F49) ]
@@ -155,7 +377,7 @@ class VX3Radio(yaesu_clone.YaesuCloneModeRadio):
def get_features(self):
rf = chirp_common.RadioFeatures()
- rf.has_bank = False
+ rf.has_bank = True
rf.has_bank_names = True
rf.has_dtcs_polarity = False
rf.valid_modes = list(set(MODES))
@@ -167,9 +389,10 @@ class VX3Radio(yaesu_clone.YaesuCloneModeRadio):
rf.valid_power_levels = POWER_LEVELS
rf.valid_characters = "".join(CHARSET)
rf.valid_name_length = 6
- rf.memory_bounds = (1, 900)
+ rf.memory_bounds = (1, 999)
rf.can_odd_split = True
rf.has_ctone = False
+ rf.has_settings = True
return rf
def get_raw_memory(self, number):
@@ -202,7 +425,11 @@ class VX3Radio(yaesu_clone.YaesuCloneModeRadio):
mem.duplex = DUPLEX[_mem.duplex]
if mem.duplex == "split":
mem.offset = chirp_common.fix_rounded_step(mem.offset)
- mem.mode = MODES[_mem.mode]
+ if _mem.txnarrow and _mem.mode == MODES.index("FM"):
+ # FM narrow
+ mem.mode = "NFM"
+ else:
+ mem.mode = MODES[_mem.mode]
mem.dtcs = chirp_common.DTCS_CODES[_mem.dcs]
mem.tuning_step = STEPS[_mem.tune_step]
mem.skip = pskip and "P" or skip and "S" or ""
@@ -235,12 +462,19 @@ class VX3Radio(yaesu_clone.YaesuCloneModeRadio):
if mem.empty:
return
+ _flag["%s_valid" % nibble] = True
+
_mem.freq = mem.freq / 1000
_mem.offset = mem.offset / 1000
_mem.tone = chirp_common.TONES.index(mem.rtone)
_mem.tmode = TMODES.index(mem.tmode)
_mem.duplex = DUPLEX.index(mem.duplex)
- _mem.mode = MODES.index(mem.mode)
+ if mem.mode == "NFM":
+ _mem.mode = MODES.index("FM")
+ _mem.txnarrow = True
+ else:
+ _mem.mode = MODES.index(mem.mode)
+ _mem.txnarrow = False
_mem.dcs = chirp_common.DTCS_CODES.index(mem.dtcs)
_mem.tune_step = STEPS.index(mem.tuning_step)
if mem.power == POWER_LEVELS[1]: # Low
@@ -262,3 +496,399 @@ class VX3Radio(yaesu_clone.YaesuCloneModeRadio):
def get_bank_model(self):
return VX3BankModel(self)
+
+ def _decode_chars(self, inarr):
+ if CHIRP_DEBUG:
+ print "@_decode_chars, type: %s" % type(inarr)
+ print inarr
+ outstr = ""
+ for i in inarr:
+ if i == 0xFF:
+ break
+ outstr += CHARSET[i & 0x7F]
+ return outstr.rstrip()
+
+ def _encode_chars(self, instr, length = 16):
+ if CHIRP_DEBUG:
+ print "@_encode_chars, type: %s" % type(instr)
+ print instr
+ outarr = []
+ instr = str(instr)
+ for i in range(length):
+ if i < len(instr):
+ outarr.append(CHARSET.index(instr[i]))
+ else:
+ outarr.append(0xFF)
+ return outarr
+
+ def get_settings(self):
+ _settings = self._memobj.settings
+ basic = RadioSettingGroup("basic", "Basic")
+ sound = RadioSettingGroup("sound", "Sound")
+ dtmf = RadioSettingGroup("dtmf", "DTMF")
+ arts = RadioSettingGroup("arts", "ARTS")
+ eai = RadioSettingGroup("eai", "Emergency")
+ msg = RadioSettingGroup("msg", "Messages")
+ top = RadioSettingGroup("top", "All Settings",
+ basic, sound, arts, dtmf, eai, msg)
+
+ basic.append( RadioSetting("att_wx", "Attenuation WX",
+ RadioSettingValueBoolean(_settings.att_wx)))
+
+ basic.append( RadioSetting("att_marine", "Attenuation Marine",
+ RadioSettingValueBoolean(_settings.att_marine)))
+
+ basic.append( RadioSetting("att_broadcast", "Attenuation Broadcast",
+ RadioSettingValueBoolean(_settings.att_broadcast)))
+
+ basic.append( RadioSetting("ars", "Auto Repeater Shift",
+ RadioSettingValueBoolean(_settings.ars)))
+
+ basic.append( RadioSetting("home_vfo", "Home->VFO",
+ RadioSettingValueBoolean(_settings.home_vfo)))
+
+ basic.append( RadioSetting("bclo", "Busy Channel Lockout",
+ RadioSettingValueBoolean(_settings.bclo)))
+
+ basic.append( RadioSetting("busyled", "Busy LED",
+ RadioSettingValueBoolean(_settings.busy_led)))
+
+ basic.append( RadioSetting("fast_tone_search", "Fast Tone search",
+ RadioSettingValueBoolean(_settings.fast_tone_search)))
+
+ basic.append( RadioSetting("priority_revert", "Priority Revert",
+ RadioSettingValueBoolean(_settings.priority_revert)))
+
+ basic.append( RadioSetting("protect_memory", "Protect memory",
+ RadioSettingValueBoolean(_settings.protect_memory)))
+
+ basic.append( RadioSetting("scan_lamp", "Scan Lamp",
+ RadioSettingValueBoolean(_settings.scan_lamp)))
+
+ basic.append( RadioSetting("split_tone", "Split tone",
+ RadioSettingValueBoolean(_settings.split_tone)))
+
+ basic.append( RadioSetting("tone_search_mute", "Tone search mute",
+ RadioSettingValueBoolean(_settings.tone_search_mute)))
+
+ basic.append( RadioSetting("txsave", "TX save",
+ RadioSettingValueBoolean(_settings.txsave)))
+
+ basic.append( RadioSetting("wx_alert", "WX Alert",
+ RadioSettingValueBoolean(_settings.wx_alert)))
+
+ opts = ["Bar Int", "Bar Ext"]
+ basic.append( RadioSetting("am_antenna", "AM antenna",
+ RadioSettingValueList(opts, opts[_settings.am_antenna])))
+
+ opts = ["Ext Ant", "Earphone"]
+ basic.append( RadioSetting("fm_antenna", "FM antenna",
+ RadioSettingValueList(opts, opts[_settings.fm_antenna])))
+
+ opts = ["off"] + [ "%0.1f" % (t / 60.0) for t in range(30, 750, 30) ]
+ basic.append( RadioSetting("apo", "APO time (hrs)",
+ RadioSettingValueList(opts, opts[_settings.apo])))
+
+ opts = ["+/- 5 MHZ", "+/- 10 MHZ", "+/- 50 MHZ", "+/- 100 MHZ"]
+ basic.append( RadioSetting("channel_counter", "Channel counter",
+ RadioSettingValueList(opts, opts[_settings.channel_counter])))
+
+ opts = ["0.3", "0.5", "0.7", "1.0", "1.5"]
+ basic.append( RadioSetting("fw_key_timer", "FW key timer (s)",
+ RadioSettingValueList(opts, opts[_settings.fw_key_timer])))
+
+ opts = ["Home", "Reverse"]
+ basic.append( RadioSetting("hm_rv", "HM/RV key",
+ RadioSettingValueList(opts, opts[_settings.hm_rv])))
+
+ opts = [ "%d" % t for t in range(2, 11) ] + ["continuous", "off"]
+ basic.append( RadioSetting("lamp", "Lamp Timer (s)",
+ RadioSettingValueList(opts, opts[_settings.lamp])))
+
+ basic.append( RadioSetting("lock", "Lock",
+ RadioSettingValueBoolean(_settings.lock)))
+
+ opts = ["key", "ptt", "key+ptt"]
+ basic.append( RadioSetting("lock_mode", "Lock mode",
+ RadioSettingValueList(opts, opts[_settings.lock_mode])))
+
+ opts = ["10", "20", "50", "100"]
+ basic.append( RadioSetting("mem_ch_step", "Memory Chan step",
+ RadioSettingValueList(opts, opts[_settings.mem_ch_step])))
+
+ opts = ["lower", "next"]
+ basic.append( RadioSetting("mem_storage", "Memory storage mode",
+ RadioSettingValueList(opts, opts[_settings.mem_storage])))
+
+ opts = [ "%d" % t for t in range(1, 10) ]
+ basic.append( RadioSetting("mic_gain", "Mic gain",
+ RadioSettingValueList(opts, opts[_settings.mic_gain])))
+
+ opts = ["monitor", "tone call"]
+ basic.append( RadioSetting("moni_tcall", "Moni/TCall button",
+ RadioSettingValueList(opts, opts[_settings.moni_tcall])))
+
+ opts = ["off"] + \
+ [ "%02d:%02d" % (t / 60, t % 60) for t in range(10, 1450, 10) ]
+ basic.append( RadioSetting("on_timer", "On Timer (hrs)",
+ RadioSettingValueList(opts, opts[_settings.on_timer])))
+
+ opts2 = ["off"] + \
+ [ "0.%d" % t for t in range(1, 10) ] + \
+ [ "%1.1f" % (t / 10.0) for t in range(10, 105, 5) ]
+ basic.append( RadioSetting("priority_time", "Priority time",
+ RadioSettingValueList(opts2, opts2[_settings.priority_time])))
+
+ opts = ["off", "20", "50", "100", "200"]
+ basic.append( RadioSetting("ptt_delay", "PTT delay (ms)",
+ RadioSettingValueList(opts, opts[_settings.ptt_delay])))
+
+ basic.append( RadioSetting("rx_save", "RX save (s)",
+ RadioSettingValueList(opts2, opts2[_settings.rx_save])))
+
+ basic.append( RadioSetting("scan_restart", "Scan restart (s)",
+ RadioSettingValueList(opts2, opts2[_settings.scan_restart])))
+
+ opts = [ "%1.1f" % (t / 10.0) for t in range(20, 105, 5) ] + \
+ ["busy", "hold"]
+ basic.append( RadioSetting("scan_resume", "Scan resume (s)",
+ RadioSettingValueList(opts, opts[_settings.scan_resume])))
+
+ opts = ["single", "continuous"]
+ basic.append( RadioSetting("smart_search", "Smart search",
+ RadioSettingValueList(opts, opts[_settings.smart_search])))
+
+ opts = ["off"] + [ "TRX %d" % t for t in range(1, 11)] + ["hold"] + \
+ [ "TX %d" % t for t in range(1, 11)]
+ basic.append( RadioSetting("sub_rx_timer", "Sub RX timer",
+ RadioSettingValueList(opts, opts[_settings.sub_rx_timer])))
+
+ opts = ["C", "F"]
+ basic.append( RadioSetting("temp_unit", "Temperature unit",
+ RadioSettingValueList(opts, opts[_settings.temp_unit])))
+
+ opts = ["off"] + [ "%1.1f" % (t / 10.0) for t in range(5, 105, 5) ]
+ basic.append( RadioSetting("tot", "Time-out timer (mins)",
+ RadioSettingValueList(opts, opts[_settings.tot])))
+
+ opts = ["all", "band"]
+ basic.append( RadioSetting("vfo_mode", "VFO mode",
+ RadioSettingValueList(opts, opts[_settings.vfo_mode])))
+
+ opts = ["off"] + [ "%d" % t for t in range(5, 65, 5) ] + ["EAI"]
+ basic.append( RadioSetting("wake_up", "Wake up (s)",
+ RadioSettingValueList(opts, opts[_settings.wake_up])))
+
+ opts = ["hold", "3 secs"]
+ basic.append( RadioSetting("vol_key_mode", "Volume key mode",
+ RadioSettingValueList(opts, opts[_settings.vol_key_mode])))
+
+ # subgroup programmable keys
+
+ opts = ["INTNET", "INT MR", "Set Mode (my key)"]
+ basic.append( RadioSetting("internet_key", "Internet key",
+ RadioSettingValueList(opts, opts[_settings.internet_key])))
+
+ keys = [ "Antenna AM", "Antenna FM", "Antenna Attenuator", "Auto Power Off",
+ "Auto Repeater Shift", "ARTS Beep", "ARTS Interval", "Busy Channel Lockout",
+ "Bell Ringer", "Bell Select", "Bank Name", "Band Edge Beep", "Beep Level",
+ "Beep Select", "Beep User", "Busy LED", "Channel Counter", "Clock Shift",
+ "CW ID", "CW Learning", "CW Pitch", "CW Training", "DC Voltage",
+ "DCS Code", "DCS Reverse", "DTMF A/M", "DTMF Delay", "DTMF Set",
+ "DTMF Speed", "EAI Timer", "Emergency Alarm", "Ext Menu", "FW Key",
+ "Half Deviation", "Home/Reverse", "Home > VFO", "INT Code",
+ "INT Conn Mode", "INT A/M", "INT Set", "INT Key", "INTNET", "Lamp",
+ "LED Light", "Lock", "Moni/T-Call", "Mic Gain", "Memory Display",
+ "Memory Write Mode", "Memory Channel Step", "Memory Name Write",
+ "Memory Protect", "Memory Skip", "Message List", "Message Reg",
+ "Message Set", "On Timer", "Open Message", "Pager Answer Back",
+ "Pager Receive Code", "Pager Transmit Code", "Pager Frequency",
+ "Priority Revert", "Priority Timer", "Password", "PTT Delay",
+ "Repeater Shift Direction", "Repeater Shift", "Receive Mode",
+ "Smart Search", "Save Rx", "Save Tx", "Scan Lamp", "Scan Resume",
+ "Scan Restart", "Speaker Out", "Squelch Level", "Squelch Type",
+ "Squelch S Meter", "Squelch Split Tone", "Step", "Stereo", "Sub Rx",
+ "Temp", "Tone Frequency", "Time Out Timer", "Tone Search Mute",
+ "Tone Search Speed", "VFO Band", "VFO Skip", "Volume Mode", "Wake Up",
+ "Weather Alert" ]
+ rs = RadioSetting("my_key", "My key",
+ RadioSettingValueList(keys, keys[_settings.my_key - 16]))
+ # TODO: fix keys list isnt exactly right order
+ # leave disabled in settings for now
+ # basic.append(rs)
+
+ # sound tab
+
+ sound.append( RadioSetting("band_edge_beep", "Band edge beep",
+ RadioSettingValueBoolean(_settings.band_edge_beep)))
+
+ opts = ["off", "key+scan", "key"]
+ sound.append( RadioSetting("beep_mode", "Beep mode",
+ RadioSettingValueList(opts, opts[_settings.beep_mode])))
+
+ _volumes = self._memobj.volumes
+
+ opts = map(str, range(0,33))
+ sound.append( RadioSetting("speaker_vol", "Speaker volume",
+ RadioSettingValueList(opts, opts[_volumes.speaker])))
+
+ sound.append( RadioSetting("earphone_vol", "Earphone volume",
+ RadioSettingValueList(opts, opts[_volumes.earphone])))
+
+ opts = ["auto", "speaker"]
+ sound.append( RadioSetting("fm_speaker_out", "FM Speaker out",
+ RadioSettingValueList(opts, opts[_settings.fm_speaker_out])))
+
+ opts = ["mono", "stereo"]
+ sound.append( RadioSetting("fm_broadcast_mode", "FM broadcast mode",
+ RadioSettingValueList(opts, opts[_settings.fm_broadcast_mode])))
+
+ opts = map(str, range(16))
+ sound.append( RadioSetting("sql_fm", "Squelch level (FM)",
+ RadioSettingValueList(opts, opts[_settings.sql_fm])))
+
+ opts = map(str, range(9))
+ sound.append( RadioSetting("sql_wfm", "Squelch level (WFM)",
+ RadioSettingValueList(opts, opts[_settings.sql_wfm])))
+
+ opts = map(str, range(16))
+ sound.append( RadioSetting("radio_am_sql", "Squelch level (Broadcast Radio AM)",
+ RadioSettingValueList(opts, opts[_settings.radio_am_sql])))
+
+ opts = map(str, range(9))
+ sound.append( RadioSetting("radio_fm_sql", "Squelch level (Broadcast Radio FM)",
+ RadioSettingValueList(opts, opts[_settings.radio_fm_sql])))
+
+ # dtmf tab
+
+ opts = ["manual", "auto"]
+ dtmf.append( RadioSetting("dtmf_autodialer", "DTMF autodialer mode",
+ RadioSettingValueList(opts, opts[_settings.dtmf_autodialer])))
+
+ opts = ["50", "250", "450", "750", "1000"]
+ dtmf.append( RadioSetting("dtmf_delay", "DTMF delay (ms)",
+ RadioSettingValueList(opts, opts[_settings.dtmf_delay])))
+
+ opts = ["50", "100"]
+ dtmf.append( RadioSetting("dtmf_speed", "DTMF speed (ms)",
+ RadioSettingValueList(opts, opts[_settings.dtmf_speed])))
+
+ opts = map(str, range(10))
+ dtmf.append( RadioSetting("dtmf_chan_active", "DTMF active",
+ RadioSettingValueList(opts, opts[_settings.dtmf_chan_active])))
+
+ for i in range(10):
+ name = "dtmf" + str(i)
+ dtmfsetting = self._memobj.dtmf[i]
+ dtmfstr = ""
+ for c in dtmfsetting.memory:
+ if c < len(DTMFCHARSET):
+ dtmfstr += DTMFCHARSET[c]
+ if CHIRP_DEBUG:
+ print dtmfstr
+ dtmfentry = RadioSettingValueString(0, 16, dtmfstr)
+ dtmfentry.set_charset(DTMFCHARSET + list(" "))
+ rs = RadioSetting(name, name.upper(), dtmfentry)
+ dtmf.append(rs)
+
+ # arts tab
+ arts.append( RadioSetting("arts", "ARTS",
+ RadioSettingValueBoolean(_settings.arts)))
+
+ opts = ["off", "in range", "always"]
+ arts.append( RadioSetting("arts_beep", "ARTS beep",
+ RadioSettingValueList(opts, opts[_settings.arts_beep])))
+
+ opts = ["15", "25"]
+ arts.append( RadioSetting("arts_interval", "ARTS interval",
+ RadioSettingValueList(opts, opts[_settings.arts_interval])))
+
+ arts.append( RadioSetting("arts_cwid_enable", "CW ID",
+ RadioSettingValueBoolean(_settings.arts_cwid_enable)))
+
+ cwid = RadioSettingValueString(0, 16,
+ self._decode_chars(_settings.arts_cwid.get_value()))
+ cwid.set_charset(CHARSET)
+ arts.append( RadioSetting("arts_cwid", "CW ID", cwid ))
+
+ # EAI tab
+
+ eai.append( RadioSetting("emergency_eai", "EAI",
+ RadioSettingValueBoolean(_settings.emergency_eai)))
+
+ opts = [ "interval %dm" % t for t in range(1, 10) ] + \
+ [ "interval %dm" % t for t in range(10, 55, 5) ] + \
+ [ "continuous %dm" % t for t in range(1, 10) ] + \
+ [ "continuous %dm" % t for t in range(10, 55, 5) ]
+
+ eai.append( RadioSetting("emergency_eai_time", "EAI time",
+ RadioSettingValueList(opts, opts[_settings.emergency_eai_time])))
+
+ opts = ["beep", "strobe", "beep+strobe", "beam", "beep+beam", "cw", "beep+cw", "cwt"]
+ eai.append( RadioSetting("emergency_signal", "emergency signal",
+ RadioSettingValueList(opts, opts[_settings.emergency_signal])))
+
+ # msg tab
+
+ opts = ["off", "dc voltage", "message"]
+ msg.append( RadioSetting("openmsg_mode", "Opening message mode",
+ RadioSettingValueList(opts, opts[_settings.openmsg_mode])))
+
+ openmsg = RadioSettingValueString(0, 6,
+ self._decode_chars(_settings.openmsg.get_value()))
+ openmsg.set_charset(CHARSET)
+ msg.append( RadioSetting("openmsg", "Opening Message", openmsg ))
+
+ return top
+
+ def set_settings(self, uisettings):
+ for element in uisettings:
+ if not isinstance(element, RadioSetting):
+ self.set_settings(element)
+ continue
+ if not element.changed():
+ continue
+ try:
+ setting = element.get_name()
+ _settings = self._memobj.settings
+ if re.match('dtmf\d', setting):
+ # set dtmf fields
+ dtmfstr = str(element.value).strip()
+ newval = []
+ for i in range(0,16):
+ if i < len(dtmfstr):
+ newval.append(DTMFCHARSET.index(dtmfstr[i]))
+ else:
+ newval.append(0xFF)
+ if CHIRP_DEBUG:
+ print newval
+ idx = int(setting[-1:])
+ _settings = self._memobj.dtmf[idx]
+ _settings.memory = newval
+ continue
+ if re.match('.*_vol$', setting):
+ # volume fields
+ voltype = re.sub('_vol$','', setting)
+ setattr(self._memobj.volumes, voltype, element.value)
+ continue
+ if setting == "my_key":
+ # my_key is memory is off by 9 from list, beware hacks!
+ opts = element.value.get_options()
+ optsidx = opts.index(element.value.get_value())
+ idx = optsidx + 16
+ setattr(_settings, "my_key", idx)
+ continue
+ oldval = getattr(_settings, setting)
+ newval = element.value
+ if setting == "arts_cwid":
+ newval = self._encode_chars(newval)
+ if setting == "openmsg":
+ newval = self._encode_chars(newval, 6)
+ if CHIRP_DEBUG:
+ print "Setting %s(%s) <= %s" % (setting,
+ oldval, newval)
+ setattr(_settings, setting, newval)
+ except Exception, e:
+ print element.get_name()
+ raise
+
\ No newline at end of file
diff --git a/chirp/vx5.py b/chirp/vx5.py
index e532146..65a5ba4 100644
--- a/chirp/vx5.py
+++ b/chirp/vx5.py
@@ -14,7 +14,7 @@
# 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, yaesu_clone, directory
+from chirp import chirp_common, yaesu_clone, directory, errors
from chirp import bitwise
MEM_FORMAT = """
@@ -82,18 +82,18 @@ POWER_LEVELS = [chirp_common.PowerLevel("Hi", watts=5.00),
chirp_common.PowerLevel("L1", watts=0.05)]
class VX5BankModel(chirp_common.BankModel):
- def get_num_banks(self):
+ def get_num_mappings(self):
return 5
- def get_banks(self):
+ def get_mappings(self):
banks = []
- for i in range(0, self.get_num_banks()):
+ for i in range(0, self.get_num_mappings()):
bank = chirp_common.Bank(self, "%i" % (i+1), "MG%i" % (i+1))
bank.index = i
banks.append(bank)
return banks
- def add_memory_to_bank(self, memory, bank):
+ def add_memory_to_mapping(self, memory, bank):
_members = self._radio._memobj.bank_groups[bank.index].members
_bank_used = self._radio._memobj.bank_used[bank.index]
for i in range(0, len(_members)):
@@ -107,7 +107,7 @@ class VX5BankModel(chirp_common.BankModel):
return True
raise Exception(_("{bank} is full").format(bank=bank))
- def remove_memory_from_bank(self, memory, bank):
+ def remove_memory_from_mapping(self, memory, bank):
_members = self._radio._memobj.bank_groups[bank.index].members
_bank_used = self._radio._memobj.bank_used[bank.index]
@@ -128,7 +128,7 @@ class VX5BankModel(chirp_common.BankModel):
if not remaining_members:
_bank_used.current_member = 0xFF
- def get_bank_memories(self, bank):
+ def get_mapping_memories(self, bank):
memories = []
_members = self._radio._memobj.bank_groups[bank.index].members
@@ -143,10 +143,11 @@ class VX5BankModel(chirp_common.BankModel):
memories.append(self._radio.get_memory(member.channel+1))
return memories
- def get_memory_banks(self, memory):
+ def get_memory_mappings(self, memory):
banks = []
- for bank in self.get_banks():
- if memory.number in [x.number for x in self.get_bank_memories(bank)]:
+ for bank in self.get_mappings():
+ if memory.number in [x.number for x in
+ self.get_mapping_memories(bank)]:
banks.append(bank)
return banks
@@ -215,7 +216,7 @@ class VX5Radio(yaesu_clone.YaesuCloneModeRadio):
mem.tmode = TMODES[_mem.tmode & 0x3] # masked so bad mems can be read
if mem.duplex == "split":
mem.offset = chirp_common.fix_rounded_step(mem.offset)
- mem.rtone = mem.ctone = chirp_common.TONES[_mem.tone]
+ mem.rtone = mem.ctone = chirp_common.OLD_TONES[_mem.tone]
mem.dtcs = chirp_common.DTCS_CODES[_mem.dtcs]
mem.skip = _flg.pskip and "P" or _flg.skip and "S" or ""
@@ -262,7 +263,11 @@ class VX5Radio(yaesu_clone.YaesuCloneModeRadio):
else:
_mem.power = 0
_mem.tmode = TMODES.index(mem.tmode)
- _mem.tone = chirp_common.TONES.index(mem.rtone)
+ try:
+ _mem.tone = chirp_common.OLD_TONES.index(mem.rtone)
+ except ValueError:
+ raise errors.UnsupportedToneError(
+ ("This radio does not support tone %s" % mem.rtone))
_mem.dtcs = chirp_common.DTCS_CODES.index(mem.dtcs)
_flg.skip = mem.skip == "S"
diff --git a/chirp/vx510.py b/chirp/vx510.py
new file mode 100644
index 0000000..7f287a9
--- /dev/null
+++ b/chirp/vx510.py
@@ -0,0 +1,201 @@
+# Copyright 2012 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/>.
+
+from chirp import chirp_common, yaesu_clone, directory
+from chirp import bitwise
+
+# This driver is unfinished and therefore does not register itself with Chirp.
+#
+# Downloads should work consistently but an upload has not been attempted.
+#
+# There is a stray byte at 0xC in the radio download that is not present in the
+# save file from the CE-21 software. This puts the first few bytes of channel 1
+# in the wrong location. A quick hack to fix the subsequent channels was to
+# insert the #seekto dynamically, but this does nothing to fix reading of
+# channel 1 (memory[0]).
+MEM_FORMAT = """
+u8 unknown1[6];
+u8 prioritych;
+
+#seekto %d;
+struct {
+ u8 empty:1,
+ txinhibit:1,
+ tot:1,
+ low_power:1,
+ bclo:1,
+ btlo:1,
+ skip:1,
+ pwrsave:1;
+ u8 unknown2:5,
+ narrow:1,
+ unknown2b:2;
+ u24 name;
+ u8 ctone;
+ u8 rtone;
+ u8 unknown3;
+ bbcd freq_rx[3];
+ bbcd freq_tx[3];
+} memory[32];
+
+char imgname[10];
+"""
+
+STEPS = [5.0, 6.25]
+CHARSET = "".join([chr(x) for x in range(ord("0"), ord("9")+1)] +
+ [chr(x) for x in range(ord("A"), ord("Z")+1)]) + "<=>*+-\/_ "
+TONES = list(chirp_common.TONES)
+TONES.remove(165.5)
+TONES.remove(171.3)
+TONES.remove(177.3)
+POWER_LEVELS = [chirp_common.PowerLevel("Hi", watts=5.00),
+ chirp_common.PowerLevel("Low", watts=1.0)]
+
+
+# @directory.register
+class VX510Radio(yaesu_clone.YaesuCloneModeRadio):
+ """Vertex VX-510V"""
+ BAUD_RATE = 9600
+ VENDOR = "Vertex Standard"
+ MODEL = "VX-510V"
+
+ _model = ""
+ _memsize = 470
+ _block_lengths = [10, 460]
+ _block_size = 8
+
+ def _checksums(self):
+ return []
+ # These checksums don't pass, so the alg might be different than Yaesu.
+ # return [yaesu_clone.YaesuChecksum(0, self._memsize - 2)]
+ # return [yaesu_clone.YaesuChecksum(0, 10),
+ # yaesu_clone.YaesuChecksum(12, self._memsize - 1)]
+
+ def get_features(self):
+ rf = chirp_common.RadioFeatures()
+ rf.can_odd_split = True
+ rf.has_bank = False
+ rf.has_ctone = True
+ rf.has_cross = True
+ rf.has_rx_dtcs = True
+ rf.has_dtcs_polarity = False
+ rf.valid_tmodes = ["", "Tone", "TSQL", "DTCS", "Cross"]
+ rf.valid_cross_modes = ["Tone->Tone", "Tone->DTCS", "DTCS->Tone",
+ "->Tone", "->DTCS", "DTCS->", "DTCS->DTCS"]
+ rf.valid_modes = ["FM", "NFM"]
+ rf.valid_duplexes = ["", "-", "+", "split", "off"]
+ rf.memory_bounds = (1, 32)
+ rf.valid_bands = [(13600000, 174000000)]
+ rf.valid_skips = ["", "S"]
+ rf.valid_power_levels = POWER_LEVELS
+ rf.valid_name_length = 4
+ rf.valid_characters = CHARSET
+ return rf
+
+ def process_mmap(self):
+ self._memobj = bitwise.parse(MEM_FORMAT % 0xA, self._mmap)
+
+ def get_raw_memory(self, number):
+ return repr(self._memobj.memory[number-1])
+
+ def get_memory(self, number):
+ mem = chirp_common.Memory()
+ mem.number = number
+
+ _mem = self._memobj.memory[number-1]
+
+ mem.empty = _mem.empty
+ mem.freq = chirp_common.fix_rounded_step(int(_mem.freq_rx) * 1000)
+
+ for i in range(0, 4):
+ index = (_mem.name >> (i*6)) & 0x3F
+ mem.name += CHARSET[index]
+
+ freq_tx = chirp_common.fix_rounded_step(int(_mem.freq_tx) * 1000)
+ if _mem.txinhibit:
+ mem.duplex = "off"
+ elif mem.freq == freq_tx:
+ mem.duplex = ""
+ mem.offset = 0
+ elif 144000000 <= mem.freq < 148000000:
+ mem.duplex = "+" if freq_tx > mem.freq else "-"
+ mem.offset = abs(mem.freq - freq_tx)
+ else:
+ mem.duplex = "split"
+ mem.offset = freq_tx
+
+ mem.mode = _mem.narrow and "NFM" or "FM"
+ mem.power = POWER_LEVELS[_mem.low_power]
+
+ rtone = int(_mem.rtone)
+ ctone = int(_mem.ctone)
+ tmode_tx = tmode_rx = ""
+
+ if rtone & 0x80:
+ tmode_tx = "DTCS"
+ mem.dtcs = chirp_common.DTCS_CODES[int(rtone) - 0x80]
+ elif rtone:
+ tmode_tx = "Tone"
+ mem.rtone = TONES[rtone - 1]
+ if not ctone:
+ # not used, but this is a better default than 88.5
+ mem.ctone = TONES[rtone - 1]
+
+ if ctone & 0x80:
+ tmode_rx = "DTCS"
+ mem.rx_dtcs = chirp_common.DTCS_CODES[int(ctone) - 0x80]
+ elif ctone:
+ tmode_rx = "Tone"
+ mem.ctone = TONES[ctone - 1]
+
+ if tmode_tx == "Tone" and not tmode_rx:
+ mem.tmode = "Tone"
+ elif tmode_tx == tmode_rx and tmode_tx == "Tone" and mem.rtone == mem.ctone:
+ mem.tmode = "TSQL"
+ elif tmode_tx == tmode_rx and tmode_tx == "DTCS" and mem.dtcs == mem.rx_dtcs:
+ mem.tmode = "DTCS"
+ elif tmode_rx or tmode_tx:
+ mem.tmode = "Cross"
+ mem.cross_mode = "%s->%s" % (tmode_tx, tmode_rx)
+
+ mem.skip = _mem.skip and "S" or ""
+
+ return mem
+
+ @classmethod
+ def match_model(cls, filedata, filename):
+ return len(filedata) == cls._memsize
+
+
+# @directory.register
+class VX510File(VX510Radio, chirp_common.FileBackedRadio):
+ """Vertex CE-21 File"""
+ VENDOR = "Vertex Standard"
+ MODEL = "CE-21 File"
+
+ _model = ""
+ _memsize = 664
+
+ def _checksums(self):
+ return [yaesu_clone.YaesuChecksum(0, self._memsize - 1)]
+
+ def process_mmap(self):
+ # CE-21 file is missing the 0xC byte, probably a checksum.
+ # It's not a YaesuChecksum.
+ self._memobj = bitwise.parse(MEM_FORMAT % 0x9, self._mmap)
+
+ @classmethod
+ def match_model(cls, filedata, filename):
+ return len(filedata) == cls._memsize
diff --git a/chirp/vx6.py b/chirp/vx6.py
index b4764db..0cdb115 100644
--- a/chirp/vx6.py
+++ b/chirp/vx6.py
@@ -15,6 +15,7 @@
from chirp import chirp_common, yaesu_clone, directory
from chirp import bitwise
+from textwrap import dedent
# flags.{even|odd}_pskip: These are actually "preferential *scan* channels".
# Is that what they mean on other radios as well?
@@ -37,7 +38,14 @@ from chirp import bitwise
# }
MEM_FORMAT = """
#seekto 0x018A;
-u16 bank_sizes[24];
+struct {
+ u16 in_use;
+} bank_used[24];
+
+#seekto 0x0214;
+u16 banksoff1;
+#seekto 0x0294;
+u16 banksoff2;
#seekto 0x097A;
struct {
@@ -46,8 +54,8 @@ struct {
#seekto 0x0C0A;
struct {
- u16 channel[100];
-} bank_channels[24];
+ u16 channels[100];
+} banks[24];
#seekto 0x1ECA;
struct {
@@ -59,7 +67,7 @@ struct {
odd_skip:1,
odd_valid:1,
odd_masked:1;
-} flags[450];
+} flags[500];
#seekto 0x21CA;
struct {
@@ -81,7 +89,7 @@ struct {
u8 tone;
u8 dcs;
u8 unknown5;
-} memory[900];
+} memory[999];
"""
DUPLEX = ["", "-", "+", "split"]
@@ -104,6 +112,98 @@ POWER_LEVELS_220 = [chirp_common.PowerLevel("Hi", watts=1.50),
chirp_common.PowerLevel("L3", watts=1.00),
chirp_common.PowerLevel("L2", watts=0.50),
chirp_common.PowerLevel("L1", watts=0.20)]
+
+class VX6Bank(chirp_common.NamedBank):
+ """A VX6 Bank"""
+ def get_name(self):
+ _bank = self._model._radio._memobj.bank_names[self.index]
+ name = ""
+ for i in _bank.name:
+ if i == 0xFF:
+ break
+ name += CHARSET[i & 0x7F]
+ return name.rstrip()
+
+ def set_name(self, name):
+ name = name.upper()
+ _bank = self._model._radio._memobj.bank_names[self.index]
+ _bank.name = [CHARSET.index(x) for x in name.ljust(6)[:6]]
+
+class VX6BankModel(chirp_common.BankModel):
+ """A VX-6 bank model"""
+
+ def get_num_mappings(self):
+ return len(self.get_mappings())
+
+ def get_mappings(self):
+ banks = self._radio._memobj.banks
+ bank_mappings = []
+ for index, _bank in enumerate(banks):
+ bank = VX6Bank(self, "%i" % index, "b%i" % (index + 1))
+ bank.index = index
+ bank_mappings.append(bank)
+
+ return bank_mappings
+
+ def _get_channel_numbers_in_bank(self, bank):
+ _bank_used = self._radio._memobj.bank_used[bank.index]
+ if _bank_used.in_use == 0xFFFF:
+ return set()
+
+ _members = self._radio._memobj.banks[bank.index]
+ return set([int(ch) + 1 for ch in _members.channels if ch != 0xFFFF])
+
+ def _update_bank_with_channel_numbers(self, bank, channels_in_bank):
+ _members = self._radio._memobj.banks[bank.index]
+ if len(channels_in_bank) > len(_members.channels):
+ raise Exception("Too many entries in bank %d" % bank.index)
+
+ empty = 0
+ for index, channel_number in enumerate(sorted(channels_in_bank)):
+ _members.channels[index] = channel_number - 1
+ empty = index + 1
+ for index in range(empty, len(_members.channels)):
+ _members.channels[index] = 0xFFFF
+
+ def add_memory_to_mapping(self, memory, bank):
+ channels_in_bank = self._get_channel_numbers_in_bank(bank)
+ channels_in_bank.add(memory.number)
+ self._update_bank_with_channel_numbers(bank, channels_in_bank)
+ _bank_used = self._radio._memobj.bank_used[bank.index]
+ _bank_used.in_use = 0x0000 # enable
+
+ # also needed for unit to recognize any banks?
+ self._radio._memobj.banksoff1 = 0x0000
+ self._radio._memobj.banksoff2 = 0x0000
+ # TODO: turn back off (0xFFFF) when all banks are empty?
+
+ def remove_memory_from_mapping(self, memory, bank):
+ channels_in_bank = self._get_channel_numbers_in_bank(bank)
+ try:
+ channels_in_bank.remove(memory.number)
+ except KeyError:
+ raise Exception("Memory %i is not in bank %s. Cannot remove" % \
+ (memory.number, bank))
+ self._update_bank_with_channel_numbers(bank, channels_in_bank)
+
+ if not channels_in_bank:
+ _bank_used = self._radio._memobj.bank_used[bank.index]
+ _bank_used.in_use = 0xFFFF # disable bank
+
+ def get_mapping_memories(self, bank):
+ memories = []
+ for channel in self._get_channel_numbers_in_bank(bank):
+ memories.append(self._radio.get_memory(channel))
+
+ return memories
+
+ def get_memory_mappings(self, memory):
+ banks = []
+ for bank in self.get_mappings():
+ if memory.number in self._get_channel_numbers_in_bank(bank):
+ banks.append(bank)
+
+ return banks
@directory.register
class VX6Radio(yaesu_clone.YaesuCloneModeRadio):
@@ -117,6 +217,23 @@ class VX6Radio(yaesu_clone.YaesuCloneModeRadio):
_block_lengths = [10, 32578]
_block_size = 16
+ @classmethod
+ def get_prompts(cls):
+ rp = chirp_common.RadioPrompts()
+ rp.pre_download = _(dedent("""\
+ 1. Turn radio off.
+ 2. Connect cable to MIC/SP jack.
+ 3. Press and hold in the [F/W] key while turning the radio on
+ ("CLONE" will appear on the display).
+ 4. <b>After clicking OK</b>, press the [BAND] key to send image."""))
+ rp.pre_upload = _(dedent("""\
+ 1. Turn radio off.
+ 2. Connect cable to MIC/SP jack.
+ 3. Press and hold in the [F/W] key while turning the radio on
+ ("CLONE" will appear on the display).
+ 4. Press the [V/M] key ("-WAIT-" will appear on the LCD)."""))
+ return rp
+
def _checksums(self):
return [ yaesu_clone.YaesuChecksum(0x0000, 0x7F49) ]
@@ -125,14 +242,15 @@ class VX6Radio(yaesu_clone.YaesuCloneModeRadio):
def get_features(self):
rf = chirp_common.RadioFeatures()
- rf.has_bank = False
+ rf.has_bank = True
+ rf.has_bank_names = True
rf.has_dtcs_polarity = False
rf.valid_modes = ["FM", "WFM", "AM", "NFM"]
rf.valid_tmodes = ["", "Tone", "TSQL", "DTCS"]
rf.valid_duplexes = DUPLEX
rf.valid_tuning_steps = STEPS
rf.valid_power_levels = POWER_LEVELS
- rf.memory_bounds = (1, 900)
+ rf.memory_bounds = (1, 999)
rf.valid_bands = [(500000, 998990000)]
rf.valid_characters = "".join(CHARSET)
rf.valid_name_length = 6
@@ -244,28 +362,7 @@ class VX6Radio(yaesu_clone.YaesuCloneModeRadio):
if mem.name.strip():
_mem.name[0] |= 0x80
-# def get_banks(self):
-# _banks = self._memobj.bank_names
-#
-# banks = []
-# for bank in _banks:
-# name = ""
-# for i in bank.name:
-# name += CHARSET[i & 0x7F]
-# banks.append(name.rstrip())
-#
-# return banks
-#
-# # Return channels for a bank. Bank given as number
-# def get_bank_channels(self, bank):
-# nchannels = 0
-# size = self._memobj.bank_sizes[bank]
-# if size <= 198:
-# nchannels = 1 + size/2
-# _channels = self._memobj.bank_channels[bank]
-# channels = []
-# for i in range(0, nchannels):
-# channels.append(int(_channels.channel[i]))
-#
-# return channels
+ def get_bank_model(self):
+ return VX6BankModel(self)
+
diff --git a/chirp/vx7.py b/chirp/vx7.py
index d657ddb..5fff4b1 100644
--- a/chirp/vx7.py
+++ b/chirp/vx7.py
@@ -15,6 +15,7 @@
from chirp import chirp_common, yaesu_clone, directory
from chirp import bitwise
+from textwrap import dedent
MEM_FORMAT = """
#seekto 0x0611;
@@ -103,18 +104,18 @@ def _is220(freq):
class VX7BankModel(chirp_common.BankModel):
"""A VX-7 Bank model"""
- def get_num_banks(self):
+ def get_num_mappings(self):
return 9
- def get_banks(self):
+ def get_mappings(self):
banks = []
- for i in range(0, self.get_num_banks()):
+ for i in range(0, self.get_num_mappings()):
bank = chirp_common.Bank(self, "%i" % (i+1), "MG%i" % (i+1))
bank.index = i
banks.append(bank)
return banks
- def add_memory_to_bank(self, memory, bank):
+ def add_memory_to_mapping(self, memory, bank):
_members = self._radio._memobj.bank_members[bank.index]
_bank_used = self._radio._memobj.bank_used[bank.index]
for i in range(0, 48):
@@ -123,7 +124,7 @@ class VX7BankModel(chirp_common.BankModel):
_bank_used.in_use = 0x0000
break
- def remove_memory_from_bank(self, memory, bank):
+ def remove_memory_from_mapping(self, memory, bank):
_members = self._radio._memobj.bank_members[bank.index].members
_bank_used = self._radio._memobj.bank_used[bank.index]
@@ -143,7 +144,7 @@ class VX7BankModel(chirp_common.BankModel):
if not remaining_members:
_bank_used.in_use = 0xFFFF
- def get_bank_memories(self, bank):
+ def get_mapping_memories(self, bank):
memories = []
_members = self._radio._memobj.bank_members[bank.index].members
@@ -158,11 +159,11 @@ class VX7BankModel(chirp_common.BankModel):
memories.append(self._radio.get_memory(number+1))
return memories
- def get_memory_banks(self, memory):
+ def get_memory_mappings(self, memory):
banks = []
- for bank in self.get_banks():
+ for bank in self.get_mappings():
if memory.number in [x.number for x in
- self.get_bank_memories(bank)]:
+ self.get_mapping_memories(bank)]:
banks.append(bank)
return banks
@@ -183,6 +184,23 @@ class VX7Radio(yaesu_clone.YaesuCloneModeRadio):
_block_lengths = [ 10, 8, 16193 ]
_block_size = 8
+ @classmethod
+ def get_prompts(cls):
+ rp = chirp_common.RadioPrompts()
+ rp.pre_download = _(dedent("""\
+ 1. Turn radio off.
+ 2. Connect cable to MIC/SP jack.
+ 3. Press and hold in the [MON-F] key while turning the radio on
+ ("CLONE" will appear on the display).
+ 4. <b>After clicking OK</b>, press the [BAND] key to send image."""))
+ rp.pre_upload = _(dedent("""\
+ 1. Turn radio off.
+ 2. Connect cable to MIC/SP jack.
+ 3. Press and hold in the [MON-F] key while turning the radio on
+ ("CLONE" will appear on the display).
+ 4. Press the [V/M] key ("CLONE WAIT" will appear on the LCD)."""))
+ return rp
+
def _checksums(self):
return [ yaesu_clone.YaesuChecksum(0x0592, 0x0610),
yaesu_clone.YaesuChecksum(0x0612, 0x0690),
diff --git a/chirp/vx8.py b/chirp/vx8.py
index 67791f5..ceae189 100644
--- a/chirp/vx8.py
+++ b/chirp/vx8.py
@@ -13,8 +13,15 @@
# 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 re
+
from chirp import chirp_common, yaesu_clone, directory
from chirp import bitwise
+from chirp.settings import RadioSettingGroup, RadioSetting
+from chirp.settings import RadioSettingValueInteger, RadioSettingValueString
+from chirp.settings import RadioSettingValueList, RadioSettingValueBoolean
+from textwrap import dedent
MEM_FORMAT = """
#seekto 0x54a;
@@ -22,6 +29,50 @@ struct {
u16 in_use;
} bank_used[24];
+#seekto 0x64a;
+struct {
+ u8 unknown0[4];
+ u8 frequency_band;
+ u8 unknown1:6,
+ manual_or_mr:2;
+ u8 unknown2:7,
+ mr_banks:1;
+ u8 unknown3;
+ u16 mr_index;
+ u16 bank_index;
+ u16 bank_enable;
+ u8 unknown4[5];
+ u8 unknown5:6,
+ power:2;
+ u8 unknown6:4,
+ tune_step:4;
+ u8 unknown7:6,
+ duplex:2;
+ u8 unknown8:6,
+ tone_mode:2;
+ u8 unknown9:2,
+ tone:6;
+ u8 unknown10;
+ u8 unknown11:6,
+ mode:2;
+ bbcd freq0[4];
+ bbcd offset_freq[4];
+ u8 unknown12[2];
+ char label[16];
+ u8 unknown13[6];
+ bbcd band_lower[4];
+ bbcd band_upper[4];
+ bbcd rx_freq[4];
+ u8 unknown14[22];
+ bbcd freq1[4];
+ u8 unknown15[11];
+ u8 unknown16:3,
+ volume:5;
+ u8 unknown17[18];
+ u8 active_menu_item;
+ u8 checksum;
+} vfo_info[6];
+
#seekto 0x135A;
struct {
u8 unknown[2];
@@ -63,6 +114,156 @@ struct {
u8 unknown7[3];
} memory[900];
+#seekto 0xC0CA;
+struct {
+ u8 unknown0:6,
+ rx_baud:2;
+ u8 unknown1:4,
+ tx_delay:4;
+ u8 custom_symbol;
+ u8 unknown2;
+ struct {
+ char callsign[6];
+ u8 ssid;
+ } my_callsign;
+ u8 unknown3:4,
+ selected_position_comment:4;
+ u8 unknown4;
+ u8 set_time_manually:1,
+ tx_interval_beacon:1,
+ ring_beacon:1,
+ ring_msg:1,
+ aprs_mute:1,
+ unknown6:1,
+ tx_smartbeacon:1,
+ af_dual:1;
+ u8 unknown7:1,
+ aprs_units_wind_mph:1,
+ aprs_units_rain_inch:1,
+ aprs_units_temperature_f:1
+ aprs_units_altitude_ft:1,
+ unknown8:1,
+ aprs_units_distance_m:1,
+ aprs_units_position_mmss:1;
+ u8 unknown9:6,
+ aprs_units_speed:2;
+ u8 unknown11:1,
+ filter_other:1,
+ filter_status:1,
+ filter_item:1,
+ filter_object:1,
+ filter_weather:1,
+ filter_position:1,
+ filter_mic_e:1;
+ u8 unknown12:2,
+ timezone:6;
+ u8 unknown13:4,
+ beacon_interval:4;
+ u8 unknown14;
+ u8 unknown15:7,
+ latitude_sign:1;
+ u8 latitude_degree;
+ u8 latitude_minute;
+ u8 latitude_second;
+ u8 unknown16:7,
+ longitude_sign:1;
+ u8 longitude_degree;
+ u8 longitude_minute;
+ u8 longitude_second;
+ u8 unknown17:4,
+ selected_position:4;
+ u8 unknown18:5,
+ selected_beacon_status_txt:3;
+ u8 unknown19:6,
+ gps_units_altitude_ft:1,
+ gps_units_position_sss:1;
+ u8 unknown20:6,
+ gps_units_speed:2;
+ u8 unknown21[4];
+ struct {
+ struct {
+ char callsign[6];
+ u8 ssid;
+ } entry[8];
+ } digi_path_7;
+ u8 unknown22[2];
+ struct {
+ char padded_string[16];
+ } message_macro[7];
+ u8 unknown23:5,
+ selected_msg_group:3;
+ u8 unknown24;
+ struct {
+ char padded_string[9];
+ } msg_group[8];
+ u8 unknown25[4];
+ u8 active_smartbeaconing;
+ struct {
+ u8 low_speed_mph;
+ u8 high_speed_mph;
+ u8 slow_rate_min;
+ u8 fast_rate_sec;
+ u8 turn_angle;
+ u8 turn_slop;
+ u8 turn_time_sec;
+ } smartbeaconing_profile[3];
+ u8 unknown26:2,
+ flash_msg:6;
+ u8 unknown27:2,
+ flash_grp:6;
+ u8 unknown28:2,
+ flash_bln:6;
+ u8 selected_digi_path;
+ struct {
+ struct {
+ char callsign[6];
+ u8 ssid;
+ } entry[2];
+ } digi_path_3_6[4];
+ u8 unknown30:6,
+ selected_my_symbol:2;
+ u8 unknown31[3];
+ u8 unknown32:2,
+ vibrate_msg:6;
+ u8 unknown33:2,
+ vibrate_grp:6;
+ u8 unknown34:2,
+ vibrate_bln:6;
+} aprs;
+
+#seekto 0x%04X;
+struct {
+ bbcd date[3];
+ u8 unknown1;
+ bbcd time[2];
+ u8 sequence;
+ u8 unknown2;
+ u8 sender_callsign[7];
+ u8 data_type;
+ u8 yeasu_data_type;
+ u8 unknown3;
+ u8 unknown4:1,
+ callsign_is_ascii:1,
+ unknown5:6;
+ u8 unknown6;
+ u16 pkt_len;
+ u16 in_use;
+} aprs_beacon_meta[%d];
+
+#seekto 0x%04X;
+struct {
+ u8 dst_callsign[6];
+ u8 dst_callsign_ssid;
+ u8 src_callsign[6];
+ u8 src_callsign_ssid;
+ u8 path_and_body[%d];
+} aprs_beacon_pkt[%d];
+
+#seekto 0xf92a;
+struct {
+ char padded_string[60];
+} aprs_beacon_status_txt[5];
+
#seekto 0xFECA;
u8 checksum;
"""
@@ -73,7 +274,7 @@ MODES = ["FM", "AM", "WFM"]
STEPS = list(chirp_common.TUNING_STEPS)
STEPS.remove(30.0)
STEPS.append(100.0)
-STEPS.insert(2, 0.0) # There is a skipped tuning step ad index 2 (?)
+STEPS.insert(2, 0.0) # There is a skipped tuning step at index 2 (?)
SKIPS = ["", "S", "P"]
CHARSET = ["%i" % int(x) for x in range(0, 10)] + \
@@ -107,70 +308,120 @@ class VX8Bank(chirp_common.NamedBank):
class VX8BankModel(chirp_common.BankModel):
"""A VX-8 bank model"""
- def get_num_banks(self):
- return 24
+ def __init__(self, radio, name='Banks'):
+ super(VX8BankModel, self).__init__(radio, name)
- def get_banks(self):
- banks = []
_banks = self._radio._memobj.bank_info
-
- index = 0
- for _bank in _banks:
+ self._bank_mappings = []
+ for index, _bank in enumerate(_banks):
bank = VX8Bank(self, "%i" % index, "BANK-%i" % index)
bank.index = index
- banks.append(bank)
- index += 1
+ self._bank_mappings.append(bank)
- return banks
+ def get_num_mappings(self):
+ return len(self._bank_mappings)
- def add_memory_to_bank(self, memory, bank):
- _members = self._radio._memobj.bank_members[bank.index]
+ def get_mappings(self):
+ return self._bank_mappings
+
+ def _channel_numbers_in_bank(self, bank):
_bank_used = self._radio._memobj.bank_used[bank.index]
- for i in range(0, 100):
- if _members.channel[i] == 0xFFFF:
- _members.channel[i] = memory.number - 1
- _bank_used.in_use = 0x06
+ if _bank_used.in_use == 0xFFFF:
+ return set()
+
+ _members = self._radio._memobj.bank_members[bank.index]
+ return set([int(ch) + 1 for ch in _members.channel if ch != 0xFFFF])
+
+ def update_vfo(self):
+ chosen_bank = [None, None]
+ chosen_mr = [None, None]
+
+ flags = self._radio._memobj.flag
+
+ # Find a suitable bank and MR for VFO A and B.
+ for bank in self.get_mappings():
+ for channel in self._channel_numbers_in_bank(bank):
+ chosen_bank[0] = bank.index
+ chosen_mr[0] = channel
+ if not flags[channel].nosubvfo:
+ chosen_bank[1] = bank.index
+ chosen_mr[1] = channel
+ break
+ if chosen_bank[1]:
break
- def remove_memory_from_bank(self, memory, bank):
+ for vfo_index in (0, 1):
+ # 3 VFO info structs are stored as 3 pairs of (master, backup)
+ vfo = self._radio._memobj.vfo_info[vfo_index * 2]
+ vfo_bak = self._radio._memobj.vfo_info[(vfo_index * 2) + 1]
+
+ if vfo.checksum != vfo_bak.checksum:
+ print "Warning: VFO settings are inconsistent with backup"
+ else:
+ if ((chosen_bank[vfo_index] is None) and
+ (vfo.bank_index != 0xFFFF)):
+ print "Disabling banks for VFO %d" % vfo_index
+ vfo.bank_index = 0xFFFF
+ vfo.mr_index = 0xFFFF
+ vfo.bank_enable = 0xFFFF
+ elif ((chosen_bank[vfo_index] is not None) and
+ (vfo.bank_index == 0xFFFF)):
+ print "Enabling banks for VFO %d" % vfo_index
+ vfo.bank_index = chosen_bank[vfo_index]
+ vfo.mr_index = chosen_mr[vfo_index]
+ vfo.bank_enable = 0x0000
+ vfo_bak.bank_index = vfo.bank_index
+ vfo_bak.mr_index = vfo.mr_index
+ vfo_bak.bank_enable = vfo.bank_enable
+
+ def _update_bank_with_channel_numbers(self, bank, channels_in_bank):
_members = self._radio._memobj.bank_members[bank.index]
+ if len(channels_in_bank) > len(_members.channel):
+ raise Exception("Too many entries in bank %d" % bank.index)
+
+ empty = 0
+ for index, channel_number in enumerate(sorted(channels_in_bank)):
+ _members.channel[index] = channel_number - 1
+ empty = index + 1
+ for index in range(empty, len(_members.channel)):
+ _members.channel[index] = 0xFFFF
+
+ def add_memory_to_mapping(self, memory, bank):
+ channels_in_bank = self._channel_numbers_in_bank(bank)
+ channels_in_bank.add(memory.number)
+ self._update_bank_with_channel_numbers(bank, channels_in_bank)
+
_bank_used = self._radio._memobj.bank_used[bank.index]
+ _bank_used.in_use = 0x06
- remaining_members = 0
- found = False
- for i in range(0, len(_members.channel)):
- if _members.channel[i] == (memory.number - 1):
- _members.channel[i] = 0xFFFF
- found = True
- elif _members.channel[i] != 0xFFFF:
- remaining_members += 1
+ self.update_vfo()
- if not found:
+ def remove_memory_from_mapping(self, memory, bank):
+ channels_in_bank = self._channel_numbers_in_bank(bank)
+ try:
+ channels_in_bank.remove(memory.number)
+ except KeyError:
raise Exception("Memory %i is not in bank %s. Cannot remove" % \
- (memory.number, bank))
+ (memory.number, bank))
+ self._update_bank_with_channel_numbers(bank, channels_in_bank)
- if not remaining_members:
+ if not channels_in_bank:
+ _bank_used = self._radio._memobj.bank_used[bank.index]
_bank_used.in_use = 0xFFFF
- def get_bank_memories(self, bank):
- memories = []
- _members = self._radio._memobj.bank_members[bank.index]
- _bank_used = self._radio._memobj.bank_used[bank.index]
-
- if _bank_used.in_use == 0xFFFF:
- return memories
+ self.update_vfo()
- for channel in _members.channel:
- if channel != 0xFFFF:
- memories.append(self._radio.get_memory(int(channel)+1))
+ def get_mapping_memories(self, bank):
+ memories = []
+ for channel in self._channel_numbers_in_bank(bank):
+ memories.append(self._radio.get_memory(channel))
return memories
- def get_memory_banks(self, memory):
+ def get_memory_mappings(self, memory):
banks = []
- for bank in self.get_banks():
- if memory.number in \
- [x.number for x in self.get_bank_memories(bank)]:
+ for bank in self.get_mappings():
+ if memory.number in self._channel_numbers_in_bank(bank):
banks.append(bank)
return banks
@@ -191,9 +442,33 @@ class VX8Radio(yaesu_clone.YaesuCloneModeRadio):
_memsize = 65227
_block_lengths = [ 10, 65217 ]
_block_size = 32
+ _mem_params = (0xC24A, # APRS beacon metadata address.
+ 40, # Number of beacons stored.
+ 0xC60A, # APRS beacon content address.
+ 194, # Length of beacon data stored.
+ 40) # Number of beacons stored.
+ _has_vibrate = False
+ _has_af_dual = True
+ @classmethod
+ def get_prompts(cls):
+ rp = chirp_common.RadioPrompts()
+ rp.pre_download = _(dedent("""\
+ 1. Turn radio off.
+ 2. Connect cable to DATA jack.
+ 3. Press and hold in the [FW] key while turning the radio on
+ ("CLONE" will appear on the display).
+ 4. <b>After clicking OK</b>, press the [BAND] key to send image."""))
+ rp.pre_upload = _(dedent("""\
+ 1. Turn radio off.
+ 2. Connect cable to DATA jack.
+ 3. Press and hold in the [FW] key while turning the radio on
+ ("CLONE" will appear on the display).
+ 4. Press the [MODE] key ("-WAIT-" will appear on the LCD)."""))
+ return rp
+
def process_mmap(self):
- self._memobj = bitwise.parse(MEM_FORMAT, self._mmap)
+ self._memobj = bitwise.parse(MEM_FORMAT % self._mem_params, self._mmap)
def get_features(self):
rf = chirp_common.RadioFeatures()
@@ -217,7 +492,22 @@ class VX8Radio(yaesu_clone.YaesuCloneModeRadio):
return repr(self._memobj.memory[number])
def _checksums(self):
- return [ yaesu_clone.YaesuChecksum(0x0000, 0xFEC9) ]
+ return [ yaesu_clone.YaesuChecksum(0x064A, 0x06C8),
+ yaesu_clone.YaesuChecksum(0x06CA, 0x0748),
+ yaesu_clone.YaesuChecksum(0x074A, 0x07C8),
+ yaesu_clone.YaesuChecksum(0x07CA, 0x0848),
+ yaesu_clone.YaesuChecksum(0x0000, 0xFEC9) ]
+
+ @staticmethod
+ def _add_ff_pad(val, length):
+ return val.ljust(length, "\xFF")[:length]
+
+ @classmethod
+ def _strip_ff_pads(cls, messages):
+ result = []
+ for msg_text in messages:
+ result.append(str(msg_text).rstrip("\xFF"))
+ return result
def get_memory(self, number):
flag = self._memobj.flag[number-1]
@@ -243,22 +533,22 @@ class VX8Radio(yaesu_clone.YaesuCloneModeRadio):
mem.power = POWER_LEVELS[3 - _mem.power]
mem.skip = flag.pskip and "P" or flag.skip and "S" or ""
- for i in str(_mem.label):
- if i == "\xFF":
- break
- mem.name += CHARSET[ord(i)]
+ charset = ''.join(CHARSET).ljust(256, '.')
+ mem.name = str(_mem.label).rstrip("\xFF").translate(charset)
return mem
def _debank(self, mem):
bm = self.get_bank_model()
- for bank in bm.get_memory_banks(mem):
- bm.remove_memory_from_bank(mem, bank)
+ for bank in bm.get_memory_mappings(mem):
+ bm.remove_memory_from_mapping(mem, bank)
def set_memory(self, mem):
_mem = self._memobj.memory[mem.number-1]
flag = self._memobj.flag[mem.number-1]
+ self._debank(mem)
+
if not mem.empty and not flag.valid:
_wipe_memory(_mem)
@@ -292,7 +582,7 @@ class VX8Radio(yaesu_clone.YaesuCloneModeRadio):
_mem.power = 0
label = "".join([chr(CHARSET.index(x)) for x in mem.name.rstrip()])
- _mem.label = label.ljust(16, "\xFF")
+ _mem.label = self._add_ff_pad(label, 16)
# We only speak english here in chirpville
_mem.charsetbits[0] = 0x00
_mem.charsetbits[1] = 0x00
@@ -307,4 +597,685 @@ class VX8Radio(yaesu_clone.YaesuCloneModeRadio):
class VX8DRadio(VX8Radio):
"""Yaesu VX-8DR"""
_model = "AH29D"
+ _mem_params = (0xC24A, # APRS beacon metadata address.
+ 50, # Number of beacons stored.
+ 0xC6FA, # APRS beacon content address.
+ 146, # Length of beacon data stored.
+ 50) # Number of beacons stored.
VARIANT = "DR"
+
+ _SG_RE = re.compile(r"(?P<sign>[-+NESW]?)(?P<d>[\d]+)[\s\.,]*"
+ "(?P<m>[\d]*)[\s\']*(?P<s>[\d]*)")
+
+ _RX_BAUD = ("off", "1200 baud", "9600 baud")
+ _TX_DELAY = ("100ms", "150ms", "200ms", "250ms", "300ms",
+ "400ms", "500ms", "750ms", "1000ms")
+ _WIND_UNITS = ("m/s", "mph")
+ _RAIN_UNITS = ("mm", "inch")
+ _TEMP_UNITS = ("C", "F")
+ _ALT_UNITS = ("m", "ft")
+ _DIST_UNITS = ("km", "mile")
+ _POS_UNITS = ("dd.mmmm'", "dd mm'ss\"")
+ _SPEED_UNITS = ("km/h", "knot", "mph")
+ _TIME_SOURCE = ("manual", "GPS")
+ _TZ = ("-13:00", "-13:30", "-12:00", "-12:30", "-11:00", "-11:30",
+ "-10:00", "-10:30", "-09:00", "-09:30", "-08:00", "-08:30",
+ "-07:00", "-07:30", "-06:00", "-06:30", "-05:00", "-05:30",
+ "-04:00", "-04:30", "-03:00", "-03:30", "-02:00", "-02:30",
+ "-01:00", "-01:30", "-00:00", "-00:30", "+01:00", "+01:30",
+ "+02:00", "+02:30", "+03:00", "+03:30", "+04:00", "+04:30",
+ "+05:00", "+05:30", "+06:00", "+06:30", "+07:00", "+07:30",
+ "+08:00", "+08:30", "+09:00", "+09:30", "+10:00", "+10:30",
+ "+11:00", "+11:30")
+ _BEACON_TYPE = ("Off", "Interval", "SmartBeaconing")
+ _SMARTBEACON_PROFILE = ("Off", "Type 1", "Type 2", "Type 3")
+ _BEACON_INT = ("30s", "1m", "2m", "3m", "5m", "10m", "15m",
+ "20m", "30m", "60m")
+ _DIGI_PATHS = ("OFF", "WIDE1-1", "WIDE1-1, WIDE2-1", "Digi Path 4",
+ "Digi Path 5", "Digi Path 6", "Digi Path 7", "Digi Path 8")
+ _MSG_GROUP_NAMES = ("Message Group 1", "Message Group 2",
+ "Message Group 3", "Message Group 4",
+ "Message Group 5", "Message Group 6",
+ "Message Group 7", "Message Group 8")
+ _POSITIONS = ("GPS", "Manual Latitude/Longitude",
+ "Manual Latitude/Longitude", "P1", "P2", "P3", "P4",
+ "P5", "P6", "P7", "P8", "P9")
+ _FLASH = ("OFF", "2 seconds", "4 seconds", "6 seconds", "8 seconds",
+ "10 seconds", "20 seconds", "30 seconds", "60 seconds",
+ "CONTINUOUS", "every 2 seconds", "every 3 seconds",
+ "every 4 seconds", "every 5 seconds", "every 6 seconds",
+ "every 7 seconds", "every 8 seconds", "every 9 seconds",
+ "every 10 seconds", "every 20 seconds", "every 30 seconds",
+ "every 40 seconds", "every 50 seconds", "every minute",
+ "every 2 minutes", "every 3 minutes", "every 4 minutes",
+ "every 5 minutes", "every 6 minutes", "every 7 minutes",
+ "every 8 minutes", "every 9 minutes", "every 10 minutes")
+ _MY_SYMBOL = ("/[ Person", "/b Bike", "/> Car", "User selected")
+
+ def get_features(self):
+ rf = VX8Radio.get_features(self)
+ rf.has_settings = True
+ return rf
+
+ @classmethod
+ def _digi_path_to_str(cls, path):
+ path_cmp = []
+ for entry in path.entry:
+ callsign = str(entry.callsign).rstrip("\xFF")
+ if not callsign:
+ break
+ path_cmp.append("%s-%d" % (callsign, entry.ssid))
+ return ",".join(path_cmp)
+
+ @staticmethod
+ def _latlong_sanity(sign, l_d, l_m, l_s, is_lat):
+ if sign not in (0, 1):
+ sign = 0
+ if is_lat:
+ d_max = 90
+ else:
+ d_max = 180
+ if l_d < 0 or l_d > d_max:
+ l_d = 0
+ l_m = 0
+ l_s = 0
+ if l_m < 0 or l_m > 60:
+ l_m = 0
+ l_s = 0
+ if l_s < 0 or l_s > 60:
+ l_s = 0
+ return sign, l_d, l_m, l_s
+
+ @classmethod
+ def _latlong_to_str(cls, sign, l_d, l_m, l_s, is_lat, to_sexigesimal=True):
+ sign, l_d, l_m, l_s = cls._latlong_sanity(sign, l_d, l_m, l_s, is_lat)
+ mult = sign and -1 or 1
+ if to_sexigesimal:
+ return "%d,%d'%d\"" % (mult * l_d, l_m, l_s)
+ return "%0.5f" % (mult * l_d + (l_m / 60.0) + (l_s / (60.0 * 60.0)))
+
+ @classmethod
+ def _str_to_latlong(cls, lat_long, is_lat):
+ sign = 0
+ result = [0, 0, 0]
+
+ lat_long = lat_long.strip()
+
+ if not lat_long:
+ return 1, 0, 0, 0
+
+ try:
+ # DD.MMMMM is the simple case, try that first.
+ val = float(lat_long)
+ if val < 0:
+ sign = 1
+ val = abs(val)
+ result[0] = int(val)
+ result[1] = int(val * 60) % 60
+ result[2] = int(val * 3600) % 60
+ except ValueError:
+ # Try DD MM'SS" if DD.MMMMM failed.
+ match = cls._SG_RE.match(lat_long.strip())
+ if match:
+ if match.group("sign") and (match.group("sign") in "SE-"):
+ sign = 1
+ else:
+ sign = 0
+ if match.group("d"):
+ result[0] = int(match.group("d"))
+ if match.group("m"):
+ result[1] = int(match.group("m"))
+ if match.group("s"):
+ result[2] = int(match.group("s"))
+ elif len(lat_long) > 4:
+ raise Exception("Lat/Long should be DD MM'SS\" or DD.MMMMM")
+
+ return cls._latlong_sanity(sign, result[0], result[1], result[2],
+ is_lat)
+
+ def _get_aprs_general_settings(self):
+ menu = RadioSettingGroup("aprs_general", "APRS General")
+ aprs = self._memobj.aprs
+
+ val = RadioSettingValueString(0, 6,
+ str(aprs.my_callsign.callsign).rstrip("\xFF"))
+ rs = RadioSetting("aprs.my_callsign.callsign", "My Callsign", val)
+ rs.set_apply_callback(self.apply_callsign, aprs.my_callsign)
+ menu.append(rs)
+
+ val = RadioSettingValueList(
+ chirp_common.APRS_SSID,
+ chirp_common.APRS_SSID[aprs.my_callsign.ssid])
+ rs = RadioSetting("aprs.my_callsign.ssid", "My SSID", val)
+ menu.append(rs)
+
+ val = RadioSettingValueList(self._MY_SYMBOL,
+ self._MY_SYMBOL[aprs.selected_my_symbol])
+ rs = RadioSetting("aprs.selected_my_symbol", "My Symbol", val)
+ menu.append(rs)
+
+ symbols = list(chirp_common.APRS_SYMBOLS)
+ selected = aprs.custom_symbol
+ if aprs.custom_symbol >= len(chirp_common.APRS_SYMBOLS):
+ symbols.append("%d" % aprs.custom_symbol)
+ selected = len(symbols) - 1
+ val = RadioSettingValueList(symbols, symbols[selected])
+ rs = RadioSetting("aprs.custom_symbol_text", "User Selected Symbol",
+ val)
+ rs.set_apply_callback(self.apply_custom_symbol, aprs)
+ menu.append(rs)
+
+ val = RadioSettingValueList(
+ chirp_common.APRS_POSITION_COMMENT,
+ chirp_common.APRS_POSITION_COMMENT[aprs.selected_position_comment])
+ rs = RadioSetting("aprs.selected_position_comment", "Position Comment",
+ val)
+ menu.append(rs)
+
+ latitude = self._latlong_to_str(aprs.latitude_sign,
+ aprs.latitude_degree,
+ aprs.latitude_minute,
+ aprs.latitude_second,
+ True, aprs.aprs_units_position_mmss)
+ longitude = self._latlong_to_str(aprs.longitude_sign,
+ aprs.longitude_degree,
+ aprs.longitude_minute,
+ aprs.longitude_second,
+ False, aprs.aprs_units_position_mmss)
+
+ # TODO: Rebuild this when aprs_units_position_mmss changes.
+ # TODO: Rebuild this when latitude/longitude change.
+ # TODO: Add saved positions p1 - p10 to memory map.
+ position_str = list(self._POSITIONS)
+ #position_str[1] = "%s %s" % (latitude, longitude)
+ #position_str[2] = "%s %s" % (latitude, longitude)
+ val = RadioSettingValueList(position_str,
+ position_str[aprs.selected_position])
+ rs = RadioSetting("aprs.selected_position", "My Position", val)
+ menu.append(rs)
+
+ val = RadioSettingValueString(0, 10, latitude)
+ rs = RadioSetting("latitude", "Manual Latitude", val)
+ rs.set_apply_callback(self.apply_lat_long, aprs)
+ menu.append(rs)
+
+ val = RadioSettingValueString(0, 11, longitude)
+ rs = RadioSetting("longitude", "Manual Longitude", val)
+ rs.set_apply_callback(self.apply_lat_long, aprs)
+ menu.append(rs)
+
+ val = RadioSettingValueList(self._TIME_SOURCE,
+ self._TIME_SOURCE[aprs.set_time_manually])
+ rs = RadioSetting("aprs.set_time_manually", "Time Source", val)
+ menu.append(rs)
+
+ val = RadioSettingValueList(self._TZ, self._TZ[aprs.timezone])
+ rs = RadioSetting("aprs.timezone", "Timezone", val)
+ menu.append(rs)
+
+ val = RadioSettingValueList(self._SPEED_UNITS,
+ self._SPEED_UNITS[aprs.aprs_units_speed])
+ rs = RadioSetting("aprs.aprs_units_speed", "APRS Speed Units", val)
+ menu.append(rs)
+
+ val = RadioSettingValueList(self._SPEED_UNITS,
+ self._SPEED_UNITS[aprs.gps_units_speed])
+ rs = RadioSetting("aprs.gps_units_speed", "GPS Speed Units", val)
+ menu.append(rs)
+
+ val = RadioSettingValueList(self._ALT_UNITS,
+ self._ALT_UNITS[aprs.aprs_units_altitude_ft])
+ rs = RadioSetting("aprs.aprs_units_altitude_ft", "APRS Altitude Units",
+ val)
+ menu.append(rs)
+
+ val = RadioSettingValueList(self._ALT_UNITS,
+ self._ALT_UNITS[aprs.gps_units_altitude_ft])
+ rs = RadioSetting("aprs.gps_units_altitude_ft", "GPS Altitude Units",
+ val)
+ menu.append(rs)
+
+ val = RadioSettingValueList(self._POS_UNITS,
+ self._POS_UNITS[aprs.aprs_units_position_mmss])
+ rs = RadioSetting("aprs.aprs_units_position_mmss",
+ "APRS Position Format", val)
+ menu.append(rs)
+
+ val = RadioSettingValueList(self._POS_UNITS,
+ self._POS_UNITS[aprs.gps_units_position_sss])
+ rs = RadioSetting("aprs.gps_units_position_sss",
+ "GPS Position Format", val)
+ menu.append(rs)
+
+ val = RadioSettingValueList(self._DIST_UNITS,
+ self._DIST_UNITS[aprs.aprs_units_distance_m])
+ rs = RadioSetting("aprs.aprs_units_distance_m", "APRS Distance Units",
+ val)
+ menu.append(rs)
+
+ val = RadioSettingValueList(self._WIND_UNITS,
+ self._WIND_UNITS[aprs.aprs_units_wind_mph])
+ rs = RadioSetting("aprs.aprs_units_wind_mph", "APRS Wind Speed Units",
+ val)
+ menu.append(rs)
+
+ val = RadioSettingValueList(self._RAIN_UNITS,
+ self._RAIN_UNITS[aprs.aprs_units_rain_inch])
+ rs = RadioSetting("aprs.aprs_units_rain_inch", "APRS Rain Units", val)
+ menu.append(rs)
+
+ val = RadioSettingValueList(self._TEMP_UNITS,
+ self._TEMP_UNITS[aprs.aprs_units_temperature_f])
+ rs = RadioSetting("aprs.aprs_units_temperature_f",
+ "APRS Temperature Units", val)
+ menu.append(rs)
+
+ return menu
+
+ def _get_aprs_rx_settings(self):
+ menu = RadioSettingGroup("aprs_rx", "APRS Receive")
+ aprs = self._memobj.aprs
+
+ val = RadioSettingValueList(self._RX_BAUD, self._RX_BAUD[aprs.rx_baud])
+ rs = RadioSetting("aprs.rx_baud", "Modem RX", val)
+ menu.append(rs)
+
+ val = RadioSettingValueBoolean(aprs.aprs_mute)
+ rs = RadioSetting("aprs.aprs_mute", "APRS Mute", val)
+ menu.append(rs)
+
+ if self._has_af_dual:
+ val = RadioSettingValueBoolean(aprs.af_dual)
+ rs = RadioSetting("aprs.af_dual", "AF Dual", val)
+ menu.append(rs)
+
+ val = RadioSettingValueBoolean(aprs.ring_msg)
+ rs = RadioSetting("aprs.ring_msg", "Ring on Message RX", val)
+ menu.append(rs)
+
+ val = RadioSettingValueBoolean(aprs.ring_beacon)
+ rs = RadioSetting("aprs.ring_beacon", "Ring on Beacon RX", val)
+ menu.append(rs)
+
+ val = RadioSettingValueList(self._FLASH,
+ self._FLASH[aprs.flash_msg])
+ rs = RadioSetting("aprs.flash_msg", "Flash on personal message", val)
+ menu.append(rs)
+
+ if self._has_vibrate:
+ val = RadioSettingValueList(self._FLASH,
+ self._FLASH[aprs.vibrate_msg])
+ rs = RadioSetting("aprs.vibrate_msg",
+ "Vibrate on personal message", val)
+ menu.append(rs)
+
+ val = RadioSettingValueList(self._FLASH[:10],
+ self._FLASH[aprs.flash_bln])
+ rs = RadioSetting("aprs.flash_bln", "Flash on bulletin message", val)
+ menu.append(rs)
+
+ if self._has_vibrate:
+ val = RadioSettingValueList(self._FLASH[:10],
+ self._FLASH[aprs.vibrate_bln])
+ rs = RadioSetting("aprs.vibrate_bln",
+ "Vibrate on bulletin message", val)
+ menu.append(rs)
+
+ val = RadioSettingValueList(self._FLASH[:10],
+ self._FLASH[aprs.flash_grp])
+ rs = RadioSetting("aprs.flash_grp", "Flash on group message", val)
+ menu.append(rs)
+
+ if self._has_vibrate:
+ val = RadioSettingValueList(self._FLASH[:10],
+ self._FLASH[aprs.vibrate_grp])
+ rs = RadioSetting("aprs.vibrate_grp",
+ "Vibrate on group message", val)
+ menu.append(rs)
+
+ filter_val = [m.padded_string for m in aprs.msg_group]
+ filter_val = self._strip_ff_pads(filter_val)
+ for index, filter_text in enumerate(filter_val):
+ val = RadioSettingValueString(0, 9, filter_text)
+ rs = RadioSetting("aprs.msg_group_%d" % index,
+ "Message Group %d" % (index + 1), val)
+ menu.append(rs)
+ rs.set_apply_callback(self.apply_ff_padded_string,
+ aprs.msg_group[index])
+ # TODO: Use filter_val as the list entries and update it on edit.
+ val = RadioSettingValueList(
+ self._MSG_GROUP_NAMES,
+ self._MSG_GROUP_NAMES[aprs.selected_msg_group])
+ rs = RadioSetting("aprs.selected_msg_group", "Selected Message Group",
+ val)
+ menu.append(rs)
+
+ val = RadioSettingValueBoolean(aprs.filter_mic_e)
+ rs = RadioSetting("aprs.filter_mic_e", "Receive Mic-E Beacons", val)
+ menu.append(rs)
+
+ val = RadioSettingValueBoolean(aprs.filter_position)
+ rs = RadioSetting("aprs.filter_position", "Receive Position Beacons",
+ val)
+ menu.append(rs)
+
+ val = RadioSettingValueBoolean(aprs.filter_weather)
+ rs = RadioSetting("aprs.filter_weather", "Receive Weather Beacons",
+ val)
+ menu.append(rs)
+
+ val = RadioSettingValueBoolean(aprs.filter_object)
+ rs = RadioSetting("aprs.filter_object", "Receive Object Beacons", val)
+ menu.append(rs)
+
+ val = RadioSettingValueBoolean(aprs.filter_item)
+ rs = RadioSetting("aprs.filter_item", "Receive Item Beacons", val)
+ menu.append(rs)
+
+ val = RadioSettingValueBoolean(aprs.filter_status)
+ rs = RadioSetting("aprs.filter_status", "Receive Status Beacons", val)
+ menu.append(rs)
+
+ val = RadioSettingValueBoolean(aprs.filter_other)
+ rs = RadioSetting("aprs.filter_other", "Receive Other Beacons", val)
+ menu.append(rs)
+
+ return menu
+
+ def _get_aprs_tx_settings(self):
+ menu = RadioSettingGroup("aprs_tx", "APRS Transmit")
+ aprs = self._memobj.aprs
+
+ beacon_type = (aprs.tx_smartbeacon << 1) | aprs.tx_interval_beacon;
+ val = RadioSettingValueList(self._BEACON_TYPE,
+ self._BEACON_TYPE[beacon_type])
+ rs = RadioSetting("aprs.transmit", "TX Beacons", val)
+ rs.set_apply_callback(self.apply_beacon_type, aprs)
+ menu.append(rs)
+
+ val = RadioSettingValueList(self._TX_DELAY,
+ self._TX_DELAY[aprs.tx_delay])
+ rs = RadioSetting("aprs.tx_delay", "TX Delay", val)
+ menu.append(rs)
+
+ val = RadioSettingValueList(self._BEACON_INT,
+ self._BEACON_INT[aprs.beacon_interval])
+ rs = RadioSetting("aprs.beacon_interval", "Beacon Interval", val)
+ menu.append(rs)
+
+ desc = []
+ status = [m.padded_string for m in self._memobj.aprs_beacon_status_txt]
+ status = self._strip_ff_pads(status)
+ for index, msg_text in enumerate(status):
+ val = RadioSettingValueString(0, 60, msg_text)
+ desc.append("Beacon Status Text %d" % (index + 1))
+ rs = RadioSetting("aprs_beacon_status_txt_%d" % index, desc[-1],
+ val)
+ rs.set_apply_callback(self.apply_ff_padded_string,
+ self._memobj.aprs_beacon_status_txt[index])
+ menu.append(rs)
+ val = RadioSettingValueList(desc,
+ desc[aprs.selected_beacon_status_txt])
+ rs = RadioSetting("aprs.selected_beacon_status_txt",
+ "Beacon Status Text", val)
+ menu.append(rs)
+
+ message_macro = [m.padded_string for m in aprs.message_macro]
+ message_macro = self._strip_ff_pads(message_macro)
+ for index, msg_text in enumerate(message_macro):
+ val = RadioSettingValueString(0, 16, msg_text)
+ rs = RadioSetting("aprs.message_macro_%d" % index,
+ "Message Macro %d" % (index + 1), val)
+ rs.set_apply_callback(self.apply_ff_padded_string,
+ aprs.message_macro[index])
+ menu.append(rs)
+
+ path_str = list(self._DIGI_PATHS)
+ path_str[3] = self._digi_path_to_str(aprs.digi_path_3_6[0])
+ val = RadioSettingValueString(0, 22, path_str[3])
+ rs = RadioSetting("aprs.digi_path_3", "Digi Path 4 (2 entries)", val)
+ rs.set_apply_callback(self.apply_digi_path, aprs.digi_path_3_6[0])
+ menu.append(rs)
+
+ path_str[4] = self._digi_path_to_str(aprs.digi_path_3_6[1])
+ val = RadioSettingValueString(0, 22, path_str[4])
+ rs = RadioSetting("aprs.digi_path_4", "Digi Path 5 (2 entries)", val)
+ rs.set_apply_callback(self.apply_digi_path, aprs.digi_path_3_6[1])
+ menu.append(rs)
+
+ path_str[5] = self._digi_path_to_str(aprs.digi_path_3_6[2])
+ val = RadioSettingValueString(0, 22, path_str[5])
+ rs = RadioSetting("aprs.digi_path_5", "Digi Path 6 (2 entries)", val)
+ rs.set_apply_callback(self.apply_digi_path, aprs.digi_path_3_6[2])
+ menu.append(rs)
+
+ path_str[6] = self._digi_path_to_str(aprs.digi_path_3_6[3])
+ val = RadioSettingValueString(0, 22, path_str[6])
+ rs = RadioSetting("aprs.digi_path_6", "Digi Path 7 (2 entries)", val)
+ rs.set_apply_callback(self.apply_digi_path, aprs.digi_path_3_6[3])
+ menu.append(rs)
+
+ path_str[7] = self._digi_path_to_str(aprs.digi_path_7)
+ val = RadioSettingValueString(0, 88, path_str[7])
+ rs = RadioSetting("aprs.digi_path_7", "Digi Path 8 (8 entries)", val)
+ rs.set_apply_callback(self.apply_digi_path, aprs.digi_path_7)
+ menu.append(rs)
+
+ # Show friendly messages for empty slots rather than blanks.
+ # TODO: Rebuild this when digi_path_[34567] change.
+ #path_str[3] = path_str[3] or self._DIGI_PATHS[3]
+ #path_str[4] = path_str[4] or self._DIGI_PATHS[4]
+ #path_str[5] = path_str[5] or self._DIGI_PATHS[5]
+ #path_str[6] = path_str[6] or self._DIGI_PATHS[6]
+ #path_str[7] = path_str[7] or self._DIGI_PATHS[7]
+ path_str[3] = self._DIGI_PATHS[3]
+ path_str[4] = self._DIGI_PATHS[4]
+ path_str[5] = self._DIGI_PATHS[5]
+ path_str[6] = self._DIGI_PATHS[6]
+ path_str[7] = self._DIGI_PATHS[7]
+ val = RadioSettingValueList(path_str,
+ path_str[aprs.selected_digi_path])
+ rs = RadioSetting("aprs.selected_digi_path", "Selected Digi Path", val)
+ menu.append(rs)
+
+ return menu
+
+ def _get_aprs_smartbeacon(self):
+ menu = RadioSettingGroup("aprs_smartbeacon", "APRS SmartBeacon")
+ aprs = self._memobj.aprs
+
+ val = RadioSettingValueList(
+ self._SMARTBEACON_PROFILE,
+ self._SMARTBEACON_PROFILE[aprs.active_smartbeaconing])
+ rs = RadioSetting("aprs.active_smartbeaconing", "SmartBeacon profile",
+ val)
+ menu.append(rs)
+
+ for profile in range(3):
+ pfx = "type%d" % (profile + 1)
+ path = "aprs.smartbeaconing_profile[%d]" % profile
+ prof = aprs.smartbeaconing_profile[profile]
+
+ low_val = RadioSettingValueInteger(2, 30, prof.low_speed_mph)
+ high_val = RadioSettingValueInteger(3, 70, prof.high_speed_mph)
+ low_val.get_max = lambda: min(30, int(high_val.get_value()) - 1)
+
+ rs = RadioSetting("%s.low_speed_mph" % path,
+ "%s Low Speed (mph)" % pfx, low_val)
+ menu.append(rs)
+
+ rs = RadioSetting("%s.high_speed_mph" % path,
+ "%s High Speed (mph)" % pfx, high_val)
+ menu.append(rs)
+
+ val = RadioSettingValueInteger(1, 100, prof.slow_rate_min)
+ rs = RadioSetting("%s.slow_rate_min" % path,
+ "%s Slow rate (minutes)" % pfx, val)
+ menu.append(rs)
+
+ val = RadioSettingValueInteger(10, 180, prof.fast_rate_sec)
+ rs = RadioSetting("%s.fast_rate_sec" % path,
+ "%s Fast rate (seconds)" % pfx, val)
+ menu.append(rs)
+
+ val = RadioSettingValueInteger(5, 90, prof.turn_angle)
+ rs = RadioSetting("%s.turn_angle" % path,
+ "%s Turn angle (degrees)" % pfx, val)
+ menu.append(rs)
+
+ val = RadioSettingValueInteger(1, 255, prof.turn_slop)
+ rs = RadioSetting("%s.turn_slop" % path,
+ "%s Turn slop" % pfx, val)
+ menu.append(rs)
+
+ val = RadioSettingValueInteger(5, 180, prof.turn_time_sec)
+ rs = RadioSetting("%s.turn_time_sec" % path,
+ "%s Turn time (seconds)" % pfx, val)
+ menu.append(rs)
+
+ return menu
+
+ def _get_settings(self):
+ top = RadioSettingGroup("all", "All Settings",
+ self._get_aprs_general_settings(),
+ self._get_aprs_rx_settings(),
+ self._get_aprs_tx_settings(),
+ self._get_aprs_smartbeacon())
+ return top
+
+ def get_settings(self):
+ try:
+ return self._get_settings()
+ except:
+ import traceback
+ print "Failed to parse settings:"
+ traceback.print_exc()
+ return None
+
+ @staticmethod
+ def apply_custom_symbol(setting, obj):
+ # Ensure new value falls within known bounds, otherwise leave it as
+ # it's a custom value from the radio that's outside our list.
+ if setting.value.get_value() in chirp_common.APRS_SYMBOLS:
+ setattr(obj, "custom_symbol",
+ chirp_common.APRS_SYMBOLS.index(setting.value.get_value()))
+
+ @classmethod
+ def _apply_callsign(cls, callsign, obj, default_ssid=None):
+ ssid = default_ssid
+ dash_index = callsign.find("-")
+ if dash_index >= 0:
+ ssid = callsign[dash_index + 1:]
+ callsign = callsign[:dash_index]
+ try:
+ ssid = int(ssid) % 16
+ except ValueError:
+ ssid = default_ssid
+ setattr(obj, "callsign", cls._add_ff_pad(callsign, 6))
+ if ssid is not None:
+ setattr(obj, "ssid", ssid)
+
+ def apply_beacon_type(cls, setting, obj):
+ beacon_type = str(setting.value.get_value())
+ beacon_index = cls._BEACON_TYPE.index(beacon_type)
+ tx_smartbeacon = beacon_index >> 1
+ tx_interval_beacon = beacon_index & 1
+ if tx_interval_beacon:
+ setattr(obj, "tx_interval_beacon", 1)
+ setattr(obj, "tx_smartbeacon", 0)
+ elif tx_smartbeacon:
+ setattr(obj, "tx_interval_beacon", 0)
+ setattr(obj, "tx_smartbeacon", 1)
+ else:
+ setattr(obj, "tx_interval_beacon", 0)
+ setattr(obj, "tx_smartbeacon", 0)
+
+ @classmethod
+ def apply_callsign(cls, setting, obj, default_ssid=None):
+ # Uppercase, strip SSID then FF pad to max string length.
+ callsign = setting.value.get_value().upper()
+ cls._apply_callsign(callsign, obj, default_ssid)
+
+ def apply_digi_path(self, setting, obj):
+ # Parse and map to aprs.digi_path_4_7[0-3] or aprs.digi_path_8
+ # and FF terminate.
+ path = str(setting.value.get_value())
+ callsigns = [c.strip() for c in path.split(",")]
+ for index in range(len(obj.entry)):
+ try:
+ self._apply_callsign(callsigns[index], obj.entry[index], 0)
+ except IndexError:
+ self._apply_callsign("", obj.entry[index], 0)
+ if len(callsigns) > len(obj.entry):
+ raise Exception("This path only supports %d entries" % (index + 1))
+
+ @classmethod
+ def apply_ff_padded_string(cls, setting, obj):
+ # FF pad.
+ val = setting.value.get_value()
+ max_len = getattr(obj, "padded_string").size() / 8
+ val = str(val).rstrip()
+ setattr(obj, "padded_string", cls._add_ff_pad(val, max_len))
+
+ @classmethod
+ def apply_lat_long(cls, setting, obj):
+ name = setting.get_name()
+ is_latitude = name.endswith("latitude")
+ lat_long = setting.value.get_value().strip()
+ sign, l_d, l_m, l_s = cls._str_to_latlong(lat_long, is_latitude)
+ if os.getenv("CHIRP_DEBUG"):
+ print "%s: %d %d %d %d" % (name, sign, l_d, l_m, l_s)
+ setattr(obj, "%s_sign" % name, sign)
+ setattr(obj, "%s_degree" % name, l_d)
+ setattr(obj, "%s_minute" % name, l_m)
+ setattr(obj, "%s_second" % name, l_s)
+
+ def set_settings(self, settings):
+ _mem = self._memobj
+ for element in settings:
+ if not isinstance(element, RadioSetting):
+ self.set_settings(element)
+ continue
+ if not element.changed():
+ continue
+ try:
+ if element.has_apply_callback():
+ print "Using apply callback"
+ try:
+ element.run_apply_callback()
+ except NotImplementedError as e:
+ print e
+ continue
+
+ # Find the object containing setting.
+ obj = _mem
+ bits = element.get_name().split(".")
+ setting = bits[-1]
+ for name in bits[:-1]:
+ if name.endswith("]"):
+ name, index = name.split("[")
+ index = int(index[:-1])
+ obj = getattr(obj, name)[index]
+ else:
+ obj = getattr(obj, name)
+
+ try:
+ old_val = getattr(obj, setting)
+ if os.getenv("CHIRP_DEBUG"):
+ print "Setting %s(%r) <= %s" % (
+ element.get_name(), old_val, element.value)
+ setattr(obj, setting, element.value)
+ except AttributeError as e:
+ print "Setting %s is not in the memory map: %s" % (
+ element.get_name(), e)
+ except Exception, e:
+ print element.get_name()
+ raise
+
+ at directory.register
+class VX8GERadio(VX8DRadio):
+ """Yaesu VX-8GE"""
+ _model = "AH041"
+ VARIANT = "GE"
+ _has_vibrate = True
+ _has_af_dual = False
diff --git a/chirp/wouxun.py b/chirp/wouxun.py
index bb1f3c9..992b9fa 100644
--- a/chirp/wouxun.py
+++ b/chirp/wouxun.py
@@ -20,8 +20,10 @@ import os
from chirp import util, chirp_common, bitwise, memmap, errors, directory
from chirp.settings import RadioSetting, RadioSettingGroup, \
RadioSettingValueBoolean, RadioSettingValueList, \
- RadioSettingValueInteger, RadioSettingValueString
+ RadioSettingValueInteger, RadioSettingValueString, \
+ RadioSettingValueFloat
from chirp.wouxun_common import wipe_memory, do_download, do_upload
+from textwrap import dedent
FREQ_ENCODE_TABLE = [ 0x7, 0xa, 0x0, 0x9, 0xb, 0x2, 0xe, 0x1, 0x3, 0xf ]
@@ -54,7 +56,7 @@ class KGUVD1PRadio(chirp_common.CloneModeRadio,
MODEL = "KG-UVD1P"
_model = "KG669V"
- _querymodel = "HiWOUXUN\x02"
+ _querymodel = ("HiWOUXUN\x02", "PROGUV6X\x02")
CHARSET = list("0123456789") + [chr(x + ord("A")) for x in range(0, 26)] + \
list("?+-")
@@ -83,6 +85,12 @@ class KGUVD1PRadio(chirp_common.CloneModeRadio,
u8 unknown[2];
} memory[199];
+ #seekto 0x0842;
+ u16 fm_presets_0[9];
+
+ #seekto 0x0882;
+ u16 fm_presets_1[9];
+
#seekto 0x0970;
struct {
u16 vhf_rx_start;
@@ -95,10 +103,86 @@ class KGUVD1PRadio(chirp_common.CloneModeRadio,
u16 uhf_tx_stop;
} freq_ranges;
- #seekto 0x0E5C;
+ #seekto 0x0E00;
+ struct {
+ char welcome1[6];
+ char welcome2[6];
+ char single_band[6];
+ } strings;
+
+ #seekto 0x0E20;
struct {
- u8 unknown_flag1:7,
+ u8 unknown_flag_01:6,
+ vfo_b_ch_disp:2;
+ u8 unknown_flag_02:5,
+ vfo_a_fr_step:3;
+ u8 unknown_flag_03:4,
+ vfo_a_squelch:4;
+ u8 unknown_flag_04:7,
+ power_save:1;
+ u8 unknown_flag_05:8;
+ u8 unknown_flag_06:6,
+ roger_beep:2;
+ u8 unknown_flag_07:2,
+ transmit_time_out:6;
+ u8 unknown_flag_08:4,
+ vox:4;
+ u8 unknown_1[4];
+ u8 unknown_flag_09:6,
+ voice:2;
+ u8 unknown_flag_10:7,
+ beep:1;
+ u8 unknown_flag_11:7,
+ ani_id_enable:1;
+ u8 unknown_2[2];
+ u8 unknown_flag_12:5,
+ vfo_b_fr_step:3;
+ u8 unknown_3[1];
+ u8 unknown_flag_13:3,
+ ani_id_tx_delay:5;
+ u8 unknown_4[1];
+ u8 unknown_flag_14:6,
+ ani_id_sidetone:2;
+ u8 unknown_flag_15:4,
+ tx_time_out_alert:4;
+ u8 unknown_flag_16:6,
+ vfo_a_ch_disp:2;
+ u8 unknown_flag_15:6,
+ scan_mode:2;
+ u8 unknown_flag_16:7,
+ kbd_lock:1;
+ u8 unknown_flag_17:6,
+ ponmsg:2;
+ u8 unknown_flag_18:5,
+ pf1_function:3;
+ u8 unknown_5[1];
+ u8 unknown_flag_19:7,
+ auto_backlight:1;
+ u8 unknown_flag_20:7,
+ sos_ch:1;
+ u8 unknown_6;
+ u8 sd_available;
+ u8 unknown_flag_21:7,
+ auto_lock_kbd:1;
+ u8 unknown_flag_22:4,
+ vfo_b_squelch:4;
+ u8 unknown_7[1];
+ u8 unknown_flag_23:7,
+ stopwatch:1;
+ u8 vfo_a_cur_chan;
+ u8 unknown_flag_24:7,
+ dual_band_receive:1;
+ u8 current_vfo:1,
+ unknown_flag_24:7;
+ u8 unknown_8[2];
+ u8 mode_password[6];
+ u8 reset_password[6];
+ u8 ani_id_content[6];
+ u8 unknown_flag_25:7,
menu_available:1;
+ u8 unknown_9[1];
+ u8 priority_chan;
+ u8 vfo_b_cur_chan;
} settings;
#seekto 0x1008;
@@ -110,18 +194,46 @@ class KGUVD1PRadio(chirp_common.CloneModeRadio,
"""
@classmethod
- def get_experimental_warning(cls):
- return ('This version of the Wouxun driver allows you to modify the '
+ def get_prompts(cls):
+ rp = chirp_common.RadioPrompts()
+ rp.experimental = ('This version of the Wouxun driver allows you to modify the '
'frequency range settings of your radio. This has been tested '
'and reports from other users indicate that it is a safe '
'thing to do. However, modifications to this value may have '
'unintended consequences, including damage to your device. '
'You have been warned. Proceed at your own risk!')
+ rp.pre_download = _(dedent("""\
+ 1. Turn radio off.
+ 2. Connect cable to mic/spkr connector.
+ 3. Make sure connector is firmly connected.
+ 4. Turn radio on.
+ 5. Ensure that the radio is tuned to channel with no activity.
+ 6. Click OK to download image from device."""))
+ rp.pre_upload = _(dedent("""\
+ 1. Turn radio off.
+ 2. Connect cable to mic/spkr connector.
+ 3. Make sure connector is firmly connected.
+ 4. Turn radio on.
+ 5. Ensure that the radio is tuned to channel with no activity.
+ 6. Click OK to upload image to device."""))
+ return rp
+
+ @classmethod
+ def _get_querymodel(cls):
+ if isinstance(cls._querymodel, str):
+ while True:
+ yield cls._querymodel
+ else:
+ i = 0
+ while True:
+ yield cls._querymodel[i % len(cls._querymodel)]
+ i += 1
def _identify(self):
"""Do the original wouxun identification dance"""
- for _i in range(0, 5):
- self.pipe.write(self._querymodel)
+ query = self._get_querymodel()
+ for _i in range(0, 10):
+ self.pipe.write(query.next())
resp = self.pipe.read(9)
if len(resp) != 9:
print "Got:\n%s" % util.hexprint(resp)
@@ -218,50 +330,51 @@ class KGUVD1PRadio(chirp_common.CloneModeRadio,
def get_settings(self):
freqranges = RadioSettingGroup("freqranges", "Freq ranges")
- top = RadioSettingGroup("top", "All Settings", freqranges)
+ fm_preset = RadioSettingGroup("fm_preset", "FM Presets")
+ top = RadioSettingGroup("top", "All Settings", freqranges, fm_preset)
rs = RadioSetting("menu_available", "Menu Available",
RadioSettingValueBoolean(
self._memobj.settings.menu_available))
top.append(rs)
- rs = RadioSetting("vhf_rx_start", "VHF RX Lower Limit (MHz)",
- RadioSettingValueInteger(136, 174,
+ rs = RadioSetting("vhf_rx_start", "1st band RX Lower Limit (MHz)",
+ RadioSettingValueInteger(50, 174,
decode_freq(
self._memobj.freq_ranges.vhf_rx_start)))
freqranges.append(rs)
- rs = RadioSetting("vhf_rx_stop", "VHF RX Upper Limit (MHz)",
- RadioSettingValueInteger(136, 174,
+ rs = RadioSetting("vhf_rx_stop", "1st band RX Upper Limit (MHz)",
+ RadioSettingValueInteger(50, 174,
decode_freq(
self._memobj.freq_ranges.vhf_rx_stop)))
freqranges.append(rs)
- rs = RadioSetting("uhf_rx_start", "UHF RX Lower Limit (MHz)",
- RadioSettingValueInteger(216, 520,
+ rs = RadioSetting("uhf_rx_start", "2nd band RX Lower Limit (MHz)",
+ RadioSettingValueInteger(136, 520,
decode_freq(
self._memobj.freq_ranges.uhf_rx_start)))
freqranges.append(rs)
- rs = RadioSetting("uhf_rx_stop", "UHF RX Upper Limit (MHz)",
- RadioSettingValueInteger(216, 520,
+ rs = RadioSetting("uhf_rx_stop", "2nd band RX Upper Limit (MHz)",
+ RadioSettingValueInteger(136, 520,
decode_freq(
self._memobj.freq_ranges.uhf_rx_stop)))
freqranges.append(rs)
- rs = RadioSetting("vhf_tx_start", "VHF TX Lower Limit (MHz)",
- RadioSettingValueInteger(136, 174,
+ rs = RadioSetting("vhf_tx_start", "1st band TX Lower Limit (MHz)",
+ RadioSettingValueInteger(50, 174,
decode_freq(
self._memobj.freq_ranges.vhf_tx_start)))
freqranges.append(rs)
- rs = RadioSetting("vhf_tx_stop", "VHF TX Upper Limit (MHz)",
- RadioSettingValueInteger(136, 174,
+ rs = RadioSetting("vhf_tx_stop", "1st TX Upper Limit (MHz)",
+ RadioSettingValueInteger(50, 174,
decode_freq(
self._memobj.freq_ranges.vhf_tx_stop)))
freqranges.append(rs)
- rs = RadioSetting("uhf_tx_start", "UHF TX Lower Limit (MHz)",
- RadioSettingValueInteger(216, 520,
+ rs = RadioSetting("uhf_tx_start", "2st band TX Lower Limit (MHz)",
+ RadioSettingValueInteger(136, 520,
decode_freq(
self._memobj.freq_ranges.uhf_tx_start)))
freqranges.append(rs)
- rs = RadioSetting("uhf_tx_stop", "UHF TX Upper Limit (MHz)",
- RadioSettingValueInteger(216, 520,
+ rs = RadioSetting("uhf_tx_stop", "2st band TX Upper Limit (MHz)",
+ RadioSettingValueInteger(136, 520,
decode_freq(
self._memobj.freq_ranges.uhf_tx_stop)))
freqranges.append(rs)
@@ -269,19 +382,236 @@ class KGUVD1PRadio(chirp_common.CloneModeRadio,
# tell the decoded ranges to UI
self.valid_freq = [
( decode_freq(self._memobj.freq_ranges.vhf_rx_start) * 1000000,
- (decode_freq(self._memobj.freq_ranges.vhf_rx_stop)+1) * 1000000),
+ (decode_freq(self._memobj.freq_ranges.vhf_rx_stop)+1) * 1000000),
( decode_freq(self._memobj.freq_ranges.uhf_rx_start) * 1000000,
(decode_freq(self._memobj.freq_ranges.uhf_rx_stop)+1) * 1000000)]
+ def _filter(name):
+ filtered = ""
+ for char in str(name):
+ if char in chirp_common.CHARSET_ASCII:
+ filtered += char
+ else:
+ filtered += " "
+ return filtered
+
+ # add some radio specific settings
+ options = ["Off", "Welcome", "V bat"]
+ rs = RadioSetting("ponmsg", "Poweron message",
+ RadioSettingValueList(options,
+ options[self._memobj.settings.ponmsg]))
+ top.append(rs)
+ rs = RadioSetting("strings.welcome1", "Power-On Message 1",
+ RadioSettingValueString(0, 6,
+ _filter(self._memobj.strings.welcome1)))
+ top.append(rs)
+ rs = RadioSetting("strings.welcome2", "Power-On Message 2",
+ RadioSettingValueString(0, 6,
+ _filter(self._memobj.strings.welcome2)))
+ top.append(rs)
+ rs = RadioSetting("strings.single_band", "Single Band Message",
+ RadioSettingValueString(0, 6,
+ _filter(self._memobj.strings.single_band)))
+ top.append(rs)
+ options = ["Channel", "ch/freq","Name", "VFO"]
+ rs = RadioSetting("vfo_a_ch_disp", "VFO A Channel disp mode",
+ RadioSettingValueList(options,
+ options[self._memobj.settings.vfo_a_ch_disp]))
+ top.append(rs)
+ rs = RadioSetting("vfo_b_ch_disp", "VFO B Channel disp mode",
+ RadioSettingValueList(options,
+ options[self._memobj.settings.vfo_b_ch_disp]))
+ top.append(rs)
+ options = ["5.0", "6.25", "10.0", "12.5", "25.0", "50.0", "100.0"]
+ rs = RadioSetting("vfo_a_fr_step", "VFO A Frequency Step",
+ RadioSettingValueList(options,
+ options[self._memobj.settings.vfo_a_fr_step]))
+ top.append(rs)
+ rs = RadioSetting("vfo_b_fr_step", "VFO B Frequency Step",
+ RadioSettingValueList(options,
+ options[self._memobj.settings.vfo_b_fr_step]))
+ top.append(rs)
+ rs = RadioSetting("vfo_a_squelch", "VFO A Squelch",
+ RadioSettingValueInteger(0, 9,
+ self._memobj.settings.vfo_a_squelch))
+ top.append(rs)
+ rs = RadioSetting("vfo_b_squelch", "VFO B Squelch",
+ RadioSettingValueInteger(0, 9,
+ self._memobj.settings.vfo_b_squelch))
+ top.append(rs)
+ rs = RadioSetting("vfo_a_cur_chan", "VFO A current channel",
+ RadioSettingValueInteger(1, 128,
+ self._memobj.settings.vfo_a_cur_chan))
+ top.append(rs)
+ rs = RadioSetting("vfo_b_cur_chan", "VFO B current channel",
+ RadioSettingValueInteger(1, 128,
+ self._memobj.settings.vfo_b_cur_chan))
+ top.append(rs)
+ rs = RadioSetting("priority_chan", "Priority channel",
+ RadioSettingValueInteger(0, 199,
+ self._memobj.settings.priority_chan))
+ top.append(rs)
+ rs = RadioSetting("power_save", "Power save",
+ RadioSettingValueBoolean(
+ self._memobj.settings.power_save))
+ top.append(rs)
+ options = ["Off", "Scan", "Lamp", "SOS", "Radio"]
+ rs = RadioSetting("pf1_function", "PF1 Function select",
+ RadioSettingValueList(options,
+ options[self._memobj.settings.pf1_function]))
+ top.append(rs)
+ options = ["Off", "Begin", "End", "Both"]
+ rs = RadioSetting("roger_beep", "Roger beep select",
+ RadioSettingValueList(options,
+ options[self._memobj.settings.roger_beep]))
+ top.append(rs)
+ options = ["%s" % x for x in range(15, 615, 15)]
+ rs = RadioSetting("transmit_time_out", "TX Time-out Timer",
+ RadioSettingValueList(options,
+ options[self._memobj.settings.transmit_time_out]))
+ top.append(rs)
+ rs = RadioSetting("tx_time_out_alert", "TX Time-out Alert",
+ RadioSettingValueInteger(0, 10,
+ self._memobj.settings.tx_time_out_alert))
+ top.append(rs)
+ rs = RadioSetting("vox", "Vox",
+ RadioSettingValueInteger(0, 10,
+ self._memobj.settings.vox))
+ top.append(rs)
+ options = ["Off", "Chinese", "English"]
+ rs = RadioSetting("voice", "Voice",
+ RadioSettingValueList(options,
+ options[self._memobj.settings.voice]))
+ top.append(rs)
+ rs = RadioSetting("beep", "Beep",
+ RadioSettingValueBoolean(self._memobj.settings.beep))
+ top.append(rs)
+ rs = RadioSetting("ani_id_enable", "ANI id enable",
+ RadioSettingValueBoolean(
+ self._memobj.settings.ani_id_enable))
+ top.append(rs)
+ rs = RadioSetting("ani_id_tx_delay", "ANI id tx delay",
+ RadioSettingValueInteger(0, 30,
+ self._memobj.settings.ani_id_tx_delay))
+ top.append(rs)
+ options = ["Off", "Key", "ANI", "Key+ANI"]
+ rs = RadioSetting("ani_id_sidetone", "ANI id sidetone",
+ RadioSettingValueList(options,
+ options[self._memobj.settings.ani_id_sidetone]))
+ top.append(rs)
+ options = ["Time", "Carrier", "Search"]
+ rs = RadioSetting("scan_mode", "Scan mode",
+ RadioSettingValueList(options,
+ options[self._memobj.settings.scan_mode]))
+ top.append(rs)
+ rs = RadioSetting("kbd_lock", "Keyboard lock",
+ RadioSettingValueBoolean(
+ self._memobj.settings.kbd_lock))
+ top.append(rs)
+ rs = RadioSetting("auto_lock_kbd", "Auto lock keyboard",
+ RadioSettingValueBoolean(
+ self._memobj.settings.auto_lock_kbd))
+ top.append(rs)
+ rs = RadioSetting("auto_backlight", "Auto backlight",
+ RadioSettingValueBoolean(
+ self._memobj.settings.auto_backlight))
+ top.append(rs)
+ options = ["CH A", "CH B"]
+ rs = RadioSetting("sos_ch", "SOS CH",
+ RadioSettingValueList(options,
+ options[self._memobj.settings.sos_ch]))
+ top.append(rs)
+ rs = RadioSetting("stopwatch", "Stopwatch",
+ RadioSettingValueBoolean(
+ self._memobj.settings.stopwatch))
+ top.append(rs)
+ rs = RadioSetting("dual_band_receive", "Dual band receive",
+ RadioSettingValueBoolean(
+ self._memobj.settings.dual_band_receive))
+ top.append(rs)
+ options = ["VFO A", "VFO B"]
+ rs = RadioSetting("current_vfo", "Current VFO",
+ RadioSettingValueList(options,
+ options[self._memobj.settings.current_vfo]))
+ top.append(rs)
+
+ options = ["Dual", "Single"]
+ rs = RadioSetting("sd_available", "Single/Dual Band",
+ RadioSettingValueList(options,
+ options[self._memobj.settings.sd_available]))
+ top.append(rs)
+
+ _pwd = self._memobj.settings.mode_password
+ rs = RadioSetting("mode_password", "Mode password (000000 disabled)",
+ RadioSettingValueInteger(0, 9, _pwd[0]),
+ RadioSettingValueInteger(0, 9, _pwd[1]),
+ RadioSettingValueInteger(0, 9, _pwd[2]),
+ RadioSettingValueInteger(0, 9, _pwd[3]),
+ RadioSettingValueInteger(0, 9, _pwd[4]),
+ RadioSettingValueInteger(0, 9, _pwd[5]))
+ top.append(rs)
+ _pwd = self._memobj.settings.reset_password
+ rs = RadioSetting("reset_password", "Reset password (000000 disabled)",
+ RadioSettingValueInteger(0, 9, _pwd[0]),
+ RadioSettingValueInteger(0, 9, _pwd[1]),
+ RadioSettingValueInteger(0, 9, _pwd[2]),
+ RadioSettingValueInteger(0, 9, _pwd[3]),
+ RadioSettingValueInteger(0, 9, _pwd[4]),
+ RadioSettingValueInteger(0, 9, _pwd[5]))
+ top.append(rs)
+
+ dtmfchars = "0123456789 *#ABCD"
+ _codeobj = self._memobj.settings.ani_id_content
+ _code = "".join([dtmfchars[x] for x in _codeobj if int(x) < 0x1F])
+ val = RadioSettingValueString(0, 6, _code, False)
+ val.set_charset(dtmfchars)
+ rs = RadioSetting("settings.ani_id_content", "PTT-ID Code", val)
+ def apply_ani_id(setting, obj):
+ value = []
+ for j in range(0, 6):
+ try:
+ value.append(dtmfchars.index(str(setting.value)[j]))
+ except IndexError:
+ value.append(0xFF)
+ obj.ani_id_content = value
+ rs.set_apply_callback(apply_ani_id, self._memobj.settings)
+ top.append(rs)
+
+ for i in range(0, 9):
+ if self._memobj.fm_presets_0[i] != 0xFFFF:
+ used = True
+ preset = self._memobj.fm_presets_0[i]/10.0+76
+ else:
+ used = False
+ preset = 76
+ rs = RadioSetting("fm_presets_0_%1i" % i, "Team 1 Location %i" % (i+1),
+ RadioSettingValueBoolean(used),
+ RadioSettingValueFloat(76, 108, preset, 0.1, 1))
+ fm_preset.append(rs)
+ for i in range(0, 9):
+ if self._memobj.fm_presets_1[i] != 0xFFFF:
+ used = True
+ preset = self._memobj.fm_presets_1[i]/10.0+76
+ else:
+ used = False
+ preset = 76
+ rs = RadioSetting("fm_presets_1_%1i" % i, "Team 2 Location %i" % (i+1),
+ RadioSettingValueBoolean(used),
+ RadioSettingValueFloat(76, 108, preset, 0.1, 1))
+ fm_preset.append(rs)
+
return top
def set_settings(self, settings):
for element in settings:
if not isinstance(element, RadioSetting):
- if element.get_name() != "freqranges" :
- self.set_settings(element)
- else:
+ if element.get_name() == "freqranges" :
self._set_freq_settings(element)
+ elif element.get_name() == "fm_preset" :
+ self._set_fm_preset(element)
+ else:
+ self.set_settings(element)
+ continue
else:
try:
if "." in element.get_name():
@@ -293,12 +623,37 @@ class KGUVD1PRadio(chirp_common.CloneModeRadio,
else:
obj = self._memobj.settings
setting = element.get_name()
- print "Setting %s = %s" % (setting, element.value)
- setattr(obj, setting, element.value)
+
+ if element.has_apply_callback():
+ print "Using apply callback"
+ element.run_apply_callback()
+ else:
+ print "Setting %s = %s" % (setting, element.value)
+ setattr(obj, setting, element.value)
except Exception, e:
print element.get_name()
raise
+ def _set_fm_preset(self, settings):
+ obj = self._memobj
+ for element in settings:
+ try:
+ (bank, index) = (int(a) for a in element.get_name().split("_")[-2:])
+ val = element.value
+ if val[0].get_value():
+ value = int(val[1].get_value()*10-760)
+ else:
+ value = 0xffff
+ print "Setting fm_presets_%1i[%1i] = %s" % (bank, index, value)
+ if bank == 0:
+ setting = self._memobj.fm_presets_0
+ else:
+ setting = self._memobj.fm_presets_1
+ setting[index] = value
+ except Exception, e:
+ print element.get_name()
+ raise
+
def _set_freq_settings(self, settings):
for element in settings:
try:
@@ -317,7 +672,8 @@ class KGUVD1PRadio(chirp_common.CloneModeRadio,
code = int("%03o" % (val & 0x07FF))
pol = (val & 0x8000) and "R" or "N"
return code, pol
-
+
+ tpol = False
if _mem.tx_tone != 0xFFFF and _mem.tx_tone > 0x2800:
tcode, tpol = _get_dcs(_mem.tx_tone)
mem.dtcs = tcode
@@ -328,6 +684,7 @@ class KGUVD1PRadio(chirp_common.CloneModeRadio,
else:
txmode = ""
+ rpol = False
if _mem.rx_tone != 0xFFFF and _mem.rx_tone > 0x2800:
rcode, rpol = _get_dcs(_mem.rx_tone)
mem.rx_dtcs = rcode
@@ -348,8 +705,8 @@ class KGUVD1PRadio(chirp_common.CloneModeRadio,
mem.tmode = "Cross"
mem.cross_mode = "%s->%s" % (txmode, rxmode)
- if mem.tmode == "DTCS":
- mem.dtcs_polarity = "%s%s" % (tpol, rpol)
+ # always set it even if no dtcs is used
+ mem.dtcs_polarity = "%s%s" % (tpol or "N", rpol or "N")
if os.getenv("CHIRP_DEBUG"):
print "Got TX %s (%i) RX %s (%i)" % (txmode, _mem.tx_tone,
@@ -415,7 +772,7 @@ class KGUVD1PRadio(chirp_common.CloneModeRadio,
def _set_dcs(code, pol):
val = int("%i" % code, 8) + 0x2800
if pol == "R":
- val += 0xA000
+ val += 0x8000
return val
if mem.tmode == "Cross":
@@ -513,7 +870,7 @@ class KGUV6DRadio(KGUVD1PRadio):
"""Wouxun KG-UV6 (D and X variants)"""
MODEL = "KG-UV6"
- _querymodel = "HiWXUVD1\x02"
+ _querymodel = ("HiWXUVD1\x02", "HiKGUVD1\x02")
_MEM_FORMAT = """
#seekto 0x0010;
@@ -591,7 +948,8 @@ class KGUV6DRadio(KGUVD1PRadio):
auto_backlight:1;
u8 unknown_flag_20:7,
sos_ch:1;
- u8 unknown_6[2];
+ u8 unknown_6;
+ u8 sd_available;
u8 unknown_flag_21:7,
auto_lock_kbd:1;
u8 unknown_flag_22:4,
@@ -632,7 +990,7 @@ class KGUV6DRadio(KGUVD1PRadio):
u8 pad[2];
} vfo_settings[2];
- #seekto 0x0f80;
+ #seekto 0x0f82;
u16 fm_presets_0[9];
#seekto 0x0ff0;
@@ -661,7 +1019,7 @@ class KGUV6DRadio(KGUVD1PRadio):
u8 pad[9];
} vfo_offset[2];
- #seekto 0x1f80;
+ #seekto 0x1f82;
u16 fm_presets_1[9];
"""
@@ -673,7 +1031,8 @@ class KGUV6DRadio(KGUVD1PRadio):
def get_settings(self):
freqranges = RadioSettingGroup("freqranges", "Freq ranges")
- top = RadioSettingGroup("top", "All Settings", freqranges)
+ fm_preset = RadioSettingGroup("fm_preset", "FM Presets")
+ top = RadioSettingGroup("top", "All Settings", freqranges, fm_preset)
rs = RadioSetting("menu_available", "Menu Available",
RadioSettingValueBoolean(
@@ -761,8 +1120,15 @@ class KGUV6DRadio(KGUVD1PRadio):
RadioSettingValueList(options,
options[self._memobj.settings.vfo_b_ch_disp]))
top.append(rs)
- # TODO - vfo_a_fr_step
- # TODO -vfo_b_fr_step:3;
+ options = ["2.5", "5.0", "6.25", "10.0", "12.5", "25.0", "50.0", "100.0"]
+ rs = RadioSetting("vfo_a_fr_step", "VFO A Frequency Step",
+ RadioSettingValueList(options,
+ options[self._memobj.settings.vfo_a_fr_step]))
+ top.append(rs)
+ rs = RadioSetting("vfo_b_fr_step", "VFO B Frequency Step",
+ RadioSettingValueList(options,
+ options[self._memobj.settings.vfo_b_fr_step]))
+ top.append(rs)
rs = RadioSetting("vfo_a_squelch", "VFO A Squelch",
RadioSettingValueInteger(0, 9, self._memobj.settings.vfo_a_squelch))
top.append(rs)
@@ -773,7 +1139,7 @@ class KGUV6DRadio(KGUVD1PRadio):
RadioSettingValueInteger(1, 199, self._memobj.settings.vfo_a_cur_chan))
top.append(rs)
rs = RadioSetting("vfo_b_cur_chan", "VFO B current channel",
- RadioSettingValueInteger(0, 199, self._memobj.settings.vfo_b_cur_chan))
+ RadioSettingValueInteger(1, 199, self._memobj.settings.vfo_b_cur_chan))
top.append(rs)
rs = RadioSetting("priority_chan", "Priority channel",
RadioSettingValueInteger(0, 199, self._memobj.settings.priority_chan))
@@ -796,7 +1162,14 @@ class KGUV6DRadio(KGUVD1PRadio):
RadioSettingValueList(options,
options[self._memobj.settings.roger_beep]))
top.append(rs)
- # TODO - transmit_time_out:6;
+ options = ["%s" % x for x in range(15, 615, 15)]
+ rs = RadioSetting("transmit_time_out", "TX Time-out Timer",
+ RadioSettingValueList(options,
+ options[self._memobj.settings.transmit_time_out]))
+ top.append(rs)
+ rs = RadioSetting("tx_time_out_alert", "TX Time-out Alert",
+ RadioSettingValueInteger(0, 10, self._memobj.settings.tx_time_out_alert))
+ top.append(rs)
rs = RadioSetting("vox", "Vox",
RadioSettingValueInteger(0, 10, self._memobj.settings.vox))
top.append(rs)
@@ -819,7 +1192,6 @@ class KGUV6DRadio(KGUVD1PRadio):
RadioSettingValueList(options,
options[self._memobj.settings.ani_id_sidetone]))
top.append(rs)
- # TODO tx_time_out_alert:4;
options = ["Time", "Carrier", "Search"]
rs = RadioSetting("scan_mode", "Scan mode",
RadioSettingValueList(options,
@@ -850,6 +1222,13 @@ class KGUV6DRadio(KGUVD1PRadio):
RadioSettingValueList(options,
options[self._memobj.settings.current_vfo]))
top.append(rs)
+
+ options = ["Dual", "Single"]
+ rs = RadioSetting("sd_available", "Single/Dual Band",
+ RadioSettingValueList(options,
+ options[self._memobj.settings.sd_available]))
+ top.append(rs)
+
_pwd = self._memobj.settings.mode_password
rs = RadioSetting("mode_password", "Mode password (000000 disabled)",
RadioSettingValueInteger(0, 9, _pwd[0]),
@@ -868,22 +1247,100 @@ class KGUV6DRadio(KGUVD1PRadio):
RadioSettingValueInteger(0, 9, _pwd[4]),
RadioSettingValueInteger(0, 9, _pwd[5]))
top.append(rs)
- try:
- _ani = self._memobj.settings.ani_id_content
- rs = RadioSetting("ani_id_content", "ANI Code",
- RadioSettingValueInteger(0, 9, _ani[0]),
- RadioSettingValueInteger(0, 9, _ani[1]),
- RadioSettingValueInteger(0, 9, _ani[2]),
- RadioSettingValueInteger(0, 9, _ani[3]),
- RadioSettingValueInteger(0, 9, _ani[4]),
- RadioSettingValueInteger(0, 9, _ani[5]))
- top.append(rs)
- except Exception:
- print ("Your ANI code is not five digits, which is not currently"
- " supported in CHIRP.")
+
+ dtmfchars = "0123456789 *#ABCD"
+ _codeobj = self._memobj.settings.ani_id_content
+ _code = "".join([dtmfchars[x] for x in _codeobj if int(x) < 0x1F])
+ val = RadioSettingValueString(0, 6, _code, False)
+ val.set_charset(dtmfchars)
+ rs = RadioSetting("settings.ani_id_content", "ANI Code", val)
+ def apply_ani_id(setting, obj):
+ value = []
+ for j in range(0, 6):
+ try:
+ value.append(dtmfchars.index(str(setting.value)[j]))
+ except IndexError:
+ value.append(0xFF)
+ obj.ani_id_content = value
+ rs.set_apply_callback(apply_ani_id, self._memobj.settings)
+ top.append(rs)
+
+ for i in range(0, 9):
+ if self._memobj.fm_presets_0[i] != 0xFFFF:
+ used = True
+ preset = self._memobj.fm_presets_0[i]/10.0+76
+ else:
+ used = False
+ preset = 76
+ rs = RadioSetting("fm_presets_0_%1i" % i, "Team 1 Location %i" % (i+1),
+ RadioSettingValueBoolean(used),
+ RadioSettingValueFloat(76, 108, preset, 0.1, 1))
+ fm_preset.append(rs)
+ for i in range(0, 9):
+ if self._memobj.fm_presets_1[i] != 0xFFFF:
+ used = True
+ preset = self._memobj.fm_presets_1[i]/10.0+76
+ else:
+ used = False
+ preset = 76
+ rs = RadioSetting("fm_presets_1_%1i" % i, "Team 2 Location %i" % (i+1),
+ RadioSettingValueBoolean(used),
+ RadioSettingValueFloat(76, 108, preset, 0.1, 1))
+ fm_preset.append(rs)
return top
+ def set_settings(self, settings):
+ for element in settings:
+ if not isinstance(element, RadioSetting):
+ if element.get_name() == "freqranges" :
+ self._set_freq_settings(element)
+ elif element.get_name() == "fm_preset" :
+ self._set_fm_preset(element)
+ else:
+ self.set_settings(element)
+ 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 element.has_apply_callback():
+ print "Using apply callback"
+ element.run_apply_callback()
+ else:
+ print "Setting %s = %s" % (setting, element.value)
+ setattr(obj, setting, element.value)
+ except Exception, e:
+ print element.get_name()
+ raise
+
+ def _set_fm_preset(self, settings):
+ obj = self._memobj
+ for element in settings:
+ try:
+ (bank, index) = (int(a) for a in element.get_name().split("_")[-2:])
+ val = element.value
+ if val[0].get_value():
+ value = int(val[1].get_value()*10-760)
+ else:
+ value = 0xffff
+ print "Setting fm_presets_%1i[%1i] = %s" % (bank, index, value)
+ if bank == 0:
+ setting = self._memobj.fm_presets_0
+ else:
+ setting = self._memobj.fm_presets_1
+ setting[index] = value
+ except Exception, e:
+ print element.get_name()
+ raise
+
@classmethod
def match_model(cls, filedata, filename):
if len(filedata) == 8192 and \
@@ -894,9 +1351,11 @@ class KGUV6DRadio(KGUVD1PRadio):
@directory.register
class KG816Radio(KGUVD1PRadio,
chirp_common.ExperimentalRadio):
- """Wouxun KG816"""
- MODEL = "KG816"
+ """Wouxun KG-816"""
+ MODEL = "KG-816"
+ _querymodel = "HiWOUXUN\x02"
+
_MEM_FORMAT = """
#seekto 0x0010;
struct {
@@ -953,42 +1412,42 @@ class KG816Radio(KGUVD1PRadio,
top = RadioSettingGroup("top", "All Settings", freqranges)
rs = RadioSetting("vhf_rx_start", "vhf rx start",
- RadioSettingValueInteger(136, 520,
+ RadioSettingValueInteger(66, 520,
decode_freq(
self._memobj.freq_ranges.vhf_rx_start)))
freqranges.append(rs)
rs = RadioSetting("vhf_rx_stop", "vhf rx stop",
- RadioSettingValueInteger(136, 520,
+ RadioSettingValueInteger(66, 520,
decode_freq(
self._memobj.freq_ranges.vhf_rx_stop)))
freqranges.append(rs)
rs = RadioSetting("uhf_rx_start", "uhf rx start",
- RadioSettingValueInteger(136, 520,
+ RadioSettingValueInteger(66, 520,
decode_freq(
self._memobj.freq_ranges.uhf_rx_start)))
freqranges.append(rs)
rs = RadioSetting("uhf_rx_stop", "uhf rx stop",
- RadioSettingValueInteger(136, 520,
+ RadioSettingValueInteger(66, 520,
decode_freq(
self._memobj.freq_ranges.uhf_rx_stop)))
freqranges.append(rs)
rs = RadioSetting("vhf_tx_start", "vhf tx start",
- RadioSettingValueInteger(136, 520,
+ RadioSettingValueInteger(66, 520,
decode_freq(
self._memobj.freq_ranges.vhf_tx_start)))
freqranges.append(rs)
rs = RadioSetting("vhf_tx_stop", "vhf tx stop",
- RadioSettingValueInteger(136, 520,
+ RadioSettingValueInteger(66, 520,
decode_freq(
self._memobj.freq_ranges.vhf_tx_stop)))
freqranges.append(rs)
rs = RadioSetting("uhf_tx_start", "uhf tx start",
- RadioSettingValueInteger(136, 520,
+ RadioSettingValueInteger(66, 520,
decode_freq(
self._memobj.freq_ranges.uhf_tx_start)))
freqranges.append(rs)
rs = RadioSetting("uhf_tx_stop", "uhf tx stop",
- RadioSettingValueInteger(136, 520,
+ RadioSettingValueInteger(66, 520,
decode_freq(
self._memobj.freq_ranges.uhf_tx_stop)))
freqranges.append(rs)
@@ -1010,3 +1469,12 @@ class KG816Radio(KGUVD1PRadio,
return True
return False
+
+ at directory.register
+class KG818Radio(KG816Radio):
+ """Wouxun KG-818"""
+ MODEL = "KG-818"
+
+ @classmethod
+ def match_model(cls, filedata, filename):
+ return False
diff --git a/chirp/yaesu_clone.py b/chirp/yaesu_clone.py
index d94b796..fd8419b 100644
--- a/chirp/yaesu_clone.py
+++ b/chirp/yaesu_clone.py
@@ -17,6 +17,7 @@ CMD_ACK = 0x06
from chirp import chirp_common, util, memmap, errors
import time, os
+from textwrap import dedent
def _safe_read(pipe, count):
buf = ""
@@ -178,6 +179,21 @@ class YaesuCloneModeRadio(chirp_common.CloneModeRadio):
VENDOR = "Yaesu"
_model = "ABCDE"
+ @classmethod
+ def get_prompts(cls):
+ rp = chirp_common.RadioPrompts()
+ rp.pre_download = _(dedent("""\
+ 1. Turn radio off.
+ 2. Connect data cable.
+ 3. Prepare radio for clone.
+ 4. <b>After clicking OK</b>, press the key to send image."""))
+ rp.pre_upload = _(dedent("""\
+ 1. Turn radio off.
+ 2. Connect data cable.
+ 3. Prepare radio for clone.
+ 4. Press the key to receive the image."""))
+ return rp
+
def _checksums(self):
"""Return a list of checksum objects that need to be calculated"""
return []
@@ -211,5 +227,5 @@ class YaesuCloneModeRadio(chirp_common.CloneModeRadio):
def _wipe_memory_banks(self, mem):
"""Remove @mem from all the banks it is currently in"""
bm = self.get_bank_model()
- for bank in bm.get_memory_banks(mem):
- bm.remove_memory_from_bank(mem, bank)
+ for bank in bm.get_memory_mappings(mem):
+ bm.remove_memory_from_mapping(mem, bank)
diff --git a/chirpui/bandplans.py b/chirpui/bandplans.py
new file mode 100644
index 0000000..9d34cd1
--- /dev/null
+++ b/chirpui/bandplans.py
@@ -0,0 +1,107 @@
+# Copyright 2013 Sean Burford <sburford at google.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 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 gtk
+from chirp import bandplan, bandplan_na, bandplan_au
+from chirp import bandplan_iaru_r1, bandplan_iaru_r2, bandplan_iaru_r3
+from chirpui import inputdialog
+
+
+class BandPlans(object):
+ def __init__(self, config):
+ self._config = config
+ self.plans = {}
+
+ # Migrate old "automatic repeater offset" setting to
+ # "North American Amateur Band Plan"
+ ro = self._config.get("autorpt", "memedit")
+ if ro is not None:
+ self._config.set_bool("north_america", ro, "bandplan")
+ self._config.remove_option("autorpt", "memedit")
+ # And default new users to North America.
+ if not self._config.is_defined("north_america", "bandplan"):
+ self._config.set_bool("north_america", True, "bandplan")
+
+ for plan in (bandplan_na, bandplan_au, bandplan_iaru_r1,
+ bandplan_iaru_r2, bandplan_iaru_r3):
+ name = plan.DESC.get("name", plan.SHORTNAME)
+ self.plans[plan.SHORTNAME] = (name, plan)
+
+ rpt_inputs = []
+ for band in plan.BANDS:
+ # Check for duplicates.
+ duplicates = [x for x in plan.BANDS if x == band]
+ if len(duplicates) > 1:
+ print "Bandplan %s has duplicates %s" % (name, duplicates)
+ # Add repeater inputs.
+ rpt_input = band.inverse()
+ if rpt_input not in plan.BANDS:
+ rpt_inputs.append(band.inverse())
+ plan.bands = list(plan.BANDS)
+ plan.bands.extend(rpt_inputs)
+
+ def get_defaults_for_frequency(self, freq):
+ freq = int(freq)
+ result = bandplan.Band((freq, freq), repr(freq))
+
+ for shortname, details in self.plans.items():
+ if self._config.get_bool(shortname, "bandplan"):
+ matches = [x for x in details[1].bands if x.contains(result)]
+ # Add matches to defaults, favoring more specific matches.
+ matches = sorted(matches, key=lambda x: x.width(),
+ reverse=True)
+ for match in matches:
+ result.mode = match.mode or result.mode
+ result.step_khz = match.step_khz or result.step_khz
+ result.offset = match.offset or result.offset
+ result.duplex = match.duplex or result.duplex
+ result.tones = match.tones or result.tones
+ if match.name:
+ result.name = '/'.join((result.name or '', match.name))
+ # Limit ourselves to one band plan match for simplicity.
+ # Note that if the user selects multiple band plans by editing
+ # the config file it will work as expected (except where plans
+ # conflict).
+ if matches:
+ break
+
+ return result
+
+ def select_bandplan(self, parent_window):
+ plans = ["None"]
+ for shortname, details in self.plans.iteritems():
+ if self._config.get_bool(shortname, "bandplan"):
+ plans.insert(0, details[0])
+ else:
+ plans.append(details[0])
+
+ d = inputdialog.ChoiceDialog(plans, parent=parent_window,
+ title="Choose Defaults")
+ d.label.set_text(_("Band plans define default channel settings for "
+ "frequencies in a region. Choose a band plan "
+ "or None for completely manual channel "
+ "settings."))
+ d.label.set_line_wrap(True)
+ r = d.run()
+
+ if r == gtk.RESPONSE_OK:
+ selection = d.choice.get_active_text()
+ for shortname, details in self.plans.iteritems():
+ self._config.set_bool(shortname, selection == details[0],
+ "bandplan")
+ if selection == details[0]:
+ print "Selected band plan %s: %s" % (shortname, selection)
+
+ d.destroy()
diff --git a/chirpui/bankedit.py b/chirpui/bankedit.py
index 23c05da..a6cb71e 100644
--- a/chirpui/bankedit.py
+++ b/chirpui/bankedit.py
@@ -22,69 +22,69 @@ from gobject import TYPE_INT, TYPE_STRING, TYPE_BOOLEAN
from chirp import chirp_common
from chirpui import common, miscwidgets
-class BankNamesJob(common.RadioJob):
- def __init__(self, bm, editor, cb):
+class MappingNamesJob(common.RadioJob):
+ def __init__(self, model, editor, cb):
common.RadioJob.__init__(self, cb, None)
- self.__bm = bm
+ self.__model = model
self.__editor = editor
def execute(self, radio):
- self.__editor.banks = []
+ self.__editor.mappings = []
- banks = self.__bm.get_banks()
- for bank in banks:
- self.__editor.banks.append((bank, bank.get_name()))
+ mappings = self.__model.get_mappings()
+ for mapping in mappings:
+ self.__editor.mappings.append((mapping, mapping.get_name()))
gobject.idle_add(self.cb, *self.cb_args)
-class BankNameEditor(common.Editor):
+class MappingNameEditor(common.Editor):
def refresh(self):
- def got_banks():
+ def got_mappings():
self._keys = []
- for bank, name in self.banks:
- self._keys.append(bank.get_index())
- self.listw.set_item(bank.get_index(),
- bank.get_index(),
+ for mapping, name in self.mappings:
+ self._keys.append(mapping.get_index())
+ self.listw.set_item(mapping.get_index(),
+ mapping.get_index(),
name)
- self.listw.connect("item-set", self.bank_changed)
+ self.listw.connect("item-set", self.mapping_changed)
- job = BankNamesJob(self._bm, self, got_banks)
- job.set_desc(_("Retrieving bank information"))
+ job = MappingNamesJob(self._model, self, got_mappings)
+ job.set_desc(_("Retrieving %s information") % self._type)
self.rthread.submit(job)
- def get_bank_list(self):
- banks = []
+ def get_mapping_list(self):
+ mappings = []
keys = self.listw.get_keys()
for key in keys:
- banks.append(self.listw.get_item(key)[2])
+ mappings.append(self.listw.get_item(key)[2])
- return banks
-
- def bank_changed(self, listw, key):
+ return mappings
+
+ def mapping_changed(self, listw, key):
def cb(*args):
self.emit("changed")
name = self.listw.get_item(key)[2]
- bank, oldname = self.banks[self._keys.index(key)]
+ mapping, oldname = self.mappings[self._keys.index(key)]
def trigger_changed(*args):
self.emit("changed")
job = common.RadioJob(trigger_changed, "set_name", name)
- job.set_target(bank)
- job.set_desc(_("Setting name on bank"))
+ job.set_target(mapping)
+ job.set_desc(_("Setting name on %s") % self._type.lower())
self.rthread.submit(job)
return True
- def __init__(self, rthread):
- common.Editor.__init__(self)
- self.rthread = rthread
- self._bm = rthread.radio.get_bank_model()
+ def __init__(self, rthread, model):
+ super(MappingNameEditor, self).__init__(rthread)
+ self._model = model
+ self._type = common.unpluralize(model.get_name())
types = [(gobject.TYPE_STRING, "key"),
- (gobject.TYPE_STRING, _("Bank")),
+ (gobject.TYPE_STRING, self._type),
(gobject.TYPE_STRING, _("Name"))]
self.listw = miscwidgets.KeyedListWidget(types)
@@ -93,7 +93,7 @@ class BankNameEditor(common.Editor):
self.listw.set_sort_column(1, -1)
self.listw.show()
- self.banks = []
+ self.mappings = []
sw = gtk.ScrolledWindow()
sw.set_policy(gtk.POLICY_AUTOMATIC, gtk.POLICY_AUTOMATIC)
@@ -109,51 +109,60 @@ class BankNameEditor(common.Editor):
self.refresh()
self._loaded = True
-class MemoryBanksJob(common.RadioJob):
- def __init__(self, bm, cb, number):
+ def other_editor_changed(self, target_editor):
+ self._loaded = False
+ if self.is_focused():
+ self.refresh_all_memories()
+
+ def mappings_changed(self):
+ pass
+
+class MemoryMappingsJob(common.RadioJob):
+ def __init__(self, model, cb, number):
common.RadioJob.__init__(self, cb, None)
- self.__bm = bm
+ self.__model = model
self.__number = number
def execute(self, radio):
mem = radio.get_memory(self.__number)
if mem.empty:
- banks = []
+ mappings = []
indexes = []
else:
- banks = self.__bm.get_memory_banks(mem)
+ mappings = self.__model.get_memory_mappings(mem)
indexes = []
- if isinstance(self.__bm, chirp_common.BankIndexInterface):
- for bank in banks:
- indexes.append(self.__bm.get_memory_index(mem, bank))
- self.cb(mem, banks, indexes, *self.cb_args)
-
-class BankMembershipEditor(common.Editor):
+ if isinstance(self.__model,
+ chirp_common.MappingModelIndexInterface):
+ for mapping in mappings:
+ indexes.append(self.__model.get_memory_index(mem, mapping))
+ self.cb(mem, mappings, indexes, *self.cb_args)
+
+class MappingMembershipEditor(common.Editor):
def _number_to_path(self, number):
return (number - self._rf.memory_bounds[0],)
- def _get_next_bank_index(self, bank):
- # NB: Only works for one-to-one bank models right now!
+ def _get_next_mapping_index(self, mapping):
+ # NB: Only works for one-to-one models right now!
iter = self._store.get_iter_first()
indexes = []
- ncols = len(self._cols) + len(self.banks)
+ ncols = len(self._cols) + len(self.mappings)
while iter:
vals = self._store.get(iter, *tuple([n for n in range(0, ncols)]))
loc = vals[self.C_LOC]
index = vals[self.C_INDEX]
- banks = vals[self.C_BANKS:]
- if True in banks and banks.index(True) == bank:
+ mappings = vals[self.C_MAPPINGS:]
+ if True in mappings and mappings.index(True) == mapping:
indexes.append(index)
iter = self._store.iter_next(iter)
- index_bounds = self._bm.get_index_bounds()
+ index_bounds = self._model.get_index_bounds()
num_indexes = index_bounds[1] - index_bounds[0]
indexes.sort()
for i in range(0, num_indexes):
if i not in indexes:
return i + index_bounds[0] # In case not zero-origin index
- return 0 # If the bank is full, just wrap around!
+ return 0 # If the mapping is full, just wrap around!
def _toggled_cb(self, rend, path, colnum):
try:
@@ -165,56 +174,60 @@ class BankMembershipEditor(common.Editor):
if not self._store.get(iter, self.C_FILLED)[0]:
return
- # The bank index is the column number, minus the 3 label columns
- bank, name = self.banks[colnum - len(self._cols)]
+ # The mapping index is the column number, minus the 3 label columns
+ mapping, name = self.mappings[colnum - len(self._cols)]
loc, = self._store.get(self._store.get_iter(path), self.C_LOC)
+ is_indexed = isinstance(self._model,
+ chirp_common.MappingModelIndexInterface)
+
if rend.get_active():
# Changing from True to False
- fn = "remove_memory_from_bank"
+ fn = "remove_memory_from_mapping"
index = None
else:
# Changing from False to True
- fn = "add_memory_to_bank"
- if self._rf.has_bank_index:
- index = self._get_next_bank_index(colnum - len(self._cols))
+ fn = "add_memory_to_mapping"
+ if is_indexed:
+ index = self._get_next_mapping_index(colnum - len(self._cols))
else:
index = None
def do_refresh_memory(*args):
- # Step 2: Update our notion of the memory's bank information
+ # Step 2: Update our notion of the memory's mapping information
self.refresh_memory(loc)
- def do_bank_index(result, memory):
+ def do_mapping_index(result, memory):
if isinstance(result, Exception):
- common.show_error("Failed to add {mem} to bank: {err}"
+ common.show_error("Failed to add {mem} to mapping: {err}"
.format(mem=memory.number,
err=str(result)),
parent=self.editorset.parent_window)
return
self.emit("changed")
- # Step 3: Set the memory's bank index (maybe)
- if not self._rf.has_bank_index or index is None:
+ # Step 3: Set the memory's mapping index (maybe)
+ if not is_indexed or index is None:
return do_refresh_memory()
job = common.RadioJob(do_refresh_memory,
- "set_memory_index", memory, bank, index)
- job.set_target(self._bm)
- job.set_desc(_("Updating bank index "
- "for memory {num}").format(num=memory.number))
+ "set_memory_index", memory, mapping, index)
+ job.set_target(self._model)
+ job.set_desc(_("Updating {type} index "
+ "for memory {num}").format(type=self._type,
+ num=memory.number))
self.rthread.submit(job)
- def do_bank_adjustment(memory):
- # Step 1: Do the bank add/remove
- job = common.RadioJob(do_bank_index, fn, memory, bank)
- job.set_target(self._bm)
+ def do_mapping_adjustment(memory):
+ # Step 1: Do the mapping add/remove
+ job = common.RadioJob(do_mapping_index, fn, memory, mapping)
+ job.set_target(self._model)
job.set_cb_args(memory)
- job.set_desc(_("Updating bank information "
+ job.set_desc(_("Updating mapping information "
"for memory {num}").format(num=memory.number))
self.rthread.submit(job)
# Step 0: Fetch the memory
- job = common.RadioJob(do_bank_adjustment, "get_memory", loc)
+ job = common.RadioJob(do_mapping_adjustment, "get_memory", loc)
job.set_desc(_("Getting memory {num}").format(num=loc))
self.rthread.submit(job)
@@ -224,36 +237,38 @@ class BankMembershipEditor(common.Editor):
def refresh_memory(*args):
self.refresh_memory(loc)
- def set_index(banks, memory):
+ def set_index(mappings, memory):
self.emit("changed")
# Step 2: Set the index
job = common.RadioJob(refresh_memory, "set_memory_index",
- memory, banks[0], int(new))
- job.set_target(self._bm)
+ memory, mappings[0], int(new))
+ job.set_target(self._model)
job.set_desc(_("Setting index "
"for memory {num}").format(num=memory.number))
self.rthread.submit(job)
- def get_bank(memory):
- # Step 1: Get the first/only bank
- job = common.RadioJob(set_index, "get_memory_banks", memory)
+ def get_mapping(memory):
+ # Step 1: Get the first/only mapping
+ job = common.RadioJob(set_index, "get_memory_mappings", memory)
job.set_cb_args(memory)
- job.set_target(self._bm)
- job.set_desc(_("Getting bank for "
- "memory {num}").format(num=memory.number))
+ job.set_target(self._model)
+ job.set_desc(_("Getting {type} for "
+ "memory {num}").format(type=self._type,
+ num=memory.number))
self.rthread.submit(job)
# Step 0: Get the memory
- job = common.RadioJob(get_bank, "get_memory", loc)
+ job = common.RadioJob(get_mapping, "get_memory", loc)
job.set_desc(_("Getting memory {num}").format(num=loc))
self.rthread.submit(job)
- def __init__(self, rthread, editorset):
- common.Editor.__init__(self)
- self.rthread = rthread
+ def __init__(self, rthread, editorset, model):
+ super(MappingMembershipEditor, self).__init__(rthread)
+
self.editorset = editorset
self._rf = rthread.radio.get_features()
- self._bm = rthread.radio.get_bank_model()
+ self._model = model
+ self._type = common.unpluralize(model.get_name())
self._view_cols = [
(_("Loc"), TYPE_INT, gtk.CellRendererText, ),
@@ -271,19 +286,22 @@ class BankMembershipEditor(common.Editor):
self.C_FREQ = 2
self.C_NAME = 3
self.C_INDEX = 4
- self.C_BANKS = 5 # and beyond
+ self.C_MAPPINGS = 5 # and beyond
cols = list(self._cols)
self._index_cache = []
- for i in range(0, self._bm.get_num_banks()):
- label = "Bank %i" % (i+1)
+ for i in range(0, self._model.get_num_mappings()):
+ label = "%s %i" % (self._type, (i+1))
cols.append((label, TYPE_BOOLEAN, gtk.CellRendererToggle))
self._store = gtk.ListStore(*tuple([y for x,y,z in cols]))
self._view = gtk.TreeView(self._store)
+ is_indexed = isinstance(self._model,
+ chirp_common.MappingModelIndexInterface)
+
colnum = 0
for label, dtype, rtype in cols:
if not rtype:
@@ -306,7 +324,7 @@ class BankMembershipEditor(common.Editor):
elif colnum == self.C_INDEX:
rend.set_property("editable", True)
rend.connect("edited", self._index_edited_cb)
- col.set_visible(self._rf.has_bank_index)
+ col.set_visible(is_indexed)
colnum += 1
# A non-rendered column to absorb extra space in the row
@@ -330,7 +348,7 @@ class BankMembershipEditor(common.Editor):
self._loaded = False
def refresh_memory(self, number):
- def got_mem(memory, banks, indexes):
+ def got_mem(memory, mappings, indexes):
iter = self._store.get_iter(self._number_to_path(memory.number))
row = [self.C_FILLED, not memory.empty,
self.C_LOC, memory.number,
@@ -339,29 +357,30 @@ class BankMembershipEditor(common.Editor):
# Hack for only one index right now
self.C_INDEX, indexes and indexes[0] or 0,
]
- for i in range(0, len(self.banks)):
+ for i in range(0, len(self.mappings)):
row.append(i + len(self._cols))
- row.append(self.banks[i][0] in banks)
+ row.append(self.mappings[i][0] in mappings)
self._store.set(iter, *tuple(row))
if memory.number == self._rf.memory_bounds[1] - 1:
- print "Got all bank info in %s" % (time.time() - self._start)
+ print "Got all %s info in %s" % (self._type,
+ (time.time() - self._start))
- job = MemoryBanksJob(self._bm, got_mem, number)
- job.set_desc(_("Getting bank information "
- "for memory {num}").format(num=number))
+ job = MemoryMappingsJob(self._model, got_mem, number)
+ job.set_desc(_("Getting {type} information "
+ "for memory {num}").format(type=self._type, num=number))
self.rthread.submit(job)
def refresh_all_memories(self):
for i in range(*self._rf.memory_bounds):
self.refresh_memory(i)
- def refresh_banks(self, and_memories=False):
- def got_banks():
+ def refresh_mappings(self, and_memories=False):
+ def got_mappings():
for i in range(len(self._cols) - len(self._view_cols) - 1,
- len(self.banks)):
+ len(self.mappings)):
col = self._view.get_column(i + len(self._view_cols))
- bank, name = self.banks[i]
+ mapping, name = self.mappings[i]
if name:
col.set_title(name)
else:
@@ -369,8 +388,8 @@ class BankMembershipEditor(common.Editor):
if and_memories:
self.refresh_all_memories()
- job = BankNamesJob(self._bm, self, got_banks)
- job.set_desc(_("Getting bank information"))
+ job = MappingNamesJob(self._model, self, got_mappings)
+ job.set_desc(_("Getting %s information") % self._type)
self.rthread.submit(job)
def focus(self):
@@ -379,14 +398,14 @@ class BankMembershipEditor(common.Editor):
return
self._start = time.time()
- self.refresh_banks(True)
+ self.refresh_mappings(True)
self._loaded = True
- def memories_changed(self):
+ def other_editor_changed(self, target_editor):
self._loaded = False
if self.is_focused():
self.refresh_all_memories()
- def banks_changed(self):
- self.refresh_banks()
+ def mappings_changed(self):
+ self.refresh_mappings()
diff --git a/chirpui/clone.py b/chirpui/clone.py
index 0786e1e..7210d4a 100644
--- a/chirpui/clone.py
+++ b/chirpui/clone.py
@@ -56,6 +56,8 @@ class CloneSettingsDialog(gtk.Dialog):
port = conf.get("last_port")
elif ports:
port = ports[0]
+ else:
+ port = ""
if not port in ports:
ports.insert(0, port)
diff --git a/chirpui/common.py b/chirpui/common.py
index adacaec..8511b8b 100644
--- a/chirpui/common.py
+++ b/chirpui/common.py
@@ -34,9 +34,11 @@ class Editor(gobject.GObject):
root = None
- def __init__(self):
+ def __init__(self, rthread):
gobject.GObject.__init__(self)
+ self.read_only = False
self._focused = False
+ self.rthread = rthread
def is_focused(self):
return self._focused
@@ -56,6 +58,18 @@ class Editor(gobject.GObject):
def hotkey(self, action):
pass
+ def set_read_only(self, read_only):
+ self.read_only = read_only
+
+ def get_read_only(self):
+ return self.read_only
+
+ def prepare_close(self):
+ pass
+
+ def other_editor_changed(self, editor):
+ pass
+
gobject.type_register(Editor)
def DBG(*args):
@@ -128,16 +142,26 @@ class RadioThread(threading.Thread, gobject.GObject):
(gobject.TYPE_STRING,)),
}
- def __init__(self, radio):
+ def __init__(self, radio, parent=None):
threading.Thread.__init__(self)
gobject.GObject.__init__(self)
self.__queue = {}
+ if parent:
+ self.__runlock = parent._get_run_lock()
+ self.status = lambda msg: parent.status(msg)
+ else:
+ self.__runlock = threading.Lock()
+ self.status = self._status
+
self.__counter = threading.Semaphore(0)
- self.__enabled = True
self.__lock = threading.Lock()
- self.__runlock = threading.Lock()
+
+ self.__enabled = True
self.radio = radio
+ def _get_run_lock(self):
+ return self.__runlock
+
def _qlock(self):
self.__lock.acquire()
@@ -196,7 +220,7 @@ class RadioThread(threading.Thread, gobject.GObject):
self.__counter.release()
self.__enabled = False
- def status(self, msg):
+ def _status(self, msg):
jobs = 0
for i in dict(self.__queue):
jobs += len(self.__queue[i])
@@ -224,14 +248,14 @@ class RadioThread(threading.Thread, gobject.GObject):
DBG("Running job at priority %i" % i)
break
self._qunlock()
-
+
if job:
self.lock()
self.status(job.desc)
job.execute(self.radio)
last_job_desc = job.desc
self.unlock()
-
+
print "RadioThread exiting"
def log_exception():
@@ -389,3 +413,8 @@ def show_diff_blob(title, result):
d.run()
d.destroy()
+def unpluralize(string):
+ if string.endswith("s"):
+ return string[:-1]
+ else:
+ return string
diff --git a/chirpui/config.py b/chirpui/config.py
index d82a1db..c5b55c1 100644
--- a/chirpui/config.py
+++ b/chirpui/config.py
@@ -54,6 +54,12 @@ class ChirpConfig:
def is_defined(self, key, section):
return self.__config.has_option(section, key)
+ def remove_option(self, section, key):
+ self.__config.remove_option(section, key)
+
+ if not self.__config.items(section):
+ self.__config.remove_section(section)
+
class ChirpConfigProxy:
def __init__(self, config, section="global"):
self._config = config
@@ -98,6 +104,9 @@ class ChirpConfigProxy:
def is_defined(self, key, section=None):
return self._config.is_defined(key, section or self._section)
+ def remove_option(self, key, section):
+ self._config.remove_option(section, key)
+
_CONFIG = None
def get(section="global"):
global _CONFIG
diff --git a/chirpui/dstaredit.py b/chirpui/dstaredit.py
index 942c6c5..df0baa8 100644
--- a/chirpui/dstaredit.py
+++ b/chirpui/dstaredit.py
@@ -179,8 +179,7 @@ class DStarEditor(common.Editor):
self.rthread.submit(job)
def __init__(self, rthread):
- common.Editor.__init__(self)
- self.rthread = rthread
+ super(DStarEditor, self).__init__(rthread)
self.loaded = False
diff --git a/chirpui/editorset.py b/chirpui/editorset.py
index 1f578a0..c38b7d8 100644
--- a/chirpui/editorset.py
+++ b/chirpui/editorset.py
@@ -19,7 +19,7 @@ import gobject
from chirp import chirp_common, directory, generic_csv, generic_xml
from chirpui import memedit, dstaredit, bankedit, common, importdialog
-from chirpui import inputdialog, reporting, settingsedit
+from chirpui import inputdialog, reporting, settingsedit, radiobrowser, config
class EditorSet(gtk.VBox):
__gsignals__ = {
@@ -35,6 +35,68 @@ class EditorSet(gtk.VBox):
(gobject.TYPE_STRING,)),
}
+ def _make_device_mapping_editors(self, device, devrthread, index):
+ sub_index = 0
+ memory_editor = self.editors["memedit%i" % index]
+ mappings = device.get_mapping_models()
+ for mapping_model in mappings:
+ members = bankedit.MappingMembershipEditor(devrthread, self,
+ mapping_model)
+ label = mapping_model.get_name()
+ if self.rf.has_sub_devices:
+ label += "(%s)" % device.VARIANT
+ lab = gtk.Label(label)
+ self.tabs.append_page(members.root, lab)
+ self.editors["mapping_members%i%i" % (index, sub_index)] = members
+
+ basename = common.unpluralize(mapping_model.get_name())
+ names = bankedit.MappingNameEditor(devrthread, mapping_model)
+ label = "%s Names" % basename
+ if self.rf.has_sub_devices:
+ label += " (%s)" % device.VARIANT
+ lab = gtk.Label(label)
+ self.tabs.append_page(names.root, lab)
+ self.editors["mapping_names%i%i" % (index, sub_index)] = names
+
+ members.root.show()
+ members.connect("changed", self.editor_changed)
+ if hasattr(mapping_model.get_mappings()[0], "set_name"):
+ names.root.show()
+ members.connect("changed", lambda x: names.mappings_changed())
+ names.connect("changed", lambda x: members.mappings_changed())
+ names.connect("changed", self.editor_changed)
+
+ def _make_device_editors(self, device, devrthread, index):
+ if isinstance(device, chirp_common.IcomDstarSupport):
+ memories = memedit.DstarMemoryEditor(devrthread)
+ else:
+ memories = memedit.MemoryEditor(devrthread)
+
+ memories.connect("usermsg", lambda e, m: self.emit("usermsg", m))
+ memories.connect("changed", self.editor_changed)
+
+ if self.rf.has_sub_devices:
+ label = (_("Memories (%(variant)s)") %
+ dict(variant=device.VARIANT))
+ rf = device.get_features()
+ else:
+ label = _("Memories")
+ rf = self.rf
+ lab = gtk.Label(label)
+ self.tabs.append_page(memories.root, lab)
+ memories.root.show()
+ self.editors["memedit%i" % index] = memories
+
+ self._make_device_mapping_editors(device, devrthread, index)
+
+ if isinstance(device, chirp_common.IcomDstarSupport):
+ editor = dstaredit.DStarEditor(devrthread)
+ self.tabs.append_page(editor.root, gtk.Label(_("D-STAR")))
+ editor.root.show()
+ editor.connect("changed", self.dstar_changed, memories)
+ editor.connect("changed", self.editor_changed)
+ self.editors["dstar"] = editor
+
def __init__(self, source, parent_window=None, filename=None, tempname=None):
gtk.VBox.__init__(self, True, 0)
@@ -49,78 +111,51 @@ class EditorSet(gtk.VBox):
else:
raise Exception("Unknown source type")
- self.rthread = common.RadioThread(self.radio)
- self.rthread.setDaemon(True)
- self.rthread.start()
+ rthread = common.RadioThread(self.radio)
+ rthread.setDaemon(True)
+ rthread.start()
- self.rthread.connect("status", lambda e, m: self.emit("status", m))
+ rthread.connect("status", lambda e, m: self.emit("status", m))
self.tabs = gtk.Notebook()
self.tabs.connect("switch-page", self.tab_selected)
self.tabs.set_tab_pos(gtk.POS_LEFT)
- self.editors = {
- "memedit" : None,
- "dstar" : None,
- "bank_names" : None,
- "bank_members" : None,
- "settings" : None,
- }
-
- if isinstance(self.radio, chirp_common.IcomDstarSupport):
- self.editors["memedit"] = memedit.DstarMemoryEditor(self.rthread)
- self.editors["dstar"] = dstaredit.DStarEditor(self.rthread)
- else:
- self.editors["memedit"] = memedit.MemoryEditor(self.rthread)
-
- self.editors["memedit"].connect("usermsg",
- lambda e, m: self.emit("usermsg", m))
-
- rf = self.radio.get_features()
+ self.editors = {}
- if rf.has_bank:
- self.editors["bank_members"] = \
- bankedit.BankMembershipEditor(self.rthread, self)
-
- if rf.has_bank_names:
- self.editors["bank_names"] = bankedit.BankNameEditor(self.rthread)
-
- if rf.has_settings:
- self.editors["settings"] = settingsedit.SettingsEditor(self.rthread)
-
- lab = gtk.Label(_("Memories"))
- self.tabs.append_page(self.editors["memedit"].root, lab)
- self.editors["memedit"].root.show()
-
- if self.editors["dstar"]:
- lab = gtk.Label(_("D-STAR"))
- self.tabs.append_page(self.editors["dstar"].root, lab)
- self.editors["dstar"].root.show()
- self.editors["dstar"].connect("changed", self.dstar_changed)
-
- if self.editors["bank_names"]:
- lab = gtk.Label(_("Bank Names"))
- self.tabs.append_page(self.editors["bank_names"].root, lab)
- self.editors["bank_names"].root.show()
- self.editors["bank_names"].connect("changed", self.banks_changed)
-
- if self.editors["bank_members"]:
- lab = gtk.Label(_("Banks"))
- self.tabs.append_page(self.editors["bank_members"].root, lab)
- self.editors["bank_members"].root.show()
- self.editors["bank_members"].connect("changed", self.banks_changed)
-
- if self.editors["settings"]:
- lab = gtk.Label(_("Settings"))
- self.tabs.append_page(self.editors["settings"].root, lab)
- self.editors["settings"].root.show()
+ self.rf = self.radio.get_features()
+ if self.rf.has_sub_devices:
+ devices = self.radio.get_sub_devices()
+ else:
+ devices = [self.radio]
+
+ index = 0
+ for device in devices:
+ devrthread = common.RadioThread(device, rthread)
+ devrthread.setDaemon(True)
+ devrthread.start()
+ self._make_device_editors(device, devrthread, index)
+ index += 1
+
+ if self.rf.has_settings:
+ editor = settingsedit.SettingsEditor(rthread)
+ self.tabs.append_page(editor.root, gtk.Label(_("Settings")))
+ editor.root.show()
+ editor.connect("changed", self.editor_changed)
+ self.editors["settings"] = editor
+
+ conf = config.get()
+ if (hasattr(self.rthread.radio, '_memobj') and
+ conf.get_bool("developer", "state")):
+ editor = radiobrowser.RadioBrowser(self.rthread)
+ lab = gtk.Label(_("Browser"))
+ self.tabs.append_page(editor.root, lab)
+ editor.connect("changed", self.editor_changed)
+ self.editors["browser"] = editor
self.pack_start(self.tabs)
self.tabs.show()
- # pylint: disable-msg=E1101
- self.editors["memedit"].connect("changed", self.editor_changed)
-
self.label = self.text_label = None
self.make_label()
self.modified = (tempname is not None)
@@ -171,31 +206,19 @@ class EditorSet(gtk.VBox):
self.modified = False
self.update_tab()
- def dstar_changed(self, *args):
- print "D-STAR editor changed"
- memedit = self.editors["memedit"]
- dstared = self.editors["dstar"]
+ def dstar_changed(self, dstared, memedit):
memedit.set_urcall_list(dstared.editor_ucall.get_callsigns())
memedit.set_repeater_list(dstared.editor_rcall.get_callsigns())
memedit.prefill()
- if not isinstance(self.radio, chirp_common.LiveRadio):
- self.modified = True
- self.update_tab()
-
- def banks_changed(self, *args):
- print "Banks changed"
- if self.editors["bank_members"]:
- self.editors["bank_members"].banks_changed()
- if not isinstance(self.radio, chirp_common.LiveRadio):
- self.modified = True
- self.update_tab()
- def editor_changed(self, *args):
+ def editor_changed(self, target_editor=None):
+ print "%s changed" % target_editor
if not isinstance(self.radio, chirp_common.LiveRadio):
self.modified = True
self.update_tab()
- if self.editors["bank_members"]:
- self.editors["bank_members"].memories_changed()
+ for editor in self.editors.values():
+ if editor != target_editor:
+ editor.other_editor_changed(target_editor)
def get_tab_label(self):
return self.label
@@ -226,7 +249,8 @@ class EditorSet(gtk.VBox):
if count > 0:
self.editor_changed()
- gobject.idle_add(self.editors["memedit"].prefill)
+ current_editor = self.get_current_editor()
+ gobject.idle_add(current_editor.prefill)
return count
@@ -251,6 +275,13 @@ class EditorSet(gtk.VBox):
raise Exception(_("Internal Error"))
def do_import(self, filen):
+ current_editor = self.get_current_editor()
+ if not isinstance(current_editor, memedit.MemoryEditor):
+ # FIXME: We need a nice message to let the user know that they
+ # need to select the appropriate memory editor tab before doing
+ # and import so that we know which thread and editor to import
+ # into and refresh. This will do for the moment.
+ common.show_error("Memory editor must be selected before import")
try:
src_radio = directory.get_radio_by_image(filen)
except Exception, e:
@@ -338,11 +369,13 @@ class EditorSet(gtk.VBox):
self)
def prime(self):
+ # NOTE: this is only called to prime new CSV files, so assume
+ # only one memory editor for now
mem = chirp_common.Memory()
mem.freq = 146010000
def cb(*args):
- gobject.idle_add(self.editors["memedit"].prefill)
+ gobject.idle_add(self.editors["memedit0"].prefill)
job = common.RadioJob(cb, "set_memory", mem)
job.set_desc(_("Priming memory"))
@@ -358,16 +391,25 @@ class EditorSet(gtk.VBox):
v.unfocus()
def set_read_only(self, read_only=True):
- self.editors["memedit"].set_read_only(read_only)
-
+ for editor in self.editors.values():
+ editor and editor.set_read_only(read_only)
+
def get_read_only(self):
- return self.editors["memedit"].get_read_only()
+ return self.editors["memedit0"].get_read_only()
def prepare_close(self):
- self.editors["memedit"].prepare_close()
+ for editor in self.editors.values():
+ editor and editor.prepare_close()
def get_current_editor(self):
- for e in self.editors.values():
+ for lab, e in self.editors.items():
if e and self.tabs.page_num(e.root) == self.tabs.get_current_page():
return e
raise Exception("No editor selected?")
+
+ @property
+ def rthread(self):
+ """Magic rthread property to return the rthread of the currently-
+ selected editor"""
+ e = self.get_current_editor()
+ return e.rthread
diff --git a/chirpui/mainapp.py b/chirpui/mainapp.py
index e3b2eb1..719d096 100644
--- a/chirpui/mainapp.py
+++ b/chirpui/mainapp.py
@@ -40,6 +40,7 @@ from chirp import ic9x, kenwood_live, idrp, vx7, vx5, vx6
from chirp import CHIRP_VERSION, chirp_common, detect, errors
from chirp import icf, ic9x_icf
from chirpui import editorset, clone, miscwidgets, config, reporting, fips
+from chirpui import bandplans
CONF = config.get()
@@ -129,7 +130,7 @@ class ChirpMain(gtk.Window):
for _editortype, actions in mappings.items():
for _action in actions:
action = self.menu_ag.get_action(_action)
- action.set_sensitive(_editortype == editortype)
+ action.set_sensitive(editortype.startswith(_editortype))
def _connect_editorset(self, eset):
eset.connect("want-close", self.do_close)
@@ -300,8 +301,6 @@ If you think that it is valid, you can select a radio model below to force an op
# We have to actually instantiate the IC9xICFRadio to get its
# sub-devices
radio = ic9x_icf.IC9xICFRadio(fname)
- devices = radio.get_sub_devices()
- del radio
else:
try:
radio = directory.get_radio_by_image(fname)
@@ -315,39 +314,30 @@ If you think that it is valid, you can select a radio model below to force an op
common.show_error(os.path.basename(fname) + ": " + str(e))
return
- if radio.get_features().has_sub_devices:
- devices = radio.get_sub_devices()
- else:
- devices = [radio]
-
- prio = len(devices)
first_tab = False
- for device in devices:
- try:
- eset = editorset.EditorSet(device, self,
- filename=fname,
- tempname=tempname)
- except Exception, e:
- common.log_exception()
- common.show_error(
- _("There was an error opening {fname}: {error}").format(
- fname=fname,
- error=error))
- return
-
- eset.set_read_only(read_only)
- self._connect_editorset(eset)
- eset.show()
- tab = self.tabs.append_page(eset, eset.get_tab_label())
- if first_tab:
- self.tabs.set_current_page(tab)
- first_tab = False
-
- if hasattr(eset.rthread.radio, "errors") and \
- eset.rthread.radio.errors:
- msg = _("{num} errors during open:").format(num=len(eset.rthread.radio.errors))
- common.show_error_text(msg,
- "\r\n".join(eset.rthread.radio.errors))
+ try:
+ eset = editorset.EditorSet(radio, self,
+ filename=fname,
+ tempname=tempname)
+ except Exception, e:
+ common.log_exception()
+ common.show_error(
+ _("There was an error opening {fname}: {error}").format(
+ fname=fname,
+ error=e))
+ return
+
+ eset.set_read_only(read_only)
+ self._connect_editorset(eset)
+ eset.show()
+ self.tabs.append_page(eset, eset.get_tab_label())
+
+ if hasattr(eset.rthread.radio, "errors") and \
+ eset.rthread.radio.errors:
+ msg = _("{num} errors during open:").format(
+ num=len(eset.rthread.radio.errors))
+ common.show_error_text(msg,
+ "\r\n".join(eset.rthread.radio.errors))
def do_live_warning(self, radio):
d = gtk.MessageDialog(parent=self, buttons=gtk.BUTTONS_OK)
@@ -369,23 +359,12 @@ If you think that it is valid, you can select a radio model below to force an op
d.destroy()
def do_open_live(self, radio, tempname=None, read_only=False):
- if radio.get_features().has_sub_devices:
- devices = radio.get_sub_devices()
- else:
- devices = [radio]
-
- first_tab = True
- for device in devices:
- eset = editorset.EditorSet(device, self, tempname=tempname)
- eset.connect("want-close", self.do_close)
- eset.connect("status", self.ev_status)
- eset.set_read_only(read_only)
- eset.show()
-
- tab = self.tabs.append_page(eset, eset.get_tab_label())
- if first_tab:
- self.tabs.set_current_page(tab)
- first_tab = False
+ eset = editorset.EditorSet(radio, self, tempname=tempname)
+ eset.connect("want-close", self.do_close)
+ eset.connect("status", self.ev_status)
+ eset.set_read_only(read_only)
+ eset.show()
+ self.tabs.append_page(eset, eset.get_tab_label())
if isinstance(radio, chirp_common.LiveRadio):
reporting.report_model_usage(radio, "live", True)
@@ -585,7 +564,7 @@ If you think that it is valid, you can select a radio model below to force an op
return True
title = _("Proceed with experimental driver?")
- text = rclass.get_experimental_warning()
+ text = rclass.get_prompts().experimental
msg = _("This radio's driver is experimental. "
"Do you want to proceed?")
resp, squelch = common.show_warning(msg, text,
@@ -596,6 +575,34 @@ If you think that it is valid, you can select a radio model below to force an op
CONF.set_bool(sql_key, not squelch, "state")
return resp == gtk.RESPONSE_YES
+ def _show_instructions(self, radio, message):
+ if message is None:
+ return
+
+ if CONF.get_bool("clone_instructions", "noconfirm"):
+ return
+
+ d = gtk.MessageDialog(parent=self, buttons=gtk.BUTTONS_OK)
+ d.set_markup("<big><b>" + _("{name} Instructions").format(
+ name=radio.get_name()) + "</b></big>")
+ msg = _("{instructions}").format(instructions=message)
+ d.format_secondary_markup(msg)
+
+ again = gtk.CheckButton(_("Don't show instructions for any radio again"))
+ again.show()
+ d.vbox.pack_start(again, 0, 0, 0)
+ h_button_box = d.vbox.get_children()[2]
+ try:
+ ok_button = h_button_box.get_children()[0]
+ ok_button.grab_default()
+ ok_button.grab_focus()
+ except AttributeError:
+ # don't grab focus on GTK+ 2.0
+ pass
+ d.run()
+ d.destroy()
+ CONF.set_bool("clone_instructions", again.get_active(), "noconfirm")
+
def do_download(self, port=None, rtype=None):
d = clone.CloneSettingsDialog(parent=self)
settings = d.run()
@@ -609,6 +616,8 @@ If you think that it is valid, you can select a radio model below to force an op
# User does not want to proceed with experimental driver
return
+ self._show_instructions(rclass, rclass.get_prompts().pre_download)
+
print "User selected %s %s on port %s" % (rclass.VENDOR,
rclass.MODEL,
settings.port)
@@ -646,6 +655,11 @@ If you think that it is valid, you can select a radio model below to force an op
d.destroy()
if not settings:
return
+ prompts = radio.get_prompts()
+
+ if prompts.display_pre_upload_prompt_before_opening_port == True:
+ print "Opening port after pre_upload prompt."
+ self._show_instructions(radio, prompts.pre_upload)
if isinstance(radio, chirp_common.ExperimentalRadio) and \
not self._confirm_experimental(radio.__class__):
@@ -664,6 +678,10 @@ If you think that it is valid, you can select a radio model below to force an op
d.destroy()
return
+ if prompts.display_pre_upload_prompt_before_opening_port == False:
+ print "Opening port before pre_upload prompt."
+ self._show_instructions(radio, prompts.pre_upload)
+
radio.set_pipe(ser)
ct = clone.CloneThread(radio, "out", cb=self.cb_cloneout, parent=self)
@@ -718,6 +736,7 @@ If you think that it is valid, you can select a radio model below to force an op
(_("EVE Files (VX5)") + " (*.eve)", "*.eve"),
(_("ICF Files") + " (*.icf)", "*.icf"),
(_("Kenwood HMK Files") + " (*.hmk)", "*.hmk"),
+ (_("Kenwood ITM Files") + " (*.itm)", "*.itm"),
(_("Travel Plus Files") + " (*.tpe)", "*.tpe"),
(_("VX5 Commander Files") + " (*.vx5)", "*.vx5"),
(_("VX6 Commander Files") + " (*.vx6)", "*.vx6"),
@@ -783,7 +802,7 @@ If you think that it is valid, you can select a radio model below to force an op
county.set_active(0)
state.connect("changed", _changed, county)
- d = inputdialog.FieldDialog(title="RepeaterBook Query", parent=self)
+ d = inputdialog.FieldDialog(title=_("RepeaterBook Query"), parent=self)
d.add_field("State", state)
d.add_field("County", county)
d.add_field("Band", band)
@@ -842,7 +861,7 @@ If you think that it is valid, you can select a radio model below to force an op
if not os.path.exists(filename):
print "Failed, headers were:"
print str(headers)
- common.show_error("RepeaterBook query failed")
+ common.show_error(_("RepeaterBook query failed"))
self.window.set_cursor(None)
return
@@ -875,6 +894,87 @@ If you think that it is valid, you can select a radio model below to force an op
else:
self.do_open_live(radio, read_only=True)
+ def do_przemienniki_prompt(self):
+ d = inputdialog.FieldDialog(title='przemienniki.net query',
+ parent=self)
+ fields = {
+ "Country":
+ (miscwidgets.make_choice(['by', 'cz', 'de', 'lt', 'pl',
+ 'sk', 'uk'], False),
+ lambda x: str(x.get_active_text())),
+ "Band":
+ (miscwidgets.make_choice(['10m', '4m', '6m', '2m', '70cm',
+ '23cm', '13cm', '3cm'], False, '2m'),
+ lambda x: str(x.get_active_text())),
+ "Mode":
+ (miscwidgets.make_choice(['fm', 'dv'], False),
+ lambda x: str(x.get_active_text())),
+ "Only Working":
+ (miscwidgets.make_choice(['', 'yes'], False),
+ lambda x: str(x.get_active_text())),
+ "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)
+ while d.run() == gtk.RESPONSE_OK:
+ query = "http://przemienniki.net/export/chirp.csv?"
+ args = []
+ for name, (value, fn) in fields.items():
+ if isinstance(value, gtk.Entry):
+ contents = value.get_text()
+ else:
+ contents = value.get_active_text()
+ if contents:
+ try:
+ _value = fn(value)
+ except ValueError:
+ common.show_error(_("Invalid value for %s") % name)
+ query = None
+ continue
+
+ args.append("=".join((name.replace(" ", "").lower(),
+ contents)))
+ query += "&".join(args)
+ print query
+ d.destroy()
+ return query
+
+ d.destroy()
+ return query
+
+ def do_przemienniki(self, do_import):
+ url = self.do_przemienniki_prompt()
+ if not url:
+ return
+
+ fn = tempfile.mktemp(".csv")
+ filename, headers = urllib.urlretrieve(url, fn)
+ if not os.path.exists(filename):
+ print "Failed, headers were:"
+ print str(headers)
+ common.show_error(_("Query failed"))
+ return
+
+ class PRRadio(generic_csv.CSVRadio,
+ chirp_common.NetworkSourceRadio):
+ VENDOR = "przemienniki.net"
+ MODEL = ""
+
+ try:
+ radio = PRRadio(filename)
+ except Exception, e:
+ common.show_error(str(e))
+ return
+
+ if do_import:
+ eset = self.get_current_editorset()
+ count = eset.do_import(filename)
+ else:
+ self.do_open_live(radio, read_only=True)
+
def do_rfinder_prompt(self):
fields = {"1Email" : (gtk.Entry(),
lambda x: "@" in x),
@@ -952,7 +1052,8 @@ If you think that it is valid, you can select a radio model below to force an op
"3Zipcode" : (gtk.Entry(), lambda x: x),
}
- d = inputdialog.FieldDialog(title="RadioReference.com Query", parent=self)
+ d = inputdialog.FieldDialog(title=_("RadioReference.com Query"),
+ 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:], "radioreference") or "")
@@ -1051,14 +1152,13 @@ If you think that it is valid, you can select a radio model below to force an op
d.set_name("CHIRP")
d.set_version(CHIRP_VERSION)
- d.set_copyright("Copyright 2012 Dan Smith (KK7DS)")
+ d.set_copyright("Copyright 2013 Dan Smith (KK7DS)")
d.set_website("http://chirp.danplanet.com")
- d.set_authors(("Dan Smith <dsmith at danplanet.com>",
- _("With significant contributions by:"),
- "Marco IZ3GME",
- "Rick WZ3RO",
+ d.set_authors(("Dan Smith KK7DS <dsmith at danplanet.com>",
+ _("With significant contributions from:"),
"Tom KD7LXL",
- "Vernon N7OH"
+ "Marco IZ3GME",
+ "Jim KC9HI"
))
d.set_translator_credits("Polish: Grzegorz SQ2RBY" +
os.linesep +
@@ -1066,12 +1166,33 @@ If you think that it is valid, you can select a radio model below to force an op
os.linesep +
"Dutch: Michael PD4MT" +
os.linesep +
- "German: Benjamin HB9EUK")
+ "German: Benjamin HB9EUK" +
+ os.linesep +
+ "Hungarian: Attila HA7JA" +
+ os.linesep +
+ "Russian: Dmitry Slukin" +
+ os.linesep +
+ "Portuguese (BR): Crezivando PP7CJ")
d.set_comments(verinfo)
d.run()
d.destroy()
+ def do_documentation(self):
+ d = gtk.MessageDialog(buttons=gtk.BUTTONS_OK, parent=self,
+ type=gtk.MESSAGE_INFO)
+
+ d.set_markup("<b><big>" + _("CHIRP Documentation") + "</big></b>\r\n")
+ msg = _("Documentation for CHIRP, including FAQs, and help for common "
+ "problems is available on the CHIRP web site, please go to\n\n"
+ "<a href=\"http://chirp.danplanet.com/projects/chirp/wiki/"
+ "Documentation\">"
+ "http://chirp.danplanet.com/projects/chirp/wiki/"
+ "Documentation</a>\n")
+ d.format_secondary_markup(msg.replace("\n","\r\n"))
+ d.run()
+ d.destroy()
+
def do_columns(self):
eset = self.get_current_editorset()
driver = directory.get_driver(eset.rthread.radio.__class__)
@@ -1098,7 +1219,7 @@ If you think that it is valid, you can select a radio model below to force an op
vbox.pack_start(label)
fields = []
- memedit = eset.editors["memedit"]
+ memedit = eset.get_current_editor() #.editors["memedit"]
unsupported = memedit.get_unsupported_columns()
for colspec in memedit.cols:
if colspec[0].startswith("_"):
@@ -1132,7 +1253,9 @@ If you think that it is valid, you can select a radio model below to force an op
conf = config.get("memedit")
conf.set_bool("hide_unused", action.get_active())
else:
- eset.editors["memedit"].set_hide_unused(action.get_active())
+ for editortype, editor in eset.editors.iteritems():
+ if "memedit" in editortype:
+ editor.set_hide_unused(action.get_active())
def do_clearq(self):
eset = self.get_current_editorset()
@@ -1173,8 +1296,8 @@ If you think that it is valid, you can select a radio model below to force an op
conf = config.get()
conf.set_bool("no_report", not action.get_active())
- def do_toggle_autorpt(self, action):
- CONF.set_bool("autorpt", action.get_active(), "memedit")
+ def do_toggle_no_smart_tmode(self, action):
+ CONF.set_bool("no_smart_tmode", not action.get_active(), "memedit")
def do_toggle_developer(self, action):
conf = config.get()
@@ -1186,7 +1309,7 @@ If you think that it is valid, you can select a radio model below to force an op
def do_change_language(self):
langs = ["Auto", "English", "Polish", "Italian", "Dutch", "German",
- "Hungarian"]
+ "Hungarian", "Russian", "Portuguese (BR)"]
d = inputdialog.ChoiceDialog(langs, parent=self,
title="Choose Language")
d.label.set_text(_("Choose a language or Auto to use the "
@@ -1253,8 +1376,12 @@ If you think that it is valid, you can select a radio model below to force an op
self.do_export()
elif action in ["qrbook", "irbook"]:
self.do_repeaterbook(action[0] == "i")
+ elif action in ["qpr", "ipr"]:
+ self.do_przemienniki(action[0] == "i")
elif action == "about":
self.do_about()
+ elif action == "documentation":
+ self.do_documentation()
elif action == "columns":
self.do_columns()
elif action == "hide_unused":
@@ -1263,8 +1390,12 @@ If you think that it is valid, you can select a radio model below to force an op
self.do_clearq()
elif action == "report":
self.do_toggle_report(_action)
- elif action == "autorpt":
- self.do_toggle_autorpt(_action)
+ elif action == "channel_defaults":
+ # The memedit thread also has an instance of bandplans.
+ bp = bandplans.BandPlans(CONF)
+ bp.select_bandplan(self)
+ elif action == "no_smart_tmode":
+ self.do_toggle_no_smart_tmode(_action)
elif action == "developer":
self.do_toggle_developer(_action)
elif action in ["cut", "copy", "paste", "delete",
@@ -1314,6 +1445,7 @@ If you think that it is valid, you can select a radio model below to force an op
<menu action="view">
<menuitem action="columns"/>
<menuitem action="hide_unused"/>
+ <menuitem action="no_smart_tmode"/>
<menu action="viewdeveloper">
<menuitem action="devshowraw"/>
<menuitem action="devdiffraw"/>
@@ -1327,21 +1459,24 @@ If you think that it is valid, you can select a radio model below to force an op
<menu action="importsrc" name="importsrc">
<menuitem action="iradioreference"/>
<menuitem action="irbook"/>
+ <menuitem action="ipr"/>
<menuitem action="irfinder"/>
</menu>
<menu action="querysrc" name="querysrc">
<menuitem action="qradioreference"/>
<menuitem action="qrbook"/>
+ <menuitem action="qpr"/>
<menuitem action="qrfinder"/>
</menu>
<menu action="stock" name="stock"/>
<separator/>
- <menuitem action="autorpt"/>
+ <menuitem action="channel_defaults"/>
<separator/>
<menuitem action="cancelq"/>
</menu>
<menu action="help">
<menuitem action="about"/>
+ <menuitem action="documentation"/>
<menuitem action="report"/>
<menuitem action="developer"/>
</menu>
@@ -1383,28 +1518,32 @@ If you think that it is valid, you can select a radio model below to force an op
('iradioreference', None, _("RadioReference.com"), None, None, self.mh),
('irfinder', None, _("RFinder"), None, None, self.mh),
('irbook', None, _("RepeaterBook"), None, None, self.mh),
+ ('ipr', None, _("przemienniki.net"), None, None, self.mh),
('querysrc', None, _("Query data source"), 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),
('export_chirp', None, _("CHIRP Native File"), None, None, self.mh),
('export_csv', None, _("CSV File"), None, None, self.mh),
('stock', None, _("Import from stock config"), None, None, self.mh),
+ ('channel_defaults', None, _("Channel defaults"), None, None, self.mh),
('cancelq', gtk.STOCK_STOP, None, "Escape", None, self.mh),
('help', None, _('Help'), None, None, self.mh),
('about', gtk.STOCK_ABOUT, None, None, None, self.mh),
+ ('documentation', None, _("Documentation"), None, None, self.mh),
]
conf = config.get()
re = not conf.get_bool("no_report");
hu = conf.get_bool("hide_unused", "memedit")
- ro = conf.get_bool("autorpt", "memedit")
dv = conf.get_bool("developer", "state")
+ st = not conf.get_bool("no_smart_tmode", "memedit")
toggles = [\
('report', None, _("Report statistics"), None, None, self.mh, re),
('hide_unused', None, _("Hide Unused Fields"), None, None, self.mh, hu),
- ('autorpt', None, _("Automatic Repeater Offset"), None, None, self.mh, ro),
+ ('no_smart_tmode', None, _("Smart Tone Modes"), None, None, self.mh, st),
('developer', None, _("Enable Developer Functions"), None, None, self.mh, dv),
]
@@ -1539,6 +1678,9 @@ If you think that it is valid, you can select a radio model below to force an op
aboutitem = self.menu_uim.get_widget("/MenuBar/help/about")
macapp.insert_app_menu_item(aboutitem, 0)
+ documentationitem = self.menu_uim.get_widget("/MenuBar/help/documentation")
+ macapp.insert_app_menu_item(documentationitem, 0)
+
macapp.set_use_quartz_accelerators(False)
macapp.ready()
@@ -1547,6 +1689,19 @@ If you think that it is valid, you can select a radio model below to force an op
def __init__(self, *args, **kwargs):
gtk.Window.__init__(self, *args, **kwargs)
+ def expose(window, event):
+ allocation = window.get_allocation()
+ CONF.set_int("window_w", allocation.width, "state")
+ CONF.set_int("window_h", allocation.height, "state")
+ self.connect("expose_event", expose)
+
+ def state_change(window, event):
+ CONF.set_bool(
+ "window_maximized",
+ event.new_window_state == gtk.gdk.WINDOW_STATE_MAXIMIZED,
+ "state")
+ self.connect("window-state-event", state_change)
+
d = CONF.get("last_dir", "state")
if d and os.path.isdir(d):
platform.get_platform().set_last_dir(d)
@@ -1579,7 +1734,16 @@ If you think that it is valid, you can select a radio model below to force an op
self.add(vbox)
- self.set_default_size(800, 600)
+ try:
+ width = CONF.get_int("window_w", "state")
+ height = CONF.get_int("window_h", "state")
+ except Exception:
+ width = 800
+ height = 600
+
+ self.set_default_size(width, height)
+ if CONF.get_bool("window_maximized", "state"):
+ self.maximize()
self.set_title("CHIRP")
self.connect("delete_event", self.ev_delete)
@@ -1598,10 +1762,6 @@ If you think that it is valid, you can select a radio model below to force an op
d.destroy()
CONF.set_bool("warned_about_reporting", True)
- if not CONF.is_defined("autorpt", "memedit"):
- print "autorpt not set et"
- CONF.set_bool("autorpt", True, "memedit")
-
self.update_recent_files()
self.update_stock_configs()
self.setup_extra_hotkeys()
diff --git a/chirpui/memdetail.py b/chirpui/memdetail.py
index cc605fc..d202964 100644
--- a/chirpui/memdetail.py
+++ b/chirpui/memdetail.py
@@ -34,6 +34,9 @@ class ValueEditor:
def _init(self, data):
"""Type-specific initialization"""
+ def set_sensitive(self, sensitive):
+ self._widget.set_sensitive(sensitive)
+
def get_widget(self):
"""Returns the widget associated with this editor"""
return self._widget
@@ -157,21 +160,24 @@ class OffsetEditor(FreqEditor):
class MemoryDetailEditor(gtk.Dialog):
"""Detail editor for a memory"""
- def _add(self, tab, row, name, editor, labeltxt):
+ def _add(self, tab, row, name, editor, labeltxt, colindex=0):
label = gtk.Label(labeltxt)
img = gtk.Image()
label.show()
- tab.attach(label, 0, 1, row, row+1)
+ tab.attach(label, colindex, colindex + 1, row, row + 1)
+ colindex += 1
editor.get_widget().show()
- tab.attach(editor.get_widget(), 1, 2, row, row+1)
+ tab.attach(editor.get_widget(), colindex, colindex + 1, row, row + 1)
+ colindex += 1
img.set_size_request(15, -1)
img.show()
- tab.attach(img, 2, 3, row, row+1)
+ tab.attach(img, colindex, colindex + 1, row, row + 1)
self._editors[name] = label, editor, img
+ return label, editor, img
def _set_doc(self, name, doc):
label, editor, _img = self._editors[name]
@@ -179,8 +185,12 @@ class MemoryDetailEditor(gtk.Dialog):
self._tips.set_tip(editor.get_widget(), doc)
def _make_ui(self):
+ sw = gtk.ScrolledWindow()
+ sw.set_policy(gtk.POLICY_NEVER, gtk.POLICY_AUTOMATIC)
+ sw.show()
tab = gtk.Table(len(self._order), 3, False)
- self.vbox.pack_start(tab, 1, 1, 1)
+ sw.add_with_viewport(tab)
+ self.vbox.pack_start(sw, 1, 1, 1)
tab.show()
row = 0
@@ -225,18 +235,21 @@ class MemoryDetailEditor(gtk.Dialog):
row += 1
self._order.append(name)
+ def _title(self):
+ return _("Edit Memory #{num}").format(num=self._memory.number)
+
def __init__(self, features, memory, parent=None):
+ self._memory = memory
gtk.Dialog.__init__(self,
- title=_("Edit Memory"
- "#{num}").format(num=memory.number),
+ title=self._title(),
flags=gtk.DIALOG_MODAL,
parent=parent,
buttons=(gtk.STOCK_OK, gtk.RESPONSE_OK,
gtk.STOCK_CANCEL, gtk.RESPONSE_CANCEL))
+ self.set_size_request(-1, 500)
self._tips = gtk.Tooltips()
self._features = features
- self._memory = memory
self._editors = {}
self._elements = {
@@ -322,3 +335,34 @@ class MemoryDetailEditor(gtk.Dialog):
def get_memory(self):
self._memory.empty = False
return self._memory
+
+class MultiMemoryDetailEditor(MemoryDetailEditor):
+ def _title(self):
+ return _("Edit Multiple Memories")
+
+ def __init__(self, features, memory, parent=None):
+ self._selections = dict()
+ super(MultiMemoryDetailEditor, self).__init__(features, memory, parent)
+
+ def _toggle_selector(self, selector, *widgets):
+ for widget in widgets:
+ widget.set_sensitive(selector.get_active())
+
+ def _add(self, tab, row, name, editor, labeltxt):
+ label, editor, img = super(MultiMemoryDetailEditor, self)._add(
+ tab, row, name, editor, labeltxt, 1)
+
+ selector = gtk.CheckButton()
+ tab.attach(selector, 0, 1, row, row + 1)
+ selector.show()
+ self._toggle_selector(selector, label, editor, img)
+ selector.connect("toggled", self._toggle_selector, label, editor, img)
+ self._selections[name] = selector
+ self._tips.set_tip(
+ selector,
+ _("Check this to change the {name} value").format(
+ name=labeltxt))
+
+
+ def get_fields(self):
+ return [k for k, v in self._selections.items() if v.get_active()]
diff --git a/chirpui/memedit.py b/chirpui/memedit.py
index 5764ee0..b0c3ab9 100644
--- a/chirpui/memedit.py
+++ b/chirpui/memedit.py
@@ -33,6 +33,7 @@ import pickle
import os
from chirpui import common, shiftdialog, miscwidgets, config, memdetail
+from chirpui import bandplans
from chirp import chirp_common, errors, directory, import_logic
def handle_toggle(_, path, store, col):
@@ -104,10 +105,8 @@ class MemoryEditor(common.Editor):
choices = {
_("Tone") : chirp_common.TONES,
_("ToneSql") : chirp_common.TONES,
- _("DTCS Code") : sorted(chirp_common.DTCS_CODES +
- chirp_common.DTCS_EXTRA_CODES),
- _("DTCS Rx Code") : sorted(chirp_common.DTCS_CODES +
- chirp_common.DTCS_EXTRA_CODES),
+ _("DTCS Code") : chirp_common.ALL_DTCS_CODES,
+ _("DTCS Rx Code") : chirp_common.ALL_DTCS_CODES,
_("DTCS Pol") : ["NN", "NR", "RN", "RR"],
_("Mode") : chirp_common.MODES,
_("Power") : [],
@@ -121,13 +120,14 @@ class MemoryEditor(common.Editor):
return self.rthread.radio.filter_name(new)
def ed_offset(self, _, path, new, __):
- return chirp_common.parse_freq(new)
+ new = chirp_common.parse_freq(new)
+ return abs(new)
def ed_freq(self, _foo, path, new, colnum):
iter = self.store.get_iter(path)
- prev, = self.store.get(iter, colnum)
+ was_filled, prev = self.store.get(iter, self.col("_filled"), colnum)
- def set_offset(path, offset):
+ def set_offset(offset):
if offset > 0:
dup = "+"
elif offset == 0:
@@ -136,17 +136,37 @@ class MemoryEditor(common.Editor):
dup = "-"
offset *= -1
+ if not dup in self.choices[_("Duplex")]:
+ print "Duplex %s not supported by this radio" % dup
+ return
+
if offset:
self.store.set(iter, self.col(_("Offset")), offset)
self.store.set(iter, self.col(_("Duplex")), dup)
def set_ts(ts):
- self.store.set(iter, self.col(_("Tune Step")), ts)
+ if ts in self.choices[_("Tune Step")]:
+ self.store.set(iter, self.col(_("Tune Step")), ts)
+ else:
+ print "Tune step %s not supported by this radio" % ts
def get_ts(path):
return self.store.get(iter, self.col(_("Tune Step")))[0]
+ def set_mode(mode):
+ if mode in self.choices[_("Mode")]:
+ self.store.set(iter, self.col(_("Mode")), mode)
+ else:
+ print "Mode %s not supported by this radio (%s)" % (
+ mode, self.choices[_("Mode")])
+
+ def set_tone(tone):
+ if tone in self.choices[_("Tone")]:
+ self.store.set(iter, self.col(_("Tone")), tone)
+ else:
+ print "Tone %s not supported by this radio" % tone
+
try:
new = chirp_common.parse_freq(new)
except ValueError, e:
@@ -156,16 +176,16 @@ class MemoryEditor(common.Editor):
if not self._features.has_nostep_tuning:
set_ts(chirp_common.required_step(new))
- if new and self._config.get_bool("autorpt") and new != prev:
- try:
- band = chirp_common.freq_to_band(new)
- set_offset(path, 0)
- for lo, hi, offset in chirp_common.STD_OFFSETS[band]:
- if new > lo and new < hi:
- set_offset(path, offset)
- break
- except Exception, e:
- pass
+ is_changed = new != prev if was_filled else True
+ if new is not None and is_changed:
+ defaults = self.bandplans.get_defaults_for_frequency(new)
+ set_offset(defaults.offset or 0)
+ if defaults.step_khz:
+ set_ts(defaults.step_khz)
+ if defaults.mode:
+ set_mode(defaults.mode)
+ if defaults.tones:
+ set_tone(defaults.tones[0])
return new
@@ -192,15 +212,58 @@ class MemoryEditor(common.Editor):
# RX frequency as the default TX frequency
self.store.set(iter, self.col("Offset"), freq)
else:
- band = int(freq / 100000000)
- if chirp_common.STD_OFFSETS.has_key(band):
- offset = chirp_common.STD_OFFSETS[band][0][2]
- else:
- offset = 0
+ defaults = self.bandplans.get_defaults_for_frequency(freq)
+ offset = defaults.offset or 0
self.store.set(iter, self.col(_("Offset")), abs(offset))
return new
+ def ed_tone_field(self, _foo, path, new, col):
+ if self._config.get_bool("no_smart_tmode"):
+ return new
+
+ iter = self.store.get_iter(path)
+
+ # Python scoping hurts us here, so store this as a list
+ # that we can modify, instead of helpful variables :(
+ modes = list(self.store.get(iter,
+ self.col(_("Tone Mode")),
+ self.col(_("Cross Mode"))))
+
+ def _tm(*tmodes):
+ if modes[0] not in tmodes:
+ modes[0] = tmodes[0]
+ self.store.set(iter, self.col(_("Tone Mode")), modes[0])
+ def _cm(*cmodes):
+ if modes[0] == "Cross" and modes[1] not in cmodes:
+ modes[1] = cmodes[0]
+ self.store.set(iter, self.col(_("Cross Mode")), modes[1])
+
+ if col == self.col(_("DTCS Code")):
+ _tm("DTCS", "Cross")
+ _cm(*tuple([x for x in chirp_common.CROSS_MODES
+ if x.startswith("DTCS->")]))
+ elif col == self.col(_("DTCS Rx Code")):
+ _tm("Cross")
+ _cm(*tuple([x for x in chirp_common.CROSS_MODES
+ if x.endswith("->DTCS")]))
+ elif col == self.col(_("DTCS Pol")):
+ _tm("DTCS", "Cross")
+ _cm(*tuple([x for x in chirp_common.CROSS_MODES
+ if "DTCS" in x]))
+ elif col == self.col(_("Tone")):
+ _tm("Tone", "Cross")
+ _cm(*tuple([x for x in chirp_common.CROSS_MODES
+ if x.startswith("Tone->")]))
+ elif col == self.col(_("ToneSql")):
+ _tm("TSQL", "Cross")
+ _cm(*tuple([x for x in chirp_common.CROSS_MODES
+ if x.endswith("->Tone")]))
+ elif col == self.col(_("Cross Mode")):
+ _tm("Cross")
+
+ return new
+
def _get_cols_to_hide(self, iter):
tmode, duplex, cmode = self.store.get(iter,
self.col(_("Tone Mode")),
@@ -248,7 +311,7 @@ class MemoryEditor(common.Editor):
if "DTCS" not in cmode:
hide += [self.col(_("DTCS Pol"))]
- if duplex == "" or duplex == "(None)":
+ if duplex == "" or duplex == "(None)" or duplex == "off":
hide += [self.col(_("Offset"))]
@@ -276,6 +339,12 @@ class MemoryEditor(common.Editor):
_("Frequency") : self.ed_freq,
_("Duplex") : self.ed_duplex,
_("Offset") : self.ed_offset,
+ _("Tone") : self.ed_tone_field,
+ _("ToneSql"): self.ed_tone_field,
+ _("DTCS Code"): self.ed_tone_field,
+ _("DTCS Rx Code"): self.ed_tone_field,
+ _("DTCS Pol"): self.ed_tone_field,
+ _("Cross Mode"): self.ed_tone_field,
}
if funcs.has_key(cap):
@@ -306,7 +375,7 @@ class MemoryEditor(common.Editor):
msgs = self.rthread.radio.validate_memory(mem)
if msgs:
- common.show_error(_("Error setting memory") + \
+ common.show_error(_("Error setting memory") + ": " + \
"\r\n\r\n".join(msgs))
self.prefill()
return
@@ -397,7 +466,7 @@ class MemoryEditor(common.Editor):
else:
iter = store.iter_next(_iter)
- pos, = store.get(iter, self.col("Loc"))
+ pos, = store.get(iter, self.col(_("Loc")))
sd = shiftdialog.ShiftDialog(self.rthread)
@@ -455,12 +524,12 @@ class MemoryEditor(common.Editor):
return True # We changed memories
- def _delete_rows_and_shift(self, paths):
+ def _delete_rows_and_shift(self, paths, all=False):
iter = self.store.get_iter(paths[0])
starting_loc, = self.store.get(iter, self.col(_("Loc")))
for i in range(0, len(paths)):
sd = shiftdialog.ShiftDialog(self.rthread)
- sd.delete(starting_loc, quiet=True)
+ sd.delete(starting_loc, quiet=True, all=all)
sd.destroy()
self.prefill()
@@ -640,23 +709,57 @@ class MemoryEditor(common.Editor):
job.set_desc(_("Getting raw memory {number}").format(number=loc_b))
self.rthread.submit(job)
- def edit_memory(self, memory):
- dlg = memdetail.MemoryDetailEditor(self._features, memory)
+ def _copy_field(self, src_memory, dst_memory, field):
+ if field.startswith("extra_"):
+ field = field.split("_", 1)[1]
+ value = src_memory.extra[field].value.get_value()
+ dst_memory.extra[field].value = value
+ else:
+ setattr(dst_memory, field, getattr(src_memory, field))
+
+ def _apply_multiple(self, src_memory, fields, locations):
+ for location in locations:
+ def apply_and_set(memory):
+ for field in fields:
+ self._copy_field(src_memory, memory, field)
+ cb = (memory.number == locations[-1]
+ and self._set_memory_cb or None)
+ job = common.RadioJob(cb, "set_memory", memory)
+ job.set_desc(_("Writing memory {number}").format(
+ number=memory.number))
+ self.rthread.submit(job)
+ job = common.RadioJob(apply_and_set, "get_memory", location)
+ job.set_desc(_("Getting original memory {number}").format(
+ number=location))
+ self.rthread.submit(job)
+
+ def edit_memory(self, memory, locations):
+ if len(locations) > 1:
+ dlg = memdetail.MultiMemoryDetailEditor(self._features, memory)
+ else:
+ dlg = memdetail.MemoryDetailEditor(self._features, memory)
r = dlg.run()
if r == gtk.RESPONSE_OK:
self.need_refresh = True
mem = dlg.get_memory()
- mem.name = self.rthread.radio.filter_name(mem.name)
- job = common.RadioJob(self._set_memory_cb, "set_memory", mem)
- job.set_desc(_("Writing memory {number}").format(number=mem.number))
- self.rthread.submit(job)
- self.emit("changed")
+ if len(locations) > 1:
+ self._apply_multiple(memory, dlg.get_fields(), locations)
+ else:
+ mem.name = self.rthread.radio.filter_name(mem.name)
+ job = common.RadioJob(self._set_memory_cb, "set_memory", mem)
+ job.set_desc(_("Writing memory {number}").format(
+ number=mem.number))
+ self.rthread.submit(job)
dlg.destroy()
def mh(self, _action, store, paths):
action = _action.get_name()
- iter = store.get_iter(paths[0])
- cur_pos, = store.get(iter, self.col(_("Loc")))
+ selected = []
+ for path in paths:
+ iter = store.get_iter(path)
+ loc, = store.get(iter, self.col(_("Loc")))
+ selected.append(loc)
+ cur_pos = selected[0]
require_contiguous = ["delete_s", "move_up", "move_dn"]
if action in require_contiguous:
@@ -677,6 +780,8 @@ class MemoryEditor(common.Editor):
changed = self._delete_rows(paths)
elif action == "delete_s":
changed = self._delete_rows_and_shift(paths)
+ elif action == "delete_sall":
+ changed = self._delete_rows_and_shift(paths, all=True)
elif action in ["move_up", "move_dn"]:
changed = self._move_up_down(paths, action)
elif action == "exchange":
@@ -691,6 +796,7 @@ class MemoryEditor(common.Editor):
self._diff_raw(paths)
elif action == "edit":
job = common.RadioJob(self.edit_memory, "get_memory", cur_pos)
+ job.set_cb_args(selected)
self.rthread.submit(job)
if changed:
@@ -724,8 +830,11 @@ class MemoryEditor(common.Editor):
<menuitem action="edit"/>
<menuitem action="insert_prev"/>
<menuitem action="insert_next"/>
- <menuitem action="delete"/>
- <menuitem action="delete_s"/>
+ <menu action="deletes">
+ <menuitem action="delete"/>
+ <menuitem action="delete_s"/>
+ <menuitem action="delete_sall"/>
+ </menu>
<menuitem action="move_up"/>
<menuitem action="move_dn"/>
<menuitem action="exchange"/>
@@ -747,8 +856,10 @@ class MemoryEditor(common.Editor):
("edit", _("Edit")),
("insert_prev", _("Insert row above")),
("insert_next", _("Insert row below")),
- ("delete", issingle and _("Delete") or _("Delete all")),
- ("delete_s", _("Delete (and shift up)")),
+ ("deletes", _("Delete")),
+ ("delete", issingle and _("this memory") or _("these memories")),
+ ("delete_s", _("...and shift block up")),
+ ("delete_sall", _("...and shift all memories up")),
("move_up", _("Move up")),
("move_dn", _("Move down")),
("exchange", _("Exchange memories")),
@@ -1022,7 +1133,7 @@ class MemoryEditor(common.Editor):
def _limit_key(self, which):
if which not in ["lo", "hi"]:
- raise Exception(_("Internal Error: Invalid limit {number").format(number=which))
+ raise Exception(_("Internal Error: Invalid limit {number}").format(number=which))
return "%s_%s" % (directory.radio_class_id(self.rthread.radio.__class__),
which)
@@ -1110,12 +1221,6 @@ class MemoryEditor(common.Editor):
self.prefill()
self._config.set_bool("hide_empty", not show)
- def set_read_only(self, read_only):
- self.read_only = read_only
-
- def get_read_only(self):
- return self.read_only
-
def set_hide_unused(self, hide_unused):
self.hide_unused = hide_unused
self.prefill()
@@ -1175,13 +1280,14 @@ class MemoryEditor(common.Editor):
return user_visible
def __init__(self, rthread):
- common.Editor.__init__(self)
- self.rthread = rthread
+ super(MemoryEditor, self).__init__(rthread)
self.defaults = dict(self.defaults)
self._config = config.get("memedit")
+ self.bandplans = bandplans.BandPlans(config.get())
+
self.allowed_bands = [144, 440]
self.count = 100
self.show_special = self._config.get_bool("show_special")
@@ -1208,6 +1314,8 @@ class MemoryEditor(common.Editor):
self.choices[_("Power")] = [str(x) for x in
self._features["valid_power_levels"]]
self.choices[_("DTCS Pol")] = self._features["valid_dtcs_pols"]
+ self.choices[_("DTCS Code")] = self._features["valid_dtcs_codes"]
+ self.choices[_("DTCS Rx Code")] = self._features["valid_dtcs_codes"]
if self._features["valid_power_levels"]:
self.defaults[_("Power")] = self._features["valid_power_levels"][0]
@@ -1371,7 +1479,9 @@ class MemoryEditor(common.Editor):
cols = self.view.get_columns()
self._config.set("column_order_%s" % self.__class__.__name__,
",".join([x.get_title() for x in cols]))
-
+
+ def other_editor_changed(self, target_editor):
+ self.need_refresh = True
class DstarMemoryEditor(MemoryEditor):
def _get_cols_to_hide(self, iter):
diff --git a/chirpui/radiobrowser.py b/chirpui/radiobrowser.py
new file mode 100644
index 0000000..db96b04
--- /dev/null
+++ b/chirpui/radiobrowser.py
@@ -0,0 +1,320 @@
+import gtk
+import gobject
+import pango
+import re
+import os
+
+from chirp import bitwise
+from chirpui import common
+
+def do_insert_line_with_tags(b, line):
+ def i(text, *tags):
+ b.insert_with_tags_by_name(b.get_end_iter(), text, *tags)
+
+ def ident(name):
+ if "unknown" in name:
+ i(name, 'grey', 'bold')
+ else:
+ i(name, 'bold')
+
+ def nonzero(value):
+ i(value, 'red', 'bold')
+
+ def foo(value):
+ i(value, 'blue', 'bold')
+
+ m = re.match("^( *)([A-z0-9_]+: )(0x[A-F0-9]+) \((.*)\)$", line)
+ if m:
+ i(m.group(1))
+ ident(m.group(2))
+ if m.group(3) == '0x00':
+ i(m.group(3))
+ else:
+ nonzero(m.group(3))
+ i(' (')
+ for char in m.group(4):
+ if char == '1':
+ nonzero(char)
+ else:
+ i(char)
+ i(')')
+ return
+
+ m = re.match("^( *)([A-z0-9_]+: )(.*)$", line)
+ if m:
+ i(m.group(1))
+ ident(m.group(2))
+ i(m.group(3))
+ return
+
+ m = re.match("^(.*} )([A-z0-9_]+)( \()([0-9]+)( bytes at )(0x[A-F0-9]+)",
+ line)
+ if m:
+ i(m.group(1))
+ ident(m.group(2))
+ i(m.group(3))
+ foo(m.group(4))
+ i(m.group(5))
+ foo(m.group(6))
+ i(")")
+ return
+
+ i(line)
+
+def do_insert_with_tags(buf, text):
+ buf.set_text('')
+ lines = text.split(os.linesep)
+ for line in lines:
+ do_insert_line_with_tags(buf, line)
+ buf.insert_with_tags_by_name(buf.get_end_iter(), os.linesep)
+
+def classname(obj):
+ return str(obj.__class__).split('.')[-1]
+
+def bitwise_type(classname):
+ return classname.split("DataElement")[0]
+
+class FixedEntry(gtk.Entry):
+ def __init__(self, *args, **kwargs):
+ super(FixedEntry, self).__init__(*args, **kwargs)
+ fontdesc = pango.FontDescription("Courier bold 10")
+ self.modify_font(fontdesc)
+
+class IntegerEntry(FixedEntry):
+ def _colorize(self, _self):
+ value = self.get_text()
+ if value.startswith("0x"):
+ value = value[2:]
+ value = value.replace("0", "")
+ if not value:
+ self.modify_text(gtk.STATE_NORMAL, None)
+ else:
+ self.modify_text(gtk.STATE_NORMAL, gtk.gdk.color_parse('red'))
+
+ def __init__(self, *args, **kwargs):
+ super(IntegerEntry, self).__init__(*args, **kwargs)
+ self.connect("changed", self._colorize)
+
+class BitwiseEditor(gtk.HBox):
+ def __init__(self, element):
+ super(BitwiseEditor, self).__init__(False, 3)
+ self._element = element
+ self._build_ui()
+
+class IntegerEditor(BitwiseEditor):
+ def _changed(self, entry, base):
+ if not self._update:
+ return
+ value = entry.get_text()
+ if value.startswith("0x"):
+ value = value[2:]
+ self._element.set_value(int(value, base))
+ self._update_entries(skip=entry)
+
+ def _update_entries(self, skip=None):
+ self._update = False
+ for ent, format_spec in self._entries:
+ if ent != skip:
+ ent.set_text(format_spec.format(int(self._element)))
+ self._update = True
+
+ def _build_ui(self):
+ self._entries = []
+ self._update = True
+
+ hexdigits = ((self._element.size() / 4) +
+ (self._element.size() % 4 and 1 or 0))
+ formats = [('Hex', 16, '0x{:0%iX}' % hexdigits),
+ ('Dec', 10, '{:d}'),
+ ('Bin', 2, '{:0%ib}' % self._element.size())]
+ for name, base, format_spec in formats:
+ lab = gtk.Label(name)
+ self.pack_start(lab, 0, 0, 0)
+ lab.show()
+ int(self._element)
+ ent = IntegerEntry()
+ self._entries.append((ent, format_spec))
+ ent.connect('changed', self._changed, base)
+ self.pack_start(ent, 0, 0, 0)
+ ent.show()
+ self._update_entries()
+
+class BCDArrayEditor(BitwiseEditor):
+ def _changed(self, entry, hexent):
+ self._element.set_value(int(entry.get_text()))
+ self._format_hexent(hexent)
+
+ def _format_hexent(self, hexent):
+ value = ""
+ for i in self._element:
+ a, b = i.get_value()
+ value += "%i%i" % (a, b)
+ hexent.set_text(value)
+
+ def _build_ui(self):
+ lab = gtk.Label("Dec")
+ lab.show()
+ self.pack_start(lab, 0, 0, 0)
+ ent = FixedEntry()
+ ent.set_text(str(int(self._element)))
+ ent.show()
+ self.pack_start(ent, 1, 1, 1)
+
+ lab = gtk.Label("Hex")
+ lab.show()
+ self.pack_start(lab, 0, 0, 0)
+
+ hexent = FixedEntry()
+ hexent.show()
+ self.pack_start(hexent, 1, 1, 1)
+ hexent.set_editable(False)
+
+ ent.connect('changed', self._changed, hexent)
+ self._format_hexent(hexent)
+
+class CharArrayEditor(BitwiseEditor):
+ def _changed(self, entry):
+ self._element.set_value(entry.get_text().ljust(len(self._element)))
+
+ def _build_ui(self):
+ ent = FixedEntry(len(self._element))
+ ent.set_text(str(self._element))
+ ent.connect('changed', self._changed)
+ ent.show()
+ self.pack_start(ent, 1, 1, 1)
+
+class OtherEditor(BitwiseEditor):
+ def _build_ui(self):
+ name = classname(self._element)
+ name = bitwise_type(name)
+ if isinstance(self._element, bitwise.arrayDataElement):
+ name += " %s[%i]" % (
+ bitwise_type(classname(self._element[0])),
+ len(self._element))
+
+ l = gtk.Label(name)
+ l.show()
+ self.pack_start(l, 1, 1, 1)
+
+class RadioBrowser(common.Editor):
+ def _build_ui(self):
+ self._display = gtk.Table(20, 2)
+
+ self._store = gtk.TreeStore(gobject.TYPE_STRING, gobject.TYPE_PYOBJECT)
+ self._tree = gtk.TreeView(self._store)
+
+ rend = gtk.CellRendererText()
+ tvc = gtk.TreeViewColumn('Element', rend, text=0)
+ self._tree.append_column(tvc)
+ self._tree.connect('button_press_event', self._tree_click)
+ self._tree.set_size_request(200, -1)
+
+ self.root = gtk.HBox(False, 3)
+ sw = gtk.ScrolledWindow()
+ sw.add(self._tree)
+ sw.show()
+ self.root.pack_start(sw, 0, 0, 0)
+ sw = gtk.ScrolledWindow()
+ sw.add_with_viewport(self._display)
+ sw.show()
+ self.root.pack_start(sw, 1, 1, 1)
+ self._tree.show()
+ self._display.show()
+ self.root.show()
+
+ def _fill(self, name, obj, parent=None):
+ iter = self._store.append(parent, (name, obj))
+
+ if isinstance(obj, bitwise.structDataElement):
+ for name, item in obj.items():
+ if isinstance(item, bitwise.structDataElement):
+ self._fill(name, item, iter)
+ elif isinstance(item, bitwise.arrayDataElement):
+ self._fill("%s[%i]" % (name, len(item)), item, iter)
+ elif isinstance(obj, bitwise.arrayDataElement):
+ i = 0
+ for item in obj:
+ if isinstance(obj[0], bitwise.structDataElement):
+ self._fill("%s[%i]" % (name, i), item, iter)
+ i += 1
+
+ def _tree_click(self, view, event):
+ if event.button != 1:
+ return
+
+ index = [0]
+
+ def pack(widget, pos):
+ self._display.attach(widget, pos, pos + 1, index[0], index[0] + 1,
+ xoptions=gtk.FILL, yoptions=0)
+
+ def next_row():
+ index[0] += 1
+
+ def abandon(child):
+ self._display.remove(child)
+
+ pathinfo = view.get_path_at_pos(int(event.x), int(event.y))
+ path = pathinfo[0]
+ iter = self._store.get_iter(path)
+ name, obj = self._store.get(iter, 0, 1)
+
+ self._display.foreach(abandon)
+
+ for name, item in obj.items():
+ if item.size() % 8 == 0:
+ name = '<b>%s</b> <small>(%s %i bytes)</small>' % (
+ name, bitwise_type(classname(item)), item.size() / 8)
+ else:
+ name = '<b>%s</b> <small>(%s %i bits)</small>' % (
+ name, bitwise_type(classname(item)), item.size())
+ l = gtk.Label(name + " ")
+ l.set_use_markup(True)
+ l.show()
+ pack(l, 0)
+
+ if (isinstance(item, bitwise.intDataElement) or
+ isinstance(item, bitwise.bcdDataElement)):
+ e = IntegerEditor(item)
+ elif (isinstance(item, bitwise.arrayDataElement) and
+ isinstance(item[0], bitwise.bcdDataElement)):
+ e = BCDArrayEditor(item)
+ elif (isinstance(item, bitwise.arrayDataElement) and
+ isinstance(item[0], bitwise.charDataElement)):
+ e = CharArrayEditor(item)
+ else:
+ e = OtherEditor(item)
+ e.show()
+ pack(e, 1)
+ next_row()
+
+
+ def __init__(self, rthread):
+ super(RadioBrowser, self).__init__(rthread)
+ self._radio = rthread.radio
+ self._focused = False
+ self._build_ui()
+ self._fill('root', self._radio._memobj)
+
+ def focus(self):
+ self._focused = True
+
+ def unfocus(self):
+ if self._focused:
+ self.emit("changed")
+ self._focused = False
+
+if __name__ == "__main__":
+ from chirp import *
+ from chirp import directory
+ import sys
+
+ r = directory.get_radio_by_image(sys.argv[1])
+ class Foo:
+ radio = r
+ w = gtk.Window()
+ b = RadioBrowser(Foo)
+ w.set_default_size(1024, 768)
+ w.add(b.root)
+ w.show()
+ gtk.main()
diff --git a/chirpui/settingsedit.py b/chirpui/settingsedit.py
index b67982f..535296b 100644
--- a/chirpui/settingsedit.py
+++ b/chirpui/settingsedit.py
@@ -26,9 +26,8 @@ class RadioSettingProxy(settings.RadioSetting):
class SettingsEditor(common.Editor):
def __init__(self, rthread):
- common.Editor.__init__(self)
- self._rthread = rthread
-
+ super(SettingsEditor, self).__init__(rthread)
+ self._changed = False
self.root = gtk.HBox(False, 10)
self._store = gtk.TreeStore(gobject.TYPE_STRING,
gobject.TYPE_PYOBJECT)
@@ -57,15 +56,23 @@ class SettingsEditor(common.Editor):
job = common.RadioJob(self._build_ui, "get_settings")
job.set_desc("Getting radio settings")
- self._rthread.submit(job)
+ self.rthread.submit(job)
def _save_settings(self):
if self._top_setting_group is None:
return
- job = common.RadioJob(None, "set_settings", self._top_setting_group)
+ def setting_cb(result):
+ if isinstance(result, Exception):
+ common.show_error(_("Error in setting value: %s") % result)
+ elif self._changed:
+ self.emit("changed")
+ self._changed = False
+
+ job = common.RadioJob(setting_cb, "set_settings",
+ self._top_setting_group)
job.set_desc("Setting radio settings")
- self._rthread.submit(job)
+ self.rthread.submit(job)
def _load_setting(self, value, widget):
if isinstance(value, settings.RadioSettingValueInteger):
@@ -73,6 +80,8 @@ class SettingsEditor(common.Editor):
adj.configure(value.get_value(),
value.get_min(), value.get_max(),
value.get_step(), 1, 0)
+ elif isinstance(value, settings.RadioSettingValueFloat):
+ widget.set_text(value.format())
elif isinstance(value, settings.RadioSettingValueBoolean):
widget.set_active(value.get_value())
elif isinstance(value, settings.RadioSettingValueList):
@@ -89,9 +98,11 @@ class SettingsEditor(common.Editor):
print "Unsupported widget type %s for %s" % (value.__class__,
element.get_name())
- def _save_setting(self, widget, value):
+ def _do_save_setting(self, widget, value):
if isinstance(value, settings.RadioSettingValueInteger):
value.set_value(widget.get_adjustment().get_value())
+ elif isinstance(value, settings.RadioSettingValueFloat):
+ value.set_value(widget.get_text())
elif isinstance(value, settings.RadioSettingValueBoolean):
value.set_value(widget.get_active())
elif isinstance(value, settings.RadioSettingValueList):
@@ -103,8 +114,15 @@ class SettingsEditor(common.Editor):
element.value.__class__,
element.get_name())
+ self._changed = True
self._save_settings()
+ def _save_setting(self, widget, value):
+ try:
+ self._do_save_setting(widget, value)
+ except settings.InvalidValueError, e:
+ common.show_error(_("Invalid setting value: %s") % e)
+
def _build_ui_group(self, group):
def pack(widget, pos):
self._table.attach(widget, pos, pos+1, self._index, self._index+1,
@@ -139,6 +157,9 @@ class SettingsEditor(common.Editor):
widget = gtk.SpinButton()
print "Digits: %i" % widget.get_digits()
signal = "value-changed"
+ elif isinstance(value, settings.RadioSettingValueFloat):
+ widget = gtk.Entry()
+ signal = "focus-out-event"
elif isinstance(value, settings.RadioSettingValueBoolean):
widget = gtk.CheckButton(_("Enabled"))
signal = "toggled"
@@ -157,10 +178,16 @@ class SettingsEditor(common.Editor):
lalign.add(widget)
lalign.show()
+ widget.set_sensitive(value.get_mutable())
+
arraybox.pack_start(lalign, 1, 1, 1)
widget.show()
self._load_setting(value, widget)
- widget.connect(signal, self._save_setting, value)
+ if signal == "focus-out-event":
+ widget.connect(signal, lambda w, e, v:
+ self._save_setting(w, v), value)
+ else:
+ widget.connect(signal, self._save_setting, value)
self._index += 1
diff --git a/chirpui/shiftdialog.py b/chirpui/shiftdialog.py
index f35802e..0ca50a4 100644
--- a/chirpui/shiftdialog.py
+++ b/chirpui/shiftdialog.py
@@ -65,12 +65,15 @@ class ShiftDialog(gtk.Dialog):
count / len(memories))
i.number = dst
- self.rthread.radio.set_memory(i)
+ if i.empty:
+ self.rthread.radio.erase_memory(i.number)
+ else:
+ self.rthread.radio.set_memory(i)
count += 1.0
return int(count)
- def _get_mems_until_hole(self, start, endokay=False):
+ def _get_mems_until_hole(self, start, endokay=False, all=False):
mems = []
llimit, ulimit = self.rthread.radio.get_features().memory_bounds
@@ -81,7 +84,7 @@ class ShiftDialog(gtk.Dialog):
number=pos), 0)
try:
mem = self.rthread.radio.get_memory(pos)
- if mem.empty:
+ if mem.empty and not all:
break
except errors.InvalidMemoryLocation:
break
@@ -109,9 +112,8 @@ class ShiftDialog(gtk.Dialog):
print "No memory list?"
return 0
- def _delete_hole(self, start):
- mems = self._get_mems_until_hole(start+1, endokay=True)
-
+ def _delete_hole(self, start, all=False):
+ mems = self._get_mems_until_hole(start+1, endokay=True, all=all)
if mems:
count = self._shift_memories(-1, mems)
self.rthread.radio.erase_memory(count+start)
@@ -126,12 +128,12 @@ class ShiftDialog(gtk.Dialog):
else:
gobject.idle_add(self.set_response_sensitive, gtk.RESPONSE_OK, True)
- def threadfn(self, newhole, func):
+ def threadfn(self, newhole, func, *args):
self.status("Waiting for radio to become available", 0)
self.rthread.lock()
try:
- count = func(newhole)
+ count = func(newhole, *args)
except errors.InvalidMemoryLocation, e:
self.status(str(e), 0)
self.finished()
@@ -145,13 +147,13 @@ class ShiftDialog(gtk.Dialog):
def insert(self, newhole, quiet=False):
self.quiet = quiet
self.thread = threading.Thread(target=self.threadfn,
- args=(newhole,self._insert_hole))
+ args=(newhole, self._insert_hole))
self.thread.start()
gtk.Dialog.run(self)
- def delete(self, newhole, quiet=False):
+ def delete(self, newhole, quiet=False, all=False):
self.quiet = quiet
self.thread = threading.Thread(target=self.threadfn,
- args=(newhole,self._delete_hole))
+ args=(newhole, self._delete_hole, all))
self.thread.start()
gtk.Dialog.run(self)
diff --git a/chirpw b/chirpw
index cd97918..570d13c 100755
--- a/chirpw
+++ b/chirpw
@@ -15,14 +15,15 @@
# 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
+from chirp import elib_intl
from chirp import platform, CHIRP_VERSION
from chirpui import config
# Hack to setup environment
platform.get_platform()
-import gtk
import sys
import os
import locale
@@ -51,12 +52,14 @@ conf = config.get()
manual_language = conf.get("language", "state")
langs = []
if manual_language and manual_language != "Auto":
- lang_codes = { "English" : "en_US",
- "Polish" : "pl",
- "Italian" : "it",
- "Dutch" : "nl",
- "German" : "de",
- "Hungarian" : "hu",
+ lang_codes = { "English" : "en_US",
+ "Polish" : "pl",
+ "Italian" : "it",
+ "Dutch" : "nl",
+ "German" : "de",
+ "Hungarian" : "hu",
+ "Russian" : "ru",
+ "Portuguese (BR)" : "pt_BR",
}
try:
print lang_codes[manual_language]
@@ -72,12 +75,21 @@ else:
except:
pass
+try:
+ if os.name == "nt":
+ elib_intl._putenv("LANG", langs[0])
+ else:
+ os.putenv("LANG", langs[0])
+except IndexError:
+ pass
path = "locale"
gettext.bindtextdomain("CHIRP", localepath)
gettext.textdomain("CHIRP")
lang = gettext.translation("CHIRP", localepath, languages=langs,
fallback=True)
+import gtk
+
# Python <2.6 does not have str.format(), which chirp uses to make translation
# strings nicer. So, instead of installing the gettext standard "_()" function,
# we can install our own, which returns a string of the following class,
@@ -91,7 +103,13 @@ class CompatStr(str):
return base
pyver = sys.version.split()[0]
-vmaj, vmin, vrel = pyver.split(".", 3)
+
+try :
+ vmaj, vmin, vrel = pyver.split(".", 3)
+except :
+ vmaj, vmin = pyver.split(".", 2)
+ vrel = 0
+
if int(vmaj) < 2 or int(vmin) < 6:
# Python <2.6, emulate str.format()
import __builtin__
diff --git a/locale/de/LC_MESSAGES/CHIRP.mo b/locale/de/LC_MESSAGES/CHIRP.mo
new file mode 100644
index 0000000..127e167
Binary files /dev/null and b/locale/de/LC_MESSAGES/CHIRP.mo differ
diff --git a/locale/en_US/LC_MESSAGES/CHIRP.mo b/locale/en_US/LC_MESSAGES/CHIRP.mo
new file mode 100644
index 0000000..46476c1
Binary files /dev/null and b/locale/en_US/LC_MESSAGES/CHIRP.mo differ
diff --git a/locale/hu/LC_MESSAGES/CHIRP.mo b/locale/hu/LC_MESSAGES/CHIRP.mo
new file mode 100644
index 0000000..0709dbf
Binary files /dev/null and b/locale/hu/LC_MESSAGES/CHIRP.mo differ
diff --git a/locale/it/LC_MESSAGES/CHIRP.mo b/locale/it/LC_MESSAGES/CHIRP.mo
new file mode 100644
index 0000000..0b94898
Binary files /dev/null and b/locale/it/LC_MESSAGES/CHIRP.mo differ
diff --git a/locale/nl/LC_MESSAGES/CHIRP.mo b/locale/nl/LC_MESSAGES/CHIRP.mo
new file mode 100644
index 0000000..39d703e
Binary files /dev/null and b/locale/nl/LC_MESSAGES/CHIRP.mo differ
diff --git a/locale/pl/LC_MESSAGES/CHIRP.mo b/locale/pl/LC_MESSAGES/CHIRP.mo
new file mode 100644
index 0000000..022eee2
Binary files /dev/null and b/locale/pl/LC_MESSAGES/CHIRP.mo differ
diff --git a/locale/pt_BR/LC_MESSAGES/CHIRP.mo b/locale/pt_BR/LC_MESSAGES/CHIRP.mo
new file mode 100644
index 0000000..07fe797
Binary files /dev/null and b/locale/pt_BR/LC_MESSAGES/CHIRP.mo differ
diff --git a/locale/ru/LC_MESSAGES/CHIRP.mo b/locale/ru/LC_MESSAGES/CHIRP.mo
new file mode 100644
index 0000000..8e9b2e1
Binary files /dev/null and b/locale/ru/LC_MESSAGES/CHIRP.mo differ
--
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