Project

General

Profile

Bug #4591 » ft7100.py

d0885068 - Dan Smith, 04/07/2023 04:48 PM

 
1
# Copyright 2011 Dan Smith <dsmith@danplanet.com>
2
#
3
# FT-2900-specific modifications by Richard Cochran, <ag6qr@sonic.net>
4
# Initial work on settings by Chris Fosnight, <chris.fosnight@gmail.com>
5
# FT-7100-specific modifications by Bruno Maire, <bruno@e48.ch>
6
#
7
# This program is free software: you can redistribute it and/or modify
8
# it under the terms of the GNU General Public License as published by
9
# the Free Software Foundation, either version 3 of the License, or
10
# (at your option) any later version.
11
#
12
# This program is distributed in the hope that it will be useful,
13
# but WITHOUT ANY WARRANTY; without even the implied warranty of
14
# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
15
# GNU General Public License for more details.
16
#
17
# You should have received a copy of the GNU General Public License
18
# along with this program.  If not, see <http://www.gnu.org/licenses/>.
19

    
20
import time
21
import logging
22

    
23
from chirp import util, memmap, chirp_common, bitwise, directory, errors
24
from chirp.drivers.yaesu_clone import YaesuCloneModeRadio
25
from chirp.settings import RadioSetting, RadioSettingGroup, \
26
    RadioSettingValueList, RadioSettingValueString, RadioSettings, \
27
    RadioSettingValueInteger, RadioSettingValueBoolean
28

    
29

    
30
LOG = logging.getLogger(__name__)
31

    
32
ACK = b"\x06"
33
NB_OF_BLOCKS = 248
34
BLOCK_LEN = 32
35

    
36

    
37
def _send(pipe, data):
38
    time.sleep(0.035)   # Same delay as "FT7100 Programmer" from RT Systems
39
    # pipe.write(data) --> It seems, that the single bytes are sent too fast
40
    # so send character per character with a delay
41
    for ch in data:
42
        pipe.write(bytes([ch]))
43
        time.sleep(0.0012)  # 0.0011 is to short. No ACK after a few packets
44
    echo = pipe.read(len(data))
45
    if data == b"":
46
        raise Exception("Failed to read echo."
47
                        " Maybe serial hardware not connected."
48
                        " Maybe radio not powered or not in receiving mode.")
49
    if data != echo:
50
        LOG.debug("expecting echo\n%s\n", util.hexprint(data))
51
        LOG.debug("got echo\n%s\n", util.hexprint(echo))
52
        raise Exception("Got false echo. Expected: %r, got: %r.",
53
                        data, echo)
54

    
55

    
56
def _send_ack(pipe):
57
    time.sleep(0.01)  # Wait for radio input buffer ready
58
    # time.sleep(0.0003) is the absolute working minimum.
59
    # This delay is not critical for the transfer as there are not many ACKs.
60
    _send(pipe, ACK)
61

    
62

    
63
def _wait_for_ack(pipe):
64
    echo = pipe.read(1)
65
    if echo == b"":
66
        raise Exception("Failed to read ACK. No response from radio.")
67
    if echo != ACK:
68
        raise Exception("Failed to read ACK.  Expected: %r, got: %r.",
69
                        ACK, echo)
70

    
71

    
72
def _download(radio):
73
    LOG.debug("in _download\n")
74
    data = b""
75
    for _i in range(0, 60):
76
        chunk = radio.pipe.read(BLOCK_LEN)
77
        LOG.debug("Header:\n%s", util.hexprint(data))
78
        data += chunk
79
        if data == radio.IDBLOCK:
80
            break
81
        if len(data) > len(radio.IDBLOCK):
82
            break
83
    if data == b"":
84
        raise Exception("Got no data from radio.")
85
    if data != radio.IDBLOCK:
86
        raise Exception("Got false header. Expected: %r, got: %r." % (
87
                          radio.IDBLOCK, data))
88
    _send_ack(radio.pipe)
89

    
90
    # read 16 Byte block
91
    # and ignore it because it is constant. This might be a bug.
92
    # It was built in at the very beginning and discovered very late that the
93
    # data might be necessary later to write to the radio.
94
    # Now the data is hardcoded in _upload(radio)
95
    data = radio.pipe.read(16)
96
    _send_ack(radio.pipe)
97
    LOG.debug('Magic 16-byte chunk:\n%s' % util.hexprint(data))
98

    
99
    # initialize data, the big var that holds all memory
100
    data = b""
101
    for block_nr in range(NB_OF_BLOCKS):
102
        chunk = radio.pipe.read(BLOCK_LEN)
103
        if len(chunk) != BLOCK_LEN:
104
            LOG.debug("Block %i ", block_nr)
105
            LOG.debug("Got: %i:\n%s", len(chunk), util.hexprint(chunk))
106
            LOG.debug("len chunk is %i\n", len(chunk))
107
            raise Exception("Failed to get full data block")
108
        else:
109
            data += chunk
110
        _send_ack(radio.pipe)
111

    
112
        if radio.status_fn:
113
            status = chirp_common.Status()
114
            status.max = NB_OF_BLOCKS * BLOCK_LEN
115
            status.cur = len(data)
116
            status.msg = "Cloning from radio"
117
            radio.status_fn(status)
118

    
119
    LOG.debug("Total: %i", len(data))
120
    _send_ack(radio.pipe)
121

    
122
    # for debugging purposes, dump the channels, in hex.
123
    for _i in range(0, (NB_OF_BLOCKS * BLOCK_LEN) // 26):
124
        _start_data = 4 + 26 * _i
125
        chunk = data[_start_data:_start_data + 26]
126
        LOG.debug("channel %i:\n%s", _i-21, util.hexprint(chunk))
127

    
128
    return memmap.MemoryMapBytes(data)
129

    
130

    
131
def _upload(radio):
132
    data = radio.pipe.read(256)  # Clear buffer
133
    _send(radio.pipe, radio.IDBLOCK)
134
    _wait_for_ack(radio.pipe)
135

    
136
    # write 16 Byte block
137
    # If there should be a problem, see remarks in _download(radio)
138
    _send(radio.pipe, b"\xEE\x77\x01\x00\x0E\x07\x0E\x07"
139
                      b"\x00\x00\x00\x00\x00\x02\x00\x00")
140
    _wait_for_ack(radio.pipe)
141

    
142
    for block_nr in range(NB_OF_BLOCKS):
143
        data = radio.get_mmap()[block_nr * BLOCK_LEN:
144
                                (block_nr + 1) * BLOCK_LEN]
145
        LOG.debug("Writing block_nr %i:\n%s", block_nr, util.hexprint(data))
146
        _send(radio.pipe, data)
147
        _wait_for_ack(radio.pipe)
148

    
149
        if radio.status_fn:
150
            status = chirp_common.Status()
151
            status.max = NB_OF_BLOCKS * BLOCK_LEN
152
            status.cur = block_nr * BLOCK_LEN
153
            status.msg = "Cloning to radio"
154
            radio.status_fn(status)
155
        block_nr += 1
156

    
157

    
158
MEM_FORMAT = """
159
struct mem {
160
  u8   is_used:1,
161
       is_masked:1,
162
       is_skip:1,
163
       unknown11:3,
164
       show_name:1,
165
       is_split:1;
166
  u8   unknown2;
167
  ul32 freq_rx_Hz;
168
  ul32 freq_tx_Hz;
169
  ul16 offset_10khz;
170
  u8   unknown_dependent_of_band_144_b0000_430_b0101:4,
171
       tuning_step_index_1_2:4;
172
  u8   unknown51:2,
173
       is_offset_minus:1,
174
       is_offset_plus:1,
175
       unknown52:1,
176
       tone_mode_index:3;
177
  u8   tone_index;
178
  u8   dtcs_index;
179
  u8   is_mode_am:1,
180
       unknown71:2,
181
       is_packet96:1
182
       unknown72:2,
183
       power_index:2;
184
  u8   unknown81:2,
185
       tuning_step_index_2_2:4,
186
       unknown82:2;
187
  char name[6];
188
  u8   unknown9;
189
  u8   unknownA;
190
};
191

    
192
// Settings are often present multiple times.
193
// The memories which is written to are mapped here
194
struct
195
{
196
#seekto 0x41;
197
  u8        current_band;
198
#seekto 0xa1;
199
  u8        apo;
200
#seekto 0xa2;
201
  u8        ars_vhf;
202
#seekto 0xe2;
203
  u8        ars_uhf;
204
#seekto 0xa3;
205
  u8        arts_vhf;
206
#seekto 0xa3;
207
  u8        arts_uhf;
208
#seekto 0xa4;
209
  u8        beep;
210
#seekto 0xa5;
211
  u8        cwid;
212
#seekto 0x80;
213
  char        cwidw[6];
214
#seekto 0xa7;
215
  u8        dim;
216
#seekto 0xaa;
217
  u8        dcsnr_vhf;
218
#seekto 0xea;
219
  u8        dcsnr_uhf;
220
#seekto 0xab;
221
  u8        disp;
222
#seekto 0xac;
223
  u8        dtmfd;
224
#seekto 0xad;
225
  u8        dtmfs;
226
#seekto 0xae;
227
  u8        dtmfw;
228
#seekto 0xb0;
229
  u8        lockt;
230
#seekto 0xb1;
231
  u8        mic;
232
#seekto 0xb2;
233
  u8        mute;
234
#seekto 0xb4;
235
  u8        button[4];
236
#seekto 0xb8;
237
  u8        rf_sql_vhf;
238
#seekto 0xf8;
239
  u8        rf_sql_uhf;
240
#seekto 0xb9;
241
  u8        scan_vhf;
242
#seekto 0xf9;
243
  u8        scan_uhf;
244
#seekto 0xbc;
245
  u8        speaker_cnt;
246
#seekto 0xff;
247
  u8        tot;
248
#seekto 0xc0;
249
  u8        txnar_vhf;
250
#seekto 0x100;
251
  u8        txnar_uhf;
252
#seekto 0xc1;
253
  u8        vfotr;
254
#seekto 0xc2;
255
  u8        am;
256
} overlay;
257

    
258
// All known memories
259
#seekto 0x20;
260
  u8        nb_mem_used_vhf;
261
#seekto 0x22;
262
  u8        nb_mem_used_vhf_and_limits;
263
#seekto 0x24;
264
  u8        nb_mem_used_uhf;
265
#seekto 0x26;
266
  u8        nb_mem_used_uhf_and_limits;
267

    
268
#seekto 0x41;
269
  u8        current_band;
270

    
271
#seekto 0x42;
272
  u8        current_nb_mem_used_vhf_maybe_not;
273

    
274
#seekto 0x4c;
275
  u8        priority_channel_maybe_1;   // not_implemented
276
  u8        priority_channel_maybe_2;   // not_implemented
277
  u8        priority_channel;           // not_implemented
278

    
279
#seekto 0x87;
280
  u8        opt_01_apo_1_4;
281
#seekto 0xa1;
282
  u8        opt_01_apo_2_4;
283
#seekto 0xc5;
284
  u8        opt_01_apo_3_4;
285
#seekto 0xe1;
286
  u8        opt_01_apo_4_4;
287

    
288
#seekto 0x88;
289
  u8        opt_02_ars_vhf_1_2;
290
#seekto 0xa2;
291
  u8        opt_02_ars_vhf_2_2;
292
#seekto 0xc6;
293
  u8        opt_02_ars_uhf_1_2;
294
#seekto 0xe2;
295
  u8        opt_02_ars_uhf_2_2;
296

    
297
#seekto 0x89;
298
  u8        opt_03_arts_mode_vhf_1_2;
299
#seekto 0xa3;
300
  u8        opt_03_arts_mode_vhf_2_2;
301
#seekto 0xc7;
302
  u8        opt_03_arts_mode_uhf_1_2;
303
#seekto 0xa3;
304
  u8        opt_03_arts_mode_vhf_2_2;
305

    
306
#seekto 0x8a;
307
  u8        opt_04_beep_1_2;
308
#seekto 0xa4;
309
  u8        opt_04_beep_2_2;
310

    
311
#seekto 0x8b;
312
  u8        opt_05_cwid_on_1_4;
313
#seekto 0xa5;
314
  u8        opt_05_cwid_on_2_4;
315
#seekto 0xc9;
316
  u8        opt_05_cwid_on_3_4;
317
#seekto 0xe5;
318
  u8        opt_05_cwid_on_4_4;
319

    
320
#seekto 0x80;
321
  char        opt_06_cwidw[6];
322

    
323
#seekto 0x8d;
324
  u8        opt_07_dim_1_4;
325
#seekto 0xa7;
326
  u8        opt_07_dim_2_4;
327
#seekto 0xcb;
328
  u8        opt_07_dim_3_4;
329
#seekto 0xe7;
330
  u8        opt_07_dim_4_4;
331

    
332
#seekto 0x90;
333
  u8        opt_10_dcsnr_vhf_1_2;
334
#seekto 0xaa;
335
  u8        opt_10_dcsnr_vhf_2_2;
336
#seekto 0xce;
337
  u8        opt_10_dcsnr_uhf_1_2;
338
#seekto 0xea;
339
  u8        opt_10_dcsnr_uhf_2_2;
340

    
341
#seekto 0x91;
342
  u8        opt_11_disp_1_4;
343
#seekto 0xab;
344
  u8        opt_11_disp_2_4;
345
#seekto 0xcf;
346
  u8        opt_11_disp_3_4;
347
#seekto 0xeb;
348
  u8        opt_11_disp_4_4;
349

    
350
#seekto 0x92;
351
  u8        opt_12_dtmf_delay_1_4;
352
#seekto 0xac;
353
  u8        opt_12_dtmf_delay_2_4;
354
#seekto 0xd0;
355
  u8        opt_12_dtmf_delay_3_4;
356
#seekto 0xec;
357
  u8        opt_12_dtmf_delay_4_4;
358

    
359
#seekto 0x93;
360
  u8        opt_13_dtmf_speed_1_4;
361
#seekto 0xad;
362
  u8        opt_13_dtmf_speed_2_4;
363
#seekto 0xd1;
364
  u8        opt_13_dtmf_speed_3_4;
365
#seekto 0xed;
366
  u8        opt_13_dtmf_speed_4_4;
367

    
368
#seekto 0x94;
369
  u8        opt_14_dtmfw_index_1_4;
370
#seekto 0xae;
371
  u8        opt_14_dtmfw_index_2_4;
372
#seekto 0xd2;
373
  u8        opt_14_dtmfw_index_3_4;
374
#seekto 0xee;
375
  u8        opt_14_dtmfw_index_4_4;
376

    
377
#seekto 0x96;
378
  u8        opt_16_lockt_1_4;
379
#seekto 0xb0;
380
  u8        opt_16_lockt_2_4;
381
#seekto 0xd4;
382
  u8        opt_16_lockt_3_4;
383
#seekto 0xf0;
384
  u8        opt_16_lockt_4_4;
385

    
386
#seekto 0x97;
387
  u8        opt_17_mic_MH48_1_4;
388
#seekto 0xb1;
389
  u8        opt_17_mic_MH48_2_4;
390
#seekto 0xd5;
391
  u8        opt_17_mic_MH48_3_4;
392
#seekto 0xf1;
393
  u8        opt_17_mic_MH48_4_4;
394

    
395
#seekto 0x98;
396
  u8        opt_18_mute_1_4;
397
#seekto 0xb2;
398
  u8        opt_18_mute_2_4;
399
#seekto 0xd6;
400
  u8        opt_18_mute_3_4;
401
#seekto 0xf2;
402
  u8        opt_18_mute_4_4;
403

    
404
#seekto 0x9a;
405
  u8        opt_20_pg_p_1_4[4];
406
#seekto 0xb4;
407
  u8        opt_20_pg_p_2_4[4];
408
#seekto 0xd8;
409
  u8        opt_20_pg_p_3_4[4];
410
#seekto 0xf4;
411
  u8        opt_20_pg_p_4_4[4];
412

    
413
#seekto 0x9e;
414
  u8        opt_24_rf_sql_vhf_1_2;
415
#seekto 0xb8;
416
  u8        opt_24_rf_sql_vhf_2_2;
417
#seekto 0xdc;
418
  u8        opt_24_rf_sql_uhf_1_2;
419
#seekto 0xf8;
420
  u8        opt_24_rf_sql_uhf_2_2;
421

    
422
#seekto 0x9f;
423
  u8        opt_25_scan_resume_vhf_1_2;
424
#seekto 0xb9;
425
  u8        opt_25_scan_resume_vhf_2_2;
426
#seekto 0xdd;
427
  u8        opt_25_scan_resume_uhf_1_2;
428
#seekto 0xf9;
429
  u8        opt_25_scan_resume_uhf_2_2;
430

    
431
#seekto 0xbc;
432
  u8        opt_28_speaker_cnt_1_2;
433
#seekto 0xfc;
434
  u8        opt_28_speaker_cnt_2_2;
435

    
436
#seekto 0xbf;
437
  u8        opt_31_tot_1_2;
438
#seekto 0xff;
439
  u8        opt_31_tot_2_2;
440

    
441
#seekto 0xc0;
442
  u8        opt_32_tx_nar_vhf;
443
#seekto 0x100;
444
  u8        opt_32_tx_nar_uhf;
445

    
446
#seekto 0xc1;
447
  u8        opt_33_vfo_tr;
448

    
449
#seekto 0xc2;
450
  u8        opt_34_am_1_2;
451
#seekto 0x102;
452
  u8        opt_34_am_2_2;
453

    
454
#seekto 260;
455
struct {
456
  struct mem mem_struct;
457
  char        fill_ff[2];
458
} unknown00;
459

    
460
struct {
461
  struct mem mem_struct;
462
  char        fill_00[6];
463
} current_vfo_vhf_uhf[2];
464

    
465
struct {
466
  struct mem mem_struct;
467
  char        fill_ff[6];
468
} current_mem_vhf_uhf[2];
469

    
470
struct {
471
  struct mem mem_struct;
472
  char        fill_ff[6];
473
} home_vhf_uhf[2];
474

    
475
struct {
476
  struct mem mem_struct;
477
  char        fill_010003000000[6];
478
} vhome;
479

    
480
struct {
481
  struct mem mem_struct;
482
  char        fill_010001000000[6];
483
} unknown01;
484

    
485
struct {
486
  char name[32];
487
} Vertex_Standard_AH003M_Backup_DT;
488

    
489
struct mem
490
  memory[260];
491

    
492
struct {
493
  char name[24];
494
} Vertex_Standard_AH003M;
495

    
496
struct {
497
  u8 dtmf[16];
498
} dtmf_mem[16];
499
"""
500

    
501
MODES_VHF = ["FM", "AM"]
502
MODES_UHF = ["FM"]      # AM can be set but is ignored by the radio
503
DUPLEX = ["", "-", "+", "split"]
504
TONE_MODES_RADIO = ["", "Tone", "TSQL", "CTCSS Bell", "DTCS"]
505
TONE_MODES = ["", "Tone", "TSQL", "DTCS"]
506
POWER_LEVELS = [
507
    chirp_common.PowerLevel("Low", watts=5),
508
    chirp_common.PowerLevel("Low2", watts=10),
509
    chirp_common.PowerLevel("Low3", watts=20),
510
    chirp_common.PowerLevel("High", watts=35),
511
    ]
512
TUNING_STEPS = [5.0, 10.0, 12.5, 15.0, 20.0, 25.0, 50.0]
513
SKIP_VALUES = ["", "S"]
514
CHARSET = r"!\"#$%&'()*+,-./0123456789:;<=>?@ABCDEFGHIJKLMNOPQRSTUVWXYZ[\]^ _"
515
DTMF_CHARSET = "0123456789*# "
516
SPECIAL_CHANS = ['VFO-VHF', 'VFO-UHF', 'Home-VHF', 'Home-UHF', 'VFO', 'Home']
517
SCAN_LIMITS = ["L1", "U1", "L2", "U2", "L3", "U3", "L4", "U4", "L5", "U5"]
518

    
519

    
520
def do_download(radio):
521
    """This is your download function"""
522
    return _download(radio)
523

    
524

    
525
@directory.register
526
class FT7100Radio(YaesuCloneModeRadio):
527

    
528
    """Yaesu FT-7100M"""
529
    MODEL = "FT-7100M"
530
    VARIANT = ""
531
    IDBLOCK = b"Vartex Standard AH003M M-Map V04"
532
    BAUD_RATE = 9600
533
    NEEDS_COMPAT_SERIAL = False
534

    
535
    # Return information about this radio's features, including
536
    # how many memories it has, what bands it supports, etc
537
    def get_features(self):
538
        LOG.debug("get_features")
539
        rf = chirp_common.RadioFeatures()
540
        rf.has_bank = False
541

    
542
        rf.memory_bounds = (0, 240)
543
        # This radio supports 120 + 10 + 120 + 10 = 260 memories
544
        # These are zero based for chirpc
545
        rf.valid_bands = [
546
            (108000000, 180000000),  # Supports 2-meters tx
547
            (320000000, 999990000),  # Supports 70-centimeters tx
548
            ]
549
        rf.can_odd_split = True
550
        rf.has_ctone = True
551
        rf.has_rx_dtcs = True
552
        rf.has_cross = False
553
        rf.has_dtcs_polarity = False
554
        rf.has_bank = False
555
        rf.has_bank_names = False
556
        rf.has_settings = True
557
        rf.has_sub_devices = True
558
        rf.valid_tuning_steps = TUNING_STEPS
559
        rf.valid_modes = MODES_VHF
560
        rf.valid_tmodes = TONE_MODES
561
        rf.valid_power_levels = POWER_LEVELS
562
        rf.valid_duplexes = DUPLEX
563
        rf.valid_skips = SKIP_VALUES
564
        rf.valid_name_length = 6
565
        rf.valid_characters = CHARSET
566
        rf.valid_special_chans = SPECIAL_CHANS
567
        return rf
568

    
569
    def sync_in(self):
570
        start = time.time()
571
        try:
572
            self._mmap = _download(self)
573
        except errors.RadioError:
574
            raise
575
        except Exception as e:
576
            raise errors.RadioError("Failed to communicate with radio: %s" % e)
577
        LOG.info("Downloaded in %.2f sec", (time.time() - start))
578
        self.process_mmap()
579

    
580
    def sync_out(self):
581
        self.pipe.timeout = 1
582
        start = time.time()
583
        try:
584
            _upload(self)
585
        except errors.RadioError:
586
            raise
587
        except Exception as e:
588
            raise errors.RadioError("Failed to communicate with radio: %s" % e)
589
        LOG.info("Uploaded in %.2f sec", (time.time() - start))
590

    
591
    def process_mmap(self):
592
        self._memobj = bitwise.parse(MEM_FORMAT, self._mmap)
593

    
594
    def get_raw_memory(self, number):
595
        return repr(self._memobj.memory[number])
596

    
597
    def get_memory(self, number):
598
        LOG.debug("get_memory Number: %r", number)
599

    
600
        # Create a high-level memory object to return to the UI
601
        mem = chirp_common.Memory()
602

    
603
        # Get a low-level memory object mapped to the image
604
        if isinstance(number, int) and number < 0:
605
            number = SPECIAL_CHANS[number + 10]
606
        if isinstance(number, str):
607
            mem.number = -10 + SPECIAL_CHANS.index(number)
608
            mem.extd_number = number
609
            band = 0
610
            if self._memobj.overlay.current_band != 0:
611
                band = 1
612
            if number == 'VFO-VHF':
613
                _mem = self._memobj.current_vfo_vhf_uhf[0].mem_struct
614
            elif number == 'VFO-UHF':
615
                _mem = self._memobj.current_vfo_vhf_uhf[1].mem_struct
616
            elif number == 'Home-VHF':
617
                _mem = self._memobj.home_vhf_uhf[0].mem_struct
618
            elif number == 'Home-UHF':
619
                _mem = self._memobj.home_vhf_uhf[1].mem_struct
620
            elif number == 'VFO':
621
                _mem = self._memobj.current_vfo_vhf_uhf[band].mem_struct
622
            elif number == 'Home':
623
                _mem = self._memobj.home_vhf_uhf[band].mem_struct
624
            _mem.is_used = True
625
        else:
626
            mem.number = number                 # Set the memory number
627
            _mem = self._memobj.memory[number]
628
            upper_channel = self._memobj.nb_mem_used_vhf
629
            upper_limit = self._memobj.nb_mem_used_vhf_and_limits
630
            if number >= upper_channel and number < upper_limit:
631
                i = number - upper_channel
632
                mem.extd_number = SCAN_LIMITS[i]
633
            if number >= 260-10:
634
                i = number - (260-10)
635
                mem.extd_number = SCAN_LIMITS[i]
636

    
637
        # Convert your low-level frequency to Hertz
638
        mem.freq = int(_mem.freq_rx_Hz)
639
        mem.name = str(_mem.name).rstrip()  # Set the alpha tag
640

    
641
        mem.rtone = chirp_common.TONES[_mem.tone_index]
642
        mem.ctone = chirp_common.TONES[_mem.tone_index]
643

    
644
        mem.dtcs = chirp_common.DTCS_CODES[_mem.dtcs_index]
645
        mem.rx_dtcs = chirp_common.DTCS_CODES[_mem.dtcs_index]
646

    
647
        tmode_radio = TONE_MODES_RADIO[_mem.tone_mode_index]
648
        # CTCSS Bell is TSQL plus a flag in the extra setting
649
        is_ctcss_bell = tmode_radio == "CTCSS Bell"
650
        if is_ctcss_bell:
651
            mem.tmode = "TSQL"
652
        else:
653
            mem.tmode = tmode_radio
654

    
655
        mem.duplex = ""
656
        if _mem.is_offset_plus:
657
            mem.duplex = "+"
658
        elif _mem.is_offset_minus:
659
            mem.duplex = "-"
660
        elif _mem.is_split:
661
            mem.duplex = "split"
662

    
663
        if _mem.is_split:
664
            mem.offset = int(_mem.freq_tx_Hz)
665
        else:
666
            mem.offset = int(_mem.offset_10khz)*10000   # 10kHz to Hz
667

    
668
        if _mem.is_mode_am:
669
            mem.mode = "AM"
670
        else:
671
            mem.mode = "FM"
672

    
673
        mem.power = POWER_LEVELS[_mem.power_index]
674

    
675
        mem.tuning_step = TUNING_STEPS[_mem.tuning_step_index_1_2]
676

    
677
        if _mem.is_skip:
678
            mem.skip = "S"
679
        else:
680
            mem.skip = ""
681

    
682
        # mem.comment = ""
683

    
684
        mem.empty = not _mem.is_used
685

    
686
        mem.extra = RadioSettingGroup("Extra", "extra")
687

    
688
        rs = RadioSetting("show_name", "Show Name",
689
                          RadioSettingValueBoolean(_mem.show_name))
690
        mem.extra.append(rs)
691
        rs = RadioSetting("is_masked", "Is Masked",
692
                          RadioSettingValueBoolean(_mem.is_masked))
693
        mem.extra.append(rs)
694
        rs = RadioSetting("is_packet96", "Packet 9600",
695
                          RadioSettingValueBoolean(_mem.is_packet96))
696
        mem.extra.append(rs)
697
        rs = RadioSetting("is_ctcss_bell", "CTCSS Bell",
698
                          RadioSettingValueBoolean(is_ctcss_bell))
699
        mem.extra.append(rs)
700

    
701
        return mem
702

    
703
    def set_memory(self, mem):
704
        LOG.debug("set_memory Number: %r", mem.number)
705
        if mem.number < 0:
706
            number = SPECIAL_CHANS[mem.number+10]
707
            if number == 'VFO-VHF':
708
                _mem = self._memobj.current_vfo_vhf_uhf[0].mem_struct
709
            elif number == 'VFO-UHF':
710
                _mem = self._memobj.current_vfo_vhf_uhf[1].mem_struct
711
            elif number == 'Home-VHF':
712
                _mem = self._memobj.home_vhf_uhf[0].mem_struct
713
            elif number == 'Home-UHF':
714
                _mem = self._memobj.home_vhf_uhf[1].mem_struct
715
            else:
716
                raise errors.RadioError("Unexpected Memory Number: %r",
717
                                        mem.number)
718
        else:
719
            _mem = self._memobj.memory[mem.number]
720

    
721
        _mem.name = mem.name.ljust(6)
722

    
723
        _mem.tone_index = chirp_common.TONES.index(mem.rtone)
724
        _mem.dtcs_index = chirp_common.DTCS_CODES.index(mem.dtcs)
725
        if mem.tmode == "TSQL":
726
            _mem.tone_index = chirp_common.TONES.index(mem.ctone)
727
        if mem.tmode == "DTSC-R":
728
            _mem.dtcs_index = chirp_common.DTCS_CODES.index(mem.rx_dtcs)
729

    
730
        _mem.is_offset_plus = 0
731
        _mem.is_offset_minus = 0
732
        _mem.is_split = 0
733
        _mem.freq_rx_Hz = mem.freq
734
        _mem.freq_tx_Hz = mem.freq
735
        if mem.duplex == "+":
736
            _mem.is_offset_plus = 1
737
            _mem.freq_tx_Hz = mem.freq + mem.offset
738
            _mem.offset_10khz = int(mem.offset/10000)
739
        elif mem.duplex == "-":
740
            _mem.is_offset_minus = 1
741
            _mem.freq_tx_Hz = mem.freq - mem.offset
742
            _mem.offset_10khz = int(mem.offset/10000)
743
        elif mem.duplex == "split":
744
            _mem.is_split = 1
745
            _mem.freq_tx_Hz = mem.offset
746
            # No change to _mem.offset_10khz
747

    
748
        _mem.is_mode_am = mem.mode == "AM"
749

    
750
        if mem.power:
751
            _mem.power_index = POWER_LEVELS.index(mem.power)
752

    
753
        _mem.tuning_step_index_1_2 = TUNING_STEPS.index(mem.tuning_step)
754

    
755
        _mem.is_skip = mem.skip == "S"
756

    
757
        _mem.is_used = not mem.empty
758

    
759
        # if name not empty: show name
760
        _mem.show_name = mem.name.strip() != ""
761

    
762
        # In testmode there is no setting in mem.extra
763
        # This is why the following line is not located in the else part of
764
        # the if structure below
765
        _mem.tone_mode_index = TONE_MODES_RADIO.index(mem.tmode)
766

    
767
        for setting in mem.extra:
768
            if setting.get_name() == "is_ctcss_bell":
769
                if mem.tmode == "TSQL" and setting.value:
770
                    _mem.tone_mode_index = TONE_MODES_RADIO.index("CTCSS Bell")
771
            else:
772
                setattr(_mem, setting.get_name(), setting.value)
773

    
774
        LOG.debug("encoded mem\n%s\n", (util.hexprint(_mem.get_raw()[0:25])))
775
        LOG.debug(repr(_mem))
776

    
777
    def get_settings(self):
778
        common = RadioSettingGroup("common", "Common Settings")
779
        band = RadioSettingGroup("band", "Band dependent Settings")
780
        arts = RadioSettingGroup("arts",
781
                                 "Auto Range Transponder System (ARTS)")
782
        dtmf = RadioSettingGroup("dtmf", "DTMF Settings")
783
        mic_button = RadioSettingGroup("mic_button", "Microphone Buttons")
784
        setmode = RadioSettings(common, band, arts, dtmf, mic_button)
785

    
786
        _overlay = self._memobj.overlay
787

    
788
        # numbers and names of settings refer to the way they're
789
        # presented in the set menu, as well as the list starting on
790
        # page 49 of the manual
791

    
792
        # 1 Automatic Power Off
793
        opts = [
794
            "Off", "30 Min",
795
            "1 Hour", "1.5 Hours",
796
            "2 Hours", "2.5 Hours",
797
            "3 Hours", "3.5 Hours",
798
            "4 Hours", "4.5 Hours",
799
            "5 Hours", "5.5 Hours",
800
            "6 Hours", "6.5 Hours",
801
            "7 Hours", "7.5 Hours",
802
            "8 Hours", "8.5 Hours",
803
            "9 Hours", "9.5 Hours",
804
            "10 Hours", "10.5 Hours",
805
            "11 Hours", "11.5 Hours",
806
            "12 Hours",
807
            ]
808
        common.append(
809
            RadioSetting(
810
                "apo", "Automatic Power Off",
811
                RadioSettingValueList(opts, opts[_overlay.apo])))
812

    
813
        # 2 Automatic Repeater Shift function
814
        opts = ["Off", "On"]
815
        band.append(
816
            RadioSetting(
817
                "ars_vhf", "Automatic Repeater Shift VHF",
818
                RadioSettingValueList(opts, opts[_overlay.ars_vhf])))
819
        band.append(
820
            RadioSetting(
821
                "ars_uhf", "Automatic Repeater Shift UHF",
822
                RadioSettingValueList(opts, opts[_overlay.ars_uhf])))
823

    
824
        # 3  Selects the ARTS mode.
825
        # -> Only useful to set it on the radio directly
826

    
827
        # 4 Enables/disables the key/button beeper.
828
        opts = ["Off", "On"]
829
        common.append(
830
            RadioSetting(
831
                "beep", "Key/Button Beep",
832
                RadioSettingValueList(opts, opts[_overlay.beep])))
833

    
834
        # 5 Enables/disables the CW IDer during ARTS operation.
835
        opts = ["Off", "On"]
836
        arts.append(
837
            RadioSetting(
838
                "cwid", "Enables/Disables the CW ID",
839
                RadioSettingValueList(opts, opts[_overlay.cwid])))
840

    
841
        # 6  Callsign during ARTS operation.
842
        cwidw = _overlay.cwidw.get_raw()
843
        cwidw = cwidw.rstrip('\x00')
844
        val = RadioSettingValueString(0, 6, cwidw)
845
        val.set_charset(CHARSET)
846
        rs = RadioSetting("cwidw", "CW Identifier Callsign", val)
847

    
848
        def apply_cwid(setting):
849
            value_string = setting.value.get_value()
850
            _overlay.cwidw.set_value(value_string)
851
        rs.set_apply_callback(apply_cwid)
852
        arts.append(rs)
853

    
854
        # 7 Front panel display's illumination level.
855
        opts = ["0: Off", "1: Max", "2", "3", "4", "5", "6", "7: Min"]
856
        common.append(
857
            RadioSetting(
858
                "dim", "Display Illumination",
859
                RadioSettingValueList(opts, opts[_overlay.dim])))
860

    
861
        # 8 Setting the DCS code number.
862
        #   Note: This Menu item can be set independently for each band,
863
        #         and independently in each memory.
864

    
865
        # 9 Activates the DCS Code Search
866
        # -> Only useful if set on radio itself
867

    
868
        # 10 Selects 'Normal' or 'Inverted' DCS coding.
869
        opts = ["TRX Normal", "RX Reversed", "TX Reversed", "TRX Reversed"]
870
        band.append(
871
            RadioSetting(
872
                "dcsnr_vhf", "DCS coding VHF",
873
                RadioSettingValueList(opts, opts[_overlay.dcsnr_vhf])))
874
        band.append(
875
            RadioSetting(
876
                "dcsnr_uhf", "DCS coding UHF",
877
                RadioSettingValueList(opts, opts[_overlay.dcsnr_uhf])))
878

    
879
        # 11 Selects the 'sub' band display format
880
        opts = ["Frequency", "Off / Sub Band disabled",
881
                "DC Input Voltage", "CW ID"]
882
        common.append(
883
            RadioSetting(
884
                "disp", "Sub Band Display Format",
885
                RadioSettingValueList(opts, opts[_overlay.disp])))
886

    
887
        # 12 Setting the DTMF Autodialer delay time
888
        opts = ["50 ms", "250 ms", "450 ms", "750 ms", "1 s"]
889
        dtmf.append(
890
            RadioSetting(
891
                "dtmfd", "Autodialer delay time",
892
                RadioSettingValueList(opts, opts[_overlay.dtmfd])))
893

    
894
        # 13 Setting the DTMF Autodialer sending speed
895
        opts = ["50 ms", "75 ms", "100 ms"]
896
        dtmf.append(
897
            RadioSetting(
898
                "dtmfs", "Autodialer sending speed",
899
                RadioSettingValueList(opts, opts[_overlay.dtmfs])))
900

    
901
        # 14 Current DTMF Autodialer memory
902
        rs = RadioSetting("dtmfw", "Current Autodialer memory",
903
                          RadioSettingValueInteger(1, 16, _overlay.dtmfw + 1))
904

    
905
        def apply_dtmfw(setting):
906
            _overlay.dtmfw = setting.value.get_value() - 1
907
        rs.set_apply_callback(apply_dtmfw)
908
        dtmf.append(rs)
909

    
910
        # DTMF Memory
911
        for i in range(16):
912
            dtmf_string = ""
913
            for j in range(16):
914
                dtmf_char = ''
915
                dtmf_int = int(self._memobj.dtmf_mem[i].dtmf[j])
916
                if dtmf_int < 10:
917
                    dtmf_char = str(dtmf_int)
918
                elif dtmf_int == 14:
919
                    dtmf_char = '*'
920
                elif dtmf_int == 15:
921
                    dtmf_char = '#'
922
                elif dtmf_int == 255:
923
                    break
924
                dtmf_string += dtmf_char
925
            radio_setting_value_string = RadioSettingValueString(0, 16,
926
                                                                 dtmf_string)
927
            radio_setting_value_string.set_charset(DTMF_CHARSET)
928
            rs = RadioSetting("dtmf_%02i" % i,
929
                              "DTMF Mem " + str(i+1),
930
                              radio_setting_value_string)
931

    
932
            def apply_dtmf(setting, index):
933
                radio_setting_value_string = setting.value.get_value().rstrip()
934
                j = 0
935
                for dtmf_char in radio_setting_value_string:
936
                    dtmf_int = 255
937
                    if dtmf_char in "0123456789":
938
                        dtmf_int = int(dtmf_char)
939
                    elif dtmf_char == '*':
940
                        dtmf_int = 14
941
                    elif dtmf_char == '#':
942
                        dtmf_int = 15
943
                    if dtmf_int < 255:
944
                        self._memobj.dtmf_mem[index].dtmf[j] = dtmf_int
945
                        j += 1
946
                if j < 16:
947
                    self._memobj.dtmf_mem[index].dtmf[j] = 255
948
            rs.set_apply_callback(apply_dtmf, i)
949
            dtmf.append(rs)
950

    
951
        # 16 Enables/disables the PTT switch lock
952
        opts = ["Off", "Band A", "Band B", "Both"]
953
        common.append(
954
            RadioSetting(
955
                "lockt", "PTT switch lock",
956
                RadioSettingValueList(opts, opts[_overlay.lockt])))
957

    
958
        # 17 Selects the Microphone type to be used
959
        opts = ["MH-42", "MH-48"]
960
        common.append(
961
            RadioSetting(
962
                "mic", "Microphone type",
963
                RadioSettingValueList(opts, opts[_overlay.mic])))
964

    
965
        # 18 Reduces the audio level on the sub receiver when the
966
        #    main receiver is active
967
        opts = ["Off", "On"]
968
        common.append(
969
            RadioSetting(
970
                "mute", "Mute Sub Receiver",
971
                RadioSettingValueList(opts, opts[_overlay.mute])))
972

    
973
        # 20 - 23 Programming the microphones button assignment
974
        buttons = [
975
            "ACC / P1",
976
            "P / P2",
977
            "P1 / P3",
978
            "P2 / P4",
979
            ]
980
        opts_button = ["Low", "Tone", "MHz", "Rev", "Home", "Band",
981
                       "VFO / Memory", "Sql Off", "1750 Hz Tone Call",
982
                       "Repeater", "Priority"]
983
        for i, button in enumerate(buttons):
984
            rs = RadioSetting(
985
                "button" + str(i), button,
986
                RadioSettingValueList(opts_button,
987
                                      opts_button[_overlay.button[i]]))
988

    
989
            def apply_button(setting, index):
990
                value_string = setting.value.get_value()
991
                value_int = opts_button.index(value_string)
992
                _overlay.button[index] = value_int
993
            rs.set_apply_callback(apply_button, i)
994
            mic_button.append(rs)
995

    
996
        # 24 Adjusts the RF SQL threshold level
997
        opts = ["Off", "S-1", "S-5", "S-9", "S-FULL"]
998
        band.append(
999
            RadioSetting(
1000
                "rf_sql_vhf", "RF Sql VHF",
1001
                RadioSettingValueList(opts, opts[_overlay.rf_sql_vhf])))
1002
        band.append(
1003
            RadioSetting(
1004
                "rf_sql_uhf", "RF Sql UHF",
1005
                RadioSettingValueList(opts, opts[_overlay.rf_sql_uhf])))
1006

    
1007
        # 25 Selects the Scan-Resume mode
1008
        opts = ["Busy", "Time"]
1009
        band.append(
1010
            RadioSetting(
1011
                "scan_vhf", "Scan-Resume VHF",
1012
                RadioSettingValueList(opts, opts[_overlay.scan_vhf])))
1013
        band.append(
1014
            RadioSetting(
1015
                "scan_uhf", "Scan-Resume UHF",
1016
                RadioSettingValueList(opts, opts[_overlay.scan_uhf])))
1017

    
1018
        # 28 Defining the audio path to the external speaker
1019
        opts = ["Off", "Band A", "Band B", "Both"]
1020
        common.append(
1021
            RadioSetting(
1022
                "speaker_cnt", "External Speaker",
1023
                RadioSettingValueList(opts, opts[_overlay.speaker_cnt])))
1024

    
1025
        # 31 Sets the Time-Out Timer
1026
        opts = ["Off", "Band A", "Band B", "Both"]
1027
        common.append(
1028
            RadioSetting(
1029
                "tot", "TX Time-Out [Min.] (0 = Off)",
1030
                RadioSettingValueInteger(0, 30, _overlay.tot)))
1031

    
1032
        # 32 Reducing the MIC Gain (and Deviation)
1033
        opts = ["Off", "On"]
1034
        band.append(
1035
            RadioSetting(
1036
                "txnar_vhf", "TX Narrowband VHF",
1037
                RadioSettingValueList(opts, opts[_overlay.txnar_vhf])))
1038
        band.append(
1039
            RadioSetting(
1040
                "txnar_uhf", "TX Narrowband UHF",
1041
                RadioSettingValueList(opts, opts[_overlay.txnar_uhf])))
1042

    
1043
        # 33 Enables/disables the VFO Tracking feature
1044
        opts = ["Off", "On"]
1045
        common.append(
1046
            RadioSetting(
1047
                "vfotr", "VFO Tracking",
1048
                RadioSettingValueList(opts, opts[_overlay.vfotr])))
1049

    
1050
        # 34 Selects the receiving mode on the VHF band
1051
        opts = ["Inhibit (only FM)", "AM", "Auto"]
1052
        common.append(
1053
            RadioSetting(
1054
                "am", "AM Mode",
1055
                RadioSettingValueList(opts, opts[_overlay.am])))
1056

    
1057
        # Current Band
1058
        opts = ["VHF", "UHF"]
1059
        common.append(
1060
            RadioSetting(
1061
                "current_band", "Current Band",
1062
                RadioSettingValueList(opts, opts[_overlay.current_band])))
1063

    
1064
        # Show number of VHF and UHF channels
1065
        val = RadioSettingValueString(0, 7,
1066
                                      str(int(self._memobj.nb_mem_used_vhf)))
1067
        val.set_mutable(False)
1068
        rs = RadioSetting("num_chan_vhf", "Number of VHF channels", val)
1069
        common.append(rs)
1070
        val = RadioSettingValueString(0, 7,
1071
                                      str(int(self._memobj.nb_mem_used_uhf)))
1072
        val.set_mutable(False)
1073
        rs = RadioSetting("num_chan_uhf", "Number of UHF channels", val)
1074
        common.append(rs)
1075

    
1076
        return setmode
1077

    
1078
    def set_settings(self, uisettings):
1079
        _overlay = self._memobj.overlay
1080
        for element in uisettings:
1081
            if not isinstance(element, RadioSetting):
1082
                self.set_settings(element)
1083
                continue
1084
            if not element.changed():
1085
                continue
1086

    
1087
            try:
1088
                name = element.get_name()
1089
                value = element.value
1090

    
1091
                if element.has_apply_callback():
1092
                    LOG.debug("Using apply callback")
1093
                    element.run_apply_callback()
1094
                else:
1095
                    setattr(_overlay, name, value)
1096

    
1097
                LOG.debug("Setting %s: %s", name, value)
1098
            except Exception:
1099
                LOG.debug(element.get_name())
1100
                raise
1101

    
1102
    def _get_upper_vhf_limit(self):
1103
        if self._memobj is None:
1104
            # test with tox has no _memobj
1105
            upper_vhf_limit = 120
1106
        else:
1107
            upper_vhf_limit = int(self._memobj.nb_mem_used_vhf)
1108
        return upper_vhf_limit
1109

    
1110
    def _get_upper_uhf_limit(self):
1111
        if self._memobj is None:
1112
            # test with tox has no _memobj
1113
            upper_uhf_limit = 120
1114
        else:
1115
            upper_uhf_limit = int(self._memobj.nb_mem_used_uhf)
1116
        return upper_uhf_limit
1117

    
1118
    @classmethod
1119
    def match_model(cls, filedata, filename):
1120
        return filedata[0x1ec0:0x1ec0+len(cls.IDBLOCK)] == cls.IDBLOCK.encode()
1121

    
1122
    @classmethod
1123
    def get_prompts(cls):
1124
        rp = chirp_common.RadioPrompts()
1125
        rp.pre_download = _(
1126
            "1. Turn Radio off.\n"
1127
            "2. Connect data cable.\n"
1128
            "3. While holding \"TONE\" and \"REV\" buttons, turn radio on.\n"
1129
            "4. <b>After clicking OK</b>, press \"TONE\" to send image.\n")
1130
        rp.pre_upload = _(
1131
            "1. Turn Radio off.\n"
1132
            "2. Connect data cable.\n"
1133
            "3. While holding \"TONE\" and \"REV\" buttons, turn radio on.\n"
1134
            "4. Press \"REV\" to receive image.\n"
1135
            "5. Make sure display says \"CLONE RX\" and green led is"
1136
            " blinking\n"
1137
            "6. Click OK to start transfer.\n")
1138
        return rp
1139

    
1140
    def get_sub_devices(self):
1141
        if not self.VARIANT:
1142
            return [FT7100RadioVHF(self._mmap),
1143
                    FT7100RadioUHF(self._mmap)]
1144
        else:
1145
            return []
1146

    
1147

    
1148
class FT7100RadioVHF(FT7100Radio):
1149
    VARIANT = "VHF Band"
1150

    
1151
    def get_features(self):
1152
        LOG.debug("VHF get_features")
1153
        upper_vhf_limit = self._get_upper_vhf_limit()
1154
        rf = FT7100Radio.get_features(self)
1155
        rf.memory_bounds = (1, upper_vhf_limit)
1156
        # Normally this band supports 120 + 10 memories. 1 based for chirpw
1157
        rf.valid_bands = [(108000000, 180000000)]  # Supports 2-meters tx
1158
        rf.valid_modes = MODES_VHF
1159
        rf.valid_special_chans = ['VFO', 'Home']
1160
        rf.has_sub_devices = False
1161
        return rf
1162

    
1163
    def get_memory(self, number):
1164
        LOG.debug("get_memory VHF Number: %s", number)
1165
        if isinstance(number, int):
1166
            if number >= 0:
1167
                mem = FT7100Radio.get_memory(self, number + 0 - 1)
1168
            else:
1169
                mem = FT7100Radio.get_memory(self, number)
1170
            mem.number = number
1171
        else:
1172
            mem = FT7100Radio.get_memory(self, number + '-VHF')
1173
            mem.extd_number = number
1174
            mem.immutable = ["number", "extd_number", "skip"]
1175
        return mem
1176

    
1177
    def set_memory(self, mem):
1178
        LOG.debug("set_memory VHF Number: %s", mem.number)
1179
        # We modify memory, so dupe() it to avoid changing our caller's
1180
        # version
1181
        mem = mem.dupe()
1182
        if isinstance(mem.number, int):
1183
            if mem.number >= 0:
1184
                mem.number += -1
1185
        else:
1186
            mem.number += '-VHF'
1187
        super(FT7100RadioVHF, self).set_memory(mem)
1188
        return
1189

    
1190

    
1191
class FT7100RadioUHF(FT7100Radio):
1192
    VARIANT = "UHF Band"
1193

    
1194
    def get_features(self):
1195
        LOG.debug("UHF get_features")
1196
        upper_uhf_limit = self._get_upper_uhf_limit()
1197
        rf = FT7100Radio.get_features(self)
1198
        rf.memory_bounds = (1, upper_uhf_limit)
1199
        # Normally this band supports 120 + 10 memories. 1 based for chirpw
1200
        rf.valid_bands = [(320000000, 999990000)]  # Supports 70-centimeters tx
1201
        rf.valid_modes = MODES_UHF
1202
        rf.valid_special_chans = ['VFO', 'Home']
1203
        rf.has_sub_devices = False
1204
        return rf
1205

    
1206
    def get_memory(self, number):
1207
        LOG.debug("get_memory UHF Number: %s", number)
1208
        upper_vhf_limit = self._get_upper_vhf_limit()
1209
        if isinstance(number, int):
1210
            if number >= 0:
1211
                mem = FT7100Radio.get_memory(self, number + 10 +
1212
                                             upper_vhf_limit - 1)
1213
            else:
1214
                mem = FT7100Radio.get_memory(self, number)
1215
            mem.number = number
1216
        else:
1217
            mem = FT7100Radio.get_memory(self, number + '-UHF')
1218
            mem.extd_number = number
1219
            mem.immutable = ["number", "extd_number", "skip"]
1220
        return mem
1221

    
1222
    def set_memory(self, mem):
1223
        LOG.debug("set_memory UHF Number: %s", mem.number)
1224
        # We modify memory, so dupe() it to avoid changing our caller's
1225
        # version
1226
        mem = mem.dupe()
1227
        upper_vhf_limit = self._get_upper_vhf_limit()
1228
        if isinstance(mem.number, int):
1229
            if mem.number >= 0:
1230
                mem.number += upper_vhf_limit - 1 + 10
1231
        else:
1232
            mem.number += '-UHF'
1233
        super(FT7100RadioUHF, self).set_memory(mem)
1234
        return
(16-16/20)