<div dir="ltr">Oh, I sent this a minute too early. The description on the patch is inaccurate, and should read something like:<div>Adds support for a raw-mode Icom driver. This is required for the IC-2730A (part of #2745).</div></div><div class="gmail_extra"><br><div class="gmail_quote">On Sun, Jan 14, 2018 at 2:59 PM, Rhett Robinson <span dir="ltr"><<a href="mailto:rrhett@gmail.com" target="_blank">rrhett@gmail.com</a>></span> wrote:<br><blockquote class="gmail_quote" style="margin:0 0 0 .8ex;border-left:1px #ccc solid;padding-left:1ex"># HG changeset patch<br>
# User Rhett Robinson <<a href="mailto:rrhett@gmail.com">rrhett@gmail.com</a>><br>
# Date 1515970485 28800<br>
# Sun Jan 14 14:54:45 2018 -0800<br>
# Node ID 6ffdeb62bf3efde8a94ddd24109b99<wbr>48185f832b<br>
# Parent 16dc67ee13d18c5645c0bdbe0d20d0<wbr>21da702492<br>
[ic2730a] Add support for Icom IC-2730A. Fixes #2745<br>
<br>
diff -r 16dc67ee13d1 -r 6ffdeb62bf3e chirp/drivers/icf.py<br>
--- a/chirp/drivers/icf.py Wed Jan 03 11:06:05 2018 -0500<br>
+++ b/chirp/drivers/icf.py Sun Jan 14 14:54:45 2018 -0800<br>
@@ -85,6 +85,7 @@<br>
<br>
def _process_frames(self):<br>
if not self.data.startswith("\xFE\<wbr>xFE"):<br>
+ LOG.error("Out of sync with radio:\n%s" % util.hexprint(self.data))<br>
raise errors.InvalidDataError("Out of sync with radio")<br>
elif len(self.data) < 5:<br>
return [] # Not enough data for a full frame<br>
@@ -134,11 +135,11 @@<br>
return self._process_frames()<br>
<br>
<br>
-def get_model_data(pipe, mdata="\x00\x00\x00\x00"):<br>
- """Query the radio connected to @pipe for its model data"""<br>
- send_clone_frame(pipe, 0xe0, mdata, raw=True)<br>
+def get_model_data(radio, mdata="\x00\x00\x00\x00"):<br>
+ """Query the @radio for its model data"""<br>
+ send_clone_frame(radio, 0xe0, mdata)<br>
<br>
- stream = RadioStream(pipe)<br>
+ stream = RadioStream(radio.pipe)<br>
frames = stream.get_frames()<br>
<br>
if len(frames) != 1:<br>
@@ -164,27 +165,11 @@<br>
return resp<br>
<br>
<br>
-def send_clone_frame(pipe, cmd, data, raw=False, checksum=False):<br>
- """Send a clone frame with @cmd and @data to the radio attached<br>
- to @pipe"""<br>
- cs = 0<br>
+def send_clone_frame(radio, cmd, data, checksum=False):<br>
+ """Send a clone frame with @cmd and @data to the @radio"""<br>
+ payload = radio.get_payload(data, checksum)<br>
<br>
- if raw:<br>
- hed = data<br>
- else:<br>
- hed = ""<br>
- for byte in data:<br>
- val = ord(byte)<br>
- hed += "%02X" % val<br>
- cs += val<br>
-<br>
- if checksum:<br>
- cs = ((cs ^ 0xFFFF) + 1) & 0xFF<br>
- cs = "%02X" % cs<br>
- else:<br>
- cs = ""<br>
-<br>
- frame = "\xfe\xfe\xee\xef%s%s%s\xfd" % (chr(cmd), hed, cs)<br>
+ frame = "\xfe\xfe\xee\xef%s%s\xfd" % (chr(cmd), payload)<br>
<br>
if SAVE_PIPE:<br>
LOG.debug("Saving data...")<br>
@@ -197,30 +182,14 @@<br>
# return frame<br>
pass<br>
<br>
- pipe.write(frame)<br>
+ radio.pipe.write(frame)<br>
<br>
return frame<br>
<br>
<br>
-def process_bcd(bcddata):<br>
- """Convert BCD-encoded data to raw"""<br>
- data = ""<br>
- i = 0<br>
- while i < range(len(bcddata)) and i+1 < len(bcddata):<br>
- try:<br>
- val = int("%s%s" % (bcddata[i], bcddata[i+1]), 16)<br>
- i += 2<br>
- data += struct.pack("B", val)<br>
- except ValueError, e:<br>
- LOG.error("Failed to parse byte: %s" % e)<br>
- break<br>
-<br>
- return data<br>
-<br>
-<br>
-def process_data_frame(frame, _mmap):<br>
+def process_data_frame(radio, frame, _mmap):<br>
"""Process a data frame, adding the payload to @_mmap"""<br>
- _data = process_bcd(frame.payload)<br>
+ _data = radio.process_frame_payload(<wbr>frame.payload)<br>
if len(_mmap) >= 0x10000:<br>
saddr, = struct.unpack(">I", _data[0:4])<br>
length, = struct.unpack("B", _data[4])<br>
@@ -264,7 +233,7 @@<br>
<br>
<br>
def _clone_from_radio(radio):<br>
- md = get_model_data(radio.pipe)<br>
+ md = get_model_data(radio)<br>
<br>
if md[0:4] != radio.get_model():<br>
LOG.info("This model: %s" % util.hexprint(md[0:4]))<br>
@@ -274,8 +243,7 @@<br>
if radio.is_hispeed():<br>
start_hispeed_clone(radio, CMD_CLONE_OUT)<br>
else:<br>
- send_clone_frame(radio.pipe, CMD_CLONE_OUT,<br>
- radio.get_model(), raw=True)<br>
+ send_clone_frame(radio, CMD_CLONE_OUT, radio.get_model())<br>
<br>
LOG.debug("Sent clone frame")<br>
<br>
@@ -291,7 +259,7 @@<br>
<br>
for frame in frames:<br>
if frame.cmd == CMD_CLONE_DAT:<br>
- src, dst = process_data_frame(frame, _mmap)<br>
+ src, dst = process_data_frame(radio, frame, _mmap)<br>
if last_size != (dst - src):<br>
LOG.debug("ICF Size change from %i to %i at %04x" %<br>
(last_size, dst - src, src))<br>
@@ -342,7 +310,7 @@<br>
chunk = struct.pack(">HB", i, size)<br>
chunk += _mmap[i:i+size]<br>
<br>
- send_clone_frame(radio.pipe,<br>
+ send_clone_frame(radio,<br>
CMD_CLONE_DAT,<br>
chunk,<br>
checksum=True)<br>
@@ -360,22 +328,22 @@<br>
# Uncomment to save out a capture of what we actually write to the radio<br>
# SAVE_PIPE = file("pipe_capture.log", "w", 0)<br>
<br>
- md = get_model_data(radio.pipe)<br>
+ md = get_model_data(radio)<br>
<br>
if md[0:4] != radio.get_model():<br>
raise errors.RadioError("I can't talk to this model")<br>
<br>
# This mimics what the Icom software does, but isn't required and just<br>
# takes longer<br>
- # md = get_model_data(radio.pipe, model=md[0:2]+"\x00\x00")<br>
- # md = get_model_data(radio.pipe, model=md[0:2]+"\x00\x00")<br>
+ # md = get_model_data(radio, mdata=md[0:2]+"\x00\x00")<br>
+ # md = get_model_data(radio, mdata=md[0:2]+"\x00\x00")<br>
<br>
stream = RadioStream(radio.pipe)<br>
<br>
if radio.is_hispeed():<br>
start_hispeed_clone(radio, CMD_CLONE_IN)<br>
else:<br>
- send_clone_frame(radio.pipe, CMD_CLONE_IN, radio.get_model(), raw=True)<br>
+ send_clone_frame(radio, CMD_CLONE_IN, radio.get_model())<br>
<br>
frames = []<br>
<br>
@@ -384,7 +352,7 @@<br>
break<br>
frames += stream.get_frames()<br>
<br>
- send_clone_frame(radio.pipe, CMD_CLONE_END, radio.get_endframe(), raw=True)<br>
+ send_clone_frame(radio, CMD_CLONE_END, radio.get_endframe())<br>
<br>
if SAVE_PIPE:<br>
SAVE_PIPE.close()<br>
@@ -409,6 +377,7 @@<br>
try:<br>
return _clone_to_radio(radio)<br>
except Exception, e:<br>
+ logging.exception("Failed to communicate with the radio")<br>
raise errors.RadioError("Failed to communicate with the radio: %s" % e)<br>
<br>
<br>
@@ -579,6 +548,13 @@<br>
raise errors.RadioError("Out of slots in this bank")<br>
<br>
<br>
+def compute_checksum(data):<br>
+ cs = 0<br>
+ for byte in data:<br>
+ cs += ord(byte)<br>
+ return ((cs ^ 0xFFFF) + 1) & 0xFF<br>
+<br>
+<br>
class IcomCloneModeRadio(chirp_<wbr>common.CloneModeRadio):<br>
"""Base class for Icom clone-mode radios"""<br>
VENDOR = "Icom"<br>
@@ -612,6 +588,31 @@<br>
"""Returns the ranges this radio likes to have in a clone"""<br>
return cls._ranges<br>
<br>
+ def process_frame_payload(self, payload):<br>
+ """Convert BCD-encoded data to raw"""<br>
+ bcddata = payload<br>
+ data = ""<br>
+ i = 0<br>
+ while i+1 < len(bcddata):<br>
+ try:<br>
+ val = int("%s%s" % (bcddata[i], bcddata[i+1]), 16)<br>
+ i += 2<br>
+ data += struct.pack("B", val)<br>
+ except ValueError, e:<br>
+ LOG.error("Failed to parse byte: %s" % e)<br>
+ break<br>
+<br>
+ return data<br>
+<br>
+ def get_payload(self, data, checksum):<br>
+ """Returns the data with optional checksum BCD-encoded for the radio."""<br>
+ payload = ""<br>
+ for byte in data:<br>
+ payload += "%02X" % ord(byte)<br>
+ if checksum:<br>
+ payload += "%02X" % compute_checksum(data)<br>
+ return payload<br>
+<br>
def sync_in(self):<br>
self._mmap = clone_from_radio(self)<br>
self.process_mmap()<br>
@@ -646,6 +647,67 @@<br>
return honor_speed_switch_setting(<wbr>self, settings)<br>
<br>
<br>
+def flip_high_order_bit(data):<br>
+ return [chr(ord(d) ^ 0x80) for d in list(data)]<br>
+<br>
+<br>
+def escape_raw_byte(byte):<br>
+ """Escapes a raw byte for sending to the radio"""<br>
+ # Certain bytes are used as control characters to the radio, so if one of<br>
+ # these bytes is present in the stream to the radio, it gets escaped as<br>
+ # 0xff followed by (byte & 0x0f)<br>
+ if ord(byte) > 0xf9:<br>
+ return "\xff%s" % (chr(ord(byte) & 0xf))<br>
+ return byte<br>
+<br>
+<br>
+def unescape_raw_bytes(escaped_<wbr>data):<br>
+ """Unescapes raw bytes from the radio."""<br>
+ data = ""<br>
+ i = 0<br>
+ while i < len(escaped_data):<br>
+ byte = escaped_data[i]<br>
+ if byte == '\xff':<br>
+ if i + 1 >= len(escaped_data):<br>
+ raise errors.InvalidDataError(<br>
+ "Unexpected escape character at end of data")<br>
+ i += 1<br>
+ byte = chr(0xf0 | ord(escaped_data[i]))<br>
+ data += byte<br>
+ i += 1<br>
+ return data<br>
+<br>
+<br>
+class IcomRawCloneModeRadio(<wbr>IcomCloneModeRadio):<br>
+ """Subclass for Icom clone-mode radios using the raw data protocol."""<br>
+<br>
+ def process_frame_payload(self, payload):<br>
+ """Payloads from a raw-clone-mode radio are already in raw format."""<br>
+ return unescape_raw_bytes(payload)<br>
+<br>
+ def get_payload(self, data, checksum):<br>
+ """Returns the data with optional checksum in raw format."""<br>
+ if checksum:<br>
+ cs = chr(compute_checksum(data))<br>
+ else:<br>
+ cs = ""<br>
+ payload = "%s%s" % (data, cs)<br>
+ # Escape control characters.<br>
+ escaped_payload = [escape_raw_byte(b) for b in payload]<br>
+ return "".join(escaped_payload)<br>
+<br>
+ def sync_in(self):<br>
+ # The radio returns all the bytes with the high-order bit flipped.<br>
+ _mmap = clone_from_radio(self)<br>
+ _mmap = flip_high_order_bit(_mmap.get_<wbr>packed())<br>
+ self._mmap = memmap.MemoryMap(_mmap)<br>
+ self.process_mmap()<br>
+<br>
+ def get_mmap(self):<br>
+ _data = flip_high_order_bit(self._<wbr>mmap.get_packed())<br>
+ return memmap.MemoryMap(_data)<br>
+<br>
+<br>
class IcomLiveRadio(chirp_common.<wbr>LiveRadio):<br>
"""Base class for an Icom Live-mode radio"""<br>
VENDOR = "Icom"<br>
</blockquote></div><br></div>