|
# 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 itertools import chain
|
|
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 "xFF" * 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 unknown127[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 = chain(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 = b"\xFF" * 256
|
|
|
|
ACK_CMD = b"\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 = {0o10:"BOT", 0o01:"EOT", 0o00:"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, b"\x45") #"E"
|
|
|
|
|
|
def _checksum(data):
|
|
"""the radio block checksum algorithm"""
|
|
cs = 0
|
|
for byte in data:
|
|
cs += byte if type(byte) == type(0) else 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 == b"\x15":
|
|
_close_radio(r)
|
|
raise errors.RadioError(
|
|
"Bad checksum in block 0x%02x write" % addr)
|
|
|
|
# everything else
|
|
_close_radio(r)
|
|
raise errors.RadioError(
|
|
"Problem with the ack to block %02x write, ack %03i" %
|
|
(addr, ack[0]))
|
|
|
|
|
|
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] == 0x5a:
|
|
# "Z"
|
|
## just return false to flag about empty block
|
|
_handshake(radio, "after zero block")
|
|
return b'\xff' * BLOCK_SIZE
|
|
#return False
|
|
elif rxdata[0] != 0x57:
|
|
# 57
|
|
LOG.error(f"Got non-W command: {rxdata[0]}")
|
|
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 = 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, b"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, b"\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, b"\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, b"\x4F") # 'O'
|
|
|
|
|
|
def do_download(radio):
|
|
""" The download function """
|
|
# UI progress
|
|
status = chirp_common.Status()
|
|
data = b""
|
|
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.MemoryMapBytes(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 = b""
|
|
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]
|
|
|
|
if type(data) != type(b'\xFF'):
|
|
data = data.encode('latin1')
|
|
|
|
# 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: # or (addr > 0x29 and addr < 0x50):
|
|
frame = _make_frame("Z", addr) + b"\xFF"
|
|
else:
|
|
cs = _checksum(data)
|
|
frame = _make_frame("W", addr) + data + chr(cs).encode('latin1')
|
|
|
|
_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 = b""
|
|
|
|
for k, v in data.items():
|
|
# 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).encode('latin1') + chr(c).encode('latin1') + chr(k - 1).encode('latin1') + chr(i).encode('latin1')
|
|
c = c + 1
|
|
|
|
# fill to match a full 256 bytes block
|
|
fdata += (len(fdata) % 256) * b"\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 = b""
|
|
for i in range(1, len(data) + 1):
|
|
line = chr(i).encode('latin1') + chr(len(data[i])).encode('latin1')
|
|
line += b"\xff" * 14
|
|
bdata += line
|
|
|
|
# fill to match a full 256 bytes block
|
|
bdata += (256 - (len(bdata)) % 256) * b"\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 = b"\x00\xf0\xff\xff\xff" * self._chs_progs + \
|
|
b"\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]
|
|
|
|
if type(rid) != type(b'\xFF'):
|
|
rid = rid.encode('latin1')
|
|
|
|
# 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 = b"Chirp"
|
|
self._fill(0xbb, sign)
|
|
|
|
try:
|
|
self._prep_data()
|
|
do_upload(self)
|
|
except errors.RadioError:
|
|
raise
|
|
except Exception as 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 - 1])
|
|
|
|
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(b"\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) or (_mem.get_raw(asbytes=False)[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(b"\xFF" * 48)
|
|
return mem
|
|
|
|
# Freq and offset
|
|
mem.freq = int(_mem.rxfreq) * 10
|
|
# tx freq can be blank
|
|
if _mem.get_raw(asbytes=False)[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.bnumb = 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(b"\xFF" * 48)
|
|
return mem
|
|
|
|
# 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(b"\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] + list(range(48, 58)) + \
|
|
list(range(65, 91)) + list(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] == ord("M"):
|
|
vu = RadioSetting("keys.kVOL_UP", "VOL UP(Left Arrow Up)",
|
|
RadioSettingValueList(KEYS.values(),
|
|
KEYS[int(keys.kVOL_UP)]))
|
|
fkeys.append(vu)
|
|
|
|
vd = RadioSetting("keys.kVOL_DOWN", "VOL DOWN(Left Arrow Down)",
|
|
RadioSettingValueList(KEYS.values(),
|
|
KEYS[int(keys.kVOL_DOWN)]))
|
|
fkeys.append(vd)
|
|
|
|
chu = RadioSetting("keys.kPF1", "Group UP(Right Side Up Arrow)",
|
|
RadioSettingValueList(KEYS.values(),
|
|
KEYS[int(keys.kPF1)]))
|
|
fkeys.append(chu)
|
|
|
|
chd = RadioSetting("keys.kPF2", "Group DOWN(Right Side Down Arrow)",
|
|
RadioSettingValueList(KEYS.values(),
|
|
KEYS[int(keys.kPF2)]))
|
|
fkeys.append(chd)
|
|
|
|
foot = RadioSetting("keys.kORANGE", "Foot switch",
|
|
RadioSettingValueList(KEYS.values(),
|
|
KEYS[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] == ord("P"):
|
|
scn_name = "Open Circle"
|
|
|
|
scn = RadioSetting("keys.kSCN", scn_name,
|
|
RadioSettingValueList(KEYS.values(),
|
|
KEYS[int(keys.kSCN)]))
|
|
fkeys.append(scn)
|
|
|
|
a_name = "A"
|
|
if self.TYPE[0] == ord("P"):
|
|
a_name = "Closed circle"
|
|
|
|
a = RadioSetting("keys.kA", a_name,
|
|
RadioSettingValueList(KEYS.values(),
|
|
KEYS[int(keys.kA)]))
|
|
fkeys.append(a)
|
|
|
|
da_name = "D/A"
|
|
if self.TYPE[0] == ord("P"):
|
|
da_name = "< key"
|
|
|
|
da = RadioSetting("keys.kDA", da_name,
|
|
RadioSettingValueList(KEYS.values(),
|
|
KEYS[int(keys.kDA)]))
|
|
fkeys.append(da)
|
|
|
|
gu_name = "Triangle up"
|
|
if self.TYPE[0] == ord("P"):
|
|
gu_name = "Side 1"
|
|
|
|
gu = RadioSetting("keys.kGROUP_UP", gu_name,
|
|
RadioSettingValueList(KEYS.values(),
|
|
KEYS[int(keys.kGROUP_UP)]))
|
|
fkeys.append(gu)"""
|
|
|
|
# Side keys on portables
|
|
"""gd_name = "Triangle Down"
|
|
if self.TYPE[0] == ord("P"):
|
|
gd_name = "> key"
|
|
|
|
gd = RadioSetting("keys.kGROUP_DOWN", gd_name,
|
|
RadioSettingValueList(KEYS.values(),
|
|
KEYS[int(keys.kGROUP_DOWN)]))
|
|
fkeys.append(gd)"""
|
|
|
|
if self.TYPE[0] == ord("P"):
|
|
knob = RadioSetting("keys.kP_KNOB", "Knob",
|
|
RadioSettingValueList(KEYS.values(),
|
|
KEYS[int(keys.kP_KNOB)]))
|
|
fkeys.append(knob)
|
|
|
|
orange = RadioSetting("keys.kPF1", "Orange",
|
|
RadioSettingValueList(KEYS.values(),
|
|
KEYS[int(keys.kPF1)]))
|
|
fkeys.append(orange)
|
|
|
|
side1 = RadioSetting("keys.kSIDE1", "Side 1",
|
|
RadioSettingValueList(KEYS.values(),
|
|
KEYS[int(keys.kSIDE1)]))
|
|
fkeys.append(side1)
|
|
|
|
mon_name = "MON"
|
|
if self.TYPE[0] == ord("P"):
|
|
mon_name = "Side 2"
|
|
|
|
mon = RadioSetting("keys.kMON", mon_name,
|
|
RadioSettingValueList(KEYS.values(),
|
|
KEYS[int(keys.kMON)]))
|
|
fkeys.append(mon)
|
|
|
|
sbtn = RadioSetting("keys.kSCN", "Scan/S",
|
|
RadioSettingValueList(KEYS.values(),
|
|
KEYS[int(keys.kSCN)]))
|
|
fkeys.append(sbtn)
|
|
|
|
abtn = RadioSetting("keys.kA", "A",
|
|
RadioSettingValueList(KEYS.values(),
|
|
KEYS[int(keys.kA)]))
|
|
fkeys.append(abtn)
|
|
|
|
bbtn = RadioSetting("keys.kLEFT", "Left Arrow(B on Portable)",
|
|
RadioSettingValueList(KEYS.values(),
|
|
KEYS[int(keys.kLEFT)]))
|
|
fkeys.append(bbtn)
|
|
|
|
cbtn = RadioSetting("keys.kRIGHT", "Right Arrow(C on Portable)",
|
|
RadioSettingValueList(KEYS.values(),
|
|
KEYS[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[int(keys.k0)]))
|
|
fkeys.append(btn0)
|
|
|
|
btn1 = RadioSetting("keys.k1", "Keypad 1",
|
|
RadioSettingValueList(KEYS.values(),
|
|
KEYS[int(keys.k1)]))
|
|
fkeys.append(btn1)
|
|
|
|
btn2 = RadioSetting("keys.k2", "Keypad 2",
|
|
RadioSettingValueList(KEYS.values(),
|
|
KEYS[int(keys.k2)]))
|
|
fkeys.append(btn2)
|
|
|
|
btn3 = RadioSetting("keys.k3", "Keypad 3",
|
|
RadioSettingValueList(KEYS.values(),
|
|
KEYS[int(keys.k3)]))
|
|
fkeys.append(btn3)
|
|
|
|
btn4 = RadioSetting("keys.k4", "Keypad 4",
|
|
RadioSettingValueList(KEYS.values(),
|
|
KEYS[int(keys.k4)]))
|
|
fkeys.append(btn4)
|
|
|
|
btn5 = RadioSetting("keys.k5", "Keypad 5",
|
|
RadioSettingValueList(KEYS.values(),
|
|
KEYS[int(keys.k5)]))
|
|
fkeys.append(btn5)
|
|
|
|
btn6 = RadioSetting("keys.k6", "Keypad 6",
|
|
RadioSettingValueList(KEYS.values(),
|
|
KEYS[int(keys.k6)]))
|
|
fkeys.append(btn6)
|
|
|
|
btn7 = RadioSetting("keys.k7", "Keypad 7",
|
|
RadioSettingValueList(KEYS.values(),
|
|
KEYS[int(keys.k7)]))
|
|
fkeys.append(btn7)
|
|
|
|
btn8 = RadioSetting("keys.k8", "Keypad 8",
|
|
RadioSettingValueList(KEYS.values(),
|
|
KEYS[int(keys.k8)]))
|
|
fkeys.append(btn8)
|
|
|
|
btn9 = RadioSetting("keys.k9", "Keypad 9",
|
|
RadioSettingValueList(KEYS.values(),
|
|
KEYS[int(keys.k9)]))
|
|
fkeys.append(btn9)
|
|
|
|
starbtn = RadioSetting("keys.kASTR", "Keypad *",
|
|
RadioSettingValueList(KEYS.values(),
|
|
KEYS[int(keys.kASTR)]))
|
|
fkeys.append(starbtn)
|
|
|
|
poundbtn = RadioSetting("keys.kPOUND", "Keypad #",
|
|
RadioSettingValueList(KEYS.values(),
|
|
KEYS[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 = 12
|
|
# 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 = list(KEYS.keys())[
|
|
list(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 = b"P0280"
|
|
VARIANTS = {
|
|
b"P0280\x04\xFF": (250, 144, 174, "K Non-Keypad Model"),#VERIFIED variant. Range expanded for ham bands. Orig: 146, 174
|
|
b"P0280\x05\xFF": (250, 136, 162, "K2 Non-Keypad Model"),
|
|
b"P0280\xFF\xFF": (250, 144, 174, "K3 Keypad Model"),#Range expanded for ham bands. Orig: 146, 174
|
|
b"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 = b"P0380"
|
|
VARIANTS = {
|
|
b"P0380\x06\xFF": (250, 420, 490, "K Non-Keypad Model"),#Range expanded for ham bands. Orig: 450, 490
|
|
b"P0380\x07\xFF": (250, 420, 512, "K2 Non-Keypad Model"),#Range expanded for ham bands. Orig: 470, 512
|
|
b"P0380\x08\xFF": (250, 400, 450, "K3 Non-Keypad Model"),#Range expanded for ham bands. Orig: 400, 430
|
|
b"P0380\x0a\xFF": (250, 420, 490, "K4 Keypad Model"),#VERIFIED variant. Range expanded for ham bands. Orig: 450, 490
|
|
b"P0380\xFF\xFF": (250, 400, 450, "K6 Keypad Model"),#Range expanded for ham bands. Orig: 400, 430
|
|
b"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 = b"M0780"
|
|
VARIANTS = {
|
|
b"M0780\x04\xFF": (250, 144, 174, "K"),#VERIFIED variant. #Range expanded for ham bands. Orig: 146, 174
|
|
b"M0780\x05\xFF": (250, 136, 174, "K2"),
|
|
}
|
|
|
|
@directory.register
|
|
class TK880_Radios(Kenwood_Serie_80):
|
|
"""Kenwood TK-880 Radio """
|
|
MODEL = "TK-880"
|
|
TYPE = b"M0880"
|
|
VARIANTS = {
|
|
b"M0880\x06\xFF": (250, 420, 490, "K"),#VERIFIED variant. #Range expanded for ham bands. Orig: 450, 490
|
|
b"M0880\x07\xFF": (250, 420, 512, "K2"),#Range expanded for ham bands. Orig: 485, 512
|
|
b"M0880\x08\xFF": (250, 400, 450, "K3"),#Range expanded for ham bands. Orig: 400, 430
|
|
}
|
|
|