Project

General

Profile

Bug #4819 » alinco_2011.py

changed check from "2009" to "2011" - Jim Unroe, 05/15/2017 03:44 AM

 
1
# Copyright 2011 Dan Smith <dsmith@danplanet.com>
2
#           2016 Matt Weyland <lt-betrieb@hb9uf.ch>
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
from chirp import chirp_common, bitwise, memmap, errors, directory, util
18
from chirp.settings import RadioSettingGroup, RadioSetting
19
from chirp.settings import RadioSettingValueBoolean, RadioSettings
20

    
21
from textwrap import dedent
22

    
23
import time
24
import logging
25

    
26
LOG = logging.getLogger(__name__)
27

    
28

    
29
DRX35_MEM_FORMAT = """
30
#seekto 0x0120;
31
u8 used_flags[25];
32

    
33
#seekto 0x0200;
34
struct {
35
  u8 new_used:1,
36
     unknown1:1,
37
     isnarrow:1,
38
     isdigital:1,
39
     ishigh:1,
40
     unknown2:3;
41
  u8 unknown3:6,
42
     duplex:2;
43
  u8 unknown4:4,
44
     tmode:4;
45
  u8 unknown5:4,
46
     step:4;
47
  bbcd freq[4];
48
  u8 unknown6[1];
49
  bbcd offset[3];
50
  u8 rtone;
51
  u8 ctone;
52
  u8 dtcs_tx;
53
  u8 dtcs_rx;
54
  u8 name[7];
55
  u8 unknown8[2];
56
  u8 unknown9:6,
57
     power:2;
58
  u8 unknownA[6];
59
} memory[100];
60

    
61
#seekto 0x0130;
62
u8 skips[25];
63
"""
64

    
65
# 0000 0111
66
# 0000 0010
67

    
68
# Response length is:
69
# 1. \r\n
70
# 2. Four-digit address, followed by a colon
71
# 3. 16 bytes in hex (32 characters)
72
# 4. \r\n
73
RLENGTH = 2 + 5 + 32 + 2
74

    
75
STEPS = [5.0, 10.0, 12.5, 15.0, 20.0, 25.0, 30.0]
76

    
77

    
78
def isascii(data):
79
    for byte in data:
80
        if (ord(byte) < ord(" ") or ord(byte) > ord("~")) and \
81
                byte not in "\r\n":
82
            return False
83
    return True
84

    
85

    
86
def tohex(data):
87
    if isascii(data):
88
        return repr(data)
89
    string = ""
90
    for byte in data:
91
        string += "%02X" % ord(byte)
92
    return string
93

    
94

    
95
class AlincoStyleRadio(chirp_common.CloneModeRadio):
96
    """Base class for all known Alinco radios"""
97
    _memsize = 0
98
    _model = "NONE"
99

    
100
    def _send(self, data):
101
        LOG.debug("PC->R: (%2i) %s" % (len(data), tohex(data)))
102
        self.pipe.write(data)
103
        self.pipe.read(len(data))
104

    
105
    def _read(self, length):
106
        data = self.pipe.read(length)
107
        LOG.debug("R->PC: (%2i) %s" % (len(data), tohex(data)))
108
        return data
109

    
110
    def _download_chunk(self, addr):
111
        if addr % 16:
112
            raise Exception("Addr 0x%04x not on 16-byte boundary" % addr)
113

    
114
        cmd = "AL~F%04XR\r\n" % addr
115
        self._send(cmd)
116

    
117
        resp = self._read(RLENGTH).strip()
118
        if len(resp) == 0:
119
            raise errors.RadioError("No response from radio")
120
        if ":" not in resp:
121
            raise errors.RadioError("Unexpected response from radio")
122
        addr, _data = resp.split(":", 1)
123
        data = ""
124
        for i in range(0, len(_data), 2):
125
            data += chr(int(_data[i:i+2], 16))
126

    
127
        if len(data) != 16:
128
            LOG.debug("Response was:")
129
            LOG.debug("|%s|")
130
            LOG.debug("Which I converted to:")
131
            LOG.debug(util.hexprint(data))
132
            raise Exception("Radio returned less than 16 bytes")
133

    
134
        return data
135

    
136
    def _download(self, limit):
137
        self._identify()
138

    
139
        data = ""
140
        for addr in range(0, limit, 16):
141
            data += self._download_chunk(addr)
142
            time.sleep(0.1)
143

    
144
            if self.status_fn:
145
                status = chirp_common.Status()
146
                status.cur = addr + 16
147
                status.max = self._memsize
148
                status.msg = "Downloading from radio"
149
                self.status_fn(status)
150

    
151
        self._send("AL~E\r\n")
152
        self._read(20)
153

    
154
        return memmap.MemoryMap(data)
155

    
156
    def _identify(self):
157
        for _i in range(0, 3):
158
            self._send("%s\r\n" % self._model)
159
            resp = self._read(6)
160
            if resp.strip() == "OK":
161
                return True
162
            time.sleep(1)
163

    
164
        return False
165

    
166
    def _upload_chunk(self, addr):
167
        if addr % 16:
168
            raise Exception("Addr 0x%04x not on 16-byte boundary" % addr)
169

    
170
        _data = self._mmap[addr:addr+16]
171
        data = "".join(["%02X" % ord(x) for x in _data])
172

    
173
        cmd = "AL~F%04XW%s\r\n" % (addr, data)
174
        self._send(cmd)
175

    
176
    def _upload(self, limit):
177
        if not self._identify():
178
            raise Exception("I can't talk to this model")
179

    
180
        for addr in range(0x100, limit, 16):
181
            self._upload_chunk(addr)
182
            time.sleep(0.1)
183

    
184
            if self.status_fn:
185
                status = chirp_common.Status()
186
                status.cur = addr + 16
187
                status.max = self._memsize
188
                status.msg = "Uploading to radio"
189
                self.status_fn(status)
190

    
191
        self._send("AL~E\r\n")
192
        self.pipe._read(20)
193

    
194
    def process_mmap(self):
195
        self._memobj = bitwise.parse(DRX35_MEM_FORMAT, self._mmap)
196

    
197
    def sync_in(self):
198
        try:
199
            self._mmap = self._download(self._memsize)
200
        except errors.RadioError:
201
            raise
202
        except Exception, e:
203
            raise errors.RadioError("Failed to communicate with radio: %s" % e)
204
        self.process_mmap()
205

    
206
    def sync_out(self):
207
        try:
208
            self._upload(self._memsize)
209
        except errors.RadioError:
210
            raise
211
        except Exception, e:
212
            raise errors.RadioError("Failed to communicate with radio: %s" % e)
213

    
214
    def get_raw_memory(self, number):
215
        return repr(self._memobj.memory[number])
216

    
217

    
218
DUPLEX = ["", "-", "+"]
219
TMODES = ["", "Tone", "", "TSQL"] + [""] * 12
220
TMODES[12] = "DTCS"
221
DCS_CODES = {
222
    "Alinco": chirp_common.DTCS_CODES,
223
    "Jetstream": [17] + chirp_common.DTCS_CODES,
224
}
225

    
226
CHARSET = (["\x00"] * 0x30) + \
227
    [chr(x + ord("0")) for x in range(0, 10)] + \
228
    [chr(x + ord("A")) for x in range(0, 26)] + [" "] + \
229
    list("\x00" * 128)
230

    
231

    
232
def _get_name(_mem):
233
    name = ""
234
    for i in _mem.name:
235
        if i in [0x00, 0xFF]:
236
            break
237
        name += CHARSET[i]
238
    return name
239

    
240

    
241
def _set_name(mem, _mem):
242
    name = [0x00] * 7
243
    j = 0
244
    for i in range(0, 7):
245
        try:
246
            name[j] = CHARSET.index(mem.name[i])
247
            j += 1
248
        except IndexError:
249
            pass
250
        except ValueError:
251
            pass
252
    return name
253

    
254
ALINCO_TONES = list(chirp_common.TONES)
255
ALINCO_TONES.remove(159.8)
256
ALINCO_TONES.remove(165.5)
257
ALINCO_TONES.remove(171.3)
258
ALINCO_TONES.remove(177.3)
259
ALINCO_TONES.remove(183.5)
260
ALINCO_TONES.remove(189.9)
261
ALINCO_TONES.remove(196.6)
262
ALINCO_TONES.remove(199.5)
263
ALINCO_TONES.remove(206.5)
264
ALINCO_TONES.remove(229.1)
265
ALINCO_TONES.remove(254.1)
266

    
267

    
268
class DRx35Radio(AlincoStyleRadio):
269
    """Base class for the DR-x35 radios"""
270
    _range = [(118000000, 155000000)]
271
    _power_levels = []
272
    _valid_tones = ALINCO_TONES
273

    
274
    def get_features(self):
275
        rf = chirp_common.RadioFeatures()
276
        rf.valid_tmodes = ["", "Tone", "TSQL", "DTCS"]
277
        rf.valid_modes = ["FM", "NFM"]
278
        rf.valid_skips = ["", "S"]
279
        rf.valid_bands = self._range
280
        rf.memory_bounds = (0, 99)
281
        rf.has_ctone = True
282
        rf.has_bank = False
283
        rf.has_dtcs_polarity = False
284
        rf.valid_tuning_steps = STEPS
285
        rf.valid_name_length = 7
286
        rf.valid_power_levels = self._power_levels
287
        return rf
288

    
289
    def _get_used(self, number):
290
        _usd = self._memobj.used_flags[number / 8]
291
        bit = (0x80 >> (number % 8))
292
        return _usd & bit
293

    
294
    def _set_used(self, number, is_used):
295
        _usd = self._memobj.used_flags[number / 8]
296
        bit = (0x80 >> (number % 8))
297
        if is_used:
298
            _usd |= bit
299
        else:
300
            _usd &= ~bit
301

    
302
    def _get_power(self, _mem):
303
        if self._power_levels:
304
            return self._power_levels[_mem.ishigh]
305
        return None
306

    
307
    def _set_power(self, _mem, mem):
308
        if self._power_levels:
309
            _mem.ishigh = mem.power is None or \
310
                mem.power == self._power_levels[1]
311

    
312
    def _get_extra(self, _mem, mem):
313
        mem.extra = RadioSettingGroup("extra", "Extra")
314
        dig = RadioSetting("isdigital", "Digital",
315
                           RadioSettingValueBoolean(bool(_mem.isdigital)))
316
        dig.set_doc("Digital/Packet mode enabled")
317
        mem.extra.append(dig)
318

    
319
    def _set_extra(self, _mem, mem):
320
        for setting in mem.extra:
321
            setattr(_mem, setting.get_name(), setting.value)
322

    
323
    def get_memory(self, number):
324
        _mem = self._memobj.memory[number]
325
        _skp = self._memobj.skips[number / 8]
326
        _usd = self._memobj.used_flags[number / 8]
327
        bit = (0x80 >> (number % 8))
328

    
329
        mem = chirp_common.Memory()
330
        mem.number = number
331
        if not self._get_used(number) and self.MODEL != "JT220M":
332
            mem.empty = True
333
            return mem
334

    
335
        mem.freq = int(_mem.freq) * 100
336
        mem.rtone = self._valid_tones[_mem.rtone]
337
        mem.ctone = self._valid_tones[_mem.ctone]
338
        mem.duplex = DUPLEX[_mem.duplex]
339
        mem.offset = int(_mem.offset) * 100
340
        mem.tmode = TMODES[_mem.tmode]
341
        mem.dtcs = DCS_CODES[self.VENDOR][_mem.dtcs_tx]
342
        mem.tuning_step = STEPS[_mem.step]
343

    
344
        if _mem.isnarrow:
345
            mem.mode = "NFM"
346

    
347
        mem.power = self._get_power(_mem)
348

    
349
        if _skp & bit:
350
            mem.skip = "S"
351

    
352
        mem.name = _get_name(_mem).rstrip()
353

    
354
        self._get_extra(_mem, mem)
355

    
356
        return mem
357

    
358
    def set_memory(self, mem):
359
        _mem = self._memobj.memory[mem.number]
360
        _skp = self._memobj.skips[mem.number / 8]
361
        _usd = self._memobj.used_flags[mem.number / 8]
362
        bit = (0x80 >> (mem.number % 8))
363

    
364
        if self._get_used(mem.number) and not mem.empty:
365
            # Initialize the memory
366
            _mem.set_raw("\x00" * 32)
367

    
368
        self._set_used(mem.number, not mem.empty)
369
        if mem.empty:
370
            return
371

    
372
        _mem.freq = mem.freq / 100
373

    
374
        try:
375
            _tone = mem.rtone
376
            _mem.rtone = self._valid_tones.index(mem.rtone)
377
            _tone = mem.ctone
378
            _mem.ctone = self._valid_tones.index(mem.ctone)
379
        except ValueError:
380
            raise errors.UnsupportedToneError("This radio does not support " +
381
                                              "tone %.1fHz" % _tone)
382

    
383
        _mem.duplex = DUPLEX.index(mem.duplex)
384
        _mem.offset = mem.offset / 100
385
        _mem.tmode = TMODES.index(mem.tmode)
386
        _mem.dtcs_tx = DCS_CODES[self.VENDOR].index(mem.dtcs)
387
        _mem.dtcs_rx = DCS_CODES[self.VENDOR].index(mem.dtcs)
388
        _mem.step = STEPS.index(mem.tuning_step)
389

    
390
        _mem.isnarrow = mem.mode == "NFM"
391
        self._set_power(_mem, mem)
392

    
393
        if mem.skip:
394
            _skp |= bit
395
        else:
396
            _skp &= ~bit
397

    
398
        _mem.name = _set_name(mem, _mem)
399

    
400
        self._set_extra(_mem, mem)
401

    
402

    
403
@directory.register
404
class DR03Radio(DRx35Radio):
405
    """Alinco DR03"""
406
    VENDOR = "Alinco"
407
    MODEL = "DR03T"
408

    
409
    _model = "DR135"
410
    _memsize = 4096
411
    _range = [(28000000, 29695000)]
412

    
413
    @classmethod
414
    def match_model(cls, filedata, filename):
415
        return len(filedata) == cls._memsize and \
416
            filedata[0x64] == chr(0x00) and filedata[0x65] == chr(0x28)
417

    
418

    
419
@directory.register
420
class DR06Radio(DRx35Radio):
421
    """Alinco DR06"""
422
    VENDOR = "Alinco"
423
    MODEL = "DR06T"
424

    
425
    _model = "DR435"
426
    _memsize = 4096
427
    _range = [(50000000, 53995000)]
428

    
429
    @classmethod
430
    def match_model(cls, filedata, filename):
431
        return len(filedata) == cls._memsize and \
432
            filedata[0x64] == chr(0x00) and filedata[0x65] == chr(0x50)
433

    
434

    
435
@directory.register
436
class DR135Radio(DRx35Radio):
437
    """Alinco DR135"""
438
    VENDOR = "Alinco"
439
    MODEL = "DR135T"
440

    
441
    _model = "DR135"
442
    _memsize = 4096
443
    _range = [(118000000, 173000000)]
444

    
445
    @classmethod
446
    def match_model(cls, filedata, filename):
447
        return len(filedata) == cls._memsize and \
448
            filedata[0x64] == chr(0x01) and filedata[0x65] == chr(0x44)
449

    
450

    
451
@directory.register
452
class DR235Radio(DRx35Radio):
453
    """Alinco DR235"""
454
    VENDOR = "Alinco"
455
    MODEL = "DR235T"
456

    
457
    _model = "DR235"
458
    _memsize = 4096
459
    _range = [(216000000, 280000000)]
460

    
461
    @classmethod
462
    def match_model(cls, filedata, filename):
463
        return len(filedata) == cls._memsize and \
464
            filedata[0x64] == chr(0x02) and filedata[0x65] == chr(0x22)
465

    
466

    
467
@directory.register
468
class DR435Radio(DRx35Radio):
469
    """Alinco DR435"""
470
    VENDOR = "Alinco"
471
    MODEL = "DR435T"
472

    
473
    _model = "DR435"
474
    _memsize = 4096
475
    _range = [(350000000, 511000000)]
476

    
477
    @classmethod
478
    def match_model(cls, filedata, filename):
479
        return len(filedata) == cls._memsize and \
480
            filedata[0x64] == chr(0x04) and filedata[0x65] == chr(0x00)
481

    
482

    
483
@directory.register
484
class DJ596Radio(DRx35Radio):
485
    """Alinco DJ596"""
486
    VENDOR = "Alinco"
487
    MODEL = "DJ596"
488

    
489
    _model = "DJ596"
490
    _memsize = 4096
491
    _range = [(136000000, 174000000), (400000000, 511000000)]
492
    _power_levels = [chirp_common.PowerLevel("Low", watts=1.00),
493
                     chirp_common.PowerLevel("High", watts=5.00)]
494

    
495
    @classmethod
496
    def match_model(cls, filedata, filename):
497
        return len(filedata) == cls._memsize and \
498
            filedata[0x64] == chr(0x45) and filedata[0x65] == chr(0x01)
499

    
500

    
501
@directory.register
502
class JT220MRadio(DRx35Radio):
503
    """Jetstream JT220"""
504
    VENDOR = "Jetstream"
505
    MODEL = "JT220M"
506

    
507
    _model = "DR136"
508
    _memsize = 8192
509
    _range = [(216000000, 280000000)]
510

    
511
    @classmethod
512
    def match_model(cls, filedata, filename):
513
        return len(filedata) == cls._memsize and \
514
            filedata[0x60:0x64] == "2011" # was "2009"
515

    
516

    
517
@directory.register
518
class DJ175Radio(DRx35Radio):
519
    """Alinco DJ175"""
520
    VENDOR = "Alinco"
521
    MODEL = "DJ175"
522

    
523
    _model = "DJ175"
524
    _memsize = 6896
525
    _range = [(136000000, 174000000), (400000000, 511000000)]
526
    _power_levels = [
527
        chirp_common.PowerLevel("Low", watts=0.50),
528
        chirp_common.PowerLevel("Mid", watts=2.00),
529
        chirp_common.PowerLevel("High", watts=5.00),
530
        ]
531

    
532
    @classmethod
533
    def match_model(cls, filedata, filename):
534
        return len(filedata) == cls._memsize
535

    
536
    def _get_used(self, number):
537
        return self._memobj.memory[number].new_used
538

    
539
    def _set_used(self, number, is_used):
540
        self._memobj.memory[number].new_used = is_used
541

    
542
    def _get_power(self, _mem):
543
        return self._power_levels[_mem.power]
544

    
545
    def _set_power(self, _mem, mem):
546
        if mem.power in self._power_levels:
547
            _mem.power = self._power_levels.index(mem.power)
548

    
549
    def _download_chunk(self, addr):
550
        if addr % 16:
551
            raise Exception("Addr 0x%04x not on 16-byte boundary" % addr)
552

    
553
        cmd = "AL~F%04XR\r\n" % addr
554
        self._send(cmd)
555

    
556
        _data = self._read(34).strip()
557
        if len(_data) == 0:
558
            raise errors.RadioError("No response from radio")
559

    
560
        data = ""
561
        for i in range(0, len(_data), 2):
562
            data += chr(int(_data[i:i+2], 16))
563

    
564
        if len(data) != 16:
565
            LOG.debug("Response was:")
566
            LOG.debug("|%s|")
567
            LOG.debug("Which I converted to:")
568
            LOG.debug(util.hexprint(data))
569
            raise Exception("Radio returned less than 16 bytes")
570

    
571
        return data
572

    
573

    
574
DJG7EG_MEM_FORMAT = """
575
#seekto 0x200;
576
ul16 bank[50];
577
ul16 special_bank[7];
578
#seekto 0x1200;
579
struct {
580
    u8   unknown;
581
    ul32 freq;
582
    u8   mode;
583
    u8   step;
584
    ul32 offset;
585
    u8   duplex;
586
    u8   squelch_type;
587
    u8   tx_tone;
588
    u8   rx_tone;
589
    u8   dcs;
590
#seek 3;
591
    u8   skip;
592
#seek 12;
593
    char name[32];
594
} memory[1000];
595
"""
596

    
597

    
598
@directory.register
599
class AlincoDJG7EG(AlincoStyleRadio):
600
    """Alinco DJ-G7EG"""
601
    VENDOR = "Alinco"
602
    MODEL = "DJ-G7EG"
603
    BAUD_RATE = 57600
604

    
605
    # Those are different from the other Alinco radios.
606
    STEPS = [5.0, 6.25, 8.33, 10.0, 12.5, 15.0, 20.0, 25.0, 30.0, 50.0,
607
             100.0, 125.0, 150.0, 200.0, 500.0, 1000.0]
608
    DUPLEX = ["", "+", "-"]
609
    MODES = ["NFM", "FM", "AM", "WFM"]
610
    TMODES = ["", "??1", "Tone", "TSQL", "TSQL-R", "DTCS"]
611

    
612
    # This is a bit of a hack to avoid overwriting _identify()
613
    _model = "AL~DJ-G7EG"
614
    _memsize = 0x1a7c0
615
    _range = [(500000, 1300000000)]
616

    
617
    @classmethod
618
    def get_prompts(cls):
619
        rp = chirp_common.RadioPrompts()
620
        rp.pre_download = _(dedent("""\
621
            1. Ensure your firmware version is 4_10 or higher
622
            2. Turn radio off
623
            3. Connect your interface cable
624
            4. Turn radio on
625
            5. Press and release PTT 3 times while holding MONI key
626
            6. Supported baud rates: 57600 (default) and 19200
627
               (rotate dial while holding MONI to change)
628
            7. Click OK
629
            """))
630
        rp.pre_upload = _(dedent("""\
631
            1. Ensure your firmware version is 4_10 or higher
632
            2. Turn radio off
633
            3. Connect your interface cable
634
            4. Turn radio on
635
            5. Press and release PTT 3 times while holding MONI key
636
            6. Supported baud rates: 57600 (default) and 19200
637
               (rotate dial while holding MONI to change)
638
            7. Click OK
639
            """))
640
        return rp
641

    
642
    def get_features(self):
643
        rf = chirp_common.RadioFeatures()
644
        rf.has_dtcs_polarity = False
645
        rf.has_bank = False
646
        rf.has_settings = False
647

    
648
        rf.valid_modes = self.MODES
649
        rf.valid_tmodes = ["", "Tone", "TSQL", "Cross", "TSQL-R", "DTCS"]
650
        rf.valid_tuning_steps = self.STEPS
651
        rf.valid_bands = self._range
652
        rf.valid_skips = ["", "S"]
653
        rf.valid_characters = chirp_common.CHARSET_ASCII
654
        rf.valid_name_length = 16
655
        rf.memory_bounds = (0, 999)
656

    
657
        return rf
658

    
659
    def _download_chunk(self, addr):
660
        if addr % 0x40:
661
            raise Exception("Addr 0x%04x not on 64-byte boundary" % addr)
662

    
663
        cmd = "AL~F%05XR\r" % addr
664
        self._send(cmd)
665

    
666
        # Response: "\r\n[ ... data ... ]\r\n
667
        # data is encoded in hex, hence we read two chars per byte
668
        _data = self._read(2+2*64+2).strip()
669
        if len(_data) == 0:
670
            raise errors.RadioError("No response from radio")
671

    
672
        data = ""
673
        for i in range(0, len(_data), 2):
674
            data += chr(int(_data[i:i+2], 16))
675

    
676
        if len(data) != 64:
677
            LOG.debug("Response was:")
678
            LOG.debug("|%s|")
679
            LOG.debug("Which I converted to:")
680
            LOG.debug(util.hexprint(data))
681
            raise Exception("Chunk from radio has wrong size")
682

    
683
        return data
684

    
685
    def _detect_baudrate_and_identify(self):
686
        if self._identify():
687
            return True
688
        else:
689
            # Apparenly Alinco support suggests to try again at a lower baud
690
            # rate if their cable fails with the default rate. See #4355.
691
            LOG.info("Could not talk to radio. Trying again at 19200 baud")
692
            self.pipe.baudrate = 19200
693
            return self._identify()
694

    
695
    def _download(self, limit):
696
        self._detect_baudrate_and_identify()
697

    
698
        data = "\x00"*0x200
699

    
700
        for addr in range(0x200, limit, 0x40):
701
            data += self._download_chunk(addr)
702
            # Other Alinco drivers delay here, but doesn't seem to be necessary
703
            # for this model.
704

    
705
            if self.status_fn:
706
                status = chirp_common.Status()
707
                status.cur = addr
708
                status.max = limit
709
                status.msg = "Downloading from radio"
710
                self.status_fn(status)
711
        return memmap.MemoryMap(data)
712

    
713
    def _upload_chunk(self, addr):
714
        if addr % 0x40:
715
            raise Exception("Addr 0x%04x not on 64-byte boundary" % addr)
716

    
717
        _data = self._mmap[addr:addr+0x40]
718
        data = "".join(["%02X" % ord(x) for x in _data])
719

    
720
        cmd = "AL~F%05XW%s\r" % (addr, data)
721
        self._send(cmd)
722

    
723
        resp = self._read(6)
724
        if resp.strip() != "OK":
725
            raise Exception("Unexpected response from radio: %s" % resp)
726

    
727
    def _upload(self, limit):
728
        if not self._detect_baudrate_and_identify():
729
            raise Exception("I can't talk to this model")
730

    
731
        for addr in range(0x200, self._memsize, 0x40):
732
            self._upload_chunk(addr)
733
            # Other Alinco drivers delay here, but doesn't seem to be necessary
734
            # for this model.
735

    
736
            if self.status_fn:
737
                status = chirp_common.Status()
738
                status.cur = addr
739
                status.max = self._memsize
740
                status.msg = "Uploading to radio"
741
                self.status_fn(status)
742

    
743
    def process_mmap(self):
744
        self._memobj = bitwise.parse(DJG7EG_MEM_FORMAT, self._mmap)
745

    
746
    def get_memory(self, number):
747
        _mem = self._memobj.memory[number]
748
        mem = chirp_common.Memory()
749
        mem.number = number
750
        if _mem.unknown == 0:
751
            mem.empty = True
752
        else:
753
            mem.freq = int(_mem.freq)
754
            mem.mode = self.MODES[_mem.mode]
755
            mem.tuning_step = self.STEPS[_mem.step]
756
            mem.offset = int(_mem.offset)
757
            mem.duplex = self.DUPLEX[_mem.duplex]
758
            if self.TMODES[_mem.squelch_type] == "TSQL" and \
759
                    _mem.tx_tone != _mem.rx_tone:
760
                mem.tmode = "Cross"
761
                mem.cross_mode = "Tone->Tone"
762
            else:
763
                mem.tmode = self.TMODES[_mem.squelch_type]
764
            mem.rtone = ALINCO_TONES[_mem.tx_tone-1]
765
            mem.ctone = ALINCO_TONES[_mem.rx_tone-1]
766
            mem.dtcs = DCS_CODES[self.VENDOR][_mem.dcs]
767
            if _mem.skip:
768
                mem.skip = "S"
769
            # FIXME find out what every other byte is used for. Japanese?
770
            mem.name = str(_mem.name.get_raw()[::2]).rstrip('\0')
771
        return mem
772

    
773
    def set_memory(self, mem):
774
        # Get a low-level memory object mapped to the image
775
        _mem = self._memobj.memory[mem.number]
776
        if mem.empty:
777
            _mem.unknown = 0x00  # Maybe 0 is empty, 2 is used?
778
        else:
779
            _mem.unknown = 0x02
780
            _mem.freq = mem.freq
781
            _mem.mode = self.MODES.index(mem.mode)
782
            _mem.step = self.STEPS.index(mem.tuning_step)
783
            _mem.offset = mem.offset
784
            _mem.duplex = self.DUPLEX.index(mem.duplex)
785
            if mem.tmode == "Cross":
786
                _mem.squelch_type = self.TMODES.index("TSQL")
787
                try:
788
                    _mem.tx_tone = ALINCO_TONES.index(mem.rtone)+1
789
                except ValueError:
790
                    raise errors.UnsupportedToneError(
791
                        "This radio does not support tone %.1fHz" % mem.rtone)
792
                try:
793
                    _mem.rx_tone = ALINCO_TONES.index(mem.ctone)+1
794
                except ValueError:
795
                    raise errors.UnsupportedToneError(
796
                        "This radio does not support tone %.1fHz" % mem.ctone)
797
            elif mem.tmode == "TSQL":
798
                _mem.squelch_type = self.TMODES.index("TSQL")
799
                # Note how the same TSQL tone is copied to both memory
800
                # locaations
801
                try:
802
                    _mem.tx_tone = ALINCO_TONES.index(mem.ctone)+1
803
                    _mem.rx_tone = ALINCO_TONES.index(mem.ctone)+1
804
                except ValueError:
805
                    raise errors.UnsupportedToneError(
806
                        "This radio does not support tone %.1fHz" % mem.ctone)
807
            else:
808
                _mem.squelch_type = self.TMODES.index(mem.tmode)
809
                try:
810
                    _mem.tx_tone = ALINCO_TONES.index(mem.rtone)+1
811
                except ValueError:
812
                    raise errors.UnsupportedToneError(
813
                        "This radio does not support tone %.1fHz" % mem.rtone)
814
                try:
815
                    _mem.rx_tone = ALINCO_TONES.index(mem.ctone)+1
816
                except ValueError:
817
                    raise errors.UnsupportedToneError(
818
                        "This radio does not support tone %.1fHz" % mem.ctone)
819
            _mem.dcs = DCS_CODES[self.VENDOR].index(mem.dtcs)
820
            _mem.skip = (mem.skip == "S")
821
            _mem.name = "\x00".join(mem.name).ljust(32, "\x00")
(3-3/4)