Project

General

Profile

Bug #4591 » ft7100.py

Dan Smith, 04/06/2023 04:14 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(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 == "":
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: '{}', got: '{}'."
53
                        .format(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 == "":
66
        raise Exception("Failed to read ACK. No response from radio.")
67
    if echo != ACK:
68
        raise Exception("Failed to read ACK.  Expected: '{}', got: '{}'."
69
                        .format(util.hexprint(ACK), util.hexprint(echo)))
70

    
71

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

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

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

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

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

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

    
125
    return memmap.MemoryMapBytes(data)
126

    
127

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

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

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

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

    
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 = b"Vartex Standard AH003M M-Map V04"
529
    BAUD_RATE = 9600
530
    NEEDS_COMPAT_SERIAL = False
531

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

    
679
        # mem.comment = ""
680

    
681
        mem.empty = not _mem.is_used
682

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

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

    
698
        return mem
699

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

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

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

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

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

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

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

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

    
754
        _mem.is_used = not mem.empty
755

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

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

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

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

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

    
783
        _overlay = self._memobj.overlay
784

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

    
1073
        return setmode
1074

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

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

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

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

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

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

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

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

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

    
1144

    
1145
class FT7100RadioVHF(FT7100Radio):
1146
    VARIANT = "VHF Band"
1147

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

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

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

    
1187

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

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

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

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