Project

General

Profile

New Model #4933 » my-bf-t1.py

Henk Groningen, 12/16/2017 08:13 AM

 
1
# Copyright 2017 Pavel Milanes, CO7WT, <pavelmc@gmail.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 2 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
# 16-12-217 Added Basic Settings Tab, PA3CQN
17

    
18
import time
19
import struct
20
import logging
21

    
22
LOG = logging.getLogger(__name__)
23

    
24
from time import sleep
25
from chirp import chirp_common, directory, memmap
26
from chirp import bitwise, errors, util
27
from chirp.settings import RadioSetting, RadioSettingGroup, \
28
				RadioSettingValueBoolean, RadioSettingValueList, \
29
				RadioSettingValueInteger, RadioSettingValueString, \
30
				RadioSettingValueFloat, RadioSettings
31
from textwrap import dedent
32

    
33
# A note about the memmory in these radios
34
# mainly speculation until proven otherwise:
35
#
36
# The '9100' OEM software only manipulates the lower 0x180 bytes on read/write
37
# operations as we know, the file generated by the OEM software IN NOT an exact
38
# eeprom image, it's a crude text file with a pseudo csv format
39

    
40
MEM_SIZE = 0x180 # 384 bytes
41
BLOCK_SIZE = 0x10
42
ACK_CMD = "\x06"
43
MODES = ["NFM", "FM"]
44
SKIP_VALUES = ["S", ""]
45
TONES = chirp_common.TONES
46
DTCS = sorted(chirp_common.DTCS_CODES + [645])
47

    
48
# This is a general serial timeout for all serial read functions.
49
# Practice has show that about 0.07 sec will be enough to cover all radios.
50
STIMEOUT = 0.07
51

    
52
# this var controls the verbosity in the debug and by default it's low (False)
53
# make it True and you will to get a very verbose debug.log
54
debug = True
55

    
56
##### ID strings #####################################################
57

    
58
# BF-T1 handheld
59
BFT1_magic = "\x05PROGRAM"
60
BFT1_ident = " BF9100S"
61

    
62

    
63
def _clean_buffer(radio):
64
	"""Cleaning the read serial buffer, hard timeout to survive an infinite
65
	data stream"""
66

    
67
	dump = "1"
68
	datacount = 0
69

    
70
	try:
71
		while len(dump) > 0:
72
			dump = radio.pipe.read(100)
73
			datacount += len(dump)
74
			# hard limit to survive a infinite serial data stream
75
			# 5 times bigger than a normal rx block (20 bytes)
76
			if datacount > 101:
77
				seriale = "Please check your serial port selection."
78
				raise errors.RadioError(seriale)
79

    
80
	except Exception:
81
		raise errors.RadioError("Unknown error cleaning the serial buffer")
82

    
83

    
84
def _rawrecv(radio, amount = 0):
85
	"""Raw read from the radio device"""
86

    
87
	# var to hold the data to return
88
	data = ""
89

    
90
	try:
91
		if amount == 0:
92
			data = radio.pipe.read()
93
		else:
94
			data = radio.pipe.read(amount)
95

    
96
		# DEBUG
97
		if debug is True:
98
			LOG.debug("<== (%d) bytes:\n\n%s" %
99
					  (len(data), util.hexprint(data)))
100

    
101
		# fail if no data is received
102
		if len(data) == 0:
103
			raise errors.RadioError("No data received from radio")
104

    
105
	except:
106
		raise errors.RadioError("Error reading data from radio")
107

    
108
	return data
109

    
110

    
111
def _send(radio, data):
112
	"""Send data to the radio device"""
113

    
114
	try:
115
		radio.pipe.write(data)
116

    
117
		# DEBUG
118
		if debug is True:
119
			LOG.debug("==> (%d) bytes:\n\n%s" %
120
					  (len(data), util.hexprint(data)))
121
	except:
122
		raise errors.RadioError("Error sending data to radio")
123

    
124

    
125
def _make_frame(cmd, addr, data=""):
126
	"""Pack the info in the header format"""
127
	frame = struct.pack(">BHB", ord(cmd), addr, BLOCK_SIZE)
128

    
129
	# add the data if set
130
	if len(data) != 0:
131
		frame += data
132

    
133
	return frame
134

    
135

    
136
def _recv(radio, addr):
137
	"""Get data from the radio"""
138

    
139
	# Get the full 20 bytes at a time
140
	# 4 bytes header + 16 bytes of data (BLOCK_SIZE)
141

    
142
	# get the whole block
143
	block = _rawrecv(radio, BLOCK_SIZE + 4)
144

    
145
	# short answer
146
	if len(block) < (BLOCK_SIZE + 4):
147
		raise errors.RadioError("Wrong block length (short) at 0x%04x" % addr)
148

    
149
	# long answer
150
	if len(block) > (BLOCK_SIZE + 4):
151
		raise errors.RadioError("Wrong block length (long) at 0x%04x" % addr)
152

    
153

    
154
	# header validation
155
	c, a, l = struct.unpack(">cHB", block[0:4])
156
	if c != "W" or a != addr or l != BLOCK_SIZE:
157
		LOG.debug("Invalid header for block 0x%04x:" % addr)
158
		LOG.debug("CMD: %s  ADDR: %04x  SIZE: %02x" % (c, a, l))
159
		raise errors.RadioError("Invalid header for block 0x%04x:" % addr)
160

    
161
	# return the data, 16 bytes of payload
162
	return block[4:]
163

    
164

    
165
def _start_clone_mode(radio, status):
166
	"""Put the radio in clone mode, 3 tries"""
167

    
168
	# cleaning the serial buffer
169
	_clean_buffer(radio)
170

    
171
	# prep the data to show in the UI
172
	status.cur = 0
173
	status.msg = "Identifying the radio..."
174
	status.max = 3
175
	radio.status_fn(status)
176

    
177
	try:
178
		for a in range(0, status.max):
179
			# Update the UI
180
			status.cur = a + 1
181
			radio.status_fn(status)
182

    
183
			# send the magic word
184
			_send(radio, radio._magic)
185

    
186
			# Now you get a x06 of ACK if all goes well
187
			ack = _rawrecv(radio, 1)
188

    
189
			if ack == ACK_CMD:
190
				# DEBUG
191
				LOG.info("Magic ACK received")
192
				status.cur = status.max
193
				radio.status_fn(status)
194

    
195
				return True
196

    
197
		return False
198

    
199
	except errors.RadioError:
200
		raise
201
	except Exception, e:
202
		raise errors.RadioError("Error sending Magic to radio:\n%s" % e)
203

    
204

    
205
def _do_ident(radio, status):
206
	"""Put the radio in PROGRAM mode & identify it"""
207
	#  set the serial discipline (default)
208
	radio.pipe.baudrate = 9600
209
	radio.pipe.parity = "N"
210
	radio.pipe.bytesize = 8
211
	radio.pipe.stopbits = 1
212
	radio.pipe.timeout = STIMEOUT
213

    
214
	# open the radio into program mode
215
	if _start_clone_mode(radio, status) is False:
216
		raise errors.RadioError("Radio did not enter clone mode, wrong model?")
217

    
218
	# Ok, poke it to get the ident string
219
	_send(radio, "\x02")
220
	ident = _rawrecv(radio, len(radio._id))
221

    
222
	# basic check for the ident
223
	if len(ident) != len(radio._id):
224
		raise errors.RadioError("Radio send a odd identification block.")
225

    
226
	# check if ident is OK
227
	if ident != radio._id:
228
		LOG.debug("Incorrect model ID, got this:\n\n" + util.hexprint(ident))
229
		raise errors.RadioError("Radio identification failed.")
230

    
231
	# handshake
232
	_send(radio, ACK_CMD)
233
	ack = _rawrecv(radio, 1)
234

    
235
	#checking handshake
236
	if len(ack) == 1 and ack == ACK_CMD:
237
		# DEBUG
238
		LOG.info("ID ACK received")
239
	else:
240
		LOG.debug("Radio handshake failed.")
241
		raise errors.RadioError("Radio handshake failed.")
242

    
243
	# DEBUG
244
	LOG.info("Positive ident, this is a %s %s" % (radio.VENDOR, radio.MODEL))
245

    
246
	return True
247

    
248

    
249
def _download(radio):
250
	"""Get the memory map"""
251

    
252
	# UI progress
253
	status = chirp_common.Status()
254

    
255
	# put radio in program mode and identify it
256
	_do_ident(radio, status)
257

    
258
	# reset the progress bar in the UI
259
	status.max = MEM_SIZE / BLOCK_SIZE
260
	status.msg = "Cloning from radio..."
261
	status.cur = 0
262
	radio.status_fn(status)
263

    
264
	# cleaning the serial buffer
265
	_clean_buffer(radio)
266

    
267
	data = ""
268
	for addr in range(0, MEM_SIZE, BLOCK_SIZE):
269
		# sending the read request
270
		_send(radio, _make_frame("R", addr))
271

    
272
		# read
273
		d = _recv(radio, addr)
274

    
275
		# aggregate the data
276
		data += d
277

    
278
		# UI Update
279
		status.cur = addr / BLOCK_SIZE
280
		status.msg = "Cloning from radio..."
281
		radio.status_fn(status)
282

    
283
	# close comms with the radio
284
	_send(radio, "\x62")
285
	# DEBUG
286
	LOG.info("Close comms cmd sent, radio must reboot now.")
287

    
288
	return data
289

    
290

    
291
def _upload(radio):
292
	"""Upload procedure"""
293

    
294
	# UI progress
295
	status = chirp_common.Status()
296

    
297
	# put radio in program mode and identify it
298
	_do_ident(radio, status)
299

    
300
	# get the data to upload to radio
301
	data = radio.get_mmap()
302

    
303
	# Reset the UI progress
304
	status.max = MEM_SIZE / BLOCK_SIZE
305
	status.cur = 0
306
	status.msg = "Cloning to radio..."
307
	radio.status_fn(status)
308

    
309
	# cleaning the serial buffer
310
	_clean_buffer(radio)
311

    
312
	# the fun start here
313
	for addr in range(0, MEM_SIZE, BLOCK_SIZE):
314
		# getting the block of data to send
315
		d = data[addr:addr + BLOCK_SIZE]
316

    
317
		# build the frame to send
318
		frame = _make_frame("W", addr, d)
319

    
320
		# send the frame
321
		_send(radio, frame)
322

    
323
		# receiving the response
324
		ack = _rawrecv(radio, 1)
325

    
326
		# basic check
327
		if len(ack) != 1:
328
			raise errors.RadioError("No ACK when writing block 0x%04x" % addr)
329

    
330
		if ack != ACK_CMD:
331
			raise errors.RadioError("Bad ACK writing block 0x%04x:" % addr)
332

    
333
		 # UI Update
334
		status.cur = addr / BLOCK_SIZE
335
		status.msg = "Cloning to radio..."
336
		radio.status_fn(status)
337

    
338
	# close comms with the radio
339
	_send(radio, "\x62")
340
	# DEBUG
341
	LOG.info("Close comms cmd sent, radio must reboot now.")
342

    
343

    
344
def _model_match(cls, data):
345
	"""Match the opened/downloaded image to the correct version"""
346

    
347
	# we don't have a reliable fingerprint in the mem space by now.
348
	# then we just aim for a specific zone filled with \xff
349
	rid = data[0x0158:0x0160]
350

    
351
	if rid == ("\xff" * 8):
352
		return True
353

    
354
	return False
355

    
356

    
357
def _decode_ranges(low, high):
358
	"""Unpack the data in the ranges zones in the memmap and return
359
	a tuple with the integer corresponding to the Mhz it means"""
360
	return (int(low) * 100000, int(high) * 100000)
361

    
362

    
363
MEM_FORMAT = """
364
#seekto 0x0000;         // normal 1-20 mem channels
365
						// channel 0 is Emergent CH
366
struct {
367
  lbcd rxfreq[4];       // rx freq.
368
  u8 rxtone;            // x00 = none
369
						// x01 - x32 = index of the analog tones
370
						// x33 - x9b = index of Digital tones
371
						// Digital tone polarity is handled below
372
  lbcd txoffset[4];     // the difference against RX
373
						// pending to find the offset polarity in settings
374
  u8 txtone;            // Idem to rxtone
375
  u8 noskip:1,      // if true is included in the scan
376
	 wide:1,        // 1 = Wide, 0 = Narrow
377
	 ttondinv:1,    // if true TX tone is Digital & Inverted
378
	 unA:1,         //
379
	 rtondinv:1,    // if true RX tone is Digital & Inverted
380
	 unB:1,         //
381
	 offplus:1,     // TX = RX + offset
382
	 offminus:1;    // TX = RX - offset
383
  u8 empty[5];
384
} memory[21];
385

    
386
#seekto 0x0150;     // Unknown data... settings?
387
struct {
388
  lbcd vhfl[2];     // VHF low limit
389
  lbcd vhfh[2];     // VHF high limit
390
  lbcd uhfl[2];     // UHF low limit
391
  lbcd uhfh[2];     // UHF high limit
392
  u8 finger[8];     // can we use this as fingerprint "\xFF" * 16
393
  u8 unk0;
394
  u8 unk1;
395
  u8 squelch;
396
  u8 vox;
397
  u8 timeout;
398
  u8 batsave:1,
399
	 fm_funct:1,
400
	 ste:1,
401
	 blo:1,
402
	 beep:1,
403
	 lock:1,
404
	 backlight:2;
405
  u8 scantype;
406
  u8 channel;
407
  u8 fmrange;
408
  u8 alarm;
409
  u8 voice;
410
  u8 volume;
411
  u16 fm_vfo;
412
  u8 relaym;
413
  u8 tx_pwr;
414
} settings;
415

    
416
#seekto 0x0170;     // Relay CH: same structure of memory ?
417
struct {
418
  lbcd rxfreq[4];       // rx freq.
419
  u8 rxtone;            // x00 = none
420
						// x01 - x32 = index of the analog tones
421
						// x33 - x9b = index of Digital tones
422
						// Digital tone polarity is handled below
423
  lbcd txoffset[4];     // the difference against RX
424
						// pending to find the offset polarity in settings
425
  u8 txtone;            // Idem to rxtone
426
  u8 noskip:1,      // if true is included in the scan
427
	 wide:1,        // 1 = Wide, 0 = Narrow
428
	 ttondinv:1,    // if true TX tone is Digital & Inverted
429
	 unC:1,         //
430
	 rtondinv:1,    // if true RX tone is Digital & Inverted
431
	 unD:1,         //
432
	 offplus:1,     // TX = RX + offset
433
	 offminus:1;    // TX = RX - offset
434
  u8 empty[5];
435
} relaych;
436

    
437
"""
438

    
439

    
440
@directory.register
441
class BFT1(chirp_common.CloneModeRadio, chirp_common.ExperimentalRadio):
442
	"""Baofeng BT-F1 radio & possibly alike radios"""
443
	VENDOR = "Baofeng"
444
	MODEL = "BF-T1"
445
	_vhf_range = (136000000, 174000000)
446
	_uhf_range = (400000000, 470000000)
447
	_upper = 20
448
	_magic = BFT1_magic
449
	_id = BFT1_ident
450

    
451
	@classmethod
452
	def get_prompts(cls):
453
		rp = chirp_common.RadioPrompts()
454
		rp.experimental = \
455
			('This driver is experimental.\n'
456
			 '\n'
457
			 'Please keep a copy of your memories with the original software '
458
			 'if you treasure them, this driver is new and may contain'
459
			 ' bugs.\n'
460
			 '\n'
461
			 'Channel Zero is "Emergency CH", "Relay CH" is not implemented yet.'
462
			 'Settings is implemented, but still missing FM_VFO and LIMITS.'
463
			 )
464
		rp.pre_download = _(dedent("""\
465
			Follow these instructions to download your info:
466

    
467
			1 - Turn off your radio
468
			2 - Connect your interface cable
469
			3 - Turn on your radio
470
			4 - Do the download of your radio data
471

    
472
			"""))
473
		rp.pre_upload = _(dedent("""\
474
			Follow these instructions to upload your info:
475

    
476
			1 - Turn off your radio
477
			2 - Connect your interface cable
478
			3 - Turn on your radio
479
			4 - Do the upload of your radio data
480

    
481
			"""))
482
		return rp
483

    
484
	def get_features(self):
485
		"""Get the radio's features"""
486

    
487
		rf = chirp_common.RadioFeatures()
488
		rf.has_settings = True
489
		rf.has_bank = False
490
		rf.has_tuning_step = False
491
		rf.can_odd_split = True
492
		rf.has_name = False
493
		rf.has_offset = True
494
		rf.has_mode = True
495
		rf.valid_modes = MODES
496
		rf.has_dtcs = True
497
		rf.has_rx_dtcs = True
498
		rf.has_dtcs_polarity = True
499
		rf.has_ctone = True
500
		rf.has_cross = True
501
		rf.valid_duplexes = ["", "-", "+", "split"]
502
		rf.valid_tmodes = ['', 'Tone', 'TSQL', 'DTCS', 'Cross']
503
		rf.valid_cross_modes = [
504
			"Tone->Tone",
505
			"DTCS->",
506
			"->DTCS",
507
			"Tone->DTCS",
508
			"DTCS->Tone",
509
			"->Tone",
510
			"DTCS->DTCS"]
511
		rf.valid_skips = SKIP_VALUES
512
		rf.valid_dtcs_codes = DTCS
513
		rf.memory_bounds = (0, self._upper)
514

    
515
		# normal dual bands
516
		rf.valid_bands = [self._vhf_range, self._uhf_range]
517

    
518
		return rf
519

    
520
	def process_mmap(self):
521
		"""Process the mem map into the mem object"""
522

    
523
		# Get it
524
		self._memobj = bitwise.parse(MEM_FORMAT, self._mmap)
525

    
526
		# set the band limits as the memmap
527
		settings = self._memobj.settings
528
		self._vhf_range = _decode_ranges(settings.vhfl, settings.vhfh)
529
		self._uhf_range = _decode_ranges(settings.uhfl, settings.uhfh)
530

    
531
	def sync_in(self):
532
		"""Download from radio"""
533
		data = _download(self)
534
		self._mmap = memmap.MemoryMap(data)
535
		self.process_mmap()
536

    
537
	def sync_out(self):
538
		"""Upload to radio"""
539

    
540
		try:
541
			_upload(self)
542
		except errors.RadioError:
543
			raise
544
		except Exception, e:
545
			raise errors.RadioError("Error: %s" % e)
546

    
547
	def _decode_tone(self, val, inv):
548
		"""Parse the tone data to decode from mem, it returns:
549
		Mode (''|DTCS|Tone), Value (None|###), Polarity (None,N,R)"""
550

    
551
		if val == 0:
552
			return '', None, None
553
		elif val < 51:  # analog tone
554
			return 'Tone', TONES[val - 1], None
555
		elif val > 50:  # digital tone
556
			pol = "N"
557
			# polarity?
558
			if inv == 1:
559
				pol = "R"
560

    
561
			return 'DTCS', DTCS[val - 51], pol
562

    
563
	def _encode_tone(self, memtone, meminv, mode, tone, pol):
564
		"""Parse the tone data to encode from UI to mem"""
565

    
566
		if mode == '' or mode is None:
567
			memtone.set_value(0)
568
			meminv.set_value(0)
569
		elif mode == 'Tone':
570
			# caching errors for analog tones.
571
			try:
572
				memtone.set_value(TONES.index(tone) + 1)
573
				meminv.set_value(0)
574
			except:
575
				msg = "TCSS Tone '%d' is not supported" % tone
576
				LOG.error(msg)
577
				raise errors.RadioError(msg)
578

    
579
		elif mode == 'DTCS':
580
			# caching errors for digital tones.
581
			try:
582
				memtone.set_value(DTCS.index(tone) + 51)
583
				if pol == "R":
584
					meminv.set_value(True)
585
				else:
586
					meminv.set_value(False)
587
			except:
588
				msg = "Digital Tone '%d' is not supported" % tone
589
				LOG.error(msg)
590
				raise errors.RadioError(msg)
591
		else:
592
			msg = "Internal error: invalid mode '%s'" % mode
593
			LOG.error(msg)
594
			raise errors.InvalidDataError(msg)
595

    
596
	def get_raw_memory(self, number):
597
		return repr(self._memobj.memory[number])
598

    
599
	def get_memory(self, number):
600
		"""Get the mem representation from the radio image"""
601
		_mem = self._memobj.memory[number]
602

    
603
		# Create a high-level memory object to return to the UI
604
		mem = chirp_common.Memory()
605

    
606
		# Memory number
607
		mem.number = number
608

    
609
		if _mem.get_raw()[0] == "\xFF":
610
			mem.empty = True
611
			return mem
612

    
613
		# Freq and offset
614
		mem.freq = int(_mem.rxfreq) * 10
615

    
616
		# TX freq (Stored as a difference)
617
		mem.offset = int(_mem.txoffset) * 10
618
		mem.duplex = ""
619

    
620
		# must work out the polarity
621
		if mem.offset != 0:
622
			if _mem.offminus == 1:
623
				mem.duplex = "-"
624
				#  tx below RX
625

    
626
			if _mem.offplus == 1:
627
				#  tx above RX
628
				mem.duplex = "+"
629

    
630
			# split RX/TX in different bands
631
			if mem.offset > 71000000:
632
				mem.duplex = "split"
633

    
634
				# show the actual value in the offset, depending on the shift
635
				if _mem.offminus == 1:
636
					mem.offset = mem.freq - mem.offset
637
				if _mem.offplus == 1:
638
					mem.offset = mem.freq + mem.offset
639

    
640
		# wide/narrow
641
		mem.mode = MODES[int(_mem.wide)]
642

    
643
		# skip
644
		mem.skip = SKIP_VALUES[_mem.noskip]
645

    
646
		# tone data
647
		rxtone = txtone = None
648
		txtone = self._decode_tone(_mem.txtone, _mem.ttondinv)
649
		rxtone = self._decode_tone(_mem.rxtone, _mem.rtondinv)
650
		chirp_common.split_tone_decode(mem, txtone, rxtone)
651

    
652

    
653
		return mem
654

    
655
	def set_memory(self, mem):
656
		"""Set the memory data in the eeprom img from the UI"""
657
		# get the eprom representation of this channel
658
		_mem = self._memobj.memory[mem.number]
659

    
660
		# if empty memmory
661
		if mem.empty:
662
			# the channel itself
663
			_mem.set_raw("\xFF" * 16)
664
			# return it
665
			return mem
666

    
667
		# frequency
668
		_mem.rxfreq = mem.freq / 10
669

    
670
		# duplex/ offset Offset is an absolute value
671
		_mem.txoffset = mem.offset / 10
672

    
673
		# must work out the polarity
674
		if mem.duplex == "":
675
			_mem.offplus = 0
676
			_mem.offminus = 0
677
		elif mem.duplex == "+":
678
			_mem.offplus = 1
679
			_mem.offminus = 0
680
		elif mem.duplex == "-":
681
			_mem.offplus = 0
682
			_mem.offminus = 1
683
		elif mem.duplex == "split":
684
			if mem.freq > mem.offset:
685
				_mem.offplus = 0
686
				_mem.offminus = 1
687
				_mem.txoffset = (mem.freq - mem.offset) / 10
688
			else:
689
				_mem.offplus = 1
690
				_mem.offminus = 0
691
				_mem.txoffset = (mem.offset - mem.freq) / 10
692

    
693
		# wide/narrow
694
		_mem.wide = MODES.index(mem.mode)
695

    
696
		# skip
697
		_mem.noskip = SKIP_VALUES.index(mem.skip)
698

    
699
		# tone data
700
		((txmode, txtone, txpol), (rxmode, rxtone, rxpol)) = \
701
			chirp_common.split_tone_encode(mem)
702
		self._encode_tone(_mem.txtone, _mem.ttondinv, txmode, txtone, txpol)
703
		self._encode_tone(_mem.rxtone, _mem.rtondinv, rxmode, rxtone, rxpol)
704

    
705
		return mem
706
# added this ( PA3CQN )
707
	def get_settings(self):
708
		_settings = self._memobj.settings
709
		basic = RadioSettingGroup("basic", "Basic Settings")
710

    
711
		group = RadioSettings(basic)
712

    
713
		options = ["Time", "Carrier", "Search"]
714
		rs = RadioSetting("scantype", "Scan Type",
715
						  RadioSettingValueList(options,
716
												options[_settings.scantype]))
717
		basic.append(rs)
718
		
719
		options = ["Off"] + ["%s sec" % (x*30) for x in range(1, 7)]
720
		rs = RadioSetting("timeout", "Time Out Timer",
721
						  RadioSettingValueList(
722
							  options, options[_settings.timeout]))
723
		basic.append(rs)
724

    
725
		rs = RadioSetting("squelch", "Squelch Level",
726
						  RadioSettingValueInteger(0, 9, _settings.squelch))
727
		basic.append(rs)
728

    
729
		rs = RadioSetting("vox", "VOX Level",
730
						  RadioSettingValueInteger(0, 9, _settings.vox))
731
		basic.append(rs)
732

    
733
		# 9100.exe sets volume to 255, correct to 8 or less
734
		if _settings.volume > 8:
735
			_settings.volume = 8
736
			
737
		rs = RadioSetting("volume", "Volume Level",
738
						  RadioSettingValueInteger(0, 8, _settings.volume))
739
		basic.append(rs)
740

    
741
		rs = RadioSetting("channel", "Current Channel",
742
						  RadioSettingValueInteger(1, 20, _settings.channel))
743
		basic.append(rs)
744

    
745
		options = ["Off","English", "Chinese"]
746
		rs = RadioSetting("voice", "Voice Prompt",
747
						  RadioSettingValueList(options,
748
												options[_settings.voice]))
749
		basic.append(rs)
750

    
751
		options = ["Off"] + ["%s h" % (x*0.5) for x in range(1, 17)]
752
		rs = RadioSetting("alarm", "Alarm Time",
753
						  RadioSettingValueList(
754
							  options, options[_settings.alarm]))
755
		basic.append(rs)
756

    
757
		options = ["76-108", "65-76"]
758
		rs = RadioSetting("fmrange", "FM Range",
759
						  RadioSettingValueList(options,
760
												options[_settings.fmrange]))
761
		basic.append(rs)
762

    
763
		options = ["Off", "RX sync", "TX sync"]
764
		rs = RadioSetting("relaym", "Relay Mode",
765
						  RadioSettingValueList(options,
766
												options[_settings.relaym]))
767
		basic.append(rs)
768

    
769
		options = ["Off", "Key", "On"]
770
		rs = RadioSetting("backlight", "Backlight",
771
						  RadioSettingValueList(options,
772
												options[_settings.backlight]))
773
		basic.append(rs)
774

    
775
		options = ["0.5 Watt", "1.0 Watt"]
776
		rs = RadioSetting("tx_pwr", "TX Power",
777
						  RadioSettingValueList(options,
778
												options[_settings.tx_pwr]))
779
		basic.append(rs)
780

    
781
		rs = RadioSetting("lock", "Key Lock",
782
						  RadioSettingValueBoolean(_settings.lock))
783
		basic.append(rs)
784

    
785
		rs = RadioSetting("beep", "Key Beep",
786
						  RadioSettingValueBoolean(_settings.beep))
787
		basic.append(rs)
788

    
789
		rs = RadioSetting("blo", "Busy Lockout",
790
						  RadioSettingValueBoolean(_settings.blo))
791
		basic.append(rs)
792

    
793
		rs = RadioSetting("ste", "Squelch Tail Eliminate",
794
						  RadioSettingValueBoolean(_settings.ste))
795
		basic.append(rs)
796

    
797
		rs = RadioSetting("batsave", "Battery Save",
798
						  RadioSettingValueBoolean(_settings.batsave))
799
		basic.append(rs)
800

    
801
		rs = RadioSetting("fm_funct", "FM Function",
802
						  RadioSettingValueBoolean(_settings.fm_funct))
803
		basic.append(rs)
804

    
805
		return group
806

    
807
	def set_settings(self, settings):
808
		_settings = self._memobj.settings
809
		for element in settings:
810
			if not isinstance(element, RadioSetting):
811
				self.set_settings(element)
812
				continue
813
			else:
814
				try:
815
					name = element.get_name()
816
					if "." in name:
817
						bits = name.split(".")
818
						obj = self._memobj
819
						for bit in bits[:-1]:
820
							if "/" in bit:
821
								bit, index = bit.split("/", 1)
822
								index = int(index)
823
								obj = getattr(obj, bit)[index]
824
							else:
825
								obj = getattr(obj, bit)
826
						setting = bits[-1]
827
					else:
828
						obj = _settings
829
						setting = element.get_name()
830

    
831
					if element.has_apply_callback():
832
						LOG.debug("Using apply callback")
833
						element.run_apply_callback()
834
					elif setting == "volume": 
835
						if element.value > 8: # never should happen
836
							element.value = 8
837
						setattr(obj, setting, element.value)
838
					else:
839
						LOG.debug("Setting %s = %s" % (setting, element.value))
840
						setattr(obj, setting, element.value)
841
				except Exception, e:
842
					LOG.debug(element.get_name())
843
					raise
844

    
845
# settings experimental end 
846
					
847
	@classmethod
848
	def match_model(cls, filedata, filename):
849
		match_size = False
850
		match_model = False
851

    
852
		# testing the file data size
853
		if len(filedata) == MEM_SIZE:
854
			match_size = True
855

    
856
			# DEBUG
857
			if debug is True:
858
				LOG.debug("BF-T1 matched!")
859

    
860

    
861
		# testing the firmware model fingerprint
862
		match_model = _model_match(cls, filedata)
863

    
864
		if match_size and match_model:
865
			return True
866
		else:
867
			return False
(64-64/77)