Project

General

Profile

Bug #1611 » thd72.py

Tom Hayward, 03/19/2017 07:53 PM

 
1
# Copyright 2010 Vernon Mauery <vernon@mauery.org>
2
# Copyright 2016 Angus Ainslie <angus@akkea.ca>
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, errors, util, directory
18
from chirp import bitwise, memmap
19
from chirp.settings import RadioSettingGroup, RadioSetting, RadioSettings
20
from chirp.settings import RadioSettingValueInteger, RadioSettingValueString
21
from chirp.settings import RadioSettingValueList, RadioSettingValueBoolean
22
import time
23
import struct
24
import sys
25
import logging
26

    
27
LOG = logging.getLogger(__name__)
28

    
29
# TH-D72 memory map
30
# 0x0000..0x0200: startup password and other stuff
31
# 0x0200..0x0400: current channel and other settings
32
#   0x244,0x246: last menu numbers
33
#   0x249: last f menu number
34
# 0x0400..0x0c00: APRS settings and likely other settings
35
# 0x0c00..0x1500: memory channel flags
36
# 0x1500..0x5380: 0-999 channels
37
# 0x5380..0x54c0: 0-9 scan channels
38
# 0x54c0..0x5560: 0-9 wx channels
39
# 0x5560..0x5e00: ?
40
# 0x5e00..0x7d40: 0-999 channel names
41
# 0x7d40..0x7de0: ?
42
# 0x7de0..0x7e30: wx channel names
43
# 0x7e30..0x7ed0: ?
44
# 0x7ed0..0x7f20: group names
45
# 0x7f20..0x8b00: ?
46
# 0x8b00..0x9c00: last 20 APRS entries
47
# 0x9c00..0xe500: ?
48
# 0xe500..0xe7d0: startup bitmap
49
# 0xe7d0..0xe800: startup bitmap filename
50
# 0xe800..0xead0: gps-logger bitmap
51
# 0xe8d0..0xeb00: gps-logger bipmap filename
52
# 0xeb00..0xff00: ?
53
# 0xff00..0xffff: stuff?
54

    
55
# memory channel
56
# 0 1 2 3  4 5     6            7     8     9    a          b c d e   f
57
# [freq ]  ? mode  tmode/duplex rtone ctone dtcs cross_mode [offset]  ?
58

    
59
mem_format = """
60
#seekto 0x0000;
61
struct {
62
  ul16 version;
63
  u8   shouldbe32;
64
  u8   efs[11];
65
  u8   unknown0[3];
66
  u8   radio_custom_image;
67
  u8   gps_custom_image;
68
  u8   unknown1[7];
69
  u8   passwd[6];
70
} frontmatter;
71

    
72
#seekto 0x02c0;
73
struct {
74
  ul32 start_freq;
75
  ul32 end_freq;
76
} prog_vfo[6];
77

    
78
#seekto 0x0300;
79
struct {
80
  char power_on_msg[8];
81
  u8 unknown0[8];
82
  u8 unknown1[2];
83
  u8 lamp_timer;
84
  u8 contrast;
85
  u8 battery_saver;
86
  u8 APO;
87
  u8 unknown2;
88
  u8 key_beep;
89
  u8 unknown3[8];
90
  u8 unknown4;
91
  u8 balance;
92
  u8 unknown5[23];
93
  u8 lamp_control;
94
} settings;
95

    
96
#seekto 0x0c00;
97
struct {
98
  u8 disabled:4,
99
     prog_vfo:4;
100
  u8 skip;
101
} flag[1032];
102

    
103
#seekto 0x1500;
104
struct {
105
  ul32 freq;
106
  u8 unknown1;
107
  u8 mode;
108
  u8 tone_mode:4,
109
     duplex:4;
110
  u8 rtone;
111
  u8 ctone;
112
  u8 dtcs;
113
  u8 cross_mode;
114
  ul32 offset;
115
  u8 unknown2;
116
} memory[1032];
117

    
118
#seekto 0x5e00;
119
struct {
120
    char name[8];
121
} channel_name[1000];
122

    
123
#seekto 0x7de0;
124
struct {
125
    char name[8];
126
} wx_name[10];
127

    
128
#seekto 0x7ed0;
129
struct {
130
    char name[8];
131
} group_name[10];
132
"""
133

    
134
THD72_SPECIAL = {}
135

    
136
for i in range(0, 10):
137
    THD72_SPECIAL["L%i" % i] = 1000 + (i * 2)
138
    THD72_SPECIAL["U%i" % i] = 1000 + (i * 2) + 1
139
for i in range(0, 10):
140
    THD72_SPECIAL["WX%i" % (i + 1)] = 1020 + i
141
THD72_SPECIAL["C VHF"] = 1030
142
THD72_SPECIAL["C UHF"] = 1031
143

    
144
THD72_SPECIAL_REV = {}
145
for k, v in THD72_SPECIAL.items():
146
    THD72_SPECIAL_REV[v] = k
147

    
148
TMODES = {
149
    0x08: "Tone",
150
    0x04: "TSQL",
151
    0x02: "DTCS",
152
    0x01: "Cross",
153
    0x00: "",
154
}
155
TMODES_REV = {
156
    "": 0x00,
157
    "Cross": 0x01,
158
    "DTCS": 0x02,
159
    "TSQL": 0x04,
160
    "Tone": 0x08,
161
}
162

    
163
MODES = {
164
    0x00: "FM",
165
    0x01: "NFM",
166
    0x02: "AM",
167
}
168

    
169
MODES_REV = {
170
    "FM": 0x00,
171
    "NFM": 0x01,
172
    "AM": 0x2,
173
}
174

    
175
DUPLEX = {
176
    0x00: "",
177
    0x01: "+",
178
    0x02: "-",
179
    0x04: "split",
180
}
181
DUPLEX_REV = {
182
    "": 0x00,
183
    "+": 0x01,
184
    "-": 0x02,
185
    "split": 0x04,
186
}
187

    
188

    
189
EXCH_R = "R\x00\x00\x00\x00"
190
EXCH_W = "W\x00\x00\x00\x00"
191

    
192
DEFAULT_PROG_VFO = (
193
    (136000000, 174000000),
194
    (410000000, 470000000),
195
    (118000000, 136000000),
196
    (136000000, 174000000),
197
    (320000000, 400000000),
198
    (400000000, 524000000),
199
)
200
# index of PROG_VFO used for setting memory.unknown1 and memory.unknown2
201
UNKNOWN_LOOKUP = (0, 7, 4, 0, 4, 7)
202

    
203
def get_prog_vfo(frequency):
204
    for i, (start, end) in enumerate(DEFAULT_PROG_VFO):
205
        if start <= frequency < end:
206
            return i
207
    raise ValueError("Frequency is out of range.")
208

    
209

    
210
@directory.register
211
class THD72Radio(chirp_common.CloneModeRadio):
212

    
213
    BAUD_RATE = 9600
214
    VENDOR = "Kenwood"
215
    MODEL = "TH-D72 (clone mode)"
216
    HARDWARE_FLOW = sys.platform == "darwin"  # only OS X driver needs hw flow
217

    
218
    mem_upper_limit = 1022
219
    _memsize = 65536
220
    _model = ""  # FIXME: REMOVE
221
    _dirty_blocks = []
222

    
223
    _LCD_CONTRAST = ["Level %d" % x for x in range(1, 16)]
224
    _LAMP_CONTROL = ["Manual", "Auto"]
225
    _LAMP_TIMER = ["Seconds %d" % x for x in range(2, 11)]
226
    _BATTERY_SAVER = ["OFF", "0.03 Seconds", "0.2 Seconds", "0.4 Seconds",
227
                      "0.6 Seconds", "0.8 Seconds", "1 Seconds", "2 Seconds",
228
                      "3 Seconds", "4 Seconds", "5 Seconds"]
229
    _APO = ["OFF", "15 Minutes", "30 Minutes", "60 Minutes"]
230
    _AUDIO_BALANCE = ["Center", "A +50%", "A +100%", "B +50%", "B +100%"]
231
    _KEY_BEEP = ["OFF", "Radio & GPS", "Radio Only", "GPS Only"]
232

    
233
    def get_features(self):
234
        rf = chirp_common.RadioFeatures()
235
        rf.memory_bounds = (0, 1031)
236
        rf.valid_bands = [(118000000, 174000000),
237
                          (320000000, 524000000)]
238
        rf.has_cross = True
239
        rf.can_odd_split = True
240
        rf.has_dtcs_polarity = False
241
        rf.has_tuning_step = False
242
        rf.has_bank = False
243
        rf.has_settings = True
244
        rf.valid_tuning_steps = []
245
        rf.valid_modes = MODES_REV.keys()
246
        rf.valid_tmodes = TMODES_REV.keys()
247
        rf.valid_duplexes = DUPLEX_REV.keys()
248
        rf.valid_skips = ["", "S"]
249
        rf.valid_characters = chirp_common.CHARSET_ALPHANUMERIC
250
        rf.valid_name_length = 8
251
        return rf
252

    
253
    def process_mmap(self):
254
        self._memobj = bitwise.parse(mem_format, self._mmap)
255
        self._dirty_blocks = []
256

    
257
    def _detect_baud(self):
258
        for baud in [9600, 19200, 38400, 57600]:
259
            self.pipe.baudrate = baud
260
            try:
261
                self.pipe.write("\r\r")
262
            except:
263
                break
264
            self.pipe.read(32)
265
            try:
266
                id = self.get_id()
267
                LOG.info("Radio %s at %i baud" % (id, baud))
268
                return True
269
            except errors.RadioError:
270
                pass
271

    
272
        raise errors.RadioError("No response from radio")
273

    
274
    def get_special_locations(self):
275
        return sorted(THD72_SPECIAL.keys())
276

    
277
    def add_dirty_block(self, memobj):
278
        block = memobj._offset / 256
279
        if block not in self._dirty_blocks:
280
            self._dirty_blocks.append(block)
281
        self._dirty_blocks.sort()
282
        print("dirty blocks: ", self._dirty_blocks)
283

    
284
    def get_channel_name(self, number):
285
        if number < 999:
286
            name = str(self._memobj.channel_name[number].name) + '\xff'
287
        elif number >= 1020 and number < 1030:
288
            number -= 1020
289
            name = str(self._memobj.wx_name[number].name) + '\xff'
290
        else:
291
            return ''
292
        return name[:name.index('\xff')].rstrip()
293

    
294
    def set_channel_name(self, number, name):
295
        name = name[:8] + '\xff' * 8
296
        if number < 999:
297
            self._memobj.channel_name[number].name = name[:8]
298
            self.add_dirty_block(self._memobj.channel_name[number])
299
        elif number >= 1020 and number < 1030:
300
            number -= 1020
301
            self._memobj.wx_name[number].name = name[:8]
302
            self.add_dirty_block(self._memobj.wx_name[number])
303

    
304
    def get_raw_memory(self, number):
305
        return repr(self._memobj.memory[number]) + \
306
            repr(self._memobj.flag[number])
307

    
308
    def get_memory(self, number):
309
        if isinstance(number, str):
310
            try:
311
                number = THD72_SPECIAL[number]
312
            except KeyError:
313
                raise errors.InvalidMemoryLocation("Unknown channel %s" %
314
                                                   number)
315

    
316
        if number < 0 or number > (max(THD72_SPECIAL.values()) + 1):
317
            raise errors.InvalidMemoryLocation(
318
                "Number must be between 0 and 999")
319

    
320
        _mem = self._memobj.memory[number]
321
        flag = self._memobj.flag[number]
322

    
323
        mem = chirp_common.Memory()
324
        mem.number = number
325

    
326
        if number > 999:
327
            mem.extd_number = THD72_SPECIAL_REV[number]
328
        if flag.disabled == 0xf:
329
            mem.empty = True
330
            return mem
331

    
332
        mem.name = self.get_channel_name(number)
333
        mem.freq = int(_mem.freq)
334
        mem.tmode = TMODES[int(_mem.tone_mode)]
335
        mem.rtone = chirp_common.TONES[_mem.rtone]
336
        mem.ctone = chirp_common.TONES[_mem.ctone]
337
        mem.dtcs = chirp_common.DTCS_CODES[_mem.dtcs]
338
        mem.duplex = DUPLEX[int(_mem.duplex)]
339
        mem.offset = int(_mem.offset)
340
        mem.mode = MODES[int(_mem.mode)]
341

    
342
        if number < 999:
343
            mem.skip = chirp_common.SKIP_VALUES[int(flag.skip)]
344
            mem.cross_mode = chirp_common.CROSS_MODES[_mem.cross_mode]
345
        if number > 999:
346
            mem.cross_mode = chirp_common.CROSS_MODES[0]
347
            mem.immutable = ["number", "bank", "extd_number", "cross_mode"]
348
            if number >= 1020 and number < 1030:
349
                mem.immutable += ["freq", "offset", "tone", "mode",
350
                                  "tmode", "ctone", "skip"]  # FIXME: ALL
351
            else:
352
                mem.immutable += ["name"]
353

    
354
        return mem
355

    
356
    def set_memory(self, mem):
357
        LOG.debug("set_memory(%d)" % mem.number)
358
        if mem.number < 0 or mem.number > (max(THD72_SPECIAL.values()) + 1):
359
            raise errors.InvalidMemoryLocation(
360
                "Number must be between 0 and 999")
361

    
362
        # weather channels can only change name, nothing else
363
        if mem.number >= 1020 and mem.number < 1030:
364
            self.set_channel_name(mem.number, mem.name)
365
            return
366

    
367
        flag = self._memobj.flag[mem.number]
368
        self.add_dirty_block(self._memobj.flag[mem.number])
369

    
370
        # only delete non-WX channels
371
        was_empty = flag.disabled == 0xf
372
        if mem.empty:
373
            flag.disabled = 0xf
374
            return
375
        flag.disabled = 0
376

    
377
        _mem = self._memobj.memory[mem.number]
378
        self.add_dirty_block(_mem)
379
        if was_empty:
380
            self.initialize(_mem)
381

    
382
        _mem.freq = mem.freq
383

    
384
        if mem.number < 999:
385
            self.set_channel_name(mem.number, mem.name)
386

    
387
        _mem.tone_mode = TMODES_REV[mem.tmode]
388
        _mem.rtone = chirp_common.TONES.index(mem.rtone)
389
        _mem.ctone = chirp_common.TONES.index(mem.ctone)
390
        _mem.dtcs = chirp_common.DTCS_CODES.index(mem.dtcs)
391
        _mem.cross_mode = chirp_common.CROSS_MODES.index(mem.cross_mode)
392
        _mem.duplex = DUPLEX_REV[mem.duplex]
393
        _mem.offset = mem.offset
394
        _mem.mode = MODES_REV[mem.mode]
395

    
396
        prog_vfo = get_prog_vfo(mem.freq)
397
        flag.prog_vfo = prog_vfo
398
        _mem.unknown1 = _mem.unknown2 = UNKNOWN_LOOKUP[prog_vfo]
399

    
400
        if mem.number < 999:
401
            flag.skip = chirp_common.SKIP_VALUES.index(mem.skip)
402

    
403
    def sync_in(self):
404
        self._detect_baud()
405
        self._mmap = self.download()
406
        self.process_mmap()
407

    
408
    def sync_out(self):
409
        self._detect_baud()
410
        if len(self._dirty_blocks):
411
            self.upload(self._dirty_blocks)
412
        else:
413
            self.upload()
414

    
415
    def read_block(self, block, count=256):
416
        self.pipe.write(struct.pack("<cBHB", "R", 0, block, 0))
417
        r = self.pipe.read(5)
418
        if len(r) != 5:
419
            raise Exception("Did not receive block response")
420

    
421
        cmd, _zero, _block, zero = struct.unpack("<cBHB", r)
422
        if cmd != "W" or _block != block:
423
            raise Exception("Invalid response: %s %i" % (cmd, _block))
424

    
425
        data = ""
426
        while len(data) < count:
427
            data += self.pipe.read(count - len(data))
428

    
429
        self.pipe.write(chr(0x06))
430
        if self.pipe.read(1) != chr(0x06):
431
            raise Exception("Did not receive post-block ACK!")
432

    
433
        return data
434

    
435
    def write_block(self, block, map):
436
        self.pipe.write(struct.pack("<cBHB", "W", 0, block, 0))
437
        base = block * 256
438
        self.pipe.write(map[base:base + 256])
439

    
440
        ack = self.pipe.read(1)
441

    
442
        return ack == chr(0x06)
443

    
444
    def download(self, raw=False, blocks=None):
445
        if blocks is None:
446
            blocks = range(self._memsize / 256)
447
        else:
448
            blocks = [b for b in blocks if b < self._memsize / 256]
449

    
450
        if self.command("0M PROGRAM") != "0M":
451
            raise errors.RadioError("No response from self")
452

    
453
        allblocks = range(self._memsize / 256)
454
        self.pipe.baudrate = 57600
455
        try:
456
            self.pipe.setRTS()
457
        except AttributeError:
458
            self.pipe.rts = True
459
        self.pipe.read(1)
460
        data = ""
461
        LOG.debug("reading blocks %d..%d" % (blocks[0], blocks[-1]))
462
        total = len(blocks)
463
        count = 0
464
        for i in allblocks:
465
            if i not in blocks:
466
                data += 256 * '\xff'
467
                continue
468
            data += self.read_block(i)
469
            count += 1
470
            if self.status_fn:
471
                s = chirp_common.Status()
472
                s.msg = "Cloning from radio"
473
                s.max = total
474
                s.cur = count
475
                self.status_fn(s)
476

    
477
        self.pipe.write("E")
478

    
479
        if raw:
480
            return data
481
        return memmap.MemoryMap(data)
482

    
483
    def upload(self, blocks=None):
484
        if blocks is None:
485
            blocks = range((self._memsize / 256) - 2)
486
        else:
487
            blocks = [b for b in blocks if b < self._memsize / 256]
488

    
489
        if self.command("0M PROGRAM") != "0M":
490
            raise errors.RadioError("No response from self")
491

    
492
        self.pipe.baudrate = 57600
493
        try:
494
            self.pipe.setRTS()
495
        except AttributeError:
496
            self.pipe.rts = True
497
        self.pipe.read(1)
498
        LOG.debug("writing blocks %d..%d" % (blocks[0], blocks[-1]))
499
        total = len(blocks)
500
        count = 0
501
        for i in blocks:
502
            r = self.write_block(i, self._mmap)
503
            count += 1
504
            if not r:
505
                raise errors.RadioError("self NAK'd block %i" % i)
506
            if self.status_fn:
507
                s = chirp_common.Status()
508
                s.msg = "Cloning to radio"
509
                s.max = total
510
                s.cur = count
511
                self.status_fn(s)
512

    
513
        self.pipe.write("E")
514
        # clear out blocks we uploaded from the dirty blocks list
515
        self._dirty_blocks = [b for b in self._dirty_blocks if b not in blocks]
516

    
517
    def command(self, cmd, timeout=0.5):
518
        start = time.time()
519

    
520
        data = ""
521
        LOG.debug("PC->D72: %s" % cmd)
522
        self.pipe.write(cmd + "\r")
523
        while not data.endswith("\r") and (time.time() - start) < timeout:
524
            data += self.pipe.read(1)
525
        LOG.debug("D72->PC: %s" % data.strip())
526
        return data.strip()
527

    
528
    def get_id(self):
529
        r = self.command("ID")
530
        if r.startswith("ID "):
531
            return r.split(" ")[1]
532
        else:
533
            raise errors.RadioError("No response to ID command")
534

    
535
    def initialize(self, mmap):
536
        mmap.set_raw("\x00\xc8\xb3\x08\x00\x01\x00\x08"
537
                     "\x08\x00\xc0\x27\x09\x00\x00\x00")
538

    
539
    def _get_settings(self):
540
        top = RadioSettings(self._get_display_settings(),
541
                            self._get_audio_settings(),
542
                            self._get_battery_settings())
543
        return top
544

    
545
    def set_settings(self, settings):
546
        _mem = self._memobj
547
        for element in settings:
548
            if not isinstance(element, RadioSetting):
549
                self.set_settings(element)
550
                continue
551
            if not element.changed():
552
                continue
553
            try:
554
                if element.has_apply_callback():
555
                    LOG.debug("Using apply callback")
556
                    try:
557
                        element.run_apply_callback()
558
                    except NotImplementedError as e:
559
                        LOG.error(e)
560
                    continue
561

    
562
                # Find the object containing setting.
563
                obj = _mem
564
                bits = element.get_name().split(".")
565
                setting = bits[-1]
566
                for name in bits[:-1]:
567
                    if name.endswith("]"):
568
                        name, index = name.split("[")
569
                        index = int(index[:-1])
570
                        obj = getattr(obj, name)[index]
571
                    else:
572
                        obj = getattr(obj, name)
573

    
574
                try:
575
                    old_val = getattr(obj, setting)
576
                    LOG.debug("Setting %s(%r) <= %s" % (
577
                        element.get_name(), old_val, element.value))
578
                    setattr(obj, setting, element.value)
579
                except AttributeError as e:
580
                    LOG.error("Setting %s is not in the memory map: %s" %
581
                              (element.get_name(), e))
582
            except Exception, e:
583
                LOG.debug(element.get_name())
584
                raise
585

    
586
    def get_settings(self):
587
        try:
588
            return self._get_settings()
589
        except:
590
            import traceback
591
            LOG.error("Failed to parse settings: %s", traceback.format_exc())
592
            return None
593

    
594
    @classmethod
595
    def apply_power_on_msg(cls, setting, obj):
596
        message = setting.value.get_value()
597
        setattr(obj, "power_on_msg", cls._add_ff_pad(message, 8))
598

    
599
    def apply_lcd_contrast(cls, setting, obj):
600
        rawval = setting.value.get_value()
601
        val = cls._LCD_CONTRAST.index(rawval) + 1
602
        obj.contrast = val
603

    
604
    def apply_lamp_control(cls, setting, obj):
605
        rawval = setting.value.get_value()
606
        val = cls._LAMP_CONTROL.index(rawval)
607
        obj.lamp_control = val
608

    
609
    def apply_lamp_timer(cls, setting, obj):
610
        rawval = setting.value.get_value()
611
        val = cls._LAMP_TIMER.index(rawval) + 2
612
        obj.lamp_timer = val
613

    
614
    def _get_display_settings(self):
615
        menu = RadioSettingGroup("display", "Display")
616
        display_settings = self._memobj.settings
617

    
618
        val = RadioSettingValueString(
619
            0, 8, str(display_settings.power_on_msg).rstrip("\xFF"))
620
        rs = RadioSetting("display.power_on_msg", "Power on message", val)
621
        rs.set_apply_callback(self.apply_power_on_msg, display_settings)
622
        menu.append(rs)
623

    
624
        val = RadioSettingValueList(
625
            self._LCD_CONTRAST,
626
            self._LCD_CONTRAST[display_settings.contrast - 1])
627
        rs = RadioSetting("display.contrast", "LCD Contrast",
628
                          val)
629
        rs.set_apply_callback(self.apply_lcd_contrast, display_settings)
630
        menu.append(rs)
631

    
632
        val = RadioSettingValueList(
633
            self._LAMP_CONTROL,
634
            self._LAMP_CONTROL[display_settings.lamp_control])
635
        rs = RadioSetting("display.lamp_control", "Lamp Control",
636
                          val)
637
        rs.set_apply_callback(self.apply_lamp_control, display_settings)
638
        menu.append(rs)
639

    
640
        val = RadioSettingValueList(
641
            self._LAMP_TIMER,
642
            self._LAMP_TIMER[display_settings.lamp_timer - 2])
643
        rs = RadioSetting("display.lamp_timer", "Lamp Timer",
644
                          val)
645
        rs.set_apply_callback(self.apply_lamp_timer, display_settings)
646
        menu.append(rs)
647

    
648
        return menu
649

    
650
    def apply_battery_saver(cls, setting, obj):
651
        rawval = setting.value.get_value()
652
        val = cls._BATTERY_SAVER.index(rawval)
653
        obj.battery_saver = val
654

    
655
    def apply_APO(cls, setting, obj):
656
        rawval = setting.value.get_value()
657
        val = cls._APO.index(rawval)
658
        obj.APO = val
659

    
660
    def _get_battery_settings(self):
661
        menu = RadioSettingGroup("battery", "Battery")
662
        battery_settings = self._memobj.settings
663

    
664
        val = RadioSettingValueList(
665
            self._BATTERY_SAVER,
666
            self._BATTERY_SAVER[battery_settings.battery_saver])
667
        rs = RadioSetting("battery.battery_saver", "Battery Saver",
668
                          val)
669
        rs.set_apply_callback(self.apply_battery_saver, battery_settings)
670
        menu.append(rs)
671

    
672
        val = RadioSettingValueList(
673
            self._APO,
674
            self._APO[battery_settings.APO])
675
        rs = RadioSetting("battery.APO", "Auto Power Off",
676
                          val)
677
        rs.set_apply_callback(self.apply_APO, battery_settings)
678
        menu.append(rs)
679

    
680
        return menu
681

    
682
    def apply_balance(cls, setting, obj):
683
        rawval = setting.value.get_value()
684
        val = cls._AUDIO_BALANCE.index(rawval)
685
        obj.balance = val
686

    
687
    def apply_key_beep(cls, setting, obj):
688
        rawval = setting.value.get_value()
689
        val = cls._KEY_BEEP.index(rawval)
690
        obj.key_beep = val
691

    
692
    def _get_audio_settings(self):
693
        menu = RadioSettingGroup("audio", "Audio")
694
        audio_settings = self._memobj.settings
695

    
696
        val = RadioSettingValueList(
697
            self._AUDIO_BALANCE,
698
            self._AUDIO_BALANCE[audio_settings.balance])
699
        rs = RadioSetting("audio.balance", "Balance",
700
                          val)
701
        rs.set_apply_callback(self.apply_balance, audio_settings)
702
        menu.append(rs)
703

    
704
        val = RadioSettingValueList(
705
            self._KEY_BEEP,
706
            self._KEY_BEEP[audio_settings.key_beep])
707
        rs = RadioSetting("audio.key_beep", "Key Beep",
708
                          val)
709
        rs.set_apply_callback(self.apply_key_beep, audio_settings)
710
        menu.append(rs)
711

    
712
        return menu
713

    
714
    @staticmethod
715
    def _add_ff_pad(val, length):
716
        return val.ljust(length, "\xFF")[:length]
717

    
718
    @classmethod
719
    def _strip_ff_pads(cls, messages):
720
        result = []
721
        for msg_text in messages:
722
            result.append(str(msg_text).rstrip("\xFF"))
723
        return result
724

    
725
if __name__ == "__main__":
726
    import sys
727
    import serial
728
    import detect
729
    import getopt
730

    
731
    def fixopts(opts):
732
        r = {}
733
        for opt in opts:
734
            k, v = opt
735
            r[k] = v
736
        return r
737

    
738
    def usage():
739
        print "Usage: %s <-i input.img>|<-o output.img> -p port " \
740
            "[[-f first-addr] [-l last-addr] | [-b list,of,blocks]]" % \
741
            sys.argv[0]
742
        sys.exit(1)
743

    
744
    opts, args = getopt.getopt(sys.argv[1:], "i:o:p:f:l:b:")
745
    opts = fixopts(opts)
746
    first = last = 0
747
    blocks = None
748
    if '-i' in opts:
749
        fname = opts['-i']
750
        download = False
751
    elif '-o' in opts:
752
        fname = opts['-o']
753
        download = True
754
    else:
755
        usage()
756
    if '-p' in opts:
757
        port = opts['-p']
758
    else:
759
        usage()
760

    
761
    if '-f' in opts:
762
        first = int(opts['-f'], 0)
763
    if '-l' in opts:
764
        last = int(opts['-l'], 0)
765
    if '-b' in opts:
766
        blocks = [int(b, 0) for b in opts['-b'].split(',')]
767
        blocks.sort()
768

    
769
    ser = serial.Serial(port=port, baudrate=9600, timeout=0.25)
770
    r = THD72Radio(ser)
771
    memmax = r._memsize
772
    if not download:
773
        memmax -= 512
774

    
775
    if blocks is None:
776
        if first < 0 or first > (r._memsize - 1):
777
            raise errors.RadioError("first address out of range")
778
        if (last > 0 and last < first) or last > memmax:
779
            raise errors.RadioError("last address out of range")
780
        elif last == 0:
781
            last = memmax
782
        first /= 256
783
        if last % 256 != 0:
784
            last += 256
785
        last /= 256
786
        blocks = range(first, last)
787

    
788
    if download:
789
        data = r.download(True, blocks)
790
        file(fname, "wb").write(data)
791
    else:
792
        r._mmap = file(fname, "rb").read(r._memsize)
793
        r.upload(blocks)
794
    print "\nDone"
(11-11/11)