|
# Copyright 2012 Dan Smith <dsmith@danplanet.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/>.
|
|
|
|
# driver author Pavel Milanes, CO7WT, pavelmc@gmail.com, co7wt@frcuba.co.cu
|
|
|
|
import logging
|
|
import struct
|
|
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 - 0x08000 => Radio Memory / Settings data
|
|
# 0x08000 - 0x10000 => FIRMWARE... hum...
|
|
###############################################################
|
|
|
|
MEM_FORMAT = """
|
|
#seekto 0x0000;
|
|
struct {
|
|
u8 unknown0[14]; // x00-x0d unknown
|
|
u8 banks; // x0e how many banks are programmed
|
|
u8 channels; // x0f how many total channels are programmed
|
|
// --
|
|
ul16 tot; // x10 TOT value: range(15, 600, 15); x04b0 = off
|
|
u8 unknown1[4]; // x12-x15 unknown1[4]
|
|
u8 tot_alert; // x16 TOT pre alert: range(0,10); 0 = off
|
|
u8 unknown2[8]; // x17-x1F unknown
|
|
u8 battery_save; // Only for portable: FF = off, x32 = on
|
|
// --
|
|
u8 unknown3[10]; // x20
|
|
u8 unknown4:3, // x2d
|
|
c2t:1, // 1 bit clear to transpond: 1-off
|
|
// This is relative to DTMF / 2-Tone settings
|
|
unknown5:4;
|
|
u8 unknown6[5]; // x2b-x2f
|
|
// --
|
|
u8 unknown7[16]; // x30 ?
|
|
u8 unknown8[16]; // x40 ?
|
|
u8 unknown9[16]; // x50 ?
|
|
u8 unknown10[16]; // x60 ?
|
|
// --
|
|
u8 add[16]; // x70-x7f 128 bits corresponding add/skip values
|
|
// --
|
|
u8 unknown11:4, // x80
|
|
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
|
|
unknown12:2;
|
|
u8 unknown13; // x81
|
|
u8 unknown14: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
|
|
unknown15:1,
|
|
panel_test:1; // 1 bit panel test enabled
|
|
u8 unknown16; // x83
|
|
u8 unknown17: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 unknown18[5]; // x85-x89
|
|
u8 min_vol; // minimum volume posible: range(0,32); 0 = off
|
|
u8 tone_vol; // minimum tone volume posible:
|
|
// xff = continous, range(0, 31)
|
|
u8 unknown19[4]; // x8c-x8f
|
|
// --
|
|
u8 unknown20[4]; // x90-x93
|
|
char poweronmesg[8]; // x94-x9b power on mesg 8 bytes, off is "\FF" * 8
|
|
u8 unknown21[4]; // x9c-x9f
|
|
// --
|
|
u8 unknown22[7]; // xa0-xa6
|
|
char ident[8]; // xa7-xae radio identification string
|
|
u8 unknown23; // xaf
|
|
// --
|
|
u8 unknown24[11]; // xaf-xba
|
|
char lastsoftversion[5]; // software version employed to program the radio
|
|
} settings;
|
|
|
|
#seekto 0xd0;
|
|
struct {
|
|
u8 unknown[4];
|
|
char radio[6];
|
|
char data[6];
|
|
} passwords;
|
|
|
|
#seekto 0x0110;
|
|
struct {
|
|
u8 kA; // Portable > Closed circle
|
|
u8 kDA; // Protable > Triangle to Left
|
|
u8 kGROUP_DOWN; // Protable > Triangle to Right
|
|
u8 kGROUP_UP; // Protable > Side 1
|
|
u8 kSCN; // Portable > Open Circle
|
|
u8 kMON; // Protable > Side 2
|
|
u8 kFOOT;
|
|
u8 kCH_UP;
|
|
u8 kCH_DOWN;
|
|
u8 kVOL_UP;
|
|
u8 kVOL_DOWN;
|
|
u8 unknown30[5];
|
|
// --
|
|
u8 unknown31[4];
|
|
u8 kP_KNOB; // Just portable: channel knob
|
|
u8 unknown32[11];
|
|
} keys;
|
|
|
|
#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 0x200;
|
|
struct {
|
|
char line1[32];
|
|
char line2[32];
|
|
} message;
|
|
|
|
#seekto 0x2000;
|
|
struct {
|
|
u8 bnumb; // mem number
|
|
u8 bank; // to which bank it belongs
|
|
char name[8]; // name 8 chars
|
|
u8 unknown20[2]; // unknown yet
|
|
lbcd rxfreq[4]; // rx freq
|
|
// --
|
|
lbcd txfreq[4]; // tx freq
|
|
u8 rx_unkw; // unknown yet
|
|
u8 tx_unkw; // unknown yet
|
|
ul16 rx_tone; // rx tone
|
|
ul16 tx_tone; // tx tone
|
|
u8 unknown23[5]; // unknown yet
|
|
u8 signaling; // xFF = off, x30 DTMF, x31 2-Tone
|
|
// See the zone on x7000
|
|
// --
|
|
u8 ptt_id:2, // ??? BOT = 0, EOT = 1, Both = 2, NONE = 3
|
|
beat_shift:1, // 1 = off
|
|
unknown26:2 // ???
|
|
power:1, // power: 0 low / 1 high
|
|
compander:1, // 1 = off
|
|
wide:1; // wide 1 / 0 narrow
|
|
u8 unknown27:6, // ???
|
|
busy_lock:1, // 1 = off
|
|
unknown28:1; // ???
|
|
u8 unknown29[14]; // unknown yet
|
|
} memory[128];
|
|
|
|
#seekto 0x5900;
|
|
struct {
|
|
char model[8];
|
|
u8 unknown50[4];
|
|
char type[2];
|
|
u8 unknown51[2];
|
|
// --
|
|
char serial[8];
|
|
u8 unknown52[8];
|
|
} id;
|
|
|
|
#seekto 0x7000;
|
|
struct {
|
|
lbcd dt2_id[5]; // DTMF lbcd ID (000-9999999999)
|
|
// 2-Tone = "11 f1 ff ff ff" ???
|
|
// None = "00 f0 ff ff ff"
|
|
} dtmf;
|
|
"""
|
|
|
|
MEM_SIZE = 0x8000 # 32,768 bytes
|
|
BLOCK_SIZE = 256
|
|
BLOCKS = MEM_SIZE / BLOCK_SIZE
|
|
MEM_BLOCKS = range(0, BLOCKS)
|
|
|
|
# define and empty block of data, as it will be used a lot in this code
|
|
EMPTY_BLOCK = "\xFF" * 256
|
|
|
|
RO_BLOCKS = range(0x10, 0x1F) + range(0x59, 0x5f)
|
|
ACK_CMD = "\x06"
|
|
|
|
POWER_LEVELS = [chirp_common.PowerLevel("Low", watts=1),
|
|
chirp_common.PowerLevel("High", watts=5)]
|
|
|
|
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
|
|
|
|
LIST_TOT = ["off"] + ["%s" % x for x in range(15, 615, 15)]
|
|
LIST_TOT_PRE = ["off"] + ["%s" % x for x in range(1, 11)]
|
|
VOL = ["off"] + ["%s" % x for x in range(1, 32)]
|
|
TVOL = ["%s" % x for x in range(0, 33)]
|
|
TVOL[32] = "Continous"
|
|
|
|
KEYS = {
|
|
0x33: "Display character",
|
|
0x35: "Home Channel", # Posible portable only, chek it
|
|
0x37: "CH down",
|
|
0x38: "CH up",
|
|
0x39: "Key lock",
|
|
0x3a: "Lamp", # Portable only
|
|
0x3b: "Public address",
|
|
0x3c: "Reverse", # Just in updated firmwares (768G)
|
|
0x3d: "Horn alert",
|
|
0x3e: "Selectable QT", # Just in updated firmwares (768G)
|
|
0x3f: "2-tone encode",
|
|
0x40: "Monitor A: open mommentary",
|
|
0x41: "Monitor B: Open Toggle",
|
|
0x42: "Monitor C: Carrier mommentary",
|
|
0x43: "Monitor D: Carrier toogle",
|
|
0x44: "Operator selectable tone",
|
|
0x45: "Redial",
|
|
0x46: "RF Power Low", # portable only ?
|
|
0x47: "Scan",
|
|
0x48: "Scan del/add",
|
|
0x4a: "GROUP down",
|
|
0x4b: "GROUP up",
|
|
#0x4e: "Tone off (Experimental)", # undocumented !!!!
|
|
0x4f: "None",
|
|
0x50: "VOL down",
|
|
0x51: "VOL up",
|
|
0x52: "Talk around",
|
|
0x5d: "AUX",
|
|
0xa1: "Channel Up/Down" # Knob for portables only
|
|
}
|
|
|
|
|
|
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")
|
|
|
|
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")
|
|
|
|
|
|
def _close_radio(radio):
|
|
"""Get the radio out of program mode"""
|
|
# 3 times, it will don't harm in normal work,
|
|
# but it help's a lot in the developer process
|
|
_raw_send(radio, "\x45\x45\x45")
|
|
|
|
|
|
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
|
|
raise Exception(mesg)
|
|
|
|
|
|
def _check_write_ack(r, ack, addr):
|
|
"""Process the ack from the flock write process
|
|
this is half handshake needed in tx data block"""
|
|
# all ok
|
|
if ack == ACK_CMD:
|
|
return
|
|
|
|
# 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)
|
|
# when the RX block has two bytes and the first is \x5A
|
|
# then the block is all \xFF
|
|
if len(rxdata) == 2 and rxdata[0] == "\x5A":
|
|
_handshake(radio, "short block")
|
|
return False
|
|
else:
|
|
rcs = ord(rxdata[-1])
|
|
data = rxdata[1:-1]
|
|
ccs = _checksum(data)
|
|
|
|
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):
|
|
"""Open the radio into program mode and check if it's the correct model"""
|
|
radio.pipe.setTimeout(0.25) # only works in the range 0.2 - 0.3
|
|
radio.pipe.setParity("E")
|
|
|
|
_raw_send(radio, "PROGRAM")
|
|
ack = _raw_recv(radio, 1)
|
|
|
|
if ack != ACK_CMD:
|
|
# bad response, properly close the radio before exception
|
|
_close_radio(radio)
|
|
raise errors.RadioError("The radio doesn't accept program mode")
|
|
|
|
_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)
|
|
# LOG.debug("Incorrect model ID, got %s" % util.hexprint(rid))
|
|
raise errors.RadioError(
|
|
"Incorrect model ID, got %s, it not contains %s" %
|
|
(rid.strip("\xff"), radio.TYPE))
|
|
|
|
# DEBUG
|
|
LOG.debug("Full ident string is: %s" % util.hexprint(rid))
|
|
_handshake(radio)
|
|
|
|
|
|
def do_download(radio):
|
|
""" The download function """
|
|
_open_radio(radio)
|
|
|
|
# speed up the reading
|
|
radio.pipe.setTimeout(0.03) # only works in the range 0.25 and up
|
|
|
|
# UI progress
|
|
status = chirp_common.Status()
|
|
status.cur = 0
|
|
status.max = MEM_SIZE / 256
|
|
status.msg = "Cloning from radio..."
|
|
radio.status_fn(status)
|
|
data = ""
|
|
count = 0
|
|
|
|
for addr in MEM_BLOCKS:
|
|
_send(radio, _make_frame("R", addr))
|
|
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
|
|
status.msg = "Cloning from radio..."
|
|
radio.status_fn(status)
|
|
|
|
count += 1
|
|
|
|
_close_radio(radio)
|
|
return memmap.MemoryMap(data)
|
|
|
|
|
|
def do_upload(radio):
|
|
""" The upload function """
|
|
_open_radio(radio)
|
|
|
|
# Radio need time to write data to eeprom
|
|
# 0.55 seconds as per the original software...
|
|
radio.pipe.setTimeout(0.55)
|
|
|
|
# UI progress
|
|
status = chirp_common.Status()
|
|
status.cur = 0
|
|
status.max = BLOCKS
|
|
status.msg = "Cloning to radio..."
|
|
radio.status_fn(status)
|
|
|
|
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)
|
|
ack = _raw_recv(radio, 1)
|
|
_check_write_ack(radio, ack, addr)
|
|
|
|
# UI Update
|
|
status.cur = count
|
|
status.msg = "Cloning to radio..."
|
|
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]
|
|
if (rid in cls.VARIANTS):
|
|
# correct model
|
|
return True
|
|
else:
|
|
#LOG.debug("Wrong Kenwood radio, ID:")
|
|
#LOG.debug(util.hexprint(rid))
|
|
# DEBUG
|
|
#print("Wrong Kenwood radio, ID:")
|
|
#print util.hexprint(rid)
|
|
return False
|
|
|
|
|
|
class Kenwood_Serie_60G(chirp_common.CloneModeRadio):
|
|
"""Kenwood Serie 60G Radios base class"""
|
|
VENDOR = "Kenwood"
|
|
BAUD_RATE = 9600
|
|
_range = [136000000, 162000000]
|
|
_memsize = MEM_SIZE
|
|
_upper = 128
|
|
_chs_progs = 0
|
|
NAME_LENGTH = 8
|
|
|
|
@classmethod
|
|
def get_prompts(cls):
|
|
rp = chirp_common.RadioPrompts()
|
|
rp.experimental = \
|
|
('This driver in experimental, it is Beta code do not trust it'
|
|
'At the moment there is no support for banks, so all new'
|
|
'channels will be assigned to bank #1')
|
|
rp.pre_download = _(dedent("""\
|
|
This is beta code, keep a backup of your data with the KPG
|
|
software before doing any change to your radio.
|
|
|
|
This beta code is intended to test against TK-760G ONLY !!!
|
|
|
|
The settings are incomplete and read only by now"""))
|
|
rp.pre_upload = _(dedent("""\
|
|
This is beta code, keep a backup of your data with the KPG
|
|
software before doing any change to your radio.
|
|
|
|
This beta code is intended to test against TK-760G ONLY !!!
|
|
|
|
The settings are incomplete and read only by now"""))
|
|
return rp
|
|
|
|
def get_features(self):
|
|
"""Return information about this radio's features"""
|
|
rf = chirp_common.RadioFeatures()
|
|
rf.has_settings = True
|
|
rf.has_bank = False
|
|
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 = 8
|
|
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 _bank_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 flog data"""
|
|
rchs = 0
|
|
data = dict()
|
|
|
|
# sorting the data
|
|
for ch in range(0, self._upper):
|
|
mem = self._memobj.memory[ch]
|
|
if mem.get_raw()[0] != "\xFF":
|
|
bank = int(mem.bank)
|
|
try:
|
|
data[bank].append(ch)
|
|
except:
|
|
data[bank] = []
|
|
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():
|
|
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
|
|
# ############## PLEASE MODIFICATE ###################
|
|
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]
|
|
except KeyError:
|
|
LOG.debug("Wrong Kenwood radio, ID or unknown variant")
|
|
LOG.debug(util.hexprint(rid))
|
|
raise errors.RadioError(
|
|
"Wrong Kenwood radio, ID or unknown variant")
|
|
# DEBUG
|
|
print("Wrong Kenwood radio, ID or unknown variant")
|
|
print util.hexprint(fp)
|
|
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"""
|
|
try:
|
|
self._bank_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)
|
|
# DEBUG
|
|
print("memobj is %s" % type(self._memobj))
|
|
# to ser 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)
|
|
|
|
# bank and number in the channel
|
|
mem.extra = RadioSettingGroup("extra", "Extra")
|
|
bank = RadioSetting("bank", "Bank it belongs",
|
|
RadioSettingValueInteger(1, 128, _mem.bank))
|
|
mem.extra.append(bank)
|
|
bnumb = RadioSetting("bnumb", "Ch number in the bank",
|
|
RadioSettingValueInteger(1, 128, _mem.bnumb))
|
|
mem.extra.append(bnumb)
|
|
|
|
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
|
|
else:
|
|
_mem.txfreq = mem.freq / 10
|
|
# tx_unkw = xff if duplex = off
|
|
if mem.duplex == "off":
|
|
_mem.tx_unkw = "\xff"
|
|
|
|
# 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 == 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
|
|
# DEBUG
|
|
print("Setting new channel %02i" % (mem.number - 1))
|
|
|
|
@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
|
|
mess = self._memobj.message
|
|
keys = self._memobj.keys
|
|
idm = self._memobj.id
|
|
passwd = self._memobj.passwords
|
|
|
|
# basic features of the radio
|
|
basic = RadioSettingGroup("basic", "Basic Settings")
|
|
# dealer settings
|
|
dealer = RadioSettingGroup("dealer", "Dealer Settings")
|
|
# buttons
|
|
fkeys = RadioSettingGroup("keys", "Front keys config")
|
|
|
|
# TODO / PLANED
|
|
# adjust feqs
|
|
#freqs = RadioSettingGroup("freqs", "Adjust Frequencies")
|
|
|
|
top = RadioSettings(basic, dealer, fkeys)
|
|
|
|
# Basic
|
|
tot = RadioSetting("settings.tot", "Time out timer",
|
|
RadioSettingValueList(LIST_TOT, LIST_TOT[
|
|
LIST_TOT.index(str(int(sett.tot)))]))
|
|
basic.append(tot)
|
|
|
|
totalert = RadioSetting("settings.tot_alert", "TOT pre alert",
|
|
RadioSettingValueList(LIST_TOT_PRE,
|
|
LIST_TOT_PRE[int(sett.tot_alert)]))
|
|
basic.append(totalert)
|
|
|
|
minvol = RadioSetting("settings.min_vol", "Default volume",
|
|
RadioSettingValueList(VOL,
|
|
VOL[int(sett.min_vol)]))
|
|
basic.append(minvol)
|
|
|
|
tv = int(sett.tone_vol)
|
|
if tv == 255: tv = 32
|
|
tvol = RadioSetting("settings.tone_vol", "Tone volume",
|
|
RadioSettingValueList(TVOL, TVOL[tv]))
|
|
basic.append(tvol)
|
|
|
|
c2t = RadioSetting("settings.c2t", "Clear to Transpond",
|
|
RadioSettingValueBoolean(not sett.c2t))
|
|
basic.append(c2t)
|
|
|
|
ptone = RadioSetting("settings.poweron_tone", "Power On tone",
|
|
RadioSettingValueBoolean(sett.poweron_tone))
|
|
basic.append(ptone)
|
|
|
|
ctone = RadioSetting("settings.control_tone", "Control (key) tone",
|
|
RadioSettingValueBoolean(sett.control_tone))
|
|
basic.append(ctone)
|
|
|
|
wtone = RadioSetting("settings.warn_tone", "Warning tone",
|
|
RadioSettingValueBoolean(sett.warn_tone))
|
|
basic.append(wtone)
|
|
|
|
# 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))
|
|
basic.append(bsave)
|
|
|
|
ponm = str(sett.poweronmesg).strip("\xff")
|
|
pom = RadioSetting("settings.poweronmesg", "Power on message",
|
|
RadioSettingValueString(0, 8, ponm, False))
|
|
basic.append(pom)
|
|
|
|
|
|
# dealer
|
|
# clean the model / CHs / Type in the same layout as the KPG program
|
|
modelstr = str(idm.model) + " [" + str(self._upper) + "CH]: " + \
|
|
str(idm.type) + ", " + str(self._range[0]/1000000) + "-" + \
|
|
str(self._range[1]/1000000) + " Mhz"
|
|
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(modelstr)):
|
|
if ord(modelstr[i]) in valid_chars:
|
|
mstr += modelstr[i]
|
|
|
|
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", "Software Version", val)
|
|
dealer.append(sver)
|
|
|
|
l1 = str(mess.line1).strip(" \xff")
|
|
line1 = RadioSetting("message.line1", "Comment 1",
|
|
RadioSettingValueString(0, 32, l1))
|
|
dealer.append(line1)
|
|
|
|
l2 = str(mess.line2).strip(" \xff")
|
|
line2 = RadioSetting("message.line2", "Comment 2",
|
|
RadioSettingValueString(0, 32, l2))
|
|
dealer.append(line2)
|
|
|
|
sprog = RadioSetting("settings.self_prog", "Self program",
|
|
RadioSettingValueBoolean(sett.self_prog))
|
|
dealer.append(sprog)
|
|
|
|
clone = RadioSetting("settings.clone", "Allow clone",
|
|
RadioSettingValueBoolean(sett.clone))
|
|
dealer.append(clone)
|
|
|
|
panel = RadioSetting("settings.panel_test", "Panel Test",
|
|
RadioSettingValueBoolean(sett.panel_test))
|
|
dealer.append(panel)
|
|
|
|
fmw = RadioSetting("settings.firmware_prog", "Firmware program",
|
|
RadioSettingValueBoolean(sett.firmware_prog))
|
|
dealer.append(fmw)
|
|
|
|
rpass = RadioSetting("passwords.radio", "Radio Password",
|
|
RadioSettingValueString(0, 6,
|
|
str(passwd.radio).strip("\xff")))
|
|
dealer.append(rpass)
|
|
|
|
dpass = RadioSetting("passwords.data", "Data Password",
|
|
RadioSettingValueString(0, 6,
|
|
str(passwd.data).strip("\xff")))
|
|
dealer.append(dpass)
|
|
|
|
|
|
# front keys
|
|
# The Mobile only parameters are wraped here
|
|
if self.TYPE[0] == "M":
|
|
vu = RadioSetting("keys.kVOL_UP", "Left UP",
|
|
RadioSettingValueList(KEYS.values(),
|
|
KEYS.values()[KEYS.keys().index(
|
|
int(keys.kVOL_UP))]))
|
|
fkeys.append(vu)
|
|
|
|
vd = RadioSetting("keys.kVOL_DOWN", "Left DOWN",
|
|
RadioSettingValueList(KEYS.values(),
|
|
KEYS.values()[KEYS.keys().index(
|
|
int(keys.kVOL_DOWN))]))
|
|
fkeys.append(vd)
|
|
|
|
chu = RadioSetting("keys.kCH_UP", "Right UP",
|
|
RadioSettingValueList(KEYS.values(),
|
|
KEYS.values()[KEYS.keys().index(
|
|
int(keys.kCH_UP))]))
|
|
fkeys.append(chu)
|
|
|
|
chd = RadioSetting("keys.kCH_DOWN", "Right DOWN",
|
|
RadioSettingValueList(KEYS.values(),
|
|
KEYS.values()[KEYS.keys().index(
|
|
int(keys.kCH_DOWN))]))
|
|
fkeys.append(chd)
|
|
|
|
foot = RadioSetting("keys.kFOOT", "Foot switch",
|
|
RadioSettingValueList(KEYS.values(),
|
|
KEYS.values()[KEYS.keys().index(
|
|
int(keys.kCH_DOWN))]))
|
|
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 = "Triangle to Left"
|
|
|
|
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 = "Triangle to Right"
|
|
|
|
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 = "Side 1"
|
|
|
|
gd = RadioSetting("keys.kGROUP_DOWN", gd_name,
|
|
RadioSettingValueList(KEYS.values(),
|
|
KEYS.values()[KEYS.keys().index(
|
|
int(keys.kGROUP_DOWN))]))
|
|
fkeys.append(gd)
|
|
|
|
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)
|
|
|
|
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(".")
|
|
# you must ignore the settings with "not"
|
|
# this are READ ONLY attributes
|
|
if inter == "not":
|
|
continue
|
|
|
|
obj = getattr(mobj, inter)
|
|
value = element.value
|
|
|
|
# integers case + special case
|
|
if setting in ["tot", "tot_alert", "min_vol", "tone_vol"]:
|
|
value = int(value)
|
|
# 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) == True:
|
|
value = 0x32
|
|
else:
|
|
value = 0xff
|
|
|
|
# String cases
|
|
if setting in ["poweronmesg", "line1", "line2"]:
|
|
# poweron is 8 / linesX is 32
|
|
just = 8
|
|
# lines with 32
|
|
if "line" in setting:
|
|
just = 32
|
|
# password with 6
|
|
if "radio" in setting or "data" in setting:
|
|
just = 6
|
|
|
|
# empty case
|
|
if len(str(value)) == 0:
|
|
value = "\xff" * just
|
|
else:
|
|
# password don't like spaces
|
|
if not ("radio" in setting or "data" in setting):
|
|
value = str(value).ljust(just)
|
|
else:
|
|
value = str(value).ljust("\xff")
|
|
|
|
# case keys, with special config
|
|
if inter == "keys":
|
|
value = KEYS.keys()[KEYS.values().index(str(value))]
|
|
|
|
# Apply al configs done
|
|
setattr(obj, setting, value)
|
|
|
|
|
|
# This kenwwood family is known as "60-G Serie"
|
|
# all this radios ending in G are compatible:
|
|
#
|
|
# Portables VHF TK-260/270/272/278
|
|
# Portables UHF TK-360/370/372/378/388
|
|
#
|
|
# Mobiles VHF TK-760/762/768
|
|
# Mobiles VHF TK-860/862/868
|
|
#
|
|
# Just dealing with VHF models at moment,
|
|
# this are the radios I can get in hand
|
|
|
|
# WARNING !!!! Radios With Password in the data section <=###############
|
|
#
|
|
# when a radio has a data password (aka to program it) the last byte (#8)
|
|
# in the id code change from \xf1 to \xb1; so we remove this last byte
|
|
# from the identification procedures and variants.
|
|
#
|
|
# this effectively render the data password USELESS even if set.
|
|
# this can change if user request it with high priority
|
|
|
|
@directory.register
|
|
class TK768G_Radios(Kenwood_Serie_60G):
|
|
"""Kenwood TK-768G Radios [M/C]"""
|
|
MODEL = "TK-768G"
|
|
_memsize = MEM_SIZE
|
|
_hasbanks = False
|
|
TYPE = "M7680"
|
|
# type MAP
|
|
# ======================================================
|
|
# TK-768G[8CH] M2 (136-162) "M7680\x15\xff"
|
|
# TK-768G[8CH] M (148-172) "M7680\x14\xff"
|
|
# TK-768G[128CH] C2 (136-162) "M76805\xff"
|
|
# TK-768G[128CH] C2 (148-172) "M76804\xff"
|
|
# ======================================================
|
|
VARIANTS = {
|
|
"M7680\x15\xff": (8, 136, 162, "M2"),
|
|
"M7680\x14\xff": (8, 148, 174, "M"),
|
|
"M76805\xff": (128, 136, 162, "C2"),
|
|
"M76804\xff": (128, 148, 174, "C"),
|
|
}
|
|
|
|
|
|
@directory.register
|
|
class TK762G_Radios(Kenwood_Serie_60G):
|
|
"""Kenwood TK-762G Radios [K/E/NE]"""
|
|
MODEL = "TK-762G"
|
|
_memsize = MEM_SIZE
|
|
_hasbanks = False
|
|
TYPE = "M7620"
|
|
# type MAP
|
|
# ======================================================
|
|
# TK-762G[8CH] K2 (136-162) "M7620\x05\xff"
|
|
# TK-762G[8CH] K (148-174) "M7620\x04\xff"
|
|
# TK-762G[8CH] E (148-174) "M7620$\xff"
|
|
# TK-762G[8CH] NE (148-174) "M7620T\xff"
|
|
# ======================================================
|
|
VARIANTS = {
|
|
"M7620\x05\xff": (8, 136, 162, "K2"),
|
|
"M7620\x04\xff": (8, 148, 172, "K"),
|
|
"M7620$\xff": (8, 148, 172, "E"),
|
|
"M7620T\xff": (8, 148, 172, "NE"),
|
|
}
|
|
|
|
|
|
@directory.register
|
|
class TK760G_Radios(Kenwood_Serie_60G):
|
|
"""Kenwood TK-760G Radios [K/M/(N)E]"""
|
|
MODEL = "TK-760G"
|
|
_memsize = MEM_SIZE
|
|
_hasbanks = True
|
|
TYPE = "M7600"
|
|
# type MAP
|
|
# ======================================================
|
|
# TK-760G[128CH] K2 (136-162) "M7600\x05\xff"
|
|
# TK-760G[128CH] K (148-174) "M7600\x04\xff"
|
|
# TK-760G[128CH] M (146-174) "M7600\x14\xff"
|
|
# TK-760G[128CH] N/E (146-174) "M7600T\xff"
|
|
# ======================================================
|
|
VARIANTS = {
|
|
"M7600\x05\xff": (128, 136, 162, "K2"),
|
|
"M7600\x04\xff": (128, 148, 174, "K"),
|
|
"M7600\x14\xff": (128, 146, 174, "M"),
|
|
"M7600T\xff": (128, 146, 174, "NE")
|
|
}
|
|
|
|
|
|
@directory.register
|
|
class TK278G_Radios(Kenwood_Serie_60G):
|
|
"""Kenwood TK-278G Radio C/C1/M/M1"""
|
|
MODEL = "TK-278G"
|
|
_memsize = MEM_SIZE
|
|
_hasbanks = True # but 16 CH has no banks
|
|
TYPE = "P2780"
|
|
# type MAP
|
|
# ======================================================
|
|
# TK-278G[128CH] C1 (136-150) "P27805\xff"
|
|
# TK-278G[128CH] C (150-174) "P27804\xff"
|
|
# TK-278G [16CH] M1 (136-150) "P2780\x15\xff"
|
|
# TK-278G [16CH] M (150-174) "P2780\x14\xff"
|
|
# ======================================================
|
|
VARIANTS = {
|
|
"P27805\xff": (128, 136, 150, "C1"),
|
|
"P27804\xff": (128, 150, 174, "C"),
|
|
"P2780\x15\xff": (16, 136, 150, "M1"),
|
|
"P2780\x14\xff": (16, 150, 174, "M")
|
|
}
|
|
|
|
|
|
@directory.register
|
|
class TK272G_Radios(Kenwood_Serie_60G):
|
|
"""Kenwood TK-272G Radio K/K1"""
|
|
MODEL = "TK-272G"
|
|
_memsize = MEM_SIZE
|
|
TYPE = "P2720"
|
|
_hasbanks = True
|
|
# type MAP
|
|
# ======================================================
|
|
# TK-272G [32CH] K1 (136-150) "P2720\x05\xfb"
|
|
# TK-272G [32CH] K (150-174) "P2720\x04\xfb"
|
|
# ======================================================
|
|
VARIANTS = {
|
|
"P2720\x05\xfb": (32, 136, 150, "K1"),
|
|
"P2720\x04\xfb": (32, 150, 174, "K")
|
|
}
|
|
|
|
|
|
@directory.register
|
|
class TK270G_Radios(Kenwood_Serie_60G):
|
|
"""Kenwood TK-270G Radio K/K1/M/E/NE/NT"""
|
|
MODEL = "TK-270G"
|
|
_memsize = MEM_SIZE
|
|
_hasbanks = True
|
|
TYPE = "P2700"
|
|
# type MAP
|
|
# ======================================================
|
|
# TK-270G[128CH] NE/NT (146-174) "P2700T\xff"
|
|
# TK-270G[128CH] E (146-174) "P2700$\xff"
|
|
# TK-270G[128CH] M (150-174) "P2700\x14\xff"
|
|
# TK-270G[128CH] K1 (136-150) "P2700\x05\xff"
|
|
# TK-270G[128CH] K (150-174) "P2700\x04\xff"
|
|
# ======================================================
|
|
VARIANTS = {
|
|
"P2700T\xff": (128, 146, 174, "NE/NT"),
|
|
"P2700$\xff": (128, 146, 174, "E"),
|
|
"P2700\x14\xff": (128, 150, 174, "M"),
|
|
"P2700\x05\xff": (128, 136, 150, "K1"),
|
|
"P2700\x04\xff": (128, 150, 174, "K"),
|
|
}
|
|
|
|
|
|
@directory.register
|
|
class TK260G_Radios(Kenwood_Serie_60G):
|
|
"""Kenwood TK-260G Radio K/K1/M/E/NE/NT"""
|
|
MODEL = "TK-260G"
|
|
_memsize = MEM_SIZE
|
|
_hasbanks = False
|
|
TYPE = "P2600"
|
|
# type MAP
|
|
# ======================================================
|
|
# TK-260G [8CH] NE/NT1 (136-150) "P2600U\xff"
|
|
# TK-260G [8CH] NE/NT (146-174) "P2600T\xff"
|
|
# TK-260G [8CH] E (146-174) "P2600$\xff"
|
|
# TK-260G [8CH] M (150-174) "P2600\x14\xff"
|
|
# TK-260G [8CH] K1 (136-150) "P2600\x05\xff"
|
|
# TK-260G [8CH] K (150-174) "P2600\x04\xff"
|
|
# ======================================================
|
|
VARIANTS = {
|
|
"P2600U\xff": (8, 136, 150, "N1"),
|
|
"P2600T\xff": (8, 146, 174, "N"),
|
|
"P2600$\xff": (8, 146, 174, "E"),
|
|
"P2600\x14\xff": (8, 150, 174, "M"),
|
|
"P2600\x05\xff": (8, 136, 150, "K1"),
|
|
"P2600\x04\xff": (8, 150, 174, "K")
|
|
}
|
|
|