Project

General

Profile

Bug #10714 » icv86.py

d20ebb07 - Dan Smith, 07/10/2023 02:17 PM

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

    
16
import logging
17

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

    
24
LOG = logging.getLogger(__name__)
25

    
26

    
27
ICV86_MEM_FORMAT = """
28
#seekto 0x040;
29
u8 skips[26];
30

    
31
#seekto 0x060;
32
u8 used[26];
33

    
34
#seekto 0x00BF;
35
struct {
36
  u8 reserved1[8];
37
  u8 reserved2:6,
38
     disp_type:2;
39
  u8 reserved3;
40
  u8 reserved4:7,
41
     dial_assignment:1;
42
  u8 reserved5[8];
43
  u8 reserved6:6,
44
     lcd:2;
45
  u8 reserved7[2];
46
  u8 reserved8:6,
47
     mic:2;
48
} settings;
49

    
50
#seekto 0x0200;
51
struct {
52
  ul32 freq;
53
  ul32 offset;
54
  char name[5];
55
  u8 reserved1:2,
56
     rtone:6;
57
  u8 reserved2:2,
58
     ctone:6;
59
  u8 reserved3:1,
60
     dtcs:7;
61
  u8 reserved4:5,
62
     tuning_step:3;
63
  u8 reserved5:2,
64
     mode:1,
65
     rev:1,
66
     duplex:2,
67
     reserved6:2;
68
  u8 reserved7:2,
69
     dtcs_polarity:2,
70
     tmode:4;
71
  u8 reserved8:5,
72
     tx:1,
73
     power:2;
74
  u8 reserved9;
75
  u8 reserved10;
76
  u8 reserved11;
77
  u8 reserved12;
78
} memory[207];
79

    
80
"""
81

    
82
SPECIAL = {
83
    "0A": 200, "0B": 201,
84
    "1A": 202, "1B": 203,
85
    "2A": 204, "2B": 205,
86
    "C": 206,
87
}
88

    
89
SPECIAL_REV = {
90
    200: "0A", 201: "0B",
91
    202: "1A", 203: "1B",
92
    204: "2A", 205: "2B",
93
    206: "C",
94
}
95

    
96
TMODES = ["", "Tone", "TSQL", "DTCS", "DTCS-R"]
97
MODES = ["FM", "NFM"]
98
SKIPS = ["", "S"]
99
DUPLEXES = ["", "-", "+"]
100
DTCS_POLARITY = ["NN", "NR", "RN", "RR"]
101
TUNING_STEPS = [5., 10., 12.5, 15., 20., 25., 30., 50.]
102
POWER_LEVELS = [
103
    chirp_common.PowerLevel("Extra High", watts=7.0),
104
    chirp_common.PowerLevel("High", watts=5.5),
105
    chirp_common.PowerLevel("Mid", watts=2.5),
106
    chirp_common.PowerLevel("Low", watts=0.5),
107
]
108

    
109

    
110
@directory.register
111
class ICV86Radio(icf.IcomCloneModeRadio):
112
    """Icom IC-V86"""
113
    VENDOR = "Icom"
114
    MODEL = "IC-V86"
115

    
116
    _model = "\x40\x66\x00\x01"
117
    _memsize = 5504
118
    _endframe = "Icom Inc\x2eAC"
119

    
120
    _ranges = [(0x0000, 5504, 32)]
121

    
122
    def get_features(self):
123
        rf = chirp_common.RadioFeatures()
124

    
125
        rf.memory_bounds = (0, 199)
126
        rf.valid_modes = MODES
127
        rf.valid_tmodes = TMODES
128
        rf.valid_duplexes = DUPLEXES
129
        rf.valid_tuning_steps = TUNING_STEPS
130
        rf.valid_power_levels = POWER_LEVELS
131
        rf.valid_skips = SKIPS
132
        rf.valid_name_length = 5
133
        rf.valid_special_chans = sorted(SPECIAL.keys())
134
        rf.valid_bands = [(136000000, 174000000)]
135
        rf.has_ctone = True
136
        rf.has_offset = True
137
        rf.has_bank = False
138
        rf.has_settings = True
139

    
140
        return rf
141

    
142
    def __init__(self, pipe):
143
        icf.IcomCloneModeRadio.__init__(self, pipe)
144

    
145
    def sync_in(self):
146
        icf.IcomCloneModeRadio.sync_in(self)
147

    
148
    def sync_out(self):
149
        icf.IcomCloneModeRadio.sync_out(self)
150

    
151
    def process_mmap(self):
152
        self._memobj = bitwise.parse(ICV86_MEM_FORMAT, self._mmap)
153

    
154
    def get_settings(self):
155
        _settings = self._memobj.settings
156

    
157
        setmode = RadioSettingGroup("setmode", "General Settings")
158

    
159
        settings = RadioSettings(setmode)
160

    
161
        # LCD Backlight
162
        opts = ["Off", "On", "Auto"]
163
        setmode.append(
164
            RadioSetting(
165
                "lcd", "LCD Backlight",
166
                RadioSettingValueList(opts, opts[_settings.lcd])))
167

    
168
        # Mic Gain
169
        rs = RadioSetting("mic", "Mic Gain",
170
                          RadioSettingValueInteger(1, 4, _settings.mic + 1))
171

    
172
        def apply_mic(s, obj):
173
            setattr(obj, s.get_name(), int(s.value) - 1)
174
        rs.set_apply_callback(apply_mic, self._memobj.settings)
175
        setmode.append(rs)
176

    
177
        # Dial Assignment
178
        opts = ["Volume", "Tuning"]
179
        setmode.append(
180
            RadioSetting(
181
                "dial_assignment", "Dial Assignment",
182
                RadioSettingValueList(opts, opts[_settings.dial_assignment])))
183

    
184
        # Display Type
185
        opts = ["Frequency", "Channel", "Name"]
186
        setmode.append(
187
            RadioSetting(
188
                "disp_type", "Display Type",
189
                RadioSettingValueList(opts, opts[_settings.disp_type])))
190

    
191
        return settings
192

    
193
    def set_settings(self, settings):
194
        _settings = self._memobj.settings
195
        for element in settings:
196
            if not isinstance(element, RadioSetting):
197
                self.set_settings(element)
198
                continue
199
            if not element.changed():
200
                continue
201

    
202
            try:
203
                if element.has_apply_callback():
204
                    LOG.debug("Using apply callback")
205
                    element.run_apply_callback()
206
                else:
207
                    setting = element.get_name()
208
                    LOG.debug("Setting %s = %s" % (setting, element.value))
209
                    setattr(_settings, setting, element.value)
210
            except Exception:
211
                LOG.debug(element.get_name())
212
                raise
213

    
214
    def _get_memory(self, number):
215
        bit = 1 << (number % 8)
216
        byte = int(number / 8)
217

    
218
        mem = chirp_common.Memory()
219
        mem.number = number
220

    
221
        _mem = self._memobj.memory[number]
222

    
223
        if number < 200:
224
            _usd = self._memobj.used[byte]
225
            _skp = self._memobj.skips[byte]
226
        else:
227
            mem.extd_number = SPECIAL_REV[number]
228
            mem.immutable = ["name", "number", "extd_number", "skip"]
229
            _usd = self._memobj.used[byte] if (number <= 206) else None
230
            _skp = None
231

    
232
        if _usd is not None and (_usd & bit):
233
            mem.empty = True
234
            return mem
235

    
236
        mem.freq = _mem.freq
237
        mem.offset = int(_mem.offset)
238
        if number < 200:
239
            mem.name = str(_mem.name).rstrip()
240
        mem.rtone = chirp_common.TONES[_mem.rtone]
241
        mem.ctone = chirp_common.TONES[_mem.ctone]
242
        mem.dtcs = chirp_common.DTCS_CODES[_mem.dtcs]
243
        mem.tuning_step = TUNING_STEPS[_mem.tuning_step]
244
        mem.mode = MODES[_mem.mode]
245
        mem.duplex = DUPLEXES[_mem.duplex]
246
        mem.dtcs_polarity = DTCS_POLARITY[_mem.dtcs_polarity]
247
        mem.tmode = TMODES[_mem.tmode]
248
        mem.power = POWER_LEVELS[_mem.power]
249

    
250
        # Extras
251
        mem.extra = RadioSettingGroup("extra", "Extra")
252
        rev = RadioSetting("rev", "Reverse duplex",
253
                           RadioSettingValueBoolean(bool(_mem.rev)))
254
        rev.set_doc("Reverse duplex")
255
        mem.extra.append(rev)
256

    
257
        tx = RadioSetting("tx", "Tx permission",
258
                          RadioSettingValueBoolean(bool(_mem.tx)))
259
        tx.set_doc("Tx permission")
260
        mem.extra.append(tx)
261

    
262
        if _skp is not None:
263
            mem.skip = (_skp & bit) and "S" or ""
264

    
265
        return mem
266

    
267
    def get_memory(self, number):
268
        if not self._mmap:
269
            self.sync_in()
270

    
271
        assert (self._mmap)
272

    
273
        if isinstance(number, str):
274
            try:
275
                number = SPECIAL[number]
276
            except KeyError:
277
                raise errors.InvalidMemoryLocation("Unknown channel %s" % number)
278

    
279
        return self._get_memory(number)
280

    
281
    def _fill_memory(self, number):
282
        _mem = self._memobj.memory[number]
283

    
284
        assert (_mem)
285

    
286
        # zero-fill
287
        _mem.freq = 146010000
288
        _mem.offset = 146010000
289
        _mem.name = str("").ljust(5)
290
        _mem.reserved1 = 0x0
291
        _mem.rtone = 0x8
292
        _mem.reserved2 = 0x0
293
        _mem.ctone = 0x8
294
        _mem.reserved3 = 0x0
295
        _mem.dtcs = 0x0
296
        _mem.reserved4 = 0x0
297
        _mem.tuning_step = 0x0
298
        _mem.reserved5 = 0x0
299
        _mem.mode = 0x0
300
        _mem.rev = 0x0
301
        _mem.duplex = 0x0
302
        _mem.reserved6 = 0x0
303
        _mem.reserved7 = 0x0
304
        _mem.dtcs_polarity = 0x0
305
        _mem.tmode = 0x0
306
        _mem.tx = 0x1
307
        _mem.reserved8 = 0x0
308
        _mem.power = 0x0
309
        _mem.reserved9 = 0x0
310
        _mem.reserved10 = 0x0
311
        _mem.reserved11 = 0x0
312
        _mem.reserved12 = 0x0
313

    
314
    def _set_memory(self, mem):
315
        bit = 1 << (mem.number % 8)
316
        byte = int(mem.number / 8)
317

    
318
        _mem = self._memobj.memory[mem.number]
319
        _usd = self._memobj.used[byte] if mem.number <= 206 else None
320
        _skp = self._memobj.skips[byte] if mem.number < 200 else None
321

    
322
        assert (_mem)
323

    
324
        if mem.empty:
325
            self._fill_memory(mem.number)
326
            if _usd is not None:
327
                _usd |= bit
328
            return
329

    
330
        if _usd is None or (_usd & bit):
331
            self._fill_memory(mem.number)
332

    
333
        _mem.freq = mem.freq
334
        _mem.offset = int(mem.offset)
335
        _mem.name = str(mem.name).ljust(5)
336
        _mem.rtone = chirp_common.TONES.index(mem.rtone)
337
        _mem.ctone = chirp_common.TONES.index(mem.ctone)
338
        _mem.dtcs = chirp_common.DTCS_CODES.index(mem.dtcs)
339
        _mem.tuning_step = TUNING_STEPS.index(mem.tuning_step)
340
        _mem.mode = MODES.index(mem.mode)
341
        _mem.duplex = DUPLEXES.index(mem.duplex)
342
        _mem.dtcs_polarity = DTCS_POLARITY.index(mem.dtcs_polarity)
343
        _mem.tmode = TMODES.index(mem.tmode)
344
        _mem.power = 0 if mem.power is None else POWER_LEVELS.index(mem.power)
345

    
346
        for setting in mem.extra:
347
            setattr(_mem, setting.get_name(), setting.value)
348

    
349
        if _usd is not None:
350
            _usd &= ~bit
351

    
352
        if _skp is not None:
353
            if mem.skip == "S":
354
                _skp |= bit
355
            else:
356
                _skp &= ~bit
357

    
358
    def set_memory(self, mem):
359
        if not self._mmap:
360
            self.sync_in()
361

    
362
        assert (self._mmap)
363

    
364
        return self._set_memory(mem)
365

    
366
    def get_raw_memory(self, number):
367
        return repr(self._memobj.memory[number]) + \
368
            repr(self._memobj.used[(number)])
(3-3/3)