Project

General

Profile

New Model #2999 » tk760g.py

Release candidate driver, Basic Settings, full bank support but will fail the tests in chirp due to different paradigm. - Pavel Milanes, 12/27/2015 05:43 PM

 
# 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 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; // Only for portable: FF = off, x32 = on
// --
u8 unknown4[10]; // x20
u8 unknown5:3, // x2d
c2t:1, // 1 bit clear to transpond: 1-off
// This is relative to DTMF / 2-Tone settings
unknown6:4;
u8 unknown7[5]; // x2b-x2f
// --
u8 unknown8[16]; // x30 ?
u8 unknown9[16]; // x40 ?
u8 unknown10[16]; // x50 ?
u8 unknown11[16]; // x60 ?
// --
u8 add[16]; // x70-x7f 128 bits corresponding add/skip values
// --
u8 unknown12: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
unknown13:2;
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
unknown16:1,
panel_test:1; // 1 bit panel test enabled
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; // minimum volume posible: range(0,32); 0 = off
u8 tone_vol; // minimum tone volume posible:
// xff = continous, range(0, 31)
u8 unknown20[4]; // x8c-x8f
// --
u8 unknown21[4]; // x90-x93
char poweronmesg[8]; // x94-x9b power on mesg 8 bytes, off is "\FF" * 8
u8 unknown22[4]; // x9c-x9f
// --
u8 unknown23[7]; // xa0-xa6
char ident[8]; // xa7-xae radio identification string
u8 unknown24; // xaf
// --
u8 unknown26[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 0x6000;
struct {
u8 code[8];
u8 unknown60[7];
u8 count;
} bot[128];

#seekto 0x6800;
struct {
u8 code[8];
u8 unknown61[7];
u8 count;
} eot[128];

#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

TOT = ["off"] + ["%s" % x for x in range(15, 615, 15)]
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)]

## BOT = 0, EOT = 1, Both = 2, NONE = 3
#PTTID = ["BOT", "EOT", "Both", "none"]

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:
return False


class Kenwood60GBankModel(chirp_common.BankModel):
"""Testing the bank model on kennwood"""

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):
# in thes case we pass it, because all channels must be assigned to
# at least one channel
pass

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_60G(chirp_common.CloneModeRadio):
"""Kenwood Serie 60G Radios base class"""
VENDOR = "Kenwood"
BAUD_RATE = 9600
_memsize = MEM_SIZE
NAME_LENGTH = 8
_range = [136000000, 162000000]
_upper = 128
_chs_progs = 0
_num_banks = 128
_bclass = memBank

@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, in fact it is disabled if detected'
'=> 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.'
)
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 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
"""))
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 = 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 _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 flog data"""
rchs = 0
data = dict()

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

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

# building the data for the memmap
fdata = ""

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

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

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

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

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

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

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

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

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

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

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

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

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, see LOG output.")
return False

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

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

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

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

def process_mmap(self):
"""Process the memory object"""
# how many channels are programed
self._chs_progs = ord(self._mmap[15])
# load the memobj
self._memobj = bitwise.parse(MEM_FORMAT, self._mmap)
# to 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)

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

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

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

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

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

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

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

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

return mem

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

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

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

# frequency
_mem.rxfreq = mem.freq / 10

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

# duplex
if mem.duplex == "+":
_mem.txfreq = (mem.freq + mem.offset) / 10
elif mem.duplex == "-":
_mem.txfreq = (mem.freq - mem.offset) / 10
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
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 (TOT)",
RadioSettingValueList(TOT, TOT[
TOT.index(str(int(sett.tot)))]))
basic.append(tot)

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

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

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

# 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)]))
basic.append(minvol)

tv = int(sett.tone_vol)
if tv == 255:
tv = 32
tvol = RadioSetting("settings.tone_vol", "Minimum tone volume",
RadioSettingValueList(TVOL, TVOL[tv]))
basic.append(tvol)

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

#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)

#pwd = str(passwd.radio).strip("\xff").ljust(6, "0")
#pwr = RadioSetting("passwords.radio",
#"Radio Password (000000 disabled)",
#RadioSettingValueInteger(0, 9, pwd[0]),
#RadioSettingValueInteger(0, 9, pwd[1]),
#RadioSettingValueInteger(0, 9, pwd[2]),
#RadioSettingValueInteger(0, 9, pwd[3]),
#RadioSettingValueInteger(0, 9, pwd[4]),
#RadioSettingValueInteger(0, 9, pwd[5]))
#dealer.append(pwr)

#pwd = str(passwd.data).strip("\xff").ljust(6, "0")
#pwd = RadioSetting("passwords.data",
#"Data Password (000000 disabled)",
#RadioSettingValueInteger(0, 9, pwd[0]),
#RadioSettingValueInteger(0, 9, pwd[1]),
#RadioSettingValueInteger(0, 9, pwd[2]),
#RadioSettingValueInteger(0, 9, pwd[3]),
#RadioSettingValueInteger(0, 9, pwd[4]),
#RadioSettingValueInteger(0, 9, pwd[5]))
#dealer.append(pwd)

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

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

chu = RadioSetting("keys.kCH_UP", "CH UP",
RadioSettingValueList(KEYS.values(),
KEYS.values()[KEYS.keys().index(
int(keys.kCH_UP))]))
fkeys.append(chu)

chd = RadioSetting("keys.kCH_DOWN", "CH 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 = "< key"

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

gu_name = "Triangle up"
if self.TYPE[0] == "P":
gu_name = "> key"

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",
"sql_level", "tot_rekey", "tot_reset"]:
# catching the "off" values as zero
try:
value = int(value)
except:
value = 0

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

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

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

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

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

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

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

## password with special case
#if setting == "radio" or setting == "data":
#pass

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

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

def get_bank_model(self):
"""Pass the bank model to the UI part"""
rf = self.get_features()
if rf.has_bank is True:
return Kenwood60GBankModel(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)


# 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"
TYPE = "M7680"
# Note that 8 CH don't have banks
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"
TYPE = "M7620"
# Note that 8 CH don't have banks
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"
TYPE = "M7600"
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"
TYPE = "P2780"
# Note that 16 CH don't have banks
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"
TYPE = "P2720"
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"
TYPE = "P2700"
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"
_hasbanks = False
TYPE = "P2600"
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")
}
(3-3/3)