|
# Latest update: April, 2021 Add hasattr test at line 564
|
|
import struct
|
|
import logging
|
|
from chirp.drivers import icf
|
|
from chirp import chirp_common, util, errors, bitwise, directory
|
|
from chirp.memmap import MemoryMapBytes
|
|
from chirp.settings import RadioSetting, RadioSettingGroup, \
|
|
RadioSettingValueList, RadioSettingValueBoolean
|
|
|
|
LOG = logging.getLogger(__name__)
|
|
|
|
MEM_FORMAT = """
|
|
bbcd number[2];
|
|
u8 unknown:3
|
|
split:1,
|
|
unknown_0:4;
|
|
lbcd freq[5];
|
|
u8 unknown2:5,
|
|
mode:3;
|
|
u8 filter;
|
|
u8 unknown_1:3,
|
|
dig:1,
|
|
unknown_2:4;
|
|
"""
|
|
|
|
|
|
# http://www.vk4adc.com/
|
|
# web/index.php/reference-information/49-general-ref-info/182-civ7400
|
|
MEM_IC7000_FORMAT = """
|
|
u8 bank;
|
|
bbcd number[2];
|
|
u8 spl:4,
|
|
skip:4;
|
|
lbcd freq[5];
|
|
u8 mode;
|
|
u8 filter;
|
|
u8 duplex:4,
|
|
tmode:4;
|
|
bbcd rtone[3];
|
|
bbcd ctone[3];
|
|
u8 dtcs_polarity;
|
|
bbcd dtcs[2];
|
|
lbcd freq_tx[5];
|
|
u8 mode_tx;
|
|
u8 filter_tx;
|
|
u8 duplex_tx:4,
|
|
tmode_tx:4;
|
|
bbcd rtone_tx[3];
|
|
bbcd ctone_tx[3];
|
|
u8 dtcs_polarity_tx;
|
|
bbcd dtcs_tx[2];
|
|
char name[9];
|
|
"""
|
|
|
|
MEM_IC7100_FORMAT = """
|
|
u8 bank; // 1 bank number
|
|
bbcd number[2]; // 2,3
|
|
u8 splitSelect; // 4 split and select memory settings
|
|
lbcd freq[5]; // 5-9 operating freq
|
|
u8 mode; // 10 operating mode
|
|
u8 filter; // 11 filter
|
|
u8 dataMode; // 12 data mode setting (on or off)
|
|
u8 duplex:4, // 13 duplex on/-/+
|
|
tmode:4; // 13 tone
|
|
u8 dsql:4, // 14 digital squelch
|
|
unknown1:4; // 14 zero
|
|
bbcd rtone[3]; // 15-17 repeater tone freq
|
|
bbcd ctone[3]; // 18-20 tone squelch setting
|
|
u8 dtcsPolarity; // 21 DTCS polarity
|
|
u8 unknown2:4, // 22 zero
|
|
firstDtcs:4; // 22 first digit of DTCS code
|
|
u8 secondDtcs:4, // 23 second digit DTCS
|
|
thirdDtcs:4; // 23 third digit DTCS
|
|
u8 digitalSquelch; // 24 Digital code squelch setting
|
|
lbcd duplexOffset[3]; // 25-27 duplex offset freq
|
|
char destCall[8]; // 28-35 destination call sign
|
|
char accessRepeaterCall[8];// 36-43 access repeater call sign
|
|
char linkRepeaterCall[8]; // 44-51 gateway/link repeater call sign
|
|
bbcd duplexSettings[47]; // repeat of 5-51 for duplex
|
|
char name[16]; // 52-60 Name of station
|
|
"""
|
|
|
|
MEM_IC910_FORMAT = """
|
|
u8 bank; // 1 bank number
|
|
bbcd number[2]; // 2,3
|
|
lbcd freq[5]; // 4-8 operating freq
|
|
u8 mode; // 9 operating mode
|
|
u8 filter; // 10 filter
|
|
u8 tmode:4, // 11 tone
|
|
duplex:4; // 11 duplex off/-/+
|
|
bbcd rtone[3]; // 12-14 repeater tone freq
|
|
bbcd ctone[3]; // 15-17 tone squelch setting
|
|
lbcd duplexOffset[3]; // 18-20 duplex offset freq
|
|
"""
|
|
|
|
mem_duptone_format = """
|
|
bbcd number[2];
|
|
u8 unknown1;
|
|
lbcd freq[5];
|
|
u8 unknown2:5,
|
|
mode:3;
|
|
u8 unknown1;
|
|
u8 unknown2:2,
|
|
duplex:2,
|
|
unknown3:1,
|
|
tmode:3;
|
|
u8 unknown4;
|
|
bbcd rtone[2];
|
|
u8 unknown5;
|
|
bbcd ctone[2];
|
|
u8 dtcs_polarity;
|
|
bbcd dtcs[2];
|
|
u8 unknown[11];
|
|
char name[9];
|
|
"""
|
|
|
|
MEM_IC7300_FORMAT = """
|
|
bbcd number[2]; // 1,2
|
|
u8 spl:4, // 3 split and select memory settings
|
|
select:4;
|
|
lbcd freq[5]; // 4-8 receive freq
|
|
u8 mode; // 9 operating mode
|
|
u8 filter; // 10 filter 1-3 (undocumented)
|
|
u8 dataMode:4, // 11 data mode setting (on or off)
|
|
tmode:4; // 11 tone type
|
|
char pad1;
|
|
bbcd rtone[2]; // 12-14 tx tone freq
|
|
char pad2;
|
|
bbcd ctone[2]; // 15-17 tone rx squelch setting
|
|
// This is duplicated from 4-17 above, even duplicated by number in the
|
|
// manual!
|
|
lbcd freq_tx[5]; // 4-8 receive freq
|
|
u8 mode_tx; // 9 operating mode
|
|
u8 filter_tx; // 10 filter 1-3 (undocumented)
|
|
u8 dataMode_tx:4, // 11 data mode setting (on or off)
|
|
tmode_tx:4; // 11 tone type
|
|
char pad1_tx;
|
|
bbcd rtone_tx[2]; // 12-14 tx tone freq
|
|
char pad2_tx;
|
|
bbcd ctone_tx[2]; // 15-17 tone rx squelch setting
|
|
// End TX duplicate block
|
|
char name[10]; // 18-27 Callsign
|
|
"""
|
|
|
|
MEM_IC9700_FORMAT = """
|
|
u8 bank;
|
|
bbcd number[2];
|
|
u8 select_memory;
|
|
lbcd freq[5];
|
|
bbcd mode;
|
|
u8 filter;
|
|
bbcd data_mode;
|
|
u8 duplex:4,
|
|
tmode:4;
|
|
u8 dig_sql:4,
|
|
unused1:4;
|
|
bbcd rtone[3];
|
|
bbcd ctone[3];
|
|
u8 dtcs_polarity;
|
|
bbcd dtcs[2];
|
|
u8 dig_code;
|
|
lbcd duplexOffset[3];
|
|
char urcall[8];
|
|
char rpt1call[8];
|
|
char rpt2call[8];
|
|
char name[16];
|
|
"""
|
|
|
|
MEM_IC9700_SAT_FORMAT = """
|
|
bbcd number[2];
|
|
lbcd freq[5];
|
|
bbcd mode;
|
|
u8 filter;
|
|
bbcd data_mode;
|
|
u8 unused1:4,
|
|
tmode:4;
|
|
u8 unused2:4,
|
|
dig_sql:4;
|
|
bbcd rtone[3];
|
|
bbcd ctone[3];
|
|
u8 dtcs_polarity;
|
|
bbcd dtcs[2];
|
|
u8 dig_code;
|
|
char urcall[8];
|
|
char rpt1call[8];
|
|
char rpt2call[8];
|
|
struct {
|
|
lbcd freq[5];
|
|
bbcd mode;
|
|
u8 filter;
|
|
bbcd data_mode;
|
|
u8 unused1:4,
|
|
tmode:4;
|
|
u8 unused2:4,
|
|
dig_sql:4;
|
|
bbcd rtone[3];
|
|
bbcd ctone[3];
|
|
u8 dtcs_polarity;
|
|
bbcd dtcs[2];
|
|
u8 dig_code;
|
|
char urcall[8];
|
|
char rpt1call[8];
|
|
char rpt2call[8];
|
|
} tx;
|
|
char name[16];
|
|
"""
|
|
|
|
MEM_IC7400_FORMAT = """
|
|
bbcd number[2];
|
|
u8 unknown1;
|
|
lbcd freq[5];
|
|
u8 mode;
|
|
u8 filter;
|
|
u8 unknown2:7,
|
|
dig:1;
|
|
u8 unknown3:2,
|
|
duplex:2,
|
|
unknown4:1,
|
|
tmode:3;
|
|
bbcd rtone[3];
|
|
bbcd ctone[3];
|
|
u8 dtcs_polarity;
|
|
bbcd dtcs[2];
|
|
// As with IC-7300 it seems the following are duplicated parameters save for
|
|
// `dig` which seem to be zeroed in unknown5
|
|
lbcd freq_tx[5];
|
|
u8 mode_tx;
|
|
u8 filter_tx;
|
|
u8 unknown5;
|
|
u8 unknown6:2,
|
|
duplex_tx:2,
|
|
unknown7:1,
|
|
tmode_tx:3;
|
|
bbcd rtone_tx[3];
|
|
bbcd ctone_tx[3];
|
|
u8 dtcs_polarity_tx;
|
|
bbcd dtcs_tx[2];
|
|
// End TX duplicate block
|
|
char name[8];
|
|
"""
|
|
|
|
MEM_IC7610_FORMAT = """
|
|
bbcd number[2]; // 1,2
|
|
u8 spl:4, // 3 split and select memory settings
|
|
select:4;
|
|
lbcd freq[5]; // 4-8 receive freq
|
|
u8 mode; // 9 operating mode
|
|
u8 filter; // 10 filter 1-3 (undocumented)
|
|
u8 dataMode:4, // 11 data mode setting (on or off)
|
|
tmode:4; // 11 tone type
|
|
char pad1;
|
|
bbcd rtone[2]; // 12-14 tx tone freq
|
|
char pad2;
|
|
bbcd ctone[2]; // 15-17 tone rx squelch setting
|
|
char name[10]; // 18-27 Callsign
|
|
"""
|
|
|
|
SPLIT = ["", "spl"]
|
|
|
|
|
|
class Frame:
|
|
"""Base class for an ICF frame"""
|
|
_cmd = 0x00
|
|
_sub: int | None = 0x00
|
|
|
|
def __init__(self):
|
|
self._data = b""
|
|
|
|
def set_command(self, cmd, sub=None):
|
|
"""Set the command number (and optional subcommand)"""
|
|
self._cmd = cmd
|
|
self._sub = sub
|
|
|
|
def get_data(self):
|
|
"""Return the data payload"""
|
|
return self._data
|
|
|
|
def set_data(self, data):
|
|
"""Set the data payload"""
|
|
self._data = data
|
|
|
|
def send(self, src, dst, serial, willecho=True):
|
|
"""Send the frame over @serial, using @src and @dst addresses"""
|
|
hdr = struct.pack("BBBBB", 0xFE, 0xFE, src, dst, self._cmd)
|
|
# Some commands have no subcommand
|
|
if self._sub is not None:
|
|
hdr += bytes([self._sub])
|
|
raw = bytearray(hdr)
|
|
if isinstance(self._data, MemoryMapBytes):
|
|
data = self._data.get_packed()
|
|
else:
|
|
data = self._data
|
|
raw.extend(data)
|
|
raw.append(0xFD)
|
|
|
|
LOG.debug("%02x -> %02x (%i):\n%s" %
|
|
(src, dst, len(raw), util.hexprint(bytes(raw))))
|
|
|
|
serial.write(raw)
|
|
if willecho:
|
|
echo = serial.read(len(raw))
|
|
if echo != raw and echo:
|
|
LOG.debug("Echo differed (%i/%i)" % (len(raw), len(echo)))
|
|
LOG.debug(util.hexprint(bytes(raw)))
|
|
LOG.debug(util.hexprint(bytes(echo)))
|
|
|
|
def read(self, serial):
|
|
"""Read the frame from @serial"""
|
|
data = bytearray()
|
|
while not (data and data[-1] == 0xFD):
|
|
char = serial.read(1)
|
|
if not char:
|
|
LOG.debug("Read %i bytes total" % len(data))
|
|
raise errors.RadioError("Timeout")
|
|
data.extend(char)
|
|
|
|
if data[0] == 0xFD:
|
|
raise errors.RadioError("Radio reported error")
|
|
|
|
src, dst = struct.unpack("BB", data[2:4])
|
|
LOG.debug("%02x <- %02x:\n%s" % (dst, src, util.hexprint(bytes(data))))
|
|
|
|
self._cmd = data[4]
|
|
# If we've been set with a None subcommand, assume we don't expect
|
|
# it from the radio
|
|
if self._sub is None:
|
|
dataidx = 5
|
|
else:
|
|
self._sub = data[5]
|
|
dataidx = 6
|
|
self._data = data[dataidx:-1]
|
|
|
|
return src, dst
|
|
|
|
def get_obj(self):
|
|
raise errors.RadioError("Generic frame has no structure")
|
|
|
|
|
|
class MemFrame(Frame):
|
|
"""A memory frame"""
|
|
_cmd = 0x1A
|
|
_sub = 0x00
|
|
_loc = 0
|
|
FORMAT = MEM_FORMAT
|
|
|
|
def set_location(self, loc):
|
|
"""Set the memory location number"""
|
|
self._loc = loc
|
|
self._data = struct.pack(">H", int("%04i" % loc, 16))
|
|
|
|
def make_empty(self):
|
|
"""Mark as empty so the radio will erase the memory"""
|
|
self._data = struct.pack(">HB", int("%04i" % self._loc, 16), 0xFF)
|
|
|
|
def is_empty(self):
|
|
"""Return True if memory is marked as empty"""
|
|
return len(self._data) < 5
|
|
|
|
def get_obj(self):
|
|
"""Return a bitwise parsed object"""
|
|
# Make sure we're assignable
|
|
self._data = MemoryMapBytes(bytes(self._data))
|
|
return bitwise.parse(self.FORMAT, self._data)
|
|
|
|
def initialize(self):
|
|
"""Initialize to sane values"""
|
|
self._data = bytes(b'\x00' * (self.get_obj().size() // 8))
|
|
|
|
|
|
class BankMemFrame(MemFrame):
|
|
"""A memory frame for radios with multiple banks"""
|
|
FORMAT = MEM_IC7000_FORMAT
|
|
_bnk = 0
|
|
|
|
def set_location(self, loc, bank=1):
|
|
self._loc = loc
|
|
self._bnk = bank
|
|
self._data = struct.pack(
|
|
">BH", int("%02i" % bank, 16), int("%04i" % loc, 16))
|
|
|
|
def make_empty(self):
|
|
"""Mark as empty so the radio will erase the memory"""
|
|
self._data = struct.pack(
|
|
">BHB", int("%02i" % self._bnk, 16),
|
|
int("%04i" % self._loc, 16), 0xFF)
|
|
|
|
def get_obj(self):
|
|
# Make sure we're assignable
|
|
self._data = MemoryMapBytes(bytes(self._data))
|
|
return bitwise.parse(self.FORMAT, self._data)
|
|
|
|
|
|
class IC7100MemFrame(BankMemFrame):
|
|
FORMAT = MEM_IC7100_FORMAT
|
|
|
|
|
|
class IC910MemFrame(BankMemFrame):
|
|
FORMAT = MEM_IC910_FORMAT
|
|
|
|
|
|
class DupToneMemFrame(MemFrame):
|
|
def get_obj(self):
|
|
self._data = MemoryMapBytes(bytes(self._data))
|
|
return bitwise.parse(mem_duptone_format, self._data)
|
|
|
|
|
|
class IC7400MemFrame(MemFrame):
|
|
def get_obj(self):
|
|
self._data = MemoryMapBytes(bytes(self._data))
|
|
return bitwise.parse(MEM_IC7400_FORMAT, self._data)
|
|
|
|
|
|
class IC7300MemFrame(MemFrame):
|
|
FORMAT = MEM_IC7300_FORMAT
|
|
|
|
|
|
class IC7610MemFrame(MemFrame):
|
|
FORMAT = MEM_IC7610_FORMAT
|
|
|
|
|
|
class IC9700MemFrame(BankMemFrame):
|
|
FORMAT = MEM_IC9700_FORMAT
|
|
|
|
|
|
class IC9700SatMemFrame(MemFrame):
|
|
_sub = 0x07
|
|
FORMAT = MEM_IC9700_SAT_FORMAT
|
|
|
|
|
|
class SpecialChannel(object):
|
|
"""Info for special (named) channels"""
|
|
|
|
def __init__(self):
|
|
self.name = None
|
|
self.location = None
|
|
self.channel = None
|
|
|
|
def __repr__(self):
|
|
s = "SpecialChannel(name=%r, location=%r, channel=%r)"
|
|
return s % (self.name, self.location, self.channel)
|
|
|
|
|
|
class BankSpecialChannel(SpecialChannel):
|
|
"""Info for special (named) channels for radios with multiple banks"""
|
|
|
|
def __init__(self):
|
|
super(BankSpecialChannel, self).__init__()
|
|
self.bank = None
|
|
|
|
def __repr__(self):
|
|
s = "BankSpecialChannel(name=%r, location=%r, bank=%r, channel=%r)"
|
|
return s % (self.name, self.location, self.bank, self.channel)
|
|
|
|
|
|
class IcomCIVRadio(icf.IcomLiveRadio):
|
|
"""Base class for ICOM CIV-based radios"""
|
|
BAUD_RATE = 19200
|
|
MODEL = "CIV Radio"
|
|
# RTS is interpreted as "transmit now" on some interface boxes for these
|
|
WANTS_RTS = False
|
|
_model = "\x00"
|
|
_template = 0
|
|
|
|
# complete list of modes from CI-V documentation
|
|
# each radio supports a subset
|
|
# WARNING: "S-AM" and "PSK" are not valid (yet) for chirp
|
|
_MODES = [
|
|
"LSB", "USB", "AM", "CW", "RTTY", "FM", "WFM", "CWR",
|
|
"RTTYR", "S-AM", "PSK", None, None, None, None, None,
|
|
None, None, None, None, None, None, None, None,
|
|
"DV",
|
|
]
|
|
|
|
# Unified modes where mode and filter are combined. See note at
|
|
# _unified_modes.
|
|
_UNIFIED_MODES = {
|
|
'FM': 'NFM',
|
|
'CW': 'NCW'
|
|
}
|
|
|
|
def mem_to_ch_bnk(self, mem):
|
|
if self._adjust_bank_loc_start:
|
|
mem -= 1
|
|
l, h = self._bank_index_bounds
|
|
bank_no = (mem // (h - l + 1)) + l
|
|
channel = mem % (h - l + 1) + l
|
|
return (channel, bank_no)
|
|
|
|
def _is_special(self, number):
|
|
return False
|
|
|
|
def _get_special_info(self, number):
|
|
raise errors.RadioError("Radio does not support special channels")
|
|
|
|
def _send_frame(self, frame):
|
|
return frame.send(ord(self._model), 0xE0, self.pipe,
|
|
willecho=self._willecho)
|
|
|
|
def _recv_frame(self, frame=None):
|
|
if not frame:
|
|
frame = Frame()
|
|
frame.read(self.pipe)
|
|
return frame
|
|
|
|
def _initialize(self):
|
|
pass
|
|
|
|
def _detect_echo(self):
|
|
echo_test = b"\xfe\xfe\xe0\xe0\xfa\xfd"
|
|
self.pipe.write(echo_test)
|
|
resp = self.pipe.read(6)
|
|
LOG.debug("Echo:\n%s" % util.hexprint(bytes(resp)))
|
|
return resp == echo_test
|
|
|
|
def _detect_baudrate(self):
|
|
if self._baud_detected:
|
|
return
|
|
|
|
# Don't ever try to run this twice, even if we fail
|
|
self._baud_detected = True
|
|
|
|
bauds = [9600, 19200, 38400, 57600, 115200, 4800]
|
|
bauds.remove(self.BAUD_RATE)
|
|
bauds.insert(0, self.BAUD_RATE)
|
|
self.pipe.timeout = 0.25
|
|
for baud in bauds:
|
|
LOG.debug('Trying %i baud' % baud)
|
|
self.pipe.baudrate = baud
|
|
self._willecho = self._detect_echo()
|
|
LOG.debug("Interface echo: %s" % self._willecho)
|
|
try:
|
|
self._get_template_memory()
|
|
LOG.info('Detected %i baud' % baud)
|
|
break
|
|
except errors.RadioError:
|
|
pass
|
|
else:
|
|
LOG.warning('Unable to detect baudrate, using default of %i' % (
|
|
self.BAUD_RATE))
|
|
self.pipe.baudrate = self.BAUD_RATE
|
|
|
|
# Restore the historical default of 1s timeout for this driver
|
|
self.pipe.timeout = 1
|
|
|
|
def __init__(self, *args, **kwargs):
|
|
icf.IcomLiveRadio.__init__(self, *args, **kwargs)
|
|
|
|
self._classes = {
|
|
"mem": MemFrame,
|
|
}
|
|
|
|
if self.pipe:
|
|
self._willecho = self._detect_echo()
|
|
LOG.debug("Interface echo: %s" % self._willecho)
|
|
self.pipe.timeout = 1
|
|
else:
|
|
self._willecho = False
|
|
|
|
self._baud_detected = False
|
|
|
|
# f = Frame()
|
|
# f.set_command(0x19, 0x00)
|
|
# self._send_frame(f)
|
|
#
|
|
# res = f.read(self.pipe)
|
|
# if res:
|
|
# LOG.debug("Result: %x->%x (%i)" %
|
|
# (res[0], res[1], len(f.get_data())))
|
|
# LOG.debug(util.hexprint(f.get_data()))
|
|
#
|
|
# self._id = f.get_data()[0]
|
|
self._rf = chirp_common.RadioFeatures()
|
|
|
|
# On some radios, the filter field is used to signify normal versus
|
|
# narrow modes, rather than being a distinct passband feature. As
|
|
# such, mode + filter comprises a "unified mode" value that can be
|
|
# mapped into a Chirp mode.
|
|
self._unified_modes = False
|
|
|
|
# Icom live radios with bank support present their memories to the
|
|
# user starting from 1. For some reason, IC-7000 and IC-7100 were
|
|
# implemented with the Chirp location starting from 0, so that the
|
|
# user must mentally adjust. While adding IC-910 support, allowance
|
|
# was made to provide a 1-based start, using the following setting.
|
|
# This is not currently applied to the IC-7000 or IC-7100 due to the
|
|
# inability to test, and also since changing it may cause issues if
|
|
# location limit keys have been saved in the user's config file.
|
|
self._adjust_bank_loc_start = False
|
|
|
|
self._initialize()
|
|
|
|
def get_features(self):
|
|
return self._rf
|
|
|
|
def _get_template_memory(self):
|
|
f = self._classes["mem"]()
|
|
f.set_location(self._template)
|
|
self._send_frame(f)
|
|
f.read(self.pipe)
|
|
return f
|
|
|
|
def get_raw_memory(self, number):
|
|
self._detect_baudrate()
|
|
LOG.debug("Getting %s (raw)" % number)
|
|
f = self._classes["mem"]()
|
|
if self._is_special(number):
|
|
info = self._get_special_info(number)
|
|
LOG.debug("Special info: %s" % info)
|
|
ch = info.channel
|
|
if self._rf.has_bank:
|
|
bnk = info.bank
|
|
elif self._rf.has_bank:
|
|
ch, bnk = self.mem_to_ch_bnk(number)
|
|
else:
|
|
ch = number
|
|
if self._rf.has_bank:
|
|
f.set_location(ch, bnk)
|
|
loc = "bank %i, channel %02i" % (bnk, ch)
|
|
else:
|
|
f.set_location(ch)
|
|
loc = "number %i" % ch
|
|
self._send_frame(f)
|
|
f.read(self.pipe)
|
|
if f.get_data() and f.get_data()[-1] == "\xFF":
|
|
return "Memory " + loc + " empty."
|
|
else:
|
|
return repr(f.get_obj())
|
|
|
|
# We have a simple mapping between the memory location in the frequency
|
|
# editor and (bank, channel) of the radio. The mapping doesn't
|
|
# change so we use a little math to calculate what bank a location
|
|
# is in. We can't change the bank a location is in so we just pass.
|
|
def _get_bank(self, loc):
|
|
if self._adjust_bank_loc_start:
|
|
loc -= 1
|
|
l, h = self._bank_index_bounds
|
|
return loc // (h - l + 1)
|
|
|
|
def _set_bank(self, loc, bank):
|
|
pass
|
|
|
|
def get_memory(self, number):
|
|
self._detect_baudrate()
|
|
LOG.debug("Getting %s" % number)
|
|
f = self._classes["mem"]()
|
|
mem = chirp_common.Memory()
|
|
if self._is_special(number):
|
|
info = self._get_special_info(number)
|
|
LOG.debug("Special info: %s" % info)
|
|
if self._rf.has_bank:
|
|
f.set_location(info.channel, info.bank)
|
|
else:
|
|
f.set_location(info.channel)
|
|
mem.number = info.location
|
|
mem.extd_number = info.name
|
|
mem.immutable = ["number", "extd_number"]
|
|
else:
|
|
if self._rf.has_bank:
|
|
ch, bnk = self.mem_to_ch_bnk(number)
|
|
f.set_location(ch, bnk)
|
|
LOG.debug("Bank %i, Channel %02i" % (bnk, ch))
|
|
else:
|
|
f.set_location(number)
|
|
mem.number = number
|
|
self._send_frame(f)
|
|
|
|
f = self._recv_frame(f)
|
|
if len(f.get_data()) == 0:
|
|
raise errors.RadioError("Radio reported error")
|
|
if f.get_data() and f.get_data()[-1] == 0xFF:
|
|
mem.empty = True
|
|
LOG.debug("Found %i empty" % mem.number)
|
|
return mem
|
|
|
|
memobj = f.get_obj()
|
|
LOG.debug(repr(memobj))
|
|
|
|
try:
|
|
if memobj.skip == 1:
|
|
mem.skip = ""
|
|
else:
|
|
mem.skip = "S"
|
|
except AttributeError:
|
|
pass
|
|
|
|
mem.freq = int(memobj.freq)
|
|
try:
|
|
# Note that memobj.mode could be a bcd on some radios, so we must
|
|
# coerce it to an int for the index operation.
|
|
mem.mode = self._MODES[int(memobj.mode)]
|
|
|
|
# We do not know what a variety of the positions between
|
|
# PSK and DV mean, so let's behave as if those values
|
|
# are not set to maintain consistency between known-unknown
|
|
# values and unknown-unknown ones.
|
|
if mem.mode is None:
|
|
raise IndexError(memobj.mode)
|
|
except IndexError:
|
|
LOG.error(
|
|
"Bank %s location %s is set for mode %s, but no known "
|
|
"mode matches that value.",
|
|
int(memobj.bank),
|
|
int(memobj.number),
|
|
repr(memobj.mode),
|
|
)
|
|
raise
|
|
if self._unified_modes and memobj.filter == 2:
|
|
try:
|
|
# Adjust mode to its narrow variant
|
|
mem.mode = self._UNIFIED_MODES[mem.mode]
|
|
except KeyError:
|
|
LOG.error(
|
|
"Bank %s location %s is set for mode %s with filter %s, "
|
|
"but no known mode matches that combination.",
|
|
int(memobj.bank),
|
|
int(memobj.number),
|
|
repr(memobj.mode),
|
|
int(memobj.filter),
|
|
)
|
|
raise
|
|
|
|
if self._rf.has_name:
|
|
mem.name = str(memobj.name).rstrip()
|
|
|
|
if self._rf.valid_tmodes:
|
|
mem.tmode = self._rf.valid_tmodes[memobj.tmode]
|
|
|
|
if self._rf.has_dtcs_polarity:
|
|
if memobj.dtcs_polarity == 0x11:
|
|
mem.dtcs_polarity = "RR"
|
|
elif memobj.dtcs_polarity == 0x10:
|
|
mem.dtcs_polarity = "RN"
|
|
elif memobj.dtcs_polarity == 0x01:
|
|
mem.dtcs_polarity = "NR"
|
|
else:
|
|
mem.dtcs_polarity = "NN"
|
|
|
|
if self._rf.has_dtcs:
|
|
mem.dtcs = bitwise.bcd_to_int(memobj.dtcs)
|
|
|
|
if "Tone" in self._rf.valid_tmodes:
|
|
mem.rtone = int(memobj.rtone) / 10.0
|
|
|
|
if "TSQL" in self._rf.valid_tmodes and self._rf.has_ctone:
|
|
mem.ctone = int(memobj.ctone) / 10.0
|
|
|
|
if self._rf.valid_duplexes:
|
|
mem.duplex = self._rf.valid_duplexes[memobj.duplex]
|
|
|
|
if self._rf.can_odd_split and memobj.spl:
|
|
if hasattr(memobj, "duplex"):
|
|
mem.duplex = "split"
|
|
mem.offset = int(memobj.freq_tx)
|
|
mem.immutable = []
|
|
elif hasattr(memobj, "duplexOffset"):
|
|
mem.offset = int(memobj.duplexOffset) * 100
|
|
else:
|
|
mem.immutable = ["offset"]
|
|
|
|
try:
|
|
dig = RadioSetting("dig", "Digital",
|
|
RadioSettingValueBoolean(bool(memobj.dig)))
|
|
except AttributeError:
|
|
pass
|
|
else:
|
|
dig.set_doc("Enable digital mode")
|
|
if not mem.extra:
|
|
mem.extra = RadioSettingGroup("extra", "Extra")
|
|
mem.extra.append(dig)
|
|
|
|
if not self._unified_modes:
|
|
options = ["Wide", "Mid", "Narrow"]
|
|
try:
|
|
fil = RadioSetting(
|
|
"filter", "Filter",
|
|
RadioSettingValueList(options,
|
|
current_index=memobj.filter - 1))
|
|
except AttributeError:
|
|
pass
|
|
else:
|
|
fil.set_doc("Filter settings")
|
|
if not mem.extra:
|
|
mem.extra = RadioSettingGroup("extra", "Extra")
|
|
mem.extra.append(fil)
|
|
|
|
return mem
|
|
|
|
def set_memory(self, mem):
|
|
self._detect_baudrate()
|
|
LOG.debug("Setting %s(%s)" % (mem.number, mem.extd_number))
|
|
f = self._get_template_memory()
|
|
if self._is_special(mem.number):
|
|
info = self._get_special_info(mem.number)
|
|
LOG.debug("Special info: %s" % info)
|
|
ch = info.channel
|
|
if self._rf.has_bank:
|
|
bnk = info.bank
|
|
elif self._rf.has_bank:
|
|
ch, bnk = self.mem_to_ch_bnk(mem.number)
|
|
LOG.debug("Bank %i, Channel %02i" % (bnk, ch))
|
|
else:
|
|
ch = mem.number
|
|
if mem.empty:
|
|
if self._rf.has_bank:
|
|
f.set_location(ch, bnk)
|
|
else:
|
|
f.set_location(ch)
|
|
LOG.debug("Making %i empty" % mem.number)
|
|
f.make_empty()
|
|
self._send_frame(f)
|
|
|
|
# The next two lines accept the radio's status after setting the memory
|
|
# and reports the results to the debug log. This is needed for the
|
|
# IC-7000. No testing was done to see if it breaks memory delete on the
|
|
# IC-746 or IC-7200.
|
|
f = self._recv_frame()
|
|
LOG.debug("Result:\n%s" % util.hexprint(bytes(f.get_data())))
|
|
return
|
|
|
|
# f.set_data(MemoryMap(self.get_raw_memory(mem.number)))
|
|
# f.initialize()
|
|
|
|
memobj = f.get_obj()
|
|
if self._rf.has_bank:
|
|
memobj.bank = bnk
|
|
memobj.number = ch
|
|
else:
|
|
memobj.number = ch
|
|
if mem.skip == "S":
|
|
memobj.skip = 0
|
|
else:
|
|
try:
|
|
memobj.skip = 1
|
|
except KeyError:
|
|
pass
|
|
memobj.freq = int(mem.freq)
|
|
mode = mem.mode
|
|
if self._unified_modes:
|
|
lookup = [
|
|
k for k, v in self._UNIFIED_MODES.items() if v == mode]
|
|
if lookup:
|
|
mode = lookup[0]
|
|
memobj.filter = 2
|
|
else:
|
|
memobj.filter = 1
|
|
memobj.mode = self._MODES.index(mode)
|
|
if self._rf.has_name:
|
|
name_length = len(memobj.name.get_value())
|
|
memobj.name = mem.name.ljust(name_length)[:name_length]
|
|
|
|
if self._rf.valid_tmodes:
|
|
memobj.tmode = self._rf.valid_tmodes.index(mem.tmode)
|
|
|
|
if self._rf.has_ctone:
|
|
memobj.ctone = int(mem.ctone * 10)
|
|
memobj.rtone = int(mem.rtone * 10)
|
|
|
|
if self._rf.has_dtcs_polarity:
|
|
if mem.dtcs_polarity == "RR":
|
|
memobj.dtcs_polarity = 0x11
|
|
elif mem.dtcs_polarity == "RN":
|
|
memobj.dtcs_polarity = 0x10
|
|
elif mem.dtcs_polarity == "NR":
|
|
memobj.dtcs_polarity = 0x01
|
|
else:
|
|
memobj.dtcs_polarity = 0x00
|
|
|
|
if self._rf.has_dtcs:
|
|
bitwise.int_to_bcd(memobj.dtcs, mem.dtcs)
|
|
|
|
if self._rf.can_odd_split and mem.duplex == "split":
|
|
memobj.spl = 1
|
|
if hasattr(memobj, "duplex"):
|
|
memobj.duplex = 0
|
|
memobj.freq_tx = int(mem.offset)
|
|
memobj.tmode_tx = memobj.tmode
|
|
memobj.ctone_tx = memobj.ctone
|
|
memobj.rtone_tx = memobj.rtone
|
|
if self._rf.has_dtcs:
|
|
memobj.dtcs_polarity_tx = memobj.dtcs_polarity
|
|
memobj.dtcs_tx = memobj.dtcs
|
|
elif self._rf.valid_duplexes:
|
|
memobj.duplex = self._rf.valid_duplexes.index(mem.duplex)
|
|
if hasattr(memobj, "duplexOffset"):
|
|
memobj.duplexOffset = int(mem.offset) // 100
|
|
|
|
for setting in mem.extra:
|
|
if setting.get_name() == "filter":
|
|
setattr(memobj, setting.get_name(), int(setting.value) + 1)
|
|
else:
|
|
setattr(memobj, setting.get_name(), setting.value)
|
|
|
|
LOG.debug(repr(memobj))
|
|
self._send_frame(f)
|
|
|
|
f = self._recv_frame()
|
|
LOG.debug("Result:\n%s" % util.hexprint(bytes(f.get_data())))
|
|
|
|
|
|
@directory.register
|
|
class Icom7200Radio(IcomCIVRadio):
|
|
"""Icom IC-7200"""
|
|
MODEL = "IC-7200"
|
|
_model = "\x76"
|
|
_template = 201
|
|
|
|
_num_banks = 1 # Banks not supported
|
|
|
|
def _initialize(self):
|
|
self._rf.has_bank = False
|
|
self._rf.has_dtcs_polarity = False
|
|
self._rf.has_dtcs = False
|
|
self._rf.has_ctone = False
|
|
self._rf.has_offset = False
|
|
self._rf.has_name = False
|
|
self._rf.has_tuning_step = False
|
|
self._rf.valid_modes = ["LSB", "USB", "AM", "CW", "RTTY",
|
|
"CWR", "RTTYR"]
|
|
self._rf.valid_tmodes = []
|
|
self._rf.valid_duplexes = []
|
|
self._rf.valid_bands = [(30000, 60000000)]
|
|
self._rf.valid_skips = []
|
|
self._rf.memory_bounds = (1, 201)
|
|
|
|
|
|
@directory.register
|
|
class Icom7000Radio(IcomCIVRadio):
|
|
"""Icom IC-7000"""
|
|
MODEL = "IC-7000"
|
|
_model = "\x70"
|
|
_template = 102
|
|
|
|
_num_banks = 5 # Banks A-E
|
|
_bank_index_bounds = (1, 99)
|
|
_bank_class = icf.IcomBank
|
|
|
|
def _initialize(self):
|
|
self._classes["mem"] = BankMemFrame
|
|
self._rf.has_bank = True
|
|
self._rf.has_dtcs_polarity = True
|
|
self._rf.has_dtcs = True
|
|
self._rf.has_ctone = True
|
|
self._rf.has_offset = True
|
|
self._rf.has_name = True
|
|
self._rf.has_tuning_step = False
|
|
self._rf.valid_modes = ["LSB", "USB", "AM", "CW", "RTTY", "FM", "WFM"]
|
|
self._rf.valid_tmodes = ["", "Tone", "TSQL", "DTCS"]
|
|
self._rf.valid_duplexes = ["", "-", "+", "split"]
|
|
self._rf.valid_bands = [(30000, 199999999), (400000000, 470000000)]
|
|
self._rf.valid_tuning_steps = []
|
|
self._rf.valid_skips = ["S", ""]
|
|
self._rf.valid_name_length = 9
|
|
self._rf.valid_characters = chirp_common.CHARSET_ASCII
|
|
self._rf.memory_bounds = (0, 99 * self._num_banks - 1)
|
|
self._rf.can_odd_split = True
|
|
|
|
|
|
@directory.register
|
|
class Icom7100Radio(IcomCIVRadio):
|
|
"""Icom IC-7100"""
|
|
MODEL = "IC-7100"
|
|
_model = "\x88"
|
|
_template = 102
|
|
|
|
_num_banks = 5
|
|
_bank_index_bounds = (1, 99)
|
|
_bank_class = icf.IcomBank
|
|
|
|
def _initialize(self):
|
|
self._classes["mem"] = IC7100MemFrame
|
|
self._rf.has_bank = True
|
|
self._rf.has_bank_index = False
|
|
self._rf.has_bank_names = False
|
|
self._rf.has_dtcs_polarity = False
|
|
self._rf.has_dtcs = False
|
|
self._rf.has_ctone = True
|
|
self._rf.has_offset = False
|
|
self._rf.has_name = True
|
|
self._rf.has_tuning_step = False
|
|
self._rf.valid_modes = [
|
|
"LSB", "USB", "AM", "CW", "RTTY", "FM", "WFM", "CWR", "RTTYR", "DV"
|
|
]
|
|
self._rf.valid_tmodes = ["", "Tone", "TSQL", "DTCS"]
|
|
self._rf.valid_duplexes = ["", "-", "+"]
|
|
self._rf.valid_bands = [(30000, 199999999), (400000000, 470000000)]
|
|
self._rf.valid_tuning_steps = []
|
|
self._rf.valid_skips = []
|
|
self._rf.valid_name_length = 16
|
|
self._rf.valid_characters = chirp_common.CHARSET_ASCII
|
|
self._rf.memory_bounds = (1, 99 * self._num_banks - 1)
|
|
self._adjust_bank_loc_start = True
|
|
|
|
|
|
@directory.register
|
|
class Icom746Radio(IcomCIVRadio):
|
|
"""Icom IC-746"""
|
|
MODEL = "IC-746"
|
|
BAUD_RATE = 9600
|
|
_model = "\x56"
|
|
_template = 102
|
|
|
|
_num_banks = 1 # Banks not supported
|
|
|
|
def _initialize(self):
|
|
self._classes["mem"] = DupToneMemFrame
|
|
self._rf.has_bank = False
|
|
self._rf.has_dtcs_polarity = False
|
|
self._rf.has_dtcs = False
|
|
self._rf.has_ctone = True
|
|
self._rf.has_offset = False
|
|
self._rf.has_name = True
|
|
self._rf.has_tuning_step = False
|
|
self._rf.valid_modes = ["LSB", "USB", "AM", "CW", "RTTY", "FM"]
|
|
self._rf.valid_tmodes = ["", "Tone", "TSQL"]
|
|
self._rf.valid_duplexes = ["", "-", "+"]
|
|
self._rf.valid_bands = [(30000, 199999999)]
|
|
self._rf.valid_tuning_steps = []
|
|
self._rf.valid_skips = []
|
|
self._rf.valid_name_length = 9
|
|
self._rf.valid_characters = chirp_common.CHARSET_ASCII
|
|
self._rf.memory_bounds = (1, 99)
|
|
|
|
|
|
@directory.register
|
|
class Icom7400Radio(IcomCIVRadio):
|
|
"""Icom IC-7400"""
|
|
MODEL = "IC-7400"
|
|
BAUD_RATE = 9600
|
|
_model = "\x66"
|
|
_template = 102
|
|
|
|
_num_banks = 1 # Banks not supported
|
|
|
|
def _initialize(self):
|
|
self._classes["mem"] = IC7400MemFrame
|
|
self._rf.has_bank = False
|
|
self._rf.has_dtcs_polarity = True
|
|
self._rf.has_dtcs = True
|
|
self._rf.has_ctone = True
|
|
self._rf.has_offset = False
|
|
self._rf.has_name = True
|
|
self._rf.has_tuning_step = False
|
|
self._rf.valid_modes = [
|
|
"LSB", "USB", "AM", "CW", "RTTY", "FM", "USB", "CWR", "RTTYR"
|
|
]
|
|
self._rf.valid_tmodes = ["", "Tone", "TSQL", "DTCS"]
|
|
self._rf.valid_duplexes = ["", "-", "+"]
|
|
self._rf.valid_bands = [(30000, 174000000)]
|
|
self._rf.valid_tuning_steps = []
|
|
self._rf.valid_skips = []
|
|
self._rf.valid_name_length = 8
|
|
self._rf.valid_characters = " !#$%&'()*+,-./" \
|
|
"0123456789" \
|
|
":;<=>?" \
|
|
"ABCDEFGHIJKLMNOPQRSTUVWXYZ" \
|
|
"[\\]^_" \
|
|
"abcdefghijklmnopqrstuvwxyz" \
|
|
"{|}~"
|
|
self._rf.memory_bounds = (1, 99)
|
|
|
|
|
|
@directory.register
|
|
class Icom910Radio(IcomCIVRadio):
|
|
"""Icom IC-910"""
|
|
MODEL = "IC-910"
|
|
BAUD_RATE = 19200
|
|
_model = "\x60"
|
|
_template = 100
|
|
|
|
_num_banks = 3 # Banks for 2m, 70cm, 23cm
|
|
_bank_index_bounds = (1, 99)
|
|
_bank_class = icf.IcomBank
|
|
|
|
_SPECIAL_CHANNELS = {
|
|
"1A": 100,
|
|
"1b": 101,
|
|
"2A": 102,
|
|
"2b": 103,
|
|
"3A": 104,
|
|
"3b": 105,
|
|
"C": 106,
|
|
}
|
|
_SPECIAL_CHANNELS_REV = {v: k for k, v in _SPECIAL_CHANNELS.items()}
|
|
|
|
_SPECIAL_BANKS = {
|
|
"2m": 1,
|
|
"70cm": 2,
|
|
"23cm": 3,
|
|
}
|
|
_SPECIAL_BANKS_REV = {v: k for k, v in _SPECIAL_BANKS.items()}
|
|
|
|
def _get_special_names(self, band):
|
|
return sorted([band + "-" + key
|
|
for key in self._SPECIAL_CHANNELS.keys()])
|
|
|
|
def _is_special(self, number):
|
|
return isinstance(number, str) or number >= 1000
|
|
|
|
def _get_special_info(self, number):
|
|
info = BankSpecialChannel()
|
|
if isinstance(number, str):
|
|
info.name = number
|
|
(band_name, chan_name) = number.split("-")
|
|
info.bank = self._SPECIAL_BANKS[band_name]
|
|
info.channel = self._SPECIAL_CHANNELS[chan_name]
|
|
info.location = info.bank * 1000 + info.channel
|
|
else:
|
|
info.location = number
|
|
(info.bank, info.channel) = divmod(number, 1000)
|
|
band_name = self._SPECIAL_BANKS_REV[info.bank]
|
|
chan_name = self._SPECIAL_CHANNELS_REV[info.channel]
|
|
info.name = band_name + "-" + chan_name
|
|
return info
|
|
|
|
# The IC-910 has a bank of memories for each band. The 23cm band is only
|
|
# available when the optional UX-910 unit is installed, but there is no
|
|
# direct means of detecting its presence. Instead, attempt to access the
|
|
# first memory in the 23cm bank. If that's successful, the unit is there,
|
|
# and we can present all 3 banks to the user. Otherwise, the unit is not
|
|
# installed, so we present 2 banks to the user, for 2m and 70cm.
|
|
def _detect_23cm_unit(self):
|
|
if not self.pipe:
|
|
return True
|
|
self._detect_baudrate()
|
|
f = IC910MemFrame()
|
|
f.set_location(1, 3) # First memory in 23cm bank
|
|
self._send_frame(f)
|
|
f.read(self.pipe)
|
|
if f._cmd == 0xFA: # Error code lands in command field
|
|
self._num_banks = 2
|
|
LOG.debug("UX-910 unit is %sinstalled" %
|
|
("not " if self._num_banks == 2 else ""))
|
|
return self._num_banks == 3
|
|
|
|
def _initialize(self):
|
|
self._classes["mem"] = IC910MemFrame
|
|
self._has_23cm_unit = self._detect_23cm_unit()
|
|
self._rf.has_bank = True
|
|
self._rf.has_dtcs_polarity = False
|
|
self._rf.has_dtcs = False
|
|
self._rf.has_ctone = True
|
|
self._rf.has_offset = True
|
|
self._rf.has_name = False
|
|
self._rf.has_tuning_step = False
|
|
self._rf.valid_modes = ["LSB", "USB", "CW", "NCW", "FM", "NFM"]
|
|
self._rf.valid_tmodes = ["", "Tone", "TSQL"]
|
|
self._rf.valid_duplexes = ["", "-", "+"]
|
|
self._rf.valid_bands = [(136000000, 174000000),
|
|
(420000000, 480000000)]
|
|
self._rf.valid_tuning_steps = []
|
|
self._rf.valid_skips = []
|
|
self._rf.valid_special_chans = (self._get_special_names("2m") +
|
|
self._get_special_names("70cm"))
|
|
self._rf.memory_bounds = (1, 99 * self._num_banks)
|
|
|
|
if self._has_23cm_unit:
|
|
self._rf.valid_bands.append((1240000000, 1320000000))
|
|
self._rf.valid_special_chans += self._get_special_names("23cm")
|
|
|
|
# Combine mode and filter into unified mode
|
|
self._unified_modes = True
|
|
|
|
# Use Chirp locations starting with 1
|
|
self._adjust_bank_loc_start = True
|
|
|
|
|
|
@directory.register
|
|
class Icom7300Radio(IcomCIVRadio): # Added March, 2021 by Rick DeWitt
|
|
"""Icom IC-7300"""
|
|
MODEL = "IC-7300"
|
|
BAUD_RATE = 115200
|
|
_model = "\x94"
|
|
_template = 100 # Use P1 as blank template
|
|
|
|
_SPECIAL_CHANNELS = {
|
|
"P1": 100,
|
|
"P2": 101,
|
|
}
|
|
_SPECIAL_CHANNELS_REV = dict(zip(_SPECIAL_CHANNELS.values(),
|
|
_SPECIAL_CHANNELS.keys()))
|
|
|
|
def _is_special(self, number):
|
|
return isinstance(number, str) or number > 99
|
|
|
|
def _get_special_info(self, number):
|
|
info = SpecialChannel()
|
|
if isinstance(number, str):
|
|
info.name = number
|
|
info.channel = self._SPECIAL_CHANNELS[number]
|
|
info.location = info.channel
|
|
else:
|
|
info.location = number
|
|
info.name = self._SPECIAL_CHANNELS_REV[number]
|
|
info.channel = info.location
|
|
return info
|
|
|
|
def _initialize(self):
|
|
self._classes["mem"] = IC7300MemFrame
|
|
self._rf.has_name = True
|
|
self._rf.has_dtcs = False
|
|
self._rf.has_dtcs_polarity = False
|
|
self._rf.has_bank = False
|
|
self._rf.has_tuning_step = False
|
|
self._rf.has_nostep_tuning = True
|
|
self._rf.can_odd_split = True
|
|
self._rf.memory_bounds = (1, 99)
|
|
self._rf.valid_modes = [
|
|
"LSB", "USB", "AM", "CW", "RTTY", "FM", "CWR", "RTTYR",
|
|
"Data+LSB", "Data+USB", "Data+AM", "N/A", "N/A", "Data+FM"
|
|
]
|
|
self._rf.valid_tmodes = ["", "Tone", "TSQL"]
|
|
# self._rf.valid_duplexes = ["", "-", "+", "split"]
|
|
self._rf.valid_duplexes = [] # To prevent using memobj.duplex
|
|
self._rf.valid_bands = [(30000, 74800000)]
|
|
self._rf.valid_skips = []
|
|
self._rf.valid_name_length = 10
|
|
self._rf.valid_characters = chirp_common.CHARSET_ASCII
|
|
self._rf.valid_special_chans = sorted(self._SPECIAL_CHANNELS.keys())
|
|
|
|
|
|
@directory.register
|
|
class Icom7610Radio(Icom7300Radio):
|
|
MODEL = "IC-7610"
|
|
_model = '\x98'
|
|
|
|
def _initialize(self):
|
|
super()._initialize()
|
|
self._classes['mem'] = IC7610MemFrame
|
|
|
|
|
|
@directory.register
|
|
class Icom9700Radio(IcomCIVRadio):
|
|
MODEL = 'IC-9700'
|
|
_model = '\xA2'
|
|
_template = 100
|
|
BANDS = {
|
|
1: (144, 148),
|
|
2: (430, 450),
|
|
3: (1240, 1300),
|
|
}
|
|
_MODES = [
|
|
"LSB", "USB", "AM", "CW", "RTTY", "FM", "CWR",
|
|
"RTTY-R", None, None, None, None, None, None, None, None, None,
|
|
"DV", None, None, None, None, "DD", None, None, None, None, None,
|
|
]
|
|
|
|
def get_sub_devices(self):
|
|
return [Icom9700RadioBand(self, 1),
|
|
Icom9700RadioBand(self, 2),
|
|
Icom9700RadioBand(self, 3),
|
|
Icom9700SatelliteBand(self)]
|
|
|
|
def _initialize(self):
|
|
super()._initialize()
|
|
self._rf.has_sub_devices = True
|
|
self._rf.memory_bounds = (1, 99)
|
|
self._classes['mem'] = IC9700MemFrame
|
|
|
|
|
|
class Icom9700RadioBand(Icom9700Radio):
|
|
_SPECIAL_CHANNELS = {
|
|
"1A": 100,
|
|
"1B": 101,
|
|
"2A": 102,
|
|
"2B": 103,
|
|
"3A": 104,
|
|
"4B": 105,
|
|
"C1": 106,
|
|
"C2": 107,
|
|
}
|
|
_SPECIAL_CHANNELS_REV = dict(zip(_SPECIAL_CHANNELS.values(),
|
|
_SPECIAL_CHANNELS.keys()))
|
|
|
|
def _detect_echo(self):
|
|
self._parent._willecho
|
|
|
|
def _is_special(self, number):
|
|
return isinstance(number, str) or number > 99
|
|
|
|
def _get_special_info(self, number):
|
|
info = BankSpecialChannel()
|
|
info.bank = self._band
|
|
if isinstance(number, str):
|
|
info.name = number
|
|
info.channel = self._SPECIAL_CHANNELS[number]
|
|
info.location = info.channel
|
|
else:
|
|
info.location = number
|
|
info.name = self._SPECIAL_CHANNELS_REV[number]
|
|
info.channel = info.location
|
|
return info
|
|
|
|
def __init__(self, parent, band):
|
|
self._parent = parent
|
|
self._band = band
|
|
self.VARIANT = '%i band' % (self.BANDS[band][0])
|
|
super().__init__(parent.pipe)
|
|
|
|
def mem_to_ch_bnk(self, mem):
|
|
return mem, self._band
|
|
|
|
def _initialize(self):
|
|
super()._initialize()
|
|
self._rf.has_name = True
|
|
self._rf.valid_tmodes = ['', 'Tone', 'TSQL', 'DTCS']
|
|
self._rf.has_dtcs = True
|
|
self._rf.has_dtcs_polarity = True
|
|
self._rf.has_bank = True
|
|
self._rf.has_tuning_step = False
|
|
self._rf.has_nostep_tuning = True
|
|
self._rf.can_odd_split = False
|
|
self._rf.memory_bounds = (1, 99)
|
|
self._rf.valid_bands = [(x * 1000000, y * 1000000) for x, y in
|
|
[self.BANDS[self._band]]]
|
|
self._rf.valid_name_length = 16
|
|
self._rf.valid_characters = (chirp_common.CHARSET_ALPHANUMERIC +
|
|
'!#$%&\\?"\'`^+-*/.,:=<>()[]{}|_~@')
|
|
self._rf.valid_special_chans = sorted(self._SPECIAL_CHANNELS.keys())
|
|
# Last item is RPS for DD mode
|
|
self._rf.valid_duplexes = ['', '-', '+']
|
|
self._rf.valid_modes = [x for x in self._MODES if x]
|
|
if self._band != 3:
|
|
self._rf.valid_modes.remove('DD')
|
|
self._classes['mem'] = IC9700MemFrame
|
|
|
|
|
|
class Icom9700SatelliteBand(Icom9700Radio):
|
|
VARIANT = 'Satellite'
|
|
|
|
def __init__(self, parent):
|
|
self._parent = parent
|
|
super().__init__(parent.pipe)
|
|
|
|
def _detect_echo(self):
|
|
self._parent._willecho
|
|
|
|
def _initialize(self):
|
|
super()._initialize()
|
|
self._rf.has_name = True
|
|
self._rf.valid_tmodes = ['', 'Tone', 'TSQL', 'DTCS']
|
|
self._rf.has_dtcs = True
|
|
self._rf.has_dtcs_polarity = True
|
|
self._rf.has_bank = False
|
|
self._rf.has_tuning_step = False
|
|
self._rf.has_nostep_tuning = True
|
|
self._rf.can_odd_split = True
|
|
self._rf.memory_bounds = (1, 99)
|
|
self._rf.valid_bands = [(x * 1000000, y * 1000000) for x, y in
|
|
self.BANDS.values()]
|
|
self._rf.valid_name_length = 16
|
|
self._rf.valid_characters = (chirp_common.CHARSET_ALPHANUMERIC +
|
|
'!#$%&\\?"\'`^+-*/.,:=<>()[]{}|_~@')
|
|
# Last item is RPS for DD mode
|
|
self._rf.valid_duplexes = ['split']
|
|
self._rf.valid_modes = [x for x in self._MODES if x]
|
|
self._rf.valid_modes.remove('DD')
|
|
self._classes['mem'] = IC9700SatMemFrame
|
|
|
|
def _get_template_memory(self):
|
|
f = self._classes["mem"]()
|
|
f.initialize()
|
|
return f
|
|
|
|
def get_memory(self, number):
|
|
self._detect_baudrate()
|
|
LOG.debug("Getting %s" % number)
|
|
f = self._classes["mem"]()
|
|
mem = chirp_common.Memory()
|
|
f.set_location(number)
|
|
mem.number = number
|
|
self._send_frame(f)
|
|
|
|
f = self._recv_frame(f)
|
|
if len(f.get_data()) == 0:
|
|
raise errors.RadioError("Radio reported error")
|
|
if f.get_data() and f.get_data()[-1] == 0xFF:
|
|
mem.empty = True
|
|
mem.duplex = 'split'
|
|
LOG.debug("Found %i empty" % mem.number)
|
|
return mem
|
|
|
|
memobj = f.get_obj()
|
|
LOG.debug(repr(memobj))
|
|
|
|
mem.freq = int(memobj.freq)
|
|
mem.mode = self._MODES[int(memobj.mode)]
|
|
mem.name = str(memobj.name).rstrip()
|
|
mem.tmode = self._rf.valid_tmodes[memobj.tmode]
|
|
|
|
if memobj.dtcs_polarity == 0x11:
|
|
mem.dtcs_polarity = "RR"
|
|
elif memobj.dtcs_polarity == 0x10:
|
|
mem.dtcs_polarity = "RN"
|
|
elif memobj.dtcs_polarity == 0x01:
|
|
mem.dtcs_polarity = "NR"
|
|
else:
|
|
mem.dtcs_polarity = "NN"
|
|
|
|
mem.dtcs = bitwise.bcd_to_int(memobj.dtcs)
|
|
mem.rtone = int(memobj.rtone) / 10.0
|
|
mem.ctone = int(memobj.ctone) / 10.0
|
|
mem.duplex = 'split'
|
|
mem.offset = int(memobj.tx.freq)
|
|
mem.immutable = ["duplex"]
|
|
|
|
try:
|
|
dig = RadioSetting("dig", "Digital",
|
|
RadioSettingValueBoolean(bool(memobj.dig)))
|
|
except AttributeError:
|
|
pass
|
|
else:
|
|
dig.set_doc("Enable digital mode")
|
|
if not mem.extra:
|
|
mem.extra = RadioSettingGroup("extra", "Extra")
|
|
mem.extra.append(dig)
|
|
|
|
options = ["Wide", "Mid", "Narrow"]
|
|
fil = RadioSetting(
|
|
"filter", "Filter",
|
|
RadioSettingValueList(options,
|
|
current_index=memobj.filter - 1))
|
|
fil.set_doc("Filter settings")
|
|
if not mem.extra:
|
|
mem.extra = RadioSettingGroup("extra", "Extra")
|
|
mem.extra.append(fil)
|
|
|
|
return mem
|
|
|
|
def set_memory(self, mem):
|
|
self._detect_baudrate()
|
|
LOG.debug("Setting %s(%s)" % (mem.number, mem.extd_number))
|
|
f = self._get_template_memory()
|
|
ch = mem.number
|
|
if mem.empty:
|
|
LOG.debug("Making %i empty" % mem.number)
|
|
f.set_location(ch)
|
|
f.make_empty()
|
|
self._send_frame(f)
|
|
f = self._recv_frame()
|
|
LOG.debug("Result (%r):\n%s",
|
|
f._cmd, util.hexprint(bytes(f.get_data())))
|
|
return
|
|
|
|
memobj = f.get_obj()
|
|
memobj.number = ch
|
|
memobj.freq = int(mem.freq)
|
|
mode = mem.mode
|
|
memobj.mode = self._MODES.index(mode)
|
|
name_length = len(memobj.name.get_value())
|
|
memobj.name = mem.name.ljust(name_length)[:name_length]
|
|
memobj.tmode = self._rf.valid_tmodes.index(mem.tmode)
|
|
memobj.ctone = int(mem.ctone * 10)
|
|
memobj.rtone = int(mem.rtone * 10)
|
|
|
|
if mem.dtcs_polarity == "RR":
|
|
memobj.dtcs_polarity = 0x11
|
|
elif mem.dtcs_polarity == "RN":
|
|
memobj.dtcs_polarity = 0x10
|
|
elif mem.dtcs_polarity == "NR":
|
|
memobj.dtcs_polarity = 0x01
|
|
else:
|
|
memobj.dtcs_polarity = 0x00
|
|
|
|
bitwise.int_to_bcd(memobj.dtcs, mem.dtcs)
|
|
|
|
memobj.tx.freq = int(mem.offset)
|
|
memobj.tx.tmode = memobj.tmode
|
|
memobj.tx.ctone = memobj.ctone
|
|
memobj.tx.rtone = memobj.rtone
|
|
memobj.tx.dtcs_polarity = memobj.dtcs_polarity
|
|
memobj.tx.dtcs = memobj.dtcs
|
|
|
|
memobj.urcall = memobj.rpt1call = memobj.rpt2call = ' ' * 8
|
|
memobj.tx.urcall = memobj.tx.rpt1call = memobj.tx.rpt2call = ' ' * 8
|
|
memobj.filter = memobj.tx.filter = 1
|
|
|
|
for setting in mem.extra:
|
|
if setting.get_name() == "filter":
|
|
setattr(memobj, setting.get_name(), int(setting.value) + 1)
|
|
else:
|
|
setattr(memobj, setting.get_name(), setting.value)
|
|
|
|
LOG.debug(repr(memobj))
|
|
self._send_frame(f)
|
|
|
|
f = self._recv_frame()
|
|
LOG.debug("Result (%r):\n%s",
|
|
f._cmd, util.hexprint(bytes(f.get_data())))
|
|
if f._cmd == 0xFA:
|
|
LOG.error('Radio refused memory')
|
|
|
|
|
|
def probe_model(ser):
|
|
"""Probe the radio attached to @ser for its model"""
|
|
f = Frame()
|
|
f.set_command(0x19, 0x00)
|
|
|
|
models = {}
|
|
for rclass in directory.DRV_TO_RADIO.values():
|
|
if issubclass(rclass, IcomCIVRadio):
|
|
models[rclass.MODEL] = rclass
|
|
|
|
for rclass in models.values():
|
|
model = ord(rclass._model)
|
|
f.send(model, 0xE0, ser)
|
|
try:
|
|
f.read(ser)
|
|
except errors.RadioError:
|
|
continue
|
|
|
|
if len(f.get_data()) == 1:
|
|
md = f.get_data()[0]
|
|
if (md == model):
|
|
return rclass
|
|
|
|
if f.get_data():
|
|
LOG.debug("Got data, but not 1 byte:")
|
|
LOG.debug(util.hexprint(bytes(f.get_data())))
|
|
raise errors.RadioError("Unknown response")
|
|
|
|
raise errors.RadioError("Unsupported model")
|