# HG changeset patch # User Tom Hayward tom@tomh.us # Date 1333491695 21600 # Node ID 1bb3df3d624fa4ee7dfc959b1dc6bd42a5e019f0 # Parent 91be43cc7ac4063d921b0d8eb0bdd73fdb9aa9fa Add import support for Kenwood *.hmk files.
diff -r 91be43cc7ac4 -r 1bb3df3d624f chirp/directory.py --- a/chirp/directory.py Tue Apr 03 11:19:10 2012 -0700 +++ b/chirp/directory.py Tue Apr 03 16:21:35 2012 -0600 @@ -91,6 +91,9 @@ if image_file.lower().endswith(".csv"): return get_radio("Generic_CSV")(image_file)
+ if image_file.lower().endswith(".hmk"): + return get_radio("Kenwood_HMK")(image_file) + if icf.is_9x_icf(image_file): return get_radio("Icom_IC91_92AD_ICF")(image_file)
diff -r 91be43cc7ac4 -r 1bb3df3d624f chirp/kenwood_hmk.py --- /dev/null Thu Jan 01 00:00:00 1970 +0000 +++ b/chirp/kenwood_hmk.py Tue Apr 03 16:21:35 2012 -0600 @@ -0,0 +1,227 @@ +# Copyright 2008 Dan Smith dsmith@danplanet.com +# Copyright 2012 Tom Haywward tom@tomh.us +# +# 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 3 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 os +import csv + +from chirp import chirp_common, errors, directory + +class OmittedHeaderError(Exception): + pass + +@directory.register +class HMKRadio(chirp_common.CloneModeRadio): + VENDOR = "Kenwood" + MODEL = "HMK" + FILE_EXTENSION = "hmk" + + ATTR_MAP = { + "!!Ch" : (int, "number"), + "M.Name" : (str, "name"), + "Rx Freq." : (chirp_common.parse_freq, "freq"), + "Shift/Split" : (str, "duplex"), + "Offset" : (chirp_common.parse_freq, "offset"), + "T/CT/DCS" : (str, "tmode"), + "TO Freq." : (float, "rtone"), + "CT Freq." : (float, "ctone"), + "DCS Code" : (int, "dtcs"), + "Mode" : (str, "mode"), + "Tx Freq." : (chirp_common.parse_freq, "txfreq"), + "Rx Step" : (float, "tuning_step"), + "L.Out" : (str, "skip"), + } + + TMODE_MAP = { + "Off": "", + "T": "Tone", + "CT": "TSQL", + "DCS": "DTCS", + "": "Cross", + } + + SKIP_MAP = { + "Off": "", + "On": "S", + } + + DUPLEX_MAP = { + " ": "", + "S": "split", + "+": "+", + "-": "-", + } + + def _blank(self): + self.errors = [] + self.memories = [] + for i in range(0, 1000): + m = chirp_common.Memory() + m.number = i + m.empty = True + self.memories.append(m) + + def __init__(self, pipe): + chirp_common.CloneModeRadio.__init__(self, None) + + self._filename = pipe + if self._filename and os.path.exists(self._filename): + self.load() + else: + self._blank() + + def get_features(self): + rf = chirp_common.RadioFeatures() + rf.has_bank = False + rf.has_dtcs_polarity = False + rf.memory_bounds = (0, len(self.memories)) + rf.has_infinite_number = True + + rf.valid_modes = list(chirp_common.MODES) + rf.valid_tmodes = list(chirp_common.TONE_MODES) + rf.valid_duplexes = ["", "-", "+", "split"] + rf.valid_tuning_steps = list(chirp_common.TUNING_STEPS) + rf.valid_bands = [(1, 10000000000)] + rf.valid_skips = ["", "S"] + rf.valid_characters = chirp_common.CHARSET_ASCII + rf.valid_name_length = 999 + + return rf + + def _parse_quoted_line(self, line): + line = line.replace("\n", "") + line = line.replace("\r", "") + line = line.replace('"', "") + + return line.split(",") + + def _get_datum_by_header(self, headers, data, header): + if header not in headers: + raise OmittedHeaderError("Header %s not provided" % header) + + try: + return data[headers.index(header)] + except IndexError: + raise OmittedHeaderError("Header %s not provided on this line" %\ + header) + + def _parse_csv_data_line(self, headers, line): + mem = chirp_common.Memory() + odd_split = False + + for header, (typ, attr) in self.ATTR_MAP.items(): + try: + val = self._get_datum_by_header(headers, line, header) + if not val and typ == int: + val = None + elif attr == "duplex": + val = typ(self.DUPLEX_MAP[val]) + if val == "split": + odd_split = True + elif attr == "skip": + val = typ(self.SKIP_MAP[val]) + elif attr == "tmode": + val = typ(self.TMODE_MAP[val]) + elif attr == 'txfreq': + tx_freq = typ(val) + else: + val = typ(val) + if hasattr(mem, attr): + setattr(mem, attr, val) + except OmittedHeaderError, e: + pass + except Exception, e: + raise Exception("[%s] %s" % (attr, e)) + + if odd_split: + mem.offset = tx_freq + + return mem + + def load(self, filename=None): + if filename is None and self._filename is None: + raise errors.RadioError("Need a location to load from") + + if filename: + self._filename = filename + + self._blank() + + f = file(self._filename, "r") + for i in range(0, 10): + f.readline().strip() + + #f.seek(0, 0) + reader = csv.reader(f, delimiter=chirp_common.SEPCHAR, quotechar='"') + + good = 0 + lineno = 0 + for line in reader: + lineno += 1 + if lineno == 1: + header = line + continue + + if len(header) > len(line): + print "Line %i has %i columns, expected %i" % (lineno, + len(line), + len(header)) + self.errors.append("Column number mismatch on line %i" % lineno) + continue + + try: + mem = self._parse_csv_data_line(header, line) + if mem.number is None: + raise Exception("Invalid Location field" % lineno) + except Exception, e: + print "Line %i: %s" % (lineno, e) + self.errors.append("Line %i: %s" % (lineno, e)) + continue + + self.__grow(mem.number) + self.memories[mem.number] = mem + good += 1 + + if not good: + print self.errors + raise errors.InvalidDataError("No channels found") + + def load_mmap(self, filename): + return self.load(filename) + + def get_memories(self, lo=0, hi=999): + return [x for x in self.memories if x.number >= lo and x.number <= hi] + + def get_memory(self, number): + try: + return self.memories[number] + except: + raise errors.InvalidMemoryLocation("No such memory %s" % number) + + def __grow(self, target): + delta = target - len(self.memories) + if delta < 0: + return + + delta += 1 + + for i in range(len(self.memories), len(self.memories) + delta + 1): + m = chirp_common.Memory() + m.empty = True + m.number = i + self.memories.append(m) + + def get_raw_memory(self, number): + return ",".join(self.memories[number].to_csv()) diff -r 91be43cc7ac4 -r 1bb3df3d624f chirpui/mainapp.py --- a/chirpui/mainapp.py Tue Apr 03 11:19:10 2012 -0700 +++ b/chirpui/mainapp.py Tue Apr 03 16:21:35 2012 -0600 @@ -667,6 +667,7 @@ (_("CSV Files") + " (*.csv)", "*.csv"), (_("EVE Files (VX5)") + " (*.eve)", "*.eve"), (_("ICF Files") + " (*.icf)", "*.icf"), + (_("Kenwood HMK Files") + " (*.hmk)", "*.hmk"), (_("VX5 Commander Files") + " (*.vx5)", "*.vx5"), (_("VX7 Commander Files") + " (*.vx7)", "*.vx7")] filen = platform.get_platform().gui_open_file(types=types)