Project

General

Profile

Bug #6641 » ft7100.py

Tony Brittingham, 09/25/2019 10:17 AM

 
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
from textwrap import dedent
30

    
31
LOG = logging.getLogger(__name__)
32

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

    
37

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

    
56

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

    
63

    
64
def _wait_for_ack(pipe):
65
    echo = pipe.read(1)
66
    if echo == "":
67
        raise Exception("Failed to read ACK. No response from radio.")
68
    if echo != ACK:
69
        raise Exception("Failed to read ACK.  Expected: '{}', got: '{}'."
70
                        .format(util.hexprint(ACK), util.hexprint(echo)))
71

    
72

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

    
89
    # read 16 Byte block
90
    # and ignore it because it is constant. This might be a bug.
91
    # It was built in at the very beginning and discovered very late that the
92
    # data might be necessary later to write to the radio.
93
    # Now the data is hardcoded in _upload(radio)
94
    data = radio.pipe.read(16)
95
    _send_ack(radio.pipe)
96

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

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

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

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

    
126
    return memmap.MemoryMap(data)
127

    
128

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

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

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

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

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

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

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

    
265
#seekto 0x41;
266
  u8        current_band;
267

    
268
#seekto 0x42;
269
  u8        current_nb_mem_used_vhf_maybe_not;
270

    
271
#seekto 0x4c;
272
  u8        priority_channel_maybe_1;   // not_implemented
273
  u8        priority_channel_maybe_2;   // not_implemented
274
  u8        priority_channel;           // not_implemented
275

    
276
#seekto 0x87;
277
  u8        opt_01_apo_1_4;
278
#seekto 0xa1;
279
  u8        opt_01_apo_2_4;
280
#seekto 0xc5;
281
  u8        opt_01_apo_3_4;
282
#seekto 0xe1;
283
  u8        opt_01_apo_4_4;
284

    
285
#seekto 0x88;
286
  u8        opt_02_ars_vhf_1_2;
287
#seekto 0xa2;
288
  u8        opt_02_ars_vhf_2_2;
289
#seekto 0xc6;
290
  u8        opt_02_ars_uhf_1_2;
291
#seekto 0xe2;
292
  u8        opt_02_ars_uhf_2_2;
293

    
294
#seekto 0x89;
295
  u8        opt_03_arts_mode_vhf_1_2;
296
#seekto 0xa3;
297
  u8        opt_03_arts_mode_vhf_2_2;
298
#seekto 0xc7;
299
  u8        opt_03_arts_mode_uhf_1_2;
300
#seekto 0xa3;
301
  u8        opt_03_arts_mode_vhf_2_2;
302

    
303
#seekto 0x8a;
304
  u8        opt_04_beep_1_2;
305
#seekto 0xa4;
306
  u8        opt_04_beep_2_2;
307

    
308
#seekto 0x8b;
309
  u8        opt_05_cwid_on_1_4;
310
#seekto 0xa5;
311
  u8        opt_05_cwid_on_2_4;
312
#seekto 0xc9;
313
  u8        opt_05_cwid_on_3_4;
314
#seekto 0xe5;
315
  u8        opt_05_cwid_on_4_4;
316

    
317
#seekto 0x80;
318
  char        opt_06_cwidw[6];
319

    
320
#seekto 0x8d;
321
  u8        opt_07_dim_1_4;
322
#seekto 0xa7;
323
  u8        opt_07_dim_2_4;
324
#seekto 0xcb;
325
  u8        opt_07_dim_3_4;
326
#seekto 0xe7;
327
  u8        opt_07_dim_4_4;
328

    
329
#seekto 0x90;
330
  u8        opt_10_dcsnr_vhf_1_2;
331
#seekto 0xaa;
332
  u8        opt_10_dcsnr_vhf_2_2;
333
#seekto 0xce;
334
  u8        opt_10_dcsnr_uhf_1_2;
335
#seekto 0xea;
336
  u8        opt_10_dcsnr_uhf_2_2;
337

    
338
#seekto 0x91;
339
  u8        opt_11_disp_1_4;
340
#seekto 0xab;
341
  u8        opt_11_disp_2_4;
342
#seekto 0xcf;
343
  u8        opt_11_disp_3_4;
344
#seekto 0xeb;
345
  u8        opt_11_disp_4_4;
346

    
347
#seekto 0x92;
348
  u8        opt_12_dtmf_delay_1_4;
349
#seekto 0xac;
350
  u8        opt_12_dtmf_delay_2_4;
351
#seekto 0xd0;
352
  u8        opt_12_dtmf_delay_3_4;
353
#seekto 0xec;
354
  u8        opt_12_dtmf_delay_4_4;
355

    
356
#seekto 0x93;
357
  u8        opt_13_dtmf_speed_1_4;
358
#seekto 0xad;
359
  u8        opt_13_dtmf_speed_2_4;
360
#seekto 0xd1;
361
  u8        opt_13_dtmf_speed_3_4;
362
#seekto 0xed;
363
  u8        opt_13_dtmf_speed_4_4;
364

    
365
#seekto 0x94;
366
  u8        opt_14_dtmfw_index_1_4;
367
#seekto 0xae;
368
  u8        opt_14_dtmfw_index_2_4;
369
#seekto 0xd2;
370
  u8        opt_14_dtmfw_index_3_4;
371
#seekto 0xee;
372
  u8        opt_14_dtmfw_index_4_4;
373

    
374
#seekto 0x96;
375
  u8        opt_16_lockt_1_4;
376
#seekto 0xb0;
377
  u8        opt_16_lockt_2_4;
378
#seekto 0xd4;
379
  u8        opt_16_lockt_3_4;
380
#seekto 0xf0;
381
  u8        opt_16_lockt_4_4;
382

    
383
#seekto 0x97;
384
  u8        opt_17_mic_MH48_1_4;
385
#seekto 0xb1;
386
  u8        opt_17_mic_MH48_2_4;
387
#seekto 0xd5;
388
  u8        opt_17_mic_MH48_3_4;
389
#seekto 0xf1;
390
  u8        opt_17_mic_MH48_4_4;
391

    
392
#seekto 0x98;
393
  u8        opt_18_mute_1_4;
394
#seekto 0xb2;
395
  u8        opt_18_mute_2_4;
396
#seekto 0xd6;
397
  u8        opt_18_mute_3_4;
398
#seekto 0xf2;
399
  u8        opt_18_mute_4_4;
400

    
401
#seekto 0x9a;
402
  u8        opt_20_pg_p_1_4[4];
403
#seekto 0xb4;
404
  u8        opt_20_pg_p_2_4[4];
405
#seekto 0xd8;
406
  u8        opt_20_pg_p_3_4[4];
407
#seekto 0xf4;
408
  u8        opt_20_pg_p_4_4[4];
409

    
410
#seekto 0x9e;
411
  u8        opt_24_rf_sql_vhf_1_2;
412
#seekto 0xb8;
413
  u8        opt_24_rf_sql_vhf_2_2;
414
#seekto 0xdc;
415
  u8        opt_24_rf_sql_uhf_1_2;
416
#seekto 0xf8;
417
  u8        opt_24_rf_sql_uhf_2_2;
418

    
419
#seekto 0x9f;
420
  u8        opt_25_scan_resume_vhf_1_2;
421
#seekto 0xb9;
422
  u8        opt_25_scan_resume_vhf_2_2;
423
#seekto 0xdd;
424
  u8        opt_25_scan_resume_uhf_1_2;
425
#seekto 0xf9;
426
  u8        opt_25_scan_resume_uhf_2_2;
427

    
428
#seekto 0xbc;
429
  u8        opt_28_speaker_cnt_1_2;
430
#seekto 0xfc;
431
  u8        opt_28_speaker_cnt_2_2;
432

    
433
#seekto 0xbf;
434
  u8        opt_31_tot_1_2;
435
#seekto 0xff;
436
  u8        opt_31_tot_2_2;
437

    
438
#seekto 0xc0;
439
  u8        opt_32_tx_nar_vhf;
440
#seekto 0x100;
441
  u8        opt_32_tx_nar_uhf;
442

    
443
#seekto 0xc1;
444
  u8        opt_33_vfo_tr;
445

    
446
#seekto 0xc2;
447
  u8        opt_34_am_1_2;
448
#seekto 0x102;
449
  u8        opt_34_am_2_2;
450

    
451
#seekto 260;
452
struct {
453
  struct mem mem_struct;
454
  char        fill_ff[2];
455
} unknown00;
456

    
457
struct {
458
  struct mem mem_struct;
459
  char        fill_00[6];
460
} current_vfo_vhf_uhf[2];
461

    
462
struct {
463
  struct mem mem_struct;
464
  char        fill_ff[6];
465
} current_mem_vhf_uhf[2];
466

    
467
struct {
468
  struct mem mem_struct;
469
  char        fill_ff[6];
470
} home_vhf_uhf[2];
471

    
472
struct {
473
  struct mem mem_struct;
474
  char        fill_010003000000[6];
475
} vhome;
476

    
477
struct {
478
  struct mem mem_struct;
479
  char        fill_010001000000[6];
480
} unknown01;
481

    
482
struct {
483
  char name[32];
484
} Vertex_Standard_AH003M_Backup_DT;
485

    
486
struct mem
487
  memory[260];
488

    
489
struct {
490
  char name[24];
491
} Vertex_Standard_AH003M;
492

    
493
struct {
494
  u8 dtmf[16];
495
} dtmf_mem[16];
496
"""
497

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

    
516

    
517
def do_download(radio):
518
    """This is your download function"""
519
    return _download(radio)
520

    
521

    
522
@directory.register
523
class FT7100Radio(YaesuCloneModeRadio):
524

    
525
    """Yaesu FT-7100M"""
526
    MODEL = "FT-7100M"
527
    VARIANT = ""
528
    IDBLOCK = "Vartex Standard AH003M M-Map V04"
529
    BAUD_RATE = 9600
530

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

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

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

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

    
587
    def process_mmap(self):
588
        self._memobj = bitwise.parse(MEM_FORMAT, self._mmap)
589

    
590
    def get_raw_memory(self, number):
591
        return repr(self._memobj.memory[number])
592

    
593
    def get_memory(self, number):
594
        LOG.debug("get_memory Number: {}".format(number))
595

    
596
        # Create a high-level memory object to return to the UI
597
        mem = chirp_common.Memory()
598

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

    
633
        # Convert your low-level frequency to Hertz
634
        mem.freq = int(_mem.freq_rx_Hz)
635
        mem.name = str(_mem.name).rstrip()  # Set the alpha tag
636

    
637
        mem.rtone = chirp_common.TONES[_mem.tone_index]
638
        mem.ctone = chirp_common.TONES[_mem.tone_index]
639

    
640
        mem.dtcs = chirp_common.DTCS_CODES[_mem.dtcs_index]
641
        mem.rx_dtcs = chirp_common.DTCS_CODES[_mem.dtcs_index]
642

    
643
        tmode_radio = TONE_MODES_RADIO[_mem.tone_mode_index]
644
        # CTCSS Bell is TSQL plus a flag in the extra setting
645
        is_ctcss_bell = tmode_radio == "CTCSS Bell"
646
        if is_ctcss_bell:
647
            mem.tmode = "TSQL"
648
        else:
649
            mem.tmode = tmode_radio
650

    
651
        mem.duplex = ""
652
        if _mem.is_offset_plus:
653
            mem.duplex = "+"
654
        elif _mem.is_offset_minus:
655
            mem.duplex = "-"
656
        elif _mem.is_split:
657
            mem.duplex = "split"
658

    
659
        if _mem.is_split:
660
            mem.offset = int(_mem.freq_tx_Hz)
661
        else:
662
            mem.offset = int(_mem.offset_10khz)*10000   # 10kHz to Hz
663

    
664
        if _mem.is_mode_am:
665
            mem.mode = "AM"
666
        else:
667
            mem.mode = "FM"
668

    
669
        mem.power = POWER_LEVELS[_mem.power_index]
670

    
671
        mem.tuning_step = TUNING_STEPS[_mem.tuning_step_index_1_2]
672

    
673
        if _mem.is_skip:
674
            mem.skip = "S"
675
        else:
676
            mem.skip = ""
677

    
678
        # mem.comment = ""
679

    
680
        mem.empty = not _mem.is_used
681

    
682
        mem.extra = RadioSettingGroup("Extra", "extra")
683

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

    
697
        return mem
698

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

    
717
        _mem.name = mem.name.ljust(6)
718

    
719
        _mem.tone_index = chirp_common.TONES.index(mem.rtone)
720
        _mem.dtcs_index = chirp_common.DTCS_CODES.index(mem.dtcs)
721
        if mem.tmode == "TSQL":
722
            _mem.tone_index = chirp_common.TONES.index(mem.ctone)
723
        if mem.tmode == "DTSC-R":
724
            _mem.dtcs_index = chirp_common.DTCS_CODES.index(mem.rx_dtcs)
725

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

    
744
        _mem.is_mode_am = mem.mode == "AM"
745

    
746
        if mem.power:
747
            _mem.power_index = POWER_LEVELS.index(mem.power)
748

    
749
        _mem.tuning_step_index_1_2 = TUNING_STEPS.index(mem.tuning_step)
750

    
751
        _mem.is_skip = mem.skip == "S"
752

    
753
        _mem.is_used = not mem.empty
754

    
755
        # if name not empty: show name
756
        _mem.show_name = mem.name.strip() != ""
757

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

    
763
        for setting in mem.extra:
764
            if setting.get_name() == "is_ctcss_bell":
765
                if mem.tmode == "TSQL" and setting.value:
766
                    _mem.tone_mode_index = TONE_MODES_RADIO.index("CTCSS Bell")
767
            else:
768
                setattr(_mem, setting.get_name(), setting.value)
769

    
770
        LOG.debug("encoded mem\n%s\n", (util.hexprint(_mem.get_raw()[0:25])))
771
        LOG.debug(repr(_mem))
772

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

    
782
        _overlay = self._memobj.overlay
783

    
784
        # numbers and names of settings refer to the way they're
785
        # presented in the set menu, as well as the list starting on
786
        # page 49 of the manual
787

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

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

    
820
        # 3  Selects the ARTS mode.
821
        # -> Only useful to set it on the radio directly
822

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

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

    
837
        # 6  Callsign during ARTS operation.
838
        cwidw = _overlay.cwidw.get_raw()
839
        cwidw = cwidw.rstrip('\x00')
840
        val = RadioSettingValueString(0, 6, cwidw)
841
        val.set_charset(CHARSET)
842
        rs = RadioSetting("cwidw", "CW Identifier Callsign", val)
843

    
844
        def apply_cwid(setting):
845
            value_string = setting.value.get_value()
846
            _overlay.cwidw.set_value(value_string)
847
        rs.set_apply_callback(apply_cwid)
848
        arts.append(rs)
849

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

    
857
        # 8 Setting the DCS code number.
858
        #   Note: This Menu item can be set independently for each band,
859
        #         and independently in each memory.
860

    
861
        # 9 Activates the DCS Code Search
862
        # -> Only useful if set on radio itself
863

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

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

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

    
890
        # 13 Setting the DTMF Autodialer sending speed
891
        opts = ["50 ms", "75 ms", "100 ms"]
892
        dtmf.append(
893
            RadioSetting(
894
                "dtmfs", "Autodialer sending speed",
895
                RadioSettingValueList(opts, opts[_overlay.dtmfs])))
896

    
897
        # 14 Current DTMF Autodialer memory
898
        rs = RadioSetting("dtmfw", "Current Autodialer memory",
899
                          RadioSettingValueInteger(1, 16, _overlay.dtmfw + 1))
900

    
901
        def apply_dtmfw(setting):
902
            _overlay.dtmfw = setting.value.get_value() - 1
903
        rs.set_apply_callback(apply_dtmfw)
904
        dtmf.append(rs)
905

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

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

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

    
954
        # 17 Selects the Microphone type to be used
955
        opts = ["MH-42", "MH-48"]
956
        common.append(
957
            RadioSetting(
958
                "mic", "Microphone type",
959
                RadioSettingValueList(opts, opts[_overlay.mic])))
960

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

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

    
985
            def apply_button(setting, index):
986
                value_string = setting.value.get_value()
987
                value_int = opts_button.index(value_string)
988
                _overlay.button[index] = value_int
989
            rs.set_apply_callback(apply_button, i)
990
            mic_button.append(rs)
991

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

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

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

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

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

    
1039
        # 33 Enables/disables the VFO Tracking feature
1040
        opts = ["Off", "On"]
1041
        common.append(
1042
            RadioSetting(
1043
                "vfotr", "VFO Tracking",
1044
                RadioSettingValueList(opts, opts[_overlay.vfotr])))
1045

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

    
1053
        # Current Band
1054
        opts = ["VHF", "UHF"]
1055
        common.append(
1056
            RadioSetting(
1057
                "current_band", "Current Band",
1058
                RadioSettingValueList(opts, opts[_overlay.current_band])))
1059

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

    
1072
        return setmode
1073

    
1074
    def set_settings(self, uisettings):
1075
        _overlay = self._memobj.overlay
1076
        for element in uisettings:
1077
            if not isinstance(element, RadioSetting):
1078
                self.set_settings(element)
1079
                continue
1080
            if not element.changed():
1081
                continue
1082

    
1083
            try:
1084
                name = element.get_name()
1085
                value = element.value
1086

    
1087
                if element.has_apply_callback():
1088
                    LOG.debug("Using apply callback")
1089
                    element.run_apply_callback()
1090
                else:
1091
                    setattr(_overlay, name, value)
1092

    
1093
                LOG.debug("Setting %s: %s", name, value)
1094
            except Exception, e:
1095
                LOG.debug(element.get_name())
1096
                raise
1097

    
1098
    def _get_upper_vhf_limit(self):
1099
        if self._memobj is None:
1100
            # test with tox has no _memobj
1101
            upper_vhf_limit = 130
1102
        else:
1103
            upper_vhf_limit = int(self._memobj.nb_mem_used_vhf_and_limits)
1104
        return upper_vhf_limit
1105

    
1106
    def _get_upper_uhf_limit(self):
1107
        if self._memobj is None:
1108
            # test with tox has no _memobj
1109
            upper_uhf_limit = 130
1110
        else:
1111
            upper_uhf_limit = int(self._memobj.nb_mem_used_uhf_and_limits)
1112
        return upper_uhf_limit
1113

    
1114
    @classmethod
1115
    def match_model(cls, filedata, filename):
1116
        return filedata[0x1ec0:0x1ec0+len(cls.IDBLOCK)] == cls.IDBLOCK
1117

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

    
1135
    def get_sub_devices(self):
1136
        if not self.VARIANT:
1137
            return [FT7100RadioVHF(self._mmap),
1138
                    FT7100RadioUHF(self._mmap)]
1139
        else:
1140
            return []
1141

    
1142

    
1143
class FT7100RadioVHF(FT7100Radio):
1144
    VARIANT = "VHF Band"
1145

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

    
1158
    def get_memory(self, number):
1159
        LOG.debug("get_memory VHF Number: {}".format(number))
1160
        if isinstance(number, int):
1161
            if number >= 0:
1162
                mem = FT7100Radio.get_memory(self, number + 0 - 1)
1163
            else:
1164
                mem = FT7100Radio.get_memory(self, number)
1165
            mem.number = number
1166
        else:
1167
            mem = FT7100Radio.get_memory(self, number + '-VHF')
1168
            mem.extd_number = number
1169
            mem.immutable = ["number", "extd_number", "skip"]
1170
        return mem
1171

    
1172
    def set_memory(self, mem):
1173
        LOG.debug("set_memory VHF Number: {}".format(mem.number))
1174
        # mem is used further in test by tox. So save the modified members.
1175
        _number = mem.number
1176
        if isinstance(mem.number, int):
1177
            if mem.number >= 0:
1178
                mem.number += -1
1179
        else:
1180
            mem.number += '-VHF'
1181
        super(FT7100RadioVHF, self).set_memory(mem)
1182
        # Restore modified members
1183
        mem.number = _number
1184
        return
1185

    
1186

    
1187
class FT7100RadioUHF(FT7100Radio):
1188
    VARIANT = "UHF Band"
1189

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

    
1202
    def get_memory(self, number):
1203
        LOG.debug("get_memory UHF Number: {}".format(number))
1204
        upper_vhf_limit = self._get_upper_vhf_limit()
1205
        if isinstance(number, int):
1206
            if number >= 0:
1207
                mem = FT7100Radio.get_memory(self, number +
1208
                                             upper_vhf_limit - 1)
1209
            else:
1210
                mem = FT7100Radio.get_memory(self, number)
1211
            mem.number = number
1212
        else:
1213
            mem = FT7100Radio.get_memory(self, number + '-UHF')
1214
            mem.extd_number = number
1215
            mem.immutable = ["number", "extd_number", "skip"]
1216
        return mem
1217

    
1218
    def set_memory(self, mem):
1219
        LOG.debug("set_memory UHF Number: {}".format(mem.number))
1220
        # mem is used further in test by tox. So save the modified members.
1221
        _number = mem.number
1222
        upper_vhf_limit = self._get_upper_vhf_limit()
1223
        if isinstance(mem.number, int):
1224
            if mem.number >= 0:
1225
                mem.number += upper_vhf_limit - 1
1226
        else:
1227
            mem.number += '-UHF'
1228
        super(FT7100RadioUHF, self).set_memory(mem)
1229
        # Restore modified members
1230
        mem.number = _number
1231
        return
(2-2/2)