Project

General

Profile

Bug #10503 » icv80.py

e8c3bb82 - Dan Smith, 04/08/2023 03:55 PM

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

    
16
import logging
17

    
18
from chirp.drivers import icf
19
from chirp import chirp_common, memmap, bitwise, errors, directory
20
from chirp.settings import RadioSetting, RadioSettingGroup, \
21
    RadioSettingValueInteger, RadioSettingValueList, \
22
    RadioSettingValueBoolean, RadioSettingValueString, \
23
    RadioSettingValueFloat, RadioSettings
24

    
25
LOG = logging.getLogger(__name__)
26

    
27

    
28
ICV80_MEM_FORMAT = """
29

    
30
#seekto 0x0000;
31
struct {
32
  ul16 freq;
33
  ul16 offset;
34
  char name[5];
35
  u8 unknown0:2,
36
     rtone:6;
37
  u8 unknown1:2,
38
     ctone:6;
39
  u8 unknown2:1,
40
     dtcs:7;
41
  u8 unknown3:5,
42
     tuning_step:3;
43
  u8 unknown4:2,
44
     mode:1,
45
     reverse_duplex:1,
46
     duplex:2,
47
     mult:2;
48
  u8 unknown6:2,
49
     dtcs_polarity:2,
50
     unknown8:2,
51
     tmode:2;
52
  u8 unknown8:5,
53
     tx_inhibit:1,
54
     power:2;
55
} memory[208];
56

    
57
#seekto 0x0cf0;
58
u8 skip[32];
59

    
60
#seekto 0x0d10;
61
u8 unused[32];
62

    
63
#seekto 0x0e50;
64
struct {
65
  u8 unknown1:6,
66
     beep:2;
67
  u8 unknown2:3,
68
     tot:5;
69
  u8 unknown3;
70
  u8 unknown4:6,
71
     auto_pwr_off:2;
72
  u8 unknown5:6,
73
     lockout:2;
74
  u8 unknown6:7,
75
     squelch_delay:1;
76
  u8 unknown7_0;
77
  u8 unknown7_1:6,
78
     mem_display1:2;
79
  u8 unknown8:6,
80
     mem_display2:2; // CS stores the display value here as well
81
  u8 unknown9:7,
82
     dial_func:1;
83
  u8 unknown10:7,
84
     lcd:1;
85
  u8 unknown11:5,
86
     pwr_save:3;
87
  u8 unknown12:7,
88
     sel_speed:1;
89
  u8 unknown13:6,
90
     mic_mode:2;
91
  u8 unknown14:6,
92
     battery_save:2;
93
  u8 unknown15;
94
  u8 unknown16:6,
95
     resume:2;
96
  u8 unknown17:5,
97
     func_mode:3;
98
  u8 unknown18:6,
99
     backlight:2;
100
  u8 unknown19;
101
  u8 unknown:4,
102
     vox_gain:4;
103
  u8 unknown20:6,
104
     mic_gain:2;
105
  u8 unknown21:5
106
     vox_delay:3;
107
  u8 unknown22:4,
108
     vox_tot:4;
109
  u8 unknown23[2];
110
  u8 unknown24:6,
111
     edge:2;
112
  u8 unknown25;
113
  u8 unknown26:7,
114
     auto_low_pwr:1;
115
  u8 unknown27[3];
116

    
117
} settings;
118

    
119

    
120
"""
121

    
122
SPECIAL_CHANNELS = {
123
    "1A": 200, "1B": 201,
124
    "2A": 202, "2B": 203,
125
    "3A": 204, "3B": 205,
126
    "C": 206,
127
}
128

    
129
TMODES = ["", "Tone", "TSQL", "DTCS"]
130
MODES = ["FM", "NFM"]
131
SKIPS = ["", "S"]
132
DUPLEXES = ["", "-", "+"]
133
DTCS_POLARITY = ["NN", "NR", "RN", "RR"]
134
TUNING_STEPS = [5.0, 10.0, 12.5, 15.0, 20.0, 25.0, 30.0, 50.0]
135
POWER_LEVELS = [
136
    chirp_common.PowerLevel("High", watts=5.5),
137
    chirp_common.PowerLevel("Low", watts=0.5),
138
    chirp_common.PowerLevel("Mid", watts=2.5)
139
]
140

    
141

    
142
@directory.register
143
class ICV80Radio(icf.IcomCloneModeRadio, chirp_common.ExperimentalRadio):
144
    """Icom IC-V80"""
145
    VENDOR = "Icom"
146
    MODEL = "IC-V80"
147

    
148
    _model = "\x32\x54\x00\x01"
149
    _memsize = 0x0E80
150
    _endframe = "Icom Inc\x2e7C"
151
    _can_hispeed = True
152
    _ranges = [(0x0000, 0x0CE0, 32),
153
               (0x0CE0, 0x0D40, 16),
154
               (0x0D40, 0x0E00, 32),
155
               (0x0E00, 0x0E20, 16),
156
               (0x0E20, 0x0E60, 32),
157
               (0x0E60, 0x0E70, 16),
158
               (0x0E70, 0x0E72,  2),
159
               (0x0E72, 0x0E77,  5),
160
               (0x0E77, 0x0E78,  1),
161
               (0x0E78, 0x0E80,  8),
162
               ]
163

    
164
    @classmethod
165
    def get_prompts(cls):
166
        rp = chirp_common.RadioPrompts()
167
        rp.experimental = ("This radio driver is currently under development, "
168
                           "and not all the features or functions may work as"
169
                           "expected. You should proceed with caution.")
170
        return rp
171

    
172
    def get_features(self):
173
        rf = chirp_common.RadioFeatures()
174

    
175
        rf.memory_bounds = (0, 199)
176
        rf.valid_modes = MODES
177
        rf.valid_tmodes = TMODES
178
        rf.valid_duplexes = DUPLEXES
179
        rf.valid_tuning_steps = TUNING_STEPS
180
        rf.valid_power_levels = POWER_LEVELS
181
        rf.valid_skips = SKIPS
182
        rf.valid_name_length = 5
183
        rf.valid_special_chans = sorted(SPECIAL_CHANNELS.keys())
184
        rf.valid_bands = [(136000000, 174000000)]
185
        rf.has_ctone = True
186
        rf.has_offset = True
187
        rf.has_bank = False
188
        rf.has_settings = True
189

    
190
        return rf
191

    
192
    def process_mmap(self):
193
        self._memobj = bitwise.parse(ICV80_MEM_FORMAT, self._mmap)
194

    
195
    def get_settings(self):
196
        _settings = self._memobj.settings
197

    
198
        setmode = RadioSettingGroup("setmode", "Set Mode")
199
        display = RadioSettingGroup("display", "Display")
200
        sounds = RadioSettingGroup("sounds", "Sounds")
201
        scan = RadioSettingGroup("scan", "Scan")
202
        settings = RadioSettings(setmode, display, sounds, scan)
203

    
204
        # TOT
205
        opts = ["Off"] + ["%d min" % t for t in range(1, 31)]
206
        setmode.append(
207
            RadioSetting(
208
                "tot", "Time out Timer",
209
                RadioSettingValueList(opts, opts[_settings.tot])))
210

    
211
        # Lockout
212
        opts = ["Off", "Rpt", "Busy"]
213
        setmode.append(
214
            RadioSetting(
215
                "lockout", "Lockout",
216
                RadioSettingValueList(opts, opts[_settings.lockout])))
217

    
218
        # Auto Power Off
219
        opts = ["Off", "30 min", "1 hr", "2 hrs"]
220
        setmode.append(
221
            RadioSetting(
222
                "auto_pwr_off", "Auto Power Off",
223
                RadioSettingValueList(opts, opts[_settings.auto_pwr_off])))
224

    
225
        # Power Save
226
        opts = ["Off", "1:2", "1:8", "1:16", "Auto"]
227
        setmode.append(
228
            RadioSetting(
229
                "pwr_save", "Power Save",
230
                RadioSettingValueList(opts, opts[_settings.pwr_save])))
231

    
232
        # Battery Save
233
        opts = ["Off", "Ni-MH", "Li-Ion"]
234
        setmode.append(
235
            RadioSetting(
236
                "battery_save", "Battery Save",
237
                RadioSettingValueList(opts, opts[_settings.battery_save])))
238

    
239
        # Auto Low Power
240
        opts = ["Off", "On"]
241
        setmode.append(
242
            RadioSetting(
243
                "auto_low_pwr", "Auto Low Power",
244
                RadioSettingValueList(opts, opts[_settings.auto_low_pwr])))
245

    
246
        # Squelch Delay
247
        opts = ["Short", "Long"]
248
        setmode.append(
249
            RadioSetting(
250
                "squelch_delay", "Squelch Delay",
251
                RadioSettingValueList(opts, opts[_settings.squelch_delay])))
252

    
253
        # MIC Simple Mode
254
        opts = ["Simple", "Normal 1", "Normal 2"]
255
        setmode.append(
256
            RadioSetting(
257
                "mic_mode", "Mic Simple Mode",
258
                RadioSettingValueList(opts, opts[_settings.mic_mode])))
259

    
260
        # MIC Gain
261
        opts = ["1", "2", "3", "4"]
262
        setmode.append(
263
            RadioSetting(
264
                "mic_gain", "Mic Gain",
265
                RadioSettingValueList(opts, opts[_settings.mic_gain])))
266

    
267
        # VOX Gain
268
        opts = ["Off"] + ["%d" % t for t in range(1, 11)]
269
        setmode.append(
270
            RadioSetting(
271
                "vox_gain", "VOX Gain",
272
                RadioSettingValueList(opts, opts[_settings.vox_gain])))
273

    
274
        # VOX Delay
275
        opts = ["0.5 sec", "1.0 sec", "1.5 sec", "2.0 sec", "2.5 sec",
276
                "3.0 sec"]
277
        setmode.append(
278
            RadioSetting(
279
                "vox_delay", "VOX Delay",
280
                RadioSettingValueList(opts, opts[_settings.vox_delay])))
281

    
282
        # VOX Time out Timer
283
        opts = ["Off", "1 min", "2 min", "3 min", "4 min", "5 min", "10 min",
284
                "15 min"]
285
        setmode.append(
286
            RadioSetting(
287
                "vox_tot", "VOX Time-Out Timer",
288
                RadioSettingValueList(opts, opts[_settings.vox_tot])))
289

    
290
        # Select Speed
291
        opts = ["Manual", "Auto"]
292
        setmode.append(
293
            RadioSetting(
294
                "sel_speed", "Select Speed",
295
                RadioSettingValueList(opts, opts[_settings.sel_speed])))
296

    
297
        # Dial Function
298
        opts = ["Audio Volume", "Tuning Dial"]
299
        setmode.append(
300
            RadioSetting(
301
                "dial_func", "Dial Function",
302
                RadioSettingValueList(opts, opts[_settings.dial_func])))
303

    
304
        # Function Mode
305
        opts = ["0 sec", "1 sec", "2 sec", "3 sec", "Manual"]
306
        setmode.append(
307
            RadioSetting(
308
                "func_mode", "Function Mode",
309
                RadioSettingValueList(opts, opts[_settings.func_mode])))
310

    
311
        # Backlight
312
        opts = ["Off", "On", "Auto"]
313
        display.append(
314
            RadioSetting(
315
                "backlight", "Backlight",
316
                RadioSettingValueList(opts, opts[_settings.backlight])))
317

    
318
        # LCD Contrast
319
        opts = ["Low", "Auto"]
320
        display.append(
321
            RadioSetting(
322
                "lcd", "LCD Contrast",
323
                RadioSettingValueList(opts, opts[_settings.lcd])))
324

    
325
        # Memory Display
326
        opts = ["Frequency", "Channel", "Name"]
327
        display.append(
328
            RadioSetting(
329
                "mem_display1", "Memory Display",
330
                RadioSettingValueList(opts, opts[_settings.mem_display1])))
331

    
332
        # Beep
333
        opts = ["Off", "1", "2", "3"]
334
        sounds.append(
335
            RadioSetting(
336
                "beep", "Beep",
337
                RadioSettingValueList(opts, opts[_settings.beep])))
338

    
339
        # Edge
340
        opts = ["All", "P1", "P2", "P3"]
341
        scan.append(
342
            RadioSetting(
343
                "edge", "Edge",
344
                RadioSettingValueList(opts, opts[_settings.edge])))
345

    
346
        # Resume
347
        opts = ["T-5", "T-10", "T-15", "P-2"]
348
        scan.append(
349
            RadioSetting(
350
                "resume", "Resume",
351
                RadioSettingValueList(opts, opts[_settings.resume])))
352

    
353
        return settings
354

    
355
    def set_settings(self, settings):
356
        _settings = self._memobj.settings
357
        for element in settings:
358
            if not isinstance(element, RadioSetting):
359
                self.set_settings(element)
360
                continue
361
            if not element.changed():
362
                continue
363

    
364
            try:
365
                if element.has_apply_callback():
366
                    LOG.debug("Using apply callback")
367
                    element.run_apply_callback()
368
                else:
369
                    setting = element.get_name()
370
                    LOG.debug("Setting %s = %s" % (setting, element.value))
371
                    setattr(_settings, setting, element.value)
372
                    # This appears to need to be mirrored?
373
                    if element.get_name() == 'mem_display1':
374
                        _settings.mem_display2 = _settings.mem_display1
375
            except Exception as e:
376
                LOG.debug(element.get_name())
377
                raise
378

    
379
    def _get_memory(self, number, extd_number=None):
380
        bit = 1 << (number % 8)
381
        byte = number // 8
382

    
383
        _mem = self._memobj.memory[number]
384
        _unused = self._memobj.unused[byte]
385
        _skip = self._memobj.skip[byte]
386

    
387
        mem = chirp_common.Memory(number)
388

    
389
        if extd_number is not None:
390
            mem.extd_number = extd_number
391
            mem.immutable = ["name", "number", "extd_number", "skip"]
392
            if extd_number == "C":
393
                _unused = False
394

    
395
        if (_unused & bit):
396
            mem.empty = True
397
            return mem
398

    
399
        if int(_mem.mult):
400
            mult = 6250
401
        else:
402
            mult = 5000
403
        mem.freq = int(_mem.freq) * mult
404
        mem.offset = int(_mem.offset) * mult
405
        if mem.extd_number == "":
406
            mem.name = str(_mem.name).rstrip()
407
            mem.skip = (_skip & bit) and "S" or ""
408
        mem.duplex = DUPLEXES[_mem.duplex]
409
        mem.power = POWER_LEVELS[_mem.power]
410
        mem.tuning_step = TUNING_STEPS[_mem.tuning_step]
411
        mem.mode = MODES[_mem.mode]
412
        mem.tmode = TMODES[_mem.tmode]
413
        mem.rtone = chirp_common.TONES[_mem.rtone]
414
        mem.ctone = chirp_common.TONES[_mem.ctone]
415
        mem.dtcs = chirp_common.DTCS_CODES[_mem.dtcs]
416
        mem.dtcs_polarity = DTCS_POLARITY[_mem.dtcs_polarity]
417

    
418
        # Reverse duplex
419
        mem.extra = RadioSettingGroup("extra", "Extra")
420
        rev = RadioSetting("reverse_duplex", "Reverse duplex",
421
                           RadioSettingValueBoolean(bool(_mem.reverse_duplex)))
422
        rev.set_doc("Reverse duplex")
423
        mem.extra.append(rev)
424

    
425
        # Tx inhibit
426
        tx_inhibit = RadioSetting("tx_inhibit", "TX inhibit",
427
                                  RadioSettingValueBoolean(
428
                                      bool(not _mem.tx_inhibit)))
429
        tx_inhibit.set_doc("TX inhibit")
430
        mem.extra.append(tx_inhibit)
431

    
432
        return mem
433

    
434
    def get_memory(self, number):
435
        extd_number = None
436
        if isinstance(number, str):
437
            try:
438
                extd_number = number
439
                number = SPECIAL_CHANNELS[number]
440
            except KeyError:
441
                raise errors.InvalidMemoryLocation(
442
                    "Unknown channel %s" % number)
443

    
444
        return self._get_memory(number, extd_number)
445

    
446
    def _fill_memory(self, mem):
447
        number = mem.number
448
        _mem = self._memobj.memory[number]
449

    
450
        # zero-fill
451
        _mem.freq = 146010000 // 5000
452
        _mem.offset = 600000 // 5000
453
        _mem.name = str("").ljust(5)
454
        _mem.duplex = 0x0
455
        _mem.reverse_duplex = 0x0
456
        _mem.tx_inhibit = 0x1
457
        _mem.power = 0x0
458
        _mem.tuning_step = 0x0
459
        _mem.mode = 0x0
460
        _mem.tmode = 0x0
461
        _mem.rtone = 0x8
462
        _mem.ctone = 0x8
463
        _mem.dtcs = 0x0
464
        _mem.dtcs_polarity = 0x0
465

    
466
        for setting in mem.extra:
467
            setattr(_mem, setting.get_name(), setting.value)
468

    
469
    def set_memory(self, mem):
470
        bit = 1 << (mem.number % 8)
471
        byte = mem.number // 8
472

    
473
        _mem = self._memobj.memory[mem.number]
474
        _unused = self._memobj.unused[byte]
475
        _skip = (mem.extd_number == "") and self._memobj.skip[byte] or None
476
        assert(_mem)
477

    
478
        if mem.empty:
479
            self._fill_memory(mem)
480
            _unused |= bit
481
            if _skip is not None:
482
                _skip |= bit
483
            return
484

    
485
        _mem.set_raw(b'\x00' * 15)
486

    
487
        if chirp_common.required_step(mem.freq) == 12.5:
488
            mult = 6250
489
        else:
490
            mult = 5000
491
        _mem.mult = 0 if mult == 5000 else 3
492
        _mem.freq = mem.freq // mult
493
        _mem.offset = int(mem.offset) // mult
494
        _mem.name = str(mem.name).ljust(5)
495
        _mem.duplex = DUPLEXES.index(mem.duplex)
496
        power = mem.power or POWER_LEVELS[0]
497
        _mem.power = POWER_LEVELS.index(power)
498
        _mem.tuning_step = TUNING_STEPS.index(mem.tuning_step)
499
        _mem.mode = MODES.index(mem.mode)
500
        _mem.tmode = TMODES.index(mem.tmode)
501
        _mem.rtone = chirp_common.TONES.index(mem.rtone)
502
        _mem.ctone = chirp_common.TONES.index(mem.ctone)
503
        _mem.dtcs = chirp_common.DTCS_CODES.index(mem.dtcs)
504
        _mem.dtcs_polarity = DTCS_POLARITY.index(mem.dtcs_polarity)
505

    
506
        # Set used
507
        _unused &= ~bit
508

    
509
        # Set skip
510
        if _skip is not None:
511
            if mem.skip == "S":
512
                _skip |= bit
513
            else:
514
                _skip &= ~bit
515

    
516
    def get_raw_memory(self, number):
517
        return repr(self._memobj.memory[number])
(3-3/3)