<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">&lt;<a href="mailto:rrhett@gmail.com" target="_blank">rrhett@gmail.com</a>&gt;</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 &lt;<a href="mailto:rrhett@gmail.com">rrhett@gmail.com</a>&gt;<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(&quot;\xFE\<wbr>xFE&quot;):<br>
+            LOG.error(&quot;Out of sync with radio:\n%s&quot; % util.hexprint(self.data))<br>
             raise errors.InvalidDataError(&quot;Out of sync with radio&quot;)<br>
         elif len(self.data) &lt; 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=&quot;\x00\x00\x00\x00&quot;):<br>
-    &quot;&quot;&quot;Query the radio connected to @pipe for its model data&quot;&quot;&quot;<br>
-    send_clone_frame(pipe, 0xe0, mdata, raw=True)<br>
+def get_model_data(radio, mdata=&quot;\x00\x00\x00\x00&quot;):<br>
+    &quot;&quot;&quot;Query the @radio for its model data&quot;&quot;&quot;<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>
-    &quot;&quot;&quot;Send a clone frame with @cmd and @data to the radio attached<br>
-    to @pipe&quot;&quot;&quot;<br>
-    cs = 0<br>
+def send_clone_frame(radio, cmd, data, checksum=False):<br>
+    &quot;&quot;&quot;Send a clone frame with @cmd and @data to the @radio&quot;&quot;&quot;<br>
+    payload = radio.get_payload(data, checksum)<br>
<br>
-    if raw:<br>
-        hed = data<br>
-    else:<br>
-        hed = &quot;&quot;<br>
-        for byte in data:<br>
-            val = ord(byte)<br>
-            hed += &quot;%02X&quot; % val<br>
-            cs += val<br>
-<br>
-    if checksum:<br>
-        cs = ((cs ^ 0xFFFF) + 1) &amp; 0xFF<br>
-        cs = &quot;%02X&quot; % cs<br>
-    else:<br>
-        cs = &quot;&quot;<br>
-<br>
-    frame = &quot;\xfe\xfe\xee\xef%s%s%s\xfd&quot; % (chr(cmd), hed, cs)<br>
+    frame = &quot;\xfe\xfe\xee\xef%s%s\xfd&quot; % (chr(cmd), payload)<br>
<br>
     if SAVE_PIPE:<br>
         LOG.debug(&quot;Saving data...&quot;)<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>
-    &quot;&quot;&quot;Convert BCD-encoded data to raw&quot;&quot;&quot;<br>
-    data = &quot;&quot;<br>
-    i = 0<br>
-    while i &lt; range(len(bcddata)) and i+1 &lt; len(bcddata):<br>
-        try:<br>
-            val = int(&quot;%s%s&quot; % (bcddata[i], bcddata[i+1]), 16)<br>
-            i += 2<br>
-            data += struct.pack(&quot;B&quot;, val)<br>
-        except ValueError, e:<br>
-            LOG.error(&quot;Failed to parse byte: %s&quot; % 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>
     &quot;&quot;&quot;Process a data frame, adding the payload to @_mmap&quot;&quot;&quot;<br>
-    _data = process_bcd(frame.payload)<br>
+    _data = radio.process_frame_payload(<wbr>frame.payload)<br>
     if len(_mmap) &gt;= 0x10000:<br>
         saddr, = struct.unpack(&quot;&gt;I&quot;, _data[0:4])<br>
         length, = struct.unpack(&quot;B&quot;, _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(&quot;This model: %s&quot; % 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(&quot;Sent clone frame&quot;)<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(&quot;ICF Size change from %i to %i at %04x&quot; %<br>
                               (last_size, dst - src, src))<br>
@@ -342,7 +310,7 @@<br>
             chunk = struct.pack(&quot;&gt;HB&quot;, 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(&quot;pipe_capture.log&quot;, &quot;w&quot;, 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(&quot;I can&#39;t talk to this model&quot;)<br>
<br>
     # This mimics what the Icom software does, but isn&#39;t required and just<br>
     # takes longer<br>
-    # md = get_model_data(radio.pipe, model=md[0:2]+&quot;\x00\x00&quot;)<br>
-    # md = get_model_data(radio.pipe, model=md[0:2]+&quot;\x00\x00&quot;)<br>
+    # md = get_model_data(radio, mdata=md[0:2]+&quot;\x00\x00&quot;)<br>
+    # md = get_model_data(radio, mdata=md[0:2]+&quot;\x00\x00&quot;)<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(&quot;Failed to communicate with the radio&quot;)<br>
         raise errors.RadioError(&quot;Failed to communicate with the radio: %s&quot; % e)<br>
<br>
<br>
@@ -579,6 +548,13 @@<br>
         raise errors.RadioError(&quot;Out of slots in this bank&quot;)<br>
<br>
<br>
+def compute_checksum(data):<br>
+    cs = 0<br>
+    for byte in data:<br>
+        cs += ord(byte)<br>
+    return ((cs ^ 0xFFFF) + 1) &amp; 0xFF<br>
+<br>
+<br>
 class IcomCloneModeRadio(chirp_<wbr>common.CloneModeRadio):<br>
     &quot;&quot;&quot;Base class for Icom clone-mode radios&quot;&quot;&quot;<br>
     VENDOR = &quot;Icom&quot;<br>
@@ -612,6 +588,31 @@<br>
         &quot;&quot;&quot;Returns the ranges this radio likes to have in a clone&quot;&quot;&quot;<br>
         return cls._ranges<br>
<br>
+    def process_frame_payload(self, payload):<br>
+        &quot;&quot;&quot;Convert BCD-encoded data to raw&quot;&quot;&quot;<br>
+        bcddata = payload<br>
+        data = &quot;&quot;<br>
+        i = 0<br>
+        while i+1 &lt; len(bcddata):<br>
+            try:<br>
+                val = int(&quot;%s%s&quot; % (bcddata[i], bcddata[i+1]), 16)<br>
+                i += 2<br>
+                data += struct.pack(&quot;B&quot;, val)<br>
+            except ValueError, e:<br>
+                LOG.error(&quot;Failed to parse byte: %s&quot; % e)<br>
+                break<br>
+<br>
+        return data<br>
+<br>
+    def get_payload(self, data, checksum):<br>
+        &quot;&quot;&quot;Returns the data with optional checksum BCD-encoded for the radio.&quot;&quot;&quot;<br>
+        payload = &quot;&quot;<br>
+        for byte in data:<br>
+            payload += &quot;%02X&quot; % ord(byte)<br>
+        if checksum:<br>
+            payload += &quot;%02X&quot; % 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>
+    &quot;&quot;&quot;Escapes a raw byte for sending to the radio&quot;&quot;&quot;<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 &amp; 0x0f)<br>
+    if ord(byte) &gt; 0xf9:<br>
+        return &quot;\xff%s&quot; % (chr(ord(byte) &amp; 0xf))<br>
+    return byte<br>
+<br>
+<br>
+def unescape_raw_bytes(escaped_<wbr>data):<br>
+    &quot;&quot;&quot;Unescapes raw bytes from the radio.&quot;&quot;&quot;<br>
+    data = &quot;&quot;<br>
+    i = 0<br>
+    while i &lt; len(escaped_data):<br>
+        byte = escaped_data[i]<br>
+        if byte == &#39;\xff&#39;:<br>
+            if i + 1 &gt;= len(escaped_data):<br>
+                raise errors.InvalidDataError(<br>
+                    &quot;Unexpected escape character at end of data&quot;)<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>
+    &quot;&quot;&quot;Subclass for Icom clone-mode radios using the raw data protocol.&quot;&quot;&quot;<br>
+<br>
+    def process_frame_payload(self, payload):<br>
+        &quot;&quot;&quot;Payloads from a raw-clone-mode radio are already in raw format.&quot;&quot;&quot;<br>
+        return unescape_raw_bytes(payload)<br>
+<br>
+    def get_payload(self, data, checksum):<br>
+        &quot;&quot;&quot;Returns the data with optional checksum in raw format.&quot;&quot;&quot;<br>
+        if checksum:<br>
+            cs = chr(compute_checksum(data))<br>
+        else:<br>
+            cs = &quot;&quot;<br>
+        payload = &quot;%s%s&quot; % (data, cs)<br>
+        # Escape control characters.<br>
+        escaped_payload = [escape_raw_byte(b) for b in payload]<br>
+        return &quot;&quot;.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>
     &quot;&quot;&quot;Base class for an Icom Live-mode radio&quot;&quot;&quot;<br>
     VENDOR = &quot;Icom&quot;<br>
</blockquote></div><br></div>