Project

General

Profile

New Model #6525 » ftm7250d.py

FTM-7250D driver for Chirp - Iain White, 06/29/2020 09:32 AM

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

    
17
import logging
18
from textwrap import dedent
19

    
20
from chirp.drivers import yaesu_clone, ft1d
21
from chirp import chirp_common, directory, bitwise
22
from chirp.settings import RadioSettings
23

    
24
LOG = logging.getLogger(__name__)
25

    
26
POWER_LEVELS = [chirp_common.PowerLevel("Low", watts=5),
27
                chirp_common.PowerLevel("Mid", watts=25),
28
                chirp_common.PowerLevel("Hi", watts=50)]
29

    
30
TMODES = ["", "Tone", "TSQL", "DTCS", "TSQL-R", None, None, "Pager", "Cross"]
31
CROSS_MODES = [None, "DTCS->", "Tone->DTCS", "DTCS->Tone"]
32

    
33
MODES = ["FM", "NFM"]
34
STEPS = [0, 5, 6.25, 10, 12.5, 15, 20, 25, 50, 100]  # 0 = auto
35
RFSQUELCH = ["OFF", "S1", "S2", "S3", "S4", "S5", "S6", "S7", "S8"]
36

    
37
# Charset is subset of ASCII + some unknown chars \x80-\x86
38
VALID_CHARS = ["%i" % int(x) for x in range(0, 10)] + \
39
    list(":>=<?@") + \
40
    [chr(x) for x in range(ord("A"), ord("Z") + 1)] + \
41
    list("[\\]_") + \
42
    [chr(x) for x in range(ord("a"), ord("z") + 1)] + \
43
    list("%*+,-/=$ ")
44

    
45
MEM_FORMAT = """
46
#seekto 0xceca;
47
struct {
48
  u8 unknown5;
49
  u8 unknown3;
50
  u8 unknown4:6,
51
     dsqtype:2;
52
  u8 dsqcode;
53
  u8 unknown1[2];
54
  char mycall[10];
55
  u8 unknown2[368];
56
} settings;
57

    
58
#seekto 0xfec9;
59
u8 checksum;
60
"""
61

    
62

    
63
@directory.register
64
class FTM7250Radio(ft1d.FT1Radio):
65
    """Yaesu FTM-7250D"""
66
    BAUD_RATE = 38400
67
    VENDOR = "Yaesu"
68
    MODEL = "FTM-7250D"
69
    VARIANT = "R"
70

    
71
    _model = "AH52N"
72
    _memsize = 65227
73
    _block_lengths = [10, 65217]
74
    _has_vibrate = False
75
    _has_af_dual = False
76

    
77
    _mem_params = (199,            # size of memories array
78
                   199)            # size of flags array
79

    
80
    @classmethod
81
    def get_prompts(cls):
82
        rp = chirp_common.RadioPrompts()
83
        rp.pre_download = _(dedent("""\
84
            1. Turn radio off.
85
            2. Connect cable to MIC Jack.
86
            3. Press and hold in the [MHz(SETUP)] key while turning the radio
87
                 on ("CLONE" will appear on the display).
88
            4. <b>After clicking OK</b>, press the [GM(AMS)] key
89
                 to send image."""))
90
        rp.pre_upload = _(dedent("""\
91
            1. Turn radio off.
92
            2. Connect cable to MIC Jack.
93
            3. Press and hold in the [MHz(SETUP)] key while turning the radio
94
                 on ("CLONE" will appear on the display).
95
            4. Press the [MHz(SETUP)] key
96
                 ("-WAIT-" will appear on the LCD)."""))
97
        return rp
98

    
99
    def process_mmap(self):
100
        mem_format = ft1d.MEM_FORMAT + MEM_FORMAT
101
        self._memobj = bitwise.parse(mem_format % self._mem_params, self._mmap)
102

    
103
    def get_features(self):
104
        rf = chirp_common.RadioFeatures()
105
        rf.has_dtcs_polarity = False
106
        rf.valid_modes = list(MODES)
107
        rf.valid_tmodes = [x for x in TMODES if x is not None]
108
        rf.valid_cross_modes = [x for x in CROSS_MODES if x is not None]
109
        rf.valid_duplexes = list(ft1d.DUPLEX)
110
        rf.valid_tuning_steps = list(STEPS)
111
        rf.valid_bands = [(136000000, 174000000),(420000000, 450000000)]
112
        # rf.valid_skips = SKIPS
113
        rf.valid_power_levels = POWER_LEVELS
114
        rf.valid_characters = "".join(VALID_CHARS)
115
        rf.valid_name_length = 8
116
        rf.memory_bounds = (1, 199)
117
        rf.can_odd_split = True
118
        rf.has_ctone = False
119
        rf.has_bank = False
120
        rf.has_bank_names = False
121
        # disable until implemented
122
        rf.has_settings = False
123
        return rf
124

    
125
    def _decode_label(self, mem):
126
        # TODO preserve the unknown \x80-x86 chars?
127
        return str(mem.label).rstrip("\xFF").decode('ascii', 'replace')
128

    
129
    def _encode_label(self, mem):
130
        label = mem.name.rstrip().encode('ascii', 'ignore')
131
        return self._add_ff_pad(label, 16)
132

    
133
    def _encode_charsetbits(self, mem):
134
        # TODO this is a setting to decide if the memory should be displayed
135
        # as a name or frequency. Should we expose this setting to the user
136
        # instead of autoselecting it (and losing their preference)?
137
        if mem.name.rstrip() == '':
138
            return [0x00, 0x00]
139
        return [0x00, 0x80]
140

    
141
    def _decode_power_level(self, mem):
142
        return POWER_LEVELS[mem.power - 1]
143

    
144
    def _encode_power_level(self, mem):
145
        return POWER_LEVELS.index(mem.power) + 1
146

    
147
    def _decode_mode(self, mem):
148
        return MODES[mem.mode_alt]
149

    
150
    def _encode_mode(self, mem):
151
        return MODES.index(mem.mode)
152

    
153
    def _get_tmode(self, mem, _mem):
154
        if _mem.tone_mode > 8:
155
            tmode = "Cross"
156
            mem.cross_mode = CROSS_MODES[_mem.tone_mode - 8]
157
        else:
158
            tmode = TMODES[_mem.tone_mode]
159

    
160
        if tmode == "Pager":
161
            # TODO chirp_common does not allow 'Pager'
162
            #   Expose as a different setting?
163
            mem.tmode = ""
164
        else:
165
            mem.tmode = tmode
166

    
167
    def _set_tmode(self, _mem, mem):
168
        if mem.tmode == "Cross":
169
            _mem.tone_mode = 8 + CROSS_MODES.index(mem.cross_mode)
170
        else:
171
            _mem.tone_mode = TMODES.index(mem.tmode)
172

    
173
    def _set_mode(self, _mem, mem):
174
        _mem.mode_alt = self._encode_mode(mem)
175

    
176
    def get_bank_model(self):
177
        return None
178

    
179
    def _debank(self, mem):
180
        return
181

    
182
    def _checksums(self):
183
        return [yaesu_clone.YaesuChecksum(0x064A, 0x06C8),
184
                yaesu_clone.YaesuChecksum(0x06CA, 0x0748),
185
                yaesu_clone.YaesuChecksum(0x074A, 0x07C8),
186
                yaesu_clone.YaesuChecksum(0x07CA, 0x0848),
187
                yaesu_clone.YaesuChecksum(0x0000, 0xFEC9)]
188

    
189
    def _get_settings(self):
190
        # TODO
191
        top = RadioSettings()
192
        return top
193

    
194
    @classmethod
195
    def _wipe_memory(cls, mem):
196
        mem.set_raw("\x00" * (mem.size() / 8))
197

    
198
    def sync_out(self):
199
        # Need to give enough time for the radio to ACK after writes
200
        self.pipe.timeout = 1
201
        return super(FTM7250Radio, self).sync_out()
(1-1/3)