Project

General

Profile

New Model #4395 » tk280.py

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

 
# Copyright 2016 Pavel Milanes, CO7WT, <pavelmc@gmail.com>
#
# This program is free software: you can redistribute it and/or modify
# it under the terms of the GNU General Public License as published by
# the Free Software Foundation, either version 2 of the License, or
# (at your option) any later version.
#
# This program is distributed in the hope that it will be useful,
# but WITHOUT ANY WARRANTY; without even the implied warranty of
# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
# GNU General Public License for more details.
#
# You should have received a copy of the GNU General Public License
# along with this program. If not, see <http://www.gnu.org/licenses/>.

import logging
import struct
import time
import sys
import decimal

from chirp import chirp_common, directory, memmap, errors, util, bitwise
from textwrap import dedent
from chirp.settings import RadioSettingGroup, RadioSetting, \
RadioSettingValueBoolean, RadioSettingValueList, \
RadioSettingValueString, RadioSettingValueInteger, \
RadioSettings

LOG = logging.getLogger(__name__)

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

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

MEM_FORMAT = """
#seekto 0x0000;
struct {
u8 format[2]; // x00-x01, Edit>>Model Information>>Radio Format, '03 00':Conventional Format, '00 03':Trunked Format
u8 unknown0[12]; // x02-x0d, unknown, all xFF when in UNPROGRAM mode
u8 banks; // x0e, how many banks are programmed
u8 channels; // x0f, how many total channels are programmed
// --
ul16 tot; // x10-x11, TOT value: range(15, 600, 15); x04b0 = off
u8 tot_rekey; // x12, TOT Re-key value range(0, 60); off= 0
u8 unknown1; // x13, unknown
u8 tot_reset; // x14, TOT Re-key value range(0, 60); off= 0
u8 unknown2; // x15, unknows
u8 tot_alert; // x16, TOT pre alert: range(0,10); 0 = off
u8 unknown3[7]; // x17-x1d, unknown
u8 sql_level; // x1e, SQ reference level
u8 battery_save; // x1f, Only for portable: 'FF'=off, '32'=Long, '30'=Short, '31'=Middle
// --
u8 sc_nfo_priority; // x20, Scan Information, Priority, '31'=Selected, '30'=Fixed, 'FF'=None
ul16 sc_nfo_lbt_a; // x21-x22, Scan Information, Look Back Time A[sec], range(0.35, 5.0, .05)
ul16 sc_nfo_lbt_b; // x23-x24, Scan Information, Look Back Time B[sec], range(0.5, 5.0, .05)
ul16 sc_nfo_ddtime; // x25-x26, Scan Information, Dropout Delay Time[sec], range(0, 300, 1)
ul16 sc_nfo_dwell; // x27-x28, Scan Information, Dwell Time[sec], range(0, 300, 1)
u8 sc_nfo_revert; // x29, Scan Information, Revert Channel, (30=Last Called, 31=Last Used, 34=Priority, 35=Priority+TalkBack)
u8 sc_nfo_grp_scan; // x2a, 8th bit, Scan Information, Group Scan, 30=Single, 31=Multi
u8 sc_nfo_prio_grp; // x2b, Scan Information, Priority Group, [None,1-32?](limited by groups programmed)
u8 sc_nfo_prio_ch; // x2c, Scan Information, Priority Channel, [None,1-128?](Limited by channels programmed)
u8 unknown5:2, // x2d, 2 bits, unknown
ptt_release_tone:1, // 1 bit, PTT Release Tone, '1'=off, '0'=on
sc_nfo_rev_disp:1, // 1 bit, Scan Information, Revert Channel Display, '1'=disable, '0'=enable
c2t:1, // 1 bit, clear to transpond: 1-off, This is relative to DTMF / 2-Tone settings
ost_direct:1, // 1 bit, Operator Selectable Tone, Direct OST (1=disable, 0=enable)
ost_backup:1, // 1 bit, Operator Selectable Tone, Back Up (1=disable, 0=enable)
unknown117:1; // 1 bit, unknown
u8 unknown113[2]; // x2e-x2f, 2 bytes, unknown
// --
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)
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)
u8 unknown8[4]; // x3C ?
u8 unknown9[16]; // x40 ?
u8 unknown10[16]; // x50 ?
u8 add[16]; // x60, 128 bits, corresponding channel add/skip values, UNCONFIRMED
// --
u8 unknown11[16]; // x70-x7f, unknown
// --
u8 unknown12:1, // x80
ptt_inhib_ta:1, // 1 bit, Inhibit PTT ID in TA(TalkAround), 1=off
sel_call_alert_led:1, // 1 bit, Selective Call Alert LED, '1'=enable, '0'=disable
battery_warn:1, // 1 bit, Battery Warning, '0'=enable, '1'=disable
off_hook_decode:1, // 1 bit, off hook decode enabled: 1-off
off_hook_horn_alert:1, // 1 bit, off hook horn alert: 1-off
busy_led:1, // 1 bit, Busy LED, '0'=enable, '1'=disable
disp_char:1; // 1 bit, Optional Features 1, Display Character (1=Channel Name, 0=Grp#/Ch#)
u8 unknown14; // x81
u8 unknown15:3, // x82
self_prog:1, // 1 bit, Self programming enabled: 1-on
clone:1, // 1 bit, clone enabled: 1-on
firmware_prog:1, // 1 bit, firmware programming enabled: 1-on
panel_tuning:1, // 1 bit, Panel Tuning, 1-on, Requires panel_test=on
panel_test:1; // 1 bit, panel test enabled, 1-on
u8 unknown17; // x83
u8 unknown18:5, // x84
warn_tone:1, // 1 bit, warning tone, enabled: 1-on
control_tone:1, // 1 bit, control tone (key tone), enabled: 1-on
poweron_tone:1; // 1 bit, power on tone, enabled: 1-on
u8 unknown19[5]; // x85-x89
u8 min_vol; // x8A, minimum volume posible: range(0,32); 0 = off
u8 tone_vol; // x8B, minimum tone volume posible, FF = Continuous, range(0, 31)
u8 sub_lcd_disp; // x8C, Optional Features 1, Sub LCD Display (FF=none, 30=Group Number, 31=Channel Number)
u8 grp_name_len; // x8D, Optional Features 1, Group Name text length (0-10)
u8 unknown119[2]; // x8E-x8F, unknown
u8 unknown21:3, // x90, unknown
access_log_sig:1, // 1 bit, Optional Features 2, Access Logic Signal, '1'=Continuous, '0'=Pulse
sq_logic_sig:1, // 1 bit, Optional Features 2, Squelch Logic Signal, '1'=COR, '0'=TOR
unknown111:1, // 1 bit, unknown
access_log_type:1, // 1 bit, Optional Features 2, Access Logic Type, '1'=Active Low, '0'=Active High
sq_logic_type:1; // 1 bit, Optional Features 2, Squelch Logic Type, '1'=Active Low, '0'=Active High
u8 unknown106:4, // x91, 4 bits, unknown
em_type:2, // 2 bit, Emergency Type, '11'=None, '10'=DTMF, '00'=FleetSync
em_mode_type:1, // 1 bit, Emergency Mode Type, '1'=Silent, '0'=Audible
em_display:1; // 1 bit, Emergency Display, 'FE'=Revert, 'FF'=Text(text in Emergency struct)
u8 unknown107[2]; // x92-x93, 2 bytes, unknown
char poweronmesg[12]; // x94-x9f, 12 bytes, power on mesg 12 bytes, off is "\FF" * 12
// --
u8 unknown23[6]; // xa0-xa5, 6 bytes, unknown
u8 unknown133; // xA6, FleetSync Enhanced Function, 00:enable, FF:disable
char ident[8]; // xa7-xae, radio identification string
u8 unknown26[12]; // xaf-xba, Passwords, see passwords struct below
char lastsoftversion[5]; // xbb-xbf, software version employed to program the radio
char dtmf_prim_code[7]; // xC0-xC6, DTMF Decode, Primary Code
char dtmf_sec_code[7]; // xC7-xCD, DTMF Decode, Secondary Code
char dtmf_DBD_code[7]; // xCE-xD4, DTMF Decode, Dead Beat Disable Code
char ptt_id_bot[16]; // xD5-xE4, 16 Bytes, PTT ID Begin of TX
char ptt_id_eot[16]; // xE5-xF4, 16 Bytes, PTT ID End of TX
ul16 d_auto_r_timer; // xF5-xF6, 2 bytes, DTMF Auto Reset Timer [sec], 'Off', '0-300'
u8 d_enc_dig_time; // xF7, 1 byte, DTMF Encode Digit Time [dig/sec], 6,8,10,15
u8 unknown100; // xF8, 1 byte, unknown
ul16 d_enc_first_d; // xF9-xFA, 2 bytes, DTMF Encode First Digit [msec], 0,100,500,1000
ul16 d_enc_sym_d; // xFB-xFC, 2 bytes, DTMF Encode * and # Digit [msec], 0,100,500,1000
u8 d_dir_access:1, // xFD, 1 bit, DTMF Encode * Direct Access, '1'=disable, '0'=enable
unknown101:1, // 1 bits, unknown
d_store_send:1, // 1 bit, DTMF Encode Store and Send, '1'=disable, '0'=enable
d_man_dial:1, // 1 bit, DTMF Manual Dial, '1'=enable, '0'=disable
d_side_tone:1, // 1 bit, DTMF Side Tone, '1'=enable, '0'=disable
d_db_dis_resp:1, // 1 bit, DTMF Dead Beat Disable Response, '1'=TX Inhibit, '0'=TX/RX Inhibit
d_sec_dec_resp:1, // 1 bit, DTMF Secondary Decode Response, '1'=Alert, '0'=Transpond
d_prim_dec_resp:1; // 1 bit, DTMF Primary Decode Response, '1'=Alert, '0'=Transpond
u8 unknown102:1, // xFE, 1 bit, unknown
d_call_alert:1, // 1 bit, DTMF Call Alert, '1'=Normal, '0'=Continuous
unknown103:1, // 1 bit, Somehow related to DTMF, Secondary Decode Response, changes to '1' when "Alert+Transpond" selected
unknown132:1, // 1 bit, Somehow related to DTMF, Primary Decode Response, changes to '1' when "Alert+Transpond" selected
d_kp_auto_ptt:1, // 1 bit, DTMF Keypad Auto-PTT, '0'=enable, '1'=disable
ptt_id_when:2, // 2 bit, Optional Features 2, PTT ID, '10'=BOT, '01'=EOT, '00'=Both
signalling_type:1; // 1 bit, Optional Features 1, Signalling, '1'=OR, '0'=AND
} settings;

#seekto 0xA7;
struct {
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.
} id;

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

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

#seekto 0x0140;
struct {
lbcd tf01_rx[4];
lbcd tf01_tx[4];
u8 tf01_u_rx;
u8 tf01_u_tx;
lbcd tf02_rx[4];
lbcd tf02_tx[4];
u8 tf02_u_rx;
u8 tf02_u_tx;
lbcd tf03_rx[4];
lbcd tf03_tx[4];
u8 tf03_u_rx;
u8 tf03_u_tx;
lbcd tf04_rx[4];
lbcd tf04_tx[4];
u8 tf04_u_rx;
u8 tf04_u_tx;
lbcd tf05_rx[4];
lbcd tf05_tx[4];
u8 tf05_u_rx;
u8 tf05_u_tx;
lbcd tf06_rx[4];
lbcd tf06_tx[4];
u8 tf06_u_rx;
u8 tf06_u_tx;
lbcd tf07_rx[4];
lbcd tf07_tx[4];
u8 tf07_u_rx;
u8 tf07_u_tx;
lbcd tf08_rx[4];
lbcd tf08_tx[4];
u8 tf08_u_rx;
u8 tf08_u_tx;
lbcd tf09_rx[4];
lbcd tf09_tx[4];
u8 tf09_u_rx;
u8 tf09_u_tx;
lbcd tf10_rx[4];
lbcd tf10_tx[4];
u8 tf10_u_rx;
u8 tf10_u_tx;
lbcd tf11_rx[4];
lbcd tf11_tx[4];
u8 tf11_u_rx;
u8 tf11_u_tx;
lbcd tf12_rx[4];
lbcd tf12_tx[4];
u8 tf12_u_rx;
u8 tf12_u_tx;
lbcd tf13_rx[4];
lbcd tf13_tx[4];
u8 tf13_u_rx;
u8 tf13_u_tx;
lbcd tf14_rx[4];
lbcd tf14_tx[4];
u8 tf14_u_rx;
u8 tf14_u_tx;
lbcd tf15_rx[4];
lbcd tf15_tx[4];
u8 tf15_u_rx;
u8 tf15_u_tx;
lbcd tf16_rx[4];
lbcd tf16_tx[4];
u8 tf16_u_rx;
u8 tf16_u_tx;
} test_freq;

#seekto 0x1E7;
struct {
u8 d_enc_hold_time; // x1E7, 1 byte, DTMF Encode Hold Time [sec], Off, 0.5-2.0, .1 increments
u8 unknown120; // x1E8, 1 byte, unknown
u8 ptt_id_type; // x1E9, 1 byte, PTT ID Type, '00'=DTMF, '01'=FleetSync, NOT related to emergency
u8 unknown112; // x1EA, 1 byte, unknown
u8 com_0; // x1EB, 1 byte, Com 0(Accessory Connector), FF=none, 33=rem, 30=Data, 35=Data+GPS NOT related to emergency
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
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
u8 em_group; // x1EE, 1 byte, Emergency Group
u8 em_chan; // x1EF, 1 byte, Emergency Channel
ul16 em_key_delay; // x1F0-x1F1, 2 bytes, Emergency Key Delay Time [sec], Off, .1-5.0 in .1 increments
u8 em_active_time; // x1F2, 1 byte, Emergency Active time [sec], 1-60
u8 unknown_105; // x1F3, 1 byte, unknown
u8 em_int_time; // x1F4, 1 byte, Emergency Interval Time [sec], 30-180
u8 unknown107; // x1F5, 1 byte, unknown
char em_text[10]; // x1F6-x1FF, 10 bytes, Emergency Text
char line1[32]; // x200-x21F, 32 Bytes, Embedded Message Line 1
char line2[32]; // x220-x23F, 32 Bytes, Embedded Message Line 2
u8 em_dtmf_id[16]; // x240-x24F, 16 bytes, Emergency DTMF ID
} misc;

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

#seekto 0x800;
struct {
u8 2t_a_tone_y[4]; // x800-x803, 2-Tone, A Tone(Hz), related to 2t_a_tone_x and 2t_a_tone_z
u8 2t_b_tone_y[4]; // x804-x807, 2-Tone, B Tone(Hz), related to 2t_b_tone_x and 2t_b_tone_z
u8 2t_c_tone_y[4]; // x808-x80B, 2-Tone, C Tone(Hz), related to 2t_c_tone_z
u8 2t_a_tone_z[2]; // x80C-x80D, 2-Tone, A Tone(Hz), related to 2t_a_tone_x and 2t_a_tone_y
u8 2t_b_tone_z[2]; // x80E-x80F, 2-Tone, B Tone(Hz), related to 2t_b_tone_x and 2t_b_tone_y
u8 2t_c_tone_z[2]; // x810-x811, 2-Tone, C Tone(Hz), related to 2t_c_tone_y
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)
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)
u8 2t_call_alert; // x814, 2-Tone, Call Alert, (FF=no, x30=Normal, x31=Continuous)
u8 2t_ar_timer[2]; // x815-x816, 2-Tone, Auto Reset Timer [sec], range(Off, 1-300) increments of 1
u8 unknown115:5, // x817,
2t_transpond:1, // 1 bit, 6th bit, 2-Tone, Transpond (1=disable, 0=enable)
2t_dec2_c_type:1, // 1 bit, 7th bit, 2-Tone, Decoder2 Call Type, (1=Individual, 0=Group)
2t_dec1_c_type:1; // 1 bit, 8th bit, 2-Tone, Decoder1 Call Type, (1=Individual, 0=Group)
u8 unknown116[8]; // x818-x81E, unknown

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

#seekto 0x1000;
struct {
u8 grp_num; // x1000, Group Number
u8 grp_chan; // x1001, Channels in Group
char grp_name[10]; // x1002-x100B, Group Name
u8 grp_data_grp; // x100C, Data Group
u8 grp_data_chan; // x100D, Data Channel
u8 unknown108[2]; // x100E-x100F, unknown
} groups;

#seekto 0x2000;
struct {
u8 bnumb; // x2000, 1 byte, Channel Number
u8 bank; // x2001, 1 byte, to which bank/group it belongs
char name[10]; // x2002-x200B, 10 bytes, Channel name, 10 chars
lbcd rxfreq[4]; // x200C-x200F, 4 bytes, rx freq
lbcd txfreq[4]; // x2010-x2013, 4 bytes, tx freq
u8 rx_unkw; // x2014, unknown yet
u8 tx_unkw; // x2015, unknown yet
ul16 rx_tone; // x2016-x2017, 2 bytes, rx tone
ul16 tx_tone; // x2018-x2019, 2 bytes, tx tone
u8 unknown23[5]; // x201A-x201E, 5 bytes, unknown yet
u8 signalling; // x201F, 1 byte, Option Signaling, xFF = off, x30 DTMF, x31 2-Tone 1, x32 FleetSync,
u8 unknown24:1, // x2020, 1 bit, unknown
ptt_id:1, // 1 bit, PTT ID, '1'=off, '0'=on
beat_shift:1, // 1 bit, Beat Shift, 1 = off
busy_lock:1, // 1 bit, Busy Channel Lock, 1=none, 0=QT/DQT Tone, see also 2 bits in x2021
data_en:1, // 1 bit, Data, 1=enable
power:1, // 1 bit, TX Power: 0 low / 1 high
compander:1, // 1 bit, Compander, 1 = off
wide:1; // 1 bit, Wide/Narrow, wide 1 / 0 narrow
u8 unknown27:4, // x2021, 4 bits, unknown
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
unknown28:2; // 2 bits, unknown
u8 unknown29[14]; // x2022, 14 bytes, unknown yet
} memory[250]; // Channel Scan/Add is under settings.add (x60)

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

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

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

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

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

NOTE = """ MENTAL NOTE ABOUT RADIO MEM

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

read: (hex)
00 - 03
07 - 08
10
20 - 21
58 - 7F

write: (hex)
00 - 03
07 - 08
10
20 - 21
60 - 7F


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

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

I must investigate further on this.
"""

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

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

ACK_CMD = "\x06"

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

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

TONES = chirp_common.TONES
# TONES.remove(254.1)
DTCS_CODES = chirp_common.DTCS_CODES


TOT = ["off"] + ["%s" % x for x in range(15, 615, 15)] # define list of valid options
TOT_PRE = ["off"] + ["%s" % x for x in range(1, 11)] #
TOT_REKEY = ["off"] + ["%s" % x for x in range(1, 61)] #
TOT_RESET = ["off"] + ["%s" % x for x in range(1, 16)] #
VOL = ["off"] + ["%s" % x for x in range(1, 32)] #
TVOL = ["%s" % x for x in range(0, 33)] #
TVOL[32] = "Continous" #
SQL = ["off"] + ["%s" % x for x in range(1, 10)] #
#SIG_TYPE = {1: "OR", 0: "AND"}
SIG_TYPE = ["AND", "OR"] # Signalling Type
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
#CM1 = {0xFF:"None", 0x33:"REM", 0x36:"Man Down In"} # Com1, com options are model dependent so combined all into CM0
BSAVE = {0xFF:"Off", 0x32:"Long", 0x30:"Short", 0x31:"Middle"} # Battery Save
PIDT = {0x00:"DTMF", 0x01:"FleetSync"} # PTT ID Type
PID = {10:"BOT", 01:"EOT", 00:"Both"} # PTT ID
SLT = {1:"Active Low", 0:"Active High"} # Squelch Logic Type
SLS = {1:"COR", 0:"TOR"} # Squelch Logic Signal
ALT = {1:"Active Low", 0:"Active High"} # Access Logic Type
ALS = {1:"Continuous", 0:"Pulse"} # Access Logic Signal
SIP = {0x31:"Selected", 0x30:"Fixed", 0xFF:"None"} # Scan Information Priority
PG = ["None"] + ["%s" % x for x in range(1, 32)] # Scan Information Priority Group
PCH = ["None"] + ["%s" % x for x in range(1, 250)] # Scan Information Priority Channel
#LBTA = ["%s" % x for x in drange(.35, 5.0, .05)] # Scan Info Look Back Time A
LBTA = [x / 100.0 for x in range(35, 505, 5)]
#LBTB = ["%s" % x for x in drange(.50, 5.0, .05)] # Scan Info Look Back Time B
LBTB = [x / 100.0 for x in range(50, 505, 5)]
REVCH = {0x30:"Last Called", 0x31:"Last Used", 0x34:"Priority", 0x35:"Priority+TalkBack"} # Scan Info Revert Channel
DDT = ["%s" % x for x in range(0, 300)] # Scan Info Dropout Delay Time
DWT = ["%s" % x for x in range(0, 300)] # Scan Info Dwell Time
GRPSC = {0x30:"Single", 0x31:"Multi"} # Scan Info Group Scan


# For debugging purposes
debug = True

KEYS = {
0x30: "Memory(RCL/STO)",
0x31: "DTMF ID(BOT)",
0x32: "DTMF ID(EOT)",
0x33: "Display character",
0x34: "Emergency",
0x35: "Home Channel", # Possible portable only, check it
0x37: "CH down",
0x38: "CH up",
0x39: "Key lock",
0x3a: "Lamp", # Portable only
0x3b: "Public address",
0x3c: "Reverse", # Not all firmware versions?
0x3d: "Horn alert",
0x3e: "Memory(RCL)",
0x3f: "Memory(STO)",
0x40: "Monitor A: Open Momentary",
0x41: "Monitor B: Open Toggle",
0x42: "Monitor C: Carrier Squelch Momentary",
0x43: "Monitor D: Carrier Squelch Toogle",
0x44: "AUX",
0x45: "Redial",
0x46: "RF Power Low", # portable only ?
0x47: "Scan",
0x48: "Scan Del/Add",
0x4a: "GROUP down",
0x4b: "GROUP up",
0x4e: "Operator Selectable Tone",
0x4f: "None",
0x50: "VOL down",
0x51: "VOL up",
0x52: "Talk around",
0x5d: "AUX",
0xa1: "Channel Up/Down", # Knob for portables only
0xa2: "Group Up/Down" # Knob for portables only
}


ver = "Read Radio"

def _raw_recv(radio, amount):
"""Raw read from the radio device"""
data = ""
try:
data = radio.pipe.read(amount)
except:
raise errors.RadioError("Error reading data from radio")

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

return data


def _raw_send(radio, data):
"""Raw send to the radio device"""
try:
radio.pipe.write(data)
except:
raise errors.RadioError("Error sending data to radio")

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


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


def _checksum(data):
"""the radio block checksum algorithm"""
cs = 0
for byte in data:
cs += ord(byte)
return cs % 256


def _send(radio, frame):
"""Generic send data to the radio"""
_raw_send(radio, frame)


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


def _handshake(radio, msg=""):
"""Make a full handshake"""
# send ACK
_raw_send(radio, ACK_CMD)
# receive ACK
ack = _raw_recv(radio, 1)
# check ACK
if ack != ACK_CMD:
_close_radio(radio)
mesg = "Handshake failed " + msg
# DEBUG
LOG.debug(mesg)
LOG.debug("ack: " + util.hexprint(ack))
raise Exception(mesg)


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

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

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


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

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

# when the RX block has two bytes and the first is #\x5A 'Z'
# then the block is all \xFF
elif len(rxdata) == 2 and rxdata[0] == "\x5a":
# "Z"
## just return false to flag about empty block
_handshake(radio, "after zero block")
return b'\xff' * BLOCK_SIZE
#return False
elif rxdata[0] != b'W':
# 57
LOG.error('Got non-W command:')
LOG.error(util.hexprint(rxdata))
# raise errors.RadioError('Received unexpected response from radio')
return False
elif len(rxdata) != 258:
_close_radio(radio)
# not the amount of data we want
msg = "The radio send %d bytes, we need 258" % len(rxdata)
# DEBUG
LOG.error(msg)
LOG.debug(rxdata)
raise errors.RadioError(msg)
else:
rcs = ord(rxdata[-1])
data = rxdata[1:-1]
ccs = _checksum(data)
#LOG.debug("data: " + util.hexprint(rxdata))
if rcs != ccs:
_close_radio(radio)
raise errors.RadioError(
"Block Checksum Error! real %02x, calculated %02x" %
(rcs, ccs))
_handshake(radio, "after checksum")
return data

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

# DEBUG
LOG.debug("Entering program mode.")
# max tries
tries = 10

# UI
status.cur = 0
status.max = tries
status.msg = "Entering program mode..."

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

# line dance between each try:
radio.pipe.rts = True
radio.pipe.dtr = True
time.sleep(0.02)
radio.pipe.dtr = False

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

if len(ack) == 0 or ack != ACK_CMD:
# DEBUG
#LOG.debug(ack % i)
LOG.debug("Try %s failed, traying again..." % i)
time.sleep(0.25)
else:
exito = True
break

status.cur += 1
radio.status_fn(status)


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

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


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

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

# DEBUG
LOG.debug("Incorrect model ID:")
LOG.debug(util.hexprint(rid))

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

# DEBUG
LOG.debug("Full ident string is:")
LOG.debug(util.hexprint(rid))
_handshake(radio)

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

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

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

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


def do_download(radio):
""" The download function """
# UI progress
status = chirp_common.Status()
data = ""
count = 0

# open the radio
_open_radio(radio, status)

# reset UI data
status.cur = 0
status.max = MEM_SIZE / BLOCK_SIZE
status.msg = "Cloning from radio..."
radio.status_fn(status)

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

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

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

data += d
# UI Update
status.cur = count
radio.status_fn(status)

count += 1

_close_radio(radio)
return memmap.MemoryMap(data)


def do_upload(radio):
""" The upload function """

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

# UI progress
status = chirp_common.Status()
data = ""
count = 0

# open the radio
_open_radio(radio, status)

# update UI
status.cur = 0
status.max = MEM_SIZE / BLOCK_SIZE
status.msg = "Cloning to radio..."
radio.status_fn(status)

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

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

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

# The blocks from x59-x5F are NOT programmable
# The blocks from x11-x1F are writed only if not empty
if addr in RO_BLOCKS:
# checking if in the range of optional blocks
if addr >= 0x10 and addr <= 0x1F:
# block is empty ?
if data == EMPTY_BLOCK:
# no write of this block
# but we have to continue updating the counters
count += 1
raddr = count * 256
continue
else:
count += 1
raddr = count * 256
continue

if data == EMPTY_BLOCK:
frame = _make_frame("Z", addr) + "\xFF"
else:
cs = _checksum(data)
frame = _make_frame("W", addr) + data + chr(cs)

_send(radio, frame)

# get the ACK
ack = _raw_recv(radio, 1)
_check_write_ack(radio, ack, addr)

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

# UI Update
status.cur = count
radio.status_fn(status)

count += 1
raddr = count * 256

_close_radio(radio)


def model_match(cls, data):
"""Match the opened/downloaded image to the correct version"""
rid = data[0xA7:0xAE]
LOG.debug("model_match: " + util.hexprint(rid))
if (rid in cls.VARIANTS):
# correct model
LOG.debug("Model_match successful")
return True
else:
LOG.error("Model_match fail")
return False


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

def get_num_mappings(self):
return self._radio._num_banks

def get_mappings(self):
banks = []
for i in range(0, self._radio._num_banks):
bindex = i + 1
bank = self._radio._bclass(self, i, "%03i" % bindex)
bank.index = i
banks.append(bank)
return banks

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

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

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

def get_mapping_memories(self, bank):
memories = []
for i in range(0, self._radio._upper):
if self._radio._get_bank(i) == bank.index:
memories.append(self._radio.get_memory(i))
return memories

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


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


class Kenwood_Serie_80(chirp_common.CloneModeRadio):
"""Kenwood Serie 80 Radios base class"""
VENDOR = "Kenwood"
BAUD_RATE = 9600
_memsize = MEM_SIZE
NAME_LENGTH = 8
_range = [400000000, 520000000] # specifically for tk380
_upper = 250 # originally 250
_chs_progs = 0
_num_banks = 32 # was 128, originally 250
_bclass = memBank
_kind = ""
VARIANT = ""
MODEL = ""

@classmethod
def get_prompts(cls):
rp = chirp_common.RadioPrompts()
rp.experimental = \
('This driver is experimental and for personal use only.'
'It has a limited set of features, but the most used.'
''
'The most notorius missing features are this:'
'=> PTT ID Settings'
'=> Priority / Home channel'
'=> Bank names'
'=> Others'
''
'If you need one of this, get your official software to do it'
'and raise and issue on the chirp site about it and maybe'
'it will be implemented in the future.'
''
'The band limits on some of this radios are set here far from'
'the vendor limits to cover most of the ham bands. It is a'
'known fact that some these radios can work safely outside the OEM'
'limits, but each radio is different, you may find trouble'
'with some particular radios, but this is also possible with'
'the OEM software; as usual YMMV.'
''
'A detail: this driver is slow reading from the radio in'
'Windows, due to the way windows serial works.'
)
rp.pre_download = _(dedent("""\
Follow this instructions to download your info:
1 - Turn off your radio
2 - Connect your interface cable
3 - Turn on your radio (unblock it if password protected)
4 - Do the download of your radio data
"""))
rp.pre_upload = _(dedent("""\
Follow this instructions to upload your info:
1 - Turn off your radio
2 - Connect your interface cable
3 - Turn on your radio (unblock it if password protected)
4 - Do the upload of your radio data
"""))
return rp

def get_features(self):
"""Return information about this radio's features"""
rf = chirp_common.RadioFeatures()
rf.has_settings = True
rf.has_bank = True
rf.has_tuning_step = False
rf.has_name = True
rf.has_offset = True
rf.has_mode = True
rf.has_dtcs = True
rf.has_rx_dtcs = True
rf.has_dtcs_polarity = True
rf.has_ctone = True
rf.has_cross = True
rf.valid_modes = MODES
rf.valid_duplexes = ["", "-", "+", "off"]
rf.valid_tmodes = ['', 'Tone', 'TSQL', 'DTCS', 'Cross']
rf.valid_cross_modes = [
"Tone->Tone",
"DTCS->",
"->DTCS",
"Tone->DTCS",
"DTCS->Tone",
"->Tone",
"DTCS->DTCS"]
rf.valid_power_levels = POWER_LEVELS
rf.valid_characters = VALID_CHARS
rf.valid_skips = SKIP_VALUES
rf.valid_dtcs_codes = DTCS_CODES
rf.valid_bands = [self._range]
rf.valid_name_length = 10
rf.memory_bounds = (1, self._upper)
return rf

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

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

# sorting the data
for ch in range(0, self._upper):
mem = self._memobj.memory[ch]
bnumb = int(mem.bnumb)
bank = int(mem.bank)
if bnumb != 255 and (bank != 255 and bank != 0):
try:
data[bank].append(ch)
except:
data[bank] = list()
data[bank].append(ch)
data[bank].sort()
# counting the real channels
rchs = rchs + 1

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

# building the data for the memmap
fdata = ""

for k, v in data.iteritems():
# posible bad data
if k == 0:
k = 1
raise errors.InvalidValueError(
"Invalid bank value '%k', bad data in the image? \
Triying to fix this, review your bank data!" % k)
c = 1
for i in v:
fdata += chr(k) + chr(c) + chr(k - 1) + chr(i)
c = c + 1

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

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

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

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

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

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

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

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

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

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

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

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

except KeyError:
LOG.debug("Wrong Kenwood radio, ID or unknown variant")
LOG.debug(util.hexprint(rid))
#LOG.debug(self._VARIANT)
raise errors.RadioError(
"Wrong Kenwood radio, ID or unknown variant, see LOG output.")
return False

def sync_in(self):
"""Do a download of the radio eeprom"""
self._mmap = do_download(self)
self.process_mmap()

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

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

try:
self._prep_data()
do_upload(self)
except errors.RadioError:
raise
except Exception, e:
raise errors.RadioError("Failed to communicate with radio: %s" % e)

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

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

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

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

def _decode_tone(self, val):
"""Parse the tone data to decode from mem, it returns:
Mode (''|DTCS|Tone), Value (None|###), Polarity (None,N,R)"""
val = int(val)
if val == 65535:
return '', None, None
elif val >= 0x2800:
code = int("%03o" % (val & 0x07FF))
pol = (val & 0x8000) and "R" or "N"
return 'DTCS', code, pol
else:
a = val / 10.0
return 'Tone', a, None

def _encode_tone(self, memval, mode, value, pol):
"""Parse the tone data to encode from UI to mem"""
if mode == '':
memval.set_raw("\xff\xff")
elif mode == 'Tone':
memval.set_value(int(value * 10))
elif mode == 'DTCS':
val = int("%i" % value, 8) + 0x2800
if pol == "R":
val += 0xA000
memval.set_value(val)
else:
raise Exception("Internal error: invalid mode `%s'" % mode)

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

return result

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

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

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

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

# Memory number
mem.number = number

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

# Freq and offset
mem.freq = int(_mem.rxfreq) * 10
# tx freq can be blank
if _mem.get_raw()[16] == "\xFF":
# TX freq not set
mem.offset = 0
mem.duplex = "off"
else:
# TX feq set
offset = (int(_mem.txfreq) * 10) - mem.freq
if offset < 0:
mem.offset = abs(offset)
mem.duplex = "-"
elif offset > 0:
mem.offset = offset
mem.duplex = "+"
else:
mem.offset = 0

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

# power
mem.power = POWER_LEVELS[_mem.power]

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

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

# tone data
rxtone = txtone = None
txtone = self._decode_tone(_mem.tx_tone)
rxtone = self._decode_tone(_mem.rx_tone)
chirp_common.split_tone_decode(mem, txtone, rxtone)

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

# validate bank
b = int(_mem.bank)
if b > 127 or b == 0:
_mem.bank = b = 1

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

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

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

bs = RadioSetting("beat_shift", "Beat shift",
RadioSettingValueBoolean(
not bool(_mem.beat_shift)))
mem.extra.append(bs)

cp = RadioSetting("compander", "Compander",
RadioSettingValueBoolean(
not bool(_mem.compander)))
mem.extra.append(cp)

bl = RadioSetting("busy_lock", "Busy Channel lock",
RadioSettingValueBoolean(
not bool(_mem.busy_lock)))
mem.extra.append(bl)

return mem

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

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

# if empty memmory
if mem.empty:
_mem.set_raw("\xFF" * 48)
return

# frequency
_mem.rxfreq = mem.freq / 10

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

# duplex
if mem.duplex == "+":
_mem.txfreq = (mem.freq + mem.offset) / 10
elif mem.duplex == "-":
_mem.txfreq = (mem.freq - mem.offset) / 10
elif mem.duplex == "off":
for byte in _mem.txfreq:
byte.set_raw("\xFF")
else:
_mem.txfreq = mem.freq / 10

# tone data
((txmode, txtone, txpol), (rxmode, rxtone, rxpol)) = \
chirp_common.split_tone_encode(mem)
self._encode_tone(_mem.tx_tone, txmode, txtone, txpol)
self._encode_tone(_mem.rx_tone, rxmode, rxtone, rxpol)

# name TAG of the channel
_namelength = self.get_features().valid_name_length
for i in range(_namelength):
try:
_mem.name[i] = mem.name[i]
except IndexError:
_mem.name[i] = "\x20"

# power
# default power is low
if mem.power is None:
mem.power = POWER_LEVELS[0]

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

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

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

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

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

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

return mem

@classmethod
def match_model(cls, filedata, filename):
match_size = False
match_model = False

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

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

if match_size and match_model:
return True
else:
return False

def get_settings(self):
"""Translate the bit in the mem_struct into settings in the UI"""
sett = self._memobj.settings
msc = self._memobj.misc
keys = self._memobj.keys
idm = self._memobj.id
passwd = self._memobj.passwords
fsync = self._memobj.fleetsync

# Optional Features 1
optfeat1 = RadioSettingGroup("optfeat1", "Optional Features 1")
# Optional Features 2
optfeat2 = RadioSettingGroup("optfeat2", "Optional Features 2")
# dealer settings
dealer = RadioSettingGroup("dealer", "Dealer Settings")
# buttons
fkeys = RadioSettingGroup("keys", "Controls")
# Scan Information
scaninf = RadioSettingGroup("scaninf", "Scan Information")
# DTMF
dtmfset = RadioSettingGroup("dtmfset", "DTMF")
## FleetSync
##flsync = RadioSettingGroup("flsync", "FleetSync")

# TODO / PLANED
# adjust feqs
#freqs = RadioSettingGroup("freqs", "Adjust Frequencies")

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

# optfeat1
## If user changes passwords to blank, which is actually x20 not xFF, that sets impossible password. Read-only to view unknown passwords only
rtmp = str(passwd.radio).strip("\xff")
radp = RadioSettingValueString(0, 6, rtmp)
radp.set_mutable(False)
radiop = RadioSetting("passwords.radio", "Radio Password", radp)
optfeat1.append(radiop)
rtmp = str(passwd.data).strip("\xff")
datp = RadioSettingValueString(0, 6, rtmp)
datp.set_mutable(False)
datap = RadioSetting("passwords.data", "Data Password", datp)
optfeat1.append(datap)
ponm = str(sett.poweronmesg).strip("\xff")
pom = RadioSetting("settings.poweronmesg", "Power on message",
RadioSettingValueString(0, 12, ponm))
optfeat1.append(pom)
sigtyp = RadioSetting("settings.signalling_type", "Signalling Type",
RadioSettingValueList(
SIG_TYPE, SIG_TYPE[(int(sett.signalling_type))]))
optfeat1.append(sigtyp)
"""LOG.debug(msc.com_0)
com0 = RadioSetting("misc.com_0", "Com 0(Acc. Connector)[Model Dependant]",
RadioSettingValueList(CM0.values(),
CM0[int(msc.com_0)]))
optfeat1.append(com0)
com1 = RadioSetting("misc.com_1", "Com 1(Internal Port)[Model Dependant]",
RadioSettingValueList(CM0.values(),
CM0[int(msc.com_1)]))
optfeat1.append(com1)
if self.TYPE[0] == "M":
com2 = RadioSetting("misc.com_2", "Com 2(Internal Port)[Model Dependant]",
RadioSettingValueList(CM0.values(),
CM0[int(msc.com_2)]))
optfeat1.append(com2)
"""
if self.TYPE[0] == "P":
bsav = RadioSetting("settings.battery_save", "Battery Save",
RadioSettingValueList(BSAVE.values(),
BSAVE[int(sett.battery_save)]))
optfeat1.append(bsav)
tot = RadioSetting("settings.tot", "Time Out Timer (TOT)",
RadioSettingValueList(TOT, TOT[
TOT.index(str(int(sett.tot)))]))
optfeat1.append(tot)

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

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

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

c2t = RadioSetting("settings.c2t", "Clear to Transpond",
RadioSettingValueBoolean(
not bool(sett.c2t)))
optfeat1.append(c2t)
if self.TYPE[0] == "P":
bw = RadioSetting("settings.battery_warn", "Battery Warning",
RadioSettingValueBoolean(
not bool(sett.battery_warn)))
optfeat1.append(bw)
if self.TYPE[0] == "M":
ohd = RadioSetting("settings.off_hook_decode", "Off Hook Decode",
RadioSettingValueBoolean(
not bool(sett.off_hook_decode)))
optfeat1.append(ohd)
ohha = RadioSetting("settings.off_hook_horn_alert", "Off Hook Horn Alert",
RadioSettingValueBoolean(
not bool(sett.off_hook_horn_alert)))
optfeat1.append(ohha)
bled = RadioSetting("settings.busy_led", "Busy LED",
RadioSettingValueBoolean(
not bool(sett.busy_led)))
optfeat1.append(bled)
scled = RadioSetting("settings.sel_call_alert_led", "Selective Call Alert LED",
RadioSettingValueBoolean(
sett.sel_call_alert_led))
optfeat1.append(scled)
# this feature is for mobile only
#if self.TYPE[0] == "M":
minvol = RadioSetting("settings.min_vol", "Minimum Volume",
RadioSettingValueList(VOL,
VOL[int(sett.min_vol)]))
optfeat2.append(minvol)

tv = int(sett.tone_vol)
if tv == 255:
tv = 32
tvol = RadioSetting("settings.tone_vol", "Tone Volume",
RadioSettingValueList(TVOL, TVOL[tv]))
optfeat2.append(tvol)

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


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

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

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

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

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

# PTT ID Section
pdt = RadioSetting("misc.ptt_id_type", "PTT ID Type",
RadioSettingValueList(PIDT.values(),
PIDT[int(msc.ptt_id_type)]))
optfeat2.append(pdt)
"""
LOG.debug("PTT ID When: " + str(sett.ptt_id_when))
pti = RadioSetting("settings.ptt_id_when", "PTT ID",
RadioSettingValueList(PID.values(),
PID[int(sett.ptt_id_when)]))
optfeat2.append(pti)
"""
bot = str(sett.ptt_id_bot).strip("\xff")
pttbot = RadioSetting("settings.ptt_id_bot", "PTT Begin of TX",
RadioSettingValueString(0, 16, bot))
optfeat2.append(pttbot)
eot = str(sett.ptt_id_eot).strip("\xff")
ptteot = RadioSetting("settings.ptt_id_eot", "PTT End of TX",
RadioSettingValueString(0, 16, eot))
optfeat2.append(ptteot)
inhta = RadioSetting("settings.ptt_inhib_ta", "PTT Inhibit ID in TA(TalkAround)",
RadioSettingValueBoolean(not sett.ptt_inhib_ta))
optfeat2.append(inhta)

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

for i in range(0, len(self._VARIANT)):
if ord(self._VARIANT[i]) in valid_chars:
mstr += self._VARIANT[i]
"""LOG.debug(mstr) // not in non-trunked radios
val = RadioSettingValueString(0, 35, mstr)
val.set_mutable(False)
mod = RadioSetting("not.mod", "Radio Version", val)
dealer.append(mod)

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

svp = str(sett.lastsoftversion).strip(" \xff")
val = RadioSettingValueString(0, 5, svp)
val.set_mutable(False)
sver = RadioSetting("not.softver", "Last Used Software Version", val)
dealer.append(sver)
vtmp = str(ver.strip(" \xff"))
LOG.debug(vtmp)
rev = RadioSettingValueString(0, 10, vtmp)
rev.set_mutable(False)
frev = RadioSetting("not.ver", "Radio Version(not from saved file)", rev)
dealer.append(frev)

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

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

panel = RadioSetting("settings.panel_test", "Panel Test",
RadioSettingValueBoolean(sett.panel_test))
optfeat2.append(panel)
ptun = RadioSetting("settings.panel_tuning", "Panel Tuning",
RadioSettingValueBoolean(sett.panel_tuning))
optfeat2.append(ptun)
fmw = RadioSetting("settings.firmware_prog", "Firmware Programming",
RadioSettingValueBoolean(sett.firmware_prog))
optfeat2.append(fmw)
clone = RadioSetting("settings.clone", "Allow clone",
RadioSettingValueBoolean(sett.clone))
optfeat2.append(clone)
sprog = RadioSetting("settings.self_prog", "Self Programming",
RadioSettingValueBoolean(sett.self_prog))
optfeat2.append(sprog)
# Logic Signal Section
sqlt = RadioSetting("settings.sq_logic_type", "Squelch Logic Type",
RadioSettingValueList(SLT.values(),
SLT[int(sett.sq_logic_type)]))
optfeat2.append(sqlt)
sqls = RadioSetting("settings.sq_logic_sig", "Squelch Logic Signal",
RadioSettingValueList(SLS.values(),
SLS[int(sett.sq_logic_sig)]))
optfeat2.append(sqls)
aclt = RadioSetting("settings.access_log_type", "Acess Logic Type",
RadioSettingValueList(ALT.values(),
ALT[int(sett.access_log_type)]))
optfeat2.append(aclt)
acls = RadioSetting("settings.access_log_sig", "Acess Logic Signal",
RadioSettingValueList(ALS.values(),
ALS[int(sett.access_log_sig)]))
optfeat2.append(acls)
# Extended Function Section
dtxqt = RadioSetting("fleetsync.data_tx_qt", "Data TX with QT/DQT",
RadioSettingValueBoolean(not fsync.data_tx_qt))
optfeat2.append(dtxqt)
# Scan Information Section
"""scip = RadioSetting("settings.sc_nfo_priority", "Priority",
RadioSettingValueList(SIP.values(),
SIP[int(sett.sc_nfo_priority)]))
scaninf.append(scip)"""
"""pgr = RadioSetting("settings.sc_nfo_prio_grp", "Priority Group",
RadioSettingValueList(PG,
PG[int(sett.sc_nfo_prio_grp)]))
scaninf.append(pgr)"""
"""pch = RadioSetting("settings.sc_nfo_prio_ch", "Priority Channel",
RadioSettingValueList(PCH,
PCH[int(sett.sc_nfo_prio_ch)]))
scaninf.append(pch)"""
"""LOG.debug(sett.sc_nfo_lbt_a)
LOG.debug(sett.sc_nfo_lbt_a / 1000.00)
lkbka = RadioSetting("settings.sc_nfo_lbt_a", "Look Back Time A[sec]",
RadioSettingValueList(LBTA, '%s' % (sett.sc_nfo_lbt_a / 1000.00)))
scaninf.append(lkbka)"""
"""lkbkb = RadioSetting("settings.sc_nfo_lbt_b", "Look Back Time B[sec]",
RadioSettingValueList(LBTB, '%i' % sett.sc_nfo_lbt_b))
scaninf.append(lkbkb)"""
"""revchan = RadioSetting("settings.sc_nfo_revert", "Revert Channel",
RadioSettingValueList(REVCH.values(),
REVCH[int(sett.sc_nfo_revert)]))
scaninf.append(revchan)"""
drdt = RadioSetting("settings.sc_nfo_ddtime", "Dropout Delay Time[sec]",
RadioSettingValueList(DDT,
DDT[int(sett.sc_nfo_ddtime)]))
scaninf.append(drdt)
dwet = RadioSetting("settings.sc_nfo_dwell", "Dwell Time[sec]",
RadioSettingValueList(DWT,
DWT[int(sett.sc_nfo_dwell)]))
scaninf.append(dwet)
"""grps = RadioSetting("settings.sc_nfo_grp_scan", "Group Scan",
RadioSettingValueList(GRPSC.values(),
GRPSC[int(sett.sc_nfo_grp_scan)]))
scaninf.append(grps)"""
rchd = RadioSetting("settings.sc_nfo_rev_disp", "Revert Channel Display",
RadioSettingValueBoolean(not bool(sett.sc_nfo_rev_disp)))
scaninf.append(rchd)
# DTMF Settings
# Decode Section
deccode = str(sett.dtmf_prim_code).strip("\xff")
decpc = RadioSetting("settings.dtmf_prim_code", "Decode Primary Code",
RadioSettingValueString(0, 7, deccode))
dtmfset.append(decpc)
deccode = str(sett.dtmf_sec_code).strip("\xff")
decpc = RadioSetting("settings.dtmf_sec_code", "Decode Secondary Code",
RadioSettingValueString(0, 7, deccode))
dtmfset.append(decpc)
deccode = str(sett.dtmf_DBD_code).strip("\xff")
decpc = RadioSetting("settings.dtmf_DBD_code", "Decode Dead Beat Disable Code",
RadioSettingValueString(0, 7, deccode))
dtmfset.append(decpc)

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

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

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

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

foot = RadioSetting("keys.kORANGE", "Foot switch",
RadioSettingValueList(KEYS.values(),
KEYS.values()[KEYS.keys().index(
int(keys.kORANGE))]))
fkeys.append(foot)

# this is the common buttons for all

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

scn = RadioSetting("keys.kSCN", scn_name,
RadioSettingValueList(KEYS.values(),
KEYS.values()[KEYS.keys().index(
int(keys.kSCN))]))
fkeys.append(scn)

a_name = "A"
if self.TYPE[0] == "P":
a_name = "Closed circle"

a = RadioSetting("keys.kA", a_name,
RadioSettingValueList(KEYS.values(),
KEYS.values()[KEYS.keys().index(
int(keys.kA))]))
fkeys.append(a)

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

da = RadioSetting("keys.kDA", da_name,
RadioSettingValueList(KEYS.values(),
KEYS.values()[KEYS.keys().index(
int(keys.kDA))]))
fkeys.append(da)

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

gu = RadioSetting("keys.kGROUP_UP", gu_name,
RadioSettingValueList(KEYS.values(),
KEYS.values()[KEYS.keys().index(
int(keys.kGROUP_UP))]))
fkeys.append(gu)"""

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

gd = RadioSetting("keys.kGROUP_DOWN", gd_name,
RadioSettingValueList(KEYS.values(),
KEYS.values()[KEYS.keys().index(
int(keys.kGROUP_DOWN))]))
fkeys.append(gd)"""
if self.TYPE[0] == "P":
knob = RadioSetting("keys.kP_KNOB", "Knob",
RadioSettingValueList(KEYS.values(),
KEYS.values()[KEYS.keys().index(
int(keys.kP_KNOB))]))
fkeys.append(knob)
orange = RadioSetting("keys.kPF1", "Orange",
RadioSettingValueList(KEYS.values(),
KEYS.values()[KEYS.keys().index(
int(keys.kPF1))]))
fkeys.append(orange)

side1 = RadioSetting("keys.kSIDE1", "Side 1",
RadioSettingValueList(KEYS.values(),
KEYS.values()[KEYS.keys().index(
int(keys.kSIDE1))]))
fkeys.append(side1)

mon_name = "MON"
if self.TYPE[0] == "P":
mon_name = "Side 2"

mon = RadioSetting("keys.kMON", mon_name,
RadioSettingValueList(KEYS.values(),
KEYS.values()[KEYS.keys().index(
int(keys.kMON))]))
fkeys.append(mon)
sbtn = RadioSetting("keys.kSCN", "Scan/S",
RadioSettingValueList(KEYS.values(),
KEYS.values()[KEYS.keys().index(
int(keys.kSCN))]))
fkeys.append(sbtn)
abtn = RadioSetting("keys.kA", "A",
RadioSettingValueList(KEYS.values(),
KEYS.values()[KEYS.keys().index(
int(keys.kA))]))
fkeys.append(abtn)
bbtn = RadioSetting("keys.kLEFT", "Left Arrow(B on Portable)",
RadioSettingValueList(KEYS.values(),
KEYS.values()[KEYS.keys().index(
int(keys.kLEFT))]))
fkeys.append(bbtn)
cbtn = RadioSetting("keys.kRIGHT", "Right Arrow(C on Portable)",
RadioSettingValueList(KEYS.values(),
KEYS.values()[KEYS.keys().index(
int(keys.kRIGHT))]))
fkeys.append(cbtn)
# TODO Make the following (keypad 0-9,*,#) contingent on variant. Only concerned with TK-380 for now
btn0 = RadioSetting("keys.k0", "Keypad 0",
RadioSettingValueList(KEYS.values(),
KEYS.values()[KEYS.keys().index(
int(keys.k0))]))
fkeys.append(btn0)
btn1 = RadioSetting("keys.k1", "Keypad 1",
RadioSettingValueList(KEYS.values(),
KEYS.values()[KEYS.keys().index(
int(keys.k1))]))
fkeys.append(btn1)
btn2 = RadioSetting("keys.k2", "Keypad 2",
RadioSettingValueList(KEYS.values(),
KEYS.values()[KEYS.keys().index(
int(keys.k2))]))
fkeys.append(btn2)
btn3 = RadioSetting("keys.k3", "Keypad 3",
RadioSettingValueList(KEYS.values(),
KEYS.values()[KEYS.keys().index(
int(keys.k3))]))
fkeys.append(btn3)
btn4 = RadioSetting("keys.k4", "Keypad 4",
RadioSettingValueList(KEYS.values(),
KEYS.values()[KEYS.keys().index(
int(keys.k4))]))
fkeys.append(btn4)
btn5 = RadioSetting("keys.k5", "Keypad 5",
RadioSettingValueList(KEYS.values(),
KEYS.values()[KEYS.keys().index(
int(keys.k5))]))
fkeys.append(btn5)
btn6 = RadioSetting("keys.k6", "Keypad 6",
RadioSettingValueList(KEYS.values(),
KEYS.values()[KEYS.keys().index(
int(keys.k6))]))
fkeys.append(btn6)
btn7 = RadioSetting("keys.k7", "Keypad 7",
RadioSettingValueList(KEYS.values(),
KEYS.values()[KEYS.keys().index(
int(keys.k7))]))
fkeys.append(btn7)
btn8 = RadioSetting("keys.k8", "Keypad 8",
RadioSettingValueList(KEYS.values(),
KEYS.values()[KEYS.keys().index(
int(keys.k8))]))
fkeys.append(btn8)
btn9 = RadioSetting("keys.k9", "Keypad 9",
RadioSettingValueList(KEYS.values(),
KEYS.values()[KEYS.keys().index(
int(keys.k9))]))
fkeys.append(btn9)
starbtn = RadioSetting("keys.kASTR", "Keypad *",
RadioSettingValueList(KEYS.values(),
KEYS.values()[KEYS.keys().index(
int(keys.kASTR))]))
fkeys.append(starbtn)
poundbtn = RadioSetting("keys.kPOUND", "Keypad #",
RadioSettingValueList(KEYS.values(),
KEYS.values()[KEYS.keys().index(
int(keys.kPOUND))]))
fkeys.append(poundbtn)

return top


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

mobj = self._memobj

for element in settings:
if not isinstance(element, RadioSetting):
self.set_settings(element)
continue

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

obj = getattr(mobj, inter)
#LOG.debug("obj: " + str(obj))
value = element.value
#LOG.debug("value: " + str(value))

# integers case + special case
if setting in ["tot", "tot_alert", "min_vol", "tone_vol",
"sql_level", "tot_rekey", "tot_reset"]:
# catching the "off" values as zero
try:
value = int(value)
except:
value = 0

# tot case step 15
if setting == "tot":
value = value * 15
# off is special
if value == 0:
value = 0x4b0

# Caso tone_vol
if setting == "tone_vol":
# off is special
if value == 32:
value = 0xff

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

# this cases are inverted
if setting == "c2t":
value = not value

# case battery save is special
if setting == "battery_save":
if bool(value) is True:
value = 0x32
else:
value = 0xff

# String cases
if setting in ["poweronmesg", "line1", "line2"]:
# some vars
value = str(value)
just = 8
# lines with 32
if "line" in setting:
just = 32

# empty case
if len(value) == 0:
value = "\xff" * just
else:
value = value.ljust(just)

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

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

def get_bank_model(self):
"""Pass the bank model to the UI part"""
rf = self.get_features()
if rf.has_bank is True:
return Kenwood80BankModel(self)
else:
return None

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

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

def _set_bank(self, loc, bank):
"""Set the bank data for a specific channel"""
try:
b = int(bank)
if b > 127:
b = 0
mem = self._memobj.memory[loc - 1]
mem.bank = b + 1
except:
msg = "You can't have a channel without a bank, click another bank"
raise errors.InvalidDataError(msg)


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

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

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

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

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

(19-19/21)