Project

General

Profile

New Model #4395 » tk280.py

1st working series driver - Thomas P, 03/13/2024 12:37 PM

 
1
# Copyright 2016 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
import logging
17
import struct
18
import time
19
import sys
20
import decimal
21

    
22
from chirp import chirp_common, directory, memmap, errors, util, bitwise
23
from textwrap import dedent
24
from chirp.settings import RadioSettingGroup, RadioSetting, \
25
    RadioSettingValueBoolean, RadioSettingValueList, \
26
    RadioSettingValueString, RadioSettingValueInteger, \
27
    RadioSettings
28

    
29
LOG = logging.getLogger(__name__)
30

    
31
##### IMPORTANT DATA ##########################################
32
# This radios have a span of
33
# 0x00000 - 0x07FFF => Radio Memory / Settings data
34
# 0x08000 - 	    => FIRMWARE... hum...
35
###############################################################
36

    
37
# Notes: March 5 2024
38
# This file was heavily modified from tk760g.py for use with TK-380 by Thomas P. It is far from perfect but the
39
# main objective was to map a Kenwood x80 series to help further development.
40
# If radio is programmed in trunked mode, this module won't work as the mem map changes drastically. Stick to Conventional.
41
# FYI: Tk-981, 980, 480 and 481 are ONLY trunked mode so have different mem map.
42
#
43
# The UI is only for previewing the settings. Making changes to settings causes an error. Upload to radio is still disabled.
44
#
45
# I believe the memmap is complete-ish but most likely needs corrections (data types, etc) if planning on completing a functional UI section.
46
# 
47
# Many features are missing from the UI. Such as...
48
#	2-Tone, FleetSync, Emergency Information, Operator Selectable Tone, Test Frequency
49
#
50
# Some features are shown in UI but incomplete. Such as...
51
# 	Scan Information, DTMF
52
#	
53
#	
54

    
55
MEM_FORMAT = """
56
#seekto 0x0000;
57
struct {
58
  u8 format[2];			// x00-x01, Edit>>Model Information>>Radio Format, '03 00':Conventional Format, '00 03':Trunked Format
59
  u8 unknown0[12];          	// x02-x0d, unknown, all xFF when in UNPROGRAM mode
60
  u8 banks;                 	// x0e, how many banks are programmed
61
  u8 channels;              	// x0f, how many total channels are programmed
62
  // --
63
  ul16 tot;                 	// x10-x11, TOT value: range(15, 600, 15); x04b0 = off
64
  u8 tot_rekey;             	// x12, TOT Re-key value range(0, 60); off= 0
65
  u8 unknown1;              	// x13, unknown
66
  u8 tot_reset;             	// x14, TOT Re-key value range(0, 60); off= 0
67
  u8 unknown2;              	// x15, unknows
68
  u8 tot_alert;             	// x16, TOT pre alert: range(0,10); 0 = off
69
  u8 unknown3[7];           	// x17-x1d, unknown
70
  u8 sql_level;             	// x1e,  SQ reference level
71
  u8 battery_save;          	// x1f, Only for portable: 'FF'=off, '32'=Long, '30'=Short, '31'=Middle
72
  // --
73
  u8 sc_nfo_priority;		// x20, Scan Information, Priority, '31'=Selected, '30'=Fixed, 'FF'=None
74
  ul16 sc_nfo_lbt_a;          	// x21-x22, Scan Information, Look Back Time A[sec], range(0.35, 5.0, .05)
75
  ul16 sc_nfo_lbt_b;		// x23-x24, Scan Information, Look Back Time B[sec], range(0.5, 5.0, .05)
76
  ul16 sc_nfo_ddtime;		// x25-x26, Scan Information, Dropout Delay Time[sec], range(0, 300, 1)
77
  ul16 sc_nfo_dwell;		// x27-x28, Scan Information, Dwell Time[sec], range(0, 300, 1)
78
  u8 sc_nfo_revert;		// x29, Scan Information, Revert Channel, (30=Last Called, 31=Last Used, 34=Priority, 35=Priority+TalkBack)
79
  u8 sc_nfo_grp_scan;		// x2a, 8th bit, Scan Information, Group Scan, 30=Single, 31=Multi
80
  u8 sc_nfo_prio_grp;          	// x2b, Scan Information, Priority Group, [None,1-32?](limited by groups programmed)
81
  u8 sc_nfo_prio_ch;		// x2c, Scan Information, Priority Channel, [None,1-128?](Limited by channels programmed)
82
  u8 unknown5:2,            	// x2d, 2 bits, unknown
83
     ptt_release_tone:1,	//	1 bit, PTT Release Tone, '1'=off, '0'=on
84
     sc_nfo_rev_disp:1,		//	1 bit, Scan Information, Revert Channel Display, '1'=disable, '0'=enable
85
     c2t:1,                 	// 	1 bit, clear to transpond: 1-off, This is relative to DTMF / 2-Tone settings
86
     ost_direct:1,	   	//	1 bit, Operator Selectable Tone, Direct OST (1=disable, 0=enable)
87
     ost_backup:1,		//	1 bit, Operator Selectable Tone, Back Up (1=disable, 0=enable)
88
     unknown117:1;		//	1 bit, unknown
89
  u8 unknown113[2];		// x2e-x2f, 2 bytes, unknown
90
  // --
91
  u8 2t_a_tone_x[6];		// x30-x35, 2-Tone, A Tone [Hz], (41 24 FB 24 87 23), related to 2t_a_tone_y and 2t_a_tone_z, only 2-Tone 1 (not 2 or 3)
92
  u8 2t_b_tone_x[6];		// x36-x3B, 2-Tone, B Tone [Hz], (41 24 FB 24 87 23), related to 2t_b_tone_y and 2t_a_tone_z, only 2-Tone 1 (not 2 or 3)
93
  u8 unknown8[4];          	// x3C ?
94
  u8 unknown9[16];          	// x40 ?
95
  u8 unknown10[16];         	// x50 ?
96
  u8 add[16];         		// x60, 128 bits, corresponding channel add/skip values, UNCONFIRMED
97
  // --
98
  u8 unknown11[16];              // x70-x7f, unknown
99
  // --
100
  u8 unknown12:1,           	// x80
101
     ptt_inhib_ta:1,		//	1 bit, Inhibit PTT ID in TA(TalkAround), 1=off
102
     sel_call_alert_led:1,	//	1 bit, Selective Call Alert LED, '1'=enable, '0'=disable
103
     battery_warn:1,		//	1 bit, Battery Warning, '0'=enable, '1'=disable
104
     off_hook_decode:1,     	// 	1 bit, off hook decode enabled: 1-off
105
     off_hook_horn_alert:1,	// 	1 bit, off hook horn alert: 1-off
106
     busy_led:1,		//	1 bit, Busy LED, '0'=enable, '1'=disable
107
     disp_char:1;	   	//	1 bit, Optional Features 1, Display Character (1=Channel Name, 0=Grp#/Ch#)
108
  u8 unknown14;             	// x81
109
  u8 unknown15:3,           	// x82
110
     self_prog:1,           	// 	1 bit, Self programming enabled: 1-on
111
     clone:1,               	// 	1 bit, clone enabled: 1-on
112
     firmware_prog:1,       	// 	1 bit, firmware programming enabled: 1-on
113
     panel_tuning:1,	   	//	1 bit, Panel Tuning, 1-on, Requires panel_test=on
114
     panel_test:1;          	// 	1 bit, panel test enabled, 1-on
115
  u8 unknown17;             	// x83
116
  u8 unknown18:5,           	// x84
117
     warn_tone:1,           	// 	1 bit, warning tone, enabled: 1-on
118
     control_tone:1,        	// 	1 bit, control tone (key tone), enabled: 1-on
119
     poweron_tone:1;        	// 	1 bit, power on tone, enabled: 1-on
120
  u8 unknown19[5];          	// x85-x89
121
  u8 min_vol;               	// x8A, minimum volume posible: range(0,32); 0 = off
122
  u8 tone_vol;              	// x8B, minimum tone volume posible, FF = Continuous, range(0, 31)
123
  u8 sub_lcd_disp;          	// x8C, Optional Features 1, Sub LCD Display (FF=none, 30=Group Number, 31=Channel Number)
124
  u8 grp_name_len;		// x8D, Optional Features 1, Group Name text length (0-10)
125
  u8 unknown119[2];		// x8E-x8F, unknown
126
  u8 unknown21:3,		// x90, unknown
127
     access_log_sig:1,		//	1 bit, Optional Features 2, Access Logic Signal, '1'=Continuous, '0'=Pulse
128
     sq_logic_sig:1,		//	1 bit, Optional Features 2, Squelch Logic Signal, '1'=COR, '0'=TOR
129
     unknown111:1,		//	1 bit, unknown
130
     access_log_type:1,		//	1 bit, Optional Features 2, Access Logic Type, '1'=Active Low, '0'=Active High
131
     sq_logic_type:1;          	// 	1 bit, Optional Features 2, Squelch Logic Type, '1'=Active Low, '0'=Active High
132
  u8 unknown106:4,	   	// x91, 4 bits, unknown 
133
     em_type:2,		   	//	2 bit, Emergency Type, '11'=None, '10'=DTMF, '00'=FleetSync
134
     em_mode_type:1,	   	//	1 bit, Emergency Mode Type, '1'=Silent, '0'=Audible
135
     em_display:1;	   	//	1 bit, Emergency Display, 'FE'=Revert, 'FF'=Text(text in Emergency struct)
136
  u8 unknown107[2];	   	// x92-x93, 2 bytes, unknown
137
  char poweronmesg[12];     	// x94-x9f, 12 bytes, power on mesg 12 bytes, off is "\FF" * 12
138
  // --
139
  u8 unknown23[6];          	// xa0-xa5, 6 bytes, unknown
140
  u8 unknown133;		// xA6, FleetSync Enhanced Function, 00:enable, FF:disable
141
  char ident[8];		// xa7-xae, radio identification string
142
  u8 unknown26[12];		// xaf-xba, Passwords, see passwords struct below
143
  char lastsoftversion[5];	// xbb-xbf, software version employed to program the radio
144
  char dtmf_prim_code[7];	// xC0-xC6, DTMF Decode, Primary Code
145
  char dtmf_sec_code[7];	// xC7-xCD, DTMF Decode, Secondary Code
146
  char dtmf_DBD_code[7];	// xCE-xD4, DTMF Decode, Dead Beat Disable Code
147
  char ptt_id_bot[16];		// xD5-xE4, 16 Bytes, PTT ID Begin of TX
148
  char ptt_id_eot[16];		// xE5-xF4, 16 Bytes, PTT ID End of TX
149
  ul16 d_auto_r_timer;		// xF5-xF6, 2 bytes, DTMF Auto Reset Timer [sec], 'Off', '0-300'
150
  u8 d_enc_dig_time;		// xF7, 1 byte, DTMF Encode Digit Time [dig/sec], 6,8,10,15
151
  u8 unknown100;		// xF8, 1 byte, unknown
152
  ul16 d_enc_first_d;		// xF9-xFA, 2 bytes, DTMF Encode First Digit [msec], 0,100,500,1000
153
  ul16 d_enc_sym_d;		// xFB-xFC, 2 bytes, DTMF Encode * and # Digit [msec], 0,100,500,1000
154
  u8 d_dir_access:1,		// xFD, 1 bit, DTMF Encode * Direct Access, '1'=disable, '0'=enable
155
     unknown101:1,		// 	1 bits, unknown
156
     d_store_send:1,		// 	1 bit, DTMF Encode Store and Send, '1'=disable, '0'=enable
157
     d_man_dial:1,		// 	1 bit, DTMF Manual Dial, '1'=enable, '0'=disable
158
     d_side_tone:1,		// 	1 bit, DTMF Side Tone, '1'=enable, '0'=disable
159
     d_db_dis_resp:1,		// 	1 bit, DTMF Dead Beat Disable Response, '1'=TX Inhibit, '0'=TX/RX Inhibit
160
     d_sec_dec_resp:1,		// 	1 bit, DTMF Secondary Decode Response, '1'=Alert, '0'=Transpond		
161
     d_prim_dec_resp:1;		// 	1 bit, DTMF Primary Decode Response, '1'=Alert, '0'=Transpond
162
  u8 unknown102:1,		// xFE, 1 bit, unknown
163
     d_call_alert:1,		// 	1 bit, DTMF Call Alert, '1'=Normal, '0'=Continuous
164
     unknown103:1,		// 	1 bit, Somehow related to DTMF, Secondary Decode Response, changes to '1' when "Alert+Transpond" selected
165
     unknown132:1,		//	1 bit, Somehow related to DTMF, Primary Decode Response, changes to '1' when "Alert+Transpond" selected
166
     d_kp_auto_ptt:1,		// 	1 bit, DTMF Keypad Auto-PTT, '0'=enable, '1'=disable
167
     ptt_id_when:2,		//	2 bit, Optional Features 2, PTT ID, '10'=BOT, '01'=EOT, '00'=Both
168
     signalling_type:1;		//	1 bit, Optional Features 1, Signalling, '1'=OR, '0'=AND
169
} settings;
170

    
171
#seekto 0xA7;
172
struct {
173
  char type;			// 0xA7, M or P... trying to work around absent model and type in tk-380 but doesn't work. Still need the id struct.
174
} id;
175

    
176
#seekto 0xAF;
177
struct {
178
  char radio[6];		// 0xAF-0xB4, 6 digit Radio Password
179
  char data[6];			// 0xB5-0xBA, 6 digit Data Password
180
} passwords;
181

    
182
#seekto 0x0110;
183
struct {
184
  u8 kA;		// 0x110, A button
185
  u8 kLEFT;		// 0x111, Triangle to Left
186
  u8 kRIGHT;		// 0x112, Triangle to Right
187
  u8 kSIDE1;		// 0x113, Side button 1 (lamp)
188
  u8 kSCN;		// 0x114, S switch
189
  u8 kMON;		// 0x115, Side button 2 (mon)
190
  u8 kORANGE;		// 0x116, Orange button on portable, Foot Switch on mobile
191
  u8 kPF1;		// 0x117, PF1(ORANGE) on portable, Group Up (Right Side Up Arrow) on mobile
192
  u8 kPF2; 		// 0x118, PF2(BLACK) on portable, Group Down (Right Side Down Arrow) on mobile
193
  u8 kVOL_UP;		// 0x119, Volume Up (Left Side Up Arrow), Mobile only
194
  u8 kVOL_DOWN;		// 0x11A, Volume Down (Left Side Down Arrow), Mobile only
195
  u8 unknown30[9];	// 0x11B-0x123, unknown
196
  u8 kP_KNOB;		// 0x124, Just portable: channel knob
197
  u8 unknown131[4];	// 0x125-0x128, unknown
198
  u8 unknown31[7];	// 0x129-0x12F, unknown
199
  u8 k0;		// 0x130, Numkey 0
200
  u8 k1;		// 0x131, Numkey 1
201
  u8 k2;		// 0x132, Numkey 2
202
  u8 k3;		// 0x133, Numkey 3
203
  u8 k4;		// 0x134, Numkey 4
204
  u8 k5;		// 0x135, Numkey 5
205
  u8 k6;		// 0x136, Numkey 6
206
  u8 k7;		// 0x137, Numkey 7
207
  u8 k8;		// 0x138, Numkey 8
208
  u8 k9;		// 0x139, Numkey 9
209
  u8 unknown130[4];	// 0x13A-0x13D, Unknown
210
  u8 kASTR;		// 0x13E, Numkey *
211
  u8 kPOUND;		// 0x13F, numkey #
212
} keys;	//These are ALL the keys on TK-380 keypad version. These locations are assigned functions from values in KEYS below
213

    
214
#seekto 0x0140;
215
struct {
216
  lbcd tf01_rx[4];
217
  lbcd tf01_tx[4];
218
  u8 tf01_u_rx;
219
  u8 tf01_u_tx;
220
  lbcd tf02_rx[4];
221
  lbcd tf02_tx[4];
222
  u8 tf02_u_rx;
223
  u8 tf02_u_tx;
224
  lbcd tf03_rx[4];
225
  lbcd tf03_tx[4];
226
  u8 tf03_u_rx;
227
  u8 tf03_u_tx;
228
  lbcd tf04_rx[4];
229
  lbcd tf04_tx[4];
230
  u8 tf04_u_rx;
231
  u8 tf04_u_tx;
232
  lbcd tf05_rx[4];
233
  lbcd tf05_tx[4];
234
  u8 tf05_u_rx;
235
  u8 tf05_u_tx;
236
  lbcd tf06_rx[4];
237
  lbcd tf06_tx[4];
238
  u8 tf06_u_rx;
239
  u8 tf06_u_tx;
240
  lbcd tf07_rx[4];
241
  lbcd tf07_tx[4];
242
  u8 tf07_u_rx;
243
  u8 tf07_u_tx;
244
  lbcd tf08_rx[4];
245
  lbcd tf08_tx[4];
246
  u8 tf08_u_rx;
247
  u8 tf08_u_tx;
248
  lbcd tf09_rx[4];
249
  lbcd tf09_tx[4];
250
  u8 tf09_u_rx;
251
  u8 tf09_u_tx;
252
  lbcd tf10_rx[4];
253
  lbcd tf10_tx[4];
254
  u8 tf10_u_rx;
255
  u8 tf10_u_tx;
256
  lbcd tf11_rx[4];
257
  lbcd tf11_tx[4];
258
  u8 tf11_u_rx;
259
  u8 tf11_u_tx;
260
  lbcd tf12_rx[4];
261
  lbcd tf12_tx[4];
262
  u8 tf12_u_rx;
263
  u8 tf12_u_tx;
264
  lbcd tf13_rx[4];
265
  lbcd tf13_tx[4];
266
  u8 tf13_u_rx;
267
  u8 tf13_u_tx;
268
  lbcd tf14_rx[4];
269
  lbcd tf14_tx[4];
270
  u8 tf14_u_rx;
271
  u8 tf14_u_tx;
272
  lbcd tf15_rx[4];
273
  lbcd tf15_tx[4];
274
  u8 tf15_u_rx;
275
  u8 tf15_u_tx;
276
  lbcd tf16_rx[4];
277
  lbcd tf16_tx[4];
278
  u8 tf16_u_rx;
279
  u8 tf16_u_tx;
280
} test_freq;
281

    
282
#seekto 0x1E7;
283
struct {
284
  u8 d_enc_hold_time;	// x1E7, 1 byte, DTMF Encode Hold Time [sec], Off, 0.5-2.0, .1 increments
285
  u8 unknown120;	// x1E8, 1 byte, unknown
286
  u8 ptt_id_type;	// x1E9, 1 byte, PTT ID Type, '00'=DTMF, '01'=FleetSync, NOT related to emergency
287
  u8 unknown112;	// x1EA, 1 byte, unknown
288
  u8 com_0;		// x1EB, 1 byte, Com 0(Accessory Connector), FF=none, 33=rem, 30=Data, 35=Data+GPS NOT related to emergency
289
  u8 com_1;		// x1EC, 1 byte, Com 1(Internal Port), FF=none, 33=rem, 36=man down in, 31=GPS, NOT related to emergency but convenient to put here
290
  u8 com_2;		// x1ED, 1 byte, Com 2(Internal Port), FF=none, 33=rem, 36=man down in, 31=GPS, 32=AUX Hook/PTT, 34=Data PTT, 35=Data+GPS
291
  u8 em_group;		// x1EE, 1 byte, Emergency Group
292
  u8 em_chan;		// x1EF, 1 byte, Emergency Channel
293
  ul16 em_key_delay;	// x1F0-x1F1, 2 bytes, Emergency Key Delay Time [sec], Off, .1-5.0 in .1 increments
294
  u8 em_active_time;	// x1F2, 1 byte, Emergency Active time [sec], 1-60
295
  u8 unknown_105;	// x1F3, 1 byte, unknown
296
  u8 em_int_time;	// x1F4, 1 byte, Emergency Interval Time [sec], 30-180
297
  u8 unknown107;	// x1F5, 1 byte, unknown
298
  char em_text[10];	// x1F6-x1FF, 10 bytes, Emergency Text
299
  char line1[32];	// x200-x21F, 32 Bytes, Embedded Message Line 1
300
  char line2[32];	// x220-x23F, 32 Bytes,	Embedded Message Line 2
301
  u8 em_dtmf_id[16];	// x240-x24F, 16 bytes, Emergency DTMF ID
302
} misc;
303

    
304
#seekto 0x700;
305
struct {
306
  ul16 ost_dec_tone;	// x700-x701, 2 bytes, Operator Selectable Tones, QT/DQT Decode
307
  ul16 ost_enc_tone;	// x702-x703, 2 bytes, Operator Selectable Tones, QT/DQT Encode
308
  char ost_name[10];	// x704-x70D, 10 bytes, Operator Selectable Tones, OST Name
309
  u8 unknown118[2];	// x70E-x70F, 2 bytes, unknown
310
} ost[16];			// Operator Selectable Tones
311

    
312
#seekto 0x800;
313
struct {
314
  u8 2t_a_tone_y[4];	// x800-x803, 2-Tone, A Tone(Hz), related to 2t_a_tone_x and 2t_a_tone_z
315
  u8 2t_b_tone_y[4];	// x804-x807, 2-Tone, B Tone(Hz), related to 2t_b_tone_x and 2t_b_tone_z
316
  u8 2t_c_tone_y[4];	// x808-x80B, 2-Tone, C Tone(Hz), related to 2t_c_tone_z
317
  u8 2t_a_tone_z[2];	// x80C-x80D, 2-Tone, A Tone(Hz), related to 2t_a_tone_x and 2t_a_tone_y
318
  u8 2t_b_tone_z[2];	// x80E-x80F, 2-Tone, B Tone(Hz), related to 2t_b_tone_x and 2t_b_tone_y
319
  u8 2t_c_tone_z[2];	// x810-x811, 2-Tone, C Tone(Hz), related to 2t_c_tone_y
320
  u8 2t_dec1_ca_form;	// x812, 2-Tone, Decoder1 Call Format, (30=A-B, 31=A-C, 32=C-B, 33=A, 34=B, 35=C)
321
  u8 2t_dec2_ca_form;	// x813, 2-Tone, Decoder2 Call Format, (FF=None, 30=A-B, 31=A-C, 32=C-B, 33=A, 34=B, 35=C)
322
  u8 2t_call_alert;	// x814, 2-Tone, Call Alert, (FF=no, x30=Normal, x31=Continuous)
323
  u8 2t_ar_timer[2];	// x815-x816, 2-Tone, Auto Reset Timer [sec], range(Off, 1-300) increments of 1
324
  u8 unknown115:5,	// x817, 
325
     2t_transpond:1,	//	1 bit, 6th bit, 2-Tone, Transpond (1=disable, 0=enable)
326
     2t_dec2_c_type:1,	//	1 bit, 7th bit, 2-Tone, Decoder2 Call Type, (1=Individual, 0=Group)
327
     2t_dec1_c_type:1;	//	1 bit, 8th bit, 2-Tone, Decoder1 Call Type, (1=Individual, 0=Group)
328
  u8 unknown116[8];	// x818-x81E, unknown
329

    
330
} 2tone[3];		// Attempt at capturing 2-Tone 1, 2 and 3
331

    
332
#seekto 0x1000;
333
struct {
334
  u8 grp_num;		// x1000, Group Number
335
  u8 grp_chan;		// x1001, Channels in Group
336
  char grp_name[10];	// x1002-x100B, Group Name
337
  u8 grp_data_grp;	// x100C, Data Group
338
  u8 grp_data_chan;	// x100D, Data Channel
339
  u8 unknown108[2];	// x100E-x100F, unknown
340
} groups;
341

    
342
#seekto 0x2000;
343
struct {
344
  u8 bnumb;             // x2000, 1 byte, Channel Number
345
  u8 bank;              // x2001, 1 byte, to which bank/group it belongs
346
  char name[10];        // x2002-x200B, 10 bytes, Channel name, 10 chars
347
  lbcd rxfreq[4];       // x200C-x200F, 4 bytes, rx freq
348
  lbcd txfreq[4];       // x2010-x2013, 4 bytes, tx freq
349
  u8 rx_unkw;           // x2014, unknown yet
350
  u8 tx_unkw;           // x2015, unknown yet
351
  ul16 rx_tone;         // x2016-x2017, 2 bytes, rx tone
352
  ul16 tx_tone;         // x2018-x2019, 2 bytes, tx tone
353
  u8 unknown23[5];      // x201A-x201E, 5 bytes, unknown yet
354
  u8 signalling;         // x201F, 1 byte, Option Signaling, xFF = off, x30 DTMF, x31 2-Tone 1, x32 FleetSync, 
355
  u8 unknown24:1,    	// x2020, 1 bit, unknown
356
     ptt_id:1,		// 	1 bit, PTT ID, '1'=off, '0'=on
357
     beat_shift:1,	// 	1 bit, Beat Shift, 1 = off
358
     busy_lock:1,	// 	1 bit, Busy Channel Lock, 1=none, 0=QT/DQT Tone, see also 2 bits in x2021
359
     data_en:1,		// 	1 bit, Data, 1=enable
360
     power:1,		// 	1 bit, TX Power: 0 low / 1 high
361
     compander:1,	// 	1 bit, Compander, 1 = off
362
     wide:1;		// 	1 bit, Wide/Narrow, wide 1 / 0 narrow
363
  u8 unknown27:4,	// x2021, 4 bits, unknown
364
     busy_lock_opt:2,	//	2 bits, Busy Lock Options, 11=none, 00=Carrier Only, 01=Option Signalling, x2020 busy_lock must be '0' for these options
365
     unknown28:2;	// 	2 bits, unknown
366
  u8 unknown29[14];	// x2022, 14 bytes, unknown yet
367
} memory[250];		// Channel Scan/Add is under settings.add (x60)
368

    
369
#seekto 0x5000;
370
struct {
371
  u8 d_num;		// x5000, DTMF Memory number
372
  u8 unknown32;		// x5001, unknown
373
  char d_an[10];	// x5002-x500B, A/N
374
  u8 unknown33[4];	// x500C-x500F, unknown
375
  u8 d_code[8];		// x5010-x5017, Code
376
  u8 unknown34[8];	// x5018-x501F, unkown
377
} dtmf_memory[32];	// KPG-49D>>Edit>>DTMF
378

    
379
#seekto 0x6000;
380
struct {
381
  u8 fs_fleet_id[3];	// x6000, 3 bytes, FleetSync, Fleet(Own):100-349 and ID(Own): 1000-3999, method unknown so far
382
  u8 unknown126[2];	// x6001-x6002, 2 bytes, unknown
383
  ul16 fs_max_ack_wt;	// x6003-x6004, 2 bytes, FleetSync, Parameter, Maximum ACK Wait Time[sec]: 0.5-60, .10 increments
384
  ul16 fs_dtx_mod_dt;	// x6005-x6006, 2 bytes, FleetSync, Parameter, Data TX Modulation Delay Time[msec]: 0-6000, 1 increments
385
  u8 unknown125[3];	// x6007-x6009, 3 bytes, unknown
386
  u8 fs_uid_enc_blk[4];	// x600A-x600D, 4 bytes, FleetSync, Unit ID Encode Block: 1000-4999, method unknown so far
387
  u8 fs_gtc_count;	// x600E, 1 byte, FleetSync, Parameter, GTC Count: 0-5
388
  ul16 fs_tx_bw_time;	// x600F-x6010, 2 bytes, FleetSync, Parameter, TX Busy Wait Time [sec]: 0.5-60.0, .10 increments
389
  ul16 fs_ack_delay;	// x6011-x6012, 2 bytes, FleetSync, Parameter, ACK Delay Time[sec]: 0.1-60.0, .10 increments
390
  u8 fs_num_retries;	// x6013, 1 byte, FleetSync, Parameter, Number of Retries: 0-8
391
  ul16 fs_txdel_rxcap;	// x6014, 2 byes, FleetSync, Parameter, TX Delay Time(RX Capture)[sec]: 0.0-25.0, .10 increments
392
  u8 unknown124[3];	// x6015-x6017, 3 bytes, unknown
393
  u8 fs_baud;		// x6018, 1 byte, FleetSync, Baud Rate [bps]: (x31=2400, 30=1200)
394
  ul16 fs_mm_timer;	// x6019-x601A, 2 bytes, FleetSync, Message Mode Timer[sec]: (off, 1-300)
395
  u8 fs_em_stat_resp;	// x601B, 1 byte(8th bit), FleetSync, Emergency Status Response: (x00=none, x01=Alert)
396
  u8 fs_ptt_st;		// x601C, 8th bit, FleetSync, PTT ID Side Tone: (1=enable, 0=disable)
397
  u8 unknown124[3];	// x601D-601F, 3 bytes, unknown
398
  u8 fs_stat_m_data;	// x6020, 4th bit, FleetSync, Status Message on Data Channel: (1=disable, 0=enable)
399
  u8 fs_caller_id_st:1,	// x6021, 1st bit, FleetSync, Caller ID Stack: (1=disable, 0=enable)
400
     unknown121:1,	//	2nd bit, unknown
401
     fs_stat_8090:1,	//	3rd bit, FleetSync, Status 80-99(Special): 1=disable, 0=enable
402
     unknown122:1,	//	4th bit, unknown
403
     data_tx_qt:1,	//	5th bit, Optional Features 2, Data TX with QT/DQT, '1'=disable, '0'enable
404
     fs_man_dial:1,	//	6th bit, FleetSync, Manual Dial: 0=disable, 1=enable
405
     fs_if_call:1,	//	7th bit, FleetSync, Inter-fleet Call: 1=disable, 0=enable
406
     fs_rand_acc:1;	//	8th bit, FleetSync, Random Access (Contention): 0=enable, 1=disable
407
  u8 unknown123:5,	// x6022, 1-5 bit, unknown
408
     fs_ss_val:1,	//	6th bit, FleetSync, Stun Status Validation: (1=enable, 0=disable)
409
     fs_ca_cont:1,	//	7th bit, FleetSync, Call Alert(Continuous): (1=enable, 0=disable)
410
     fs_call_id_disp:1;	//	8th bit, FleetSync, Caller ID Display (1=disable, 0=enable)
411
} fleetsync;
412

    
413
#seekto 0x6040;
414
struct {
415
  u8 em_call_fleet;	// x6040, 1 byte, Emergency FleetSync Call Fleet #, 100-350
416
  u8 em_call_id[2];	// x6041, 2 byte, Emergency FleetSync Call ID #, 1000-3999
417
} emergency;
418

    
419
#seekto 0x6C00;
420
struct {
421
  u8 fs_idl_fleet;	// x6C00, 1 byte, FleetSync, ID List, Fleet: 100-349
422
  ul16 fs_idl_id;	// x6C01-x6C02, 2 bytes, FleetSync, ID: 1000-4999 x004E=ALL
423
  char fs_id_name[10];	// x6C03-x6C0C, 10 bytes, FleetSync, ID List, ID Name
424
  u8 fs_idl_tx_inhibit;	// x6C0D, 1 byte, FleetSync, ID List, TX Inhibit: FF=No, FE=Yes
425
  u8 unknown127[2];	// x6C0E-x6C0F, 2 bytes, unknown
426
} fs_id_list[64];	// FleetSync ID List, only coded to 64 because the list is split by fs_stat_list below ???? 
427
			// ID List items 65-100 can be seen from x7C01-x7E3C in the same format
428

    
429
#seekto 0x7000;
430
struct {
431
  u8 fs_sl_status;	// x7000, 1 byte, FleetSync, Status List, Status: 10-99
432
  u8 unknown120;	// x7001, 1 byte, unknown
433
  char fs_stat_name[16]; // x7002-x7011, 16 bytes, FleetSync, Status List, Status Name
434
  u8 fs_sl_tx_inhibit;	// x7012, 1 byte, FleetSync, Status List, TX Inhibit: FE=Yes, FF=No
435
  u8 unknown128[13];	// x7013-x701F, 13 bytes, unknown
436
} fs_stat_list[50];	//FleetSync Status List
437
"""
438

    
439
NOTE = """ MENTAL NOTE ABOUT RADIO MEM
440

    
441
The OEM insist on not reading/writing some mem segments, see below
442

    
443
read: (hex)
444
    00 - 03
445
    07 - 08
446
    10
447
    20 - 21
448
    58 - 7F
449

    
450
write: (hex)
451
    00 - 03
452
    07 - 08
453
    10
454
    20 - 21
455
    60 - 7F
456

    
457

    
458
This can be an artifact to just read/write in the needed mem space and speed
459
up things, if so the first read blocks has all the data about channel groups
460
and freq/tones & names employed.
461

    
462
This is a copied trick from the "60G series" ones and may use the same schema.
463

    
464
I must investigate further on this.
465
"""
466

    
467
MEM_SIZE = 0x8000  # 32,768 bytes (128 blocks of 256 bytes)
468
BLOCK_SIZE = 256
469
MEM_BLOCKS = range(0, MEM_SIZE / BLOCK_SIZE)
470
# undefined yet...
471
RO_BLOCKS = range(0x10, 0x1F) + range(0x59, 0x5f) + range(0x4FFF, 0x50FF)
472

    
473
# define and empty block of data, as it will be used a lot in this code
474
EMPTY_BLOCK = "\xFF" * 256
475

    
476
ACK_CMD = "\x06"
477

    
478
# TK-280:1,5 TK-380:1,4 TK-780:25 TK-880:5,25
479
POWER_LEVELS = [chirp_common.PowerLevel("Low", watts=1),
480
                chirp_common.PowerLevel("High", watts=4)]
481

    
482
MODES = ["NFM", "FM"]  # 12.5 / 25 Khz
483
VALID_CHARS = chirp_common.CHARSET_UPPER_NUMERIC + "_-*()/\-+=)."
484
SKIP_VALUES = ["", "S"]
485

    
486
TONES = chirp_common.TONES
487
# TONES.remove(254.1)
488
DTCS_CODES = chirp_common.DTCS_CODES
489

    
490

    
491
TOT = ["off"] + ["%s" % x for x in range(15, 615, 15)]   # define list of valid options
492
TOT_PRE = ["off"] + ["%s" % x for x in range(1, 11)]     #
493
TOT_REKEY = ["off"] + ["%s" % x for x in range(1, 61)]   #
494
TOT_RESET = ["off"] + ["%s" % x for x in range(1, 16)]   #
495
VOL = ["off"] + ["%s" % x for x in range(1, 32)]         #
496
TVOL = ["%s" % x for x in range(0, 33)]                  #
497
TVOL[32] = "Continous"                                   #
498
SQL = ["off"] + ["%s" % x for x in range(1, 10)]         #
499
#SIG_TYPE = {1: "OR", 0: "AND"}
500
SIG_TYPE = ["AND", "OR"]					# Signalling Type
501
CM0 = {0xFF:"None", 0x30:"Data", 0x31:"GPS", 0x32:"AUX Hook/PTT", 0x33:"REM", 0x34:"Data PTT", 0x35:"Data+GPS", 0x36:"Man Down In"} # ALL comm opts
502
#CM1 = {0xFF:"None", 0x33:"REM", 0x36:"Man Down In"} 		# Com1, com options are model dependent so combined all into CM0
503
BSAVE = {0xFF:"Off", 0x32:"Long", 0x30:"Short", 0x31:"Middle"} 	# Battery Save
504
PIDT = {0x00:"DTMF", 0x01:"FleetSync"} 				# PTT ID Type
505
PID = {10:"BOT", 01:"EOT", 00:"Both"} 				# PTT ID
506
SLT = {1:"Active Low", 0:"Active High"} 			# Squelch Logic Type
507
SLS = {1:"COR", 0:"TOR"} 					# Squelch Logic Signal
508
ALT = {1:"Active Low", 0:"Active High"} 			# Access Logic Type
509
ALS = {1:"Continuous", 0:"Pulse"} 				# Access Logic Signal
510
SIP = {0x31:"Selected", 0x30:"Fixed", 0xFF:"None"} 		# Scan Information Priority
511
PG = ["None"] + ["%s" % x for x in range(1, 32)]         	# Scan Information Priority Group
512
PCH = ["None"] + ["%s" % x for x in range(1, 250)]         	# Scan Information Priority Channel
513
#LBTA = ["%s" % x for x in drange(.35, 5.0, .05)]			# Scan Info Look Back Time A
514
LBTA = [x / 100.0 for x in range(35, 505, 5)]
515
#LBTB = ["%s" % x for x in drange(.50, 5.0, .05)]			# Scan Info Look Back Time B
516
LBTB = [x / 100.0 for x in range(50, 505, 5)]
517
REVCH = {0x30:"Last Called", 0x31:"Last Used", 0x34:"Priority", 0x35:"Priority+TalkBack"} # Scan Info Revert Channel
518
DDT = ["%s" % x for x in range(0, 300)]				# Scan Info Dropout Delay Time
519
DWT = ["%s" % x for x in range(0, 300)]				# Scan Info Dwell Time
520
GRPSC = {0x30:"Single", 0x31:"Multi"}				# Scan Info Group Scan
521

    
522

    
523
# For debugging purposes
524
debug = True
525

    
526
KEYS = {
527
    0x30: "Memory(RCL/STO)",
528
    0x31: "DTMF ID(BOT)",
529
    0x32: "DTMF ID(EOT)",
530
    0x33: "Display character",
531
    0x34: "Emergency",
532
    0x35: "Home Channel",                   # Possible portable only, check it
533
    0x37: "CH down",
534
    0x38: "CH up",
535
    0x39: "Key lock",
536
    0x3a: "Lamp",                           # Portable only
537
    0x3b: "Public address",
538
    0x3c: "Reverse",                        # Not all firmware versions?
539
    0x3d: "Horn alert",
540
    0x3e: "Memory(RCL)",
541
    0x3f: "Memory(STO)",
542
    0x40: "Monitor A: Open Momentary",
543
    0x41: "Monitor B: Open Toggle",
544
    0x42: "Monitor C: Carrier Squelch Momentary",
545
    0x43: "Monitor D: Carrier Squelch Toogle",
546
    0x44: "AUX",
547
    0x45: "Redial",
548
    0x46: "RF Power Low",                   # portable only ?
549
    0x47: "Scan",
550
    0x48: "Scan Del/Add",
551
    0x4a: "GROUP down",
552
    0x4b: "GROUP up",
553
    0x4e: "Operator Selectable Tone",
554
    0x4f: "None",
555
    0x50: "VOL down",
556
    0x51: "VOL up",
557
    0x52: "Talk around",
558
    0x5d: "AUX",
559
    0xa1: "Channel Up/Down",                 # Knob for portables only
560
    0xa2: "Group Up/Down"		# Knob for portables only
561
    }
562

    
563

    
564
ver = "Read Radio"
565

    
566
def _raw_recv(radio, amount):
567
    """Raw read from the radio device"""
568
    data = ""
569
    try:
570
        data = radio.pipe.read(amount)
571
    except:
572
        raise errors.RadioError("Error reading data from radio")
573

    
574
    # DEBUG
575
    if debug is True:
576
        LOG.debug("<== (%d) bytes:\n\n%s" % (len(data), util.hexprint(data)))
577

    
578
    return data
579

    
580

    
581
def _raw_send(radio, data):
582
    """Raw send to the radio device"""
583
    try:
584
        radio.pipe.write(data)
585
    except:
586
        raise errors.RadioError("Error sending data to radio")
587

    
588
    # DEBUG
589
    if debug is True:
590
        LOG.debug("==> (%d) bytes:\n\n%s" % (len(data), util.hexprint(data)))
591

    
592

    
593
def _close_radio(radio):
594
    """Get the radio out of program mode"""
595
    _raw_send(radio, "\x45")     #"E"
596

    
597

    
598
def _checksum(data):
599
    """the radio block checksum algorithm"""
600
    cs = 0
601
    for byte in data:
602
            cs += ord(byte)
603
    return cs % 256
604

    
605

    
606
def _send(radio, frame):
607
    """Generic send data to the radio"""
608
    _raw_send(radio, frame)
609

    
610

    
611
def _make_frame(cmd, addr):
612
    """Pack the info in the format it likes"""
613
    return struct.pack(">BH", ord(cmd), addr)
614

    
615

    
616
def _handshake(radio, msg=""):
617
    """Make a full handshake"""
618
    # send ACK
619
    _raw_send(radio, ACK_CMD)
620
    # receive ACK
621
    ack = _raw_recv(radio, 1)
622
    # check ACK
623
    if ack != ACK_CMD:
624
        _close_radio(radio)
625
        mesg = "Handshake failed " + msg
626
        # DEBUG
627
        LOG.debug(mesg)
628
        LOG.debug("ack: " + util.hexprint(ack))
629
        raise Exception(mesg)
630

    
631

    
632
def _check_write_ack(r, ack, addr):
633
    """Process the ack from the write process
634
    this is half handshake needed in tx data block"""
635
    # all ok
636
    if ack == ACK_CMD:
637
        return True
638

    
639
    # Explicit BAD checksum
640
    if ack == "\x15":
641
        _close_radio(r)
642
        raise errors.RadioError(
643
            "Bad checksum in block %02x write" % addr)
644

    
645
    # everything else
646
    _close_radio(r)
647
    raise errors.RadioError(
648
        "Problem with the ack to block %02x write, ack %03i" %
649
        (addr, int(ack)))
650

    
651

    
652
def _recv(radio):
653
    """Receive data from the radio, 258 bytes split in (cmd, data, checksum)
654
    checking the checksum to be correct, and returning just
655
    256 bytes of data or false if short empty block"""
656
    rxdata = _raw_recv(radio, BLOCK_SIZE + 2)
657

    
658
    if not rxdata:
659
        raise errors.RadioError('No response from radio')
660
    #elif len(rxdata) < 258:    #ignoring smaller blocks while testing 981
661

    
662
    # when the RX block has two bytes and the first is #\x5A 'Z'
663
    # then the block is all \xFF
664
    elif len(rxdata) == 2 and rxdata[0] == "\x5a":
665
        # "Z"
666
        ## just return false to flag about empty block
667
        _handshake(radio, "after zero block")
668
        return b'\xff' * BLOCK_SIZE
669
        #return False
670
    elif rxdata[0] != b'W':
671
        # 57
672
        LOG.error('Got non-W command:')
673
        LOG.error(util.hexprint(rxdata))
674
        # raise errors.RadioError('Received unexpected response from radio')
675
        return False
676
    elif len(rxdata) != 258:
677
        _close_radio(radio)
678
        # not the amount of data we want
679
        msg = "The radio send %d bytes, we need 258" % len(rxdata)
680
        # DEBUG
681
        LOG.error(msg)
682
        LOG.debug(rxdata)
683
        raise errors.RadioError(msg)
684
    else:
685
        rcs = ord(rxdata[-1])
686
        data = rxdata[1:-1]
687
        ccs = _checksum(data)
688
	#LOG.debug("data: " + util.hexprint(rxdata))
689
        if rcs != ccs:
690
            _close_radio(radio)
691
            raise errors.RadioError(
692
                "Block Checksum Error! real %02x, calculated %02x" %
693
                (rcs, ccs))
694
	
695
        _handshake(radio, "after checksum")
696
        return data
697

    
698
def _open_radio(radio, status):
699
    """Open the radio into program mode and check if it's the correct model"""
700
    # The OEM sets the timeout to 0.550 second, we tested it and no joy.
701
    radio.pipe.baudrate = 9600
702
    radio.pipe.timeout = 0.7    # Originally 0.7
703
    radio.pipe.parity = "E"   # 'E' and 'O' work similarly but 'N' doesn't on x80 series
704

    
705
    # DEBUG
706
    LOG.debug("Entering program mode.")
707
    # max tries
708
    tries = 10
709

    
710
    # UI
711
    status.cur = 0
712
    status.max = tries
713
    status.msg = "Entering program mode..."
714

    
715
    # try a few times to get the radio into program mode
716
    exito = False
717
    for i in range(0, tries):
718
        # flush in pyserial 3.0 way
719
        #radio.pipe.reset_input_buffer()
720
        #radio.pipe.reset_output_buffer()
721
        time.sleep(0.02)
722

    
723
        # line dance between each try:
724
        radio.pipe.rts = True
725
        radio.pipe.dtr = True
726
        time.sleep(0.02)
727
        radio.pipe.dtr = False
728

    
729
        # now send the magic
730
        _raw_send(radio, "PROGRAM")
731
        ack = _raw_recv(radio, 1)
732

    
733
        if len(ack) == 0 or ack != ACK_CMD:
734
            # DEBUG
735
            #LOG.debug(ack % i)
736
            LOG.debug("Try %s failed, traying again..." % i)
737
            time.sleep(0.25)
738
        else:
739
            exito = True
740
            break
741

    
742
        status.cur += 1
743
        radio.status_fn(status)
744

    
745

    
746
    if exito is False:
747
        _close_radio(radio)
748
        LOG.debug("Radio did not accepted PROGRAM command in %s atempts" % tries)
749
        raise errors.RadioError("The radio doesn't accept program mode")
750

    
751
    # DEBUG
752
    LOG.debug("Received ACK to the PROGRAM command, send ID query.")
753

    
754

    
755
    # lest's check the radio model
756
    _raw_send(radio, "\x02")
757
    rid = _raw_recv(radio, 8)
758

    
759
    if not (radio.TYPE in rid):
760
        # bad response, properly close the radio before exception
761
        _close_radio(radio)
762

    
763
        # DEBUG
764
        LOG.debug("Incorrect model ID:")
765
        LOG.debug(util.hexprint(rid))
766

    
767
        raise errors.RadioError(
768
            "Incorrect model ID, got %s, it doesn't contain %s" %
769
            (rid.strip("\xff"), radio.TYPE))
770

    
771
    # DEBUG
772
    LOG.debug("Full ident string is:")
773
    LOG.debug(util.hexprint(rid))
774
    _handshake(radio)
775

    
776
    # alert the user of the success
777
    status.msg = "Radio ident success!"
778
    radio.status_fn(status)
779

    
780
    # this radios checks radio version/revision (example: v2.00k)
781
    _raw_send(radio, "\x50")    # 'P'
782
    #global ver
783
    ver = _raw_recv(radio, 10)
784

    
785
    # DEBUG
786
    LOG.debug("Version returned by the radios is:")
787
    LOG.debug(util.hexprint(ver))
788
    _handshake(radio)
789
    # the radio that was procesed returned this:
790
    # v2.00k.. [76 32 2e 30 30 6b ef ff]
791

    
792
    # now the OEM writes simpy "O" and get no answer...
793
    # after that we are ready to receive the radio image or to write to it
794
    _raw_send(radio, "\x4F")    # 'O'
795

    
796

    
797
def do_download(radio):
798
    """ The download function """
799
    # UI progress
800
    status = chirp_common.Status()
801
    data = ""
802
    count = 0
803

    
804
    # open the radio
805
    _open_radio(radio, status)
806

    
807
    # reset UI data
808
    status.cur = 0
809
    status.max = MEM_SIZE / BLOCK_SIZE
810
    status.msg = "Cloning from radio..."
811
    radio.status_fn(status)
812

    
813
    # set the timeout and if windows keep it bigger
814
    if sys.platform in ["win32", "cygwin"]:
815
        # bigger timeout
816
        radio.pipe.timeout = 0.55
817
    else:
818
        # Linux can keep up, MAC?
819
        radio.pipe.timeout = 0.30     #changed from 0.05 for testing
820

    
821
    # DEBUG
822
    LOG.debug("Starting the download from radio")
823

    
824
    for addr in MEM_BLOCKS:
825
        # send request, but before flush the rx buffer
826
        radio.pipe.flush()
827
        _send(radio, _make_frame("R", addr))
828
        time.sleep(0.1)
829
        # now we get the data
830
        d = _recv(radio)
831
        # if empty block, it return false
832
        # aka we asume a empty 256 xFF block
833
        if d is False:
834
            d = EMPTY_BLOCK
835

    
836
        data += d
837
        # UI Update
838
        status.cur = count
839
        radio.status_fn(status)
840

    
841
        count += 1
842

    
843
    _close_radio(radio)
844
    return memmap.MemoryMap(data)
845

    
846

    
847
def do_upload(radio):
848
    """ The upload function """
849

    
850
    # DISABLED in this pre-alpha state_close_radio(radio)
851
    _close_radio(radio)
852
    #raise errors.RadioError("No upload possible yet, this is a test driver")
853

    
854
    # UI progress
855
    status = chirp_common.Status()
856
    data = ""
857
    count = 0
858

    
859
    # open the radio
860
    _open_radio(radio, status)
861

    
862
    # update UI
863
    status.cur = 0
864
    status.max = MEM_SIZE / BLOCK_SIZE
865
    status.msg = "Cloning to radio..."
866
    radio.status_fn(status)
867

    
868
    # the default for the original soft as measured
869
    radio.pipe.timeout = 0.7	# originally 0.5
870

    
871
    # DEBUG
872
    LOG.debug("Starting the upload to the radio")
873

    
874
    count = 0
875
    raddr = 0
876
    for addr in MEM_BLOCKS:
877
        # this is the data block to write
878
        data = radio.get_mmap()[raddr:raddr+BLOCK_SIZE]
879

    
880
        # The blocks from x59-x5F are NOT programmable
881
        # The blocks from x11-x1F are writed only if not empty
882
        if addr in RO_BLOCKS:
883
            # checking if in the range of optional blocks
884
            if addr >= 0x10 and addr <= 0x1F:
885
                # block is empty ?
886
                if data == EMPTY_BLOCK:
887
                    # no write of this block
888
                    # but we have to continue updating the counters
889
                    count += 1
890
                    raddr = count * 256
891
                    continue
892
            else:
893
                count += 1
894
                raddr = count * 256
895
                continue
896

    
897
        if data == EMPTY_BLOCK:
898
            frame = _make_frame("Z", addr) + "\xFF"
899
        else:
900
            cs = _checksum(data)
901
            frame = _make_frame("W", addr) + data + chr(cs)
902

    
903
        _send(radio, frame)
904

    
905
        # get the ACK
906
        ack = _raw_recv(radio, 1)
907
        _check_write_ack(radio, ack, addr)
908

    
909
        # DEBUG
910
        LOG.debug("Sending block %02x" % addr)
911

    
912
        # UI Update
913
        status.cur = count
914
        radio.status_fn(status)
915

    
916
        count += 1
917
        raddr = count * 256
918

    
919
    _close_radio(radio)
920

    
921
	
922

    
923
def model_match(cls, data):
924
    """Match the opened/downloaded image to the correct version"""
925
    rid = data[0xA7:0xAE]
926
    LOG.debug("model_match: " + util.hexprint(rid))
927
    if (rid in cls.VARIANTS):
928
        # correct model
929
        LOG.debug("Model_match successful")
930
        return True
931
    else:
932
        LOG.error("Model_match fail")
933
        return False
934

    
935

    
936
class Kenwood80BankModel(chirp_common.BankModel):
937
    """Testing the bank model on kennwood"""
938
    channelAlwaysHasBank = True
939

    
940
    def get_num_mappings(self):
941
        return self._radio._num_banks
942

    
943
    def get_mappings(self):
944
        banks = []
945
        for i in range(0, self._radio._num_banks):
946
            bindex = i + 1
947
            bank = self._radio._bclass(self, i, "%03i" % bindex)
948
            bank.index = i
949
            banks.append(bank)
950
        return banks
951

    
952
    def add_memory_to_mapping(self, memory, bank):
953
        self._radio._set_bank(memory.number, bank.index)
954

    
955
    def remove_memory_from_mapping(self, memory, bank):
956
        if self._radio._get_bank(memory.number) != bank.index:
957
            raise Exception("Memory %i not in bank %s. Cannot remove." %
958
                            (memory.number, bank))
959

    
960
        # We can't "Remove" it for good
961
        # the kenwood paradigm don't allow it
962
        # instead we move it to bank 0
963
        self._radio._set_bank(memory.number, 0)
964

    
965
    def get_mapping_memories(self, bank):
966
        memories = []
967
        for i in range(0, self._radio._upper):
968
            if self._radio._get_bank(i) == bank.index:
969
                memories.append(self._radio.get_memory(i))
970
        return memories
971

    
972
    def get_memory_mappings(self, memory):
973
        index = self._radio._get_bank(memory.number)
974
        return [self.get_mappings()[index]]
975

    
976

    
977
class memBank(chirp_common.Bank):
978
    """A bank model for kenwood"""
979
    # Integral index of the bank (not to be confused with per-memory
980
    # bank indexes
981
    index = 1
982

    
983

    
984
class Kenwood_Serie_80(chirp_common.CloneModeRadio):
985
    """Kenwood Serie 80 Radios base class"""
986
    VENDOR = "Kenwood"
987
    BAUD_RATE = 9600
988
    _memsize = MEM_SIZE
989
    NAME_LENGTH = 8
990
    _range = [400000000, 520000000]	# specifically for tk380
991
    _upper = 250         # originally 250
992
    _chs_progs = 0
993
    _num_banks = 32     # was 128, originally 250
994
    _bclass = memBank
995
    _kind = ""
996
    VARIANT = ""
997
    MODEL = ""
998

    
999
    @classmethod
1000
    def get_prompts(cls):
1001
        rp = chirp_common.RadioPrompts()
1002
        rp.experimental = \
1003
            ('This driver is experimental and for personal use only.'
1004
             'It has a limited set of features, but the most used.'
1005
             ''
1006
             'The most notorius missing features are this:'
1007
             '=> PTT ID Settings'
1008
             '=> Priority / Home channel'
1009
             '=> Bank names'
1010
             '=> Others'
1011
             ''
1012
             'If you need one of this, get your official software to do it'
1013
             'and raise and issue on the chirp site about it and maybe'
1014
             'it will be implemented in the future.'
1015
             ''
1016
             'The band limits on some of this radios are set here far from'
1017
             'the vendor limits to cover most of the ham bands. It is a'
1018
             'known fact that some these radios can work safely outside the OEM'
1019
             'limits, but each radio is different, you may find trouble'
1020
             'with some particular radios, but this is also possible with'
1021
             'the OEM software; as usual YMMV.'
1022
             ''
1023
             'A detail: this driver is slow reading from the radio in'
1024
             'Windows, due to the way windows serial works.'
1025
             )
1026
        rp.pre_download = _(dedent("""\
1027
            Follow this instructions to download your info:
1028
            1 - Turn off your radio
1029
            2 - Connect your interface cable
1030
            3 - Turn on your radio (unblock it if password protected)
1031
            4 - Do the download of your radio data
1032
            """))
1033
        rp.pre_upload = _(dedent("""\
1034
            Follow this instructions to upload your info:
1035
            1 - Turn off your radio
1036
            2 - Connect your interface cable
1037
            3 - Turn on your radio (unblock it if password protected)
1038
            4 - Do the upload of your radio data
1039
            """))
1040
        return rp
1041

    
1042
    def get_features(self):
1043
        """Return information about this radio's features"""
1044
        rf = chirp_common.RadioFeatures()
1045
        rf.has_settings = True
1046
        rf.has_bank = True
1047
        rf.has_tuning_step = False
1048
        rf.has_name = True
1049
        rf.has_offset = True
1050
        rf.has_mode = True
1051
        rf.has_dtcs = True
1052
        rf.has_rx_dtcs = True
1053
        rf.has_dtcs_polarity = True
1054
        rf.has_ctone = True
1055
        rf.has_cross = True
1056
        rf.valid_modes = MODES
1057
        rf.valid_duplexes = ["", "-", "+", "off"]
1058
        rf.valid_tmodes = ['', 'Tone', 'TSQL', 'DTCS', 'Cross']
1059
        rf.valid_cross_modes = [
1060
            "Tone->Tone",
1061
            "DTCS->",
1062
            "->DTCS",
1063
            "Tone->DTCS",
1064
            "DTCS->Tone",
1065
            "->Tone",
1066
            "DTCS->DTCS"]
1067
        rf.valid_power_levels = POWER_LEVELS
1068
        rf.valid_characters = VALID_CHARS
1069
        rf.valid_skips = SKIP_VALUES
1070
        rf.valid_dtcs_codes = DTCS_CODES
1071
        rf.valid_bands = [self._range]
1072
        rf.valid_name_length = 10
1073
        rf.memory_bounds = (1, self._upper)
1074
        return rf
1075

    
1076
    def _fill(self, offset, data):
1077
        """Fill an specified area of the memmap with the passed data"""
1078
        for addr in range(0, len(data)):
1079
            self._mmap[offset + addr] = data[addr]
1080

    
1081
    def _prep_data(self):
1082
        """Prepare the areas in the memmap to do a consistend write
1083
        it has to make an update on the x300 area with banks and channel
1084
        info; other in the x1000 with banks and channel counts
1085
        and a last one in x7000 with flag data"""
1086
        rchs = 0
1087
        data = dict()
1088

    
1089
        # sorting the data
1090
        for ch in range(0, self._upper):
1091
            mem = self._memobj.memory[ch]
1092
            bnumb = int(mem.bnumb)
1093
            bank = int(mem.bank)
1094
            if bnumb != 255 and (bank != 255 and bank != 0):
1095
                try:
1096
                    data[bank].append(ch)
1097
                except:
1098
                    data[bank] = list()
1099
                    data[bank].append(ch)
1100
                data[bank].sort()
1101
                # counting the real channels
1102
                rchs = rchs + 1
1103

    
1104
        # updating the channel/bank count
1105
        self._memobj.settings.channels = rchs
1106
        self._chs_progs = rchs
1107
        self._memobj.settings.banks = len(data)
1108

    
1109
        # building the data for the memmap
1110
        fdata = ""
1111

    
1112
        for k, v in data.iteritems():
1113
            # posible bad data
1114
            if k == 0:
1115
                k = 1
1116
                raise errors.InvalidValueError(
1117
                    "Invalid bank value '%k', bad data in the image? \
1118
                    Triying to fix this, review your bank data!" % k)
1119
            c = 1
1120
            for i in v:
1121
                fdata += chr(k) + chr(c) + chr(k - 1) + chr(i)
1122
                c = c + 1
1123

    
1124
        # fill to match a full 256 bytes block
1125
        fdata += (len(fdata) % 256) * "\xFF"
1126

    
1127
        # updating the data in the memmap [x300]
1128
        self._fill(0x300, fdata)
1129

    
1130
        # update the info in x1000; it has 2 bytes with
1131
        # x00 = bank , x01 = bank's channel count
1132
        # the rest of the 14 bytes are \xff
1133
        bdata = ""
1134
        for i in range(1, len(data) + 1):
1135
            line = chr(i) + chr(len(data[i]))
1136
            line += "\xff" * 14
1137
            bdata += line
1138

    
1139
        # fill to match a full 256 bytes block
1140
        bdata += (256 - (len(bdata)) % 256) * "\xFF"
1141

    
1142
        # fill to match the whole area
1143
        bdata += (16 - len(bdata) / 256) * EMPTY_BLOCK
1144

    
1145
        # updating the data in the memmap [x1000]
1146
        self._fill(0x1000, bdata)
1147

    
1148
        # DTMF id for each channel, 5 bytes lbcd at x7000
1149
        # ############## TODO ###################
1150
        fldata = "\x00\xf0\xff\xff\xff" * self._chs_progs + \
1151
            "\xff" * (5 * (self._upper - self._chs_progs))
1152

    
1153
        # write it
1154
        # updating the data in the memmap [x7000]
1155
        self._fill(0x7000, fldata)
1156

    
1157
    def _set_variant(self):
1158
        """Select and set the correct variables for the class acording
1159
        to the correct variant of the radio"""
1160
        rid = self._mmap[0xA7:0xAE]
1161

    
1162
        # indentify the radio variant and set the enviroment to it's values
1163
        try:
1164
            self._upper, low, high, self._kind = self.VARIANTS[rid]
1165
            self._range = [low * 1000000, high * 1000000]
1166

    
1167
            # setting the bank data in the features, 8 & 16 CH dont have banks
1168
            if self._upper < 32:
1169
                rf = chirp_common.RadioFeatures()
1170
                rf.has_bank = False
1171

    
1172
            # put the VARIANT in the class, clean the model / CHs / Type
1173
            # in the same layout as the KPG program
1174
            self._VARIANT = self.MODEL + " [" + str(self._upper) + "CH]: "
1175
            self._VARIANT += self._kind + ", " + str(self._range[0]/1000000) + "-"
1176
            self._VARIANT += str(self._range[1]/1000000) + " Mhz"
1177
            LOG.debug(self._VARIANT)
1178

    
1179
        except KeyError:
1180
            LOG.debug("Wrong Kenwood radio, ID or unknown variant")
1181
            LOG.debug(util.hexprint(rid))
1182
            #LOG.debug(self._VARIANT)
1183
            raise errors.RadioError(
1184
                "Wrong Kenwood radio, ID or unknown variant, see LOG output.")
1185
            return False
1186

    
1187
    def sync_in(self):
1188
        """Do a download of the radio eeprom"""
1189
        self._mmap = do_download(self)
1190
        self.process_mmap()
1191

    
1192
    def sync_out(self):
1193
        """Do an upload to the radio eeprom"""
1194

    
1195
        # chirp signature on the eprom ;-)
1196
        sign = "Chirp"
1197
        self._fill(0xbb, sign)
1198

    
1199
        try:
1200
            self._prep_data()
1201
            do_upload(self)
1202
        except errors.RadioError:
1203
            raise
1204
        except Exception, e:
1205
            raise errors.RadioError("Failed to communicate with radio: %s" % e)
1206

    
1207
    def process_mmap(self):
1208
        """Process the memory object"""
1209
        # how many channels are programed
1210
        self._chs_progs = ord(self._mmap[15])
1211

    
1212
        # load the memobj
1213
        self._memobj = bitwise.parse(MEM_FORMAT, self._mmap)
1214

    
1215
        # to set the vars on the class to the correct ones
1216
        self._set_variant()
1217

    
1218
    def get_raw_memory(self, number):
1219
        """Return a raw representation of the memory object, which
1220
        is very helpful for development"""
1221
        return repr(self._memobj.memory[number])
1222

    
1223
    def _decode_tone(self, val):
1224
        """Parse the tone data to decode from mem, it returns:
1225
        Mode (''|DTCS|Tone), Value (None|###), Polarity (None,N,R)"""
1226
        val = int(val)
1227
        if val == 65535:
1228
            return '', None, None
1229
        elif val >= 0x2800:
1230
            code = int("%03o" % (val & 0x07FF))
1231
            pol = (val & 0x8000) and "R" or "N"
1232
            return 'DTCS', code, pol
1233
        else:
1234
            a = val / 10.0
1235
            return 'Tone', a, None
1236

    
1237
    def _encode_tone(self, memval, mode, value, pol):
1238
        """Parse the tone data to encode from UI to mem"""
1239
        if mode == '':
1240
            memval.set_raw("\xff\xff")
1241
        elif mode == 'Tone':
1242
            memval.set_value(int(value * 10))
1243
        elif mode == 'DTCS':
1244
            val = int("%i" % value, 8) + 0x2800
1245
            if pol == "R":
1246
                val += 0xA000
1247
            memval.set_value(val)
1248
        else:
1249
            raise Exception("Internal error: invalid mode `%s'" % mode)
1250

    
1251
    def _get_scan(self, chan):
1252
        """Get the channel scan status from the 16 bytes array on the eeprom
1253
        then from the bits on the byte, return '' or 'S' as needed"""
1254
        result = "S"
1255
        byte = int(chan/8)
1256
        bit = chan % 8
1257
        res = self._memobj.settings.add[byte] & (pow(2, bit))
1258
        if res > 0:
1259
            result = ""
1260

    
1261
        return result
1262

    
1263
    def _set_scan(self, chan, value):
1264
        """Set the channel scan status from UI to the mem_map"""
1265
        byte = int(chan/8)
1266
        bit = chan % 8
1267

    
1268
        # get the actual value to see if I need to change anything
1269
        actual = self._get_scan(chan)
1270
        if actual != value:
1271
            # I have to flip the value
1272
            rbyte = self._memobj.settings.add[byte]
1273
            rbyte = rbyte ^ pow(2, bit)
1274
            self._memobj.settings.add[byte] = rbyte
1275

    
1276
    def get_memory(self, number):
1277
        # Get a low-level memory object mapped to the image
1278
        _mem = self._memobj.memory[number - 1]
1279

    
1280
        # Create a high-level memory object to return to the UI
1281
        mem = chirp_common.Memory()
1282

    
1283
        # Memory number
1284
        mem.number = number
1285

    
1286
        # this radio has a setting about the amount of real chans of the 128
1287
        # olso in the channel has xff on the Rx freq it's empty
1288
        if (number > (self._chs_progs + 1)) or (_mem.get_raw()[0] == "\xFF"):
1289
            mem.empty = True
1290
            # but is not enough, you have to crear the memory in the mmap
1291
            # to get it ready for the sync_out process
1292
            _mem.set_raw("\xFF" * 48)
1293
            return mem
1294

    
1295
        # Freq and offset
1296
        mem.freq = int(_mem.rxfreq) * 10
1297
        # tx freq can be blank
1298
        if _mem.get_raw()[16] == "\xFF":
1299
            # TX freq not set
1300
            mem.offset = 0
1301
            mem.duplex = "off"
1302
        else:
1303
            # TX feq set
1304
            offset = (int(_mem.txfreq) * 10) - mem.freq
1305
            if offset < 0:
1306
                mem.offset = abs(offset)
1307
                mem.duplex = "-"
1308
            elif offset > 0:
1309
                mem.offset = offset
1310
                mem.duplex = "+"
1311
            else:
1312
                mem.offset = 0
1313

    
1314
        # name TAG of the channel
1315
        mem.name = str(_mem.name).rstrip()
1316

    
1317
        # power
1318
        mem.power = POWER_LEVELS[_mem.power]
1319

    
1320
        # wide/marrow
1321
        mem.mode = MODES[_mem.wide]
1322

    
1323
        # skip
1324
        mem.skip = self._get_scan(number - 1)
1325

    
1326
        # tone data
1327
        rxtone = txtone = None
1328
        txtone = self._decode_tone(_mem.tx_tone)
1329
        rxtone = self._decode_tone(_mem.rx_tone)
1330
        chirp_common.split_tone_decode(mem, txtone, rxtone)
1331

    
1332
        # Extra
1333
        # bank and number in the channel
1334
        mem.extra = RadioSettingGroup("extra", "Extra")
1335

    
1336
        # validate bank
1337
        b = int(_mem.bank)
1338
        if b > 127 or b == 0:
1339
            _mem.bank = b = 1
1340

    
1341
        bank = RadioSetting("bank", "Bank it belongs",
1342
                            RadioSettingValueInteger(1, 128, b))
1343
        mem.extra.append(bank)
1344

    
1345
        # validate bnumb
1346
        if int(_mem.bnumb) > 127:
1347
            _mem.bank = mem.number
1348

    
1349
        bnumb = RadioSetting("bnumb", "Ch number in the bank",
1350
                             RadioSettingValueInteger(0, 127, _mem.bnumb))
1351
        mem.extra.append(bnumb)
1352

    
1353
        bs = RadioSetting("beat_shift", "Beat shift",
1354
                          RadioSettingValueBoolean(
1355
                              not bool(_mem.beat_shift)))
1356
        mem.extra.append(bs)
1357

    
1358
        cp = RadioSetting("compander", "Compander",
1359
                          RadioSettingValueBoolean(
1360
                              not bool(_mem.compander)))
1361
        mem.extra.append(cp)
1362

    
1363
        bl = RadioSetting("busy_lock", "Busy Channel lock",
1364
                          RadioSettingValueBoolean(
1365
                              not bool(_mem.busy_lock)))
1366
        mem.extra.append(bl)
1367

    
1368
        return mem
1369

    
1370
    def set_memory(self, mem):
1371
        """Set the memory data in the eeprom img from the UI
1372
        not ready yet, so it will return as is"""
1373

    
1374
        # get the eprom representation of this channel
1375
        _mem = self._memobj.memory[mem.number - 1]
1376

    
1377
        # if empty memmory
1378
        if mem.empty:
1379
            _mem.set_raw("\xFF" * 48)
1380
            return
1381

    
1382
        # frequency
1383
        _mem.rxfreq = mem.freq / 10
1384

    
1385
        # this are a mistery yet, but so falr there is no impact
1386
        # whit this default values for new channels
1387
        if int(_mem.rx_unkw) == 0xff:
1388
            _mem.rx_unkw = 0x35
1389
            _mem.tx_unkw = 0x32
1390

    
1391
        # duplex
1392
        if mem.duplex == "+":
1393
            _mem.txfreq = (mem.freq + mem.offset) / 10
1394
        elif mem.duplex == "-":
1395
            _mem.txfreq = (mem.freq - mem.offset) / 10
1396
        elif mem.duplex == "off":
1397
            for byte in _mem.txfreq:
1398
                byte.set_raw("\xFF")
1399
        else:
1400
            _mem.txfreq = mem.freq / 10
1401

    
1402
        # tone data
1403
        ((txmode, txtone, txpol), (rxmode, rxtone, rxpol)) = \
1404
            chirp_common.split_tone_encode(mem)
1405
        self._encode_tone(_mem.tx_tone, txmode, txtone, txpol)
1406
        self._encode_tone(_mem.rx_tone, rxmode, rxtone, rxpol)
1407

    
1408
        # name TAG of the channel
1409
        _namelength = self.get_features().valid_name_length
1410
        for i in range(_namelength):
1411
            try:
1412
                _mem.name[i] = mem.name[i]
1413
            except IndexError:
1414
                _mem.name[i] = "\x20"
1415

    
1416
        # power
1417
        # default power is low
1418
        if mem.power is None:
1419
            mem.power = POWER_LEVELS[0]
1420

    
1421
        _mem.power = POWER_LEVELS.index(mem.power)
1422

    
1423
        # wide/marrow
1424
        _mem.wide = MODES.index(mem.mode)
1425

    
1426
        # scan add property
1427
        self._set_scan(mem.number - 1, mem.skip)
1428

    
1429
        # bank and number in the channel
1430
        if int(_mem.bnumb) == 0xff:
1431
            _mem.bnumb = mem.number - 1
1432
            _mem.bank = 1
1433

    
1434
        # extra settings
1435
        for setting in mem.extra:
1436
            if setting != "bank" or setting != "bnumb":
1437
                setattr(_mem, setting.get_name(), not bool(setting.value))
1438

    
1439
        # all data get sync after channel mod
1440
        self._prep_data()
1441

    
1442
        return mem
1443

    
1444
    @classmethod
1445
    def match_model(cls, filedata, filename):
1446
        match_size = False
1447
        match_model = False
1448

    
1449
        # testing the file data size
1450
        if len(filedata) == MEM_SIZE:
1451
            match_size = True
1452

    
1453
        # testing the firmware model fingerprint
1454
        match_model = model_match(cls, filedata)
1455

    
1456
        if match_size and match_model:
1457
            return True
1458
        else:
1459
            return False
1460

    
1461
    def get_settings(self):
1462
        """Translate the bit in the mem_struct into settings in the UI"""
1463
        sett = self._memobj.settings
1464
        msc = self._memobj.misc
1465
        keys = self._memobj.keys
1466
        idm = self._memobj.id
1467
        passwd = self._memobj.passwords
1468
        fsync = self._memobj.fleetsync
1469
        
1470

    
1471
        # Optional Features 1
1472
        optfeat1 = RadioSettingGroup("optfeat1", "Optional Features 1")
1473
        # Optional Features 2
1474
        optfeat2 = RadioSettingGroup("optfeat2", "Optional Features 2")
1475
        # dealer settings
1476
        dealer = RadioSettingGroup("dealer", "Dealer Settings")
1477
        # buttons
1478
        fkeys = RadioSettingGroup("keys", "Controls")
1479
        # Scan Information
1480
        scaninf = RadioSettingGroup("scaninf", "Scan Information")
1481
        # DTMF
1482
        dtmfset = RadioSettingGroup("dtmfset", "DTMF")
1483
        ## FleetSync
1484
        ##flsync = RadioSettingGroup("flsync", "FleetSync")
1485
        
1486

    
1487
        # TODO / PLANED
1488
        # adjust feqs
1489
        #freqs = RadioSettingGroup("freqs", "Adjust Frequencies")
1490

    
1491
        top = RadioSettings(optfeat1, optfeat2, dealer, fkeys, scaninf, dtmfset)
1492

    
1493
        # optfeat1
1494
        ## If user changes passwords to blank, which is actually x20 not xFF, that sets impossible password. Read-only to view unknown passwords only
1495
        rtmp = str(passwd.radio).strip("\xff")
1496
        radp = RadioSettingValueString(0, 6, rtmp)
1497
        radp.set_mutable(False)
1498
        radiop = RadioSetting("passwords.radio", "Radio Password", radp)
1499
        optfeat1.append(radiop)
1500
        
1501
        rtmp = str(passwd.data).strip("\xff")
1502
        datp = RadioSettingValueString(0, 6, rtmp)
1503
        datp.set_mutable(False)
1504
        datap = RadioSetting("passwords.data", "Data Password", datp)
1505
        optfeat1.append(datap)
1506
        
1507
        
1508
        ponm = str(sett.poweronmesg).strip("\xff")
1509
        pom = RadioSetting("settings.poweronmesg", "Power on message",
1510
                           RadioSettingValueString(0, 12, ponm))
1511
        optfeat1.append(pom)
1512
        
1513
        sigtyp = RadioSetting("settings.signalling_type", "Signalling Type",
1514
                           RadioSettingValueList(
1515
                           	SIG_TYPE, SIG_TYPE[(int(sett.signalling_type))]))
1516
        optfeat1.append(sigtyp)
1517
        
1518
        """LOG.debug(msc.com_0)
1519
        com0 = RadioSetting("misc.com_0", "Com 0(Acc. Connector)[Model Dependant]",
1520
                           RadioSettingValueList(CM0.values(),
1521
                           			CM0[int(msc.com_0)]))
1522
        optfeat1.append(com0)
1523
        
1524
        com1 = RadioSetting("misc.com_1", "Com 1(Internal Port)[Model Dependant]",
1525
                           RadioSettingValueList(CM0.values(),
1526
                           			CM0[int(msc.com_1)]))
1527
        optfeat1.append(com1)
1528
        
1529
        if self.TYPE[0] == "M":
1530
        	com2 = RadioSetting("misc.com_2", "Com 2(Internal Port)[Model Dependant]",
1531
                           RadioSettingValueList(CM0.values(),
1532
                           			CM0[int(msc.com_2)]))
1533
        	optfeat1.append(com2)
1534
        """
1535
        if self.TYPE[0] == "P":
1536
        	bsav = RadioSetting("settings.battery_save", "Battery Save",
1537
                           RadioSettingValueList(BSAVE.values(),
1538
                           			BSAVE[int(sett.battery_save)]))
1539
        	optfeat1.append(bsav)
1540
        
1541
        tot = RadioSetting("settings.tot", "Time Out Timer (TOT)",
1542
                           RadioSettingValueList(TOT, TOT[
1543
                           TOT.index(str(int(sett.tot)))]))
1544
        optfeat1.append(tot)
1545

    
1546
        totalert = RadioSetting("settings.tot_alert", "TOT pre alert",
1547
                                RadioSettingValueList(TOT_PRE,
1548
                                TOT_PRE[int(sett.tot_alert)]))                     
1549
        optfeat1.append(totalert)
1550

    
1551
        totrekey = RadioSetting("settings.tot_rekey", "TOT re-key time",
1552
                                RadioSettingValueList(TOT_REKEY,
1553
                                TOT_REKEY[int(sett.tot_rekey)]))                      
1554
        optfeat1.append(totrekey)
1555

    
1556
        totreset = RadioSetting("settings.tot_reset", "TOT reset time",
1557
                                RadioSettingValueList(TOT_RESET,
1558
                                TOT_RESET[int(sett.tot_reset)]))
1559
        optfeat1.append(totreset)
1560

    
1561
        c2t = RadioSetting("settings.c2t", "Clear to Transpond",
1562
                           RadioSettingValueBoolean(
1563
                           	not bool(sett.c2t)))
1564
        optfeat1.append(c2t)
1565
        
1566
        if self.TYPE[0] == "P":
1567
        	bw = RadioSetting("settings.battery_warn", "Battery Warning",
1568
                           RadioSettingValueBoolean(
1569
                           	not bool(sett.battery_warn)))
1570
        	optfeat1.append(bw)
1571
        
1572
        if self.TYPE[0] == "M":
1573
        	ohd = RadioSetting("settings.off_hook_decode", "Off Hook Decode",
1574
                           RadioSettingValueBoolean(
1575
                           	not bool(sett.off_hook_decode)))
1576
        	optfeat1.append(ohd)
1577
        
1578
        	ohha = RadioSetting("settings.off_hook_horn_alert", "Off Hook Horn Alert",
1579
                           RadioSettingValueBoolean(
1580
                           	not bool(sett.off_hook_horn_alert)))
1581
        	optfeat1.append(ohha)
1582
        
1583
        bled = RadioSetting("settings.busy_led", "Busy LED",
1584
                           RadioSettingValueBoolean(
1585
                           	not bool(sett.busy_led)))
1586
        optfeat1.append(bled)
1587
        
1588
        scled = RadioSetting("settings.sel_call_alert_led", "Selective Call Alert LED",
1589
                           RadioSettingValueBoolean(
1590
                           	sett.sel_call_alert_led))
1591
        optfeat1.append(scled)
1592
        
1593
        # this feature is for mobile only
1594
        #if self.TYPE[0] == "M":
1595
        minvol = RadioSetting("settings.min_vol", "Minimum Volume",
1596
                                  RadioSettingValueList(VOL,
1597
                                  VOL[int(sett.min_vol)]))
1598
        optfeat2.append(minvol)
1599

    
1600
        tv = int(sett.tone_vol)
1601
        if tv == 255:
1602
        	tv = 32
1603
        tvol = RadioSetting("settings.tone_vol", "Tone Volume",
1604
                                RadioSettingValueList(TVOL, TVOL[tv]))
1605
        optfeat2.append(tvol)
1606

    
1607
        """sql = RadioSetting("settings.sql_level", "SQL Ref Level",
1608
                           RadioSettingValueList(
1609
                           SQL, SQL[int(sett.sql_level)]))                
1610
        optfeat1.append(sql)"""
1611

    
1612
        
1613

    
1614
        # Tone Volume Section
1615
        ptone = RadioSetting("settings.poweron_tone", "Power On Tone",
1616
                             RadioSettingValueBoolean(sett.poweron_tone))
1617
        optfeat2.append(ptone)
1618

    
1619
        wtone = RadioSetting("settings.warn_tone", "Warning Tone",
1620
                             RadioSettingValueBoolean(sett.warn_tone))
1621
        optfeat2.append(wtone)
1622

    
1623
        ctone = RadioSetting("settings.control_tone", "Control (Key) Tone",
1624
                             RadioSettingValueBoolean(sett.control_tone))
1625
        optfeat2.append(ctone)
1626

    
1627
        pttr = RadioSetting("settings.ptt_release_tone", "PTT Release Tone",
1628
        		RadioSettingValueBoolean(not sett.ptt_release_tone))
1629
        optfeat2.append(pttr)
1630

    
1631
        # Save Battery only for portables?
1632
        """if self.TYPE[0] == "P":
1633
            bs = int(sett.battery_save) == 0x32 and True or False
1634
            bsave = RadioSetting("settings.battery_save", "Battery Saver",
1635
                                 RadioSettingValueBoolean(bs))
1636
            optfeat1.append(bsave)""" # Replaced above
1637

    
1638
        # PTT ID Section
1639
        pdt = RadioSetting("misc.ptt_id_type", "PTT ID Type",
1640
                           RadioSettingValueList(PIDT.values(),
1641
                           			PIDT[int(msc.ptt_id_type)]))
1642
        optfeat2.append(pdt)
1643
        """
1644
        LOG.debug("PTT ID When: " + str(sett.ptt_id_when))
1645
        pti = RadioSetting("settings.ptt_id_when", "PTT ID",
1646
                           RadioSettingValueList(PID.values(),
1647
                           			PID[int(sett.ptt_id_when)]))
1648
        optfeat2.append(pti)
1649
        """
1650
        bot = str(sett.ptt_id_bot).strip("\xff")
1651
        pttbot = RadioSetting("settings.ptt_id_bot", "PTT Begin of TX",
1652
        		RadioSettingValueString(0, 16, bot))
1653
        optfeat2.append(pttbot)
1654
        
1655
        eot = str(sett.ptt_id_eot).strip("\xff")
1656
        ptteot = RadioSetting("settings.ptt_id_eot", "PTT End of TX",
1657
        		RadioSettingValueString(0, 16, eot))
1658
        optfeat2.append(ptteot)
1659
        
1660
        inhta = RadioSetting("settings.ptt_inhib_ta", "PTT Inhibit ID in TA(TalkAround)",
1661
        		RadioSettingValueBoolean(not sett.ptt_inhib_ta))
1662
        optfeat2.append(inhta)
1663

    
1664
        # dealer
1665
        mstr = ""
1666
        valid_chars = [32, 44, 45, 47, 58, 91, 93] + range(48, 58) + \
1667
            range(65, 91) + range(97, 123)
1668

    
1669
        for i in range(0, len(self._VARIANT)):
1670
            if ord(self._VARIANT[i]) in valid_chars:
1671
                mstr += self._VARIANT[i]
1672
	
1673
	"""LOG.debug(mstr)		// not in non-trunked radios
1674
	
1675
        val = RadioSettingValueString(0, 35, mstr)
1676
        val.set_mutable(False)
1677
        mod = RadioSetting("not.mod", "Radio Version", val)
1678
        dealer.append(mod)
1679

    
1680
        sn = str(idm.serial).strip(" \xff")
1681
        val = RadioSettingValueString(0, 8, sn)
1682
        val.set_mutable(False)
1683
        serial = RadioSetting("not.serial", "Serial number", val)
1684
        dealer.append(serial)"""
1685

    
1686
        svp = str(sett.lastsoftversion).strip(" \xff")
1687
        val = RadioSettingValueString(0, 5, svp)
1688
        val.set_mutable(False)
1689
        sver = RadioSetting("not.softver", "Last Used Software Version", val)
1690
        dealer.append(sver)
1691
        
1692
        vtmp = str(ver.strip(" \xff"))
1693
        LOG.debug(vtmp)
1694
        rev = RadioSettingValueString(0, 10, vtmp)
1695
        rev.set_mutable(False)
1696
        frev = RadioSetting("not.ver", "Radio Version(not from saved file)", rev)
1697
        dealer.append(frev)
1698

    
1699
        l1 = str(msc.line1).strip(" \xff")
1700
        line1 = RadioSetting("misc.line1", "Comment 1",
1701
                           RadioSettingValueString(0, 32, l1))
1702
        dealer.append(line1)
1703

    
1704
        l2 = str(msc.line2).strip(" \xff")
1705
        line2 = RadioSetting("misc.line2", "Comment 2",
1706
                             RadioSettingValueString(0, 32, l2))
1707
        dealer.append(line2)
1708

    
1709
        panel = RadioSetting("settings.panel_test", "Panel Test",
1710
                             RadioSettingValueBoolean(sett.panel_test))
1711
        optfeat2.append(panel)
1712
        
1713
        ptun = RadioSetting("settings.panel_tuning", "Panel Tuning",
1714
        		RadioSettingValueBoolean(sett.panel_tuning))
1715
        optfeat2.append(ptun)
1716
        
1717
        fmw = RadioSetting("settings.firmware_prog", "Firmware Programming",
1718
                           RadioSettingValueBoolean(sett.firmware_prog))
1719
        optfeat2.append(fmw)
1720
        
1721
        clone = RadioSetting("settings.clone", "Allow clone",
1722
                             RadioSettingValueBoolean(sett.clone))
1723
        optfeat2.append(clone)
1724
        
1725
        sprog = RadioSetting("settings.self_prog", "Self Programming",
1726
                             RadioSettingValueBoolean(sett.self_prog))
1727
        optfeat2.append(sprog)
1728
        
1729
        # Logic Signal Section
1730
        sqlt = RadioSetting("settings.sq_logic_type", "Squelch Logic Type",
1731
                           RadioSettingValueList(SLT.values(),
1732
                           			SLT[int(sett.sq_logic_type)]))
1733
        optfeat2.append(sqlt)
1734
        
1735
        sqls = RadioSetting("settings.sq_logic_sig", "Squelch Logic Signal",
1736
                           RadioSettingValueList(SLS.values(),
1737
                           			SLS[int(sett.sq_logic_sig)]))
1738
        optfeat2.append(sqls)
1739
        
1740
        aclt = RadioSetting("settings.access_log_type", "Acess Logic Type",
1741
                           RadioSettingValueList(ALT.values(),
1742
                           			ALT[int(sett.access_log_type)]))
1743
        optfeat2.append(aclt)
1744
        
1745
        acls = RadioSetting("settings.access_log_sig", "Acess Logic Signal",
1746
                           RadioSettingValueList(ALS.values(),
1747
                           			ALS[int(sett.access_log_sig)]))
1748
        optfeat2.append(acls)
1749
        
1750
        # Extended Function Section
1751
        dtxqt = RadioSetting("fleetsync.data_tx_qt", "Data TX with QT/DQT",
1752
        		RadioSettingValueBoolean(not fsync.data_tx_qt))
1753
        optfeat2.append(dtxqt)
1754
        
1755
        # Scan Information Section
1756
        """scip = RadioSetting("settings.sc_nfo_priority", "Priority",
1757
                           RadioSettingValueList(SIP.values(),
1758
                           			SIP[int(sett.sc_nfo_priority)]))
1759
        scaninf.append(scip)"""
1760
        
1761
        """pgr = RadioSetting("settings.sc_nfo_prio_grp", "Priority Group",
1762
                                RadioSettingValueList(PG,
1763
                                PG[int(sett.sc_nfo_prio_grp)]))                     
1764
        scaninf.append(pgr)"""
1765
        
1766
        """pch = RadioSetting("settings.sc_nfo_prio_ch", "Priority Channel",
1767
                                RadioSettingValueList(PCH,
1768
                                PCH[int(sett.sc_nfo_prio_ch)]))                     
1769
        scaninf.append(pch)"""
1770
        """LOG.debug(sett.sc_nfo_lbt_a)
1771
        LOG.debug(sett.sc_nfo_lbt_a / 1000.00)
1772
        lkbka = RadioSetting("settings.sc_nfo_lbt_a", "Look Back Time A[sec]",
1773
                           RadioSettingValueList(LBTA, '%s' % (sett.sc_nfo_lbt_a / 1000.00)))
1774
        scaninf.append(lkbka)"""
1775
        
1776
        """lkbkb = RadioSetting("settings.sc_nfo_lbt_b", "Look Back Time B[sec]",
1777
                           RadioSettingValueList(LBTB, '%i' % sett.sc_nfo_lbt_b))
1778
        scaninf.append(lkbkb)"""
1779
        
1780
        """revchan = RadioSetting("settings.sc_nfo_revert", "Revert Channel",
1781
                           RadioSettingValueList(REVCH.values(),
1782
                           			REVCH[int(sett.sc_nfo_revert)]))
1783
        scaninf.append(revchan)"""
1784
        
1785
        drdt = RadioSetting("settings.sc_nfo_ddtime", "Dropout Delay Time[sec]",
1786
                                RadioSettingValueList(DDT,
1787
                                DDT[int(sett.sc_nfo_ddtime)]))                     
1788
        scaninf.append(drdt)
1789
        
1790
        dwet = RadioSetting("settings.sc_nfo_dwell", "Dwell Time[sec]",
1791
                                RadioSettingValueList(DWT,
1792
                                DWT[int(sett.sc_nfo_dwell)]))                     
1793
        scaninf.append(dwet)
1794
        
1795
        """grps = RadioSetting("settings.sc_nfo_grp_scan", "Group Scan",
1796
                           RadioSettingValueList(GRPSC.values(),
1797
                           			GRPSC[int(sett.sc_nfo_grp_scan)]))
1798
        scaninf.append(grps)"""
1799
        
1800
        rchd = RadioSetting("settings.sc_nfo_rev_disp", "Revert Channel Display",
1801
                             RadioSettingValueBoolean(not bool(sett.sc_nfo_rev_disp)))
1802
        scaninf.append(rchd)
1803
        
1804
        # DTMF Settings
1805
        # Decode Section
1806
        deccode = str(sett.dtmf_prim_code).strip("\xff")
1807
        decpc = RadioSetting("settings.dtmf_prim_code", "Decode Primary Code",
1808
        		RadioSettingValueString(0, 7, deccode))
1809
        dtmfset.append(decpc)
1810
        
1811
        deccode = str(sett.dtmf_sec_code).strip("\xff")
1812
        decpc = RadioSetting("settings.dtmf_sec_code", "Decode Secondary Code",
1813
        		RadioSettingValueString(0, 7, deccode))
1814
        dtmfset.append(decpc)
1815
        
1816
        deccode = str(sett.dtmf_DBD_code).strip("\xff")
1817
        decpc = RadioSetting("settings.dtmf_DBD_code", "Decode Dead Beat Disable Code",
1818
        		RadioSettingValueString(0, 7, deccode))
1819
        dtmfset.append(decpc)
1820

    
1821
        # front keys
1822
        # The Mobile only parameters are wraped here
1823
        if self.TYPE[0] == "M":
1824
            vu = RadioSetting("keys.kVOL_UP", "VOL UP(Left Arrow Up)",
1825
                              RadioSettingValueList(KEYS.values(),
1826
                              KEYS.values()[KEYS.keys().index(
1827
                                  int(keys.kVOL_UP))]))
1828
            fkeys.append(vu)
1829

    
1830
            vd = RadioSetting("keys.kVOL_DOWN", "VOL DOWN(Left Arrow Down)",
1831
                              RadioSettingValueList(KEYS.values(),
1832
                              KEYS.values()[KEYS.keys().index(
1833
                                  int(keys.kVOL_DOWN))]))
1834
            fkeys.append(vd)
1835

    
1836
            chu = RadioSetting("keys.kPF1", "Group UP(Right Side Up Arrow)",
1837
                               RadioSettingValueList(KEYS.values(),
1838
                               KEYS.values()[KEYS.keys().index(
1839
                                   int(keys.kPF1))]))
1840
            fkeys.append(chu)
1841

    
1842
            chd = RadioSetting("keys.kPF2", "Group DOWN(Right Side Down Arrow)",
1843
                               RadioSettingValueList(KEYS.values(),
1844
                               KEYS.values()[KEYS.keys().index(
1845
                                   int(keys.kPF2))]))
1846
            fkeys.append(chd)
1847

    
1848
            foot = RadioSetting("keys.kORANGE", "Foot switch",
1849
                               RadioSettingValueList(KEYS.values(),
1850
                               KEYS.values()[KEYS.keys().index(
1851
                                   int(keys.kORANGE))]))
1852
            fkeys.append(foot)
1853

    
1854
        # this is the common buttons for all
1855

    
1856
        # 260G model don't have the front keys
1857
        """if not "P2600" in self.TYPE:
1858
            scn_name = "SCN"
1859
            if self.TYPE[0] == "P":
1860
                scn_name = "Open Circle"
1861

    
1862
            scn = RadioSetting("keys.kSCN", scn_name,
1863
                               RadioSettingValueList(KEYS.values(),
1864
                               KEYS.values()[KEYS.keys().index(
1865
                                   int(keys.kSCN))]))
1866
            fkeys.append(scn)
1867

    
1868
            a_name = "A"
1869
            if self.TYPE[0] == "P":
1870
                a_name = "Closed circle"
1871

    
1872
            a = RadioSetting("keys.kA", a_name,
1873
                             RadioSettingValueList(KEYS.values(),
1874
                             KEYS.values()[KEYS.keys().index(
1875
                                 int(keys.kA))]))
1876
            fkeys.append(a)
1877

    
1878
            da_name = "D/A"
1879
            if self.TYPE[0] == "P":
1880
                da_name = "< key"
1881

    
1882
            da = RadioSetting("keys.kDA", da_name,
1883
                              RadioSettingValueList(KEYS.values(),
1884
                              KEYS.values()[KEYS.keys().index(
1885
                                  int(keys.kDA))]))
1886
            fkeys.append(da)
1887

    
1888
            gu_name = "Triangle up"
1889
            if self.TYPE[0] == "P":
1890
                gu_name = "Side 1"
1891

    
1892
            gu = RadioSetting("keys.kGROUP_UP", gu_name,
1893
                              RadioSettingValueList(KEYS.values(),
1894
                              KEYS.values()[KEYS.keys().index(
1895
                                  int(keys.kGROUP_UP))]))
1896
            fkeys.append(gu)"""
1897

    
1898
        # Side keys on portables
1899
        """gd_name = "Triangle Down"
1900
        if self.TYPE[0] == "P":
1901
            gd_name = "> key"
1902

    
1903
        gd = RadioSetting("keys.kGROUP_DOWN", gd_name,
1904
                          RadioSettingValueList(KEYS.values(),
1905
                          KEYS.values()[KEYS.keys().index(
1906
                              int(keys.kGROUP_DOWN))]))
1907
        fkeys.append(gd)"""
1908
        
1909
        if self.TYPE[0] == "P":
1910
        	knob = RadioSetting("keys.kP_KNOB", "Knob",
1911
                           RadioSettingValueList(KEYS.values(),
1912
                           KEYS.values()[KEYS.keys().index(
1913
                               int(keys.kP_KNOB))]))
1914
        	fkeys.append(knob)
1915
        
1916
        	orange = RadioSetting("keys.kPF1", "Orange",
1917
                           RadioSettingValueList(KEYS.values(),
1918
                           KEYS.values()[KEYS.keys().index(
1919
                               int(keys.kPF1))]))
1920
        	fkeys.append(orange)
1921

    
1922
		side1 = RadioSetting("keys.kSIDE1", "Side 1",
1923
                           RadioSettingValueList(KEYS.values(),
1924
                           KEYS.values()[KEYS.keys().index(
1925
                               int(keys.kSIDE1))]))
1926
        	fkeys.append(side1)
1927

    
1928
        mon_name = "MON"
1929
        if self.TYPE[0] == "P":
1930
            mon_name = "Side 2"
1931

    
1932
        mon = RadioSetting("keys.kMON", mon_name,
1933
                           RadioSettingValueList(KEYS.values(),
1934
                           KEYS.values()[KEYS.keys().index(
1935
                               int(keys.kMON))]))
1936
        fkeys.append(mon)
1937
        
1938
        sbtn = RadioSetting("keys.kSCN", "Scan/S",
1939
                           RadioSettingValueList(KEYS.values(),
1940
                           KEYS.values()[KEYS.keys().index(
1941
                               int(keys.kSCN))]))
1942
        fkeys.append(sbtn)
1943
        
1944
        abtn = RadioSetting("keys.kA", "A",
1945
                           RadioSettingValueList(KEYS.values(),
1946
                           KEYS.values()[KEYS.keys().index(
1947
                               int(keys.kA))]))
1948
        fkeys.append(abtn)
1949
        
1950
        bbtn = RadioSetting("keys.kLEFT", "Left Arrow(B on Portable)",
1951
                           RadioSettingValueList(KEYS.values(),
1952
                           KEYS.values()[KEYS.keys().index(
1953
                               int(keys.kLEFT))]))
1954
        fkeys.append(bbtn)
1955
        
1956
        cbtn = RadioSetting("keys.kRIGHT", "Right Arrow(C on Portable)",
1957
                           RadioSettingValueList(KEYS.values(),
1958
                           KEYS.values()[KEYS.keys().index(
1959
                               int(keys.kRIGHT))]))
1960
        fkeys.append(cbtn)
1961
        
1962
        # TODO Make the following (keypad 0-9,*,#) contingent on variant. Only concerned with TK-380 for now
1963
        btn0 = RadioSetting("keys.k0", "Keypad 0",
1964
                           RadioSettingValueList(KEYS.values(),
1965
                           KEYS.values()[KEYS.keys().index(
1966
                               int(keys.k0))]))
1967
        fkeys.append(btn0)
1968
        
1969
        btn1 = RadioSetting("keys.k1", "Keypad 1",
1970
                           RadioSettingValueList(KEYS.values(),
1971
                           KEYS.values()[KEYS.keys().index(
1972
                               int(keys.k1))]))
1973
        fkeys.append(btn1)
1974
        
1975
        btn2 = RadioSetting("keys.k2", "Keypad 2",
1976
                           RadioSettingValueList(KEYS.values(),
1977
                           KEYS.values()[KEYS.keys().index(
1978
                               int(keys.k2))]))
1979
        fkeys.append(btn2)
1980
        
1981
        btn3 = RadioSetting("keys.k3", "Keypad 3",
1982
                           RadioSettingValueList(KEYS.values(),
1983
                           KEYS.values()[KEYS.keys().index(
1984
                               int(keys.k3))]))
1985
        fkeys.append(btn3)
1986
        
1987
        btn4 = RadioSetting("keys.k4", "Keypad 4",
1988
                           RadioSettingValueList(KEYS.values(),
1989
                           KEYS.values()[KEYS.keys().index(
1990
                               int(keys.k4))]))
1991
        fkeys.append(btn4)
1992
        
1993
        btn5 = RadioSetting("keys.k5", "Keypad 5",
1994
                           RadioSettingValueList(KEYS.values(),
1995
                           KEYS.values()[KEYS.keys().index(
1996
                               int(keys.k5))]))
1997
        fkeys.append(btn5)
1998
        
1999
        btn6 = RadioSetting("keys.k6", "Keypad 6",
2000
                           RadioSettingValueList(KEYS.values(),
2001
                           KEYS.values()[KEYS.keys().index(
2002
                               int(keys.k6))]))
2003
        fkeys.append(btn6)
2004
        
2005
        btn7 = RadioSetting("keys.k7", "Keypad 7",
2006
                           RadioSettingValueList(KEYS.values(),
2007
                           KEYS.values()[KEYS.keys().index(
2008
                               int(keys.k7))]))
2009
        fkeys.append(btn7)
2010
        
2011
        btn8 = RadioSetting("keys.k8", "Keypad 8",
2012
                           RadioSettingValueList(KEYS.values(),
2013
                           KEYS.values()[KEYS.keys().index(
2014
                               int(keys.k8))]))
2015
        fkeys.append(btn8)
2016
        
2017
        btn9 = RadioSetting("keys.k9", "Keypad 9",
2018
                           RadioSettingValueList(KEYS.values(),
2019
                           KEYS.values()[KEYS.keys().index(
2020
                               int(keys.k9))]))
2021
        fkeys.append(btn9)
2022
        
2023
        starbtn = RadioSetting("keys.kASTR", "Keypad *",
2024
                           RadioSettingValueList(KEYS.values(),
2025
                           KEYS.values()[KEYS.keys().index(
2026
                               int(keys.kASTR))]))
2027
        fkeys.append(starbtn)
2028
        
2029
        poundbtn = RadioSetting("keys.kPOUND", "Keypad #",
2030
                           RadioSettingValueList(KEYS.values(),
2031
                           KEYS.values()[KEYS.keys().index(
2032
                               int(keys.kPOUND))]))
2033
        fkeys.append(poundbtn)
2034

    
2035
        return top
2036

    
2037
    
2038

    
2039
    def set_settings(self, settings):
2040
        """Translate the settings in the UI into bit in the mem_struct
2041
        I don't understand well the method used in many drivers
2042
        so, I used mine, ugly but works ok"""
2043

    
2044
        mobj = self._memobj
2045

    
2046
        for element in settings:
2047
            if not isinstance(element, RadioSetting):
2048
                self.set_settings(element)
2049
                continue
2050

    
2051
            # Let's roll the ball
2052
            if "." in element.get_name():
2053
                inter, setting = element.get_name().split(".")
2054
                #LOG.debug("inter: " + inter + " setting: " + setting)
2055
                # you must ignore the settings with "not"
2056
                # this are READ ONLY attributes
2057
                if inter == "not":
2058
                    continue
2059

    
2060
                obj = getattr(mobj, inter)
2061
                #LOG.debug("obj: " + str(obj))
2062
                value = element.value
2063
                #LOG.debug("value: " + str(value))
2064

    
2065
                # integers case + special case
2066
                if setting in ["tot", "tot_alert", "min_vol", "tone_vol",
2067
                               "sql_level", "tot_rekey", "tot_reset"]:
2068
                    # catching the "off" values as zero
2069
                    try:
2070
                        value = int(value)
2071
                    except:
2072
                        value = 0
2073

    
2074
                    # tot case step 15
2075
                    if setting == "tot":
2076
                        value = value * 15
2077
                        # off is special
2078
                        if value == 0:
2079
                            value = 0x4b0
2080

    
2081
                    # Caso tone_vol
2082
                    if setting == "tone_vol":
2083
                        # off is special
2084
                        if value == 32:
2085
                            value = 0xff
2086

    
2087
                # Bool types + inverted
2088
                if setting in ["c2t", "poweron_tone", "control_tone",
2089
                               "warn_tone", "battery_save", "self_prog",
2090
                               "clone", "panel_test"]:
2091
                    value = bool(value)
2092

    
2093
                    # this cases are inverted
2094
                    if setting == "c2t":
2095
                        value = not value
2096

    
2097
                    # case battery save is special
2098
                    if setting == "battery_save":
2099
                        if bool(value) is True:
2100
                            value = 0x32
2101
                        else:
2102
                            value = 0xff
2103

    
2104
                # String cases
2105
                if setting in ["poweronmesg", "line1", "line2"]:
2106
                    # some vars
2107
                    value = str(value)
2108
                    just = 8
2109
                    # lines with 32
2110
                    if "line" in setting:
2111
                        just = 32
2112

    
2113
                    # empty case
2114
                    if len(value) == 0:
2115
                        value = "\xff" * just
2116
                    else:
2117
                        value = value.ljust(just)
2118

    
2119
                # case keys, with special config
2120
                if inter == "keys":
2121
                    value = KEYS.keys()[KEYS.values().index(str(value))]
2122

    
2123
            # Apply al configs done
2124
            setattr(obj, setting, value)
2125

    
2126
    def get_bank_model(self):
2127
        """Pass the bank model to the UI part"""
2128
        rf = self.get_features()
2129
        if rf.has_bank is True:
2130
            return Kenwood80BankModel(self)
2131
        else:
2132
            return None
2133

    
2134
    def _get_bank(self, loc):
2135
        """Get the bank data for a specific channel"""
2136
        mem = self._memobj.memory[loc - 1]
2137
        bank = int(mem.bank) - 1
2138

    
2139
        if bank > self._num_banks or bank < 1:
2140
            # all channels must belong to a bank, even with just 1 bank
2141
            return 0
2142
        else:
2143
            return bank
2144

    
2145
    def _set_bank(self, loc, bank):
2146
        """Set the bank data for a specific channel"""
2147
        try:
2148
            b = int(bank)
2149
            if b > 127:
2150
                b = 0
2151
            mem = self._memobj.memory[loc - 1]
2152
            mem.bank = b + 1
2153
        except:
2154
            msg = "You can't have a channel without a bank, click another bank"
2155
            raise errors.InvalidDataError(msg)
2156

    
2157

    
2158
# Driver built on radio model/variants I had. Future models need ident strings specified and ALL INFO verified. There may be more variants.
2159

    
2160
@directory.register
2161
class TK280_Radios(Kenwood_Serie_80):
2162
    """Kenwood TK-280 Radio"""
2163
    MODEL = "TK-280"
2164
    TYPE = "P0280"
2165
    VARIANTS = {
2166
        "P0280\x04\xFF":    (250, 144, 174, "K Non-Keypad Model"),#VERIFIED variant. Range expanded for ham bands. Orig: 146, 174
2167
        "P0280\x05\xFF":    (250, 136, 162, "K2 Non-Keypad Model"),
2168
        "P0280\xFF\xFF":    (250, 144, 174, "K3 Keypad Model"),#Range expanded for ham bands. Orig: 146, 174
2169
        "P0280\xFF\xFF":    (250, 136, 162, "K4 Keypad Model"),
2170
        }
2171

    
2172
@directory.register
2173
class TK380_Radios(Kenwood_Serie_80):
2174
    """Kenwood TK-380 Radio """
2175
    MODEL = "TK-380"
2176
    TYPE = "P0380"
2177
    VARIANTS = {
2178
        "P0380\x06\xFF":    (250, 420, 490, "K Non-Keypad Model"),#Range expanded for ham bands. Orig: 450, 490
2179
        "P0380\x07\xFF":    (250, 420, 512, "K2 Non-Keypad Model"),#Range expanded for ham bands. Orig: 470, 512
2180
        "P0380\x08\xFF":    (250, 400, 450, "K3 Non-Keypad Model"),#Range expanded for ham bands. Orig: 400, 430
2181
        "P0380\x0a\xFF":    (250, 420, 490, "K4 Keypad Model"),#VERIFIED variant. Range expanded for ham bands. Orig: 450, 490
2182
        "P0380\xFF\xFF":    (250, 400, 450, "K6 Keypad Model"),#Range expanded for ham bands. Orig: 400, 430
2183
        "P0380\xFF\xFF":    (250, 420, 520, "K5 Keypad Model"),#Range expanded for ham bands. Orig: 470, 520
2184
        }
2185

    
2186
@directory.register
2187
class TK780_Radios(Kenwood_Serie_80):
2188
    """Kenwood TK-780 Radio """
2189
    MODEL = "TK-780"
2190
    TYPE = "M0780"
2191
    VARIANTS = {
2192
        "M0780\x04\xFF":    (250, 144, 174, "K"),#VERIFIED variant. #Range expanded for ham bands. Orig: 146, 174
2193
        "M0780\x05\xFF":    (250, 136, 162, "K2"),
2194
        }
2195

    
2196
@directory.register
2197
class TK880_Radios(Kenwood_Serie_80):
2198
    """Kenwood TK-880 Radio """
2199
    MODEL = "TK-880"
2200
    TYPE = "M0880"
2201
    VARIANTS = {
2202
        "M0880\x06\xFF":    (250, 420, 490, "K"),#VERIFIED variant. #Range expanded for ham bands. Orig: 450, 490
2203
        "M0880\x07\xFF":    (250, 420, 512, "K2"),#Range expanded for ham bands. Orig: 485, 512
2204
        "M0880\x08\xFF":    (250, 400, 450, "K3"),#Range expanded for ham bands. Orig: 400, 430
2205
        }
2206

    
(19-19/20)