Project

General

Profile

Bug #10904 » ft8100.py

8f63f1d6 - Dan Smith, 10/19/2023 07:09 PM

 
1
# Copyright 2010 Eric Allen <eric@hackerengineer.net>
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 time
17
import os
18

    
19
from chirp import chirp_common, directory, bitwise, errors
20
from chirp.drivers import yaesu_clone
21

    
22
TONES = chirp_common.OLD_TONES
23

    
24
TMODES = ["", "Tone"]
25

    
26
MODES = ['FM', 'AM']
27

    
28
STEPS = [5.0, 10.0, 12.5, 15.0, 20.0, 25.0, 50.0]
29

    
30
DUPLEX = ["", "-", "+", "split"]
31

    
32
# "M" for masked memories, which are invisible until un-masked
33
SKIPS = ["", "S", "M"]
34

    
35
POWER_LEVELS_VHF = [chirp_common.PowerLevel("Low", watts=5),
36
                    chirp_common.PowerLevel("Mid", watts=20),
37
                    chirp_common.PowerLevel("High", watts=50)]
38

    
39
POWER_LEVELS_UHF = [chirp_common.PowerLevel("Low", watts=5),
40
                    chirp_common.PowerLevel("Mid", watts=20),
41
                    chirp_common.PowerLevel("High", watts=35)]
42

    
43
SPECIALS = {'1L': -1,
44
            '1U': -2,
45
            '2L': -3,
46
            '2U': -4,
47
            'Home': -5}
48

    
49
MEM_FORMAT = """
50
#seekto 0x{skips:X};
51
u8 skips[13];
52

    
53
#seekto 0x{enables:X};
54
u8 enables[13];
55

    
56
struct mem_struct {{
57
    u8 unknown4:2,
58
       baud9600:1,
59
       am:1,
60
       unknown4b:4;
61
    u8 power:2,
62
       duplex:2,
63
       unknown1b:4;
64
    u8 unknown2:1,
65
       tone_enable:1,
66
       tone:6;
67
    bbcd freq[3];
68
    bbcd offset[3];
69
}};
70

    
71
#seekto 0x{memories:X};
72
struct mem_struct memory[99];
73
"""
74

    
75

    
76
@directory.register
77
class FT8100Radio(yaesu_clone.YaesuCloneModeRadio):
78
    """Implementation for Yaesu FT-8100"""
79
    MODEL = "FT-8100"
80

    
81
    _memstart = 0
82
    _memsize = 2968
83
    _block_lengths = [10, 32, 114, 101, 101, 97, 128, 128, 128, 128, 128, 9, 9,
84
                      9, 9, 9, 9, 9, 9, 9, 9, 9, 9, 9, 9, 9, 9, 9, 9, 9, 9, 9,
85
                      9, 9, 9, 9, 9, 9, 9, 9, 9, 9, 9, 9, 9, 9, 9, 9, 9, 9, 9,
86
                      9, 9, 9, 9, 9, 9, 9, 9, 9, 9, 9, 9, 9, 9, 9, 9, 9, 9, 9,
87
                      9, 9, 9, 9, 9, 9, 9, 9, 9, 9, 9, 9, 9, 9, 9, 9, 9, 9, 9,
88
                      9, 9, 9, 9, 9, 9, 9, 9, 9, 9, 9, 9, 9, 9, 9, 9, 9, 9, 9,
89
                      9, 9, 9, 9, 9, 9, 9, 9, 9, 9, 9, 9, 9, 9, 9, 9, 9, 9, 9,
90
                      9, 9, 9, 9, 9, 9, 9, 9, 9, 9, 9, 9, 9, 9, 9, 9, 9, 9, 9,
91
                      9, 9, 9, 9, 9, 9, 9, 9, 9, 9, 9, 9, 9, 9, 9, 9, 9, 9, 9,
92
                      9, 9, 9, 9, 9, 9, 9, 9, 9, 9, 9, 9, 9, 9, 9, 9, 9, 9, 9,
93
                      9, 9, 9, 9, 9, 9, 9, 9, 9, 9, 9, 9, 9, 9, 9, 9, 9, 9, 9,
94
                      9, 9, 9, 9, 9, 9, 9, 9, 9, 9, 9, 9, 9, 9, 9, 9, 1]
95

    
96
    @classmethod
97
    def match_model(cls, data, path):
98
        if (len(data) == cls._memsize and
99
                data[1:10] == b'\x01\x01\x07\x08\x02\x01\x01\x00\x01'):
100
            return True
101

    
102
        return False
103

    
104
    def get_features(self):
105
        rf = chirp_common.RadioFeatures()
106
        rf.memory_bounds = (1, 99)
107
        rf.has_ctone = False
108
        rf.has_dtcs = False
109
        rf.has_dtcs_polarity = False
110
        rf.has_bank = False
111
        rf.has_name = False
112

    
113
        rf.valid_modes = list(MODES)
114
        rf.valid_tmodes = list(TMODES)
115
        rf.valid_duplexes = list(DUPLEX)
116
        rf.valid_power_levels = POWER_LEVELS_VHF
117
        rf.has_sub_devices = self.VARIANT == ''
118

    
119
        rf.valid_tuning_steps = list(STEPS)
120

    
121
        rf.valid_bands = [(110000000, 550000000),
122
                          (750000000, 1300000000)]
123

    
124
        rf.valid_skips = SKIPS
125

    
126
        rf.can_odd_split = True
127

    
128
        # TODO
129
        # rf.valid_special_chans = SPECIALS.keys()
130

    
131
        # TODO
132
        # rf.has_tuning_step = False
133
        return rf
134

    
135
    def sync_in(self):
136
        super(FT8100Radio, self).sync_in()
137
        self.pipe.write(bytes([yaesu_clone.CMD_ACK]))
138
        self.pipe.read(1)
139

    
140
    def sync_out(self):
141
        self.update_checksums()
142
        return _clone_out(self)
143

    
144
    def process_mmap(self):
145
        if not self._memstart:
146
            return
147

    
148
        mem_format = MEM_FORMAT.format(memories=self._memstart,
149
                                       skips=self._skipstart,
150
                                       enables=self._enablestart
151
                                       )
152

    
153
        self._memobj = bitwise.parse(mem_format, self._mmap)
154

    
155
    def get_sub_devices(self):
156
        return [FT8100RadioVHF(self._mmap), FT8100RadioUHF(self._mmap)]
157

    
158
    def get_memory(self, number):
159
        bit, byte = self._bit_byte(number)
160

    
161
        _mem = self._memobj.memory[number - 1]
162

    
163
        mem = chirp_common.Memory()
164
        mem.number = number
165

    
166
        mem.freq = int(_mem.freq) * 1000
167
        if _mem.tone >= len(TONES) or _mem.duplex >= len(DUPLEX):
168
            mem.empty = True
169
            return mem
170
        else:
171
            mem.rtone = TONES[_mem.tone]
172
        mem.tmode = TMODES[_mem.tone_enable]
173
        mem.mode = MODES[_mem.am]
174
        mem.duplex = DUPLEX[_mem.duplex]
175

    
176
        if _mem.duplex == DUPLEX.index("split"):
177
            tx_freq = int(_mem.offset) * 1000
178
            print(self.VARIANT, number, tx_freq, mem.freq)
179
            mem.offset = tx_freq - mem.freq
180
        else:
181
            mem.offset = int(_mem.offset) * 1000
182

    
183
        if int(mem.freq / 100) == 4:
184
            mem.power = POWER_LEVELS_UHF[_mem.power]
185
        else:
186
            mem.power = POWER_LEVELS_VHF[_mem.power]
187

    
188
        # M01 can't be disabled
189
        if not self._memobj.enables[byte] & bit and number != 1:
190
            mem.empty = True
191

    
192
        print('R', self.VARIANT, number, _mem.baud9600)
193

    
194
        return mem
195

    
196
    def get_raw_memory(self, number):
197
        return repr(self._memobj.memory[number - 1])
198

    
199
    def set_memory(self, mem):
200
        bit, byte = self._bit_byte(mem.number)
201

    
202
        _mem = self._memobj.memory[mem.number - 1]
203

    
204
        _mem.freq = int(mem.freq / 1000)
205
        _mem.tone = TONES.index(mem.rtone)
206
        _mem.tone_enable = TMODES.index(mem.tmode)
207
        _mem.am = MODES.index(mem.mode)
208
        _mem.duplex = DUPLEX.index(mem.duplex)
209

    
210
        if mem.duplex == "split":
211
            tx_freq = mem.freq + mem.offset
212
            _mem.split_high = tx_freq / 10000000
213
            _mem.offset = (tx_freq % 10000000) / 1000
214
        else:
215
            _mem.offset = int(mem.offset / 1000)
216

    
217
        if mem.power:
218
            _mem.power = POWER_LEVELS_VHF.index(mem.power)
219
        else:
220
            _mem.power = 0
221

    
222
        if mem.empty:
223
            self._memobj.enables[byte] &= ~bit
224
        else:
225
            self._memobj.enables[byte] |= bit
226

    
227
        # TODO expose these options
228
        _mem.baud9600 = 0
229
        _mem.am = 0
230

    
231
        # These need to be cleared, otherwise strange things happen
232
        _mem.unknown4 = 0
233
        _mem.unknown4b = 0
234
        _mem.unknown1b = 0
235
        _mem.unknown2 = 0
236

    
237
    def _checksums(self):
238
        return [yaesu_clone.YaesuChecksum(0x0000, 0x0B96)]
239

    
240
    # I didn't believe this myself, but it seems that there's a bit for
241
    # enabling VHF M01, but no bit for UHF01, and the enables are shifted down,
242
    # so that the first bit is for M02
243
    def _bit_byte(self, number):
244
        if self.VARIANT == 'VHF':
245
            bit = 1 << ((number - 1) % 8)
246
            byte = (number - 1) / 8
247
        else:
248
            bit = 1 << ((number - 2) % 8)
249
            byte = (number - 2) / 8
250

    
251
        return bit, byte
252

    
253

    
254
class FT8100RadioVHF(FT8100Radio):
255
    """Yaesu FT-8100 VHF subdevice"""
256
    VARIANT = "VHF"
257
    _memstart = 0x447
258
    _skipstart = 0x02D
259
    _enablestart = 0x04D
260

    
261

    
262
class FT8100RadioUHF(FT8100Radio):
263
    """Yaesu FT-8100 UHF subdevice"""
264
    VARIANT = "UHF"
265
    _memstart = 0x7E6
266
    _skipstart = 0x03A
267
    _enablestart = 0x05A
268

    
269

    
270
def _clone_out(radio):
271
    try:
272
        return __clone_out(radio)
273
    except Exception as e:
274
        raise errors.RadioError("Failed to communicate with the radio: %s" % e)
275

    
276

    
277
def __clone_out(radio):
278
    pipe = radio.pipe
279
    block_lengths = radio._block_lengths
280
    total_written = 0
281

    
282
    def _status():
283
        status = chirp_common.Status()
284
        status.msg = "Cloning to radio"
285
        status.max = sum(block_lengths)
286
        status.cur = total_written
287
        radio.status_fn(status)
288

    
289
    start = time.time()
290

    
291
    pos = 0
292
    for block in radio._block_lengths:
293
        if os.getenv("CHIRP_DEBUG"):
294
            print("\nSending %i-%i" % (pos, pos + block))
295
        out = radio.get_mmap()[pos:pos + block]
296

    
297
        # need to chew byte-by-byte here or else we lose the ACK...not sure why
298
        for b in out:
299
            pipe.write(bytes([b]))
300
            pipe.read(1)  # chew the echo
301

    
302
        ack = pipe.read(1)
303

    
304
        if ack[0] != yaesu_clone.CMD_ACK:
305
            raise Exception("block not ack'ed: %s" % repr(ack))
306

    
307
        total_written += len(out)
308
        _status()
309

    
310
        pos += block
311

    
312
    print("Clone completed in %i seconds" % (time.time() - start))
313

    
314
    return True
(4-4/18)