Project

General

Profile

New Model #1003 » icomciv.py

icomciv.py with partial IC-7100 support - Nick Partofthething, 07/12/2015 06:12 PM

 

import struct
import logging
from chirp.drivers import icf
from chirp import chirp_common, util, errors, bitwise, directory
from chirp.memmap import MemoryMap
from chirp.drivers.icf import IcomBankModel

LOG = logging.getLogger(__name__)

MEM_FORMAT = """
bbcd number[2];
u8 unknown1;
lbcd freq[5];
u8 unknown2:5,
mode:3;
"""
MEM_VFO_FORMAT = """
u8 vfo;
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 unknown6[2];
bbcd dtcs;
u8 unknown[17];
char name[9];
"""
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 unknown6[2];
bbcd dtcs;
u8 unknown[11];
char name[9];
"""

MEM_IC7100_FORMAT = """
u8 bank; // 1 bank number
bbcd number[2]; // 2,3
u8 splitSelect; // 4 split and select memory settings
lbcd freq[5]; // 5-9 operating freq
u8 mode; // 10 operating mode
u8 filter; // 11 filter
u8 dataMode; // 12 data mode setting (on or off)
u8 duplex:4, // 13 duplex on/-/+
tmode:4; // 13 tone
u8 dsql:4, // 14 digital squelch
unknown1:4; // 14 zero
bbcd rtone[3]; // 15-17 repeater tone freq
bbcd ctone[3]; // 18-20 tone squelch setting
u8 dtcsPolarity; // 21 DTCS polarity
u8 unknown2:4, // 22 zero
firstDtcs:4; // 22 first digit of DTCS code
u8 secondDtcs:4, // 23 second digit DTCS
thirdDtcs:4; // 23 third digit DTCS
u8 digitalSquelch; // 24 Digital code squelch setting
u8 duplexOffset[3]; // 25-27 duplex offset freq
char destCall[8]; // 28-35 destination call sign
char accessRepeaterCall[8];// 36-43 access repeater call sign
char linkRepeaterCall[8]; // 44-51 gateway/link repeater call sign
bbcd duplexSettings[47]; // repeat of 5-51 for duplex
char name[16]; // 52-60 Name of station
"""


class Frame:
"""Base class for an ICF frame"""
_cmd = 0x00
_sub = 0x00

def __init__(self):
self._data = ""

def set_command(self, cmd, sub):
"""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"""
raw = struct.pack("BBBBBB", 0xFE, 0xFE, src, dst, self._cmd, self._sub)
raw += str(self._data) + chr(0xFD)

LOG.debug("%02x -> %02x (%i):\n%s" %
(src, dst, len(raw), util.hexprint(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(raw))
LOG.debug(util.hexprint(echo))

def read(self, serial):
"""Read the frame from @serial"""
data = ""
while not data.endswith(chr(0xFD)):
char = serial.read(1)
if not char:
LOG.debug("Read %i bytes total" % len(data))
raise errors.RadioError("Timeout")
data += char

if data == chr(0xFD):
raise errors.RadioError("Radio reported error")

src, dst = struct.unpack("BB", data[2:4])
LOG.debug("%02x <- %02x:\n%s" % (src, dst, util.hexprint(data)))

self._cmd = ord(data[4])
self._sub = ord(data[5])
self._data = data[6:-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

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"""
self._data = MemoryMap(str(self._data)) # Make sure we're assignable
return bitwise.parse(MEM_FORMAT, self._data)

def initialize(self):
"""Initialize to sane values"""
self._data = MemoryMap("".join(["\x00"] * (self.get_obj().size() / 8)))


class MultiVFOMemFrame(MemFrame):
"""A memory frame for radios with multiple VFOs"""
def set_location(self, loc, vfo=1):
self._loc = loc
self._data = struct.pack(">BH", vfo, int("%04i" % loc, 16))

def get_obj(self):
self._data = MemoryMap(str(self._data)) # Make sure we're assignable
return bitwise.parse(MEM_VFO_FORMAT, self._data)

class IC7100MemFrame(MultiVFOMemFrame):
"""A memory frame for IC-7100"""

def get_obj(self):
self._data = MemoryMap(str(self._data)) # Make sure we're assignable
return bitwise.parse(MEM_IC7100_FORMAT, self._data)


class DupToneMemFrame(MemFrame):
def get_obj(self):
self._data = MemoryMap(str(self._data))
return bitwise.parse(mem_duptone_format, self._data)


class IcomCIVRadio(icf.IcomLiveRadio):
"""Base class for ICOM CIV-based radios"""
BAUD_RATE = 19200
MODEL = "CIV Radio"
_model = "\x00"
_template = 0

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 = "\xfe\xfe\xe0\xe0\xfa\xfd"
self.pipe.write(echo_test)
resp = self.pipe.read(6)
LOG.debug("Echo:\n%s" % util.hexprint(resp))
return resp == echo_test

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.setTimeout(1)

# 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()

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):
f = self._classes["mem"]()
f.set_location(number)
self._send_frame(f)
f.read(self.pipe)
return repr(f.get_obj())

def get_memory(self, number):
LOG.debug("Getting %i" % number)
f = self._classes["mem"]()
f.set_location(number)
self._send_frame(f)

mem = chirp_common.Memory()
mem.number = number

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] == "\xFF":
mem.empty = True
return mem

memobj = f.get_obj()
LOG.debug(repr(memobj))

mem.freq = int(memobj.freq)
mem.mode = self._rf.valid_modes[memobj.mode]

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:
# FIXME
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]

return mem

def set_memory(self, mem):
f = self._get_template_memory()
if mem.empty:
f.set_location(mem.number)
f.make_empty()
self._send_frame(f)
return

# f.set_data(MemoryMap(self.get_raw_memory(mem.number)))
# f.initialize()

memobj = f.get_obj()
memobj.number = mem.number
memobj.freq = int(mem.freq)
memobj.mode = self._rf.valid_modes.index(mem.mode)
if self._rf.has_name:
memobj.name = mem.name.ljust(self._rf.valid_name_length)[:self._rf.valid_name_length]

if self._rf.valid_tmodes:
memobj.tmode = self._rf.valid_tmodes.index(mem.tmode)

if self._rf.valid_duplexes:
memobj.duplex = self._rf.valid_duplexes.index(mem.duplex)

if self._rf.has_ctone:
memobj.ctone = int(mem.ctone * 10)
memobj.rtone = int(mem.rtone * 10)

LOG.debug(repr(memobj))
self._send_frame(f)

f = self._recv_frame()
LOG.debug("Result:\n%s" % util.hexprint(f.get_data()))


@directory.register
class Icom7200Radio(IcomCIVRadio):
"""Icom IC-7200"""
MODEL = "7200"
_model = "\x76"
_template = 201

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.valid_modes = ["LSB", "USB", "AM", "CW", "RTTY"]
self._rf.valid_tmodes = []
self._rf.valid_duplexes = []
self._rf.valid_bands = [(1800000, 59000000)]
self._rf.valid_tuning_steps = []
self._rf.valid_skips = []
self._rf.memory_bounds = (1, 200)


@directory.register
class Icom7000Radio(IcomCIVRadio):
"""Icom IC-7000"""
MODEL = "7000"
_model = "\x70"
_template = 102

def _initialize(self):
self._classes["mem"] = MultiVFOMemFrame
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"]
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 = 9
self._rf.valid_characters = chirp_common.CHARSET_ASCII
self._rf.memory_bounds = (1, 99)

@directory.register
class Icom7100Radio(IcomCIVRadio):
"""Icom IC-7100"""
MODEL = "7100"
_model = "\x88"
_template = 102
_num_banks = 5

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", "CW-R", "RTTY-R"]
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)

def get_bank_model(self):
rf = self.get_features()
if rf.has_bank:
if not rf.has_bank_index:
return IcomBankModel(self)


@directory.register
class Icom746Radio(IcomCIVRadio):
"""Icom IC-746"""
MODEL = "746"
BAUD_RATE = 9600
_model = "\x56"
_template = 102

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)

CIV_MODELS = {
(0x76, 0xE0): Icom7200Radio,
(0x70, 0xE0): Icom7000Radio,
(0x46, 0xE0): Icom746Radio,
(0x88, 0xE0): Icom7100Radio
}


def probe_model(ser):
"""Probe the radio attatched to @ser for its model"""
f = Frame()
f.set_command(0x19, 0x00)

for model, controller in CIV_MODELS.keys():
f.send(model, controller, ser)
try:
f.read(ser)
except errors.RadioError:
continue

if len(f.get_data()) == 1:
model = ord(f.get_data()[0])
return CIV_MODELS[(model, controller)]

if f.get_data():
LOG.debug("Got data, but not 1 byte:")
LOG.debug(util.hexprint(f.get_data()))
raise errors.RadioError("Unknown response")

raise errors.RadioError("Unsupported model")
(1-1/2)