[chirp_devel] [PATCH] [FT2900] Add support for settings. Fix #2867
Richard Cochran
Sun Feb 28 20:47:52 PST 2016
# HG changeset patch
# User Richard Cochran <ag6qr at sonic.net>
# Date 1456721059 28800
# Sun Feb 28 20:44:19 2016 -0800
# Node ID 432300d60fae4c517c8f98f3d52aef115c9a96e8
# Parent 1c398653986fb3e0a50bfbbc58f1a2f5c9ab2b95
[FT2900] Add support for settings. Fix #2867
This submission adds support for settings to the ft-2900/ft-1900 driver.
Special thanks to Chris Fosnight for getting a start at the settings code.
He gave his code to me (Rich Cochran), and I finished implementing more
settings, and tested both reading and writing of all the settings.
Settings are grouped into categories according to the manual, pp 72-73.
Issue #2867
diff -r 1c398653986f -r 432300d60fae chirp/drivers/ft2900.py
--- a/chirp/drivers/ft2900.py Sat Feb 27 17:33:04 2016 -0800
+++ b/chirp/drivers/ft2900.py Sun Feb 28 20:44:19 2016 -0800
@@ -1,6 +1,7 @@
# Copyright 2011 Dan Smith <dsmith at danplanet.com>
#
# FT-2900-specific modifications by Richard Cochran, <ag6qr at sonic.net>
+# Initial work on settings by Chris Fosnight, <chris.fosnight at 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
@@ -21,6 +22,8 @@
from chirp import util, memmap, chirp_common, bitwise, directory, errors
from chirp.drivers.yaesu_clone import YaesuCloneModeRadio
+from chirp.settings import RadioSetting, RadioSettingGroup, \
+ RadioSettingValueList, RadioSettingValueString, RadioSettings
from textwrap import dedent
@@ -164,6 +167,87 @@
_send(radio.pipe, chr(cs & 0xFF))
MEM_FORMAT = """
+#seekto 0x0080;
+struct {
+ u8 apo;
+ u8 arts_beep;
+ u8 bell;
+ u8 dimmer;
+ u8 cw_id_string[16];
+ u8 cw_trng;
+ u8 x95;
+ u8 x96;
+ u8 x97;
+ u8 int_cd;
+ u8 int_set;
+ u8 x9A;
+ u8 x9B;
+ u8 lock;
+ u8 x9D;
+ u8 mic_gain;
+ u8 open_msg;
+ u8 openMsg_Text[6];
+ u8 rf_sql;
+ u8 unk:6,
+ pag_abk:1,
+ unk:1;
+ u8 pag_cdr_1;
+ u8 pag_cdr_2;
+ u8 pag_cdt_1;
+ u8 pag_cdt_2;
+ u8 prog_p1;
+ u8 xAD;
+ u8 prog_p2;
+ u8 xAF;
+ u8 prog_p3;
+ u8 xB1;
+ u8 prog_p4;
+ u8 xB3;
+ u8 resume;
+ u8 tot;
+ u8 unk:1,
+ cw_id:1,
+ unk:1,
+ ts_speed:1,
+ ars:1,
+ unk:2,
+ dtmf_mode:1;
+ u8 unk:1,
+ ts_mut:1
+ wires_auto:1,
+ busy_lockout:1,
+ edge_beep:1,
+ unk:3;
+ u8 unk:2,
+ s_search:1,
+ unk:2,
+ cw_trng_units:1,
+ unk:2;
+ u8 dtmf_speed:1,
+ unk:2,
+ arts_interval:1,
+ unk:1,
+ inverted_dcs:1,
+ unk:1,
+ mw_mode:1;
+ u8 unk:2,
+ wires_mode:1,
+ wx_alert:1,
+ unk:1,
+ wx_vol_max:1,
+ revert:1,
+ unk:1;
+ u8 vfo_scan;
+ u8 scan_mode;
+ u8 dtmf_delay;
+ u8 beep;
+ u8 xBF;
+} settings;
+
+#seekto 0x00d0;
+ u8 passwd[4];
+ u8 mbs;
+
#seekto 0x00c0;
struct {
u16 in_use;
@@ -175,6 +259,11 @@
#seekto 0x00f0;
u8 curChannelMem[20];
+#seekto 0x1e0;
+struct {
+ u8 dtmf_string[16];
+} dtmf_strings[10];
+
#seekto 0x0127;
u8 curChannelNum;
@@ -424,6 +513,7 @@
rf.has_dtcs_polarity = False
rf.has_bank = True
rf.has_bank_names = True
+ rf.has_settings = True
rf.valid_tuning_steps = STEPS
rf.valid_modes = MODES
@@ -620,6 +710,496 @@
LOG.debug("encoded mem\n%s\n" % (util.hexprint(_mem.get_raw()[0:20])))
+ def get_settings(self):
+ _settings = self._memobj.settings
+ _dtmf_strings = self._memobj.dtmf_strings
+ _passwd = self._memobj.passwd
+
+ repeater = RadioSettingGroup("repeater", "Repeater Settings")
+ ctcss = RadioSettingGroup("ctcss", "CTCSS/DCS/EPCS Settings")
+ arts = RadioSettingGroup("arts", "ARTS Settings")
+ mbls = RadioSettingGroup("banks", "Memory Settings")
+ scan = RadioSettingGroup("scan", "Scan Settings")
+ dtmf = RadioSettingGroup("dtmf", "DTMF Settings")
+ wires = RadioSettingGroup("wires", "WiRES(tm) Settings")
+ switch = RadioSettingGroup("switch", "Switch/Knob Settings")
+ disp = RadioSettingGroup("disp", "Display Settings")
+ misc = RadioSettingGroup("misc", "Miscellaneous Settings")
+
+ setmode = RadioSettings(repeater, ctcss, arts, mbls, scan,
+ dtmf, wires, switch, disp, misc)
+
+ # numbers and names of settings refer to the way they're
+ # presented in the set menu, as well as the list starting on
+ # page 74 of the manual
+
+ # 1 APO
+ opts = ["Off", "30 Min", "1 Hour", "3 Hour", "5 Hour", "8 Hour"]
+ misc.append(
+ RadioSetting(
+ "apo", "Automatic Power Off",
+ RadioSettingValueList(opts, opts[_settings.apo])))
+
+ # 2 AR.BEP
+ opts = ["Off", "In Range", "Always"]
+ arts.append(
+ RadioSetting(
+ "arts_beep", "ARTS Beep",
+ RadioSettingValueList(opts, opts[_settings.arts_beep])))
+
+ # 3 AR.INT
+ opts = ["15 Sec", "25 Sec"]
+ arts.append(
+ RadioSetting(
+ "arts_interval", "ARTS Polling Interval",
+ RadioSettingValueList(opts, opts[_settings.arts_interval])))
+
+ # 4 ARS
+ opts = ["Off", "On"]
+ repeater.append(
+ RadioSetting(
+ "ars", "Automatic Repeater Shift",
+ RadioSettingValueList(opts, opts[_settings.ars])))
+
+ # 5 BCLO
+ opts = ["Off", "On"]
+ misc.append(RadioSetting(
+ "busy_lockout", "Busy Channel Lock-Out",
+ RadioSettingValueList(opts, opts[_settings.busy_lockout])))
+
+ # 6 BEEP
+ opts = ["Off", "Key+Scan", "Key"]
+ switch.append(RadioSetting(
+ "beep", "Enable the Beeper",
+ RadioSettingValueList(opts, opts[_settings.beep])))
+
+ # 7 BELL
+ opts = ["Off", "1", "3", "5", "8", "Continuous"]
+ ctcss.append(RadioSetting("bell", "Bell Repetitions",
+ RadioSettingValueList(opts, opts[_settings.bell])))
+
+ # 8 BNK.LNK
+ for i in range(0, 8):
+ opts = ["Off", "On"]
+ mbs = (self._memobj.mbs >> i) & 1
+ rs = RadioSetting("mbs%i" % i, "Bank %s Scan" % (i + 1),
+ RadioSettingValueList(opts, opts[mbs]))
+
+ def apply_mbs(s, index):
+ if int(s.value):
+ self._memobj.mbs |= (1 << index)
+ else:
+ self._memobj.mbs &= ~(1 << index)
+ rs.set_apply_callback(apply_mbs, i)
+ mbls.append(rs)
+
+ # 9 BNK.NM - A per-bank attribute, nothing to do here.
+
+ # 10 CLK.SFT - A per-channel attribute, nothing to do here.
+
+ # 11 CW.ID
+ opts = ["Off", "On"]
+ arts.append(RadioSetting("cw_id", "CW ID Enable",
+ RadioSettingValueList(opts, opts[_settings.cw_id])))
+
+ cw_id_text = ""
+ for i in _settings.cw_id_string:
+ try:
+ cw_id_text += CHARSET[i & 0x7F]
+ except IndexError:
+ if i != 0xff:
+ LOG.debug("unknown char index in cw id: %x " % (i))
+
+ val = RadioSettingValueString(0, 16, cw_id_text, True)
+ val.set_charset(CHARSET + "abcdefghijklmnopqrstuvwxyz")
+ rs = RadioSetting("cw_id_string", "CW Identifier Text", val)
+
+ def apply_cw_id(s):
+ str = s.value.get_value().upper().rstrip()
+ mval = ""
+ mval = [chr(CHARSET.index(x)) for x in str]
+ for x in range(len(mval), 16):
+ mval.append(chr(0xff))
+ for x in range(0, 16):
+ _settings.cw_id_string[x] = ord(mval[x])
+ rs.set_apply_callback(apply_cw_id)
+ arts.append(rs)
+
+ # 12 CWTRNG
+ opts = ["Off", "4WPM", "5WPM", "6WPM", "7WPM", "8WPM", "9WPM",
+ "10WPM", "11WPM", "12WPM", "13WPM", "15WPM", "17WPM",
+ "20WPM", "24WPM", "30WPM", "40WPM"]
+ misc.append(RadioSetting("cw_trng", "CW Training",
+ RadioSettingValueList(opts, opts[_settings.cw_trng])))
+
+ # todo: make the setting of the units here affect the display
+ # of the speed. Not critical, but would be slick.
+ opts = ["CPM", "WPM"]
+ misc.append(RadioSetting("cw_trng_units", "CW Training Units",
+ RadioSettingValueList(opts,
+ opts[_settings.cw_trng_units])))
+
+ # 13 DC VLT - a read-only status, so nothing to do here
+
+ # 14 DCS CD - A per-channel attribute, nothing to do here
+
+ # 15 DCS.RV
+ opts = ["Disabled", "Enabled"]
+ ctcss.append(RadioSetting(
+ "inverted_dcs",
+ "\"Inverted\" DCS Code Decoding",
+ RadioSettingValueList(opts,
+ opts[_settings.inverted_dcs])))
+
+ # 16 DIMMER
+ opts = ["Off"] + ["Level %d" % (x) for x in range(1, 11)]
+ disp.append(RadioSetting("dimmer", "Dimmer",
+ RadioSettingValueList(opts,
+ opts[_settings
+ .dimmer])))
+
+ # 17 DT.A/M
+ opts = ["Manual", "Auto"]
+ dtmf.append(RadioSetting("dtmf_mode", "DTMF Autodialer",
+ RadioSettingValueList(opts,
+ opts[_settings
+ .dtmf_mode])))
+
+ # 18 DT.DLY
+ opts = ["50 ms", "250 ms", "450 ms", "750 ms", "1000 ms"]
+ dtmf.append(RadioSetting("dtmf_delay", "DTMF Autodialer Delay Time",
+ RadioSettingValueList(opts,
+ opts[_settings
+ .dtmf_delay])))
+
+ # 19 DT.SET
+ for memslot in range(0, 10):
+ dtmf_memory = ""
+ for i in _dtmf_strings[memslot].dtmf_string:
+ if i != 0xFF:
+ try:
+ dtmf_memory += CHARSET[i]
+ except IndexError:
+ LOG.debug("unknown char index in dtmf: %x " % (i))
+
+ val = RadioSettingValueString(0, 16, dtmf_memory, True)
+ val.set_charset(CHARSET + "abcdef")
+ rs = RadioSetting("dtmf_string_%d" % memslot,
+ "DTMF Memory %d" % memslot, val)
+
+ def apply_dtmf(s, i):
+ LOG.debug("applying dtmf for %x\n" % i)
+ str = s.value.get_value().upper().rstrip()
+ LOG.debug("str is %s\n" % str)
+ mval = ""
+ mval = [chr(CHARSET.index(x)) for x in str]
+ for x in range(len(mval), 16):
+ mval.append(chr(0xff))
+ for x in range(0, 16):
+ _dtmf_strings[i].dtmf_string[x] = ord(mval[x])
+ rs.set_apply_callback(apply_dtmf, memslot)
+ dtmf.append(rs)
+
+ # 20 DT.SPD
+ opts = ["50 ms", "100 ms"]
+ dtmf.append(RadioSetting("dtmf_speed",
+ "DTMF Autodialer Sending Speed",
+ RadioSettingValueList(opts,
+ opts[_settings.
+ dtmf_speed])))
+
+ # 21 EDG.BEP
+ opts = ["Off", "On"]
+ mbls.append(RadioSetting("edge_beep", "Band Edge Beeper",
+ RadioSettingValueList(opts,
+ opts[_settings.
+ edge_beep])))
+
+ # 22 INT.CD
+ opts = ["DTMF %X" % (x) for x in range(0, 16)]
+ wires.append(RadioSetting("int_cd", "Access Number for WiRES(TM)",
+ RadioSettingValueList(opts, opts[_settings.int_cd])))
+
+ # 23 ING MD
+ opts = ["Sister Radio Group", "Friends Radio Group"]
+ wires.append(RadioSetting("wires_mode",
+ "Internet Link Connection Mode",
+ RadioSettingValueList(opts,
+ opts[_settings.
+ wires_mode])))
+
+ # 24 INT.A/M
+ opts = ["Manual", "Auto"]
+ wires.append(RadioSetting("wires_auto", "Internet Link Autodialer",
+ RadioSettingValueList(opts,
+ opts[_settings
+ .wires_auto])))
+ # 25 INT.SET
+ opts = ["F%d" % (x) for x in range(0, 10)]
+
+ wires.append(RadioSetting("int_set", "Memory Register for "
+ "non-WiRES Internet",
+ RadioSettingValueList(opts,
+ opts[_settings
+ .int_set])))
+
+ # 26 LOCK
+ opts = ["Key", "Dial", "Key + Dial", "PTT",
+ "Key + PTT", "Dial + PTT", "All"]
+ switch.append(RadioSetting("lock", "Control Locking",
+ RadioSettingValueList(opts,
+ opts[_settings
+ .lock])))
+
+ # 27 MCGAIN
+ opts = ["Level %d" % (x) for x in range(1, 10)]
+ misc.append(RadioSetting("mic_gain", "Microphone Gain",
+ RadioSettingValueList(opts,
+ opts[_settings
+ .mic_gain])))
+
+ # 28 MEM.SCN
+ opts = ["Tag 1", "Tag 2", "All Channels"]
+ rs = RadioSetting("scan_mode", "Memory Scan Mode",
+ RadioSettingValueList(opts,
+ opts[_settings
+ .scan_mode - 1]))
+ # this setting is unusual in that it starts at 1 instead of 0.
+ # that is, index 1 corresponds to "Tag 1", and index 0 is invalid.
+ # so we create a custom callback to handle this.
+
+ def apply_scan_mode(s):
+ myopts = ["Tag 1", "Tag 2", "All Channels"]
+ _settings.scan_mode = myopts.index(s.value.get_value()) + 1
+ rs.set_apply_callback(apply_scan_mode)
+ mbls.append(rs)
+
+ # 29 MW MD
+ opts = ["Lower", "Next"]
+ mbls.append(RadioSetting("mw_mode", "Memory Write Mode",
+ RadioSettingValueList(opts,
+ opts[_settings
+ .mw_mode])))
+
+ # 30 NM SET - This is per channel, so nothing to do here
+
+ # 31 OPN.MSG
+ opts = ["Off", "DC Supply Voltage", "Text Message"]
+ disp.append(RadioSetting("open_msg", "Opening Message Type",
+ RadioSettingValueList(opts,
+ opts[_settings.
+ open_msg])))
+
+ openmsg = ""
+ for i in _settings.openMsg_Text:
+ try:
+ openmsg += CHARSET[i & 0x7F]
+ except IndexError:
+ if i != 0xff:
+ LOG.debug("unknown char index in openmsg: %x " % (i))
+
+ val = RadioSettingValueString(0, 6, openmsg, True)
+ val.set_charset(CHARSET + "abcdefghijklmnopqrstuvwxyz")
+ rs = RadioSetting("openMsg_Text", "Opening Message Text", val)
+
+ def apply_openmsg(s):
+ str = s.value.get_value().upper().rstrip()
+ mval = ""
+ mval = [chr(CHARSET.index(x)) for x in str]
+ for x in range(len(mval), 6):
+ mval.append(chr(0xff))
+ for x in range(0, 6):
+ _settings.openMsg_Text[x] = ord(mval[x])
+ rs.set_apply_callback(apply_openmsg)
+ disp.append(rs)
+
+ # 32 PAGER - a per-channel attribute
+
+ # 33 PAG.ABK
+ opts = ["Off", "On"]
+ ctcss.append(RadioSetting("pag_abk", "Paging Answer Back",
+ RadioSettingValueList(opts,
+ opts[_settings
+ .pag_abk])))
+
+ # 34 PAG.CDR
+ opts = ["%2.2d" % (x) for x in range(1, 50)]
+ ctcss.append(RadioSetting("pag_cdr_1", "Receive Page Code 1",
+ RadioSettingValueList(opts,
+ opts[_settings
+ .pag_cdr_1])))
+
+ ctcss.append(RadioSetting("pag_cdr_2", "Receive Page Code 2",
+ RadioSettingValueList(opts,
+ opts[_settings
+ .pag_cdr_2])))
+
+ # 35 PAG.CDT
+ opts = ["%2.2d" % (x) for x in range(1, 50)]
+ ctcss.append(RadioSetting("pag_cdt_1", "Transmit Page Code 1",
+ RadioSettingValueList(opts,
+ opts[_settings
+ .pag_cdt_1])))
+
+ ctcss.append(RadioSetting("pag_cdt_2", "Transmit Page Code 2",
+ RadioSettingValueList(opts,
+ opts[_settings
+ .pag_cdt_2])))
+
+ # Common Button Options
+ button_opts = ["Squelch Off", "Weather", "Smart Search",
+ "Tone Scan", "Scan", "T Call", "ARTS"]
+
+ # 36 PRG P1
+ opts = button_opts + ["DC Volts"]
+ switch.append(RadioSetting(
+ "prog_p1", "P1 Button",
+ RadioSettingValueList(opts, opts[_settings.prog_p1])))
+
+ # 37 PRG P2
+ opts = button_opts + ["Dimmer"]
+ switch.append(RadioSetting(
+ "prog_p2", "P2 Button",
+ RadioSettingValueList(opts, opts[_settings.prog_p2])))
+
+ # 38 PRG P3
+ opts = button_opts + ["Mic Gain"]
+ switch.append(RadioSetting(
+ "prog_p3", "P3 Button",
+ RadioSettingValueList(opts, opts[_settings.prog_p3])))
+
+ # 39 PRG P4
+ opts = button_opts + ["Skip"]
+ switch.append(RadioSetting(
+ "prog_p4", "P4 Button",
+ RadioSettingValueList(opts, opts[_settings.prog_p4])))
+
+ # 40 PSWD
+ password = ""
+ for i in _passwd:
+ if i != 0xFF:
+ try:
+ password += CHARSET[i]
+ except IndexError:
+ LOG.debug("unknown char index in password: %x " % (i))
+
+ val = RadioSettingValueString(0, 4, password, True)
+ val.set_charset(CHARSET[0:15] + "abcdef ")
+ rs = RadioSetting("passwd", "Password", val)
+
+ def apply_password(s):
+ str = s.value.get_value().upper().rstrip()
+ mval = ""
+ mval = [chr(CHARSET.index(x)) for x in str]
+ for x in range(len(mval), 4):
+ mval.append(chr(0xff))
+ for x in range(0, 4):
+ _passwd[x] = ord(mval[x])
+ rs.set_apply_callback(apply_password)
+ misc.append(rs)
+
+ # 41 RESUME
+ opts = ["3 Sec", "5 Sec", "10 Sec", "Busy", "Hold"]
+ scan.append(RadioSetting("resume", "Scan Resume Mode",
+ RadioSettingValueList(opts, opts[_settings.resume])))
+
+ # 42 RF.SQL
+ opts = ["Off"] + ["S-%d" % (x) for x in range(1, 10)]
+ misc.append(RadioSetting("rf_sql", "RF Squelch Threshold",
+ RadioSettingValueList(opts, opts[_settings.rf_sql])))
+
+ # 43 RPT - per channel attribute, nothing to do here
+
+ # 44 RVRT
+ opts = ["Off", "On"]
+ misc.append(RadioSetting("revert", "Priority Revert",
+ RadioSettingValueList(opts, opts[_settings.revert])))
+
+ # 45 S.SRCH
+ opts = ["Single", "Continuous"]
+ misc.append(RadioSetting("s_search", "Smart Search Sweep Mode",
+ RadioSettingValueList(opts, opts[_settings.s_search])))
+
+ # 46 SHIFT - per channel setting, nothing to do here
+
+ # 47 SKIP = per channel setting, nothing to do here
+
+ # 48 SPLIT - per channel attribute, nothing to do here
+
+ # 49 SQL.TYP - per channel attribute, nothing to do here
+
+ # 50 STEP - per channel attribute, nothing to do here
+
+ # 51 TEMP - read-only status, nothing to do here
+
+ # 52 TN FRQ - per channel attribute, nothing to do here
+
+ # 53 TOT
+ opts = ["Off", "1 Min", "3 Min", "5 Min", "10 Min"]
+ misc.append(RadioSetting("tot", "Timeout Timer",
+ RadioSettingValueList(opts,
+ opts[_settings.tot])))
+
+ # 54 TS MUT
+ opts = ["Off", "On"]
+ ctcss.append(RadioSetting("ts_mut", "Tone Search Mute",
+ RadioSettingValueList(opts,
+ opts[_settings
+ .ts_mut])))
+
+ # 55 TS SPEED
+ opts = ["Fast", "Slow"]
+ ctcss.append(RadioSetting("ts_speed", "Tone Search Scanner Speed",
+ RadioSettingValueList(opts,
+ opts[_settings
+ .ts_speed])))
+
+ # 56 VFO.SCN
+ opts = ["+/- 1MHz", "+/- 2MHz", "+/-5MHz", "All"]
+ scan.append(RadioSetting("vfo_scan", "VFO Scanner Width",
+ RadioSettingValueList(opts,
+ opts[_settings
+ .vfo_scan])))
+
+ # 57 WX.ALT
+ opts = ["Off", "On"]
+ misc.append(RadioSetting("wx_alert", "Weather Alert Scan",
+ RadioSettingValueList(opts, opts[_settings.wx_alert])))
+
+ # 58 WX.VOL
+ opts = ["Normal", "Maximum"]
+ misc.append(RadioSetting("wx_vol_max", "Weather Alert Volume",
+ RadioSettingValueList(opts, opts[_settings.wx_vol_max])))
+
+ # 59 W/N DV - this is a per-channel attribute, nothing to do here
+
+ return setmode
+
+ def set_settings(self, uisettings):
+ _settings = self._memobj.settings
+ for element in uisettings:
+ if not isinstance(element, RadioSetting):
+ self.set_settings(element)
+ continue
+ if not element.changed():
+ continue
+
+ try:
+ name = element.get_name()
+ value = element.value
+
+ if element.has_apply_callback():
+ LOG.debug("Using apply callback")
+ element.run_apply_callback()
+ else:
+ obj = getattr(_settings, name)
+ setattr(_settings, name, value)
+
+ LOG.debug("Setting %s: %s" % (name, value))
+ except Exception, e:
+ LOG.debug(element.get_name())
+ raise
+
def get_bank_model(self):
return FT2900BankModel(self)
More information about the chirp_devel
mailing list