Project

General

Profile

Bug #10824 » ft50.py

3b585b41 - Dan Smith, 09/05/2023 04:09 PM

 
1
# Copyright 2011 Dan Smith <dsmith@danplanet.com>
2
#
3
# This program is free software: you can redistribute it and/or modify
4
# it under the terms of the GNU General Public License as published by
5
# the Free Software Foundation, either version 3 of the License, or
6
# (at your option) any later version.
7
#
8
# This program is distributed in the hope that it will be useful,
9
# but WITHOUT ANY WARRANTY; without even the implied warranty of
10
# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
11
# GNU General Public License for more details.
12
#
13
# You should have received a copy of the GNU General Public License
14
# along with this program.  If not, see <http://www.gnu.org/licenses/>.
15
import time
16
import logging
17
import re
18

    
19
from chirp.drivers import yaesu_clone
20
from chirp import chirp_common, directory, errors, bitwise, util
21
from chirp.settings import RadioSetting, RadioSettingGroup, \
22
    RadioSettingValueInteger, RadioSettingValueList, \
23
    RadioSettingValueBoolean, RadioSettingValueString, \
24
    RadioSettings
25

    
26
LOG = logging.getLogger(__name__)
27

    
28
MEM_FORMAT = """
29
struct flag_struct {
30
  u8 unknown1f:5,
31
     skip:1,
32
     mask:1,
33
     used:1;
34
};
35

    
36
struct mem_struct {
37
  u8 showname:1,
38
     unknown1:3,
39
     unknown2:2,
40
     unknown3:2;
41
  u8 ishighpower:1,
42
     power:2,
43
     unknown4:1,
44
     tuning_step:4;
45
  u8 codememno:4,
46
     codeorpage:2,
47
     duplex:2;
48
  u8 tmode:2,
49
     tone:6;
50
  u8 unknown5:1,
51
     dtcs:7;
52
  u8 unknown6:6,
53
     mode:2;
54
  bbcd freq[3];
55
  bbcd offset[3];
56
  u8 name[4];
57
};
58

    
59
#seekto 0x000C;
60
struct {
61
  u8 extendedrx_flg;    // Seems to be set to 03 when extended rx is enabled
62
  u8 extendedrx;        // Seems to be set to 01 when extended rx is enabled
63
} extendedrx_struct;    // UNFINISHED!!
64

    
65
#seekto 0x001A;
66
struct flag_struct flag[100];
67

    
68
#seekto 0x079C;
69
struct flag_struct flag_repeat[100];
70

    
71
#seekto 0x00AA;
72
struct mem_struct memory[100];
73
struct mem_struct special[11];
74

    
75
#seekto 0x08C7;
76
struct {
77
  u8 sub_display;
78
  u8 unknown1s;
79
  u8 apo;
80
  u8 timeout;
81
  u8 lock;
82
  u8 rxsave;
83
  u8 lamp;
84
  u8 bell;
85
  u8 cwid[16];
86
  u8 unknown2s;
87
  u8 artsmode;
88
  u8 artsbeep;
89
  u8 unknown3s;
90
  u8 unknown4s;
91
  struct {
92
    u8 header[3];
93
    u8 mem_num;
94
    u8 digits[16];
95
  } autodial[8];
96
  struct {
97
    u8 header[3];
98
    u8 mem_num;
99
    u8 digits[32];
100
  } autodial9_ro;
101
  bbcd pagingcodec_ro[2];
102
  bbcd pagingcodep[2];
103
  struct {
104
    bbcd digits[2];
105
  } pagingcode[6];
106
  u8 code_dec_c_en:1,
107
     code_dec_p_en:1,
108
     code_dec_1_en:1,
109
     code_dec_2_en:1,
110
     code_dec_3_en:1,
111
     code_dec_4_en:1,
112
     code_dec_5_en:1,
113
     code_dec_6_en:1;
114
  u8 pagingspeed;
115
  u8 pagingdelay;
116
  u8 pagingbell;
117
  u8 paginganswer;
118

    
119
  #seekto 0x0E30;
120
  u8 squelch;       // squelch
121
  u8 unknown0c;
122
  u8 rptl:1,        // repeater input tracking
123
     amod:1,        // auto mode
124
     scnl:1,        // scan lamp
125
     resm:1,        // scan resume mode 0=5sec, 1=carr
126
     ars:1,         // automatic repeater shift
127
     keybeep:1,     // keypad beep
128
     lck:1,         // lock
129
     unknown1c:1;
130
  u8 lgt:1,
131
     pageamsg:1,
132
     unknown2c:1,
133
     bclo:1,        // Busy channel lock out
134
     unknown3c:2,
135
     cwid_en:1,     // CWID off/on
136
     tsav:1;        // TX save
137
  u8 unknown4c:4,
138
     artssped:1,    // ARTS/SPED: 0=15s, 1=25s
139
     unknown5c:1,
140
     rvhm:1,        // RVHM: 0=home, 1=rev
141
     mon:1;         // MON: 0=mon, 1=tcal
142
} settings;
143

    
144
#seekto 0x080E;
145
struct mem_struct vfo_mem[10];
146

    
147

    
148
"""
149

    
150
# 10 VFO memories: A145, A220, A380, A430, A800,
151
#                  B145, B220, B380, B430, B800
152

    
153
DUPLEX = ["", "-", "+"]
154
MODES = ["FM", "AM", "WFM"]
155
SKIP_VALUES = ["", "S"]
156
TMODES = ["", "Tone", "TSQL", "DTCS"]
157
TUNING_STEPS = [5.0, 10.0, 12.5, 15.0, 20.0, 25.0, 50.0]
158
TONES = list(chirp_common.OLD_TONES)
159

    
160
# CHARSET = "0123456789ABCDEFGHIJKLMNOPQRSTUVWXYZ ()+-=*/???|0123456789"
161
# the = displays as an underscored dash on radio
162
# the first ? is an uppercase delta - \xA7
163
# the second ? is an uppercase gamma - \xD1
164
# the third ? is an uppercase sigma - \xCF
165
NUMERIC_CHARSET = list("0123456789")
166
CHARSET = [str(x) for x in range(0, 10)] + \
167
    [chr(x) for x in range(ord("A"), ord("Z")+1)] + \
168
    list(" ()+-=*/" + ("\x00" * 3) + "|") + NUMERIC_CHARSET
169
DTMFCHARSET = NUMERIC_CHARSET + list("ABCD*#")
170

    
171
POWER_LEVELS = [chirp_common.PowerLevel("Hi", watts=5.0),
172
                chirp_common.PowerLevel("L3", watts=2.5),
173
                chirp_common.PowerLevel("L2", watts=1.0),
174
                chirp_common.PowerLevel("L1", watts=0.1)]
175
SPECIALS = ["L1", "U1", "L2", "U2", "L3", "U3", "L4", "U4", "L5", "U5", "UNK"]
176

    
177

    
178
@directory.register
179
class FT50Radio(yaesu_clone.YaesuCloneModeRadio):
180
    """Yaesu FT-50"""
181
    BAUD_RATE = 9600
182
    VENDOR = "Yaesu"
183
    MODEL = "FT-50"
184

    
185
    _model = ""
186
    _memsize = 3723
187
    _block_lengths = [10, 16, 112, 16, 16, 1776, 1776, 1]
188
    # _block_delay = 0.15
189
    _block_size = 8
190

    
191
    @classmethod
192
    def get_prompts(cls):
193
        rp = chirp_common.RadioPrompts()
194
        rp.pre_download = _(
195
            "1. Turn radio off.\n"
196
            "2. Connect cable to MIC/SP jack.\n"
197
            "3. Press and hold [PTT] &amp; Knob while turning the\n"
198
            "     radio on.\n"
199
            "4. <b>After clicking OK</b>, press the [PTT] switch to send"
200
            " image.\n")
201
        rp.pre_upload = _(
202
            "1. Turn radio off.\n"
203
            "2. Connect cable to MIC/SP jack.\n"
204
            "3. Press and hold [PTT] &amp; Knob while turning the\n"
205
            "     radio on.\n"
206
            "4. Press the [MONI] switch (\"WAIT\" will appear on the LCD).\n"
207
            "5. Press OK.\n")
208
        return rp
209

    
210
    def get_features(self):
211
        rf = chirp_common.RadioFeatures()
212
        rf.memory_bounds = (1, 100)
213
        rf.valid_duplexes = DUPLEX
214
        rf.valid_tmodes = TMODES
215
        rf.valid_power_levels = POWER_LEVELS
216
        rf.valid_tuning_steps = TUNING_STEPS
217
        rf.valid_power_levels = POWER_LEVELS
218
        rf.valid_characters = "".join(CHARSET)
219
        rf.valid_name_length = 4
220
        rf.valid_modes = MODES
221
        # Specials not yet implemented
222
        # rf.valid_special_chans = SPECIALS
223
        rf.valid_bands = [(76000000, 200000000),
224
                          (300000000, 540000000),
225
                          (590000000, 999000000)]
226
        # rf.can_odd_split = True
227
        rf.has_ctone = False
228
        rf.has_bank = False
229
        rf.has_settings = True
230
        rf.has_dtcs_polarity = False
231

    
232
        return rf
233

    
234
    def _checksums(self):
235
        return [yaesu_clone.YaesuChecksum(0x0000, 0xE89)]
236

    
237
    def process_mmap(self):
238
        self._memobj = bitwise.parse(MEM_FORMAT, self._mmap)
239

    
240
    def get_raw_memory(self, number):
241
        return repr(self._memobj.memory[number - 1])
242

    
243
    def get_memory(self, number):
244
        mem = chirp_common.Memory()
245
        _mem = self._memobj.memory[number-1]
246
        _flg = self._memobj.flag[number-1]
247
        mem.number = number
248

    
249
        # if not _flg.visible:
250
        #    mem.empty = True
251
        if not _flg.used:
252
            mem.empty = True
253
            return mem
254

    
255
        for i in _mem.name:
256
            mem.name += CHARSET[i & 0x7F]
257
        mem.name = mem.name.rstrip()
258

    
259
        mem.freq = chirp_common.fix_rounded_step(int(_mem.freq) * 1000)
260
        mem.duplex = DUPLEX[_mem.duplex]
261
        mem.offset = chirp_common.fix_rounded_step(int(_mem.offset) * 1000)
262
        mem.rtone = mem.ctone = TONES[_mem.tone]
263
        mem.tmode = TMODES[_mem.tmode]
264
        mem.mode = MODES[_mem.mode]
265
        mem.tuning_step = TUNING_STEPS[_mem.tuning_step]
266
        mem.dtcs = chirp_common.DTCS_CODES[_mem.dtcs]
267
        # Power is stored as 2 bits to describe the 3 low power levels
268
        # High power is determined by a different bit.
269
        if not _mem.ishighpower:
270
            mem.power = POWER_LEVELS[3 - _mem.power]
271
        else:
272
            mem.power = POWER_LEVELS[0]
273
        mem.skip = SKIP_VALUES[_flg.skip]
274

    
275
        return mem
276

    
277
    def set_memory(self, mem):
278
        _mem = self._memobj.memory[mem.number-1]
279
        _flg = self._memobj.flag[mem.number-1]
280
        _flg_repeat = self._memobj.flag_repeat[mem.number-1]
281

    
282
        if mem.empty:
283
            _flg.used = False
284
            return
285

    
286
        if (len(mem.name) == 0):
287
            _mem.name = [0x24] * 4
288
            _mem.showname = 0
289
        else:
290
            _mem.showname = 1
291
            for i in range(0, 4):
292
                _mem.name[i] = CHARSET.index(mem.name.ljust(4)[i])
293

    
294
        _mem.freq = int(mem.freq / 1000)
295
        _mem.duplex = DUPLEX.index(mem.duplex)
296
        _mem.offset = int(mem.offset / 1000)
297
        _mem.mode = MODES.index(mem.mode)
298
        _mem.tuning_step = TUNING_STEPS.index(mem.tuning_step)
299
        if mem.power:
300
            if (mem.power == POWER_LEVELS[0]):
301
                # low power level is not changed when high power is selected
302
                _mem.ishighpower = 0x01
303
                if (_mem.power == 3):
304
                    # Set low power to L3 (0x02) if it is
305
                    # set to 3 (new object default)
306
                    LOG.debug("SETTING DEFAULT?")
307
                    _mem.power = 0x02
308
            else:
309
                _mem.ishighpower = 0x00
310
                _mem.power = 3 - POWER_LEVELS.index(mem.power)
311
        else:
312
            _mem.ishighpower = 0x01
313
            _mem.power = 0x02
314
        _mem.tmode = TMODES.index(mem.tmode)
315
        try:
316
            _mem.tone = TONES.index(mem.rtone)
317
        except ValueError:
318
            raise errors.UnsupportedToneError(
319
                ("This radio does not support tone %s" % mem.rtone))
320
        _mem.dtcs = chirp_common.DTCS_CODES.index(mem.dtcs)
321

    
322
        _flg.skip = SKIP_VALUES.index(mem.skip)
323

    
324
        # initialize new channel to safe defaults
325
        if not mem.empty and not _flg.used:
326
            _flg.used = True
327
            _flg.mask = True        # Mask = True to be visible on radio
328
            _mem.unknown1 = 0x00
329
            _mem.unknown2 = 0x00
330
            _mem.unknown3 = 0x00
331
            _mem.unknown4 = 0x00
332
            _mem.unknown5 = 0x00
333
            _mem.unknown6 = 0x00
334
            _mem.codememno = 0x02   # Not implemented in chirp
335
            _mem.codeorpage = 0x00  # Not implemented in chirp
336

    
337
        # Duplicate flags to repeated part in memory
338
        _flg_repeat.skip = _flg.skip
339
        _flg_repeat.mask = _flg.mask
340
        _flg_repeat.used = _flg.used
341

    
342
    def _decode_cwid(self, inarr):
343
        LOG.debug("@_decode_chars, type: %s" % type(inarr))
344
        LOG.debug(inarr)
345
        outstr = ""
346
        for i in inarr:
347
            if i == 0xFF:
348
                break
349
            outstr += CHARSET[i & 0x7F]
350
        LOG.debug(outstr)
351
        return outstr.rstrip()
352

    
353
    def _encode_cwid(self, instr, length=16):
354
        LOG.debug("@_encode_chars, type: %s" % type(instr))
355
        LOG.debug(instr)
356
        outarr = []
357
        instr = str(instr)
358
        for i in range(0, length):
359
            if i < len(instr):
360
                outarr.append(CHARSET.index(instr[i]))
361
            else:
362
                outarr.append(0xFF)
363
        return outarr
364

    
365
    def get_settings(self):
366
        _settings = self._memobj.settings
367
        basic = RadioSettingGroup("basic", "Basic")
368
        dtmf = RadioSettingGroup("dtmf", "DTMF Code & Paging")
369
        arts = RadioSettingGroup("arts", "ARTS")
370
        autodial = RadioSettingGroup("autodial", "AutoDial")
371
        top = RadioSettings(basic, autodial, arts, dtmf)
372

    
373
        rs = RadioSetting(
374
                "squelch", "Squelch",
375
                RadioSettingValueInteger(0, 15, _settings.squelch))
376
        basic.append(rs)
377

    
378
        rs = RadioSetting(
379
                "keybeep", "Keypad Beep",
380
                RadioSettingValueBoolean(_settings.keybeep))
381
        basic.append(rs)
382

    
383
        rs = RadioSetting(
384
                "scnl", "Scan Lamp",
385
                RadioSettingValueBoolean(_settings.scnl))
386
        basic.append(rs)
387

    
388
        options = ["off", "30m", "1h", "3h", "5h", "8h"]
389
        rs = RadioSetting(
390
                "apo", "APO time (hrs)",
391
                RadioSettingValueList(options, options[_settings.apo]))
392
        basic.append(rs)
393

    
394
        options = ["off", "1m", "2.5m", "5m", "10m"]
395
        rs = RadioSetting(
396
                "timeout", "Time Out Timer",
397
                RadioSettingValueList(options, options[_settings.timeout]))
398
        basic.append(rs)
399

    
400
        options = ["key", "dial", "key+dial", "ptt",
401
                   "key+ptt", "dial+ptt", "all"]
402
        rs = RadioSetting(
403
                "lock", "Lock mode",
404
                RadioSettingValueList(options, options[_settings.lock]))
405
        basic.append(rs)
406

    
407
        options = ["off", "0.2", "0.3", "0.5", "1.0", "2.0"]
408
        rs = RadioSetting(
409
                "rxsave", "RX Save (sec)",
410
                RadioSettingValueList(options, options[_settings.rxsave]))
411
        basic.append(rs)
412

    
413
        options = ["5sec", "key", "tgl"]
414
        rs = RadioSetting(
415
                "lamp", "Lamp mode",
416
                RadioSettingValueList(options, options[_settings.lamp]))
417
        basic.append(rs)
418

    
419
        options = ["off", "1", "3", "5", "8", "rpt"]
420
        rs = RadioSetting(
421
                "bell", "Bell Repetitions",
422
                RadioSettingValueList(options, options[_settings.bell]))
423
        basic.append(rs)
424

    
425
        rs = RadioSetting(
426
                "cwid_en", "CWID Enable",
427
                RadioSettingValueBoolean(_settings.cwid_en))
428
        arts.append(rs)
429

    
430
        cwid = RadioSettingValueString(
431
                0, 16, self._decode_cwid(_settings.cwid.get_value()))
432
        cwid.set_charset(CHARSET)
433
        rs = RadioSetting("cwid", "CWID", cwid)
434
        arts.append(rs)
435

    
436
        options = ["off", "rx", "tx", "trx"]
437
        rs = RadioSetting(
438
                "artsmode", "ARTS Mode",
439
                RadioSettingValueList(
440
                    options, options[_settings.artsmode]))
441
        arts.append(rs)
442

    
443
        options = ["off", "in range", "always"]
444
        rs = RadioSetting(
445
                "artsbeep", "ARTS Beep",
446
                RadioSettingValueList(options, options[_settings.artsbeep]))
447
        arts.append(rs)
448

    
449
        for i in range(0, 8):
450
            dialsettings = _settings.autodial[i]
451
            dialstr = ""
452
            for c in dialsettings.digits:
453
                if c < len(DTMFCHARSET):
454
                    dialstr += DTMFCHARSET[c]
455
            dialentry = RadioSettingValueString(0, 16, dialstr)
456
            dialentry.set_charset(DTMFCHARSET + list(" "))
457
            rs = RadioSetting("autodial" + str(i+1),
458
                              "AutoDial " + str(i+1), dialentry)
459
            autodial.append(rs)
460

    
461
        dialstr = ""
462
        for c in _settings.autodial9_ro.digits:
463
            if c < len(DTMFCHARSET):
464
                dialstr += DTMFCHARSET[c]
465
        dialentry = RadioSettingValueString(0, 32, dialstr)
466
        dialentry.set_mutable(False)
467
        rs = RadioSetting("autodial9_ro", "AutoDial 9 (read only)", dialentry)
468
        autodial.append(rs)
469

    
470
        options = ["50ms", "100ms"]
471
        rs = RadioSetting(
472
                "pagingspeed", "Paging Speed",
473
                RadioSettingValueList(options, options[_settings.pagingspeed]))
474
        dtmf.append(rs)
475

    
476
        options = ["250ms", "450ms", "750ms", "1000ms"]
477
        rs = RadioSetting(
478
                "pagingdelay", "Paging Delay",
479
                RadioSettingValueList(options, options[_settings.pagingdelay]))
480
        dtmf.append(rs)
481

    
482
        options = ["off", "1", "3", "5", "8", "rpt"]
483
        rs = RadioSetting(
484
                "pagingbell", "Paging Bell Repetitions",
485
                RadioSettingValueList(options, options[_settings.pagingbell]))
486
        dtmf.append(rs)
487

    
488
        options = ["off", "ans", "for"]
489
        rs = RadioSetting(
490
                "paginganswer", "Paging Answerback",
491
                RadioSettingValueList(options,
492
                                      options[_settings.paginganswer]))
493
        dtmf.append(rs)
494

    
495
        rs = RadioSetting(
496
                "code_dec_c_en", "Paging Code C Decode Enable",
497
                RadioSettingValueBoolean(_settings.code_dec_c_en))
498
        dtmf.append(rs)
499

    
500
        _str = str(bitwise.bcd_to_int(_settings.pagingcodec_ro))
501
        code = RadioSettingValueString(0, 3, _str)
502
        code.set_charset(NUMERIC_CHARSET + list(" "))
503
        code.set_mutable(False)
504
        rs = RadioSetting("pagingcodec_ro", "Paging Code C (read only)", code)
505
        dtmf.append(rs)
506

    
507
        rs = RadioSetting(
508
                "code_dec_p_en", "Paging Code P Decode Enable",
509
                RadioSettingValueBoolean(_settings.code_dec_p_en))
510
        dtmf.append(rs)
511

    
512
        _str = str(bitwise.bcd_to_int(_settings.pagingcodep))
513
        code = RadioSettingValueString(0, 3, _str)
514
        code.set_charset(NUMERIC_CHARSET + list(" "))
515
        rs = RadioSetting("pagingcodep", "Paging Code P", code)
516
        dtmf.append(rs)
517

    
518
        for i in range(0, 6):
519
            num = str(i+1)
520
            name = "code_dec_" + num + "_en"
521
            rs = RadioSetting(
522
                    name, "Paging Code " + num + " Decode Enable",
523
                    RadioSettingValueBoolean(getattr(_settings, name)))
524
            dtmf.append(rs)
525

    
526
            _str = str(bitwise.bcd_to_int(_settings.pagingcode[i].digits))
527
            code = RadioSettingValueString(0, 3, _str)
528
            code.set_charset(NUMERIC_CHARSET + list(" "))
529
            rs = RadioSetting("pagingcode" + num, "Paging Code " + num, code)
530
            dtmf.append(rs)
531

    
532
        return top
533

    
534
    def set_settings(self, uisettings):
535
        for element in uisettings:
536
            if not isinstance(element, RadioSetting):
537
                self.set_settings(element)
538
                continue
539
            if not element.changed():
540
                continue
541
            try:
542
                setting = element.get_name()
543
                _settings = self._memobj.settings
544
                if re.match(r'autodial\d', setting):
545
                    # set autodial fields
546
                    dtmfstr = str(element.value).strip()
547
                    newval = []
548
                    for i in range(0, 16):
549
                        if i < len(dtmfstr):
550
                            newval.append(DTMFCHARSET.index(dtmfstr[i]))
551
                        else:
552
                            newval.append(0xFF)
553
                    LOG.debug(newval)
554
                    idx = int(setting[-1:]) - 1
555
                    _settings = self._memobj.settings.autodial[idx]
556
                    _settings.digits = newval
557
                    continue
558
                if (setting == "pagingcodep"):
559
                    bitwise.int_to_bcd(_settings.pagingcodep,
560
                                       int(element.value))
561
                    continue
562
                if re.match(r'pagingcode\d', setting):
563
                    idx = int(setting[-1:]) - 1
564
                    bitwise.int_to_bcd(_settings.pagingcode[idx].digits,
565
                                       int(element.value))
566
                    continue
567
                newval = element.value
568
                oldval = getattr(_settings, setting)
569
                if setting == "cwid":
570
                    newval = self._encode_cwid(newval)
571
                LOG.debug("Setting %s(%s) <= %s" % (setting, oldval, newval))
572
                setattr(_settings, setting, newval)
573
            except Exception:
574
                LOG.debug(element.get_name())
575
                raise
576

    
577
    @classmethod
578
    def match_model(cls, filedata, filename):
579
        return len(filedata) == cls._memsize
580

    
581
    def sync_out(self):
582
        self.update_checksums()
583
        return _clone_out(self)
584

    
585

    
586
def _clone_out(radio):
587
    try:
588
        return __clone_out(radio)
589
    except Exception as e:
590
        raise errors.RadioError("Failed to communicate with the radio: %s" % e)
591

    
592

    
593
def __clone_out(radio):
594
    pipe = radio.pipe
595
    pipe.timeout = 1
596
    block_lengths = radio._block_lengths
597
    total_written = 0
598

    
599
    def _status():
600
        status = chirp_common.Status()
601
        status.msg = "Cloning to radio"
602
        status.max = sum(block_lengths)
603
        status.cur = total_written
604
        radio.status_fn(status)
605

    
606
    start = time.time()
607

    
608
    blocks = 0
609
    pos = 0
610
    for block in radio._block_lengths:
611
        blocks += 1
612
        data = radio.get_mmap()[pos:pos + block]
613
        LOG.debug("Sending block: %s" % util.hexprint(data))
614

    
615
        recvd = b""
616
        # Radio echos every block received
617
        for byte in data:
618
            time.sleep(0.01)
619
            pipe.write(bytes([byte]))
620
            # flush & sleep so don't loose ack
621
            pipe.flush()
622
            time.sleep(0.015)
623
            recvd += pipe.read(1)  # chew the echo
624
        LOG.debug("Echo was %s" % util.hexprint(recvd))
625
        LOG.debug("Bytes sent: %i" % len(data))
626

    
627
        # Radio does not ack last block
628
        if (blocks < 8):
629
            buf = pipe.read(block)
630
            LOG.debug("ACK attempt: " + util.hexprint(buf))
631
            if buf and buf[0] != yaesu_clone.CMD_ACK:
632
                buf = pipe.read(block)
633
            if not buf or buf[-1] != yaesu_clone.CMD_ACK:
634
                raise errors.RadioError("Radio did not ack block %i" % blocks)
635

    
636
        total_written += len(data)
637
        _status()
638
        pos += block
639

    
640
    pipe.read(pos)  # Chew the echo if using a 2-pin cable
641

    
642
    LOG.debug("Clone completed in %i seconds" % (time.time() - start))
(10-10/12)