Developers
Threads by month
- ----- 2024 -----
- December
- November
- October
- September
- August
- July
- June
- May
- April
- March
- February
- January
- ----- 2023 -----
- December
- November
- October
- September
- August
- July
- June
- May
- April
- March
- February
- January
- ----- 2022 -----
- December
- November
- October
- September
- August
- July
- June
- May
- April
- March
- February
- January
- ----- 2021 -----
- December
- November
- October
- September
- August
- July
- June
- May
- April
- March
- February
- January
- ----- 2020 -----
- December
- November
- October
- September
- August
- July
- June
- May
- April
- March
- February
- January
- ----- 2019 -----
- December
- November
- October
- September
- August
- July
- June
- May
- April
- March
- February
- January
- ----- 2018 -----
- December
- November
- October
- September
- August
- July
- June
- May
- April
- March
- February
- January
- ----- 2017 -----
- December
- November
- October
- September
- August
- July
- June
- May
- April
- March
- February
- January
- ----- 2016 -----
- December
- November
- October
- September
- August
- July
- June
- May
- April
- March
- February
- January
- ----- 2015 -----
- December
- November
- October
- September
- August
- July
- June
- May
- April
- March
- February
- January
- ----- 2014 -----
- December
- November
- October
- September
- August
- July
- June
- May
- April
- March
- February
- January
- ----- 2013 -----
- December
- November
- October
- September
- August
- July
- June
- May
- April
- March
- February
- January
- ----- 2012 -----
- December
- November
- October
- September
- August
- July
- June
- May
- April
- March
- February
- January
- ----- 2011 -----
- December
- November
- October
- September
- August
- July
- June
- May
- April
- March
December 2018
- 10 participants
- 15 discussions
04 Dec '18
I have not run tox on this driver. Fedora has dropped support for Tox on any
python installation < 3.0. Adding a downrev tox is not an option on my system.
Sorry. I will fix any problems uncovered by the test system. The image file is
an attachement to the issue.
# HG changeset patch
# User Jim Lieb <lieb(a)sea-troll.net>
# Date 1543888193 28800
# Mon Dec 03 17:49:53 2018 -0800
# Node ID 61dd024d4c987bc43c4fa9929a130eec67ac4366
# Parent c6cab71d7d7d76b63367f5529a1b2ea075a8db1b
[kguv9dplus] Add support for Wouxon KG-UV9D Plus
Issue: #3509
Adds support for Wouxon KG-UV9d Plus model available from Powerwerx
This supports all functions relative to the Wouxon/Powerwerx configuration
app with the following exceptions:
1. Scanning CTCSS/DCS (menu 51) is only available on the radio. It appears
to be in volatile memory and gets reset to default (CTCSS) on power cycle.
The supplied app doesn't support it either.
2. DTMF Transmit Interval is on the configuration tab and seems to operate
on the supplied app but it does not appear to be written to the radio.
As a result, it is not in the memory map and doesn't appear at all in
this driver.
3. The supplied app fills in '0's to the right of either the menu or reset
password UI fields. The Chirp UI doesn't seem to support field writeback so
a less than 6 digit password has trailing blanks. However, the full '0'
padded
password is correctly uploaded to radio. Re-downloading the radio will show
the
filled out field correctly. For example, a password of '1234' is actually
sent
to the radio as '123400'.
4. Some tabs (Call ids in partiicular) would be better presented as a three
column
table.
Signed-off-by: Jim Lieb <lieb(a)sea-troll.net>
diff -r c6cab71d7d7d -r 61dd024d4c98 chirp/drivers/kguv9dplus.py
--- /dev/null Thu Jan 01 00:00:00 1970 +0000
+++ b/chirp/drivers/kguv9dplus.py Mon Dec 03 17:49:53 2018 -0800
@@ -0,0 +1,1752 @@
+# Copyright 2018 Jim Lieb <lieb(a)sea-troll.net>
+#
+# Driver for Wouxon KG-UV9D Plus
+#
+# Borrowed from other chirp drivers, especially the KG-UV8D Plus
+# by Krystian Struzik <toner_82(a)tlen.pl>
+#
+# 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/>.
+
+"""Wouxun KG-UV9D Plus radio management module"""
+
+import time
+import os
+import logging
+import struct
+import string
+from chirp import util, chirp_common, bitwise, memmap, errors, directory
+from chirp.settings import RadioSetting, RadioSettingValue,
RadioSettingGroup, \
+ RadioSettingValueBoolean, RadioSettingValueList, \
+ RadioSettingValueInteger, RadioSettingValueString, \
+ RadioSettings, InvalidValueError
+
+LOG = logging.getLogger(__name__)
+
+CMD_IDENT = 0x80
+CMD_HANGUP = 0x81
+CMD_RCONF = 0x82
+CMD_WCONF = 0x83
+CMD_RCHAN = 0x84
+CMD_WCHAN = 0x85
+
+cmd_name = {
+ CMD_IDENT : "ident",
+ CMD_HANGUP : "hangup",
+ CMD_RCONF : "read config",
+ CMD_WCONF : "write config",
+ CMD_RCHAN : "read channel memory", # Unused
+ CMD_WCHAN : "write channel memory" # Unused because it is a hack...
+ }
+
+# This is used to write the configuration of the radio base on info gleaned
+# from the downloaded app. There are empty spaces and we honor them because
we
+# don't know what they are (yet) although we read the whole of memory.
+#
+# Channel memory is separate. There are 1000 (1-999) channels. These are
read/written
+# to the radio in 4 channel (96 byte) records starting at address 0xa00 and
ending at
+# 0x4800 (presuming the end of channel 1000 is 0x4860-1
+
+config_map = ( # map address, write size, write count
+ (0x40, 16, 1), # Passwords
+ (0x740, 40, 1), # FM chan 1-20
+ (0x780, 16, 1), # vfo-b-150
+ (0x790, 16, 1), # vfo-b-450
+ (0x800, 16, 1), # vfo-a-150
+ (0x810, 16, 1), # vfo-a-450
+ (0x820, 16, 1), # vfo-a-300
+ (0x830, 16, 1), # vfo-a-700
+ (0x840, 16, 1), # vfo-a-200
+ (0x860, 16, 1), # area-a-conf
+ (0x870, 16, 1), # area-b-conf
+ (0x880, 16, 1), # radio conf 0
+ (0x890, 16, 1), # radio conf 1
+ (0x8a0, 16, 1), # radio conf 2
+ (0x8b0, 16, 1), # radio conf 3
+ (0x8c0, 16, 1), # PTT-ANI
+ (0x8d0, 16, 1), # SCC
+ (0x8e0, 16, 1), # power save
+ (0x8f0, 16, 1), # Display banner
+ (0x940, 64, 2), # Scan groups and names
+ (0xa00, 64, 249),# Memory Channels 1-996
+ (0x4840, 48, 1), # 997-999
+ (0x4900, 32, 249),# Names 1-996
+ (0x6820, 24, 1), # 997-999
+ (0x7400, 64, 5), # CALL-ID 1-20, names 1-20
+ )
+
+
+MEM_VALID = 0xfc
+MEM_INVALID = 0xff
+
+# Radio memory map. This matches the reads/writes above.
+# structure elements whose name starts with x are currently unidentified
+
+_MEM_FORMAT02 = """
+#seekto 0x40;
+
+struct {
+ char reset[6];
+ char x46[2];
+ char mode_sw[6];
+ char x4e;
+} passwords;
+
+#seekto 0x740;
+
+struct {
+ u16 fm_freq;
+} fm_chans[20];
+
+// each band has its own configuration, essentially its default params
+
+struct vfo {
+ u32 freq;
+ u32 offset;
+ u16 encqt;
+ u16 decqt;
+ u8 bit7_4:3,
+ qt:3,
+ bit1_0:2;
+ u8 bit7:1,
+ scan:1,
+ bit5:1,
+ pwr:2,
+ mod:1,
+ bit1:1,
+ fm_dev:1;
+ u8 pad2:6,
+ shift:2;
+ u8 zeros;
+};
+
+#seekto 0x780;
+
+struct {
+ struct vfo band_150;
+ struct vfo band_450;
+} vfo_b;
+
+#seekto 0x800;
+
+struct {
+ struct vfo band_150;
+ struct vfo band_450;
+ struct vfo band_300;
+ struct vfo band_700;
+ struct vfo band_200;
+} vfo_a;
+
+// There are two independent radios, aka areas (as described
+// in the manual as the upper and lower portions of the display...
+
+struct area_conf {
+ u8 w_mode;
+ u8 x861;
+ u8 w_chan;
+ u8 scan_grp;
+ u8 bcl;
+ u8 sql;
+ u8 cset;
+ u8 step;
+ u8 scan_mode;
+ u8 x869;
+ u8 scan_range;
+ u8 x86b;
+ u8 x86c;
+ u8 x86d;
+ u8 x86e;
+ u8 x86f;
+};
+
+#seekto 0x860;
+
+struct area_conf a_conf;
+
+#seekto 0x870;
+
+struct area_conf b_conf;
+
+#seekto 0x880;
+
+struct {
+ u8 menu_avail;
+ u8 reset_avail;
+ u8 x882;
+ u8 x883;
+ u8 lang;
+ u8 x885;
+ u8 beep;
+ u8 auto_am;
+ u8 qt_sw;
+ u8 lock;
+ u8 x88a;
+ u8 pf1;
+ u8 pf2;
+ u8 pf3;
+ u8 s_mute;
+ u8 type_set;
+ u8 tot;
+ u8 toa;
+ u8 ptt_id;
+ u8 x893;
+ u8 id_dly;
+ u8 x895;
+ u8 voice_sw;
+ u8 s_tone;
+ u8 abr_lvl;
+ u8 ring_time;
+ u8 roger;
+ u8 x89b;
+ u8 abr;
+ u8 save_m;
+ u8 lock_m;
+ u8 auto_lk;
+ u8 rpt_ptt;
+ u8 rpt_spk;
+ u8 rpt_rct;
+ u8 prich_sw;
+ u16 pri_ch;
+ u8 x8a6;
+ u8 x8a7;
+ u8 dtmf_st;
+ u8 dtmf_tx;
+ u8 x8aa;
+ u8 sc_qt;
+ u8 apo_tmr;
+ u8 vox_grd;
+ u8 vox_dly;
+ u8 rpt_kpt;
+ struct {
+ u16 scan_st;
+ u16 scan_end;
+ } a;
+ struct {
+ u16 scan_st;
+ u16 scan_end;
+ } b;
+ u8 x8b8;
+ u8 x8b9;
+ u8 x8ba;
+ u8 ponmsg;
+ u8 blcdsw;
+ u8 bledsw;
+ u8 x8be;
+ u8 x8bf;
+} settings;
+
+
+#seekto 0x8c0;
+struct {
+ u8 code[6];
+ char x8c6[10];
+} my_callid;
+
+#seekto 0x8d0;
+struct {
+ u8 scc[6];
+ char x8d6[10];
+} stun;
+
+#seekto 0x8e0;
+struct {
+ u16 wake;
+ u16 sleep;
+} save[4];
+
+#seekto 0x8f0;
+struct {
+ char banner[16];
+} display;
+
+#seekto 0x940;
+struct {
+ struct {
+ i16 scan_st;
+ i16 scan_end;
+ } addrs[10];
+ u8 x0968[8];
+ struct {
+ char name[8];
+ } names[10];
+} scn_grps;
+
+// this array of structs is marshalled via the R/WCHAN commands
+#seekto 0xa00;
+struct {
+ u32 rxfreq;
+ u32 txfreq;
+ u16 encQT;
+ u16 decQT;
+ u8 bit7_5:3, // all ones
+ qt:3,
+ bit1_0:2;
+ u8 bit7:1,
+ scan:1,
+ bit5:1,
+ pwr:2,
+ mod:1,
+ bit1:1,
+ fm_dev:1;
+ u8 state;
+ u8 c3;
+} chan_blk[999];
+
+// nobody really sees this. It is marshalled with chan_blk in 4 entry chunks
+#seekto 0x4900;
+
+// Tracks with the index of chan_blk[]
+struct {
+ char name[8];
+} chan_name[999];
+
+#seekto 0x7400;
+struct {
+ u8 cid[6];
+ u8 pad[2];
+}call_ids[20];
+
+// This array tracks with the index of call_ids[]
+struct {
+ char name[6];
+ char pad[2];
+} cid_names[20];
+ """
+
+
+# Support for the Wouxun KG-UV9D Plus radio
+# Serial coms are at 19200 baud
+# The data is passed in variable length records
+# Record structure:
+# Offset Usage
+# 0 start of record (\x7d)
+# 1 Command (6 commands, see above)
+# 2 direction (\xff PC-> Radio, \x00 Radio -> PC)
+# 3 length of payload (excluding header/checksum) (n)
+# 4 payload (n bytes)
+# 4+n+1 checksum - byte sum (% 256) of bytes 1 -> 4+n
+#
+# Memory Read Records:
+# the payload is 3 bytes, first 2 are offset (big endian),
+# 3rd is number of bytes to read
+# Memory Write Records:
+# the maximum payload size (from the Wouxun software) seems to be 66 bytes
+# (2 bytes location + 64 bytes data).
+
+def _pkt_encode(op, payload):
+ """Assemble a packet for the radio and encode it for transmission.
+ Yes indeed, the checksum we store is only 4 bits. Why, I suspect it's a
bug in
+ the radio firmware guys didn't want to fix, i.e. a typo 0xff -> 0xf..."""
+
+ data = bytearray()
+ data.append(0x7d) # tag that marks the beginning of the packet
+ data.append(op)
+ data.append(0xff) # 0xff is from app to radio
+ # calc checksum from op to end
+ cksum = op + 0xff
+ if (payload):
+ data.append(len(payload))
+ cksum += len(payload)
+ for byte in payload:
+ cksum += byte
+ data.append(byte)
+ else:
+ data.append(0x00)
+
+ data.append(cksum & 0xf) # Yea, this is a 4 bit cksum (also known as a
bug)
+
+ # now obfuscate by an xor starting with first payload byte ^ 0x52
+ # including the trailing cksum.
+ xorbits = 0x52
+ for i, byte in enumerate(data[4:]):
+ xord = xorbits ^ byte
+ data[i + 4] = xord
+ xorbits = xord
+ return(data)
+
+def _pkt_decode(data):
+ """Take a packet hot off the wire and decode it into clear text and
return the fields.
+ We say <<cleartext>> here because all it turns out to be is annoying
obfuscation.
+ This is the inverse of pkt_decode"""
+
+ # we don't care about data[0]. It is always 0x7d and not included in
checksum
+ op = data[1]
+ direction = data[2]
+ bytecount = data[3]
+
+ # First un-obfuscate the payload and cksum
+ payload = bytearray()
+ xorbits = 0x52
+ for i, byte in enumerate(data[4:]):
+ payload.append(xorbits ^ byte)
+ xorbits = byte
+
+ # Calculate the checksum starting with the 3 bytes of the header
+ cksum = op + direction + bytecount
+ for byte in payload[:-1]:
+ cksum += byte
+ cksum_match = (cksum & 0xf) == payload[-1] # yes, a 4 bit cksum to match
the encode
+ if (not cksum_match):
+ LOG.debug("Checksum missmatch: %x != %x; " % (cksum, payload[-1]))
+ return (cksum_match, op, payload[:-1])
+
+# UI callbacks to process input for mapping UI fields to memory cells
+
+def freq2int(val, min, max):
+ """Convert a frequency as a string to a u32. Units is Hz
+ """
+ _freq = chirp_common.parse_freq(str(val))
+ if _freq > max or _freq < min:
+ raise InvalidValueError("Frequency %s is not with in %s-%s" %
+ (chirp_common.format_freq(_freq),
+ chirp_common.format_freq(min),
+ chirp_common.format_freq(max)))
+ return _freq
+
+def int2freq(freq):
+ """
+ Convert a u32 frequency to a string for UI data entry/display
+ This is stored in the radio as units of 10Hz which we compensate to Hz.
+ A value of -1 indicates <no freqency>, i.e. unused channel.
+ """
+ if (int(freq) > 0):
+ f = chirp_common.format_freq(freq)
+ return f
+ else:
+ return ""
+
+def freq2short(val, min, max):
+ """Convert a frequency as a string to a u16 which is units of 10KHz
+ """
+ _freq = chirp_common.parse_freq(str(val))
+ if _freq > max or _freq < min:
+ raise InvalidValueError("Frequency %s is not with in %s-%s" %
+ (chirp_common.format_freq(_freq),
+ chirp_common.format_freq(min),
+ chirp_common.format_freq(max)))
+ return _freq/100000 & 0xFFFF
+
+def short2freq(freq):
+ """
+ Convert a short frequency to a string for UI data entry/display
+ This is stored in the radio as units of 10KHz which we compensate to
Hz.
+ A value of -1 indicates <no frequency>, i.e. unused channel.
+ """
+ if (int(freq) > 0):
+ f = chirp_common.format_freq(freq *100000)
+ return f
+ else:
+ return ""
+
+def tone2short(t):
+ """Convert a string tone or DCS to an encoded u16
+ """
+ tone = str(t)
+ if tone == "----":
+ u16tone = 0x0000
+ elif tone[0] == 'D': # This is a DCS code
+ c = tone[1: -1]
+ code = int(c, 8)
+ if tone[-1] == 'I':
+ code |= 0x4000
+ u16tone = code | 0x8000
+ else: # This is an analog CTCSS
+ u16tone = int(tone[0:-2]+tone[-1]) & 0xffff # strip the '.'
+ return u16tone
+
+def short2tone(tone):
+ """ Map a binary CTCSS/DCS to a string name for the tone
+ """
+ if tone == 0 or tone == 0xffff:
+ ret = "----"
+ else:
+ code = tone & 0x3fff
+ if tone & 0x8000: # This is a DCS
+ if tone & 0x4000: #This is an inverse code
+ ret = "D%0.3oI" % code
+ else:
+ ret = "D%0.3oN" % code
+ else: # Just plain old analog CTCSS
+ ret = "%4.1f" % (code / 10.0)
+ return ret
+
+def callid2str(cid):
+ """Caller ID per MDC-1200 spec? Must be 3-6 digits (100 - 999999).
+ One digit (binary) per byte, terminated with '0xc'
+ """
+
+ bin2ascii = " 1234567890"
+ cidstr = ""
+ for i in range(0, 6):
+ b = cid[i].get_value()
+ if b == 0xc: # the cid EOL
+ break;
+ if b == 0 or b > 0xa:
+ raise InvalidValueError("Caller ID code has illegal byte 0x%x" %
b)
+ cidstr += bin2ascii[b]
+ return cidstr
+
+def str2callid(val):
+ """ Convert caller id strings from callid2str.
+ """
+ ascii2bin = "0123456789"
+ s = str(val).strip()
+ if len(s) < 3 or len(s) > 6:
+ raise InvalidValueError("Caller ID must be at least 3 and no more
than 6 digits")
+ if s[0] == '0':
+ raise InvalidValueError("First digit of a Caller ID cannot be a zero
'0'")
+ blk = bytearray()
+ for c in s:
+ if c not in ascii2bin:
+ raise InvalidValueError("Caller ID must be all digits 0x%x" % c)
+ b = (0xa, 0x1, 0x2, 0x3, 0x4, 0x5, 0x6, 0x7, 0x8, 0x9)[int(c)]
+ blk.append(b)
+ if len(blk) < 6:
+ blk.append(0xc) # EOL a short ID
+ if len(blk) < 6:
+ for i in range(0, (6 - len(blk))):
+ blk.append(0xf0)
+ return blk
+
+def digits2str(digits, padding=' ', width=6):
+ """Convert a password or SCC digit string to a string
+ Passwords are expanded to and must be 6 chars. Fill them with '0'
+ """
+
+ bin2ascii = "0123456789"
+ digitsstr = ""
+ for i in range(0, 6):
+ b = digits[i].get_value()
+ if b == 0xc: # the digits EOL
+ break;
+ if b >= 0xa:
+ raise InvalidValueError("Value has illegal byte 0x%x" % ord(b))
+ digitsstr += bin2ascii[b]
+ digitsstr = digitsstr.ljust(width, padding)
+ return digitsstr
+
+def str2digits(val):
+ """ Callback for edited strings from digits2str.
+ """
+ ascii2bin = " 0123456789"
+ s = str(val).strip()
+ if len(s) < 3 or len(s) > 6:
+ raise InvalidValueError("Value must be at least 3 and no more than 6
digits")
+ blk = bytearray()
+ for c in s:
+ if c not in ascii2bin:
+ raise InvalidValueError("Value must be all digits 0x%x" % c)
+ blk.append(int(c))
+ for i in range(len(blk), 6):
+ blk.append(0xc) # EOL a short ID
+ return blk
+
+def name2str(name):
+ """ Convert a callid or scan group name to a string
+ Deal with fixed field padding (\0 or \0xff)
+ """
+
+ namestr = ""
+ for i in range(0, len(name)):
+ b = ord(name[i].get_value())
+ if b != 0 and b != 0xff:
+ namestr += chr(b)
+ return namestr
+
+def str2name(val, size=6, fillchar='\0', emptyfill='\0'):
+ """ Convert a string to a name. A name is a 6 element bytearray
+ with ascii chars.
+ """
+ val = str(val).rstrip(' \t\r\n\0\0xff')
+ if len(val) == 0:
+ name = "".ljust(size, emptyfill)
+ else:
+ name = val.ljust(size, fillchar)
+ return name
+
+def pw2str(pw):
+ """Convert a password string (6 digits) to a string
+ Passwords must be 6 digits. If it is shorter, pad right with '0'
+ """
+ pwstr = ""
+ ascii2bin = "0123456789"
+ for i in range(0, len(pw)):
+ b = pw[i].get_value()
+ if b not in ascii2bin:
+ raise InvalidValueError("Value must be digits 0-9")
+ pwstr += b
+ pwstr = pwstr.ljust(6, '0')
+ return pwstr
+
+def str2pw(val):
+ """Store a password from UI to memory obj
+ If we clear the password (make it go away), change the
+ empty string to '000000' since the radio must have *something*
+ Also, fill a < 6 digit pw with 0's
+ """
+ ascii2bin = "0123456789"
+ val = str(val).rstrip(' \t\r\n\0\0xff')
+ if len(val) == 0: # a null password
+ val = "000000"
+ for i in range(0, len(val)):
+ b = val[i]
+ if b not in ascii2bin:
+ raise InvalidValueError("Value must be digits 0-9")
+ if len(val) == 0:
+ pw = "".ljust(6, '\0')
+ else:
+ pw = val.ljust(6, '0')
+ return pw
+
+
+# Helpers to replace python2 things like confused str/byte
+
+def _hex_print(data, addrfmt=None):
+ """Return a hexdump-like encoding of @data
+ We expect data to be a bytearray, not a string.
+ Expanded from borrowed code to use the first 2 bytes as the address
+ per comm packet format.
+ """
+ if addrfmt is None:
+ addrfmt = '%(addr)03i'
+ addr = 0
+ else: # assume first 2 bytes are address
+ a = struct.unpack(">H", data[0:2])
+ addr = a[0]
+ data = data[2:]
+
+ block_size = 16
+
+ lines = (len(data) / block_size)
+ if (len(data) % block_size > 0):
+ lines += 1
+
+ out = ""
+ left = len(data)
+ for block in range(0, lines):
+ addr += block * block_size
+ try:
+ out += addrfmt % locals()
+ except (OverflowError, ValueError, TypeError, KeyError):
+ out += "%03i" % addr
+ out += ': '
+
+ if left < block_size:
+ limit = left
+ else:
+ limit = block_size
+
+ for j in range(0, block_size):
+ if (j < limit):
+ out += "%02x " % data[(block * block_size) + j]
+ else:
+ out += " "
+
+ out += " "
+
+ for j in range(0, block_size):
+
+ if (j < limit):
+ _byte = data[(block * block_size) + j]
+ if _byte >= 0x20 and _byte < 0x7F:
+ out += "%s" % chr(_byte)
+ else:
+ out += "."
+ else:
+ out += " "
+ out += "\n"
+ if (left > block_size):
+ left -= block_size
+
+ return out
+
+# Useful UI lists
+
+STEPS = [2.5, 5.0, 6.25, 10.0, 12.5, 25.0, 50.0, 100.0]
+S_TONES = [str(x) for x in [1000, 1450, 1750, 2100]]
+STEP_LIST = [str(x)+"kHz" for x in STEPS]
+ROGER_LIST = ["Off", "Begin", "End", "Both"]
+TIMEOUT_LIST = [str(x) + "s" for x in range(15, 601, 15)]
+TOA_LIST = ["Off"] + ["%ds" % t for t in range(1, 10)]
+BANDWIDTH_LIST = ["Wide", "Narrow"]
+LANGUAGE_LIST = ["English", "Chinese"]
+PF1KEY_LIST = ["OFF", "call id", "r-alarm", "SOS", "SF-TX"]
+PF2KEY_LIST = ["OFF", "Scan", "Second", "lamp", "SDF-DIR", "K-lamp"]
+PF3KEY_LIST = ["OFF", "Call ID", "R-ALARM", "SOS","SF-TX"]
+WORKMODE_LIST = ["VFO freq", "Channel No.", "Ch. No.+Freq.", "Ch. No.+Name"]
+BACKLIGHT_LIST = ["Off"] + ["%sS" % t for t in range(1, 31)] + ["Always On"]
+SAVE_MODES = ["Off", "1", "2", "3", "4"]
+LOCK_MODES = ["key-lk", "key+pg", "key+ptt", "all"]
+APO_TIMES = ["Off"] + ["%dm" % t for t in range(15, 151, 15)]
+OFFSET_LIST = ["none", "+", "-"]
+PONMSG_LIST = ["Battery Volts", "Bitmap"]
+SPMUTE_LIST = ["QT", "QT*T", "QT&T"]
+DTMFST_LIST = ["Off", "DT-ST", "ANI-ST", "DT-ANI"]
+DTMF_TIMES = ["%d" % x for x in range(80, 501, 20)]
+PTTID_LIST = ["Off", "Begin", "End", "Both"]
+ID_DLY_LIST = ["%dms" % t for t in range(100, 3001, 100)]
+VOX_GRDS = ["Off"] + ["%dlevel" % l for l in range(1,11)]
+VOX_DLYS = ["Off"] + ["%ds" %t for t in range(1, 5)]
+RPT_KPTS = ["Off"] + ["%dms" % t for t in range(100, 5001, 100)]
+LIST_1_5 = ["%s" % x for x in range(1,6)]
+LIST_0_9 = ["%s" % x for x in range(0, 10)]
+LIST_1_20 = ["%s" % x for x in range(1, 21)]
+LIST_OFF_10 = ["Off"] + ["%s" % x for x in range(1, 11)]
+SCANGRP_LIST = ["All"] + ["%s" % x for x in range(1, 11)]
+SCANMODE_LIST = ["TO", "CO", "SE"]
+SCANRANGE_LIST = ["Current band", "freq range", "ALL"]
+SCQT_LIST = ["Decoder", "Encoder", "Both"]
+S_MUTE_LIST = ["off", "rx mute", "tx mute", "r/t mute"]
+POWER_LIST = ["Low", "Med", "High"]
+RPTMODE_LIST = ["Radio", "One direction Repeater", "Two direction repeater"]
+TONE_LIST = ["----"] + ["%s" % str(t) for t in chirp_common.TONES] + \
+ ["D%0.3dN" % dts for dts in chirp_common.DTCS_CODES] + \
+ ["D%0.3dI" % dts for dts in chirp_common.DTCS_CODES]
+
+(a)directory.register
+class KGUV9DPlusRadio(chirp_common.CloneModeRadio,
+ chirp_common.ExperimentalRadio):
+
+ """Wouxun KG-UV9D Plus"""
+ VENDOR = "Wouxun"
+ MODEL = "KG-UV9D Plus"
+ _model = "KG-UV9D"
+ _rev = "00" # default rev for the radio I know about...
+ _file_ident = "kg-uv9d"
+ BAUD_RATE = 19200
+ POWER_LEVELS = [chirp_common.PowerLevel("L", watts=1),
+ chirp_common.PowerLevel("M", watts=2),
+ chirp_common.PowerLevel("H", watts=5)]
+ _mmap = ""
+
+ def _read_record(self):
+ """ Read and validate the header of a radio reply.
+ A record is a formatted byte stream as follows:
+ 0x7D All records start with this
+ opcode This is in the set of legal commands. The radio reply
matches the request
+ dir This is the direction, 0xFF to the radio, 0x00 from the
radio.
+ cnt Count of bytes in payload (not including the trailing
checksum byte)
+ """
+
+ data = bytearray(self.pipe.read(4)) # first get the header and
validate it
+ if (len(data) < 4):
+ raise errors.RadioError('Radio did not respond')
+ if (data[0] != 0x7D):
+ raise errors.RadioError('Radio reply garbled (%02x)' % data[0])
+ if (data[1] not in cmd_name):
+ raise errors.RadioError("Unrecognized opcode (%02x)" % data[1])
+ if (data[2] != 0x00):
+ raise errors.RadioError("Direction incorrect. Got (%02x)" %
data[2])
+ payload_len = data[3]
+ data.extend(self.pipe.read(payload_len + 1)) # don't forget to read
the checksum byte
+ if (len(data) != (payload_len + 5)): # we got a short read
+ raise errors.RadioError("Radio reply wrong size. Wanted %d, got
%d" %
+ ((payload_len + 1), (len(data) - 4)))
+ return _pkt_decode(data)
+
+ def _write_record(self, cmd, payload = None):
+ """ Write a request packet to the radio.
+ """
+
+ packet = _pkt_encode(cmd, payload)
+ self.pipe.write(packet)
+
+
+ @classmethod
+ def match_model(cls, filedata, filename):
+ """Look for bits in the file image and see if it looks like ours...
+ TODO: there is a bunch of rubbish between 0x50 and 0x160 that is
still an known unknown
+ """
+ return cls._file_ident in filedata[0x51:0x59].lower()
+
+ def _identify(self):
+ """ Identify the radio
+ The ident block identifies the radio and its capabilities. This block
is always 78 bytes
+ The rev == '01' is the base radio and '02' seems to be the '-Plus'
version
+ I don't really trust the content after the model and revision. One
would assume this is
+ pretty much constant data but I have seen differences between my
radio and the dump named
+ KG-UV9D-Plus-OutOfBox-Read.txt from bug #3509. The first five bands
match the OEM windows
+ app except the 350-400 band. The OOB trace has the 700MHz band
different.
+
+ TODO: This could be smarter and reject a radio not actually a UV9D...
+ """
+
+ for _i in range(0, 10): # retry 10 times just in case we get junk
until sync'd up
+ self._write_record(CMD_IDENT)
+ chksum_match, op, _resp = self._read_record()
+ if len(_resp) == 0:
+ raise Exception("Radio not responding")
+ if len(_resp) != 74:
+ LOG.error("Expected and IDENT reply of 78 bytes. Got (%d)" %
len(_resp))
+ continue
+ if not chksum_match:
+ LOG.error("Checksum error: retrying ident...")
+ time.sleep(0.100)
+ continue
+ if op != CMD_IDENT:
+ LOG.error("Expected IDENT reply. Got (%02x)" % op)
+ continue
+ LOG.debug("Got:\n%s" % _hex_print(_resp))
+ (mod, rev) = struct.unpack(">7s2s", _resp[0:9])
+ LOG.debug("Model %s, rev %s" % (mod, rev))
+ if mod == self._model:
+ self._rev = rev
+ return
+ else:
+ raise Exception("Unable to identify radio")
+ raise Exception("All retries to identify failed")
+
+ def process_mmap(self):
+ if self._rev == "02" or self._rev == "00":
+ self._memobj = bitwise.parse(_MEM_FORMAT02, self._mmap)
+ else: ## this is where you elif the other variants and non-Plus
radios
+ raise errors.RadioError("Unrecognized model variation (%s). No
memory map for it" %
+ self._rev)
+
+ def sync_in(self):
+ """ Public sync_in
+ Download contents of the radio. Throw errors back to the core if
the radio does
+ not respond.
+ """
+ try:
+ self._identify()
+ self._mmap = self._do_download()
+ self._write_record(CMD_HANGUP)
+ except errors.RadioError:
+ raise
+ except Exception, e:
+ LOG.exception('Unknown error during download process')
+ raise errors.RadioError("Failed to communicate with radio: %s" %
e)
+ self.process_mmap()
+
+ def sync_out(self):
+ """ Public sync_out
+ Upload the modified memory image into the radio.
+ """
+
+ try:
+ self._identify()
+ self._do_upload()
+ self._write_record(CMD_HANGUP)
+ except errors.RadioError:
+ raise
+ except Exception, e:
+ raise errors.RadioError("Failed to communicate with radio: %s" %
e)
+ return
+
+ def _do_download(self):
+ """ Read the whole of radio memory in 64 byte chunks.
+ We load the config space followed by loading memory channels. The
radio seems
+ to be a "clone" type and the memory channels are actually within the
config space.
+ There are separate commands (CMD_RCHAN, CMD_WCHAN) for reading
channel memory but
+ these seem to be a hack that can only do 4 channels at a time. Since
the radio only
+ supports 999, (can only support 3 chars in the display UI?) although
the vendors
+ app reads 1000 channels, it hacks back to config writes (CMD_WCONF)
for the last 3
+ channels and names. We keep it simple and just read the whole thing
even though
+ the vendor app doesn't. Channels are separate in their app simply
+ because the radio protocol has read/write commands to access it. What
they do
+ is simply marshal the frequency+mode bits in 4 channel chunks
followed by a separate
+ chunk of for names. In config space, they are two separate arrays
1..999. Given that
+ this space is not a multiple of 4, there is hackery on upload to do
the writes to
+ config space. See upload for this.
+ """
+
+ mem = bytearray(0x8000) # The radio's memory map is 32k
+ for addr in range(0, 0x8000, 64):
+ req = bytearray(struct.pack(">HB", addr, 64))
+ self._write_record(CMD_RCONF, req)
+ chksum_match, op, resp = self._read_record()
+ if not chksum_match:
+ LOG.debug(_hex_print(resp))
+ raise Exception("Checksum error while reading configuration
(0x%x)" % addr)
+ pa = struct.unpack(">H", resp[0:2])
+ pkt_addr = pa[0]
+ payload = resp[2:]
+ if op != CMD_RCONF or addr != pkt_addr:
+ raise Exception("Expected CMD_RCONF (%x) reply. Got (%02x:
%x)" %
+ (addr, op, pkt_addr))
+ LOG.debug("Config read (0x%x):\n%s" % (addr, _hex_print(resp,
'0x%(addr)04x')))
+ for i in range(0, len(payload) - 1):
+ mem[addr + i] = payload[i]
+ if self.status_fn:
+ status = chirp_common.Status()
+ status.cur = addr
+ status.max = 0x8000
+ status.msg = "Cloning from radio"
+ self.status_fn(status)
+ strmem = "".join([chr(x) for x in mem])
+ return memmap.MemoryMap(strmem)
+
+ def _do_upload(self):
+ """Walk through the config map and write updated records to the
radio. The
+ config map contains only the regions we know about. We don't use the
channel
+ memory commands to avoid the hackery of using config write commands
to fill
+ in the last 3 channel memory and names slots. As we discover other
useful goodies
+ in the map, we can add more slots...
+ """
+ for ar,size, count in config_map:
+ for addr in range(ar, ar +(size*count), size):
+ req = bytearray(struct.pack(">H", addr))
+ req.extend(self.get_mmap()[addr:addr + size])
+ self._write_record(CMD_WCONF, req)
+ LOG.debug("Config write (0x%x):\n%s" % (addr,
_hex_print(req)))
+ chksum_match, op, ack = self._read_record()
+ LOG.debug("Config write ack [%x]\n%s" % (addr,
_hex_print(ack)))
+ a = struct.unpack(">H", ack) # big endian int()...
+ ack = a[0]
+ if not chksum_match or op != CMD_WCONF or addr != ack:
+ msg = ""
+ if not chksum_match:
+ msg += "Checksum err, "
+ if op != CMD_WCONF:
+ msg += "cmd mismatch %x != %x, " % (op, CMD_WCONF)
+ if addr != ack:
+ msg += "ack error %x != %x, " % (addr, ack)
+ raise Exception("Radio did not ack block: %s error" %
msg)
+ if self.status_fn:
+ status = chirp_common.Status()
+ status.cur = addr
+ status.max = 0x8000
+ status.msg = "Update radio"
+ self.status_fn(status)
+
+ def get_features(self):
+ """ Public get_features
+ Return the features of this radio once we have identified it and
+ gotten its bits
+ """
+ rf = chirp_common.RadioFeatures()
+ rf.has_settings = True
+ rf.has_ctone = True
+ rf.has_rx_dtcs = True
+ rf.has_cross = True
+ rf.has_tuning_step = False
+ rf.has_bank = False
+ rf.can_odd_split = True
+ rf.valid_skips = ["", "S"]
+ rf.valid_tmodes = ["", "Tone", "TSQL", "DTCS", "Cross"]
+ rf.valid_cross_modes = [
+ "Tone->Tone",
+ "Tone->DTCS",
+ "DTCS->Tone",
+ "DTCS->",
+ "->Tone",
+ "->DTCS",
+ "DTCS->DTCS",
+ ]
+ rf.valid_modes = ["FM", "NFM", "AM"]
+ rf.valid_power_levels = self.POWER_LEVELS
+ rf.valid_name_length = 8
+ rf.valid_duplexes = ["", "-", "+", "split", "off"]
+ rf.valid_bands = [(108000000, 136000000), # Aircraft (receive only
AM)
+ (136000000, 180000000), # supports 2m
+ (230000000, 250000000),
+ (350000000, 400000000),
+ (400000000, 520000000), # supports 70cm
+ (700000000, 985000000)]
+ rf.valid_characters = chirp_common.CHARSET_ASCII
+ rf.memory_bounds = (1, 999) # 999 memories
+ return rf
+
+ @classmethod
+ def get_prompts(cls):
+ rp = chirp_common.RadioPrompts()
+ rp.experimental = ("This radio driver is currently under development.
"
+ "There are no known issues with it, but you should
"
+ "proceed with caution.")
+ return rp
+
+ def get_raw_memory(self, number):
+ return repr(self._memobj.chan_blk[number])
+
+ def _get_tone(self, _mem, mem):
+ """Decode both the encode and decode CTSS/DCS codes from the memory
channel
+ and stuff them into the UI memory channel row.
+ """
+ txtone = short2tone(_mem.encQT)
+ rxtone = short2tone(_mem.decQT)
+ polarity = "NN"
+
+ if txtone == "----":
+ txmode = ""
+ elif txtone[0] == "D":
+ mem.tx_dtcs = int(txtone[1,-1])
+ polarity[0] = txtone[-1]
+ txmode = "DTCS"
+ else:
+ mem.rtone = float(txtone)
+ txmode = "Tone"
+
+ if rxtone == "----":
+ rxmode = ""
+ elif rxtone[0] == "D":
+ mem.rx_dtcs = int(rxtone[1,4])
+ polarity[1] = rxtone[-1]
+ rxmode = "DTCS"
+ else:
+ mem.ctone = float(rxtone)
+ rxmode = "Tone"
+
+ if txmode == "Tone" and len(rxmode) == 0:
+ 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 (len(rxmode) + len(txmode)) > 0:
+ mem.cross_mode = "%s->%s" % (txmode, rxmode)
+
+ mem.dtcs_polarity = polarity
+
+ LOG.debug("_get_tone: Got TX %s (%i) RX %s (%i)" %
+ (txmode, _mem.encQT, rxmode, _mem.decQT))
+
+ def get_memory(self, number):
+ """ Public get_memory
+ Return the channel memory referenced by number to the UI.
+ """
+ _mem = self._memobj.chan_blk[number - 1]
+ _nam = self._memobj.chan_name[number - 1]
+
+ mem = chirp_common.Memory()
+ mem.number = number
+ _valid = _mem.state
+ if _valid != MEM_VALID:
+ mem.empty = True
+ return mem
+ else:
+ mem.empty = False
+
+ mem.freq = int(_mem.rxfreq) * 10
+
+ if _mem.txfreq == 0xFFFFFFFF:
+ # TX freq not set
+ mem.duplex = "off"
+ mem.offset = 0
+ elif int(_mem.rxfreq) == int(_mem.txfreq):
+ mem.duplex = ""
+ mem.offset = 0
+ elif abs(int(_mem.rxfreq) * 10 - int(_mem.txfreq) * 10) > 70000000:
+ mem.duplex = "split"
+ mem.offset = int(_mem.txfreq) * 10
+ else:
+ mem.duplex = int(_mem.rxfreq) > int(_mem.txfreq) and "-" or "+"
+ mem.offset = abs(int(_mem.rxfreq) - int(_mem.txfreq)) * 10
+
+ for char in _nam.name:
+ if char != 0:
+ mem.name += chr(char)
+ mem.name = mem.name.rstrip()
+
+ self._get_tone(_mem, mem)
+
+ mem.skip = "" if bool(_mem.scan) else "S"
+
+ mem.power = self.POWER_LEVELS[_mem.pwr]
+ if _mem.mod == 1:
+ mem.mode = "AM"
+ elif _mem.fm_dev == 0:
+ mem.mode = "FM"
+ else:
+ mem.mode = "NFM"
+ # qt has no home in the UI
+ return mem
+
+ def _set_tone(self, mem, _mem):
+ """Update the memory channel block CTCC/DCS tones from the UI fields
+ """
+ def _set_dcs(code, pol):
+ val = int("%i" % code, 8) + 0x8000
+ if pol == "R":
+ val += 0x4000
+ return val
+
+ rx_mode = tx_mode = None
+ rxtone = txtone = 0x0000
+
+ if mem.tmode == "Tone":
+ tx_mode = "Tone"
+ txtone = int(mem.rtone * 10)
+ elif mem.tmode == "TSQL":
+ rx_mode = tx_mode = "Tone"
+ rxtone = txtone = int(mem.ctone * 10)
+ elif mem.tmode == "DTCS":
+ tx_mode = rx_mode = "DTCS"
+ txtone = _set_dcs(mem.dtcs, mem.dtcs_polarity[0])
+ rxtone = _set_dcs(mem.dtcs, mem.dtcs_polarity[1])
+ elif mem.tmode == "Cross":
+ tx_mode, rx_mode = mem.cross_mode.split("->")
+ if tx_mode == "DTCS":
+ txtone = _set_dcs(mem.dtcs, mem.dtcs_polarity[0])
+ elif tx_mode == "Tone":
+ txtone = int(mem.rtone * 10)
+ if rx_mode == "DTCS":
+ rxtone = _set_dcs(mem.rx_dtcs, mem.dtcs_polarity[1])
+ elif rx_mode == "Tone":
+ rxtone = int(mem.ctone * 10)
+
+ _mem.decQT = rxtone
+ _mem.encQT = txtone
+
+ LOG.debug("Set TX %s (%i) RX %s (%i)" %
+ (tx_mode, _mem.encQT, rx_mode, _mem.decQT))
+
+ def set_memory(self, mem):
+ """ Public set_memory
+ Inverse of get_memory. Update the radio memory image from the
+ mem object
+ """
+ number = mem.number
+
+ _mem = self._memobj.chan_blk[number - 1]
+ _nam = self._memobj.chan_name[number - 1]
+
+ if mem.empty:
+ _mem.set_raw("\xFF" * (_mem.size() / 8))
+ _nam.name = str2name("", 8, '\0', '\0')
+ _mem.state = MEM_INVALID
+ return
+
+ _mem.rxfreq = int(mem.freq / 10)
+ if mem.duplex == "off":
+ _mem.txfreq = 0xFFFFFFFF
+ elif mem.duplex == "split":
+ _mem.txfreq = int(mem.offset / 10)
+ elif mem.duplex == "+":
+ _mem.txfreq = int(mem.freq / 10) + int(mem.offset / 10)
+ elif mem.duplex == "-":
+ _mem.txfreq = int(mem.freq / 10) - int(mem.offset / 10)
+ else:
+ _mem.txfreq = int(mem.freq / 10)
+ _mem.scan = int(mem.skip != "S")
+ if mem.mode == "FM":
+ _mem.mod == 0 # make sure forced AM is off
+ _mem.fm_dev = 0
+ elif mem.mode == "NFM":
+ _mem.mod = 0
+ _mem.fm_dev = 1
+ elif mem.mode == "AM":
+ _mem.mod = 1 # AM on
+ _mem.fm_dev = 0 # default to wide FM bandwidth
+ else:
+ _mem.mod = 0
+ _mem.fm_dev = 0 # default is wide FM
+ _mem.fm_dev = int(mem.mode != "FM")
+ # set the tone
+ self._set_tone(mem, _mem)
+ # set the power
+ if mem.power:
+ _mem.pwr = self.POWER_LEVELS.index(mem.power)
+ else:
+ _mem.pwr = True
+
+ # Set fields we can't access via the UI table to safe defaults
+ _mem.qt = 0 # mute mode to QT
+
+ _nam.name = str2name(mem.name, 8, '\0', '\0')
+ _mem.state = MEM_VALID
+
+## Build the UI configuration tabs
+## the channel memory tab is built by the core. We have no control over it
+
+ def _core_tab(self):
+ """ Build Core Configuration tab
+ Radio settings common to all modes and areas go here.
+ """
+ s = self._memobj.settings
+
+ cf = RadioSettingGroup("cfg_grp", "Configuration")
+
+ cf.append(RadioSetting("auto_am",
+ "Auto detect AM(53)",
+ RadioSettingValueBoolean(s.auto_am)))
+ cf.append(RadioSetting("qt_sw",
+ "Scan tone detect(59)",
+ RadioSettingValueBoolean(s.qt_sw)))
+ cf.append(RadioSetting("s_mute",
+ "SubFreq Mute(60)",
+ RadioSettingValueList(S_MUTE_LIST,
+ S_MUTE_LIST[s.s_mute])))
+ cf.append(RadioSetting("tot",
+ "Transmit timeout Timer(10)",
+ RadioSettingValueList(TIMEOUT_LIST,
+ TIMEOUT_LIST[s.tot])))
+ cf.append(RadioSetting("toa",
+ "Transmit Timeout Alarm(11)",
+ RadioSettingValueList(TOA_LIST,
TOA_LIST[s.toa])))
+ cf.append(RadioSetting("ptt_id",
+ "PTT Caller ID mode(23)",
+ RadioSettingValueList(PTTID_LIST,
+ PTTID_LIST[s.ptt_id])))
+ cf.append(RadioSetting("id_dly",
+ "Caller ID Delay time(25)",
+ RadioSettingValueList(ID_DLY_LIST,
+ ID_DLY_LIST[s.id_dly])))
+ cf.append(RadioSetting("voice_sw",
+ "Voice Guide(12)",
+ RadioSettingValueBoolean(s.voice_sw)))
+ cf.append(RadioSetting("beep",
+ "Keypad Beep(13)",
+ RadioSettingValueBoolean(s.beep)))
+ cf.append(RadioSetting("s_tone",
+ "Side Tone(36)",
+ RadioSettingValueList(S_TONES,
+ S_TONES[s.s_tone])))
+ cf.append(RadioSetting("ring_time",
+ "Ring Time(26)",
+ RadioSettingValueList(LIST_OFF_10,
+
LIST_OFF_10[s.ring_time])))
+ cf.append(RadioSetting("roger",
+ "Roger Beep(9)",
+ RadioSettingValueList(ROGER_LIST,
+ ROGER_LIST[s.roger])))
+ cf.append(RadioSetting("blcdsw",
+ "Backlight(41)",
+ RadioSettingValueBoolean(s.blcdsw)))
+ cf.append(RadioSetting("abr",
+ "Auto Backlight Time(1)",
+ RadioSettingValueList(BACKLIGHT_LIST,
+ BACKLIGHT_LIST[s.abr])))
+ cf.append(RadioSetting("abr_lvl",
+ "Backlight Brightness(27)",
+ RadioSettingValueList(LIST_1_5,
+ LIST_1_5[s.abr_lvl])))
+ cf.append(RadioSetting("lock",
+ "Keypad Lock",
+ RadioSettingValueBoolean(s.lock)))
+ cf.append(RadioSetting("lock_m",
+ "Keypad Lock Mode(35)",
+ RadioSettingValueList(LOCK_MODES,
+ LOCK_MODES[s.lock_m])))
+ cf.append(RadioSetting("auto_lk",
+ "Keypad Autolock(34)",
+ RadioSettingValueBoolean(s.auto_lk)))
+ cf.append(RadioSetting("prich_sw",
+ "Priority Channel Scan(33)",
+ RadioSettingValueBoolean(s.prich_sw)))
+ cf.append(RadioSetting("pri_ch",
+ "Priority Channel(32)",
+ RadioSettingValueInteger(1, 999, s.pri_ch)))
+ cf.append(RadioSetting("dtmf_st",
+ "DTMF Sidetone(22)",
+ RadioSettingValueList(DTMFST_LIST,
+
DTMFST_LIST[s.dtmf_st])))
+ cf.append(RadioSetting("sc_qt",
+ "Scan QT Save Mode(38)",
+ RadioSettingValueList(SCQT_LIST,
+ SCQT_LIST[s.sc_qt])))
+ cf.append(RadioSetting("apo_tmr",
+ "Automatic Power-off(39)",
+ RadioSettingValueList(APO_TIMES,
+ APO_TIMES[s.apo_tmr])))
+ cf.append(RadioSetting("vox_grd",
+ "VOX level(7)", # VOX "guard" is really VOX
trigger audio level
+ RadioSettingValueList(VOX_GRDS,
+ VOX_GRDS[s.vox_grd])))
+ cf.append(RadioSetting("vox_dly",
+ "VOX Delay(37)",
+ RadioSettingValueList(VOX_DLYS,
+ VOX_DLYS[s.vox_dly])))
+ cf.append(RadioSetting("lang",
+ "Menu Language(14)",
+ RadioSettingValueList(LANGUAGE_LIST,
+ LANGUAGE_LIST[s.lang])))
+ cf.append(RadioSetting("ponmsg",
+ "Poweron message(40)",
+ RadioSettingValueList(
+ PONMSG_LIST, PONMSG_LIST[s.ponmsg])))
+ cf.append(RadioSetting("bledsw",
+ "Receive LED(42)",
+ RadioSettingValueBoolean(s.bledsw)))
+ return cf
+
+ def _repeater_tab(self):
+ """Repeater mode functions
+ """
+ s = self._memobj.settings
+ cf = RadioSettingGroup("repeater", "Repeater Functions")
+
+ cf.append(RadioSetting("type_set",
+ "Radio Mode(43)",
+ RadioSettingValueList(RPTMODE_LIST,
+
RPTMODE_LIST[s.type_set])))
+ cf.append(RadioSetting("rpt_ptt",
+ "Repeater PTT(45)",
+ RadioSettingValueBoolean(s.rpt_ptt)))
+ cf.append(RadioSetting("rpt_spk",
+ "Repeater Mode Speaker(44)",
+ RadioSettingValueBoolean(s.rpt_spk)))
+ cf.append(RadioSetting("rpt_kpt",
+ "Repeater Hold Time(46)",
+ RadioSettingValueList(RPT_KPTS,
+ RPT_KPTS[s.rpt_kpt])))
+ cf.append(RadioSetting("rpt_rct",
+ "Repeater Receipt Tone(47)",
+ RadioSettingValueBoolean(s.rpt_rct)))
+ return cf
+
+ def _admin_tab(self):
+ """Admin functions not present in radio menu...
+ These are admin functions not radio operation configuration
+ """
+
+ def apply_cid(setting, obj):
+ c = str2callid(setting.value)
+ obj.code = c
+
+ def apply_scc(setting, obj):
+ c = str2digits(setting.value)
+ obj.scc = c
+
+ def apply_mode_sw(setting, obj):
+ pw = str2pw(setting.value)
+ obj.mode_sw = pw
+ setting.value = pw2str(obj.mode_sw)
+
+ def apply_reset(setting, obj):
+ pw = str2pw(setting.value)
+ obj.reset = pw
+ setting.value = pw2str(obj.reset)
+
+ def apply_wake(setting, obj):
+ obj.wake = int(setting.value)/10
+
+ def apply_sleep(setting, obj):
+ obj.sleep = int(setting.value)/10
+
+ pw = self._memobj.passwords # admin passwords
+ s = self._memobj.settings
+
+ cf = RadioSettingGroup("admin", "Admin Functions")
+
+ cf.append(RadioSetting("menu_avail",
+ "Menu available in channel mode",
+ RadioSettingValueBoolean(s.menu_avail)))
+ mode_sw = RadioSettingValueString(0, 6, pw2str(pw.mode_sw), False)
+ rs = RadioSetting("passwords.mode_sw", "Mode Switch Password",
mode_sw)
+ rs.set_apply_callback(apply_mode_sw, pw)
+ cf.append(rs)
+
+ cf.append(RadioSetting("reset_avail",
+ "Radio Reset Available",
+ RadioSettingValueBoolean(s.reset_avail)))
+ reset = RadioSettingValueString(0, 6, pw2str(pw.reset), False)
+ rs = RadioSetting("passwords.reset", "Radio Reset Password", reset)
+ rs.set_apply_callback(apply_reset, pw)
+ cf.append(rs)
+
+ cf.append(RadioSetting("dtmf_tx",
+ "DTMF Tx Duration",
+ RadioSettingValueList(DTMF_TIMES,
+ DTMF_TIMES[s.dtmf_tx])))
+ cid = self._memobj.my_callid
+ my_callid = RadioSettingValueString(3, 6, callid2str(cid.code),
False)
+ rs = RadioSetting("my_callid.code", "PTT Caller ID code(24)",
my_callid)
+ rs.set_apply_callback(apply_cid, cid)
+ cf.append(rs)
+
+ stun = self._memobj.stun
+ st = RadioSettingValueString(0, 6, digits2str(stun.scc), False)
+ rs = RadioSetting("stun.scc", "Security code", st)
+ rs.set_apply_callback(apply_scc, stun)
+ cf.append(rs)
+
+ cf.append(RadioSetting("settings.save_m",
+ "Save Mode (2)",
+ RadioSettingValueList(SAVE_MODES,
+ SAVE_MODES[s.save_m])))
+ for i in range(0,4):
+ sm = self._memobj.save[i]
+ wake = RadioSettingValueInteger(0, 18000, sm.wake * 10, 1)
+ wf = RadioSetting("save[%i].wake" % i, "Save Mode %d Wake Time" %
(i+1), wake)
+ wf.set_apply_callback(apply_wake, sm)
+ cf.append(wf)
+
+ slp = RadioSettingValueInteger(0, 18000, sm.sleep * 10, 1)
+ wf = RadioSetting("save[%i].sleep" % i, "Save Mode %d Sleep Time"
% (i+1), slp)
+ wf.set_apply_callback(apply_sleep, sm)
+ cf.append(wf)
+
+ _msg = str(self._memobj.display.banner).split("\0")[0]
+ val = RadioSettingValueString(0, 16, _msg)
+ val.set_mutable(True)
+ cf.append(RadioSetting("display.banner", "Display Message", val))
+ return cf
+
+ def _fm_tab(self):
+ """FM Broadcast channels
+ """
+ def apply_fm(setting, obj):
+ f = freq2short(setting.value, 76000000, 108000000)
+ obj.fm_freq = f
+
+ fm = RadioSettingGroup("fm_chans", "FM Broadcast")
+ for ch in range(0,20):
+ chan = self._memobj.fm_chans[ch]
+ freq = RadioSettingValueString(0,20, short2freq(chan.fm_freq))
+ rs = RadioSetting("fm_%d" % (ch + 1),
+ "FM Channel %d" % (ch + 1), freq)
+ rs.set_apply_callback(apply_fm, chan)
+ fm.append(rs)
+ return fm
+
+ def _scan_grp(self):
+ """Scan groups
+ """
+ def apply_name(setting, obj):
+ name = str2name(setting.value, 8, '\0', '\0')
+ obj.name = name
+
+ def apply_start(setting, obj):
+ """Do a callback to deal with RadioSettingInteger limitation
+ on memory address resolution
+ """
+ obj.scan_st = int(setting.value)
+
+ def apply_end(setting, obj):
+ """Do a callback to deal with RadioSettingInteger limitation
+ on memory address resolution
+ """
+ obj.scan_end = int(setting.value)
+
+ sgrp = self._memobj.scn_grps
+ scan = RadioSettingGroup("scn_grps", "Channel Scanner Groups")
+ for i in range(0, 10):
+ s_grp = sgrp.addrs[i]
+ s_name = sgrp.names[i]
+ rs_name = RadioSettingValueString(0, 8, name2str(s_name.name))
+ rs = RadioSetting("scn_grps.names[%i].name" % i,
+ "Group %i Name" % (i + 1), rs_name)
+ rs.set_apply_callback(apply_name, s_name)
+ scan.append(rs)
+ rs_st = RadioSettingValueInteger(1, 999, s_grp.scan_st)
+ rs = RadioSetting("scn_grps.addrs[%i].scan_st" % i,
+ "Starting Channel", rs_st)
+ rs.set_apply_callback(apply_start, s_grp)
+ scan.append(rs)
+ rs_end = RadioSettingValueInteger(1, 999, s_grp.scan_end)
+ rs = RadioSetting("scn_grps.addrs[%i].scan_end" % i,
+ "Last Channel", rs_end)
+ rs.set_apply_callback(apply_end, s_grp)
+ scan.append(rs)
+ return scan
+
+ def _callid_grp(self):
+ """Caller IDs to be recognized by radio
+ This really should be a table in the UI
+ """
+ def apply_callid(setting, obj):
+ c = str2callid(setting.value)
+ obj.cid = c
+
+ def apply_name(setting, obj):
+ name = str2name(setting.value, 6, '\0', '\xff')
+ obj.name = name
+
+ cid = RadioSettingGroup("callids", "Caller IDs")
+ for i in range(0, 20):
+ callid = self._memobj.call_ids[i]
+ name = self._memobj.cid_names[i]
+ c_name = RadioSettingValueString(0, 6, name2str(name.name))
+ rs = RadioSetting("cid_names[%i].name" % i,
+ "Caller ID %i Name" % (i + 1), c_name)
+ rs.set_apply_callback(apply_name, name)
+ cid.append(rs)
+ c_id = RadioSettingValueString(0, 6, callid2str(callid.cid),
False)
+ rs = RadioSetting("call_ids[%i].cid" % i,
+ "Caller ID Code", c_id)
+ rs.set_apply_callback(apply_callid, callid)
+ cid.append(rs)
+ return cid
+
+ def _band_tab(self, area, band):
+ """ Build a band tab inside a VFO/Area
+ """
+ def apply_freq(setting, lo, hi, obj):
+ f = freq2int(setting.value, lo, hi)
+ obj.freq = f/10
+
+ def apply_offset(setting, obj):
+ f = freq2int(setting.value, 0, 5000000)
+ obj.offset = f/10
+
+ def apply_enc(setting, obj):
+ t = tone2short(setting.value)
+ obj.encqt = t
+
+ def apply_dec(setting, obj):
+ t = tone2short(setting.value)
+ obj.decqt = t
+
+ if area == "a":
+ if band == 150:
+ c = self._memobj.vfo_a.band_150
+ lo = 108000000
+ hi = 180000000
+ elif band == 200:
+ c = self._memobj.vfo_a.band_200
+ lo = 230000000
+ hi = 250000000
+ elif band == 300:
+ c = self._memobj.vfo_a.band_300
+ lo = 350000000
+ hi = 400000000
+ elif band == 450:
+ c = self._memobj.vfo_a.band_450
+ lo = 400000000
+ hi = 512000000
+ else: # 700
+ c = self._memobj.vfo_a.band_700
+ lo = 700000000
+ hi = 985000000
+ else: # area 'b'
+ if band == 150:
+ c = self._memobj.vfo_b.band_150
+ lo = 136000000
+ hi = 180000000
+ else: # 450
+ c = self._memobj.vfo_b.band_450
+ lo = 400000000
+ hi = 512000000
+
+ prefix = "vfo_%s.band_%d" %(area, band)
+ bf = RadioSettingGroup(prefix, "%dMHz Band" % band)
+ freq = RadioSettingValueString(0, 15, int2freq(c.freq * 10))
+ rs = RadioSetting(prefix + ".freq", "Rx Frequency", freq)
+ rs.set_apply_callback(apply_freq, lo, hi, c)
+ bf.append(rs)
+
+ off = RadioSettingValueString(0, 15, int2freq(c.offset * 10))
+ rs = RadioSetting(prefix + ".offset", "Tx Offset(28)", off)
+ rs.set_apply_callback(apply_offset, c)
+ bf.append(rs)
+
+ rs = RadioSetting(prefix + ".encqt",
+ "Encode QT(17,19)",
+ RadioSettingValueList(TONE_LIST,
short2tone(c.encqt)))
+ rs.set_apply_callback(apply_enc, c)
+ bf.append(rs)
+
+ rs = RadioSetting(prefix + ".decqt",
+ "Decode QT(16,18)",
+ RadioSettingValueList(TONE_LIST,
short2tone(c.decqt)))
+ rs.set_apply_callback(apply_dec, c)
+ bf.append(rs)
+
+ bf.append(RadioSetting(prefix + ".qt",
+ "Mute Mode(21)",
+ RadioSettingValueList(SPMUTE_LIST,
SPMUTE_LIST[c.qt])))
+ bf.append(RadioSetting(prefix + ".scan",
+ "Scan this(48)",
+ RadioSettingValueBoolean(c.scan)))
+ bf.append(RadioSetting(prefix + ".pwr",
+ "Power(5)",
+ RadioSettingValueList(
+ POWER_LIST, POWER_LIST[c.pwr])))
+ bf.append(RadioSetting(prefix + ".mod",
+ "AM Modulation(54)",
+ RadioSettingValueBoolean(c.mod)))
+ bf.append(RadioSetting(prefix + ".fm_dev",
+ "FM Deviation(4)",
+ RadioSettingValueList(
+ BANDWIDTH_LIST,
BANDWIDTH_LIST[c.fm_dev])))
+ bf.append(RadioSetting(prefix + ".shift",
+ "Frequency Shift(6)",
+ RadioSettingValueList(OFFSET_LIST,
OFFSET_LIST[c.shift])))
+ return bf
+
+ def _area_tab(self, area):
+ """Build a VFO tab
+ """
+ def apply_scan_st(setting, scan_lo, scan_hi, obj):
+ f = freq2short(setting.value, scan_lo, scan_hi)
+ obj.scan_st = f
+
+ def apply_scan_end(setting, scan_lo, scan_hi, obj):
+ f = freq2short(setting.value, scan_lo, scan_hi)
+ obj.scan_end = f
+
+ if area == "a":
+ desc = "Area A Settings"
+ c = self._memobj.a_conf
+ scan_lo = 108000000
+ scan_hi = 985000000
+ scan_rng = self._memobj.settings.a
+ band_list = (150, 200, 300, 450, 700)
+ else:
+ desc = "Area B Settings"
+ c = self._memobj.b_conf
+ scan_lo = 136000000
+ scan_hi = 512000000
+ scan_rng = self._memobj.settings.b
+ band_list = (150, 450)
+
+ prefix = "%s_conf" % area
+ af = RadioSettingGroup(prefix, desc)
+ af.append(RadioSetting(prefix + ".w_mode",
+ "Workmode",
+ RadioSettingValueList(WORKMODE_LIST,
+
WORKMODE_LIST[c.w_mode])))
+ af.append(RadioSetting(prefix + ".w_chan",
+ "Channel",
+ RadioSettingValueInteger(1, 999, c.w_chan)))
+ af.append(RadioSetting(prefix + ".scan_grp",
+ "Scan Group(49)",
+ RadioSettingValueList(SCANGRP_LIST,
SCANGRP_LIST[c.scan_grp])))
+ af.append(RadioSetting(prefix + ".bcl",
+ "Busy Channel Lock-out(15)",
+ RadioSettingValueBoolean(c.bcl)))
+ af.append(RadioSetting(prefix + ".sql",
+ "Squelch Level(8)",
+ RadioSettingValueList(LIST_0_9,
LIST_0_9[c.sql])))
+ af.append(RadioSetting(prefix + ".cset",
+ "Call ID Group(52)",
+ RadioSettingValueList(LIST_1_20,
LIST_1_20[c.cset])))
+ af.append(RadioSetting(prefix + ".step",
+ "Frequency Step(3)",
+ RadioSettingValueList(STEP_LIST,
STEP_LIST[c.step])))
+ af.append(RadioSetting(prefix + ".scan_mode",
+ "Scan Mode(20)",
+ RadioSettingValueList(SCANMODE_LIST,
+
SCANMODE_LIST[c.scan_mode])))
+ af.append(RadioSetting(prefix + ".scan_range",
+ "Scan Range(50)",
+ RadioSettingValueList(SCANRANGE_LIST,
+
SCANRANGE_LIST[c.scan_range])))
+ st = RadioSettingValueString(0, 15, short2freq(scan_rng.scan_st))
+ rs = RadioSetting("settings.%s.scan_st" % area, "Frequency Scan
Start", st)
+ rs.set_apply_callback(apply_scan_st, scan_lo, scan_hi, scan_rng)
+ af.append(rs)
+
+ end = RadioSettingValueString(0, 15, short2freq(scan_rng.scan_end))
+ rs = RadioSetting("settings.%s.scan_end" % area, "Frequency Scan
End", end)
+ rs.set_apply_callback(apply_scan_end, scan_lo, scan_hi, scan_rng)
+ af.append(rs)
+ # Each area has its own set of bands
+ for band in (band_list):
+ af.append(self._band_tab(area, band))
+ return af
+
+ def _key_tab(self):
+ """Build radio key/button menu
+ """
+ s = self._memobj.settings
+ kf = RadioSettingGroup("key_grp", "Key Settings")
+
+
+
+ kf.append(RadioSetting("settings.pf1",
+ "PF1 Key function(55)",
+ RadioSettingValueList(
+ PF1KEY_LIST,
+ PF1KEY_LIST[s.pf1])))
+ kf.append(RadioSetting("settings.pf2",
+ "PF2 Key function(56)",
+ RadioSettingValueList(
+ PF2KEY_LIST,
+ PF2KEY_LIST[s.pf2])))
+ kf.append(RadioSetting("settings.pf3",
+ "PF3 Key function(57)",
+ RadioSettingValueList(
+ PF3KEY_LIST,
+ PF3KEY_LIST[s.pf3])))
+ return kf
+
+ def _get_settings(self):
+ """Build the radio configuration settings menus
+ """
+
+ core_grp = self._core_tab()
+ fm_grp = self._fm_tab()
+ area_a_grp = self._area_tab( "a")
+ area_b_grp = self._area_tab("b")
+ key_grp = self._key_tab()
+ scan_grp = self._scan_grp()
+ callid_grp = self._callid_grp()
+ admin_grp = self._admin_tab()
+ rpt_grp = self._repeater_tab()
+
+ core_grp.append(key_grp)
+ core_grp.append(admin_grp)
+ core_grp.append(rpt_grp)
+ group = RadioSettings(core_grp,
+ area_a_grp,
+ area_b_grp,
+ fm_grp,
+ scan_grp,
+ callid_grp
+ )
+ return group
+
+ def get_settings(self):
+ """ Public build out linkage between radio settings and UI
+ """
+ try:
+ return self._get_settings()
+ except:
+ import traceback
+ LOG.error("Failed to parse settings: %s", traceback.format_exc())
+ return None
+
+ def _is_freq(self, element):
+ """This is a hack to smoke out whether we need to do frequency
translations
+ for otherwise innocent u16s and u32s
+ """
+ return "rxfreq" in element.get_name() or \
+ "txfreq" in element.get_name() or \
+ "scan_st" in element.get_name() or \
+ "scan_end" in element.get_name() or \
+ "offset" in element.get_name() or \
+ "fm_stop" in element.get_name()
+
+ def set_settings(self, settings):
+ """ Public update radio settings via UI callback
+ A lot of this should be in common code....
+ """
+
+ for element in settings:
+ if not isinstance(element, RadioSetting):
+ LOG.debug("set_settings: not instance %s" %
element.get_name())
+ 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]:
+ if "[" in bit and "]" in bit: # decode an array
index
+ bit, index = bit.split("[", 1)
+ index, junk = index.split("]", 1)
+ index = int(index)
+ obj = getattr(obj, bit)[index]
+ else:
+ obj = getattr(obj, bit)
+ setting = bits[-1]
+ else:
+ obj = self._memobj.settings
+ setting = element.get_name()
+
+ if element.has_apply_callback():
+ LOG.debug("Using apply callback")
+ element.run_apply_callback()
+ else:
+ LOG.debug("Setting %s = %s" % (setting,
element.value))
+ if self._is_freq(element):
+ setattr(obj, setting, int(element.value)/10)
+ else:
+ setattr(obj, setting, element.value)
+ except Exception, e:
+ LOG.debug("set_settings: Exception with %s" %
element.get_name())
+ raise
+
--
Jim Lieb
Mobile: 831-295-9317
GPG Key: 79BB52C7BD0530F5
"If ease of use was the only requirement, we would all be riding tricycles"
- Douglas Engelbart 1925–2013
2
1
[chirp_devel] [PATCH] [FT818] New Model Support Yaesu FT-818 FIXED MISTAKE #5607
by Vinny Stipo 03 Dec '18
by Vinny Stipo 03 Dec '18
03 Dec '18
# HG changeset patch
# User Vincent Stipo <v(a)xpctech.com>
# Date 1543854671 28800
# Mon Dec 03 08:31:11 2018 -0800
# Node ID c703ce827039991456dfa3c4e1428fdda190ec14
# Parent c6cab71d7d7d76b63367f5529a1b2ea075a8db1b
[FT818] New Model Support Yaesu FT-818 FIXED MISTAKE #5607
diff --git a/chirp/drivers/ft818.py b/chirp/drivers/ft818.py
new file mode 100755
--- /dev/null
+++ b/chirp/drivers/ft818.py
@@ -0,0 +1,256 @@
+#
+# Copyright 2012 Filippi Marco <iz3gme.marco(a)gmail.com>
+# Copyright 2018 Vinny Stipo <v(a)xpctech.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/>.
+
+"""FT818 management module"""
+
+from chirp.drivers import ft817
+from chirp import chirp_common, errors, directory
+from chirp.settings import RadioSetting, RadioSettingGroup, \
+ RadioSettingValueInteger, RadioSettingValueList, \
+ RadioSettingValueBoolean, RadioSettingValueString, \
+ RadioSettings
+import os
+import logging
+from textwrap import dedent
+from chirp.util import safe_charset_string
+
+LOG = logging.getLogger(__name__)
+
+
+(a)directory.register
+class FT818Radio(ft817.FT817Radio):
+
+ """Yaesu FT-818"""
+ BAUD_RATE = 9600
+ MODEL = "FT-818"
+ _model = ""
+ _memsize = 6573
+ _block_lengths = [2, 40, 208, 208, 208, 208, 198, 53, 130, 118, 130]
+
+ MEM_FORMAT = """
+ struct mem_struct {
+ u8 tag_on_off:1,
+ tag_default:1,
+ unknown1:3,
+ mode:3;
+ u8 duplex:2,
+ is_duplex:1,
+ is_cwdig_narrow:1,
+ is_fm_narrow:1,
+ freq_range:3;
+ u8 skip:1,
+ unknown2:1,
+ ipo:1,
+ att:1,
+ unknown3:4;
+ u8 ssb_step:2,
+ am_step:3,
+ fm_step:3;
+ u8 unknown4:6,
+ tmode:2;
+ u8 unknown5:2,
+ tx_mode:3,
+ tx_freq_range:3;
+ u8 unknown6:1,
+ unknown_toneflag:1,
+ tone:6;
+ u8 unknown7:1,
+ dcs:7;
+ ul16 rit;
+ u32 freq;
+ u32 offset;
+ u8 name[8];
+ };
+
+ #seekto 0x4;
+ struct {
+ u8 fst:1,
+ lock:1,
+ nb:1,
+ pbt:1,
+ unknownb:1,
+ dsp:1,
+ agc:2;
+ u8 vox:1,
+ vlt:1,
+ bk:1,
+ kyr:1,
+ unknown5:1,
+ cw_paddle:1,
+ pwr_meter_mode:2;
+ u8 vfob_band_select:4,
+ vfoa_band_select:4;
+ u8 unknowna;
+ u8 backlight:2,
+ color:2,
+ contrast:4;
+ u8 beep_freq:1,
+ beep_volume:7;
+ u8 arts_beep:2,
+ main_step:1,
+ cw_id:1,
+ scope:1,
+ pkt_rate:1,
+ resume_scan:2;
+ u8 op_filter:2,
+ lock_mode:2,
+ cw_pitch:4;
+ u8 sql_rf_gain:1,
+ ars_144:1,
+ ars_430:1,
+ cw_weight:5;
+ u8 cw_delay;
+ u8 unknown8:1,
+ sidetone:7;
+ u8 batt_chg: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,
+ unknown9:1,
+ apo_time:3;
+ u8 dcs_inv:2,
+ unknown10:1,
+ tot_time:5;
+ u8 mic_scan:1,
+ ssb_mic:7;
+ u8 mic_key:1,
+ am_mic:7;
+ u8 unknown11:1,
+ fm_mic:7;
+ u8 unknown12:1,
+ dig_mic:7;
+ u8 extended_menu:1,
+ pkt_mic:7;
+ u8 unknown14:1,
+ pkt9600_mic:7;
+ 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,
+ menu_sel:4;
+ u16 unknown17;
+ u8 art:1,
+ scn_mode:2,
+ dw:1,
+ pri:1,
+ unknown18:1,
+ tx_power:2;
+ u8 spl:1,
+ unknown:1,
+ uhf_antenna:1,
+ vhf_antenna:1,
+ air_antenna:1,
+ bc_antenna:1,
+ sixm_antenna:1,
+ hf_antenna:1;
+ } settings;
+
+ #seekto 0x2A;
+ struct mem_struct vfoa[16];
+ struct mem_struct vfob[16];
+ struct mem_struct home[4];
+ struct mem_struct qmb;
+ struct mem_struct mtqmb;
+ struct mem_struct mtune;
+
+ #seekto 0x431;
+ u8 visible[25];
+ u8 pmsvisible;
+
+ #seekto 0x44B;
+ u8 filled[25];
+ u8 pmsfilled;
+
+ #seekto 0x465;
+ struct mem_struct memory[200];
+ struct mem_struct pms[2];
+
+ #seekto 0x1903;
+ u8 callsign[7];
+
+ #seekto 0x19AD;
+ struct mem_struct sixtymeterchannels[5];
+ """
+
+ SPECIAL_MEMORIES = {
+ "VFOa-1.8M": -37,
+ "VFOa-3.5M": -36,
+ "VFOa-5M": -35,
+ "VFOa-7M": -34,
+ "VFOa-10M": -33,
+ "VFOa-14M": -32,
+ "VFOa-18M": -31,
+ "VFOa-21M": -30,
+ "VFOa-24M": -29,
+ "VFOa-28M": -28,
+ "VFOa-50M": -27,
+ "VFOa-FM": -26,
+ "VFOa-AIR": -25,
+ "VFOa-144": -24,
+ "VFOa-430": -23,
+ "VFOa-HF": -22,
+ "VFOb-1.8M": -21,
+ "VFOb-3.5M": -20,
+ "VFOb-5M": -19,
+ "VFOb-7M": -18,
+ "VFOb-10M": -17,
+ "VFOb-14M": -16,
+ "VFOb-18M": -15,
+ "VFOb-21M": -14,
+ "VFOb-24M": -13,
+ "VFOb-28M": -12,
+ "VFOb-50M": -11,
+ "VFOb-FM": -10,
+ "VFOb-AIR": -9,
+ "VFOb-144M": -8,
+ "VFOb-430M": -7,
+ "VFOb-HF": -6,
+ "HOME HF": -5,
+ "HOME 50M": -4,
+ "HOME 144M": -3,
+ "HOME 430M": -2,
+ "QMB": -1,
+ }
+ FIRST_VFOB_INDEX = -6
+ LAST_VFOB_INDEX = -21
+ FIRST_VFOA_INDEX = -22
+ LAST_VFOA_INDEX = -37
+
+ SPECIAL_PMS = {
+ "PMS-L": -39,
+ "PMS-U": -38,
+ }
+ LAST_PMS_INDEX = -39
+
+ SPECIAL_MEMORIES.update(SPECIAL_PMS)
+
+ SPECIAL_MEMORIES_REV = dict(zip(SPECIAL_MEMORIES.values(),
+ SPECIAL_MEMORIES.keys()))
+
+
+
+
diff --git a/tests/images/Yaesu_FT-818.img b/tests/images/Yaesu_FT-818.img
new file mode 100644
index e69de29bb2d1d6434b8b29ae775ad8c2e48c5391..59b6e62681e131d4fee9f5c83518637a7753cc57
GIT binary patch
literal 6730
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
1
0
03 Dec '18
# HG changeset patch
# User Vinny Stipo <v(a)xpctech.com>
# Date 1543851731 28800
# Mon Dec 03 07:42:11 2018 -0800
# Node ID 6722e181470ab505a5075a4b8811c71e4bd31ab2
# Parent c6cab71d7d7d76b63367f5529a1b2ea075a8db1b
[FT818] New Model Support Yaesu FT-818 #5607
diff --git a/chirp/drivers/ft818.py b/chirp/drivers/ft818.py
new file mode 100755
--- /dev/null
+++ b/chirp/drivers/ft818.py
@@ -0,0 +1,256 @@
+#
+# Copyright 2012 Filippi Marco <iz3gme.marco(a)gmail.com>
+# Copyright 2018 Vinny Stipo <v(a)xpctech.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/>.
+
+"""FT818 management module"""
+
+from chirp.drivers import ft817
+from chirp import chirp_common, errors, directory
+from chirp.settings import RadioSetting, RadioSettingGroup, \
+ RadioSettingValueInteger, RadioSettingValueList, \
+ RadioSettingValueBoolean, RadioSettingValueString, \
+ RadioSettings
+import os
+import logging
+from textwrap import dedent
+from chirp.util import safe_charset_string
+
+LOG = logging.getLogger(__name__)
+
+
+(a)directory.register
+class FT817NDRadio(ft817.FT817Radio):
+
+ """Yaesu FT-818"""
+ BAUD_RATE = 9600
+ MODEL = "FT-818"
+ _model = ""
+ _memsize = 6573
+ _block_lengths = [2, 40, 208, 208, 208, 208, 198, 53, 130, 118, 130]
+
+ MEM_FORMAT = """
+ struct mem_struct {
+ u8 tag_on_off:1,
+ tag_default:1,
+ unknown1:3,
+ mode:3;
+ u8 duplex:2,
+ is_duplex:1,
+ is_cwdig_narrow:1,
+ is_fm_narrow:1,
+ freq_range:3;
+ u8 skip:1,
+ unknown2:1,
+ ipo:1,
+ att:1,
+ unknown3:4;
+ u8 ssb_step:2,
+ am_step:3,
+ fm_step:3;
+ u8 unknown4:6,
+ tmode:2;
+ u8 unknown5:2,
+ tx_mode:3,
+ tx_freq_range:3;
+ u8 unknown6:1,
+ unknown_toneflag:1,
+ tone:6;
+ u8 unknown7:1,
+ dcs:7;
+ ul16 rit;
+ u32 freq;
+ u32 offset;
+ u8 name[8];
+ };
+
+ #seekto 0x4;
+ struct {
+ u8 fst:1,
+ lock:1,
+ nb:1,
+ pbt:1,
+ unknownb:1,
+ dsp:1,
+ agc:2;
+ u8 vox:1,
+ vlt:1,
+ bk:1,
+ kyr:1,
+ unknown5:1,
+ cw_paddle:1,
+ pwr_meter_mode:2;
+ u8 vfob_band_select:4,
+ vfoa_band_select:4;
+ u8 unknowna;
+ u8 backlight:2,
+ color:2,
+ contrast:4;
+ u8 beep_freq:1,
+ beep_volume:7;
+ u8 arts_beep:2,
+ main_step:1,
+ cw_id:1,
+ scope:1,
+ pkt_rate:1,
+ resume_scan:2;
+ u8 op_filter:2,
+ lock_mode:2,
+ cw_pitch:4;
+ u8 sql_rf_gain:1,
+ ars_144:1,
+ ars_430:1,
+ cw_weight:5;
+ u8 cw_delay;
+ u8 unknown8:1,
+ sidetone:7;
+ u8 batt_chg: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,
+ unknown9:1,
+ apo_time:3;
+ u8 dcs_inv:2,
+ unknown10:1,
+ tot_time:5;
+ u8 mic_scan:1,
+ ssb_mic:7;
+ u8 mic_key:1,
+ am_mic:7;
+ u8 unknown11:1,
+ fm_mic:7;
+ u8 unknown12:1,
+ dig_mic:7;
+ u8 extended_menu:1,
+ pkt_mic:7;
+ u8 unknown14:1,
+ pkt9600_mic:7;
+ 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,
+ menu_sel:4;
+ u16 unknown17;
+ u8 art:1,
+ scn_mode:2,
+ dw:1,
+ pri:1,
+ unknown18:1,
+ tx_power:2;
+ u8 spl:1,
+ unknown:1,
+ uhf_antenna:1,
+ vhf_antenna:1,
+ air_antenna:1,
+ bc_antenna:1,
+ sixm_antenna:1,
+ hf_antenna:1;
+ } settings;
+
+ #seekto 0x2A;
+ struct mem_struct vfoa[16];
+ struct mem_struct vfob[16];
+ struct mem_struct home[4];
+ struct mem_struct qmb;
+ struct mem_struct mtqmb;
+ struct mem_struct mtune;
+
+ #seekto 0x431;
+ u8 visible[25];
+ u8 pmsvisible;
+
+ #seekto 0x44B;
+ u8 filled[25];
+ u8 pmsfilled;
+
+ #seekto 0x465;
+ struct mem_struct memory[200];
+ struct mem_struct pms[2];
+
+ #seekto 0x1903;
+ u8 callsign[7];
+
+ #seekto 0x19AD;
+ struct mem_struct sixtymeterchannels[5];
+ """
+
+ SPECIAL_MEMORIES = {
+ "VFOa-1.8M": -37,
+ "VFOa-3.5M": -36,
+ "VFOa-5M": -35,
+ "VFOa-7M": -34,
+ "VFOa-10M": -33,
+ "VFOa-14M": -32,
+ "VFOa-18M": -31,
+ "VFOa-21M": -30,
+ "VFOa-24M": -29,
+ "VFOa-28M": -28,
+ "VFOa-50M": -27,
+ "VFOa-FM": -26,
+ "VFOa-AIR": -25,
+ "VFOa-144": -24,
+ "VFOa-430": -23,
+ "VFOa-HF": -22,
+ "VFOb-1.8M": -21,
+ "VFOb-3.5M": -20,
+ "VFOb-5M": -19,
+ "VFOb-7M": -18,
+ "VFOb-10M": -17,
+ "VFOb-14M": -16,
+ "VFOb-18M": -15,
+ "VFOb-21M": -14,
+ "VFOb-24M": -13,
+ "VFOb-28M": -12,
+ "VFOb-50M": -11,
+ "VFOb-FM": -10,
+ "VFOb-AIR": -9,
+ "VFOb-144M": -8,
+ "VFOb-430M": -7,
+ "VFOb-HF": -6,
+ "HOME HF": -5,
+ "HOME 50M": -4,
+ "HOME 144M": -3,
+ "HOME 430M": -2,
+ "QMB": -1,
+ }
+ FIRST_VFOB_INDEX = -6
+ LAST_VFOB_INDEX = -21
+ FIRST_VFOA_INDEX = -22
+ LAST_VFOA_INDEX = -37
+
+ SPECIAL_PMS = {
+ "PMS-L": -39,
+ "PMS-U": -38,
+ }
+ LAST_PMS_INDEX = -39
+
+ SPECIAL_MEMORIES.update(SPECIAL_PMS)
+
+ SPECIAL_MEMORIES_REV = dict(zip(SPECIAL_MEMORIES.values(),
+ SPECIAL_MEMORIES.keys()))
+
+
+
+
diff --git a/tests/images/Yaesu_FT-818.img b/tests/images/Yaesu_FT-818.img
new file mode 100644
index e69de29bb2d1d6434b8b29ae775ad8c2e48c5391..c4684cbcdf80a9296910a0d22bc3d527737bda54
GIT binary patch
literal 6730
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
ayvfm{=98vw4b*GaWIcGSAO2^|+WT+&of%;O
1
0
03 Dec '18
# HG changeset patch
# User Vinny Stipo <v(a)xpctech.com>
# Date 1543738069 28800
# Sun Dec 02 00:07:49 2018 -0800
# Node ID 4bddc1caa07f1389711bf6ddf1b0a75f74c80c2f
# Parent c6cab71d7d7d76b63367f5529a1b2ea075a8db1b
[FT818] New Model Support Yaesu FT-818 #5607
diff --git a/chirp/drivers/ft818.py b/chirp/drivers/ft818.py
new file mode 100755
--- /dev/null
+++ b/chirp/drivers/ft818.py
@@ -0,0 +1,1098 @@
+#
+# Copyright 2012 Filippi Marco <iz3gme.marco(a)gmail.com>
+# Copyright 2018 Vinny Stipo <v(a)xpctech.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/>.
+
+"""FT818 management module"""
+
+from chirp.drivers import yaesu_clone
+from chirp import chirp_common, util, memmap, errors, directory, bitwise
+from chirp.settings import RadioSetting, RadioSettingGroup, \
+ RadioSettingValueInteger, RadioSettingValueList, \
+ RadioSettingValueBoolean, RadioSettingValueString, \
+ RadioSettings
+import time
+import logging
+from textwrap import dedent
+
+LOG = logging.getLogger(__name__)
+
+CMD_ACK = 0x06
+
+(a)directory.register
+class FT818Radio(yaesu_clone.YaesuCloneModeRadio):
+
+ """Yaesu FT-818"""
+ BAUD_RATE = 9600
+ MODEL = "FT-818"
+ _model = ""
+
+ DUPLEX = ["", "-", "+", "split"]
+ # narrow modes has to be at end
+ MODES = ["LSB", "USB", "CW", "CWR", "AM", "FM", "DIG", "PKT", "NCW",
+ "NCWR", "NFM"]
+ TMODES = ["", "Tone", "TSQL", "DTCS"]
+ STEPSFM = [5.0, 6.25, 10.0, 12.5, 15.0, 20.0, 25.0, 50.0]
+ STEPSAM = [2.5, 5.0, 9.0, 10.0, 12.5, 25.0]
+ STEPSSSB = [1.0, 2.5, 5.0]
+
+ # warning ranges has to be in this exact order
+ VALID_BANDS = [(100000, 33000000), (33000000, 56000000),
+ (76000000, 108000000), (108000000, 137000000),
+ (137000000, 154000000), (420000000, 470000000)]
+
+ CHARSET = list(chirp_common.CHARSET_ASCII)
+ CHARSET.remove("\\")
+
+
+ _memsize = 6573
+
+ # block 9 (130 Bytes long) is to be repeted 40 times
+ _block_lengths = [2, 40, 208, 208, 208, 208, 198, 53, 130, 118, 130]
+
+ MEM_FORMAT = """
+ struct mem_struct {
+ u8 tag_on_off:1,
+ tag_default:1,
+ unknown1:3,
+ mode:3;
+ u8 duplex:2,
+ is_duplex:1,
+ is_cwdig_narrow:1,
+ is_fm_narrow:1,
+ freq_range:3;
+ u8 skip:1,
+ unknown2:1,
+ ipo:1,
+ att:1,
+ unknown3:4;
+ u8 ssb_step:2,
+ am_step:3,
+ fm_step:3;
+ u8 unknown4:6,
+ tmode:2;
+ u8 unknown5:2,
+ tx_mode:3,
+ tx_freq_range:3;
+ u8 unknown6:1,
+ unknown_toneflag:1,
+ tone:6;
+ u8 unknown7:1,
+ dcs:7;
+ ul16 rit;
+ u32 freq;
+ u32 offset;
+ u8 name[8];
+ };
+
+ #seekto 0x4;
+ struct {
+ u8 fst:1,
+ lock:1,
+ nb:1,
+ pbt:1,
+ unknownb:1,
+ dsp:1,
+ agc:2;
+ u8 vox:1,
+ vlt:1,
+ bk:1,
+ kyr:1,
+ unknown5:1,
+ cw_paddle:1,
+ pwr_meter_mode:2;
+ u8 vfob_band_select:4,
+ vfoa_band_select:4;
+ u8 unknowna;
+ u8 backlight:2,
+ color:2,
+ contrast:4;
+ u8 beep_freq:1,
+ beep_volume:7;
+ u8 arts_beep:2,
+ main_step:1,
+ cw_id:1,
+ scope:1,
+ pkt_rate:1,
+ resume_scan:2;
+ u8 op_filter:2,
+ lock_mode:2,
+ cw_pitch:4;
+ u8 sql_rf_gain:1,
+ ars_144:1,
+ ars_430:1,
+ cw_weight:5;
+ u8 cw_delay;
+ u8 unknown8:1,
+ sidetone:7;
+ u8 batt_chg: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,
+ unknown9:1,
+ apo_time:3;
+ u8 dcs_inv:2,
+ unknown10:1,
+ tot_time:5;
+ u8 mic_scan:1,
+ ssb_mic:7;
+ u8 mic_key:1,
+ am_mic:7;
+ u8 unknown11:1,
+ fm_mic:7;
+ u8 unknown12:1,
+ dig_mic:7;
+ u8 extended_menu:1,
+ pkt_mic:7;
+ u8 unknown14:1,
+ pkt9600_mic:7;
+ 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,
+ menu_sel:4;
+ u16 unknown17;
+ u8 art:1,
+ scn_mode:2,
+ dw:1,
+ pri:1,
+ unknown18:1,
+ tx_power:2;
+ u8 spl:1,
+ unknown:1,
+ uhf_antenna:1,
+ vhf_antenna:1,
+ air_antenna:1,
+ bc_antenna:1,
+ sixm_antenna:1,
+ hf_antenna:1;
+ } settings;
+
+ #seekto 0x2A;
+ struct mem_struct vfoa[16];
+ struct mem_struct vfob[16];
+ struct mem_struct home[4];
+ struct mem_struct qmb;
+ struct mem_struct mtqmb;
+ struct mem_struct mtune;
+
+ #seekto 0x431;
+ u8 visible[25];
+ u8 pmsvisible;
+
+ #seekto 0x44B;
+ u8 filled[25];
+ u8 pmsfilled;
+
+ #seekto 0x465;
+ struct mem_struct memory[200];
+ struct mem_struct pms[2];
+
+ #seekto 0x1903;
+ u8 callsign[7];
+
+ #seekto 0x19AD;
+ struct mem_struct sixtymeterchannels[5];
+ """
+ _CALLSIGN_CHARSET = [chr(x) for x in range(ord("0"), ord("9") + 1) +
+ range(ord("A"), ord("Z") + 1) + [ord(" ")]]
+ _CALLSIGN_CHARSET_REV = dict(zip(_CALLSIGN_CHARSET,
+ range(0, len(_CALLSIGN_CHARSET))))
+
+ # WARNING Index are hard wired in memory management code !!!
+ SPECIAL_MEMORIES = {
+ "VFOa-1.8M": -37,
+ "VFOa-3.5M": -36,
+ "VFOa-5M": -35,
+ "VFOa-7M": -34,
+ "VFOa-10M": -33,
+ "VFOa-14M": -32,
+ "VFOa-18M": -31,
+ "VFOa-21M": -30,
+ "VFOa-24M": -29,
+ "VFOa-28M": -28,
+ "VFOa-50M": -27,
+ "VFOa-FM": -26,
+ "VFOa-AIR": -25,
+ "VFOa-144": -24,
+ "VFOa-430": -23,
+ "VFOa-HF": -22,
+ "VFOb-1.8M": -21,
+ "VFOb-3.5M": -20,
+ "VFOb-5M": -19,
+ "VFOb-7M": -18,
+ "VFOb-10M": -17,
+ "VFOb-14M": -16,
+ "VFOb-18M": -15,
+ "VFOb-21M": -14,
+ "VFOb-24M": -13,
+ "VFOb-28M": -12,
+ "VFOb-50M": -11,
+ "VFOb-FM": -10,
+ "VFOb-AIR": -9,
+ "VFOb-144M": -8,
+ "VFOb-430M": -7,
+ "VFOb-HF": -6,
+ "HOME HF": -5,
+ "HOME 50M": -4,
+ "HOME 144M": -3,
+ "HOME 430M": -2,
+ "QMB": -1,
+ }
+ FIRST_VFOB_INDEX = -6
+ LAST_VFOB_INDEX = -21
+ FIRST_VFOA_INDEX = -22
+ LAST_VFOA_INDEX = -37
+
+ SPECIAL_PMS = {
+ "PMS-L": -39,
+ "PMS-U": -38,
+ }
+ LAST_PMS_INDEX = -39
+
+ SPECIAL_MEMORIES.update(SPECIAL_PMS)
+
+ SPECIAL_MEMORIES_REV = dict(zip(SPECIAL_MEMORIES.values(),
+ SPECIAL_MEMORIES.keys()))
+
+ @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
+ time.sleep(0.5)
+ if len(data) == block + 2 and data[0] == chr(blocknum):
+ checksum = yaesu_clone.YaesuChecksum(1, block)
+ if checksum.get_existing(data) != \
+ checksum.get_calculated(data):
+ raise Exception("Checksum Failed [%02X<>%02X] block %02X" %
+ (checksum.get_existing(data),
+ checksum.get_calculated(data), blocknum))
+ # Chew away the block number and the checksum
+ data = data[1:block + 1]
+ else:
+ raise Exception("Unable to read block %02X expected %i got %i"
+ % (blocknum, block + 2, len(data)))
+
+ LOG.debug("Read %i" % len(data))
+ return data
+
+ def _clone_in(self):
+ # Be very patient with the radio
+ self.pipe.timeout = 2
+
+ start = time.time()
+
+ data = ""
+ blocks = 0
+ status = chirp_common.Status()
+ 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)
+ repeat = 40
+ else:
+ repeat = 1
+ for _i in range(0, repeat):
+ data += self._read(block, blocks, blocks == nblocks - 1)
+ self.pipe.write(chr(CMD_ACK))
+ blocks += 1
+ status.cur = blocks
+ self.status_fn(status)
+
+ 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, "
+ "Please choose the correct model and try again."))
+
+ LOG.info("Clone completed in %i seconds" % (time.time() - start))
+
+ return memmap.MemoryMap(data)
+
+ def _clone_out(self):
+ delay = 0.5
+ start = time.time()
+
+ blocks = 0
+ pos = 0
+ status = chirp_common.Status()
+ status.msg = _("Cloning to radio")
+ status.max = len(self._block_lengths) + 39
+ for block in self._block_lengths:
+ if blocks == 8:
+ # repeated read of 40 block same size (memory area)
+ repeat = 40
+ else:
+ repeat = 1
+ for _i in range(0, repeat):
+ time.sleep(0.01)
+ checksum = yaesu_clone.YaesuChecksum(pos, pos + block - 1)
+ LOG.debug("Block %i - will send from %i to %i byte " %
+ (blocks, pos, pos + block))
+ LOG.debug(util.hexprint(chr(blocks)))
+ LOG.debug(util.hexprint(self.get_mmap()[pos:pos + block]))
+ LOG.debug(util.hexprint(chr(checksum.get_calculated(
+ self.get_mmap()))))
+ self.pipe.write(chr(blocks))
+ self.pipe.write(self.get_mmap()[pos:pos + block])
+ self.pipe.write(chr(checksum.get_calculated(self.get_mmap())))
+ buf = self.pipe.read(1)
+ if not buf or buf[0] != chr(CMD_ACK):
+ time.sleep(delay)
+ buf = self.pipe.read(1)
+ if not buf or buf[0] != chr(CMD_ACK):
+ LOG.debug(util.hexprint(buf))
+ raise Exception(_("Radio did not ack block %i") % blocks)
+ pos += block
+ blocks += 1
+ status.cur = blocks
+ self.status_fn(status)
+
+ LOG.info("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:
+ raise errors.RadioError("Failed to communicate with radio: %s" % e)
+ self.process_mmap()
+
+ def sync_out(self):
+ try:
+ self._clone_out()
+ 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(self.MEM_FORMAT, self._mmap)
+
+ def get_features(self):
+ rf = chirp_common.RadioFeatures()
+ rf.has_bank = False
+ rf.has_dtcs_polarity = False
+ rf.has_nostep_tuning = True
+ rf.valid_modes = list(set(self.MODES))
+ rf.valid_tmodes = list(self.TMODES)
+ rf.valid_duplexes = list(self.DUPLEX)
+ rf.valid_tuning_steps = list(self.STEPSFM)
+ rf.valid_bands = self.VALID_BANDS
+ rf.valid_skips = ["", "S"]
+ rf.valid_power_levels = []
+ rf.valid_characters = "".join(self.CHARSET)
+ rf.valid_name_length = 8
+ rf.valid_special_chans = sorted(self.SPECIAL_MEMORIES.keys())
+ rf.memory_bounds = (1, 200)
+ rf.can_odd_split = True
+ rf.has_ctone = False
+ rf.has_settings = True
+ return rf
+
+ def get_raw_memory(self, number):
+ return repr(self._memobj.memory[number - 1])
+
+ def _get_duplex(self, mem, _mem):
+ if _mem.is_duplex == 1:
+ mem.duplex = self.DUPLEX[_mem.duplex]
+ else:
+ mem.duplex = ""
+
+ def _get_tmode(self, mem, _mem):
+ mem.tmode = self.TMODES[_mem.tmode]
+ mem.rtone = chirp_common.TONES[_mem.tone]
+ mem.dtcs = chirp_common.DTCS_CODES[_mem.dcs]
+
+ def _set_duplex(self, mem, _mem):
+ _mem.duplex = self.DUPLEX.index(mem.duplex)
+ _mem.is_duplex = mem.duplex != ""
+
+ def _set_tmode(self, mem, _mem):
+ _mem.tmode = self.TMODES.index(mem.tmode)
+ # have to put this bit to 0 otherwise we get strange display in tone
+ # frequency (menu 83). See bug #88 and #163
+ _mem.unknown_toneflag = 0
+ _mem.tone = chirp_common.TONES.index(mem.rtone)
+ _mem.dcs = chirp_common.DTCS_CODES.index(mem.dtcs)
+
+ def get_memory(self, number):
+ if isinstance(number, str):
+ return self._get_special(number)
+ elif number < 0:
+ # I can't stop delete operation from loosing extd_number but
+ # I know how to get it back
+ return self._get_special(self.SPECIAL_MEMORIES_REV[number])
+ else:
+ return self._get_normal(number)
+
+ def set_memory(self, memory):
+ if memory.number < 0:
+ return self._set_special(memory)
+ else:
+ return self._set_normal(memory)
+
+ def _get_special(self, number):
+ mem = chirp_common.Memory()
+ mem.number = self.SPECIAL_MEMORIES[number]
+ mem.extd_number = number
+
+ if mem.number in range(self.FIRST_VFOA_INDEX,
+ self.LAST_VFOA_INDEX - 1,
+ -1):
+ _mem = self._memobj.vfoa[-self.LAST_VFOA_INDEX + mem.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", "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", "extd_number",
+ "name", "dtcs_polarity", "power", "comment"]
+ elif mem.number == -1:
+ _mem = self._memobj.qmb
+ 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
+ used = (self._memobj.pmsvisible >> bitindex) & 0x01
+ valid = (self._memobj.pmsfilled >> bitindex) & 0x01
+ if not used:
+ mem.empty = True
+ if not valid:
+ mem.empty = True
+ return mem
+ _mem = self._memobj.pms[-self.LAST_PMS_INDEX + mem.number]
+ immutable = ["number", "skip", "rtone", "ctone", "extd_number",
+ "dtcs", "tmode", "cross_mode", "dtcs_polarity",
+ "power", "duplex", "offset", "comment"]
+ else:
+ raise Exception("Sorry, special memory index %i " % mem.number +
+ "unknown you hit a bug!!")
+
+ mem = self._get_memory(mem, _mem)
+ mem.immutable = immutable
+
+ return mem
+
+ def _set_special(self, mem):
+ if mem.empty and mem.number not in self.SPECIAL_PMS.values():
+ # can't delete special memories!
+ raise Exception("Sorry, special memory can't be deleted")
+
+ cur_mem = self._get_special(self.SPECIAL_MEMORIES_REV[mem.number])
+
+ # TODO add frequency range check for vfo and home memories
+ if mem.number in range(self.FIRST_VFOA_INDEX,
+ self.LAST_VFOA_INDEX - 1,
+ -1):
+ _mem = self._memobj.vfoa[-self.LAST_VFOA_INDEX + mem.number]
+ 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]
+ elif mem.number in range(-2, -6, -1):
+ _mem = self._memobj.home[5 + mem.number]
+ elif mem.number == -1:
+ _mem = self._memobj.qmb
+ elif mem.number in self.SPECIAL_PMS.values():
+ # this case has to be last because 817 pms keys overlap with
+ # 857 derived class other special memories
+ bitindex = -self.LAST_PMS_INDEX + mem.number
+ wasused = (self._memobj.pmsvisible >> bitindex) & 0x01
+ wasvalid = (self._memobj.pmsfilled >> bitindex) & 0x01
+ if mem.empty:
+ if wasvalid and not wasused:
+ # pylint get confused by &= operator
+ self._memobj.pmsfilled = self._memobj.pmsfilled & \
+ ~ (1 << bitindex)
+ # pylint get confused by &= operator
+ self._memobj.pmsvisible = self._memobj.pmsvisible & \
+ ~ (1 << bitindex)
+ return
+ # pylint get confused by |= operator
+ self._memobj.pmsvisible = self._memobj.pmsvisible | 1 << bitindex
+ self._memobj.pmsfilled = self._memobj.pmsfilled | 1 << bitindex
+ _mem = self._memobj.pms[-self.LAST_PMS_INDEX + mem.number]
+ else:
+ raise Exception("Sorry, special memory index %i " % mem.number +
+ "unknown you hit a bug!!")
+
+ for key in cur_mem.immutable:
+ if key != "extd_number":
+ if cur_mem.__dict__[key] != mem.__dict__[key]:
+ raise errors.RadioError("Editing field `%s' " % key +
+ "is not supported on this channel")
+
+ self._set_memory(mem, _mem)
+
+ def _get_normal(self, number):
+ _mem = self._memobj.memory[number - 1]
+ used = (self._memobj.visible[(number - 1) / 8] >> (number - 1) % 8) \
+ & 0x01
+ valid = (self._memobj.filled[(number - 1) / 8] >> (number - 1) % 8) \
+ & 0x01
+
+ mem = chirp_common.Memory()
+ mem.number = number
+ if not used:
+ mem.empty = True
+ if not valid or _mem.freq == 0xffffffff:
+ return mem
+
+ return self._get_memory(mem, _mem)
+
+ def _set_normal(self, mem):
+ _mem = self._memobj.memory[mem.number - 1]
+ wasused = (self._memobj.visible[(mem.number - 1) / 8] >>
+ (mem.number - 1) % 8) & 0x01
+ wasvalid = (self._memobj.filled[(mem.number - 1) / 8] >>
+ (mem.number - 1) % 8) & 0x01
+
+ if mem.empty:
+ if mem.number == 1:
+ # as Dan says "yaesus are not good about that :("
+ # if you ulpoad an empty image you can brick your radio
+ raise Exception("Sorry, can't delete first memory")
+ if wasvalid and not wasused:
+ self._memobj.filled[(mem.number - 1) / 8] &= \
+ ~(1 << (mem.number - 1) % 8)
+ _mem.set_raw("\xFF" * (_mem.size() / 8)) # clean up
+ self._memobj.visible[(mem.number - 1) / 8] &= \
+ ~(1 << (mem.number - 1) % 8)
+ return
+ if not wasvalid:
+ _mem.set_raw("\x00" * (_mem.size() / 8)) # clean up
+
+ self._memobj.visible[(mem.number - 1) / 8] |= 1 << (mem.number - 1) % 8
+ self._memobj.filled[(mem.number - 1) / 8] |= 1 << (mem.number - 1) % 8
+ self._set_memory(mem, _mem)
+
+ def _get_memory(self, mem, _mem):
+ mem.freq = int(_mem.freq) * 10
+ mem.offset = int(_mem.offset) * 10
+ self._get_duplex(mem, _mem)
+ mem.mode = self.MODES[_mem.mode]
+ if mem.mode == "FM":
+ if _mem.is_fm_narrow == 1:
+ mem.mode = "NFM"
+ mem.tuning_step = self.STEPSFM[_mem.fm_step]
+ elif mem.mode == "AM":
+ mem.tuning_step = self.STEPSAM[_mem.am_step]
+ elif mem.mode == "CW" or mem.mode == "CWR":
+ if _mem.is_cwdig_narrow == 1:
+ mem.mode = "N" + mem.mode
+ mem.tuning_step = self.STEPSSSB[_mem.ssb_step]
+ else:
+ try:
+ mem.tuning_step = self.STEPSSSB[_mem.ssb_step]
+ except IndexError:
+ pass
+ mem.skip = _mem.skip and "S" or ""
+ self._get_tmode(mem, _mem)
+
+ if _mem.tag_on_off == 1:
+ for i in _mem.name:
+ if i == 0xFF:
+ break
+ 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 *
+ LOG.info("Replacing char %x with *" % i)
+ mem.name += "*"
+ mem.name = mem.name.rstrip()
+ else:
+ mem.name = ""
+
+ mem.extra = RadioSettingGroup("extra", "Extra")
+ ipo = RadioSetting("ipo", "IPO",
+ RadioSettingValueBoolean(bool(_mem.ipo)))
+ ipo.set_doc("Bypass preamp")
+ mem.extra.append(ipo)
+
+ att = RadioSetting("att", "ATT",
+ RadioSettingValueBoolean(bool(_mem.att)))
+ att.set_doc("10dB front end attenuator")
+ mem.extra.append(att)
+
+ return mem
+
+ def _set_memory(self, mem, _mem):
+ if len(mem.name) > 0: # not supported in chirp
+ # so I make label visible if have one
+ _mem.tag_on_off = 1
+ else:
+ _mem.tag_on_off = 0
+ _mem.tag_default = 0 # never use default label "CH-nnn"
+ self._set_duplex(mem, _mem)
+ if mem.mode[0] == "N": # is it narrow?
+ _mem.mode = self.MODES.index(mem.mode[1:])
+ # here I suppose it's safe to set both
+ _mem.is_fm_narrow = _mem.is_cwdig_narrow = 1
+ else:
+ _mem.mode = self.MODES.index(mem.mode)
+ # here I suppose it's safe to set both
+ _mem.is_fm_narrow = _mem.is_cwdig_narrow = 0
+ i = 0
+ for lo, hi in self.VALID_BANDS:
+ if mem.freq > lo and mem.freq < hi:
+ break
+ i += 1
+ _mem.freq_range = i
+ # all this should be safe also when not in split but ...
+ if mem.duplex == "split":
+ _mem.tx_mode = _mem.mode
+ i = 0
+ for lo, hi in self.VALID_BANDS:
+ if mem.offset >= lo and mem.offset < hi:
+ break
+ i += 1
+ _mem.tx_freq_range = i
+ _mem.skip = mem.skip == "S"
+ self._set_tmode(mem, _mem)
+ try:
+ _mem.ssb_step = self.STEPSSSB.index(mem.tuning_step)
+ except ValueError:
+ pass
+ try:
+ _mem.am_step = self.STEPSAM.index(mem.tuning_step)
+ except ValueError:
+ pass
+ try:
+ _mem.fm_step = self.STEPSFM.index(mem.tuning_step)
+ except ValueError:
+ pass
+ _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] = ord(mem.name.ljust(8)[i])
+
+ for setting in mem.extra:
+ setattr(_mem, setting.get_name(), setting.value)
+
+ def validate_memory(self, mem):
+ msgs = yaesu_clone.YaesuCloneModeRadio.validate_memory(self, mem)
+
+ lo, hi = self.VALID_BANDS[2] # this is fm broadcasting
+ if mem.freq >= lo and mem.freq <= hi:
+ if mem.mode != "FM":
+ msgs.append(chirp_common.ValidationError(
+ "Only FM is supported in this band"))
+ # TODO check that step is valid in current mode
+ return msgs
+
+ @classmethod
+ def match_model(cls, filedata, filename):
+ return len(filedata) == cls._memsize
+
+ 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")
+ antenna = RadioSettingGroup("antenna", "Antenna selection")
+ panelcontr = RadioSettingGroup("panelcontr", "Panel controls")
+
+ top = RadioSettings(basic, cw, packet,
+ panelcontr, panel, extended, antenna)
+
+ rs = RadioSetting("ars_144", "144 ARS",
+ RadioSettingValueBoolean(_settings.ars_144))
+ basic.append(rs)
+ rs = RadioSetting("ars_430", "430 ARS",
+ RadioSettingValueBoolean(_settings.ars_430))
+ basic.append(rs)
+ rs = RadioSetting("pkt9600_mic", "Paket 9600 mic level",
+ RadioSettingValueInteger(0, 100,
+ _settings.pkt9600_mic))
+ packet.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 level",
+ 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)
+ options = ["OFF", "ON", "Auto"]
+ rs = RadioSetting("backlight", "Backlight",
+ RadioSettingValueList(options,
+ options[_settings.backlight]))
+ panel.append(rs)
+ options = ["6h", "8h", "10h"]
+ rs = RadioSetting("batt_chg", "Battery charge",
+ RadioSettingValueList(options,
+ options[_settings.batt_chg]))
+ basic.append(rs)
+ options = ["440Hz", "880Hz"]
+ rs = RadioSetting("beep_freq", "Beep frequency",
+ RadioSettingValueList(options,
+ options[_settings.beep_freq]))
+ panel.append(rs)
+ rs = RadioSetting("beep_volume", "Beep volume",
+ RadioSettingValueInteger(0, 100,
+ _settings.beep_volume))
+ panel.append(rs)
+ options = ["4800", "9600", "38400"]
+ rs = RadioSetting("cat_rate", "CAT rate",
+ RadioSettingValueList(options,
+ options[_settings.cat_rate]))
+ basic.append(rs)
+ options = ["Blue", "Amber", "Violet"]
+ rs = RadioSetting("color", "Color",
+ RadioSettingValueList(options,
+ options[_settings.color]))
+ panel.append(rs)
+ rs = RadioSetting("contrast", "Contrast",
+ RadioSettingValueInteger(1, 12,
+ _settings.contrast - 1))
+ panel.append(rs)
+ rs = RadioSetting("cw_delay", "CW delay (*10 ms)",
+ RadioSettingValueInteger(1, 250,
+ _settings.cw_delay))
+ cw.append(rs)
+ rs = RadioSetting("cw_id", "CW id",
+ RadioSettingValueBoolean(_settings.cw_id))
+ cw.append(rs)
+ options = ["Normal", "Reverse"]
+ rs = RadioSetting("cw_paddle", "CW paddle",
+ RadioSettingValueList(options,
+ options[_settings.cw_paddle]))
+ cw.append(rs)
+ options = ["%i Hz" % i for i in range(300, 1001, 50)]
+ rs = RadioSetting("cw_pitch", "CW pitch",
+ RadioSettingValueList(options,
+ options[_settings.cw_pitch]))
+ 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 = ["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)
+ rs = RadioSetting("dig_disp", "Dig disp (*10 Hz)",
+ RadioSettingValueInteger(-300, 300,
+ _settings.dig_disp))
+ packet.append(rs)
+ rs = RadioSetting("dig_mic", "Dig mic",
+ RadioSettingValueInteger(0, 100,
+ _settings.dig_mic))
+ packet.append(rs)
+ options = ["RTTY", "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("fm_mic", "FM mic",
+ RadioSettingValueInteger(0, 100,
+ _settings.fm_mic))
+ basic.append(rs)
+ options = ["Dial", "Freq", "Panel"]
+ rs = RadioSetting("lock_mode", "Lock mode",
+ RadioSettingValueList(options,
+ options[_settings.lock_mode]))
+ panel.append(rs)
+ options = ["Fine", "Coarse"]
+ rs = RadioSetting("main_step", "Main step",
+ RadioSettingValueList(options,
+ options[_settings.main_step]))
+ panel.append(rs)
+ rs = RadioSetting("mem_group", "Mem group",
+ RadioSettingValueBoolean(_settings.mem_group))
+ basic.append(rs)
+ rs = RadioSetting("mic_key", "Mic key",
+ RadioSettingValueBoolean(_settings.mic_key))
+ cw.append(rs)
+ rs = RadioSetting("mic_scan", "Mic scan",
+ RadioSettingValueBoolean(_settings.mic_scan))
+ basic.append(rs)
+ options = ["Off", "SSB", "CW"]
+ rs = RadioSetting("op_filter", "Optional filter",
+ RadioSettingValueList(options,
+ options[_settings.op_filter]))
+ basic.append(rs)
+ rs = RadioSetting("pkt_mic", "Packet mic",
+ RadioSettingValueInteger(0, 100, _settings.pkt_mic))
+ packet.append(rs)
+ options = ["1200", "9600"]
+ rs = RadioSetting("pkt_rate", "Packet rate",
+ RadioSettingValueList(options,
+ options[_settings.pkt_rate]))
+ packet.append(rs)
+ options = ["Off", "3 sec", "5 sec", "10 sec"]
+ rs = RadioSetting("resume_scan", "Resume scan",
+ RadioSettingValueList(options,
+ options[_settings.resume_scan])
+ )
+ basic.append(rs)
+ options = ["Cont", "Chk"]
+ rs = RadioSetting("scope", "Scope",
+ RadioSettingValueList(options,
+ options[_settings.scope]))
+ basic.append(rs)
+ rs = RadioSetting("sidetone", "Sidetone",
+ RadioSettingValueInteger(0, 100, _settings.sidetone))
+ cw.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",
+ RadioSettingValueInteger(0, 100, _settings.ssb_mic))
+ basic.append(rs)
+ options = ["%i" % i for i in range(0, 21)]
+ options[0] = "Off"
+ rs = RadioSetting("tot_time", "Time-out timer",
+ RadioSettingValueList(options,
+ options[_settings.tot_time]))
+ basic.append(rs)
+ rs = RadioSetting("vox_delay", "VOX delay (*100 ms)",
+ RadioSettingValueInteger(1, 25, _settings.vox_delay))
+ basic.append(rs)
+ rs = RadioSetting("vox_gain", "VOX Gain",
+ RadioSettingValueInteger(0, 100, _settings.vox_gain))
+ basic.append(rs)
+ rs = RadioSetting("extended_menu", "Extended menu",
+ RadioSettingValueBoolean(_settings.extended_menu))
+ extended.append(rs)
+ options = ["Tn-Rn", "Tn-Riv", "Tiv-Rn", "Tiv-Riv"]
+ rs = RadioSetting("dcs_inv", "DCS coding",
+ RadioSettingValueList(options,
+ options[_settings.dcs_inv]))
+ extended.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 = ["Hi", "L3", "L2", "L1"]
+ rs = RadioSetting("tx_power", "TX power",
+ RadioSettingValueList(options,
+ options[_settings.tx_power]))
+ basic.append(rs)
+
+ options = ["Front", "Rear"]
+ rs = RadioSetting("hf_antenna", "HF",
+ RadioSettingValueList(options,
+ options[_settings.hf_antenna]))
+ antenna.append(rs)
+ rs = RadioSetting("sixm_antenna", "6M",
+ RadioSettingValueList(options,
+ options[_settings.sixm_antenna]
+ ))
+ antenna.append(rs)
+ rs = RadioSetting("bc_antenna", "Broadcasting",
+ RadioSettingValueList(options,
+ options[_settings.bc_antenna]))
+ antenna.append(rs)
+ rs = RadioSetting("air_antenna", "Air band",
+ RadioSettingValueList(options,
+ options[_settings.air_antenna])
+ )
+ antenna.append(rs)
+ rs = RadioSetting("vhf_antenna", "VHF",
+ RadioSettingValueList(options,
+ options[_settings.vhf_antenna])
+ )
+ antenna.append(rs)
+ rs = RadioSetting("uhf_antenna", "UHF",
+ RadioSettingValueList(options,
+ options[_settings.uhf_antenna])
+ )
+ antenna.append(rs)
+
+ st = RadioSettingValueString(0, 7, ''.join([self._CALLSIGN_CHARSET[x]
+ for x in self._memobj.
+ callsign]))
+ st.set_charset(self._CALLSIGN_CHARSET)
+ rs = RadioSetting("callsign", "Callsign", st)
+ cw.append(rs)
+
+ rs = RadioSetting("spl", "Split",
+ RadioSettingValueBoolean(_settings.spl))
+ panelcontr.append(rs)
+ options = ["None", "Up", "Down"]
+ rs = RadioSetting("scn_mode", "Scan mode",
+ RadioSettingValueList(options,
+ options[_settings.scn_mode]))
+ panelcontr.append(rs)
+ rs = RadioSetting("pri", "Priority",
+ RadioSettingValueBoolean(_settings.pri))
+ panelcontr.append(rs)
+ rs = RadioSetting("dw", "Dual watch",
+ RadioSettingValueBoolean(_settings.dw))
+ panelcontr.append(rs)
+ rs = RadioSetting("art", "Auto-range transponder",
+ RadioSettingValueBoolean(_settings.art))
+ panelcontr.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)
+
+ 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()
+ try:
+ LOG.debug("Setting %s(%s) <= %s" % (setting,
+ getattr(obj, setting),
+ element.value))
+ except AttributeError:
+ LOG.debug("Setting %s <= %s" % (setting, element.value))
+ if setting == "contrast":
+ setattr(obj, setting, int(element.value) + 1)
+ elif setting == "callsign":
+ self._memobj.callsign = \
+ [self._CALLSIGN_CHARSET_REV[x] for x in
+ str(element.value)]
+ else:
+ setattr(obj, setting, element.value)
+ except:
+ LOG.debug(element.get_name())
+ raise
+
+
diff --git a/tests/images/Yaesu_FT-818.img b/tests/images/Yaesu_FT-818.img
new file mode 100644
index e69de29bb2d1d6434b8b29ae775ad8c2e48c5391..a86870c3805f17e0c98acb300906098efcb0871e
GIT binary patch
literal 6730
zc$~$UYit}>701V$gt$rUxDI(KhOU}Kg*2%wC)}hTT6P{gyR)-9GrOMM*<JDQYn!-^
zNq~Zz3WSl0l}}M5tO`K|rb_B0ZbB4V@en9RtpwuLR4Qr&ZoFGURiUb~1VTtugqd^S
z6EA!Lu^%EOznpW=J@<dkJ$G(iSCqwM|6?=Pw9Y^695{V#ouXVDh<=Lr>rj*tMae2k
zXY{wk^`xRa&~u0UhB#Rl^owMelg<Z^MI=dng3X32Ln0B@*{r47c-#7k#WrNuR7&(F
zkmSZ>8?txAYeV+^Xj&v{2P1hqrF6{9(FoEB#U*4W11Y1#Sfkj4ocq<ZNYu5=+aG=$
z{_*D_M%F0~CW*7jMP>bWla!3{*tY#~NX)^1=X_SNA?LHoI{%aNS;dB&&nkqR56jzK
z=R?=||D6wwpux$3eUhruJZf+6tQ0km;LX%r6agbVg4q}6q6k(amzKoX+W#7DPaCwf
zrYi;cEHxIDwm#OjCCd48%IOYeQ{;Z>D@L@90O@Wv4EAe4cEbj@KN}I-M0LLwlCg+<
z5XgyXuu+jUn4x4dkSPy{fz-gGq!-A{3V72|&UYx;DUbjaJcvM8=gcA|MoDF)OCSzX
zBNIY;1Y#S=_KPxk<}9`u%p1%Ga`5K!{g{|WiIJ@W33qB%VzFHT0R|<c)*ac~OE(|_
zq<elWwx7=7{rIJT5Gp1@j3XsIPi^i2@|zTRd)QW8=4}U%-}T^i21|ZyKbVA3=i_->
zYT}(1(mZcCg4<Vt9CnRlEC-m_$k9qnAsZvIyzyDGj>@xxwe#*YUdOTQm^VtwgKjM9
z_mem~YJo@4EfJ!Fb4+iyURz6Y;BwL>kU&GFL7S4TGQqkQyuI@TcnkMaC{fZakkF>A
zWhjC3x%JomU~@QQ<)a90hh#sFdT&-~4UC0KEarV;vwu6}Z5`J2;T&vLSchaZRAMp7
z$WAfW$X0!Mr^^q$LQkPsq$B~P_%5m7T3(ZMu>8_#QYD5_pyWe9ewM<qX(m5~Eg}hP
zA!AFrUjBI>cuS+Qr^@SKqH3tX6|xt|um4UWP)$|Z>8(4rN&RHgu?Vn@gK`CnkzRp>
zv1MyTsj?`EbWo7V&8s-0PLN5iN>o(v>+*hXAA{KXbDk}$myocJ3N_a)5xx$olI4{o
z>527XNvaY`EM)l=IoJQVNfciz%eB2K=@m$TazU(HQlg?-B`@W=+3_NH%b~1Zm3B%<
zfHFSsmK%U<$>Q@#S9Cd}&Gl!ojp^{J-!9mMv1xvc+^3LknTvI#=AI=|3#^Zlp4i;?
zLvjj2Y|yWp=p@moS!QQ?k3hm4q|HEHNBaoQXfd6YGfIe<QMyw?ROAoRs>l0T{^U6l
zfnR4m1WvA5`vzX8WSOM4sW(i*`&4t7%`PAfeA6N?@Z}!ToY*hvP4M>FPN+r|86LC2
zIr!3|)WvwWB*a0LOgXmB^o963bU&w`BzI%Z%S$swa~AGXJG@MC%%)QyL3m~HTV{ty
zq>N%EesdFvpi)c9<U8-+jOGR{MzBWDzk-RO4>AJ3=E5AAL(=i=yxc?X1veTUFzFRY
z*j<WQjS{T4-~IxxqiLX!2F+VyehUn!Hp;pgxq@43p2iVWgb4-m)_Sy$^n+Z*l<Kr)
zy-u~m`_5*Gkh@V>hY%gPwkB0cYuLaxX`CG(_X0WoJ|_7*?=G7}q9Hvg*IesK(kY5&
ztXsCewM6@l<I6iGBtW&;{nMJCy}SE&2_!(8#R;gl_5D1#OI&{cC?ZLtOj(y|n~<=F
z7-lR2>MdPz<o(cYDG|8MTGz>hlJm2%*a$JhCc<(6UAgtkpOC6Iyl6r>PuEGz1$!dx
z1mO%@XR3-~OI#Se3aZi%xtTERnFg);#D(YjBjTV;2F{dwdw%!RV3R?%U7_A~fkt<o
z1)?FhUZn(V<{t!ap%M*^MbHJLl!Dl3)eAPKJRCL2=qiNtQ6TGqyqdyQVr$%{S0b8`
zW@IanTTM({FBe+}6E-~w<ZYZcdU6fgDJWsp*r2tLn162?+mKoSvJG<GwMg=&)$8&)
z#3qM?opuX^&3yb!=l@2slT|s{3FMziOnld3WQV8&6=kY5X&t5z<l8DG+XdpFl9yrL
zb_1EfQPU}n@8{wJ3L###F6n@fXiltWB~@`^`MT`w(UUl%q+jzKTx>uD;mb&mo)RK?
zIYrc)9V}7;iof{QG&zNwnvoQ8>E0od1J^4{8Kop0KHE$wZ-lUWjz`T1dC&12P;Zi|
zdVqYIXvcN95*2}jcS+hVOG>1KzBPsYoEf60T{7heFbS(3&!a-re_5dwT&qaxT^I(v
zg}1Y+%Qg$&TjhMBIX>}1-wl%MLP?kQLrC}rEX2HB{`w7^w`ey~)P#hp%K2q!oy)Bi
zqMeLk%HA+xFJyjlSNePKI^^Bhpf?jG4$>{&M?pIa7U>~m7K3hiA9Tx4^^pkl=-DRL
z>4ko*(NOWMN^q}wqlXYx)1_Ki_(6*7w4P`61J!uti$ptqx*$hAe|_$50^Tk?gCjsD
zd<&T26?}Oig?Ad%`5oN;TMZ`P9+D;pA#W~Li10T1cN0FIAn8nQIs8qFZ%s18YA9QB
zDZ#G~JVDz1s?Fz+E#iED@&!JR;y2SnWU|vuuBvU44#YWe8>|zXAR9CXiHpA{JuzRc
zMr4T1y$95l=!0BHuy%$SDZb`K9}Ag{aB}p<&~^SUfmZ?fBEw#KLU!Ihf~WPvQF|la
zJ^<c!(_U5fEIGCT_%Q2ONAP#CVQQmzuTDp?MQ>UoM+r<%-Ww3LJnz69((Iq5lecL|
zRWkq8B&h}P_Mbm!-u)9k8n1urjQEN3{texIAKtS^QNG;%6MD=;=lwU@eMbLiPdjp8
zUpq4JdOPxQU-}q(XY|<k=T5#iK6zB>Xq?Io)Ps*rj2yJa-Enn12+ZT72g~DmT^%0@
z9-gQNQ}FMl<HaXjBYnskpE_zyIwyv!xvA>G`ti}vI44I24#EE^=!c%lKdw#Gt2MA6
wope55t2$3vkDoYdP1MVgeR<NH7!HmAADc8!4F~2I#`I7}x;6e-@sUUV1%Ck@rT_o{
2
2
Hello
Please find the driver for FT-7100 in the attachment.
It works fine with the radio and the FT-7100 Programmer from "FT Systems".
There are a few issues to integrate the functionality to Chirp.
a) The total number of memories is constant. However it is possible to define
the number of VHF and UHV channels. --> What mechanism do I have to use?
- User interface to ask the number of channels. How?
- Refresh the user interface after work. How?
b) There is one more tone_mode available named "CTCSS Bell". How do I add it?
c) There are a few more RadioSetting like show_name, is_masked and
is_packet96. How do I add them?
b) Testing is missing
Best regards,
Bruno
2
6