Project

General

Profile

Bug #4545 » id51plus.py

97deb87ec2be2e3263f36b4289a81774d629822a - Dan Smith, 11/20/2022 05:09 AM

 
1
# Copyright 2015 Eric Dropps <kc1ckh@kc1ckh.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
import logging
16

    
17
from chirp.drivers import icf, id31
18
from chirp import directory, bitwise, memmap
19

    
20
LOG = logging.getLogger(__name__)
21

    
22
MEM_FORMAT = """
23
struct {
24
  u24 freq;
25
  u16 offset;
26
  u16 rtone:6,
27
      ctone:6,
28
      unknown2:1,
29
      mode:3;
30
  u8 dtcs;
31
  u8 tune_step:4,
32
     unknown5:4;
33
  u8 unknown4;
34
  u8 tmode:4,
35
     duplex:2,
36
     dtcs_polarity:2;
37
  char name[16];
38
  u8 unknown13;
39
  u8 urcall[7];
40
  u8 rpt1call[7];
41
  u8 rpt2call[7];
42
} memory[500];
43

    
44
#seekto 0x6A40;
45
u8 used_flags[70];
46

    
47
#seekto 0x6A86;
48
u8 skip_flags[69];
49

    
50
#seekto 0x6ACB;
51
u8 pskp_flags[69];
52

    
53
#seekto 0x6B40;
54
struct {
55
  u8 unknown:3,
56
     bank:5;
57
  u8 index;
58
} banks[500];
59

    
60
#seekto 0x6FD0;
61
struct {
62
  char name[16];
63
} bank_names[26];
64

    
65

    
66
#seekto 0xA8C0;
67
struct {
68
  u24 freq;
69
  u16 offset; 
70
  u8 unknown1[4];
71
  u8 call[7];
72
  char name[16];
73
  char subname[8];
74
  u8 unknown3[10];
75
} repeaters[750];
76

    
77
#seekto 0x1384E;
78
struct {
79
  u8 call[7];
80
} rptcall[750];
81

    
82
#seekto 0x14FBE;
83
struct {
84
 char name[16];
85
} rptgroup_names[30];
86

    
87
#seekto 0x1519E;
88
struct {
89
  char call[8];
90
  char tag[4];
91
} mycall[6];
92

    
93
#seekto 0x151E6;
94
struct {
95
  char call[8];
96
} urcall[200];
97

    
98
#seekto 0x15826;
99
struct {
100
  char name[16];
101
} urcallname[200];
102
"""
103

    
104
@directory.register
105
class ID51PLUSRadio(id31.ID31Radio):
106
    """Icom ID-51 Plus/50th Anniversary"""
107
    MODEL = "ID-51 Plus"
108

    
109
    _memsize = 0x1FB40
110
    _model = "\x33\x90\x00\x02"
111
    _endframe = "Icom Inc\x2E\x44\x41"
112
    _bank_class = id31.ID31Bank
113
    _ranges = [(0x00000, 0x1FB40, 32)]
114

    
115
    MODES = {0: "FM", 1: "NFM", 3: "AM", 5: "DV"}
116

    
117
    @classmethod
118
    def match_model(cls, filedata, filename):
119
        """Given contents of a stored file (@filedata), return True if
120
        this radio driver handles the represented model"""
121

    
122
        # The default check for ICOM is just to check memory size
123
        # Since the ID-51 and ID-51 Plus/Anniversary have exactly
124
        # the same memory size, we need to do a more detailed check.
125
        if len(filedata) == cls._memsize:
126
            LOG.debug('File has correct memory size, '
127
                      'checking 20 bytes at offset 0x1AF40')
128
            snip = filedata[0x1AF40:0x1AF60]
129
            if snip != ('\xFF\xFF\xFF\xFF\xFF\xFF\xFF\xFF'
130
                        '\xFF\xFF\xFF\xFF\xFF\xFF\xFF\xFF'
131
                        '\xFF\xFF\xFF\xFF\xFF\xFF\xFF\xFF'
132
                        '\xFF\xFF\xFF\xFF\xFF\xFF\xFF\xFF'):
133
                LOG.debug('bytes matched ID-51 Plus Signature')
134
                return True
135
            else:
136
                LOG.debug('bytes did not match ID-51 Plus Signature')
137
        return False
138

    
139
    def _get_bank(self, loc):
140
        _bank = self._memobj.banks[loc]
141
        LOG.debug("Bank Value for location %s is %s" % (loc, _bank.bank))
142
        if _bank.bank == 0x1F:
143
            return None         
144
        else:
145
            return _bank.bank
146

    
147
    def _set_bank(self, loc, bank):
148
        _bank = self._memobj.banks[loc]
149
        if bank is None:
150
            _bank.bank = 0x1F
151
        else:
152
            _bank.bank = bank
153

    
154
    def get_features(self):
155
        rf = super(ID51PLUSRadio, self).get_features()
156
        rf.valid_bands = [(108000000, 174000000), (380000000, 479000000)]
157
        return rf
158

    
159
    def get_repeater_call_list(self):
160
        calls = []
161
        # Unlike previous DStar radios, there is not a separate repeater
162
        # callsign list. It's only the DV Memory banks.
163
        for repeater in self._memobj.repeaters:
164
            call = id31._decode_call(repeater.call)
165
            if call == "CALLSIGN":
166
                call = ""
167
            calls.append(call.rstrip())
168
        return calls
169

    
170
    def process_mmap(self):
171
        self._memobj = bitwise.parse(MEM_FORMAT, self._mmap)
172

    
173

    
174
@directory.register
175
class ID51PLUS2Radio(ID51PLUSRadio):
176
    MODEL = 'ID-51 Plus2'
177
    _model = b'\x33\x90\x00\x03'
178

    
179
    _icf_data = {
180
        'MapRev': 1,
181
        'EtcData': 400001,
182
    }
183

    
184
    @classmethod
185
    def match_model(cls, filedata, filename):
186
        # This model is always matched with metadata
187
        return False
188

    
189
    # This is actually a raw mode radio, but the id31 in its class
190
    # hierarchy is not. So hack in the raw mode things for testing.
191

    
192
    def process_frame_payload(self, payload):
193
        """Payloads from a raw-clone-mode radio are already in raw format."""
194
        return icf.unescape_raw_bytes(payload)
195

    
196
    def get_payload(self, data, raw, checksum):
197
        """Returns the data with optional checksum in raw format."""
198
        if checksum:
199
            cs = chr(icf.compute_checksum(data))
200
        else:
201
            cs = ""
202
        payload = "%s%s" % (data, cs)
203
        # Escape control characters.
204
        escaped_payload = [icf.escape_raw_byte(b) for b in payload]
205
        return "".join(escaped_payload)
206

    
207
    def sync_in(self):
208
        # The radio returns all the bytes with the high-order bit flipped.
209
        _mmap = icf.clone_from_radio(self)
210
        _mmap = icf.flip_high_order_bit(_mmap.get_packed())
211
        self._mmap = memmap.MemoryMap(_mmap)
212
        self.process_mmap()
213

    
214
    def get_mmap(self):
215
        _data = icf.flip_high_order_bit(self._mmap.get_packed())
216
        return memmap.MemoryMap(_data)
(7-7/10)