[chirp_devel] It should be added to the program a new driver KYD IP-620
Alex K
Tue Sep 29 16:57:55 PDT 2015
I long ago left a request to add a new radio (IP-620) to CHIRP.
http://chirp.danplanet.com/issues/2033 I learned the basics pyton, a little
bit to understand the code CHIRP now graduated driver radio. Please look at
it and if you are satisfied, then include it in the repository.
-------------- next part --------------
An HTML attachment was scrubbed...
URL: http://intrepid.danplanet.com/pipermail/chirp_devel/attachments/20150930/2be9a774/attachment-0001.html
-------------- next part --------------
# Copyright 2015 Lepik.stv <lepik.stv at gmail.com>
# based on modification of Dan Smith's and Jim Unroe original work
#
# 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/>.
"""KYD IP-620 radios management module"""
# TODO: Power on message
# TODO: Channel Name
import struct
import time
import os
import logging
from chirp import util, chirp_common, bitwise, memmap, errors, directory
from chirp.settings import RadioSetting, RadioSettingGroup, \
RadioSettingValueBoolean, RadioSettingValueList, \
RadioSettingValueInteger, RadioSettingValueString, \
RadioSettings
LOG = logging.getLogger(__name__)
IP620_MEM_FORMAT = """
#seekto 0x0000;
struct { // Channel memory structure
lbcd rx_freq[4]; // RX frequency
lbcd tx_freq[4]; // TX frequency
ul16 rx_tone; // RX tone
ul16 tx_tone; // TX tone
u8 unknown_1:4 // n-a
busy_loc:2, // NO-00, Crrier wave-01, SM-10
n_a:2; // n-a
u8 unknown_2:1 // n-a
scan_add:1, // Scan add
n_a:1, // n-a
w_n:1, // Narrow-0 Wide-1
lout:1, // LOCKOUT OFF-0 ON-1
n_a_:1, // n-a
power:2; // Power low-00 middle-01 high-10
u8 unknown_3; // n-a
u8 unknown_4; // n-a
} memory[200];
#seekto 0x1000;
struct {
u8 chan_name[6];
u8 unknown_1[10];
} chan_names[200];
#seekto 0x0C80;
struct { // Settings memory structure ( A-Frequency mode )
lbcd freq_a_rx[4];
lbcd freq_a_tx[4];
ul16 freq_a_rx_tone; // RX tone
ul16 freq_a_tx_tone; // TX tone
u8 unknown_1_5:4
freq_a_busy_loc:2,
n_a:2;
u8 unknown_1_6:3
freq_a_w_n:1,
n_a:1,
na:1,
freq_a_power:2;
u8 unknown_1_7;
u8 unknown_1_8;
} settings_freq_a;
#seekto 0x0E20;
struct {
u8 chan_disp_way; // ok
u8 step_freq; // ok
u8 rf_sql; // ok
u8 bat_save; // ok
u8 chan_pri; // ok
u8 end_beep; // ok
u8 tot; // ok
u8 vox; // ok
u8 chan_pri_num; // ok
u8 n_a_2;
u8 ch_mode; // ok
u8 n_a_3;
u8 call_tone; // ok
u8 beep; // ok
u8 unknown_1_1[2];
u8 unknown_1_2[8];
u8 scan_rev; // ok
u8 unknown_1_3[2];
u8 enc; // ok
u8 vox_dly; // ok
u8 wait_back_light;// ok
u8 unknown_1_4[2];
} settings;
#seekto 0x0E40;
struct {
u8 fm_radio; // ok
u8 auto_lock; // ok
u8 unknown_1[8];
u8 pon_msg[6];
} settings_misc;
#seekto 0x1C80;
struct {
u8 unknown_1[16];
u8 unknown_2[16];
} settings_radio_3;
"""
CMD_ACK = "\x06"
WRITE_BLOCK_SIZE = 0x10
READ_BLOCK_SIZE = 0x40
CHAR_LENGTH_MAX = 6
OFF_ON_LIST = ["OFF", "ON"]
ON_OFF_LIST = ["ON", "OFF"]
NO_YES_LIST = ["NO", "YES"]
STEPS = [5.0, 6.25, 10.0, 12.5, 25.0]
STEP_LIST = [str(x) for x in STEPS]
BAT_SAVE_LIST = ["OFF", "0.2 Sec", "0.4 Sec", "0.6 Sec", "0.8 Sec","1.0 Sec"]
SHIFT_LIST = ["", "-", "+"]
SCANM_LIST = ["Time", "Carrier wave", "Search"]
ENDBEEP_LIST = ["OFF", "Begin", "End", "Begin/End"]
POWER_LEVELS = [chirp_common.PowerLevel("Low", watts=1.00), chirp_common.PowerLevel("Medium", watts=2.50), chirp_common.PowerLevel("High", watts=5.00)]
TIMEOUT_LIST = ["OFF", "1 Min", "3 Min", "10 Min"]
TOTALERT_LIST = ["", "OFF"] + ["%s seconds" % x for x in range(1, 11)]
VOX_LIST = ["OFF"] + ["%s" % x for x in range(1, 17)]
VOXDELAY_LIST = ["0.3 Sec", "0.5 Sec", "1.0 Sec", "1.5 Sec", "2.0 Sec", "3.0 Sec", "4.0 Sec", "5.0 Sec"]
PRI_NUM = [3, 5, 8, 10]
PRI_NUM_LIST = [str(x) for x in PRI_NUM]
CH_FLAG_LIST = ["Channel+Freq", "Channel+Name"]
BACKLIGHT_LIST = ["Always Off", "Auto", "Always On"]
BUSYLOCK_LIST = ["NO", "Carrier", "SM"]
KEYBLOCK_LIST = ["Manual", "Auto"]
CALLTONE_LIST = ["OFF", "1", "2", "3", "4", "5", "6", "7", "8", "1750"]
RFSQL_LIST = ["OFF", "S-1", "S-2", "S-3", "S-4", "S-5", "S-6","S-7", "S-8", "S-FULL"]
IP620_CHARSET = "0123456789ABCDEFGHIJKLMNOPQRSTUVWXYZ?+-* "
IP620_BANDS = [
(136000000, 174000000),
(200000000, 260000000),
(300000000, 340000000), # <--- this band supports only Russian model (ARGUT A-36)
(350000000, 390000000),
(400000000, 480000000),
(420000000, 510000000),
(450000000, 520000000),
]
@directory.register
class IP620Radio(chirp_common.CloneModeRadio,
chirp_common.ExperimentalRadio):
"""KYD IP-620"""
VENDOR = "KYD"
MODEL = "IP-620"
BAUD_RATE = 9600
_ranges = [
(0x0000, 0x2000),
]
_memsize = 0x2000
def _ip620_exit_programming_mode(self):
try:
self.pipe.write("\x06")
except errors.RadioError:
raise
except Exception, e:
raise errors.RadioError("Radio refused to exit programming mode: %s" % e)
def _ip620_enter_programming_mode(self):
try:
self.pipe.write("iUHOUXUN")
self.pipe.write("\x02")
time.sleep(0.2)
_ack = self.pipe.read(1)
except errors.RadioError:
raise
except Exception, e:
raise errors.RadioError("Error communicating with radio: %s" % e)
if not _ack:
raise errors.RadioError("No response from radio")
elif _ack != CMD_ACK:
raise errors.RadioError("Radio refused to enter programming mode")
try:
self.pipe.write("\x02")
_ident = self.pipe.read(8)
except errors.RadioError:
raise
except Exception, e:
raise errors.RadioError("Error communicating with radio: %s" % e)
if not _ident.startswith("\x06\x4B\x47\x36\x37\x01\x56\xF8"):
print util.hexprint(_ident)
raise errors.RadioError("Radio returned unknown identification string")
try:
self.pipe.write(CMD_ACK)
_ack = self.pipe.read(1)
except errors.RadioError:
raise
except Exception, e:
raise errors.RadioError("Error communicating with radio: %s" % e)
if _ack != CMD_ACK:
raise errors.RadioError("Radio refused to enter programming mode")
def _ip620_write_block(self, block_addr):
_cmd = struct.pack(">cHb", 'W', block_addr, WRITE_BLOCK_SIZE)
_data = self.get_mmap()[block_addr:block_addr + WRITE_BLOCK_SIZE]
LOG.debug("Writing Data:")
LOG.debug(util.hexprint(_cmd + _data))
try:
self.pipe.write(_cmd + _data)
if self.pipe.read(1) != CMD_ACK:
raise Exception("No ACK")
except:
raise errors.RadioError("Failed to send block "
"to radio at %04x" % block_addr)
def _ip620_read_block(self, block_addr):
_cmd = struct.pack(">cHb", 'R', block_addr, READ_BLOCK_SIZE)
_expectedresponse = "W" + _cmd[1:]
LOG.debug("Reading block %04x..." % (block_addr))
try:
self.pipe.write(_cmd)
_response = self.pipe.read(4 + READ_BLOCK_SIZE)
if _response[:4] != _expectedresponse:
raise Exception("Error reading block %04x." % (block_addr))
_block_data = _response[4:]
self.pipe.write(CMD_ACK)
_ack = self.pipe.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 _do_download(self):
self._ip620_enter_programming_mode()
_data = ""
_status = chirp_common.Status()
_status.msg = "Cloning from radio"
_status.cur = 0
_status.max = self._memsize
for _addr in range(0, self._memsize, READ_BLOCK_SIZE):
_status.cur = _addr + READ_BLOCK_SIZE
self.status_fn(_status)
_block = self._ip620_read_block(_addr)
_data += _block
LOG.debug("Address: %04x" % _addr)
LOG.debug(util.hexprint(_block))
self._ip620_exit_programming_mode()
return memmap.MemoryMap(_data)
def _do_upload(self):
_status = chirp_common.Status()
_status.msg = "Uploading to radio"
self._ip620_enter_programming_mode()
_status.cur = 0
_status.max = self._memsize
for _start_addr, _end_addr in self._ranges:
for _addr in range(_start_addr, _end_addr, WRITE_BLOCK_SIZE):
_status.cur = _addr + WRITE_BLOCK_SIZE
self.status_fn(_status)
self._ip620_write_block(_addr)
self._ip620_exit_programming_mode()
@classmethod
def get_prompts(cls):
rp = chirp_common.RadioPrompts()
rp.experimental = ("This radio driver is currently under development. "
"There are no known issues with it, but you should "
"proceed with caution. However, proceed at your own risk!")
return rp
def get_features(self):
rf = chirp_common.RadioFeatures()
rf.has_settings = True
rf.has_bank = False
rf.has_ctone = False
rf.has_cross = False
rf.has_rx_dtcs = False
rf.has_tuning_step = False
rf.can_odd_split = False
rf.has_name = True
rf.valid_skips = []
rf.valid_tmodes = ["", "Tone", "TSQL", "DTCS"]
#rf.valid_cross_modes = ["Tone->Tone", "Tone->DTCS", "DTCS->Tone", "->Tone", "->DTCS", "DTCS->", "DTCS->DTCS"]
rf.valid_power_levels = POWER_LEVELS
rf.valid_duplexes = SHIFT_LIST
rf.valid_modes = ["FM", "NFM"]
rf.memory_bounds = (1, 200)
rf.valid_bands = IP620_BANDS
rf.valid_characters = ''.join(set(IP620_CHARSET))
rf.valid_name_length = CHAR_LENGTH_MAX
return rf
def process_mmap(self):
self._memobj = bitwise.parse(IP620_MEM_FORMAT, self._mmap)
def sync_in(self):
try:
self._mmap = self._do_download()
except errors.RadioError:
raise
except Exception, e:
raise errors.RadioError("Failed to communicate with radio: %s" % e)
self.process_mmap()
def sync_out(self):
self._do_upload()
def get_raw_memory(self, number):
return repr(self._memobj.memory[number - 1])
def _get_tone(self, _mem, mem):
def _get_dcs(val):
code = int("%03o" % (val & 0x07FF))
pol = (val & 0x8000) and "R" or "N"
return code, pol
if _mem.tx_tone != 0xFFFF and _mem.tx_tone > 0x2800:
tcode, tpol = _get_dcs(_mem.tx_tone)
mem.dtcs = tcode
txmode = "DTCS"
elif _mem.tx_tone != 0xFFFF:
mem.rtone = _mem.tx_tone / 10.0
txmode = "Tone"
else:
txmode = ""
if _mem.rx_tone != 0xFFFF and _mem.rx_tone > 0x2800:
rcode, rpol = _get_dcs(_mem.rx_tone)
mem.rx_dtcs = rcode
rxmode = "DTCS"
elif _mem.rx_tone != 0xFFFF:
mem.ctone = _mem.rx_tone / 10.0
rxmode = "Tone"
else:
rxmode = ""
if txmode == "Tone" and not rxmode:
mem.tmode = "Tone"
elif txmode == rxmode and txmode == "Tone" and mem.rtone == mem.ctone:
mem.tmode = "TSQL"
elif txmode == rxmode and txmode == "DTCS" and mem.dtcs == mem.rx_dtcs:
mem.tmode = "DTCS"
elif rxmode or txmode:
mem.tmode = "Cross"
mem.cross_mode = "%s->%s" % (txmode, rxmode)
if mem.tmode == "DTCS":
mem.dtcs_polarity = "%s%s" % (tpol, rpol)
LOG.debug("Got TX %s (%i) RX %s (%i)" % (txmode, _mem.tx_tone,
rxmode, _mem.rx_tone))
def get_memory(self, number):
_mem = self._memobj.memory[number - 1]
_nam = self._memobj.chan_names[number - 1]
def _is_empty():
for i in range(0, 4):
if _mem.rx_freq[i].get_raw() != "\xFF":
return False
return True
mem = chirp_common.Memory()
mem.number = number
if _is_empty():
mem.empty = True
return mem
mem.freq = int(_mem.rx_freq) * 10
if int(_mem.rx_freq) == int(_mem.tx_freq):
mem.duplex = ""
mem.offset = 0
else:
mem.duplex = int(_mem.rx_freq) > int(_mem.tx_freq) and "-" or "+"
mem.offset = abs(int(_mem.rx_freq) - int(_mem.tx_freq)) * 10
mem.mode = _mem.w_n and "FM" or "NFM"
self._get_tone(_mem, mem)
mem.power = POWER_LEVELS[_mem.power]
mem.extra = RadioSettingGroup("Extra", "extra")
rs = RadioSetting("lout", "Lock out",
RadioSettingValueList(OFF_ON_LIST,
OFF_ON_LIST[_mem.lout]))
mem.extra.append(rs)
rs = RadioSetting("busy_loc", "Busy lock",
RadioSettingValueList(BUSYLOCK_LIST,
BUSYLOCK_LIST[_mem.busy_loc]))
mem.extra.append(rs)
rs = RadioSetting("scan_add", "Scan add",
RadioSettingValueList(NO_YES_LIST,
NO_YES_LIST[_mem.scan_add]))
mem.extra.append(rs)
count = 0
for i in _nam.chan_name:
if i == 0xFF:
break
try:
mem.name += IP620_CHARSET[i]
except Exception:
LOG.error("Unknown name char %i: 0x%02x (mem %i)" %
(count, i, number - 1))
mem.name += " "
count += 1
mem.name = mem.name.rstrip()
return mem
def _set_tone(self, mem, _mem):
def _set_dcs(code, pol):
val = int("%i" % code, 8) + 0x2800
if pol == "R":
val += 0x8000
return val
if mem.tmode == "Cross":
tx_mode, rx_mode = mem.cross_mode.split("->")
elif mem.tmode == "Tone":
tx_mode = mem.tmode
rx_mode = None
else:
tx_mode = rx_mode = mem.tmode
if tx_mode == "DTCS":
_mem.tx_tone = mem.tmode != "DTCS" and \
_set_dcs(mem.dtcs, mem.dtcs_polarity[0]) or \
_set_dcs(mem.rx_dtcs, mem.dtcs_polarity[0])
elif tx_mode:
_mem.tx_tone = tx_mode == "Tone" and \
int(mem.rtone * 10) or int(mem.ctone * 10)
else:
_mem.tx_tone = 0xFFFF
if rx_mode == "DTCS":
_mem.rx_tone = _set_dcs(mem.rx_dtcs, mem.dtcs_polarity[1])
elif rx_mode:
_mem.rx_tone = int(mem.ctone * 10)
else:
_mem.rx_tone = 0xFFFF
LOG.debug("Set TX %s (%i) RX %s (%i)" % (tx_mode, _mem.tx_tone,
rx_mode, _mem.rx_tone))
def set_memory(self, mem):
_mem = self._memobj.memory[mem.number - 1]
if mem.empty:
_mem.set_raw("\xFF" * (_mem.size() / 8))
return
_mem.rx_freq = mem.freq / 10
if mem.duplex == "OFF":
for i in range(0, 4):
_mem.tx_freq[i].set_raw("\xFF")
elif mem.duplex == "+":
_mem.tx_freq = (mem.freq + mem.offset) / 10
elif mem.duplex == "-":
_mem.tx_freq = (mem.freq - mem.offset) / 10
else:
_mem.tx_freq = mem.freq / 10
_mem.w_n = mem.mode == "FM"
self._set_tone(mem, _mem)
_mem.power = mem.power == POWER_LEVELS[1]
for setting in mem.extra:
setattr(_mem, setting.get_name(), setting.value)
def get_settings(self):
_settings = self._memobj.settings
_settings_misc = self._memobj.settings_misc
basic = RadioSettingGroup("basic", "Basic Settings")
top = RadioSettings(basic)
rs = RadioSetting("rf_sql", "Squelch level (SQL)",
RadioSettingValueList(RFSQL_LIST,
RFSQL_LIST[_settings.rf_sql]))
basic.append(rs)
rs = RadioSetting("step_freq", "Step frequency KHz (STP)",
RadioSettingValueList(STEP_LIST,
STEP_LIST[_settings.step_freq]))
basic.append(rs)
rs = RadioSetting("fm_radio", "FM radio (DW)",
RadioSettingValueList(OFF_ON_LIST,
OFF_ON_LIST[_settings_misc.fm_radio]))
basic.append(rs)
rs = RadioSetting("call_tone", "Call tone (CK)",
RadioSettingValueList(CALLTONE_LIST,
CALLTONE_LIST[_settings.call_tone]))
basic.append(rs)
rs = RadioSetting("tot", "Time-out timer (TOT)",
RadioSettingValueList(TIMEOUT_LIST,
TIMEOUT_LIST[_settings.tot]))
basic.append(rs)
rs = RadioSetting("chan_disp_way", "Channel display way",
RadioSettingValueList(CH_FLAG_LIST,
CH_FLAG_LIST[_settings.chan_disp_way]))
basic.append(rs)
rs = RadioSetting("vox", "VOX Gain (VOX)",
RadioSettingValueList(VOX_LIST,
VOX_LIST[_settings.vox]))
basic.append(rs)
rs = RadioSetting("vox_dly", "VOX Delay",
RadioSettingValueList(VOXDELAY_LIST,
VOXDELAY_LIST[_settings.vox_dly]))
basic.append(rs)
rs = RadioSetting("beep", "Beep (BP)",
RadioSettingValueList(OFF_ON_LIST,
OFF_ON_LIST[_settings.beep]))
basic.append(rs)
rs = RadioSetting("auto_lock", "Auto lock (KY)",
RadioSettingValueList(NO_YES_LIST,
NO_YES_LIST[_settings_misc.auto_lock]))
basic.append(rs)
rs = RadioSetting("bat_save", "Battery Saver (SAV)",
RadioSettingValueList(BAT_SAVE_LIST,
BAT_SAVE_LIST[_settings.bat_save]))
basic.append(rs)
rs = RadioSetting("chan_pri", "Channel PRI (PRI)",
RadioSettingValueList(OFF_ON_LIST,
OFF_ON_LIST[_settings.chan_pri]))
basic.append(rs)
rs = RadioSetting("chan_pri_num", "Channel PRI time Sec (PRI)",
RadioSettingValueList(PRI_NUM_LIST,
PRI_NUM_LIST[_settings.chan_pri_num]))
basic.append(rs)
rs = RadioSetting("end_beep", "End beep (ET)",
RadioSettingValueList(ENDBEEP_LIST,
ENDBEEP_LIST[_settings.end_beep]))
basic.append(rs)
rs = RadioSetting("ch_mode", "CH mode",
RadioSettingValueList(ON_OFF_LIST,
ON_OFF_LIST[_settings.ch_mode]))
basic.append(rs)
rs = RadioSetting("scan_rev", "Scan rev (SCAN)",
RadioSettingValueList(SCANM_LIST,
SCANM_LIST[_settings.scan_rev]))
basic.append(rs)
rs = RadioSetting("enc", "Frequency lock (ENC)",
RadioSettingValueList(OFF_ON_LIST,
OFF_ON_LIST[_settings.enc]))
basic.append(rs)
rs = RadioSetting("wait_back_light", "Wait back light (LED)",
RadioSettingValueList(BACKLIGHT_LIST,
BACKLIGHT_LIST[_settings.wait_back_light]))
basic.append(rs)
return top
def _set_misc_settings(self, settings):
for element in settings:
try:
setattr(self._memobj.settings_misc,
element.get_name(),
element.value)
except Exception, e:
LOG.debug(element.get_name())
raise
def set_settings(self, settings):
_settings = self._memobj.settings
_settings_misc = self._memobj.settings_misc
for element in settings:
if not isinstance(element, RadioSetting):
self.set_settings(element)
continue
if not element.changed():
continue
try:
setting = element.get_name()
if setting in ["auto_lock","fm_radio"]:
oldval = getattr(_settings_misc, setting)
else:
oldval = getattr(_settings, setting)
newval = element.value
LOG.debug("Setting %s(%s) <= %s" % (setting, oldval, newval))
if setting in ["auto_lock","fm_radio"]:
setattr(_settings_misc, setting, newval)
else:
setattr(_settings, setting, newval)
except Exception, e:
LOG.debug(element.get_name())
raise
More information about the chirp_devel
mailing list