Here is the factory image from the Radtel T18 hand held radio.
Jim KC9HI
On Wed, Jul 5, 2017 at 9:29 PM, Jim Unroe rock.unroe@gmail.com wrote:
# HG changeset patch # User Jim Unroe rock.unroe@gmail.com # Date 1499304522 14400 # Node ID a6eea51cc96a13c63737c5ef83bb48a66b6736ea # Parent 0b2aaa54f3082a8fe735b4e32091466c50b9900b [New Model] Add Radtel Model T18
This patch adds support for programming the channels and global settings of the Radtel model T18 hand held radio.
#4969
diff -r 0b2aaa54f308 -r a6eea51cc96a chirp/drivers/radtel_t18.py --- /dev/null Thu Jan 01 00:00:00 1970 +0000 +++ b/chirp/drivers/radtel_t18.py Wed Jul 05 21:28:42 2017 -0400 @@ -0,0 +1,501 @@ +# Copyright 2017 Jim Unroe rock.unroe@gmail.com +# +# This program is free software: you can redistribute it and/or modify +# it under the terms of the GNU General Public License as published by +# the Free Software Foundation, either version 2 of the License, or +# (at your option) any later version. +# +# This program is distributed in the hope that it will be useful, +# but WITHOUT ANY WARRANTY; without even the implied warranty of +# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the +# GNU General Public License for more details. +# +# You should have received a copy of the GNU General Public License +# along with this program. If not, see http://www.gnu.org/licenses/.
+import time +import os +import struct +import unittest +import logging
+from chirp import chirp_common, directory, memmap +from chirp import bitwise, errors, util +from chirp.settings import RadioSetting, RadioSettingGroup, \
- RadioSettingValueInteger, RadioSettingValueList, \
- RadioSettingValueBoolean, RadioSettings
+LOG = logging.getLogger(__name__)
+MEM_FORMAT = """ +#seekto 0x0010; +struct {
- lbcd rxfreq[4];
- lbcd txfreq[4];
- lbcd rxtone[2];
- lbcd txtone[2];
- u8 unknown1:1,
compander:1,
scramble:1,
skip:1,
highpower:1,
narrow:1,
unknown2:1,
bcl:1;
- u8 unknown3[3];
+} memory[16]; +#seekto 0x03C0; +struct {
- u8 unknown1:1,
scanmode:1,
unknown2:2,
voiceprompt:2,
batterysaver:1,
beep:1;
- u8 squelchlevel;
- u8 unused2;
- u8 timeouttimer;
- u8 voxlevel;
- u8 unknown3;
- u8 unused;
- u8 voxdelay;
+} settings; +"""
+CMD_ACK = "\x06" +BLOCK_SIZE = 0x08
+VOICE_LIST = ["Off", "Chinese", "English"] +TIMEOUTTIMER_LIST = ["Off", "30 seconds", "60 seconds", "90 seconds",
"120 seconds", "150 seconds", "180 seconds",
"210 seconds", "240 seconds", "270 seconds",
"300 seconds"]
+SCANMODE_LIST = ["Carrier", "Time"] +VOXLEVEL_LIST = ["Off", "1", "2", "3", "4", "5", "6", "7", "8", "9"] +VOXDELAY_LIST = ["0.5 seconds", "1.0 seconds", "1.5 seconds",
"2.0 seconds", "2.5 seconds", "3.0 seconds"]
+SETTING_LISTS = {
- "voice": VOICE_LIST,
- "timeouttimer": TIMEOUTTIMER_LIST,
- "scanmode": SCANMODE_LIST,
- "voxlevel": VOXLEVEL_LIST,
- "voxdelay": VOXDELAY_LIST
+}
+def _t18_enter_programming_mode(radio):
- serial = radio.pipe
- try:
serial.write("\x02")
time.sleep(0.1)
serial.write("1ROGRAM")
ack = serial.read(1)
- except:
raise errors.RadioError("Error communicating with radio")
- if not ack:
raise errors.RadioError("No response from radio")
- elif ack != CMD_ACK:
raise errors.RadioError("Radio refused to enter programming mode")
- try:
serial.write("\x02")
ident = serial.read(8)
- except:
raise errors.RadioError("Error communicating with radio")
- if not ident.startswith("SMP558"):
LOG.debug(util.hexprint(ident))
raise errors.RadioError("Radio returned unknown identification string")
- try:
serial.write(CMD_ACK)
ack = serial.read(1)
- except:
raise errors.RadioError("Error communicating with radio")
- if ack != CMD_ACK:
raise errors.RadioError("Radio refused to enter programming mode")
- try:
serial.write("\x05")
response = serial.read(6)
- except:
raise errors.RadioError("Error communicating with radio")
- if not response == ("\xFF" * 6):
LOG.debug(util.hexprint(response))
raise errors.RadioError("Radio returned unexpected response")
- try:
serial.write(CMD_ACK)
ack = serial.read(1)
- except:
raise errors.RadioError("Error communicating with radio")
- if ack != CMD_ACK:
raise errors.RadioError("Radio refused to enter programming mode")
+def _t18_exit_programming_mode(radio):
- serial = radio.pipe
- try:
serial.write("b")
- except:
raise errors.RadioError("Radio refused to exit programming mode")
+def _t18_read_block(radio, block_addr, block_size):
- serial = radio.pipe
- cmd = struct.pack(">cHb", 'R', block_addr, BLOCK_SIZE)
- expectedresponse = "W" + cmd[1:]
- LOG.debug("Reading block %04x..." % (block_addr))
- try:
serial.write(cmd)
response = serial.read(4 + BLOCK_SIZE)
if response[:4] != expectedresponse:
raise Exception("Error reading block %04x." % (block_addr))
block_data = response[4:]
serial.write(CMD_ACK)
ack = serial.read(1)
- except:
raise errors.RadioError("Failed to read block at %04x" % block_addr)
- if ack != CMD_ACK:
raise Exception("No ACK reading block %04x." % (block_addr))
- return block_data
+def _t18_write_block(radio, block_addr, block_size):
- serial = radio.pipe
- cmd = struct.pack(">cHb", 'W', block_addr, BLOCK_SIZE)
- data = radio.get_mmap()[block_addr:block_addr + 8]
- LOG.debug("Writing Data:")
- LOG.debug(util.hexprint(cmd + data))
- try:
serial.write(cmd + data)
if serial.read(1) != CMD_ACK:
raise Exception("No ACK")
- except:
raise errors.RadioError("Failed to send block "
"to radio at %04x" % block_addr)
+def do_download(radio):
- LOG.debug("download")
- _t18_enter_programming_mode(radio)
- data = ""
- status = chirp_common.Status()
- status.msg = "Cloning from radio"
- status.cur = 0
- status.max = radio._memsize
- for addr in range(0, radio._memsize, BLOCK_SIZE):
status.cur = addr + BLOCK_SIZE
radio.status_fn(status)
block = _t18_read_block(radio, addr, BLOCK_SIZE)
data += block
LOG.debug("Address: %04x" % addr)
LOG.debug(util.hexprint(block))
- _t18_exit_programming_mode(radio)
- return memmap.MemoryMap(data)
+def do_upload(radio):
- status = chirp_common.Status()
- status.msg = "Uploading to radio"
- _t18_enter_programming_mode(radio)
- status.cur = 0
- status.max = radio._memsize
- for start_addr, end_addr in radio._ranges:
for addr in range(start_addr, end_addr, BLOCK_SIZE):
status.cur = addr + BLOCK_SIZE
radio.status_fn(status)
_t18_write_block(radio, addr, BLOCK_SIZE)
- _t18_exit_programming_mode(radio)
+def model_match(cls, data):
- """Match the opened/downloaded image to the correct version"""
- if len(data) == cls._memsize:
rid = data[0x03D0:0x03D8]
return "P558" in rid
- else:
return False
+@directory.register +class T18Radio(chirp_common.CloneModeRadio):
- """radtel T18"""
- VENDOR = "Radtel"
- MODEL = "T18"
- BAUD_RATE = 9600
- _ranges = [
(0x0000, 0x03F0),
- ]
- _memsize = 0x03F0
- def get_features(self):
rf = chirp_common.RadioFeatures()
rf.has_settings = True
rf.valid_modes = ["NFM", "FM"] # 12.5 KHz, 25 kHz.
rf.valid_skips = ["", "S"]
rf.valid_tmodes = ["", "Tone", "TSQL", "DTCS", "Cross"]
rf.valid_duplexes = ["", "-", "+", "split", "off"]
rf.can_odd_split = True
rf.has_rx_dtcs = True
rf.has_ctone = True
rf.has_cross = True
rf.valid_cross_modes = [
"Tone->Tone",
"DTCS->",
"->DTCS",
"Tone->DTCS",
"DTCS->Tone",
"->Tone",
"DTCS->DTCS"]
rf.has_tuning_step = False
rf.has_bank = False
rf.has_name = False
rf.memory_bounds = (1, 16)
rf.valid_bands = [(400000000, 470000000)]
return rf
- def process_mmap(self):
self._memobj = bitwise.parse(MEM_FORMAT, self._mmap)
- def sync_in(self):
self._mmap = do_download(self)
self.process_mmap()
- def sync_out(self):
do_upload(self)
- def get_raw_memory(self, number):
return repr(self._memobj.memory[number - 1])
- def _decode_tone(self, val):
val = int(val)
if val == 16665:
return '', None, None
elif val >= 12000:
return 'DTCS', val - 12000, 'R'
elif val >= 8000:
return 'DTCS', val - 8000, 'N'
else:
return 'Tone', val / 10.0, None
- def _encode_tone(self, memval, mode, value, pol):
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_memory(self, number):
_mem = self._memobj.memory[number - 1]
mem = chirp_common.Memory()
mem.number = number
mem.freq = int(_mem.rxfreq) * 10
# We'll consider any blank (i.e. 0MHz frequency) to be empty
if mem.freq == 0:
mem.empty = True
return mem
if _mem.rxfreq.get_raw() == "\xFF\xFF\xFF\xFF":
mem.freq = 0
mem.empty = True
return mem
if _mem.txfreq.get_raw() == "\xFF\xFF\xFF\xFF":
mem.duplex = "off"
mem.offset = 0
elif int(_mem.rxfreq) == int(_mem.txfreq):
mem.duplex = ""
mem.offset = 0
else:
mem.duplex = int(_mem.rxfreq) > int(_mem.txfreq) and "-" or "+"
mem.offset = abs(int(_mem.rxfreq) - int(_mem.txfreq)) * 10
mem.mode = not _mem.narrow and "FM" or "NFM"
mem.skip = _mem.skip and "S" or ""
txtone = self._decode_tone(_mem.txtone)
rxtone = self._decode_tone(_mem.rxtone)
chirp_common.split_tone_decode(mem, txtone, rxtone)
mem.extra = RadioSettingGroup("Extra", "extra")
rs = RadioSetting("bcl", "Busy Channel Lockout",
RadioSettingValueBoolean(not _mem.bcl))
mem.extra.append(rs)
rs = RadioSetting("scramble", "Scramble",
RadioSettingValueBoolean(not _mem.scramble))
mem.extra.append(rs)
rs = RadioSetting("compander", "Compander",
RadioSettingValueBoolean(not _mem.compander))
mem.extra.append(rs)
return mem
- def set_memory(self, mem):
# Get a low-level memory object mapped to the image
_mem = self._memobj.memory[mem.number - 1]
if mem.empty:
_mem.set_raw("\xFF" * (_mem.size() / 8))
return
_mem.rxfreq = mem.freq / 10
if mem.duplex == "off":
for i in range(0, 4):
_mem.txfreq[i].set_raw("\xFF")
elif mem.duplex == "split":
_mem.txfreq = mem.offset / 10
elif 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
txtone, rxtone = chirp_common.split_tone_encode(mem)
self._encode_tone(_mem.txtone, *txtone)
self._encode_tone(_mem.rxtone, *rxtone)
_mem.narrow = 'N' in mem.mode
_mem.skip = mem.skip == "S"
for setting in mem.extra:
# NOTE: Only three settings right now, all are inverted
setattr(_mem, setting.get_name(), not int(setting.value))
- def get_settings(self):
_settings = self._memobj.settings
basic = RadioSettingGroup("basic", "Basic Settings")
top = RadioSettings(basic)
rs = RadioSetting("squelchlevel", "Squelch level",
RadioSettingValueInteger(
0, 9, _settings.squelchlevel))
basic.append(rs)
rs = RadioSetting("timeouttimer", "Timeout timer",
RadioSettingValueList(
TIMEOUTTIMER_LIST,
TIMEOUTTIMER_LIST[
_settings.timeouttimer]))
basic.append(rs)
rs = RadioSetting("scanmode", "Scan mode",
RadioSettingValueList(
SCANMODE_LIST,
SCANMODE_LIST[_settings.scanmode]))
basic.append(rs)
rs = RadioSetting("voiceprompt", "Voice prompt",
RadioSettingValueList(
VOICE_LIST,
VOICE_LIST[_settings.voiceprompt]))
basic.append(rs)
rs = RadioSetting("voxlevel", "Vox level",
RadioSettingValueList(
VOXLEVEL_LIST,
VOXLEVEL_LIST[_settings.voxlevel]))
basic.append(rs)
rs = RadioSetting("voxdelay", "VOX delay",
RadioSettingValueList(
VOXDELAY_LIST,
VOXDELAY_LIST[_settings.voxdelay]))
basic.append(rs)
rs = RadioSetting("batterysaver", "Battery saver",
RadioSettingValueBoolean(_settings.batterysaver))
basic.append(rs)
rs = RadioSetting("beep", "Beep",
RadioSettingValueBoolean(_settings.beep))
basic.append(rs)
return top
- def set_settings(self, settings):
for element in settings:
if not isinstance(element, RadioSetting):
self.set_settings(element)
continue
else:
try:
if "." in element.get_name():
bits = element.get_name().split(".")
obj = self._memobj
for bit in bits[:-1]:
obj = getattr(obj, bit)
setting = bits[-1]
else:
obj = self._memobj.settings
setting = element.get_name()
if element.has_apply_callback():
LOG.debug("Using apply callback")
element.run_apply_callback()
else:
LOG.debug("Setting %s = %s" % (setting, element.value))
setattr(obj, setting, element.value)
except Exception, e:
LOG.debug(element.get_name())
raise
- @classmethod
- def match_model(cls, filedata, filename):
match_size = False
match_model = False
# testing the file data size
if len(filedata) == cls._memsize:
match_size = True
# testing the model fingerprint
match_model = model_match(cls, filedata)
if match_size and match_model:
return True
else:
return False