[hamradio-commits] [chirp] 01/04: Imported Upstream version 20160110
Iain R. Learmonth
irl at moszumanska.debian.org
Fri Jan 15 16:58:28 UTC 2016
This is an automated email from the git hooks/post-receive script.
irl pushed a commit to branch master
in repository chirp.
commit f7a4d19cb59d14b4895f25a3ced3c3b257593410
Author: Iain R. Learmonth <irl at debian.org>
Date: Wed Jan 13 12:57:58 2016 +0000
Imported Upstream version 20160110
---
PKG-INFO | 2 +-
chirp/__init__.py | 2 +-
chirp/drivers/icomciv.py | 80 +-
chirp/drivers/icw32.py | 43 +
chirp/drivers/tk760.py | 851 +++++++++++
chirp/drivers/tk760g.py | 1557 ++++++++++++++++++++
chirp/drivers/ts2000.py | 8 +-
stock_configs/DE Freenet Frequencies.csv | 7 +
stock_configs/EU LPD and PMR Channels.csv | 156 +-
stock_configs/FR Marine VHF Channels.csv | 114 +-
.../UK Business Radio Simple Light Frequencies.csv | 16 +
stock_configs/US 60 meter channels (Center).csv | 2 +-
stock_configs/US 60 meter channels (Dial).csv | 2 +-
stock_configs/US Calling Frequencies.csv | 2 +-
stock_configs/US FRS and GMRS Channels.csv | 46 +-
stock_configs/US MURS Channels.csv | 10 +-
stock_configs/US Marine VHF Channels.csv | 120 +-
17 files changed, 2764 insertions(+), 254 deletions(-)
diff --git a/PKG-INFO b/PKG-INFO
index 1a8ca68..599b43a 100644
--- a/PKG-INFO
+++ b/PKG-INFO
@@ -1,6 +1,6 @@
Metadata-Version: 1.0
Name: chirp
-Version: daily-20151225
+Version: daily-20160110
Summary: UNKNOWN
Home-page: UNKNOWN
Author: UNKNOWN
diff --git a/chirp/__init__.py b/chirp/__init__.py
index 1fd94d9..9121671 100644
--- a/chirp/__init__.py
+++ b/chirp/__init__.py
@@ -17,7 +17,7 @@ import os
import sys
from glob import glob
-CHIRP_VERSION="daily-20151225"
+CHIRP_VERSION="daily-20160110"
module_dir = os.path.dirname(sys.modules["chirp"].__file__)
__all__ = []
diff --git a/chirp/drivers/icomciv.py b/chirp/drivers/icomciv.py
index bcb7c40..60e8b19 100644
--- a/chirp/drivers/icomciv.py
+++ b/chirp/drivers/icomciv.py
@@ -14,25 +14,31 @@ lbcd freq[5];
u8 unknown2:5,
mode:3;
"""
+# http://www.vk4adc.com/
+# web/index.php/reference-information/49-general-ref-info/182-civ7400
MEM_IC7000_FORMAT = """
u8 bank;
bbcd number[2];
-u8 skip;
+u8 spl:4,
+ skip:4;
lbcd freq[5];
-u8 unknown2:5,
- mode:3;
-u8 unknown1;
-u8 unknown2:2,
- duplex:2,
- unknown3:1,
- tmode:3;
-u8 unknown4;
-bbcd rtone[2];
-u8 unknown5;
-bbcd ctone[2];
+u8 mode;
+u8 filter;
+u8 duplex:4,
+ tmode:4;
+bbcd rtone[3];
+bbcd ctone[3];
u8 dtcs_polarity;
bbcd dtcs[2];
-u8 unknown[17];
+lbcd freq_tx[5];
+u8 mode_tx;
+u8 filter_tx;
+u8 duplex_tx:4,
+ tmode_tx:4;
+bbcd rtone_tx[3];
+bbcd ctone_tx[3];
+u8 dtcs_polarity_tx;
+bbcd dtcs_tx[2];
char name[9];
"""
mem_duptone_format = """
@@ -56,6 +62,8 @@ u8 unknown[11];
char name[9];
"""
+SPLIT = ["", "spl"]
+
class Frame:
"""Base class for an ICF frame"""
@@ -108,7 +116,7 @@ class Frame:
raise errors.RadioError("Radio reported error")
src, dst = struct.unpack("BB", data[2:4])
- LOG.debug("%02x <- %02x:\n%s" % (src, dst, util.hexprint(data)))
+ LOG.debug("%02x <- %02x:\n%s" % (dst, src, util.hexprint(data)))
self._cmd = ord(data[4])
self._sub = ord(data[5])
@@ -248,10 +256,19 @@ class IcomCIVRadio(icf.IcomLiveRadio):
def get_raw_memory(self, number):
f = self._classes["mem"]()
- f.set_location(number)
+ if self._rf.has_bank:
+ ch, bnk = self.mem_to_ch_bnk(number)
+ f.set_location(ch, bnk)
+ loc = "bank %i, channel %02i" % (bnk, ch)
+ else:
+ f.set_location(number)
+ loc = "number %i" % number
self._send_frame(f)
f.read(self.pipe)
- return repr(f.get_obj())
+ if f.get_data() and f.get_data()[-1] == "\xFF":
+ return "Memory " + loc + " empty."
+ else:
+ return repr(f.get_obj())
# We have a simple mapping between the memory location in the frequency
# editor and (bank, channel) of the radio. The mapping doesn't
@@ -277,12 +294,14 @@ class IcomCIVRadio(icf.IcomLiveRadio):
mem = chirp_common.Memory()
mem.number = number
+ mem.immutable = []
f = self._recv_frame(f)
if len(f.get_data()) == 0:
raise errors.RadioError("Radio reported error")
if f.get_data() and f.get_data()[-1] == "\xFF":
mem.empty = True
+ LOG.debug("Found %i empty" % mem.number)
return mem
memobj = f.get_obj()
@@ -323,10 +342,17 @@ class IcomCIVRadio(icf.IcomLiveRadio):
if self._rf.valid_duplexes:
mem.duplex = self._rf.valid_duplexes[memobj.duplex]
+ if self._rf.can_odd_split and memobj.spl:
+ mem.duplex = "split"
+ mem.offset = int(memobj.freq_tx)
+ mem.immutable = []
+ else:
+ mem.immutable = ["offset"]
+
return mem
def set_memory(self, mem):
- LOG.debug("Setting %i" % mem.number)
+ LOG.debug("Setting %i(%s)" % (mem.number, mem.extd_number))
if self._rf.has_bank:
ch, bnk = self.mem_to_ch_bnk(mem.number)
LOG.debug("Bank %i, Channel %02i" % (bnk, ch))
@@ -369,9 +395,6 @@ class IcomCIVRadio(icf.IcomLiveRadio):
if self._rf.valid_tmodes:
memobj.tmode = self._rf.valid_tmodes.index(mem.tmode)
- if self._rf.valid_duplexes:
- memobj.duplex = self._rf.valid_duplexes.index(mem.duplex)
-
if self._rf.has_ctone:
memobj.ctone = int(mem.ctone * 10)
memobj.rtone = int(mem.rtone * 10)
@@ -389,6 +412,18 @@ class IcomCIVRadio(icf.IcomLiveRadio):
if self._rf.has_dtcs:
bitwise.int_to_bcd(memobj.dtcs, mem.dtcs)
+ if self._rf.can_odd_split and mem.duplex == "split":
+ memobj.spl = 1
+ memobj.duplex = 0
+ memobj.freq_tx = int(mem.offset)
+ memobj.tmode_tx = memobj.tmode
+ memobj.ctone_tx = memobj.ctone
+ memobj.rtone_tx = memobj.rtone
+ memobj.dtcs_polarity_tx = memobj.dtcs_polarity
+ memobj.dtcs_tx = memobj.dtcs
+ elif self._rf.valid_duplexes:
+ memobj.duplex = self._rf.valid_duplexes.index(mem.duplex)
+
LOG.debug(repr(memobj))
self._send_frame(f)
@@ -438,18 +473,19 @@ class Icom7000Radio(IcomCIVRadio):
self._rf.has_dtcs_polarity = True
self._rf.has_dtcs = True
self._rf.has_ctone = True
- self._rf.has_offset = False
+ self._rf.has_offset = True
self._rf.has_name = True
self._rf.has_tuning_step = False
self._rf.valid_modes = ["LSB", "USB", "AM", "CW", "RTTY", "FM", "WFM"]
self._rf.valid_tmodes = ["", "Tone", "TSQL", "DTCS"]
- self._rf.valid_duplexes = ["", "-", "+"]
+ self._rf.valid_duplexes = ["", "-", "+", "split"]
self._rf.valid_bands = [(30000, 199999999), (400000000, 470000000)]
self._rf.valid_tuning_steps = []
self._rf.valid_skips = ["S", ""]
self._rf.valid_name_length = 9
self._rf.valid_characters = chirp_common.CHARSET_ASCII
self._rf.memory_bounds = (0, 99 * self._num_banks - 1)
+ self._rf.can_odd_split = True
@directory.register
diff --git a/chirp/drivers/icw32.py b/chirp/drivers/icw32.py
index dc08ea0..fd11bd7 100644
--- a/chirp/drivers/icw32.py
+++ b/chirp/drivers/icw32.py
@@ -206,3 +206,46 @@ class ICW32ARadioUHF(ICW32ARadio):
VARIANT = "UHF"
_limits = (400000000, 470000000)
_mem_positions = (0x06E0, 0x0E2E)
+
+
+# IC-W32E are the very same as IC-W32A but have a different _model
+ at directory.register
+class ICW32ERadio(ICW32ARadio):
+ """Icom IC-W32E"""
+ MODEL = "IC-W32E"
+
+ _model = "\x18\x82\x00\x02"
+
+ # an extra byte is added to distinguish file images from IC-W32A
+ # it will be allocated and initialized to 0x00 in _clone_from_radio
+ # (icf.py) but radio will not send it
+ # That byte is not sent to radio because the _clone_to_radio use _ranges
+ # for the send cycle
+ _memsize = ICW32ARadio._memsize + 1
+
+ def get_sub_devices(self):
+ # this is needed because sub devices must be of a child class
+ return [ICW32ERadioVHF(self._mmap), ICW32ERadioUHF(self._mmap)]
+
+ @classmethod
+ def match_model(cls, filedata, filename):
+ if not len(filedata) == cls._memsize:
+ return False
+ return filedata[-16 - 1: -1] == "IcomCloneFormat3" and \
+ filedata[-1] == chr(0x00)
+
+
+# this is the very same as ICW32ARadioVHF but have ICW32ERadio as parent class
+class ICW32ERadioVHF(ICW32ERadio):
+ """ICW32 VHF subdevice"""
+ VARIANT = "VHF"
+ _limits = (118000000, 174000000)
+ _mem_positions = (0x0000, 0x0DC0)
+
+
+# this is the very same as ICW32ARadioUHF but have ICW32ERadio as parent class
+class ICW32ERadioUHF(ICW32ERadio):
+ """ICW32 UHF subdevice"""
+ VARIANT = "UHF"
+ _limits = (400000000, 470000000)
+ _mem_positions = (0x06E0, 0x0E2E)
diff --git a/chirp/drivers/tk760.py b/chirp/drivers/tk760.py
new file mode 100644
index 0000000..5fb46a4
--- /dev/null
+++ b/chirp/drivers/tk760.py
@@ -0,0 +1,851 @@
+# Copyright 2016 Dan Smith <dsmith at 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 CO7WT, co7wt at frcuba.co.cu, pavelmc at gmail.com
+
+import time
+import struct
+import logging
+
+LOG = logging.getLogger(__name__)
+
+from chirp import chirp_common, directory, memmap
+from chirp import bitwise, errors, util
+from chirp.settings import RadioSettingGroup, RadioSetting, \
+ RadioSettingValueBoolean, RadioSettingValueList, \
+ RadioSettingValueString, RadioSettingValueInteger, \
+ RadioSettings
+from textwrap import dedent
+
+MEM_FORMAT = """
+#seekto 0x0000;
+struct {
+ lbcd rxfreq[4];
+ lbcd txfreq[4];
+} memory[32];
+
+#seekto 0x0100;
+struct {
+ lbcd rx_tone[2];
+ lbcd tx_tone[2];
+} tone[32];
+
+#seekto 0x0180;
+struct {
+ u8 unknown0:1,
+ unknown1:1,
+ wide:1, // wide: 1 = wide, 0 = narrow
+ power:1, // power: 1 = high, 0 = low
+ busy_lock:1, // busy lock: 1 = off, 0 = on
+ pttid:1, // ptt id: 1 = off, 0 = on
+ dtmf:1, // dtmf signaling: 1 = off, 0 = on
+ twotone:1; // 2-tone signaling: 1 = off, 0 = on
+} ch_settings[32];
+
+#seekto 0x02B0;
+struct {
+ u8 unknown10[16]; // x02b0
+ u8 unknown11[16]; // x02c0
+ u8 active[4]; // x02d0
+ u8 scan[4]; // x02d4
+ u8 unknown12[8]; // x02d8
+ u8 unknown13; // x02e0
+ u8 kMON; // 0x02d1 MON Key
+ u8 kA; // 0x02d2 A Key
+ u8 kSCN; // 0x02d3 SCN Key
+ u8 kDA; // 0x02d4 D/A Key
+ u8 unknown14; // x02e5
+ u8 min_vol; // x02e6 byte 0-31 0 = off
+ u8 poweron_tone; // x02e7 power on tone 0 = off, 1 = on
+ u8 tot; // x02e8 Time out Timer 0 = off, 1 = 30s (max 300)
+ u8 unknown15[3]; // x02e9-x02eb
+ u8 dealer_tuning; // x02ec ? bit 0? 0 = off, 1 = on
+ u8 clone; // x02ed ? bit 0? 0 = off, 1 = on
+ u8 unknown16[2]; // x02ee-x2ef
+ u8 unknown17[16]; // x02f0
+ u8 unknown18[5]; // x0300
+ u8 clear2transpond; // x0305 byte 0 = off, 1 = on
+ u8 off_hook_decode; // x0306 byte 0 = off, 1 = on
+ u8 off_hook_hornalert; // x0307 byte 0 = off, 1 = on
+ u8 unknown19[8]; // x0308-x030f
+ u8 unknown20[16]; // x0310
+} settings;
+"""
+
+KEYS = {
+ 0x00: "Disabled",
+ 0x01: "Monitor",
+ 0x02: "Talk Around",
+ 0x03: "Horn Alert",
+ 0x04: "Public Adress",
+ 0x05: "Auxiliary",
+ 0x06: "Scan",
+ 0x07: "Scan Del/Add",
+ 0x08: "Home Channel",
+ 0x09: "Operator Selectable Tone"
+}
+
+MEM_SIZE = 0x400
+BLOCK_SIZE = 8
+MEM_BLOCKS = range(0, (MEM_SIZE / BLOCK_SIZE))
+ACK_CMD = "\x06"
+TIMEOUT = 0.05 # from 0.03 up it' s safe, we set in 0.05 for a margin
+
+POWER_LEVELS = [chirp_common.PowerLevel("Low", watts=1),
+ chirp_common.PowerLevel("High", watts=5)]
+MODES = ["NFM", "FM"]
+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(30, 330, 30)]
+VOL = ["off"] + ["%s" % x for x in range(1, 32)]
+
+
+def rawrecv(radio, amount):
+ """Raw read from the radio device"""
+ data = ""
+ try:
+ data = radio.pipe.read(amount)
+ #print("<= %02i: %s" % (len(data), util.hexprint(data)))
+ except:
+ raise errors.RadioError("Error reading data from radio")
+
+ return data
+
+
+def rawsend(radio, data):
+ """Raw send to the radio device"""
+ try:
+ radio.pipe.write(data)
+ #print("=> %02i: %s" % (len(data), util.hexprint(data)))
+ except:
+ raise errors.RadioError("Error sending data from radio")
+
+
+def send(radio, frame):
+ """Generic send data to the radio"""
+ rawsend(radio, frame)
+
+
+def make_frame(cmd, addr, data=""):
+ """Pack the info in the format it likes"""
+ ts = struct.pack(">BHB", ord(cmd), addr, 8)
+ if data == "":
+ return ts
+ else:
+ if len(data) == 8:
+ return ts + data
+ else:
+ raise errors.InvalidValueError("Data length of unexpected length")
+
+
+def handshake(radio, msg="", full=False):
+ """Make a full handshake, if not full just hals"""
+ # send ACK if commandes
+ if full is True:
+ rawsend(radio, ACK_CMD)
+ # receive ACK
+ ack = rawrecv(radio, 1)
+ # check ACK
+ if ack != ACK_CMD:
+ #close_radio(radio)
+ mesg = "Handshake failed: " + msg
+ raise errors.RadioError(mesg)
+
+
+def recv(radio):
+ """Receive data from the radio, 12 bytes, 4 in the header, 8 as data"""
+ rxdata = rawrecv(radio, 12)
+
+ if len(rxdata) != 12:
+ raise errors.RadioError(
+ "Received a length of data that is not possible")
+ return
+
+ cmd, addr, length = struct.unpack(">BHB", rxdata[0:4])
+ data = ""
+ if length == 8:
+ data = rxdata[4:]
+
+ return data
+
+
+def open_radio(radio):
+ """Open the radio into program mode and check if it's the correct model"""
+ # Set serial discipline
+ try:
+ radio.pipe.setParity("N")
+ radio.pipe.setTimeout(TIMEOUT)
+ radio.pipe.flushOutput()
+ radio.pipe.flushInput()
+ except:
+ msg = "Serial error: Can't set serial line discipline"
+ raise errors.RadioError(msg)
+
+ magic = "PROGRAM"
+ for i in range(0, len(magic)):
+ ack = rawrecv(radio, 1)
+ time.sleep(0.05)
+ send(radio, magic[i])
+
+ handshake(radio, "Radio not entering Program mode")
+ rawsend(radio, "\x02")
+ ident = rawrecv(radio, 8)
+ handshake(radio, "Comm error after ident", True)
+
+ if not (radio.TYPE in ident):
+ LOG.debug("Incorrect model ID, got %s" % util.hexprint(ident))
+ msg = "Incorrect model ID, got %s, it not contains %s" % \
+ (ident[0:5], radio.TYPE)
+ raise errors.RadioError(msg)
+
+ # DEBUG
+ #print("Full ident string is %s" % util.hexprint(ident))
+
+ # this is needed, I don't know why, yet
+ send(radio, make_frame("W", 0x03e1, "\xff\x01" + "\xff" * 6))
+ handshake(radio, "Comm error after setup", True)
+
+
+def do_download(radio):
+ """This is your download function"""
+ open_radio(radio)
+
+ # UI progress
+ status = chirp_common.Status()
+ status.cur = 0
+ status.max = MEM_SIZE / BLOCK_SIZE
+ status.msg = "Cloning from radio..."
+ radio.status_fn(status)
+
+ data = ""
+ for addr in MEM_BLOCKS:
+ send(radio, make_frame("R", addr * BLOCK_SIZE))
+ data += recv(radio)
+ handshake(radio, "Rx error in block %03i" % addr, True)
+ # DEBUG
+ #print("Block: %04x, Pos: %06x" % (addr, addr * BLOCK_SIZE))
+
+ # UI Update
+ status.cur = addr
+ status.msg = "Cloning from radio..."
+ radio.status_fn(status)
+
+ return memmap.MemoryMap(data)
+
+
+def do_upload(radio):
+ """Upload info to radio"""
+ open_radio(radio)
+
+ # UI progress
+ status = chirp_common.Status()
+ status.cur = 0
+ status.max = MEM_SIZE / BLOCK_SIZE
+ status.msg = "Cloning to radio..."
+ radio.status_fn(status)
+ count = 0
+
+ for addr in MEM_BLOCKS:
+ # UI Update
+ status.cur = addr
+ status.msg = "Cloning to radio..."
+ radio.status_fn(status)
+
+ block = addr * BLOCK_SIZE
+ if block > 0x0378:
+ # it seems that from this point forward is read only !?!?!?
+ continue
+
+ send(radio, make_frame("W", block,
+ radio.get_mmap()[block:block + BLOCK_SIZE]))
+
+ # DEBUG
+ #print("Block: %04x, Pos: %04x" % (addr, addr * BLOCK_SIZE))
+
+ time.sleep(0.1)
+ handshake(radio, "Rx error in block %03i" % addr)
+
+
+def get_rid(data):
+ """Extract the radio identification from the firmware"""
+ rid = data[0x0378:0x0380]
+ # we have to invert rid
+ nrid = ""
+ for i in range(1, len(rid) + 1):
+ nrid += rid[-i]
+ rid = nrid
+
+ return rid
+
+
+def model_match(cls, data):
+ """Match the opened/downloaded image to the correct version"""
+ rid = get_rid(data)
+
+ # DEBUG
+ #print("Full ident string is %s" % util.hexprint(rid))
+
+ if (rid in cls.VARIANTS):
+ # correct model
+ return True
+ else:
+ return False
+
+
+class Kenwood_M60_Radio(chirp_common.CloneModeRadio):
+ """Kenwood Mobile Family 60 Radios"""
+ VENDOR = "Kenwood"
+ _range = [350000000, 500000000] # don't mind, it will be overited
+ _upper = 32
+ 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.\n'
+ '\n'
+ 'The most notorius missing features are this:\n'
+ '=> PTT ID, in fact it is disabled if detected\n'
+ '=> Priority / Home channel\n'
+ '=> DTMF/2-Tone\n'
+ '=> Others\n'
+ '\n'
+ 'If you need one of this, get your official software to do it'
+ 'and raise and issue on the chirp site about it; 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 - 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 - Do the upload of your radio data
+ """))
+ return rp
+
+ def get_features(self):
+ rf = chirp_common.RadioFeatures()
+ rf.has_settings = True
+ rf.has_bank = False
+ rf.has_tuning_step = False
+ rf.has_name = False
+ 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_skips = SKIP_VALUES
+ rf.valid_dtcs_codes = DTCS_CODES
+ rf.valid_bands = [self._range]
+ rf.memory_bounds = (1, self._upper)
+ return rf
+
+ def sync_in(self):
+ """Download from radio"""
+ self._mmap = do_download(self)
+ self.process_mmap()
+
+ def sync_out(self):
+ """Upload to radio"""
+ # Get the data ready for upload
+ try:
+ self._prep_data()
+ except:
+ raise errors.RadioError("Error processing the radio data")
+
+ # do the upload
+ try:
+ do_upload(self)
+ except:
+ raise errors.RadioError("Error uploading data to radio")
+
+ def set_variant(self):
+ """Select and set the correct variables for the class acording
+ to the correct variant of the radio"""
+ rid = get_rid(self._mmap)
+
+ # 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]
+
+ # 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 + ", "
+ self._VARIANT += str(self._range[0]/1000000) + "-"
+ self._VARIANT += str(self._range[1]/1000000) + " Mhz"
+
+ 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.")
+
+ def _prep_data(self):
+ """Prepare the areas in the memmap to do a consistend write
+ it has to make an update on the x200 flag data"""
+ achs = 0
+
+ for i in range(0, self._upper):
+ if self.get_active(i) is True:
+ achs += 1
+
+ # The x0200 area has the settings for the DTMF/2-Tone per channel,
+ # as by default any of this radios has the DTMF IC installed;
+ # we clean this areas
+ fldata = "\x00\xf0\xff\xff\xff" * achs + \
+ "\xff" * (5 * (self._upper - achs))
+ self._fill(0x0200, fldata)
+
+ 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 process_mmap(self):
+ """Process the mem map into the mem object"""
+ 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 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)"""
+ if val.get_raw() == "\xFF\xFF":
+ return '', None, None
+
+ val = int(val)
+ if val >= 12000:
+ a = val - 12000
+ return 'DTCS', a, 'R'
+ elif val >= 8000:
+ a = val - 8000
+ return 'DTCS', a, 'N'
+ 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[0].set_raw(0xFF)
+ memval[1].set_raw(0xFF)
+ elif mode == 'Tone':
+ memval.set_value(int(value * 10))
+ elif mode == 'DTCS':
+ flag = 0x80 if pol == 'N' else 0xC0
+ memval.set_value(value)
+ memval[1].set_bits(flag)
+ else:
+ raise Exception("Internal error: invalid mode `%s'" % mode)
+
+ def get_scan(self, chan):
+ """Get the channel scan status from the 4 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.scan[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.scan[byte]
+ rbyte = rbyte ^ pow(2, bit)
+ self._memobj.settings.scan[byte] = rbyte
+
+ def get_active(self, chan):
+ """Get the channel active status from the 4 bytes array on the eeprom
+ then from the bits on the byte, return True/False"""
+ byte = int(chan/8)
+ bit = chan % 8
+ res = self._memobj.settings.active[byte] & (pow(2, bit))
+ return bool(res)
+
+ def set_active(self, chan, value=True):
+ """Set the channel active 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_active(chan)
+ if actual != bool(value):
+ # I have to flip the value
+ rbyte = self._memobj.settings.active[byte]
+ rbyte = rbyte ^ pow(2, bit)
+ self._memobj.settings.active[byte] = rbyte
+
+ def get_memory(self, number):
+ """Get the mem representation from the radio image"""
+ _mem = self._memobj.memory[number - 1]
+ _tone = self._memobj.tone[number - 1]
+ _ch = self._memobj.ch_settings[number - 1]
+
+ # Create a high-level memory object to return to the UI
+ mem = chirp_common.Memory()
+
+ # Memory number
+ mem.number = number
+
+ if _mem.get_raw()[0] == "\xFF" or not self.get_active(number - 1):
+ 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" * 8)
+ return mem
+
+ # Freq and offset
+ mem.freq = int(_mem.rxfreq) * 10
+ # tx freq can be blank
+ if _mem.get_raw()[4] == "\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
+
+ # power
+ mem.power = POWER_LEVELS[_ch.power]
+
+ # wide/marrow
+ mem.mode = MODES[_ch.wide]
+
+ # skip
+ mem.skip = self.get_scan(number - 1)
+
+ # tone data
+ rxtone = txtone = None
+ txtone = self.decode_tone(_tone.tx_tone)
+ rxtone = self.decode_tone(_tone.rx_tone)
+ chirp_common.split_tone_decode(mem, txtone, rxtone)
+
+ # Extra
+ # bank and number in the channel
+ mem.extra = RadioSettingGroup("extra", "Extra")
+
+ bl = RadioSetting("busy_lock", "Busy Channel lock",
+ RadioSettingValueBoolean(
+ not bool(_ch.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 a low-level memory object mapped to the image
+ _mem = self._memobj.memory[mem.number - 1]
+ _tone = self._memobj.tone[mem.number - 1]
+ _ch = self._memobj.ch_settings[mem.number - 1]
+
+ # Empty memory
+ if mem.empty:
+ _mem.set_raw("\xFF" * 8)
+ # empty the active bit
+ self.set_active(mem.number - 1, False)
+ return
+
+ # freq rx
+ _mem.rxfreq = mem.freq / 10
+
+ # freq tx
+ 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 i in range(0, 4):
+ _mem.txfreq[i].set_raw("\xFF")
+ else:
+ _mem.txfreq = mem.freq / 10
+
+ # tone data
+ ((txmode, txtone, txpol), (rxmode, rxtone, rxpol)) = \
+ chirp_common.split_tone_encode(mem)
+ self.encode_tone(_tone.tx_tone, txmode, txtone, txpol)
+ self.encode_tone(_tone.rx_tone, rxmode, rxtone, rxpol)
+
+ # power, default power is low
+ if mem.power is None:
+ mem.power = POWER_LEVELS[0]
+
+ _ch.power = POWER_LEVELS.index(mem.power)
+
+ # wide/marrow
+ _ch.wide = MODES.index(mem.mode)
+
+ # skip
+ self.set_scan(mem.number - 1, mem.skip)
+
+ # extra settings
+ for setting in mem.extra:
+ setattr(_mem, setting.get_name(), setting.value)
+
+ # set the mem a active in the _memmap
+ self.set_active(mem.number - 1)
+
+ 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
+
+ # basic features of the radio
+ basic = RadioSettingGroup("basic", "Basic Settings")
+ # buttons
+ fkeys = RadioSettingGroup("keys", "Front keys config")
+
+ top = RadioSettings(basic, fkeys)
+
+ # Basic
+ val = RadioSettingValueString(0, 35, self.VARIANT)
+ val.set_mutable(False)
+ mod = RadioSetting("not.mod", "Radio version", val)
+ basic.append(mod)
+
+ tot = RadioSetting("settings.tot", "Time Out Timer (TOT)",
+ RadioSettingValueList(TOT, TOT[int(sett.tot)]))
+ basic.append(tot)
+
+ minvol = RadioSetting("settings.min_vol", "Minimum volume",
+ RadioSettingValueList(VOL,
+ VOL[int(sett.min_vol)]))
+ basic.append(minvol)
+
+ ptone = RadioSetting("settings.poweron_tone", "Power On tone",
+ RadioSettingValueBoolean(
+ bool(sett.poweron_tone)))
+ basic.append(ptone)
+
+ sprog = RadioSetting("settings.dealer_tuning", "Dealer Tuning",
+ RadioSettingValueBoolean(
+ bool(sett.dealer_tuning)))
+ basic.append(sprog)
+
+ clone = RadioSetting("settings.clone", "Allow clone",
+ RadioSettingValueBoolean(
+ bool(sett.clone)))
+ basic.append(clone)
+
+ # front keys
+ mon = RadioSetting("settings.kMON", "MON",
+ RadioSettingValueList(KEYS.values(),
+ KEYS.values()[KEYS.keys().index(
+ int(sett.kMON))]))
+ fkeys.append(mon)
+
+ a = RadioSetting("settings.kA", "A",
+ RadioSettingValueList(KEYS.values(),
+ KEYS.values()[KEYS.keys().index(
+ int(sett.kA))]))
+ fkeys.append(a)
+
+ scn = RadioSetting("settings.kSCN", "SCN",
+ RadioSettingValueList(KEYS.values(),
+ KEYS.values()[KEYS.keys().index(
+ int(sett.kSCN))]))
+ fkeys.append(scn)
+
+ da = RadioSetting("settings.kDA", "D/A",
+ RadioSettingValueList(KEYS.values(),
+ KEYS.values()[KEYS.keys().index(
+ int(sett.kDA))]))
+ fkeys.append(da)
+
+ 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
+
+ # case keys, with special config
+ if setting[0] == "k":
+ value = KEYS.keys()[KEYS.values().index(str(value))]
+
+ # integers case + special case
+ if setting in ["tot", "min_vol"]:
+ # catching the "off" values as zero
+ try:
+ value = int(value)
+ except:
+ value = 0
+
+ # Bool types + inverted
+ if setting in ["poweron_tone", "dealer_tuning", "clone"]:
+ value = bool(value)
+
+ # Apply al configs done
+ # DEBUG
+ #print("%s: %s" % (setting, value))
+ setattr(obj, setting, value)
+
+
+# This are the oldest family 60 models (Black keys), just mobiles support here
+
+ at directory.register
+class TK760_Radio(Kenwood_M60_Radio):
+ """Kenwood TK-760 Radios"""
+ MODEL = "TK-760"
+ TYPE = "M0760"
+ VARIANTS = {
+ "M0760\x01\x00\x00": (32, 136, 156, "K2"),
+ "M0760\x00\x00\x00": (32, 148, 174, "K")
+ }
+
+
+ at directory.register
+class TK762_Radio(Kenwood_M60_Radio):
+ """Kenwood TK-762 Radios"""
+ MODEL = "TK-762"
+ TYPE = "M0762"
+ VARIANTS = {
+ "M0762\x01\x00\x00": (2, 136, 156, "K2"),
+ "M0762\x00\x00\x00": (2, 148, 174, "K")
+ }
+
+
+ at directory.register
+class TK768_Radio(Kenwood_M60_Radio):
+ """Kenwood TK-768 Radios"""
+ MODEL = "TK-768"
+ TYPE = "M0768"
+ VARIANTS = {
+ "M0768\x21\x00\x00": (32, 136, 156, "K2"),
+ "M0768\x20\x00\x00": (32, 148, 174, "K")
+ }
+
+
+ at directory.register
+class TK860_Radio(Kenwood_M60_Radio):
+ """Kenwood TK-860 Radios"""
+ MODEL = "TK-860"
+ TYPE = "M0860"
+ VARIANTS = {
+ "M0860\x05\x00\x00": (32, 406, 430, "F4"),
+ "M0860\x04\x00\x00": (32, 488, 512, "F3"),
+ "M0860\x03\x00\x00": (32, 470, 496, "F2"),
+ "M0860\x02\x00\x00": (32, 450, 476, "F1")
+ }
+
+
+ at directory.register
+class TK862_Radio(Kenwood_M60_Radio):
+ """Kenwood TK-862 Radios"""
+ MODEL = "TK-862"
+ TYPE = "M0862"
+ VARIANTS = {
+ "M0862\x05\x00\x00": (2, 406, 430, "F4"),
+ "M0862\x04\x00\x00": (2, 488, 512, "F3"),
+ "M0862\x03\x00\x00": (2, 470, 496, "F2"),
+ "M0862\x02\x00\x00": (2, 450, 476, "F1")
+ }
+
+
+ at directory.register
+class TK868_Radio(Kenwood_M60_Radio):
+ """Kenwood TK-868 Radios"""
+ MODEL = "TK-868"
+ TYPE = "M0868"
+ VARIANTS = {
+ "M0868\x25\x00\x00": (2, 406, 430, "F4"),
+ "M0868\x24\x00\x00": (2, 488, 512, "F3"),
+ "M0868\x23\x00\x00": (2, 470, 496, "F2"),
+ "M0868\x22\x00\x00": (2, 450, 476, "F1")
+ }
diff --git a/chirp/drivers/tk760g.py b/chirp/drivers/tk760g.py
new file mode 100644
index 0000000..94ccd69
--- /dev/null
+++ b/chirp/drivers/tk760g.py
@@ -0,0 +1,1557 @@
+# Copyright 2012 Dan Smith <dsmith at 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 at gmail.com, co7wt at 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"""
+ 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))
+
+ # Warning about removing a channel on bank 0
+ #if bank.index == self._radio._get_bank(memory.number) == 0:
+ #mesg = "Can't remove, this is the default bank for "
+ #mesg += "all channels"
+ #raise Exception(mesg)
+
+ # 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 = 0
+
+
+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
+ _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, 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
+
+ # 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"
+
+ 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
+ mstr = ""
+ valid_chars = [32, 44, 45, 47, 58, 91, 93] + range(48, 58) + \
+ range(65, 91) + range(97, 123)
+
+ for i in range(0, len(self._VARIANT)):
+ if ord(self._VARIANT[i]) in valid_chars:
+ mstr += self._VARIANT[i]
+
+ 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)
+
+ # 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 = "Side 1"
+
+ gu = RadioSetting("keys.kGROUP_UP", gu_name,
+ RadioSettingValueList(KEYS.values(),
+ KEYS.values()[KEYS.keys().index(
+ int(keys.kGROUP_UP))]))
+ fkeys.append(gu)
+
+ # Side keys on portables
+ gd_name = "Triangle Down"
+ if self.TYPE[0] == "P":
+ gd_name = "> key"
+
+ gd = RadioSetting("keys.kGROUP_DOWN", gd_name,
+ RadioSettingValueList(KEYS.values(),
+ KEYS.values()[KEYS.keys().index(
+ int(keys.kGROUP_DOWN))]))
+ fkeys.append(gd)
+
+ 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
+
+ at 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"),
+ }
+
+
+ at 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"),
+ }
+
+
+ at 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")
+ }
+
+
+ at 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")
+ }
+
+
+ at 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")
+ }
+
+
+ at 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"),
+ }
+
+
+ at 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")
+ }
diff --git a/chirp/drivers/ts2000.py b/chirp/drivers/ts2000.py
index a47ef88..970c021 100644
--- a/chirp/drivers/ts2000.py
+++ b/chirp/drivers/ts2000.py
@@ -25,7 +25,7 @@ TS2000_DUPLEX = dict(kenwood_live.DUPLEX)
TS2000_DUPLEX[3] = "="
TS2000_DUPLEX[4] = "split"
TS2000_MODES = ["?", "LSB", "USB", "CW", "FM", "AM",
- "FSK", "CR-R", "?", "FSK-R"]
+ "FSK", "CW-R", "?", "FSK-R"]
TS2000_TMODES = ["", "Tone", "TSQL", "DTCS"]
TS2000_TONES = list(chirp_common.OLD_TONES)
TS2000_TONES.remove(69.3)
@@ -125,7 +125,7 @@ class TS2000Radio(KenwoodLiveRadio):
spec = " " + spec
# use the same variable names as the Kenwood docs
- #_p1 = spec[3]
+ # _p1 = spec[3]
_p2 = spec[4]
_p3 = spec[5:7]
_p4 = spec[7:18]
@@ -135,11 +135,11 @@ class TS2000Radio(KenwoodLiveRadio):
_p8 = spec[21:23]
_p9 = spec[23:25]
_p10 = spec[25:28]
- #_p11 = spec[28]
+ # _p11 = spec[28]
_p12 = spec[29]
_p13 = spec[30:39]
_p14 = spec[39:41]
- #_p15 = spec[41]
+ # _p15 = spec[41]
_p16 = spec[42:49]
mem.number = int(_p2 + _p3) # concat bank num and chan num
diff --git a/stock_configs/DE Freenet Frequencies.csv b/stock_configs/DE Freenet Frequencies.csv
new file mode 100644
index 0000000..e9ec9bc
--- /dev/null
+++ b/stock_configs/DE Freenet Frequencies.csv
@@ -0,0 +1,7 @@
+Location,Name,Frequency,Duplex,Offset,Tone,rToneFreq,cToneFreq,DtcsCode,DtcsPolarity,Mode,TStep,Skip,Comment,URCALL,RPT1CALL,RPT2CALL
+1,FRNET1,149.025000,,0.000000,,88.5,88.5,023,NN,NFM,5.00,,,,,
+2,FRNET2,149.037500,,0.000000,,88.5,88.5,023,NN,NFM,5.00,,,,,
+3,FRNET3,149.050000,,0.000000,,88.5,88.5,023,NN,NFM,5.00,,,,,
+4,FRNET4,149.087500,,0.000000,,88.5,88.5,023,NN,NFM,5.00,,,,,
+5,FRNET5,149.100000,,0.000000,,88.5,88.5,023,NN,NFM,5.00,,,,,
+6,FRNET6,149.112500,,0.000000,,88.5,88.5,023,NN,NFM,5.00,,,,,
diff --git a/stock_configs/EU LPD and PMR Channels.csv b/stock_configs/EU LPD and PMR Channels.csv
index 0595902..57eab2e 100644
--- a/stock_configs/EU LPD and PMR Channels.csv
+++ b/stock_configs/EU LPD and PMR Channels.csv
@@ -1,78 +1,78 @@
-Location,Name,Frequency,Duplex,Offset,Tone,rToneFreq,cToneFreq,DtcsCode,DtcsPolarity,Mode,TStep,Skip,Comment,URCALL,RPT1CALL,RPT2CALL
-1,LPD 01,433.075000,,0.600000,,88.5,88.5,023,NN,FM,5.00,,,,,,
-2,LPD 02,433.100000,,0.600000,,88.5,88.5,023,NN,FM,5.00,,,,,,
-3,LPD 03,433.125000,,0.600000,,88.5,88.5,023,NN,FM,5.00,,,,,,
-4,LPD 04,433.150000,,0.600000,,88.5,88.5,023,NN,FM,5.00,,,,,,
-5,LPD 05,433.175000,,0.600000,,88.5,88.5,023,NN,FM,5.00,,,,,,
-6,LPD 06,433.200000,,0.600000,,88.5,88.5,023,NN,FM,5.00,,,,,,
-7,LPD 07,433.225000,,0.600000,,88.5,88.5,023,NN,FM,5.00,,,,,,
-8,LPD 08,433.250000,,0.600000,,88.5,88.5,023,NN,FM,5.00,,,,,,
-9,LPD 09,433.275000,,0.600000,,88.5,88.5,023,NN,FM,5.00,,,,,,
-10,LPD 10,433.300000,,0.600000,,88.5,88.5,023,NN,FM,5.00,,,,,,
-11,LPD 11,433.325000,,0.600000,,88.5,88.5,023,NN,FM,5.00,,,,,,
-12,LPD 12,433.350000,,0.600000,,88.5,88.5,023,NN,FM,5.00,,,,,,
-13,LPD 13,433.375000,,0.600000,,88.5,88.5,023,NN,FM,5.00,,,,,,
-14,LPD 14,433.400000,,0.600000,,88.5,88.5,023,NN,FM,5.00,,,,,,
-15,LPD 15,433.425000,,0.600000,,88.5,88.5,023,NN,FM,5.00,,,,,,
-16,LPD 16,433.450000,,0.600000,,88.5,88.5,023,NN,FM,5.00,,,,,,
-17,LPD 17,433.475000,,0.600000,,88.5,88.5,023,NN,FM,5.00,,,,,,
-18,LPD 18,433.500000,,0.600000,,88.5,88.5,023,NN,FM,5.00,,,,,,
-19,LPD 19,433.525000,,0.600000,,88.5,88.5,023,NN,FM,5.00,,,,,,
-20,LPD 20,433.550000,,0.600000,,88.5,88.5,023,NN,FM,5.00,,,,,,
-21,LPD 21,433.575000,,0.600000,,88.5,88.5,023,NN,FM,5.00,,,,,,
-22,LPD 22,433.600000,,0.600000,,88.5,88.5,023,NN,FM,5.00,,,,,,
-23,LPD 23,433.625000,,0.600000,,88.5,88.5,023,NN,FM,5.00,,,,,,
-24,LPD 24,433.650000,,0.600000,,88.5,88.5,023,NN,FM,5.00,,,,,,
-25,LPD 25,433.675000,,0.600000,,88.5,88.5,023,NN,FM,5.00,,,,,,
-26,LPD 26,433.700000,,0.600000,,88.5,88.5,023,NN,FM,5.00,,,,,,
-27,LPD 27,433.725000,,0.600000,,88.5,88.5,023,NN,FM,5.00,,,,,,
-28,LPD 28,433.750000,,0.600000,,88.5,88.5,023,NN,FM,5.00,,,,,,
-29,LPD 29,433.775000,,0.600000,,88.5,88.5,023,NN,FM,5.00,,,,,,
-30,LPD 30,433.800000,,0.600000,,88.5,88.5,023,NN,FM,5.00,,,,,,
-31,LPD 31,433.825000,,0.600000,,88.5,88.5,023,NN,FM,5.00,,,,,,
-32,LPD 32,433.850000,,0.600000,,88.5,88.5,023,NN,FM,5.00,,,,,,
-33,LPD 33,433.875000,,0.600000,,88.5,88.5,023,NN,FM,5.00,,,,,,
-34,LPD 34,433.900000,,0.600000,,88.5,88.5,023,NN,FM,5.00,,,,,,
-35,LPD 35,433.925000,,0.600000,,88.5,88.5,023,NN,FM,5.00,,,,,,
-36,LPD 36,433.950000,,0.600000,,88.5,88.5,023,NN,FM,5.00,,,,,,
-37,LPD 37,433.975000,,0.600000,,88.5,88.5,023,NN,FM,5.00,,,,,,
-38,LPD 38,434.000000,,0.600000,,88.5,88.5,023,NN,FM,5.00,,,,,,
-39,LPD 39,434.025000,,0.600000,,88.5,88.5,023,NN,FM,5.00,,,,,,
-40,LPD 40,434.050000,,0.600000,,88.5,88.5,023,NN,FM,5.00,,,,,,
-41,LPD 41,434.075000,,0.600000,,88.5,88.5,023,NN,FM,5.00,,,,,,
-42,LPD 42,434.100000,,0.600000,,88.5,88.5,023,NN,FM,5.00,,,,,,
-43,LPD 43,434.125000,,0.600000,,88.5,88.5,023,NN,FM,5.00,,,,,,
-44,LPD 44,434.150000,,0.600000,,88.5,88.5,023,NN,FM,5.00,,,,,,
-45,LPD 45,434.175000,,0.600000,,88.5,88.5,023,NN,FM,5.00,,,,,,
-46,LPD 46,434.200000,,0.600000,,88.5,88.5,023,NN,FM,5.00,,,,,,
-47,LPD 47,434.225000,,0.600000,,88.5,88.5,023,NN,FM,5.00,,,,,,
-48,LPD 48,434.250000,,0.600000,,88.5,88.5,023,NN,FM,5.00,,,,,,
-49,LPD 49,434.275000,,0.600000,,88.5,88.5,023,NN,FM,5.00,,,,,,
-50,LPD 50,434.300000,,0.600000,,88.5,88.5,023,NN,FM,5.00,,,,,,
-51,LPD 51,434.325000,,0.600000,,88.5,88.5,023,NN,FM,5.00,,,,,,
-52,LPD 52,434.350000,,0.600000,,88.5,88.5,023,NN,FM,5.00,,,,,,
-53,LPD 53,434.375000,,0.600000,,88.5,88.5,023,NN,FM,5.00,,,,,,
-54,LPD 54,434.400000,,0.600000,,88.5,88.5,023,NN,FM,5.00,,,,,,
-55,LPD 55,434.425000,,0.600000,,88.5,88.5,023,NN,FM,5.00,,,,,,
-56,LPD 56,434.450000,,0.600000,,88.5,88.5,023,NN,FM,5.00,,,,,,
-57,LPD 57,434.475000,,0.600000,,88.5,88.5,023,NN,FM,5.00,,,,,,
-58,LPD 58,434.500000,,0.600000,,88.5,88.5,023,NN,FM,5.00,,,,,,
-59,LPD 59,434.525000,,0.600000,,88.5,88.5,023,NN,FM,5.00,,,,,,
-60,LPD 60,434.550000,,0.600000,,88.5,88.5,023,NN,FM,5.00,,,,,,
-61,LPD 61,434.575000,,0.600000,,88.5,88.5,023,NN,FM,5.00,,,,,,
-62,LPD 62,434.600000,,0.600000,,88.5,88.5,023,NN,FM,5.00,,,,,,
-63,LPD 63,434.625000,,0.600000,,88.5,88.5,023,NN,FM,5.00,,,,,,
-64,LPD 64,434.650000,,0.600000,,88.5,88.5,023,NN,FM,5.00,,,,,,
-65,LPD 65,434.675000,,0.600000,,88.5,88.5,023,NN,FM,5.00,,,,,,
-66,LPD 66,434.700000,,0.600000,,88.5,88.5,023,NN,FM,5.00,,,,,,
-67,LPD 67,434.725000,,0.600000,,88.5,88.5,023,NN,FM,5.00,,,,,,
-68,LPD 68,434.750000,,0.600000,,88.5,88.5,023,NN,FM,5.00,,,,,,
-69,LPD 69,434.775000,,0.600000,,88.5,88.5,023,NN,FM,5.00,,,,,,
-71,PMR 1,446.006250,,0.600000,,88.5,88.5,023,NN,NFM,6.25,,,,,,
-72,PMR 2,446.018750,,0.600000,,88.5,88.5,023,NN,NFM,6.25,,,,,,
-73,PMR 3,446.031250,,0.600000,,88.5,88.5,023,NN,NFM,6.25,,,,,,
-74,PMR 4,446.043750,,0.600000,,88.5,88.5,023,NN,NFM,6.25,,,,,,
-75,PMR 5,446.056250,,0.600000,,88.5,88.5,023,NN,NFM,6.25,,,,,,
-76,PMR 6,446.068750,,0.600000,,88.5,88.5,023,NN,NFM,6.25,,,,,,
-77,PMR 7,446.081250,,0.600000,,88.5,88.5,023,NN,NFM,6.25,,,,,,
-78,PMR 8,446.093750,,0.600000,,88.5,88.5,023,NN,NFM,6.25,,,,,,
+Location,Name,Frequency,Duplex,Offset,Tone,rToneFreq,cToneFreq,DtcsCode,DtcsPolarity,Mode,TStep,Skip,Comment,URCALL,RPT1CALL,RPT2CALL
+1,LPD 01,433.075000,,0.600000,,88.5,88.5,023,NN,FM,5.00,,,,,
+2,LPD 02,433.100000,,0.600000,,88.5,88.5,023,NN,FM,5.00,,,,,
+3,LPD 03,433.125000,,0.600000,,88.5,88.5,023,NN,FM,5.00,,,,,
+4,LPD 04,433.150000,,0.600000,,88.5,88.5,023,NN,FM,5.00,,,,,
+5,LPD 05,433.175000,,0.600000,,88.5,88.5,023,NN,FM,5.00,,,,,
+6,LPD 06,433.200000,,0.600000,,88.5,88.5,023,NN,FM,5.00,,,,,
+7,LPD 07,433.225000,,0.600000,,88.5,88.5,023,NN,FM,5.00,,,,,
+8,LPD 08,433.250000,,0.600000,,88.5,88.5,023,NN,FM,5.00,,,,,
+9,LPD 09,433.275000,,0.600000,,88.5,88.5,023,NN,FM,5.00,,,,,
+10,LPD 10,433.300000,,0.600000,,88.5,88.5,023,NN,FM,5.00,,,,,
+11,LPD 11,433.325000,,0.600000,,88.5,88.5,023,NN,FM,5.00,,,,,
+12,LPD 12,433.350000,,0.600000,,88.5,88.5,023,NN,FM,5.00,,,,,
+13,LPD 13,433.375000,,0.600000,,88.5,88.5,023,NN,FM,5.00,,,,,
+14,LPD 14,433.400000,,0.600000,,88.5,88.5,023,NN,FM,5.00,,,,,
+15,LPD 15,433.425000,,0.600000,,88.5,88.5,023,NN,FM,5.00,,,,,
+16,LPD 16,433.450000,,0.600000,,88.5,88.5,023,NN,FM,5.00,,,,,
+17,LPD 17,433.475000,,0.600000,,88.5,88.5,023,NN,FM,5.00,,,,,
+18,LPD 18,433.500000,,0.600000,,88.5,88.5,023,NN,FM,5.00,,,,,
+19,LPD 19,433.525000,,0.600000,,88.5,88.5,023,NN,FM,5.00,,,,,
+20,LPD 20,433.550000,,0.600000,,88.5,88.5,023,NN,FM,5.00,,,,,
+21,LPD 21,433.575000,,0.600000,,88.5,88.5,023,NN,FM,5.00,,,,,
+22,LPD 22,433.600000,,0.600000,,88.5,88.5,023,NN,FM,5.00,,,,,
+23,LPD 23,433.625000,,0.600000,,88.5,88.5,023,NN,FM,5.00,,,,,
+24,LPD 24,433.650000,,0.600000,,88.5,88.5,023,NN,FM,5.00,,,,,
+25,LPD 25,433.675000,,0.600000,,88.5,88.5,023,NN,FM,5.00,,,,,
+26,LPD 26,433.700000,,0.600000,,88.5,88.5,023,NN,FM,5.00,,,,,
+27,LPD 27,433.725000,,0.600000,,88.5,88.5,023,NN,FM,5.00,,,,,
+28,LPD 28,433.750000,,0.600000,,88.5,88.5,023,NN,FM,5.00,,,,,
+29,LPD 29,433.775000,,0.600000,,88.5,88.5,023,NN,FM,5.00,,,,,
+30,LPD 30,433.800000,,0.600000,,88.5,88.5,023,NN,FM,5.00,,,,,
+31,LPD 31,433.825000,,0.600000,,88.5,88.5,023,NN,FM,5.00,,,,,
+32,LPD 32,433.850000,,0.600000,,88.5,88.5,023,NN,FM,5.00,,,,,
+33,LPD 33,433.875000,,0.600000,,88.5,88.5,023,NN,FM,5.00,,,,,
+34,LPD 34,433.900000,,0.600000,,88.5,88.5,023,NN,FM,5.00,,,,,
+35,LPD 35,433.925000,,0.600000,,88.5,88.5,023,NN,FM,5.00,,,,,
+36,LPD 36,433.950000,,0.600000,,88.5,88.5,023,NN,FM,5.00,,,,,
+37,LPD 37,433.975000,,0.600000,,88.5,88.5,023,NN,FM,5.00,,,,,
+38,LPD 38,434.000000,,0.600000,,88.5,88.5,023,NN,FM,5.00,,,,,
+39,LPD 39,434.025000,,0.600000,,88.5,88.5,023,NN,FM,5.00,,,,,
+40,LPD 40,434.050000,,0.600000,,88.5,88.5,023,NN,FM,5.00,,,,,
+41,LPD 41,434.075000,,0.600000,,88.5,88.5,023,NN,FM,5.00,,,,,
+42,LPD 42,434.100000,,0.600000,,88.5,88.5,023,NN,FM,5.00,,,,,
+43,LPD 43,434.125000,,0.600000,,88.5,88.5,023,NN,FM,5.00,,,,,
+44,LPD 44,434.150000,,0.600000,,88.5,88.5,023,NN,FM,5.00,,,,,
+45,LPD 45,434.175000,,0.600000,,88.5,88.5,023,NN,FM,5.00,,,,,
+46,LPD 46,434.200000,,0.600000,,88.5,88.5,023,NN,FM,5.00,,,,,
+47,LPD 47,434.225000,,0.600000,,88.5,88.5,023,NN,FM,5.00,,,,,
+48,LPD 48,434.250000,,0.600000,,88.5,88.5,023,NN,FM,5.00,,,,,
+49,LPD 49,434.275000,,0.600000,,88.5,88.5,023,NN,FM,5.00,,,,,
+50,LPD 50,434.300000,,0.600000,,88.5,88.5,023,NN,FM,5.00,,,,,
+51,LPD 51,434.325000,,0.600000,,88.5,88.5,023,NN,FM,5.00,,,,,
+52,LPD 52,434.350000,,0.600000,,88.5,88.5,023,NN,FM,5.00,,,,,
+53,LPD 53,434.375000,,0.600000,,88.5,88.5,023,NN,FM,5.00,,,,,
+54,LPD 54,434.400000,,0.600000,,88.5,88.5,023,NN,FM,5.00,,,,,
+55,LPD 55,434.425000,,0.600000,,88.5,88.5,023,NN,FM,5.00,,,,,
+56,LPD 56,434.450000,,0.600000,,88.5,88.5,023,NN,FM,5.00,,,,,
+57,LPD 57,434.475000,,0.600000,,88.5,88.5,023,NN,FM,5.00,,,,,
+58,LPD 58,434.500000,,0.600000,,88.5,88.5,023,NN,FM,5.00,,,,,
+59,LPD 59,434.525000,,0.600000,,88.5,88.5,023,NN,FM,5.00,,,,,
+60,LPD 60,434.550000,,0.600000,,88.5,88.5,023,NN,FM,5.00,,,,,
+61,LPD 61,434.575000,,0.600000,,88.5,88.5,023,NN,FM,5.00,,,,,
+62,LPD 62,434.600000,,0.600000,,88.5,88.5,023,NN,FM,5.00,,,,,
+63,LPD 63,434.625000,,0.600000,,88.5,88.5,023,NN,FM,5.00,,,,,
+64,LPD 64,434.650000,,0.600000,,88.5,88.5,023,NN,FM,5.00,,,,,
+65,LPD 65,434.675000,,0.600000,,88.5,88.5,023,NN,FM,5.00,,,,,
+66,LPD 66,434.700000,,0.600000,,88.5,88.5,023,NN,FM,5.00,,,,,
+67,LPD 67,434.725000,,0.600000,,88.5,88.5,023,NN,FM,5.00,,,,,
+68,LPD 68,434.750000,,0.600000,,88.5,88.5,023,NN,FM,5.00,,,,,
+69,LPD 69,434.775000,,0.600000,,88.5,88.5,023,NN,FM,5.00,,,,,
+71,PMR 1,446.006250,,0.600000,,88.5,88.5,023,NN,NFM,6.25,,,,,
+72,PMR 2,446.018750,,0.600000,,88.5,88.5,023,NN,NFM,6.25,,,,,
+73,PMR 3,446.031250,,0.600000,,88.5,88.5,023,NN,NFM,6.25,,,,,
+74,PMR 4,446.043750,,0.600000,,88.5,88.5,023,NN,NFM,6.25,,,,,
+75,PMR 5,446.056250,,0.600000,,88.5,88.5,023,NN,NFM,6.25,,,,,
+76,PMR 6,446.068750,,0.600000,,88.5,88.5,023,NN,NFM,6.25,,,,,
+77,PMR 7,446.081250,,0.600000,,88.5,88.5,023,NN,NFM,6.25,,,,,
+78,PMR 8,446.093750,,0.600000,,88.5,88.5,023,NN,NFM,6.25,,,,,
diff --git a/stock_configs/FR Marine VHF Channels.csv b/stock_configs/FR Marine VHF Channels.csv
index 02bc3ee..da8bac7 100644
--- a/stock_configs/FR Marine VHF Channels.csv
+++ b/stock_configs/FR Marine VHF Channels.csv
@@ -1,58 +1,58 @@
Location,Name,Frequency,Duplex,Offset,Tone,rToneFreq,cToneFreq,DtcsCode,DtcsPolarity,Mode,TStep,Skip,Comment,URCALL,RPT1CALL,RPT2CALL
-1,SEA 01,160.650000,-,4.600000,,88.5,88.5,023,NN,FM,5.00,,,,,,
-2,SEA 02,160.700000,-,4.600000,,88.5,88.5,023,NN,FM,5.00,,,,,,
-3,SEA 03,160.750000,-,4.600000,,88.5,88.5,023,NN,FM,5.00,,,,,,
-4,SEA 04,160.800000,-,4.600000,,88.5,88.5,023,NN,FM,5.00,,,,,,
-5,SEA 05,160.850000,-,4.600000,,88.5,88.5,023,NN,FM,5.00,,,,,,
-6,SEA 06,156.300000,,0.000000,,88.5,88.5,023,NN,FM,5.00,,,,,,
-7,SEA 07,160.950000,-,4.600000,,88.5,88.5,023,NN,FM,5.00,,,,,,
-8,SEA 08,156.400000,,0.000000,,88.5,88.5,023,NN,FM,5.00,,,,,,
-9,SEA 09,156.450000,,0.000000,,88.5,88.5,023,NN,FM,5.00,,,,,,
-10,SEA 10,156.500000,,0.000000,,88.5,88.5,023,NN,FM,5.00,,,,,,
-11,SEA 11,156.550000,,0.000000,,88.5,88.5,023,NN,FM,5.00,,,,,,
-12,SEA 12,156.600000,,0.000000,,88.5,88.5,023,NN,FM,5.00,,,,,,
-13,SEA 13,156.650000,,0.000000,,88.5,88.5,023,NN,FM,5.00,,,,,,
-14,SEA 14,156.700000,,0.000000,,88.5,88.5,023,NN,FM,5.00,,,,,,
-15,SEA 15,156.750000,,0.000000,,88.5,88.5,023,NN,FM,5.00,,,,,,
-16,SEA 16,156.800000,,0.000000,,88.5,88.5,023,NN,FM,5.00,,,,,,
-17,SEA 17,156.850000,,0.000000,,88.5,88.5,023,NN,FM,5.00,,,,,,
-18,SEA 18,161.500000,-,4.600000,,88.5,88.5,023,NN,FM,5.00,,,,,,
-19,SEA 19,161.550000,-,4.600000,,88.5,88.5,023,NN,FM,5.00,,,,,,
-20,SEA 20,161.600000,-,4.600000,,88.5,88.5,023,NN,FM,5.00,,,,,,
-21,SEA 21,161.650000,-,4.600000,,88.5,88.5,023,NN,FM,5.00,,,,,,
-22,SEA 22,161.700000,-,4.600000,,88.5,88.5,023,NN,FM,5.00,,,,,,
-23,SEA 23,161.750000,-,4.600000,,88.5,88.5,023,NN,FM,5.00,,,,,,
-24,SEA 24,161.800000,-,4.600000,,88.5,88.5,023,NN,FM,5.00,,,,,,
-25,SEA 25,161.850000,-,4.600000,,88.5,88.5,023,NN,FM,5.00,,,,,,
-26,SEA 26,161.900000,-,4.600000,,88.5,88.5,023,NN,FM,5.00,,,,,,
-27,SEA 27,161.950000,-,4.600000,,88.5,88.5,023,NN,FM,5.00,,,,,,
-28,SEA 28,162.000000,-,4.600000,,88.5,88.5,023,NN,FM,5.00,,,,,,
-29,SEA 60,160.625000,-,4.600000,,88.5,88.5,023,NN,FM,5.00,,,,,,
-30,SEA 61,160.675000,-,4.600000,,88.5,88.5,023,NN,FM,5.00,,,,,,
-31,SEA 62,160.725000,-,4.600000,,88.5,88.5,023,NN,FM,5.00,,,,,,
-32,SEA 63,160.775000,-,4.600000,,88.5,88.5,023,NN,FM,5.00,,,,,,
-33,SEA 64,160.825000,-,4.600000,,88.5,88.5,023,NN,FM,5.00,,,,,,
-34,SEA 65,160.875000,-,4.600000,,88.5,88.5,023,NN,FM,5.00,,,,,,
-35,SEA 66,160.925000,-,4.600000,,88.5,88.5,023,NN,FM,5.00,,,,,,
-36,SEA 67,156.375000,,0.000000,,88.5,88.5,023,NN,FM,5.00,,,,,,
-37,SEA 68,156.425000,,0.000000,,88.5,88.5,023,NN,FM,5.00,,,,,,
-38,SEA 69,156.475000,,0.000000,,88.5,88.5,023,NN,FM,5.00,,,,,,
-39,SEA 70,156.525000,,0.000000,,88.5,88.5,023,NN,FM,5.00,,,,,,
-40,SEA 71,156.575000,,0.000000,,88.5,88.5,023,NN,FM,5.00,,,,,,
-41,SEA 72,156.625000,,0.000000,,88.5,88.5,023,NN,FM,5.00,,,,,,
-42,SEA 73,156.675000,,0.000000,,88.5,88.5,023,NN,FM,5.00,,,,,,
-43,SEA 74,156.725000,,0.000000,,88.5,88.5,023,NN,FM,5.00,,,,,,
-44,SEA 75,156.775000,,0.000000,,88.5,88.5,023,NN,FM,5.00,,,,,,
-45,SEA 76,156.825000,,0.000000,,88.5,88.5,023,NN,FM,5.00,,,,,,
-46,SEA 77,156.875000,,0.000000,,88.5,88.5,023,NN,FM,5.00,,,,,,
-47,SEA 78,161.525000,-,4.600000,,88.5,88.5,023,NN,FM,5.00,,,,,,
-48,SEA 79,161.575000,-,4.600000,,88.5,88.5,023,NN,FM,5.00,,,,,,
-49,SEA 80,161.625000,-,4.600000,,88.5,88.5,023,NN,FM,5.00,,,,,,
-50,SEA 81,161.675000,-,4.600000,,88.5,88.5,023,NN,FM,5.00,,,,,,
-51,SEA 82,161.725000,-,4.600000,,88.5,88.5,023,NN,FM,5.00,,,,,,
-52,SEA 83,161.775000,-,4.600000,,88.5,88.5,023,NN,FM,5.00,,,,,,
-53,SEA 84,161.825000,-,4.600000,,88.5,88.5,023,NN,FM,5.00,,,,,,
-54,SEA 85,161.875000,-,4.600000,,88.5,88.5,023,NN,FM,5.00,,,,,,
-55,SEA 86,161.925000,-,4.600000,,88.5,88.5,023,NN,FM,5.00,,,,,,
-56,SEA 87,157.375000,,0.000000,,88.5,88.5,023,NN,FM,5.00,,,,,,
-57,SEA 88,157.425000,,0.000000,,88.5,88.5,023,NN,FM,5.00,,,,,,
+1,SEA 01,160.650000,-,4.600000,,88.5,88.5,023,NN,FM,5.00,,,,,
+2,SEA 02,160.700000,-,4.600000,,88.5,88.5,023,NN,FM,5.00,,,,,
+3,SEA 03,160.750000,-,4.600000,,88.5,88.5,023,NN,FM,5.00,,,,,
+4,SEA 04,160.800000,-,4.600000,,88.5,88.5,023,NN,FM,5.00,,,,,
+5,SEA 05,160.850000,-,4.600000,,88.5,88.5,023,NN,FM,5.00,,,,,
+6,SEA 06,156.300000,,0.000000,,88.5,88.5,023,NN,FM,5.00,,,,,
+7,SEA 07,160.950000,-,4.600000,,88.5,88.5,023,NN,FM,5.00,,,,,
+8,SEA 08,156.400000,,0.000000,,88.5,88.5,023,NN,FM,5.00,,,,,
+9,SEA 09,156.450000,,0.000000,,88.5,88.5,023,NN,FM,5.00,,,,,
+10,SEA 10,156.500000,,0.000000,,88.5,88.5,023,NN,FM,5.00,,,,,
+11,SEA 11,156.550000,,0.000000,,88.5,88.5,023,NN,FM,5.00,,,,,
+12,SEA 12,156.600000,,0.000000,,88.5,88.5,023,NN,FM,5.00,,,,,
+13,SEA 13,156.650000,,0.000000,,88.5,88.5,023,NN,FM,5.00,,,,,
+14,SEA 14,156.700000,,0.000000,,88.5,88.5,023,NN,FM,5.00,,,,,
+15,SEA 15,156.750000,,0.000000,,88.5,88.5,023,NN,FM,5.00,,,,,
+16,SEA 16,156.800000,,0.000000,,88.5,88.5,023,NN,FM,5.00,,,,,
+17,SEA 17,156.850000,,0.000000,,88.5,88.5,023,NN,FM,5.00,,,,,
+18,SEA 18,161.500000,-,4.600000,,88.5,88.5,023,NN,FM,5.00,,,,,
+19,SEA 19,161.550000,-,4.600000,,88.5,88.5,023,NN,FM,5.00,,,,,
+20,SEA 20,161.600000,-,4.600000,,88.5,88.5,023,NN,FM,5.00,,,,,
+21,SEA 21,161.650000,-,4.600000,,88.5,88.5,023,NN,FM,5.00,,,,,
+22,SEA 22,161.700000,-,4.600000,,88.5,88.5,023,NN,FM,5.00,,,,,
+23,SEA 23,161.750000,-,4.600000,,88.5,88.5,023,NN,FM,5.00,,,,,
+24,SEA 24,161.800000,-,4.600000,,88.5,88.5,023,NN,FM,5.00,,,,,
+25,SEA 25,161.850000,-,4.600000,,88.5,88.5,023,NN,FM,5.00,,,,,
+26,SEA 26,161.900000,-,4.600000,,88.5,88.5,023,NN,FM,5.00,,,,,
+27,SEA 27,161.950000,-,4.600000,,88.5,88.5,023,NN,FM,5.00,,,,,
+28,SEA 28,162.000000,-,4.600000,,88.5,88.5,023,NN,FM,5.00,,,,,
+29,SEA 60,160.625000,-,4.600000,,88.5,88.5,023,NN,FM,5.00,,,,,
+30,SEA 61,160.675000,-,4.600000,,88.5,88.5,023,NN,FM,5.00,,,,,
+31,SEA 62,160.725000,-,4.600000,,88.5,88.5,023,NN,FM,5.00,,,,,
+32,SEA 63,160.775000,-,4.600000,,88.5,88.5,023,NN,FM,5.00,,,,,
+33,SEA 64,160.825000,-,4.600000,,88.5,88.5,023,NN,FM,5.00,,,,,
+34,SEA 65,160.875000,-,4.600000,,88.5,88.5,023,NN,FM,5.00,,,,,
+35,SEA 66,160.925000,-,4.600000,,88.5,88.5,023,NN,FM,5.00,,,,,
+36,SEA 67,156.375000,,0.000000,,88.5,88.5,023,NN,FM,5.00,,,,,
+37,SEA 68,156.425000,,0.000000,,88.5,88.5,023,NN,FM,5.00,,,,,
+38,SEA 69,156.475000,,0.000000,,88.5,88.5,023,NN,FM,5.00,,,,,
+39,SEA 70,156.525000,,0.000000,,88.5,88.5,023,NN,FM,5.00,,,,,
+40,SEA 71,156.575000,,0.000000,,88.5,88.5,023,NN,FM,5.00,,,,,
+41,SEA 72,156.625000,,0.000000,,88.5,88.5,023,NN,FM,5.00,,,,,
+42,SEA 73,156.675000,,0.000000,,88.5,88.5,023,NN,FM,5.00,,,,,
+43,SEA 74,156.725000,,0.000000,,88.5,88.5,023,NN,FM,5.00,,,,,
+44,SEA 75,156.775000,,0.000000,,88.5,88.5,023,NN,FM,5.00,,,,,
+45,SEA 76,156.825000,,0.000000,,88.5,88.5,023,NN,FM,5.00,,,,,
+46,SEA 77,156.875000,,0.000000,,88.5,88.5,023,NN,FM,5.00,,,,,
+47,SEA 78,161.525000,-,4.600000,,88.5,88.5,023,NN,FM,5.00,,,,,
+48,SEA 79,161.575000,-,4.600000,,88.5,88.5,023,NN,FM,5.00,,,,,
+49,SEA 80,161.625000,-,4.600000,,88.5,88.5,023,NN,FM,5.00,,,,,
+50,SEA 81,161.675000,-,4.600000,,88.5,88.5,023,NN,FM,5.00,,,,,
+51,SEA 82,161.725000,-,4.600000,,88.5,88.5,023,NN,FM,5.00,,,,,
+52,SEA 83,161.775000,-,4.600000,,88.5,88.5,023,NN,FM,5.00,,,,,
+53,SEA 84,161.825000,-,4.600000,,88.5,88.5,023,NN,FM,5.00,,,,,
+54,SEA 85,161.875000,-,4.600000,,88.5,88.5,023,NN,FM,5.00,,,,,
+55,SEA 86,161.925000,-,4.600000,,88.5,88.5,023,NN,FM,5.00,,,,,
+56,SEA 87,157.375000,,0.000000,,88.5,88.5,023,NN,FM,5.00,,,,,
+57,SEA 88,157.425000,,0.000000,,88.5,88.5,023,NN,FM,5.00,,,,,
diff --git a/stock_configs/UK Business Radio Simple Light Frequencies.csv b/stock_configs/UK Business Radio Simple Light Frequencies.csv
new file mode 100644
index 0000000..803995b
--- /dev/null
+++ b/stock_configs/UK Business Radio Simple Light Frequencies.csv
@@ -0,0 +1,16 @@
+Location,Name,Frequency,Duplex,Offset,Tone,rToneFreq,cToneFreq,DtcsCode,DtcsPolarity,Mode,TStep,Skip,Comment,URCALL,RPT1CALL,RPT2CALL
+1,BRSL1,77.687500,,0.000000,,88.5,88.5,023,NN,NFM,12.50,,,,,
+2,BRSL2,86.337500,,0.000000,,88.5,88.5,023,NN,NFM,12.50,,,,,
+3,BRSL3,86.350000,,0.000000,,88.5,88.5,023,NN,NFM,12.50,,,,,
+4,BRSL4,86.362500,,0.000000,,88.5,88.5,023,NN,NFM,12.50,,,,,
+5,BRSL5,86.375000,,0.000000,,88.5,88.5,023,NN,NFM,12.50,,,,,
+6,BRSL6,164.050000,,0.000000,,88.5,88.5,023,NN,NFM,12.50,,,,,
+7,BRSL7,164.062500,,0.000000,,88.5,88.5,023,NN,NFM,12.50,,,,,
+8,BRSL8,169.087500,,0.000000,,88.5,88.5,023,NN,NFM,12.50,,,,,
+9,BRSL9,169.312500,,0.000000,,88.5,88.5,023,NN,NFM,12.50,,,,,
+10,BRSL10,173.050000,,0.000000,,88.5,88.5,023,NN,NFM,12.50,,,,,
+11,BRSL11,173.062500,,0.000000,,88.5,88.5,023,NN,NFM,12.50,,,,,
+12,BRSL12,173.087500,,0.000000,,88.5,88.5,023,NN,NFM,12.50,,,,,
+13,BRSL13,449.312500,,0.000000,,88.5,88.5,023,NN,NFM,12.50,,,,,
+14,BRSL14,449.400000,,0.000000,,88.5,88.5,023,NN,NFM,12.50,,,,,
+15,BRSL15,449.475000,,0.000000,,88.5,88.5,023,NN,NFM,12.50,,,,,
diff --git a/stock_configs/US 60 meter channels (Center).csv b/stock_configs/US 60 meter channels (Center).csv
index c650a3b..df22f65 100644
--- a/stock_configs/US 60 meter channels (Center).csv
+++ b/stock_configs/US 60 meter channels (Center).csv
@@ -1,4 +1,4 @@
-Location,Name,Frequency,Duplex,Offset,Tone,rToneFreq,cToneFreq,DtcsCode,DtcsPolarity,Mode,TStep,Skip,URCALL,RPT1CALL,RPT2CALL
+Location,Name,Frequency,Duplex,Offset,Tone,rToneFreq,cToneFreq,DtcsCode,DtcsPolarity,Mode,TStep,Skip,Comment,URCALL,RPT1CALL,RPT2CALL
1,60m CH1,5.332000,,0.600000,,88.5,88.5,023,NN,USB,5.00,,,,,
2,60m CH2,5.348000,,0.600000,,88.5,88.5,023,NN,USB,5.00,,,,,
3,60m CH3,5.358500,,0.600000,,88.5,88.5,023,NN,USB,5.00,,,,,
diff --git a/stock_configs/US 60 meter channels (Dial).csv b/stock_configs/US 60 meter channels (Dial).csv
index db3c13a..ed016f3 100644
--- a/stock_configs/US 60 meter channels (Dial).csv
+++ b/stock_configs/US 60 meter channels (Dial).csv
@@ -1,4 +1,4 @@
-Location,Name,Frequency,Duplex,Offset,Tone,rToneFreq,cToneFreq,DtcsCode,DtcsPolarity,Mode,TStep,Skip,URCALL,RPT1CALL,RPT2CALL
+Location,Name,Frequency,Duplex,Offset,Tone,rToneFreq,cToneFreq,DtcsCode,DtcsPolarity,Mode,TStep,Skip,Comment,URCALL,RPT1CALL,RPT2CALL
1,60m CH1,5.330500,,0.600000,,88.5,88.5,023,NN,USB,5.00,,,,,
2,60m CH2,5.346500,,0.600000,,88.5,88.5,023,NN,USB,5.00,,,,,
3,60m CH3,5.357000,,0.600000,,88.5,88.5,023,NN,USB,5.00,,,,,
diff --git a/stock_configs/US Calling Frequencies.csv b/stock_configs/US Calling Frequencies.csv
index 4b8b804..d597d86 100644
--- a/stock_configs/US Calling Frequencies.csv
+++ b/stock_configs/US Calling Frequencies.csv
@@ -1,4 +1,4 @@
-Location,Name,Frequency,Duplex,Offset,Tone,rToneFreq,cToneFreq,DtcsCode,DtcsPolarity,Mode,TStep,Skip,URCALL,RPT1CALL,RPT2CALL
+Location,Name,Frequency,Duplex,Offset,Tone,rToneFreq,cToneFreq,DtcsCode,DtcsPolarity,Mode,TStep,Skip,Comment,URCALL,RPT1CALL,RPT2CALL
1,6m Call,52.525000,,0.000000,,88.5,88.5,023,NN,FM,5.00,,,,,
2,2m Call,146.520000,,0.000000,,88.5,88.5,023,NN,FM,5.00,,,,,
3,220 Call,223.500000,,0.000000,,88.5,88.5,023,NN,FM,5.00,,,,,
diff --git a/stock_configs/US FRS and GMRS Channels.csv b/stock_configs/US FRS and GMRS Channels.csv
index bfa250a..f32fbc7 100644
--- a/stock_configs/US FRS and GMRS Channels.csv
+++ b/stock_configs/US FRS and GMRS Channels.csv
@@ -1,23 +1,23 @@
-Location,Name,Frequency,Duplex,Offset,Tone,rToneFreq,cToneFreq,DtcsCode,DtcsPolarity,Mode,TStep,Skip,Comment,URCALL,RPT1CALL,RPT2CALL
-1,FRS1,462.562500,,5.000000,,88.5,88.5,023,NN,NFM,12.50,,,,,,
-2,FRS2,462.587500,,5.000000,,88.5,88.5,023,NN,NFM,12.50,,,,,,
-3,FRS3,462.612500,,5.000000,,88.5,88.5,023,NN,NFM,12.50,,,,,,
-4,FRS4,462.637500,,5.000000,,88.5,88.5,023,NN,NFM,12.50,,,,,,
-5,FRS5,462.662500,,5.000000,,88.5,88.5,023,NN,NFM,12.50,,,,,,
-6,FRS6,462.687500,,5.000000,,88.5,88.5,023,NN,NFM,12.50,,,,,,
-7,FRS7,462.712500,,5.000000,,88.5,88.5,023,NN,NFM,12.50,,,,,,
-8,FRS8,467.562500,,5.000000,,88.5,88.5,023,NN,NFM,12.50,,,,,,
-9,FRS9,467.587500,,5.000000,,88.5,88.5,023,NN,NFM,12.50,,,,,,
-10,FRS10,467.612500,,5.000000,,88.5,88.5,023,NN,NFM,12.50,,,,,,
-11,FRS11,467.637500,,5.000000,,88.5,88.5,023,NN,NFM,12.50,,,,,,
-12,FRS12,467.662500,,5.000000,,88.5,88.5,023,NN,NFM,12.50,,,,,,
-13,FRS13,467.687500,,5.000000,,88.5,88.5,023,NN,NFM,12.50,,,,,,
-14,FRS14,467.712500,,5.000000,,88.5,88.5,023,NN,NFM,12.50,,,,,,
-15,GMRS1,462.550000,,5.000000,,88.5,88.5,023,NN,FM,25.00,,,,,,
-16,GMRS2,462.575000,,5.000000,,88.5,88.5,023,NN,FM,25.00,,,,,,
-17,GMRS3,462.600000,,5.000000,,88.5,88.5,023,NN,FM,25.00,,,,,,
-18,GMRS4,462.625000,,5.000000,,88.5,88.5,023,NN,FM,25.00,,,,,,
-19,GMRS5,462.650000,,5.000000,,88.5,88.5,023,NN,FM,25.00,,,,,,
-20,GMRS6,462.675000,,5.000000,,88.5,88.5,023,NN,FM,25.00,,,,,,
-21,GMRS7,462.700000,,5.000000,,88.5,88.5,023,NN,FM,25.00,,,,,,
-22,GMRS8,462.725000,,5.000000,,88.5,88.5,023,NN,FM,25.00,,,,,,
+Location,Name,Frequency,Duplex,Offset,Tone,rToneFreq,cToneFreq,DtcsCode,DtcsPolarity,Mode,TStep,Skip,Comment,URCALL,RPT1CALL,RPT2CALL
+1,FRS1,462.562500,,5.000000,,88.5,88.5,023,NN,NFM,12.50,,,,,
+2,FRS2,462.587500,,5.000000,,88.5,88.5,023,NN,NFM,12.50,,,,,
+3,FRS3,462.612500,,5.000000,,88.5,88.5,023,NN,NFM,12.50,,,,,
+4,FRS4,462.637500,,5.000000,,88.5,88.5,023,NN,NFM,12.50,,,,,
+5,FRS5,462.662500,,5.000000,,88.5,88.5,023,NN,NFM,12.50,,,,,
+6,FRS6,462.687500,,5.000000,,88.5,88.5,023,NN,NFM,12.50,,,,,
+7,FRS7,462.712500,,5.000000,,88.5,88.5,023,NN,NFM,12.50,,,,,
+8,FRS8,467.562500,,5.000000,,88.5,88.5,023,NN,NFM,12.50,,,,,
+9,FRS9,467.587500,,5.000000,,88.5,88.5,023,NN,NFM,12.50,,,,,
+10,FRS10,467.612500,,5.000000,,88.5,88.5,023,NN,NFM,12.50,,,,,
+11,FRS11,467.637500,,5.000000,,88.5,88.5,023,NN,NFM,12.50,,,,,
+12,FRS12,467.662500,,5.000000,,88.5,88.5,023,NN,NFM,12.50,,,,,
+13,FRS13,467.687500,,5.000000,,88.5,88.5,023,NN,NFM,12.50,,,,,
+14,FRS14,467.712500,,5.000000,,88.5,88.5,023,NN,NFM,12.50,,,,,
+15,GMRS1,462.550000,,5.000000,,88.5,88.5,023,NN,FM,25.00,,,,,
+16,GMRS2,462.575000,,5.000000,,88.5,88.5,023,NN,FM,25.00,,,,,
+17,GMRS3,462.600000,,5.000000,,88.5,88.5,023,NN,FM,25.00,,,,,
+18,GMRS4,462.625000,,5.000000,,88.5,88.5,023,NN,FM,25.00,,,,,
+19,GMRS5,462.650000,,5.000000,,88.5,88.5,023,NN,FM,25.00,,,,,
+20,GMRS6,462.675000,,5.000000,,88.5,88.5,023,NN,FM,25.00,,,,,
+21,GMRS7,462.700000,,5.000000,,88.5,88.5,023,NN,FM,25.00,,,,,
+22,GMRS8,462.725000,,5.000000,,88.5,88.5,023,NN,FM,25.00,,,,,
diff --git a/stock_configs/US MURS Channels.csv b/stock_configs/US MURS Channels.csv
index 4e5384c..e6fed17 100644
--- a/stock_configs/US MURS Channels.csv
+++ b/stock_configs/US MURS Channels.csv
@@ -1,6 +1,6 @@
Location,Name,Frequency,Duplex,Offset,Tone,rToneFreq,cToneFreq,DtcsCode,DtcsPolarity,Mode,TStep,Skip,Comment,URCALL,RPT1CALL,RPT2CALL
-1,MURS 1,151.820000,,0.000000,,88.5,88.5,023,NN,NFM,5.00,,,,,,
-2,MURS 2,151.880000,,0.000000,,88.5,88.5,023,NN,NFM,5.00,,,,,,
-3,MURS 3,151.940000,,0.000000,,88.5,88.5,023,NN,NFM,5.00,,,,,,
-4,Blue Dot,154.570000,,0.000000,,88.5,88.5,023,NN,FM,5.00,,,,,,
-5,Green Dot,154.600000,,0.000000,,88.5,88.5,023,NN,FM,5.00,,,,,,
+1,MURS 1,151.820000,,0.000000,,88.5,88.5,023,NN,NFM,5.00,,,,,
+2,MURS 2,151.880000,,0.000000,,88.5,88.5,023,NN,NFM,5.00,,,,,
+3,MURS 3,151.940000,,0.000000,,88.5,88.5,023,NN,NFM,5.00,,,,,
+4,Blue Dot,154.570000,,0.000000,,88.5,88.5,023,NN,FM,5.00,,,,,
+5,Green Dot,154.600000,,0.000000,,88.5,88.5,023,NN,FM,5.00,,,,,
diff --git a/stock_configs/US Marine VHF Channels.csv b/stock_configs/US Marine VHF Channels.csv
index 5dc7369..21a6f79 100644
--- a/stock_configs/US Marine VHF Channels.csv
+++ b/stock_configs/US Marine VHF Channels.csv
@@ -1,61 +1,61 @@
Location,Name,Frequency,Duplex,Offset,Tone,rToneFreq,cToneFreq,DtcsCode,DtcsPolarity,Mode,TStep,Skip,Comment,URCALL,RPT1CALL,RPT2CALL
-1,SEA 01,160.650000,-,4.600000,,88.5,88.5,023,NN,FM,25.00,S,,,,,
-2,SEA 02,160.700000,-,4.600000,,88.5,88.5,023,NN,FM,25.00,S,,,,,
-3,SEA 03,160.750000,-,4.600000,,88.5,88.5,023,NN,FM,25.00,S,,,,,
-4,SEA 04,160.800000,-,4.600000,,88.5,88.5,023,NN,FM,25.00,S,,,,,
-5,SEA 05,160.850000,-,4.600000,,88.5,88.5,023,NN,FM,25.00,S,,,,,
-6,SEA 06,156.300000,,0.000000,,88.5,88.5,023,NN,FM,25.00,,,,,,
-7,SEA 07,160.950000,-,4.600000,,88.5,88.5,023,NN,FM,25.00,S,,,,,
-8,SEA 08,156.400000,,0.000000,,88.5,88.5,023,NN,FM,25.00,,,,,,
-9,SEA 09,156.450000,,0.000000,,88.5,88.5,023,NN,FM,25.00,,,,,,
-10,SEA 10,156.500000,,0.000000,,88.5,88.5,023,NN,FM,25.00,,,,,,
-11,SEA 11,156.550000,,0.000000,,88.5,88.5,023,NN,FM,25.00,,,,,,
-12,SEA 12,156.600000,,0.000000,,88.5,88.5,023,NN,FM,25.00,,,,,,
-13,SEA 13,156.650000,,0.000000,,88.5,88.5,023,NN,FM,25.00,,,,,,
-14,SEA 14,156.700000,,0.000000,,88.5,88.5,023,NN,FM,25.00,,,,,,
-15,SEA 15,156.750000,,0.000000,,88.5,88.5,023,NN,FM,25.00,,,,,,
-16,SEA 16,156.800000,,0.000000,,88.5,88.5,023,NN,FM,25.00,,,,,,
-17,SEA 17,156.850000,,0.000000,,88.5,88.5,023,NN,FM,25.00,,,,,,
-18,SEA 18,161.500000,-,4.600000,,88.5,88.5,023,NN,FM,25.00,S,,,,,
-19,SEA 19,161.550000,-,4.600000,,88.5,88.5,023,NN,FM,25.00,S,,,,,
-20,SEA 20,161.600000,-,4.600000,,88.5,88.5,023,NN,FM,25.00,,,,,,
-21,SEA 21,161.650000,-,4.600000,,88.5,88.5,023,NN,FM,25.00,S,,,,,
-22,SEA 22,161.700000,-,4.600000,,88.5,88.5,023,NN,FM,25.00,S,,,,,
-23,SEA 23,161.750000,-,4.600000,,88.5,88.5,023,NN,FM,25.00,S,,,,,
-24,SEA 24,161.800000,-,4.600000,,88.5,88.5,023,NN,FM,25.00,S,,,,,
-25,SEA 25,161.850000,-,4.600000,,88.5,88.5,023,NN,FM,25.00,S,,,,,
-26,SEA 26,161.900000,-,4.600000,,88.5,88.5,023,NN,FM,25.00,S,,,,,
-27,SEA 27,161.950000,-,4.600000,,88.5,88.5,023,NN,FM,25.00,S,,,,,
-28,SEA 28,162.000000,-,4.600000,,88.5,88.5,023,NN,FM,25.00,S,,,,,
-29,SEA 60,160.625000,-,4.600000,,88.5,88.5,023,NN,FM,25.00,S,,,,,
-30,SEA 61,160.675000,-,4.600000,,88.5,88.5,023,NN,FM,25.00,S,,,,,
-31,SEA 62,160.725000,-,4.600000,,88.5,88.5,023,NN,FM,25.00,S,,,,,
-32,SEA 63,160.775000,-,4.600000,,88.5,88.5,023,NN,FM,25.00,S,,,,,
-33,SEA 64,160.825000,-,4.600000,,88.5,88.5,023,NN,FM,25.00,S,,,,,
-34,SEA 65,160.875000,-,4.600000,,88.5,88.5,023,NN,FM,25.00,S,,,,,
-35,SEA 66,160.925000,-,4.600000,,88.5,88.5,023,NN,FM,25.00,S,,,,,
-36,SEA 67,156.375000,,0.000000,,88.5,88.5,023,NN,FM,25.00,,,,,,
-37,SEA 68,156.425000,,0.000000,,88.5,88.5,023,NN,FM,25.00,,,,,,
-38,SEA 69,156.475000,,0.000000,,88.5,88.5,023,NN,FM,25.00,,,,,,
-39,DSC 70,156.525000,,0.000000,,88.5,88.5,023,NN,FM,25.00,,,,,,
-40,SEA 71,156.575000,,0.000000,,88.5,88.5,023,NN,FM,25.00,,,,,,
-41,SEA 72,156.625000,,0.000000,,88.5,88.5,023,NN,FM,25.00,,,,,,
-42,SEA 73,156.675000,,0.000000,,88.5,88.5,023,NN,FM,25.00,,,,,,
-43,SEA 74,156.725000,,0.000000,,88.5,88.5,023,NN,FM,25.00,,,,,,
-44,SEA 77,156.875000,,0.000000,,88.5,88.5,023,NN,FM,25.00,,,,,,
-45,SEA 78,161.525000,-,4.600000,,88.5,88.5,023,NN,FM,25.00,S,,,,,
-46,SEA 79,161.575000,-,4.600000,,88.5,88.5,023,NN,FM,25.00,S,,,,,
-47,SEA 80,161.625000,-,4.600000,,88.5,88.5,023,NN,FM,25.00,S,,,,,
-48,SEA 81,161.675000,-,4.600000,,88.5,88.5,023,NN,FM,25.00,S,,,,,
-49,SEA 82,161.725000,-,4.600000,,88.5,88.5,023,NN,FM,25.00,S,,,,,
-50,SEA 83,161.775000,-,4.600000,,88.5,88.5,023,NN,FM,25.00,S,,,,,
-51,SEA 84,161.825000,-,4.600000,,88.5,88.5,023,NN,FM,25.00,S,,,,,
-52,SEA 85,161.875000,-,4.600000,,88.5,88.5,023,NN,FM,25.00,S,,,,,
-53,SEA 86,161.925000,-,4.600000,,88.5,88.5,023,NN,FM,25.00,S,,,,,
-54,AIS 87,161.975000,-,4.600000,,88.5,88.5,023,NN,FM,25.00,S,,,,,
-55,AIS 88,162.025000,-,4.600000,,88.5,88.5,023,NN,FM,25.00,S,,,,,
-56,SEA F1,155.625000,,0.000000,,88.5,88.5,023,NN,FM,25.00,,,,,,
-57,SEA F2,155.775000,,0.000000,,88.5,88.5,023,NN,FM,25.00,,,,,,
-58,SEA F3,155.825000,,0.000000,,88.5,88.5,023,NN,FM,25.00,,,,,,
-59,SEA L1,155.500000,,0.000000,,88.5,88.5,023,NN,FM,25.00,,,,,,
-60,SEA L2,155.525000,,0.000000,,88.5,88.5,023,NN,FM,25.00,,,,,,
+1,SEA 01,160.650000,-,4.600000,,88.5,88.5,023,NN,FM,25.00,,,,,
+2,SEA 02,160.700000,-,4.600000,,88.5,88.5,023,NN,FM,25.00,,,,,
+3,SEA 03,160.750000,-,4.600000,,88.5,88.5,023,NN,FM,25.00,,,,,
+4,SEA 04,160.800000,-,4.600000,,88.5,88.5,023,NN,FM,25.00,,,,,
+5,SEA 05,160.850000,-,4.600000,,88.5,88.5,023,NN,FM,25.00,,,,,
+6,SEA 06,156.300000,,0.000000,,88.5,88.5,023,NN,FM,25.00,,,,,
+7,SEA 07,160.950000,-,4.600000,,88.5,88.5,023,NN,FM,25.00,,,,,
+8,SEA 08,156.400000,,0.000000,,88.5,88.5,023,NN,FM,25.00,,,,,
+9,SEA 09,156.450000,,0.000000,,88.5,88.5,023,NN,FM,25.00,,,,,
+10,SEA 10,156.500000,,0.000000,,88.5,88.5,023,NN,FM,25.00,,,,,
+11,SEA 11,156.550000,,0.000000,,88.5,88.5,023,NN,FM,25.00,,,,,
+12,SEA 12,156.600000,,0.000000,,88.5,88.5,023,NN,FM,25.00,,,,,
+13,SEA 13,156.650000,,0.000000,,88.5,88.5,023,NN,FM,25.00,,,,,
+14,SEA 14,156.700000,,0.000000,,88.5,88.5,023,NN,FM,25.00,,,,,
+15,SEA 15,156.750000,,0.000000,,88.5,88.5,023,NN,FM,25.00,,,,,
+16,SEA 16,156.800000,,0.000000,,88.5,88.5,023,NN,FM,25.00,,,,,
+17,SEA 17,156.850000,,0.000000,,88.5,88.5,023,NN,FM,25.00,,,,,
+18,SEA 18,161.500000,-,4.600000,,88.5,88.5,023,NN,FM,25.00,,,,,
+19,SEA 19,161.550000,-,4.600000,,88.5,88.5,023,NN,FM,25.00,,,,,
+20,SEA 20,161.600000,-,4.600000,,88.5,88.5,023,NN,FM,25.00,,,,,
+21,SEA 21,161.650000,-,4.600000,,88.5,88.5,023,NN,FM,25.00,,,,,
+22,SEA 22,161.700000,-,4.600000,,88.5,88.5,023,NN,FM,25.00,,,,,
+23,SEA 23,161.750000,-,4.600000,,88.5,88.5,023,NN,FM,25.00,,,,,
+24,SEA 24,161.800000,-,4.600000,,88.5,88.5,023,NN,FM,25.00,,,,,
+25,SEA 25,161.850000,-,4.600000,,88.5,88.5,023,NN,FM,25.00,,,,,
+26,SEA 26,161.900000,-,4.600000,,88.5,88.5,023,NN,FM,25.00,,,,,
+27,SEA 27,161.950000,-,4.600000,,88.5,88.5,023,NN,FM,25.00,,,,,
+28,SEA 28,162.000000,-,4.600000,,88.5,88.5,023,NN,FM,25.00,,,,,
+29,SEA 60,160.625000,-,4.600000,,88.5,88.5,023,NN,FM,25.00,,,,,
+30,SEA 61,160.675000,-,4.600000,,88.5,88.5,023,NN,FM,25.00,,,,,
+31,SEA 62,160.725000,-,4.600000,,88.5,88.5,023,NN,FM,25.00,,,,,
+32,SEA 63,160.775000,-,4.600000,,88.5,88.5,023,NN,FM,25.00,,,,,
+33,SEA 64,160.825000,-,4.600000,,88.5,88.5,023,NN,FM,25.00,,,,,
+34,SEA 65,160.875000,-,4.600000,,88.5,88.5,023,NN,FM,25.00,,,,,
+35,SEA 66,160.925000,-,4.600000,,88.5,88.5,023,NN,FM,25.00,,,,,
+36,SEA 67,156.375000,,0.000000,,88.5,88.5,023,NN,FM,25.00,,,,,
+37,SEA 68,156.425000,,0.000000,,88.5,88.5,023,NN,FM,25.00,,,,,
+38,SEA 69,156.475000,,0.000000,,88.5,88.5,023,NN,FM,25.00,,,,,
+39,DSC 70,156.525000,,0.000000,,88.5,88.5,023,NN,FM,25.00,,,,,
+40,SEA 71,156.575000,,0.000000,,88.5,88.5,023,NN,FM,25.00,,,,,
+41,SEA 72,156.625000,,0.000000,,88.5,88.5,023,NN,FM,25.00,,,,,
+42,SEA 73,156.675000,,0.000000,,88.5,88.5,023,NN,FM,25.00,,,,,
+43,SEA 74,156.725000,,0.000000,,88.5,88.5,023,NN,FM,25.00,,,,,
+44,SEA 77,156.875000,,0.000000,,88.5,88.5,023,NN,FM,25.00,,,,,
+45,SEA 78,161.525000,-,4.600000,,88.5,88.5,023,NN,FM,25.00,,,,,
+46,SEA 79,161.575000,-,4.600000,,88.5,88.5,023,NN,FM,25.00,,,,,
+47,SEA 80,161.625000,-,4.600000,,88.5,88.5,023,NN,FM,25.00,,,,,
+48,SEA 81,161.675000,-,4.600000,,88.5,88.5,023,NN,FM,25.00,,,,,
+49,SEA 82,161.725000,-,4.600000,,88.5,88.5,023,NN,FM,25.00,,,,,
+50,SEA 83,161.775000,-,4.600000,,88.5,88.5,023,NN,FM,25.00,,,,,
+51,SEA 84,161.825000,-,4.600000,,88.5,88.5,023,NN,FM,25.00,,,,,
+52,SEA 85,161.875000,-,4.600000,,88.5,88.5,023,NN,FM,25.00,,,,,
+53,SEA 86,161.925000,-,4.600000,,88.5,88.5,023,NN,FM,25.00,,,,,
+54,AIS 87,161.975000,-,4.600000,,88.5,88.5,023,NN,FM,25.00,,,,,
+55,AIS 88,162.025000,-,4.600000,,88.5,88.5,023,NN,FM,25.00,,,,,
+56,SEA F1,155.625000,,0.000000,,88.5,88.5,023,NN,FM,25.00,,,,,
+57,SEA F2,155.775000,,0.000000,,88.5,88.5,023,NN,FM,25.00,,,,,
+58,SEA F3,155.825000,,0.000000,,88.5,88.5,023,NN,FM,25.00,,,,,
+59,SEA L1,155.500000,,0.000000,,88.5,88.5,023,NN,FM,25.00,,,,,
+60,SEA L2,155.525000,,0.000000,,88.5,88.5,023,NN,FM,25.00,,,,,
--
Alioth's /usr/local/bin/git-commit-notice on /srv/git.debian.org/git/pkg-hamradio/chirp.git
More information about the pkg-hamradio-commits
mailing list